feat: autonomous trading engine — full implementation
- Database migration 018 with 13 tables for trading engine state - Trading engine service (services/trading/) with 12 pure computation modules: position sizer, stop-loss manager, reserve pool, circuit breaker, risk tier controller, correlation matrix, tax lots, trading window, gradual entry, notifications, micro-trading, backtester - Core TradingEngine with pre-trade evaluation pipeline and integration wiring - FastAPI HTTP service with 14 endpoints (health, config, decisions, metrics, backtest) - Performance tracker with Sharpe ratio, drawdown, profit factor computation - 194 Python tests (165 property-based + 29 integration) - Frontend: 13 TanStack Query hooks, 7 dashboard panels, tabbed Trading Engine page - Helm chart entry, network policy, nginx proxy, ingress for trading-engine - Shared infrastructure: enums, Redis keys, TradingConfig in AppConfig
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
"""Tax lot tracking for cost basis and wash sale detection.
|
||||
|
||||
Feature: autonomous-trading-engine
|
||||
|
||||
Pure computation module for FIFO tax lot closing and wash sale
|
||||
detection within the 30-day window. Used by the Trading Engine
|
||||
for tax-loss harvesting awareness.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from datetime import date, timedelta
|
||||
|
||||
|
||||
@dataclass
|
||||
class TaxLot:
|
||||
"""A single tax lot representing a purchase of shares."""
|
||||
|
||||
ticker: str
|
||||
quantity: int
|
||||
cost_basis_per_share: float
|
||||
acquisition_date: date
|
||||
status: str = "open" # open | closed | washed
|
||||
|
||||
|
||||
@dataclass
|
||||
class ClosedLot:
|
||||
"""Result of closing a tax lot via FIFO."""
|
||||
|
||||
ticker: str
|
||||
quantity: int
|
||||
cost_basis_per_share: float
|
||||
exit_price: float
|
||||
realized_pnl: float
|
||||
acquisition_date: date
|
||||
closed_date: date
|
||||
|
||||
|
||||
class TaxLotTracker:
|
||||
"""Pure computation for FIFO lot closing and wash sale detection."""
|
||||
|
||||
def close_lots_fifo(
|
||||
self,
|
||||
lots: list[TaxLot],
|
||||
quantity: int,
|
||||
exit_price: float,
|
||||
exit_date: date,
|
||||
) -> list[ClosedLot]:
|
||||
"""Close lots in FIFO order (earliest acquired first).
|
||||
|
||||
Processes open lots sorted by acquisition_date ascending,
|
||||
closing shares until the requested quantity is fulfilled.
|
||||
Returns a list of ClosedLot records with realized P&L.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
lots:
|
||||
All tax lots for the ticker (open ones will be selected).
|
||||
quantity:
|
||||
Number of shares to close.
|
||||
exit_price:
|
||||
Price per share at exit.
|
||||
exit_date:
|
||||
Date the lots are being closed.
|
||||
|
||||
Returns
|
||||
-------
|
||||
list[ClosedLot]
|
||||
Closed lot records in FIFO order.
|
||||
"""
|
||||
open_lots = sorted(
|
||||
[lot for lot in lots if lot.status == "open"],
|
||||
key=lambda lot: lot.acquisition_date,
|
||||
)
|
||||
|
||||
closed: list[ClosedLot] = []
|
||||
remaining = quantity
|
||||
|
||||
for lot in open_lots:
|
||||
if remaining <= 0:
|
||||
break
|
||||
|
||||
close_qty = min(lot.quantity, remaining)
|
||||
realized_pnl = (exit_price - lot.cost_basis_per_share) * close_qty
|
||||
|
||||
closed.append(
|
||||
ClosedLot(
|
||||
ticker=lot.ticker,
|
||||
quantity=close_qty,
|
||||
cost_basis_per_share=lot.cost_basis_per_share,
|
||||
exit_price=exit_price,
|
||||
realized_pnl=realized_pnl,
|
||||
acquisition_date=lot.acquisition_date,
|
||||
closed_date=exit_date,
|
||||
)
|
||||
)
|
||||
|
||||
remaining -= close_qty
|
||||
|
||||
return closed
|
||||
|
||||
def check_wash_sale(
|
||||
self,
|
||||
loss_date: date,
|
||||
purchases: list[TaxLot],
|
||||
) -> bool:
|
||||
"""Check whether any purchase falls within the 30-day wash sale window.
|
||||
|
||||
A wash sale occurs when the same ticker is purchased within
|
||||
30 days before or after a loss-closing date.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
loss_date:
|
||||
The date the loss was realized.
|
||||
purchases:
|
||||
Tax lots representing purchases of the same ticker.
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
True if any purchase is within the 30-day window.
|
||||
"""
|
||||
window_start = loss_date - timedelta(days=30)
|
||||
window_end = loss_date + timedelta(days=30)
|
||||
|
||||
for lot in purchases:
|
||||
if window_start <= lot.acquisition_date <= window_end:
|
||||
return True
|
||||
|
||||
return False
|
||||
Reference in New Issue
Block a user