Fixed health check; Fixed dex config; Add rank trader info view;

This commit is contained in:
icy
2025-11-01 18:58:32 +08:00
parent 3033a1308d
commit f413f87f39
17 changed files with 518 additions and 83 deletions

View File

@@ -3,7 +3,7 @@ import useSWR from 'swr';
import { api } from '../lib/api';
import type { TraderInfo, CreateTraderRequest, AIModel, Exchange } from '../types';
import { useLanguage } from '../contexts/LanguageContext';
import { t } from '../i18n/translations';
import { t, Language } from '../i18n/translations';
import { getExchangeIcon } from './ExchangeIcons';
import { getModelIcon } from './ModelIcons';
import { TraderConfigModal } from './TraderConfigModal';
@@ -107,12 +107,15 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
// Aster 交易所需要特殊字段
if (e.id === 'aster') {
return e.asterUser && e.asterSigner && e.asterPrivateKey;
return e.asterUser && e.asterUser.trim() !== '' &&
e.asterSigner && e.asterSigner.trim() !== '' &&
e.asterPrivateKey && e.asterPrivateKey.trim() !== '';
}
// Hyperliquid 只需要私钥作为apiKey不需要secretKey
// Hyperliquid 只需要私钥作为apiKey和钱包地址
if (e.id === 'hyperliquid') {
return e.apiKey && e.hyperliquidWalletAddr;
return e.apiKey && e.apiKey.trim() !== '' &&
e.hyperliquidWalletAddr && e.hyperliquidWalletAddr.trim() !== '';
}
// Binance 等其他交易所需要 apiKey 和 secretKey
@@ -375,11 +378,31 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
if (existingExchange) {
// 更新现有配置
updatedExchanges = allExchanges?.map(e =>
e.id === exchangeId ? { ...e, apiKey, secretKey, testnet, hyperliquidWalletAddr, asterUser, asterSigner, asterPrivateKey, enabled: true } : e
e.id === exchangeId ? {
...e,
apiKey,
secretKey,
testnet,
hyperliquidWalletAddr,
asterUser,
asterSigner,
asterPrivateKey,
enabled: true
} : e
) || [];
} else {
// 添加新配置
const newExchange = { ...exchangeToUpdate, apiKey, secretKey, testnet, hyperliquidWalletAddr, asterUser, asterSigner, asterPrivateKey, enabled: true };
const newExchange = {
...exchangeToUpdate,
apiKey,
secretKey,
testnet,
hyperliquidWalletAddr,
asterUser,
asterSigner,
asterPrivateKey,
enabled: true
};
updatedExchanges = [...(allExchanges || []), newExchange];
}
@@ -780,6 +803,7 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
setShowExchangeModal(false);
setEditingExchange(null);
}}
language={language}
/>
)}
@@ -1083,19 +1107,29 @@ function ExchangeConfigModal({
editingExchangeId,
onSave,
onDelete,
onClose
onClose,
language
}: {
allExchanges: Exchange[];
editingExchangeId: string | null;
onSave: (exchangeId: string, apiKey: string, secretKey?: string, testnet?: boolean, hyperliquidWalletAddr?: string, asterUser?: string, asterSigner?: string, asterPrivateKey?: string) => Promise<void>;
onDelete: (exchangeId: string) => void;
onClose: () => void;
language: Language;
}) {
const [selectedExchangeId, setSelectedExchangeId] = useState(editingExchangeId || '');
const [apiKey, setApiKey] = useState('');
const [secretKey, setSecretKey] = useState('');
const [passphrase, setPassphrase] = useState('');
const [testnet, setTestnet] = useState(false);
// Hyperliquid 特定字段
const [hyperliquidWalletAddr, setHyperliquidWalletAddr] = useState('');
// Aster 特定字段
const [asterUser, setAsterUser] = useState('');
const [asterSigner, setAsterSigner] = useState('');
const [asterPrivateKey, setAsterPrivateKey] = useState('');
// 获取当前编辑的交易所信息
const selectedExchange = allExchanges?.find(e => e.id === selectedExchangeId);
@@ -1107,6 +1141,14 @@ function ExchangeConfigModal({
setSecretKey(selectedExchange.secretKey || '');
setPassphrase(''); // Don't load existing passphrase for security
setTestnet(selectedExchange.testnet || false);
// Hyperliquid 字段
setHyperliquidWalletAddr(selectedExchange.hyperliquidWalletAddr || '');
// Aster 字段
setAsterUser(selectedExchange.asterUser || '');
setAsterSigner(selectedExchange.asterSigner || '');
setAsterPrivateKey(''); // Don't load existing private key for security
}
}, [editingExchangeId, selectedExchange]);
@@ -1117,11 +1159,21 @@ function ExchangeConfigModal({
// 根据交易所类型验证不同字段
if (selectedExchange?.id === 'binance') {
if (!apiKey.trim() || !secretKey.trim()) return;
await onSave(selectedExchangeId, apiKey.trim(), secretKey.trim(), testnet);
} else if (selectedExchange?.id === 'hyperliquid') {
if (!apiKey.trim() || !hyperliquidWalletAddr.trim()) return;
await onSave(selectedExchangeId, apiKey.trim(), '', testnet, hyperliquidWalletAddr.trim());
} else if (selectedExchange?.id === 'aster') {
if (!asterUser.trim() || !asterSigner.trim() || !asterPrivateKey.trim()) return;
await onSave(selectedExchangeId, '', '', testnet, undefined, asterUser.trim(), asterSigner.trim(), asterPrivateKey.trim());
} else if (selectedExchange?.id === 'okx') {
if (!apiKey.trim() || !secretKey.trim() || !passphrase.trim()) return;
await onSave(selectedExchangeId, apiKey.trim(), secretKey.trim(), testnet);
} else {
// 默认情况其他CEX交易所
if (!apiKey.trim() || !secretKey.trim()) return;
await onSave(selectedExchangeId, apiKey.trim(), secretKey.trim(), testnet);
}
await onSave(selectedExchangeId, apiKey.trim(), secretKey.trim(), testnet, undefined, undefined, undefined, undefined);
};
// 可选择的交易所列表(所有支持的交易所)
@@ -1192,51 +1244,147 @@ function ExchangeConfigModal({
{selectedExchange && (
<>
<div>
<label className="block text-sm font-semibold mb-2" style={{ color: '#EAECEF' }}>
API Key
</label>
<input
type="password"
value={apiKey}
onChange={(e) => setApiKey(e.target.value)}
placeholder="输入API密钥"
className="w-full px-3 py-2 rounded"
style={{ background: '#0B0E11', border: '1px solid #2B3139', color: '#EAECEF' }}
required
/>
</div>
{/* Binance 和其他 CEX 交易所的字段 */}
{(selectedExchange.id === 'binance' || selectedExchange.type === 'cex') && selectedExchange.id !== 'hyperliquid' && selectedExchange.id !== 'aster' && (
<>
<div>
<label className="block text-sm font-semibold mb-2" style={{ color: '#EAECEF' }}>
{t('apiKey', language)}
</label>
<input
type="password"
value={apiKey}
onChange={(e) => setApiKey(e.target.value)}
placeholder={t('enterAPIKey', language)}
className="w-full px-3 py-2 rounded"
style={{ background: '#0B0E11', border: '1px solid #2B3139', color: '#EAECEF' }}
required
/>
</div>
<div>
<label className="block text-sm font-semibold mb-2" style={{ color: '#EAECEF' }}>
Secret Key
</label>
<input
type="password"
value={secretKey}
onChange={(e) => setSecretKey(e.target.value)}
placeholder="输入密钥"
className="w-full px-3 py-2 rounded"
style={{ background: '#0B0E11', border: '1px solid #2B3139', color: '#EAECEF' }}
required
/>
</div>
<div>
<label className="block text-sm font-semibold mb-2" style={{ color: '#EAECEF' }}>
{t('secretKey', language)}
</label>
<input
type="password"
value={secretKey}
onChange={(e) => setSecretKey(e.target.value)}
placeholder={t('enterSecretKey', language)}
className="w-full px-3 py-2 rounded"
style={{ background: '#0B0E11', border: '1px solid #2B3139', color: '#EAECEF' }}
required
/>
</div>
{selectedExchange.id === 'okx' && (
<div>
<label className="block text-sm font-semibold mb-2" style={{ color: '#EAECEF' }}>
Passphrase
</label>
<input
type="password"
value={passphrase}
onChange={(e) => setPassphrase(e.target.value)}
placeholder="输入Passphrase (OKX必填)"
className="w-full px-3 py-2 rounded"
style={{ background: '#0B0E11', border: '1px solid #2B3139', color: '#EAECEF' }}
required
/>
</div>
{selectedExchange.id === 'okx' && (
<div>
<label className="block text-sm font-semibold mb-2" style={{ color: '#EAECEF' }}>
{t('passphrase', language)}
</label>
<input
type="password"
value={passphrase}
onChange={(e) => setPassphrase(e.target.value)}
placeholder={t('enterPassphrase', language)}
className="w-full px-3 py-2 rounded"
style={{ background: '#0B0E11', border: '1px solid #2B3139', color: '#EAECEF' }}
required
/>
</div>
)}
</>
)}
{/* Hyperliquid 交易所的字段 */}
{selectedExchange.id === 'hyperliquid' && (
<>
<div>
<label className="block text-sm font-semibold mb-2" style={{ color: '#EAECEF' }}>
{t('privateKey', language)}
</label>
<input
type="password"
value={apiKey}
onChange={(e) => setApiKey(e.target.value)}
placeholder={t('enterPrivateKey', language)}
className="w-full px-3 py-2 rounded"
style={{ background: '#0B0E11', border: '1px solid #2B3139', color: '#EAECEF' }}
required
/>
<div className="text-xs mt-1" style={{ color: '#848E9C' }}>
{t('hyperliquidPrivateKeyDesc', language)}
</div>
</div>
<div>
<label className="block text-sm font-semibold mb-2" style={{ color: '#EAECEF' }}>
{t('walletAddress', language)}
</label>
<input
type="text"
value={hyperliquidWalletAddr}
onChange={(e) => setHyperliquidWalletAddr(e.target.value)}
placeholder={t('enterWalletAddress', language)}
className="w-full px-3 py-2 rounded"
style={{ background: '#0B0E11', border: '1px solid #2B3139', color: '#EAECEF' }}
required
/>
<div className="text-xs mt-1" style={{ color: '#848E9C' }}>
{t('hyperliquidWalletAddressDesc', language)}
</div>
</div>
</>
)}
{/* Aster 交易所的字段 */}
{selectedExchange.id === 'aster' && (
<>
<div>
<label className="block text-sm font-semibold mb-2" style={{ color: '#EAECEF' }}>
{t('user', language)}
</label>
<input
type="text"
value={asterUser}
onChange={(e) => setAsterUser(e.target.value)}
placeholder={t('enterUser', language)}
className="w-full px-3 py-2 rounded"
style={{ background: '#0B0E11', border: '1px solid #2B3139', color: '#EAECEF' }}
required
/>
</div>
<div>
<label className="block text-sm font-semibold mb-2" style={{ color: '#EAECEF' }}>
{t('signer', language)}
</label>
<input
type="text"
value={asterSigner}
onChange={(e) => setAsterSigner(e.target.value)}
placeholder={t('enterSigner', language)}
className="w-full px-3 py-2 rounded"
style={{ background: '#0B0E11', border: '1px solid #2B3139', color: '#EAECEF' }}
required
/>
</div>
<div>
<label className="block text-sm font-semibold mb-2" style={{ color: '#EAECEF' }}>
{t('privateKey', language)}
</label>
<input
type="password"
value={asterPrivateKey}
onChange={(e) => setAsterPrivateKey(e.target.value)}
placeholder={t('enterPrivateKey', language)}
className="w-full px-3 py-2 rounded"
style={{ background: '#0B0E11', border: '1px solid #2B3139', color: '#EAECEF' }}
required
/>
</div>
</>
)}
<div>
@@ -1248,21 +1396,21 @@ function ExchangeConfigModal({
className="form-checkbox rounded"
style={{ accentColor: '#F0B90B' }}
/>
<span style={{ color: '#EAECEF' }}>使</span>
<span style={{ color: '#EAECEF' }}>{t('useTestnet', language)}</span>
</label>
<div className="text-xs mt-1" style={{ color: '#848E9C' }}>
{t('testnetDescription', language)}
</div>
</div>
<div className="p-4 rounded" style={{ background: 'rgba(240, 185, 11, 0.1)', border: '1px solid rgba(240, 185, 11, 0.2)' }}>
<div className="text-sm font-semibold mb-2" style={{ color: '#F0B90B' }}>
{t('securityWarning', language)}
</div>
<div className="text-xs space-y-1" style={{ color: '#848E9C' }}>
<div> API密钥将被加密存储使</div>
<div> </div>
<div> </div>
<div>{t('securityTip1', language)}</div>
<div>{t('securityTip2', language)}</div>
<div>{t('securityTip3', language)}</div>
</div>
</div>
</>
@@ -1275,15 +1423,22 @@ function ExchangeConfigModal({
className="flex-1 px-4 py-2 rounded text-sm font-semibold"
style={{ background: '#2B3139', color: '#848E9C' }}
>
{t('cancel', language)}
</button>
<button
type="submit"
disabled={!selectedExchange || !apiKey.trim() || !secretKey.trim() || (selectedExchange?.id === 'okx' && !passphrase.trim())}
disabled={
!selectedExchange ||
(selectedExchange.id === 'binance' && (!apiKey.trim() || !secretKey.trim())) ||
(selectedExchange.id === 'okx' && (!apiKey.trim() || !secretKey.trim() || !passphrase.trim())) ||
(selectedExchange.id === 'hyperliquid' && (!apiKey.trim() || !hyperliquidWalletAddr.trim())) ||
(selectedExchange.id === 'aster' && (!asterUser.trim() || !asterSigner.trim() || !asterPrivateKey.trim())) ||
(selectedExchange.type === 'cex' && selectedExchange.id !== 'hyperliquid' && selectedExchange.id !== 'aster' && selectedExchange.id !== 'binance' && selectedExchange.id !== 'okx' && (!apiKey.trim() || !secretKey.trim()))
}
className="flex-1 px-4 py-2 rounded text-sm font-semibold disabled:opacity-50"
style={{ background: '#F0B90B', color: '#000' }}
>
{t('saveConfiguration', language)}
</button>
</div>
</form>

View File

@@ -3,7 +3,7 @@ import useSWR from 'swr';
import { api } from '../lib/api';
import type { CompetitionData } from '../types';
import { ComparisonChart } from './ComparisonChart';
import { TraderConfigModal } from './TraderConfigModal';
import { TraderConfigViewModal } from './TraderConfigViewModal';
import { getTraderColor } from '../utils/traderColors';
import { useLanguage } from '../contexts/LanguageContext';
import { t } from '../i18n/translations';
@@ -273,8 +273,8 @@ export function CompetitionPage() {
</div>
)}
{/* Trader Config Modal */}
<TraderConfigModal
{/* Trader Config View Modal */}
<TraderConfigViewModal
isOpen={isModalOpen}
onClose={closeModal}
traderData={selectedTrader}

View File

@@ -0,0 +1,211 @@
import { useState } from 'react';
import type { TraderConfigData } from '../types';
// 提取下划线后面的名称部分
function getShortName(fullName: string): string {
const parts = fullName.split('_');
return parts.length > 1 ? parts[parts.length - 1] : fullName;
}
interface TraderConfigViewModalProps {
isOpen: boolean;
onClose: () => void;
traderData?: TraderConfigData | null;
}
export function TraderConfigViewModal({
isOpen,
onClose,
traderData
}: TraderConfigViewModalProps) {
const [copiedField, setCopiedField] = useState<string | null>(null);
if (!isOpen || !traderData) return null;
const copyToClipboard = async (text: string, fieldName: string) => {
try {
await navigator.clipboard.writeText(text);
setCopiedField(fieldName);
setTimeout(() => setCopiedField(null), 2000);
} catch (error) {
console.error('Failed to copy:', error);
}
};
const CopyButton = ({ text, fieldName }: { text: string; fieldName: string }) => (
<button
onClick={() => copyToClipboard(text, fieldName)}
className="ml-2 px-2 py-1 text-xs rounded transition-all duration-200 hover:scale-105"
style={{
background: copiedField === fieldName ? 'rgba(14, 203, 129, 0.1)' : 'rgba(240, 185, 11, 0.1)',
color: copiedField === fieldName ? '#0ECB81' : '#F0B90B',
border: `1px solid ${copiedField === fieldName ? 'rgba(14, 203, 129, 0.3)' : 'rgba(240, 185, 11, 0.3)'}`
}}
>
{copiedField === fieldName ? '✓ 已复制' : '📋 复制'}
</button>
);
const InfoRow = ({ label, value, copyable = false, fieldName = '' }: {
label: string;
value: string | number | boolean;
copyable?: boolean;
fieldName?: string;
}) => (
<div className="flex justify-between items-start py-2 border-b border-[#2B3139] last:border-b-0">
<span className="text-sm text-[#848E9C] font-medium">{label}</span>
<div className="flex items-center text-right">
<span className="text-sm text-[#EAECEF] font-mono">
{typeof value === 'boolean' ? (value ? '是' : '否') : value}
</span>
{copyable && typeof value === 'string' && value && (
<CopyButton text={value} fieldName={fieldName} />
)}
</div>
</div>
);
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50 backdrop-blur-sm">
<div
className="bg-[#1E2329] border border-[#2B3139] rounded-xl shadow-2xl max-w-2xl w-full mx-4 max-h-[90vh] overflow-y-auto"
onClick={(e) => e.stopPropagation()}
>
{/* Header */}
<div className="flex items-center justify-between p-6 border-b border-[#2B3139] bg-gradient-to-r from-[#1E2329] to-[#252B35]">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-lg bg-gradient-to-br from-[#F0B90B] to-[#E1A706] flex items-center justify-center">
<span className="text-lg">👁</span>
</div>
<div>
<h2 className="text-xl font-bold text-[#EAECEF]">
</h2>
<p className="text-sm text-[#848E9C] mt-1">
{traderData.trader_name}
</p>
</div>
</div>
<div className="flex items-center gap-2">
{/* Running Status */}
<div
className="px-3 py-1 rounded-full text-xs font-bold flex items-center gap-1"
style={traderData.is_running
? { background: 'rgba(14, 203, 129, 0.1)', color: '#0ECB81' }
: { background: 'rgba(246, 70, 93, 0.1)', color: '#F6465D' }
}
>
<span>{traderData.is_running ? '●' : '○'}</span>
{traderData.is_running ? '运行中' : '已停止'}
</div>
<button
onClick={onClose}
className="w-8 h-8 rounded-lg text-[#848E9C] hover:text-[#EAECEF] hover:bg-[#2B3139] transition-colors flex items-center justify-center"
>
</button>
</div>
</div>
{/* Content */}
<div className="p-6 space-y-6">
{/* Basic Info */}
<div className="bg-[#0B0E11] border border-[#2B3139] rounded-lg p-5">
<h3 className="text-lg font-semibold text-[#EAECEF] mb-4 flex items-center gap-2">
🤖
</h3>
<div className="space-y-3">
<InfoRow label="交易员ID" value={traderData.trader_id || ''} copyable fieldName="trader_id" />
<InfoRow label="交易员名称" value={traderData.trader_name} copyable fieldName="trader_name" />
<InfoRow label="AI模型" value={getShortName(traderData.ai_model).toUpperCase()} />
<InfoRow label="交易所" value={getShortName(traderData.exchange_id).toUpperCase()} />
<InfoRow label="初始余额" value={`$${traderData.initial_balance.toLocaleString()}`} />
</div>
</div>
{/* Trading Configuration */}
<div className="bg-[#0B0E11] border border-[#2B3139] rounded-lg p-5">
<h3 className="text-lg font-semibold text-[#EAECEF] mb-4 flex items-center gap-2">
</h3>
<div className="space-y-3">
<InfoRow label="保证金模式" value={traderData.is_cross_margin ? '全仓' : '逐仓'} />
<InfoRow label="BTC/ETH 杠杆" value={`${traderData.btc_eth_leverage}x`} />
<InfoRow label="山寨币杠杆" value={`${traderData.altcoin_leverage}x`} />
<InfoRow
label="交易币种"
value={traderData.trading_symbols || '使用默认币种'}
copyable
fieldName="trading_symbols"
/>
</div>
</div>
{/* Signal Sources */}
<div className="bg-[#0B0E11] border border-[#2B3139] rounded-lg p-5">
<h3 className="text-lg font-semibold text-[#EAECEF] mb-4 flex items-center gap-2">
📡
</h3>
<div className="space-y-3">
<InfoRow label="Coin Pool 信号" value={traderData.use_coin_pool} />
<InfoRow label="OI Top 信号" value={traderData.use_oi_top} />
</div>
</div>
{/* Custom Prompt */}
<div className="bg-[#0B0E11] border border-[#2B3139] rounded-lg p-5">
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold text-[#EAECEF] flex items-center gap-2">
💬
</h3>
{traderData.custom_prompt && (
<CopyButton text={traderData.custom_prompt} fieldName="custom_prompt" />
)}
</div>
<div className="space-y-3">
<InfoRow label="覆盖默认提示词" value={traderData.override_base_prompt} />
{traderData.custom_prompt ? (
<div>
<div className="text-sm text-[#848E9C] mb-2">
{traderData.override_base_prompt ? '自定义提示词' : '附加提示词'}
</div>
<div
className="p-3 rounded border text-sm text-[#EAECEF] font-mono leading-relaxed max-h-48 overflow-y-auto"
style={{
background: '#0B0E11',
border: '1px solid #2B3139',
whiteSpace: 'pre-wrap'
}}
>
{traderData.custom_prompt}
</div>
</div>
) : (
<div className="text-sm text-[#848E9C] italic p-3 rounded border" style={{ border: '1px solid #2B3139' }}>
使
</div>
)}
</div>
</div>
</div>
{/* Footer */}
<div className="flex justify-end gap-3 p-6 border-t border-[#2B3139] bg-gradient-to-r from-[#1E2329] to-[#252B35]">
<button
onClick={onClose}
className="px-6 py-3 bg-[#2B3139] text-[#EAECEF] rounded-lg hover:bg-[#404750] transition-all duration-200 border border-[#404750]"
>
</button>
<button
onClick={() => copyToClipboard(JSON.stringify(traderData, null, 2), 'full_config')}
className="px-6 py-3 bg-gradient-to-r from-[#F0B90B] to-[#E1A706] text-black rounded-lg hover:from-[#E1A706] hover:to-[#D4951E] transition-all duration-200 font-medium shadow-lg"
>
{copiedField === 'full_config' ? '✓ 已复制配置' : '📋 复制完整配置'}
</button>
</div>
</div>
</div>
);
}

View File

@@ -164,6 +164,28 @@ export const translations = {
useOfficialAPI: 'Use official API service',
useCustomAPI: 'Use custom API endpoint',
// Exchange Configuration
secretKey: 'Secret Key',
privateKey: 'Private Key',
walletAddress: 'Wallet Address',
user: 'User',
signer: 'Signer',
passphrase: 'Passphrase',
enterSecretKey: 'Enter Secret Key',
enterPrivateKey: 'Enter Private Key',
enterWalletAddress: 'Enter Wallet Address',
enterUser: 'Enter User',
enterSigner: 'Enter Signer Address',
enterPassphrase: 'Enter Passphrase (Required for OKX)',
hyperliquidPrivateKeyDesc: 'Hyperliquid uses private key for trading authentication',
hyperliquidWalletAddressDesc: 'Wallet address corresponding to the private key',
securityWarning: '⚠️ Security Notice',
securityTip1: '• API keys will be encrypted and stored. Recommend using read-only or futures trading permissions',
securityTip2: '• Do not grant withdrawal permissions to ensure fund safety',
securityTip3: '• After deleting configuration, related traders will not be able to trade normally',
testnetDescription: 'Enable to connect to exchange testnet environment for simulation trading',
saveConfiguration: 'Save Configuration',
// Trader Configuration
positionMode: 'Position Mode',
crossMarginMode: 'Cross Margin',
@@ -406,6 +428,28 @@ export const translations = {
useOfficialAPI: '使用官方API服务',
useCustomAPI: '使用自定义API端点',
// Exchange Configuration
secretKey: '密钥',
privateKey: '私钥',
walletAddress: '钱包地址',
user: '用户名',
signer: '签名者',
passphrase: '口令',
enterSecretKey: '输入密钥',
enterPrivateKey: '输入私钥',
enterWalletAddress: '输入钱包地址',
enterUser: '输入用户名',
enterSigner: '输入签名者地址',
enterPassphrase: '输入Passphrase (OKX必填)',
hyperliquidPrivateKeyDesc: 'Hyperliquid 使用私钥进行交易认证',
hyperliquidWalletAddressDesc: '与私钥对应的钱包地址',
securityWarning: '⚠️ 安全提示',
securityTip1: '• API密钥将被加密存储建议使用只读或期货交易权限',
securityTip2: '• 不要授予提现权限,确保资金安全',
securityTip3: '• 删除配置后,相关交易员将无法正常交易',
testnetDescription: '启用后将连接到交易所测试环境,用于模拟交易',
saveConfiguration: '保存配置',
// Trader Configuration
positionMode: '仓位模式',
crossMarginMode: '全仓模式',

View File

@@ -179,3 +179,21 @@ export interface CompetitionData {
traders: CompetitionTraderData[];
count: number;
}
// Trader Configuration Data for View Modal
export 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;
is_cross_margin: boolean;
use_coin_pool: boolean;
use_oi_top: boolean;
initial_balance: number;
is_running: boolean;
}