Setup Guide
Getting started#
What You'll Need#
Type 1 - Interactive Brokers Live#
- Interactive Brokers account - a paper trading account is perfect for testing. Sign up at interactivebrokers.com. Both paper and live accounts are supported.
- IBKR credentials ready - Submit your credentials via the dashboard - VoltRouter handles provisioning and keeps the Gateway running. Nothing to download or configure.
- TradingView Pro or higher - webhook alerts require a paid TradingView plan. Free accounts cannot send webhooks. Any other webhook-capable platform (Python, bots, AI agents) works without restriction.
- A card for top-ups - any major debit or credit card accepted.
Type 2 - Alpaca Live / Tradovate Live#
- Alpaca account - sign up at alpaca.markets. Both live and paper trading are supported. Generate an API key and secret from your Alpaca dashboard.
- No server or Gateway needed - cloud-native brokers connect via API key only. No monthly infrastructure fee. Just add credentials in the VoltRouter dashboard and you are ready to route.
- TradingView Pro or higher (or any webhook source) - same as Type 1.
- A card for top-ups - any major debit or credit card accepted.
VoltRouter Signup#
- Go to voltrouter.com and enter your email address
- Click Send magic link - no password required
- Check your email and click the link to activate your account and reach your dashboard
- To log back in later, use the Already have an account? Log in link on the sign-in page
The Connect Surface#
Connect is where your broker accounts live: add a broker, watch each account's status, compose the alert payload, and prove a signal can route before anything real depends on it.
Adding a broker#
- One card per venue. Available now: Interactive Brokers, Tradovate, Alpaca, Bybit, Binance, and Hyperliquid. Venues that are not live yet carry a Coming soon badge and no controls.
- Each card states its cost structure from the live price schedule: per signal, and for hosted-gateway brokers the monthly Gateway fee of .
- Crypto venues connect on the card itself: an API key and secret at the exchanges, and at Hyperliquid, which is on-chain, an API wallet VoltRouter generates and you approve by signing once in your own browser wallet. Futures and equities brokers follow their guided connection flow (see Broker Setup above).
Account cards#
- One card per connected account. The head row shows your label, the broker, one plain status word with its next action, and a readiness dot that summarizes whether a signal can route right now.
- The head row is a toggle. A card whose readiness dot is anything but ready opens on load; a healthy card stays collapsed until you open it.
- Inside: the connection check (a preflight that runs your composed payload against this account), gateway sign-in for IBKR accounts, and the Automated restarts toggle. Turning automated restarts off deletes the stored restart credentials in the same action.
Account actions#
- Cancel orders - cancels every open order on this account. Positions are untouched.
- Close positions - closes every open position on this account with market orders.
- Remove - deletes the account connection. For hosted IBKR accounts the gateway stops; reconnecting later provisions a new one, which can be charged for.
- Each action is separately confirmed. The confirm reads what is open right now and states the fee from the price schedule: a per-order fee for cancelling, a base plus per-position fee (capped) for closing. If the live count cannot be read, the dialog says so and acts on whatever is open; it never invents a number.
Webhook builder#
Every account card carries an inline builder that composes a payload guaranteed to validate. What the builder emits is what the server accepts.
- Instrument and Symbol - validated against what this broker accepts. Only an IBKR card asks for an instrument. A crypto card asks for none: its symbol is the pair as the venue names it, with no contract month.
- Market - on a crypto card, the markets its venue routes and no others. A card whose venue routes one market shows no choice and composes that market.
- Action - fixed uppercase, or the TradingView action placeholder where supported.
- Quantity - a fixed number, the TradingView contracts placeholder for strategy alerts, or omitted to use the account default.
- Order type - market, limit, stop, take profit, or trailing, with only that type's price fields shown. A crypto card offers no trailing stop.
- Strategy - pick an existing payload token or create one inline. The strategy field can be removed; the builder warns what stays unattributed without it.
- Toggles - measurement fields (
ref_price,ref_time,bar_time; these unlock execution-quality readings on the Performance page) and a duplicate-protection id (client_order_id).
Builder output#
- Alert message - the composed JSON, copyable. The API key is hidden on screen and included in full when you copy, so a screenshot of this page is harmless. Values in double braces are filled in by TradingView when the alert fires.
- Webhook URL - copyable, and carries no key.
- Cost per signal - for the composed configuration, from the price schedule.
- Test signal - checks the composed payload against this account. Nothing is ordered and nothing is charged.
- In TradingView - a numbered checklist beside the output: create the alert, enable the webhook, paste the URL, paste the message.
With no broker accounts connected, the add-broker cards are the page and the builder shows one line naming its dependency: connect an account first. That is the only disabled control on the surface.
Alert Platform#
- Open TradingView and navigate to your chart or strategy
- Click the Alerts button (clock icon) → Create Alert
- Set your alert condition (price cross, indicator signal, strategy alert, etc.)
- In the Notifications tab, enable Webhook URL
- Paste your VoltRouter webhook URL from the dashboard
- Set the Message field to the JSON payload (see examples below)
Building Your Alert Message#
The message field is the JSON body sent to VoltRouter when the alert fires. Build it as a string literal - you can reference TradingView built-in variables.
The futures examples below name a contract month (MNQH27 is March 2027), and the month you send must be one still trading.
{
"broker": "ibkr",
"symbol": "MNQH27",
"action": "BUY",
"quantity": 1,
"broker_account_id": "<your-broker-account-id>",
"api_key": "sk_your-key-from-dashboard"
}{
"broker": "ibkr",
"symbol": "MNQH27",
"action": "BUY",
"quantity": 1,
"order_type": "LIMIT",
"take_profit_price": 21500.00,
"strategy_id": "breakout_v2",
"broker_account_id": "<your-broker-account-id>",
"api_key": "sk_your-key-from-dashboard"
}{
"broker": "alpaca",
"symbol": "AAPL",
"action": "BUY",
"quantity": 10,
"broker_account_id": "<your-broker-account-id>",
"api_key": "sk_your-key-from-dashboard"
}{
"broker": "ibkr",
"symbol": "SIL",
"action": "BUY",
"quantity": 1,
"expiry": "202703",
"broker_account_id": "<your-broker-account-id>",
"api_key": "sk_your-key-from-dashboard"
}{
"broker": "ibkr",
"symbol": "MNQH27",
"action": "BUY",
"quantity": 1,
"extra": {
"strategy": "Adaptive",
"strategyParameters": { "adaptivePriority": "Normal" }
},
"broker_account_id": "<your-broker-account-id>",
"api_key": "sk_your-key-from-dashboard"
}The easiest way to build alerts is with the published Pine Script v6 library. Add this to the top of your script:
import yuzername/VoltRouterWebhook/5 as vrManual approach (Pine Script v5/v6): Pass your JSON payload as the
alert_message parameter directly in your strategy function:alertMsg = '{"symbol":"' + syminfo.ticker + '","action":"BUY","quantity":1,"broker":"ibkr","order_type":"MARKET","broker_account_id":"YOUR_ACCOUNT_ID","api_key":"YOUR_KEY"}'strategy.entry("Long", strategy.long, alert_message=alertMsg)
Signing out#
Signing out does not stop routing. Your alerts authenticate with the API key in their payload, not with the browser session, so signals keep arriving and keep being routed while you are signed out. The session only controls access to this dashboard.
To actually stop routing, use the kill switch rules on the Controls surface, or disconnect the broker account. To invalidate the credential your alerts are using, regenerate the API key on the Account surface: every alert still sending the old key is rejected until you update it, and your webhook URL does not change.
Email preferences#
Email preferences on the Account surface sets the five emails VoltRouter sends you. Each row shows the email, its current setting and the control that changes it.
Session debrief summarises the CME session that just closed at 16:00 Chicago time: the signals sent and what they cost. It is sent at the time on its row, in the timezone shown there, once the session has closed. A session with no signals sends no debrief.
Pre-market briefing looks ahead to the next session: the strategies that have already run since the last close and the strategies up next, each with its decay status. It is sent at the time on its row, in the timezone shown there, once the session has closed, and it is sent even when nothing ran.
Silence alerts, Position check emails and Contract expiry warning each have their own passage, which says what the email is and when it is sent: Silence alerts, Position check and Contract expiry.
Is my data secure?#
Can I test with paper or demo trading?#
Venues#
IBKR - Hosted Gateway Type 1#
VoltRouter provisions and manages a dedicated IB Gateway instance for every IBKR user. You do not need to download, install, or configure anything locally. Once you submit your credentials via the dashboard, VoltRouter provisions your Gateway and keeps it running.
- Log into your VoltRouter dashboard
- In the Broker Accounts section, click Connect Broker → Interactive Brokers
- Enter your IBKR secondary username, then click Connect - VoltRouter provisions your Gateway (allow up to a few minutes)
- Once provisioned, click Open Gateway on your broker account card - a browser tab opens with the IB Gateway login screen
- Log in with your secondary IBKR credentials from your own device and network, then complete your IBKR 2FA prompt
- Return to the dashboard - status will show Active once the connection is confirmed
IBKR allows only one active session per username. Opening the IBKR mobile app or Client Portal under the same username will disconnect your Gateway. Create a secondary username: log into Client Portal → Settings → User Management → Add User. Grant trading permissions only. Use that secondary username when connecting to VoltRouter - not your main account username and not your account number (e.g. U1234567).
IBKR invalidates Gateway sessions every Sunday at approximately 01:00 AM US Eastern time. This is an IBKR security requirement that cannot be bypassed. You have two options:
Manual (default): Each Sunday morning, click Open Gateway on your dashboard, log in with your secondary credentials, and complete your IBKR 2FA prompt. Your Gateway runs unattended for the rest of the week. No additional setup required.
Automated: Save your restart credentials via the dashboard (secondary username, password, and TOTP secret). VoltRouter will handle the Sunday reconnect without any action from you. To enable this, your secondary IBKR username must have Mobile Authenticator (TOTP) configured - not IB Key. Go to Client Portal → Settings → Two-Factor Authentication to switch. Once configured, enter your credentials and the 32-character TOTP secret in the Restart Credentials section of your broker account card.
Do I need to install or configure IB Gateway?#
How are my IBKR credentials stored?#
Can I use a live IBKR account?#
Can I connect both a paper and live IBKR account?#
Alpaca - API Key Type 2#
Alpaca connects via API key - no hosted infrastructure required. Paper and live accounts are both supported and handled by a toggle in the dashboard.
- Log into app.alpaca.markets
- Go to API Keys and generate a key/secret pair (use paper keys for testing)
- In your VoltRouter dashboard, click Connect Broker → Alpaca
- Paste your API key and secret, select Live or Paper, then click Connect
- Status shows Active immediately - no provisioning delay
Crypto pairs on Alpaca are written with the slash, BTC/USD or LINK/USD, and a FLAT closes under that same spelling. You never type the venue's own form: VoltRouter derives it for the close, and your signal log keeps the symbol exactly as your alert sent it.
Fractional quantities are accepted on Alpaca, for crypto and for fractional shares. Alpaca owns the rule, so a size below its minimum comes back as a 422 carrying Alpaca's own sentence, with the signal fee refunded. Contract brokers such as IBKR and Tradovate still require a whole number.
Tradovate Live#
Tradovate futures execution is available - connect your credentials via the dashboard. Full E2E verification is in progress; some edge cases may not yet be supported.
Bybit#
Binance USDT-M Futures Live#
Binance USDT-M perpetual futures connect with an API key and secret, the same way Alpaca does - no hosted infrastructure required. Simulation runs on Binance Testnet, which is the environment Binance provides for USDT-M futures; keys are bound to one environment, so a Testnet key works only on Testnet and a live key only on live.
- For Testnet, create the key on testnet.binancefuture.com. That is a separate site with its own login, by GitHub or Google, and not your exchange account: a key cut on binance.com will not authenticate against Testnet, and the reverse is also true
- For live, create the key on binance.com under API Management
- Enable Futures trading permission on the key. A read-only key cannot place orders and VoltRouter's connection check will say so
- Set your Binance futures account to one-way position mode. VoltRouter never changes position mode, and a hedge-mode account rejects orders
- In your VoltRouter dashboard, connect the account with the key, the secret, and your Live or Testnet selection
Hyperliquid Perps Live#
Hyperliquid does not use API keys. It is an on-chain venue, and orders are authorised by a signature from a wallet you control. You type nothing to connect it: VoltRouter generates an API wallet (the venue calls it an agent), shows you its address, and you approve it by signing once in your own browser wallet. The account the agent trades for is read from that signature, so there is no address to enter either. Simulation runs on Hyperliquid Testnet, which is a separate deployment with its own funds and its own agents.
You need a browser wallet on the device you connect from: MetaMask, Rabby, or any wallet that injects a provider into the page. Without one, the card says so and offers nothing to press.
- On the Connect page, open the Hyperliquid card and choose Testnet or Mainnet. The API wallet is generated for the chain you pick, and it signs on that chain only
- Press Generate API wallet. VoltRouter creates it and shows you its address. Its private key never leaves the server and is never shown to anyone, including you
- Press Approve in wallet and sign the approval your wallet shows you. Check that the agent address in the prompt matches the one on the card. VoltRouter submits your signed approval to Hyperliquid and then proves the agent works before the account will route anything
- To trade on Testnet, fund the account from the venue's testnet faucet. The faucet requires the same address to have made a prior deposit on mainnet; that is Hyperliquid's rule for the faucet, not VoltRouter's, and a brand new address is refused by it
Which brokers are supported?#
broker field in your signal to ibkr or alpaca. Tradovate support is in development.Payload reference#
Supported Fields#
| Field | Type | Required | Brokers | Description |
|---|---|---|---|---|
| api_key | string | Yes | All | Your VoltRouter API key. Include in the JSON body only - never in the URL. Rotate from dashboard if compromised. |
| symbol | string | Yes | All | Ticker symbol. e.g. MNQ, ES, AAPL. See symbol formats below. |
| action | BUY / SELL / FLAT | Yes | All | Direction or close. FLAT closes the entire position regardless of quantity. On IBKR it closes the position in the contract the payload names and no other month of the same root. A close also cancels the entry's protective orders on venues that place them as separate orders. FLAT is refused on crypto spot: a spot sale is SELL with a quantity. On a crypto perp venue the position is read back about five seconds after an accepted FLAT; where some of it is still open (residual), the volt shows what is left and you are emailed. VoltRouter sends no second close. |
| broker_account_id | string | Yes | All | ID of the broker account to route this signal to. Copy from the Connected Brokers panel in your dashboard. Signals without a valid, owned broker_account_id are rejected (400 if missing, 404 if not found or not owned, 409 if the IBKR account is not yet provisioned). |
| quantity | integer | BUY and SELL | All | Number of contracts or shares. Ignored for FLAT - actual position size is resolved at execution time. |
| market | perp / spot | No | Crypto | Which market the symbol trades on. Absent means perp, so an existing alert needs no change. Send "market": "spot" only for a venue whose card lists spot; the webhook builder offers the markets each card's venue routes and composes the one it routes when there is only one. On spot there is no FLAT: a sale is SELL with the quantity to sell, and the builder offers Buy and Sell only. Where the venue takes its fee in the coin bought, you hold slightly less than the quantity bought, and a SELL of the full bought quantity is refused by the venue in its own words. |
| broker | ibkr / alpaca / tradovate | No | All | Which broker to route to. Must match credentials configured in the dashboard. |
| mode | paper / live | No | All | mode: paper or live. Must match the account named by broker_account_id; a mismatch is refused with a sentence and an email. Composed by the builder. |
| order_type | string | No | All | Default: MARKET. Standard types (MARKET, LIMIT, STOP, STOP_LIMIT) work on all brokers. See advanced order types table below for IBKR-only types. |
| take_profit_price | number | No | All | Required for LIMIT and STOP_LIMIT. Send with stop_loss_price to place a bracket - take profit fills as a limit order. |
| stop_loss_price | number | No | All | Stop loss price. Send with take_profit_price to place a bracket. Triggers as a stop-market. Mutually exclusive with trailing_stop_percent. |
| trailing_stop_percent | number | No | IBKR, Alpaca | Trailing stop as a percentage (0.1–100). Mutually exclusive with stop_loss_price. |
| entry_price | number | No | IBKR | Explicit bracket entry price. When set, the bracket entry leg uses a limit order at this price instead of market. |
| time_in_force | DAY / GTC / IOC / FOK / OPG | No | IBKR, Alpaca | Default: GTC for limit/stop orders. OPG (at the open) is IBKR only. IOC and FOK cancel immediately if not fully filled. |
| expiry | string | No | IBKR | Contract expiry. YYYYMM (e.g. "202606") or CME code (e.g. "MNQM26"). Futures only. A futures signal must name its contract month here or in the symbol; with neither it is refused. |
| extended_hours | boolean | No | IBKR, Alpaca | Allow order to fill outside regular trading hours. Defaults to false. |
| extra | object | No | IBKR | Native IB order properties passed through verbatim. Use for IB algos (Adaptive, VWAP, Arrival Price) and any IB-specific order fields. See extra field section below. |
| strategy_id | string | No | All | Tags this signal to a named strategy. Strategies appear automatically in the dashboard on first use and can be renamed, archived, and filtered from the Strategies tab in the Volt Log card. Rate limiting is applied per strategy_id. Signals can be filtered by strategy in the signal log. A strategy stays on the account it first routes to. |
| client_order_id | string | No | All | Idempotency key - duplicate signals with the same key are discarded and not charged. |
| dry_run | boolean | No | All | Validate only - returns the parsed signal without placing an order or deducting balance. Useful for testing payload format. |
| comment | string | No | All | Freeform note attached to the signal log entry. Max 255 characters. |
| source | string | No | All | Where this signal came from: tradingview, script, llm_agent, or other. Helps us understand where signals come from; never affects routing or pricing. An unrecognised value is stored as undeclared and the signal routes normally, with a note in the response. |
take_profit_price and stop_loss_price together on a BUY or SELL to place a bracket. Take profit fills as a limit. Stop loss triggers as a stop-market.ℹ Trailing stop - send
trailing_stop_percent (e.g. 1.5) to trail 1.5% from the market price. Cannot be combined with stop_loss_price.
Advanced Order Types - IBKR Only#
These order types are supported when broker is ibkr. Set via the order_type field.
| order_type | IB Name | Description |
|---|---|---|
| MARKET | MKT | Standard market order. Default. |
| LIMIT | LMT | Limit order at take_profit_price. |
| STOP | STP | Stop-market at stop_loss_price. |
| STOP_LIMIT | STP LMT | Stop-limit. Requires both stop_loss_price and take_profit_price. |
| MIT | MIT | Market if touched. Triggers a market order when price touches stop_loss_price. |
| MOC | MOC | Market on close. Executes at the official closing price. |
| MOO | MOO | Market on open. Executes at the official opening price. |
| LOC | LOC | Limit on close. Limit order participating in the closing auction. |
| LIT | LIT | Limit if touched. Triggers a limit order when price touches stop_loss_price. |
| MIDPRICE | MID | Midpoint price order. Attempts to fill at the mid of the bid/ask spread. |
| REL | REL | Relative / pegged-to-primary order. Dynamically adjusts with the market. |
| BOX_TOP | BOX TOP | Box top order. Executes at the current best price on the opposite side. |
| SNAP_MID | SNAP MID | Snap to midpoint. Pegged to the midpoint of the NBBO. |
| SNAP_MKT | SNAP MKT | Snap to market. Pegged to the best price on the same side. |
| SNAP_PRM | SNAP PRM | Snap to primary. Pegged to the primary exchange best price. |
Symbol Formats - IBKR Futures#
VoltRouter resolves futures contracts dynamically. Several symbol formats are supported.
| Format | Example | Description |
|---|---|---|
| Root only | MNQ | Refused for futures: VoltRouter never picks a contract month for you. A futures signal with no contract month is answered "Missing contract month for MNQ. Put the month in the symbol (e.g. MNQZ26) or set an "expiry" field." Use one of the two formats below. |
| CME expiry code | MNQM26 | Symbol + month code (F G H J K M N Q U V X Z) + 2-digit year. Resolves to that specific contract month. |
| YYYYMM expiry | MNQ + expiry: "202606" | Pass root symbol with a separate expiry field in YYYYMM format. |
| Aliased micro contracts | SIL, MGC | VoltRouter resolves these automatically - send the symbol as-is. Micro Silver (SIL) and Micro Gold (MGC) are disambiguated internally from their full-size counterparts. |
| tradingClass override | SI_SIL | Advanced: underscore separates root from tradingClass for manual disambiguation. Not required for SIL or MGC - use this only if directed by support or for unlisted contracts. |
| Regional exchange suffix | FCPO.MY | Dot separates symbol from 2-letter exchange code. Used for non-US exchange futures (e.g. Malaysian Palm Oil on Bursa). |
MHI, HSI) may return "No security definition found" from IB Gateway. This is a contract resolution issue - the active contract may not be discoverable via the standard conid lookup. If you encounter this, try passing the full expiry explicitly (e.g. MHIK26) or contact support.
The extra Field - Native IB Order Properties#
The extra field passes any native Interactive Brokers order property directly to the IB API without VoltRouter needing to model it explicitly. This is for advanced users who need IB-specific behaviour not covered by standard fields.
{
"broker": "ibkr",
"symbol": "MNQH27",
"action": "BUY",
"quantity": 1,
"extra": {
"strategy": "Adaptive",
"strategyParameters": { "adaptivePriority": "Patient" }
},
"api_key": "sk_your-key-from-dashboard"
}Valid adaptivePriority values: Patient, Normal, Urgent. Patient minimises market impact; Urgent prioritises speed of fill.
{
"broker": "ibkr",
"symbol": "AAPL",
"action": "BUY",
"quantity": 100,
"asset_class": "equity",
"order_type": "LIMIT",
"take_profit_price": 195.00,
"extra": {
"discretionaryAmt": 0.05
},
"api_key": "sk_your-key-from-dashboard"
}extra are forwarded verbatim to the IB API. Invalid or unsupported properties are silently ignored by IB Gateway - they do not cause a VoltRouter error but the order may not behave as expected. Refer to the IB API documentation for supported order properties.⚠ For bracket orders (
order_type: "BRACKET"), extra is applied to the entry leg only. The take-profit and stop-loss child legs are not affected.VoltRouter sets
orderRef on every IBKR order it places, and an orderRef in extra is replaced.
Close Positions and Cancel Orders#
Both actions live on each account card on the Connect surface (see The Connect Surface above) and are also callable directly.
Close positions#
- Closes every open position on one broker account with market orders. It sells nothing on crypto spot: a spot holding is not a position.
- On IBKR each futures position is closed on its own contract, the one IBKR reports the position in, so a position in a later month is never closed by an order on the front month. If that contract cannot be read back from IBKR, the position is left open, the answer names it and says why, and the other positions still close.
- On a crypto perp venue the positions are read back about five seconds after the closes, before the answer. A position still open is listed as "Still open after the close" with its quantity, the result is partial, and you are emailed. VoltRouter sends no second close. The fee is unchanged.
- API form:
POST /volt/panic/:broker_account_idwith yourapi_keyin the JSON body. The key never goes in the URL. - Cost: base fee plus a per-position fee, capped. The current figures are shown in the confirmation dialog before you commit.
Cancel orders#
- Cancels every open order without closing any position.
- API form:
POST /volt/cancel-allwith yourapi_keyin the JSON body. - Cost: a per-cancelled-order fee, shown in the confirmation dialog.
Can I route to multiple broker accounts?#
broker_account_id - the ID of the account to route to. Copy the ID from the Webhook Config section of the dashboard, which pre-fills it based on your connected accounts. Signals without a valid, owned broker_account_id are rejected.What is broker_account_id?#
What order types are supported?#
How do I close an entire position regardless of its size?#
action: "FLAT" - VoltRouter queries your actual position size at execution time and closes it completely. The quantity field in the signal is ignored for FLAT. FLAT is refused on crypto spot: a spot sale is SELL with a quantity. On a crypto perp venue the position is read back about five seconds later. Residual means part of it was still open at a second read five seconds after that: the volt shows the quantity left and you are emailed. VoltRouter sends no second close, because it cannot tell what is left from a position opened since; closing the remainder is yours to decide.What is the extra field for?#
Risk rules and Controls#
The Controls surface is everything that stops or shapes routing. It states what is set; this section explains what each setting does.
Routing state#
The indicator at the top of Controls answers one question: is routing paused right now. Routing runs uninterrupted until a rule or manual pause stops it. When a kill switch rule fires, the indicator turns amber, reads paused, and names the rule that caused it. The pause ends on its own when that rule's pause duration elapses; there is no manual resume, because there is no manual pause. This indicator and the routing row on the Overview health panel render from one source, so the two can never disagree.
Kill switch rules#
Rules are set in advance and executed mechanically at routing time. VoltRouter never intercepts at execution, and decay status never auto-pauses: stopping is the kill switch's job, armed by you, and nothing else on the platform pauses routing on your behalf.
Each rule counts signals in a rolling window and pauses routing when the count reaches its threshold. A rule is scoped to all strategies or to one, chosen by display name and stored against the strategy's permanent id, so renaming a strategy never detaches its rules. Per-strategy scope needs a strategy to exist; until one does, a rule covers all of them. Archived strategies are not offered, because a rule armed against a strategy that no longer routes would never fire.
An armed rule is evaluated on every signal. A disarmed rule is kept but never evaluated, which is the difference between turning a rule off and removing it.
Scheduled flatten#
A scheduled flatten closes what one strategy holds on one broker account, at a time you set, on the days you choose or on a single date. It is scoped to the strategy, not to the account: two strategies trading the same symbol on the same account each close their own share, and neither touches the other's.
The quantity comes from your own routed fills. VoltRouter sums the fills it can attribute to that strategy through the signals that produced them, and closes that number. It never reads a position from the broker and never guesses at fills it cannot attribute; where such fills exist, the run says so.
At the scheduled time the run cancels that strategy's working orders first, then places one market order per symbol, opposite the position, for the whole tracked quantity. It waits, reads the position back, and if anything is still open it tries once more. It never loops. Each cancelled order and each close order is charged at the rates on the pricing page.
The time is a wall time in the timezone you choose, resolved on the day it fires. On the spring-forward day, a time that does not exist fires at the first valid moment after the gap. On the fall-back day, a time that happens twice fires on the first of the two.
A single date is the holiday early-close mechanism: it fires once and then turns itself off. VoltRouter holds no exchange calendar and never will, so an early close is a date you enter.
A run that closes everything is quiet. A run that needed a retry, left a position open, failed, or fired late is shown here and emailed to you. A run that could not reach your broker at the scheduled time is recorded as missed and is not held for later: a close that arrives long after the time you asked for is a trade you did not ask for.
Scheduled flatten fires from the VoltRouter server. If the server or your connection is down at the scheduled time, it cannot fire. A missed run appears here and you receive an email. It is available on venues where VoltRouter can cancel a single order; where it cannot, the schedule is refused rather than run without that step.
A scheduled flatten sells nothing on crypto spot. A spot symbol the strategy holds is listed on the run as not closed, quietly, and the run is not emailed for it.
Risk limits#
The effective limit is the minimum of all set values. Every scope can tighten itself, and no scope can loosen a scope above it. The effective cell shows the number that will actually be enforced and names the scope that set it.
VoltRouter and broker account are the scopes enforced today, so they are the scopes the table shows. The VoltRouter column is a ceiling on the whole platform: it is not editable and nothing can raise itself above it. The broker account column is yours to set, one account at a time: choose the account from the picker at the top of the section, and the table, its Save and the account's size caps are that account's. The same choice carries to the daily spend limit below, and if the account you are leaving has changes not yet saved, the section asks you to discard them or stay. Max position bounds the quantity a single signal may carry. Signals per minute bounds how often an account may route.
Crypto accounts also carry size caps by symbol. Max position is a whole number of at least 1, and on a crypto venue the quantity is in coins, so a whole coin is the tightest it can go. A size cap is set per symbol and market (perp or spot) under the limits of the account chosen in the picker, in the same units as your payload's quantity, and fractions are allowed: a cap of 0.001 on BTCUSDT perp refuses a signal for 0.002 and passes one for 0.001. A symbol with no cap is not refused. Max position still applies alongside it, a spot SELL is capped like a BUY, and FLAT is never capped. A cap on a symbol with no signals yet is marked "no signal seen yet", which is how a misspelt symbol shows itself. Update a cap to change it and clear a cap to remove it.
Daily spend limit#
This limit gates the total signal fees charged per day. It does not bound trading profit or loss, and it never has: it measures what you are billed, not what your strategy makes or loses.
The section has two rows and one Save. All accounts is the limit across every account. The second row is the limit of the broker account chosen in the picker, the same choice as in Risk limits. Save writes whichever of the two you changed. The All accounts limit and each broker account limit run as independent gates. Whichever is reached first stops routing for the rest of the day. Both counters reset at 00:00 UTC, which is the same day boundary the rejection messages count down to.
An empty field means no limit is set, and signal fees are not capped by the day. Emptying a field that had a value and saving removes the limit.
Position check#
Every five minutes VoltRouter reads the positions IBKR reports for each connected IBKR account and compares them with what it recorded. For each contract it keeps three numbers: held, the position IBKR reports; placed, the net of the fills VoltRouter placed (a signal's orders and its own panic closes); and other, the net of every other fill it recorded on that account in that contract. What VoltRouter recorded is placed plus other. Each size is shown with its direction: "2 long" above zero, "1 short" below zero, and "0" at zero, never with a minus sign.
Two things can be said. The position does not match the fills when what IBKR holds is not what VoltRouter recorded, with a short counting against a long. A contract whose last trade date has passed is not raised when IBKR holds nothing, since an expired contract cannot be held. Part of the position was traded outside VoltRouter when IBKR holds a position, it is what VoltRouter recorded, and other is not zero.
"Outside" is not raised for a contract that has a fill VoltRouter cannot attribute: one recorded without IBKR's order identity, or one made on the same day as a panic close whose identity was not recorded. Some older panic closes left no such record, so a fill of theirs cannot be told from a trade made by hand, and staying quiet is better than naming your own panic close as an outside trade.
A finding is shown only when two reads in a row, five minutes apart, give the same three numbers and no signal reached the account in between, so an order on its way to a fill is never mistaken for a mismatch. It clears by itself on the first read that no longer shows it.
On Overview and by email. Only a finding where IBKR holds a position that VoltRouter's record does not explain is shown on Overview, under the health bar, as Position check, and emailed. It is the one you can act on: a position is open in IBKR, and only you can say whether it should be. For example IBKR holds 2 MNQZ6 and no fill explains them: "MNQZ6: IBKR holds 2 long, VoltRouter recorded 0. Check this position in IBKR.", with the account after it. Acknowledge quiets one: it is then shown muted, and comes back as new only if one of its three numbers changes. Acknowledging changes no fill, no signal and no order.
Each pass sends you at most one email listing those findings you have not been emailed about yet, one line each, for example "MNQZ6 on IBKR paper: IBKR holds 2 long, VoltRouter recorded 0. Check this position in IBKR." Turn it off with Position check emails in Email preferences on Account.
On Controls. Every other finding is kept in Position check on Controls, for the account chosen in the picker there: muted, one line each, and never emailed. These need no action. Where IBKR holds nothing, no position is open and what is off is VoltRouter's record of the fills, for example "MCLN26: IBKR holds 0, VoltRouter recorded 1 short". Where part was traded outside VoltRouter, the numbers add up and the trade was yours, for example after VoltRouter bought 2 and a trade made by hand in IBKR bought 1 more: "MNQZ6: IBKR holds 3 long, 1 long of it not placed by VoltRouter". Once you have checked IBKR, Dismiss on a line takes it off the list. Dismissing only hides the line: it repairs nothing, and the gap it named is still in VoltRouter's record. A dismissed line comes back if its numbers change. The section is absent for an account with nothing left in it.
The check is IBKR only today: the crypto venues report a position's size without its direction, so a signed comparison there needs its own work. VoltRouter never acts on a finding. It places no order, cancels nothing and changes no record because of one: what to do about a position is yours to decide.
Can I set a daily spend cap?#
max_daily_loss_usd in Risk Controls on your dashboard. Once your total signal spend for the day reaches that amount, all further signals are rejected until midnight UTC. Leave it blank to disable the cap.Strategies and Signals#
Each strategy's page derives everything it shows from your own signals and your own uploaded backtest. This section explains the figures.
The payload token#
The token under the strategy title is the strategy_id your alerts send. It is the join key: every signal carrying this value attributes to this strategy. Changing the token creates an alias rather than a replacement, so prior tokens keep attributing, history is never orphaned, and alerts you have not updated keep working. The display name is separate: renaming never breaks attribution and never changes links.
Units#
Baseline figures from an uploaded backtest are percent values. Live figures derive from each closed trip's return on notional, which is a fraction. The page states both in percent points: live returns are multiplied out explicitly so the live tiles and their baseline sub-lines read in one unit.
The equity curve#
The curve is cumulative return over closed round trips, in percent points, computed over the curated sample: trips you have excluded are off the curve and out of every derived figure, and the trip numbering belongs to the curated sequence. The curve renders at any sample size, because every point on it is a real trade; the line beneath it states the curated count. Derived statistics gate separately until the sample crosses the trip threshold. When your baseline carries a per-trade series, the backtest renders on the same chart in yellow and the live segment starts from the backtest's terminal value: the color change is what marks where the backtest ends and live begins. The Timeframe picker on the curve's label line narrows the curve and its drawdown to the trips that closed in that window, starting the curve from zero at the window's first trip; it changes the chart only, the figures, the distribution and the trips table keep the whole sample, and the backtest shows on All only.
Below the chart, the trips table lists every closed trip, excluded ones included, newest exit first: the newest 50 show until you choose Show all, which adds the older trips below them.
The return distribution#
The histogram bins each closed trip's return on notional, as a percent. Bin count follows the sample size (a square-root rule, between 5 and 20 equal-width bins), and a dashed marker sits at zero when the range straddles it. A binned distribution is an aggregate rather than a record of trades, so it gates with the statistics row: at small sample sizes the bins describe the binning rule more than the strategy.
The decay monitor#
Decay status is informational. It never pauses routing and it never acts on your account: stopping is the kill switch's job, armed by you in advance. The monitor compares your live sample against your uploaded baseline and reports one status word - building, tracking, drifting, or breaking - alongside the three signals it evaluates, with their live values and thresholds.
Silence alerts#
Silence detection emails you when a strategy that usually fires goes quiet. It is off by default and armed per strategy, from the switch on the strategy row. Nothing fires for a strategy you have not armed: only you know which quiet is abnormal, so arming follows the same principle the kill switch does, you arm it in advance and the platform executes mechanically.
The switch sits in the row on the Strategies page and again in the header of the strategy's own page, labelled "Silence alerts", with the threshold beside it: "Silence alerts [off] ~18h". The figure is there whether or not the strategy is armed, so you see what you are arming before you flip it.
The threshold is the gap the detector tolerates before it emails you. It comes from your uploaded backtest when you have one (the typical interval between signals, multiplied out), and otherwise from your own live history (the 90th percentile gap between signals, multiplied out) once the strategy has enough of it. Until one of the two yields a number the switch is unavailable and says why, because there is nothing yet to measure quiet against.
Arming measures from the moment you armed, never from a gap that accumulated before it, so arming a strategy that has been quiet for a fortnight does not email you about the fortnight. A strategy that is archived or under a kill-switch pause is never checked, armed or not, and a repeat alert for the same strategy is held for four hours.
The account-wide switch is the Silence alerts toggle in Email preferences on Account. It is the master mute above the per-strategy arms: off there means no silence alert for any strategy, whatever is armed. Disarming a single strategy leaves the rest armed.
Contract expiry#
An alert names its contract month, and when that contract stops trading the alert stops routing until you edit it. VoltRouter says so in advance. The contract a strategy is trading is the one its most recent fill was in, where the venue reported a full last trade date for it (IBKR futures do); a strategy with no such fill shows nothing.
Days left are whole days from today's date in UTC to the contract's last trade date: zero on that day, and expired from the day after. The strategy's own page shows the contract in its header, beside the volt count and the last volt: its name, the days left and the last trade date, or the date it expired.
The badge. On the Strategies list a strategy shows nothing while its contract is outside your warning. Inside it the row carries "expires in N days" (or "expires today"), and once the contract has expired a quiet "contract expired".
The email. When strategies' contracts enter your warning, one email lists every strategy entering it at that time, soonest first, each with its contract, last trade date and days left. Each strategy and contract is emailed once: one that enters the warning later comes in a later email, and a new contract is emailed again when it does. An expired contract gets none. Update the contract month in the alert before the date.
The setting is Contract expiry warning in Email preferences on Account: a whole number of days from 0 to 30, 5 by default. 0 turns the badge and the email off.
The report card#
A report card is a shareable public page built from a frozen snapshot of your strategy's closed round trips. It unlocks once the strategy has enough closed trips; the section states the threshold it is waiting for, and that number comes from the same calculation that gates the statistics, never from a fixed figure written into the page.
Preview renders the actual artifact, watermarked, before any charge. It is built live for that one response and discarded: nothing is stored, nothing is billed, and it is visible only to you. Preview is not gated on eligibility, because previewing is how you decide whether the charge is worth incurring. An unset public name renders a placeholder there rather than your internal strategy name.
The public name is the only name a reader ever sees. It is required before the first generate, and editing it later takes effect on the live page immediately with no regeneration, no new link, and no charge. Your internal strategy name and your payload token never appear on the public page.
Generate charges your balance and mints the shareable link. Regenerating against new closed trips rebuilds the snapshot and charges again; regenerating when nothing has closed since the last card is free, and the confirm says which of the two it is before you commit. The price shown is the current schedule.
Published and unpublished is a serving decision, not a data one. Unpublished, the link answers the same not-found page a link that never existed would answer, so an unpublished page does not announce that there is something there to re-publish. The card itself is kept: republishing restores the same link, with no charge, because the artifact was already paid for.
Rotate mints a fresh link for the same card. The previous link stops working the moment you confirm, which is how you un-share a link you have already sent. Nothing is rebuilt and nothing is charged: rotation changes who can reach the page, not what the page says.
Deleting a strategy removes its report card with it, and the link stops working.
The threshold knobs#
- Recovery multiplier - multiplies the baseline's worst underwater stretch to set the recovery threshold.
- Breach k - standard errors below the baseline mean at which the distributional signal fires.
- Behavioral offset - added to the baseline's worst rolling win rate to set the win-rate floor.
An empty field shows its default as the placeholder. Clearing a field returns that knob to its default; defaults come from the server, never from the page.
What is strategy_id for?#
Can I run multiple strategies simultaneously?#
strategy_id field to tag signals from different strategies. They all route through the same account. Risk limits apply per account; rate limits apply per strategy.Receipts and errors#
Common Errors#
| Error | Meaning | Fix |
|---|---|---|
| 400 | Missing required field or invalid JSON | Check that broker_account_id, symbol, action, quantity, and api_key are all present and correctly typed. Validate your JSON is well-formed. |
| 401 | Missing or invalid API key | Ensure api_key is in the JSON body (not the URL). Copy it directly from your dashboard. |
| 402 | Insufficient balance | Top up your balance from the dashboard. |
| 403 | broker_account_id does not belong to your account | Ensure the broker_account_id in the single-account panic URL (/volt/panic/:broker_account_id) matches one of your own connected accounts. |
| 404 | broker_account_id not found or not owned | The broker_account_id in your signal was not found or does not belong to your account. Copy the account ID from the Connected Brokers panel in your dashboard. |
| 409 | IBKR account not yet provisioned | Your IBKR broker account was created but the IB Gateway container has not finished provisioning. Wait for the provisioning confirmation or contact support. No balance is deducted. |
| 422 - risk: rate limit | Signal rate exceeded max signals per minute for this strategy | Reduce alert frequency or increase max_volts_per_minute in Risk Controls on your dashboard. |
| 422 - risk: position size | Requested quantity exceeds your max position limit | Reduce quantity in your signal or increase max_position in Risk Controls. Platform ceiling applies. |
| 422 - risk: size cap by symbol | Quantity 0.002 exceeds the size cap of 0.001 for BTCUSDT perp | Lower the quantity in your signal, or raise or clear the cap under Size caps by symbol in Controls. |
| 422 - risk: daily spend | Daily spend limit reached, resets in Nh Mm | Your max_daily_loss_usd limit has been hit. Resets at midnight UTC, or raise the cap in Risk Controls. |
| 422 - venue refused the order | The venue received the order, understood it, and turned it down. Alpaca and the crypto venues both answer this way. The error field carries the venue's own words, such as an order value below the venue's minimum, a quantity below its minimum size, or insufficient margin. | Read the venue's sentence in your signal log and change what it names, usually the quantity. Nothing was routed and the signal fee is refunded automatically. |
| 422 - other | Unsupported symbol, bad expiry format, or invalid field value | Check symbol is valid for the selected broker. Use YYYYMM or CME format for expiry. See supported fields table. |
| 503 | IB Gateway not connected | Check the broker account status on your dashboard. Signals that fail with 503 are refunded automatically. |
| Broker account is inactive | The broker account the signal names is not active, so nothing was routed and nothing was charged. | Reconnect or re-enable that account from Connected Brokers in your dashboard, then resend. Also refused on dry run. |
| No position to close | FLAT sent but no open position found for this symbol. Answered this way on IBKR, Alpaca and the crypto venues alike. | Verify the symbol and broker match an open position. The position may already be closed. Nothing is routed and the signal fee is refunded automatically. |
| 422 - No open position in MNQZ6, nothing to close. This account holds MNQH7 on the same root | An IBKR FLAT named one contract month and the account holds the same root in another. FLAT closes only the contract the payload names. | Put the month of the contract you hold in the alert's symbol or expiry and resend. Nothing is routed and the signal fee is refunded automatically. |
| 422 - MNQU6 stopped trading on 2026-09-18. Update the contract month in the alert. | The IBKR futures contract the alert names is past its last trade date. | Change the contract month in the alert (the month in the symbol, or the expiry field) to one still trading. Nothing is routed and the signal fee is refunded automatically. |
| 422 - The contract month 2025-09 for MNQU25 has passed. Update the contract month in the alert. | The alert names a month before the current one and IBKR has no contract for it. | Change the contract month in the alert to one still trading. Nothing is routed and the signal fee is refunded automatically. |
| Broker not configured | No broker account connected for the requested broker type | Connect a broker account in your dashboard - click Connect Broker and add credentials for the requested broker. |
| duplicate | Signal already processed (same client_order_id) | TradingView sometimes fires an alert twice. Use a unique client_order_id per signal, or VoltRouter will deduplicate by internal ID automatically. |
| This signal does not say whether it is paper or live. This signal says <mode> but the account it names is <mode>. |
The payload has no mode, or its mode disagrees with the account it names. Nothing was routed and nothing was charged. |
Add "mode": "paper" or "mode": "live" to the alert, matching the account in broker_account_id. The webhook builder on the account card composes it for you. |
| This strategy is pinned to <label> <MODE>; this signal names <label> <MODE>. | The strategy already routed to one account, and this signal names a different one. Nothing was routed and nothing was charged. | Move the strategy from its Strategies card, or correct the alert. |
| Brackets are not routed on spot at Bybit | The signal asked for a bracket (take profit and stop loss) with "market": "spot". Spot does not route brackets. Nothing was routed. |
Send the entry without take_profit_price and stop_loss_price, or route the bracket on perp by removing market. The webhook builder does not offer Bracket on spot. |
| FLAT is not routed on spot: send SELL with a quantity | The signal sent FLAT with "market": "spot". Spot routes no FLAT: coins can arrive and leave outside VoltRouter, so a close sized from its own records could sell coins your strategy never bought. Nothing was routed and nothing was charged. |
Send SELL with the quantity to sell. Where the venue takes its fee in the coin bought, sell slightly less than the quantity bought, or the venue refuses the sale. |
IB Gateway Troubleshooting#
VoltRouter manages your IB Gateway - most issues resolve automatically. Follow these steps in order. No server access or SSH is required.
- Check the dashboard status indicator - if your IBKR broker account shows "Disconnected", wait 30 seconds for automatic reconnection before taking further action.
- Re-save your IBKR credentials in the dashboard - navigate to your connected broker account, update the credentials, and save. This triggers a fresh provisioning attempt.
- Check that your IBKR account is not locked or requiring 2FA re-approval - log into IBKR Client Portal directly to verify your account is in good standing and no action is required.
- If using a secondary IBKR user, confirm that user has trading permissions enabled in Client Portal under Settings → User Management.
- Contact hello@voltrouter.com - include your VoltRouter email and the approximate time of disconnection. We can inspect the Gateway container directly.
What happens if IB Gateway goes offline while a signal fires?#
Billing#
Funding Your Balance#
- Each signal costs - deducted from your prepaid balance when the signal is accepted and routed.
- Cancels and modifies are charged per order at the same rate as a signal, including legs cancelled by a scheduled flatten.
- IBKR users also pay a monthly Gateway infrastructure fee of /mo per connected IBKR account. This is billed separately via Stripe and shown on your dashboard.
- Balance never expires. It carries over indefinitely and is not subject to any inactivity fees.
- Signals that fail (503 Gateway disconnected, broker rejection) are refunded to your balance automatically.
The transaction ledger#
Every movement of money in or out of your balance is recorded as a line on the ledger, on the Account surface. Each line carries the date, what kind of movement it was, the signed amount, and your balance immediately after it.
Per-signal fees are rolled up. At production volume a line per signal is unreadable noise that duplicates the Volts log, so all of a day's signal fees render as one line for that day, in UTC, and that line links to the Volts log filtered to the same day. Signal-level detail lives on the volt itself. Everything else is discrete: top-ups, credits, close-all and cancel-all fees, report card charges, subscription invoices, and every refund. Refunds are never rolled into the day's fee line, so a fee and its refund are always two lines you can pair up.
Balance after. Lines recorded before the ledger existed read "not recorded" in that column. That is not a balance of zero: it means the figure was never captured and cannot be reconstructed honestly after the fact. Lines from that point forward all carry a real balance.
Reconciliation. The ledger checks itself against your balance every time you read it: each line's balance-after should equal the previous line's plus this line's amount, and the newest line should equal the balance shown at the top of the page. When they agree, the section says nothing, because a check that announces success every time is a check people stop reading. When they disagree, the section says so and names both figures. If you ever see that, contact support before topping up: the discrepancy is the useful evidence, and a top-up on top of it makes it harder to trace.
Export. The CSV carries the same lines in the same order as the screen. Timestamps are UTC and amounts are in integer cents, so a spreadsheet has nothing to round. It is a separate export from the account data export further down the page.