- Add backtest and market socket handlers for API and Worker - Add proto definitions for backtest and market services - Update client socket integration and tests - Generate protobuf code for Go and Dart - Archive completed agent tasks under agent-task/archive/2026/05/
216 lines
6.3 KiB
Go
216 lines
6.3 KiB
Go
package postgres
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/jackc/pgx/v5"
|
|
"github.com/jackc/pgx/v5/pgtype"
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
|
|
"git.toki-labs.com/toki/alt/packages/domain/backtest"
|
|
"git.toki-labs.com/toki/alt/packages/domain/market"
|
|
"git.toki-labs.com/toki/alt/services/worker/internal/storage"
|
|
"git.toki-labs.com/toki/alt/services/worker/internal/storage/postgres/sqlc"
|
|
)
|
|
|
|
var _ storage.InstrumentStore = (*Store)(nil)
|
|
var _ storage.BarStore = (*Store)(nil)
|
|
var _ storage.BacktestRunStore = (*Store)(nil)
|
|
var _ storage.BacktestResultStore = (*Store)(nil)
|
|
var _ storage.BacktestAnalysisStore = (*Store)(nil)
|
|
|
|
type Store struct {
|
|
pool *pgxpool.Pool
|
|
queries *sqlc.Queries
|
|
}
|
|
|
|
func NewStore(pool *pgxpool.Pool) *Store {
|
|
return &Store{
|
|
pool: pool,
|
|
queries: sqlc.New(pool),
|
|
}
|
|
}
|
|
|
|
func (s *Store) UpsertInstrument(ctx context.Context, inst market.Instrument) error {
|
|
params, err := mapInstrumentToParams(inst)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to map instrument: %w", err)
|
|
}
|
|
if err := s.queries.UpsertInstrument(ctx, params); err != nil {
|
|
return fmt.Errorf("failed to upsert instrument: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *Store) GetInstrument(ctx context.Context, id market.InstrumentID) (market.Instrument, error) {
|
|
row, err := s.queries.GetInstrument(ctx, string(id))
|
|
if err != nil {
|
|
if errors.Is(err, pgx.ErrNoRows) {
|
|
return market.Instrument{}, storage.ErrInstrumentNotFound
|
|
}
|
|
return market.Instrument{}, fmt.Errorf("failed to get instrument: %w", err)
|
|
}
|
|
inst, err := mapRowToInstrument(row)
|
|
if err != nil {
|
|
return market.Instrument{}, fmt.Errorf("failed to map instrument row: %w", err)
|
|
}
|
|
return inst, nil
|
|
}
|
|
|
|
func (s *Store) ListInstruments(ctx context.Context) ([]market.Instrument, error) {
|
|
rows, err := s.queries.ListInstruments(ctx)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to list instruments: %w", err)
|
|
}
|
|
insts := make([]market.Instrument, len(rows))
|
|
for i, row := range rows {
|
|
inst, err := mapRowToInstrument(row)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to map instrument row at index %d: %w", i, err)
|
|
}
|
|
insts[i] = inst
|
|
}
|
|
return insts, nil
|
|
}
|
|
|
|
func (s *Store) UpsertBar(ctx context.Context, bar market.Bar) error {
|
|
params, err := mapBarToParams(bar)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to map bar: %w", err)
|
|
}
|
|
if err := s.queries.UpsertBar(ctx, params); err != nil {
|
|
return fmt.Errorf("failed to upsert bar: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *Store) GetBars(ctx context.Context, id market.InstrumentID, timeframe market.Timeframe, from, to time.Time) ([]market.Bar, error) {
|
|
// First, fetch the instrument to know its currency
|
|
instRow, err := s.queries.GetInstrument(ctx, string(id))
|
|
if err != nil {
|
|
if errors.Is(err, pgx.ErrNoRows) {
|
|
return nil, storage.ErrInstrumentNotFound
|
|
}
|
|
return nil, fmt.Errorf("failed to get instrument for bar currency: %w", err)
|
|
}
|
|
|
|
params := sqlc.GetBarsParams{
|
|
InstrumentID: string(id),
|
|
Timeframe: string(timeframe),
|
|
Timestamp: pgtype.Timestamptz{Time: from, Valid: !from.IsZero()},
|
|
Timestamp_2: pgtype.Timestamptz{Time: to, Valid: !to.IsZero()},
|
|
}
|
|
rows, err := s.queries.GetBars(ctx, params)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get bars: %w", err)
|
|
}
|
|
|
|
bars := make([]market.Bar, len(rows))
|
|
for i, row := range rows {
|
|
bar, err := mapRowToBar(row, market.Currency(instRow.Currency))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to map bar row at index %d: %w", i, err)
|
|
}
|
|
bars[i] = bar
|
|
}
|
|
return bars, nil
|
|
}
|
|
|
|
func (s *Store) UpsertRun(ctx context.Context, run backtest.Run) error {
|
|
params, err := mapRunToParams(run)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to map run: %w", err)
|
|
}
|
|
if err := s.queries.UpsertRun(ctx, params); err != nil {
|
|
return fmt.Errorf("failed to upsert run: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *Store) GetRun(ctx context.Context, id backtest.RunID) (backtest.Run, error) {
|
|
row, err := s.queries.GetRun(ctx, string(id))
|
|
if err != nil {
|
|
if errors.Is(err, pgx.ErrNoRows) {
|
|
return backtest.Run{}, storage.ErrRunNotFound
|
|
}
|
|
return backtest.Run{}, fmt.Errorf("failed to get run: %w", err)
|
|
}
|
|
run, err := mapRowToRun(row)
|
|
if err != nil {
|
|
return backtest.Run{}, fmt.Errorf("failed to map run row: %w", err)
|
|
}
|
|
return run, nil
|
|
}
|
|
|
|
func (s *Store) ListRuns(ctx context.Context, status backtest.RunStatus) ([]backtest.Run, error) {
|
|
rows, err := s.queries.ListRuns(ctx, string(status))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to list runs: %w", err)
|
|
}
|
|
runs := make([]backtest.Run, len(rows))
|
|
for i, row := range rows {
|
|
run, err := mapRowToRun(row)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to map run row at index %d: %w", i, err)
|
|
}
|
|
runs[i] = run
|
|
}
|
|
return runs, nil
|
|
}
|
|
|
|
func (s *Store) GetRunDetail(ctx context.Context, id backtest.RunID) (storage.RunDetail, error) {
|
|
run, err := s.GetRun(ctx, id)
|
|
if err != nil {
|
|
return storage.RunDetail{}, err
|
|
}
|
|
|
|
result, err := s.GetResult(ctx, id)
|
|
if err != nil {
|
|
if errors.Is(err, storage.ErrResultNotFound) {
|
|
return storage.RunDetail{Run: run, HasResult: false}, nil
|
|
}
|
|
return storage.RunDetail{}, err
|
|
}
|
|
return storage.RunDetail{Run: run, Result: result, HasResult: true}, nil
|
|
}
|
|
|
|
func (s *Store) CompareResults(ctx context.Context, ids []backtest.RunID) ([]backtest.Result, error) {
|
|
results := make([]backtest.Result, 0, len(ids))
|
|
for _, id := range ids {
|
|
result, err := s.GetResult(ctx, id)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get result for run %s: %w", id, err)
|
|
}
|
|
results = append(results, result)
|
|
}
|
|
return results, nil
|
|
}
|
|
|
|
func (s *Store) UpsertResult(ctx context.Context, result backtest.Result) error {
|
|
params, err := mapResultToParams(result)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to map result: %w", err)
|
|
}
|
|
if err := s.queries.UpsertResult(ctx, params); err != nil {
|
|
return fmt.Errorf("failed to upsert result: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *Store) GetResult(ctx context.Context, id backtest.RunID) (backtest.Result, error) {
|
|
row, err := s.queries.GetResult(ctx, string(id))
|
|
if err != nil {
|
|
if errors.Is(err, pgx.ErrNoRows) {
|
|
return backtest.Result{}, storage.ErrResultNotFound
|
|
}
|
|
return backtest.Result{}, fmt.Errorf("failed to get result: %w", err)
|
|
}
|
|
res, err := mapRowToResult(row)
|
|
if err != nil {
|
|
return backtest.Result{}, fmt.Errorf("failed to map result row: %w", err)
|
|
}
|
|
return res, nil
|
|
}
|