-
Notifications
You must be signed in to change notification settings - Fork 2k
fix(api): use total equity instead of available balance for P&L calculation #668
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…lation Fixes NoFxAiOS#637 ## Problem System was using `availableBalance` (available funds) instead of `totalEquity` (total account value), causing incorrect P&L calculations. ### Root Cause 1. When positions opened → margin locked → available balance decreased 2. System incorrectly detected this as "withdrawal" (balance change >5%) 3. System reset `initialBalance` to the lower available balance 4. Subsequent P&L calculated from wrong baseline ### Real Example (from Issue NoFxAiOS#637) ``` Initial: $200 USDC After opening position: - Total Equity: $194 USDC (actual account value) - Available Balance: $167 USDC (locked $27 margin) System incorrectly: - Detected "withdrawal": $200 → $167 (-16.35%) - Reset initialBalance to $167 Later position loses more: - Total Equity: $194 USDC - Available Balance: $142 USDC System again incorrectly: - Detected "withdrawal": $167 → $142 (-14.76%) - Reset initialBalance to $142 Final display: - Real P&L: $194 - $200 = -$6 (-3%) ✓ - Displayed: $194 - $142 = +$52 (+36%) ✗ ``` ## Solution Use `totalEquity` (wallet balance + unrealized P&L) instead of `availableBalance` in: - `handleCreateTrader` (L393-419): Set initial balance when creating trader - `handleSyncBalance` (L794-824): Sync balance from exchange ## Changes ### handleCreateTrader (L393-419) ```go // Before: Used availableBalance if availableBalance, ok := balanceInfo["available_balance"].(float64); ok && availableBalance > 0 { actualBalance = availableBalance } // After: Use totalEquity totalWalletBalance := balanceInfo["totalWalletBalance"].(float64) totalUnrealizedProfit := balanceInfo["totalUnrealizedProfit"].(float64) actualBalance = totalWalletBalance + totalUnrealizedProfit ``` ### handleSyncBalance (L794-824) Same pattern - replace availableBalance with totalEquity calculation. ### Enhanced Logging ``` ✓ 查询到交易所总资产余额: 194.14 USDT (钱包: 194.80 + 未实现: -0.66, 用户输入: 200.00 USDT) ``` ## Impact - ✅ Fixes all users' P&L display accuracy - ✅ Prevents false withdrawal detection from margin usage - ✅ Properly tracks actual account value changes ## Testing - ✅ Compilation: `go build` - ✅ Logic verification: Matches Issue NoFxAiOS#637 analysis - ✅ Addresses all 4 comments in the issue thread --- 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
🤖 Advisory Check ResultsThese are advisory checks to help improve code quality. They won't block your PR from being merged. 📋 PR InformationTitle Format: ✅ Good - Follows Conventional Commits 🔧 Backend ChecksGo Formatting: Files needing formattingGo Vet: ✅ Good Fix locally: go fmt ./... # Format code
go vet ./... # Check for issues
go test ./... # Run tests⚛️ Frontend ChecksBuild & Type Check: ✅ Success Fix locally: cd web
npm run build # Test build (includes type checking)📖 ResourcesQuestions? Feel free to ask in the comments! 🙏 These checks are advisory and won't block your PR from being merged. This comment is automatically generated from pr-checks-run.yml. |
|
建议拆分函数,或者甚至拆分出 util 方法,专门用于此类计算。 避免逻辑都放在同一个文件中,便于后期维护 @zhouyongyou |
| totalWalletBalance := 0.0 | ||
| totalUnrealizedProfit := 0.0 | ||
|
|
||
| if wallet, ok := balanceInfo["totalWalletBalance"].(float64); ok { | ||
| totalWalletBalance = wallet | ||
| } | ||
| if unrealized, ok := balanceInfo["totalUnrealizedProfit"].(float64); ok { | ||
| totalUnrealizedProfit = unrealized | ||
| } | ||
|
|
||
| // 总资产 = 钱包余额 + 未实现盈亏 | ||
| totalEquity := totalWalletBalance + totalUnrealizedProfit | ||
|
|
||
| if totalEquity > 0 { | ||
| actualBalance = totalEquity | ||
| log.Printf("✓ 查询到交易所总资产余额: %.2f USDT (钱包: %.2f + 未实现: %.2f, 用户输入: %.2f USDT)", | ||
| actualBalance, totalWalletBalance, totalUnrealizedProfit, req.InitialBalance) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shall we abstract them as a function to reduce the repeated codes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes I think we should and have to since this file is way too big.
|
Could you fix the comments? |
代码审查报告 - PR #668审查结果:✅ 通过(重要bug修复)业务层面审查✅ 需求验证
✅ 功能完整性
技术层面审查✅ 问题分析核心问题: 影响:
✅ 解决方案公式修正: // Before - 错误公式
actualBalance = availableBalance
// After - 正确公式
totalEquity = totalWalletBalance + totalUnrealizedProfit
actualBalance = totalEquity修改位置:
✅ 代码实现创建trader(api/server.go:541-566): // ✅ 提取钱包余额和未实现盈亏
totalWalletBalance := 0.0
totalUnrealizedProfit := 0.0
if wallet, ok := balanceInfo["totalWalletBalance"].(float64); ok {
totalWalletBalance = wallet
}
if unrealized, ok := balanceInfo["totalUnrealizedProfit"].(float64); ok {
totalUnrealizedProfit = unrealized
}
// ✅ 计算总资产
totalEquity := totalWalletBalance + totalUnrealizedProfit
if totalEquity > 0 {
actualBalance = totalEquity
log.Printf("✓ 查询到交易所总资产余额: %.2f USDT (钱包: %.2f + 未实现: %.2f, 用户输入: %.2f USDT)",
actualBalance, totalWalletBalance, totalUnrealizedProfit, req.InitialBalance)
}同步余额(api/server.go:920-940): // ✅ 同样的逻辑
totalEquity := totalWalletBalance + totalUnrealizedProfit
if actualBalance <= 0 {
c.JSON(http.StatusInternalServerError, gin.H{
"error": fmt.Sprintf("无法获取总资产余额 (钱包: %.2f, 未实现: %.2f)",
totalWalletBalance, totalUnrealizedProfit)
})
return
}E2E 验证报告✅ 测试场景1:有持仓盈利✅ 测试场景2:有持仓亏损✅ 测试场景3:无持仓✅ 测试场景4:同步余额代码质量审查✅ 错误处理改进的错误信息: // Before - 不够详细
c.JSON(http.StatusInternalServerError, gin.H{"error": "无法获取可用余额"})
// After - 详细的调试信息
c.JSON(http.StatusInternalServerError, gin.H{
"error": fmt.Sprintf("无法获取总资产余额 (钱包: %.2f, 未实现: %.2f)",
totalWalletBalance, totalUnrealizedProfit)
})优点:
✅ 日志改进Before: log.Printf("✓ 查询到交易所实际余额: %.2f USDT (当前配置: %.2f USDT)",
actualBalance, req.InitialBalance)After: log.Printf("✓ 查询到交易所总资产余额: %.2f USDT (钱包: %.2f + 未实现: %.2f, 用户输入: %.2f USDT)",
actualBalance, totalWalletBalance, totalUnrealizedProfit, req.InitialBalance)优点:
✅ 类型安全类型断言处理: if wallet, ok := balanceInfo["totalWalletBalance"].(float64); ok {
totalWalletBalance = wallet
}优点:
业务影响分析📊 盈亏计算准确性Before vs After对比:
结论:
📈 对历史数据的影响注意:
🟡 改进建议(非 BLOCKING)建议1:添加数据迁移脚本// migrate_initial_balance.go
func MigrateInitialBalance(db *Database) error {
traders := db.GetAllTraders()
for _, trader := range traders {
// 重新计算正确的initial_balance
balanceInfo, err := trader.GetBalance()
if err != nil {
continue
}
totalWalletBalance := balanceInfo["totalWalletBalance"].(float64)
totalUnrealizedProfit := balanceInfo["totalUnrealizedProfit"].(float64)
totalEquity := totalWalletBalance + totalUnrealizedProfit
// 更新数据库
db.UpdateTraderInitialBalance(trader.ID, totalEquity)
log.Printf("✓ 迁移 trader %s: %.2f → %.2f",
trader.ID, trader.InitialBalance, totalEquity)
}
return nil
}建议2:添加字段说明// 在结构体注释中说明
type TraderRecord struct {
// ...
// InitialBalance 是总资产(total equity),不是可用余额
// Total Equity = Wallet Balance + Unrealized P&L
InitialBalance float64 `json:"initial_balance"`
}建议3:添加单元测试func TestCalculateTotalEquity(t *testing.T) {
tests := []struct {
name string
walletBalance float64
unrealizedPnL float64
expectedEquity float64
}{
{
name: "有盈利持仓",
walletBalance: 950,
unrealizedPnL: 50,
expectedEquity: 1000,
},
{
name: "有亏损持仓",
walletBalance: 1050,
unrealizedPnL: -50,
expectedEquity: 1000,
},
{
name: "无持仓",
walletBalance: 1000,
unrealizedPnL: 0,
expectedEquity: 1000,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
equity := tt.walletBalance + tt.unrealizedPnL
assert.Equal(t, tt.expectedEquity, equity)
})
}
}建议4:前端提示用户同步// 对于旧trader,显示提示
{trader.created_at < PR_MERGE_DATE && (
<Alert variant="info">
💡 建议点击"同步余额"以修正初始资金(修复盈亏计算)
</Alert>
)}财务准确性验证✅ 会计公式验证正确的公式: 本PR修复:
总结这是一个重要的财务准确性修复 PR: 优点
改进空间(非 BLOCKING)
审查结论:✅ 通过,强烈建议合并 重要性:
后续建议:
|
|
- Test total equity calculation (wallet balance + unrealized P&L) - Compare old logic (available balance) vs new logic (total equity) - Test field type handling and missing fields - Test edge cases (zero values, large amounts, near liquidation) All 17 test cases passed, covering: 1. CalculateTotalEquity (10 cases): profit/loss, missing fields, type errors 2. CompareAvailableBalanceVsTotalEquity (3 cases): demonstrates why total equity is more accurate 3. BalanceInfoFieldTypes (4 cases): validates type assertion behavior Related to PR NoFxAiOS#668 - ensures correct P&L calculation using total equity. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
|
Closed in favor of clean rebuild #883 from latest dev branch (was 49 commits behind with conflicts) |
🐛 Problem
The system was using available balance instead of total equity when setting and syncing initial balance, causing completely incorrect P&L calculations.
Root Cause Chain
initialBalancereset to lower available balanceReal Example (from Issue #637)
User started with $200 USDC:
Step 1: Open position
Step 2: Open another position
Final Result
User sees +36% profit when actually at -3% loss! 🚨
✅ Solution
Use total equity (wallet balance + unrealized P&L) instead of available balance.
Formula
This represents the actual account value, unaffected by margin usage.
🔧 Technical Changes
Modified Functions
1.
handleCreateTrader(L393-419)Before:
After:
2.
handleSyncBalance(L794-824)Same pattern - replaced
availableBalancewithtotalEquitycalculation.Enhanced Logging
Before:
After:
Now users can see the breakdown of wallet balance vs unrealized P&L.
📊 Impact
Before This PR
After This PR
🧪 Testing
go buildpassestotalWalletBalanceand fallback fields🔗 Related
📋 Files Modified
handleCreateTrader()(L393-419): Use total equity when creating traderhandleSyncBalance()(L794-824): Use total equity when syncing balance💡 Benefits
Co-Authored-By: Claude [email protected]
🎯 Type of Change | 變更類型
🔗 Related Issues | 相關 Issue
🧪 Testing | 測試
Test Environment | 測試環境
Manual Testing | 手動測試
🔒 Security Considerations | 安全考慮
⚡ Performance Impact | 性能影響
✅ Checklist | 檢查清單
Code Quality | 代碼質量
go build)go fmt| 已運行go fmtDocumentation | 文檔
Git
devbranch | 已 rebase 到最新dev分支By submitting this PR, I confirm | 提交此 PR,我確認:
🌟 Thank you for your contribution! | 感謝你的貢獻!