Skip to main content

Tradovate: Exclusive Features & Execution Internals

Tradovate execution vs. CrossTrade-managed beta features

Direct Tradovate execution, native order routing, and Strategy Sync are generally available. The Tradovate Copier (both signal and fill-driven modes), target-fill breakeven, profit-triggered trail activation, and durable trigger replay are beta because CrossTrade manages those behaviors outside Tradovate's native order and AutoBracket fields.

The Destinations page covers how routing works and which commands and fields each destination supports. This page goes deeper on the Tradovate side in two ways:

  1. Copy-paste examples for every capability that only exists on the Tradovate destination.
  2. Execution internals: the request sequence each command generally uses, including bounded confirmation polling that can add reads, so you can reason about latency and Tradovate's rate limits.

For multi-target scale-out brackets, see Tradovate ATM. Tradovate natively owns the multibracket, scale-out targets, fixed stops, OCO links, and continuous trails. CrossTrade adds named cloud templates, beta profit-triggered trail activation with restart-safe trigger memory, and beta target-fill breakeven. On Tradovate, strategy= / atm_strategy= names a CrossTrade template, not an NT8 or Tradovate-saved template.

CrossTrade prices Tradovate orders itself

CrossTrade never assumes the linked Tradovate login supplies market data. Relative-price conversion, position display marks, and managed ATM trigger activation are calculated by CrossTrade server-side from live market data, even when that login has entitlements. If fresh pricing isn't available, the operation fails closed or leaves the existing fixed stop unchanged. For beta triggered trails, trigger progress from before a restart is preserved, so activation evaluation resumes where it left off.

Everything here assumes a linked Tradovate identity (My Account, Brokers tab) and destination=tradovate; in the alert.

Tradovate-only features, with examples

Native trailing stops

NinjaTrader webhooks have no trailing order type (trailing there is done through ATM strategies). Tradovate supports it natively. Required fields: stop_price (the initial trigger) and trail_offset (the trail distance, in price units; peg_difference is an accepted alias).

key=your-secret-key;
command=place;
account=DemoAccount;
instrument=ES1!;
action=sell;
qty=1;
order_type=trailingstop;
stop_price=5285.00;
trail_offset=4.00;
tif=gtc;
destination=tradovate;

order_type=trailingstoplimit works the same way and additionally requires limit_price (the limit leg of the stop-limit). All three price fields are required: stop_price, limit_price, and trail_offset. Keep the limit on the fill side of the stop: at or below the stop for a sell, at or above for a buy.

key=your-secret-key;
command=place;
account=DemoAccount;
instrument=ES1!;
action=sell;
qty=1;
order_type=trailingstoplimit;
stop_price=5285.00;
limit_price=5283.00;
trail_offset=4.00;
tif=gtc;
destination=tradovate;

Market-if-touched (MIT)

A touch-price order: it becomes a market order when the price trades at your level, without resting in the book like a limit. The touch price goes in limit_price (not stop_price).

key=your-secret-key;
command=place;
account=DemoAccount;
instrument=ES1!;
action=buy;
qty=1;
order_type=mit;
limit_price=5290.00;
tif=day;
destination=tradovate;
QTS is not supported

order_type=qts appears in Tradovate's API schema, but Tradovate has confirmed that placing a QTS order is not supported; every attempt fails at the broker. CrossTrade rejects the alert up front with a clear error instead of relaying Tradovate's bare HTTP 400.

Iceberg orders (max_show)

Show the market less than your true size. max_show is the displayed quantity and must be greater than zero.

key=your-secret-key;
command=place;
account=LiveAccount1;
instrument=ES1!;
action=buy;
qty=10;
max_show=2;
order_type=limit;
limit_price=5288.00;
tif=gtc;
destination=tradovate;

Good-till-date orders (tif=gtd)

In addition to the shared day and gtc, Tradovate accepts ioc (immediate or cancel), fok (fill or kill), and gtd. GTD requires expire_time as an ISO-8601 datetime, and CrossTrade rejects the alert up front if it is missing.

key=your-secret-key;
command=place;
account=DemoAccount;
instrument=NQ1!;
action=buy;
qty=1;
order_type=limit;
limit_price=18500.00;
tif=gtd;
expire_time=2026-12-18T20:00:00Z;
destination=tradovate;

Broker-side limit-order timeouts (cancel_after)

cancel_after (minutes, 1 to 180, limit orders only) is implemented as a Tradovate-native scheduled cancel: immediately after the entry is accepted, CrossTrade submits a cancel with a future activation time. The timeout lives on Tradovate's servers, so it fires even if your machine and CrossTrade are both unreachable at that moment. If the order has already filled by then, the scheduled cancel is a harmless no-op.

key=your-secret-key;
command=place;
account=DemoAccount;
instrument=ES1!;
action=buy;
qty=1;
order_type=limit;
limit_price=5286.00;
tif=day;
cancel_after=15;
destination=tradovate;

Scheduled cancels (activation_time on cancel)

You can also schedule a cancel of any working order for a future time yourself. activation_time is ISO-8601 and the cancel executes broker-side at that moment.

key=your-secret-key;
command=cancel;
account=DemoAccount;
order_id=my-entry-42;
activation_time=2026-12-11T15:55:00Z;
destination=tradovate;
activation_time is a cancel field

activation_time schedules a cancel. It does not delay or schedule order entry. For delayed entries, use the shared delay= field, which works on both destinations.

Order labels (notes and cl_ord_id)

notes is the free-form label field: it rides to Tradovate as the order's text and shows on the order inside Tradovate's own UI and reports, which makes bot orders easy to separate from manual ones at the broker. cl_ord_id (client order id) passes through as clOrdId. When you set an order_id for later cancels and changes, it is also sent as the cl_ord_id by default, so the order is traceable in Tradovate's interface.

custom_tag is rejected

Tradovate's API has a customTag50 order field, but it identifies registered B2B partners to the exchange; it is not a user fill tag. Normal API orders that carry it are rejected by Tradovate with an unhelpful UnknownReason, so CrossTrade rejects custom_tag up front with an error that explains why. Use cl_ord_id or notes instead.

Multiple identities, one webhook surface

A single CrossTrade account can link several Tradovate identities at once (a personal login plus any number of funded-firm logins, Live and Demo both). You never tell the webhook which identity to use: the account= name is looked up across everything you have linked, and the order is routed to the identity and environment that owns that account. Names must be unique across linked identities. If two linked identities expose the same account name, CrossTrade rejects the order and asks you to rename one rather than routing unpredictably.

Note that prop firm logins (Apex, Topstep, and other Tradovate-based funded firms) always belong on the Demo environment, including funded/PA accounts, since firm accounts are simulation accounts that the firm mirrors. Link each firm identity with Link Demo; see Connecting Tradovate.

Execution internals: typical Tradovate request sequences

Unlike the NinjaTrader path, where CrossTrade hands one instruction to the Add-On, the Tradovate destination executes composite commands as REST workflows. The table shows the typical broker sequence, not an exact request transcript. Durable recovery, settlement polling, cache misses, copier mappings, and account fan-out can add, skip, or resume reads and mutations depending on current state.

Two shared resolution steps appear in many sequences:

  • Account resolution normally adds no broker request. Webhook account names are resolved from CrossTrade's server-side cache. The first signal after linking, or a name the cache has never seen such as a brand-new account at your firm, can trigger one GET /account/list sync per linked identity.
  • Symbol resolution depends on the form you send. A concrete symbol (ESU6 or ES 09-26) is passed through directly. The TradingView continuous form (ES1!) costs one GET /contract/suggest to resolve the front month. Commands that operate on an existing position or order book additionally need the contract id, which costs one GET /contract/find when it was not already learned from the suggest call.
CommandTypical broker workflow
placePOST /order/placeorder. With take_profit/stop_loss: a single POST /order/placeoso instead (entry plus brackets in one call). With cancel_after: one extra POST /order/cancelorder to schedule the timeout.
place with flatten_first=true, and flatplaceResolve the exact contract, liquidate the same-account position, then poll live net position and nonterminal same-contract orders until both are clear before placing the entry. A "no position" result is tolerated, but the order-clear barrier still runs. Each mapped follower performs its own flatten and settlement check before its entry.
closeposition (full)GET /contract/find, then POST /order/liquidateposition. 2 requests.
closeposition (partial, quantity/percent)GET /contract/find, a live position read (GET /position/find + GET /order/list + GET /fill/list, see Position freshness), then an opposing POST /order/placeorder market order for the computed quantity. 5 requests.
reverse / reversepositionGET /contract/find plus a live position read, POST /order/liquidateposition, bounded fill-derived polling until flat, then POST /order/placeorder for the new position. reverse derives the opposite side and original size; reverseposition uses the explicit entry fields. Both reject a flat starting position.
flattenA reconciled open-position scan per account (position, order, and fill reads), then one POST /order/liquidateposition per matching open position.
flatteneverythingThe flatten scan and liquidations above, plus order cleanup: GET /order/list per identity and one POST /order/cancelorder per working order. An account filter limits both phases to that account. CrossTrade deliberately does not call Tradovate's /user/canceleverything, which revokes API permissions rather than acting as a safe trading-only bulk cancel.
cancelPOST /order/cancelorder. 1 request (the order_id you assigned at place time resolves from CrossTrade's own map, not from Tradovate).
cancelordersGET /order/list for the account's identity, then one POST /order/cancelorder per working order that matches (plus GET /contract/find when narrowed to an instrument).
cancelallordersGET /order/list per linked identity, then one POST /order/cancelorder per working order. Tradovate has no bulk cancel-all endpoint, so the sweep costs one request per working order.
changeRead /orderVersion/deps whenever needed to restore omitted quantity, order type, required limit/stop prices, or a required trailing offset, then POST /order/modifyorder. Mapped followers perform the same restoration from their own live versions, with follower sizing re-applied. A fully specified change can skip the leader version read.
cancelreplaceClaim the owner-fenced durable workflow, reconcile any prior recovery stage, verify that the original order belongs to the requested account and exact replacement contract, cancel and confirm the leader, then cancel and confirm every mapped signal-copier follower before replacement fan-out. Broker calls vary by recovery stage. An ambiguous cancel or placement is recorded and never resent; the response requires reconciliation.
cancelandbracketResolve the contract, read live position, list and cancel matching orders, poll until cancellations are terminal, re-read position for the safety check, then place the OCO (or single leg). Expect roughly 10 requests plus one per canceled order, with additional polling while settlement is pending.

Three multipliers on top of the per-command numbers:

  • Position and size gates. require_market_position adds a contract lookup plus reconciled position/order/fill reads; max_positions also uses the reconciled open-position scan rather than one position request.
  • Multi-account lists. account=A,B,C replicates the alert into one full, independent execution per account, so three accounts cost three times the requests.
  • The Tradovate Copier. Every successful leader action is mirrored to each configured follower, which costs roughly the same requests again per follower (contract lookup, entry or cancel, brackets).

CrossTrade-side controls never cost Tradovate requests: alerts blocked by trade windows, the kill switch, rate_limit, monitors, or validation errors are rejected before any trading call. Authentication normally adds nothing because tokens are kept warm, but a request entering the renewal margin may renew inline before continuing.

Accepted, then rejected: the post-placement check

Tradovate's placement endpoints can answer "accepted" (HTTP 2xx with an order id) before the risk and execution layer has finished judging the order. A price outside the product's exchange price limits is the common case: the order is accepted, then rejected about a second later with InvalidPrice.

A successful webhook response therefore means "Tradovate accepted the order", not "the order is resting or filled". For ordinary placements that return a top-level order id, CrossTrade re-checks that order a few seconds after acceptance with one background GET /order/item (it never delays your webhook response). If the order was rejected broker-side, the Alert History row gains a warning carrying Tradovate's reject reason and moves to the yellow warning status. Native multibracket ATM strategies have their own strategy and child-order lifecycle instead of this single-order check.

Position freshness after a fill

Tradovate's position/find can keep reporting the pre-fill state for tens of seconds after a fill, while the fill stream updates immediately. Commands that act on the live position (require_market_position, partial closeposition, a bare reverse, cancelandbracket, and copier follower sizing) therefore compute the live net position from the fill stream: three lightweight reads (position/find, order/list, fill/list) instead of one. Chained alerts that follow an entry within seconds act on the true position. A flat reading is re-checked once (about a second) before being trusted, so commands on a genuinely flat instrument cost one extra second.

Rate limits on Tradovate, and how CrossTrade behaves

Tradovate does not publish fixed request quotas. Instead its API applies dynamic penalties: when you send too much too fast, requests start returning HTTP 429 with a penalty ticket (p-ticket) and a cooldown duration (p-time).

What CrossTrade does with that:

  • A rate-limited request fails the alert immediately, and Alert History shows a customer-safe Tradovate rate-limit message. Exact broker cooldown metadata remains in internal diagnostics.
  • Order-placing and order-cancelling requests are never auto-retried. A retry storm against a penalized session digs the hole deeper, and blindly resending an order that may have reached the broker risks a double fill. Read-only lookups are retried once on transient server errors.
  • Every error from Tradovate (rejections, penalties, outages) is recorded on the signal in Alert History with Tradovate's own reason text.

Practical guidance for staying clear of penalties:

  • Prefer single-purpose alerts over composites when firing frequently. A plain place is typically one broker mutation; a cancelandbracket on a busy instrument can require ten or more calls plus settlement polling.
  • Scope your sweeps. Both cancelallorders and cancelorders cost one request per working order (Tradovate has no bulk cancel endpoint), so prefer cancelorders narrowed to an account and instrument over a global sweep.
  • Multiply before you ship: 1 alert × 3 accounts × 2 copier followers is 9 executions' worth of requests arriving at Tradovate inside a second.
  • Use CrossTrade's own rate_limit + id fields to throttle a noisy TradingView strategy before it ever reaches Tradovate.
  • Send concrete contract symbols (ESU6) instead of ES1! in high-frequency alerts to skip the front-month lookup.
Fail-closed gates

If CrossTrade cannot read your live position while enforcing require_market_position or executing a reverse (for example during a Tradovate outage), the command fails with the error logged to Alert History. It never assumes "flat" and never places a fallback order on a failed read.

Authorization expiry

Tradovate sessions are kept alive automatically by a server-side renewal job, including while you are idle. If Tradovate revokes or evicts a session (password change, firm-side reset, or unlink), requests fail until you re-link. After repeated confirmation failures, CrossTrade emits the matching broker-session or broker-auth alert through your configured channels. Re-link from My Account, Brokers tab.