From 4aa795f9b9f2939cf80db25e7063f16a71b861ec Mon Sep 17 00:00:00 2001 From: jaxperro Date: Mon, 13 Jul 2026 10:48:11 -0400 Subject: [PATCH] protected prices conform to the book's tick (quote-precision bound) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 4dp bound (sub-penny fix) violated tick conformity on 1c books — 'max_price must conform to tick size 0.01' failed a winning SPX copy at 14:36Z. The quoted price is always a tick multiple, so its own decimal count is the finest safe precision: bound rounds to quote precision (coarser is always valid on finer ticks, never the reverse). Verified across 2dp/3dp/sub-penny quotes both sides. Co-Authored-By: Claude Fable 5 --- copybot.py | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/copybot.py b/copybot.py index f7327797..fea00e1f 100644 --- a/copybot.py +++ b/copybot.py @@ -345,13 +345,21 @@ class LedgerLiveExecutor: except Exception as e: # no truth anchor → refuse to place at all return {"ok": False, "filled_shares": 0.0, "price": price, "resp": f"pre-check failed: {e}", "paper": False} + # protected prices must (a) scale WITH the price — flat 2dp rounding + # zeroed the bound on sub-penny books — and (b) CONFORM to the tick: + # the SDK rejects more decimals than the book's tick allows + # (2026-07-13: a 4dp bound on a 1c SPX book failed a winning copy). + # The quoted price is always a tick multiple, so its own decimal + # count is the finest precision that is safe on this book; a coarser + # bound is valid on finer ticks, never the reverse. + d = 2 + for cand in (2, 3, 4): + if abs(round(price, cand) - price) < 1e-9: + d = cand + break + else: + d = 4 try: - # protected prices round to 4dp and scale WITH the price — 2dp - # rounding zeroed the bound on sub-penny books (0.001 longshots - # are the follow set's specialty; the min_price floor removal - # 2026-07-13 makes them copyable, so the executor must survive - # them). BUY never bounds below the quoted cross; SELL never - # bounds above the quoted bid. if side == "BUY": # share-flooring can shave a gated $1.00 stake to $0.99, which # the venue rejects ('invalid amount for a marketable BUY') — @@ -361,14 +369,14 @@ class LedgerLiveExecutor: r = self.client.place_market_order( token_id=token_id, side="BUY", amount=amt, - max_price=min(max(round(price * (1 + self._slip), 4), + max_price=min(max(round(price * (1 + self._slip), d), price), 0.99), order_type=self._otype) else: r = self.client.place_market_order( token_id=token_id, side="SELL", shares=sz, - min_price=min(max(round(price * (1 - self._slip), 4), - 0.0001), price), + min_price=min(max(round(price * (1 - self._slip), d), + 10 ** -d), price), order_type=self._otype) except Exception as e: # NEVER raise into the trade loop — # but a timed-out post may still be resting: sweep + measure first