Overview
If you run your own strategy, you can connect it to Innova’s copy engine without sharing your code with us. You run the strategy on your own server and send us only the signal it already generated. We validate it, check that it is still fresh, and copy it to the subscribed accounts.The provider never sends us their code, only signals.
https://cloud.innova-trading.com.
Always develop against the sandbox endpoint (/signals/test) first.
How to get access
Access is granted by Innova. There is no self-service sign-up yet.- Ask your contact at Innova for a provider token and the
x-community-slugof your community (for examplemy-community). - Keep both private. Never paste them in chats, emails or repositories.
- Start with the sandbox endpoint (
/signals/test) before sending anything real.
Endpoints
Authentication
Two headers on every call:- This is not a free-form API key. The token is personal and is handed to
you by Innova (see “How to get access”). The
x-community-slugheader is mandatory on every call. - The token is personal: whoever has it can send signals in your name, so do not share it and never paste it in chats or emails.
- No token, or a revoked token: HTTP 401.
- Your signals are isolated inside your community.
The JSON you send
Idempotency
The pair (your provider, yoursignal_id) is unique on our side.
- If you retry because of a network failure, send the same
signal_id(and the same body). - The second call returns
"status": "duplicate"with the first result and no extra trade is opened. - Never reuse a
signal_idfor a different signal: the second one would be discarded as a repeat. - We also answer
duplicatewith codeactive_same_directionif you already have an open signal on the same symbol and direction. We do not stack positions.
signal_id is deterministic: strategy + symbol + timeframe + candle
time. That way, restarting your process on the same candle never duplicates.
Freshness
A late signal cannot be copied at a price that no longer exists. Before publishing, we check:
Today these values are global (the same for every provider). If your
strategy works on longer timeframes and 120 s is too short, tell us.
The 0.2 % drift rule is directional: a move in your favor never rejects
the signal. The 1 % absolute rule is not: an
entry more than 1 % away from
the live market, on either side, is rejected with price_drift_exceeded,
because that price does not exist in the market. If there is no live quote to
compare against, a signal with a numeric entry is also rejected
(no_market_price, or price_drift_exceeded if the price cannot be measured).
Rejection codes
Branch oncode, never on the text of detail.
Nothing is silently dropped. Every decision, accepted or rejected, is
recorded and available at
GET /api/v1/provider/signals/decisions.
The response
status:published(copied),simulated(sandbox),duplicateorrejected.publishedistrueonly if the signal reached the copy engine. If it isfalse, readcode.signal_sourceis the identifier accounts use to subscribe to your strategy. Send it to your community owner.received_at - generated_atis your delivery latency. Log it.
200 whenever the call was well formed (even if the signal was
rejected: the reason is in code), 401 without a valid token, 409 when the
signal could not be saved because of a rare uniqueness conflict (retry with a different
signal_id), 422 for a bad JSON, 429 above 120 signals per minute.
Sandbox
POST /api/v1/provider/signals/test is where you should develop. It does the
same as the real endpoint (validates the JSON, runs the freshness filter,
resolves the symbol against subscribed accounts, computes lot size and
partial-close plan) and returns exactly what would happen, including
rejection reasons. It never publishes or opens anything.
The visible difference: "status": "simulated", "dry_run": true,
"published": false, and an extra execution_preview block. If
targets_matched is 0, your signal is valid but no account is subscribed to
your signal_source yet (the community owner sets that up).
Examples
/signals/test with /signals.
Replace
<YOUR_TOKEN> and <YOUR_COMMUNITY> with the values we give you.
Never commit them to a repository.Integration checklist
- Ask your contact at Innova for the token and the
x-community-slug. - Send a signal to
/signals/test. Expect"status": "simulated". - Trigger a rejection on purpose (send a
generated_atfrom one hour ago) and confirm your code handlescode: "stale". - Send an invented field and confirm your code handles the
422. - Check that your code retries with the same
signal_idwhen a call fails (the Python kit, when available, does it for you). The sandbox always answerssimulated:duplicateonly shows up on the real/signals, so you will see it there for the first time. That is expected. - Only then switch to
/signals. - Send us the
signal_sourcefrom the response so we can subscribe accounts.
Convert your PineScript indicator
Coming soon. This section and the next one describe the Python kit
(
innova-signal-kit), which is not downloadable yet. If you want early
access, ask your contact at Innova. Until then, use the plain HTTP contract
described above, which works today.PROMPT-CONVERTIR-PINE.md, copied into your folder by innova-provider init, or printed with innova-provider prompt) that you paste into an AI assistant together with
your Pine code, and it returns your strategy.py in the format the kit expects.
- Paste the prompt and your Pine code into your assistant, and save the result
as
strategy.py. - Export your candles and your indicator’s signals from TradingView to two CSV files. The Strategy Tester trade list shows the time of the candle after the signal: in that case add
--expected-shift 1to the command in step 3. - Run
innova-provider parity --bars bars.csv --expected expected.csv --symbol EURUSD --timeframe 15 --strategy-name my-strategy. - Acceptance criterion: 99 % of the signals match.
--warmupdefaults to yourREQUIRED_BARS(live,evaluatealways starts with that full history): you do not need to pass it. Check convergence: run the tool with two values ofREQUIRED_BARS(for example 300 and 600) and keep the larger one if the result improves; the tool also prints anAVISO:when more history raises the match. Indicators with path-dependent state (supertrend, ATR/RMA/EMA, trailing stops) do not converge with “5 times the period”: measured with a supertrend + RSI,REQUIRED_BARS = 70gave 94 % and150gave 100 %; start at 300. The tool answersNO APROBADO: historia insuficiente(even at 100 %) when the CSV has fewer than 4 timesREQUIRED_BARScandles (the message gives the exact figure) or when fewer than 10 signals are compared. Your CSV must meet both conditions: at least 4 timesREQUIRED_BARScandles AND at least 10 Pine signals in that stretch; if your Pine gives fewer, export more history until it does (on M15 with little activity that can take months); Pine signals before the first evaluated candle are ignored and only reported. If it still falls short, the tool lists every difference. Paste them back to your assistant and ask it to fix the conversion. - Only then move on to the sandbox (
/signals/test) and, last, to/signals.
request.security), logic inside the candle, and strategies that depend on an
open position. The prompt flags them as “Doubts” so you can decide.
Ready-made Python kit
We provide a Python kit (client with retries and latency measurement, a runner that waits for each candle close, and a strategy template where you only editstrategy.py). It is a pip package called innova-signal-kit, with a
single command: innova-provider.
Installation
Python 3.9 or newer. Installation instructions will be shared when you get access to the kit. There is no public package index entry yet. Yourstrategy.py is not part of the package: it lives in your own folder,
on your server, and Innova never receives it.
Flow
run accepts --dry-run, --once, --env-file, --margin, --bars,
--fetch-attempts and -v; parity accepts the options described above.
The README inside the kit explains everything.
If the network fails
- The client makes up to 4 attempts per send (the first plus 3 retries). If it still does not arrive, the log says
SEÑAL PERDIDAwith the candle time and thesignal_id. - If the cause was the network or a 5xx, the runner retries it once in the next cycle with
the same
signal_id(the server does not duplicate), only while the candle is still within the 120 s freshness window (true on M1, not on longer candles; the log says so). A 401 or 422 is not retried. - After a long outage the log says
Se omitieron N velas sin evaluar (desde X hasta Y): the runner only evaluates the last closed candle, so signals of those candles are not sent and not replayed (they would arrive stale). A restart between a candle close and the next cycle also loses that candle. - In
--dry-runthe 120 s candle-age guard is not applied: a test with the market closed passes and the same candle would be skipped in real mode. The reportedgenerated_at->received_atlatency includes the client retries.
Your past signals (history)
--last N). It uses the same window as live (REQUIRED_BARS closed
candles, the last one is the candle that just closed). Candles come from --bars-csv (same format
as parity) or, if you do not pass it, from /api/external/bars paging backwards with
before_time (up to 1000 per request, at most --max-bars, 10 000 by default; if that is not
enough for N signals the command says how many it found and why it stopped). The result is
history.json in your folder: each signal has only the contract fields plus kind: "backtest"
and source: "kit-history"; generated_at is the close of the signal’s candle and the
signal_id starts with backtest-. The command also prints a summary (how many signals, date
range, buy/sell).
What it is not. It is not a backtest with PnL: nothing simulates SL or TP and there is no
equity curve; you only get which signals your strategy would have given. It is always marked
backtest, so it can never be mistaken for a live signal, and it is never mixed with the live
record. Your code and parameters do not leave your machine: only the contract fields travel.
Known limit today. The server accepts before_time but does not apply it yet, so downloading
more than one page of candles does not work: the command stops with a clear message. Use
--bars-csv with your own history.
Sending it: automatic sending from the kit is not available yet. The endpoint
POST /api/v1/provider/signals/history already exists on the server, but the kit’s --send
flag is not available yet (--send fails with an error and sends nothing). If you post it
yourself over HTTP, do not send history.json as is: it has the top-level keys kind, source
and summary, and the endpoint rejects unknown keys with a 422 extra_forbidden. The body of
POST /api/v1/provider/signals/history is {"signals": [ ... ]}: only the array stored under the
signals key of history.json, without kind, source or summary. Send at most 50 signals per
request (split into batches if you have more; a longer list gets a 422 too_long). Each element
of that array carries the contract fields plus kind: "backtest" and source, which the endpoint
accepts per signal; in history entry must be a number.
History limits
These limits apply to the history you send toPOST /api/v1/provider/signals/history (the
endpoint the “Sending it” note above refers to).
Each community can have its own values. The Innova platform team can adjust both
limits for your community without a release (allowed range: retention 7 to 365 days, cap 50
to 5000 signals). The values in force for you come back in every response of the endpoint, in
the
retention_days and max_per_provider fields: trust those, not the defaults above.
When a signal is not stored, its entry in results has status: "rejected" and one of these
codes. The rest of the batch is still processed.
Signals you already sent are answered as
duplicate even when you are at the cap, so
retrying a batch is always safe. Lowering a limit never deletes what is already stored.
Prefer another language?
This is the path that works today. There is no JS or other SDK, and none is promised: the contract is plain HTTP and works from any language. What you have to implement:- Endpoints (see “Endpoints” above): read candles from
/api/external/bars, send signals to/api/v1/provider/signals/test(sandbox) and then/api/v1/provider/signals. - Headers:
Authorization: Bearer <token>andx-community-slug. - Fields: exactly those in “The JSON you send”; an unknown field is a
422. - Stop and targets: check on your side that
slandtpsare on the right side ofentry; the server does not check it. signal_id: deterministic (strategy + symbol + timeframe + candle time), and retry with the same one. See “Idempotency”.- 120 s guard: generate the signal when the candle closes and only send it
if that close was at most 120 s ago.
generated_atis “now” and the server rejects it asstalepast 120 s (see “Freshness”). Do not send the signal of an old candle (weekend, delayed data). - Candles: use only closed ones; drop the one still forming.
runner and parity module line by line.
Next Steps
Authentication
How API tokens work
Signal Best Practices
Tips for better signals