Summary
Introduce PositionMode on PortfolioConfiguration with two values:
PositionMode.NETTING (default, current behaviour) — at most one position per symbol; opposite-side signals are rejected.
PositionMode.HEDGE — allow simultaneous long + short on the same symbol, tracked as separate Trade rows with their own entry price, TP/SL, cooldowns, and P&L.
Terminology mirrors Binance / Bybit / OKX position modes, so it will be familiar to users coming from those venues.
Motivation
Today the framework enforces one position per symbol (see #[atomic-flip-issue] for why this is a sensible default). But several legitimate use cases need true hedge mode:
- Pairs trading on a single venue where one leg is the same symbol on a different time horizon.
- Delta-neutral options overlays with a perp hedge.
- Calendar / basis spreads on perp + dated futures of the same underlying.
- Defensive hedges — partially short an existing long to lock in gains without closing the long (avoids tax events on spot, avoids losing carry on perps).
Without hedge mode, these strategies can't be expressed at all.
Proposed design
1. Configuration
from investing_algorithm_framework import PositionMode
PortfolioConfiguration(
initial_balance=1000,
market="BITVAVO",
trading_symbol="EUR",
position_mode=PositionMode.HEDGE, # default: NETTING
)
2. Domain model changes
Position: keep one row per (portfolio, symbol), but expose long_size / short_size separately (or split into PositionLeg child rows — TBD in design review).
Trade: side becomes load-bearing; multiple open trades per symbol allowed, one per side.
- Risk rules (
StopLossRule, TakeProfitRule, CooldownRule, TrailingStopRule): all currently key off symbol — must key off (symbol, side) in HEDGE mode.
3. Engine changes
- Vector engine: per-bar state changes from
has_position: bool to {long: bool, short: bool}; every branch revisited.
- Event engine:
BacktestTradeOrderEvaluator._apply_fill currently matches against "the" open trade — must match by side.
OrderService: order→trade matching becomes side-aware (the COVER-with-trades path needs particular care — it was just stabilised in v9.0).
4. Reporting
- Equity curve and drawdown remain single-valued (portfolio level).
- Trade breakdown reports must group / colour by side.
- Position exposure reports show net + gross exposure separately.
Acceptance criteria
Risks
- HIGH blast radius. Touches
Position, Trade, OrderService, both backtest engines, all risk rules, and reporting. Estimated 30+ files.
- Easy to introduce silent P&L bugs — every assumption about "one position per symbol" must be audited.
- Should be designed with a written RFC before any code changes.
Estimated scope
Multi-day. Probably 1-2 weeks including design review, tests, and docs.
Release target
v10.0.0 (SemVer MAJOR — changes invariants of Position / Trade, even though current code paths stay compatible in NETTING mode the semantics of those domain objects change enough to warrant a major).
Dependencies
- Should land after #[atomic-flip-issue] and #[surface-rejections-issue] which are non-breaking v9.1.0 work.
Non-goals (for first cut)
- Cross-venue hedging (e.g. spot long on Coinbase + perp short on Binance) — still treated as separate portfolios.
- Multi-leg order types (combo orders sent atomically to the exchange) — out of scope.
Summary
Introduce
PositionModeonPortfolioConfigurationwith two values:PositionMode.NETTING(default, current behaviour) — at most one position per symbol; opposite-side signals are rejected.PositionMode.HEDGE— allow simultaneous long + short on the same symbol, tracked as separateTraderows with their own entry price, TP/SL, cooldowns, and P&L.Terminology mirrors Binance / Bybit / OKX position modes, so it will be familiar to users coming from those venues.
Motivation
Today the framework enforces one position per symbol (see #[atomic-flip-issue] for why this is a sensible default). But several legitimate use cases need true hedge mode:
Without hedge mode, these strategies can't be expressed at all.
Proposed design
1. Configuration
2. Domain model changes
Position: keep one row per(portfolio, symbol), but exposelong_size/short_sizeseparately (or split intoPositionLegchild rows — TBD in design review).Trade:sidebecomes load-bearing; multiple open trades per symbol allowed, one per side.StopLossRule,TakeProfitRule,CooldownRule,TrailingStopRule): all currently key off symbol — must key off(symbol, side)in HEDGE mode.3. Engine changes
has_position: boolto{long: bool, short: bool}; every branch revisited.BacktestTradeOrderEvaluator._apply_fillcurrently matches against "the" open trade — must match by side.OrderService: order→trade matching becomes side-aware (the COVER-with-trades path needs particular care — it was just stabilised in v9.0).4. Reporting
Acceptance criteria
PositionModeenum exported from the public API.PortfolioConfigurationacceptsposition_modewithNETTINGdefault.NETTINGmode, behaviour is byte-identical to v9.x (full regression of the v9 test suite passes unchanged).HEDGEmode: long and short positions on the same symbol coexist with independent TP/SL/cooldowns and independentTraderows.(symbol, side)-aware.docs/design/+ user-facing guide.Risks
Position,Trade,OrderService, both backtest engines, all risk rules, and reporting. Estimated 30+ files.Estimated scope
Multi-day. Probably 1-2 weeks including design review, tests, and docs.
Release target
v10.0.0 (SemVer MAJOR — changes invariants of
Position/Trade, even though current code paths stay compatible inNETTINGmode the semantics of those domain objects change enough to warrant a major).Dependencies
Non-goals (for first cut)