Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -23,13 +23,13 @@ COST = 2 * (COMMISSION + SLIPPAGE)
|
|
| 23 |
REGIME_NAMES = ["TREND", "MEAN_REVERSION", "COMPRESSION"]
|
| 24 |
HMM_K = 3
|
| 25 |
TRAIN_FRAC, PURGE, EMBARGO = 0.60, 5, 3
|
| 26 |
-
MIN_HMM, MIN_SIG =
|
| 27 |
LEV_PRE = ("3L", "3S", "5L", "5S", "BULL", "BEAR", "UP", "DOWN")
|
| 28 |
LEV_SUF = ("3LUSDT", "3SUSDT", "5LUSDT", "5SUSDT", "BULLUSDT", "BEARUSDT", "UPUSDT", "DOWNUSDT")
|
| 29 |
FEAT_COLS = ["ret", "atr_norm", "ema_diff_norm", "efficiency", "rsi_dev", "dist_ma"]
|
| 30 |
|
| 31 |
-
# OKX timeframe mapping: UI -> OKX bar string
|
| 32 |
TF_MAP = {"15m": "15m", "1h": "1H", "4h": "4H", "1d": "1D"}
|
|
|
|
| 33 |
|
| 34 |
|
| 35 |
# ---------- Helpers ----------
|
|
@@ -94,7 +94,6 @@ def _req(path, params, timeout=TIMEOUT):
|
|
| 94 |
|
| 95 |
def fetch_pairs(n=30):
|
| 96 |
"""Fetch USDT spot pairs ranked by 24h quote volume on OKX."""
|
| 97 |
-
# 1) Instruments: get tradable SPOT USDT pairs
|
| 98 |
inst = _req("/api/v5/public/instruments", {"instType": "SPOT"})
|
| 99 |
if not inst:
|
| 100 |
print("[RCEA] instruments unavailable", flush=True)
|
|
@@ -117,7 +116,6 @@ def fetch_pairs(n=30):
|
|
| 117 |
print("[RCEA] no USDT spot pairs found", flush=True)
|
| 118 |
return []
|
| 119 |
|
| 120 |
-
# 2) Tickers: rank by 24h quote volume (volCcy24h)
|
| 121 |
tick = _req("/api/v5/market/tickers", {"instType": "SPOT"})
|
| 122 |
if not tick:
|
| 123 |
print("[RCEA] tickers unavailable", flush=True)
|
|
@@ -137,29 +135,58 @@ def fetch_pairs(n=30):
|
|
| 137 |
return out
|
| 138 |
|
| 139 |
|
| 140 |
-
def fetch_klines(inst_id, timeframe,
|
| 141 |
-
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
| 142 |
bar = TF_MAP[timeframe]
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 146 |
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
if
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
if None in (o, h, l, c, v) or min(o, h, l, c) <= 0:
|
| 156 |
-
continue
|
| 157 |
-
rows.append({"open": o, "high": h, "low": l, "close": c, "volume": v})
|
| 158 |
|
| 159 |
-
if len(
|
| 160 |
return None
|
| 161 |
-
|
| 162 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 163 |
|
| 164 |
|
| 165 |
# ---------- Features ----------
|
|
@@ -279,7 +306,13 @@ def backtest(sym, f, rp, acc, pl, ps, thr, mconf, maxhold):
|
|
| 279 |
atr = f["atr"].values
|
| 280 |
trades = []; in_pos = False; ei = eprice = d = sp = tp = 0
|
| 281 |
reg = ""; rconf = 0.0
|
| 282 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 283 |
for i in range(start, n):
|
| 284 |
if not in_pos:
|
| 285 |
p = rp[i]; dom = int(np.argmax(p)); conf = float(p[dom])
|
|
@@ -420,7 +453,8 @@ def run(timeframe, n_coins, thr, lam, mconf, maxhold, progress=gr.Progress()):
|
|
| 420 |
f"**Threshold:** {thr} | **λ:** {lam} | **MinConf:** {mconf} | **MaxHold:** {maxhold}",
|
| 421 |
f"**UTC:** {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S')}"]
|
| 422 |
if len(trades) < 30:
|
| 423 |
-
scan.append(f"\n⚠️ Only {len(trades)} trades — target ≥30.
|
|
|
|
| 424 |
scan_report = "\n".join(scan)
|
| 425 |
|
| 426 |
if signals:
|
|
@@ -498,7 +532,8 @@ with gr.Blocks(title="RCEA Lite v1.0 — OKX") as demo:
|
|
| 498 |
with gr.Tab("🪙 Coins"): coin_tbl = gr.Dataframe(interactive=False)
|
| 499 |
btn.click(run, inputs=[tf, nc, thr_i, lam_i, mc_i, mh_i],
|
| 500 |
outputs=[scan_out, met_out, sig_tbl, tr_tbl, reg_tbl, coin_tbl])
|
| 501 |
-
gr.Markdown("---\n**Data source:** OKX public REST API.
|
|
|
|
| 502 |
|
| 503 |
|
| 504 |
if __name__ == "__main__":
|
|
|
|
| 23 |
REGIME_NAMES = ["TREND", "MEAN_REVERSION", "COMPRESSION"]
|
| 24 |
HMM_K = 3
|
| 25 |
TRAIN_FRAC, PURGE, EMBARGO = 0.60, 5, 3
|
| 26 |
+
MIN_HMM, MIN_SIG = 150, 80
|
| 27 |
LEV_PRE = ("3L", "3S", "5L", "5S", "BULL", "BEAR", "UP", "DOWN")
|
| 28 |
LEV_SUF = ("3LUSDT", "3SUSDT", "5LUSDT", "5SUSDT", "BULLUSDT", "BEARUSDT", "UPUSDT", "DOWNUSDT")
|
| 29 |
FEAT_COLS = ["ret", "atr_norm", "ema_diff_norm", "efficiency", "rsi_dev", "dist_ma"]
|
| 30 |
|
|
|
|
| 31 |
TF_MAP = {"15m": "15m", "1h": "1H", "4h": "4H", "1d": "1D"}
|
| 32 |
+
TARGET_BARS = 1000
|
| 33 |
|
| 34 |
|
| 35 |
# ---------- Helpers ----------
|
|
|
|
| 94 |
|
| 95 |
def fetch_pairs(n=30):
|
| 96 |
"""Fetch USDT spot pairs ranked by 24h quote volume on OKX."""
|
|
|
|
| 97 |
inst = _req("/api/v5/public/instruments", {"instType": "SPOT"})
|
| 98 |
if not inst:
|
| 99 |
print("[RCEA] instruments unavailable", flush=True)
|
|
|
|
| 116 |
print("[RCEA] no USDT spot pairs found", flush=True)
|
| 117 |
return []
|
| 118 |
|
|
|
|
| 119 |
tick = _req("/api/v5/market/tickers", {"instType": "SPOT"})
|
| 120 |
if not tick:
|
| 121 |
print("[RCEA] tickers unavailable", flush=True)
|
|
|
|
| 135 |
return out
|
| 136 |
|
| 137 |
|
| 138 |
+
def fetch_klines(inst_id, timeframe, target_bars=TARGET_BARS):
|
| 139 |
+
"""
|
| 140 |
+
Fetch OHLCV from OKX with pagination.
|
| 141 |
+
OKX returns max 300 bars per request, newest first.
|
| 142 |
+
Uses 'after' = oldest timestamp so far to page back in time.
|
| 143 |
+
"""
|
| 144 |
bar = TF_MAP[timeframe]
|
| 145 |
+
all_rows = []
|
| 146 |
+
after = None
|
| 147 |
+
max_pages = 10
|
| 148 |
+
|
| 149 |
+
for page in range(max_pages):
|
| 150 |
+
params = {"instId": inst_id, "bar": bar, "limit": "300"}
|
| 151 |
+
if after:
|
| 152 |
+
params["after"] = str(after)
|
| 153 |
+
raw = _req("/api/v5/market/candles", params)
|
| 154 |
+
if not raw:
|
| 155 |
+
break
|
| 156 |
+
|
| 157 |
+
got_new = 0
|
| 158 |
+
for k in raw:
|
| 159 |
+
if not isinstance(k, list) or len(k) < 9:
|
| 160 |
+
continue
|
| 161 |
+
if k[8] != "1": # skip unconfirmed
|
| 162 |
+
continue
|
| 163 |
+
o, h, l, c, v = _f(k[1]), _f(k[2]), _f(k[3]), _f(k[4]), _f(k[5])
|
| 164 |
+
if None in (o, h, l, c, v) or min(o, h, l, c) <= 0:
|
| 165 |
+
continue
|
| 166 |
+
all_rows.append({"ts": int(k[0]),
|
| 167 |
+
"open": o, "high": h, "low": l, "close": c, "volume": v})
|
| 168 |
+
got_new += 1
|
| 169 |
+
|
| 170 |
+
if got_new == 0:
|
| 171 |
+
break
|
| 172 |
|
| 173 |
+
try:
|
| 174 |
+
after = int(raw[-1][0])
|
| 175 |
+
except (IndexError, ValueError):
|
| 176 |
+
break
|
| 177 |
+
|
| 178 |
+
if len(all_rows) >= target_bars:
|
| 179 |
+
break
|
| 180 |
+
time.sleep(0.12)
|
|
|
|
|
|
|
|
|
|
| 181 |
|
| 182 |
+
if len(all_rows) < MIN_HMM:
|
| 183 |
return None
|
| 184 |
+
|
| 185 |
+
df = (pd.DataFrame(all_rows)
|
| 186 |
+
.drop_duplicates(subset=["ts"])
|
| 187 |
+
.sort_values("ts")
|
| 188 |
+
.reset_index(drop=True))
|
| 189 |
+
return df[["open", "high", "low", "close", "volume"]]
|
| 190 |
|
| 191 |
|
| 192 |
# ---------- Features ----------
|
|
|
|
| 306 |
atr = f["atr"].values
|
| 307 |
trades = []; in_pos = False; ei = eprice = d = sp = tp = 0
|
| 308 |
reg = ""; rconf = 0.0
|
| 309 |
+
|
| 310 |
+
# Start right after HMM training window + purge + embargo.
|
| 311 |
+
train_end = int(n * TRAIN_FRAC)
|
| 312 |
+
start = min(n - 2, train_end + PURGE + EMBARGO)
|
| 313 |
+
if start < MIN_SIG:
|
| 314 |
+
start = MIN_SIG
|
| 315 |
+
|
| 316 |
for i in range(start, n):
|
| 317 |
if not in_pos:
|
| 318 |
p = rp[i]; dom = int(np.argmax(p)); conf = float(p[dom])
|
|
|
|
| 453 |
f"**Threshold:** {thr} | **λ:** {lam} | **MinConf:** {mconf} | **MaxHold:** {maxhold}",
|
| 454 |
f"**UTC:** {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S')}"]
|
| 455 |
if len(trades) < 30:
|
| 456 |
+
scan.append(f"\n⚠️ Only {len(trades)} trades — target ≥30. "
|
| 457 |
+
f"Try lowering threshold to ~0.10 or increasing coins.")
|
| 458 |
scan_report = "\n".join(scan)
|
| 459 |
|
| 460 |
if signals:
|
|
|
|
| 532 |
with gr.Tab("🪙 Coins"): coin_tbl = gr.Dataframe(interactive=False)
|
| 533 |
btn.click(run, inputs=[tf, nc, thr_i, lam_i, mc_i, mh_i],
|
| 534 |
outputs=[scan_out, met_out, sig_tbl, tr_tbl, reg_tbl, coin_tbl])
|
| 535 |
+
gr.Markdown("---\n**Data source:** OKX public REST API. "
|
| 536 |
+
"Net of 0.10% commission + 0.05% slippage per side.")
|
| 537 |
|
| 538 |
|
| 539 |
if __name__ == "__main__":
|