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:
Celes Renata
2026-04-15 16:12:22 +00:00
parent da86132f0c
commit 4ffde8cc06
58 changed files with 14168 additions and 1 deletions
+67
View File
@@ -0,0 +1,67 @@
"""Correlation matrix operations for portfolio diversification.
Feature: autonomous-trading-engine
Pure computation module for price correlation coefficients between
tracked companies. Used by the Position Sizer to prevent
over-concentration in correlated positions.
"""
from __future__ import annotations
from services.trading.models import OpenPosition
class CorrelationMatrix:
"""In-memory correlation matrix for ticker pairs.
Stores pairwise correlation coefficients and provides lookup
methods for individual pairs and weighted portfolio averages.
"""
def __init__(self) -> None:
self._data: dict[tuple[str, str], float] = {}
def load(self, data: dict[tuple[str, str], float]) -> None:
"""Load correlation data from a dict of (ticker_a, ticker_b) -> coefficient."""
self._data = dict(data)
def get_correlation(self, ticker_a: str, ticker_b: str) -> float:
"""Return the correlation coefficient for a ticker pair.
Checks both orderings (a, b) and (b, a). Returns 0.0 if the
pair is not in the matrix.
"""
if ticker_a == ticker_b:
return 1.0
return self._data.get(
(ticker_a, ticker_b),
self._data.get((ticker_b, ticker_a), 0.0),
)
def get_portfolio_correlation(
self,
candidate: str,
positions: list[OpenPosition],
) -> float:
"""Compute weighted average correlation between candidate and positions.
Weights are based on each position's market_value. Returns 0.0
if there are no positions or total weight is zero.
"""
if not positions:
return 0.0
total_weight = 0.0
weighted_corr = 0.0
for pos in positions:
corr = self.get_correlation(candidate, pos.ticker)
weight = pos.market_value
weighted_corr += corr * weight
total_weight += weight
if total_weight == 0.0:
return 0.0
return weighted_corr / total_weight