feat: 所有参数移至config.json, 重构退出逻辑, 清理遗留接口

- 将所有硬编码参数迁移到 config.json (手续费率、止盈/止损阈值、
  超时、腿间隔、加仓步进等)
- 退出条件: 净利 >= take_profit_pct 止盈, 价差 <= 0 平仓
- 删除 Binance/dYdX 遗留代码
- 更新 README 文档
- Dashboard: 双交易所价格表、黑名单UI、按币名排序持仓
- Bitget WS: 文本ping保活
- 数据库: 重置, 无历史仓位
This commit is contained in:
jackyu66git
2026-05-04 01:46:17 +08:00
parent 2ed6ffc747
commit 21a3f9a962
10 changed files with 450 additions and 206 deletions
+47 -23
View File
@@ -253,7 +253,8 @@ func (d *Dashboard) Run() {
// DetailedStats holds aggregated PnL and duration statistics.
type DetailedStats struct {
TotalTrades int `json:"total_trades"`
TotalPnlPct float64 `json:"total_pnl_pct"`
TotalPnlUSD float64 `json:"total_pnl_usd"` // sum of all trade PnL in USD
CapitalPnlPct float64 `json:"capital_pnl_pct"` // TotalPnlUSD / InitialCapital * 100
AvgPnlPct float64 `json:"avg_pnl_pct"`
MaxProfitPct float64 `json:"max_profit_pct"`
MaxLossPct float64 `json:"max_loss_pct"`
@@ -266,7 +267,7 @@ type DetailedStats struct {
// calcDetailedStats computes trading statistics from a slice of closed trades.
// This is a pure function — no dependency on Trader internals.
func calcDetailedStats(trades []TradeRecord) DetailedStats {
func calcDetailedStats(trades []TradeRecord, initialCapital float64) DetailedStats {
ds := DetailedStats{}
if len(trades) == 0 {
return ds
@@ -275,7 +276,7 @@ func calcDetailedStats(trades []TradeRecord) DetailedStats {
ds.MaxLossPct = 1e9 // sentinel
for _, tr := range trades {
ds.TotalTrades++
ds.TotalPnlPct += tr.PnlPct
ds.TotalPnlUSD += tr.PnlUSD
if tr.PnlPct >= 0 {
ds.WinningTrades++
if tr.PnlPct > ds.MaxProfitPct {
@@ -295,7 +296,8 @@ func calcDetailedStats(trades []TradeRecord) DetailedStats {
ds.MaxLossPct = 0
}
if ds.TotalTrades > 0 {
ds.AvgPnlPct = ds.TotalPnlPct / float64(ds.TotalTrades)
ds.CapitalPnlPct = ds.TotalPnlUSD / initialCapital * 100
ds.AvgPnlPct = ds.TotalPnlUSD / float64(ds.TotalTrades) / initialCapital * 100
ds.WinRate = float64(ds.WinningTrades) / float64(ds.TotalTrades) * 100
}
if totalDur > 0 {
@@ -354,15 +356,16 @@ func (d *Dashboard) broadcastLoop() {
positions := d.trader.ReadSnapshot()
posList := make([]map[string]interface{}, 0, len(positions))
for _, pos := range positions {
posEntry := map[string]interface{}{
"coin": pos.Coin,
"direction": pos.Direction,
"amount_usd": pos.AmountUSD,
"entry_spread": pos.EntrySpread,
"scales": pos.ScaleLevels,
"duration": time.Since(pos.StartedAt).Round(time.Second).String(),
"started_at": pos.StartedAt.Format("15:04:05"),
}
posEntry := map[string]interface{}{
"coin": pos.Coin,
"direction": pos.Direction,
"amount_usd": pos.AmountUSD,
"entry_spread": pos.EntrySpread,
"scales": pos.ScaleLevels,
"duration": time.Since(pos.StartedAt).Round(time.Second).String(),
"started_at": pos.StartedAt.Format("15:04:05"),
"started_ts": pos.StartedAt.UnixMilli(),
}
// Calculate live PnL from current prices — use weighted average for scale-ins
if exMap := snap[pos.Coin]; exMap != nil {
@@ -420,7 +423,7 @@ func (d *Dashboard) broadcastLoop() {
// 4. Stats + connection status (P3-5)
converged, diverged, flat, total := d.trader.GetClosedStats()
detail := calcDetailedStats(d.trader.GetClosedTrades())
detail := calcDetailedStats(d.trader.GetClosedTrades(), d.trader.cfg.InitialCapital)
stats := map[string]interface{}{
"total_trades": total,
"converged": converged,
@@ -428,18 +431,20 @@ func (d *Dashboard) broadcastLoop() {
"flat": flat,
"open_positions": len(positions),
"coins": len(prices),
"capital": d.trader.cfg.InitialCapital,
// Detailed PnL & duration stats (session only)
"detail": map[string]interface{}{
"total_pnl": detail.TotalPnlPct,
"avg_pnl": detail.AvgPnlPct,
"max_profit": detail.MaxProfitPct,
"max_loss": detail.MaxLossPct,
"avg_dur": detail.AvgDuration,
"win_rate": detail.WinRate,
"wins": detail.WinningTrades,
"losses": detail.LosingTrades,
"total_dur": detail.TotalDuration,
"total_pnl_usd": math.Round(detail.TotalPnlUSD*100) / 100,
"capital_pnl": math.Round(detail.CapitalPnlPct*10000) / 10000,
"avg_pnl": detail.AvgPnlPct,
"max_profit": detail.MaxProfitPct,
"max_loss": detail.MaxLossPct,
"avg_dur": detail.AvgDuration,
"win_rate": detail.WinRate,
"wins": detail.WinningTrades,
"losses": detail.LosingTrades,
"total_dur": detail.TotalDuration,
},
}
@@ -459,6 +464,25 @@ func (d *Dashboard) broadcastLoop() {
d.connMu.RUnlock()
stats["connections"] = connInfo
// Blacklist — stale spread coins
bl := d.trader.GetBlacklist()
blList := make([]map[string]interface{}, 0, len(bl))
for coin, t := range bl {
if d.trader.cfg.BlacklistDuration > 0 && time.Since(t) >= d.trader.cfg.BlacklistDuration {
continue // expired, will be cleaned up on next check
}
remaining := time.Duration(0)
if d.trader.cfg.BlacklistDuration > 0 {
remaining = d.trader.cfg.BlacklistDuration - time.Since(t)
}
blList = append(blList, map[string]interface{}{
"coin": coin,
"since": t.Format("15:04:05"),
"remaining_sec": int(remaining.Seconds()),
})
}
stats["blacklist"] = blList
d.hub.Broadcast("stats", stats)
}
}