From 0909fbefae6473ccf0cd9dcf15377bde591b2c00 Mon Sep 17 00:00:00 2001 From: tinkle-community Date: Wed, 29 Oct 2025 15:30:32 +0800 Subject: [PATCH] Fix: Correct PnL calculation in trade history analysis MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixed critical issues in historical trade record and performance analysis: 1. PnL Calculation: Changed from percentage-only to actual USDT amount - Now correctly calculates: positionValue × priceChange% × leverage - Previously: 100U@5% and 1000U@5% both showed 5.0 - Now: Properly reflects different position sizes and leverage 2. Position Tracking: Added quantity and leverage to open position records - Store complete trade data for accurate PnL calculation - Previously only stored: side, openPrice, openTime - Now includes: quantity, leverage for proper accounting 3. Position Key: Fixed to distinguish long/short positions - Changed from symbol to symbol_side (e.g., BTCUSDT_long) - Prevents conflicts when holding both long and short positions 4. Sharpe Ratio: Replaced custom Newton's method with math.Sqrt - Simplified standard deviation calculation - More reliable and maintainable Impact: Win rate, profit factor, and Sharpe ratio now based on accurate USDT amounts 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: tinkle-community --- logger/decision_logger.go | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/logger/decision_logger.go b/logger/decision_logger.go index 7ac07c85..ee5bb2af 100644 --- a/logger/decision_logger.go +++ b/logger/decision_logger.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "io/ioutil" + "math" "os" "path/filepath" "time" @@ -326,7 +327,7 @@ func (l *DecisionLogger) AnalyzePerformance(lookbackCycles int) (*PerformanceAna SymbolStats: make(map[string]*SymbolPerformance), } - // 追踪持仓状态:symbol -> {side, openPrice, openTime} + // 追踪持仓状态:symbol_side -> {side, openPrice, openTime, quantity, leverage} openPositions := make(map[string]map[string]interface{}) // 遍历所有记录 @@ -337,15 +338,23 @@ func (l *DecisionLogger) AnalyzePerformance(lookbackCycles int) (*PerformanceAna } symbol := action.Symbol - posKey := symbol // 使用symbol作为key(假设同一时间一个币种只有一个方向的仓位) + side := "" + if action.Action == "open_long" || action.Action == "close_long" { + side = "long" + } else if action.Action == "open_short" || action.Action == "close_short" { + side = "short" + } + posKey := symbol + "_" + side // 使用symbol_side作为key,区分多空持仓 switch action.Action { case "open_long", "open_short": - // 记录开仓 + // 记录开仓(包括数量和杠杆) openPositions[posKey] = map[string]interface{}{ - "side": action.Action[5:], // "long" or "short" + "side": side, "openPrice": action.Price, "openTime": action.Timestamp, + "quantity": action.Quantity, + "leverage": action.Leverage, } case "close_long", "close_short": @@ -354,16 +363,21 @@ func (l *DecisionLogger) AnalyzePerformance(lookbackCycles int) (*PerformanceAna openPrice := openPos["openPrice"].(float64) openTime := openPos["openTime"].(time.Time) side := openPos["side"].(string) + quantity := openPos["quantity"].(float64) + leverage := openPos["leverage"].(int) - // 计算盈亏 - pnl := 0.0 + // 计算盈亏百分比 pnlPct := 0.0 if side == "long" { pnlPct = ((action.Price - openPrice) / openPrice) * 100 } else { pnlPct = ((openPrice - action.Price) / openPrice) * 100 } - pnl = pnlPct // 简化:用百分比代表盈亏 + + // 计算实际盈亏(USDT) + // PnL = 仓位价值 × 价格变化百分比 × 杠杆倍数 + positionValue := quantity * openPrice + pnl := positionValue * (pnlPct / 100) * float64(leverage) // 记录交易结果 outcome := TradeOutcome{ @@ -513,14 +527,7 @@ func (l *DecisionLogger) calculateSharpeRatio(records []*DecisionRecord) float64 sumSquaredDiff += diff * diff } variance := sumSquaredDiff / float64(len(returns)) - stdDev := 0.0 - if variance > 0 { - stdDev = 1.0 - // 简单的平方根计算(牛顿迭代法) - for i := 0; i < 10; i++ { - stdDev = (stdDev + variance/stdDev) / 2 - } - } + stdDev := math.Sqrt(variance) // 避免除以零 if stdDev == 0 {