diff --git a/scripts/surfer/multistrat_bot.py b/scripts/surfer/multistrat_bot.py index 731564b84..320fd0462 100644 --- a/scripts/surfer/multistrat_bot.py +++ b/scripts/surfer/multistrat_bot.py @@ -160,7 +160,11 @@ def main(): continue if val > C["max_order"] * nlv: log("WARN", "order_capped", ticker=t, value=round(val), cap=round(C["max_order"] * nlv)) - orders.append((t, "BUY" if delta > 0 else "SELL", round(abs(delta), 4), round(val))) + qty = int(round(abs(delta))) # whole shares — IBKR API rejects fractional (error 10243) + if qty == 0: + log("INFO", "skip_subshare", ticker=t, note="rounds to 0 whole shares") + continue + orders.append((t, "BUY" if delta > 0 else "SELL", qty, round(val))) for t, side, qty, val in orders: log("INFO", "planned_order", ticker=t, side=side, qty=qty, value=val) if not orders: @@ -183,10 +187,14 @@ def main(): except Exception as e: log("ERROR", "order_failed", ticker=t, err=str(e)[:120]) # continue — partial book is recoverable next run ib.sleep(3) + accepted = sum(1 for tr in placed if tr.orderStatus.status not in ("Cancelled", "ApiCancelled", "Inactive")) filled = sum(1 for tr in placed if tr.orderStatus.status == "Filled") newpos = {p.contract.symbol: p.position for p in ib.positions()} - state["last_rebalance"] = dt.date.today().isoformat(); state["rebalances"] = state.get("rebalances", 0) + 1 - log("INFO", "reconcile", placed=len(placed), filled=filled, positions=newpos or None) + if accepted > 0: # mark the period done only if orders were actually accepted (not all rejected) + state["last_rebalance"] = dt.date.today().isoformat(); state["rebalances"] = state.get("rebalances", 0) + 1 + else: + log("WARN", "no_orders_accepted", note="all rejected — not marking rebalance; will retry next run") + log("INFO", "reconcile", placed=len(placed), accepted=accepted, filled=filled, positions=newpos or None) return 0 finally: save_state(state)