eleventh version
Deploy / Test, build and deploy (push) Failing after 2m15s

This commit is contained in:
2026-06-08 15:33:56 +00:00
parent 7626c1b831
commit e074eeedf2
22 changed files with 681 additions and 55 deletions
+108
View File
@@ -564,6 +564,42 @@ func TestPreTradeDailyLossBreachHalts(t *testing.T) {
}
}
func TestPreTradeClockDriftBreachHalts(t *testing.T) {
ctx := context.Background()
repo := testutil.NewMemoryRepository()
now := time.Date(2026, 6, 8, 18, 20, 0, 0, time.UTC)
gateway := tinvest.NewFakeGateway()
gateway.ServerTime = now.Add(3 * time.Second)
notifier := &countNotifier{}
s := Scheduler{
cfg: Config{
Mode: domain.ModePaper,
Location: time.UTC,
MaxClockDrift: 2 * time.Second,
},
svc: Services{
Repo: repo,
Gateway: gateway,
Risk: risk.NewManager(repo, risk.ManagerConfig{}),
Notifier: notifier,
AccountIDHash: "hash",
},
}
_, err := s.preTradeCheck(ctx, now, "uid", domain.Portfolio{
Equity: decimal.NewFromInt(10000),
Cash: decimal.NewFromInt(10000),
}, 0, false, domain.TradingStatusNormal, now)
if !errors.Is(err, statemachine.ErrSystemHalted) {
t.Fatalf("err=%v, want ErrSystemHalted", err)
}
if !repo.Halted || repo.HaltReason != "pre-trade hard limit breached: server_clock_drift_too_high" {
t.Fatalf("halted=%v reason=%q", repo.Halted, repo.HaltReason)
}
if notifier.alerts != 1 {
t.Fatalf("alerts=%d, want 1", notifier.alerts)
}
}
func TestPreTradeUsesPhaseDeadlineForMinTimeToClose(t *testing.T) {
ctx := context.Background()
repo := testutil.NewMemoryRepository()
@@ -812,6 +848,78 @@ func TestSizeReductionRuleRecommendsLiveReadonlyInLiveTrade(t *testing.T) {
}
}
func TestRepeatedSizeReductionRuleActivatesLiveReadonly(t *testing.T) {
ctx := context.Background()
repo := testutil.NewMemoryRepository()
notifier := &countNotifier{}
tradeDate := time.Date(2026, 6, 30, 0, 0, 0, 0, time.UTC)
for i := 0; i < sizeReductionWindowTrades*2; i++ {
date := tradeDate.AddDate(0, 0, -i)
if err := repo.UpsertSignal(ctx, domain.Signal{
TradeDate: date,
InstrumentUID: "uid",
Decision: domain.DecisionEnter,
NetEdgeBps: decimal.NewFromInt(20),
}); err != nil {
t.Fatal(err)
}
if err := repo.UpsertPosition(ctx, domain.Position{
AccountIDHash: "hash",
InstrumentUID: "uid",
OpenTradeDate: date,
Lot: 1,
Status: domain.PositionExitFilled,
RealizedEdgeBps: decimal.Zero,
UpdatedAt: date.Add(time.Hour),
}); err != nil {
t.Fatal(err)
}
}
execEngine := execution.NewEngine(domain.ModeLiveTrade, "account", nil, repo)
s := Scheduler{
cfg: Config{Mode: domain.ModeLiveTrade},
sm: statemachine.New(repo, domain.ModeLiveTrade),
svc: Services{
Repo: repo,
AccountIDHash: "hash",
Notifier: notifier,
Execution: &execEngine,
Sizer: risk.NewSizer(risk.SizingConfig{
MaxPositionPct: decimal.NewFromInt(1),
MaxTotalExposurePct: decimal.NewFromInt(1),
MaxParticipationRate: decimal.NewFromInt(1),
CashUsageBuffer: decimal.NewFromInt(1),
RiskBudgetPerInstrumentPct: decimal.NewFromInt(1),
MinOrderNotionalRUB: decimal.NewFromInt(1),
}),
},
}
if err := s.applySizeReductionRule(ctx, tradeDate, true); err != nil {
t.Fatal(err)
}
if repo.Mode != domain.ModeLiveReadonly || s.cfg.Mode != domain.ModeLiveReadonly {
t.Fatalf("modes repo=%s scheduler=%s, want live_readonly", repo.Mode, s.cfg.Mode)
}
if len(repo.RiskEvents) != 2 || repo.RiskEvents[1].EventType != "live_readonly_activated" || repo.RiskEvents[1].Severity != domain.SeverityAlert {
t.Fatalf("risk events=%+v, want live_readonly activation alert", repo.RiskEvents)
}
if notifier.alerts != 1 {
t.Fatalf("alerts=%d, want 1", notifier.alerts)
}
_, err := execEngine.PlaceLimit(ctx, domain.Order{
ClientOrderID: "order",
InstrumentUID: "uid",
TradeDate: tradeDate,
Side: domain.SideBuy,
OrderType: domain.OrderTypeLimit,
LimitPrice: decimal.NewFromInt(100),
QuantityLots: 1,
})
if !errors.Is(err, execution.ErrBrokerOrdersDisabled) {
t.Fatalf("PlaceLimit err=%v, want ErrBrokerOrdersDisabled after live_readonly activation", err)
}
}
func TestBatchSignalLimitsCapSlotsAndExposure(t *testing.T) {
s := Scheduler{
cfg: Config{MaxOpenPositions: 5},