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:
parent
0f20468381
commit
5779c9a5cf
@ -25,6 +25,7 @@ const SecuritySettings = lazy(() => import('./modules/auth/pages/SecuritySetting
|
||||
// Lazy load modules - Core
|
||||
const Dashboard = lazy(() => import('./modules/dashboard/pages/Dashboard'));
|
||||
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 BacktestingDashboard = lazy(() => import('./modules/backtesting/pages/BacktestingDashboard'));
|
||||
const Investment = lazy(() => import('./modules/investment/pages/Investment'));
|
||||
@ -95,6 +96,7 @@ function App() {
|
||||
|
||||
{/* Trading */}
|
||||
<Route path="/trading" element={<Trading />} />
|
||||
<Route path="/trading/agents" element={<TradingAgents />} />
|
||||
<Route path="/ml-dashboard" element={<MLDashboard />} />
|
||||
<Route path="/backtesting" element={<BacktestingDashboard />} />
|
||||
<Route path="/investment" element={<Investment />} />
|
||||
|
||||
178
src/modules/trading/components/agents/AgentCard.tsx
Normal file
178
src/modules/trading/components/agents/AgentCard.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
200
src/modules/trading/components/agents/AgentsList.tsx
Normal file
200
src/modules/trading/components/agents/AgentsList.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
276
src/modules/trading/components/agents/BotCard.tsx
Normal file
276
src/modules/trading/components/agents/BotCard.tsx
Normal 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;
|
||||
7
src/modules/trading/components/agents/index.ts
Normal file
7
src/modules/trading/components/agents/index.ts
Normal file
@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Export all agent-related components
|
||||
*/
|
||||
|
||||
export { AgentsList } from './AgentsList';
|
||||
export { AgentCard } from './AgentCard';
|
||||
export { BotCard } from './BotCard';
|
||||
@ -66,3 +66,7 @@ export { default as PositionModifierDialog } from './PositionModifierDialog';
|
||||
export { default as RiskBasedPositionSizer } from './RiskBasedPositionSizer';
|
||||
export { default as TradeAlertsNotificationCenter } from './TradeAlertsNotificationCenter';
|
||||
export type { TradeAlert, AlertType, AlertPriority } from './TradeAlertsNotificationCenter';
|
||||
|
||||
// Trading Agents Components (OQI-007)
|
||||
export { AgentsList } from './agents/AgentsList';
|
||||
export { BotCard } from './agents/BotCard';
|
||||
|
||||
23
src/modules/trading/pages/AgentsPage.tsx
Normal file
23
src/modules/trading/pages/AgentsPage.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
242
src/services/agents.service.ts
Normal file
242
src/services/agents.service.ts
Normal 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
497
src/stores/agentsStore.ts
Normal 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;
|
||||
@ -141,6 +141,9 @@ export {
|
||||
// Chat types - Messages, Conversations
|
||||
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)
|
||||
// Also export module-specific aliases for backward compatibility
|
||||
export { type ApiResponse } from './payment.types';
|
||||
|
||||
582
src/types/tradingAgents.types.ts
Normal file
582
src/types/tradingAgents.types.ts
Normal 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';
|
||||
}
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user