File size: 5,358 Bytes
d648a0d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 | """autoresearch.py - self-improvement loop for the quad autopilot.
Runs on GitHub Actions (hourly) / local cron. Cloud + local.
Tradeable grid = Polymarket 15-min Up/Down products only (verified live via
gamma-api: only 5-min and 15-min exist; no 1h). Signal = RSI(14) < threshold
on the 15-min close, buy YES-"up", win if close[t+1] > close[t] (+15m),
buy at 0.50 + 0.03 spread => breakeven hit 0.53.
21-day full-data study (2026-08-09, 6048x5m bars/coin) FALSIFIED the old
baseline (hit 0.788 / roi +0.258 - not reproducible on fresh data). Honest
result: only ETH clears the spread-adjusted breakeven (thr=30: hit 0.609,
n=64, edge +0.079, WF OOS 0.680). BTC/SOL/XRP are coin-flips. This script
tracks that grid and flags the signal that still works.
"""
import argparse
import csv
import json
import os
import urllib.request
from datetime import datetime, timezone
COINS = ["BTC", "ETH", "SOL", "XRP"]
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"
REPORT = os.environ.get("REPORT_PATH", "/home/neo/quad_research.csv")
OOS_BARS = int(os.environ.get("OOS_BARS", "200"))
THRESHOLDS = [int(x) for x in os.environ.get("THRESHOLDS", "20,25,30,35").split(",")]
BREAKEVEN = float(os.environ.get("BREAKEVEN", "0.53"))
EDGE_FLOOR = float(os.environ.get("EDGE_FLOOR", "0.03"))
MIN_N = int(os.environ.get("MIN_N", "10"))
FIELDS = ["ts", "coin", "bars", "oos_bars", "thr", "n", "hit", "edge",
"breakeven", "status"]
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)
return [float(x[4]) for x in arr]
except Exception as e:
print(f"[{symbol}] {host} failed ({e}); trying next host")
continue
raise RuntimeError("all binance hosts failed")
def agg15(closes5):
"""Aggregate 5m closes to 15m closes (bucket = floor to 15m; last close wins)."""
buckets = {}
n = len(closes5)
for i in range(n):
buckets[i // 3] = closes5[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 validate(close15, thr, horizon=1, oos_bars=200):
"""OOS-only: last oos_bars 15m closes. Signal on candle t if RSI(t)<thr,
win = close[t+1] > close[t] (+15m). edge = hit - breakeven."""
n = len(close15)
start = max(horizon + 1, n - oos_bars)
rs = rsi(close15)
rows = []
for t in range(start, n - horizon):
if rs[t - 1] < thr:
rows.append(close15[t + horizon] > close15[t])
if not rows:
return 0, None
hit = sum(rows) / len(rows)
return len(rows), hit
def now_iso():
return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
def append_report(row):
exists = os.path.exists(REPORT)
d = os.path.dirname(REPORT)
if d:
os.makedirs(d, exist_ok=True)
with open(REPORT, "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 main():
ap = argparse.ArgumentParser()
ap.add_argument("--coin", choices=COINS + ["ALL"], default="ALL")
ap.add_argument("--thr", type=int, default=None)
args = ap.parse_args()
coins = COINS if args.coin == "ALL" else [args.coin]
thrs = [args.thr] if args.thr else THRESHOLDS
for coin in coins:
try:
closes5 = fetch_closes(coin)
except Exception as e:
print(f"[{coin}] fetch fail: {e}")
continue
c15 = agg15(closes5)
rs = rsi(c15)
current = round(rs[-1], 2) if rs[-1] is not None else None
for thr in thrs:
n, hit = validate(c15, thr, horizon=1, oos_bars=OOS_BARS)
edge = round(hit - BREAKEVEN, 3) if hit is not None else None
if n == 0 or hit is None:
status = "NO_SIGNALS"
elif edge is not None and edge >= EDGE_FLOOR and n >= MIN_N:
status = "EDGE_OK"
elif n < MIN_N:
status = "LOW_N" if edge is not None else "NO_SIGNALS"
else:
status = "NO_EDGE"
row = {"ts": now_iso(), "coin": coin, "bars": len(c15),
"oos_bars": OOS_BARS, "thr": thr, "n": n,
"hit": round(hit, 3) if hit is not None else "",
"edge": edge, "breakeven": BREAKEVEN, "status": status}
append_report(row)
print(f"[{coin}] thr={thr} n={n} hit={hit} edge={edge} "
f"(rsi_now={current}) -> {status}")
print("done", now_iso())
if __name__ == "__main__":
main()
|