Added klines, liquidations, tickers and trades to be recorded. Bundled the stats as cli argument.
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user