candles based alerts

This commit is contained in:
yash 2026-02-26 16:02:11 +03:00
parent bec3b7de5b
commit 999f675da9
11 changed files with 316 additions and 15 deletions

View file

@ -6,6 +6,7 @@ import (
"fmt"
"log/slog"
"net/http"
"strconv"
"time"
"gitea.computernetthings.ru/yash/crypto_alert_bot/internal/config"
@ -73,3 +74,95 @@ func (b *Bybit) Price(ctx context.Context, instrument entities.Instrument) (*ent
Instrument: instrument,
}, nil
}
const klineLimit = 1000
var intervalToBybitIntervalMapper = map[provider.KlineInterval]string{
provider.Kline1m: "1",
provider.Kline3m: "3",
provider.Kline5m: "5",
provider.Kline15m: "15",
provider.Kline30m: "30",
provider.Kline1H: "60",
provider.Kline4H: "240",
provider.Kline6H: "360",
provider.Kline12H: "720",
provider.Kline1D: "D",
provider.Kline1W: "W",
}
// intervalDuration returns the time.Duration corresponding to a Bybit kline interval string.
func intervalBybit(interval provider.KlineInterval) (string, error) {
i, ok := intervalToBybitIntervalMapper[interval]
if !ok {
return "", fmt.Errorf("interval not found")
}
return i, nil
}
// Candles returns OHLC candles for the given interval in the [from, to) range.
// It paginates automatically when the range exceeds klineLimit candles per request.
func (b *Bybit) Candles(ctx context.Context, instrument entities.Instrument, from, to time.Time, interval provider.KlineInterval) ([]entities.Candle, error) {
dur := interval.ToDuration()
limit := klineLimit
var allCandles []entities.Candle
batchFrom := from
for batchFrom.Before(to) {
batchTo := batchFrom.Add(time.Duration(limit) * dur)
if batchTo.After(to) {
batchTo = to
}
bybitInterval, err := intervalBybit(interval)
if err != nil {
return nil, err
}
req := marketKlineReq{
Category: categorySpot,
Symbol: b.symbol(&instrument),
Interval: bybitInterval,
Start: strconv.FormatInt(batchFrom.UnixMilli(), 10),
End: strconv.FormatInt(batchTo.UnixMilli(), 10),
Limit: &limit,
}
var resp marketKlineResp
if err := b.makeRequest(ctx, http.MethodGet, "/v5/market/kline", req, &resp); err != nil {
return nil, fmt.Errorf("failed to get kline [%s, %s]: %w", batchFrom.Format(time.RFC3339), batchTo.Format(time.RFC3339), err)
}
for _, item := range resp.List {
if len(item) < 4 {
b.log.Error("bybit candles: length of elements less then 4", "len", len(item))
continue
}
startMs, err := strconv.ParseInt(item[0], 10, 64)
if err != nil {
b.log.Error("bybit candles: failed to parse start time", "err", err)
continue
}
high, err := decimal.NewFromString(item[2])
if err != nil {
b.log.Error("bybit candles: failed to parse candle high price", "err", err)
continue
}
low, err := decimal.NewFromString(item[3])
if err != nil {
b.log.Error("bybit candles: failed to parse candle low price", "err", err)
continue
}
allCandles = append(allCandles, entities.Candle{
OpenTime: time.UnixMilli(startMs),
High: high,
Low: low,
})
}
batchFrom = batchTo
}
return allCandles, nil
}