diff --git a/scripts/surfer/ibkr_paper_book.py b/scripts/surfer/ibkr_paper_book.py index 670d87459..410e7c507 100644 --- a/scripts/surfer/ibkr_paper_book.py +++ b/scripts/surfer/ibkr_paper_book.py @@ -70,13 +70,37 @@ def main(): print(f"\nCONNECT FAILED on 127.0.0.1:{port} — is IB Gateway/TWS running + logged in to PAPER, API enabled?") print(f" ({type(e).__name__}: {str(e)[:80]}) | try --port 7497 for TWS paper") return + accts = ib.managedAccounts() + acct = accts[0] if accts else "?" + is_paper = acct.startswith("DU") # IBKR convention: DU* = paper/demo, U* = live nlv = next((float(v.value) for v in ib.accountSummary() if v.tag == "NetLiquidation"), 0.0) cash = next((float(v.value) for v in ib.accountSummary() if v.tag == "TotalCashValue"), 0.0) pos = {p.contract.symbol: p.position for p in ib.positions()} - print(f"\nIBKR PAPER account: NLV ${nlv:,.0f} cash ${cash:,.0f} | positions: {pos or 'none'}") + print(f"\nIBKR account: {acct} [{'PAPER ✅' if is_paper else 'LIVE ⚠️ — NOT paper!'}] NLV ${nlv:,.0f} cash ${cash:,.0f} | positions: {pos or 'none'}") if nlv <= 0: print(" (no NLV — check the paper account is funded with paper cash)"); ib.disconnect(); return + if mode == "mini": + if not is_paper: + print(" ABORT: account is NOT a paper account (no DU prefix). Refusing to place a test order."); ib.disconnect(); return + from ib_async import Stock, MarketOrder + c = Stock("SPY", "SMART", "USD"); ib.qualifyContracts(c) + print("\nplacing MINI test order: BUY 1 SPY (market) on PAPER...") + o = MarketOrder("BUY", 1); o.account = acct + tr = ib.placeOrder(c, o) + for _ in range(20): + ib.sleep(1) + if tr.orderStatus.status in ("Filled", "Cancelled", "ApiCancelled", "Inactive"): + break + fills = [f"{f.execution.shares}@${f.execution.price}" for f in tr.fills] + print(f" order status: {tr.orderStatus.status} filled {tr.orderStatus.filled}/1 avgPx ${tr.orderStatus.avgFillPrice or 0:.2f} fills={fills or 'none'}") + ib.sleep(1) + newpos = {p.contract.symbol: p.position for p in ib.positions()} + print(f" positions now: {newpos or 'none'}") + print(f" → VALIDATED: this is the {'PAPER' if is_paper else 'LIVE'} env, order routing works." if tr.orderStatus.status == "Filled" + else " → order not filled yet (market may be closed — it'll fill at next open; status shows it routed).") + ib.disconnect(); return + orders = [] for t, w in tw.items(): tgt_sh = nlv * w / px[t]