Files
bybit_btcusdt_ingest/trade_handler.go
T

132 lines
2.7 KiB
Go

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)
}