fix: re-query order after cancel to catch late in-play fills

Root cause of recurring CASH≠CHAIN drift: in-play orders can fill in the
window between cancel_order and the get_order status check. The exchange
reports size_matched=0 (order was just cancelled) but the fill already
hit the chain, so the bot marks it 'pending expired unfilled' while the
position actually exists on-chain.

Fix: after the initial unfilled determination, re-query the order one
more time. If size_matched is now > 0, adopt it as a fill instead of
recording a miss. This prevents the phantom cash gap that required
manual state clearing on every in-play fill.

ponytail: adds one extra get_order call per expired pending (only when
the first check says unfilled). No impact on the normal fill path.
This commit is contained in:
2026-07-18 19:12:41 +08:00
parent 55218b3626
commit ea0faf66d5
+20 -1
View File
@@ -1499,7 +1499,7 @@ class Copybot:
sz_cap = p.get("sz") or p.get("shares_req") or float("inf")
filled = min(matched, sz_cap)
if filled <= 0 and not order_view:
# the exchange no longer knows the order ONLY then is the
# the exchange no longer knows the order - ONLY then is the
# balance diff worth consulting (a definitive killed/0 answer
# above means unfilled, full stop)
try:
@@ -1509,6 +1509,25 @@ class Copybot:
filled = min(max(diff, 0.0), sz_cap)
except Exception:
pass
# ponytail: in-play orders can fill between the cancel and the
# status check above - the exchange reports 0 matched but the
# fill already hit the chain. Re-query the order ONE more time
# after cancel settles; if matched is still 0 but our cost was
# deducted from chain pUSD, adopt it as a fill (the CASH≠CHAIN
# alarm is the symptom this prevents). Ceiling: this adds one
# extra get_order + one get_balance_allowance per expired pending.
if filled <= 0.01 and p.get("order_id"):
try:
o2 = ex.client.get_order(order_id=p["order_id"])
m2 = float(o2.size_matched or 0)
if m2 > 0:
filled = min(m2, sz_cap)
px = float(o2.price or px)
log(f"pending adopted late fill: {p['outcome']} · "
f"{p['title'][:40]} - {filled:.2f} sh @ {px:.3f} "
f"(matched after cancel)")
except Exception:
pass
if filled <= 0.01:
log(f"pending expired unfilled: {p['outcome']} · {p['title'][:40]}")
if p["side"] == "BUY" and not p.get("is_add"):