package backtest import ( "context" "testing" "time" "git.toki-labs.com/toki/alt/packages/domain/market" ) type sourceStore struct { instruments []market.Instrument bars map[market.InstrumentID][]market.Bar } func (s *sourceStore) UpsertInstrument(_ context.Context, inst market.Instrument) error { s.instruments = append(s.instruments, inst) return nil } func (s *sourceStore) GetInstrument(_ context.Context, id market.InstrumentID) (market.Instrument, error) { for _, inst := range s.instruments { if inst.ID == id { return inst, nil } } return market.Instrument{}, nil } func (s *sourceStore) ListInstruments(_ context.Context) ([]market.Instrument, error) { out := make([]market.Instrument, len(s.instruments)) copy(out, s.instruments) return out, nil } func (s *sourceStore) UpsertBar(_ context.Context, bar market.Bar) error { if s.bars == nil { s.bars = make(map[market.InstrumentID][]market.Bar) } s.bars[bar.InstrumentID] = append(s.bars[bar.InstrumentID], bar) return nil } func (s *sourceStore) GetBars(_ context.Context, id market.InstrumentID, timeframe market.Timeframe, from, to time.Time) ([]market.Bar, error) { var out []market.Bar for _, bar := range s.bars[id] { if bar.Timeframe != timeframe { continue } if bar.Timestamp.Before(from) || bar.Timestamp.After(to) { continue } out = append(out, bar) } return out, nil } func TestStorageBarSourceReadsMarketBars(t *testing.T) { store := &sourceStore{} krx := market.Instrument{ID: "KRX:005930", Market: market.MarketKR, Venue: market.VenueKRX, Currency: market.CurrencyKRW} nasdaq := market.Instrument{ID: "NASDAQ:AAPL", Market: market.MarketUS, Venue: market.VenueNASDAQ, Currency: market.CurrencyUSD} if err := store.UpsertInstrument(context.Background(), krx); err != nil { t.Fatalf("upsert krx instrument: %v", err) } if err := store.UpsertInstrument(context.Background(), nasdaq); err != nil { t.Fatalf("upsert us instrument: %v", err) } day1 := time.Date(2024, 5, 27, 0, 0, 0, 0, time.UTC) day2 := time.Date(2024, 5, 28, 0, 0, 0, 0, time.UTC) if err := store.UpsertBar(context.Background(), market.Bar{InstrumentID: krx.ID, Timeframe: market.TimeframeDaily, Timestamp: day2}); err != nil { t.Fatalf("upsert day2: %v", err) } if err := store.UpsertBar(context.Background(), market.Bar{InstrumentID: krx.ID, Timeframe: market.TimeframeDaily, Timestamp: day1}); err != nil { t.Fatalf("upsert day1: %v", err) } if err := store.UpsertBar(context.Background(), market.Bar{InstrumentID: nasdaq.ID, Timeframe: market.TimeframeDaily, Timestamp: day1}); err != nil { t.Fatalf("upsert us day1: %v", err) } source := NewStorageBarSource(store, store) got, err := source.GetBars(context.Background(), market.MarketKR, market.TimeframeDaily, day1, day2) if err != nil { t.Fatalf("get bars: %v", err) } if len(got) != 2 { t.Fatalf("bars: got %d, want 2", len(got)) } if got[0].Timestamp != day1 || got[1].Timestamp != day2 { t.Fatalf("bars are not sorted by timestamp: %+v", got) } for _, bar := range got { if bar.InstrumentID != krx.ID { t.Fatalf("unexpected instrument id: %q", bar.InstrumentID) } } }