import { useState } from 'react'; import { Link } from '@tanstack/react-router'; import { useTradingConfig, useSetTradingMode, usePendingApprovals, useReviewApproval, useActiveLockouts, useCreateLockout, useDeleteLockout, useMacroStatus, useToggleMacro, useCompetitiveStatus, useToggleCompetitive, useApprovalConfig, useUpdateApprovalConfig, } from '../api/hooks'; import { useResetPaperTrading, useTradingStatus, useUpdateTradingConfig } from '../api/tradingHooks'; import { StatusBadge, LoadingSpinner, Card } from '../components/ui'; export function TradingPage() { const { data: config, isLoading: configLoading } = useTradingConfig(); const { data: approvals } = usePendingApprovals(); const { data: lockouts } = useActiveLockouts(); const { data: macroStatus } = useMacroStatus(); const { data: competitiveStatus } = useCompetitiveStatus(); const setMode = useSetTradingMode(); const resetTrading = useResetPaperTrading(); const { data: tradingStatus } = useTradingStatus(); const updateConfig = useUpdateTradingConfig(); const reviewApproval = useReviewApproval(); const toggleMacro = useToggleMacro(); const toggleCompetitive = useToggleCompetitive(); const { data: approvalConfig } = useApprovalConfig(); const updateApprovalConfig = useUpdateApprovalConfig(); const createLockout = useCreateLockout(); const deleteLockout = useDeleteLockout(); const [lockoutTicker, setLockoutTicker] = useState(''); const [lockoutReason, setLockoutReason] = useState(''); const [lockoutDuration, setLockoutDuration] = useState(60); // Normalize API field names (API returns macro_enabled/competitive_enabled, not enabled) const macroEnabled = macroStatus?.enabled ?? macroStatus?.macro_enabled ?? false; const competitiveEnabled = competitiveStatus?.enabled ?? competitiveStatus?.competitive_enabled ?? false; const autoApprovePaper = approvalConfig?.auto_approve_paper ?? true; const [confirmMode, setConfirmMode] = useState(null); const [confirmMacroToggle, setConfirmMacroToggle] = useState(false); const [confirmCompetitiveToggle, setConfirmCompetitiveToggle] = useState(false); const [confirmApprovalToggle, setConfirmApprovalToggle] = useState(false); if (configLoading) return ; const currentMode = config?.trading_mode ?? 'paper'; return (

Trading Controls

{/* Trading Mode */}

Trading Mode

{['paper', 'live', 'disabled'].map((mode) => ( ))} Override Trade
{/* Confirmation dialog for live mode */} {confirmMode && (

Are you sure you want to switch to {confirmMode} mode? This enables real order execution.

)}
{/* Paper Trading Reset */} resetTrading.mutate(params)} isResetting={resetTrading.isPending} /> {/* Risk Tier */}

Risk Tier

Controls confidence gates, position sizing, and portfolio heat limits for the trading engine.

{(['conservative', 'moderate', 'aggressive'] as const).map((tier) => { const currentTier = tradingStatus?.risk_tier ?? 'moderate'; const descriptions: Record = { conservative: 'Min confidence 0.75, max 5% position, 10% heat', moderate: 'Min confidence 0.55, max 10% position, 20% heat', aggressive: 'Min confidence 0.40, max 15% position, 30% heat', }; return ( ); })}
{/* Macro Signal Layer Toggle */}

Macro Signal Layer

{macroEnabled ? 'Enabled' : 'Disabled'} {macroStatus?.toggled_at && ( Last changed: {new Date(macroStatus.toggled_at).toLocaleString()} {macroStatus.toggled_by && ` by ${macroStatus.toggled_by}`} )}
{/* Confirmation dialog for macro toggle */} {confirmMacroToggle && (

Are you sure you want to {macroEnabled ? 'disable' : 'enable'} the macro signal layer? {macroEnabled ? ' Disabling will exclude macro signals from trend summaries and recommendations.' : ' Enabling will include global event macro signals in trend summaries and recommendations.'}

)}
{/* Competitive Signal Layer Toggle */}

Competitive Signal Layer

{competitiveEnabled ? 'Enabled' : 'Disabled'} {competitiveStatus?.toggled_at && ( Last changed: {new Date(competitiveStatus.toggled_at).toLocaleString()} {competitiveStatus.toggled_by && ` by ${competitiveStatus.toggled_by}`} )}
{/* Confirmation dialog for competitive toggle */} {confirmCompetitiveToggle && (

Are you sure you want to {competitiveEnabled ? 'disable' : 'enable'} the competitive signal layer? {competitiveEnabled ? ' Disabling will exclude historical pattern and competitive signals from trend summaries and recommendations.' : ' Enabling will include historical pattern and competitive signals in trend summaries and recommendations.'}

)}
{/* Paper Order Approval Toggle */}

Paper Order Approval

{autoApprovePaper ? 'Auto-approve paper orders' : 'Require approval for paper orders'}
{/* Confirmation dialog for approval toggle */} {confirmApprovalToggle && (

Are you sure you want to {autoApprovePaper ? 'require approval for' : 'auto-approve'} paper orders? {autoApprovePaper ? ' Enabling approval requirements will hold all paper orders for manual review before execution.' : ' Disabling approval requirements will allow paper orders to execute automatically.'}

)}
{/* Pending Approvals */}

Pending Approvals ({approvals?.length ?? 0})

{!approvals?.length ? (

No pending approvals

) : (
{approvals.map((a) => ( reviewApproval.mutate({ id: a.id, approved, review_note: note })} /> ))}
)}
{/* Active Lockouts */}

Active Lockouts ({lockouts?.length ?? 0})

{/* Lockout Creation Form */}
{ e.preventDefault(); if (!lockoutTicker.trim()) return; createLockout.mutate( { ticker: lockoutTicker.trim().toUpperCase(), reason: lockoutReason.trim(), duration_minutes: lockoutDuration }, { onSuccess: () => { setLockoutTicker(''); setLockoutReason(''); setLockoutDuration(60); }, }, ); }} >
setLockoutTicker(e.target.value.toUpperCase())} className="w-24 rounded-md border border-surface-700 bg-surface-950 px-2 py-1 text-xs font-mono text-gray-200 uppercase placeholder-gray-600" required />
setLockoutReason(e.target.value)} className="rounded-md border border-surface-700 bg-surface-950 px-2 py-1 text-xs text-gray-200 placeholder-gray-600" required />
setLockoutDuration(Number(e.target.value))} className="w-20 rounded-md border border-surface-700 bg-surface-950 px-2 py-1 text-xs text-gray-200" required />
{!lockouts?.length ? (

No active lockouts

) : (
{lockouts.map((l) => { const expiresIn = l.expires_at ? Math.max(0, Math.round((new Date(l.expires_at).getTime() - Date.now()) / 60000)) : 0; return (
{l.ticker} {l.reason}
{expiresIn > 0 ? `${expiresIn}m remaining` : 'expired'}
); })}
)}
); } function ApprovalRow({ approval, onReview }: { approval: { id: string; ticker: string; side: string; quantity: number; estimated_value: number | null; requested_at: string }; onReview: (approved: boolean, note: string) => void; }) { const [note, setNote] = useState(''); return (
{approval.ticker} qty: {approval.quantity} {approval.estimated_value != null && ( ${approval.estimated_value.toFixed(2)} )}
{new Date(approval.requested_at).toLocaleString()}
setNote(e.target.value)} className="flex-1 rounded-md border border-surface-700 bg-surface-900 px-2 py-1 text-xs text-gray-200 placeholder-gray-500" aria-label="Review note" />
); } function ResetCard({ onReset, isResetting }: { onReset: (params: { initial_capital?: number; reserve_pct?: number }) => void; isResetting: boolean; }) { const [showConfirm, setShowConfirm] = useState(false); const [capitalInput, setCapitalInput] = useState('100000'); const [reservePct, setReservePct] = useState(20); const [useCustomCapital, setUseCustomCapital] = useState(false); const capital = parseFloat(capitalInput) || 0; const reserveAmount = capital * (reservePct / 100); const activeAmount = capital - reserveAmount; return (

Paper Trading Account

{/* Capital & Reserve Configuration */}
Set initial capital (otherwise uses broker account balance)
{useCustomCapital && (
setCapitalInput(e.target.value)} className="w-36 rounded-md border border-surface-700 bg-surface-900 px-2 py-1 text-sm font-mono text-gray-200" />
)}
Active: {100 - reservePct}%
setReservePct(Number(e.target.value))} className="mt-1 w-full accent-brand-600" /> {useCustomCapital && capital > 0 && (
Reserve: ${reserveAmount.toLocaleString(undefined, { maximumFractionDigits: 0 })} Active: ${activeAmount.toLocaleString(undefined, { maximumFractionDigits: 0 })}
)}
{/* Reset Button */}

Full Reset

Liquidates all broker positions, cancels open orders, wipes local trading history, and sets capital from {useCustomCapital ? 'the amount above' : 'the broker account balance'}.

Note: To reset the Alpaca paper account balance itself, use the Alpaca dashboard.

{showConfirm && (

This will permanently delete all positions, orders, trading decisions, stop levels, portfolio snapshots, and backtest data. All broker positions will be liquidated. {useCustomCapital ? ` Capital will be set to $${capital.toLocaleString()} (${reservePct}% reserve / ${100 - reservePct}% active).` : ` Capital will be set from the broker's account balance (${reservePct}% reserve / ${100 - reservePct}% active).`}

)}
); }