Download quad_autopilot.py from 40Hz/kronos-data: direct link, hf CLI and curl.
- Browser
- Download file 8.59 kB
-
https://huggingface.co/40Hz/kronos-data/resolve/main/quad_autopilot.py
- Command line
-
hf download hf://40Hz/kronos-data/quad_autopilot.py
-
curl -L -o quad_autopilot.py https://huggingface.co/40Hz/kronos-data/resolve/main/quad_autopilot.py
8.59 kB
| """quad_autopilot.py - paper autopilot for the PolyMarket crypto Up/Down quad. | |
| Targets the 15-minute "Up or Down" markets for BTC / ETH / SOL / XRP. | |
| Validated edge (fresh 1000-bar OOS, after 3c spread): | |
| RSI(14) < 30 -> BUY YES(up), resolved at close[t+3]: hit 0.788, ROI +0.258 | |
| (5-min horizon had no edge: hit 0.515, ROI -0.015 -> skipped) | |
| Per tick (every 5 min via cron): | |
| 1. Binance 5m candles for each coin | |
| 2. RSI(14) at last closed candle; RSI < 30 -> oversold candidate | |
| 3. Find the live accepting 15-min Up/Down market for that coin (gamma-api) | |
| 4. Optional Kronos veto (--kronos): skip if P(up) < 0.10 | |
| 5. Log paper BUY_YES + projected resolve to the ledger CSV; push ledger to HF bucket | |
| PAPER ONLY: no real orders. Ledger rows are marked mode=paper. | |
| """ | |
| import argparse | |
| import csv | |
| import json | |
| import os | |
| import subprocess | |
| import sys | |
| import urllib.request | |
| from datetime import datetime, timezone | |
| COINS = ["BTC", "ETH", "SOL", "XRP"] | |
| # Tradeable edge verified 2026-08-09 on 21 days x 4 coins (6048x5m bars): | |
| # only ETH, 15-min products, RSI(14)<30, buy YES-up (+15m), breakeven 0.53. | |
| # hit 0.609 (n=64), edge +0.079, walk-forward OOS 0.680. BTC/SOL/XRP = coin-flip. | |
| # EDGE_COINS controls which coins may fire BUY_YES; "" = all (research mode). | |
| EDGE_COINS = os.environ.get("EDGE_COINS", "ETH").split(",") | |
| EDGE_THRESHOLD = float(os.environ.get("EDGE_THRESHOLD", "30")) | |
| BINANCE_HOSTS = os.environ.get("BINANCE_HOSTS", "data-api.binance.vision,api.binance.com").split(",") | |
| BINANCE_TMPL = "https://{}/api/v3/klines?symbol={}USDT&interval=5m&limit=1200" | |
| GAMMA = "https://gamma-api.polymarket.com/markets?limit=100&closed=false&order=updatedAt&ascending=false&offset=0" | |
| LEDGER = os.environ.get("LEDGER_PATH", "/home/neo/quad_ledger.csv") | |
| HF_REPO = "40Hz/kronos-data" | |
| HF_CMD = os.environ.get("HF_CMD", "hf") | |
| FIELDS = ["ts", "mode", "coin", "market_id", "question", "rsi14", "side", | |
| "action", "yes_price", "resolve_ts", "kronos_prob_up", "kronos_veto", "note"] | |
| def fetch_closes(symbol): | |
| for host in BINANCE_HOSTS: | |
| host = host.strip() | |
| url = BINANCE_TMPL.format(host, symbol) | |
| try: | |
| with urllib.request.urlopen(url, timeout=15) as r: | |
| arr = json.load(r) | |
| opens = [float(x[1]) for x in arr] | |
| closes = [float(x[4]) for x in arr] | |
| ts = [int(x[0]) for x in arr] | |
| return opens, closes, ts | |
| except Exception as e: | |
| print(f"[{symbol}] {host} failed ({e}); trying next host") | |
| continue | |
| raise RuntimeError("all binance hosts failed") | |
| def agg_bars(opens, closes, ts, bucket_min=15): | |
| """Aggregate 5m bars to bucket_min closes (floor to bucket; last close wins).""" | |
| buckets = {} | |
| for i in range(len(ts)): | |
| b = i // (bucket_min // 5) | |
| buckets[b] = closes[i] | |
| return [buckets[k] for k in sorted(buckets)] | |
| def rsi(closes, period=14): | |
| d = [closes[i] - closes[i - 1] for i in range(1, len(closes))] | |
| gains = [x if x > 0 else 0.0 for x in d] | |
| losses = [-x if x < 0 else 0.0 for x in d] | |
| ag = sum(gains[:period]) / period | |
| al = sum(losses[:period]) / period | |
| out = [] | |
| for i in range(len(d)): | |
| if i >= period: | |
| ag = (ag * (period - 1) + gains[i]) / period | |
| al = (al * (period - 1) + losses[i]) / period | |
| out.append(100 if al == 0 else 100 - 100 / (1 + ag / al)) | |
| return out | |
| def now_iso(): | |
| return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") | |
| def find_updown_markets(): | |
| """Return list of (market) for 'Up or Down' markets still accepting, grouped by coin.""" | |
| req = urllib.request.Request(GAMMA, headers={"User-Agent": "quad-lab/0.1"}) | |
| with urllib.request.urlopen(req, timeout=60) as r: | |
| markets = json.load(r) | |
| out = [] | |
| for m in markets: | |
| q = m.get("question") or "" | |
| if "up or down" not in q.lower(): | |
| continue | |
| if not m.get("acceptingOrders"): | |
| continue | |
| out.append(m) | |
| return out | |
| def pick_market(markets, coin, max_mins=20): | |
| """Choose the coin's 15-min Up/Down market that is still accepting and closes soonest.""" | |
| now = datetime.now(timezone.utc) | |
| best = None | |
| for m in markets: | |
| q = m.get("question") or "" | |
| if not q.lower().startswith(coin + " up or down"): | |
| continue | |
| try: | |
| end = datetime.fromisoformat(m["endDate"].replace("Z", "+00:00")) | |
| except Exception: | |
| continue | |
| mins = (end - now).total_seconds() / 60 | |
| if mins <= 0 or mins > max_mins: | |
| continue | |
| if best is None or mins < best[0]: | |
| best = (mins, m) | |
| return best | |
| def kronos_veto(coin, prob_cache=None): | |
| """Optional GPU veto: skip if Kronos strongly DOWN (P(up) < 0.1). | |
| Returns (prob_up, veto). If unavailable -> (None, False).""" | |
| if prob_cache is None: | |
| return None, False | |
| prob = prob_cache.get(coin) | |
| if prob is None: | |
| return None, False | |
| return prob, prob < 0.10 | |
| def load_ledger(): | |
| rows = [] | |
| if os.path.exists(LEDGER): | |
| with open(LEDGER) as f: | |
| rd = csv.DictReader(f) | |
| rows = [r for r in rd] | |
| return rows | |
| def append_ledger(row): | |
| exists = os.path.exists(LEDGER) | |
| with open(LEDGER, "a", newline="") as f: | |
| w = csv.DictWriter(f, fieldnames=FIELDS) | |
| if not exists: | |
| w.writeheader() | |
| w.writerow({k: row.get(k, "") for k in FIELDS}) | |
| def push_ledger(): | |
| try: | |
| subprocess.run([HF_CMD, "upload", HF_REPO, LEDGER, "--commit-message", | |
| "quad autopilot ledger " + now_iso()], check=True, | |
| capture_output=True, timeout=120) | |
| print(" ledger pushed to", HF_REPO) | |
| except Exception as e: | |
| print(" ledger push skipped:", e) | |
| def main(): | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--kronos", action="store_true", help="enable Kronos veto (calls GPU Space)") | |
| ap.add_argument("--push", action="store_true", help="push ledger to HF bucket") | |
| ap.add_argument("--coin", choices=COINS + ["ALL"], default="ALL") | |
| args = ap.parse_args() | |
| coins = COINS if args.coin == "ALL" else [args.coin] | |
| markets = [] | |
| if not args.kronos: | |
| try: | |
| markets = find_updown_markets() | |
| except Exception as e: | |
| print("market scan skipped:", e) | |
| prob_cache = {} # filled only when --kronos implemented | |
| lines = [] | |
| for coin in coins: | |
| try: | |
| opens, closes, ts = fetch_closes(coin) | |
| except Exception as e: | |
| print(f"[{coin}] fetch fail: {e}") | |
| continue | |
| rs5 = rsi(closes) | |
| rs15 = rsi(agg_bars(opens, closes, ts, 15)) | |
| last_r5 = rs5[-1] | |
| last_r15 = rs15[-1] | |
| price = closes[-1] | |
| # Edge verified on 15-min RSI. Require the coin to be edge-enabled. | |
| edge_ok = coin in [c.strip().upper() for c in EDGE_COINS] | |
| trigger = last_r15 < EDGE_THRESHOLD and edge_ok | |
| prob, veto = kronos_veto(coin, prob_cache if args.kronos else None) | |
| market_id = question = yes_price = resolve_ts = "" | |
| if trigger and not veto: | |
| if markets: | |
| hit = pick_market(markets, coin) | |
| if hit: | |
| mins, m = hit | |
| market_id = m["id"] | |
| question = m["question"] | |
| yes_price = (m.get("outcomePrices") or "?")[:60] | |
| resolve_ts = m["endDate"] | |
| action = "BUY_YES_15M_UP" if market_id else "BUY_YES_15M_UP_NO_MARKET" | |
| elif trigger and veto: | |
| action = "VETO" | |
| else: | |
| action = "NO_TRADE" | |
| note = (f"price={price:.2f} rsi15={last_r15:.1f} rsi5={last_r5:.1f} " | |
| f"trig={trigger} edge_ok={edge_ok} " + | |
| (f"kronos={prob:.3f} veto={veto} " if prob is not None else "") + | |
| ("market=" + market_id if market_id else "")) | |
| row = {"ts": now_iso(), "mode": "paper", "coin": coin, "market_id": market_id, | |
| "question": question, "rsi14": round(last_r15, 2), "side": "YES", | |
| "action": action, "yes_price": yes_price, "resolve_ts": resolve_ts, | |
| "kronos_prob_up": prob, "kronos_veto": bool(veto), "note": note} | |
| append_ledger(row) | |
| lines.append(f"[{coin}] rsi15={last_r15:.1f} rsi5={last_r5:.1f} " | |
| f"price={price:.2f} -> {action} {note}") | |
| print(lines[-1]) | |
| if args.push: | |
| push_ledger() | |
| print("done", now_iso()) | |
| if __name__ == "__main__": | |
| main() | |