You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
candle_patterns.py (imported by supervisor + learner)
observation + ledger tags
regime detector
NQ daily returns
daily
every 6h
regime.py --update via cron
writes .autotrade_regime.json
walk-forward retrain
5-symbol history
—
Sat 18:00
finetune/walkforward.py
trains veto adapter
Infra: systemd user units (autotrade.service CPUQuota=50%, CPU-pinned; veto.service GPU), Hermes cron (11 jobs), Telegram alerts, TopstepX REST API.
2. DECISION PIPELINE (per 3-min bar, per symbol; entry path)
fetch bars (5 symbols PARALLEL via ThreadPoolExecutor; decisions serial)
→ [P] VETO PREFETCH (background thread, fire-and-forget): builds state line for
each symbol, POSTs /decide_batch → warms the decision cache so a signal
on a seen state gets an INSTANT veto (0.01s vs 17-32s)
→ [G1] position check: open position on symbol? → trail/exit, no new entry
→ [G2] book-level: one position MAX across book (Topstep rule)
→ [G3] strategy detect: EMA(9/20) cross AND ADX(14) ≥ 18.0 → Signal
→ [G4] grade: Chronos embed + XGBoost → proba, r_hat
→ [G5] confidence window: 0.35 ≤ proba ≤ 0.50 (floor/ceil)
→ [G6] veto_fn (ordered):
G6a EVENT BLACKOUT (news calendar, deterministic)
G6b REGIME GATE (HMM file, block panic)
G6c CANDLE-CONFLICT GATE — ONLY if AUTOTRADE_BLOCK_CONFLICT=1 (validated
rule, currently OFF): block when 30-min pattern opposes signal dir
G6d LLM VETO — POST state to :8765/decide (cache-first) → action==side
G6e QUALITY GATE (min 0 = inert)
G6f AGREEMENT REPORT — voices N/2 (veto + candle pattern); logged, never gates
→ [G7] position size → market order + bracket (stop 0.5×ATR, target 2R)
Gate
Input
Condition
PASS
BLOCK
Config key (file)
P prefetch
current state text
always (bg)
cache warm
—
AUTOTRADE_VETO_PREFETCH=1 (env)
G1 position
broker
open pos
manage/trail
—
—
G2 book cap
broker
one pos/book
own symbol
skip others
—
G3 detect
bars
EMA9×20 cross + ADX≥18
Signal
wait
EMA_FAST/SLOW, ADX_GATE (config.py)
G4 grade
embed+hand
—
proba, r_hat
—
CTX=128
G5 confidence
proba
0.35≤p≤0.50
candidate
skip
PROBA_FLOOR/CEIL (config.py)
G6a blackout
events cache
in window
block
block
market_context.*
G6b regime
regime json
panic
block
block
AUTOTRADE_REGIME_GATE
G6c conflict
30m pattern vs dir
conflict & enabled
block
block
AUTOTRADE_BLOCK_CONFLICT (env, .env)
G6d LLM veto
:8765/decide
action==side
pass
block (fail-closed)
AUTOTRADE_VETO_FAIL_OPEN=0
G6e quality
veto resp
q≥min
pass
block
AUTOTRADE_QUALITY_MIN=0
G6f agreement
veto+pattern
N/A (log only)
log voices
—
—
G7 size
stop_ticks
—
enter
—
RISK_PER_TRADE=0 → SIZE=1
⚠️ G6c is wired but INACTIVE until the attribution agent validates and sets AUTOTRADE_BLOCK_CONFLICT=1 (ledger: 27 conflict samples vs 30 minimum — evidence strong: 18.5% WR vs 41.1% aligned).
3. LIFECYCLE (entry → exit)
Entry: market order at signal-bar close, size 1 contract (risk sizing off).
76 FFM cols (ffm_feature_columns.json, exact order) + 5 hand features → 337-dim
proba, r_hat
walk-forward weekly
finetune/output8b
Qwen2.5-7B 4-bit NF4 + LoRA
state text (v1 format, byte-fixed)
BUY/SELL/NO TRADE + reason
walk-forward weekly
base
~/qwen-dl
—
—
—
⚠️ RE-SAVED 2026-08-17: all 6 bundles re-serialized in current xgboost format (re-save_models.py, bit-identical predictions verified). No feature_names_in_ on XGBHead → positional inference; FFM order pinned by json. r_hat head: verified NOISE (corr +0.026) — informational only.
Ledger merge upgrade — replay now upgrades existing records with pattern tags in place (293 upgraded) — keeps tags current without duplication.
SINGLE POINT OF TRUTH (10 lines)
Live entry path: supervisor.py → bot.handle_bar → gates (strategies/base.py + supervisor.veto_fn). Everything else is advisory/post-hoc.
PROBA_FLOOR/CEIL in config.py — the only filter between signal and veto; keep in sync across config.py, .env, and .autotrade_state → evolve.floor/ceil (evolver's copy, clamped to [baseline, baseline+0.30]).
Symbols: .env AUTOTRADE_SYMBOLS (overrides config); PROTECTED list in .env; restart service after changes.
Veto adapter: llm_veto.py default AND veto.service Environment must both point to output8b.
Quality gate: AUTOTRADE_QUALITY_MIN must stay 0 (v1 model never emits quality).
signals.py must stay in ~/topstep-bot (supervisor imports it; doctor checks).
Model feature order: pinned by models/ffm_feature_columns.json (76) + strategies/ema_cross hand features — never reorder without retraining.
Veto cache: safe only because decode is greedy (do_sample=False) — if sampling is ever enabled, disable the cache.
Conflict block (G6c) is dormant until attribution validates and sets AUTOTRADE_BLOCK_CONFLICT=1 — the ledger (27 conflict samples) decides, never the eyeball.
Watchdog restarts, doctor catches rot, learner tunes floor, attribution tunes rules — all read .autotrade_state/ledger; none replaces the supervisor's judgment.