Reconstructing market structure from institutional signals.
Radon is a market-structure reconstruction system that detects institutional positioning and turns it into convex options trades using dark pool flow, volatility signals, and cross-asset positioning data.
- Detects hidden positioning through dark pool, options flow, and cross-asset signals
- Evaluates every trade through a strict three-gate framework
- Generates portfolio, scan, and scenario reports with a real-time trading terminal on top
No narrative trades. No TA trades. Flow signal or nothing.
Radon reconstructs market structure from multiple institutional signals and converts that information into executable trade ideas, risk reports, and portfolio decisions.
Inputs
- Dark pool and OTC flow from Unusual Whales
- Options flow, volatility surface, and open-interest change data
- Real-time quotes and options chains from Interactive Brokers
- Cross-asset volatility relationships, CTA positioning, and analyst context
Processing
- Signal detection and scoring
- Strategy-specific modeling
- Convex options structure design
- Fractional Kelly sizing and portfolio risk checks
Outputs
- Trade candidates and evaluations
- HTML reports for scans, portfolio state, and stress tests
- Live portfolio and order-state monitoring in the web terminal
- Execution and post-trade management through Interactive Brokers
Every trade must pass three sequential gates. If any gate fails, no trade is taken.
Potential gain must be at least 2x potential loss.
- Default posture: defined-risk structures
- Typical structures: long options, vertical spreads, calendars
- Exception: risk reversals are explicit manager-override trades
A trade needs a specific, data-backed signal that has not fully moved price yet.
- Dark pool accumulation or distribution
- LEAP implied volatility mispricing
- Cross-asset volatility divergence
- Credit-volatility dislocations or crash-risk regime shifts
Trades are sized using fractional Kelly with hard limits.
- Max position size: 2.5% of bankroll
- No pyramiding into weak signals
- Portfolio-level exposure is monitored continuously
Six active strategies drive the system.
| Strategy | Signal | Market Inefficiency | Typical Structure | Timeframe | Risk |
|---|---|---|---|---|---|
| Dark Pool Flow | Institutional accumulation or distribution | Price lag versus hidden liquidity | Calls, puts, vertical spreads | 2-6 weeks | Defined |
| LEAP IV Mispricing | Realized vol materially above long-dated IV | Long-dated volatility underpricing | Long LEAPs, diagonals | Weeks-9 months | Defined |
| GARCH Convergence | Cross-asset vol repricing lag | Surface adjustment is slower than regime change | Calendars, verticals | 2-8 weeks | Defined |
| Risk Reversal | Skew distortion between puts and calls | Put demand richer than equivalent call demand | Risk reversal | 2-8 weeks | Undefined |
| Volatility-Credit Gap (VCG-R) | VIX>28 + VCG>2.5σ divergence | Credit is lagging elevated vol stress | HYG/JNK puts, bear put spreads | 1-5 days | Defined |
| Crash Risk Index (CRI) | CTA deleveraging plus COR1M implied-correlation stress | Systematic positioning unwind | Index puts, tactical hedges | 3-5 days | Defined |
Full strategy specs live in docs/strategies.md. VCG research notes live in docs/cross_asset_volatility_credit_gap_spec_(VCG).md.
Interactive Brokers ----\
Unusual Whales ---------+--> Signal Detection Engine --> Strategy Evaluation
MenthorQ / CTA Data ----/ |
Exa / Research ---------/ v
Convex Structure Builder
|
v
Kelly Position Sizing
|
v
Execution / Monitoring
|
v
Radon Terminal
At a high level:
scripts/contains scanners, evaluators, pricing logic, reporting, and broker integrationsweb/contains the Next.js terminal for portfolio, performance, flow, orders, regime, and AI-assisted workflowssite/contains the standalone marketing websitedata/holds runtime artifacts and scan outputs
Prerequisites
- Python
3.13(Python 3.14 has ib_insync/eventkit incompatibility) - Node.js
18+ - Interactive Brokers Gateway (cloud via Tailscale, Docker, or local TWS)
- Unusual Whales API access
Install and run
git clone https://github.com/joemccann/radon.git
cd radon
pip install -r requirements.txt
cd web && npm install && cd ..
python scripts/ib_sync.py
python scripts/scanner.py --top 15Web app in web/.env:
ANTHROPIC_API_KEY=your-anthropic-key
UW_TOKEN=your-unusual-whales-key
EXA_API_KEY=your-exa-keyPython scripts and IB Gateway in the project root .env:
MENTHORQ_USER=your-menthorq-email
MENTHORQ_PASS=your-menthorq-password
# IB Gateway connection
IB_GATEWAY_HOST=ib-gateway # Cloud (Tailscale MagicDNS) or 127.0.0.1 (local)
IB_GATEWAY_PORT=4001
IB_GATEWAY_MODE=cloud # "cloud" | "docker" | "launchd"The dedicated CTA sync service and wrapper scripts source the project root .env directly. Keep MenthorQ credentials there so the scheduled 4:15 PM ET and 5:00 PM ET CTA runs, plus any RunAtLoad catch-up execution after reboot/login/wake, use the same auth context as manual CLI fetches.
scripts/run_cta_sync.sh now parses .env values literally instead of shell-sourcing them, so unquoted secrets containing shell metacharacters such as $ remain intact in the scheduled/background CTA path.
Optional shell exports:
export XAI_API_KEY="your-xai-api-key"MenthorQ-based workflows require Playwright and httpx:
pip install playwright httpx
playwright install chromiumInteractive Brokers connects on port 4001 (Gateway) or 7497 (TWS). No broker API key is required, but a Gateway instance must be reachable before live workflows. See the IB Gateway section below for cloud, Docker, and local setup options.
Radon includes a real-time trading terminal built with Next.js 16. It streams Interactive Brokers prices, computes live greeks, visualizes portfolio exposures, and serves as the operator interface for scans, evaluation, and monitoring.
cd web
npm install
npm run devVisit http://localhost:3000.
Key capabilities
- Real-time price streaming with live greeks
- Shared quote telemetry across ticker, instrument, and modify-order views with
BID,MID,ASK, andSPREADrendered in a single layout contract; spread displays use raw quote width plus midpoint percent - Multi-leg position monitoring and per-leg P&L
- YTD portfolio performance analytics with reconstructed institutional metrics that revalidate against the latest workspace portfolio sync, and a route-side ET-session refresh guard so stale prior-session
portfolio.jsonsnapshots do not block the current day’s reconstruction - Closed-market route mounts still render cached portfolio, performance, regime, and internals data immediately while background sync remains paused outside market hours
- Shared
/regimestrip renderer with a responsive5-up -> 3x2 -> stacked telemetry railcontract so label, value, delta, and context remain readable on narrower viewports - Regime history charts with cached 20-session RVOL and COR1M context
- RVOL/COR1M relationship view with spread, quadrant state, and normalized divergence
- Order management, including combo spread workflows
- Flow analysis, regime views, and thesis checks
- AI chat interface for command execution and analysis
On /regime, the relationship view classifies the latest RVOL and COR1M point against their own rolling 20-session means. The labels are relative-state labels, not fixed threshold buckets.
| State | Rule | Meaning |
|---|---|---|
| Systemic Panic | RVOL >= 20-session mean and COR1M >= 20-session mean | Realized volatility is elevated and the options market expects broad index constituents to move together. This is the most defensive state: stress is already in the tape and correlation risk is still being bid. |
| Fragile Calm | RVOL < 20-session mean and COR1M >= 20-session mean | Realized volatility looks calm, but implied correlation is elevated. The market is quiet on the surface while options still price herd behavior or crash-risk demand. |
| Stock Picker's Market | RVOL >= 20-session mean and COR1M < 20-session mean | Realized volatility is elevated, but implied correlation is still contained. Moves are happening, but they are not yet being priced as full-system lockstep stress. |
| Goldilocks | RVOL < 20-session mean and COR1M < 20-session mean | Both realized volatility and implied correlation are below their recent norms. This is the cleanest diversification backdrop in the relationship model. |
Implementation note: when live data is available, the latest relationship-state calculation uses the current intraday RVOL and/or live COR1M value on top of the cached 20-session history.
Responsive note: the live-strip cards on /regime now share a dedicated renderer that holds a fixed information hierarchy across widths. Desktop stays five-up, narrower desktop/tablet collapses to a symmetric 3 x 2, and the small-screen stack switches each row to a compact telemetry rail so the delta arrow, context text, and timestamp stay scan-friendly instead of collapsing into dead whitespace.
The repo also includes a standalone Next.js site in site/.
cd site
npm install
npm run devSet NEXT_PUBLIC_SITE_URL in the site environment so canonical, JSON-LD, robots.txt, and sitemap.xml all reference the production hostname correctly.
For Vercel, the project should use site/ as the Root Directory. The site app includes an ignored-build step in site/vercel.json so pushes only trigger a site deploy when files under site/ changed.
The marketing app is intentionally separate from web/: it carries the Radon landing-page narrative and its own deployment guardrails. To verify the site locally without colliding with another live Next.js process:
cd site
npm run lint
NEXT_DIST_DIR=.next-build npm run build
python3.13 scripts/seo_audit_report.pysite/scripts/seo_audit_report.py audits the rendered page metadata plus robots.txt, sitemap.xml, manifest.webmanifest, and the Open Graph/Twitter image routes, then writes a branded HTML report to reports/.
To generate an operator-facing SEO report against a live local instance:
python3.13 scripts/site_seo_audit.py --url http://127.0.0.1:3333 --openIf local port binding is unavailable, build the static site and point the audit at site/.next-build/server/app instead.
- Run
scanto surface dark pool and regime-aware candidates. - Run
evaluate NVDAto execute the full seven-milestone validation flow. - If edge passes, design a convex structure and size it with Kelly constraints.
- Send or stage the trade through Interactive Brokers.
- Monitor the position in the Radon Terminal and portfolio report.
| Command | Description |
|---|---|
scan |
Watchlist dark pool flow scan with CRI regime overlay and HTML report |
discover |
Market-wide or targeted discovery scan for new candidates |
leap-scan [TICKERS] |
Find LEAP IV mispricing opportunities |
garch-convergence [TICKERS] |
Cross-asset implied-versus-realized volatility divergence scan |
seasonal [TICKERS] |
Monthly seasonality analysis from EquityClock |
analyst-ratings [TICKERS] |
Ratings, price targets, and recent changes |
| Command | Description |
|---|---|
evaluate [TICKER] |
Full seven-milestone trade evaluation |
stress-test |
Interactive bear/base/bull scenario report for the current portfolio |
risk-reversal [TICKER] |
IV-skew analysis for directional risk-reversal structures |
vcg |
VCG-R scan — VIX/VVIX/HYG regression, risk-off (VIX>28+VCG>2.5), severity tiers |
cri-scan |
Crash Risk Index with CTA exposure model |
| Command | Description |
|---|---|
portfolio |
Live portfolio report with dark pool thesis checks |
free-trade |
Analyze multi-leg positions for free-trade progression |
journal |
View recent trade log entries |
sync |
Pull live portfolio data from Interactive Brokers |
blotter |
Today's fills, grouped spreads, and commission totals |
blotter-history |
Historical trades via IB Flex Query |
| Command | Description |
|---|---|
strategies |
Show the strategy registry |
menthorq-cta |
Fetch or backfill institutional CTA positioning data manually |
x-scan [@ACCOUNT] |
Fetch X sentiment through xAI |
x-scan-browser [@ACCOUNT] |
Fetch X sentiment through browser scraping |
commands |
Display the full command registry |
radon/
├── scripts/ # Python scanners, evaluators, broker integrations
│ ├── clients/ # Broker and data-provider adapters
│ ├── monitor_daemon/ # Background fill/exit/rebalance daemon
│ ├── benchmarks/ # Performance benchmarks (scanner timing)
│ └── tests/ # Python test suite
├── web/ # Next.js terminal
│ ├── components/ # Terminal UI components
│ └── app/ # Next.js routes and API
├── docs/ # Strategy and implementation documentation
│ └── autoresearch/ # Benchmark results and optimization notes
├── tasks/ # Plans, progress reports, and task tracking
├── brand/ # Radon design system and tokens
├── data/ # Runtime data and generated artifacts
├── config/ # launchd and service configuration
├── logs/ # Daemon logs (auto-rotated, gitignored)
├── requirements.txt # Python dependencies
├── CLAUDE.md # Agent and workflow rules
└── .pi/ # Command registry and agent skills
Market-data priority is intentionally strict:
- Interactive Brokers for real-time quotes, options chains, and portfolio state
- Unusual Whales for dark pool flow, sweeps, options flow, and analyst data
- Exa for company and market research
- Cboe official index feeds for COR1M historical fallback before any generic web source
- Yahoo Finance as a strict last-resort fallback
Auxiliary sources:
- MenthorQ for CTA positioning used in CRI analysis
- xAI / browser scraping for X-account sentiment workflows
Radon includes Python, frontend, and end-to-end test coverage.
- Python:
pytestfor scanners, evaluation logic, utilities, and adapters - Frontend:
Vitestfor web logic - E2E:
Playwrightfor browser workflows
python3.13 scripts/run_pytest_affected.py
python3.13 scripts/run_pytest_affected.py --files scripts/ib_sync.py scripts/tests/test_combo_entry_date.py -- -q
python -m pytest scripts/tests/ -v
cd web && npm test
cd web && npx playwright testFor scoped Python work, prefer scripts/run_pytest_affected.py over a full repo pytest run. The helper resolves changed Python files to the matching affected tests under scripts/tests/ and scripts/trade_blotter/, and skips pytest entirely when the current change set has no Python impact.
Unit tests use mocked API calls where possible, so most development work does not require a live IB or Unusual Whales connection.
Order-route integration coverage now includes a dedicated FastAPI test harness:
web/tests/order-e2e.test.tsboots an isolated test-mode FastAPI instance throughweb/tests/fastapiHarness.ts- the harness sets
RADON_API_TEST_MODE=1, pointsRADON_API_URLat the isolated server, and never reuses the live broker-backedlocalhost:8321process unless that server explicitly reportstest_mode: true - test mode disables IB Gateway / pool startup and stubs order placement, modify, cancel, and refresh endpoints so the Vitest suite does not touch an active IBC or IB session
The repo includes background-service support for the live trading environment:
| Service | Purpose |
|---|---|
| IB Gateway (cloud/Docker/launchd) | Broker session for live quotes, execution, and reports — cloud mode via Tailscale, Docker with auto-restart, or legacy IBC launchd |
| CRI scan service | Refreshes crash-risk regime data intraday and writes atomic CRI cache snapshots |
| CTA sync service | Refreshes the latest closed-session MenthorQ CTA cache at 4:15 PM ET and 5:00 PM ET, with RunAtLoad catch-up after reboot/login/wake, and writes machine-readable health state for stale-data detection |
| Monitor daemon | Tracks fills and exit orders during market hours, plus off-hours preset rebalance and Flex token checks (logs auto-rotated at 10MB) |
| Data refresh services | Keeps portfolio and order-state data current and repairs post-close CRI cache history when needed |
Historical setup helpers remain in scripts/, and the broader implementation notes live in docs/implement.md.
CTA freshness is now an explicit contract:
data/menthorq_cache/cta_{DATE}.jsonremains the daily cache artifact.data/menthorq_cache/health/cta-sync-latest.jsonis the primary machine-readable health record, anddata/menthorq_cache/health/history/cta-sync-*.jsonpreserves run history. For older tooling, the latest record is also mirrored todata/service_health/cta-sync.json.scripts/run_cta_sync.shis the launchd-safe wrapper. It resolves the repo Python runtime, parses the root.envliterally without shell expansion, and delegates toscripts/cta_sync_service.py./api/menthorq/ctacompares the latest cache date against the latest closed trading day, triggers one background CTA sync when stale, and returns explicitcache_metaplussync_healthmetadata (with async_statuscompatibility alias) so/ctacan show stale/degraded state instead of silently presenting old data as current.
For the /regime RVOL/COR1M chart, the CRI cache now preserves enough trailing SPY closes to rebuild the full prior 20 sessions of realized volatility. COR1M history now falls back to the official Cboe dashboard feed before Yahoo, and the API prefers the richer CRI cache candidate when scheduled snapshots lag and backfills missing history[].realized_vol values from cached closes before rendering the chart.
For /internals, the skew charts use the live /internals/skew-history backfill only during active ET market hours. On weekends and other closed sessions, /api/internals skips the live skew fetch and serves the newest shared long-range cache artifact from data/cache/internals_skew_history_*.json so the page keeps its full SPX/NDX history without attempting a non-trading-day refresh.
Three deployment modes controlled by IB_GATEWAY_MODE in the root .env:
| Mode | Description |
|---|---|
cloud (default) |
Gateway runs on a Hetzner VM via Tailscale MagicDNS at ib-gateway:4001. No local restart capability — health check is TCP port probe only. |
docker |
Local Docker Compose with restart: unless-stopped and healthcheck. |
launchd |
Legacy IBC launchd service on macOS. |
Cloud setup requires Tailscale on both the Gateway host and development machine. The Gateway host runs ghcr.io/gnzsnz/ib-gateway in Docker with IBC for automated 2FA handling.
How it connects: ib_client.py loads the root .env via dotenv at import time, setting DEFAULT_HOST before any module reads it. The Node WS relay (ib_realtime_server.js) also loads .env at startup. All scripts use DEFAULT_HOST — no hardcoded 127.0.0.1 in IB connection code.
Connection pool: FastAPI maintains three persistent connections (sync=3, orders=4, data=5) with retry (3 attempts, 2s backoff) and 1s stagger between roles.
Troubleshooting:
# Health check
curl -s http://localhost:8321/health | python3.13 -m json.tool
# Gateway reachable?
bash -c 'echo > /dev/tcp/ib-gateway/4001' && echo OK || echo FAIL
# Check connections on remote host
ssh root@ib-gateway "ss -tnp | grep 4001"
# Test a fresh client ID
python3.13 -c "from ib_insync import IB; ib=IB(); ib.connect('ib-gateway',4001,clientId=99,timeout=10); print('OK'); ib.disconnect()"Management commands: Available locally (via Tailscale SSH) and on the VPS:
| Local (Mac) | On VPS | Action |
|---|---|---|
ibstart |
ibstart |
Start container, wait for port 4001 |
ibstop |
ibstop |
Stop and remove container |
ibrestart |
ibrestart |
Restart container, wait for port 4001 |
ibstatus |
ibstatus |
Container state, port check, active connections |
iblogs |
iblogs |
Last 50 log lines (iblogs 100 for more) |
ibhealth |
ibhealth |
Docker healthcheck status |
Local commands are shell aliases that SSH into the VPS via ibgw() in ~/.zshrc. The VPS has ibgw at /usr/local/bin/ibgw with short aliases in .bashrc for both root and mdw users.
Rollback to local: Set IB_GATEWAY_HOST=127.0.0.1 and IB_GATEWAY_MODE=docker (or launchd) in .env.
| Term | Definition |
|---|---|
| Convexity | An asymmetric payoff where expected upside materially exceeds downside |
| CRI | Crash Risk Index, a composite crash-risk and deleveraging model |
| CTA | Commodity Trading Advisor, typically systematic trend-following funds |
| Dark Pool | Private off-exchange venue used for institutional trading |
| Edge | A specific reason the market is mispricing an outcome |
| Kelly Criterion | Position-sizing framework used to scale exposure to edge and odds |
| VCG-R | Volatility-Credit Gap, a divergence model — VIX>28 + VCG>2.5σ triggers risk-off |
