feat: 重构为三所价差异动监控系统

删除 HyperLiquid + 全部交易功能,构建自适应 surge 检测器。
- 新增 surge_detector.go: 每币独立滚动窗口基线,检测三所价差异常飙升
- 新增 SpreadCard/SurgeCard 前端组件
- 保留 momentum/trend/cumulative/trend_filter 扫描功能
- 更新文档和配置以反映新系统

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
jackyu66git
2026-05-08 02:01:18 +08:00
co-authored by Claude Opus 4.6
parent 559d7bb870
commit d38782490c
36 changed files with 1201 additions and 6374 deletions
+3 -94
View File
@@ -1,7 +1,6 @@
package main
import (
"log"
"sync"
"time"
)
@@ -11,7 +10,6 @@ type TrackedCoin struct {
Name string // Display name (BTC, ETH, etc.)
BN string // Binance symbol (BTCUSDT)
BG string // Bitget symbol (BTCUSDT)
HL string // HyperLiquid symbol (BTC)
OK string // OKX symbol (BTC-USDT-SWAP)
}
@@ -31,9 +29,9 @@ type Spread struct {
// PriceStore holds the latest prices from all exchanges, thread-safe.
type PriceStore struct {
mu sync.RWMutex
prices map[string]map[string]float64 // coin -> exchange -> price
spreads map[string]map[string]*Spread // coin -> exchange -> spread
mu sync.RWMutex
prices map[string]map[string]float64 // coin -> exchange -> price
spreads map[string]map[string]*Spread // coin -> exchange -> spread
}
func NewPriceStore() *PriceStore {
@@ -109,92 +107,3 @@ func (s *PriceStore) GetAll() map[string]map[string]float64 {
}
return snap
}
// ArbOpportunity represents a profitable arbitrage route.
type ArbOpportunity struct {
Coin string
Direction string // e.g. "BN->HL"
BuyEx string
SellEx string
BuyPrice float64
SellPrice float64
NetProfit float64 // percentage after fees
GrossBasis float64 // raw price difference %
}
// SpreadWindow tracks how long each coin's spread stays above threshold.
// Used to measure the window of opportunity between threshold-crossing and
// convergence — helps diagnose whether entry latency is a problem.
type SpreadWindow struct {
Coin string
Direction string // "BG->HL" or "HL->BG"
Since time.Time
PeakNet float64 // highest netProfit % observed during this window
}
type SpreadWindowTracker struct {
windows map[string]*SpreadWindow // key: "COIN:DIRECTION"
}
func NewSpreadWindowTracker() *SpreadWindowTracker {
return &SpreadWindowTracker{windows: make(map[string]*SpreadWindow)}
}
func (swt *SpreadWindowTracker) Tick(snap map[string]map[string]float64, threshold float64) {
now := time.Now()
for _, coin := range TrackedCoins {
if coin.BG == "" || coin.HL == "" {
continue
}
exMap := snap[coin.Name]
if exMap == nil {
continue
}
bgP := exMap[ExBitget]
hlP := exMap[ExHyperLiquid]
if bgP <= 0 || hlP <= 0 {
continue
}
// Check both directions — use netProfit() for exact fee model match
// BG→HL: buy BG (Bitget 0.020%), sell HL (HL 0.015%)
// HL→BG: buy HL (HL 0.015%), sell BG (Bitget 0.020%)
type dirCheck struct {
name string
buyPrice float64
sellPrice float64
buyFee float64
sellFee float64
}
for _, dir := range []dirCheck{
{"BG->HL", bgP, hlP, takerFees[ExBitget], takerFees[ExHyperLiquid]},
{"HL->BG", hlP, bgP, takerFees[ExHyperLiquid], takerFees[ExBitget]},
} {
key := coin.Name + ":" + dir.name
netSpr := netProfit(dir.buyPrice, dir.sellPrice, dir.buyFee, dir.sellFee)
w, exists := swt.windows[key]
if netSpr >= threshold {
if !exists {
swt.windows[key] = &SpreadWindow{
Coin: coin.Name,
Direction: dir.name,
Since: now,
PeakNet: netSpr,
}
} else if netSpr > w.PeakNet {
w.PeakNet = netSpr
}
} else {
if exists {
dur := now.Sub(w.Since)
if dur > 100*time.Millisecond {
log.Printf("[SpreadWindow] %s %s exceeded threshold for %v (peak net=%+.4f%%)",
w.Coin, w.Direction, dur.Round(time.Millisecond), w.PeakNet)
}
delete(swt.windows, key)
}
}
}
}
}