From 0cf163abe52df16682bef2f2095f857d876b637f Mon Sep 17 00:00:00 2001 From: jaxperro Date: Fri, 10 Jul 2026 01:06:03 -0400 Subject: [PATCH] host: flatten_positions.py + probe polls the indexer First live fill (2026-07-10, $5 @ 0.68 UFC 329 main): buy leg FILLED on the unified SDK but the probe's fixed 3s wait beat the data-api indexer, so the sell-back never ran. Probe now polls up to 60s; flatten_positions.py is the standalone sell-everything utility (and the emergency flatten). Co-Authored-By: Claude Fable 5 --- host/flatten_positions.py | 73 +++++++++++++++++++++++++++++++++++++++ host/order_probe_v2.py | 15 +++++--- 2 files changed, 84 insertions(+), 4 deletions(-) create mode 100644 host/flatten_positions.py diff --git a/host/flatten_positions.py b/host/flatten_positions.py new file mode 100644 index 00000000..af4b13e8 --- /dev/null +++ b/host/flatten_positions.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +"""Flatten: market-sell every open position at the SDK-resolved wallet (FAK). + +Ops utility (unified SDK). Born 2026-07-10: the order probe's buy leg filled +but its fixed 3s indexer wait missed the position, skipping the sell-back. +Also the emergency exit if the live book ever needs manual flattening. + + python3 host/flatten_positions.py # sell everything + python3 host/flatten_positions.py # sell one token only +""" +import json +import os +import ssl +import sys +import time +import urllib.request + +_SSL = ssl._create_unverified_context() + + +def get(u): + req = urllib.request.Request(u, headers={"User-Agent": "Mozilla/5.0"}) + return json.load(urllib.request.urlopen(req, timeout=15, context=_SSL)) + + +def main(): + only = sys.argv[1] if len(sys.argv) > 1 else None + from polymarket import SecureClient + client = SecureClient.create(private_key=os.environ["LIVE_PRIVATE_KEY"].strip()) + with client: + wallet = str(client.wallet) + print("wallet:", wallet) + # the data-api indexer lags fills by a few seconds — poll briefly + pos = [] + for _ in range(12): + pos = [p for p in get("https://data-api.polymarket.com/positions" + f"?user={wallet}&sizeThreshold=0&limit=100") + if (p.get("size") or 0) > 0 + and (only is None or str(p.get("asset")) == str(only))] + if pos: + break + time.sleep(5) + if not pos: + print("no open positions" + (f" for token {only}" if only else "")) + return + for p in pos: + tok, sz = str(p["asset"]), float(p["size"]) + print(f"\nSELL {sz} of {p.get('title', tok)[:60]} " + f"(avg in {p.get('avgPrice')})") + try: + r = client.place_market_order(token_id=tok, side="SELL", + shares=sz, order_type="FAK") + except Exception as e: + print(" raised:", type(e).__name__, str(e)[:200]) + continue + if getattr(r, "ok", False): + making = float(r.making_amount or 0) # shares given + taking = float(r.taking_amount or 0) # USD received + px = taking / making if making else 0 + print(f" {r.status}: sold {making} @ ~{px:.4f} → ${taking:.2f} " + f"(order {r.order_id[:18]}…)") + else: + print(f" rejected {getattr(r, 'code', '?')}: " + f"{getattr(r, 'message', r)}") + try: + b = client.get_balance_allowance(asset_type="COLLATERAL") + print(f"\ncollateral after: ${b.balance/1e6:.2f}") + except Exception as e: + print("balance check:", type(e).__name__, str(e)[:120]) + + +if __name__ == "__main__": + main() diff --git a/host/order_probe_v2.py b/host/order_probe_v2.py index 049b9c31..ec5e872e 100644 --- a/host/order_probe_v2.py +++ b/host/order_probe_v2.py @@ -86,10 +86,17 @@ with client: except Exception as e: print("BUY raised:", type(e).__name__, str(e)[:300]) sys.exit(1) - time.sleep(3) - funder = os.environ["LIVE_FUNDER_ADDRESS"].strip() - pos = get(f"https://data-api.polymarket.com/positions?user={funder}&sizeThreshold=0&limit=50") - mine = [p for p in pos if str(p.get("asset")) == str(tok)] + # the data-api indexer lags fills by seconds — poll, don't one-shot (the + # 2026-07-10 first fill was missed by a fixed 3s wait, skipping the sell) + dw = str(client.wallet) + mine = [] + for _ in range(12): + time.sleep(5) + pos = get(f"https://data-api.polymarket.com/positions?user={dw}&sizeThreshold=0&limit=50") + mine = [p for p in pos if str(p.get("asset")) == str(tok) + and (p.get("size") or 0) > 0] + if mine: + break print("\nposition after buy:", [{ "size": p.get("size"), "avg": p.get("avgPrice")} for p in mine]) if mine and (mine[0].get("size") or 0) > 0: sh = mine[0]["size"]