Summary
Add an opt-in flip_on_opposite_signal: bool = False flag on TradingStrategy so that when an OPEN_SHORT signal fires against an existing long position (or BUY against an existing short), the framework closes the current position and opens the opposite-side position atomically on the same bar.
Motivation
Today the framework enforces "one position per symbol" by silently rejecting opposite-side signals with signal_event.reason="already_in_position" / "open_short_position". For trend-following strategies that rely on clean trend flips (e.g. SuperTrend, MA crossover), this means:
- The exit and re-entry happen on separate bars (or never, if a cooldown is configured).
- Strategies miss the move during the gap.
- The behaviour is silent — users only discover it by reading
signal_events or visualising the chart.
This is the #1 friction point reported for short-enabled strategies.
Proposed design
- New field on
TradingStrategy: flip_on_opposite_signal: bool = False (default keeps current behaviour).
- When True:
- Vector engine (vector_backtest_service.py) — in the two rejection branches at ~lines 1140 / 1277, instead of emitting a rejection signal_event, close the existing position at the current bar's price and open the opposite-side position on the same bar.
- Event engine — add a
resolve_conflicts phase hook that rewrites opposite-side signals into [CLOSE, OPEN] order pairs.
- Cooldown bookkeeping must skip the synthetic close so the new opposite-side open isn't blocked by its own flip.
- Position sizing for the opposite side uses post-close cash (not pre-close).
Acceptance criteria
Non-goals
- Does not allow simultaneous long+short on the same symbol (that's hedge mode — see #[hedge-mode-issue]).
- Does not change
Position / Trade data model.
Estimated scope
~half day. Opt-in, no breaking changes.
Release target
v9.1.0 (SemVer MINOR — additive, backward-compatible).
Summary
Add an opt-in
flip_on_opposite_signal: bool = Falseflag onTradingStrategyso that when anOPEN_SHORTsignal fires against an existing long position (orBUYagainst an existing short), the framework closes the current position and opens the opposite-side position atomically on the same bar.Motivation
Today the framework enforces "one position per symbol" by silently rejecting opposite-side signals with
signal_event.reason="already_in_position"/"open_short_position". For trend-following strategies that rely on clean trend flips (e.g. SuperTrend, MA crossover), this means:signal_eventsor visualising the chart.This is the #1 friction point reported for short-enabled strategies.
Proposed design
TradingStrategy:flip_on_opposite_signal: bool = False(default keeps current behaviour).resolve_conflictsphase hook that rewrites opposite-side signals into[CLOSE, OPEN]order pairs.Acceptance criteria
flip_on_opposite_signalfield exists onTradingStrategy, defaults toFalse.SELL+ oneOPEN_SHORTorder on the same bar.COVER+ oneBUYorder on the same bar.tests/scenarios/test_deterministic_pnl_scenarios.pycovering long→short flip and short→long flip on both engines (4 tests total).Non-goals
Position/Tradedata model.Estimated scope
~half day. Opt-in, no breaking changes.
Release target
v9.1.0 (SemVer MINOR — additive, backward-compatible).