From 9cdda549c2774087d367a531144921435e155aec Mon Sep 17 00:00:00 2001 From: toki Date: Wed, 17 Jun 2026 21:33:01 +0900 Subject: [PATCH] =?UTF-8?q?feat(cli):=20=EB=B0=B1=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20operator=EC=97=90=20multi-timeframe=20=EC=A7=80?= =?UTF-8?q?=EC=9B=90=EA=B3=BC=20=ED=85=8C=EC=8A=A4=ED=8A=B8=EB=A5=BC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit runner.go, scenario.go에 multi-timeframe 기능 추가 scenario_test.go에 테스트 Cases 추가 --- apps/cli/internal/operator/runner.go | 1 + apps/cli/internal/operator/scenario.go | 1 + apps/cli/internal/operator/scenario_test.go | 41 +++++++++++++++++++++ 3 files changed, 43 insertions(+) diff --git a/apps/cli/internal/operator/runner.go b/apps/cli/internal/operator/runner.go index 8b9ceab..97d7487 100644 --- a/apps/cli/internal/operator/runner.go +++ b/apps/cli/internal/operator/runner.go @@ -55,6 +55,7 @@ var marketByName = map[string]altv1.Market{ // timeframeByName maps scenario timeframe strings onto the contract enum. var timeframeByName = map[string]altv1.Timeframe{ + "monthly": altv1.Timeframe_TIMEFRAME_MONTHLY, "daily": altv1.Timeframe_TIMEFRAME_DAILY, "minute_1": altv1.Timeframe_TIMEFRAME_MINUTE_1, "minute_5": altv1.Timeframe_TIMEFRAME_MINUTE_5, diff --git a/apps/cli/internal/operator/scenario.go b/apps/cli/internal/operator/scenario.go index d587a68..fc14c4c 100644 --- a/apps/cli/internal/operator/scenario.go +++ b/apps/cli/internal/operator/scenario.go @@ -161,6 +161,7 @@ var validVenues = map[string]bool{ // validTimeframes is the set of bar timeframe strings a list_bars request may // use, mirroring the API's accepted Timeframe values. var validTimeframes = map[string]bool{ + "monthly": true, "daily": true, "minute_1": true, "minute_5": true, diff --git a/apps/cli/internal/operator/scenario_test.go b/apps/cli/internal/operator/scenario_test.go index 7138a33..8d59f28 100644 --- a/apps/cli/internal/operator/scenario_test.go +++ b/apps/cli/internal/operator/scenario_test.go @@ -5,6 +5,8 @@ import ( "path/filepath" "strings" "testing" + + altv1 "git.toki-labs.com/toki/alt/packages/contracts/gen/go/alt/v1" ) func fixture(t *testing.T, name string) string { @@ -656,6 +658,45 @@ steps: } } +func TestTimeframeVocabularyIncludesMonthlyAndMinuteBaseline(t *testing.T) { + cases := []struct { + timeframe string + proto altv1.Timeframe + }{ + {"monthly", altv1.Timeframe_TIMEFRAME_MONTHLY}, + {"daily", altv1.Timeframe_TIMEFRAME_DAILY}, + {"minute_1", altv1.Timeframe_TIMEFRAME_MINUTE_1}, + {"minute_5", altv1.Timeframe_TIMEFRAME_MINUTE_5}, + } + for _, c := range cases { + t.Run(c.timeframe, func(t *testing.T) { + if !validTimeframes[c.timeframe] { + t.Fatalf("validTimeframes[%q] = false", c.timeframe) + } + if got := timeframeByName[c.timeframe]; got != c.proto { + t.Fatalf("timeframeByName[%q] = %v, want %v", c.timeframe, got, c.proto) + } + + yamlText := fmt.Sprintf(` +name: valid_%s_timeframe +steps: + - id: step1 + action: start_backtest + request: + strategy_id: strat1 + market: kr + timeframe: %s + from_unix_ms: 100 + to_unix_ms: 200 + symbols: ["005930"] +`, c.timeframe, c.timeframe) + if _, err := ParseScenario([]byte(yamlText)); err != nil { + t.Fatalf("ParseScenario(%q) returned error: %v", c.timeframe, err) + } + }) + } +} + func TestValidatePaperTradingScenario(t *testing.T) { yamlText := ` name: valid_paper