Fix: Correct PnL calculation in trade history analysis

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
Co-Authored-By: tinkle-community <tinklefund@gmail.com>
This commit is contained in:
tinkle-community
2025-10-29 15:30:32 +08:00
parent d7d2d5c880
commit 938926254f

View File

@@ -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 {