Skip to content

[v9.0] feat: add short selling support to vector backtest engine #433

@MDUYN

Description

@MDUYN

Summary

Add short selling support to the vector backtest engine. This enables strategies to profit from price declines by selling an asset they don't own (opening a short) and buying it back later (covering).

The vector engine is the ideal starting point because it bypasses order validation and database sync — orders are created as in-memory domain objects marked immediately as CLOSED. This makes the implementation self-contained and low-risk.

Motivation

Many trading strategies rely on short selling (mean reversion, pairs trading, market-neutral, etc.). Currently the framework only supports long-only strategies. Adding shorts to the vector engine first covers the most common use case: backtesting short strategies at high speed.

Design

Signal API

Add two new signal methods to TradingStrategy, following the existing pattern:

class MyStrategy(TradingStrategy):
    def generate_short_signals(self, data) -> Dict[str, pd.Series]:
        """Return boolean Series indicating when to open a short position."""
        ...

    def generate_cover_signals(self, data) -> Dict[str, pd.Series]:
        """Return boolean Series indicating when to close a short position."""
        ...

Both default to None (no short trading), maintaining full backward compatibility.

Signal Priority (extended from current)

for each bar, for each symbol:
    ├─ has open orders? → SKIP
    ├─ in cooldown? → SKIP
    │
    ├─ SELL signal AND has long position → close long (existing)
    ├─ COVER signal AND has short position → close short (NEW)
    │
    ├─ no position:
    │   ├─ BUY signal → open long (existing)
    │   └─ SHORT signal → open short (NEW)
    │
    └─ has position → scale-in/scale-out logic (existing)

Short Order Mechanics

Opening a short (_open_short_trade):

  • Create a SELL Order with OrderSide.SELL and metadata {"is_short": true}
  • Amount = capital / fill_price (same sizing as long entries, respects PositionSize)
  • Fill price = TradingCost.get_sell_fill_price(price) (includes slippage)
  • Cash effect: unallocated += proceeds (you receive cash when selling short)
  • Track as negative position amount internally via last_trade

Covering a short (_close_short_trade):

  • Create a BUY Order with metadata {"is_cover": true}
  • Fill price = TradingCost.get_buy_fill_price(price)
  • Cash effect: unallocated -= cost (you pay cash to buy back)
  • P&L = (open_price - close_price) * amount (inverted from long)

Trade Model

  • Trade.cost = proceeds received when opening short
  • Trade.net_gain = (open_price - cover_price) * amount - fees
  • No new fields needed — use existing orders list on Trade (first order = short, second = cover)

Position Tracking

  • Use the existing last_trade dict in the vector loop
  • Add a is_short boolean flag per symbol: sym_data['is_short'] = True
  • Position.amount already accepts negative values

Files to Change

File Change
investing_algorithm_framework/app/strategy.py Add generate_short_signals() and generate_cover_signals() stub methods
investing_algorithm_framework/infrastructure/services/backtesting/vector_backtest_service.py Add SHORT/COVER branches to the main loop, _open_short_trade(), _close_short_trade()
investing_algorithm_framework/domain/models/trade/trade.py Ensure P&L calculation handles inverted short trades (or add direction field)

Scope & Non-Goals

In scope:

  • Short/cover signals in vector backtest
  • Correct P&L calculation for short trades
  • Slippage and fees via TradingCost
  • Position sizing via PositionSize
  • Cooldown support for short entries

Out of scope (separate issues):

Estimated Effort

~300-400 lines of code, 1-2 weeks. Low risk — changes are isolated to the vector loop with no database or validation impact.

Related Issues

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions