INCIDENT FIX: in-play 'delayed' acceptances were logged as misses and filled untracked

2026-07-10 09:10-13:14Z: six $5 in-play esports copies returned AcceptedOrder
status 'delayed' with zero matched; the executor read matched==0 as a
rejection, recorded honest misses, and forgot the orders — which then filled
at the exchange minutes later. Book went blind (6 untracked fills, ~$30 real
spend vs $5 booked), caps stopped binding, exits never mirrored. Net damage
contained by the CLOB's own balance checks: −$2.7 incl fees, two legs won.

Executor invariant now: NO order outlives _order() untracked. Zero-matched
acceptances poll get_order → cancel remainder → measure truth via the
exchange's CONDITIONAL balance diff; exception paths sweep open orders on
the token the same way. 5 stub-client unit paths pass.

State surgery (machine disarmed first): cash reset to chain ($10.03), six
fills adopted as bets (4 settled −$4.38 w/ fees, Aurora G2 open+won), spend
tracker set to the real $35/day, false miss rows purged, one documented
-0.59 adjustment (bank-vs-deposit + probe).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
jaxperro
2026-07-10 11:25:02 -04:00
parent 9f1f6d985e
commit 56fe0dee92
3 changed files with 5845 additions and 5725 deletions
+66 -3
View File
@@ -278,9 +278,60 @@ class LedgerLiveExecutor:
self._otype = ot if ot in ("FAK", "FOK") else "FAK"
self._slip = float(live.get("max_slippage_pct", 0.05))
def _shares_held(self, token_id):
"""Exchange-view share balance for a token — chain truth, the arbiter
when an order's fate is ambiguous."""
b = self.client.get_balance_allowance(asset_type="CONDITIONAL",
token_id=str(token_id))
return b.balance / 1e6
def _settle_uncertain(self, token_id, side, bal0, price, order_id=None,
deadline_s=20):
"""An order may be resting/held at the exchange (in-play 'delayed'
acceptance, or an exception after posting). Poll it to a terminal
state, cancel whatever remains, and return (filled, avg_price) from
the exchange's own balance diff. INVARIANT (2026-07-10 incident: six
in-play acceptances were logged as misses and filled untracked
minutes later): no order outlives this call untracked."""
import time
px = price
deadline = time.time() + deadline_s
while order_id and time.time() < deadline:
time.sleep(2)
try:
o = self.client.get_order(order_id=order_id)
if float(o.size_matched or 0) > 0:
px = float(o.price or price)
if o.status not in ("live", "delayed"):
break
except Exception: # gone from the open view — terminal
break
try:
if order_id:
self.client.cancel_order(order_id=order_id)
else: # exception path: sweep the whole token
ids = [o.id for o in self.client.list_open_orders(
token_id=str(token_id))]
if ids:
self.client.cancel_orders(order_ids=ids)
except Exception:
pass # cancel of a just-matched order — fine
try:
bal1 = self._shares_held(token_id)
except Exception:
return 0.0, price # can't read truth: claim nothing; the
# chain_cash_gap alarm catches the rest
filled = (bal1 - bal0) if side == "BUY" else (bal0 - bal1)
return max(filled, 0.0), px
def _order(self, token_id, shares, price, side):
import math
sz = math.floor(shares * 100) / 100.0 # cost never exceeds the gated stake
try:
bal0 = self._shares_held(token_id)
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}
try:
if side == "BUY":
r = self.client.place_market_order(
@@ -293,7 +344,13 @@ class LedgerLiveExecutor:
token_id=token_id, side="SELL", shares=sz,
min_price=max(round(price * (1 - self._slip), 2), 0.01),
order_type=self._otype)
except Exception as e: # NEVER raise into the trade loop
except Exception as e: # NEVER raise into the trade loop
# but a timed-out post may still be resting: sweep + measure first
filled, px = self._settle_uncertain(token_id, side, bal0, price)
if filled > 0:
return {"ok": True, "filled_shares": filled, "price": px,
"resp": f"filled despite client error: {e}",
"paper": False}
return {"ok": False, "filled_shares": 0.0, "price": price,
"resp": f"exception: {e}", "paper": False}
if not getattr(r, "ok", False): # RejectedOrder: typed code + message
@@ -303,8 +360,14 @@ class LedgerLiveExecutor:
making = float(r.making_amount or 0) # what we gave (matched)
taking = float(r.taking_amount or 0) # what we got (matched)
filled, usd = (taking, making) if side == "BUY" else (making, taking)
return {"ok": filled > 0, "filled_shares": filled,
"price": usd / filled if filled else price,
px = usd / filled if filled else price
if filled <= 0:
# ACCEPTED with zero matched = in-play 'delayed'/'live' hold, NOT
# a rejection (the 2026-07-10 lesson). Wait it out briefly, then
# cancel-and-measure so the ledger always matches the exchange.
filled, px = self._settle_uncertain(token_id, side, bal0, price,
order_id=r.order_id)
return {"ok": filled > 0, "filled_shares": filled, "price": px,
"resp": {"order_id": r.order_id, "status": r.status,
"making": making, "taking": taking,
"trades": len(r.trade_ids)},
+6
View File
@@ -1 +1,7 @@
{"ts": 1783665419.1, "wallet": "0xbadaf319415c17f28824a43ae0cd912b9d84d874", "token": "72380260523779452108747496691590035418744958761813904753644512394906005337193", "name": "0xbadaf319", "outcome": "No", "title": "Will LAB reach $24 before 2027?", "detect_lag_s": 268.1, "their_price": 0.95, "my_price": 0.95, "slippage_pct": -0.0, "shares": 5.26, "cost": 5.0, "fee": 0.0075, "mode": "live", "book": {"bb": 0.91, "ba": 0.95, "spread": 0.04, "bid5c": 58.89, "ask5c": 40.98}}
{"ts": 1783674610, "wallet": "0x1d0034ac09da54ba7ec969d1eb756b0a1df475c5", "token": "1161663761026248928741317236361093152095776065309269380062540556077381245572", "name": "AIcAIc", "outcome": "G2 Esports", "title": "LoL: G2 Esports vs LYON - Game 2 Winner", "detect_lag_s": 6.0, "their_price": 0.64, "my_price": 0.64, "slippage_pct": 0.0, "shares": 7.8125, "cost": 5.0, "fee": 0.0844, "mode": "live", "note": "reconstructed from chain \u2014 delayed-fill incident"}
{"ts": 1783675741, "wallet": "", "token": "reconstructed-1783675741", "name": "(incident)", "outcome": "Rune Eaters", "title": "Dota 2: Rune Eaters vs GamerLegion - Game 1 Winner", "detect_lag_s": 6.0, "their_price": 0.4, "my_price": 0.4, "slippage_pct": 0.0, "shares": 12.5, "cost": 5.0, "fee": 0.15, "mode": "live", "note": "reconstructed from chain \u2014 delayed-fill incident"}
{"ts": 1783678196, "wallet": "0x1d0034ac09da54ba7ec969d1eb756b0a1df475c5", "token": "47405794293316148244590035888323531202770006061275899647071095856237793879763", "name": "AIcAIc", "outcome": "G2 Esports", "title": "LoL: G2 Esports vs LYON - Game 3 Winner", "detect_lag_s": 6.0, "their_price": 0.39, "my_price": 0.39, "slippage_pct": 0.0, "shares": 12.8205, "cost": 5.0, "fee": 0.15, "mode": "live", "note": "reconstructed from chain \u2014 delayed-fill incident"}
{"ts": 1783685626, "wallet": "", "token": "reconstructed-1783685626", "name": "(incident)", "outcome": "PlayTime", "title": "Dota 2: Aurora vs PlayTime - Game 1 Winner", "detect_lag_s": 6.0, "their_price": 0.45, "my_price": 0.45, "slippage_pct": 0.0, "shares": 11.1111, "cost": 5.0, "fee": 0.15, "mode": "live", "note": "reconstructed from chain \u2014 delayed-fill incident"}
{"ts": 1783686500, "wallet": "", "token": "reconstructed-1783686500", "name": "(incident)", "outcome": "Aurora", "title": "Dota 2: Aurora vs PlayTime - Game 1 Winner", "detect_lag_s": 6.0, "their_price": 0.57, "my_price": 0.57, "slippage_pct": 0.0, "shares": 8.7719, "cost": 5.0, "fee": 0.1132, "mode": "live", "note": "reconstructed from chain \u2014 delayed-fill incident"}
{"ts": 1783689280, "wallet": "0xd9fa6d54cf294343ee42bbb735b7cff20a4bf0b9", "token": "49886282303318964434544606851164664333698127985256361511182784657423645654000", "name": "gkmgkldfmg", "outcome": "Aurora", "title": "Dota 2: Aurora vs PlayTime - Game 2 Winner", "detect_lag_s": 6.0, "their_price": 0.69, "my_price": 0.69, "slippage_pct": 0.0, "shares": 7.2464, "cost": 5.0, "fee": 0.0674, "mode": "live", "note": "reconstructed from chain \u2014 delayed-fill incident"}
+5773 -5722
View File
File diff suppressed because it is too large Load Diff