Warning
THIS PROJECT CAN LOSE REAL MONEY. The repository defaults to paper trading, and real-money execution is intentionally locked behind several independent code and environment gates. Do not enable live mode without extensive paper testing, review, and supervision.
A production-oriented, fail-closed Python system for scanning liquid US equities, evaluating one opening-range/VWAP momentum strategy, generating structured OpenAI analyst opinions, applying deterministic risk controls, paper trading, journaling, and backtesting.
The goal is not trading frequency. It is to collect enough trustworthy evidence to decide whether a narrowly defined intraday strategy has a measurable edge while constraining downside.
Development follows the PRD milestones in order. Paper mode is the default and the only mode used during development.
- Milestone 1 — repository structure, typed configuration, domain models, safety gate, baseline tests
- Milestone 2 — market data and technical indicators
- Milestone 3 — dynamic scanner
- Milestone 4 — opening-range breakout + VWAP strategy
- Milestone 5 — deterministic risk engine
- Milestone 6 — paper broker
- Milestone 7 — SQLite journal and analytics
- Milestone 8 — historical backtester
- Milestone 9 — OpenAI structured analyst
- Milestone 10 — Robinhood MCP read-only adapter
- Milestone 11 — Robinhood order-review adapter
- Milestone 12 — live adapter behind safety gates (never enabled during development)
The model is an analyst, never the risk manager. Its output is treated as untrusted input and cannot submit an order. A trade must pass this sequence:
market data → scanner → deterministic signal → structured AI opinion
→ deterministic risk engine → broker-neutral order intent
→ paper broker (default) / guarded live adapter (disabled)
→ monitor → journal
The system fails to NO TRADE when data is missing or stale, the broker/account state is not confirmed, the AI response is invalid, an order state is ambiguous, a duplicate is possible, or any risk check errors.
Initial hard limits live in config/risk.yaml:
- $25 starting paper balance
- $10 maximum position value
- $0.50 maximum planned loss per trade
- $1.00 realized + unrealized daily loss lockout
- one open position and two trades per day
- long, US-listed common stock only; no options, crypto, futures, shorts, margin, leverage, OTC, or penny stocks
- Python 3.12 or newer
- SQLite (included with Python)
python -m venv .venv
# Windows: .venv\Scripts\activate
# macOS/Linux: source .venv/bin/activate
python -m pip install -e ".[dev]"
copy .env.example .env # Windows
# cp .env.example .env # macOS/Linux
pytestSecrets belong only in environment variables or .env, which is ignored by Git.
config/strategy.yaml— ORB/VWAP, scanner, market-data, and session schedule parametersconfig/risk.yaml— immutable-at-runtime risk and circuit-breaker thresholds.env— runtime mode, database URL, OpenAI key/model, and connector endpoint
The default environment is deliberately restrictive:
TRADING_MODE=paper
PAPER_ONLY=true
LIVE_TRADING_ENABLED=false
ROBINHOOD_ALLOW_ORDER_REVIEW=false
ROBINHOOD_ALLOW_ORDER_PLACEMENT=falseChanging only TRADING_MODE is insufficient. The live adapter re-checks all gates immediately before any broker write. No live flags are enabled or exercised by the test suite.
config/ typed settings and YAML policies
trader/domain/ broker-neutral enums and validated models
trader/broker/ broker interface, paper broker, Robinhood MCP adapter
trader/market/ data normalization, indicators, context, scanner
trader/strategy/ shared signal, scoring, ORB/VWAP decision logic
trader/risk/ sizing, validation, circuit breakers
trader/execution/ idempotent intents, monitoring, exits
trader/journal/ SQLite schema, repository, analytics
trader/backtest/ event-driven historical simulation
trader/ai/ OpenAI structured analyst boundary
trader/scheduler/ session controller
scripts/ operational entry points
tests/ unit, integration, and safety tests
The final CLI will expose:
python -m trader status
python -m trader scan
python -m trader paper
python -m trader positions
python -m trader performance
python -m trader journal
python -m trader kill
python -m trader live # always rejected under default configurationBacktesting, Robinhood setup, OpenAI configuration, journal operations, scheduler operation, and common errors are documented as their milestones land.
trader.market exposes broker-neutral validation and pure Decimal-based indicators. Candle series must be homogeneous, ordered, timezone-aware, and non-interpolated; callers can additionally require contiguous data and freshness. Incomplete, stale, future-dated, or inconsistent inputs raise a typed market-data error so the decision layer can record NO TRADE.
Implemented V1 calculations include SMA, EMA, RSI, MACD, ATR, session VWAP, elapsed-session relative volume, previous-session levels, premarket levels, and opening-range levels. One-minute candles can be resampled into complete exchange-local 5- or 15-minute bars; partial buckets are rejected rather than filled optimistically.
The scanner is observational: it ranks candidates but cannot propose or execute trades. It hard-rejects unconfirmed US common-stock eligibility, untradable or stale quotes, prices below $5, and unknown or sub-5M average daily volume. Eligible symbols are ranked dynamically by liquidity, elapsed relative volume, gap, intraday movement, and catalyst; the YAML watch universe is only a discovery seed and receives no ranking preference.
Market context evaluates SPY, QQQ, a sector proxy, volatility, scheduled high-impact events, freshness, and market status. Missing context, elevated uncertainty, a closed market, or a broad/technology selloff blocks long entries. News is accessed through an injected provider; a confirmed empty result is neutral, while provider failure or future-dated content is unknown and therefore cannot become positive evidence.
V1 implements one pure, broker-independent long strategy. It waits for a complete 10–30 minute configurable opening range (15 minutes by default), uses only completed candles available at the evaluation timestamp, requires a strict break above both VWAP and the range high, elapsed relative volume at or above 1.5×, confirmed liquidity, positive/neutral catalyst, calm complete market context, and a setup score of at least 8/12.
The evaluator returns either a typed NO_TRADE with stable journal-ready reasons or a price-only entry/stop/target plan. It cannot size a position or construct an order. Snapshot staleness, future bars, partial ranges, missing warmups, elevated volatility, scheduled high-impact events, and any failed condition produce NO_TRADE; the stop is the higher of VWAP and the opening-range low, and the target is at least the configured minimum reward/risk.
RiskEngine.validate_trade() is the sole authority for increasing exposure. It independently re-checks account confirmation/freshness/version, cash and unleveraged buying power, equity, position and pending-entry count, daily trade count, combined realized/unrealized P&L, remaining daily-loss headroom, asset/listing/direction, minimum price and liquidity, market status/context/freshness, entry time, quote/proposal freshness, setup score, reward/risk, notional, planned stop loss, total exposure, duplicate intent, and the maximum safely rounded fractional quantity.
Sizing uses Decimal and rounds down to the supported quantity increment. The safe quantity is the minimum allowed by per-trade risk, remaining daily-loss headroom, $10 notional, cash/buying power, and total exposure. The $1 daily-loss breaker is inclusive and latched for the New York session; it blocks entries and adds while deliberately preserving cancellations and position exits. In-process reservations are atomic so concurrent pending entries count against both position and trade caps; the journal milestone makes those reservations durable across restarts.
PaperBroker implements the same async broker contract intended for Robinhood but contains no live transport or credentials. It simulates fractional shares, adverse configurable slippage, optional fees, cash and reserved buying power, market and limit orders, idempotent client order IDs, cancellations, positions, mark-to-market equity, realized/unrealized P&L, and automatic stop/target exits as quotes arrive.
New paper entries still fail closed on missing/untradable/stale/future quotes, insufficient cash, unsupported order types, an invalid stop/target around the expected fill, or any attempt to add to an existing position. Limit fills never violate their limit price. Paper market-data fixtures are validated for symbol, interval, ordering, and interpolation before storage.
This is experimental software, not financial advice. Backtests and paper results do not guarantee live performance. Market data can be delayed or wrong, APIs fail, and slippage can dominate a very small account. Keep PAPER_ONLY=true until the system has accumulated and independently reviewed substantial paper-trading evidence.