feat: 所有参数移至config.json, 重构退出逻辑, 清理遗留接口
- 将所有硬编码参数迁移到 config.json (手续费率、止盈/止损阈值、 超时、腿间隔、加仓步进等) - 退出条件: 净利 >= take_profit_pct 止盈, 价差 <= 0 平仓 - 删除 Binance/dYdX 遗留代码 - 更新 README 文档 - Dashboard: 双交易所价格表、黑名单UI、按币名排序持仓 - Bitget WS: 文本ping保活 - 数据库: 重置, 无历史仓位
This commit is contained in:
+47
-23
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user