Files
nofx/trader/auto_trader_throttle_test.go
tinkle-community 110bf52908 feat: cream terminal redesign, English-only UI, autopilot launch fixes
- Redesign dashboard into a cream-paper + vermilion IBM Plex Mono terminal
  (live L2 order book, cost/liq map, WS K-line, signal matrix, orchestration
  topology, risk radar, execution log, current positions, equity curve)
- Convert all user-facing UI and backend strings/prompts from Chinese to
  English (multi-language retained, default English)
- Add /api/statistics/full endpoint + full-stats frontend wiring
- Fix Autopilot launch: reuse the existing trader instead of creating
  duplicates (eliminates repeat ~35s create cost and stale-trader 404s);
  launch sends 5m scan interval
- Fix unreadable toasts: cream theme with high-contrast text + per-type accent
- Silence background dashboard polls (getTraderConfig) to stop error-toast spam
2026-06-30 16:03:52 +08:00

98 lines
3.3 KiB
Go

package trader
import (
"nofx/kernel"
"strings"
"testing"
"time"
)
func throttleContext(symbol, side string, heldFor time.Duration, pnlPct float64) *kernel.Context {
return &kernel.Context{
Positions: []kernel.PositionInfo{
{
Symbol: symbol,
Side: side,
UnrealizedPnLPct: pnlPct,
UpdateTime: time.Now().Add(-heldFor).UnixMilli(),
},
},
}
}
func TestTradeThrottleBlocksEarlyNoiseClose(t *testing.T) {
at := &AutoTrader{}
ctx := throttleContext("xyz:INTC", "long", 20*time.Minute, -0.3)
reason := at.tradeThrottleReason(kernel.Decision{Symbol: "xyz:INTC", Action: "close_long"}, ctx, 0)
if !strings.Contains(reason, "min AI-managed hold") {
t.Fatalf("expected early close to be blocked by min hold, got %q", reason)
}
}
func TestTradeThrottleAllowsEarlyHardStop(t *testing.T) {
at := &AutoTrader{}
ctx := throttleContext("xyz:INTC", "long", 20*time.Minute, -3.0)
reason := at.tradeThrottleReason(kernel.Decision{Symbol: "xyz:INTC", Action: "close_long"}, ctx, 0)
if reason != "" {
t.Fatalf("expected hard stop close to pass, got %q", reason)
}
}
func TestTradeThrottleBlocksFlatCloseInsideNoiseWindow(t *testing.T) {
at := &AutoTrader{}
ctx := throttleContext("xyz:INTC", "long", 60*time.Minute, 0.4)
reason := at.tradeThrottleReason(kernel.Decision{Symbol: "xyz:INTC", Action: "close_long"}, ctx, 0)
if !strings.Contains(reason, "noise band") {
t.Fatalf("expected flat close to be blocked inside noise window, got %q", reason)
}
}
func TestTradeThrottleAllowsConfirmedLossAfterMinimumHold(t *testing.T) {
at := &AutoTrader{}
ctx := throttleContext("xyz:INTC", "long", 60*time.Minute, -1.2)
reason := at.tradeThrottleReason(kernel.Decision{Symbol: "xyz:INTC", Action: "close_long"}, ctx, 0)
if reason != "" {
t.Fatalf("expected confirmed loss after min hold to pass, got %q", reason)
}
}
func TestTradeThrottleAllowsLongShortPairInCycle(t *testing.T) {
at := &AutoTrader{}
ctx := &kernel.Context{}
// One open already queued this cycle (e.g. the long) — the second open
// (the short) must still be allowed so a directional pair can open.
reason := at.tradeThrottleReason(kernel.Decision{Symbol: "xyz:INTC", Action: "open_short"}, ctx, 1)
if reason != "" {
t.Fatalf("expected the second (short) open in cycle to be allowed, got %q", reason)
}
}
func TestTradeThrottleBlocksOpensOverCycleCap(t *testing.T) {
at := &AutoTrader{}
ctx := &kernel.Context{}
// under the 6-per-cycle cap, a further open is allowed
if reason := at.tradeThrottleReason(kernel.Decision{Symbol: "xyz:INTC", Action: "open_long"}, ctx, 5); reason != "" {
t.Fatalf("expected open within the 6-per-cycle cap to be allowed, got %q", reason)
}
// at the cap, the next open is blocked
if reason := at.tradeThrottleReason(kernel.Decision{Symbol: "xyz:INTC", Action: "open_long"}, ctx, 6); !strings.Contains(reason, "6 new position") {
t.Fatalf("expected open beyond the 6-per-cycle cap to be blocked, got %q", reason)
}
}
func TestTradeThrottleBlocksOpeningAgainstExistingPosition(t *testing.T) {
at := &AutoTrader{}
ctx := throttleContext("xyz:INTC", "long", 2*time.Hour, 1.0)
reason := at.tradeThrottleReason(kernel.Decision{Symbol: "xyz:INTC", Action: "open_short"}, ctx, 0)
if !strings.Contains(reason, "already has an open") {
t.Fatalf("expected opposite open to be blocked when position exists, got %q", reason)
}
}