feat: 添加OKX行情接入+趋势检测+累积变动系统+界面重构
- 新增OKX WebSocket行情连接器,扩展4交易所价格监控 - 新增z-score趋势检测引擎(TrendDetector),识别价格异动/趋势启动 - 新增累积变动跟踪(CumulativeTracker),基于1min/5min多交易所共识 - 趋势事件和累积变动事件持久化到SQLite - 新增Binance/OKX动量检测字段,扩展前端动量卡片至15列 - 迁移至macOS(darwin-arm64),更新前端依赖 - Dashboard网格重构:非交易卡片置顶,交易卡片置底 - TrackedCoin添加OK字段,添加ExBinance/ExOKX常量 - 前端新增趋势检测卡片、趋势历史卡片、累积变动卡片 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
047571921e
commit
b7767c95ae
+102
-2
@@ -189,6 +189,7 @@ type Dashboard struct {
|
||||
trader *Trader
|
||||
db *db.DB
|
||||
addr string
|
||||
cfg *Config
|
||||
|
||||
// cached arb scan results
|
||||
mu sync.RWMutex
|
||||
@@ -198,10 +199,19 @@ type Dashboard struct {
|
||||
// P3-5: connection status — exchange -> last update time
|
||||
connMu sync.RWMutex
|
||||
connMap map[string]time.Time // exchange name -> last price timestamp
|
||||
|
||||
// Momentum tracker
|
||||
momentumTracker *MomentumTracker
|
||||
|
||||
// Trend detector
|
||||
trendDetector *TrendDetector
|
||||
|
||||
// Cumulative tracker (1m/5m multi-exchange consensus)
|
||||
cumulativeTracker *CumulativeTracker
|
||||
}
|
||||
|
||||
func NewDashboard(store *PriceStore, trader *Trader, database *db.DB, addr string) *Dashboard {
|
||||
return &Dashboard{
|
||||
func NewDashboard(store *PriceStore, trader *Trader, database *db.DB, addr string, cfg *Config, momentumTracker *MomentumTracker, trendDetector *TrendDetector, cumulativeTracker *CumulativeTracker) *Dashboard {
|
||||
d := &Dashboard{
|
||||
hub: NewSSEHub(),
|
||||
history: newPriceHistory(),
|
||||
spreads: newSpreadHistory(),
|
||||
@@ -209,8 +219,33 @@ func NewDashboard(store *PriceStore, trader *Trader, database *db.DB, addr strin
|
||||
trader: trader,
|
||||
db: database,
|
||||
addr: addr,
|
||||
cfg: cfg,
|
||||
connMap: make(map[string]time.Time),
|
||||
momentumTracker: momentumTracker,
|
||||
trendDetector: trendDetector,
|
||||
cumulativeTracker: cumulativeTracker,
|
||||
}
|
||||
|
||||
// Wire trend event persistence to SQLite
|
||||
if trendDetector != nil && database != nil {
|
||||
trendDetector.OnEvent = func(ev TrendEvent) {
|
||||
database.InsertTrendEvent(ev.Coin, ev.PrevState, ev.NewState, ev.Direction,
|
||||
ev.ZScore, ev.Volatility, ev.BGChange, ev.HLChange, ev.BNChange, ev.OKXChange,
|
||||
ev.ExAgree, ev.ExTotal)
|
||||
}
|
||||
}
|
||||
|
||||
// Wire cumulative event persistence to SQLite
|
||||
if cumulativeTracker != nil && database != nil {
|
||||
cumulativeTracker.OnEvent = func(ev CmEvent) {
|
||||
database.InsertCmEvent(ev.Coin, ev.PrevState, ev.NewState, ev.Direction,
|
||||
ev.Score, ev.AvgChange, ev.ExAgree, ev.ExTotal,
|
||||
ev.BGChange1m, ev.HLChange1m, ev.BNChange1m, ev.OKXChange1m,
|
||||
ev.BGChange5m, ev.HLChange5m, ev.BNChange5m, ev.OKXChange5m)
|
||||
}
|
||||
}
|
||||
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *Dashboard) Run() {
|
||||
@@ -239,6 +274,8 @@ func (d *Dashboard) Run() {
|
||||
mux.HandleFunc("GET /api/trades", d.handleTrades)
|
||||
mux.HandleFunc("GET /api/trade/", d.handleTradeDetail)
|
||||
mux.HandleFunc("GET /api/connections", d.handleConnStatus) // P3-5
|
||||
mux.HandleFunc("GET /api/trend-history", d.handleTrendHistory)
|
||||
mux.HandleFunc("GET /api/cm-history", d.handleCmHistory)
|
||||
mux.HandleFunc("GET /events", d.handleSSE)
|
||||
mux.HandleFunc("POST /api/stop", d.handleStop)
|
||||
mux.HandleFunc("POST /api/start", d.handleStart)
|
||||
@@ -534,6 +571,32 @@ func (d *Dashboard) broadcastLoop() {
|
||||
stats["blacklist"] = blList
|
||||
|
||||
d.hub.Broadcast("stats", stats)
|
||||
|
||||
// 5. Momentum data (if enabled and tracker is available)
|
||||
if d.momentumTracker != nil && d.cfg.MomentumEnabled {
|
||||
momentumData := d.momentumTracker.Snapshot(d.cfg.MomentumThresholdPct)
|
||||
if len(momentumData) > 0 {
|
||||
d.hub.Broadcast("momentum", momentumData)
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Trend detection (if enabled)
|
||||
if d.trendDetector != nil && d.cfg.TrendEnabled {
|
||||
d.trendDetector.Tick()
|
||||
trendData := d.trendDetector.Snapshot()
|
||||
if len(trendData) > 0 {
|
||||
d.hub.Broadcast("trend", trendData)
|
||||
}
|
||||
}
|
||||
|
||||
// 7. Cumulative change tracking (always on if tracker exists)
|
||||
if d.cumulativeTracker != nil {
|
||||
d.cumulativeTracker.Tick()
|
||||
cmData := d.cumulativeTracker.GetTopCoins(30)
|
||||
if len(cmData) > 0 {
|
||||
d.hub.Broadcast("cumulative", cmData)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -664,6 +727,43 @@ func (d *Dashboard) handleConnStatus(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(w, conns)
|
||||
}
|
||||
|
||||
func (d *Dashboard) handleTrendHistory(w http.ResponseWriter, r *http.Request) {
|
||||
var events interface{}
|
||||
if d.db != nil {
|
||||
records, err := d.db.GetTrendEvents(200)
|
||||
if err == nil {
|
||||
events = records
|
||||
}
|
||||
}
|
||||
if events == nil {
|
||||
// Fallback to in-memory ring buffer
|
||||
if d.trendDetector != nil {
|
||||
events = d.trendDetector.GetEvents(200)
|
||||
} else {
|
||||
events = []interface{}{}
|
||||
}
|
||||
}
|
||||
writeJSON(w, map[string]interface{}{"events": events})
|
||||
}
|
||||
|
||||
func (d *Dashboard) handleCmHistory(w http.ResponseWriter, r *http.Request) {
|
||||
var events interface{}
|
||||
if d.db != nil {
|
||||
records, err := d.db.GetCmEvents(200)
|
||||
if err == nil {
|
||||
events = records
|
||||
}
|
||||
}
|
||||
if events == nil {
|
||||
if d.cumulativeTracker != nil {
|
||||
events = d.cumulativeTracker.GetEvents(200)
|
||||
} else {
|
||||
events = []interface{}{}
|
||||
}
|
||||
}
|
||||
writeJSON(w, map[string]interface{}{"events": events})
|
||||
}
|
||||
|
||||
func (d *Dashboard) handleTrades(w http.ResponseWriter, r *http.Request) {
|
||||
if d.db == nil {
|
||||
writeJSON(w, map[string]interface{}{"trades": []interface{}{}, "total": 0})
|
||||
|
||||
Reference in New Issue
Block a user