A theorem-gated options arbitrage agent for SPY, SPX and XSP. It hunts for four-contract price rectangles that break a no-arbitrage rule, trades only the ones it can prove are real, and waits for the mispricing to correct.
Built for the Alpaca AI Trading Agents Hackathon (28 Aug – 4 Sep 2026).
🔗 Live dashboard · Full project report · One-page write-up · How it works
| Rectangles scanned | 192,008,262 |
| Violations detected | 17,097 |
| Episodes tracked → reverted | 43,566 → 98%, median ~11 min |
| Backtest (T1, same live filters, both sides cross the spread) | +$9,829 over 672 trades, 67% win |
| Live paper trading (1½ sessions) | 21 round-trips, every one closed on reversion, −$45 |
| Broker commissions | $0 |
The signal is real and it reverts on schedule. The catch is execution: the mispricing is worth about 2¢ a contract and it costs about 2¢ to cross the spread twice and collect it. A backtest that assumes fills makes money; live, only ~3% of resting limit orders filled, and the coin flip tipped to −$45. That collision — not the theory — is the finding. An end-of-day paper measuring mid-price profit can't see it, because mid prices aren't tradeable and margin doesn't exist in a backtest.
A call-price surface must satisfy total positivity of order 2 (TP2) to be free of static arbitrage. For expiries
Four contracts form a rectangle (A, B, C, D). When
This is not a forecast. It makes no claim about where the market goes — only that a set of prices cannot all be right at once. That is why the trade is a bet on reversion, not direction, and why it can be proven rather than predicted.
- A theorem, not a model. The edge is a no-arbitrage property plus an early-exercise certificate — not a score a model happened to output.
- Refusal is the default, and it's logged. 32,505 decisions recorded; ~99% are refusals, each with the exact gate that stopped it. The interesting behaviour is what it won't trade.
- Honest by construction. The signal runs on Alpaca's indicative feed, so every order is a conservative limit — bad data produces non-fills, not bad fills. Nothing is hidden: the dashboard leads with the limitations.
Worth a look, in order: Overview (the headline) → Backtest (where the edge shows up) → Why only SPY (the constraints that shaped everything) → Decisions (real logged refusals, verbatim) → Reversion (watch a violation close).
The dashboard is fully static — it reads the committed snapshot in dashboard_data/, needs no API key, and every number regenerates from live state via scripts/compliance_report.py.
flowchart LR
A[Alpaca chain<br/>indicative feed] --> B[Rectangles<br/>forward-adjusted]
B --> C[Strong violation<br/>on crossable quotes]
C --> D[Episodes<br/>re-priced to reversion]
D --> E{Theory gate<br/>early-exercise certificate}
E -->|unresolved| X[Refuse & log]
E -->|certified| F{16 risk gates<br/>+ pre-send re-check}
F -->|rejected| X
F -->|approved| G[Conservative limit<br/>over MCP]
G --> H[Exit on reversion]
D & E & F & G --> L[(Append-only<br/>decision log)]
L --> N[LLM narrator<br/>read-only]
Full stage-by-stage walkthrough: PIPELINE.md.
MarginCalled/
├── app.py # Streamlit dashboard (the live demo)
├── src/tp2agent/ # the agent — 14 modules, standard library only
│ ├── alpaca.py # market data + chain parsing
│ ├── rectangles.py # rectangle construction + strong-violation test
│ ├── episodes.py # track each violation to reversion
│ ├── theory_gate.py # early-exercise certificate (Props 2.1 / 2.2)
│ ├── position.py # whole-contract, covered position sizing
│ ├── risk.py # 16 deterministic risk gates
│ ├── executor.py # conservative limit orders (never market)
│ ├── mcp_client.py # Alpaca MCP transport
│ ├── exits.py # reversion / expiry / deadline exits
│ ├── audit.py # append-only decision log
│ ├── narrator.py # read-only LLM explanation layer
│ └── features.py … # the study's F* feature vector, live
├── tests/ # 279 tests, one file per module
├── scripts/ # scanner, backtest, probes, compliance report
├── backtest/ # replay engine + results
├── dashboard_data/ # committed snapshot the dashboard reads (gzip)
├── reports/ # narrations, compliance, backtest sizing
├── PROJECT_REPORT.md # the full write-up (what, what happened, why)
├── WRITEUP.md # one-page summary
├── PIPELINE.md # stage-by-stage architecture
└── STREAMLIT.md # dashboard build brief
git clone https://github.com/Omkar38/MarginCalled.git
cd MarginCalled
pip install -r requirements.txt
# 1) Run the dashboard (no API key needed — reads the committed snapshot)
streamlit run app.py
# 2) Run the test suite (standard library only, no network)
python3 -m pytest tests/ -q # or: for f in tests/test_*.py; do python3 "$f"; done
# 3) Regenerate every published figure from live state
python3 scripts/compliance_report.pyLive trading additionally needs Alpaca paper keys (APCA_API_KEY_ID, APCA_API_SECRET_KEY) in a .env — see .env.example. Live keys are refused in code.
| denomination | sizing | trades | total | win |
|---|---|---|---|---|
| T1 | unit 1:1 (as traded) | 672 | +$9,829 | 67% |
| T1 | study weights | 386 | +$8,754 | 65% |
| T1 | 1:1 scaled (10× cap, 3.1× effective) | 672 | +$30,878 | 67% |
| K2 | unit 1:1 | 748 | −$5,363 | 49% |
| K2 | study weights | 312 | −$5,775 | 43% |
Every row assumes a fill at the quoted price on every signal. Live, ~3% filled — which is exactly why the live P&L and the backtest diverge.
- SPX — Alpaca publishes no greeks at any strike, and its indicative quotes price fully-in-the-money spreads at 57–68% of intrinsic (below what the contract is already worth). 12,210 violations detected, 0 executable.
- XSP — no greeks either; zero clean violations survive the quote-quality screens.
-
SPY sizing — the study's weights are short-heavy (naked calls), which a level-3 account can't submit (
403 / 40310000), so every trade is a covered 1:1. The twist: 1:1 ties up ~$4 vs ~$13,800 of margin for the ratio — roughly 2,220× more capital-efficient. The restriction helped.
| Requirement | Status |
|---|---|
| Alpaca Trading API + MCP server | ✅ MCP is the default transport, live-verified |
| Dedicated paper account, $100,000 | ✅ active, options level 3 |
| Every strategy includes options trading | ✅ |
| Public repo, MIT-licensed | ✅ LICENSE |
| Working demo at a public URL | ✅ margincalled.streamlit.app |
| One-page write-up | ✅ WRITEUP.md |
| Doc | What's in it |
|---|---|
| PROJECT_REPORT.md | The full account: what was built, what happened, and precisely why the loss occurred |
| WRITEUP.md | One page: AI logic, risk gates, Alpaca integration |
| PIPELINE.md | Every stage from a quote to a narrated decision |
| STREAMLIT.md | How the dashboard is built from the committed snapshot |
- Detection runs on Alpaca's indicative feed, not OPRA — violations are readings of that feed, not claims about the market.
- The committed dashboard snapshot is static — the record of the run, not a live feed.
- Only SPY was tradeable this run; SPX and XSP produced no executable fills (see above).
MIT © 2026 Omkar Lashkare, Jyotir Patel and Powel Polak.