Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,985 +1,1068 @@
|
|
| 1 |
-
"""
|
| 2 |
-
Tickscope MCP + ML tahmin uygulaması (Gradio)
|
| 3 |
-
|
| 4 |
-
Kurulum:
|
| 5 |
-
pip install gradio mcp pandas numpy scikit-learn plotly
|
| 6 |
-
# uv gerekli (uvx tickscope-mcp için): pip install uv veya https://docs.astral.sh/uv/
|
| 7 |
-
Çalıştırma:
|
| 8 |
-
python app.py
|
| 9 |
-
|
| 10 |
-
Akış:
|
| 11 |
-
1) uvx tickscope-mcp'yi stdio MCP sunucusu olarak başlatır
|
| 12 |
-
2) get_ohlcv ile mum verisini çeker
|
| 13 |
-
3) compute_indicators / deep_analyze çıktısını (varsa) Tickscope'tan alır
|
| 14 |
-
4) Yerel HistGradientBoosting modeli ile N bar sonrası yön olasılığı + aralık tahmini üretir
|
| 15 |
-
|
| 16 |
-
Not: Eğitim/araştırma amaçlıdır, yatırım tavsiyesi değildir.
|
| 17 |
-
"""
|
| 18 |
|
| 19 |
-
import
|
| 20 |
-
|
| 21 |
-
import math
|
| 22 |
-
import os
|
| 23 |
|
| 24 |
-
import
|
| 25 |
import numpy as np
|
| 26 |
import pandas as pd
|
| 27 |
-
import
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
from sklearn.ensemble import
|
|
|
|
|
|
|
|
|
|
| 31 |
from sklearn.linear_model import LogisticRegression
|
| 32 |
-
from sklearn.
|
| 33 |
-
from sklearn.pipeline import make_pipeline
|
| 34 |
from sklearn.preprocessing import StandardScaler
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
import sys
|
| 41 |
-
|
| 42 |
-
if shutil.which("uvx"):
|
| 43 |
-
return "uvx", ["tickscope-mcp"]
|
| 44 |
-
if shutil.which("tickscope-mcp"): # pip install tickscope-mcp
|
| 45 |
-
return "tickscope-mcp", []
|
| 46 |
-
if importlib.util.find_spec("uv"): # pip install uv -> python -m uv
|
| 47 |
-
return sys.executable, ["-m", "uv", "tool", "run", "tickscope-mcp"]
|
| 48 |
-
raise RuntimeError("Tickscope başlatılamadı. Çalıştır: pip install tickscope-mcp")
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
_CMD, _ARGS = _launcher()
|
| 52 |
-
|
| 53 |
-
SERVER = StdioServerParameters(
|
| 54 |
-
command=_CMD,
|
| 55 |
-
args=_ARGS,
|
| 56 |
-
env={
|
| 57 |
-
**os.environ,
|
| 58 |
-
"TICKSCOPE_EXCHANGES": "binance,bybit,okx",
|
| 59 |
-
"TICKSCOPE_DEFAULT_EXCHANGE": "binance",
|
| 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 |
-
first = await call_tool(session, tools, "get_ohlcv", symbol=symbol,
|
| 119 |
-
timeframe=timeframe, exchange=exchange, limit=page)
|
| 120 |
-
if first is None or (isinstance(first, dict) and "error" in first) or total <= page:
|
| 121 |
-
return first
|
| 122 |
-
props = (tool.inputSchema or {}).get("properties", {})
|
| 123 |
-
if not any(n in props for n in ALIASES["until"]):
|
| 124 |
-
return first # sayfalama desteklenmiyor
|
| 125 |
-
try:
|
| 126 |
-
frames = [to_df(first)]
|
| 127 |
-
except Exception:
|
| 128 |
-
return first
|
| 129 |
-
got = len(frames[0])
|
| 130 |
-
epoch = pd.Timestamp("1970-01-01")
|
| 131 |
-
while got < total:
|
| 132 |
-
oldest = frames[-1]["ts"].iloc[0]
|
| 133 |
-
end_ms = int((oldest - epoch) // pd.Timedelta("1ms")) - 1
|
| 134 |
-
try:
|
| 135 |
-
res = await call_tool(session, tools, "get_ohlcv", symbol=symbol, timeframe=timeframe,
|
| 136 |
-
exchange=exchange, limit=page, until=end_ms)
|
| 137 |
-
except Exception: # noqa: BLE001 (ör. Connection closed) -> eldeki sayfalarla devam
|
| 138 |
-
break
|
| 139 |
-
if res is None or (isinstance(res, dict) and "error" in res):
|
| 140 |
break
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 144 |
break
|
| 145 |
-
|
| 146 |
-
|
|
|
|
|
|
|
| 147 |
break
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 259 |
}
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 267 |
continue
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 271 |
continue
|
| 272 |
-
feats[name] = fn(s)
|
| 273 |
-
if not feats:
|
| 274 |
-
extra["deriv_note"] = ("Geçmiş özellik eklenmedi (ham-satır araçları için Pro/Bot anahtarı gerekir: "
|
| 275 |
-
"LIQUIDVISION_API_KEY). Canlı bağlam ham çıktıda.")
|
| 276 |
-
return None, df, extra
|
| 277 |
-
|
| 278 |
-
# Geçmişin kapsamadığı eski mumları kırp; <800 bar kalıyorsa o seriyi at
|
| 279 |
-
usable = {k: v for k, v in feats.items() if (df["ts"] >= v["ts"].iloc[0]).sum() >= 800}
|
| 280 |
-
if not usable:
|
| 281 |
-
extra["deriv_note"] = "Geçmiş mum verisinin yalnızca küçük kısmını kapsıyor (<800 bar); özellik eklenmedi"
|
| 282 |
-
return None, df, extra
|
| 283 |
-
start = max(v["ts"].iloc[0] for v in usable.values())
|
| 284 |
-
d2 = df[df["ts"] >= start].reset_index(drop=True)
|
| 285 |
-
d2["ts"] = d2["ts"].astype("datetime64[ns]")
|
| 286 |
-
m = d2[["ts"]].copy()
|
| 287 |
-
for v in usable.values():
|
| 288 |
-
m = pd.merge_asof(m, v, on="ts", direction="backward") # lookahead yok
|
| 289 |
-
m.index = d2.index
|
| 290 |
-
extra["deriv_used"] = {"seriler": list(usable), "baslangic": str(start), "bar": len(d2)}
|
| 291 |
-
return m.drop(columns=["ts"]).add_prefix("dv_"), d2, extra
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
# compute_indicators'ın `indicators` parametresi ZORUNLUDUR ("rsi:14", "macd:12,26,9" biçiminde spec listesi).
|
| 295 |
-
# 73 göstergenin adlarını bilmediğimiz için geniş bir aday listesi denenir; geçersiz adlar atlanır.
|
| 296 |
-
IND_NAMES = [
|
| 297 |
-
"sma", "ema", "wma", "hma", "vwma", "dema", "tema", "zlema", "kama", "alma", "rma", "smma", "lsma", "vwap",
|
| 298 |
-
"bb", "bbands", "bollinger", "bbw", "bbp", "kc", "keltner", "donchian", "dc", "envelope", "ichimoku",
|
| 299 |
-
"supertrend", "psar", "sar", "rsi", "stochrsi", "stoch", "macd", "ppo", "roc", "mom", "momentum", "cci",
|
| 300 |
-
"willr", "williams", "ao", "awesome", "ac", "uo", "tsi", "trix", "cmo", "dpo", "kst", "coppock", "fisher",
|
| 301 |
-
"rvi", "mfi", "obv", "cmf", "ad", "adl", "eom", "fi", "force", "vpt", "nvi", "pvi", "kvo", "atr", "natr",
|
| 302 |
-
"tr", "stdev", "hv", "ulcer", "chop", "massindex", "adx", "dmi", "aroon", "vortex", "wavetrend", "wt",
|
| 303 |
-
"squeeze", "ttm", "heikinashi", "ha", "pivot", "zigzag", "linreg", "zscore", "cog", "bop", "qstick",
|
| 304 |
-
"pgo", "smi", "stc", "er", "efi",
|
| 305 |
-
]
|
| 306 |
|
|
|
|
|
|
|
|
|
|
| 307 |
|
| 308 |
-
|
|
|
|
| 309 |
|
|
|
|
|
|
|
| 310 |
|
| 311 |
-
|
| 312 |
-
"""Göstergeleri KÜÇÜK PARÇALAR halinde (her parça birkaç gösterge), ana oturumdan AYRI bir Tickscope sürecinde çeker.
|
| 313 |
-
Bir parça sunucuyu çökertirse ("Connection closed") yeni süreç açılır, o parçanın adları atlanır, kalanlarla devam edilir."""
|
| 314 |
-
out = {"by_indicator": {}, "failed": [], "mode": "chunked", "reconnects": 0, "last_error": None}
|
| 315 |
-
kw = dict(symbol=symbol, timeframe=timeframe, exchange=exchange, limit=limit, include_series=True)
|
| 316 |
-
chunks = [IND_NAMES[i:i + IND_CHUNK] for i in range(0, len(IND_NAMES), IND_CHUNK)]
|
| 317 |
-
i = 0
|
| 318 |
-
while i < len(chunks):
|
| 319 |
-
try:
|
| 320 |
-
async with stdio_client(SERVER) as (r, w):
|
| 321 |
-
async with ClientSession(r, w) as session:
|
| 322 |
-
await session.initialize()
|
| 323 |
-
tools = {t.name: t for t in (await session.list_tools()).tools}
|
| 324 |
-
if "compute_indicators" not in tools:
|
| 325 |
-
return None
|
| 326 |
-
while i < len(chunks):
|
| 327 |
-
names = chunks[i]
|
| 328 |
-
res = await call_tool(session, tools, "compute_indicators", indicators=names, **kw)
|
| 329 |
-
if isinstance(res, dict) and "error" not in res and extract_series(res, 0):
|
| 330 |
-
out["by_indicator"][f"c{i}"] = res
|
| 331 |
-
else: # parçada geçersiz ad var: tek tek dene
|
| 332 |
-
for name in names:
|
| 333 |
-
r1 = await call_tool(session, tools, "compute_indicators", indicators=[name], **kw)
|
| 334 |
-
if r1 is None or (isinstance(r1, dict) and "error" in r1):
|
| 335 |
-
out["failed"].append(name)
|
| 336 |
-
else:
|
| 337 |
-
out["by_indicator"][name] = r1
|
| 338 |
-
i += 1
|
| 339 |
-
except Exception as e: # noqa: BLE001
|
| 340 |
-
out["reconnects"] += 1
|
| 341 |
-
out["last_error"] = _root(e)
|
| 342 |
-
out["failed"] += [n for n in chunks[i] if n not in out["by_indicator"]] # çökertene dokunma, atla
|
| 343 |
-
i += 1
|
| 344 |
-
if out["reconnects"] > 6:
|
| 345 |
-
break
|
| 346 |
-
return out
|
| 347 |
-
|
| 348 |
-
|
| 349 |
-
def _numeric_list(o, min_len):
|
| 350 |
-
return (isinstance(o, list) and len(o) >= min_len and
|
| 351 |
-
all(x is None or (isinstance(x, (int, float)) and not isinstance(x, bool)) for x in o[:50]))
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
def extract_series(payload, n):
|
| 355 |
-
"""compute_indicators çıktısında gezinip bar bazlı sayısal serileri bulur (şemadan bağımsız)."""
|
| 356 |
-
min_len = max(50, int(n * 0.5))
|
| 357 |
-
out = {}
|
| 358 |
-
|
| 359 |
-
def walk(o, path):
|
| 360 |
-
if isinstance(o, list):
|
| 361 |
-
if _numeric_list(o, min_len):
|
| 362 |
-
out[path] = o
|
| 363 |
-
elif len(o) >= min_len and all(isinstance(x, dict) for x in o[:3]):
|
| 364 |
-
for key in o[0]:
|
| 365 |
-
vals = [r.get(key) if isinstance(r, dict) else None for r in o]
|
| 366 |
-
if _numeric_list(vals, min_len) and key.lower() not in ("ts", "time", "timestamp", "t"):
|
| 367 |
-
out[f"{path}.{key}"] = vals
|
| 368 |
-
elif isinstance(o, dict):
|
| 369 |
-
for k, v in o.items():
|
| 370 |
-
walk(v, f"{path}.{k}" if path else str(k))
|
| 371 |
-
|
| 372 |
-
walk(payload, "")
|
| 373 |
-
return out
|
| 374 |
-
|
| 375 |
-
|
| 376 |
-
def indicator_features(ind, df: pd.DataFrame):
|
| 377 |
-
"""Tickscope'un 73 göstergesinin zaman serilerini (varsa) df'e hizalı özellik tablosuna çevirir.
|
| 378 |
-
Fiyat seviyesi gibi duran (SMA/EMA/bant vb.) seriler kapanışa göre normalize edilir."""
|
| 379 |
-
if not isinstance(ind, dict) or "error" in ind:
|
| 380 |
-
return None
|
| 381 |
-
n = len(df)
|
| 382 |
-
close = df["close"].values
|
| 383 |
-
cols = {}
|
| 384 |
-
for path, vals in extract_series(ind, n).items():
|
| 385 |
-
s = pd.to_numeric(pd.Series(vals), errors="coerce").values.astype(float)
|
| 386 |
-
arr = np.full(n, np.nan)
|
| 387 |
-
m = min(len(s), n)
|
| 388 |
-
arr[-m:] = s[-m:] # seriler sondan hizalanır
|
| 389 |
-
if np.isnan(arr).all() or np.nanstd(arr) == 0:
|
| 390 |
continue
|
| 391 |
-
ref = np.nanmedian(close[-m:])
|
| 392 |
-
if ref and abs(np.nanmedian(arr) / ref - 1) < 0.5: # fiyat seviyesi -> göreli mesafe
|
| 393 |
-
arr = arr / close - 1
|
| 394 |
-
name = "ts_" + "".join(ch if ch.isalnum() else "_" for ch in path).strip("_")
|
| 395 |
-
cols[name] = arr
|
| 396 |
-
if not cols:
|
| 397 |
-
return None
|
| 398 |
-
return pd.DataFrame(cols, index=df.index)
|
| 399 |
-
|
| 400 |
-
|
| 401 |
-
def _strip_series(o, keep=3):
|
| 402 |
-
"""Ham çıktıyı okunur tutmak için uzun serileri son `keep` değere kısaltır."""
|
| 403 |
-
if isinstance(o, list) and len(o) > 20:
|
| 404 |
-
return [f"... {len(o)} değer, son {keep}:"] + o[-keep:]
|
| 405 |
-
if isinstance(o, list):
|
| 406 |
-
return [_strip_series(x, keep) for x in o]
|
| 407 |
-
if isinstance(o, dict):
|
| 408 |
-
return {k: _strip_series(v, keep) for k, v in o.items()}
|
| 409 |
-
return o
|
| 410 |
-
|
| 411 |
-
|
| 412 |
-
def to_df(payload) -> pd.DataFrame:
|
| 413 |
-
rows = payload
|
| 414 |
-
if isinstance(payload, dict):
|
| 415 |
-
for k in ("candles", "ohlcv", "data", "rows", "result"):
|
| 416 |
-
if k in payload:
|
| 417 |
-
rows = payload[k]
|
| 418 |
-
break
|
| 419 |
-
if not isinstance(rows, list) or not rows:
|
| 420 |
-
raise ValueError(f"OHLCV verisi okunamadı: {str(payload)[:300]}")
|
| 421 |
-
|
| 422 |
-
if isinstance(rows[0], (list, tuple)):
|
| 423 |
-
cols = ["ts", "open", "high", "low", "close", "volume"][: len(rows[0])]
|
| 424 |
-
df = pd.DataFrame(rows, columns=cols)
|
| 425 |
-
else:
|
| 426 |
-
df = pd.DataFrame(rows)
|
| 427 |
-
df = df.rename(columns={"timestamp": "ts", "time": "ts", "t": "ts", "date": "ts",
|
| 428 |
-
"o": "open", "h": "high", "l": "low", "c": "close", "v": "volume"})
|
| 429 |
-
for c in ("open", "high", "low", "close", "volume"):
|
| 430 |
-
df[c] = pd.to_numeric(df[c], errors="coerce")
|
| 431 |
-
if pd.api.types.is_numeric_dtype(df["ts"]):
|
| 432 |
-
unit = "ms" if df["ts"].iloc[-1] > 1e11 else "s"
|
| 433 |
-
df["ts"] = pd.to_datetime(df["ts"], unit=unit)
|
| 434 |
-
else:
|
| 435 |
-
df["ts"] = pd.to_datetime(df["ts"])
|
| 436 |
-
return df.dropna(subset=["close"]).sort_values("ts").reset_index(drop=True)
|
| 437 |
-
|
| 438 |
-
|
| 439 |
-
# ----------------------------------------------------------------- ML
|
| 440 |
-
def make_features(df: pd.DataFrame):
|
| 441 |
-
c, h, l, o, v = df["close"], df["high"], df["low"], df["open"], df["volume"]
|
| 442 |
-
f = pd.DataFrame(index=df.index)
|
| 443 |
-
lr = np.log(c).diff()
|
| 444 |
-
|
| 445 |
-
# Getiri / momentum (çok ölçekli)
|
| 446 |
-
for n in (1, 2, 3, 6, 12, 24, 48, 96, 192):
|
| 447 |
-
f[f"ret{n}"] = c.pct_change(n)
|
| 448 |
-
|
| 449 |
-
# Çoklu zaman dilimi (lookahead'siz yuvarlanan pencereler): ~4x ve ~16x üst TF benzeri
|
| 450 |
-
dd = c.diff()
|
| 451 |
-
for k in (4, 16):
|
| 452 |
-
up_k = dd.clip(lower=0).ewm(alpha=1 / (14 * k), adjust=False).mean()
|
| 453 |
-
dn_k = (-dd.clip(upper=0)).ewm(alpha=1 / (14 * k), adjust=False).mean()
|
| 454 |
-
f[f"rsi_htf{k}"] = 100 - 100 / (1 + up_k / (dn_k + 1e-12))
|
| 455 |
-
f[f"ema_dist_htf{k}"] = c / c.ewm(span=20 * k, adjust=False).mean() - 1
|
| 456 |
-
f[f"chan_pos{k}"] = (c - l.rolling(20 * k).min()) / (h.rolling(20 * k).max() - l.rolling(20 * k).min() + 1e-12)
|
| 457 |
-
f[f"vol_htf{k}"] = lr.rolling(20 * k).std()
|
| 458 |
-
|
| 459 |
-
# Volatilite rejimi
|
| 460 |
-
for n in (6, 24, 96):
|
| 461 |
-
f[f"vol{n}"] = lr.rolling(n).std()
|
| 462 |
-
f["vol_ratio"] = f["vol6"] / (f["vol96"] + 1e-12)
|
| 463 |
-
f["vol_ratio2"] = f["vol24"] / (f["vol96"] + 1e-12)
|
| 464 |
-
f["ret24_z"] = f["ret24"] / (f["vol24"] * np.sqrt(24) + 1e-12) # volatilite-normalize momentum
|
| 465 |
-
f["ret6_z"] = f["ret6"] / (f["vol24"] * np.sqrt(6) + 1e-12)
|
| 466 |
-
|
| 467 |
-
# RSI (iki periyot)
|
| 468 |
-
d = c.diff()
|
| 469 |
-
for n in (7, 14):
|
| 470 |
-
up = d.clip(lower=0).ewm(alpha=1 / n, adjust=False).mean()
|
| 471 |
-
dn = (-d.clip(upper=0)).ewm(alpha=1 / n, adjust=False).mean()
|
| 472 |
-
f[f"rsi{n}"] = 100 - 100 / (1 + up / (dn + 1e-12))
|
| 473 |
-
|
| 474 |
-
macd = c.ewm(span=12, adjust=False).mean() - c.ewm(span=26, adjust=False).mean()
|
| 475 |
-
f["macd_h"] = (macd - macd.ewm(span=9, adjust=False).mean()) / c
|
| 476 |
-
for n in (20, 50, 200):
|
| 477 |
-
f[f"ema_dist{n}"] = c / c.ewm(span=n, adjust=False).mean() - 1
|
| 478 |
-
f["ema_slope"] = c.ewm(span=20, adjust=False).mean().pct_change(5)
|
| 479 |
-
|
| 480 |
-
tr = pd.concat([h - l, (h - c.shift()).abs(), (l - c.shift()).abs()], axis=1).max(axis=1)
|
| 481 |
-
atr = tr.ewm(alpha=1 / 14, adjust=False).mean()
|
| 482 |
-
f["atr_pct"] = atr / c
|
| 483 |
-
f["hl_range"] = (h - l) / c
|
| 484 |
-
|
| 485 |
-
# ADX (trend gücü)
|
| 486 |
-
upm, dnm = h.diff(), -l.diff()
|
| 487 |
-
pdm = ((upm > dnm) & (upm > 0)) * upm
|
| 488 |
-
mdm = ((dnm > upm) & (dnm > 0)) * dnm
|
| 489 |
-
pdi = 100 * pdm.ewm(alpha=1 / 14, adjust=False).mean() / (atr + 1e-12)
|
| 490 |
-
mdi = 100 * mdm.ewm(alpha=1 / 14, adjust=False).mean() / (atr + 1e-12)
|
| 491 |
-
f["adx"] = (100 * (pdi - mdi).abs() / (pdi + mdi + 1e-12)).ewm(alpha=1 / 14, adjust=False).mean()
|
| 492 |
-
f["di_diff"] = pdi - mdi
|
| 493 |
-
|
| 494 |
-
# Stokastik + Bollinger + kanal konumu
|
| 495 |
-
ll, hh = l.rolling(14).min(), h.rolling(14).max()
|
| 496 |
-
f["stoch"] = (c - ll) / (hh - ll + 1e-12)
|
| 497 |
-
f["bb_pos"] = (c - c.rolling(20).mean()) / (2 * c.rolling(20).std() + 1e-12)
|
| 498 |
-
f["bb_width"] = 4 * c.rolling(20).std() / c.rolling(20).mean()
|
| 499 |
-
f["dist_hi50"] = c / h.rolling(50).max() - 1
|
| 500 |
-
f["dist_lo50"] = c / l.rolling(50).min() - 1
|
| 501 |
-
|
| 502 |
-
# Mum yapısı
|
| 503 |
-
rng = (h - l) + 1e-12
|
| 504 |
-
f["body"] = (c - o) / rng
|
| 505 |
-
f["upper_wick"] = (h - np.maximum(o, c)) / rng
|
| 506 |
-
f["lower_wick"] = (np.minimum(o, c) - l) / rng
|
| 507 |
-
f["close_loc"] = (c - l) / rng
|
| 508 |
-
|
| 509 |
-
# Hacim
|
| 510 |
-
f["vol_z"] = (v - v.rolling(48).mean()) / (v.rolling(48).std() + 1e-12)
|
| 511 |
-
f["vol_trend"] = v.rolling(6).mean() / (v.rolling(48).mean() + 1e-12)
|
| 512 |
-
f["signed_vol"] = (np.sign(d) * v).rolling(12).sum() / (v.rolling(12).sum() + 1e-12) # OBV benzeri baskı
|
| 513 |
-
f["mfi_like"] = ((c - o) / rng * v).rolling(14).sum() / (v.rolling(14).sum() + 1e-12)
|
| 514 |
-
|
| 515 |
-
# İstatistiksel (otokorelasyon, çarpıklık)
|
| 516 |
-
f["autocorr"] = lr.rolling(48).corr(lr.shift(1))
|
| 517 |
-
f["skew48"] = lr.rolling(48).skew()
|
| 518 |
-
|
| 519 |
-
# Seans / zaman (döngüsel)
|
| 520 |
-
ts = df["ts"]
|
| 521 |
-
f["hour_sin"] = np.sin(2 * np.pi * ts.dt.hour / 24)
|
| 522 |
-
f["hour_cos"] = np.cos(2 * np.pi * ts.dt.hour / 24)
|
| 523 |
-
f["dow_sin"] = np.sin(2 * np.pi * ts.dt.dayofweek / 7)
|
| 524 |
-
f["dow_cos"] = np.cos(2 * np.pi * ts.dt.dayofweek / 7)
|
| 525 |
-
return f.replace([np.inf, -np.inf], np.nan), atr
|
| 526 |
-
|
| 527 |
-
|
| 528 |
-
def _models(hp=None):
|
| 529 |
-
"""hp (Optuna'dan): {"hgb": {...}, "et": {...}, "lr": {"C": ...}}; yoksa varsayılanlar."""
|
| 530 |
-
hp = hp or {}
|
| 531 |
-
h = {"max_depth": 3, "learning_rate": 0.04, "max_iter": 250, "l2_regularization": 2.0,
|
| 532 |
-
"min_samples_leaf": 40, **hp.get("hgb", {})}
|
| 533 |
-
e = {"n_estimators": 300, "max_depth": 6, "min_samples_leaf": 30, "max_features": 0.5, **hp.get("et", {})}
|
| 534 |
-
c = hp.get("lr", {}).get("C", 0.05)
|
| 535 |
-
return {
|
| 536 |
-
"hgb": HistGradientBoostingClassifier(**h, random_state=42),
|
| 537 |
-
"et": ExtraTreesClassifier(**e, n_jobs=-1, random_state=42),
|
| 538 |
-
"lr": make_pipeline(StandardScaler(), LogisticRegression(C=c, max_iter=500)),
|
| 539 |
-
}
|
| 540 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 541 |
|
| 542 |
-
def _select(Xtr, ftr, k, thr=0.95):
|
| 543 |
-
"""Yalnızca EĞİTİM verisiyle: |Spearman IC| sırasına göre, birbirine çok benzeyenleri (>thr) eleyerek k özellik seç."""
|
| 544 |
-
if k <= 0 or Xtr.shape[1] <= k:
|
| 545 |
-
return list(Xtr.columns)
|
| 546 |
-
ic = Xtr.rank().corrwith(pd.Series(np.asarray(ftr), index=Xtr.index).rank()).abs().fillna(0)
|
| 547 |
-
ic = ic.sort_values(ascending=False)
|
| 548 |
-
corr = Xtr.corr().abs().fillna(0)
|
| 549 |
-
keep = []
|
| 550 |
-
for c in ic.index:
|
| 551 |
-
if all(corr.at[c, o] < thr for o in keep):
|
| 552 |
-
keep.append(c)
|
| 553 |
-
if len(keep) >= k:
|
| 554 |
-
break
|
| 555 |
-
return keep
|
| 556 |
-
|
| 557 |
-
|
| 558 |
-
def _fit_predict(Xtr, ytr, Xte, w=None, ftr=None, k=0, with_train=False, hp=None):
|
| 559 |
-
"""Ensemble: HGB + ExtraTrees + Logistic -> olasılık ortalaması.
|
| 560 |
-
k>0: fold-içi özellik seçimi. with_train: eğitim verisi üzerindeki olasılıkları da döndür (aşırı uyum ölçümü için)."""
|
| 561 |
-
if k and ftr is not None:
|
| 562 |
-
sel = _select(Xtr, ftr, k)
|
| 563 |
-
Xtr, Xte = Xtr[sel], Xte[sel]
|
| 564 |
-
ps, pt = [], []
|
| 565 |
-
for name, m in _models(hp).items():
|
| 566 |
-
if name == "lr":
|
| 567 |
-
A, B = Xtr.fillna(0), Xte.fillna(0)
|
| 568 |
-
m.fit(A, ytr, logisticregression__sample_weight=w)
|
| 569 |
-
elif name == "et":
|
| 570 |
-
A, B = Xtr.fillna(0), Xte.fillna(0)
|
| 571 |
-
m.fit(A, ytr, sample_weight=w)
|
| 572 |
else:
|
| 573 |
-
A, B = Xtr, Xte
|
| 574 |
-
m.fit(A, ytr, sample_weight=w)
|
| 575 |
-
ps.append(m.predict_proba(B)[:, 1])
|
| 576 |
-
if with_train:
|
| 577 |
-
pt.append(m.predict_proba(A)[:, 1])
|
| 578 |
-
p = np.mean(ps, axis=0)
|
| 579 |
-
return (p, np.mean(pt, axis=0)) if with_train else p
|
| 580 |
|
|
|
|
|
|
|
| 581 |
|
| 582 |
-
|
| 583 |
-
|
| 584 |
-
|
| 585 |
-
|
| 586 |
-
pnl = pos * fwd - np.abs(pos) * fee
|
| 587 |
-
return pnl[pos != 0]
|
| 588 |
|
|
|
|
|
|
|
| 589 |
|
| 590 |
-
|
| 591 |
-
|
| 592 |
-
if n < 20:
|
| 593 |
-
return {"n": n, "mean": 0.0, "t": 0.0, "hit": np.nan, "pf": np.nan}
|
| 594 |
-
gp, gl = tr[tr > 0].sum(), -tr[tr < 0].sum()
|
| 595 |
-
return {"n": n, "mean": float(tr.mean()),
|
| 596 |
-
"t": float(tr.mean() / (tr.std() / np.sqrt(n) + 1e-12)),
|
| 597 |
-
"hit": float((tr > 0).mean()), "pf": float(gp / gl) if gl > 0 else np.inf}
|
| 598 |
|
|
|
|
| 599 |
|
| 600 |
-
|
| 601 |
-
|
| 602 |
|
|
|
|
| 603 |
|
| 604 |
-
|
| 605 |
-
|
| 606 |
-
|
| 607 |
-
|
| 608 |
-
|
| 609 |
-
|
| 610 |
-
|
| 611 |
-
|
| 612 |
-
|
| 613 |
-
|
| 614 |
-
|
| 615 |
-
|
| 616 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 617 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 618 |
|
| 619 |
-
|
| 620 |
-
"""En iyi parametreleri aynı `suggest_*` arayüzüyle geri üretmek için."""
|
| 621 |
-
def __init__(self, params):
|
| 622 |
-
self.p = params
|
| 623 |
|
| 624 |
-
|
| 625 |
-
|
|
|
|
| 626 |
|
| 627 |
-
|
| 628 |
-
|
|
|
|
| 629 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 630 |
|
| 631 |
-
def _hp_space(t):
|
| 632 |
return {
|
| 633 |
-
"
|
| 634 |
-
|
| 635 |
-
|
| 636 |
-
|
| 637 |
-
|
| 638 |
-
"et": {"max_depth": t.suggest_int("et_depth", 3, 10),
|
| 639 |
-
"min_samples_leaf": t.suggest_int("et_leaf", 10, 100, step=10),
|
| 640 |
-
"max_features": t.suggest_float("et_mf", 0.2, 0.8)},
|
| 641 |
-
"lr": {"C": t.suggest_float("lr_C", 0.005, 0.5, log=True)},
|
| 642 |
}
|
| 643 |
|
| 644 |
|
| 645 |
-
|
| 646 |
-
|
| 647 |
-
|
| 648 |
-
|
| 649 |
-
|
| 650 |
-
|
| 651 |
-
|
| 652 |
-
|
| 653 |
-
|
| 654 |
-
|
| 655 |
-
|
| 656 |
-
Xt, Yt, Ft, Wt = X.iloc[:m], Y.iloc[:m], F[:m], W[:m]
|
| 657 |
-
edges = np.linspace(int(m * 0.4), m, 4).astype(int)
|
| 658 |
-
|
| 659 |
-
def obj(trial):
|
| 660 |
-
hp = _hp_space(trial)
|
| 661 |
-
k = trial.suggest_int("k", 10, 80, step=10)
|
| 662 |
-
aucs = []
|
| 663 |
-
for j in range(3):
|
| 664 |
-
tr_end, te0, te1 = edges[j] - horizon, edges[j], edges[j + 1]
|
| 665 |
-
if tr_end < 80:
|
| 666 |
-
continue
|
| 667 |
-
p = _fit_predict(Xt.iloc[:tr_end], Yt.iloc[:tr_end], Xt.iloc[te0:te1],
|
| 668 |
-
Wt[:tr_end], Ft[:tr_end], k, hp=hp)
|
| 669 |
-
try:
|
| 670 |
-
aucs.append(roc_auc_score(Yt.iloc[te0:te1], p))
|
| 671 |
-
except ValueError:
|
| 672 |
-
pass
|
| 673 |
-
return float(np.mean(aucs) - 0.5 * np.std(aucs)) if aucs else 0.5
|
| 674 |
-
|
| 675 |
-
optuna.logging.set_verbosity(optuna.logging.WARNING)
|
| 676 |
-
study = optuna.create_study(direction="maximize", sampler=optuna.samplers.TPESampler(seed=42))
|
| 677 |
-
study.optimize(obj, n_trials=int(n_trials), timeout=timeout)
|
| 678 |
-
bp = study.best_params
|
| 679 |
-
return {"hp": _hp_space(_Fixed(bp)), "k": int(bp["k"]), "score": float(study.best_value),
|
| 680 |
-
"trials": len(study.trials), "region": m}
|
| 681 |
-
|
| 682 |
-
|
| 683 |
-
def _walk(X, Y, F, W, horizon, n_folds, k, hp=None):
|
| 684 |
-
"""Purged expanding-window walk-forward. Döner: OOS olasılık, satır indeksi, fold boyutları, ort. train-test AUC farkı."""
|
| 685 |
-
n = len(X)
|
| 686 |
-
edges = np.linspace(int(n * 0.4), n, n_folds + 1).astype(int)
|
| 687 |
-
oos_p, oos_idx, sizes, gaps = [], [], [], []
|
| 688 |
-
for j in range(n_folds):
|
| 689 |
-
tr_end, te0, te1 = edges[j] - horizon, edges[j], edges[j + 1]
|
| 690 |
-
if tr_end < 80:
|
| 691 |
-
continue
|
| 692 |
-
p, ptr = _fit_predict(X.iloc[:tr_end], Y.iloc[:tr_end], X.iloc[te0:te1],
|
| 693 |
-
W[:tr_end], F[:tr_end], k, with_train=True, hp=hp)
|
| 694 |
-
try:
|
| 695 |
-
gaps.append(roc_auc_score(Y.iloc[:tr_end], ptr) - roc_auc_score(Y.iloc[te0:te1], p))
|
| 696 |
-
except ValueError:
|
| 697 |
-
pass
|
| 698 |
-
oos_p.append(p)
|
| 699 |
-
oos_idx.extend(range(te0, te1))
|
| 700 |
-
sizes.append(te1 - te0)
|
| 701 |
-
if not oos_p:
|
| 702 |
-
raise ValueError("Walk-forward için yeterli veri yok. Mum sayısını artır.")
|
| 703 |
-
return (np.concatenate(oos_p), np.array(oos_idx), sizes,
|
| 704 |
-
float(np.mean(gaps)) if gaps else float("nan"))
|
| 705 |
-
|
| 706 |
-
|
| 707 |
-
def predict(df: pd.DataFrame, horizon: int, fee: float = 0.0006, n_folds: int = 5, ext=None,
|
| 708 |
-
max_feat: int = 40, use_optuna: bool = False, n_trials: int = 25):
|
| 709 |
-
feats, atr = make_features(df)
|
| 710 |
-
n_ext = 0
|
| 711 |
-
if ext is not None:
|
| 712 |
-
ext = ext.replace([np.inf, -np.inf], np.nan)
|
| 713 |
-
w0 = min(60, len(df) // 5)
|
| 714 |
-
# ısınma sonrası %10'dan fazla boş olan Tickscope kolonlarını at
|
| 715 |
-
ext = ext.loc[:, ext.iloc[w0:].isna().mean() <= 0.10]
|
| 716 |
-
n_ext = ext.shape[1]
|
| 717 |
-
feats = pd.concat([feats, ext], axis=1)
|
| 718 |
-
close = df["close"]
|
| 719 |
-
fwd = close.shift(-horizon) / close - 1
|
| 720 |
-
y = (fwd > 0).astype(float)
|
| 721 |
-
y[fwd.isna()] = np.nan
|
| 722 |
-
|
| 723 |
-
feats = feats.dropna(axis=1, how="all") # tamamen boş kolonları at (ör. hacim yoksa)
|
| 724 |
-
data = feats.copy()
|
| 725 |
-
data["y"], data["fwd"] = y, fwd
|
| 726 |
-
data["thr_move"] = (atr / close) * np.sqrt(horizon)
|
| 727 |
-
warm = min(60, len(data) // 5) # gösterge ısınma barları
|
| 728 |
-
train_df = data.iloc[warm:].dropna(subset=["y", "fwd", "thr_move"]).copy()
|
| 729 |
-
# Kalan NaN'ler (uzun pencereli göstergeler) satır silmek yerine doldurulur
|
| 730 |
-
train_df[list(feats.columns)] = train_df[list(feats.columns)].ffill().fillna(0)
|
| 731 |
-
if len(train_df) < 200:
|
| 732 |
-
raise ValueError(
|
| 733 |
-
f"Yeterli veri yok: {len(df)} mum geldi, {len(train_df)} kullanılabilir bar kaldı "
|
| 734 |
-
f"(en az 200 gerekir; ufuk={horizon}). Mum sayısını artır veya borsayı/sembolü kontrol et.")
|
| 735 |
-
|
| 736 |
-
cols = list(feats.columns)
|
| 737 |
-
X, Y = train_df[cols], train_df["y"].astype(int)
|
| 738 |
-
F, TM = train_df["fwd"].values, train_df["thr_move"].values
|
| 739 |
-
# Gürültü filtresi: çok küçük hareketleri (≈ yazı-tura) eğitimde ağırlıksız bırak
|
| 740 |
-
# ve büyük hareketlere daha fazla ağırlık ver.
|
| 741 |
-
W = np.clip(np.abs(F) / (TM + 1e-12), 0.05, 3.0)
|
| 742 |
-
|
| 743 |
-
# --- Geliştirme (ilk %80) / hiç dokunulmayan hold-out (son %20) ---
|
| 744 |
-
n_all = len(X)
|
| 745 |
-
n_dev = int(n_all * 0.8)
|
| 746 |
-
Xd, Yd, Fd, Wd = X.iloc[:n_dev], Y.iloc[:n_dev], F[:n_dev], W[:n_dev]
|
| 747 |
-
|
| 748 |
-
hp, tuned = None, None
|
| 749 |
-
if use_optuna:
|
| 750 |
-
tuned = _tune(Xd, Yd, Fd, Wd, horizon, n_trials)
|
| 751 |
-
if "error" not in tuned:
|
| 752 |
-
hp, max_feat = tuned["hp"], tuned["k"]
|
| 753 |
-
|
| 754 |
-
oos_p, oos_idx, sizes, gap = _walk(Xd, Yd, Fd, Wd, horizon, n_folds, max_feat, hp)
|
| 755 |
-
yo, fo = Yd.values[oos_idx], Fd[oos_idx]
|
| 756 |
-
|
| 757 |
-
# Teşhis (ablation): yalnızca temel (fiyat/hacim) özelliklerle aynı test
|
| 758 |
-
base_cols = [c for c in cols if not c.startswith(("ts_", "dv_"))]
|
| 759 |
-
auc_base = None
|
| 760 |
-
if len(base_cols) < len(cols):
|
| 761 |
-
pb, ib, _, _ = _walk(Xd[base_cols], Yd, Fd, Wd, horizon, n_folds, max_feat, hp)
|
| 762 |
-
try:
|
| 763 |
-
auc_base = float(roc_auc_score(Yd.values[ib], pb))
|
| 764 |
-
except ValueError:
|
| 765 |
-
auc_base = 0.5
|
| 766 |
|
| 767 |
-
acc = float(((oos_p > 0.5).astype(int) == yo).mean())
|
| 768 |
-
base = float(max(yo.mean(), 1 - yo.mean()))
|
| 769 |
try:
|
| 770 |
-
|
| 771 |
-
|
| 772 |
-
|
| 773 |
-
|
| 774 |
-
|
| 775 |
-
|
| 776 |
-
|
| 777 |
-
|
| 778 |
-
|
| 779 |
-
|
| 780 |
-
|
| 781 |
-
|
| 782 |
-
if
|
| 783 |
-
|
| 784 |
-
|
| 785 |
-
|
| 786 |
-
|
| 787 |
-
|
| 788 |
-
|
| 789 |
-
|
| 790 |
-
|
| 791 |
-
|
| 792 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 793 |
try:
|
| 794 |
-
|
| 795 |
-
|
| 796 |
-
|
| 797 |
-
|
| 798 |
-
|
| 799 |
-
|
| 800 |
-
# Son model: tüm veriyle, canlı tahmin
|
| 801 |
-
final_p = _fit_predict(X, Y, feats.iloc[[-1]][cols].ffill().fillna(0), W, F, max_feat, hp=hp)
|
| 802 |
-
p_up = float(final_p[0])
|
| 803 |
-
|
| 804 |
-
price, a = float(close.iloc[-1]), float(atr.iloc[-1])
|
| 805 |
-
move = a * np.sqrt(horizon)
|
| 806 |
-
center = price + (p_up - 0.5) * 2 * move
|
| 807 |
-
return {
|
| 808 |
-
"p_up": p_up, "acc": acc, "baseline": base, "auc": auc, "price": price, "atr": a,
|
| 809 |
-
"center": center, "low": center - move, "high": center + move,
|
| 810 |
-
"thr": best["thr"], "bt": best, "n_oos": len(oos_p), "fee": fee,
|
| 811 |
-
"n_ext": n_ext, "n_feat": len(cols), "auc_base": auc_base, "max_feat": max_feat,
|
| 812 |
-
"gap": gap, "auc_p": auc_p, "nested": nested, "holdout": ho, "tuned": tuned,
|
| 813 |
-
}
|
| 814 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 815 |
|
| 816 |
-
# ----------------------------------------------------------------- Ana akış
|
| 817 |
-
async def run_async(symbol, timeframe, exchange, horizon, limit):
|
| 818 |
-
extra = {}
|
| 819 |
-
async with stdio_client(SERVER) as (r, w):
|
| 820 |
-
async with ClientSession(r, w) as session:
|
| 821 |
-
await session.initialize()
|
| 822 |
-
tools = {t.name: t for t in (await session.list_tools()).tools}
|
| 823 |
-
|
| 824 |
-
ohlcv = await fetch_ohlcv(session, tools, symbol, timeframe, exchange, int(limit))
|
| 825 |
-
if ohlcv is None:
|
| 826 |
-
raise RuntimeError(f"get_ohlcv bulunamadı. Mevcut tool'lar: {list(tools)}")
|
| 827 |
-
if isinstance(ohlcv, dict) and "error" in ohlcv:
|
| 828 |
-
raise RuntimeError(ohlcv["error"])
|
| 829 |
-
|
| 830 |
-
# Buradan sonrası opsiyonel: sunucu çökse bile elimizdeki mum verisiyle devam edilir
|
| 831 |
-
deep = None
|
| 832 |
-
try:
|
| 833 |
-
deep = await call_tool(session, tools, "deep_analyze", symbol=symbol,
|
| 834 |
-
timeframe=timeframe, exchange=exchange)
|
| 835 |
-
except Exception as e: # noqa: BLE001
|
| 836 |
-
extra["deep_analyze_error"] = _root(e)
|
| 837 |
-
for name in EXTRA_TOOLS:
|
| 838 |
-
if name not in tools:
|
| 839 |
-
continue
|
| 840 |
-
try:
|
| 841 |
-
extra[name] = await call_tool(session, tools, name, symbol=symbol,
|
| 842 |
-
timeframe=timeframe, exchange=exchange)
|
| 843 |
-
except Exception as e: # noqa: BLE001
|
| 844 |
-
extra[name] = {"error": _root(e)}
|
| 845 |
-
extra["_available_tools"] = list(tools)
|
| 846 |
-
ci = tools.get("compute_indicators")
|
| 847 |
-
if ci is not None: # gerçek gösterge adlarını görebilmek için araç açıklaması
|
| 848 |
-
extra["_compute_indicators_description"] = (getattr(ci, "description", "") or "")[:4000]
|
| 849 |
-
|
| 850 |
-
# Göstergeler: ayrı Tickscope süreçleri (ana oturum kapandıktan sonra; DuckDB kilidi çakışmasın)
|
| 851 |
-
ind = None
|
| 852 |
-
try:
|
| 853 |
-
ind = await fetch_indicators(symbol, timeframe, exchange, int(limit))
|
| 854 |
-
except Exception as e: # noqa: BLE001
|
| 855 |
-
extra["indicators_error"] = _root(e)
|
| 856 |
-
if isinstance(ind, dict):
|
| 857 |
-
ser = extract_series(ind, 0)
|
| 858 |
-
extra["_ts_diag"] = {"seri_sayisi": len(ser),
|
| 859 |
-
"seri_uzunluklari": sorted({len(v) for v in ser.values()})[:10],
|
| 860 |
-
"yeniden_baglanma": ind.get("reconnects"), "son_hata": ind.get("last_error"),
|
| 861 |
-
"calismayan_adlar": ind.get("failed")}
|
| 862 |
-
return ohlcv, deep, ind, extra
|
| 863 |
-
|
| 864 |
-
|
| 865 |
-
def _root(e):
|
| 866 |
-
"""ExceptionGroup/TaskGroup sarmalını açıp asıl hatayı döndürür."""
|
| 867 |
-
while getattr(e, "exceptions", None):
|
| 868 |
-
e = e.exceptions[0]
|
| 869 |
-
return f"{type(e).__name__}: {e}"
|
| 870 |
-
|
| 871 |
-
|
| 872 |
-
def run(symbol, timeframe, exchange, horizon, limit, use_deriv=True, max_feat=40, use_optuna=False, n_trials=25):
|
| 873 |
-
try:
|
| 874 |
-
ohlcv, deep, ind, extra = asyncio.run(run_async(symbol, timeframe, exchange, horizon, limit))
|
| 875 |
-
df = to_df(ohlcv)
|
| 876 |
-
dfeat = None
|
| 877 |
-
if use_deriv:
|
| 878 |
-
dfeat, df, dextra = asyncio.run(fetch_deriv_features(symbol, exchange, timeframe, df))
|
| 879 |
-
extra.update(dextra)
|
| 880 |
-
ext = indicator_features(ind, df)
|
| 881 |
-
if dfeat is not None:
|
| 882 |
-
ext = dfeat if ext is None else pd.concat([ext, dfeat], axis=1)
|
| 883 |
-
res = predict(df, int(horizon), ext=ext, max_feat=int(max_feat),
|
| 884 |
-
use_optuna=bool(use_optuna), n_trials=int(n_trials))
|
| 885 |
-
except BaseException as e: # noqa: BLE001
|
| 886 |
-
import traceback
|
| 887 |
-
traceback.print_exc()
|
| 888 |
-
msg = _root(e)
|
| 889 |
-
hint = ""
|
| 890 |
-
if "closed" in msg.lower():
|
| 891 |
-
hint = ("\n\nMCP süreci kapandı. Terminaldeki Tickscope/uvx hata çıktısına bakın; "
|
| 892 |
-
"ayrıca aynı anda açık başka bir Tickscope süreci DuckDB kilidini tutuyor olabilir "
|
| 893 |
-
"(eski `uvx tickscope-mcp` süreçlerini kapatıp tekrar deneyin).")
|
| 894 |
-
return f"**Hata:** {msg}{hint}", None, ""
|
| 895 |
-
|
| 896 |
-
p, thr, bt = res["p_up"], res["thr"], res["bt"]
|
| 897 |
-
signal = "LONG" if p > thr else "SHORT" if p < 1 - thr else "NÖTR"
|
| 898 |
-
nest, ho = res["nested"], res["holdout"]
|
| 899 |
-
checks = [
|
| 900 |
-
("AUC > 0.52 ve p < 0.05", res["auc"] > 0.52 and res["auc_p"] < 0.05),
|
| 901 |
-
("Train–test AUC farkı < 0.20 (aşırı uyum düşük)", res["gap"] < 0.20),
|
| 902 |
-
("Nested net getiri > 0 ve t > 1.5 (≥30 işlem)", nest["n"] >= 30 and nest["mean"] > 0 and nest["t"] > 1.5),
|
| 903 |
-
("Hold-out AUC > 0.50", ho is not None and ho["auc"] > 0.50),
|
| 904 |
-
("Hold-out net getiri > 0 (≥20 işlem)", ho is not None and ho["n"] >= 20 and ho["mean"] > 0),
|
| 905 |
-
]
|
| 906 |
-
t = res["tuned"]
|
| 907 |
-
if t is None:
|
| 908 |
-
opt_txt = "kapalı (varsayılan hiperparametreler)"
|
| 909 |
-
elif "error" in t:
|
| 910 |
-
opt_txt = t["error"]
|
| 911 |
-
else:
|
| 912 |
-
g, e = t["hp"]["hgb"], t["hp"]["et"]
|
| 913 |
-
opt_txt = (f"{t['trials']} deneme · iç skor {t['score']:.3f} · k={t['k']} · "
|
| 914 |
-
f"HGB derinlik {g['max_depth']}, lr {g['learning_rate']:.3f} · ET derinlik {e['max_depth']} · "
|
| 915 |
-
f"LR C={t['hp']['lr']['C']:.3f} (yalnızca ilk %40'ta ayarlandı)")
|
| 916 |
-
passed = sum(ok for _, ok in checks)
|
| 917 |
-
edge = f"✅ {passed}/5 kriter" if passed >= 4 else f"⚠️ {passed}/5 kriter — kanıtlanamadı"
|
| 918 |
-
checks_md = "**Aşırı uyum / güvenilirlik göstergesi**\n\n" + "\n".join(
|
| 919 |
-
f"- {'✅' if ok else '❌'} {name}" for name, ok in checks)
|
| 920 |
-
ho_txt = (f"AUC {ho['auc']:.3f} · {ho['n']} işlem · net {ho['mean']:.3%} (t = {ho['t']:.2f}) · {ho['bars']} bar"
|
| 921 |
-
if ho is not None else "veri yetersiz")
|
| 922 |
-
md = f"""### {symbol} · {timeframe} · {int(horizon)} bar sonrası
|
| 923 |
-
| | |
|
| 924 |
-
|---|---|
|
| 925 |
-
| Son fiyat | **{res['price']:.4f}** |
|
| 926 |
-
| Yükseliş olasılığı | **{p:.1%}** → **{signal}** (eşik {thr:.2f}) |
|
| 927 |
-
| Tahmini merkez | {res['center']:.4f} |
|
| 928 |
-
| Aralık (ATR·√h) | {res['low']:.4f} – {res['high']:.4f} |
|
| 929 |
-
| OOS AUC / doğruluk | {res['auc']:.3f} / {res['acc']:.1%} (baseline {res['baseline']:.1%}) |
|
| 930 |
-
| OOS işlem sayısı | {bt['n']} (hit {bt['hit']:.1%}, PF {bt['pf']:.2f}) |
|
| 931 |
-
| Ort. net getiri / işlem | {bt['mean']:.3%} (t = {bt['t']:.2f}, ücret {res['fee']:.2%}/işlem) |
|
| 932 |
-
| Özellik sayısı | {res['n_feat']} (Tickscope/türev: {res['n_ext']}) → fold başına en fazla {res['max_feat']} seçilir |
|
| 933 |
-
| AUC yalnızca temel özelliklerle | {('%.3f' % res['auc_base']) if res['auc_base'] is not None else '—'} (ek veri kaynakları yardım ediyor mu?) |
|
| 934 |
-
| Optuna | {opt_txt} |
|
| 935 |
-
| Train–test AUC farkı (aşırı uyum) | {res['gap']:.3f} |
|
| 936 |
-
| AUC anlamlılığı (tek yönlü p) | {res['auc_p']:.3f} |
|
| 937 |
-
| Nested (dürüst) net getiri / işlem | {nest['mean']:.3%} (t = {nest['t']:.2f}, {nest['n']} işlem) |
|
| 938 |
-
| Hold-out (dokunulmamış son %20) | {ho_txt} |
|
| 939 |
-
| Model edge | {edge} |
|
| 940 |
-
|
| 941 |
-
{checks_md}
|
| 942 |
-
|
| 943 |
-
*Üstteki OOS/backtest satırları geliştirme kısmında (ilk %80) walk-forward ve eşik OOS'ta seçildiği için iyimserdir; güvenilir olanlar nested ve hold-out satırlarıdır. Eğitim/araştırma amaçlıdır; yatırım tavsiyesi değildir.*"""
|
| 944 |
-
|
| 945 |
-
# Grafik
|
| 946 |
-
tail = df.tail(150)
|
| 947 |
-
step = df["ts"].diff().median()
|
| 948 |
-
fx = df["ts"].iloc[-1] + step * int(horizon)
|
| 949 |
-
fig = go.Figure(go.Candlestick(x=tail["ts"], open=tail["open"], high=tail["high"],
|
| 950 |
-
low=tail["low"], close=tail["close"], name="Fiyat"))
|
| 951 |
-
fig.add_trace(go.Scatter(x=[fx], y=[res["center"]], mode="markers", name="Tahmin",
|
| 952 |
-
marker=dict(size=12, symbol="diamond"),
|
| 953 |
-
error_y=dict(type="data", symmetric=False,
|
| 954 |
-
array=[res["high"] - res["center"]],
|
| 955 |
-
arrayminus=[res["center"] - res["low"]])))
|
| 956 |
-
fig.update_layout(height=450, xaxis_rangeslider_visible=False, margin=dict(l=10, r=10, t=30, b=10))
|
| 957 |
-
|
| 958 |
-
raw = json.dumps(_strip_series({"deep_analyze": deep, "compute_indicators": ind, **extra}),
|
| 959 |
-
indent=2, ensure_ascii=False, default=str)
|
| 960 |
-
return md, fig, raw
|
| 961 |
-
|
| 962 |
-
|
| 963 |
-
with gr.Blocks(title="Tickscope Tahmin") as demo:
|
| 964 |
-
gr.Markdown("## Tickscope MCP → ML Tahmin")
|
| 965 |
-
with gr.Row():
|
| 966 |
-
symbol = gr.Textbox("BTC/USDT", label="Sembol")
|
| 967 |
-
exchange = gr.Dropdown(["binance", "bybit", "okx"], value="binance", label="Borsa")
|
| 968 |
-
timeframe = gr.Dropdown(["5m", "15m", "1h", "4h", "1d"], value="1h", label="Timeframe")
|
| 969 |
with gr.Row():
|
| 970 |
-
|
| 971 |
-
|
| 972 |
-
|
| 973 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 974 |
with gr.Row():
|
| 975 |
-
|
| 976 |
-
|
| 977 |
-
|
| 978 |
-
|
| 979 |
-
|
| 980 |
-
|
| 981 |
-
|
| 982 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 983 |
|
| 984 |
if __name__ == "__main__":
|
| 985 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
|
| 2 |
+
import warnings
|
| 3 |
+
warnings.filterwarnings("ignore")
|
|
|
|
|
|
|
| 4 |
|
| 5 |
+
import time
|
| 6 |
import numpy as np
|
| 7 |
import pandas as pd
|
| 8 |
+
import ccxt
|
| 9 |
+
import gradio as gr
|
| 10 |
+
|
| 11 |
+
from sklearn.ensemble import (
|
| 12 |
+
ExtraTreesClassifier,
|
| 13 |
+
HistGradientBoostingClassifier
|
| 14 |
+
)
|
| 15 |
from sklearn.linear_model import LogisticRegression
|
| 16 |
+
from sklearn.pipeline import Pipeline
|
|
|
|
| 17 |
from sklearn.preprocessing import StandardScaler
|
| 18 |
+
from sklearn.model_selection import TimeSeriesSplit
|
| 19 |
+
from sklearn.metrics import (
|
| 20 |
+
accuracy_score,
|
| 21 |
+
balanced_accuracy_score,
|
| 22 |
+
roc_auc_score
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
)
|
| 24 |
|
| 25 |
+
|
| 26 |
+
# =========================================================
|
| 27 |
+
# CONFIG
|
| 28 |
+
# =========================================================
|
| 29 |
+
|
| 30 |
+
SYMBOL = "BTC/USDT"
|
| 31 |
+
|
| 32 |
+
FEATURES = [
|
| 33 |
+
"rsi",
|
| 34 |
+
"macd_pct",
|
| 35 |
+
"macd_signal_pct",
|
| 36 |
+
"macd_hist_pct",
|
| 37 |
+
"supertrend_direction",
|
| 38 |
+
"supertrend_distance",
|
| 39 |
+
"vwap_distance",
|
| 40 |
+
"atr_pct",
|
| 41 |
+
"return_1",
|
| 42 |
+
"return_3",
|
| 43 |
+
"return_6",
|
| 44 |
+
"volatility_14",
|
| 45 |
+
"volume_ratio",
|
| 46 |
+
"body_pct",
|
| 47 |
+
"high_low_pct"
|
| 48 |
+
]
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
# =========================================================
|
| 52 |
+
# BINANCE DATA
|
| 53 |
+
# =========================================================
|
| 54 |
+
|
| 55 |
+
def fetch_data(timeframe="1h", candles=2000):
|
| 56 |
+
|
| 57 |
+
exchange = ccxt.binance({
|
| 58 |
+
"enableRateLimit": True,
|
| 59 |
+
"timeout": 20000
|
| 60 |
+
})
|
| 61 |
+
|
| 62 |
+
timeframe_ms = exchange.parse_timeframe(timeframe) * 1000
|
| 63 |
+
|
| 64 |
+
since = exchange.milliseconds() - candles * timeframe_ms
|
| 65 |
+
all_rows = []
|
| 66 |
+
|
| 67 |
+
while len(all_rows) < candles:
|
| 68 |
+
|
| 69 |
+
remaining = candles - len(all_rows)
|
| 70 |
+
limit = min(1000, remaining)
|
| 71 |
+
|
| 72 |
+
batch = exchange.fetch_ohlcv(
|
| 73 |
+
SYMBOL,
|
| 74 |
+
timeframe=timeframe,
|
| 75 |
+
since=since,
|
| 76 |
+
limit=limit
|
| 77 |
+
)
|
| 78 |
+
|
| 79 |
+
if not batch:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 80 |
break
|
| 81 |
+
|
| 82 |
+
all_rows.extend(batch)
|
| 83 |
+
|
| 84 |
+
last_timestamp = batch[-1][0]
|
| 85 |
+
new_since = last_timestamp + timeframe_ms
|
| 86 |
+
|
| 87 |
+
if new_since <= since:
|
| 88 |
break
|
| 89 |
+
|
| 90 |
+
since = new_since
|
| 91 |
+
|
| 92 |
+
if len(batch) < limit:
|
| 93 |
break
|
| 94 |
+
|
| 95 |
+
time.sleep(exchange.rateLimit / 1000)
|
| 96 |
+
|
| 97 |
+
if not all_rows:
|
| 98 |
+
raise RuntimeError("Binance veri döndürmedi.")
|
| 99 |
+
|
| 100 |
+
df = pd.DataFrame(
|
| 101 |
+
all_rows,
|
| 102 |
+
columns=[
|
| 103 |
+
"timestamp",
|
| 104 |
+
"open",
|
| 105 |
+
"high",
|
| 106 |
+
"low",
|
| 107 |
+
"close",
|
| 108 |
+
"volume"
|
| 109 |
+
]
|
| 110 |
+
)
|
| 111 |
+
|
| 112 |
+
df = df.drop_duplicates("timestamp")
|
| 113 |
+
df = df.sort_values("timestamp")
|
| 114 |
+
|
| 115 |
+
df["timestamp"] = pd.to_datetime(
|
| 116 |
+
df["timestamp"],
|
| 117 |
+
unit="ms",
|
| 118 |
+
utc=True
|
| 119 |
+
)
|
| 120 |
+
|
| 121 |
+
df = df.set_index("timestamp")
|
| 122 |
+
|
| 123 |
+
for col in ["open", "high", "low", "close", "volume"]:
|
| 124 |
+
df[col] = pd.to_numeric(df[col], errors="coerce")
|
| 125 |
+
|
| 126 |
+
df = df.dropna()
|
| 127 |
+
|
| 128 |
+
# Son mum henüz tamamlanmamış olabilir.
|
| 129 |
+
if len(df) > 2:
|
| 130 |
+
df = df.iloc[:-1]
|
| 131 |
+
|
| 132 |
+
return df.tail(candles)
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
# =========================================================
|
| 136 |
+
# INDICATOR CALCULATIONS
|
| 137 |
+
# =========================================================
|
| 138 |
+
|
| 139 |
+
def calculate_indicators(df):
|
| 140 |
+
|
| 141 |
+
df = df.copy()
|
| 142 |
+
|
| 143 |
+
# -----------------------------------------------------
|
| 144 |
+
# ATR - Wilder RMA
|
| 145 |
+
# -----------------------------------------------------
|
| 146 |
+
|
| 147 |
+
previous_close = df["close"].shift(1)
|
| 148 |
+
|
| 149 |
+
tr = pd.concat([
|
| 150 |
+
df["high"] - df["low"],
|
| 151 |
+
(df["high"] - previous_close).abs(),
|
| 152 |
+
(df["low"] - previous_close).abs()
|
| 153 |
+
], axis=1).max(axis=1)
|
| 154 |
+
|
| 155 |
+
df["atr"] = tr.ewm(
|
| 156 |
+
alpha=1 / 10,
|
| 157 |
+
adjust=False,
|
| 158 |
+
min_periods=10
|
| 159 |
+
).mean()
|
| 160 |
+
|
| 161 |
+
# -----------------------------------------------------
|
| 162 |
+
# RSI 14
|
| 163 |
+
# -----------------------------------------------------
|
| 164 |
+
|
| 165 |
+
delta = df["close"].diff()
|
| 166 |
+
|
| 167 |
+
gain = delta.clip(lower=0)
|
| 168 |
+
loss = -delta.clip(upper=0)
|
| 169 |
+
|
| 170 |
+
avg_gain = gain.ewm(
|
| 171 |
+
alpha=1 / 14,
|
| 172 |
+
adjust=False,
|
| 173 |
+
min_periods=14
|
| 174 |
+
).mean()
|
| 175 |
+
|
| 176 |
+
avg_loss = loss.ewm(
|
| 177 |
+
alpha=1 / 14,
|
| 178 |
+
adjust=False,
|
| 179 |
+
min_periods=14
|
| 180 |
+
).mean()
|
| 181 |
+
|
| 182 |
+
rs = avg_gain / avg_loss.replace(0, np.nan)
|
| 183 |
+
|
| 184 |
+
df["rsi"] = 100 - (100 / (1 + rs))
|
| 185 |
+
|
| 186 |
+
# -----------------------------------------------------
|
| 187 |
+
# MACD 12,26,9
|
| 188 |
+
# -----------------------------------------------------
|
| 189 |
+
|
| 190 |
+
ema12 = df["close"].ewm(
|
| 191 |
+
span=12,
|
| 192 |
+
adjust=False
|
| 193 |
+
).mean()
|
| 194 |
+
|
| 195 |
+
ema26 = df["close"].ewm(
|
| 196 |
+
span=26,
|
| 197 |
+
adjust=False
|
| 198 |
+
).mean()
|
| 199 |
+
|
| 200 |
+
df["macd"] = ema12 - ema26
|
| 201 |
+
|
| 202 |
+
df["macd_signal"] = df["macd"].ewm(
|
| 203 |
+
span=9,
|
| 204 |
+
adjust=False
|
| 205 |
+
).mean()
|
| 206 |
+
|
| 207 |
+
df["macd_hist"] = (
|
| 208 |
+
df["macd"] - df["macd_signal"]
|
| 209 |
+
)
|
| 210 |
+
|
| 211 |
+
# -----------------------------------------------------
|
| 212 |
+
# SUPERTREND 10,3
|
| 213 |
+
# -----------------------------------------------------
|
| 214 |
+
|
| 215 |
+
multiplier = 3.0
|
| 216 |
+
|
| 217 |
+
hl2 = (df["high"] + df["low"]) / 2
|
| 218 |
+
|
| 219 |
+
basic_upper = hl2 + multiplier * df["atr"]
|
| 220 |
+
basic_lower = hl2 - multiplier * df["atr"]
|
| 221 |
+
|
| 222 |
+
final_upper = basic_upper.copy()
|
| 223 |
+
final_lower = basic_lower.copy()
|
| 224 |
+
|
| 225 |
+
supertrend = pd.Series(
|
| 226 |
+
np.nan,
|
| 227 |
+
index=df.index
|
| 228 |
+
)
|
| 229 |
+
|
| 230 |
+
direction = pd.Series(
|
| 231 |
+
1,
|
| 232 |
+
index=df.index,
|
| 233 |
+
dtype=float
|
| 234 |
+
)
|
| 235 |
+
|
| 236 |
+
for i in range(1, len(df)):
|
| 237 |
+
|
| 238 |
+
previous_upper = final_upper.iloc[i - 1]
|
| 239 |
+
previous_lower = final_lower.iloc[i - 1]
|
| 240 |
+
previous_close_value = df["close"].iloc[i - 1]
|
| 241 |
+
|
| 242 |
+
current_upper = basic_upper.iloc[i]
|
| 243 |
+
current_lower = basic_lower.iloc[i]
|
| 244 |
+
|
| 245 |
+
if (
|
| 246 |
+
current_upper < previous_upper
|
| 247 |
+
or previous_close_value > previous_upper
|
| 248 |
+
):
|
| 249 |
+
final_upper.iloc[i] = current_upper
|
| 250 |
+
else:
|
| 251 |
+
final_upper.iloc[i] = previous_upper
|
| 252 |
+
|
| 253 |
+
if (
|
| 254 |
+
current_lower > previous_lower
|
| 255 |
+
or previous_close_value < previous_lower
|
| 256 |
+
):
|
| 257 |
+
final_lower.iloc[i] = current_lower
|
| 258 |
+
else:
|
| 259 |
+
final_lower.iloc[i] = previous_lower
|
| 260 |
+
|
| 261 |
+
previous_direction = direction.iloc[i - 1]
|
| 262 |
+
current_close = df["close"].iloc[i]
|
| 263 |
+
|
| 264 |
+
if previous_direction == -1:
|
| 265 |
+
|
| 266 |
+
if current_close > final_upper.iloc[i]:
|
| 267 |
+
direction.iloc[i] = 1
|
| 268 |
+
else:
|
| 269 |
+
direction.iloc[i] = -1
|
| 270 |
+
|
| 271 |
+
else:
|
| 272 |
+
|
| 273 |
+
if current_close < final_lower.iloc[i]:
|
| 274 |
+
direction.iloc[i] = -1
|
| 275 |
+
else:
|
| 276 |
+
direction.iloc[i] = 1
|
| 277 |
+
|
| 278 |
+
if direction.iloc[i] == 1:
|
| 279 |
+
supertrend.iloc[i] = final_lower.iloc[i]
|
| 280 |
+
else:
|
| 281 |
+
supertrend.iloc[i] = final_upper.iloc[i]
|
| 282 |
+
|
| 283 |
+
df["supertrend"] = supertrend
|
| 284 |
+
df["supertrend_direction"] = direction
|
| 285 |
+
|
| 286 |
+
# -----------------------------------------------------
|
| 287 |
+
# VWAP - UTC DAILY RESET
|
| 288 |
+
# -----------------------------------------------------
|
| 289 |
+
|
| 290 |
+
typical_price = (
|
| 291 |
+
df["high"] + df["low"] + df["close"]
|
| 292 |
+
) / 3
|
| 293 |
+
|
| 294 |
+
daily_group = df.index.date
|
| 295 |
+
|
| 296 |
+
pv = typical_price * df["volume"]
|
| 297 |
+
|
| 298 |
+
cumulative_pv = pv.groupby(daily_group).cumsum()
|
| 299 |
+
|
| 300 |
+
cumulative_volume = (
|
| 301 |
+
df["volume"].groupby(daily_group).cumsum()
|
| 302 |
+
)
|
| 303 |
+
|
| 304 |
+
df["vwap"] = (
|
| 305 |
+
cumulative_pv /
|
| 306 |
+
cumulative_volume.replace(0, np.nan)
|
| 307 |
+
)
|
| 308 |
+
|
| 309 |
+
# -----------------------------------------------------
|
| 310 |
+
# ADDITIONAL FEATURES
|
| 311 |
+
# -----------------------------------------------------
|
| 312 |
+
|
| 313 |
+
df["macd_pct"] = df["macd"] / df["close"] * 100
|
| 314 |
+
|
| 315 |
+
df["macd_signal_pct"] = (
|
| 316 |
+
df["macd_signal"] / df["close"] * 100
|
| 317 |
+
)
|
| 318 |
+
|
| 319 |
+
df["macd_hist_pct"] = (
|
| 320 |
+
df["macd_hist"] / df["close"] * 100
|
| 321 |
+
)
|
| 322 |
+
|
| 323 |
+
df["supertrend_distance"] = (
|
| 324 |
+
df["close"] - df["supertrend"]
|
| 325 |
+
) / df["close"] * 100
|
| 326 |
+
|
| 327 |
+
df["vwap_distance"] = (
|
| 328 |
+
df["close"] - df["vwap"]
|
| 329 |
+
) / df["close"] * 100
|
| 330 |
+
|
| 331 |
+
df["atr_pct"] = df["atr"] / df["close"] * 100
|
| 332 |
+
|
| 333 |
+
df["return_1"] = df["close"].pct_change(1) * 100
|
| 334 |
+
df["return_3"] = df["close"].pct_change(3) * 100
|
| 335 |
+
df["return_6"] = df["close"].pct_change(6) * 100
|
| 336 |
+
|
| 337 |
+
df["volatility_14"] = (
|
| 338 |
+
df["return_1"].rolling(14).std()
|
| 339 |
+
)
|
| 340 |
+
|
| 341 |
+
volume_average = df["volume"].rolling(20).mean()
|
| 342 |
+
|
| 343 |
+
df["volume_ratio"] = (
|
| 344 |
+
df["volume"] /
|
| 345 |
+
volume_average.replace(0, np.nan)
|
| 346 |
+
)
|
| 347 |
+
|
| 348 |
+
df["body_pct"] = (
|
| 349 |
+
df["close"] - df["open"]
|
| 350 |
+
) / df["open"] * 100
|
| 351 |
+
|
| 352 |
+
df["high_low_pct"] = (
|
| 353 |
+
df["high"] - df["low"]
|
| 354 |
+
) / df["close"] * 100
|
| 355 |
+
|
| 356 |
+
return df
|
| 357 |
+
|
| 358 |
+
|
| 359 |
+
# =========================================================
|
| 360 |
+
# TARGET
|
| 361 |
+
# =========================================================
|
| 362 |
+
|
| 363 |
+
def prepare_dataset(df, horizon):
|
| 364 |
+
|
| 365 |
+
data = calculate_indicators(df)
|
| 366 |
+
|
| 367 |
+
future_close = data["close"].shift(-horizon)
|
| 368 |
+
|
| 369 |
+
data["target"] = (
|
| 370 |
+
future_close > data["close"]
|
| 371 |
+
).astype(int)
|
| 372 |
+
|
| 373 |
+
# Son horizon mumunda gelecek hedefi bilinmez.
|
| 374 |
+
data = data.iloc[:-horizon].copy()
|
| 375 |
+
|
| 376 |
+
data = data.replace(
|
| 377 |
+
[np.inf, -np.inf],
|
| 378 |
+
np.nan
|
| 379 |
+
)
|
| 380 |
+
|
| 381 |
+
data = data.dropna(
|
| 382 |
+
subset=FEATURES + ["target", "atr"]
|
| 383 |
+
)
|
| 384 |
+
|
| 385 |
+
if len(data) < 500:
|
| 386 |
+
raise ValueError(
|
| 387 |
+
"Yeterli veri yok. Mum sayısını artır."
|
| 388 |
+
)
|
| 389 |
+
|
| 390 |
+
return data
|
| 391 |
+
|
| 392 |
+
|
| 393 |
+
# =========================================================
|
| 394 |
+
# MODELS
|
| 395 |
+
# =========================================================
|
| 396 |
+
|
| 397 |
+
def create_models():
|
| 398 |
+
|
| 399 |
+
models = {
|
| 400 |
+
|
| 401 |
+
"HistGradientBoosting":
|
| 402 |
+
HistGradientBoostingClassifier(
|
| 403 |
+
max_iter=180,
|
| 404 |
+
learning_rate=0.05,
|
| 405 |
+
max_leaf_nodes=15,
|
| 406 |
+
l2_regularization=2.0,
|
| 407 |
+
random_state=42
|
| 408 |
+
),
|
| 409 |
+
|
| 410 |
+
"ExtraTrees":
|
| 411 |
+
ExtraTreesClassifier(
|
| 412 |
+
n_estimators=300,
|
| 413 |
+
max_depth=8,
|
| 414 |
+
min_samples_leaf=5,
|
| 415 |
+
max_features=0.8,
|
| 416 |
+
class_weight="balanced",
|
| 417 |
+
random_state=42,
|
| 418 |
+
n_jobs=-1
|
| 419 |
+
),
|
| 420 |
+
|
| 421 |
+
"LogisticRegression":
|
| 422 |
+
Pipeline([
|
| 423 |
+
("scaler", StandardScaler()),
|
| 424 |
+
("model", LogisticRegression(
|
| 425 |
+
C=0.2,
|
| 426 |
+
max_iter=2000,
|
| 427 |
+
class_weight="balanced",
|
| 428 |
+
random_state=42
|
| 429 |
+
))
|
| 430 |
+
])
|
| 431 |
}
|
| 432 |
+
|
| 433 |
+
return models
|
| 434 |
+
|
| 435 |
+
|
| 436 |
+
def ensemble_predict(X_train, y_train, X_test):
|
| 437 |
+
|
| 438 |
+
models = create_models()
|
| 439 |
+
|
| 440 |
+
probabilities = []
|
| 441 |
+
|
| 442 |
+
for name, model in models.items():
|
| 443 |
+
|
| 444 |
+
model.fit(X_train, y_train)
|
| 445 |
+
|
| 446 |
+
probability = model.predict_proba(X_test)[:, 1]
|
| 447 |
+
|
| 448 |
+
probabilities.append(probability)
|
| 449 |
+
|
| 450 |
+
ensemble_probability = np.mean(
|
| 451 |
+
probabilities,
|
| 452 |
+
axis=0
|
| 453 |
+
)
|
| 454 |
+
|
| 455 |
+
return ensemble_probability
|
| 456 |
+
|
| 457 |
+
|
| 458 |
+
# =========================================================
|
| 459 |
+
# WALK FORWARD VALIDATION
|
| 460 |
+
# =========================================================
|
| 461 |
+
|
| 462 |
+
def walk_forward_validation(X, y):
|
| 463 |
+
|
| 464 |
+
splitter = TimeSeriesSplit(n_splits=4)
|
| 465 |
+
|
| 466 |
+
scores = []
|
| 467 |
+
fold_results = []
|
| 468 |
+
|
| 469 |
+
for fold, (train_idx, test_idx) in enumerate(
|
| 470 |
+
splitter.split(X),
|
| 471 |
+
start=1
|
| 472 |
+
):
|
| 473 |
+
|
| 474 |
+
X_train = X.iloc[train_idx]
|
| 475 |
+
X_test = X.iloc[test_idx]
|
| 476 |
+
|
| 477 |
+
y_train = y.iloc[train_idx]
|
| 478 |
+
y_test = y.iloc[test_idx]
|
| 479 |
+
|
| 480 |
+
if y_train.nunique() < 2:
|
| 481 |
continue
|
| 482 |
+
|
| 483 |
+
probabilities = ensemble_predict(
|
| 484 |
+
X_train,
|
| 485 |
+
y_train,
|
| 486 |
+
X_test
|
| 487 |
+
)
|
| 488 |
+
|
| 489 |
+
predictions = (
|
| 490 |
+
probabilities >= 0.5
|
| 491 |
+
).astype(int)
|
| 492 |
+
|
| 493 |
+
score = balanced_accuracy_score(
|
| 494 |
+
y_test,
|
| 495 |
+
predictions
|
| 496 |
+
)
|
| 497 |
+
|
| 498 |
+
scores.append(score)
|
| 499 |
+
|
| 500 |
+
fold_results.append(
|
| 501 |
+
f"Fold {fold}: {score * 100:.2f}%"
|
| 502 |
+
)
|
| 503 |
+
|
| 504 |
+
if not scores:
|
| 505 |
+
raise ValueError(
|
| 506 |
+
"Walk-forward validation yapılamadı."
|
| 507 |
+
)
|
| 508 |
+
|
| 509 |
+
return np.mean(scores), fold_results
|
| 510 |
+
|
| 511 |
+
|
| 512 |
+
# =========================================================
|
| 513 |
+
# BACKTEST
|
| 514 |
+
# =========================================================
|
| 515 |
+
|
| 516 |
+
def run_backtest(
|
| 517 |
+
raw,
|
| 518 |
+
test,
|
| 519 |
+
probabilities,
|
| 520 |
+
horizon,
|
| 521 |
+
threshold,
|
| 522 |
+
fee_pct
|
| 523 |
+
):
|
| 524 |
+
|
| 525 |
+
trades = []
|
| 526 |
+
|
| 527 |
+
next_allowed_bar = -1
|
| 528 |
+
|
| 529 |
+
fee_rate = fee_pct / 100
|
| 530 |
+
|
| 531 |
+
for timestamp, row in test.iterrows():
|
| 532 |
+
|
| 533 |
+
raw_position = raw.index.get_loc(timestamp)
|
| 534 |
+
|
| 535 |
+
if raw_position < next_allowed_bar:
|
| 536 |
continue
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 537 |
|
| 538 |
+
probability = float(
|
| 539 |
+
probabilities[test.index.get_loc(timestamp)]
|
| 540 |
+
)
|
| 541 |
|
| 542 |
+
if probability >= threshold:
|
| 543 |
+
side = "LONG"
|
| 544 |
|
| 545 |
+
elif probability <= (1 - threshold):
|
| 546 |
+
side = "SHORT"
|
| 547 |
|
| 548 |
+
else:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 549 |
continue
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 550 |
|
| 551 |
+
entry = float(row["close"])
|
| 552 |
+
atr = float(row["atr"])
|
| 553 |
+
|
| 554 |
+
if not np.isfinite(atr) or atr <= 0:
|
| 555 |
+
continue
|
| 556 |
+
|
| 557 |
+
risk_distance = atr * 1.5
|
| 558 |
+
|
| 559 |
+
if side == "LONG":
|
| 560 |
+
|
| 561 |
+
stop = entry - risk_distance
|
| 562 |
+
target = entry + risk_distance * 2
|
| 563 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 564 |
else:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 565 |
|
| 566 |
+
stop = entry + risk_distance
|
| 567 |
+
target = entry - risk_distance * 2
|
| 568 |
|
| 569 |
+
future = raw.iloc[
|
| 570 |
+
raw_position + 1:
|
| 571 |
+
raw_position + horizon + 1
|
| 572 |
+
]
|
|
|
|
|
|
|
| 573 |
|
| 574 |
+
if len(future) < horizon:
|
| 575 |
+
continue
|
| 576 |
|
| 577 |
+
exit_price = float(future["close"].iloc[-1])
|
| 578 |
+
exit_reason = "HORIZON"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 579 |
|
| 580 |
+
for candle in future.itertuples():
|
| 581 |
|
| 582 |
+
high = float(candle.high)
|
| 583 |
+
low = float(candle.low)
|
| 584 |
|
| 585 |
+
if side == "LONG":
|
| 586 |
|
| 587 |
+
# Aynı mumda iki seviyeye de dokunursa
|
| 588 |
+
# ihtiyatlı olarak stop önce kabul edilir.
|
| 589 |
+
if low <= stop:
|
| 590 |
+
exit_price = stop
|
| 591 |
+
exit_reason = "STOP"
|
| 592 |
+
break
|
| 593 |
+
|
| 594 |
+
if high >= target:
|
| 595 |
+
exit_price = target
|
| 596 |
+
exit_reason = "TARGET"
|
| 597 |
+
break
|
| 598 |
+
|
| 599 |
+
else:
|
| 600 |
+
|
| 601 |
+
if high >= stop:
|
| 602 |
+
exit_price = stop
|
| 603 |
+
exit_reason = "STOP"
|
| 604 |
+
break
|
| 605 |
+
|
| 606 |
+
if low <= target:
|
| 607 |
+
exit_price = target
|
| 608 |
+
exit_reason = "TARGET"
|
| 609 |
+
break
|
| 610 |
+
|
| 611 |
+
if side == "LONG":
|
| 612 |
+
gross_return = (exit_price - entry) / entry
|
| 613 |
+
|
| 614 |
+
else:
|
| 615 |
+
gross_return = (entry - exit_price) / entry
|
| 616 |
+
|
| 617 |
+
net_return = gross_return - (2 * fee_rate)
|
| 618 |
+
|
| 619 |
+
trades.append({
|
| 620 |
+
"time": timestamp,
|
| 621 |
+
"side": side,
|
| 622 |
+
"entry": entry,
|
| 623 |
+
"exit": exit_price,
|
| 624 |
+
"probability": probability,
|
| 625 |
+
"gross_return": gross_return * 100,
|
| 626 |
+
"net_return": net_return * 100,
|
| 627 |
+
"result": "WIN" if net_return > 0 else "LOSS",
|
| 628 |
+
"exit_reason": exit_reason
|
| 629 |
+
})
|
| 630 |
+
|
| 631 |
+
# Pozisyonlar çakışmasın.
|
| 632 |
+
next_allowed_bar = raw_position + horizon
|
| 633 |
+
|
| 634 |
+
if not trades:
|
| 635 |
|
| 636 |
+
return {
|
| 637 |
+
"count": 0,
|
| 638 |
+
"winrate": 0,
|
| 639 |
+
"profit_factor": 0,
|
| 640 |
+
"total_return": 0,
|
| 641 |
+
"trades": []
|
| 642 |
+
}
|
| 643 |
|
| 644 |
+
trade_df = pd.DataFrame(trades)
|
|
|
|
|
|
|
|
|
|
| 645 |
|
| 646 |
+
wins = trade_df[
|
| 647 |
+
trade_df["net_return"] > 0
|
| 648 |
+
]["net_return"]
|
| 649 |
|
| 650 |
+
losses = trade_df[
|
| 651 |
+
trade_df["net_return"] < 0
|
| 652 |
+
]["net_return"]
|
| 653 |
|
| 654 |
+
gross_profit = wins.sum()
|
| 655 |
+
gross_loss = abs(losses.sum())
|
| 656 |
+
|
| 657 |
+
profit_factor = (
|
| 658 |
+
gross_profit / gross_loss
|
| 659 |
+
if gross_loss > 0
|
| 660 |
+
else float("inf") if gross_profit > 0 else 0
|
| 661 |
+
)
|
| 662 |
+
|
| 663 |
+
winrate = (
|
| 664 |
+
len(wins) / len(trade_df)
|
| 665 |
+
) * 100
|
| 666 |
+
|
| 667 |
+
total_return = trade_df["net_return"].sum()
|
| 668 |
|
|
|
|
| 669 |
return {
|
| 670 |
+
"count": len(trade_df),
|
| 671 |
+
"winrate": winrate,
|
| 672 |
+
"profit_factor": profit_factor,
|
| 673 |
+
"total_return": total_return,
|
| 674 |
+
"trades": trades
|
|
|
|
|
|
|
|
|
|
|
|
|
| 675 |
}
|
| 676 |
|
| 677 |
|
| 678 |
+
# =========================================================
|
| 679 |
+
# MAIN ANALYSIS
|
| 680 |
+
# =========================================================
|
| 681 |
+
|
| 682 |
+
def analyze(
|
| 683 |
+
timeframe,
|
| 684 |
+
candles,
|
| 685 |
+
horizon,
|
| 686 |
+
threshold,
|
| 687 |
+
fee_pct
|
| 688 |
+
):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 689 |
|
|
|
|
|
|
|
| 690 |
try:
|
| 691 |
+
|
| 692 |
+
candles = int(candles)
|
| 693 |
+
horizon = int(horizon)
|
| 694 |
+
threshold = float(threshold)
|
| 695 |
+
fee_pct = float(fee_pct)
|
| 696 |
+
|
| 697 |
+
if candles < 700:
|
| 698 |
+
return "En az 700 mum gerekli."
|
| 699 |
+
|
| 700 |
+
if horizon < 1 or horizon > 48:
|
| 701 |
+
return "Horizon 1 ile 48 arasında olmalı."
|
| 702 |
+
|
| 703 |
+
if not 0.50 < threshold < 0.90:
|
| 704 |
+
return "Threshold 0.50 ile 0.90 arasında olmalı."
|
| 705 |
+
|
| 706 |
+
if fee_pct < 0 or fee_pct > 1:
|
| 707 |
+
return "Komisyon 0 ile 1 arasında olmalı."
|
| 708 |
+
|
| 709 |
+
# -------------------------------------------------
|
| 710 |
+
# DATA
|
| 711 |
+
# -------------------------------------------------
|
| 712 |
+
|
| 713 |
+
raw = fetch_data(
|
| 714 |
+
timeframe=timeframe,
|
| 715 |
+
candles=candles
|
| 716 |
+
)
|
| 717 |
+
|
| 718 |
+
data = prepare_dataset(
|
| 719 |
+
raw,
|
| 720 |
+
horizon
|
| 721 |
+
)
|
| 722 |
+
|
| 723 |
+
X = data[FEATURES]
|
| 724 |
+
y = data["target"].astype(int)
|
| 725 |
+
|
| 726 |
+
# -------------------------------------------------
|
| 727 |
+
# CHRONOLOGICAL SPLIT
|
| 728 |
+
# -------------------------------------------------
|
| 729 |
+
|
| 730 |
+
split = int(len(data) * 0.80)
|
| 731 |
+
|
| 732 |
+
X_train = X.iloc[:split]
|
| 733 |
+
X_test = X.iloc[split:]
|
| 734 |
+
|
| 735 |
+
y_train = y.iloc[:split]
|
| 736 |
+
y_test = y.iloc[split:]
|
| 737 |
+
|
| 738 |
+
if y_train.nunique() < 2:
|
| 739 |
+
return "Eğitim verisinde iki sınıf da bulunmalı."
|
| 740 |
+
|
| 741 |
+
# -------------------------------------------------
|
| 742 |
+
# WALK FORWARD
|
| 743 |
+
# -------------------------------------------------
|
| 744 |
+
|
| 745 |
+
wf_score, fold_results = walk_forward_validation(
|
| 746 |
+
X_train,
|
| 747 |
+
y_train
|
| 748 |
+
)
|
| 749 |
+
|
| 750 |
+
# -------------------------------------------------
|
| 751 |
+
# FINAL HOLDOUT
|
| 752 |
+
# -------------------------------------------------
|
| 753 |
+
|
| 754 |
+
probabilities = ensemble_predict(
|
| 755 |
+
X_train,
|
| 756 |
+
y_train,
|
| 757 |
+
X_test
|
| 758 |
+
)
|
| 759 |
+
|
| 760 |
+
predictions = (
|
| 761 |
+
probabilities >= 0.5
|
| 762 |
+
).astype(int)
|
| 763 |
+
|
| 764 |
+
accuracy = accuracy_score(
|
| 765 |
+
y_test,
|
| 766 |
+
predictions
|
| 767 |
+
)
|
| 768 |
+
|
| 769 |
+
balanced = balanced_accuracy_score(
|
| 770 |
+
y_test,
|
| 771 |
+
predictions
|
| 772 |
+
)
|
| 773 |
+
|
| 774 |
try:
|
| 775 |
+
auc = roc_auc_score(
|
| 776 |
+
y_test,
|
| 777 |
+
probabilities
|
| 778 |
+
)
|
| 779 |
+
except Exception:
|
| 780 |
+
auc = 0.5
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 781 |
|
| 782 |
+
# -------------------------------------------------
|
| 783 |
+
# BACKTEST
|
| 784 |
+
# -------------------------------------------------
|
| 785 |
+
|
| 786 |
+
backtest = run_backtest(
|
| 787 |
+
raw=raw,
|
| 788 |
+
test=data.iloc[split:],
|
| 789 |
+
probabilities=probabilities,
|
| 790 |
+
horizon=horizon,
|
| 791 |
+
threshold=threshold,
|
| 792 |
+
fee_pct=fee_pct
|
| 793 |
+
)
|
| 794 |
+
|
| 795 |
+
# -------------------------------------------------
|
| 796 |
+
# CURRENT SIGNAL
|
| 797 |
+
# -------------------------------------------------
|
| 798 |
+
|
| 799 |
+
latest = calculate_indicators(raw).dropna(
|
| 800 |
+
subset=FEATURES + ["atr"]
|
| 801 |
+
).iloc[-1]
|
| 802 |
+
|
| 803 |
+
latest_X = pd.DataFrame(
|
| 804 |
+
[latest[FEATURES].values],
|
| 805 |
+
columns=FEATURES
|
| 806 |
+
)
|
| 807 |
+
|
| 808 |
+
latest_probability = ensemble_predict(
|
| 809 |
+
X,
|
| 810 |
+
y,
|
| 811 |
+
latest_X
|
| 812 |
+
)[0]
|
| 813 |
+
|
| 814 |
+
p_up = float(latest_probability)
|
| 815 |
+
p_down = 1 - p_up
|
| 816 |
+
|
| 817 |
+
if p_up >= threshold:
|
| 818 |
+
signal = "LONG"
|
| 819 |
+
|
| 820 |
+
elif p_down >= threshold:
|
| 821 |
+
signal = "SHORT"
|
| 822 |
+
|
| 823 |
+
else:
|
| 824 |
+
signal = "NÖTR"
|
| 825 |
+
|
| 826 |
+
# -------------------------------------------------
|
| 827 |
+
# ENTRY / STOP / TARGET
|
| 828 |
+
# -------------------------------------------------
|
| 829 |
+
|
| 830 |
+
entry = float(latest["close"])
|
| 831 |
+
atr = float(latest["atr"])
|
| 832 |
+
|
| 833 |
+
if signal == "LONG":
|
| 834 |
+
|
| 835 |
+
stop = entry - 1.5 * atr
|
| 836 |
+
target = entry + 3.0 * atr
|
| 837 |
+
|
| 838 |
+
elif signal == "SHORT":
|
| 839 |
+
|
| 840 |
+
stop = entry + 1.5 * atr
|
| 841 |
+
target = entry - 3.0 * atr
|
| 842 |
+
|
| 843 |
+
else:
|
| 844 |
+
|
| 845 |
+
stop = None
|
| 846 |
+
target = None
|
| 847 |
+
|
| 848 |
+
# -------------------------------------------------
|
| 849 |
+
# INDICATOR INTERPRETATION
|
| 850 |
+
# -------------------------------------------------
|
| 851 |
+
|
| 852 |
+
rsi = float(latest["rsi"])
|
| 853 |
+
|
| 854 |
+
macd_value = float(latest["macd"])
|
| 855 |
+
macd_signal = float(latest["macd_signal"])
|
| 856 |
+
|
| 857 |
+
st_direction = int(
|
| 858 |
+
latest["supertrend_direction"]
|
| 859 |
+
)
|
| 860 |
+
|
| 861 |
+
vwap_distance = float(
|
| 862 |
+
latest["vwap_distance"]
|
| 863 |
+
)
|
| 864 |
+
|
| 865 |
+
indicator_text = (
|
| 866 |
+
f"RSI: {rsi:.2f}\n"
|
| 867 |
+
f"MACD: {'BULLISH' if macd_value > macd_signal else 'BEARISH'}\n"
|
| 868 |
+
f"Supertrend: {'BULLISH' if st_direction == 1 else 'BEARISH'}\n"
|
| 869 |
+
f"VWAP: {'ÜSTÜNDE' if vwap_distance > 0 else 'ALTINDA'}\n"
|
| 870 |
+
f"VWAP uzaklığı: {vwap_distance:.3f}%\n"
|
| 871 |
+
)
|
| 872 |
+
|
| 873 |
+
# -------------------------------------------------
|
| 874 |
+
# RESULTS
|
| 875 |
+
# -------------------------------------------------
|
| 876 |
+
|
| 877 |
+
pf = backtest["profit_factor"]
|
| 878 |
+
|
| 879 |
+
if np.isinf(pf):
|
| 880 |
+
pf_text = "∞"
|
| 881 |
+
else:
|
| 882 |
+
pf_text = f"{pf:.2f}"
|
| 883 |
+
|
| 884 |
+
if signal == "LONG":
|
| 885 |
+
signal_text = "LONG"
|
| 886 |
+
elif signal == "SHORT":
|
| 887 |
+
signal_text = "SHORT"
|
| 888 |
+
else:
|
| 889 |
+
signal_text = "NÖTR"
|
| 890 |
+
|
| 891 |
+
output = f"""
|
| 892 |
+
# BTC/USDT AI ANALİZİ
|
| 893 |
+
|
| 894 |
+
<badge> {signal_text} </badge>
|
| 895 |
+
|
| 896 |
+
## Güncel tahmin
|
| 897 |
+
|
| 898 |
+
| Metrik | Sonuç |
|
| 899 |
+
|---|---:|
|
| 900 |
+
| BTC fiyatı | {entry:,.2f} USDT |
|
| 901 |
+
| LONG olasılığı | {p_up * 100:.2f}% |
|
| 902 |
+
| SHORT olasılığı | {p_down * 100:.2f}% |
|
| 903 |
+
| Model sinyali | **{signal}** |
|
| 904 |
+
| Zaman dilimi | {timeframe} |
|
| 905 |
+
| Tahmin horizon | {horizon} mum |
|
| 906 |
+
|
| 907 |
+
## TradingView indikatörleri
|
| 908 |
+
|
| 909 |
+
```text
|
| 910 |
+
{indicator_text}
|
| 911 |
+
```
|
| 912 |
+
|
| 913 |
+
## İşlem seviyeleri
|
| 914 |
+
|
| 915 |
+
| Seviye | Fiyat |
|
| 916 |
+
|---|---:|
|
| 917 |
+
| Giriş | {entry:,.2f} |
|
| 918 |
+
| Stop Loss | {stop:,.2f if stop is not None else "—"} |
|
| 919 |
+
| 2R Hedef | {target:,.2f if target is not None else "—"} |
|
| 920 |
+
|
| 921 |
+
## Bağımsız hold-out test
|
| 922 |
+
|
| 923 |
+
| Metrik | Sonuç |
|
| 924 |
+
|---|---:|
|
| 925 |
+
| Accuracy | {accuracy * 100:.2f}% |
|
| 926 |
+
| Balanced Accuracy | {balanced * 100:.2f}% |
|
| 927 |
+
| AUC | {auc:.4f} |
|
| 928 |
+
| Walk-forward Balanced Accuracy | {wf_score * 100:.2f}% |
|
| 929 |
+
|
| 930 |
+
## Backtest sonuçları
|
| 931 |
+
|
| 932 |
+
| Metrik | Sonuç |
|
| 933 |
+
|---|---:|
|
| 934 |
+
| İşlem sayısı | {backtest["count"]} |
|
| 935 |
+
| Win Rate | {backtest["winrate"]:.2f}% |
|
| 936 |
+
| Profit Factor | {pf_text} |
|
| 937 |
+
| Net toplam getiri | {backtest["total_return"]:.2f}% |
|
| 938 |
+
| İşlem komisyonu | %{fee_pct:.3f} / taraf |
|
| 939 |
+
|
| 940 |
+
## Walk-forward fold sonuçları
|
| 941 |
+
|
| 942 |
+
""" + "\n".join(
|
| 943 |
+
f"- {x}" for x in fold_results
|
| 944 |
+
)
|
| 945 |
+
|
| 946 |
+
if backtest["trades"]:
|
| 947 |
+
|
| 948 |
+
trade_df = pd.DataFrame(
|
| 949 |
+
backtest["trades"]
|
| 950 |
+
)
|
| 951 |
+
|
| 952 |
+
output += "\n\n## Son backtest işlemleri\n\n"
|
| 953 |
+
|
| 954 |
+
output += trade_df.tail(15).to_markdown(
|
| 955 |
+
index=False
|
| 956 |
+
)
|
| 957 |
+
|
| 958 |
+
output += """
|
| 959 |
+
|
| 960 |
+
---
|
| 961 |
+
|
| 962 |
+
**Uyarı:** Bu bir istatistiksel model tahminidir, kesin fiyat tahmini değildir. Hold-out sonuçları geçmiş veriye aittir. Komisyon, kayma ve gerçek emir gerçekleşmesi sonuçları değiştirebilir. NÖTR sinyali işlem açmama anlamına gelir.
|
| 963 |
+
"""
|
| 964 |
+
|
| 965 |
+
return output
|
| 966 |
+
|
| 967 |
+
except Exception as e:
|
| 968 |
+
|
| 969 |
+
return (
|
| 970 |
+
"# HATA\n\n"
|
| 971 |
+
f"`{type(e).__name__}: {str(e)}`\n\n"
|
| 972 |
+
"Bağlantıyı, Binance erişimini ve paket kurulumlarını kontrol et."
|
| 973 |
+
)
|
| 974 |
+
|
| 975 |
+
|
| 976 |
+
# =========================================================
|
| 977 |
+
# GRADIO UI
|
| 978 |
+
# =========================================================
|
| 979 |
+
|
| 980 |
+
with gr.Blocks(
|
| 981 |
+
title="BTC AI Indicator Ensemble"
|
| 982 |
+
) as app:
|
| 983 |
+
|
| 984 |
+
gr.Markdown(
|
| 985 |
+
"""
|
| 986 |
+
# BTC AI — Indicator Ensemble
|
| 987 |
+
MCP'siz, Binance verisiyle çalışan BTC/USDT tahmin sistemi.
|
| 988 |
+
|
| 989 |
+
**Supertrend + RSI + MACD + VWAP + Machine Learning**
|
| 990 |
+
"""
|
| 991 |
+
)
|
| 992 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 993 |
with gr.Row():
|
| 994 |
+
|
| 995 |
+
timeframe = gr.Dropdown(
|
| 996 |
+
choices=[
|
| 997 |
+
"5m",
|
| 998 |
+
"15m",
|
| 999 |
+
"30m",
|
| 1000 |
+
"1h",
|
| 1001 |
+
"4h",
|
| 1002 |
+
"1d"
|
| 1003 |
+
],
|
| 1004 |
+
value="1h",
|
| 1005 |
+
label="Timeframe"
|
| 1006 |
+
)
|
| 1007 |
+
|
| 1008 |
+
candles = gr.Dropdown(
|
| 1009 |
+
choices=[
|
| 1010 |
+
1000,
|
| 1011 |
+
1500,
|
| 1012 |
+
2000,
|
| 1013 |
+
3000,
|
| 1014 |
+
4000,
|
| 1015 |
+
5000
|
| 1016 |
+
],
|
| 1017 |
+
value=2000,
|
| 1018 |
+
label="Mum sayısı"
|
| 1019 |
+
)
|
| 1020 |
+
|
| 1021 |
with gr.Row():
|
| 1022 |
+
|
| 1023 |
+
horizon = gr.Slider(
|
| 1024 |
+
minimum=1,
|
| 1025 |
+
maximum=24,
|
| 1026 |
+
value=6,
|
| 1027 |
+
step=1,
|
| 1028 |
+
label="Tahmin horizon (mum)"
|
| 1029 |
+
)
|
| 1030 |
+
|
| 1031 |
+
threshold = gr.Slider(
|
| 1032 |
+
minimum=0.51,
|
| 1033 |
+
maximum=0.80,
|
| 1034 |
+
value=0.56,
|
| 1035 |
+
step=0.01,
|
| 1036 |
+
label="Sinyal güven eşiği"
|
| 1037 |
+
)
|
| 1038 |
+
|
| 1039 |
+
fee = gr.Slider(
|
| 1040 |
+
minimum=0,
|
| 1041 |
+
maximum=0.20,
|
| 1042 |
+
value=0.10,
|
| 1043 |
+
step=0.01,
|
| 1044 |
+
label="Komisyon (%) / taraf"
|
| 1045 |
+
)
|
| 1046 |
+
|
| 1047 |
+
run_button = gr.Button(
|
| 1048 |
+
"BTC ANALİZ ET",
|
| 1049 |
+
variant="primary"
|
| 1050 |
+
)
|
| 1051 |
+
|
| 1052 |
+
output = gr.Markdown()
|
| 1053 |
+
|
| 1054 |
+
run_button.click(
|
| 1055 |
+
fn=analyze,
|
| 1056 |
+
inputs=[
|
| 1057 |
+
timeframe,
|
| 1058 |
+
candles,
|
| 1059 |
+
horizon,
|
| 1060 |
+
threshold,
|
| 1061 |
+
fee
|
| 1062 |
+
],
|
| 1063 |
+
outputs=output
|
| 1064 |
+
)
|
| 1065 |
+
|
| 1066 |
|
| 1067 |
if __name__ == "__main__":
|
| 1068 |
+
app.launch()
|