From 16d38bb3cfeab7a6ddf7fba1fb5f30f49ee5343e Mon Sep 17 00:00:00 2001 From: yash Date: Wed, 25 Feb 2026 22:33:52 +0300 Subject: [PATCH] instrument usecases & repository methods --- internal/entities/{pair.go => instrument.go} | 5 +- internal/entities/price.go | 8 +-- internal/provider/bybit/bybit.go | 14 +++--- internal/provider/provider.go | 2 +- internal/repository/postgresql/instrument.go | 53 ++++++++++++++++++++ internal/repository/repository.go | 3 ++ internal/usecase/instrument.go | 28 +++++++++++ 7 files changed, 100 insertions(+), 13 deletions(-) rename internal/entities/{pair.go => instrument.go} (64%) create mode 100644 internal/usecase/instrument.go diff --git a/internal/entities/pair.go b/internal/entities/instrument.go similarity index 64% rename from internal/entities/pair.go rename to internal/entities/instrument.go index 1bb4a3a..eac2ee2 100644 --- a/internal/entities/pair.go +++ b/internal/entities/instrument.go @@ -1,6 +1,9 @@ package entities -type Pair struct { +type InstrumentID string + +type Instrument struct { + ID InstrumentID BaseCurrency string // base currency of the pair. e.g. BTC. QuoteCurrency string // quote currency of the pair. e.g. USDT. } diff --git a/internal/entities/price.go b/internal/entities/price.go index b3a7fb8..2254c69 100644 --- a/internal/entities/price.go +++ b/internal/entities/price.go @@ -3,8 +3,8 @@ package entities import "github.com/shopspring/decimal" type Price struct { - Ask decimal.Decimal // limit seller / market buyer. ask > bid. - Bid decimal.Decimal // limit buyer / market seller. bid < ask. - Spread decimal.Decimal // delta between ask and bid. - Pair Pair // trading pair + Ask decimal.Decimal // limit seller / market buyer. ask > bid. + Bid decimal.Decimal // limit buyer / market seller. bid < ask. + Spread decimal.Decimal // delta between ask and bid. + Instrument Instrument // trading pair } diff --git a/internal/provider/bybit/bybit.go b/internal/provider/bybit/bybit.go index 65e77d6..2198648 100644 --- a/internal/provider/bybit/bybit.go +++ b/internal/provider/bybit/bybit.go @@ -36,17 +36,17 @@ func New(log *slog.Logger, cfg *config.Bybit) provider.Provider { } } -func (b *Bybit) symbol(pair *entities.Pair) string { +func (b *Bybit) symbol(pair *entities.Instrument) string { return fmt.Sprintf("%s%s", pair.BaseCurrency, pair.QuoteCurrency) } // Price returns the current price of the pair (base currency / quote currency). // e.g. BTC/USDT. -func (b *Bybit) Price(ctx context.Context, pair entities.Pair) (*entities.Price, error) { +func (b *Bybit) Price(ctx context.Context, instrument entities.Instrument) (*entities.Price, error) { // build request req := marketOrderbookReq{ Category: categorySpot, - Symbol: b.symbol(&pair), + Symbol: b.symbol(&instrument), } var resp marketOrderbookResp // make request @@ -67,9 +67,9 @@ func (b *Bybit) Price(ctx context.Context, pair entities.Pair) (*entities.Price, spread := askPrice.Sub(bidPrice) return &entities.Price{ - Ask: askPrice, - Bid: bidPrice, - Spread: spread, - Pair: pair, + Ask: askPrice, + Bid: bidPrice, + Spread: spread, + Instrument: instrument, }, nil } diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 78df467..5b3c722 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -9,5 +9,5 @@ import ( type Provider interface { // Price returns the current price of the pair (base currency / quote currency). // e.g. BTC/USDT. - Price(ctx context.Context, pair entities.Pair) (*entities.Price, error) + Price(ctx context.Context, instrument entities.Instrument) (*entities.Price, error) } diff --git a/internal/repository/postgresql/instrument.go b/internal/repository/postgresql/instrument.go index 4e9a54a..cf5ab10 100644 --- a/internal/repository/postgresql/instrument.go +++ b/internal/repository/postgresql/instrument.go @@ -1 +1,54 @@ package postgresql + +import ( + "context" + "fmt" + + "gitea.computernetthings.ru/yash/crypto_alert_bot/internal/entities" +) + +const instrumentListQuery = ` +select i.id, c_base.symbol, c_quote.symbol +from instrument i +join currency c_base on c_base.id = i.base_currency_id +join currency c_quote on c_quote.id = i.quoted_currency_id +order by i.id +offset $1 limit $2` + +func (p *Postgresql) InstrumentList(ctx context.Context, offset, limit int) ([]entities.Instrument, error) { + rows, err := p.db.Query(ctx, instrumentListQuery, offset, limit) + if err != nil { + return nil, fmt.Errorf("failed to exec instrumentListQuery: %w", err) + } + defer rows.Close() + + var instruments []entities.Instrument + for rows.Next() { + var inst entities.Instrument + if err := rows.Scan(&inst.ID, &inst.BaseCurrency, &inst.QuoteCurrency); err != nil { + return nil, fmt.Errorf("failed to scan instrument row: %w", err) + } + instruments = append(instruments, inst) + } + + return instruments, nil +} + +const createInstrumentQuery = ` +insert into instrument(base_currency_id, quoted_currency_id) +values ( + (select id from currency where symbol = $1), + (select id from currency where symbol = $2) +) +returning id` + +func (p *Postgresql) CreateInstrument(ctx context.Context, instrument *entities.Instrument) (entities.InstrumentID, error) { + var id entities.InstrumentID + + err := p.db.QueryRow(ctx, createInstrumentQuery, instrument.BaseCurrency, instrument.QuoteCurrency).Scan(&id) + if err != nil { + return "", fmt.Errorf("failed to exec createInstrumentQuery: %w", err) + } + + return id, nil +} diff --git a/internal/repository/repository.go b/internal/repository/repository.go index dd8d9c5..e989202 100644 --- a/internal/repository/repository.go +++ b/internal/repository/repository.go @@ -10,4 +10,7 @@ type Storage interface { SaveUser(ctx context.Context, user *entities.User) (entities.UserID, error) UserByID(ctx context.Context, id entities.UserID) (*entities.User, error) UserByTelegramID(ctx context.Context, tgID entities.TelegramID) (*entities.User, error) + + InstrumentList(ctx context.Context, offset, limit int) ([]entities.Instrument, error) + CreateInstrument(ctx context.Context, instrument *entities.Instrument) (entities.InstrumentID, error) } diff --git a/internal/usecase/instrument.go b/internal/usecase/instrument.go new file mode 100644 index 0000000..40b0242 --- /dev/null +++ b/internal/usecase/instrument.go @@ -0,0 +1,28 @@ +package usecase + +import ( + "context" + "fmt" + + "gitea.computernetthings.ru/yash/crypto_alert_bot/internal/entities" +) + +func (uc *Usecase) InstrumentList(ctx context.Context, offset, limit int) ([]entities.Instrument, error) { + instruments, err := uc.storage.InstrumentList(ctx, offset, limit) + if err != nil { + uc.log.Error("failed to list instruments", "offset", offset, "limit", limit, "err", err) + return nil, fmt.Errorf("failed to list instruments: %w", err) + } + + return instruments, nil +} + +func (uc *Usecase) CreateInstrument(ctx context.Context, instrument *entities.Instrument) (entities.InstrumentID, error) { + id, err := uc.storage.CreateInstrument(ctx, instrument) + if err != nil { + uc.log.Error("failed to create instrument", "instrument", instrument, "err", err) + return "", fmt.Errorf("failed to create instrument: %w", err) + } + + return id, nil +}