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
+315
View File
@@ -0,0 +1,315 @@
/**
* TanStack Query hooks for the Trading Engine API.
* All endpoints are proxied through /trading/ in nginx.
* Requirements: 14.4, 14.5, 14.6, 14.7, 15.6, 16.5, 17.4, 19.10, 20.9
*/
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { apiGet, apiPost, apiPut } from './client';
// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------
export interface TradingEngineStatus {
enabled: boolean;
paused: boolean;
risk_tier: string;
circuit_breaker_active: boolean;
circuit_breaker_trigger_type: string | null;
circuit_breaker_cooldown_expires: string | null;
active_pool: number;
reserve_pool: number;
portfolio_heat: number;
portfolio_value: number;
open_position_count: number;
last_decision_at: string | null;
micro_trading_enabled: boolean;
uptime_seconds: number | null;
}
export interface TradingDecision {
id: string;
recommendation_id: string | null;
decision: string;
skip_reason: string | null;
ticker: string;
computed_position_size: number | null;
computed_share_quantity: number | null;
risk_tier_at_decision: string;
portfolio_heat_at_decision: number | null;
active_pool_at_decision: number | null;
reserve_pool_at_decision: number | null;
circuit_breaker_status: string;
correlation_check_result: Record<string, unknown>;
sector_exposure_check_result: Record<string, unknown>;
earnings_proximity_flag: boolean;
is_micro_trade: boolean;
decision_trace: Record<string, unknown>;
created_at: string;
}
export interface TradingMetrics {
total_portfolio_value: number;
active_pool: number;
reserve_pool: number;
unrealized_pnl: number;
realized_pnl: number;
daily_pnl: number;
win_count: number;
loss_count: number;
win_rate: number;
avg_win: number;
avg_loss: number;
profit_factor: number;
sharpe_ratio: number;
max_drawdown: number;
current_drawdown_pct: number;
portfolio_heat: number;
computed_at: string;
}
export interface PortfolioSnapshot {
id: string;
snapshot_date: string;
portfolio_value: number;
active_pool: number;
reserve_pool: number;
daily_return: number | null;
cumulative_return: number | null;
unrealized_pnl: number | null;
realized_pnl: number | null;
win_count: number;
loss_count: number;
win_rate: number | null;
sharpe_ratio: number | null;
max_drawdown: number | null;
current_drawdown_pct: number | null;
portfolio_heat: number | null;
risk_tier: string | null;
positions: unknown[];
metrics: Record<string, unknown>;
created_at: string;
}
export interface TradingEngineConfig {
id: string;
enabled: boolean;
paused: boolean;
risk_tier: string;
reserve_siphon_pct: number;
polling_interval_seconds: number;
gradual_entry_tranches: number;
gradual_entry_threshold_dollars: number;
gradual_entry_interval_minutes: number;
trading_window_start_minutes: number;
trading_window_end_minutes: number;
max_open_positions: number;
circuit_breaker_daily_loss_pct: number;
circuit_breaker_single_position_loss_pct: number;
circuit_breaker_ticker_cooldown_hours: number;
circuit_breaker_volatility_pause_hours: number;
circuit_breaker_stop_loss_hits_threshold: number;
circuit_breaker_stop_loss_window_minutes: number;
active_pool_minimum: number;
emergency_drawdown_threshold_pct: number;
reserve_high_water_pct: number;
absolute_position_cap: number;
correlation_reduction_threshold: number;
correlation_rejection_threshold: number;
earnings_pre_window_days: number;
earnings_post_cooldown_days: number;
micro_trading_enabled: boolean;
micro_trading_interval_seconds: number;
micro_trading_allocation_cap_pct: number;
micro_trading_max_daily: number;
micro_trading_max_hold_minutes: number;
notification_sms_enabled: boolean;
notification_email_enabled: boolean;
notification_phone_number: string | null;
notification_email_recipient: string | null;
notification_sns_topic_arn: string | null;
notification_rate_limit_sms_per_hour: number;
notification_rate_limit_email_per_hour: number;
notification_daily_summary_time: string;
created_at: string;
updated_at: string;
}
export interface BacktestResult {
id: string;
start_date: string;
end_date: string;
initial_capital: number;
risk_tier: string;
config: Record<string, unknown>;
total_return: number | null;
sharpe_ratio: number | null;
max_drawdown: number | null;
win_rate: number | null;
profit_factor: number | null;
trade_count: number | null;
equity_curve: Array<{ date: string; portfolio_value: number }>;
status: string;
completed_at: string | null;
created_at: string;
}
export interface NotificationConfig {
sms_enabled: boolean;
email_enabled: boolean;
phone_number: string | null;
email_recipient: string | null;
sns_topic_arn: string | null;
rate_limit_sms_per_hour: number;
rate_limit_email_per_hour: number;
daily_summary_time: string;
}
export interface NotificationRecord {
id: string;
channel: string;
event_type: string;
message: string;
delivery_status: string;
retry_count: number;
error_message: string | null;
created_at: string;
delivered_at: string | null;
}
// ---------------------------------------------------------------------------
// Query hooks
// ---------------------------------------------------------------------------
/** Fetch current trading engine status (risk tier, circuit breaker, pools, etc.) */
export function useTradingStatus() {
return useQuery<TradingEngineStatus>({
queryKey: ['trading-status'],
queryFn: () => apiGet<TradingEngineStatus>('trading', '/api/trading/status'),
});
}
/** Fetch recent trading decisions with optional pagination and filters. */
export function useTradingDecisions(params?: {
ticker?: string;
decision?: string;
is_micro_trade?: boolean;
limit?: number;
offset?: number;
}) {
const qs = new URLSearchParams();
if (params?.ticker) qs.set('ticker', params.ticker);
if (params?.decision) qs.set('decision', params.decision);
if (params?.is_micro_trade !== undefined) qs.set('is_micro_trade', String(params.is_micro_trade));
if (params?.limit) qs.set('limit', String(params.limit));
if (params?.offset) qs.set('offset', String(params.offset));
const path = `/api/trading/decisions${qs.toString() ? '?' + qs : ''}`;
return useQuery<TradingDecision[]>({
queryKey: ['trading-decisions', params],
queryFn: () => apiGet<TradingDecision[]>('trading', path),
});
}
/** Fetch current performance metrics. */
export function useTradingMetrics() {
return useQuery<TradingMetrics>({
queryKey: ['trading-metrics'],
queryFn: () => apiGet<TradingMetrics>('trading', '/api/trading/metrics'),
});
}
/** Fetch historical daily portfolio snapshots. */
export function useTradingMetricsHistory() {
return useQuery<PortfolioSnapshot[]>({
queryKey: ['trading-metrics-history'],
queryFn: () => apiGet<PortfolioSnapshot[]>('trading', '/api/trading/metrics/history'),
});
}
/** Fetch trading engine configuration. */
export function useTradingConfig() {
return useQuery<TradingEngineConfig>({
queryKey: ['trading-config'],
queryFn: () => apiGet<TradingEngineConfig>('trading', '/api/trading/config'),
});
}
/** Fetch a backtest result by ID. */
export function useBacktestResult(id: string | undefined) {
return useQuery<BacktestResult>({
queryKey: ['backtest-result', id],
queryFn: () => apiGet<BacktestResult>('trading', `/api/trading/backtest/${id}`),
enabled: !!id,
});
}
/** Fetch notification preferences. */
export function useNotificationConfig() {
return useQuery<NotificationConfig>({
queryKey: ['notification-config'],
queryFn: () => apiGet<NotificationConfig>('trading', '/api/trading/notifications/config'),
});
}
/** Fetch notification history. */
export function useNotificationHistory() {
return useQuery<NotificationRecord[]>({
queryKey: ['notification-history'],
queryFn: () => apiGet<NotificationRecord[]>('trading', '/api/trading/notifications/history'),
});
}
// ---------------------------------------------------------------------------
// Mutation hooks
// ---------------------------------------------------------------------------
/** Update trading engine configuration. */
export function useUpdateTradingConfig() {
const qc = useQueryClient();
return useMutation({
mutationFn: (body: Partial<TradingEngineConfig>) =>
apiPut<TradingEngineConfig>('trading', '/api/trading/config', body),
onSuccess: () => {
qc.invalidateQueries({ queryKey: ['trading-config'] });
qc.invalidateQueries({ queryKey: ['trading-status'] });
},
});
}
/** Pause the trading engine. */
export function usePauseTradingEngine() {
const qc = useQueryClient();
return useMutation({
mutationFn: () => apiPost<unknown>('trading', '/api/trading/pause', {}),
onSuccess: () => qc.invalidateQueries({ queryKey: ['trading-status'] }),
});
}
/** Resume the trading engine. */
export function useResumeTradingEngine() {
const qc = useQueryClient();
return useMutation({
mutationFn: () => apiPost<unknown>('trading', '/api/trading/resume', {}),
onSuccess: () => qc.invalidateQueries({ queryKey: ['trading-status'] }),
});
}
/** Launch a new backtest run. */
export function useBacktestLaunch() {
const qc = useQueryClient();
return useMutation({
mutationFn: (body: { start_date: string; end_date: string; initial_capital: number; risk_tier: string }) =>
apiPost<BacktestResult>('trading', '/api/trading/backtest', body),
onSuccess: () => qc.invalidateQueries({ queryKey: ['backtest-result'] }),
});
}
/** Update notification preferences. */
export function useUpdateNotificationConfig() {
const qc = useQueryClient();
return useMutation({
mutationFn: (body: Partial<NotificationConfig>) =>
apiPut<NotificationConfig>('trading', '/api/trading/notifications/config', body),
onSuccess: () => qc.invalidateQueries({ queryKey: ['notification-config'] }),
});
}