tr8
tr8 is an algorithmic trading bot that uses LLMs to decide which sectors to buy. Every day at 4:30pm ET, it ingests news headlines, SEC filings, macro indicators, and congressional trading disclosures for all 11 SPDR sector ETFs, scores each sector’s sentiment, filters by technical conditions, and executes buys through Alpaca. It’s been paper trading a $1,000 account since April 2026.
Code is available on request — code.jyxtn.dev/tr8-repo-portfolio.
Design
Objective: Build a systematic sector rotation strategy where LLM news sentiment is the primary signal and technical conditions act as an entry gate — not the other way around. Validate it with walk-forward methodology before any live capital goes near it.
Design factors: Retail broker constraints were a constant — Alpaca API, PDT rules, fractional shares, paper trading before live. The system needed to respect regulatory constraints without custom broker integrations or exchange-level access.
Infrastructure cost had to stay near zero. The system runs once a day at market close and is dormant otherwise. An always-on server for a daily cron job is the wrong shape.
Validation rigor was non-negotiable. In-sample optimization produces parameters that look good in backtests and fail in production. Walk-forward expanding windows are the minimum standard. Consistency across regimes matters more than peak Sharpe on any single fold.
Technology evaluations and selections:
DuckDB over PostgreSQL or SQLite — columnar storage for time-series financial data, analytical query performance without a server process. Everything lands in DuckDB: signals, trades, decisions, tax records, backtest results.
Modal over always-on VPS — serverless, daily cron pattern, ~$3/month actual spend. No capacity planning, no idle cost, no babysitting.
Gaussian HMM over rule-based regime detection — a 5-state model trained on 8 macro features (VIX, EPU, yield curve, credit spread, Ken French momentum factor, cross-sector volatility, mean pairwise correlation, momentum dispersion) captures regime transitions that hand-coded rules miss. Hard rules still override for extremes: VIX ≥ 30 immediately → HIGH_VOLATILITY regardless of model state. Model uncertainty is highest exactly when you need certainty most — hard rules handle the tails.
Optuna on a Pareto front of Sharpe and max drawdown, penalized by fold variance — optimizing Sharpe alone finds parameters that exploit one favorable regime and collapse on the rest. The variance penalty across walk-forward folds forces parameters that work consistently. Consistent mediocrity outperforms inconsistent brilliance over a full market cycle.
LLM grounding constraint — the prompt instructs the model to reason only from the provided text, not prior training knowledge about a sector’s historical reputation. The signal has to come from current news flow, not from the model’s priors about tech or energy.
Implementation
Signal construction: up to 20 text excerpts per sector assembled into a grounded prompt. Structured JSON output: sentiment, confidence, bull_strength, bear_strength. Confidence adjusted contrastively: adjusted = confidence × (1 + 0.20 × aligning_strength) × (1 − 0.30 × opposing_strength). High bear_strength on a bullish call reduces confidence by up to 30%.
Regime weight multiplies into the composite score. Shock logic (EPU > 300) separately scales defensives ×1.5 and cyclicals ×0.5 — independent of the HMM state, because news-driven macro shocks can precede HMM state transitions.
Stop hierarchy evaluated in order: circuit breaker (−15% from entry, immediate), sentiment reversal (LLM scores bearish ≥ 0.65), trailing stop (activates after 1× ATR gain, trails at 1.5× ATR below peak), fixed ATR stop (2× ATR below entry until trailing activates). Exit checks every 15 minutes intraday.
An LLM generates a ~200-word narrative after every daily run — what happened, why, what the model saw — stored in DuckDB and surfaced in the dashboard alongside equity curve, sector P&L heatmap, and a “is it worth it?” panel comparing tr8 returns to VTI, SPY, and equal-weight after tax drag.
Repo → — access granted on request