Add SpreadWindowTracker to measure opportunity duration
- New SpreadWindowTracker in types.go watches BG↔HL spread for all tracked coins, both directions - Logs duration when spread stays above trade threshold then converges - Wired into main loop after each scan tick - Filters sub-100ms windows as noise
This commit is contained in:
@@ -51,6 +51,9 @@ func main() {
|
|||||||
dashboard := NewDashboard(store, trader, database, ":8888")
|
dashboard := NewDashboard(store, trader, database, ":8888")
|
||||||
go dashboard.Run()
|
go dashboard.Run()
|
||||||
|
|
||||||
|
// Spread window tracker — measures how long spreads stay above threshold
|
||||||
|
spreadTracker := NewSpreadWindowTracker()
|
||||||
|
|
||||||
// P3-4: wire real-time trade event broadcast
|
// P3-4: wire real-time trade event broadcast
|
||||||
trader.OnTradeEvent = dashboard.BroadcastEvent
|
trader.OnTradeEvent = dashboard.BroadcastEvent
|
||||||
if trader.IsConfigured() {
|
if trader.IsConfigured() {
|
||||||
@@ -170,6 +173,10 @@ func main() {
|
|||||||
dashboard.UpdateScan(makerOpps)
|
dashboard.UpdateScan(makerOpps)
|
||||||
t2 := time.Now()
|
t2 := time.Now()
|
||||||
|
|
||||||
|
// Track spread window durations (how long each opportunity stays alive)
|
||||||
|
snap := store.GetAll()
|
||||||
|
spreadTracker.Tick(snap, cfg.TradeThreshold)
|
||||||
|
|
||||||
for _, opp := range makerOpps {
|
for _, opp := range makerOpps {
|
||||||
if opp.NetProfit < cfg.ArbThreshold {
|
if opp.NetProfit < cfg.ArbThreshold {
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -119,3 +120,69 @@ type ArbOpportunity struct {
|
|||||||
NetProfit float64 // percentage after fees
|
NetProfit float64 // percentage after fees
|
||||||
GrossBasis float64 // raw price difference %
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
exMap := snap[coin.Name]
|
||||||
|
if exMap == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
bgP := exMap[ExBitget]
|
||||||
|
hlP := exMap[ExHyperLiquid]
|
||||||
|
if bgP <= 0 || hlP <= 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check both directions
|
||||||
|
for _, dir := range []struct {
|
||||||
|
name string
|
||||||
|
low float64
|
||||||
|
high float64
|
||||||
|
}{
|
||||||
|
{"BG->HL", bgP, hlP},
|
||||||
|
{"HL->BG", hlP, bgP},
|
||||||
|
} {
|
||||||
|
key := coin.Name + ":" + dir.name
|
||||||
|
spread := (dir.high - dir.low) / dir.low * 100
|
||||||
|
netSpr := spread - (makerFees[ExBitget] + makerFees[ExHyperLiquid]) // rough net
|
||||||
|
|
||||||
|
w, exists := swt.windows[key]
|
||||||
|
if netSpr >= threshold {
|
||||||
|
if !exists {
|
||||||
|
swt.windows[key] = &SpreadWindow{
|
||||||
|
Coin: coin.Name,
|
||||||
|
Direction: dir.name,
|
||||||
|
Since: now,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} 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), netSpr)
|
||||||
|
}
|
||||||
|
delete(swt.windows, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user