5209cc522e
ci/woodpecker/push/test Pipeline was successful
ci/woodpecker/push/build-1 Pipeline was successful
ci/woodpecker/push/build-2 Pipeline was successful
ci/woodpecker/push/build-3 Pipeline was successful
ci/woodpecker/push/finalize Pipeline was successful
Build and Push / lint-and-test (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.adapters.broker_adapter name:broker-adapter]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.aggregation.worker name:aggregation]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.extractor.worker name:extractor]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.ingestion.worker name:ingestion]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.lake_publisher.worker name:lake-publisher]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.parser.worker name:parser]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.recommendation.worker name:recommendation]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.scheduler.app name:scheduler]) (push) Has been cancelled
Build and Push / build-services (map[cmd:uvicorn services.api.app:app --host 0.0.0.0 --port 8000 name:query-api]) (push) Has been cancelled
Build and Push / build-services (map[cmd:uvicorn services.risk.app:app --host 0.0.0.0 --port 8000 name:risk]) (push) Has been cancelled
Build and Push / build-services (map[cmd:uvicorn services.symbol_registry.app:app --host 0.0.0.0 --port 8000 name:symbol-registry]) (push) Has been cancelled
Build and Push / build-services (map[cmd:uvicorn services.trading.app:app --host 0.0.0.0 --port 8000 name:trading-engine]) (push) Has been cancelled
Build and Push / build-dashboard (push) Has been cancelled
Build and Push / build-superset (push) Has been cancelled
Build and Push / integration-test (push) Has been cancelled
Build and Push / beta-gate (push) Has been cancelled
- Add initial capital input (toggle between broker balance or custom amount) - Add reserve/active pool split slider (0-50%, default 20%) - Backend accepts reserve_pct in reset request body - Note in UI that Alpaca balance reset requires Alpaca dashboard - Confirmation dialog shows exact capital and split being applied
380 lines
12 KiB
TypeScript
380 lines
12 KiB
TypeScript
/**
|
|
* 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<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. Polls every 2s while status is pending/running. */
|
|
export function useBacktestResult(id: string | undefined) {
|
|
return useQuery<BacktestResult>({
|
|
queryKey: ['backtest-result', id],
|
|
queryFn: () => apiGet<BacktestResult>('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<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'] }),
|
|
});
|
|
}
|
|
|
|
/** 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<string, number> }>(
|
|
'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<NotificationConfig>) =>
|
|
apiPut<NotificationConfig>('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<OverrideOrderResponse>('trading', '/api/trading/override/order', body),
|
|
onSuccess: () => {
|
|
qc.invalidateQueries({ queryKey: ['orders'] });
|
|
qc.invalidateQueries({ queryKey: ['positions'] });
|
|
qc.invalidateQueries({ queryKey: ['companies'] });
|
|
},
|
|
});
|
|
}
|