修复 scale-in 后 leg.EntryPrice 不更新 + 重启恢复时加载 scale prices
- trader.go: scale-in 后用 weightedAvgPrice 更新 LongLeg/ShortLeg.EntryPrice - trader.go: restoreOpenPositions 从 orders 表加载 scale prices 重建完整价格切片 - db/trade_repo.go: 新增 GetScalePrices(tradeID) 方法 - README: v1.2.1 版本记录
This commit is contained in:
@@ -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` 表,记录系统级开仓/加仓/平仓
|
- ✨ `system_orders` 表,记录系统级开仓/加仓/平仓
|
||||||
- ✨ 手续费改为逐笔累加 USD,不再用百分比估算
|
- ✨ 手续费改为逐笔累加 USD,不再用百分比估算
|
||||||
- ✨ Vite + React 前端,支持热加载
|
- ✨ Vite + React 前端,支持热加载
|
||||||
|
|||||||
@@ -235,6 +235,31 @@ func scanTrades(rows *sql.Rows) ([]TradeRecord, error) {
|
|||||||
return trades, rows.Err()
|
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.
|
// GetClosedStats returns convergence counts from the database.
|
||||||
func (d *DB) GetClosedStats() (converged, diverged, flat, total int, err error) {
|
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 {
|
if err = d.QueryRow("SELECT COUNT(*) FROM trades WHERE status='closed'").Scan(&total); err != nil {
|
||||||
|
|||||||
@@ -547,6 +547,10 @@ func (t *Trader) checkScaleIn(pos *ArbPosition, bgP, hlP, diffPct float64, store
|
|||||||
pos.LongEntryPrices = append(pos.LongEntryPrices, longPrice)
|
pos.LongEntryPrices = append(pos.LongEntryPrices, longPrice)
|
||||||
pos.ShortEntryPrices = append(pos.ShortEntryPrices, shortPrice)
|
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
|
// Persist scale orders to DB immediately
|
||||||
if t.db != nil && pos.DBTradeID > 0 {
|
if t.db != nil && pos.DBTradeID > 0 {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
@@ -1172,6 +1176,20 @@ func (t *Trader) restoreOpenPositions() {
|
|||||||
}
|
}
|
||||||
pos.ShortEntryPrices = []float64{*tr.ShortEntry}
|
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
|
t.positions[tr.Coin] = pos
|
||||||
// Prevent immediate re-trading of the same coin
|
// Prevent immediate re-trading of the same coin
|
||||||
t.lastTradeTime[tr.Coin] = tr.OpenedAt
|
t.lastTradeTime[tr.Coin] = tr.OpenedAt
|
||||||
|
|||||||
Reference in New Issue
Block a user