Skip to content

PositionMode.HEDGE: allow simultaneous long+short on same symbol #563

@MDUYN

Description

@MDUYN

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

  • PositionMode enum exported from the public API.
  • PortfolioConfiguration accepts position_mode with NETTING default.
  • In NETTING mode, behaviour is byte-identical to v9.x (full regression of the v9 test suite passes unchanged).
  • In HEDGE mode: long and short positions on the same symbol coexist with independent TP/SL/cooldowns and independent Trade rows.
  • All risk rules updated to be (symbol, side)-aware.
  • New deterministic scenario tests for HEDGE mode on both engines covering: simultaneous open, independent TP, independent SL, independent cooldowns, P&L attribution per side.
  • Documentation: design doc under docs/design/ + user-facing guide.

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions