diff --git a/README.md b/README.md index ba9305fa..04073116 100644 --- a/README.md +++ b/README.md @@ -465,9 +465,11 @@ cp config.json.example config.json { "id": "hyperliquid_trader", "name": "My Hyperliquid Trader", + "enabled": true, "ai_model": "deepseek", "exchange": "hyperliquid", "hyperliquid_private_key": "your_private_key_without_0x", + "hyperliquid_wallet_addr": "your_ethereum_address", "hyperliquid_testnet": false, "deepseek_key": "sk-xxxxxxxxxxxxx", "initial_balance": 1000.0, @@ -517,13 +519,14 @@ cp config.json.example config.json { "id": "aster_deepseek", "name": "Aster DeepSeek Trader", + "enabled": true, "ai_model": "deepseek", "exchange": "aster", - + "aster_user": "0x63DD5aCC6b1aa0f563956C0e534DD30B6dcF7C4e", "aster_signer": "0x21cF8Ae13Bb72632562c6Fff438652Ba1a151bb0", "aster_private_key": "4fd0a42218f3eae43a6ce26d22544e986139a01e5b34a62db53757ffca81bae1", - + "deepseek_key": "sk-xxxxxxxxxxxxx", "initial_balance": 1000.0, "scan_interval_minutes": 3 @@ -605,9 +608,14 @@ For running multiple AI traders competing against each other: |-------|-------------|---------------|-----------| | `id` | Unique identifier for this trader | `"my_trader"` | ✅ Yes | | `name` | Display name | `"My AI Trader"` | ✅ Yes | -| `ai_model` | AI provider to use | `"deepseek"` or `"qwen"` | ✅ Yes | -| `binance_api_key` | Binance API key | `"abc123..."` | ✅ Yes | -| `binance_secret_key` | Binance Secret key | `"xyz789..."` | ✅ Yes | +| `enabled` | Whether this trader is enabled
Set to `false` to skip startup | `true` or `false` | ✅ Yes | +| `ai_model` | AI provider to use | `"deepseek"` or `"qwen"` or `"custom"` | ✅ Yes | +| `exchange` | Exchange to use | `"binance"` or `"hyperliquid"` or `"aster"` | ✅ Yes | +| `binance_api_key` | Binance API key | `"abc123..."` | Required when using Binance | +| `binance_secret_key` | Binance Secret key | `"xyz789..."` | Required when using Binance | +| `hyperliquid_private_key` | Hyperliquid private key
⚠️ Remove `0x` prefix | `"your_key..."` | Required when using Hyperliquid | +| `hyperliquid_wallet_addr` | Hyperliquid wallet address | `"0xabc..."` | Required when using Hyperliquid | +| `hyperliquid_testnet` | Use testnet | `true` or `false` | ❌ No (defaults to false) | | `use_qwen` | Whether to use Qwen | `true` or `false` | ✅ Yes | | `deepseek_key` | DeepSeek API key | `"sk-xxx"` | If using DeepSeek | | `qwen_key` | Qwen API key | `"sk-xxx"` | If using Qwen | diff --git a/README.ru.md b/README.ru.md index 51d8054a..4761e106 100644 --- a/README.ru.md +++ b/README.ru.md @@ -398,9 +398,11 @@ cp config.json.example config.json { "id": "hyperliquid_trader", "name": "My Hyperliquid Trader", + "enabled": true, "ai_model": "deepseek", "exchange": "hyperliquid", "hyperliquid_private_key": "your_private_key_without_0x", + "hyperliquid_wallet_addr": "your_ethereum_address", "hyperliquid_testnet": false, "deepseek_key": "sk-xxxxxxxxxxxxx", "initial_balance": 1000.0, @@ -450,13 +452,14 @@ cp config.json.example config.json { "id": "aster_deepseek", "name": "Aster DeepSeek Trader", + "enabled": true, "ai_model": "deepseek", "exchange": "aster", - + "aster_user": "0x63DD5aCC6b1aa0f563956C0e534DD30B6dcF7C4e", "aster_signer": "0x21cF8Ae13Bb72632562c6Fff438652Ba1a151bb0", "aster_private_key": "4fd0a42218f3eae43a6ce26d22544e986139a01e5b34a62db53757ffca81bae1", - + "deepseek_key": "sk-xxxxxxxxxxxxx", "initial_balance": 1000.0, "scan_interval_minutes": 3 @@ -536,9 +539,14 @@ cp config.json.example config.json |------|----------|-----------------|--------------| | `id` | Уникальный идентификатор для этого трейдера | `"my_trader"` | ✅ Да | | `name` | Отображаемое имя | `"Мой AI Трейдер"` | ✅ Да | -| `ai_model` | Используемый AI провайдер | `"deepseek"` или `"qwen"` | ✅ Да | -| `binance_api_key` | Binance API ключ | `"abc123..."` | ✅ Да | -| `binance_secret_key` | Binance Secret ключ | `"xyz789..."` | ✅ Да | +| `enabled` | Включен ли этот трейдер
Установите в `false` для пропуска запуска | `true` или `false` | ✅ Да | +| `ai_model` | Используемый AI провайдер | `"deepseek"` или `"qwen"` или `"custom"` | ✅ Да | +| `exchange` | Используемая биржа | `"binance"` или `"hyperliquid"` или `"aster"` | ✅ Да | +| `binance_api_key` | Binance API ключ | `"abc123..."` | Требуется при использовании Binance | +| `binance_secret_key` | Binance Secret ключ | `"xyz789..."` | Требуется при использовании Binance | +| `hyperliquid_private_key` | Hyperliquid приватный ключ
⚠️ Удалите префикс `0x` | `"your_key..."` | Требуется при использовании Hyperliquid | +| `hyperliquid_wallet_addr` | Hyperliquid адрес кошелька | `"0xabc..."` | Требуется при использовании Hyperliquid | +| `hyperliquid_testnet` | Использовать тестнет | `true` или `false` | ❌ Нет (по умолчанию false) | | `use_qwen` | Использовать ли Qwen | `true` или `false` | ✅ Да | | `deepseek_key` | DeepSeek API ключ | `"sk-xxx"` | Требуется при использовании DeepSeek | | `qwen_key` | Qwen API ключ | `"sk-xxx"` | Требуется при использовании Qwen | diff --git a/README.uk.md b/README.uk.md index d47c5921..55ef2584 100644 --- a/README.uk.md +++ b/README.uk.md @@ -398,9 +398,11 @@ cp config.json.example config.json { "id": "hyperliquid_trader", "name": "My Hyperliquid Trader", + "enabled": true, "ai_model": "deepseek", "exchange": "hyperliquid", "hyperliquid_private_key": "your_private_key_without_0x", + "hyperliquid_wallet_addr": "your_ethereum_address", "hyperliquid_testnet": false, "deepseek_key": "sk-xxxxxxxxxxxxx", "initial_balance": 1000.0, @@ -450,13 +452,14 @@ cp config.json.example config.json { "id": "aster_deepseek", "name": "Aster DeepSeek Trader", + "enabled": true, "ai_model": "deepseek", "exchange": "aster", - + "aster_user": "0x63DD5aCC6b1aa0f563956C0e534DD30B6dcF7C4e", "aster_signer": "0x21cF8Ae13Bb72632562c6Fff438652Ba1a151bb0", "aster_private_key": "4fd0a42218f3eae43a6ce26d22544e986139a01e5b34a62db53757ffca81bae1", - + "deepseek_key": "sk-xxxxxxxxxxxxx", "initial_balance": 1000.0, "scan_interval_minutes": 3 @@ -536,9 +539,14 @@ cp config.json.example config.json |------|------|------------------|--------------| | `id` | Унікальний ідентифікатор для цього трейдера | `"my_trader"` | ✅ Так | | `name` | Відображуване ім'я | `"Мій AI Трейдер"` | ✅ Так | -| `ai_model` | Використовуваний AI провайдер | `"deepseek"` або `"qwen"` | ✅ Так | -| `binance_api_key` | Binance API ключ | `"abc123..."` | ✅ Так | -| `binance_secret_key` | Binance Secret ключ | `"xyz789..."` | ✅ Так | +| `enabled` | Чи увімкнений цей трейдер
Встановіть в `false` для пропуску запуску | `true` або `false` | ✅ Так | +| `ai_model` | Використовуваний AI провайдер | `"deepseek"` або `"qwen"` або `"custom"` | ✅ Так | +| `exchange` | Використовувана біржа | `"binance"` або `"hyperliquid"` або `"aster"` | ✅ Так | +| `binance_api_key` | Binance API ключ | `"abc123..."` | Потрібно при використанні Binance | +| `binance_secret_key` | Binance Secret ключ | `"xyz789..."` | Потрібно при використанні Binance | +| `hyperliquid_private_key` | Hyperliquid приватний ключ
⚠️ Видаліть префікс `0x` | `"your_key..."` | Потрібно при використанні Hyperliquid | +| `hyperliquid_wallet_addr` | Hyperliquid адреса гаманця | `"0xabc..."` | Потрібно при використанні Hyperliquid | +| `hyperliquid_testnet` | Використовувати тестнет | `true` або `false` | ❌ Ні (за замовчуванням false) | | `use_qwen` | Використовувати чи Qwen | `true` або `false` | ✅ Так | | `deepseek_key` | DeepSeek API ключ | `"sk-xxx"` | Потрібно при використанні DeepSeek | | `qwen_key` | Qwen API ключ | `"sk-xxx"` | Потрібно при використанні Qwen | diff --git a/README.zh-CN.md b/README.zh-CN.md index 9b47fa04..dbdb2df9 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -461,9 +461,11 @@ cp config.json.example config.json { "id": "hyperliquid_trader", "name": "My Hyperliquid Trader", + "enabled": true, "ai_model": "deepseek", "exchange": "hyperliquid", "hyperliquid_private_key": "your_private_key_without_0x", + "hyperliquid_wallet_addr": "your_ethereum_address", "hyperliquid_testnet": false, "deepseek_key": "sk-xxxxxxxxxxxxx", "initial_balance": 1000.0, @@ -513,13 +515,14 @@ cp config.json.example config.json { "id": "aster_deepseek", "name": "Aster DeepSeek Trader", + "enabled": true, "ai_model": "deepseek", "exchange": "aster", - + "aster_user": "0x63DD5aCC6b1aa0f563956C0e534DD30B6dcF7C4e", "aster_signer": "0x21cF8Ae13Bb72632562c6Fff438652Ba1a151bb0", "aster_private_key": "4fd0a42218f3eae43a6ce26d22544e986139a01e5b34a62db53757ffca81bae1", - + "deepseek_key": "sk-xxxxxxxxxxxxx", "initial_balance": 1000.0, "scan_interval_minutes": 3 @@ -599,9 +602,14 @@ cp config.json.example config.json |-----|------|--------|-----------| | `id` | 此trader的唯一标识符 | `"my_trader"` | ✅ 是 | | `name` | 显示名称 | `"我的AI交易员"` | ✅ 是 | -| `ai_model` | 使用的AI提供商 | `"deepseek"` 或 `"qwen"` | ✅ 是 | -| `binance_api_key` | 币安API密钥 | `"abc123..."` | ✅ 是 | -| `binance_secret_key` | 币安Secret密钥 | `"xyz789..."` | ✅ 是 | +| `enabled` | 是否启用此trader
设为`false`可跳过启动 | `true` 或 `false` | ✅ 是 | +| `ai_model` | 使用的AI提供商 | `"deepseek"` 或 `"qwen"` 或 `"custom"` | ✅ 是 | +| `exchange` | 使用的交易所 | `"binance"` 或 `"hyperliquid"` 或 `"aster"` | ✅ 是 | +| `binance_api_key` | 币安API密钥 | `"abc123..."` | 使用Binance时必填 | +| `binance_secret_key` | 币安Secret密钥 | `"xyz789..."` | 使用Binance时必填 | +| `hyperliquid_private_key` | Hyperliquid私钥
⚠️ 去掉`0x`前缀 | `"your_key..."` | 使用Hyperliquid时必填 | +| `hyperliquid_wallet_addr` | Hyperliquid钱包地址 | `"0xabc..."` | 使用Hyperliquid时必填 | +| `hyperliquid_testnet` | 是否使用测试网 | `true` 或 `false` | ❌ 否(默认false) | | `use_qwen` | 是否使用Qwen | `true` 或 `false` | ✅ 是 | | `deepseek_key` | DeepSeek API密钥 | `"sk-xxx"` | 使用DeepSeek时必填 | | `qwen_key` | Qwen API密钥 | `"sk-xxx"` | 使用Qwen时必填 | diff --git a/config.json.example b/config.json.example index 8f41523e..1dfc1786 100644 --- a/config.json.example +++ b/config.json.example @@ -3,6 +3,7 @@ { "id": "hyperliquid_deepseek", "name": "Hyperliquid DeepSeek Trader", + "enabled": true, "ai_model": "deepseek", "exchange": "hyperliquid", "hyperliquid_private_key": "your_ethereum_private_key_without_0x_prefix", @@ -15,6 +16,7 @@ { "id": "binance_qwen", "name": "Binance Qwen Trader", + "enabled": true, "ai_model": "qwen", "exchange": "binance", "binance_api_key": "your_binance_api_key", @@ -26,6 +28,7 @@ { "id": "binance_custom", "name": "Binance Custom API Trader", + "enabled": false, "ai_model": "custom", "exchange": "binance", "binance_api_key": "your_binance_api_key", @@ -35,10 +38,11 @@ "custom_model_name": "gpt-4o", "initial_balance": 1000, "scan_interval_minutes": 3 - }, + }, { "id": "aster_deepseek", "name": "Aster DeepSeek Trader", + "enabled": true, "ai_model": "deepseek", "exchange": "aster", @@ -47,7 +51,7 @@ // signer: API钱包地址 (点击生成地址后生成的地址) // privateKey: API钱包私钥 (生成地址对应的私钥) - "aster_user": "0x63DD5aCC6b1aa0f563956C0e534DD30B6dcF7C4e", + "aster_user": "0x63DD5aCC6b1aa0f563956C0e534DD30B6dcF7C4e", "aster_signer": "0x21cF8Ae13Bb72632562c6Fff438652Ba1a151bb0", "aster_private_key": "your_aster_api_wallet_private_key_without_0x_prefix", @@ -69,7 +73,7 @@ "XRPUSDT", "DOGEUSDT", "ADAUSDT", - "HYPEUSDT", + "HYPEUSDT" ], "coin_pool_api_url": "", "oi_top_api_url": "", diff --git a/config/config.go b/config/config.go index 8b843b53..75298663 100644 --- a/config/config.go +++ b/config/config.go @@ -11,6 +11,7 @@ import ( type TraderConfig struct { ID string `json:"id"` Name string `json:"name"` + Enabled bool `json:"enabled"` // 是否启用该trader AIModel string `json:"ai_model"` // "qwen" or "deepseek" // 交易平台选择(二选一) diff --git a/main.go b/main.go index 66cbfca4..12d66cea 100644 --- a/main.go +++ b/main.go @@ -56,8 +56,16 @@ func main() { // 创建TraderManager traderManager := manager.NewTraderManager() - // 添加所有trader + // 添加所有启用的trader + enabledCount := 0 for i, traderCfg := range cfg.Traders { + // 跳过未启用的trader + if !traderCfg.Enabled { + log.Printf("⏭️ [%d/%d] 跳过未启用的 %s", i+1, len(cfg.Traders), traderCfg.Name) + continue + } + + enabledCount++ log.Printf("📦 [%d/%d] 初始化 %s (%s模型)...", i+1, len(cfg.Traders), traderCfg.Name, strings.ToUpper(traderCfg.AIModel)) @@ -74,9 +82,18 @@ func main() { } } + // 检查是否至少有一个启用的trader + if enabledCount == 0 { + log.Fatalf("❌ 没有启用的trader,请在config.json中设置至少一个trader的enabled=true") + } + fmt.Println() fmt.Println("🏁 竞赛参赛者:") for _, traderCfg := range cfg.Traders { + // 只显示启用的trader + if !traderCfg.Enabled { + continue + } fmt.Printf(" • %s (%s) - 初始资金: %.0f USDT\n", traderCfg.Name, strings.ToUpper(traderCfg.AIModel), traderCfg.InitialBalance) } diff --git a/trader/auto_trader.go b/trader/auto_trader.go index 42bc2e69..c181dc25 100644 --- a/trader/auto_trader.go +++ b/trader/auto_trader.go @@ -427,14 +427,6 @@ func (at *AutoTrader) buildTradingContext() (*decision.Context, error) { unrealizedPnl := pos["unRealizedProfit"].(float64) liquidationPrice := pos["liquidationPrice"].(float64) - // 计算盈亏百分比 - pnlPct := 0.0 - if side == "long" { - pnlPct = ((markPrice - entryPrice) / entryPrice) * 100 - } else { - pnlPct = ((entryPrice - markPrice) / entryPrice) * 100 - } - // 计算占用保证金(估算) leverage := 10 // 默认值,实际应该从持仓信息获取 if lev, ok := pos["leverage"].(float64); ok { @@ -443,6 +435,14 @@ func (at *AutoTrader) buildTradingContext() (*decision.Context, error) { marginUsed := (quantity * markPrice) / float64(leverage) totalMarginUsed += marginUsed + // 计算盈亏百分比 + pnlPct := 0.0 + if side == "long" { + pnlPct = ((markPrice - entryPrice) / entryPrice) * float64(leverage) * 100 + } else { + pnlPct = ((entryPrice - markPrice) / entryPrice) * float64(leverage) * 100 + } + // 跟踪持仓首次出现时间 posKey := symbol + "_" + side currentPositionKeys[posKey] = true @@ -873,9 +873,9 @@ func (at *AutoTrader) GetPositions() ([]map[string]interface{}, error) { pnlPct := 0.0 if side == "long" { - pnlPct = ((markPrice - entryPrice) / entryPrice) * 100 + pnlPct = ((markPrice - entryPrice) / entryPrice) * float64(leverage) * 100 } else { - pnlPct = ((entryPrice - markPrice) / entryPrice) * 100 + pnlPct = ((entryPrice - markPrice) / entryPrice) * float64(leverage) * 100 } marginUsed := (quantity * markPrice) / float64(leverage) diff --git a/trader/hyperliquid_trader.go b/trader/hyperliquid_trader.go index c37494d6..fdd646e0 100644 --- a/trader/hyperliquid_trader.go +++ b/trader/hyperliquid_trader.go @@ -2,6 +2,7 @@ package trader import ( "context" + "encoding/json" "fmt" "log" "strconv" @@ -83,8 +84,13 @@ func (t *HyperliquidTrader) GetBalance() (map[string]interface{}, error) { // 解析余额信息(MarginSummary字段都是string) result := make(map[string]interface{}) - accountValue, _ := strconv.ParseFloat(accountState.CrossMarginSummary.AccountValue, 64) - totalMarginUsed, _ := strconv.ParseFloat(accountState.CrossMarginSummary.TotalMarginUsed, 64) + // 🔍 调试:打印API返回的完整CrossMarginSummary结构 + summaryJSON, _ := json.MarshalIndent(accountState.MarginSummary, " ", " ") + log.Printf("🔍 [DEBUG] Hyperliquid API CrossMarginSummary完整数据:") + log.Printf("%s", string(summaryJSON)) + + accountValue, _ := strconv.ParseFloat(accountState.MarginSummary.AccountValue, 64) + totalMarginUsed, _ := strconv.ParseFloat(accountState.MarginSummary.TotalMarginUsed, 64) // ⚠️ 关键修复:从所有持仓中累加真正的未实现盈亏 totalUnrealizedPnl := 0.0 @@ -94,19 +100,23 @@ func (t *HyperliquidTrader) GetBalance() (map[string]interface{}, error) { } // ✅ 正确理解Hyperliquid字段: - // AccountValue = 账户净值(包含未实现盈亏)= 这是真正的总资产 - // 钱包余额(已实现)= AccountValue - 未实现盈亏 - walletBalance := accountValue - totalUnrealizedPnl + // AccountValue = 总账户净值(已包含空闲资金+持仓价值+未实现盈亏) + // TotalMarginUsed = 持仓占用的保证金(已包含在AccountValue中,仅用于显示) + // + // 为了兼容auto_trader.go的计算逻辑(totalEquity = totalWalletBalance + totalUnrealizedProfit) + // 需要返回"不包含未实现盈亏的钱包余额" + walletBalanceWithoutUnrealized := accountValue - totalUnrealizedPnl - result["totalWalletBalance"] = walletBalance // 钱包余额(已实现部分) - result["availableBalance"] = accountValue - totalMarginUsed // 可用余额 - result["totalUnrealizedProfit"] = totalUnrealizedPnl // 未实现盈亏 + result["totalWalletBalance"] = walletBalanceWithoutUnrealized // 钱包余额(不含未实现盈亏) + result["availableBalance"] = accountValue - totalMarginUsed // 可用余额(总净值 - 占用保证金) + result["totalUnrealizedProfit"] = totalUnrealizedPnl // 未实现盈亏 - log.Printf("✓ Hyperliquid API返回: 账户净值=%.2f, 钱包余额=%.2f, 可用=%.2f, 未实现盈亏=%.2f", + log.Printf("✓ Hyperliquid 账户: 总净值=%.2f (钱包%.2f+未实现%.2f), 可用=%.2f, 保证金占用=%.2f", accountValue, - result["totalWalletBalance"], + walletBalanceWithoutUnrealized, + totalUnrealizedPnl, result["availableBalance"], - result["totalUnrealizedProfit"]) + totalMarginUsed) return result, nil } diff --git a/web/src/App.tsx b/web/src/App.tsx index 69992f19..3ec4e672 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -19,10 +19,38 @@ type Page = 'competition' | 'trader'; function App() { const { language, setLanguage } = useLanguage(); - const [currentPage, setCurrentPage] = useState('competition'); + + // 从URL hash读取初始页面状态(支持刷新保持页面) + const getInitialPage = (): Page => { + const hash = window.location.hash.slice(1); // 去掉 # + return hash === 'trader' || hash === 'details' ? 'trader' : 'competition'; + }; + + const [currentPage, setCurrentPage] = useState(getInitialPage()); const [selectedTraderId, setSelectedTraderId] = useState(); const [lastUpdate, setLastUpdate] = useState('--:--:--'); + // 监听URL hash变化,同步页面状态 + useEffect(() => { + const handleHashChange = () => { + const hash = window.location.hash.slice(1); + if (hash === 'trader' || hash === 'details') { + setCurrentPage('trader'); + } else if (hash === 'competition' || hash === '') { + setCurrentPage('competition'); + } + }; + + window.addEventListener('hashchange', handleHashChange); + return () => window.removeEventListener('hashchange', handleHashChange); + }, []); + + // 切换页面时更新URL hash + const navigateToPage = (page: Page) => { + setCurrentPage(page); + window.location.hash = page === 'competition' ? '' : 'trader'; + }; + // 获取trader列表 const { data: traders } = useSWR('traders', api.getTraders, { refreshInterval: 10000, @@ -180,7 +208,7 @@ function App() { {/* Page Toggle */}