mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-06 04:20:59 +08:00
fix: OKX trading issues and improve position tracking
- Add maxMktSz check for OKX market orders to prevent exceeding limits - Increase margin safety buffer (0.1% fee + 1% buffer) for all exchanges - Fix Binance position closure detection with direct trade queries - Move Recent Completed Trades before Current Positions in AI prompt - Update README screenshots with table layout for better alignment
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -276,16 +276,16 @@ export function TraderConfigModal({
|
||||
>
|
||||
{availableExchanges.map((exchange) => (
|
||||
<option key={exchange.id} value={exchange.id}>
|
||||
{getShortName(
|
||||
exchange.name || exchange.id
|
||||
).toUpperCase()}
|
||||
{getShortName(exchange.name || exchange.exchange_type || exchange.id).toUpperCase()}
|
||||
{exchange.account_name ? ` - ${exchange.account_name}` : ''}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
{/* Exchange Registration Link */}
|
||||
{formData.exchange_id && (() => {
|
||||
// Exchange ID is the exchange type (e.g., "binance", "okx", "aster")
|
||||
const exchangeType = formData.exchange_id.toLowerCase()
|
||||
// Find the selected exchange to get its type
|
||||
const selectedExchange = availableExchanges.find(e => e.id === formData.exchange_id)
|
||||
const exchangeType = selectedExchange?.exchange_type?.toLowerCase() || ''
|
||||
const regLink = EXCHANGE_REGISTRATION_LINKS[exchangeType]
|
||||
if (!regLink) return null
|
||||
return (
|
||||
|
||||
@@ -16,11 +16,23 @@ import { toast } from 'sonner'
|
||||
import { Tooltip } from './Tooltip'
|
||||
import { getShortName } from './utils'
|
||||
|
||||
// Supported exchange templates for creating new accounts
|
||||
const SUPPORTED_EXCHANGE_TEMPLATES = [
|
||||
{ exchange_type: 'binance', name: 'Binance Futures', type: 'cex' as const },
|
||||
{ exchange_type: 'bybit', name: 'Bybit Futures', type: 'cex' as const },
|
||||
{ exchange_type: 'okx', name: 'OKX Futures', type: 'cex' as const },
|
||||
{ exchange_type: 'hyperliquid', name: 'Hyperliquid', type: 'dex' as const },
|
||||
{ exchange_type: 'aster', name: 'Aster DEX', type: 'dex' as const },
|
||||
{ exchange_type: 'lighter', name: 'Lighter', type: 'dex' as const },
|
||||
]
|
||||
|
||||
interface ExchangeConfigModalProps {
|
||||
allExchanges: Exchange[]
|
||||
editingExchangeId: string | null
|
||||
onSave: (
|
||||
exchangeId: string,
|
||||
exchangeId: string | null, // null for creating new account
|
||||
exchangeType: string,
|
||||
accountName: string,
|
||||
apiKey: string,
|
||||
secretKey?: string,
|
||||
passphrase?: string, // OKX专用
|
||||
@@ -46,9 +58,8 @@ export function ExchangeConfigModal({
|
||||
onClose,
|
||||
language,
|
||||
}: ExchangeConfigModalProps) {
|
||||
const [selectedExchangeId, setSelectedExchangeId] = useState(
|
||||
editingExchangeId || ''
|
||||
)
|
||||
// Selected exchange type for creating new accounts
|
||||
const [selectedExchangeType, setSelectedExchangeType] = useState('')
|
||||
const [apiKey, setApiKey] = useState('')
|
||||
const [secretKey, setSecretKey] = useState('')
|
||||
const [passphrase, setPassphrase] = useState('')
|
||||
@@ -87,10 +98,25 @@ export function ExchangeConfigModal({
|
||||
// 保存中状态
|
||||
const [isSaving, setIsSaving] = useState(false)
|
||||
|
||||
// 获取当前编辑的交易所信息
|
||||
const selectedExchange = allExchanges?.find(
|
||||
(e) => e.id === selectedExchangeId
|
||||
)
|
||||
// 账户名称
|
||||
const [accountName, setAccountName] = useState('')
|
||||
|
||||
// 获取当前编辑的交易所信息或模板
|
||||
// For editing: find the existing account by id (UUID)
|
||||
// For creating: use the selected exchange template
|
||||
const selectedExchange = editingExchangeId
|
||||
? allExchanges?.find((e) => e.id === editingExchangeId)
|
||||
: null
|
||||
|
||||
// Get the exchange template for displaying UI fields
|
||||
const selectedTemplate = editingExchangeId
|
||||
? SUPPORTED_EXCHANGE_TEMPLATES.find((t) => t.exchange_type === selectedExchange?.exchange_type)
|
||||
: SUPPORTED_EXCHANGE_TEMPLATES.find((t) => t.exchange_type === selectedExchangeType)
|
||||
|
||||
// Get the current exchange type (from existing account or selected template)
|
||||
const currentExchangeType = editingExchangeId
|
||||
? selectedExchange?.exchange_type
|
||||
: selectedExchangeType
|
||||
|
||||
// 交易所注册链接配置
|
||||
const exchangeRegistrationLinks: Record<string, { url: string; hasReferral?: boolean }> = {
|
||||
@@ -105,6 +131,7 @@ export function ExchangeConfigModal({
|
||||
// 如果是编辑现有交易所,初始化表单数据
|
||||
useEffect(() => {
|
||||
if (editingExchangeId && selectedExchange) {
|
||||
setAccountName(selectedExchange.account_name || '')
|
||||
setApiKey(selectedExchange.apiKey || '')
|
||||
setSecretKey(selectedExchange.secretKey || '')
|
||||
setPassphrase('') // Don't load existing passphrase for security
|
||||
@@ -127,7 +154,7 @@ export function ExchangeConfigModal({
|
||||
|
||||
// 加载服务器IP(当选择binance时)
|
||||
useEffect(() => {
|
||||
if (selectedExchangeId === 'binance' && !serverIP) {
|
||||
if (currentExchangeType === 'binance' && !serverIP) {
|
||||
setLoadingIP(true)
|
||||
api
|
||||
.getServerIP()
|
||||
@@ -141,7 +168,7 @@ export function ExchangeConfigModal({
|
||||
setLoadingIP(false)
|
||||
})
|
||||
}
|
||||
}, [selectedExchangeId])
|
||||
}, [currentExchangeType])
|
||||
|
||||
const handleCopyIP = async (ip: string) => {
|
||||
try {
|
||||
@@ -231,32 +258,49 @@ export function ExchangeConfigModal({
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
if (!selectedExchangeId || isSaving) return
|
||||
if (isSaving) return
|
||||
|
||||
// For creating, we need the exchange type
|
||||
if (!editingExchangeId && !selectedExchangeType) return
|
||||
|
||||
// Validate account name
|
||||
const trimmedAccountName = accountName.trim()
|
||||
if (!trimmedAccountName) {
|
||||
toast.error(language === 'zh' ? '请输入账户名称' : 'Please enter account name')
|
||||
return
|
||||
}
|
||||
|
||||
const exchangeId = editingExchangeId || null
|
||||
const exchangeType = currentExchangeType || ''
|
||||
|
||||
setIsSaving(true)
|
||||
try {
|
||||
// 根据交易所类型验证不同字段
|
||||
if (selectedExchange?.id === 'binance') {
|
||||
if (currentExchangeType === 'binance') {
|
||||
if (!apiKey.trim() || !secretKey.trim()) return
|
||||
await onSave(selectedExchangeId, apiKey.trim(), secretKey.trim(), '', testnet)
|
||||
} else if (selectedExchange?.id === 'okx') {
|
||||
await onSave(exchangeId, exchangeType, trimmedAccountName, apiKey.trim(), secretKey.trim(), '', testnet)
|
||||
} else if (currentExchangeType === 'okx') {
|
||||
if (!apiKey.trim() || !secretKey.trim() || !passphrase.trim()) return
|
||||
await onSave(selectedExchangeId, apiKey.trim(), secretKey.trim(), passphrase.trim(), testnet)
|
||||
} else if (selectedExchange?.id === 'hyperliquid') {
|
||||
await onSave(exchangeId, exchangeType, trimmedAccountName, apiKey.trim(), secretKey.trim(), passphrase.trim(), testnet)
|
||||
} else if (currentExchangeType === 'hyperliquid') {
|
||||
if (!apiKey.trim() || !hyperliquidWalletAddr.trim()) return // 验证私钥和钱包地址
|
||||
await onSave(
|
||||
selectedExchangeId,
|
||||
exchangeId,
|
||||
exchangeType,
|
||||
trimmedAccountName,
|
||||
apiKey.trim(),
|
||||
'',
|
||||
'',
|
||||
testnet,
|
||||
hyperliquidWalletAddr.trim()
|
||||
)
|
||||
} else if (selectedExchange?.id === 'aster') {
|
||||
} else if (currentExchangeType === 'aster') {
|
||||
if (!asterUser.trim() || !asterSigner.trim() || !asterPrivateKey.trim())
|
||||
return
|
||||
await onSave(
|
||||
selectedExchangeId,
|
||||
exchangeId,
|
||||
exchangeType,
|
||||
trimmedAccountName,
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
@@ -266,10 +310,12 @@ export function ExchangeConfigModal({
|
||||
asterSigner.trim(),
|
||||
asterPrivateKey.trim()
|
||||
)
|
||||
} else if (selectedExchange?.id === 'lighter') {
|
||||
} else if (currentExchangeType === 'lighter') {
|
||||
if (!lighterWalletAddr.trim() || !lighterPrivateKey.trim()) return
|
||||
await onSave(
|
||||
selectedExchangeId,
|
||||
exchangeId,
|
||||
exchangeType,
|
||||
trimmedAccountName,
|
||||
lighterPrivateKey.trim(),
|
||||
'',
|
||||
'',
|
||||
@@ -285,16 +331,13 @@ export function ExchangeConfigModal({
|
||||
} else {
|
||||
// 默认情况(其他CEX交易所)
|
||||
if (!apiKey.trim() || !secretKey.trim()) return
|
||||
await onSave(selectedExchangeId, apiKey.trim(), secretKey.trim(), '', testnet)
|
||||
await onSave(exchangeId, exchangeType, trimmedAccountName, apiKey.trim(), secretKey.trim(), '', testnet)
|
||||
}
|
||||
} finally {
|
||||
setIsSaving(false)
|
||||
}
|
||||
}
|
||||
|
||||
// 可选择的交易所列表(所有支持的交易所)
|
||||
const availableExchanges = allExchanges || []
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4 overflow-y-auto">
|
||||
<div
|
||||
@@ -314,7 +357,7 @@ export function ExchangeConfigModal({
|
||||
: t('addExchange', language)}
|
||||
</h3>
|
||||
<div className="flex items-center gap-2">
|
||||
{selectedExchange?.id === 'binance' && (
|
||||
{currentExchangeType === 'binance' && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowGuide(true)}
|
||||
@@ -373,8 +416,8 @@ export function ExchangeConfigModal({
|
||||
{t('environmentSteps.selectTitle', language)}
|
||||
</div>
|
||||
<select
|
||||
value={selectedExchangeId}
|
||||
onChange={(e) => setSelectedExchangeId(e.target.value)}
|
||||
value={selectedExchangeType}
|
||||
onChange={(e) => setSelectedExchangeType(e.target.value)}
|
||||
className="w-full px-3 py-2 rounded"
|
||||
style={{
|
||||
background: '#0B0E11',
|
||||
@@ -391,10 +434,10 @@ export function ExchangeConfigModal({
|
||||
<option value="">
|
||||
{t('pleaseSelectExchange', language)}
|
||||
</option>
|
||||
{availableExchanges.map((exchange) => (
|
||||
<option key={exchange.id} value={exchange.id}>
|
||||
{getShortName(exchange.name)} (
|
||||
{exchange.type.toUpperCase()})
|
||||
{SUPPORTED_EXCHANGE_TEMPLATES.map((template) => (
|
||||
<option key={template.exchange_type} value={template.exchange_type}>
|
||||
{getShortName(template.name)} (
|
||||
{template.type.toUpperCase()})
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
@@ -402,32 +445,65 @@ export function ExchangeConfigModal({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedExchange && (
|
||||
{selectedTemplate && (
|
||||
<div
|
||||
className="p-4 rounded"
|
||||
style={{ background: '#0B0E11', border: '1px solid #2B3139' }}
|
||||
>
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<div className="w-8 h-8 flex items-center justify-center">
|
||||
{getExchangeIcon(selectedExchange.id, {
|
||||
{getExchangeIcon(selectedTemplate.exchange_type, {
|
||||
width: 32,
|
||||
height: 32,
|
||||
})}
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-semibold" style={{ color: '#EAECEF' }}>
|
||||
{getShortName(selectedExchange.name)}
|
||||
{getShortName(selectedTemplate.name)}
|
||||
{editingExchangeId && selectedExchange?.account_name && (
|
||||
<span className="text-sm font-normal ml-2" style={{ color: '#848E9C' }}>
|
||||
- {selectedExchange.account_name}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-xs" style={{ color: '#848E9C' }}>
|
||||
{selectedExchange.type.toUpperCase()} •{' '}
|
||||
{selectedExchange.id}
|
||||
{selectedTemplate.type.toUpperCase()} •{' '}
|
||||
{selectedTemplate.exchange_type}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 账户名称输入 */}
|
||||
<div className="mt-3">
|
||||
<label
|
||||
className="block text-sm font-semibold mb-2"
|
||||
style={{ color: '#EAECEF' }}
|
||||
>
|
||||
{language === 'zh' ? '账户名称' : 'Account Name'} *
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={accountName}
|
||||
onChange={(e) => setAccountName(e.target.value)}
|
||||
placeholder={language === 'zh' ? '例如:主账户、套利账户' : 'e.g., Main Account, Arbitrage Account'}
|
||||
className="w-full px-3 py-2 rounded"
|
||||
style={{
|
||||
background: '#1E2329',
|
||||
border: '1px solid #2B3139',
|
||||
color: '#EAECEF',
|
||||
}}
|
||||
required
|
||||
/>
|
||||
<div className="text-xs mt-1" style={{ color: '#848E9C' }}>
|
||||
{language === 'zh'
|
||||
? '为此账户设置一个易于识别的名称,以便区分同一交易所的多个账户'
|
||||
: 'Set an easily recognizable name for this account to distinguish multiple accounts on the same exchange'}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 注册链接 */}
|
||||
<a
|
||||
href={exchangeRegistrationLinks[selectedExchange.id]?.url || '#'}
|
||||
href={exchangeRegistrationLinks[currentExchangeType || '']?.url || '#'}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center justify-between p-3 rounded-lg mt-3 transition-all hover:scale-[1.02]"
|
||||
@@ -441,7 +517,7 @@ export function ExchangeConfigModal({
|
||||
<span className="text-sm" style={{ color: '#EAECEF' }}>
|
||||
{language === 'zh' ? '还没有交易所账号?点击注册' : "No exchange account? Register here"}
|
||||
</span>
|
||||
{exchangeRegistrationLinks[selectedExchange.id]?.hasReferral && (
|
||||
{exchangeRegistrationLinks[currentExchangeType || '']?.hasReferral && (
|
||||
<span
|
||||
className="text-xs px-1.5 py-0.5 rounded"
|
||||
style={{ background: 'rgba(14, 203, 129, 0.2)', color: '#0ECB81' }}
|
||||
@@ -455,15 +531,15 @@ export function ExchangeConfigModal({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedExchange && (
|
||||
{selectedTemplate && (
|
||||
<>
|
||||
{/* Binance/Bybit/OKX 的输入字段 */}
|
||||
{(selectedExchange.id === 'binance' ||
|
||||
selectedExchange.id === 'bybit' ||
|
||||
selectedExchange.id === 'okx') && (
|
||||
{(currentExchangeType === 'binance' ||
|
||||
currentExchangeType === 'bybit' ||
|
||||
currentExchangeType === 'okx') && (
|
||||
<>
|
||||
{/* 币安用户配置提示 (D1 方案) */}
|
||||
{selectedExchange.id === 'binance' && (
|
||||
{currentExchangeType === 'binance' && (
|
||||
<div
|
||||
className="mb-4 p-3 rounded cursor-pointer transition-colors"
|
||||
style={{
|
||||
@@ -605,7 +681,7 @@ export function ExchangeConfigModal({
|
||||
/>
|
||||
</div>
|
||||
|
||||
{selectedExchange.id === 'okx' && (
|
||||
{currentExchangeType === 'okx' && (
|
||||
<div>
|
||||
<label
|
||||
className="block text-sm font-semibold mb-2"
|
||||
@@ -630,7 +706,7 @@ export function ExchangeConfigModal({
|
||||
)}
|
||||
|
||||
{/* Binance 白名单IP提示 */}
|
||||
{selectedExchange.id === 'binance' && (
|
||||
{currentExchangeType === 'binance' && (
|
||||
<div
|
||||
className="p-4 rounded"
|
||||
style={{
|
||||
@@ -690,7 +766,7 @@ export function ExchangeConfigModal({
|
||||
)}
|
||||
|
||||
{/* Aster 交易所的字段 */}
|
||||
{selectedExchange.id === 'aster' && (
|
||||
{currentExchangeType === 'aster' && (
|
||||
<>
|
||||
{/* API Pro 代理钱包说明 banner */}
|
||||
<div
|
||||
@@ -829,7 +905,7 @@ export function ExchangeConfigModal({
|
||||
)}
|
||||
|
||||
{/* Hyperliquid 交易所的字段 */}
|
||||
{selectedExchange.id === 'hyperliquid' && (
|
||||
{currentExchangeType === 'hyperliquid' && (
|
||||
<>
|
||||
{/* 安全提示 banner */}
|
||||
<div
|
||||
@@ -965,7 +1041,7 @@ export function ExchangeConfigModal({
|
||||
)}
|
||||
|
||||
{/* LIGHTER 特定配置 */}
|
||||
{selectedExchange?.id === 'lighter' && (
|
||||
{currentExchangeType === 'lighter' && (
|
||||
<>
|
||||
{/* L1 Wallet Address */}
|
||||
<div className="mb-4">
|
||||
@@ -1100,30 +1176,31 @@ export function ExchangeConfigModal({
|
||||
type="submit"
|
||||
disabled={
|
||||
isSaving ||
|
||||
!selectedExchange ||
|
||||
(selectedExchange.id === 'binance' &&
|
||||
!selectedTemplate ||
|
||||
!accountName.trim() ||
|
||||
(currentExchangeType === 'binance' &&
|
||||
(!apiKey.trim() || !secretKey.trim())) ||
|
||||
(selectedExchange.id === 'okx' &&
|
||||
(currentExchangeType === 'okx' &&
|
||||
(!apiKey.trim() ||
|
||||
!secretKey.trim() ||
|
||||
!passphrase.trim())) ||
|
||||
(selectedExchange.id === 'hyperliquid' &&
|
||||
(currentExchangeType === 'hyperliquid' &&
|
||||
(!apiKey.trim() || !hyperliquidWalletAddr.trim())) || // 验证私钥和钱包地址
|
||||
(selectedExchange.id === 'aster' &&
|
||||
(currentExchangeType === 'aster' &&
|
||||
(!asterUser.trim() ||
|
||||
!asterSigner.trim() ||
|
||||
!asterPrivateKey.trim())) ||
|
||||
(selectedExchange.id === 'lighter' &&
|
||||
(currentExchangeType === 'lighter' &&
|
||||
(!lighterWalletAddr.trim() || !lighterPrivateKey.trim())) ||
|
||||
(selectedExchange.id === 'bybit' &&
|
||||
(currentExchangeType === 'bybit' &&
|
||||
(!apiKey.trim() || !secretKey.trim())) ||
|
||||
(selectedExchange.type === 'cex' &&
|
||||
selectedExchange.id !== 'hyperliquid' &&
|
||||
selectedExchange.id !== 'aster' &&
|
||||
selectedExchange.id !== 'lighter' &&
|
||||
selectedExchange.id !== 'binance' &&
|
||||
selectedExchange.id !== 'bybit' &&
|
||||
selectedExchange.id !== 'okx' &&
|
||||
(selectedTemplate?.type === 'cex' &&
|
||||
currentExchangeType !== 'hyperliquid' &&
|
||||
currentExchangeType !== 'aster' &&
|
||||
currentExchangeType !== 'lighter' &&
|
||||
currentExchangeType !== 'binance' &&
|
||||
currentExchangeType !== 'bybit' &&
|
||||
currentExchangeType !== 'okx' &&
|
||||
(!apiKey.trim() || !secretKey.trim()))
|
||||
}
|
||||
className="flex-1 px-4 py-2 rounded text-sm font-semibold disabled:opacity-50"
|
||||
|
||||
@@ -45,17 +45,20 @@ export function ExchangesSection({
|
||||
>
|
||||
<div className="flex items-center gap-2 md:gap-3">
|
||||
<div className="w-7 h-7 md:w-8 md:h-8 flex items-center justify-center flex-shrink-0">
|
||||
{getExchangeIcon(exchange.id, { width: 28, height: 28 })}
|
||||
{getExchangeIcon(exchange.exchange_type, { width: 28, height: 28 })}
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<div
|
||||
className="font-semibold text-sm md:text-base truncate"
|
||||
style={{ color: '#EAECEF' }}
|
||||
>
|
||||
{getShortName(exchange.name)}
|
||||
{exchange.exchange_type?.toUpperCase() || getShortName(exchange.name)}
|
||||
<span className="text-xs font-normal ml-1.5" style={{ color: '#F0B90B' }}>
|
||||
- {exchange.account_name || 'Default'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-xs" style={{ color: '#848E9C' }}>
|
||||
{exchange.type.toUpperCase()} •{' '}
|
||||
{exchange.type?.toUpperCase() || 'CEX'} •{' '}
|
||||
{inUse
|
||||
? t('inUse', language)
|
||||
: exchange.enabled
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { Bot, BarChart3, Trash2, Pencil } from 'lucide-react'
|
||||
import { t, type Language } from '../../../i18n/translations'
|
||||
import { getModelDisplayName } from '../index'
|
||||
import type { TraderInfo } from '../../../types'
|
||||
import type { TraderInfo, Exchange } from '../../../types'
|
||||
import { PunkAvatar, getTraderAvatar } from '../../PunkAvatar'
|
||||
|
||||
interface TradersGridProps {
|
||||
language: Language
|
||||
traders: TraderInfo[] | undefined
|
||||
exchanges?: Exchange[]
|
||||
onTraderSelect: (traderId: string) => void
|
||||
onEditTrader: (traderId: string) => void
|
||||
onDeleteTrader: (traderId: string) => void
|
||||
@@ -16,11 +17,20 @@ interface TradersGridProps {
|
||||
export function TradersGrid({
|
||||
language,
|
||||
traders,
|
||||
exchanges = [],
|
||||
onTraderSelect,
|
||||
onEditTrader,
|
||||
onDeleteTrader,
|
||||
onToggleTrader,
|
||||
}: TradersGridProps) {
|
||||
// Helper function to get exchange display name
|
||||
const getExchangeDisplayName = (exchangeId: string | undefined) => {
|
||||
if (!exchangeId) return 'Unknown'
|
||||
const exchange = exchanges.find(e => e.id === exchangeId)
|
||||
if (!exchange) return exchangeId.toUpperCase()
|
||||
const typeName = exchange.exchange_type?.toUpperCase() || exchange.name
|
||||
return exchange.account_name ? `${typeName} - ${exchange.account_name}` : typeName
|
||||
}
|
||||
if (!traders || traders.length === 0) {
|
||||
return (
|
||||
<div className="text-center py-12 md:py-16" style={{ color: '#848E9C' }}>
|
||||
@@ -74,7 +84,7 @@ export function TradersGrid({
|
||||
{getModelDisplayName(
|
||||
trader.ai_model.split('_').pop() || trader.ai_model
|
||||
)}{' '}
|
||||
Model • {trader.exchange_id?.toUpperCase()}
|
||||
Model • {getExchangeDisplayName(trader.exchange_id)}
|
||||
<span style={{ color: '#F0B90B' }}> • {trader.strategy_name || 'No Strategy'}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user