From 5aedcb656629ca1f741cdd55f374cad4a751783f Mon Sep 17 00:00:00 2001 From: jackyu66git Date: Mon, 4 May 2026 06:06:37 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20scale-in=20=E5=90=8E=20leg?= =?UTF-8?q?.EntryPrice=20=E4=B8=8D=E6=9B=B4=E6=96=B0=20+=20=E9=87=8D?= =?UTF-8?q?=E5=90=AF=E6=81=A2=E5=A4=8D=E6=97=B6=E5=8A=A0=E8=BD=BD=20scale?= =?UTF-8?q?=20prices?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - trader.go: scale-in 后用 weightedAvgPrice 更新 LongLeg/ShortLeg.EntryPrice - trader.go: restoreOpenPositions 从 orders 表加载 scale prices 重建完整价格切片 - db/trade_repo.go: 新增 GetScalePrices(tradeID) 方法 - README: v1.2.1 版本记录 --- README.md | 8 +++++++- db/trade_repo.go | 25 +++++++++++++++++++++++++ trader.go | 18 ++++++++++++++++++ 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 31a7a7f..32853bd 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,13 @@ SQLite (`data/trades.db`),三张核心表: ## 版本历史 -### v1.2 (当前) +### v1.2.1 (当前) +- ✨ scale-in 后 leg.EntryPrice 更新为加权平均(`weightedAvgPrice`) +- ✨ 进程重启恢复仓位时,从 `orders` 表加载 scale prices,重建完整价格切片 +- ✨ 新增 `GetScalePrices()` DB 方法 +- 见 v1.2: + +### v1.2 - ✨ `system_orders` 表,记录系统级开仓/加仓/平仓 - ✨ 手续费改为逐笔累加 USD,不再用百分比估算 - ✨ Vite + React 前端,支持热加载 diff --git a/db/trade_repo.go b/db/trade_repo.go index 0535370..7448a7d 100644 --- a/db/trade_repo.go +++ b/db/trade_repo.go @@ -235,6 +235,31 @@ func scanTrades(rows *sql.Rows) ([]TradeRecord, error) { return trades, rows.Err() } +// GetScalePrices returns scale-in order prices for a trade, grouped by leg. +func (d *DB) GetScalePrices(tradeID int64) (longPrices, shortPrices []float64, err error) { + rows, err := d.Query(`SELECT leg, price FROM orders + WHERE trade_id=? AND type='scale' AND price IS NOT NULL + ORDER BY id`, tradeID) + if err != nil { + return nil, nil, err + } + defer rows.Close() + for rows.Next() { + var leg string + var price float64 + if err := rows.Scan(&leg, &price); err != nil { + return nil, nil, err + } + switch leg { + case "long": + longPrices = append(longPrices, price) + case "short": + shortPrices = append(shortPrices, price) + } + } + return longPrices, shortPrices, rows.Err() +} + // GetClosedStats returns convergence counts from the database. func (d *DB) GetClosedStats() (converged, diverged, flat, total int, err error) { if err = d.QueryRow("SELECT COUNT(*) FROM trades WHERE status='closed'").Scan(&total); err != nil { diff --git a/trader.go b/trader.go index 1777493..a3f1dad 100644 --- a/trader.go +++ b/trader.go @@ -547,6 +547,10 @@ func (t *Trader) checkScaleIn(pos *ArbPosition, bgP, hlP, diffPct float64, store pos.LongEntryPrices = append(pos.LongEntryPrices, longPrice) pos.ShortEntryPrices = append(pos.ShortEntryPrices, shortPrice) + // Update leg EntryPrice to reflect weighted average across all scale levels + pos.LongLeg.EntryPrice = weightedAvgPrice(pos.LongEntryPrices, t.cfg.TradeAmountUSD) + pos.ShortLeg.EntryPrice = weightedAvgPrice(pos.ShortEntryPrices, t.cfg.TradeAmountUSD) + // Persist scale orders to DB immediately if t.db != nil && pos.DBTradeID > 0 { now := time.Now() @@ -1172,6 +1176,20 @@ func (t *Trader) restoreOpenPositions() { } pos.ShortEntryPrices = []float64{*tr.ShortEntry} } + + // Restore scale-in prices from orders table for correct weighted average + scaleLong, scaleShort, err := t.db.GetScalePrices(tr.ID) + if err == nil { + pos.LongEntryPrices = append(pos.LongEntryPrices, scaleLong...) + pos.ShortEntryPrices = append(pos.ShortEntryPrices, scaleShort...) + // Refresh leg EntryPrice to reflect all scale levels + if len(pos.LongEntryPrices) > 1 { + pos.LongLeg.EntryPrice = weightedAvgPrice(pos.LongEntryPrices, t.cfg.TradeAmountUSD) + } + if len(pos.ShortEntryPrices) > 1 { + pos.ShortLeg.EntryPrice = weightedAvgPrice(pos.ShortEntryPrices, t.cfg.TradeAmountUSD) + } + } t.positions[tr.Coin] = pos // Prevent immediate re-trading of the same coin t.lastTradeTime[tr.Coin] = tr.OpenedAt