import { useState, useEffect } from 'react' import type { AIModel, Exchange, CreateTraderRequest } from '../types' import { useLanguage } from '../contexts/LanguageContext' import { t } from '../i18n/translations' // 提取下划线后面的名称部分 function getShortName(fullName: string): string { const parts = fullName.split('_') return parts.length > 1 ? parts[parts.length - 1] : fullName } interface TraderConfigData { trader_id?: string trader_name: string ai_model: string exchange_id: string btc_eth_leverage: number altcoin_leverage: number trading_symbols: string custom_prompt: string override_base_prompt: boolean system_prompt_template: string is_cross_margin: boolean use_coin_pool: boolean use_oi_top: boolean initial_balance: number scan_interval_minutes: number } interface TraderConfigModalProps { isOpen: boolean onClose: () => void traderData?: TraderConfigData | null isEditMode?: boolean availableModels?: AIModel[] availableExchanges?: Exchange[] onSave?: (data: CreateTraderRequest) => Promise } export function TraderConfigModal({ isOpen, onClose, traderData, isEditMode = false, availableModels = [], availableExchanges = [], onSave, }: TraderConfigModalProps) { const { language } = useLanguage() const [formData, setFormData] = useState({ trader_name: '', ai_model: '', exchange_id: '', btc_eth_leverage: 5, altcoin_leverage: 3, trading_symbols: '', custom_prompt: '', override_base_prompt: false, system_prompt_template: 'default', is_cross_margin: true, use_coin_pool: false, use_oi_top: false, initial_balance: 1000, scan_interval_minutes: 3, }) const [isSaving, setIsSaving] = useState(false) const [availableCoins, setAvailableCoins] = useState([]) const [selectedCoins, setSelectedCoins] = useState([]) const [showCoinSelector, setShowCoinSelector] = useState(false) const [promptTemplates, setPromptTemplates] = useState<{ name: string }[]>([]) const [isFetchingBalance, setIsFetchingBalance] = useState(false) const [balanceFetchError, setBalanceFetchError] = useState('') useEffect(() => { if (traderData) { setFormData(traderData) // 设置已选择的币种 if (traderData.trading_symbols) { const coins = traderData.trading_symbols .split(',') .map((s) => s.trim()) .filter((s) => s) setSelectedCoins(coins) } } else if (!isEditMode) { setFormData({ trader_name: '', ai_model: availableModels[0]?.id || '', exchange_id: availableExchanges[0]?.id || '', btc_eth_leverage: 5, altcoin_leverage: 3, trading_symbols: '', custom_prompt: '', override_base_prompt: false, system_prompt_template: 'default', is_cross_margin: true, use_coin_pool: false, use_oi_top: false, initial_balance: 1000, scan_interval_minutes: 3, }) } // 确保旧数据也有默认的 system_prompt_template if (traderData && traderData.system_prompt_template === undefined) { setFormData((prev) => ({ ...prev, system_prompt_template: 'default', })) } }, [traderData, isEditMode, availableModels, availableExchanges]) // 获取系统配置中的币种列表 useEffect(() => { const fetchConfig = async () => { try { const response = await fetch('/api/config') const config = await response.json() if (config.default_coins) { setAvailableCoins(config.default_coins) } } catch (error) { console.error('Failed to fetch config:', error) // 使用默认币种列表 setAvailableCoins([ 'BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT', 'DOGEUSDT', 'ADAUSDT', ]) } } fetchConfig() }, []) // 获取系统提示词模板列表 useEffect(() => { const fetchPromptTemplates = async () => { try { const response = await fetch('/api/prompt-templates') const data = await response.json() if (data.templates) { setPromptTemplates(data.templates) } } catch (error) { console.error('Failed to fetch prompt templates:', error) // 使用默认模板列表 setPromptTemplates([{ name: 'default' }, { name: 'aggressive' }]) } } fetchPromptTemplates() }, []) // 当选择的币种改变时,更新输入框 useEffect(() => { const symbolsString = selectedCoins.join(',') setFormData((prev) => ({ ...prev, trading_symbols: symbolsString })) }, [selectedCoins]) if (!isOpen) return null const handleInputChange = (field: keyof TraderConfigData, value: any) => { setFormData((prev) => ({ ...prev, [field]: value })) // 如果是直接编辑trading_symbols,同步更新selectedCoins if (field === 'trading_symbols') { const coins = value .split(',') .map((s: string) => s.trim()) .filter((s: string) => s) setSelectedCoins(coins) } } const handleCoinToggle = (coin: string) => { setSelectedCoins((prev) => { if (prev.includes(coin)) { return prev.filter((c) => c !== coin) } else { return [...prev, coin] } }) } const handleFetchCurrentBalance = async () => { if (!isEditMode || !traderData?.trader_id) { setBalanceFetchError('只有在编辑模式下才能获取当前余额') return } setIsFetchingBalance(true) setBalanceFetchError('') try { const token = localStorage.getItem('token') const response = await fetch( `/api/account?trader_id=${traderData.trader_id}`, { headers: { Authorization: `Bearer ${token}`, }, } ) if (!response.ok) { throw new Error('获取账户余额失败') } const data = await response.json() // total_equity = 当前账户净值(包含未实现盈亏) // 这应该作为新的初始余额 const currentBalance = data.total_equity || data.balance || 0 setFormData((prev) => ({ ...prev, initial_balance: currentBalance })) // 显示成功提示 console.log('已获取当前余额:', currentBalance) } catch (error) { console.error('获取余额失败:', error) setBalanceFetchError('获取余额失败,请检查网络连接') } finally { setIsFetchingBalance(false) } } const handleSave = async () => { if (!onSave) return setIsSaving(true) try { const saveData: CreateTraderRequest = { name: formData.trader_name, ai_model_id: formData.ai_model, exchange_id: formData.exchange_id, btc_eth_leverage: formData.btc_eth_leverage, altcoin_leverage: formData.altcoin_leverage, trading_symbols: formData.trading_symbols, custom_prompt: formData.custom_prompt, override_base_prompt: formData.override_base_prompt, system_prompt_template: formData.system_prompt_template, is_cross_margin: formData.is_cross_margin, use_coin_pool: formData.use_coin_pool, use_oi_top: formData.use_oi_top, initial_balance: formData.initial_balance, scan_interval_minutes: formData.scan_interval_minutes, } await onSave(saveData) onClose() } catch (error) { console.error('保存失败:', error) } finally { setIsSaving(false) } } return (
e.stopPropagation()} > {/* Header */}
{isEditMode ? '✏️' : '➕'}

{isEditMode ? '修改交易员' : '创建交易员'}

{isEditMode ? '修改交易员配置参数' : '配置新的AI交易员'}

{/* Content */}
{/* Basic Info */}

🤖 基础配置

handleInputChange('trader_name', e.target.value) } className="w-full px-3 py-2 bg-[#0B0E11] border border-[#2B3139] rounded text-[#EAECEF] focus:border-[#F0B90B] focus:outline-none" placeholder="请输入交易员名称" />
{/* Trading Configuration */}

⚖️ 交易配置

{/* 第一行:保证金模式和初始余额 */}
{isEditMode && ( )}
handleInputChange( 'initial_balance', Number(e.target.value) ) } className="w-full px-3 py-2 bg-[#0B0E11] border border-[#2B3139] rounded text-[#EAECEF] focus:border-[#F0B90B] focus:outline-none" min="100" step="0.01" /> {!isEditMode && (

请输入您交易所账户的当前实际余额。如果输入不准确,P&L统计将会错误。

)} {isEditMode && (

点击"获取当前余额"按钮可自动获取您交易所账户的当前净值

)} {balanceFetchError && (

{balanceFetchError}

)}
{/* 第二行:AI 扫描决策间隔 */}
{ const parsedValue = Number(e.target.value) const safeValue = Number.isFinite(parsedValue) ? Math.max(3, parsedValue) : 3 handleInputChange('scan_interval_minutes', safeValue) }} className="w-full px-3 py-2 bg-[#0B0E11] border border-[#2B3139] rounded text-[#EAECEF] focus:border-[#F0B90B] focus:outline-none" min="3" max="60" step="1" />

{t('scanIntervalRecommend', language)}

{/* 第三行:杠杆设置 */}
handleInputChange( 'btc_eth_leverage', Number(e.target.value) ) } className="w-full px-3 py-2 bg-[#0B0E11] border border-[#2B3139] rounded text-[#EAECEF] focus:border-[#F0B90B] focus:outline-none" min="1" max="125" />
handleInputChange( 'altcoin_leverage', Number(e.target.value) ) } className="w-full px-3 py-2 bg-[#0B0E11] border border-[#2B3139] rounded text-[#EAECEF] focus:border-[#F0B90B] focus:outline-none" min="1" max="75" />
{/* 第三行:交易币种 */}
handleInputChange('trading_symbols', e.target.value) } className="w-full px-3 py-2 bg-[#0B0E11] border border-[#2B3139] rounded text-[#EAECEF] focus:border-[#F0B90B] focus:outline-none" placeholder="例如: BTCUSDT,ETHUSDT,ADAUSDT" /> {/* 币种选择器 */} {showCoinSelector && (
点击选择币种:
{availableCoins.map((coin) => ( ))}
)}
{/* Signal Sources */}

📡 信号源配置

handleInputChange('use_coin_pool', e.target.checked) } className="w-4 h-4" />
handleInputChange('use_oi_top', e.target.checked) } className="w-4 h-4" />
{/* Trading Prompt */}

💬 交易策略提示词

{/* 系统提示词模板选择 */}

选择预设的交易策略模板(包含交易哲学、风控原则等)

handleInputChange('override_base_prompt', e.target.checked) } className="w-4 h-4" /> {' '} 启用后将完全替换默认策略