Skip to content

[P0 嚴重] 修改交易員初始餘額後 P&L 不更新 #790

@the-dev-z

Description

@the-dev-z

[P0 嚴重] 修改交易員初始餘額後 P&L 不更新

[P0 Critical] P&L Not Updated After Editing Trader Initial Balance


📋 問題描述 | Bug Description

中文
修改交易員的初始餘額後,盈虧(P&L)計算仍基於舊餘額,導致統計數據錯誤,無法反映真實收益率。

English:
After editing trader's initial balance, P&L calculation still uses old balance, causing incorrect statistics and failing to reflect true ROI.

嚴重性 | Severity: 🔴 P0 - 數據準確性錯誤,影響用戶決策 | Data accuracy error, affects user decisions


🔄 復現步驟 | Reproduction Steps

  1. 創建交易員,設定初始餘額為 1000 USDT

    • Create trader, set initial balance to 1000 USDT
  2. 啟動交易員,等待產生交易記錄(至少 1 筆)

    • Start trader, wait for trade records (at least 1)
  3. 停止交易員

    • Stop trader
  4. 編輯交易員,將初始餘額修改為 1 USDT

    • Edit trader, change initial balance to 1 USDT
  5. 保存修改

    • Save changes
  6. 查看交易員卡片的 P&L 數據

    • Check trader card P&L data

期望結果 | Expected Result:

  • P&L 應重新計算,基準改為 1 USDT
    • P&L should recalculate based on 1 USDT
  • 收益率應大幅變化(如從 -0.17% 變為 -17%)
    • ROI should change significantly (e.g., from -0.17% to -17%)

實際結果 | Actual Result:

  • P&L 保持不變,仍基於 1000 USDT 計算
    • P&L unchanged, still calculated based on 1000 USDT
  • 收益率未更新
    • ROI not updated

✅ 期望行為 | Expected Behavior

操作 Operation 初始餘額 Initial 當前總資產 Current 期望 P&L Expected 期望收益率 ROI
創建時填 1000
Create with 1000
1000 999.83 -0.17 -0.17%
修改成 1
Edit to 1
1 999.83 +998.83 +99883%

核心邏輯 | Core Logic:

P&L = 當前總資產 - 初始餘額
P&L = Current Equity - Initial Balance

收益率 = (P&L / 初始餘額) × 100%
ROI = (P&L / Initial Balance) × 100%

❌ 實際行為 | Actual Behavior

操作 Operation 初始餘額(UI)
Initial (UI)
當前總資產
Current
實際 P&L
Actual
實際收益率
Actual ROI
創建時填 1000 1000 999.83 -0.17 -0.17%
修改成 1 1(已修改)
1 (edited)
999.83 -0.17 -0.17%

P&L 計算仍使用舊的 1000 作為基準,未隨著初始餘額修改而更新。

  • P&L still uses old 1000 as baseline, not updated with balance change.

🔬 技術根因 | Root Cause

推測原因 1 | Possible Cause 1: 後端只更新資料庫,未觸發重算 | Backend only updates DB, no recalculation

代碼位置 | Code Location: api/server.go handleUpdateTrader 函數 | function

// ❌ 當前邏輯:只更新資料庫,未重算 P&L
// ❌ Current logic: Only updates DB, no P&L recalculation
func (s *Server) handleUpdateTrader(c *gin.Context) {
    var req UpdateTraderRequest
    c.BindJSON(&req)

    // 更新資料庫 | Update database
    err := s.database.UpdateTrader(userID, traderID, req)
    if err != nil {
        c.JSON(500, gin.H{"error": err.Error()})
        return
    }

    // ❌ 缺少:重新計算 P&L 的邏輯
    // ❌ Missing: P&L recalculation logic
    // ❌ 缺少:通知運行中交易員更新基準的機制
    // ❌ Missing: Mechanism to notify running trader to update baseline

    c.JSON(200, gin.H{"message": "更新成功 | Update successful"})
}

推測原因 2 | Possible Cause 2: P&L 使用快取的初始餘額 | P&L uses cached initial balance

代碼位置 | Code Location: trader/trader.go 或前端計算邏輯 | or frontend calculation

// ❌ 可能的問題:P&L 計算時使用了創建時的快取值
// ❌ Possible issue: P&L calculation uses cached value from creation
type Trader struct {
    initialBalanceCache float64  // ← 創建時設定,從未更新 | Set on creation, never updated
}

func (t *Trader) CalculatePnL() float64 {
    return t.currentEquity - t.initialBalanceCache  // ← 使用舊值! | Uses old value!
}

推測原因 3 | Possible Cause 3: 前端顯示使用靜態值 | Frontend displays static value

// ❌ 前端可能從 API 獲取的是舊的 P&L 值
// ❌ Frontend may be getting old P&L value from API
<div>
    Total P&L: {trader.pnl}  {/* ← API 返回的舊值 | Old value from API */}
</div>

💡 建議修復方案 | Suggested Fix

方案 A(推薦)| Option A (Recommended): 禁止修改已交易過的交易員的初始餘額 | Prevent editing initial balance of traded traders

理由 | Rationale:

  • 修改初始餘額會導致歷史數據失真
    • Editing initial balance distorts historical data
  • P&L 的意義是「相對於起始資金的盈虧」,改變起始資金會讓歷史記錄變得無意義
    • P&L means "profit/loss relative to starting capital"; changing starting capital makes history meaningless

實現 | Implementation:

// api/server.go - handleUpdateTrader

// 檢查交易員是否已有交易記錄
// Check if trader has trade history
hasTraded, _ := s.database.HasTradeHistory(traderID)
if hasTraded && req.InitialBalance != nil {
    c.JSON(400, gin.H{
        "error": "無法修改已交易的交易員的初始餘額 | Cannot edit initial balance of traded trader",
        "hint": "初始餘額只能在首次啟動前設定 | Initial balance can only be set before first start",
    })
    return
}

前端配合 | Frontend Support:

// 如果交易員已啟動過,禁用初始餘額輸入框
// If trader has been started, disable initial balance input
<input
    type="number"
    value={initialBalance}
    disabled={trader.hasTraded}  // ← 已交易過則禁用 | Disabled if traded
/>

{trader.hasTraded && (
    <p className="text-amber-600">
        ⚠️ 初始餘額無法修改(交易員已啟動過)
        ⚠️ Initial balance cannot be edited (trader has been started)
    </p>
)}

方案 B(不推薦)| Option B (Not Recommended): 允許修改,但強制重算並清空歷史 | Allow editing, force recalculation and clear history

實現 | Implementation:

// api/server.go - handleUpdateTrader

if req.InitialBalance != nil && req.InitialBalance != trader.InitialBalance {
    // 警告用戶 | Warn user
    log.Printf("⚠️ 修改初始餘額:%.2f → %.2f | Editing initial balance: %.2f → %.2f",
        trader.InitialBalance, req.InitialBalance, trader.InitialBalance, req.InitialBalance)

    // 更新餘額 | Update balance
    trader.InitialBalance = req.InitialBalance

    // 重算 P&L | Recalculate P&L
    trader.RecalculatePnL()

    // ❌ 風險:歷史數據失真 | Risk: Historical data distorted
    // 選項 1:清空歷史交易記錄(用戶可能不願意)
    // Option 1: Clear history (users may not want)
    // 選項 2:保留歷史但標記「餘額已修改,P&L 僅供參考」
    // Option 2: Keep history but mark "balance edited, P&L for reference only"
}

前端確認對話框 | Frontend Confirmation:

const handleUpdateBalance = () => {
    if (window.confirm(
        '修改初始餘額將導致歷史 P&L 數據失真,確定要修改嗎?\n' +
        '建議:創建新交易員代替修改現有交易員。\n\n' +
        'Editing initial balance will distort historical P&L data. Continue?\n' +
        'Suggestion: Create new trader instead of editing existing one.'
    )) {
        // 執行修改 | Execute edit
    }
}

🧪 測試案例 | Test Cases

測試 1 | Test 1: 修改未啟動的交易員 | Edit un-started trader

  1. 創建交易員,初始餘額 1000 | Create trader with balance 1000
  2. 不啟動,直接修改餘額為 500 | Don't start, edit to 500
  3. 啟動交易員 | Start trader
  4. 期望 | Expected: P&L 基於 500 計算 | P&L calculated based on 500

測試 2 | Test 2: 修改已啟動的交易員(方案 A)| Edit started trader (Option A)

  1. 創建並啟動交易員,初始餘額 1000 | Create and start, balance 1000
  2. 產生交易記錄 | Generate trade records
  3. 嘗試修改餘額為 500 | Try to edit to 500
  4. 期望 | Expected: 後端返回錯誤「無法修改已交易的交易員的初始餘額」| Backend returns error

測試 3 | Test 3: P&L 實時更新 | P&L real-time update

  1. 創建交易員,初始餘額 1000 | Create with balance 1000
  2. 啟動並產生交易(假設當前總資產為 1050)| Start and trade (assume equity 1050)
  3. 查看 P&L → 應顯示 +50 (+5%) | Check P&L → Should show +50 (+5%)
  4. 交易繼續,總資產變為 980 | Trading continues, equity becomes 980
  5. 查看 P&L → 應顯示 -20 (-2%) | Check P&L → Should show -20 (-2%)

🎯 相關改進建議 | Related Improvements

  1. 明確初始餘額的意義 | Clarify initial balance meaning

    <Tooltip content="初始餘額代表交易員首次啟動時的資金基準,用於計算盈虧。一旦啟動,無法修改。
    Initial balance represents the capital baseline at first start, used for P&L calculation. Cannot be edited once started.">
        <InfoIcon />
    </Tooltip>
  2. 添加「重置交易員」功能 | Add "Reset Trader" feature

    • 如果用戶確實需要修改餘額,建議「重置」(清空歷史 + 修改餘額)
      • If user really needs to edit, suggest "Reset" (clear history + edit balance)
    • 或直接創建新交易員
      • Or create new trader
  3. P&L 計算透明化 | Make P&L calculation transparent

    <div className="pnl-breakdown">
        <p>當前總資產 | Current Equity: 999.83 USDT</p>
        <p>初始餘額 | Initial Balance: 1000 USDT</p>
        <p>盈虧 (P&L): 999.83 - 1000 = <strong>-0.17 USDT</strong></p>
        <p>收益率 | ROI: -0.17 / 1000 × 100% = <strong>-0.17%</strong></p>
    </div>

📊 環境資訊 | Environment

  • 版本 | Version: 基於 NoFxAiOS/nofx dev 分支 | Based on dev branch
  • 測試交易所 | Test Exchange: HYPERLIQUID, Binance
  • 資料庫 | Database: SQLite

🔗 相關 Issue | Related Issues


優先級建議 | Priority: 🔴 P0 - 影響數據準確性,建議採用方案 A(禁止修改)快速修復 | Affects data accuracy, suggest Option A (prevent editing) for quick fix

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions