package main import ( "encoding/json" "fmt" "log" "strconv" "time" ) // TradeHandler handles publicTrade stream messages, feeding the trade aggregator and writer. type TradeHandler struct { cfg Config storage *StreamStorage tickCh chan Tick aggregator *Aggregator writer *Writer } // NewTradeHandler initializes the trade handler, tick channel, aggregator, and writer. func NewTradeHandler(cfg Config, sm *StorageManager) (*TradeHandler, error) { ss := sm.GetStreamStorage("trades") if ss == nil { return nil, fmt.Errorf("trades stream storage not found") } tickCh := make(chan Tick, cfg.TickChannelBuffer) // Aggregator uses trade features.db featDB, err := OpenDBWithAutoVacuum(ss.DBPath("features.db")) if err != nil { return nil, fmt.Errorf("open trade features db: %w", err) } var lastPrice float64 err = featDB.QueryRow(` SELECT close_price FROM five_second_features ORDER BY timestamp DESC LIMIT 1 `).Scan(&lastPrice) if err != nil { lastPrice = 0 } agg := &Aggregator{ featDB: featDB, lastPrice: lastPrice, } hotDB, err := OpenDBWithAutoVacuum(ss.DBPath("hot_ticks.db")) if err != nil { featDB.Close() return nil, fmt.Errorf("open hot_ticks db: %w", err) } hotDB.SetMaxOpenConns(1) w := &Writer{ tickCh: tickCh, hotDB: hotDB, batchSize: cfg.WriterBatchSize, flushMs: cfg.WriterFlushIntervalMs, } return &TradeHandler{ cfg: cfg, storage: ss, tickCh: tickCh, aggregator: agg, writer: w, }, nil } func (th *TradeHandler) Topics() []string { return []string{fmt.Sprintf("publicTrade.%s", th.cfg.Symbol)} } func (th *TradeHandler) HandleMessage(data []byte) { var msg BybitWSMessage if err := json.Unmarshal(data, &msg); err != nil { return } if len(msg.Data) == 0 { return } recvTS := time.Now().UnixMilli() for _, raw := range msg.Data { price, err := strconv.ParseFloat(raw.P, 64) if err != nil { log.Printf("[trade_handler] bad price %q: %v", raw.P, err) continue } volume, err := strconv.ParseFloat(raw.V, 64) if err != nil { log.Printf("[trade_handler] bad volume %q: %v", raw.V, err) continue } tick := Tick{ TradeID: raw.I, Seq: raw.Seq, TradeTS: raw.T, MessageTS: msg.TS, RecvTS: recvTS, Symbol: raw.S, Side: raw.SD, Price: price, Volume: volume, TickDir: raw.L, BlockTrade: raw.BT, RPI: raw.RPI, } // Feed feature generator th.aggregator.ProcessTick(tick) // Feed writer select { case th.tickCh <- tick: default: log.Println("[trade_handler] WARNING: tick channel full, dropping tick") } } } func (th *TradeHandler) Close() { th.aggregator.Close() close(th.tickCh) }