fix: detect and record stop-loss/take-profit auto-close trades

Fixes #200

Previously, positions closed via stop-loss or take-profit were not
recorded in decision logs, causing inaccurate performance metrics
(win rate, profit factor, etc.) displayed in the frontend.

This commit implements a position snapshot mechanism to detect
auto-closed positions by comparing positions between trading cycles.

Changes:
- Add PositionSnapshot struct to track position state
- Add detectAutoClosedPositions() to detect disappeared positions
- Update position snapshots at cycle end (after AI execution)
- Support auto_close_long/auto_close_short action types in logger
- Ensure accurate performance analysis including auto-closed trades

Key fix:
- Snapshots are updated AFTER AI execution to avoid false positives
- AI manual closes won't be mistakenly detected as auto-closes
This commit is contained in:
Xeron
2025-11-02 12:35:37 +08:00
parent 7b5970567f
commit 5c9b396e5a
2 changed files with 145 additions and 14 deletions

View File

@@ -243,7 +243,7 @@ func (l *DecisionLogger) GetStatistics() (*Statistics, error) {
switch action.Action {
case "open_long", "open_short":
stats.TotalOpenPositions++
case "close_long", "close_short":
case "close_long", "close_short", "auto_close_long", "auto_close_short":
stats.TotalClosePositions++
}
}
@@ -348,9 +348,9 @@ func (l *DecisionLogger) AnalyzePerformance(lookbackCycles int) (*PerformanceAna
symbol := action.Symbol
side := ""
if action.Action == "open_long" || action.Action == "close_long" {
if action.Action == "open_long" || action.Action == "close_long" || action.Action == "auto_close_long" {
side = "long"
} else if action.Action == "open_short" || action.Action == "close_short" {
} else if action.Action == "open_short" || action.Action == "close_short" || action.Action == "auto_close_short" {
side = "short"
}
posKey := symbol + "_" + side
@@ -365,7 +365,7 @@ func (l *DecisionLogger) AnalyzePerformance(lookbackCycles int) (*PerformanceAna
"quantity": action.Quantity,
"leverage": action.Leverage,
}
case "close_long", "close_short":
case "close_long", "close_short", "auto_close_long", "auto_close_short":
// 移除已平仓记录
delete(openPositions, posKey)
}
@@ -382,9 +382,9 @@ func (l *DecisionLogger) AnalyzePerformance(lookbackCycles int) (*PerformanceAna
symbol := action.Symbol
side := ""
if action.Action == "open_long" || action.Action == "close_long" {
if action.Action == "open_long" || action.Action == "close_long" || action.Action == "auto_close_long" {
side = "long"
} else if action.Action == "open_short" || action.Action == "close_short" {
} else if action.Action == "open_short" || action.Action == "close_short" || action.Action == "auto_close_short" {
side = "short"
}
posKey := symbol + "_" + side // 使用symbol_side作为key区分多空持仓
@@ -400,7 +400,7 @@ func (l *DecisionLogger) AnalyzePerformance(lookbackCycles int) (*PerformanceAna
"leverage": action.Leverage,
}
case "close_long", "close_short":
case "close_long", "close_short", "auto_close_long", "auto_close_short":
// 查找对应的开仓记录(可能来自预填充或当前窗口)
if openPos, exists := openPositions[posKey]; exists {
openPrice := openPos["openPrice"].(float64)