From c0c0688805661bbd894ed879335ad6a8174337a4 Mon Sep 17 00:00:00 2001 From: ZhouYongyou <128128010+zhouyongyou@users.noreply.github.com> Date: Sun, 2 Nov 2025 06:11:12 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20logger=EF=BC=9A=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E6=96=B0=E5=A2=9E=E7=9A=84=E4=B8=89=E5=80=8B=E5=8B=95?= =?UTF-8?q?=E4=BD=9C=E9=A1=9E=E5=9E=8B=20=E6=9B=B4=E6=96=B0=E5=85=A7?= =?UTF-8?q?=E5=AE=B9=EF=BC=9A=201.=20DecisionAction=20=E8=A8=BB=E9=87=8B?= =?UTF-8?q?=EF=BC=9A=E6=B7=BB=E5=8A=A0=20update=5Fstop=5Floss,=20update=5F?= =?UTF-8?q?take=5Fprofit,=20partial=5Fclose=202.=20GetStatistics=EF=BC=9Ap?= =?UTF-8?q?artial=5Fclose=20=E8=A8=88=E5=85=A5=20TotalClosePositions=203.?= =?UTF-8?q?=20AnalyzePerformance=20=E9=A0=90=E5=A1=AB=E5=85=85=E9=82=8F?= =?UTF-8?q?=E8=BC=AF=EF=BC=9A=E8=99=95=E7=90=86=20partial=5Fclose=EF=BC=88?= =?UTF-8?q?=E4=B8=8D=E5=88=AA=E9=99=A4=E6=8C=81=E5=80=89=E8=A8=98=E9=8C=84?= =?UTF-8?q?=EF=BC=89=204.=20AnalyzePerformance=20=E5=88=86=E6=9E=90?= =?UTF-8?q?=E9=82=8F=E8=BC=AF=EF=BC=9A=20=20=20=20-=20partial=5Fclose=20?= =?UTF-8?q?=E6=AD=A3=E7=A2=BA=E5=88=A4=E6=96=B7=E6=8C=81=E5=80=89=E6=96=B9?= =?UTF-8?q?=E5=90=91=20=20=20=20-=20=E8=A8=98=E9=8C=84=E9=83=A8=E5=88=86?= =?UTF-8?q?=E5=B9=B3=E5=80=89=E7=9A=84=E7=9B=88=E8=99=A7=E7=B5=B1=E8=A8=88?= =?UTF-8?q?=20=20=20=20-=20=E4=BF=9D=E7=95=99=E6=8C=81=E5=80=89=E8=A8=98?= =?UTF-8?q?=E9=8C=84=EF=BC=88=E5=9B=A0=E7=82=BA=E9=82=84=E6=9C=89=E5=89=A9?= =?UTF-8?q?=E9=A4=98=E5=80=89=E4=BD=8D=EF=BC=89=20=E8=AA=AA=E6=98=8E?= =?UTF-8?q?=EF=BC=9Apartial=5Fclose=20=E6=9C=83=E8=A8=98=E9=8C=84=E7=9B=88?= =?UTF-8?q?=E8=99=A7=EF=BC=8C=E4=BD=86=E4=B8=8D=E5=88=AA=E9=99=A4=20openPo?= =?UTF-8?q?sitions=EF=BC=8C=20=20=20=20=20=20=20=E5=9B=A0=E7=82=BA?= =?UTF-8?q?=E9=82=84=E6=9C=89=E5=89=A9=E9=A4=98=E5=80=89=E4=BD=8D=E5=8F=AF?= =?UTF-8?q?=E8=83=BD=E7=B9=BC=E7=BA=8C=E4=BA=A4=E6=98=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- logger/decision_logger.go | 43 +++++++++++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/logger/decision_logger.go b/logger/decision_logger.go index efa5ab74..9891ce71 100644 --- a/logger/decision_logger.go +++ b/logger/decision_logger.go @@ -50,9 +50,9 @@ type PositionSnapshot struct { // DecisionAction 决策动作 type DecisionAction struct { - Action string `json:"action"` // open_long, open_short, close_long, close_short + Action string `json:"action"` // open_long, open_short, close_long, close_short, update_stop_loss, update_take_profit, partial_close Symbol string `json:"symbol"` // 币种 - Quantity float64 `json:"quantity"` // 数量 + Quantity float64 `json:"quantity"` // 数量(部分平仓时使用) Leverage int `json:"leverage"` // 杠杆(开仓时) Price float64 `json:"price"` // 执行价格 OrderID int64 `json:"order_id"` // 订单ID @@ -243,8 +243,9 @@ 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", "partial_close": stats.TotalClosePositions++ + // update_stop_loss 和 update_take_profit 不計入統計 } } } @@ -348,11 +349,22 @@ 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 == "partial_close" { side = "long" } else if action.Action == "open_short" || action.Action == "close_short" { side = "short" } + + // partial_close 需要根據持倉判斷方向 + if action.Action == "partial_close" && side == "" { + for key, pos := range openPositions { + if posSymbol, _ := pos["side"].(string); key == symbol+"_"+posSymbol { + side = posSymbol + break + } + } + } + posKey := symbol + "_" + side switch action.Action { @@ -368,6 +380,7 @@ func (l *DecisionLogger) AnalyzePerformance(lookbackCycles int) (*PerformanceAna case "close_long", "close_short": // 移除已平仓记录 delete(openPositions, posKey) + // partial_close 不處理,保留持倉記錄 } } } @@ -382,11 +395,23 @@ 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 == "partial_close" { side = "long" } else if action.Action == "open_short" || action.Action == "close_short" { side = "short" } + + // partial_close 需要根據持倉判斷方向 + if action.Action == "partial_close" { + // 從 openPositions 中查找持倉方向 + for key, pos := range openPositions { + if posSymbol, _ := pos["side"].(string); key == symbol+"_"+posSymbol { + side = posSymbol + break + } + } + } + posKey := symbol + "_" + side // 使用symbol_side作为key,区分多空持仓 switch action.Action { @@ -400,7 +425,7 @@ func (l *DecisionLogger) AnalyzePerformance(lookbackCycles int) (*PerformanceAna "leverage": action.Leverage, } - case "close_long", "close_short": + case "close_long", "close_short", "partial_close": // 查找对应的开仓记录(可能来自预填充或当前窗口) if openPos, exists := openPositions[posKey]; exists { openPrice := openPos["openPrice"].(float64) @@ -472,8 +497,10 @@ func (l *DecisionLogger) AnalyzePerformance(lookbackCycles int) (*PerformanceAna stats.LosingTrades++ } - // 移除已平仓记录 - delete(openPositions, posKey) + // 移除已平仓记录(partial_close 不刪除,因為還有剩餘倉位) + if action.Action != "partial_close" { + delete(openPositions, posKey) + } } } }