/** * 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; max_open_positions: number; absolute_position_cap: 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; sector_exposure_check_result: Record; earnings_proximity_flag: boolean; is_micro_trade: boolean; decision_trace: Record; 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; 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; 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({ queryKey: ['trading-status'], queryFn: () => apiGet('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({ queryKey: ['trading-decisions', params], queryFn: () => apiGet('trading', path), }); } /** Fetch current performance metrics. */ export function useTradingMetrics() { return useQuery({ queryKey: ['trading-metrics'], queryFn: () => apiGet('trading', '/api/trading/metrics'), }); } /** Fetch historical daily portfolio snapshots. */ export function useTradingMetricsHistory() { return useQuery({ queryKey: ['trading-metrics-history'], queryFn: () => apiGet('trading', '/api/trading/metrics/history'), }); } /** Fetch trading engine configuration. */ export function useTradingConfig() { return useQuery({ queryKey: ['trading-config'], queryFn: () => apiGet('trading', '/api/trading/config'), }); } /** Fetch a backtest result by ID. Polls every 2s while status is pending/running. */ export function useBacktestResult(id: string | undefined) { return useQuery({ queryKey: ['backtest-result', id], queryFn: () => apiGet('trading', `/api/trading/backtest/${id}`), enabled: !!id, refetchInterval: (query) => { const status = query.state.data?.status; if (!status || status === 'running' || status === 'pending') return 2000; return false; }, }); } /** Fetch notification preferences. */ export function useNotificationConfig() { return useQuery({ queryKey: ['notification-config'], queryFn: () => apiGet('trading', '/api/trading/notifications/config'), }); } /** Fetch notification history. */ export function useNotificationHistory() { return useQuery({ queryKey: ['notification-history'], queryFn: () => apiGet('trading', '/api/trading/notifications/history'), }); } // --------------------------------------------------------------------------- // Mutation hooks // --------------------------------------------------------------------------- /** Update trading engine configuration. */ export function useUpdateTradingConfig() { const qc = useQueryClient(); return useMutation({ mutationFn: (body: Partial) => apiPut('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('trading', '/api/trading/pause', {}), onSuccess: () => qc.invalidateQueries({ queryKey: ['trading-status'] }), }); } /** Resume the trading engine. */ export function useResumeTradingEngine() { const qc = useQueryClient(); return useMutation({ mutationFn: () => apiPost('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('trading', '/api/trading/backtest', body), onSuccess: () => qc.invalidateQueries({ queryKey: ['backtest-result'] }), }); } /** Full paper trading reset: liquidates broker positions, cancels orders, * clears all local trading state, and syncs capital from the broker. */ export function useResetPaperTrading() { const qc = useQueryClient(); return useMutation({ mutationFn: (params: { initial_capital?: number; reserve_pct?: number } = {}) => apiPost<{ reset: boolean; initial_capital: number; active_pool: number; reserve_pool: number; broker: Record }>( 'trading', '/api/trading/reset', { initial_capital: params.initial_capital ?? 0, reserve_pct: params.reserve_pct ?? undefined, }, ), onSuccess: () => { qc.invalidateQueries({ queryKey: ['trading-status'] }); qc.invalidateQueries({ queryKey: ['trading-decisions'] }); qc.invalidateQueries({ queryKey: ['trading-metrics'] }); qc.invalidateQueries({ queryKey: ['trading-metrics-history'] }); }, }); } /** Update notification preferences. */ export function useUpdateNotificationConfig() { const qc = useQueryClient(); return useMutation({ mutationFn: (body: Partial) => apiPut('trading', '/api/trading/notifications/config', body), onSuccess: () => qc.invalidateQueries({ queryKey: ['notification-config'] }), }); } // --------------------------------------------------------------------------- // Override Order (manual trade submission) // --------------------------------------------------------------------------- export interface OverrideOrderRequest { ticker: string; side: 'buy' | 'sell'; quantity: number; order_type: 'market' | 'limit' | 'stop' | 'stop_limit'; limit_price?: number; stop_price?: number; } export interface OverrideOrderResponse { job_id: string; status: string; ticker: string; side: string; quantity: number; auto_registered: boolean; } /** Submit a manual override order to the trading engine. */ export function useSubmitOverrideOrder() { const qc = useQueryClient(); return useMutation({ mutationFn: (body: OverrideOrderRequest) => apiPost('trading', '/api/trading/override/order', body), onSuccess: () => { qc.invalidateQueries({ queryKey: ['orders'] }); qc.invalidateQueries({ queryKey: ['positions'] }); qc.invalidateQueries({ queryKey: ['companies'] }); }, }); }