Files
Celes Renata 4ffde8cc06 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
2026-04-15 16:12:22 +00:00

133 lines
3.5 KiB
Python

"""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