9 Commits

Author SHA1 Message Date
Casey Judice a65256ca74 Add Step 1 "Prepare trade" pre-flight ticket (read-only, no execution)
New arb/prepare.py + GET /api/prepare: for one monthly arb pair it re-pulls
fresh quotes (incl. real Kalshi top-of-book ask size) and builds the exact
order pair a cross-venue hedge would need -- sides from best_side, size
capped to the thinner book, limit = ask + slippage buffer, capital required,
guaranteed worst-case, and warnings. Places NO orders and touches no order
endpoints anywhere.

UI: a "Prepare trade" button in the monthly drawer's breakdown card opens a
PREVIEW-ONLY ticket modal (max-size + slippage-cent inputs that re-price live;
a deliberately disabled Confirm button as the Step-2 placeholder). Also
reorders the expanded drawer so the breakdown/Prepare card is the wide left
column.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-02 22:11:09 -04:00
Casey Judice 137193d21a Match positions by contract quantity, filling exact strikes before basis
The paired view now recognizes cross-venue hedges economically (by what each
leg pays) instead of only matching curated pairs.json rows, and fills by
contract count: exact-strike (matched) pairs fill first, so a leg's excess
spills into the nearest basis pair only after every matched pairing is
exhausted. Partially consumed legs are pro-rated; size/cost/value/pnl conserve
exactly. Frontend shows matched / basis+ / basis- badges, strike band, and a
plain-English coverage note per row.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 11:27:29 -04:00
Casey Judice 36dd29b549 Map XRP/ETH below-pairs + harden Kalshi quote handling
Populate the XRP and ETH MINMON (below) pairs that were silently NO PAIR:
they were never mapped to Polymarket's "dip to" markets (and two ETH pairs
pointed at the now-resolved dip-to-2000). Map each to the nearest live Poly
strike; leave strikes with no comparable Poly market (ETH $250, below Poly's
$400 floor) as NO PAIR.

Also fix two correctness gaps surfaced while investigating:
- Settled/finalized Kalshi markets returned stale 1.0/0.0 sentinel prices and
  were scored as live; now reported as NO KALSHI (timely as month-end monthly
  contracts settle).
- A Kalshi ask of exactly $1.00 means "no resting offer" (real asks are 1-99c);
  it was treated as a tradeable quote, producing phantom BAD BASIS rows at
  -100%. Drop it at the source so no fake leg enters the arb math.
- When both venues are live but no opposite-side hedge can be built, report
  NO ARB instead of the misleading NO DATA.

No real arb affected (a $1.00 leg can never be profitable); ARB count holds
at 3 and phantom BAD BASIS rows dropped 16 to 9.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 17:44:43 -04:00
Casey Judice 757d0cb55d Distinguish NO POLY / NO KALSHI from generic NO DATA
calc.evaluate now reports NO POLY when the Kalshi side has data but the
Polymarket slug doesn't resolve (the common case: Polymarket renamed/
dropped a market while Kalshi still trades it). NO KALSHI is the reverse,
NO DATA only when both are missing. User caught this looking at BTC <70k
May: 20 of 118 pairs were silently NO-DATA when the real cause was Poly-
side absence — now visible as a dedicated NO POLY chip + filter button.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 17:06:43 -04:00
Casey Judice b7ccd14b05 Background IBKR keepalive: tickle Gateway every 60s
The IBKR Client Portal Gateway has an aggressive ~5min inactivity timeout
that drops the session if no API calls are made (causing 401s on the next
Daily-tab refresh). A daemon thread alongside the SMS poller now calls
ibkr.auth_status + tickle every minute as long as the server is running.
No-op when Gateway isn't up or not authed.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 10:20:48 -04:00
Casey Judice dfdfe8414d IBKR daily scanner: correct YES+NO conid schema + discover helper
Earlier ibkr.py assumed each strike = one conid (buy=Yes / sell=No). The
IBKR ForecastEx CP API actually exposes each strike as TWO separate conids
(YES = right=CALL, NO = right=PUT). Local symbol shape is
CFBTC_MMDDYYHH_<strike>_<YES|NO> e.g. CFBTC_05242616_71000_YES means
4pm ET May 24 2026, $71,000 YES side.

- data/ibkr_contracts.example.json: per entry now has yes_conid + no_conid
  + strike + close_iso + label (one row per strike).
- arb/daily.py: snapshots both conids in one batch; yes_ask/no_ask come
  directly from each side's ask (no more 1-bid derivation).
- scripts/discover_ibkr.py: walks /iserver/secdef/strikes + secdef/info to
  print the full YES/NO ladder for a given underlying + month + maturity.
  Filters by maturityDate so it doesn't mix expiries.

End-to-end verified: 3 sample strikes pair cleanly to KXBTCD-26MAY2416
(IBKR $71K -> Kalshi T70999.99 etc.). Live prices still pending the
user's ForecastEx market-data subscription / strikes closer to spot.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 10:19:26 -04:00
Casey Judice b1f18ca9f1 IBKR × Kalshi daily-BTC scanner (local-only)
- arb/ibkr.py: Client Portal Gateway client (localhost:5000 over self-
  signed SSL; auth_status, tickle, snapshot, secdef/search). Distinct
  NotConnected vs NotAuthed exceptions for graceful UI fallbacks.
- arb/daily.py: pairs each user-configured IBKR ForecastEx daily-BTC
  contract against the Kalshi KXBTCD market closing at the same UTC
  instant with nearest strike. Reuses arb.calc.evaluate for the worst-
  case arb math; IBKR slots into the 'pq' position. NO KALSHI status
  when no matching Kalshi market exists.
- data/ibkr_contracts.example.json: template (real file gitignored).
- /api/scan/daily route + 'IBKR × Kalshi' tab in the local UI. Tab
  shows a setup banner explaining what to do when Gateway/contracts
  aren't configured (vs crashing).
- Public Pages dashboard unchanged (positions + IBKR are local-only).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 16:16:53 -04:00
Casey Judice ba533980e4 Positions History: include sold-out / round-tripped markets
Kalshi /portfolio/settlements only lists matured-and-held markets. Anything
the user bought and sold before expiry was invisible. Added a follow-up
query on /portfolio/positions?count_filter=total_traded filtered to
position_fp=0 — captures every round-trip. realized comes straight from
Kalshi's realized_pnl_dollars (exact).

Polymarket history was skipping any conditionId where the CLOB still
reports closed=false. But if the user sold all their shares before the
market resolves, that's still a history row with realized = sell - buy.
Result tag is 'SOLD' to distinguish from resolved-and-won/-lost rows.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 20:44:52 -04:00
Casey Judice 05c0441053 Initial commit: prediction-market arb scanner + GHA alerts
- Monthly + hourly Kalshi/Polymarket arb scanner (stdlib-only Python).
- Live positions tab w/ realized P&L history.
- GitHub Actions cron workflow texts SMS via Apps Script webhook on
  newly-detected arbs. State persisted in alerts_state.json.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 19:48:50 -04:00