From 970fa61f09b81da807f39e397ff0edf4b4679e5f Mon Sep 17 00:00:00 2001 From: CoderMageFox Date: Wed, 5 Nov 2025 12:37:04 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8DInitialBalance?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E9=94=99=E8=AF=AF=E5=AF=BC=E8=87=B4=E7=9A=84?= =?UTF-8?q?P&L=E7=BB=9F=E8=AE=A1=E4=B8=8D=E5=87=86=E7=A1=AE=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 用户在使用Aster交易员时发现,即使没有开始交易,P&L统计也显示了12.5 USDT (83.33%)的盈亏。经过调查发现: **根本原因**: - 实际Aster账户余额:27.5 USDT - Web界面配置的InitialBalance:15 USDT ❌ - 错误的P&L计算:27.5 - 15 = 12.5 USDT (83.33%) **问题根源**: 1. Web界面创建交易员时默认initial_balance为1000 USDT 2. 用户手动修改时容易输入错误的值 3. 缺少自动获取实际余额的功能 4. 缺少明确的警告提示 **文件**: `trader/aster_trader.go` - ✅ 验证Aster API完全兼容Binance格式 - 添加详细的注释说明字段含义 - 添加调试日志以便排查问题 - 确认balance字段不包含未实现盈亏(与Binance一致) **关键确认**: ```go // ✅ Aster API完全兼容Binance API格式 // balance字段 = wallet balance(不包含未实现盈亏) // crossUnPnl = unrealized profit(未实现盈亏) // crossWalletBalance = balance + crossUnPnl(全仓钱包余额,包含盈亏) ``` **文件**: `web/src/components/TraderConfigModal.tsx` **新增功能**: 1. **编辑模式**:添加"获取当前余额"按钮 - 一键从交易所API获取当前账户净值 - 自动填充到InitialBalance字段 - 显示加载状态和错误提示 2. **创建模式**:添加警告提示 - ⚠️ 提醒用户必须输入交易所的当前实际余额 - 警告:如果输入不准确,P&L统计将会错误 3. **改进输入体验**: - 支持小数输入(step="0.01") - 必填字段标记(创建模式) - 实时错误提示 **代码实现**: ```typescript const handleFetchCurrentBalance = async () => { const response = await fetch(`/api/account?trader_id=${traderData.trader_id}`); const data = await response.json(); const currentBalance = data.total_equity; // 当前净值 setFormData(prev => ({ ...prev, initial_balance: currentBalance })); }; ``` 通过查阅Binance官方文档确认: | 项目 | Binance | Aster (修复后) | |------|---------|----------------| | **余额字段** | balance = 钱包余额(不含盈亏) | ✅ 相同 | | **盈亏字段** | crossUnPnl = 未实现盈亏 | ✅ 相同 | | **总权益** | balance + crossUnPnl | ✅ 相同 | | **P&L计算** | totalEquity - initialBalance | ✅ 相同 | 1. 编辑交易员配置 2. 点击"获取当前余额"按钮 3. 系统自动填充正确的InitialBalance 4. 保存配置 1. 查看交易所账户的实际余额 2. 准确输入到InitialBalance字段 3. 注意查看警告提示 4. 完成创建 - [x] 确认Aster API返回格式与Binance一致 - [x] 验证"获取当前余额"功能正常工作 - [x] 确认P&L计算公式正确 - [x] 前端构建成功 - [x] 警告提示正常显示 - **修复**: 解决InitialBalance配置错误导致的P&L统计不准确问题 - **改进**: 提升用户体验,减少配置错误 - **兼容**: 完全向后兼容,不影响现有功能 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: tinkle-community --- trader/aster_trader.go | 28 ++++++++- web/src/components/TraderConfigModal.tsx | 76 ++++++++++++++++++++++-- 2 files changed, 98 insertions(+), 6 deletions(-) diff --git a/trader/aster_trader.go b/trader/aster_trader.go index d9ba82a6..15128e13 100644 --- a/trader/aster_trader.go +++ b/trader/aster_trader.go @@ -438,13 +438,23 @@ func (t *AsterTrader) GetBalance() (map[string]interface{}, error) { return nil, err } + // 🔍 调试:打印原始API响应 + log.Printf("🔍 Aster API原始响应: %s", string(body)) + // 查找USDT余额 totalBalance := 0.0 availableBalance := 0.0 crossUnPnl := 0.0 for _, bal := range balances { + // 🔍 调试:打印每条余额记录 + log.Printf("🔍 余额记录: %+v", bal) + if asset, ok := bal["asset"].(string); ok && asset == "USDT" { + // 🔍 调试:打印USDT余额详情 + log.Printf("🔍 USDT余额详情: balance=%v, availableBalance=%v, crossUnPnl=%v", + bal["balance"], bal["availableBalance"], bal["crossUnPnl"]) + if wb, ok := bal["balance"].(string); ok { totalBalance, _ = strconv.ParseFloat(wb, 64) } @@ -458,11 +468,25 @@ func (t *AsterTrader) GetBalance() (map[string]interface{}, error) { } } + // ✅ Aster API完全兼容Binance API格式 + // balance字段 = wallet balance(不包含未实现盈亏) + // crossUnPnl = unrealized profit(未实现盈亏) + // crossWalletBalance = balance + crossUnPnl(全仓钱包余额,包含盈亏) + // + // 参考Binance官方文档: + // - Account Information V2: marginBalance = walletBalance + unrealizedProfit + // - Balance V3: crossWalletBalance = balance + crossUnPnl + + log.Printf("✓ Aster API返回: 钱包余额=%.2f, 未实现盈亏=%.2f, 可用余额=%.2f", + totalBalance, + crossUnPnl, + availableBalance) + // 返回与Binance相同的字段名,确保AutoTrader能正确解析 return map[string]interface{}{ - "totalWalletBalance": totalBalance, + "totalWalletBalance": totalBalance, // 钱包余额(不含未实现盈亏) "availableBalance": availableBalance, - "totalUnrealizedProfit": crossUnPnl, + "totalUnrealizedProfit": crossUnPnl, // 未实现盈亏 }, nil } diff --git a/web/src/components/TraderConfigModal.tsx b/web/src/components/TraderConfigModal.tsx index f96c9070..e0c7c0bf 100644 --- a/web/src/components/TraderConfigModal.tsx +++ b/web/src/components/TraderConfigModal.tsx @@ -68,6 +68,8 @@ export function TraderConfigModal({ 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) { @@ -182,6 +184,45 @@ export function TraderConfigModal({ }) } + 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 @@ -346,9 +387,22 @@ export function TraderConfigModal({
- +
+ + {isEditMode && ( + + )} +
+ {!isEditMode && ( +

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

+ )} + {isEditMode && ( +

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

+ )} + {balanceFetchError && ( +

{balanceFetchError}

+ )}