Skip to content
Nyria Docs

Configure a Reversal-Race-Safe Strategy

Configure a long and short strategy that survives fast reversals without leaving naked orders, and read the reversal-race lines in a bot's history.

By the end you will have a long+short strategy configured so that a "go short" alert arriving while the long entry is still resting (or has just filled) does the right thing: cancel the resting entry, or close the just-filled position, without ever leaving two opposing orders alive on the broker.

This tutorial assumes you already have a strategy in draft and a paper-trading bot you can fire alerts at. If you don't, run through Strategies and Bots first.

What "reversal race" actually means

A reversal race is the window between placing an entry order and knowing whether that entry filled, during which an alert for the opposite direction arrives.

Three concrete examples:

  • You send a long entry. The broker queues it as a resting limit. Two seconds later your indicator flips and sends a short entry. The long has not filled yet.
  • You send a long entry. The broker fills it instantly. Your indicator sends a short entry on the same bar close.
  • You send a long entry. The broker rejects it (insufficient buying power). Your indicator sends a short entry a moment later.

All three are "reversal races." All three need different responses. The cancel-first state machine in the executor picks the right one without you writing any glue.

Reversal race is the name of the scenario, not a literal log keyword. Nyria's executor emits structured log keys like exit_broker_position_check, cancel_first_live, and exit_skipped_entry_rejected. The "Read the logs" section below shows you how to map those to what happened.

How the cancel-first state machine works

When an exit-or-reverse alert arrives, the executor walks a strict decision tree against this trade's own entry order, never against the broker's aggregate position for that symbol. (Aggregate position can include unrelated holdings opened by a different bot or left over from a prior session, which is how naive "is there a SPY position?" checks end up closing the wrong thing.)

The decision tree:

  1. All entries already dead? If every entry transaction for this trade is rejected, cancelled, or error, the exit is a no-op. Nyria logs exit_skipped_entry_rejected and stops.
  2. All entries known filled in our DB? Skip the cancel attempt. It would just be refused with "already filled." Fall through to a close sized from the fill quantity. Saves one broker round-trip per exit.
  3. Paper / simulator? The simulator always holds the entry position, so fall through to a normal close. Nothing to cancel.
  4. Live broker with at least one entry that might still be resting? CANCEL-FIRST. Try to cancel each entry order by its broker order id.
    • Resting entry cancels cleanly: nothing was ever opened, the reverse alert becomes a no-op. Done.
    • Filled entry refuses to cancel: re-check the position, fall through to a close sized from the actual fill quantity.

The decision is identical for every broker. There are no per-broker branches in the cancel/close path, only cancel_order and place_order calls.

Why your "go short" during an open long does the right thing

Set both directions' entry behavior to Enter Position:

  • Long Behavior → on a long entry signal → Enter Position
  • Short Behavior → on a short entry signal → Enter Position

With both sides opening positions, a SHORT entry alert tells the executor two things: open a short, and treat the alert as an exit for any open long. The cancel-first state machine then handles the long side:

  • Resting long entry order? Cancel it. The short opens cleanly against a flat account.
  • Filled long position? Close it (sized from the fill, not from broker aggregate). Then open the short.
  • Already cancelled/rejected long? Skip the cancel work, open the short.

You never have to write "if I have a long and a short arrives, do X" logic. The executor does.

This only works when both directions open positions. If your strategy is long-only (Short = Do Nothing), a short alert during an open long will not close the long.

Steps

Open your strategy's behavior settings

Open the strategy you want to make reversal-race-safe, choose Edit from its actions menu, and go to the What it trades step. The Long Behavior and Short Behavior boxes are there.

Set both directions to Enter Position

Under On a long entry signal, pick Enter Position. Do the same under On a short entry signal. Long and short alerts now each open a position in their own direction.

This is the key configuration. Because both sides open positions, an entry alert for the opposite direction of an open position runs the exit pipeline before opening the new direction, which is what triggers the cancel-first state machine described above.

If your alert source only sends one direction's alerts, set the other side to Exit Long Position (or Exit Short Position) instead. That side's alerts then only close, never open. That is a long-only or short-only strategy, not a reversal one.

Independent of the flip, you can also turn on Enable exit orders. Some alert sources emit both: a "long exit" when the indicator goes flat, plus a "short entry" when it flips. Enabling exits means flat signals also close positions cleanly, instead of waiting for the opposite-side entry.

Pick order types deliberately

For reversal trading, market orders on the exit side are the safest default. A limit-exit during a fast reversal can sit unfilled while the underlying keeps moving against you.

  • Entries: limit at bid/ask/mid is fine. The cancel-first machine handles cleanup if a reversal arrives before the limit fills.
  • Exits: prefer market unless you have a strong reason for limits.

Set this in the Order Type dropdowns for each direction's entry and exit.

Set Multiple Entries to Disabled

On the What it trades step, leave Allow Multiple Entries unchecked for a clean reversal pattern. With multiple entries disabled, the lifecycle is strictly entry → exit → entry, and the cancel-first state machine has exactly one entry order to reason about per direction.

You can enable pyramiding later once the basic reversal flow is validated end-to-end.

With Multiple Entries enabled, the executor still cancel-firsts every open entry transaction for the trade, but reading the logs is harder because there are more rows to correlate.

Validate the strategy with example alerts

Run the strategy through alert validation as you normally would. Provide one example for each enabled trade type. Validation simulates a full long-entry → short-entry (which exits the long) → short-exit lifecycle.

If validation fails on the exit-opposite leg, the most common cause is that the alert template you provided for the short entry doesn't include the ticker, so the parser can't pair it with the open long.

Test on paper first

Deploy a bot against this strategy on a paper-trading account (Nyria Paper, Alpaca paper, Tradier sandbox). Fire a long-entry alert, then within a few seconds fire a short-entry alert. Then check the bot's execution log.

You should see:

  • A long-entry order placed
  • The short-entry alert triggering an exit pipeline
  • Either a cancel of the long entry (if the long was still resting) or a close of the long fill (if it filled before the short arrived)
  • The short entry placed after the long is gone

Read the logs

Open the bot's execution log on the bot detail page. The structured keys to look for, in order, when a reversal race occurs:

Log keyMeaning
exit_open_transactions_foundThe executor located this trade's entry transactions to reason about.
exit_skipped_entry_rejectedEvery entry was dead (rejected/cancelled/error). No exit needed.
exit_broker_position_checkThe decision row. Look at the decision field: close_simulator, close_skip_cancel_known_filled, cancel_first_live, or no_entries.
exit_proceeding_with_positionA position exists (paper, or all entries confirmed filled in DB). Skipping cancel, going straight to close.
bot_broker_cancel_timingA broker cancel was attempted. The success field tells you whether the resting entry cancelled or refused.
bot_broker_place_timingA close order was placed (entry refused to cancel because it had filled).

The decision=cancel_first_live row is the one that confirms cancel-first is running on the reversal. If you see decision=close_skip_cancel_known_filled, the entry was already known filled in Nyria's DB and the executor skipped the wasted cancel round-trip.

Go live

Once paper trading shows the right log shape for a fast reversal, swap the bot's account from paper to live, fund it, and monitor the first few real reversals closely. The cancel-first machine behaves identically on live and paper, but live broker rejections (insufficient buying power, halted ticker, market closed for the leg) are only visible against a funded account.

Sample alerts

These are minimum-viable JSON payloads for a TradingView (or custom webhook) reversal source. The webhook URL is your strategy's token. See Webhook Payload Reference for the complete field list.

Long entry

{
  "ticker": "SPY",
  "action": "buy"
}

Short entry (also exits an open long)

{
  "ticker": "SPY",
  "action": "sell"
}

Explicit long exit (optional, if your source emits flat signals)

{
  "ticker": "SPY",
  "action": "exit_long"
}

Explicit short exit

{
  "ticker": "SPY",
  "action": "exit_short"
}

The two-field minimum (ticker + action) is enough. Nyria pulls direction, instrument, and position-sizing from your strategy and bot configuration.

TradingView Pine snippet

If you author your alerts from a Pine strategy, the simplest reversal-safe pattern is to call alert() once per direction change inside strategy.entry:

//@version=5
strategy("Reversal demo", overlay=true)

longCondition = ta.crossover(ta.sma(close, 9), ta.sma(close, 21))
shortCondition = ta.crossunder(ta.sma(close, 9), ta.sma(close, 21))

if longCondition
    strategy.entry("Long", strategy.long)
    alert('{"ticker":"' + syminfo.ticker + '","action":"buy"}', alert.freq_once_per_bar_close)

if shortCondition
    strategy.entry("Short", strategy.short)
    alert('{"ticker":"' + syminfo.ticker + '","action":"sell"}', alert.freq_once_per_bar_close)

alert.freq_once_per_bar_close is the safe default. It prevents the same bar from firing repeated alerts as price wiggles intrabar. If you need faster reactions, switch to alert.freq_once_per_bar, but expect more alerts and rely on the cancel-first state machine to keep you safe.

Do not use alert.freq_all for a reversal strategy. It will fire on every tick the condition is true, which floods Nyria's webhook with duplicate entries that the parser will dedupe, but at the cost of unnecessary log rows and broker calls.

Common questions

Will Nyria ever leave a naked opposite-direction order open during a reversal?

No. The cancel-first state machine either cancels the resting entry (and proceeds to the new direction with a flat account) or closes the just-filled position before opening the new direction. The two operations are sequential per trade, not parallel.

What happens if the broker is unreachable when the reverse alert arrives?

The cancel attempt logs a failure (bot_broker_cancel_timing with success=false) and the close attempt also fails. The trade stays in its existing state. The executor never leaves the bot in a half-flipped state silently. The bot log shows an error. Close the position from Close positions in the strategy's menu, or at your broker. See Close a Position.

Does this work with options spreads?

Yes, with one caveat. For multi-leg entries, the executor cancels the parent broker order id (which spans all legs) and treats the entry as one atomic unit. Partial-exit safety still applies: if a spread fill happened on some legs but not others, the close path refuses to leave naked legs.

Does this work on Schwab single-leg options?

Yes. The Schwab path uses the same cancel-first state machine. See the Schwab integration page for Schwab-specific notes on auth, PM-settled index roots (SPX/NDX/RUT/VIX), and order types Schwab supports.

Does this work on crypto?

Yes. For spot crypto the cancel-first logic is identical. Perpetual futures run on Nyria Paper only today, where the new direction's entry flips the position.

What this tutorial does not promise

Per Nyria's scope, the reversal-race-safe pattern documented here uses only:

  • Single-leg or multi-leg market/limit entries and exits
  • The cancel-first state machine on exits
  • Both directions on Enter Position, so an opposite-direction entry runs the exit pipeline first

It does not rely on bracket orders, OCO, OTO, attached-leg take-profits, or trailing stops. None of those are currently supported on Nyria. They are intentionally out of scope. Do not configure your TradingView template assuming a "bracket" or "TP attached to entry" field will be honored; it won't be.

  • Strategies overview: full strategy configuration reference.
  • Trading Behavior: Enter Position, Exit Opposite Position, Do Nothing.
  • Bots: deploying a bot against this strategy.
  • Webhook Payload Reference: the complete list of fields you can include in any alert payload, plus how Nyria infers from your strategy when a field is omitted.
  • Schwab integration: Schwab-specific notes (auth, PM-settled roots, supported order types).
  • Alpaca integration: Alpaca paper-trading account setup for testing this tutorial end-to-end.