Files
polymarket_arbitrage/arbitrage/cli.py
T
gavindiaz 9259325d8a
deploy GitHub Pages / deploy (push) Has been cancelled
tests / test (push) Has been cancelled
first commit
2026-07-22 18:53:44 +08:00

139 lines
4.5 KiB
Python

"""Arbitrage CLI — `arb <subcommand>`.
Subcommands:
arb init Create the SQLite schema.
arb discover One pass of Gamma REST discovery (or --loop for forever).
arb scan Full paper loop: WS + engine + paper executor.
arb web Start the FastAPI + HTMX dashboard.
arb resolve Mark a resolution (manual fallback).
"""
from __future__ import annotations
import argparse
import asyncio
from collections.abc import Sequence
from .config import Mode, settings
from .logging_setup import configure_logging
def _parser() -> argparse.ArgumentParser:
p = argparse.ArgumentParser(prog="arb", description="Polymarket arbitrage")
sub = p.add_subparsers(dest="cmd", required=True)
sub.add_parser("init", help="create SQLite schema")
disc = sub.add_parser("discover", help="one pass of Gamma REST discovery")
disc.add_argument("--loop", action="store_true", help="run forever")
disc.add_argument("--interval", type=int, default=120, help="seconds between passes")
disc.add_argument("--max-pages", type=int, default=20)
sub.add_parser("scan", help="run the paper scan loop (WS + engine + executor)")
webp = sub.add_parser("web", help="start the dashboard")
webp.add_argument("--host", default=settings.web_host)
webp.add_argument("--port", type=int, default=settings.web_port)
res = sub.add_parser("resolve", help="mark an event as resolved")
res.add_argument("event_id")
res.add_argument(
"--winner",
default=None,
help="winning token_id (omit or set to 'invalid' for an invalid resolution)",
)
res.add_argument("--source", default="manual")
return p
async def _cmd_init() -> None:
from .db import init_db
await init_db()
print(f"initialized {settings.db_path}")
async def _cmd_discover(loop: bool, interval: int, max_pages: int) -> None:
from .clients.polymarket_rest import discover_once, discovery_loop
if loop:
await discovery_loop(interval_seconds=interval)
else:
stats = await discover_once(max_pages=max_pages)
print(
f"seen={stats.events_seen} negRisk={stats.neg_risk_events} "
f"upserted={stats.upserted} malformed={stats.skipped_malformed}"
)
async def _cmd_scan() -> None:
from .book.l2 import BookRegistry
from .clients.polymarket_ws import run_market_channels
from .engine.live_executor import LiveExecutor
from .engine.loop import hydrate_event_index, run_scan_loop
from .engine.opportunity import EventIndex, OpportunityEngine
from .engine.paper_fills import PaperExecutor
books = BookRegistry()
index = EventIndex()
hydrated = await hydrate_event_index(index)
if hydrated == 0:
print("no events in DB; run `arb discover` first")
return
token_ids = list(index.by_token_id.keys())
engine = OpportunityEngine(books=books, index=index)
executor = (
LiveExecutor(books=books, dry_run=False)
if settings.mode == Mode.LIVE
else PaperExecutor(books=books)
)
ws_task = asyncio.create_task(
run_market_channels(token_ids, registry=books), name="ws"
)
try:
await run_scan_loop(books=books, index=index, engine=engine, executor=executor)
finally:
ws_task.cancel()
try:
await ws_task
except asyncio.CancelledError:
pass
def _cmd_web(host: str, port: int) -> None:
import uvicorn
uvicorn.run("arbitrage.web.app:app", host=host, port=port, reload=False)
async def _cmd_resolve(event_id: str, winner: str | None, source: str) -> None:
from .engine.paper_fills import mark_resolution
winning = None if (winner is None or winner.lower() == "invalid") else winner
updated = await mark_resolution(event_id, winning_token_id=winning, source=source)
print(f"resolved event {event_id}: {updated} basket(s) updated")
def main(argv: Sequence[str] | None = None) -> int:
configure_logging()
args = _parser().parse_args(argv)
if args.cmd == "init":
asyncio.run(_cmd_init())
elif args.cmd == "discover":
asyncio.run(_cmd_discover(args.loop, args.interval, args.max_pages))
elif args.cmd == "scan":
asyncio.run(_cmd_scan())
elif args.cmd == "web":
_cmd_web(args.host, args.port)
elif args.cmd == "resolve":
asyncio.run(_cmd_resolve(args.event_id, args.winner, args.source))
else: # argparse guarantees required=True; belt-and-braces
return 2
return 0
if __name__ == "__main__":
raise SystemExit(main())