Paper trading readiness에서 API/worker/CLI가 같은 protobuf 계약으로 paper state를 시작하고 조회할 수 있어야 한다. Headless 운영 경로를 먼저 닫기 위해 contract, worker runtime, API forwarding, CLI scenario, client parser map과 검증 artifact를 함께 반영한다.
89 lines
2.7 KiB
Go
89 lines
2.7 KiB
Go
package papertrading
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.toki-labs.com/toki/alt/packages/domain/backtest"
|
|
"git.toki-labs.com/toki/alt/packages/domain/market"
|
|
)
|
|
|
|
func serviceTestRequest() StartRequest {
|
|
return StartRequest{
|
|
AccountID: "paper-acct-1",
|
|
Spec: backtest.RunSpec{
|
|
StrategyID: "test-simple-buy",
|
|
Market: market.MarketKR,
|
|
Timeframe: market.TimeframeDaily,
|
|
From: time.Date(2026, 5, 1, 0, 0, 0, 0, time.UTC),
|
|
To: time.Date(2026, 5, 3, 0, 0, 0, 0, time.UTC),
|
|
},
|
|
StartingCash: market.Price{Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "10000000"}},
|
|
}
|
|
}
|
|
|
|
func newTestService() *Service {
|
|
bars := makeBars(nil)
|
|
engine := NewEngine(&testBarSource{bars: bars}, &testStrategyPort{strategy: simpleBuyStrategy{id: "test-simple-buy"}})
|
|
fixed := time.Date(2026, 6, 1, 0, 0, 0, 0, time.UTC)
|
|
return NewService(engine, func() time.Time { return fixed })
|
|
}
|
|
|
|
func TestServiceStartRecordsState(t *testing.T) {
|
|
svc := newTestService()
|
|
|
|
state, err := svc.StartPaperTrading(context.Background(), serviceTestRequest())
|
|
if err != nil {
|
|
t.Fatalf("start failed: %v", err)
|
|
}
|
|
if state.Run.Status != backtest.RunStatusSucceeded {
|
|
t.Errorf("expected succeeded run status, got %q", state.Run.Status)
|
|
}
|
|
if state.Run.ID == "" {
|
|
t.Error("expected a non-empty run id")
|
|
}
|
|
// simpleBuyStrategy buys 1 unit on day 1, fills on day 2 at open 1050.
|
|
if len(state.Fills) != 1 {
|
|
t.Fatalf("expected 1 fill, got %d", len(state.Fills))
|
|
}
|
|
if state.Cash.Amount.Value != "9998950" {
|
|
t.Errorf("expected cash 9998950, got %s", state.Cash.Amount.Value)
|
|
}
|
|
if len(state.Positions) != 1 {
|
|
t.Errorf("expected 1 position, got %d", len(state.Positions))
|
|
}
|
|
}
|
|
|
|
func TestServiceGetStateReturnsRecorded(t *testing.T) {
|
|
svc := newTestService()
|
|
if _, err := svc.StartPaperTrading(context.Background(), serviceTestRequest()); err != nil {
|
|
t.Fatalf("start failed: %v", err)
|
|
}
|
|
|
|
state, err := svc.GetPaperTradingState(context.Background(), "paper-acct-1")
|
|
if err != nil {
|
|
t.Fatalf("get state failed: %v", err)
|
|
}
|
|
if state.Run.Status != backtest.RunStatusSucceeded {
|
|
t.Errorf("expected succeeded run status, got %q", state.Run.Status)
|
|
}
|
|
}
|
|
|
|
func TestServiceGetStateNotFound(t *testing.T) {
|
|
svc := newTestService()
|
|
_, err := svc.GetPaperTradingState(context.Background(), "never-started")
|
|
if !errors.Is(err, ErrAccountNotFound) {
|
|
t.Fatalf("expected ErrAccountNotFound, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestServiceStartRequiresAccountID(t *testing.T) {
|
|
svc := newTestService()
|
|
req := serviceTestRequest()
|
|
req.AccountID = ""
|
|
if _, err := svc.StartPaperTrading(context.Background(), req); err == nil {
|
|
t.Fatal("expected error for empty account id")
|
|
}
|
|
}
|