feat: 接入 CoinGlass 清算热力图隔离链路
Some checks failed
ci / build + vet + guard + race (push) Has been cancelled
Some checks failed
ci / build + vet + guard + race (push) Has been cancelled
This commit is contained in:
@@ -15,6 +15,7 @@ import (
|
||||
"cryptoHermes/internal/controller/restapi"
|
||||
"cryptoHermes/internal/repo/persistent/postgres"
|
||||
"cryptoHermes/internal/repo/webapi/binance"
|
||||
coinglassapi "cryptoHermes/internal/repo/webapi/coinglass"
|
||||
"cryptoHermes/internal/usecase"
|
||||
"cryptoHermes/internal/worker"
|
||||
pgpkg "cryptoHermes/pkg/postgres"
|
||||
@@ -47,6 +48,7 @@ func Run(cfg *config.Config, log *slog.Logger) error {
|
||||
oiRepo := postgres.NewOpenInterestRepo(pool)
|
||||
lsRepo := postgres.NewLongShortRatioRepo(pool)
|
||||
takerRepo := postgres.NewTakerVolumeRepo(pool)
|
||||
cgLiqRepo := postgres.NewCoinglassLiqHeatMapRepo(pool)
|
||||
|
||||
contextUC := usecase.NewMarketContextUsecase(
|
||||
binanceClient,
|
||||
@@ -67,23 +69,56 @@ func Run(cfg *config.Config, log *slog.Logger) error {
|
||||
lsRepo,
|
||||
)
|
||||
|
||||
collector := worker.NewCollector(worker.Deps{
|
||||
MarketData: binanceClient,
|
||||
Derivatives: binanceClient,
|
||||
KlineRepo: klineRepo,
|
||||
FundingRepo: fundingRepo,
|
||||
OIRepo: oiRepo,
|
||||
LSRepo: lsRepo,
|
||||
TakerRepo: takerRepo,
|
||||
Symbols: cfg.Collector.Symbols,
|
||||
Intervals: cfg.Collector.Intervals,
|
||||
Limit: cfg.Collector.DefaultLimit,
|
||||
Logger: log,
|
||||
})
|
||||
cgQueryUC := usecase.NewCoinglassQueryUsecase(cgLiqRepo)
|
||||
|
||||
var collector *worker.Collector
|
||||
if cfg.Collector.Enabled {
|
||||
collector = worker.NewCollector(worker.Deps{
|
||||
MarketData: binanceClient,
|
||||
Derivatives: binanceClient,
|
||||
KlineRepo: klineRepo,
|
||||
FundingRepo: fundingRepo,
|
||||
OIRepo: oiRepo,
|
||||
LSRepo: lsRepo,
|
||||
TakerRepo: takerRepo,
|
||||
Symbols: cfg.Collector.Symbols,
|
||||
Intervals: cfg.Collector.Intervals,
|
||||
Limit: cfg.Collector.DefaultLimit,
|
||||
Logger: log,
|
||||
})
|
||||
}
|
||||
|
||||
var cgCollector *worker.CoinglassCollector
|
||||
if cfg.Coinglass.Enabled {
|
||||
cgClient, err := coinglassapi.NewClient(coinglassapi.ClientOptions{
|
||||
BaseURL: cfg.Coinglass.BaseURL,
|
||||
TOTPSecret: cfg.Coinglass.TOTPSecret,
|
||||
DataAESKey: cfg.Coinglass.DataAESKey,
|
||||
Timeout: cfg.Coinglass.Timeout,
|
||||
FakeSuccessRetry: cfg.Coinglass.FakeSuccessRetry,
|
||||
FakeSuccessRetryWait: cfg.Coinglass.FakeSuccessRetryWait,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("coinglass client: %w", err)
|
||||
}
|
||||
cgCollector = worker.NewCoinglassCollector(worker.CoinglassCollectorDeps{
|
||||
Provider: cgClient,
|
||||
LiqHeatMapRepo: cgLiqRepo,
|
||||
Symbols: cfg.Collector.Symbols,
|
||||
LiqHeatMapInterval: cfg.Coinglass.LiqHeatMapInterval,
|
||||
LiqHeatMapLimit: cfg.Coinglass.LiqHeatMapLimit,
|
||||
BackoffInitial: cfg.Coinglass.BackoffInitial,
|
||||
BackoffMax: cfg.Coinglass.BackoffMax,
|
||||
Logger: log,
|
||||
})
|
||||
}
|
||||
|
||||
var sched *Scheduler
|
||||
if cfg.Collector.Enabled {
|
||||
if collector != nil || cgCollector != nil {
|
||||
sched = NewScheduler(collector, log)
|
||||
if cgCollector != nil {
|
||||
sched.WithCoinglass(cgCollector, cfg.Coinglass.LiqHeatMapCron, cfg.Coinglass.StartupJitterMax)
|
||||
}
|
||||
sched.Start(rootCtx)
|
||||
defer sched.Stop()
|
||||
}
|
||||
@@ -97,11 +132,13 @@ func Run(cfg *config.Config, log *slog.Logger) error {
|
||||
})
|
||||
|
||||
restapi.Register(fiberApp, restapi.Deps{
|
||||
Logger: log,
|
||||
MarketContext: contextUC,
|
||||
MarketQuery: queryUC,
|
||||
MarketData: binanceClient,
|
||||
KlineRepo: klineRepo,
|
||||
Logger: log,
|
||||
MarketContext: contextUC,
|
||||
MarketQuery: queryUC,
|
||||
CoinglassQuery: cgQueryUC,
|
||||
CoinglassEnabled: cfg.Coinglass.Enabled,
|
||||
MarketData: binanceClient,
|
||||
KlineRepo: klineRepo,
|
||||
})
|
||||
|
||||
listenErr := make(chan error, 1)
|
||||
|
||||
@@ -3,6 +3,8 @@ package app
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/robfig/cron/v3"
|
||||
|
||||
@@ -12,8 +14,12 @@ import (
|
||||
type Scheduler struct {
|
||||
cron *cron.Cron
|
||||
collector *worker.Collector
|
||||
coinglass *worker.CoinglassCollector
|
||||
log *slog.Logger
|
||||
rootCtx context.Context
|
||||
|
||||
coinglassLiqHeatMapCron string
|
||||
coinglassStartupJitter time.Duration
|
||||
}
|
||||
|
||||
func NewScheduler(c *worker.Collector, log *slog.Logger) *Scheduler {
|
||||
@@ -24,19 +30,37 @@ func NewScheduler(c *worker.Collector, log *slog.Logger) *Scheduler {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Scheduler) WithCoinglass(c *worker.CoinglassCollector, liqHeatMapCron string, startupJitter time.Duration) *Scheduler {
|
||||
s.coinglass = c
|
||||
s.coinglassLiqHeatMapCron = liqHeatMapCron
|
||||
s.coinglassStartupJitter = startupJitter
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *Scheduler) Start(ctx context.Context) {
|
||||
s.rootCtx = ctx
|
||||
|
||||
s.add("*/15 * * * *", "klines_15m", func(c context.Context) { _ = s.collector.CollectKlines(c, "15m") })
|
||||
s.add("0 * * * *", "klines_1h", func(c context.Context) { _ = s.collector.CollectKlines(c, "1h") })
|
||||
s.add("0 */4 * * *", "klines_4h", func(c context.Context) { _ = s.collector.CollectKlines(c, "4h") })
|
||||
s.add("5 0 * * *", "klines_1d", func(c context.Context) { _ = s.collector.CollectKlines(c, "1d") })
|
||||
s.add("10 0 * * 1", "klines_1w", func(c context.Context) { _ = s.collector.CollectKlines(c, "1w") })
|
||||
if s.collector != nil {
|
||||
s.add("*/15 * * * *", "klines_15m", func(c context.Context) { _ = s.collector.CollectKlines(c, "15m") })
|
||||
s.add("0 * * * *", "klines_1h", func(c context.Context) { _ = s.collector.CollectKlines(c, "1h") })
|
||||
s.add("0 */4 * * *", "klines_4h", func(c context.Context) { _ = s.collector.CollectKlines(c, "4h") })
|
||||
s.add("5 0 * * *", "klines_1d", func(c context.Context) { _ = s.collector.CollectKlines(c, "1d") })
|
||||
s.add("10 0 * * 1", "klines_1w", func(c context.Context) { _ = s.collector.CollectKlines(c, "1w") })
|
||||
|
||||
s.add("*/15 * * * *", "funding", func(c context.Context) { _ = s.collector.CollectFunding(c) })
|
||||
s.add("*/15 * * * *", "oi", func(c context.Context) { _ = s.collector.CollectOpenInterest(c) })
|
||||
s.add("*/15 * * * *", "ls", func(c context.Context) { _ = s.collector.CollectLongShortRatio(c) })
|
||||
s.add("*/15 * * * *", "taker", func(c context.Context) { _ = s.collector.CollectTakerVolume(c) })
|
||||
s.add("*/15 * * * *", "funding", func(c context.Context) { _ = s.collector.CollectFunding(c) })
|
||||
s.add("*/15 * * * *", "oi", func(c context.Context) { _ = s.collector.CollectOpenInterest(c) })
|
||||
s.add("*/15 * * * *", "ls", func(c context.Context) { _ = s.collector.CollectLongShortRatio(c) })
|
||||
s.add("*/15 * * * *", "taker", func(c context.Context) { _ = s.collector.CollectTakerVolume(c) })
|
||||
}
|
||||
|
||||
if s.coinglass != nil {
|
||||
spec := s.coinglassLiqHeatMapCron
|
||||
if spec == "" {
|
||||
spec = "*/5 * * * *"
|
||||
}
|
||||
s.add(spec, "coinglass_liq_heatmap", func(c context.Context) { _ = s.coinglass.CollectLiqHeatMap(c) })
|
||||
s.startCoinglassWarmup(ctx)
|
||||
}
|
||||
|
||||
s.cron.Start()
|
||||
s.log.Info("scheduler_started")
|
||||
@@ -52,6 +76,21 @@ func (s *Scheduler) add(spec, name string, fn func(context.Context)) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Scheduler) startCoinglassWarmup(ctx context.Context) {
|
||||
go func() {
|
||||
if s.coinglassStartupJitter > 0 {
|
||||
jitter := time.Duration(rand.Int63n(int64(s.coinglassStartupJitter)))
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-time.After(jitter):
|
||||
}
|
||||
}
|
||||
s.log.Info("cron_tick", "job", "coinglass_liq_heatmap_warmup")
|
||||
_ = s.coinglass.CollectLiqHeatMap(ctx)
|
||||
}()
|
||||
}
|
||||
|
||||
func (s *Scheduler) Stop() {
|
||||
s.log.Info("scheduler_stopping")
|
||||
<-s.cron.Stop().Done()
|
||||
|
||||
Reference in New Issue
Block a user