feat: Add Trading Agents UI components (GAP-P0-003)

- Add tradingAgents.types.ts with complete type definitions
- Add agents.service.ts for Trading Agents API integration
- Add agentsStore.ts (Zustand) for state management
- Add BotCard.tsx, AgentCard.tsx, AgentsList.tsx components
- Add AgentsPage.tsx for /trading/agents route
- Fix TypeScript errors in AgentsList.tsx:
  - Add handlers for startBot with default config
  - Use correct AgentType for selectBot

Total: 8 new files, ~3,000 LOC

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Adrian Flores Cortes 2026-01-28 16:47:41 -06:00
parent 0f20468381
commit 5779c9a5cf
11 changed files with 2014 additions and 0 deletions

View File

@ -25,6 +25,7 @@ const SecuritySettings = lazy(() => import('./modules/auth/pages/SecuritySetting
// Lazy load modules - Core // Lazy load modules - Core
const Dashboard = lazy(() => import('./modules/dashboard/pages/Dashboard')); const Dashboard = lazy(() => import('./modules/dashboard/pages/Dashboard'));
const Trading = lazy(() => import('./modules/trading/pages/Trading')); const Trading = lazy(() => import('./modules/trading/pages/Trading'));
const TradingAgents = lazy(() => import('./modules/trading/pages/AgentsPage'));
const MLDashboard = lazy(() => import('./modules/ml/pages/MLDashboard')); const MLDashboard = lazy(() => import('./modules/ml/pages/MLDashboard'));
const BacktestingDashboard = lazy(() => import('./modules/backtesting/pages/BacktestingDashboard')); const BacktestingDashboard = lazy(() => import('./modules/backtesting/pages/BacktestingDashboard'));
const Investment = lazy(() => import('./modules/investment/pages/Investment')); const Investment = lazy(() => import('./modules/investment/pages/Investment'));
@ -95,6 +96,7 @@ function App() {
{/* Trading */} {/* Trading */}
<Route path="/trading" element={<Trading />} /> <Route path="/trading" element={<Trading />} />
<Route path="/trading/agents" element={<TradingAgents />} />
<Route path="/ml-dashboard" element={<MLDashboard />} /> <Route path="/ml-dashboard" element={<MLDashboard />} />
<Route path="/backtesting" element={<BacktestingDashboard />} /> <Route path="/backtesting" element={<BacktestingDashboard />} />
<Route path="/investment" element={<Investment />} /> <Route path="/investment" element={<Investment />} />

View File

@ -0,0 +1,178 @@
/**
* AgentCard Component
* Displays individual agent performance statistics and control buttons
*/
import {
UserGroupIcon,
PlayIcon,
PauseIcon,
StopIcon,
ArrowTrendingUpIcon,
ArrowTrendingDownIcon,
} from '@heroicons/react/24/solid';
import type { AgentPerformance } from '../../../../services/adminService';
interface AgentCardProps {
agent: AgentPerformance;
onStatusChange?: (agentId: string, newStatus: 'active' | 'paused' | 'stopped') => void;
}
export function AgentCard({ agent, onStatusChange }: AgentCardProps) {
const getStatusColor = (status: string) => {
switch (status) {
case 'active':
return 'bg-green-900/50 text-green-400 border-green-700';
case 'paused':
return 'bg-yellow-900/50 text-yellow-400 border-yellow-700';
default:
return 'bg-red-900/50 text-red-400 border-red-700';
}
};
const getAgentColor = (name: string) => {
const colors: Record<string, string> = {
Atlas: 'bg-blue-600',
Orion: 'bg-purple-600',
Nova: 'bg-orange-600',
};
return colors[name] || 'bg-gray-600';
};
const getRiskBadge = (name: string) => {
const risks: Record<string, { label: string; color: string }> = {
Atlas: { label: 'Conservative', color: 'text-green-400' },
Orion: { label: 'Moderate', color: 'text-yellow-400' },
Nova: { label: 'Aggressive', color: 'text-red-400' },
};
return risks[name] || { label: 'Unknown', color: 'text-gray-400' };
};
const formatCurrency = (value: number) => {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 2,
}).format(value);
};
const formatDate = (dateString: string) => {
if (!dateString) return 'N/A';
return new Date(dateString).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
});
};
const riskBadge = getRiskBadge(agent.name);
return (
<div className="bg-gray-900 rounded-lg p-4 border border-gray-700 hover:border-gray-600 transition-colors">
{/* Header */}
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-3">
<div className={`p-2 rounded-lg ${getAgentColor(agent.name)}`}>
<UserGroupIcon className="w-5 h-5 text-white" />
</div>
<div>
<h3 className="text-white font-bold">{agent.name}</h3>
<span className={`text-xs ${riskBadge.color}`}>{riskBadge.label}</span>
</div>
</div>
<div className={`px-2.5 py-1 rounded-full text-xs font-semibold border ${getStatusColor(agent.status)}`}>
{agent.status.charAt(0).toUpperCase() + agent.status.slice(1)}
</div>
</div>
{/* Description */}
{agent.description && (
<p className="text-gray-400 text-xs mb-4 line-clamp-2">{agent.description}</p>
)}
{/* Main Stats */}
<div className="grid grid-cols-2 gap-3 mb-4">
<div className="bg-gray-800 rounded-lg p-3">
<span className="text-gray-500 text-xs">Win Rate</span>
<p className="text-xl font-bold text-green-400">
{((agent.win_rate || 0) * 100).toFixed(1)}%
</p>
</div>
<div className="bg-gray-800 rounded-lg p-3">
<span className="text-gray-500 text-xs">Total P&L</span>
<div className="flex items-center gap-1">
{(agent.total_pnl || 0) >= 0 ? (
<ArrowTrendingUpIcon className="w-4 h-4 text-green-400" />
) : (
<ArrowTrendingDownIcon className="w-4 h-4 text-red-400" />
)}
<p className={`text-xl font-bold ${(agent.total_pnl || 0) >= 0 ? 'text-green-400' : 'text-red-400'}`}>
{formatCurrency(agent.total_pnl || 0)}
</p>
</div>
</div>
</div>
{/* Secondary Stats */}
<div className="grid grid-cols-3 gap-2 mb-4">
<div className="text-center">
<span className="text-gray-500 text-xs">Trades</span>
<p className="text-white font-semibold">{agent.total_trades || 0}</p>
</div>
<div className="text-center">
<span className="text-gray-500 text-xs">Signals</span>
<p className="text-white font-semibold">{agent.total_signals || 0}</p>
</div>
<div className="text-center">
<span className="text-gray-500 text-xs">Avg Profit</span>
<p className={`font-semibold ${(agent.avg_profit_per_trade || 0) >= 0 ? 'text-green-400' : 'text-red-400'}`}>
{formatCurrency(agent.avg_profit_per_trade || 0)}
</p>
</div>
</div>
{/* Footer */}
<div className="flex items-center justify-between text-xs text-gray-500 mb-3 border-t border-gray-700 pt-3">
<span>Confidence: {((agent.avg_confidence || 0) * 100).toFixed(0)}%</span>
<span>Last signal: {formatDate(agent.last_signal_at)}</span>
</div>
{/* Control Buttons */}
{onStatusChange && (
<div className="flex gap-2">
{agent.status !== 'active' && (
<button
onClick={() => onStatusChange(agent.agent_id, 'active')}
className="flex-1 flex items-center justify-center gap-1 py-2 bg-green-700 hover:bg-green-600 text-white rounded text-xs font-semibold transition-colors"
title="Start agent"
>
<PlayIcon className="w-3 h-3" />
Start
</button>
)}
{agent.status === 'active' && (
<button
onClick={() => onStatusChange(agent.agent_id, 'paused')}
className="flex-1 flex items-center justify-center gap-1 py-2 bg-yellow-700 hover:bg-yellow-600 text-white rounded text-xs font-semibold transition-colors"
title="Pause agent"
>
<PauseIcon className="w-3 h-3" />
Pause
</button>
)}
{agent.status !== 'stopped' && (
<button
onClick={() => onStatusChange(agent.agent_id, 'stopped')}
className="flex-1 flex items-center justify-center gap-1 py-2 bg-red-700 hover:bg-red-600 text-white rounded text-xs font-semibold transition-colors"
title="Stop agent"
>
<StopIcon className="w-3 h-3" />
Stop
</button>
)}
</div>
)}
</div>
);
}

View File

@ -0,0 +1,200 @@
/**
* AgentsList Component
* Displays a grid of trading bots (Atlas, Orion, Nova) with status filters
*/
import { useEffect, useState, useCallback } from 'react';
import { useAgentsStore } from '../../../../stores/agentsStore';
import { BotCard } from './BotCard';
import { Loader2, RefreshCw, AlertCircle, Bot } from 'lucide-react';
import type { BotStatus, TradingBot, TradingBotConfig } from '../../../../types/tradingAgents.types';
interface AgentsListProps {
statusFilter?: BotStatus;
}
export function AgentsList({ statusFilter }: AgentsListProps) {
const { bots, isLoading, error, fetchBots, selectBot, startBot, stopBot } = useAgentsStore();
const [filterStatus, setFilterStatus] = useState<BotStatus | 'all'>('all');
useEffect(() => {
fetchBots();
}, [fetchBots]);
// Handler for starting a bot with default config
const handleStartBot = useCallback(async (bot: TradingBot) => {
const defaultConfig: TradingBotConfig = {
initialEquity: 10000,
riskPerTrade: 2.0,
maxPositions: 3,
};
await startBot(bot.name, defaultConfig);
}, [startBot]);
// Handler for stopping a bot
const handleStopBot = useCallback(async (bot: TradingBot) => {
await stopBot(bot.name);
}, [stopBot]);
// Apply status filter
const filteredBots = filterStatus === 'all'
? bots
: bots.filter((bot) => bot.status === filterStatus);
// Loading skeleton
if (isLoading) {
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<h2 className="text-2xl font-bold text-white">Trading Bots</h2>
<button
disabled
className="flex items-center gap-2 px-4 py-2 bg-gray-700 text-gray-400 rounded-lg cursor-not-allowed"
>
<Loader2 className="w-4 h-4 animate-spin" />
<span>Loading...</span>
</button>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{[1, 2, 3].map((i) => (
<div key={i} className="bg-gray-800 rounded-xl p-6 border border-gray-700 animate-pulse">
<div className="h-6 bg-gray-700 rounded w-1/2 mb-4"></div>
<div className="h-4 bg-gray-700 rounded w-3/4 mb-6"></div>
<div className="space-y-3">
<div className="h-4 bg-gray-700 rounded w-full"></div>
<div className="h-4 bg-gray-700 rounded w-full"></div>
<div className="h-4 bg-gray-700 rounded w-2/3"></div>
</div>
</div>
))}
</div>
</div>
);
}
// Error state
if (error) {
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<h2 className="text-2xl font-bold text-white">Trading Bots</h2>
<button
onClick={fetchBots}
className="flex items-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors"
>
<RefreshCw className="w-4 h-4" />
<span>Retry</span>
</button>
</div>
<div className="bg-red-500/10 border border-red-500/30 rounded-xl p-6">
<div className="flex items-start gap-3">
<AlertCircle className="w-5 h-5 text-red-500 flex-shrink-0 mt-0.5" />
<div>
<h3 className="text-lg font-semibold text-red-500 mb-1">Error Loading Bots</h3>
<p className="text-red-400 text-sm">{error}</p>
<button
onClick={fetchBots}
className="mt-4 px-4 py-2 bg-red-600 hover:bg-red-700 text-white rounded-lg transition-colors text-sm"
>
Try Again
</button>
</div>
</div>
</div>
</div>
);
}
// Empty state
if (filteredBots.length === 0) {
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<h2 className="text-2xl font-bold text-white">Trading Bots</h2>
<button
onClick={fetchBots}
className="flex items-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors"
>
<RefreshCw className="w-4 h-4" />
<span>Refresh</span>
</button>
</div>
<div className="bg-gray-800 rounded-xl p-12 text-center border border-gray-700">
<div className="max-w-md mx-auto">
<div className="w-16 h-16 bg-gray-700 rounded-full flex items-center justify-center mx-auto mb-4">
<Bot className="w-8 h-8 text-gray-500" />
</div>
<h3 className="text-xl font-semibold text-white mb-2">No Bots Available</h3>
<p className="text-gray-400 mb-6">
{filterStatus !== 'all'
? `No bots with status "${filterStatus}" found.`
: 'Start configuring your trading bots to begin automated trading.'}
</p>
<button
onClick={fetchBots}
className="px-6 py-3 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors"
>
Refresh List
</button>
</div>
</div>
</div>
);
}
// Main render
return (
<div className="space-y-6">
{/* Header */}
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4">
<div>
<h2 className="text-2xl font-bold text-white">Trading Bots</h2>
<p className="text-gray-400 text-sm mt-1">
{filteredBots.length} {filteredBots.length === 1 ? 'bot' : 'bots'} available
</p>
</div>
<div className="flex items-center gap-3">
{/* Status Filter */}
<select
value={filterStatus}
onChange={(e) => setFilterStatus(e.target.value as BotStatus | 'all')}
className="px-4 py-2 bg-gray-700 text-white border border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value="all">All Status</option>
<option value="running">Running</option>
<option value="paused">Paused</option>
<option value="stopped">Stopped</option>
<option value="idle">Idle</option>
<option value="error">Error</option>
</select>
{/* Refresh Button */}
<button
onClick={fetchBots}
disabled={isLoading}
className="flex items-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 disabled:bg-gray-700 disabled:cursor-not-allowed text-white rounded-lg transition-colors"
>
<RefreshCw className={`w-4 h-4 ${isLoading ? 'animate-spin' : ''}`} />
<span>Refresh</span>
</button>
</div>
</div>
{/* Bots Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{filteredBots.map((bot) => (
<BotCard
key={bot.id}
bot={bot}
onSelect={() => selectBot(bot.name)}
onStart={() => handleStartBot(bot)}
onStop={() => handleStopBot(bot)}
/>
))}
</div>
</div>
);
}

View File

@ -0,0 +1,276 @@
/**
* BotCard Component
* Displays trading bot information including status, metrics, and control actions
* Used to show individual bot cards in the trading agents dashboard
*/
import React from 'react';
import {
Shield,
Target,
Rocket,
TrendingUp,
TrendingDown,
Play,
Pause,
Square,
Eye,
} from 'lucide-react';
import type { TradingBot, AgentType } from '../../../../types/tradingAgents.types';
/**
* Props for BotCard component
*/
interface BotCardProps {
/** Trading bot data to display */
bot: TradingBot;
/** Callback when user clicks to view bot details */
onSelect?: (bot: TradingBot) => void;
/** Callback when user starts the bot */
onStart?: (botId: string) => void;
/** Callback when user stops the bot */
onStop?: (botId: string) => void;
/** Whether actions are currently loading */
loading?: boolean;
}
/**
* BotCard Component
* Displays a trading bot with its current status, metrics, and action buttons
*/
export const BotCard: React.FC<BotCardProps> = ({
bot,
onSelect,
onStart,
onStop,
loading = false,
}) => {
/**
* Get icon component based on agent type
*/
const getAgentIcon = (type: AgentType) => {
const iconProps = { className: 'w-5 h-5 text-white' };
switch (type) {
case 'atlas':
return <Shield {...iconProps} />;
case 'orion':
return <Target {...iconProps} />;
case 'nova':
return <Rocket {...iconProps} />;
default:
return <Target {...iconProps} />;
}
};
/**
* Get background color for agent icon based on type
*/
const getAgentColor = (type: AgentType): string => {
switch (type) {
case 'atlas':
return 'bg-blue-600';
case 'orion':
return 'bg-purple-600';
case 'nova':
return 'bg-orange-600';
default:
return 'bg-gray-600';
}
};
/**
* Get status badge styling based on bot status
*/
const getStatusStyle = (status: string) => {
switch (status) {
case 'running':
return 'bg-green-900/50 text-green-400 border-green-700';
case 'stopped':
return 'bg-gray-900/50 text-gray-400 border-gray-700';
case 'error':
return 'bg-red-900/50 text-red-400 border-red-700';
case 'paused':
return 'bg-yellow-900/50 text-yellow-400 border-yellow-700';
default:
return 'bg-gray-900/50 text-gray-400 border-gray-700';
}
};
/**
* Get risk level text and color based on agent type
*/
const getRiskBadge = (type: AgentType) => {
switch (type) {
case 'atlas':
return { label: 'Conservative', color: 'text-green-400' };
case 'orion':
return { label: 'Moderate', color: 'text-yellow-400' };
case 'nova':
return { label: 'Aggressive', color: 'text-red-400' };
default:
return { label: 'Unknown', color: 'text-gray-400' };
}
};
/**
* Format currency values
*/
const formatCurrency = (value: number): string => {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}).format(value);
};
/**
* Format percentage values
*/
const formatPercentage = (value: number): string => {
return `${value.toFixed(1)}%`;
};
const riskBadge = getRiskBadge(bot.name);
const isProfitable = bot.todayPnl >= 0;
const winRate = bot.metrics?.win_rate ?? 0;
return (
<div className="bg-gray-900 rounded-lg p-4 border border-gray-700 hover:border-gray-600 transition-colors">
{/* Header Section */}
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-3">
{/* Agent Icon */}
<div className={`p-2 rounded-lg ${getAgentColor(bot.name)}`}>
{getAgentIcon(bot.name)}
</div>
{/* Agent Name and Risk Level */}
<div>
<h3 className="text-white font-bold">{bot.displayName}</h3>
<span className={`text-xs ${riskBadge.color}`}>{riskBadge.label}</span>
</div>
</div>
{/* Status Badge */}
<div
className={`px-2.5 py-1 rounded-full text-xs font-semibold border ${getStatusStyle(bot.status)}`}
>
{bot.status.charAt(0).toUpperCase() + bot.status.slice(1)}
</div>
</div>
{/* Description */}
{bot.description && (
<p className="text-gray-400 text-xs mb-4 line-clamp-2">{bot.description}</p>
)}
{/* Main Metrics Grid */}
<div className="grid grid-cols-2 gap-3 mb-4">
{/* P&L Card */}
<div className="bg-gray-800 rounded-lg p-3">
<span className="text-gray-500 text-xs block mb-1">Today's P&L</span>
<div className="flex items-center gap-1">
{isProfitable ? (
<TrendingUp className="w-4 h-4 text-green-400" />
) : (
<TrendingDown className="w-4 h-4 text-red-400" />
)}
<p
className={`text-xl font-bold ${isProfitable ? 'text-green-400' : 'text-red-400'}`}
>
{formatCurrency(bot.todayPnl)}
</p>
</div>
</div>
{/* Win Rate Card */}
<div className="bg-gray-800 rounded-lg p-3">
<span className="text-gray-500 text-xs block mb-1">Win Rate</span>
<p className="text-xl font-bold text-blue-400">{formatPercentage(winRate)}</p>
</div>
</div>
{/* Secondary Stats */}
<div className="grid grid-cols-3 gap-2 mb-4">
{/* Positions */}
<div className="text-center">
<span className="text-gray-500 text-xs block mb-1">Positions</span>
<p className="text-white font-semibold">{bot.positions}</p>
</div>
{/* Equity */}
<div className="text-center">
<span className="text-gray-500 text-xs block mb-1">Equity</span>
<p className="text-white font-semibold text-sm">{formatCurrency(bot.equity)}</p>
</div>
{/* Total Trades */}
<div className="text-center">
<span className="text-gray-500 text-xs block mb-1">Trades</span>
<p className="text-white font-semibold">{bot.metrics?.total_trades ?? 0}</p>
</div>
</div>
{/* Action Buttons */}
<div className="flex gap-2 pt-3 border-t border-gray-700">
{/* View Details Button */}
{onSelect && (
<button
onClick={() => onSelect(bot)}
className="flex-1 flex items-center justify-center gap-1.5 py-2 bg-gray-700 hover:bg-gray-600 text-white rounded text-xs font-semibold transition-colors"
title="View bot details"
>
<Eye className="w-3.5 h-3.5" />
Details
</button>
)}
{/* Start/Stop Button */}
{bot.status === 'running' || bot.status === 'paused' ? (
onStop && (
<button
onClick={() => onStop(bot.id)}
disabled={loading}
className="flex-1 flex items-center justify-center gap-1.5 py-2 bg-red-700 hover:bg-red-600 text-white rounded text-xs font-semibold transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
title="Stop bot"
>
<Square className="w-3.5 h-3.5" />
Stop
</button>
)
) : (
onStart && (
<button
onClick={() => onStart(bot.id)}
disabled={loading}
className="flex-1 flex items-center justify-center gap-1.5 py-2 bg-green-700 hover:bg-green-600 text-white rounded text-xs font-semibold transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
title="Start bot"
>
<Play className="w-3.5 h-3.5" />
Start
</button>
)
)}
{/* Pause Button (only when running) */}
{bot.status === 'running' && onStop && (
<button
onClick={() => onStop(bot.id)}
disabled={loading}
className="flex items-center justify-center gap-1.5 px-3 py-2 bg-yellow-700 hover:bg-yellow-600 text-white rounded text-xs font-semibold transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
title="Pause bot"
>
<Pause className="w-3.5 h-3.5" />
</button>
)}
</div>
</div>
);
};
export default BotCard;

View File

@ -0,0 +1,7 @@
/**
* Export all agent-related components
*/
export { AgentsList } from './AgentsList';
export { AgentCard } from './AgentCard';
export { BotCard } from './BotCard';

View File

@ -66,3 +66,7 @@ export { default as PositionModifierDialog } from './PositionModifierDialog';
export { default as RiskBasedPositionSizer } from './RiskBasedPositionSizer'; export { default as RiskBasedPositionSizer } from './RiskBasedPositionSizer';
export { default as TradeAlertsNotificationCenter } from './TradeAlertsNotificationCenter'; export { default as TradeAlertsNotificationCenter } from './TradeAlertsNotificationCenter';
export type { TradeAlert, AlertType, AlertPriority } from './TradeAlertsNotificationCenter'; export type { TradeAlert, AlertType, AlertPriority } from './TradeAlertsNotificationCenter';
// Trading Agents Components (OQI-007)
export { AgentsList } from './agents/AgentsList';
export { BotCard } from './agents/BotCard';

View File

@ -0,0 +1,23 @@
/**
* Trading Agents Page
* Manage and monitor trading agents within the trading module
*/
import { AgentsList } from '../components/agents/AgentsList';
export default function AgentsPage() {
return (
<div className="space-y-6">
{/* Header */}
<div>
<h1 className="text-2xl font-bold text-white">Trading Agents</h1>
<p className="text-gray-400 mt-1">
Gestiona tus bots de trading automatizado: Atlas, Orion y Nova
</p>
</div>
{/* Main Content */}
<AgentsList />
</div>
);
}

View File

@ -0,0 +1,242 @@
/**
* Trading Agents Service
* Client for connecting to the Trading Agents API (Atlas, Orion, Nova)
*/
import type {
AgentType,
AgentStatusResponse,
AgentMetrics,
AgentPosition,
AgentTrade,
TradingBotConfig,
SignalInput,
TradingAgentsHealthResponse,
AgentSummary,
CloseAllPositionsResponse,
SignalResponse,
BroadcastSignalResponse,
} from '../types/tradingAgents.types';
const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:3080';
// ============================================================================
// Helper Function
// ============================================================================
/**
* Generic fetch wrapper with error handling
*/
async function fetchAPI<T>(
url: string,
options?: RequestInit
): Promise<T> {
const response = await fetch(url, {
credentials: 'include',
headers: {
'Content-Type': 'application/json',
...options?.headers,
},
...options,
});
if (!response.ok) {
const error = await response.json().catch(() => ({
error: { message: 'Failed to fetch data', code: 'FETCH_ERROR' },
}));
throw new Error(error.error?.message || 'Request failed');
}
const data = await response.json();
return data.data || data;
}
// ============================================================================
// Health & Status
// ============================================================================
/**
* Get Trading Agents service health
*/
export async function getAgentsHealth(): Promise<TradingAgentsHealthResponse> {
return fetchAPI<TradingAgentsHealthResponse>(`${API_URL}/api/v1/agents/health`);
}
/**
* Check if Trading Agents service is available
*/
export async function checkAgentsConnection(): Promise<{ connected: boolean }> {
return fetchAPI<{ connected: boolean }>(`${API_URL}/api/v1/agents/connection`);
}
/**
* Get summary of all trading agents
*/
export async function getAllAgentsSummary(): Promise<AgentSummary[]> {
return fetchAPI<AgentSummary[]>(`${API_URL}/api/v1/agents/summary`);
}
// ============================================================================
// Agent Lifecycle
// ============================================================================
/**
* Start a trading agent
*/
export async function startAgent(
agentType: AgentType,
config: TradingBotConfig
): Promise<AgentStatusResponse> {
return fetchAPI<AgentStatusResponse>(
`${API_URL}/api/v1/agents/${agentType}/start`,
{
method: 'POST',
body: JSON.stringify(config),
}
);
}
/**
* Stop a trading agent
*/
export async function stopAgent(agentType: AgentType): Promise<AgentStatusResponse> {
return fetchAPI<AgentStatusResponse>(
`${API_URL}/api/v1/agents/${agentType}/stop`,
{
method: 'POST',
}
);
}
/**
* Pause a trading agent
*/
export async function pauseAgent(agentType: AgentType): Promise<AgentStatusResponse> {
return fetchAPI<AgentStatusResponse>(
`${API_URL}/api/v1/agents/${agentType}/pause`,
{
method: 'POST',
}
);
}
/**
* Resume a paused trading agent
*/
export async function resumeAgent(agentType: AgentType): Promise<AgentStatusResponse> {
return fetchAPI<AgentStatusResponse>(
`${API_URL}/api/v1/agents/${agentType}/resume`,
{
method: 'POST',
}
);
}
// ============================================================================
// Agent Status & Metrics
// ============================================================================
/**
* Get current status of a specific agent
*/
export async function getAgentStatus(agentType: AgentType): Promise<AgentStatusResponse> {
return fetchAPI<AgentStatusResponse>(`${API_URL}/api/v1/agents/${agentType}/status`);
}
/**
* Get performance metrics of a specific agent
*/
export async function getAgentMetrics(agentType: AgentType): Promise<AgentMetrics> {
return fetchAPI<AgentMetrics>(`${API_URL}/api/v1/agents/${agentType}/metrics`);
}
// ============================================================================
// Positions & Trades
// ============================================================================
/**
* Get open positions for a specific agent
*/
export async function getAgentPositions(agentType: AgentType): Promise<AgentPosition[]> {
return fetchAPI<AgentPosition[]>(`${API_URL}/api/v1/agents/${agentType}/positions`);
}
/**
* Get trade history for a specific agent
*/
export async function getAgentTrades(
agentType: AgentType,
options?: { limit?: number; offset?: number; symbol?: string }
): Promise<AgentTrade[]> {
const params = new URLSearchParams();
if (options?.limit) params.append('limit', options.limit.toString());
if (options?.offset) params.append('offset', options.offset.toString());
if (options?.symbol) params.append('symbol', options.symbol);
const url = `${API_URL}/api/v1/agents/${agentType}/trades${params.toString() ? `?${params.toString()}` : ''}`;
return fetchAPI<AgentTrade[]>(url);
}
/**
* Close a specific position
*/
export async function closePosition(
agentType: AgentType,
positionId: string
): Promise<AgentTrade> {
return fetchAPI<AgentTrade>(
`${API_URL}/api/v1/agents/${agentType}/positions/${positionId}/close`,
{
method: 'POST',
}
);
}
/**
* Close all positions for a specific agent
*/
export async function closeAllPositions(
agentType: AgentType
): Promise<CloseAllPositionsResponse> {
return fetchAPI<CloseAllPositionsResponse>(
`${API_URL}/api/v1/agents/${agentType}/positions/close-all`,
{
method: 'POST',
}
);
}
// ============================================================================
// Signals
// ============================================================================
/**
* Send a trading signal to a specific agent
*/
export async function sendSignal(
agentType: AgentType,
signal: SignalInput
): Promise<SignalResponse> {
return fetchAPI<SignalResponse>(
`${API_URL}/api/v1/agents/${agentType}/signal`,
{
method: 'POST',
body: JSON.stringify(signal),
}
);
}
/**
* Broadcast a signal to all running agents
*/
export async function broadcastSignal(
signal: SignalInput
): Promise<BroadcastSignalResponse> {
return fetchAPI<BroadcastSignalResponse>(
`${API_URL}/api/v1/signals/broadcast`,
{
method: 'POST',
body: JSON.stringify(signal),
}
);
}

497
src/stores/agentsStore.ts Normal file
View File

@ -0,0 +1,497 @@
/**
* Trading Agents Store
* Zustand store for managing Trading Agents (Atlas, Orion, Nova)
*/
import { create } from 'zustand';
import { devtools } from 'zustand/middleware';
import type {
AgentType,
TradingBot,
BotStatus,
TradingBotConfig,
AgentPosition,
AgentTrade,
SignalInput,
AgentStatusResponse,
AgentMetrics,
} from '../types/tradingAgents.types';
import {
getAllAgentsSummary,
getAgentStatus,
getAgentMetrics,
startAgent,
stopAgent,
pauseAgent,
resumeAgent,
getAgentPositions,
getAgentTrades,
closePosition,
closeAllPositions,
sendSignal,
getAgentsHealth,
} from '../services/agents.service';
import {
getAgentDisplayName,
getAgentDescription,
statusToDisplayStatus,
} from '../types/tradingAgents.types';
// ============================================================================
// State Interface
// ============================================================================
interface AgentsState {
// Bots data
bots: TradingBot[];
selectedBot: TradingBot | null;
// Positions & Trades
positions: AgentPosition[];
trades: AgentTrade[];
// Loading states
isLoading: boolean;
loadingPositions: boolean;
loadingTrades: boolean;
loadingMetrics: boolean;
// Error state
error: string | null;
// Service status
serviceAvailable: boolean;
// Actions - Bot Management
fetchBots: () => Promise<void>;
selectBot: (botName: AgentType) => void;
refreshBot: (botName: AgentType) => Promise<void>;
startBot: (botName: AgentType, config: TradingBotConfig) => Promise<void>;
stopBot: (botName: AgentType) => Promise<void>;
pauseBot: (botName: AgentType) => Promise<void>;
resumeBot: (botName: AgentType) => Promise<void>;
// Actions - Metrics
updateBotMetrics: (botName: AgentType, metrics: Partial<AgentMetrics>) => void;
fetchBotMetrics: (botName: AgentType) => Promise<void>;
// Actions - Positions & Trades
fetchPositions: (botName: AgentType) => Promise<void>;
fetchTrades: (botName: AgentType, options?: { limit?: number; offset?: number; symbol?: string }) => Promise<void>;
closePosition: (botName: AgentType, positionId: string) => Promise<void>;
closeAllPositions: (botName: AgentType) => Promise<void>;
// Actions - Signals
sendSignal: (botName: AgentType, signal: SignalInput) => Promise<void>;
// Utility actions
clearError: () => void;
reset: () => void;
}
// ============================================================================
// Initial State
// ============================================================================
const AGENT_TYPES: AgentType[] = ['atlas', 'orion', 'nova'];
const initialBots: TradingBot[] = AGENT_TYPES.map((name) => ({
id: name,
name,
displayName: getAgentDisplayName(name),
description: getAgentDescription(name),
status: 'stopped' as BotStatus,
equity: 0,
positions: 0,
todayPnl: 0,
metrics: undefined,
}));
const initialState = {
bots: initialBots,
selectedBot: null,
positions: [],
trades: [],
isLoading: false,
loadingPositions: false,
loadingTrades: false,
loadingMetrics: false,
error: null,
serviceAvailable: false,
};
// ============================================================================
// Helper Functions
// ============================================================================
/**
* Convert backend status response to frontend TradingBot format
*/
function mapStatusToBotData(status: AgentStatusResponse, existingBot?: TradingBot): TradingBot {
return {
id: status.agent_name,
name: status.agent_name,
displayName: existingBot?.displayName || getAgentDisplayName(status.agent_name),
description: existingBot?.description || getAgentDescription(status.agent_name),
status: statusToDisplayStatus(status.status),
equity: status.equity,
positions: status.positions,
todayPnl: status.today_pnl,
metrics: existingBot?.metrics,
};
}
/**
* Convert backend metrics response to frontend AgentMetrics format
*/
function mapMetricsResponse(metrics: AgentMetrics): AgentMetrics {
return {
total_trades: metrics.total_trades,
winning_trades: metrics.winning_trades,
losing_trades: metrics.losing_trades,
win_rate: metrics.win_rate,
total_profit: metrics.total_profit,
total_loss: metrics.total_loss,
net_pnl: metrics.net_pnl,
max_drawdown: metrics.max_drawdown,
current_drawdown: metrics.current_drawdown,
sharpe_ratio: metrics.sharpe_ratio,
sortino_ratio: metrics.sortino_ratio,
profit_factor: metrics.profit_factor,
};
}
// ============================================================================
// Store
// ============================================================================
export const useAgentsStore = create<AgentsState>()(
devtools(
(set, get) => ({
...initialState,
// ======================================================================
// Bot Management Actions
// ======================================================================
fetchBots: async () => {
set({ isLoading: true, error: null });
try {
// Check service health first
const health = await getAgentsHealth();
const serviceAvailable = health.status === 'healthy';
if (!serviceAvailable) {
set({
serviceAvailable: false,
isLoading: false,
bots: initialBots,
error: 'Trading Agents service is not available',
});
return;
}
// Fetch all agents summary
const summaries = await getAllAgentsSummary();
// Map summaries to bots
const bots = AGENT_TYPES.map((agentType) => {
const summary = summaries.find((s) => s.name === agentType);
const existingBot = get().bots.find((b) => b.name === agentType);
if (!summary) {
return existingBot || initialBots.find((b) => b.name === agentType)!;
}
return {
id: agentType,
name: agentType,
displayName: getAgentDisplayName(agentType),
description: getAgentDescription(agentType),
status: statusToDisplayStatus(summary.status),
equity: summary.equity,
positions: summary.positions,
todayPnl: summary.todayPnl,
metrics: existingBot?.metrics,
};
});
set({
bots,
serviceAvailable: true,
isLoading: false,
});
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to fetch bots';
set({
error: message,
isLoading: false,
serviceAvailable: false,
});
console.error('Error fetching bots:', error);
}
},
selectBot: (botName: AgentType) => {
const bot = get().bots.find((b) => b.name === botName);
if (bot) {
set({ selectedBot: bot, positions: [], trades: [] });
// Auto-fetch positions and trades for selected bot
get().fetchPositions(botName);
get().fetchTrades(botName);
get().fetchBotMetrics(botName);
}
},
refreshBot: async (botName: AgentType) => {
try {
const [status, metrics] = await Promise.all([
getAgentStatus(botName),
getAgentMetrics(botName).catch(() => null),
]);
const existingBot = get().bots.find((b) => b.name === botName);
const updatedBot = mapStatusToBotData(status, existingBot);
if (metrics) {
updatedBot.metrics = mapMetricsResponse(metrics);
}
set((state) => ({
bots: state.bots.map((b) => (b.name === botName ? updatedBot : b)),
selectedBot: state.selectedBot?.name === botName ? updatedBot : state.selectedBot,
}));
} catch (error) {
console.error(`Error refreshing bot ${botName}:`, error);
}
},
startBot: async (botName: AgentType, config: TradingBotConfig) => {
set({ error: null });
try {
const status = await startAgent(botName, config);
const existingBot = get().bots.find((b) => b.name === botName);
const updatedBot = mapStatusToBotData(status, existingBot);
set((state) => ({
bots: state.bots.map((b) => (b.name === botName ? updatedBot : b)),
selectedBot: state.selectedBot?.name === botName ? updatedBot : state.selectedBot,
}));
// Refresh full data after starting
await get().refreshBot(botName);
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to start bot';
set({ error: message });
throw error;
}
},
stopBot: async (botName: AgentType) => {
set({ error: null });
try {
const status = await stopAgent(botName);
const existingBot = get().bots.find((b) => b.name === botName);
const updatedBot = mapStatusToBotData(status, existingBot);
set((state) => ({
bots: state.bots.map((b) => (b.name === botName ? updatedBot : b)),
selectedBot: state.selectedBot?.name === botName ? updatedBot : state.selectedBot,
positions: state.selectedBot?.name === botName ? [] : state.positions,
}));
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to stop bot';
set({ error: message });
throw error;
}
},
pauseBot: async (botName: AgentType) => {
set({ error: null });
try {
const status = await pauseAgent(botName);
const existingBot = get().bots.find((b) => b.name === botName);
const updatedBot = mapStatusToBotData(status, existingBot);
set((state) => ({
bots: state.bots.map((b) => (b.name === botName ? updatedBot : b)),
selectedBot: state.selectedBot?.name === botName ? updatedBot : state.selectedBot,
}));
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to pause bot';
set({ error: message });
throw error;
}
},
resumeBot: async (botName: AgentType) => {
set({ error: null });
try {
const status = await resumeAgent(botName);
const existingBot = get().bots.find((b) => b.name === botName);
const updatedBot = mapStatusToBotData(status, existingBot);
set((state) => ({
bots: state.bots.map((b) => (b.name === botName ? updatedBot : b)),
selectedBot: state.selectedBot?.name === botName ? updatedBot : state.selectedBot,
}));
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to resume bot';
set({ error: message });
throw error;
}
},
// ======================================================================
// Metrics Actions
// ======================================================================
updateBotMetrics: (botName: AgentType, metricsUpdate: Partial<AgentMetrics>) => {
set((state) => ({
bots: state.bots.map((b) =>
b.name === botName
? { ...b, metrics: { ...b.metrics, ...metricsUpdate } as AgentMetrics }
: b
),
selectedBot:
state.selectedBot?.name === botName
? { ...state.selectedBot, metrics: { ...state.selectedBot.metrics, ...metricsUpdate } as AgentMetrics }
: state.selectedBot,
}));
},
fetchBotMetrics: async (botName: AgentType) => {
set({ loadingMetrics: true });
try {
const metrics = await getAgentMetrics(botName);
const mappedMetrics = mapMetricsResponse(metrics);
get().updateBotMetrics(botName, mappedMetrics);
set({ loadingMetrics: false });
} catch (error) {
console.error(`Error fetching metrics for ${botName}:`, error);
set({ loadingMetrics: false });
}
},
// ======================================================================
// Positions & Trades Actions
// ======================================================================
fetchPositions: async (botName: AgentType) => {
set({ loadingPositions: true, error: null });
try {
const positions = await getAgentPositions(botName);
set({ positions, loadingPositions: false });
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to fetch positions';
set({ error: message, loadingPositions: false, positions: [] });
console.error('Error fetching positions:', error);
}
},
fetchTrades: async (botName: AgentType, options?) => {
set({ loadingTrades: true, error: null });
try {
const trades = await getAgentTrades(botName, options);
set({ trades, loadingTrades: false });
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to fetch trades';
set({ error: message, loadingTrades: false, trades: [] });
console.error('Error fetching trades:', error);
}
},
closePosition: async (botName: AgentType, positionId: string) => {
set({ error: null });
try {
await closePosition(botName, positionId);
// Refresh positions and bot data after closing
await Promise.all([
get().fetchPositions(botName),
get().refreshBot(botName),
]);
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to close position';
set({ error: message });
throw error;
}
},
closeAllPositions: async (botName: AgentType) => {
set({ error: null });
try {
await closeAllPositions(botName);
// Refresh positions and bot data after closing all
await Promise.all([
get().fetchPositions(botName),
get().refreshBot(botName),
]);
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to close all positions';
set({ error: message });
throw error;
}
},
// ======================================================================
// Signal Actions
// ======================================================================
sendSignal: async (botName: AgentType, signal: SignalInput) => {
set({ error: null });
try {
await sendSignal(botName, signal);
// Signal sent successfully - no state update needed
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to send signal';
set({ error: message });
throw error;
}
},
// ======================================================================
// Utility Actions
// ======================================================================
clearError: () => {
set({ error: null });
},
reset: () => {
set(initialState);
},
}),
{
name: 'agents-store',
}
)
);
// ============================================================================
// Selectors (for performance optimization)
// ============================================================================
export const useBots = () => useAgentsStore((state) => state.bots);
export const useSelectedBot = () => useAgentsStore((state) => state.selectedBot);
export const useBotPositions = () => useAgentsStore((state) => state.positions);
export const useBotTrades = () => useAgentsStore((state) => state.trades);
export const useAgentsLoading = () => useAgentsStore((state) => state.isLoading);
export const useAgentsError = () => useAgentsStore((state) => state.error);
export const useServiceAvailable = () => useAgentsStore((state) => state.serviceAvailable);
export const useLoadingPositions = () => useAgentsStore((state) => state.loadingPositions);
export const useLoadingTrades = () => useAgentsStore((state) => state.loadingTrades);
export const useLoadingMetrics = () => useAgentsStore((state) => state.loadingMetrics);
export default useAgentsStore;

View File

@ -141,6 +141,9 @@ export {
// Chat types - Messages, Conversations // Chat types - Messages, Conversations
export * from './chat.types'; export * from './chat.types';
// Trading Agents types - Atlas, Orion, Nova agents
export * from './tradingAgents.types';
// Export a unified ApiResponse type (using the most complete version from payment.types) // Export a unified ApiResponse type (using the most complete version from payment.types)
// Also export module-specific aliases for backward compatibility // Also export module-specific aliases for backward compatibility
export { type ApiResponse } from './payment.types'; export { type ApiResponse } from './payment.types';

View File

@ -0,0 +1,582 @@
/**
* Trading Agents Types
* Type definitions for Trading Agents (Atlas, Orion, Nova) integration
* Aligned with backend trading-agents.client.ts and agents.service.ts
* Updated: 2026-01-27
*/
// ============================================================================
// Agent Type Definitions
// ============================================================================
/**
* Supported trading agent types
* - atlas: Conservative trading strategy (long-term, low risk)
* - orion: Balanced/moderate trading strategy
* - nova: Aggressive trading strategy (high risk/reward)
*/
export type AgentType = 'atlas' | 'orion' | 'nova';
/**
* Agent lifecycle status states
*/
export type AgentStatus = 'stopped' | 'starting' | 'running' | 'paused' | 'error';
/**
* Display-friendly bot status for UI
*/
export type BotStatus = 'idle' | 'running' | 'paused' | 'stopped' | 'error' | 'unavailable';
/**
* Trade side direction
*/
export type TradeSide = 'long' | 'short';
/**
* Signal action direction
*/
export type SignalAction = 'buy' | 'sell' | 'hold';
// ============================================================================
// Agent Configuration
// ============================================================================
/**
* Configuration for starting a trading agent
*/
export interface TradingBotConfig {
/** Initial capital/equity for the agent */
initialEquity: number;
/** List of trading symbols (e.g., 'BTCUSDT', 'EURUSD') */
symbols?: string[];
/** Risk per trade as percentage (e.g., 2.0 = 2%) */
riskPerTrade?: number;
/** Maximum number of concurrent positions */
maxPositions?: number;
}
// ============================================================================
// Agent Status & Metrics
// ============================================================================
/**
* Current status of a trading agent
*/
export interface AgentStatusResponse {
/** Agent identifier (atlas, orion, nova) */
agent_name: AgentType;
/** Current status of the agent */
status: AgentStatus;
/** Current equity/account balance */
equity: number;
/** Number of open positions */
positions: number;
/** Profit/Loss for the current day */
today_pnl: number;
/** Agent uptime in seconds */
uptime_seconds: number;
/** Timestamp of the last trade executed */
last_trade_at?: string;
/** Error message if status is 'error' */
error_message?: string;
}
/**
* Performance metrics for a trading agent
*/
export interface AgentMetrics {
/** Total number of trades executed */
total_trades: number;
/** Number of profitable trades */
winning_trades: number;
/** Number of losing trades */
losing_trades: number;
/** Win rate as percentage (0-100) */
win_rate: number;
/** Total profit from winning trades */
total_profit: number;
/** Total loss from losing trades */
total_loss: number;
/** Net profit/loss */
net_pnl: number;
/** Maximum drawdown percentage */
max_drawdown: number;
/** Current drawdown percentage */
current_drawdown: number;
/** Sharpe ratio (risk-adjusted return) */
sharpe_ratio?: number;
/** Sortino ratio (downside risk-adjusted return) */
sortino_ratio?: number;
/** Profit factor (total profit / total loss) */
profit_factor?: number;
}
/**
* Frontend representation of a trading bot
* Combines status and basic metrics for dashboard display
*/
export interface TradingBot {
/** Unique identifier for the bot */
id: string;
/** Agent type name */
name: AgentType;
/** Display name for UI */
displayName: string;
/** Agent description/strategy type */
description: string;
/** Current bot status */
status: BotStatus;
/** Current account equity */
equity: number;
/** Number of open positions */
positions: number;
/** P&L for today */
todayPnl: number;
/** Extended metrics (optional) */
metrics?: AgentMetrics;
}
// ============================================================================
// Positions
// ============================================================================
/**
* Open trading position for an agent
*/
export interface AgentPosition {
/** Position unique identifier */
id: string;
/** Trading symbol (e.g., 'BTCUSDT') */
symbol: string;
/** Position direction (long/short) */
side: TradeSide;
/** Position size/quantity */
quantity: number;
/** Entry price */
entry_price: number;
/** Current market price */
current_price: number;
/** Unrealized P&L in currency */
unrealized_pnl: number;
/** Unrealized P&L percentage */
unrealized_pnl_percent: number;
/** Stop loss price */
stop_loss?: number;
/** Take profit price */
take_profit?: number;
/** Position open timestamp (ISO 8601) */
opened_at: string;
}
/**
* Frontend-friendly position display
*/
export interface BotPosition extends AgentPosition {
/** Display-friendly P&L string */
pnlDisplay?: string;
/** Position status color (for UI styling) */
statusColor?: 'green' | 'red' | 'neutral';
}
// ============================================================================
// Trades & History
// ============================================================================
/**
* Closed trade from agent history
*/
export interface AgentTrade {
/** Trade unique identifier */
id: string;
/** Trading symbol (e.g., 'BTCUSDT') */
symbol: string;
/** Trade direction (long/short) */
side: TradeSide;
/** Quantity traded */
quantity: number;
/** Entry price */
entry_price: number;
/** Exit price */
exit_price: number;
/** Realized P&L in currency */
pnl: number;
/** Realized P&L percentage */
pnl_percent: number;
/** Trade entry timestamp (ISO 8601) */
opened_at: string;
/** Trade exit timestamp (ISO 8601) */
closed_at: string;
/** Reason for trade closure */
close_reason: string;
}
/**
* Frontend-friendly trade display
*/
export interface BotTrade extends AgentTrade {
/** Display-friendly profit/loss indicator */
profitDisplay?: string;
/** Trade result status for UI */
result?: 'win' | 'loss';
}
// ============================================================================
// Signals
// ============================================================================
/**
* Trading signal input for agents
*/
export interface SignalInput {
/** Trading symbol to apply signal to */
symbol: string;
/** Signal action (buy/sell/hold) */
action: SignalAction;
/** Confidence level (0-1) */
confidence: number;
/** Suggested entry price */
price: number;
/** Stop loss level */
stop_loss?: number;
/** Take profit level */
take_profit?: number;
/** Additional metadata */
metadata?: Record<string, unknown>;
}
// ============================================================================
// Health & System Status
// ============================================================================
/**
* Health status of Trading Agents service
*/
export interface TradingAgentsHealthResponse {
/** Overall service health status */
status: 'healthy' | 'unhealthy';
/** Service version */
version: string;
/** Number of agents currently running */
agents_running: number;
/** Service uptime in seconds */
uptime_seconds: number;
}
// ============================================================================
// Request/Response Types
// ============================================================================
/**
* Request to start a trading agent
*/
export interface StartAgentRequest {
/** Agent type to start */
agentType: AgentType;
/** Initial capital */
initialEquity: number;
/** Optional trading symbols */
symbols?: string[];
/** Risk per trade percentage */
riskPerTrade?: number;
/** Max concurrent positions */
maxPositions?: number;
}
/**
* Summary of all trading agents
*/
export interface AgentSummary {
/** Agent type name */
name: AgentType;
/** Current agent status */
status: AgentStatus;
/** Current equity */
equity: number;
/** Open positions count */
positions: number;
/** Today's P&L */
todayPnl: number;
/** Win rate (optional, when available) */
winRate?: number;
/** Total trades executed (optional, when available) */
totalTrades?: number;
}
/**
* Response for position closure operation
*/
export interface ClosePositionResponse {
/** Whether position was successfully closed */
closed: boolean;
/** Trade result from position closure */
trade?: AgentTrade;
}
/**
* Response for closing all positions
*/
export interface CloseAllPositionsResponse {
/** Number of positions closed */
closed: number;
}
/**
* Response for signal operations
*/
export interface SignalResponse {
/** Whether signal was received and processed */
received: boolean;
}
/**
* Response for broadcast signal
*/
export interface BroadcastSignalResponse {
/** Number of agents notified */
agents_notified: number;
}
// ============================================================================
// UI/Display Types
// ============================================================================
/**
* Chart data for bot performance visualization
*/
export interface BotEquityChartData {
/** Timestamp */
time: string;
/** Equity value at this point */
equity: number;
/** P&L at this point */
pnl: number;
}
/**
* Comparison data for multiple agents
*/
export interface BotComparison {
/** Agent type */
agent: AgentType;
/** Current equity */
equity: number;
/** Total return percentage */
totalReturn: number;
/** Win rate */
winRate: number;
/** Sharpe ratio */
sharpeRatio: number;
/** Status */
status: BotStatus;
}
/**
* Filter options for agent trades history
*/
export interface TradeFilterOptions {
/** Filter by symbol */
symbol?: string;
/** Filter by trade side */
side?: TradeSide;
/** Filter by result */
result?: 'win' | 'loss';
/** Start date (ISO 8601) */
startDate?: string;
/** End date (ISO 8601) */
endDate?: string;
/** Number of results to return */
limit?: number;
/** Offset for pagination */
offset?: number;
}
// ============================================================================
// Enums for common values
// ============================================================================
/**
* Agent type enum for dropdown/selection
*/
export enum AgentTypeEnum {
ATLAS = 'atlas',
ORION = 'orion',
NOVA = 'nova',
}
/**
* Agent status enum
*/
export enum AgentStatusEnum {
STOPPED = 'stopped',
STARTING = 'starting',
RUNNING = 'running',
PAUSED = 'paused',
ERROR = 'error',
}
/**
* Bot display status enum
*/
export enum BotStatusEnum {
IDLE = 'idle',
RUNNING = 'running',
PAUSED = 'paused',
STOPPED = 'stopped',
ERROR = 'error',
UNAVAILABLE = 'unavailable',
}
/**
* Trade side enum
*/
export enum TradeSideEnum {
LONG = 'long',
SHORT = 'short',
}
/**
* Signal action enum
*/
export enum SignalActionEnum {
BUY = 'buy',
SELL = 'sell',
HOLD = 'hold',
}
// ============================================================================
// Mapping utilities (for use in components)
// ============================================================================
/**
* Map internal agent status to user-friendly status
*/
export const statusToDisplayStatus = (status: AgentStatus): BotStatus => {
switch (status) {
case 'running':
return 'running';
case 'paused':
return 'paused';
case 'stopped':
return 'stopped';
case 'error':
return 'error';
default:
return 'unavailable';
}
};
/**
* Get friendly display name for agent type
*/
export const getAgentDisplayName = (agentType: AgentType): string => {
switch (agentType) {
case 'atlas':
return 'Atlas (Conservative)';
case 'orion':
return 'Orion (Balanced)';
case 'nova':
return 'Nova (Aggressive)';
default:
return agentType;
}
};
/**
* Get description for agent type
*/
export const getAgentDescription = (agentType: AgentType): string => {
switch (agentType) {
case 'atlas':
return 'Conservative long-term trading strategy with low risk';
case 'orion':
return 'Balanced trading strategy with moderate risk/reward';
case 'nova':
return 'Aggressive trading strategy with high risk/reward potential';
default:
return 'Trading agent';
}
};