#!/usr/bin/env python3 """Pure exchange arbitrage: for each material, cheapest ask at any CX vs best bid at any other CX. Reports spread per unit, per ton and per m3 of cargo, and how many units the thinner side can absorb. python prun_cxarb.py --from AI1 # only routes buying at AI1 python prun_cxarb.py --to AI1 # only routes selling at AI1 python prun_cxarb.py --minqty 200 --sort ton """ import argparse, json, urllib.request FIO="https://rest.fnar.net" def get(p): with urllib.request.urlopen(FIO+p,timeout=60) as r: return json.load(r) ap=argparse.ArgumentParser() ap.add_argument("--from",dest="src",default=None); ap.add_argument("--to",dest="dst",default=None) ap.add_argument("--minqty",type=float,default=100,help="min units both sides can absorb") ap.add_argument("--sort",default="ton",choices=["unit","ton","m3","total","pct"]); ap.add_argument("--top",type=int,default=30) a=ap.parse_args() mats={m["Ticker"]:m for m in get("/material/allmaterials")} px={} for e in get("/exchange/all"): px.setdefault(e["MaterialTicker"],{})[e["ExchangeCode"]]=e rows=[] for t,cxs in px.items(): for s,es in cxs.items(): if a.src and s!=a.src: continue if not es.get("Ask"): continue for d,ed in cxs.items(): if d==s or (a.dst and d!=a.dst) or not ed.get("Bid"): continue # depth: units available at ask side / wanted at bid side (order book totals) # /exchange/all has only book totals; use min(supply at source, demand at destination) as depth proxy qty=min(es.get("Supply") or 0, ed.get("Demand") or 0) if qty8} {'bid':>8} {'spread':>7} {'%':>5} {'/ton':>7} {'/m3':>7} {'qty':>7} {'total':>9}") for r in rows[:a.top]: print(f"{r['t']:4} {r['src']:4} {r['dst']:5} {r['ask']:8.0f} {r['bid']:8.0f} {r['unit']:7.0f} {r['pct']:5.0f} {r['ton']:7.0f} {r['m3']:7.0f} {r['qty']:7.0f} {r['total']:9.0f}")