add worker data-check command and datacheck package, remove outdated plan/review docs
This commit is contained in:
parent
bee6dbb09a
commit
0bc2403b72
7 changed files with 271 additions and 209 deletions
|
|
@ -1,87 +0,0 @@
|
|||
<!-- task=m-korea-daily-data-foundation/03+01,02_data_check_smoke plan=0 tag=KIS_SMOKE -->
|
||||
|
||||
# Code Review Reference - KIS_SMOKE
|
||||
|
||||
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
|
||||
> Complete implementation-owned sections, then stop with active files in place and report ready for review.
|
||||
> Finalization is review-agent-only.
|
||||
|
||||
## 개요
|
||||
|
||||
date=2026-05-29
|
||||
task=m-korea-daily-data-foundation/03+01,02_data_check_smoke, plan=0, tag=KIS_SMOKE
|
||||
|
||||
## 이 파일을 읽는 리뷰 에이전트에게
|
||||
|
||||
> **[REVIEW AGENT ONLY]** 종결 절차는 코드리뷰 에이전트 전용이다.
|
||||
|
||||
---
|
||||
|
||||
## 구현 항목별 완료 여부
|
||||
|
||||
| 항목 | 완료 여부 |
|
||||
|------|---------|
|
||||
| [KIS_SMOKE-1] Mock Data Check Command | [ ] |
|
||||
| [KIS_SMOKE-2] Whole Worker Regression | [ ] |
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [ ] 선행 `01_provider_foundation`과 `02+01_import_storage_pipeline`의 `complete.log`를 확인한다.
|
||||
- [ ] mock fixture import와 normalized bar 조회를 실행하는 CLI 또는 worker command를 추가한다.
|
||||
- [ ] command output이 instrument count, bar count, first/last timestamp를 안정적으로 출력하도록 테스트한다.
|
||||
- [ ] `bin/test` 또는 focused worker command가 credential 없이 통과함을 검증한다.
|
||||
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
## 코드리뷰 전용 체크리스트
|
||||
|
||||
> **[REVIEW AGENT ONLY]** 구현 에이전트는 수정하지 않는다.
|
||||
|
||||
- [ ] `코드리뷰 결과`에 판정을 append한다.
|
||||
- [ ] active plan/review를 log로 아카이브한다.
|
||||
- [ ] PASS이면 `complete.log` 작성 후 task directory를 archive로 이동한다.
|
||||
- [ ] PASS이고 task group이 `m-<milestone-slug>`이면 완료 이벤트 메타데이터를 보고한다.
|
||||
- [ ] WARN/FAIL이면 다음 active plan/review 또는 `USER_REVIEW.md`를 작성한다.
|
||||
|
||||
## 계획 대비 변경 사항
|
||||
|
||||
_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._
|
||||
|
||||
## 주요 설계 결정
|
||||
|
||||
_구현 에이전트가 주요 설계 결정 사항을 기록한다._
|
||||
|
||||
## 사용자 리뷰 요청
|
||||
|
||||
_기본값은 `없음`이다. 구현 중 사용자 결정, 외부 환경 준비, 또는 계획 범위 변경 없이는 안전하게 진행할 수 없으면 아래 항목을 실제 내용으로 교체하고, 구현을 중단한 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다._
|
||||
|
||||
- 상태: 없음
|
||||
- 사유 유형: 없음
|
||||
- 결정 필요: 없음
|
||||
- 차단 근거: 없음
|
||||
- 실행한 검증/명령: 없음
|
||||
- 재개 조건: 없음
|
||||
|
||||
## 리뷰어를 위한 체크포인트
|
||||
|
||||
- 실제 KIS credential/1Password가 smoke command에 필요하지 않은지 확인한다.
|
||||
- stdout contract가 안정적이고 테스트로 고정되는지 확인한다.
|
||||
- `bin/test` 결과가 실제 출력으로 기록되는지 확인한다.
|
||||
|
||||
## 검증 결과
|
||||
|
||||
### KIS_SMOKE-1 중간 검증
|
||||
```bash
|
||||
$ cd apps/cli && go test -count=1 ./...
|
||||
```
|
||||
|
||||
### KIS_SMOKE-2 중간 검증
|
||||
```bash
|
||||
$ bin/test
|
||||
```
|
||||
|
||||
### 최종 검증
|
||||
```bash
|
||||
$ test -f agent-task/m-korea-daily-data-foundation/01_provider_foundation/complete.log
|
||||
$ test -f agent-task/m-korea-daily-data-foundation/02+01_import_storage_pipeline/complete.log
|
||||
$ bin/test
|
||||
```
|
||||
|
|
@ -1,122 +0,0 @@
|
|||
<!-- task=m-korea-daily-data-foundation/03+01,02_data_check_smoke plan=0 tag=KIS_SMOKE -->
|
||||
|
||||
# Plan - Mock Data Check Smoke
|
||||
|
||||
## 이 파일을 읽는 구현 에이전트에게
|
||||
|
||||
이 subtask는 `01_provider_foundation`과 `02+01_import_storage_pipeline` 완료 후 시작한다. 구현 완료 전 active review stub의 구현 에이전트 소유 섹션을 채운다. 실제 KIS credential이나 1Password 준비를 요구하지 않는다.
|
||||
|
||||
## 배경
|
||||
|
||||
Milestone의 `data-check`는 normalized data가 backtest milestone에서 소비 가능한 형태로 조회되고 관련 검증이 통과해야 한다. 현재는 mock fixture 기반 흐름을 사람이 반복 실행할 수 있는 smoke 표면이 없다.
|
||||
|
||||
## 사용자 리뷰 요청 흐름
|
||||
|
||||
구현 중 blocker는 active `CODE_REVIEW-cloud-G07.md`의 `사용자 리뷰 요청` 섹션에 기록한다. code-review가 검증과 종료 처리를 소유한다.
|
||||
|
||||
## 분석 결과
|
||||
|
||||
### 읽은 파일
|
||||
|
||||
- `bin/dev`
|
||||
- `bin/test`
|
||||
- `bin/worker-storage-check`
|
||||
- `apps/cli/cmd/alt/main.go`
|
||||
- `services/worker/cmd/alt-worker/main.go`
|
||||
- `services/worker/internal/jobs/builtin.go`
|
||||
- `services/worker/testdata/providers/kis/*.json`
|
||||
- `agent-task/m-korea-daily-data-foundation/01_provider_foundation/PLAN-cloud-G07.md`
|
||||
- `agent-task/m-korea-daily-data-foundation/02+01_import_storage_pipeline/PLAN-cloud-G07.md`
|
||||
|
||||
### 테스트 커버리지 공백
|
||||
|
||||
- End-to-end mock import command: 기존 없음. CLI 또는 worker command test 필요.
|
||||
- Human-runnable data check output: 기존 없음. stdout contract test 필요.
|
||||
- Full worker package regression after import pipeline: 기존 `bin/test`는 있으나 feature-specific smoke 없음.
|
||||
|
||||
### 심볼 참조
|
||||
|
||||
none. 기존 `bin/test`와 storage check는 유지한다.
|
||||
|
||||
### 분할 판단
|
||||
|
||||
`03+01,02_data_check_smoke`는 directory name상 `01_provider_foundation`과 `02+01_import_storage_pipeline`에 의존한다. Smoke command는 실제 provider/import API가 완료된 뒤 작성해야 한다.
|
||||
|
||||
### 범위 결정 근거
|
||||
|
||||
실제 KIS network smoke, 1Password credential injection, Postgres Docker orchestration은 제외한다. 이 작업은 mock fixture를 읽어 normalized daily bar 조회 가능성을 보여주는 local command/test만 만든다.
|
||||
|
||||
### 빌드 등급
|
||||
|
||||
build=`cloud-G07`, review=`cloud-G07`. terminal-facing smoke output과 bin/CLI behavior가 포함된다.
|
||||
|
||||
## 의존 관계 및 구현 순서
|
||||
|
||||
1. `agent-task/m-korea-daily-data-foundation/01_provider_foundation/complete.log` 확인
|
||||
2. `agent-task/m-korea-daily-data-foundation/02+01_import_storage_pipeline/complete.log` 확인
|
||||
3. mock smoke command 구현
|
||||
4. full verification 실행
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [ ] 선행 `01_provider_foundation`과 `02+01_import_storage_pipeline`의 `complete.log`를 확인한다.
|
||||
- [ ] mock fixture import와 normalized bar 조회를 실행하는 CLI 또는 worker command를 추가한다.
|
||||
- [ ] command output이 instrument count, bar count, first/last timestamp를 안정적으로 출력하도록 테스트한다.
|
||||
- [ ] `bin/test` 또는 focused worker command가 credential 없이 통과함을 검증한다.
|
||||
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
### [KIS_SMOKE-1] Mock Data Check Command
|
||||
|
||||
문제: [apps/cli main](/config/workspace/alt/apps/cli/cmd/alt/main.go:8)은 `version` 외에 운영자 command가 없고, worker import 결과를 재현 가능하게 확인할 표면도 없다.
|
||||
|
||||
해결 방법: 가장 작은 표면으로 `apps/cli` 또는 `services/worker/cmd`에 mock data check command를 추가한다. command는 KIS fixture를 import pipeline에 넣고 in-memory store에서 bars를 조회한 뒤 stable summary를 출력한다.
|
||||
|
||||
수정 파일 및 체크리스트:
|
||||
|
||||
- [ ] `apps/cli/cmd/alt/main.go` 또는 새 `services/worker/cmd/alt-worker-data-check/main.go` 추가/수정
|
||||
- [ ] command test 추가
|
||||
|
||||
테스트 작성: stdout에 `instrument_count=1`, `bar_count=2`, `first=2024-05-27`, `last=2024-05-28`이 있는지 확인한다.
|
||||
|
||||
중간 검증:
|
||||
|
||||
```bash
|
||||
cd apps/cli && go test -count=1 ./...
|
||||
```
|
||||
|
||||
### [KIS_SMOKE-2] Whole Worker Regression
|
||||
|
||||
문제: mock provider/import/smoke가 여러 package를 가로지른 뒤 worker 전체 regression 명령이 명시되어 있지 않다.
|
||||
|
||||
해결 방법: focused tests와 `bin/test`를 모두 실행한다. sqlc generated drift는 storage query 변경이 있을 때만 `bin/worker-storage-check`를 추가 실행한다.
|
||||
|
||||
수정 파일 및 체크리스트:
|
||||
|
||||
- [ ] smoke command 구현 후 `bin/test` 통과 확인
|
||||
- [ ] storage query/migration 변경이 있었다면 `bin/worker-storage-check` 실행
|
||||
|
||||
테스트 작성: 별도 새 테스트는 KIS_SMOKE-1에 포함한다.
|
||||
|
||||
중간 검증:
|
||||
|
||||
```bash
|
||||
bin/test
|
||||
```
|
||||
|
||||
## 수정 파일 요약
|
||||
|
||||
| 파일 | 항목 |
|
||||
|------|------|
|
||||
| `apps/cli/cmd/alt/main.go` 또는 `services/worker/cmd/alt-worker-data-check/main.go` | KIS_SMOKE-1 |
|
||||
| command test file | KIS_SMOKE-1 |
|
||||
| `bin/*` only if an entrypoint is needed | KIS_SMOKE-2 |
|
||||
|
||||
## 최종 검증
|
||||
|
||||
```bash
|
||||
test -f agent-task/m-korea-daily-data-foundation/01_provider_foundation/complete.log
|
||||
test -f agent-task/m-korea-daily-data-foundation/02+01_import_storage_pipeline/complete.log
|
||||
bin/test
|
||||
```
|
||||
|
||||
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
|
||||
21
services/worker/cmd/alt-worker-data-check/main.go
Normal file
21
services/worker/cmd/alt-worker-data-check/main.go
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
// Command alt-worker-data-check runs the Korea daily data foundation smoke: it
|
||||
// imports an embedded KIS daily-chart fixture through the normalize/import
|
||||
// pipeline into an in-memory store and prints a stable summary of the bars
|
||||
// queried back. It needs no KIS credential, 1Password, or PostgreSQL, so an
|
||||
// operator can re-run it locally to confirm normalized daily bars are queryable.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"git.toki-labs.com/toki/alt/services/worker/internal/marketdata/datacheck"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if err := datacheck.Run(context.Background(), os.Stdout); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
108
services/worker/internal/marketdata/datacheck/datacheck.go
Normal file
108
services/worker/internal/marketdata/datacheck/datacheck.go
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
// Package datacheck provides a credential-free smoke surface for the Korea
|
||||
// daily data foundation: it feeds an embedded KIS daily-chart fixture through
|
||||
// the real decode/normalize and import pipeline into an in-memory store, then
|
||||
// queries the stored bars back and prints a stable one-line summary. It proves
|
||||
// normalized daily bars are queryable in the shape the backtest milestone will
|
||||
// consume, without any live KIS credential, 1Password, or PostgreSQL.
|
||||
package datacheck
|
||||
|
||||
import (
|
||||
"context"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"git.toki-labs.com/toki/alt/packages/domain/market"
|
||||
"git.toki-labs.com/toki/alt/services/worker/internal/marketdata/importer"
|
||||
"git.toki-labs.com/toki/alt/services/worker/internal/providers/kis"
|
||||
)
|
||||
|
||||
// dailyChartResponseFixture is a self-contained copy of the KIS daily chart
|
||||
// response fixture. It is embedded here (rather than read from the module's
|
||||
// testdata tree) so the smoke command builds and runs as a normal binary: Go
|
||||
// build tooling ignores testdata, and go:embed cannot reach across module
|
||||
// directories with "..".
|
||||
//
|
||||
//go:embed fixtures/daily_itemchartprice_response.sample.json
|
||||
var dailyChartResponseFixture []byte
|
||||
|
||||
// fixtureProvider implements importer.DailyBarProvider by normalizing the
|
||||
// embedded KIS fixture. It exercises the real kis decode/normalize path instead
|
||||
// of fabricating domain bars, so the smoke covers provider normalization too.
|
||||
type fixtureProvider struct{}
|
||||
|
||||
func (fixtureProvider) FetchDailyBars(_ context.Context, _ importer.DailyBarRequest) ([]importer.InstrumentBars, error) {
|
||||
resp, err := kis.DecodeDailyItemChartPriceResponse(dailyChartResponseFixture)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("datacheck: decode fixture: %w", err)
|
||||
}
|
||||
inst := market.Instrument{
|
||||
ID: market.InstrumentID("KRX:" + resp.Output1.ShortCode),
|
||||
Market: market.MarketKR,
|
||||
Venue: market.VenueKRX,
|
||||
Symbol: resp.Output1.ShortCode,
|
||||
Name: resp.Output1.Name,
|
||||
Currency: market.CurrencyKRW,
|
||||
ProviderSymbols: map[string]string{string(market.ProviderKIS): resp.Output1.ShortCode},
|
||||
}
|
||||
bars, err := kis.NormalizeDailyBars(resp, inst)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("datacheck: normalize bars: %w", err)
|
||||
}
|
||||
return []importer.InstrumentBars{{Instrument: inst, Bars: bars}}, nil
|
||||
}
|
||||
|
||||
// request mirrors the watchlist daily bar request a real import job would carry.
|
||||
func request() importer.DailyBarRequest {
|
||||
return importer.DailyBarRequest{
|
||||
Provider: market.ProviderKIS,
|
||||
Selector: market.UniverseSelector{
|
||||
Kind: market.UniverseSelectorWatchlist,
|
||||
Venue: market.VenueKRX,
|
||||
Symbols: []string{"005930"},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Run executes the mock data check and writes a stable summary line to w. The
|
||||
// summary reports the instrument count, total daily bar count, and the first
|
||||
// and last bar dates (YYYY-MM-DD) queried back from the store.
|
||||
func Run(ctx context.Context, w io.Writer) error {
|
||||
store := newMemStore()
|
||||
imp := importer.New(fixtureProvider{}, store)
|
||||
|
||||
if _, err := imp.ImportDailyBars(ctx, request()); err != nil {
|
||||
return fmt.Errorf("datacheck: import: %w", err)
|
||||
}
|
||||
|
||||
instruments, err := store.ListInstruments(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("datacheck: list instruments: %w", err)
|
||||
}
|
||||
|
||||
// Query daily bars back over an unbounded range so the summary reflects what
|
||||
// a backtest consumer would read, not just what the importer reported.
|
||||
var bars []market.Bar
|
||||
for _, inst := range instruments {
|
||||
got, err := store.GetBars(ctx, inst.ID, market.TimeframeDaily, time.Time{}, time.Unix(1<<62, 0))
|
||||
if err != nil {
|
||||
return fmt.Errorf("datacheck: get bars for %q: %w", inst.ID, err)
|
||||
}
|
||||
bars = append(bars, got...)
|
||||
}
|
||||
sort.Slice(bars, func(i, j int) bool { return bars[i].Timestamp.Before(bars[j].Timestamp) })
|
||||
|
||||
first, last := "none", "none"
|
||||
if len(bars) > 0 {
|
||||
first = bars[0].Timestamp.Format("2006-01-02")
|
||||
last = bars[len(bars)-1].Timestamp.Format("2006-01-02")
|
||||
}
|
||||
|
||||
if _, err := fmt.Fprintf(w, "instrument_count=%d bar_count=%d first=%s last=%s\n",
|
||||
len(instruments), len(bars), first, last); err != nil {
|
||||
return fmt.Errorf("datacheck: write summary: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package datacheck
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestRunPrintsStableSummary pins the stdout contract the smoke command exposes
|
||||
// to operators: importing the embedded KIS fixture and querying bars back must
|
||||
// always report one Samsung instrument with two daily bars spanning 2024-05-27
|
||||
// to 2024-05-28, with no credential or external service.
|
||||
func TestRunPrintsStableSummary(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
if err := Run(context.Background(), &buf); err != nil {
|
||||
t.Fatalf("Run: %v", err)
|
||||
}
|
||||
|
||||
got := buf.String()
|
||||
for _, want := range []string{
|
||||
"instrument_count=1",
|
||||
"bar_count=2",
|
||||
"first=2024-05-27",
|
||||
"last=2024-05-28",
|
||||
} {
|
||||
if !strings.Contains(got, want) {
|
||||
t.Errorf("summary %q missing %q", strings.TrimSpace(got), want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
{
|
||||
"rt_cd": "0",
|
||||
"msg_cd": "MCA00000",
|
||||
"msg1": "success",
|
||||
"output1": {
|
||||
"hts_kor_isnm": "Samsung Electronics",
|
||||
"stck_shrn_iscd": "005930",
|
||||
"stck_prpr": "76000",
|
||||
"prdy_vrss": "1000",
|
||||
"prdy_vrss_sign": "2",
|
||||
"prdy_ctrt": "1.33",
|
||||
"acml_vol": "10500000",
|
||||
"acml_tr_pbmn": "798000000000"
|
||||
},
|
||||
"output2": [
|
||||
{
|
||||
"stck_bsop_date": "20240527",
|
||||
"stck_oprc": "74800",
|
||||
"stck_hgpr": "75600",
|
||||
"stck_lwpr": "74400",
|
||||
"stck_clpr": "75000",
|
||||
"acml_vol": "9100000",
|
||||
"acml_tr_pbmn": "682500000000",
|
||||
"flng_cls_code": "00",
|
||||
"prtt_rate": "0.00",
|
||||
"mod_yn": "N",
|
||||
"revl_issu_reas": ""
|
||||
},
|
||||
{
|
||||
"stck_bsop_date": "20240528",
|
||||
"stck_oprc": "75200",
|
||||
"stck_hgpr": "76200",
|
||||
"stck_lwpr": "75100",
|
||||
"stck_clpr": "76000",
|
||||
"acml_vol": "10500000",
|
||||
"acml_tr_pbmn": "798000000000",
|
||||
"flng_cls_code": "00",
|
||||
"prtt_rate": "0.00",
|
||||
"mod_yn": "N",
|
||||
"revl_issu_reas": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
68
services/worker/internal/marketdata/datacheck/store.go
Normal file
68
services/worker/internal/marketdata/datacheck/store.go
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
package datacheck
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"git.toki-labs.com/toki/alt/packages/domain/market"
|
||||
)
|
||||
|
||||
// barKey mirrors the bars primary key (instrument_id, timeframe, timestamp) so
|
||||
// the in-memory store replaces rows the same way the PostgreSQL UpsertBar does,
|
||||
// keeping the smoke's idempotency behavior aligned with real storage.
|
||||
type barKey struct {
|
||||
instrumentID market.InstrumentID
|
||||
timeframe market.Timeframe
|
||||
timestamp time.Time
|
||||
}
|
||||
|
||||
// memStore is an in-memory InstrumentStore + BarStore for the smoke command. It
|
||||
// satisfies importer.Stores without any external dependency so the data check
|
||||
// runs with no PostgreSQL or credential.
|
||||
type memStore struct {
|
||||
instruments map[market.InstrumentID]market.Instrument
|
||||
bars map[barKey]market.Bar
|
||||
}
|
||||
|
||||
func newMemStore() *memStore {
|
||||
return &memStore{
|
||||
instruments: make(map[market.InstrumentID]market.Instrument),
|
||||
bars: make(map[barKey]market.Bar),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *memStore) UpsertInstrument(_ context.Context, inst market.Instrument) error {
|
||||
s.instruments[inst.ID] = inst
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *memStore) GetInstrument(_ context.Context, id market.InstrumentID) (market.Instrument, error) {
|
||||
return s.instruments[id], nil
|
||||
}
|
||||
|
||||
func (s *memStore) ListInstruments(_ context.Context) ([]market.Instrument, error) {
|
||||
out := make([]market.Instrument, 0, len(s.instruments))
|
||||
for _, inst := range s.instruments {
|
||||
out = append(out, inst)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s *memStore) UpsertBar(_ context.Context, bar market.Bar) error {
|
||||
s.bars[barKey{bar.InstrumentID, bar.Timeframe, bar.Timestamp}] = bar
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *memStore) GetBars(_ context.Context, id market.InstrumentID, timeframe market.Timeframe, from, to time.Time) ([]market.Bar, error) {
|
||||
out := make([]market.Bar, 0)
|
||||
for k, bar := range s.bars {
|
||||
if k.instrumentID != id || k.timeframe != timeframe {
|
||||
continue
|
||||
}
|
||||
if k.timestamp.Before(from) || k.timestamp.After(to) {
|
||||
continue
|
||||
}
|
||||
out = append(out, bar)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
Loading…
Reference in a new issue