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
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:Both default to
None(no short trading), maintaining full backward compatibility.Signal Priority (extended from current)
Short Order Mechanics
Opening a short (
_open_short_trade):OrderwithOrderSide.SELLand metadata{"is_short": true}PositionSize)TradingCost.get_sell_fill_price(price)(includes slippage)unallocated += proceeds(you receive cash when selling short)last_tradeCovering a short (
_close_short_trade):Orderwith metadata{"is_cover": true}TradingCost.get_buy_fill_price(price)unallocated -= cost(you pay cash to buy back)(open_price - close_price) * amount(inverted from long)Trade Model
Trade.cost= proceeds received when opening shortTrade.net_gain=(open_price - cover_price) * amount - feesorderslist on Trade (first order = short, second = cover)Position Tracking
last_tradedict in the vector loopis_shortboolean flag per symbol:sym_data['is_short'] = TruePosition.amountalready accepts negative valuesFiles to Change
investing_algorithm_framework/app/strategy.pygenerate_short_signals()andgenerate_cover_signals()stub methodsinvesting_algorithm_framework/infrastructure/services/backtesting/vector_backtest_service.py_open_short_trade(),_close_short_trade()investing_algorithm_framework/domain/models/trade/trade.pydirectionfield)Scope & Non-Goals
In scope:
TradingCostPositionSizeOut 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