diff --git "a/app.py" "b/app.py" --- "a/app.py" +++ "b/app.py" @@ -1,1768 +1,246 @@ -import warnings -warnings.filterwarnings("ignore") +""" +Tickscope MCP + ML tahmin uygulaması (Gradio) + +Kurulum: + pip install gradio mcp pandas numpy scikit-learn plotly + # uv gerekli (uvx tickscope-mcp için): pip install uv veya https://docs.astral.sh/uv/ +Çalıştırma: + python app.py + +Akış: + 1) uvx tickscope-mcp'yi stdio MCP sunucusu olarak başlatır + 2) get_ohlcv ile mum verisini çeker + 3) compute_indicators / deep_analyze çıktısını (varsa) Tickscope'tan alır + 4) Yerel HistGradientBoosting modeli ile N bar sonrası yön olasılığı + aralık tahmini üretir +Not: Eğitim/araştırma amaçlıdır, yatırım tavsiyesi değildir. +""" + +import asyncio +import json import os -import re -import time -import tempfile +import gradio as gr import numpy as np import pandas as pd -import yfinance as yf -import gradio as gr import plotly.graph_objects as go -from plotly.subplots import make_subplots - -from sklearn.impute import SimpleImputer -from sklearn.preprocessing import StandardScaler -from sklearn.pipeline import Pipeline -from sklearn.neighbors import KNeighborsClassifier, NearestNeighbors -from sklearn.isotonic import IsotonicRegression -from sklearn.metrics import balanced_accuracy_score, accuracy_score - -APP_TITLE = "Goshawk Adaptive Risk Engine v5" -ASSETS = ["BTC-USD", "ETH-USD", "SOL-USD", "AAPL", "MSFT", "NVDA", "SPY", "QQQ", - "GC=F", "EURUSD=X"] -K_GRID = [5, 7, 9, 11, 15, 21] -SL_GRID = [0.8, 1.0, 1.2, 1.5, 1.8, 2.0, 2.5, 3.0] -RR_GRID = [1.0, 1.25, 1.5, 2.0, 2.5, 3.0, 4.0] -CLASSES = [-1, 0, 1] -SIDE_MODES = ["Long + Short", "Sadece Long", "Sadece Short"] -IND_MODES = ["Kapalı", "Onay filtresi (KNN + ST/UT)", "Sadece indikatör"] -COMBO_MODES = ["Kapalı", "Onay filtresi (KNN + birleşik)", "Sadece birleşik sinyal"] -LDC_MODES = ["Kapalı", "Onay filtresi (Lorentzian)", "Sadece Lorentzian"] -VP_MODES = ["Kapalı", "Onay filtresi (POC / Value Area)", "KNN özelliği + onay filtresi"] -GANN_MODES = ["Kapalı", "Onay filtresi (Gann Box %50)"] -SMC_MODES = ["Kapalı", "Onay filtresi (SMC yapı yönü)", "Sadece SMC (yapı kırılımı girişi)"] -SMC_SRC = ["Swing yapı", "Internal yapı"] -SMC_OBF = ["Atr", "Cumulative Mean Range"] -GANN_LEVELS = [0.0, 0.236, 0.382, 0.5, 0.618, 0.786, 1.0] -GANN_FAN = [(8, "8x1"), (4, "4x1"), (3, "3x1"), (2, "2x1"), (1, "1x1"), - (1/2, "1x2"), (1/3, "1x3"), (1/4, "1x4"), (1/8, "1x8")] - -_CACHE = {} -CACHE_TTL = 600 # saniye; Yahoo'yu gereksiz yere tekrar çağırmamak için - - -# ---------------------------------------------------------------- veri / özellik -def get_market_data(ticker, period): - key, now = (ticker, period), time.time() - if key in _CACHE and now - _CACHE[key][0] < CACHE_TTL: - return _CACHE[key][1].copy() - df = yf.download(ticker, period=period, interval="1d", - auto_adjust=True, progress=False) - if df is None or df.empty: - raise ValueError(f"Yahoo Finance {ticker} verisi döndürmedi.") - if isinstance(df.columns, pd.MultiIndex): - df.columns = df.columns.get_level_values(0) - df = df[["Open", "High", "Low", "Close", "Volume"]].dropna().copy() - df.index = pd.to_datetime(df.index) - _CACHE[key] = (now, df) - return df.copy() - - -def calc_atr(df, n=14): - pc = df.Close.shift(1) - tr = pd.concat([(df.High - df.Low), (df.High - pc).abs(), - (df.Low - pc).abs()], axis=1).max(axis=1) - return tr.ewm(alpha=1/n, adjust=False).mean() - - -def calc_rsi(s, n=14): - d = s.diff() - up = d.clip(lower=0).ewm(alpha=1/n, adjust=False).mean() - dn = (-d.clip(upper=0)).ewm(alpha=1/n, adjust=False).mean() - rs = up / dn.replace(0, np.nan) - return 100 - 100/(1+rs) - - -def make_features(df, vp=None): - c, v = df.Close, df.Volume - x = pd.DataFrame(index=df.index) - for n in [1, 2, 3, 5, 10, 20, 50]: - x[f"ret_{n}"] = c.pct_change(n) - for n in [5, 10, 20, 50]: - x[f"ema_gap_{n}"] = c/c.ewm(span=n, adjust=False).mean()-1 - x["rsi_14"] = calc_rsi(c)/100 - atr = calc_atr(df) - x["atr_pct"] = atr/c - x["range_pct"] = (df.High-df.Low)/c - x["body_pct"] = (df.Close-df.Open)/df.Open - daily_ret = c.pct_change() - x["vol_10"] = daily_ret.rolling(10).std() - x["vol_20"] = daily_ret.rolling(20).std() - x["volume_z20"] = (v-v.rolling(20).mean())/v.rolling(20).std().replace(0, np.nan) - x["volume_change"] = v.pct_change() - macd = c.ewm(span=12, adjust=False).mean()-c.ewm(span=26, adjust=False).mean() - x["macd_pct"] = macd/c - # v5: fiyat kanalı konumu ve Bollinger z-skoru - x["dist_hi_20"] = c/df.High.rolling(20).max()-1 - x["dist_lo_20"] = c/df.Low.rolling(20).min()-1 - x["bb_z20"] = (c-c.rolling(20).mean())/c.rolling(20).std().replace(0, np.nan) - if vp is not None: # Volume Profile özellikleri (ileriye bakmaz) - x["vp_poc_gap"] = c/vp["poc"]-1 - x["vp_va_pos"] = (c-vp["val"])/(vp["vah"]-vp["val"]).replace(0, np.nan) - x["vp_va_width"] = (vp["vah"]-vp["val"])/c - return x.replace([np.inf, -np.inf], np.nan), atr - - -def supertrend(df, period=10, mult=3.0): - """TradingView ta.supertrend portu. dir: +1 yükseliş, -1 düşüş.""" - atr = calc_atr(df, period).to_numpy(float) - h, l, c = df.High.to_numpy(float), df.Low.to_numpy(float), df.Close.to_numpy(float) - hl2 = (h+l)/2 - n = len(c) - up, dn = hl2-mult*atr, hl2+mult*atr - trend = np.ones(n, dtype=int) - for i in range(1, n): - if c[i-1] > up[i-1]: - up[i] = max(up[i], up[i-1]) - if c[i-1] < dn[i-1]: - dn[i] = min(dn[i], dn[i-1]) - trend[i] = trend[i-1] - if trend[i-1] == -1 and c[i] > dn[i-1]: - trend[i] = 1 - elif trend[i-1] == 1 and c[i] < up[i-1]: - trend[i] = -1 - return trend, np.where(trend == 1, up, dn) - - -def ut_bot(df, key=1.0, period=10): - """UT Bot Alerts (QuantNomad) portu. pos: +1 al, -1 sat, 0 henüz sinyal yok.""" - c = df.Close.to_numpy(float) - nloss = key*calc_atr(df, period).to_numpy(float) - n = len(c) - stop, pos = np.zeros(n), np.zeros(n, dtype=int) - stop[0] = c[0]-nloss[0] - for i in range(1, n): - prev = stop[i-1] - if c[i] > prev and c[i-1] > prev: - stop[i] = max(prev, c[i]-nloss[i]) - elif c[i] < prev and c[i-1] < prev: - stop[i] = min(prev, c[i]+nloss[i]) - elif c[i] > prev: - stop[i] = c[i]-nloss[i] - else: - stop[i] = c[i]+nloss[i] - if c[i-1] < prev and c[i] > prev: - pos[i] = 1 - elif c[i-1] > prev and c[i] < prev: - pos[i] = -1 - else: - pos[i] = pos[i-1] - return pos, stop - - - - -# ------------------------------------------- Smart Money Concepts (LuxAlgo) Python portu -# Based on LuxAlgo Smart Money Concepts; attribution retained. -# Original license: CC BY-NC-SA 4.0 — https://creativecommons.org/licenses/by-nc-sa/4.0/ -def mtf_prev_levels(df): - """Önceki gün / hafta / ay high-low (tamamlanmış dönem; ileriye bakma yok), mum bazında dizi.""" +from mcp import ClientSession, StdioServerParameters +from mcp.client.stdio import stdio_client +from sklearn.ensemble import HistGradientBoostingClassifier + +SERVER = StdioServerParameters( + command="uvx", + args=["tickscope-mcp"], + env={ + **os.environ, + "TICKSCOPE_EXCHANGES": "binance,bybit,okx", + "TICKSCOPE_DEFAULT_EXCHANGE": "binance", + }, +) + +# Tool şemasındaki argüman adları sürümler arası değişebilir; şemaya bakıp eşleştiriyoruz. +ALIASES = { + "symbol": ["symbol", "pair"], + "timeframe": ["timeframe", "interval", "tf"], + "exchange": ["exchange"], + "limit": ["limit", "count", "n", "bars"], +} + + +# ----------------------------------------------------------------- MCP yardımcıları +def build_args(tool, **wanted): + props = (tool.inputSchema or {}).get("properties", {}) out = {} - for key, rule in (("d", "D"), ("w", "W"), ("m", "M")): - per = df.index.to_period(rule) - hi = df.High.groupby(per).max().shift(1) - lo = df.Low.groupby(per).min().shift(1) - out["p"+key+"h"] = hi.reindex(per).to_numpy(float) - out["p"+key+"l"] = lo.reindex(per).to_numpy(float) - return out - - -def _smc_pivots(h, l, size): - """Pine leg(size) mantığı: pivotlar `size` mum sonra doğrulanır (ileriye bakma yok). - Dönüş: {doğrulama_mumu: [("H"|"L", seviye, pivot_mumu)]}""" - n = len(h) - hs, ls = pd.Series(h), pd.Series(l) - hmax = hs.rolling(size).max().to_numpy(float) - lmin = ls.rolling(size).min().to_numpy(float) - out, leg = {}, 0 - for i in range(size, n): - new_leg = leg - if h[i-size] > hmax[i]: - new_leg = 0 - elif l[i-size] < lmin[i]: - new_leg = 1 - if new_leg != leg: - out.setdefault(i, []).append(("L", float(l[i-size]), i-size) if new_leg == 1 - else ("H", float(h[i-size]), i-size)) - leg = new_leg - return out - - -def smc_analyze(df, swing_len=50, int_len=5, eq_len=3, eq_thr=0.1, ob_keep=5, - confluence=False, ob_filter="Atr"): - """LuxAlgo 'Smart Money Concepts' göstergesinin Python portu: swing/internal yapı (BOS, CHoCH), - order block, EQH/EQL, Fair Value Gap, premium/discount ve strong/weak high-low. - Pine kaynağından birebir değil; her mum yalnızca kendisine kadarki veriyi kullanır.""" - n = len(df) - o, h, l, c = (df[k].to_numpy(float) for k in ("Open", "High", "Low", "Close")) - atr = calc_atr(df, 200).to_numpy(float) - pc_ = np.r_[c[0], c[:-1]] - tr_ = np.maximum(h-l, np.maximum(np.abs(h-pc_), np.abs(l-pc_))) - vm = (np.cumsum(tr_)/np.arange(1, n+1)) if ob_filter == "Cumulative Mean Range" else atr - hv = (h-l) >= 2*vm # yüksek volatilite mumları: OB için high/low yer değiştirir - # LuxAlgo Pine ile aynı gövde/fitil konfluens koşulları. - # Bearish koşul önceki portta ters yazılmıştı; burada Pine ifadesi birebir korunur. - bull_bar = (h-np.maximum(c, o)) > np.minimum(c, o-l) if confluence else np.ones(n, bool) - bear_bar = (h-np.maximum(c, o)) < np.minimum(c, o-l) if confluence else np.ones(n, bool) - ph, pl = np.where(hv, l, h), np.where(hv, h, l) - swing_len = int(min(max(swing_len, 3), max(n//4, 3))) - int_len = int(min(max(int_len, 2), swing_len)) - P_s, P_i, P_e = (_smc_pivots(h, l, k) for k in (swing_len, int_len, int(eq_len))) - - new = lambda: {"lvl": np.nan, "crossed": True, "idx": -1} - sh, sl_, ih, il = new(), new(), new(), new() - s_tr = np.zeros(n, dtype=int) - i_tr = np.zeros(n, dtype=int) - top_a, bot_a = np.full(n, np.nan), np.full(n, np.nan) - swh_a, swl_a = np.full(n, np.nan), np.full(n, np.nan) - st = it = 0 - top, bot = h[0], l[0] - events, obs, points, eqs, fvgs = [], [], [], [], [] - act = [] - ob_sup_a, ob_res_a = np.full(n, np.nan), np.full(n, np.nan) - def _new_ob(d): - obs.append(d) - act.append(d) - if len(act) > 60: - act.pop(0) - prev_hi = prev_lo = np.nan - eq_h = eq_l = None - dsum = 0.0 - for i in range(n): - # --- swing / internal pivotlar - for kind, lvl, bi in P_s.get(i, []): - if kind == "H": - points.append((bi, lvl, "HH" if (np.isfinite(prev_hi) and lvl > prev_hi) else "LH", "H")) - prev_hi = lvl - sh.update(lvl=lvl, crossed=False, idx=bi) - top = lvl - else: - points.append((bi, lvl, "LL" if (np.isfinite(prev_lo) and lvl < prev_lo) else "HL", "L")) - prev_lo = lvl - sl_.update(lvl=lvl, crossed=False, idx=bi) - bot = lvl - for kind, lvl, bi in P_i.get(i, []): - (ih if kind == "H" else il).update(lvl=lvl, crossed=False, idx=bi) - # --- EQH / EQL - for kind, lvl, bi in P_e.get(i, []): - thr = eq_thr*atr[i] if np.isfinite(atr[i]) else 0.0 - if kind == "H": - if eq_h is not None and abs(eq_h[0]-lvl) < thr: - eqs.append(("EQH", eq_h[1], eq_h[0], bi, lvl, i)) - eq_h = (lvl, bi) - else: - if eq_l is not None and abs(eq_l[0]-lvl) < thr: - eqs.append(("EQL", eq_l[1], eq_l[0], bi, lvl, i)) - eq_l = (lvl, bi) - # --- order block mitigasyonu (High/Low) - for ob in act: - if ob["on"] and ((ob["bull"] and l[i] < ob["lo"]) or (not ob["bull"] and h[i] > ob["hi"])): - ob["on"] = False - act[:] = [o_ for o_ in act if o_["on"]] - # --- yapı kırılımları - if i > 0: - for internal, hp, lp in ((True, ih, il), (False, sh, sl_)): - tr = it if internal else st - extra_u = (not internal) or (ih["lvl"] != sh["lvl"] and bool(bull_bar[i])) - extra_d = (not internal) or (il["lvl"] != sl_["lvl"] and bool(bear_bar[i])) - if (np.isfinite(hp["lvl"]) and not hp["crossed"] and c[i] > hp["lvl"] >= c[i-1] - and extra_u): - events.append({"i": i, "kind": "CHoCH" if tr == -1 else "BOS", "bull": True, - "internal": internal, "lvl": hp["lvl"], "from": hp["idx"]}) - hp["crossed"] = True - seg = pl[hp["idx"]:i] - if len(seg): - j = hp["idx"] + int(np.argmin(seg)) - _new_ob({"bull": True, "internal": internal, "hi": float(ph[j]), - "lo": float(pl[j]), "idx": j, "on": True}) - if internal: - it = 1 - else: - st = 1 - if (np.isfinite(lp["lvl"]) and not lp["crossed"] and c[i] < lp["lvl"] <= c[i-1] - and extra_d): - events.append({"i": i, "kind": "CHoCH" if tr == 1 else "BOS", "bull": False, - "internal": internal, "lvl": lp["lvl"], "from": lp["idx"]}) - lp["crossed"] = True - seg = ph[lp["idx"]:i] - if len(seg): - j = lp["idx"] + int(np.argmax(seg)) - _new_ob({"bull": False, "internal": internal, "hi": float(ph[j]), - "lo": float(pl[j]), "idx": j, "on": True}) - if internal: - it = -1 - else: - st = -1 - s_tr[i], i_tr[i] = st, it - top, bot = max(top, h[i]), min(bot, l[i]) - top_a[i], bot_a[i] = top, bot - swh_a[i], swl_a[i] = sh["lvl"], sl_["lvl"] - ob_sup_a[i] = max((o_["lo"] for o_ in act if o_["bull"] and o_["lo"] < c[i]), default=np.nan) - ob_res_a[i] = min((o_["hi"] for o_ in act if (not o_["bull"]) and o_["hi"] > c[i]), default=np.nan) - # --- Fair Value Gap - if i >= 2: - delta = (c[i-1]-o[i-1])/(o[i-1]*100) if o[i-1] else 0.0 - dsum += abs(delta) - thr = dsum/max(i, 1)*2 - for g in fvgs: - if g["on"] and ((g["bull"] and l[i] < g["bot"]) or (not g["bull"] and h[i] > g["top"])): - g["on"] = False - if l[i] > h[i-2] and c[i-1] > h[i-2] and delta > thr: - fvgs.append({"bull": True, "top": float(l[i]), "bot": float(h[i-2]), "idx": i-1, "on": True}) - elif h[i] < l[i-2] and c[i-1] < l[i-2] and -delta > thr: - fvgs.append({"bull": False, "top": float(l[i-2]), "bot": float(h[i]), "idx": i-1, "on": True}) - live = lambda internal, bull: [o_ for o_ in obs if o_["on"] and o_["internal"] == internal - and o_["bull"] == bull][-int(ob_keep):] - return {"swing_trend": s_tr, "internal_trend": i_tr, "top": top_a, "bot": bot_a, "sw_hi": swh_a, "sw_lo": swl_a, "ob_sup": ob_sup_a, "ob_res": ob_res_a, **mtf_prev_levels(df), - "events": events, "points": points, "eq": eqs, - "obs": live(True, True)+live(True, False)+live(False, True)+live(False, False), - "fvg": [g for g in fvgs if g["on"]][-8:], "strong_high": st == -1, "strong_low": st == 1} - - -def add_smc_traces(fig, S, index, last_n=150, kw=None): - """SMC katmanlarını son `last_n` mumluk grafiğe ekler.""" - kw = kw or {} - n = len(index) - w0 = max(0, n-int(last_n)) - xi = lambda k: index[min(max(int(k), w0), n-1)] - for e in S["events"]: - if e["i"] < w0: - continue - col = "#16a34a" if e["bull"] else "#dc2626" - xa, xb = xi(e["from"]), xi(e["i"]) - fig.add_trace(go.Scatter(x=[xa, xb], y=[e["lvl"], e["lvl"]], mode="lines", - line=dict(width=1 if e["internal"] else 2, color=col, - dash="dash" if e["internal"] else "solid"), - showlegend=False, hoverinfo="skip"), **kw) - mid = xa + (xb-xa)/2 - fig.add_trace(go.Scatter(x=[mid], y=[e["lvl"]], mode="text", text=[e["kind"]], - textposition="top center" if e["bull"] else "bottom center", - textfont=dict(size=9 if e["internal"] else 11, color=col), - showlegend=False, hoverinfo="skip"), **kw) - for ob in S["obs"]: - col = "34,197,94" if ob["bull"] else "239,68,68" - fig.add_shape(type="rect", x0=xi(ob["idx"]), x1=index[-1], y0=ob["lo"], y1=ob["hi"], - line=dict(width=1 if ob["internal"] else 0, color=f"rgb({col})"), - fillcolor=f"rgba({col},{0.12 if ob['internal'] else 0.22})", layer="below", **kw) - for g in S["fvg"]: - if g["idx"] < w0: - continue - col = "34,197,94" if g["bull"] else "239,68,68" - fig.add_shape(type="rect", x0=xi(g["idx"]), x1=index[-1], y0=g["bot"], y1=g["top"], - line=dict(width=0), fillcolor=f"rgba({col},0.10)", layer="below", **kw) - for kind, xa_i, la, xb_i, lb, _ in S["eq"]: - if xb_i < w0: - continue - fig.add_trace(go.Scatter(x=[xi(xa_i), xi(xb_i)], y=[la, lb], mode="lines+text", - text=["", kind], textposition="top center" if kind == "EQH" else "bottom center", - line=dict(width=1, dash="dot", color="#64748b"), - textfont=dict(size=10, color="#64748b"), showlegend=False, - hoverinfo="skip"), **kw) - for bi, lvl, lab, kd in S["points"]: - if bi >= w0: - fig.add_trace(go.Scatter(x=[index[bi]], y=[lvl], mode="text", text=[lab], - textposition="top center" if kd == "H" else "bottom center", - textfont=dict(size=9, color="#475569"), showlegend=False, - hoverinfo="skip"), **kw) - if S.get("show_mtf"): - for k_, nm_ in (("pdh", "PDH"), ("pdl", "PDL"), ("pwh", "PWH"), ("pwl", "PWL"), - ("pmh", "PMH"), ("pml", "PML")): - y_ = S[k_][-1] - if np.isfinite(y_): - fig.add_trace(go.Scatter(x=[index[w0], index[-1]], y=[y_, y_], mode="lines+text", - text=["", nm_], textposition="top left", - line=dict(width=1, dash="dashdot", color="#0ea5e9"), - textfont=dict(size=9, color="#0ea5e9"), showlegend=False, - hoverinfo="skip"), **kw) - top, bot = float(S["top"][-1]), float(S["bot"][-1]) - if np.isfinite(top) and np.isfinite(bot) and top > bot: - x0 = index[max(0, n-35)] - rng_ = top-bot - for y0, y1, col, nm in [(top, top-0.05*rng_, "239,68,68", "Premium"), - (bot+0.525*rng_, bot+0.475*rng_, "148,163,184", "Equilibrium"), - (bot+0.05*rng_, bot, "34,197,94", "Discount")]: - fig.add_shape(type="rect", x0=x0, x1=index[-1], y0=y0, y1=y1, line=dict(width=0), - fillcolor=f"rgba({col},0.16)", layer="below", **kw) - for y, nm in [(top, "Strong High" if S["strong_high"] else "Weak High"), - (bot, "Strong Low" if S["strong_low"] else "Weak Low")]: - fig.add_trace(go.Scatter(x=[x0, index[-1]], y=[y, y], mode="lines+text", text=["", nm], - textposition="top left", line=dict(width=1, color="#64748b"), - textfont=dict(size=9, color="#64748b"), showlegend=False, - hoverinfo="skip"), **kw) - -# ------------------------------------------- Gann Box (Zeiierman) Python portu -def gann_box(df, lookback=150, reverse=False, levels=GANN_LEVELS): - """Görünen aralıktaki (son `lookback` mum) en yüksek / en düşük noktadan Gann Box kurar. - Zeiierman'ın TradingView göstergesinin açıklamasına göre yazıldı (Pine kaynağı birebir değil). - up: düşük, yüksekten ÖNCE geldiyse True (yükseliş kutusu). reverse: yönü çevirir.""" - w = df.tail(int(lookback)) - if len(w) < 10: - return None - hi_i, lo_i = int(np.argmax(w.High.to_numpy(float))), int(np.argmin(w.Low.to_numpy(float))) - hi, lo = float(w.High.iloc[hi_i]), float(w.Low.iloc[lo_i]) - if hi <= lo: - return None - up = lo_i < hi_i - if reverse: - up = not up - # kutu: pencerenin başı -> sonu; seviyeler lo + p*(hi-lo) - lv = {float(p): lo + float(p)*(hi-lo) for p in levels} - return {"x0": w.index[0], "x1": w.index[-1], "n": len(w), "hi": hi, "lo": lo, - "up": up, "levels": lv, "hi_date": w.index[hi_i], "lo_date": w.index[lo_i]} - - -def gann_signal(df, lookback=150): - """Her mum için yalnızca geçmişe bakan Gann Box %50 konumu: kapanış orta çizgi üstünde +1, - altında -1 (kutu o muma kadarki son `lookback` mumdan kurulur).""" - lb = int(lookback) - hi = df.High.rolling(lb, min_periods=lb).max() - lo = df.Low.rolling(lb, min_periods=lb).min() - mid = (hi+lo)/2 - c = df.Close - out = np.where(c > mid, 1, np.where(c < mid, -1, 0)) - out = np.where(mid.isna(), 0, out) - return out.astype(int), ((c-lo)/(hi-lo).replace(0, np.nan)).to_numpy(float) - - -def add_gann_traces(fig, gb, df_index, extend=True, fan=True, kw=None): - """Kutu, yüzde seviyeleri ve (isteğe bağlı) Gann Fan çizgilerini Plotly grafiğine ekler.""" - kw = kw or {} - x0, x1, n = gb["x0"], gb["x1"], gb["n"] - step = (df_index[-1]-df_index[-2]) if len(df_index) > 1 else pd.Timedelta(days=1) - x_end = x1 + step*int(n*0.5) if extend else x1 - hi, lo = gb["hi"], gb["lo"] - fig.add_shape(type="rect", x0=x0, x1=x1, y0=lo, y1=hi, line=dict(width=1, color="#64748b"), - fillcolor="rgba(100,116,139,0.05)", layer="below", **kw) - pal = {0.0: "#16a34a", 0.236: "#84cc16", 0.382: "#eab308", 0.5: "#f59e0b", - 0.618: "#f97316", 0.786: "#ef4444", 1.0: "#dc2626"} - for p, y in gb["levels"].items(): - fig.add_trace(go.Scatter(x=[x0, x_end], y=[y, y], mode="lines+text", - text=["", f"Gann {p:.3f} · {fmt_p(y)}"], textposition="middle right", - line=dict(width=1, dash="dot", color=pal.get(p, "#94a3b8")), - name=f"Gann {p:.3f}", showlegend=False, hoverinfo="name+y"), **kw) - if fan: - # köşe: yükseliş kutusunda düşük (sol-alt), düşüşte yüksek (sol-üst); 1x1 = karşı köşeye köşegen - y_start = lo if gb["up"] else hi - sign = 1 if gb["up"] else -1 - base = (hi-lo)/max(gb["n"]-1, 1) # 1x1 eğimi (fiyat/mum) - steps = (x_end - x0)/step - for mult, nm in GANN_FAN: - y_end = y_start + sign*base*mult*steps - ya, yb = (y_start, y_end) - # kutu dışına taşan kısmı kes - lo_c, hi_c = lo - (hi-lo)*0.5, hi + (hi-lo)*0.5 - if ya == yb: - continue - t_hit = min(1.0, max(0.0, ((hi_c if yb > ya else lo_c)-ya)/(yb-ya))) - xe = x0 + (x_end-x0)*t_hit - ye = ya + (yb-ya)*t_hit - fig.add_trace(go.Scatter(x=[x0, xe], y=[ya, ye], mode="lines", - line=dict(width=1.6 if mult == 1 else 0.8, - color="#2563eb" if mult == 1 else "#93c5fd"), - name=f"Gann Fan {nm}", showlegend=(mult == 1), - hoverinfo="name"), **kw) - - -def _profile(h, l, v, bins): - """Bar hacmini high-low aralığına, kutularla örtüştüğü oranda dağıtır (VPVR mantığı).""" - lo, hi = float(np.min(l)), float(np.max(h)) - if not (np.isfinite(lo) and np.isfinite(hi)) or hi <= lo: - return None - v = np.where(np.isfinite(v) & (v > 0), v, 0.0) - if v.sum() <= 0: # hacim verisi yok (ör. forex) -> zaman bazlı profil - v = np.ones_like(v) - edges = np.linspace(lo, hi, bins+1) - step = (hi-lo)/bins - rng = np.maximum(h-l, 1e-12) - ov = np.clip(np.minimum(h[:, None], edges[None, 1:]) - np.maximum(l[:, None], edges[None, :-1]), 0, None) - frac = ov/rng[:, None] - zr = (h-l) <= 1e-12 - if zr.any(): - frac[zr] = 0.0 - frac[zr, np.clip(((h[zr]-lo)/step).astype(int), 0, bins-1)] = 1.0 - return edges, (frac*v[:, None]).sum(axis=0) - - -def _value_area(edges, prof, va_pct): - pi = int(np.argmax(prof)) - lo_i = hi_i = pi - acc, target = float(prof[pi]), va_pct*float(prof.sum()) - nb = len(prof) - while acc < target and (lo_i > 0 or hi_i < nb-1): - up = prof[hi_i+1] if hi_i < nb-1 else -1.0 - dn = prof[lo_i-1] if lo_i > 0 else -1.0 - if up >= dn: - hi_i += 1 - acc += up - else: - lo_i -= 1 - acc += dn - return float((edges[pi]+edges[pi+1])/2), float(edges[lo_i]), float(edges[hi_i+1]) - - -def volume_profile(df, window=60, bins=24, va_pct=0.70): - """Kayan Volume Profile: her mum için son `window` mumdan POC / VAL / VAH. - Yalnızca o muma kadar olan veri kullanılır (ileriye bakma yok).""" - h, l, v = (df[c].to_numpy(float) for c in ("High", "Low", "Volume")) - n = len(h) - out = np.full((n, 3), np.nan) - for i in range(window-1, n): - sl = slice(i-window+1, i+1) - p = _profile(h[sl], l[sl], v[sl], bins) - if p is None: + for key, val in wanted.items(): + if val in (None, ""): continue - out[i] = _value_area(p[0], p[1], va_pct) - return pd.DataFrame(out, index=df.index, columns=["poc", "val", "vah"]) - - -def latest_profile(df, window, bins, va_pct): - w = df.tail(int(window)) - p = _profile(w.High.to_numpy(float), w.Low.to_numpy(float), w.Volume.to_numpy(float), int(bins)) - if p is None: - return None - poc, val, vah = _value_area(p[0], p[1], va_pct) - return p[0], p[1], poc, val, vah - - -def vp_signal(df, atr, vp, ext_atr): - """+1: long onaylı (kapanış POC üstünde ve VAH'ın ext_atr*ATR'den fazla üstünde değil), - -1: short onaylı (POC altında ve VAL'ın ext_atr*ATR'den fazla altında değil), 0: onay yok.""" - c, a = df.Close.to_numpy(float), atr.to_numpy(float) - poc, val, vah = (vp[k].to_numpy(float) for k in ("poc", "val", "vah")) - with np.errstate(invalid="ignore"): - ok_l = (c > poc) & ((c-vah) <= ext_atr*a) - ok_s = (c < poc) & ((val-c) <= ext_atr*a) - return np.where(ok_l, 1, np.where(ok_s, -1, 0)).astype(int) - - -# ------------------------------------------- Lorentzian Classification (jdehorty) portu -def adx_wilder(df, n=14): - h, l, c = df.High, df.Low, df.Close - up, dn = h.diff(), -l.diff() - plus = pd.Series(np.where((up > dn) & (up > 0), up, 0.0), index=df.index) - minus = pd.Series(np.where((dn > up) & (dn > 0), dn, 0.0), index=df.index) - tr = pd.concat([(h-l), (h-c.shift(1)).abs(), (l-c.shift(1)).abs()], axis=1).max(axis=1) - a = 1/n - atr = tr.ewm(alpha=a, adjust=False).mean() - pdi = 100*plus.ewm(alpha=a, adjust=False).mean()/atr - mdi = 100*minus.ewm(alpha=a, adjust=False).mean()/atr - dx = 100*(pdi-mdi).abs()/(pdi+mdi).replace(0, np.nan) - return dx.ewm(alpha=a, adjust=False).mean().to_numpy(float) - - -def _norm01(x): - """Geçmiş min/max'a göre 0-1 ölçekleme (TV 'normalize'); yalnızca geçmişe bakar.""" - s = pd.Series(x) - lo, hi = s.cummin(), s.cummax() - return ((s-lo)/(hi-lo).replace(0, np.nan)).to_numpy(float) - - -def _ldc_features(df): - src = (df.High+df.Low+df.Close)/3 - ema = lambda s, n: s.ewm(span=max(int(n), 1), adjust=False).mean() - rsi = lambda n: ema(calc_rsi(src, n)/100, 1).to_numpy(float) - e1 = ema(src, 10) - e2 = ema((src-e1).abs(), 10) - wt1 = ema((src-e1)/(0.015*e2).replace(0, np.nan), 11) - wt = _norm01((wt1-wt1.rolling(4).mean()).to_numpy(float)) - ma = src.rolling(20).mean() - md = src.rolling(20).apply(lambda a: np.abs(a-a.mean()).mean(), raw=True) - cci = _norm01(((src-ma)/(0.015*md.replace(0, np.nan))).to_numpy(float)) - adx = adx_wilder(df, 20)/100 - # varsayılan öznitelikler: RSI(14,1), WT(10,11), CCI(20,1), ADX(20,2), RSI(9,1) - return np.column_stack([rsi(14), wt, cci, adx, rsi(9)]), src.to_numpy(float) - - -def ldc_classify(df, k=8, max_back=2000, regime_thr=-0.1, kern_lb=8, kern_rw=8.0, x0=25, - use_vol=True, use_regime=True, use_adx=False, adx_thr=20, use_kernel=True, exact=True): - """Machine Learning: Lorentzian Classification (jdehorty) Python portu. - Lorentzian mesafe = sum(log(1+|x-y|)); exact=True: gerçek k-NN, False: Pine'ın yaklaşık-NN döngüsü; - etiket = 4 bar sonraki yön. Filtreler: volatilite, rejim (Kalman eğimi), ADX; kernel: - Nadaraya-Watson rational quadratic. Her mum yalnızca kendisine kadarki veriyi kullanır. - state: +1 / -1 (ML sinyali + kernel eğimi aynı yönde), 0 onay yok.""" - n = len(df) - F, s = _ldc_features(df) - y = np.full(n, np.nan) - y[:-4] = np.sign(s[4:]-s[:-4]) - ok_f = np.isfinite(F).all(axis=1) - cand = ok_f & np.isfinite(y) & (np.arange(n) % 4 != 0) - kk = int(k) - cut = int(np.floor(kk*0.75+0.5)) - pred = np.zeros(n) - for t in range(60, n): - if not ok_f[t]: - continue - hi_j = t-4 # etiketi t'de bilinen son örnek - lo_j = max(0, hi_j-int(max_back)+1) - sel = np.flatnonzero(cand[lo_j:hi_j+1]) + lo_j - if len(sel) < kk: - continue - D = np.log1p(np.abs(F[sel]-F[t])).sum(axis=1) - if exact: # gerçek k en yakın komşu (Lorentzian mesafe) - nn_ = np.argpartition(D, kk-1)[:kk] - pred[t] = y[sel][nn_].sum() - continue - last, dist, pr = -1.0, [], [] # Pine'ın kronolojik yaklaşık-NN döngüsü - for d, yv in zip(D.tolist(), y[sel].tolist()): - if d >= last: - dist.append(d) - pr.append(yv) - if len(dist) > kk: - last = dist[cut] - dist.pop(0) - pr.pop(0) - pred[t] = sum(pr) - - # --- filtreler - f_vol = (calc_atr(df, 1).to_numpy(float) > calc_atr(df, 10).to_numpy(float)) if use_vol \ - else np.ones(n, bool) - f_reg = np.ones(n, bool) - if use_regime: - o4 = ((df.Open+df.High+df.Low+df.Close)/4).to_numpy(float) - hl = (df.High-df.Low).to_numpy(float) - v1 = v2 = kl = 0.0 - klmf = np.zeros(n) - for i in range(n): - v1 = 0.2*(o4[i]-(o4[i-1] if i else o4[i]))+0.8*v1 - v2 = 0.1*hl[i]+0.8*v2 - om = abs(v1/v2) if v2 else 0.0 - al = (-om**2+np.sqrt(om**4+16*om**2))/8 - kl = al*o4[i]+(1-al)*kl - klmf[i] = kl - slope = pd.Series(np.abs(np.diff(klmf, prepend=klmf[0]))) - ema_s = slope.ewm(span=200, adjust=False).mean() - f_reg = (((slope-ema_s)/ema_s.replace(0, np.nan)) >= float(regime_thr)).fillna(False).to_numpy(bool) - f_adx = (adx_wilder(df, 14) > float(adx_thr)) if use_adx else np.ones(n, bool) - filt = f_vol & f_reg & f_adx - - sig = np.zeros(n, dtype=int) - for t in range(1, n): - sig[t] = 1 if (pred[t] > 0 and filt[t]) else -1 if (pred[t] < 0 and filt[t]) else sig[t-1] - - # --- kernel regresyon (rational quadratic), yalnızca geçmiş barlar - L = int(kern_lb)+int(x0) - i_ = np.arange(L+1) - w = (1+i_**2/(float(kern_lb)**2*2*float(kern_rw)))**(-float(kern_rw)) - yhat = np.full(n, np.nan) - for t in range(n): - m = min(L+1, t+1) - yhat[t] = (w[:m]*s[t-np.arange(m)]).sum()/w[:m].sum() - bull = np.r_[False, yhat[1:] > yhat[:-1]] - bear = np.r_[False, yhat[1:] < yhat[:-1]] - if use_kernel: - state = np.where((sig == 1) & bull, 1, np.where((sig == -1) & bear, -1, 0)).astype(int) - else: - state = sig.copy() - state[:60] = 0 - return {"state": state, "signal": sig, "pred": pred, "yhat": yhat, - "vol": f_vol, "reg": f_reg, "adx": f_adx} - - -def regime_labels(features, ref_vol): - # Volatilite kantilleri yalnızca geliştirme dönemi verisinden alınır (sızıntı yok). - q1, q2 = ref_vol.dropna().quantile([0.33, 0.67]) - vol, trend = features["vol_20"], features["ema_gap_20"] - vname = np.where(vol <= q1, "LOW_VOL", np.where(vol >= q2, "HIGH_VOL", "MID_VOL")) - tname = np.where(trend > 0.02, "UP", np.where(trend < -0.02, "DOWN", "RANGE")) - return pd.Series([f"{t}_{v}" for t, v in zip(tname, vname)], index=features.index) - - -def make_target(df, atr, horizon, threshold=0.20): - fwd = df.Close.shift(-horizon)/df.Close-1 - band = threshold*(atr/df.Close)*np.sqrt(horizon) - y = pd.Series(np.where(fwd > band, 1, np.where(fwd < -band, -1, 0)), index=df.index) - return y.where(fwd.notna() & band.notna()), fwd - - -# ---------------------------------------------------------------- model -def pipe(k): - return Pipeline([ - ("imputer", SimpleImputer(strategy="median")), - ("scale", StandardScaler()), - ("knn", KNeighborsClassifier(n_neighbors=int(k), weights="distance")) - ]) - - -def tune_k(X, y, h): - # İç kronolojik doğrulama; örtüşen etiketleri önlemek için ufuk kadar purge. - cut = int(len(X)*0.80) - tr_end = cut-h - if tr_end <= max(K_GRID)+20 or len(X)-cut < 25: - return 9, [] - scores = [] - for k in K_GRID: - model = pipe(k).fit(X.iloc[:tr_end], y.iloc[:tr_end]) - pred = model.predict(X.iloc[cut:]) - scores.append((float(balanced_accuracy_score(y.iloc[cut:], pred)), int(k))) - scores.sort(reverse=True) - return scores[0][1], scores - - -def calibrate_current(raw_cal, y_cal, raw_cur, classes): - # Bire-karşı-hepsi isotonic kalibrasyon; küçük kalibrasyon setinde ham olasılık. - classes = list(classes) - raw_vec = np.zeros(len(CLASSES)) - out = np.zeros(len(CLASSES)) - for j, cls in enumerate(CLASSES): - if cls not in classes: - continue - ci = classes.index(cls) - val = raw_cur[0, ci] - raw_vec[j] = val - truth = (np.asarray(y_cal) == cls).astype(int) - if len(truth) >= 35 and len(np.unique(truth)) == 2: - iso = IsotonicRegression(out_of_bounds="clip").fit(raw_cal[:, ci], truth) - out[j] = iso.predict([val])[0] - else: - out[j] = val - if out.sum() > 0: - return out/out.sum() - if raw_vec.sum() > 0: - return raw_vec/raw_vec.sum() - return np.full(len(CLASSES), 1/3) - - -# ---------------------------------------------------------------- bariyer / R -def stop_dist(A, i, side, sl_atr): - return A["ind_sd"][side][i] if "ind_sd" in A else sl_atr*A["a"][i] + for name in ALIASES.get(key, [key]): + if name in props: + out[name] = val + break + return out -def barrier_trade(A, i, side, sl_atr, rr, h): - """(getiri, çıkış_pozisyonu, neden) döndürür. Aynı mumda SL+TP varsa SL kabul edilir.""" - entry, av = A["c"][i], A["a"][i] - if not np.isfinite(av) or av <= 0 or not np.isfinite(entry): +async def call_tool(session, tools, name, **wanted): + tool = tools.get(name) + if tool is None: return None - if "ind_sd" in A: # SL/TP indikatör seviyelerinden (ST / UT / Volume Profile) - sd = A["ind_sd"][side][i] - if not np.isfinite(sd) or sd <= 0: - return None - td = min(sd*rr, A["ind_cap"][side][i]) + res = await session.call_tool(name, build_args(tool, **wanted)) + text = "\n".join(getattr(c, "text", "") for c in res.content) + if res.isError: + return {"error": text} + if getattr(res, "structuredContent", None): + return res.structuredContent + try: + return json.loads(text) + except Exception: + return {"text": text} + + +def to_df(payload) -> pd.DataFrame: + rows = payload + if isinstance(payload, dict): + for k in ("candles", "ohlcv", "data", "rows", "result"): + if k in payload: + rows = payload[k] + break + if not isinstance(rows, list) or not rows: + raise ValueError(f"OHLCV verisi okunamadı: {str(payload)[:300]}") + + if isinstance(rows[0], (list, tuple)): + cols = ["ts", "open", "high", "low", "close", "volume"][: len(rows[0])] + df = pd.DataFrame(rows, columns=cols) else: - sd = sl_atr*av - td = sd*rr - end = min(i+h, len(A["c"])-1) - for j in range(i+1, end+1): - hi, lo = A["h"][j], A["l"][j] - if side == 1: - sl_hit, tp_hit = lo <= entry-sd, hi >= entry+td - else: - sl_hit, tp_hit = hi >= entry+sd, lo <= entry-td - if sl_hit: - return -sd/entry, j, "SL" - if tp_hit: - return td/entry, j, "TP" - return side*(A["c"][end]-entry)/entry, end, "TIME" - - -def optimize_barriers(A, pos, regimes_arr, side, cost, current_regime, h): - pos = list(pos)[-650:] - sel = [i for i in pos if regimes_arr[i] == current_regime] - if len(sel) < 35: - sel, used = pos, "GLOBAL_FALLBACK" + df = pd.DataFrame(rows) + df = df.rename(columns={"timestamp": "ts", "time": "ts", "t": "ts", "date": "ts", + "o": "open", "h": "high", "l": "low", "c": "close", "v": "volume"}) + for c in ("open", "high", "low", "close", "volume"): + df[c] = pd.to_numeric(df[c], errors="coerce") + if pd.api.types.is_numeric_dtype(df["ts"]): + unit = "ms" if df["ts"].iloc[-1] > 1e11 else "s" + df["ts"] = pd.to_datetime(df["ts"], unit=unit) else: - used = str(current_regime) - results = [] - for sl in ([1.0] if "ind_sd" in A else SL_GRID): - for rr in RR_GRID: - vals = [] - for i in sel: - t = barrier_trade(A, i, side, sl, rr, h) - if t is not None: - vals.append(t[0]-cost) - if len(vals) >= 35: - a = np.asarray(vals) - downside = np.sqrt(np.mean(np.minimum(a, 0)**2)) - results.append((a.mean()-0.35*downside, sl, rr, len(a), - a.mean(), (a > 0).mean())) - if not results: - return 1.5, 2.0, [], used - results.sort(reverse=True) - return float(results[0][1]), float(results[0][2]), results[:10], used - - -# ---------------------------------------------------------------- risk / MC -def dd_mult(dd): - return np.select([dd <= -0.20, dd <= -0.15, dd <= -0.10, dd <= -0.06], - [0.20, 0.35, 0.50, 0.75], 1.0) - - -def adaptive_risk(min_risk, max_risk, bacc, agreement, expectancy_r, n_trades, drawdown): - # Açık, deterministik politika (pekiştirmeli öğrenme değildir). - quality = np.clip((bacc-0.33)/0.20, 0, 1) - agree = np.clip((agreement-0.33)/0.50, 0, 1) - edge = np.clip(expectancy_r/0.30, 0, 1) - sample = np.clip(n_trades/40, 0, 1) - score = 0.30*quality+0.25*agree+0.30*edge+0.15*sample - risk = min_risk+(max_risk-min_risk)*score - if drawdown <= -0.20: - risk = min_risk - elif drawdown <= -0.15: - risk = min_risk+(risk-min_risk)*0.20 - elif drawdown <= -0.10: - risk = min_risk+(risk-min_risk)*0.40 - elif drawdown <= -0.06: - risk = min_risk+(risk-min_risk)*0.70 - return float(np.clip(risk, min_risk, max_risk)), float(score) - - -def simulate_equity(r_values, base_risk_pct): - eq, peak = 1.0, 1.0 - curve, dds = [1.0], [0.0] - for r in r_values: - risk = base_risk_pct/100*float(dd_mult(eq/peak-1)) - eq *= max(0.01, 1+risk*float(r)) - peak = max(peak, eq) - curve.append(eq) - dds.append(eq/peak-1) - return np.array(curve), np.array(dds) - - -def risk_path_mc(r_multiples, base_risk_pct, sims=2000, n_trades=100): - # R biriminde bootstrap; kayıp 1R ile sınırlanmaz (maliyet nedeniyle ~-1.05R olabilir). - if len(r_multiples) < 10: - return None - rng = np.random.default_rng(20261103) - S = rng.choice(np.asarray(r_multiples, float), size=(sims, n_trades), replace=True) - eq, peak, mdd = np.ones(sims), np.ones(sims), np.zeros(sims) - paths = np.empty((n_trades+1, sims)) - paths[0] = 1.0 - for t in range(n_trades): - risk = base_risk_pct/100*dd_mult(eq/peak-1) - eq = eq*np.maximum(0.01, 1+risk*S[:, t]) - peak = np.maximum(peak, eq) - mdd = np.minimum(mdd, eq/peak-1) - paths[t+1] = eq - fin = eq-1 - return {"median": float(np.median(fin)), "p05": float(np.percentile(fin, 5)), - "p95": float(np.percentile(fin, 95)), "dd95": float(np.percentile(mdd, 5)), - "ruin": float(np.mean(eq < 0.5)), - "pct": np.percentile(paths, [5, 25, 50, 75, 95], axis=1)-1.0} - - -# ---------------------------------------------------------------- yardımcılar -def fmt_p(x): - return f"{x:,.2f}" if abs(x) >= 100 else f"{x:,.4f}" - - -def md_table(header, rows): - if not rows: - return "_Veri yok._" - out = ["| " + " | ".join(header) + " |", "|" + "|".join(["---"]*len(header)) + "|"] - out += ["| " + " | ".join(str(c) for c in r) + " |" for r in rows] - return "\n".join(out) - - -def trade_stats(R, curve, dds, trades_df): - if len(R) == 0: - return None - wins, losses = R[R > 0].sum(), -R[R < 0].sum() - reasons = trades_df.exit_reason.value_counts().to_dict() - return {"n": len(R), "win": float((R > 0).mean()), "exp": float(R.mean()), - "pf": float(wins/losses) if losses > 0 else float("inf"), - "ret": float(curve[-1]-1), "mdd": float(dds.min()), - "tp": reasons.get("TP", 0), "sl": reasons.get("SL", 0), - "time": reasons.get("TIME", 0)} - - -TRADE_COLS = ["entry_date", "exit_date", "side", "entry_price", "exit_reason", - "net_return_pct", "R", "regime"] - - -# ---------------------------------------------------------------- grafikler -def price_figure(df, ticker, trades_df, trade_ok, stop, target, ind=None, vp=None, vp_prof=None, - ldc=None, combo=None, gann=None, gann_fan=True, gann_extend=True, smc=None): - w = df.tail(150) - sub = vp is not None and vp_prof is not None - if sub: - fig = make_subplots(rows=1, cols=2, shared_yaxes=True, - column_widths=[0.86, 0.14], horizontal_spacing=0.005) - else: - fig = go.Figure() - kw = dict(row=1, col=1) if sub else {} - fig.add_trace(go.Candlestick(x=w.index, open=w.Open, high=w.High, low=w.Low, - close=w.Close, name=ticker), **kw) - for n, col in [(20, "#f59e0b"), (50, "#3b82f6")]: - ema = df.Close.ewm(span=n, adjust=False).mean().tail(150) - fig.add_trace(go.Scatter(x=w.index, y=ema, mode="lines", name=f"EMA{n}", - line=dict(width=1.2, color=col)), **kw) - if ind is not None: - st_line, st_dir, ut_stop = (np.asarray(a)[-150:] for a in ind) - for d, col, nm in [(1, "#16a34a", "Supertrend ↑"), (-1, "#dc2626", "Supertrend ↓")]: - fig.add_trace(go.Scatter(x=w.index, y=np.where(st_dir == d, st_line, np.nan), - mode="lines", name=nm, line=dict(width=1.5, color=col)), **kw) - fig.add_trace(go.Scatter(x=w.index, y=ut_stop, mode="lines", name="UT trailing stop", - line=dict(width=1, dash="dot", color="#9333ea")), **kw) - if sub: - vw = vp.tail(150) - for k, col, dash, nm in [("poc", "#f59e0b", "solid", "POC"), - ("vah", "#64748b", "dash", "VAH"), - ("val", "#64748b", "dash", "VAL")]: - fig.add_trace(go.Scatter(x=w.index, y=vw[k], mode="lines", name=nm, - line=dict(width=1.4, color=col, dash=dash)), **kw) - edges, prof, poc, val, vah = vp_prof - mid = (edges[:-1]+edges[1:])/2 - colors = ["#f59e0b" if edges[k] <= poc <= edges[k+1] - else ("#60a5fa" if val <= mid[k] <= vah else "#cbd5e1") - for k in range(len(mid))] - fig.add_trace(go.Bar(x=prof, y=mid, orientation="h", width=(edges[1]-edges[0])*0.95, - marker_color=colors, name="Volume Profile", showlegend=False, - hoverinfo="skip"), row=1, col=2) - fig.update_xaxes(showticklabels=False, row=1, col=2) - if ldc is not None: - yh = np.asarray(ldc["yhat"])[-150:] - rising = np.r_[False, np.diff(yh) > 0] - for flag, col, nm in [(True, "#16a34a", "Lorentzian kernel ↑"), (False, "#dc2626", "Lorentzian kernel ↓")]: - fig.add_trace(go.Scatter(x=w.index, y=np.where(rising == flag, yh, np.nan), mode="lines", - name=nm, line=dict(width=2, color=col)), **kw) - st_ = np.asarray(ldc["state"])[-150:] - prev = np.r_[0, st_[:-1]] - for d, nm, col, ycol, off in [(1, "LDC long", "#15803d", "Low", 0.995), - (-1, "LDC short", "#b91c1c", "High", 1.005)]: - m_ = (st_ == d) & (prev != d) - if m_.any(): - fig.add_trace(go.Scatter(x=w.index[m_], y=w[ycol].to_numpy()[m_]*off, mode="markers", - name=nm, marker=dict(symbol="diamond", size=8, color=col, - line=dict(width=1, color="white"))), **kw) - if combo is not None: - cs = np.asarray(combo)[-150:] - pv = np.r_[0, cs[:-1]] - for d, nm, col, ycol, off in [(1, "Birleşik LONG", "#f59e0b", "Low", 0.985), - (-1, "Birleşik SHORT", "#7c3aed", "High", 1.015)]: - m_ = (cs == d) & (pv != d) - if m_.any(): - fig.add_trace(go.Scatter(x=w.index[m_], y=w[ycol].to_numpy()[m_]*off, mode="markers", - name=nm, marker=dict(symbol="star", size=12, color=col, - line=dict(width=1, color="white"))), **kw) - if len(trades_df): - t = trades_df[trades_df.entry_date.isin(w.index)] - for side, sym, nm in [("LONG", "triangle-up", "OOS long"), - ("SHORT", "triangle-down", "OOS short")]: - s = t[t.side == side] - if len(s): - fig.add_trace(go.Scatter( - x=s.entry_date, y=s.entry_price, mode="markers", name=nm, - marker=dict(symbol=sym, size=9, - color=["#16a34a" if r > 0 else "#dc2626" for r in s.R], - line=dict(width=1, color="white")), - text=[f"R={r:.2f} · {why}" for r, why in zip(s.R, s.exit_reason)]), **kw) - if smc is not None: - add_smc_traces(fig, smc, df.index, last_n=150, kw=kw) - if gann is not None: - add_gann_traces(fig, gann, df.index, extend=gann_extend, fan=gann_fan, kw=kw) - if trade_ok: - fig.add_hline(y=stop, line_dash="dash", line_color="#dc2626", annotation_text="SL", **kw) - fig.add_hline(y=target, line_dash="dash", line_color="#16a34a", annotation_text="TP", **kw) - fig.update_layout(title=f"{ticker} Günlük — son 150 mum (üçgenler: OOS sinyalleri)" - + (" + Volume Profile" if sub else "") + (" + Gann Box" if gann is not None else "") + (" + SMC" if smc is not None else ""), - height=560, xaxis_rangeslider_visible=False) - return fig - - -def prob_figure(p_down, p_flat, p_up): - fig = go.Figure(go.Bar(x=["DOWN", "FLAT", "UP"], y=[p_down*100, p_flat*100, p_up*100], - marker_color=["#dc2626", "#9ca3af", "#16a34a"], - text=[f"{p*100:.1f}%" for p in (p_down, p_flat, p_up)], - textposition="outside")) - fig.update_layout(title="Kalibre edilmiş olasılıklar", height=300, - yaxis=dict(range=[0, 100], title="%"), margin=dict(t=50, b=30)) - return fig - - -def equity_figure(curve, dds): - x = np.arange(len(curve)) - fig = make_subplots(rows=2, cols=1, shared_xaxes=True, row_heights=[0.7, 0.3], - vertical_spacing=0.06) - fig.add_trace(go.Scatter(x=x, y=(curve-1)*100, mode="lines", name="Kümülatif getiri %"), 1, 1) - fig.add_trace(go.Scatter(x=x, y=dds*100, fill="tozeroy", name="Drawdown %", - line=dict(color="#dc2626")), 2, 1) - fig.update_xaxes(title_text="İşlem #", row=2, col=1) - fig.update_layout(title="OOS backtest — adaptif risk ile bileşik özsermaye", height=520) - return fig - - -def pf_gauge(pf, n): - shown = 3.0 if np.isinf(pf) else float(min(pf, 3.0)) - fig = go.Figure(go.Indicator( - mode="gauge+number", value=shown, - number=dict(valueformat=".2f", suffix=" (∞)" if np.isinf(pf) else ""), - title=dict(text=f"Profit Factor — OOS ({n} işlem)"), - gauge=dict( - axis=dict(range=[0, 3]), - bar=dict(color="#1d4ed8"), - steps=[dict(range=[0, 1], color="#fecaca"), - dict(range=[1, 1.5], color="#fef08a"), - dict(range=[1.5, 3], color="#bbf7d0")], - threshold=dict(line=dict(color="black", width=3), thickness=0.8, value=1.0)))) - fig.update_layout(height=320, margin=dict(t=70, b=20)) - return fig - - -def mc_figure(mc): - p = mc["pct"]*100 - x = np.arange(p.shape[1]) - fig = go.Figure() - fig.add_trace(go.Scatter(x=x, y=p[0], line=dict(width=0), showlegend=False, hoverinfo="skip")) - fig.add_trace(go.Scatter(x=x, y=p[4], fill="tonexty", line=dict(width=0), - fillcolor="rgba(59,130,246,0.15)", name="%5–%95")) - fig.add_trace(go.Scatter(x=x, y=p[1], line=dict(width=0), showlegend=False, hoverinfo="skip")) - fig.add_trace(go.Scatter(x=x, y=p[3], fill="tonexty", line=dict(width=0), - fillcolor="rgba(59,130,246,0.30)", name="%25–%75")) - fig.add_trace(go.Scatter(x=x, y=p[2], line=dict(color="#1d4ed8", width=2), name="Medyan")) - fig.update_layout(title="Monte Carlo — 100 işlemlik getiri yelpazesi (%)", - xaxis_title="İşlem #", yaxis_title="Getiri %", height=460) - return fig - - -# ---------------------------------------------------------------- ana motor -def run_engine(ticker, period, horizon, side_mode, equity, min_risk, max_risk, - max_leverage, fee, slip, train_window, - p_min, p_gap, flat_max, agree_min, bacc_min, min_trades, - skip_overlap, whole_units, ind_mode, st_period, st_mult, ut_key, ut_period, - vp_mode, vp_window, vp_bins, vp_va, vp_ext, - ldc_mode, ldc_k, ldc_back, ldc_regime, ldc_kern_lb, ldc_kern_rw, - ldc_use_vol, ldc_use_regime, ldc_use_adx, ldc_kernel, ldc_exact, - combo_mode, combo_votes, ind_tpsl, tpsl_smin, tpsl_smax, tpsl_minrr, - gann_mode, gann_lb, gann_fan_on, gann_ext_on, gann_rev, - smc_mode, smc_src, smc_swing_len, smc_int_len, smc_zone, smc_draw, - smc_conf, smc_obf, smc_lvl): + df["ts"] = pd.to_datetime(df["ts"]) + return df.dropna(subset=["close"]).sort_values("ts").reset_index(drop=True) + + +# ----------------------------------------------------------------- ML +def make_features(df: pd.DataFrame): + c, h, l, v = df["close"], df["high"], df["low"], df["volume"] + f = pd.DataFrame(index=df.index) + for n in (1, 3, 6, 12, 24): + f[f"ret{n}"] = c.pct_change(n) + + d = c.diff() + up = d.clip(lower=0).ewm(alpha=1 / 14, adjust=False).mean() + dn = (-d.clip(upper=0)).ewm(alpha=1 / 14, adjust=False).mean() + f["rsi"] = 100 - 100 / (1 + up / (dn + 1e-12)) + + macd = c.ewm(span=12, adjust=False).mean() - c.ewm(span=26, adjust=False).mean() + f["macd_h"] = (macd - macd.ewm(span=9, adjust=False).mean()) / c + f["ema_dist"] = c / c.ewm(span=50, adjust=False).mean() - 1 + + tr = pd.concat([h - l, (h - c.shift()).abs(), (l - c.shift()).abs()], axis=1).max(axis=1) + atr = tr.ewm(alpha=1 / 14, adjust=False).mean() + f["atr_pct"] = atr / c + f["hl_range"] = (h - l) / c + f["vol_z"] = (v - v.rolling(48).mean()) / (v.rolling(48).std() + 1e-12) + f["bb_pos"] = (c - c.rolling(20).mean()) / (2 * c.rolling(20).std() + 1e-12) + return f, atr + + +def predict(df: pd.DataFrame, horizon: int): + feats, atr = make_features(df) + close = df["close"] + y = (close.shift(-horizon) > close).astype(float) + y[close.shift(-horizon).isna()] = np.nan + + data = feats.copy() + data["y"] = y + train_df = data.dropna() + if len(train_df) < 200: + raise ValueError("Yeterli veri yok (en az ~200 geçerli bar gerekir). Limit'i artır.") + + X, Y = train_df.drop(columns="y"), train_df["y"].astype(int) + split = int(len(X) * 0.8) + gap = horizon # sızıntıyı önlemek için train/test arası boşluk + mk = lambda: HistGradientBoostingClassifier(max_depth=3, learning_rate=0.05, + max_iter=200, l2_regularization=1.0, random_state=42) + + m = mk().fit(X.iloc[:split - gap], Y.iloc[:split - gap]) + acc = float((m.predict(X.iloc[split:]) == Y.iloc[split:]).mean()) + base = float(max(Y.iloc[split:].mean(), 1 - Y.iloc[split:].mean())) + + final = mk().fit(X, Y) + last = feats.iloc[[-1]].ffill().fillna(0) + p_up = float(final.predict_proba(last)[0, 1]) + + price, a = float(close.iloc[-1]), float(atr.iloc[-1]) + move = a * np.sqrt(horizon) + center = price + (p_up - 0.5) * 2 * move + return { + "p_up": p_up, "acc": acc, "baseline": base, "price": price, "atr": a, + "center": center, "low": center - move, "high": center + move, + } + + +# ----------------------------------------------------------------- Ana akış +async def run_async(symbol, timeframe, exchange, horizon, limit): + async with stdio_client(SERVER) as (r, w): + async with ClientSession(r, w) as session: + await session.initialize() + tools = {t.name: t for t in (await session.list_tools()).tools} + + ohlcv = await call_tool(session, tools, "get_ohlcv", symbol=symbol, + timeframe=timeframe, exchange=exchange, limit=int(limit)) + if ohlcv is None: + raise RuntimeError(f"get_ohlcv bulunamadı. Mevcut tool'lar: {list(tools)}") + if isinstance(ohlcv, dict) and "error" in ohlcv: + raise RuntimeError(ohlcv["error"]) + + # Tickscope'un kendi analizleri (opsiyonel, hata verirse atla) + deep = await call_tool(session, tools, "deep_analyze", symbol=symbol, + timeframe=timeframe, exchange=exchange) + ind = await call_tool(session, tools, "compute_indicators", symbol=symbol, + timeframe=timeframe, exchange=exchange) + return ohlcv, deep, ind + + +def run(symbol, timeframe, exchange, horizon, limit): try: - ticker = str(ticker).strip().upper() - h = int(horizon) - equity, fee, slip = float(equity), float(fee), float(slip) - min_risk, max_risk = float(min_risk), float(max_risk) - if max_risk < min_risk: - raise ValueError("Maksimum risk, minimum riskten küçük olamaz.") - cost = 2*(fee+slip)/100 - allow = {1: side_mode != "Sadece Short", -1: side_mode != "Sadece Long"} - - df = get_market_data(ticker, period) - if len(df) < 450: - raise ValueError("En az 450 günlük mum gerekli.") - df = df.iloc[:-1].copy() # tamamlanmamış güncel mumu çıkar - vp_on = vp_mode != VP_MODES[0] - vp_feat = vp_mode == VP_MODES[2] - vp_win, vp_nb, vp_vap = int(vp_window), int(vp_bins), float(vp_va) - vp = volume_profile(df, vp_win, vp_nb, vp_vap) - X_all, atr_all = make_features(df, vp if vp_feat else None) - vp_arr = vp_signal(df, atr_all, vp, float(vp_ext)) - vp_names = {1: "LONG onaylı", -1: "SHORT onaylı", 0: "onay yok"} - vp_no_vol = float(df.Volume.tail(vp_win).sum()) <= 0 - lp = latest_profile(df, vp_win, vp_nb, vp_vap) - y_all, _ = make_target(df, atr_all, h) - gann_on = gann_mode != GANN_MODES[0] - gann_arr, gann_pos = gann_signal(df, int(gann_lb)) - gann_now = int(gann_arr[-1]) - GB = gann_box(df, int(gann_lb), bool(gann_rev)) - smc_on = smc_mode != SMC_MODES[0] - smc_only = smc_mode == SMC_MODES[2] - SM = smc_analyze(df, int(smc_swing_len), int(smc_int_len), confluence=bool(smc_conf), - ob_filter=smc_obf) if (smc_on or smc_draw) else None - smc_lvl_on = bool(smc_lvl) and SM is not None and smc_on - if SM is not None: - SM["show_mtf"] = bool(smc_lvl) - smc_arr = np.zeros(len(df), dtype=int) - if SM is not None: - smc_arr = (SM["swing_trend"] if smc_src == SMC_SRC[0] else SM["internal_trend"]).copy() - if smc_zone: # LuxAlgo Premium/Discount kutuları: uçlarda %5, equilibrium %47.5-%52.5 - top_, bot_ = SM["top"], SM["bot"] - rng_ = top_ - bot_ - close_ = df.Close.to_numpy(float) - discount_edge = bot_ + 0.05*rng_ - premium_edge = top_ - 0.05*rng_ - zone_ = np.where(close_ <= discount_edge, 1, - np.where(close_ >= premium_edge, -1, 0)) - smc_arr = np.where(zone_ == smc_arr, smc_arr, 0).astype(int) - smc_now = int(smc_arr[-1]) - smc_entry = np.zeros(len(df), dtype=int) # yalnızca yapı kırılımı mumlarında giriş - if SM is not None: - want_internal = smc_src == SMC_SRC[1] - ev_arr = np.zeros(len(df), dtype=int) - for e_ in SM["events"]: - if e_["internal"] == want_internal: - ev_arr[e_["i"]] = 1 if e_["bull"] else -1 - smc_entry = np.where(ev_arr == smc_arr, ev_arr, 0).astype(int) - - valid = X_all.notna().all(axis=1) & y_all.notna() - X = X_all.loc[valid].copy() - y = y_all.loc[X.index].astype(int) - if len(X) < 300: - raise ValueError("Temiz özelliklerden sonra yeterli veri kalmadı.") - - oos_n = min(180, max(75, int(len(X)*0.22))) - dev_end = len(X)-oos_n-h - if dev_end < 200: - raise ValueError("OOS ayrımı için yeterli geliştirme verisi yok.") - tw = int(train_window) - dev_X, dev_y = X.iloc[:dev_end], y.iloc[:dev_end] - if len(dev_X) > tw: - dev_X, dev_y = dev_X.iloc[-tw:], dev_y.iloc[-tw:] - - regimes_all = regime_labels(X_all, X_all.loc[dev_X.index, "vol_20"]) - reg_arr = regimes_all.to_numpy() - A = {"h": df.High.to_numpy(float), "l": df.Low.to_numpy(float), - "c": df.Close.to_numpy(float), "a": atr_all.to_numpy(float)} - pos_of = lambda idx: df.index.get_indexer(idx) - - # --- indikatörler (Supertrend + UT Bot), tamamen Python'da - st_dir, st_line = supertrend(df, int(st_period), float(st_mult)) - ut_pos, ut_stop = ut_bot(df, float(ut_key), int(ut_period)) - ind_arr = np.where((st_dir == ut_pos) & (ut_pos != 0), ut_pos, 0).astype(int) - ind_only = ind_mode == "Sadece indikatör" - ind_confirm = ind_mode == IND_MODES[1] - ind_now = int(ind_arr[-1]) - - # --- Lorentzian Classification (ML + kernel) ve birleşik sinyal (oylama) - combo_on = combo_mode != COMBO_MODES[0] - combo_only = combo_mode == COMBO_MODES[2] - ldc_on = ldc_mode != LDC_MODES[0] and not combo_only - ldc_only = ldc_mode == LDC_MODES[2] and not combo_only - ldc_confirm = ldc_mode == LDC_MODES[1] and not combo_only - if combo_only: # oylar zaten birleşik sinyale girer; tekil filtreler çift sayılmasın - ind_only = ind_confirm = vp_on = False - ind_only = ind_only and not ldc_only # iki "Sadece" modu birlikteyse Lorentzian öncelikli - if smc_only: # backtest yalnızca SMC girişlerine göre; diğer sinyal/filtre modları devre dışı - ind_only = ind_confirm = vp_on = ldc_on = ldc_only = ldc_confirm = False - combo_on = combo_only = False - need_ldc = ldc_mode != LDC_MODES[0] or combo_on - LD = ldc_classify(df, ldc_k, ldc_back, ldc_regime, ldc_kern_lb, ldc_kern_rw, - use_vol=bool(ldc_use_vol), use_regime=bool(ldc_use_regime), - use_adx=bool(ldc_use_adx), use_kernel=bool(ldc_kernel), - exact=bool(ldc_exact)) if need_ldc else None - ldc_arr = LD["state"] if LD else np.zeros(len(df), dtype=int) - ldc_now = int(ldc_arr[-1]) - vp_now = int(vp_arr[-1]) - votes = np.vstack([ind_arr, ldc_arr, vp_arr]) # ST+UT, Lorentzian, Volume Profile - n_long, n_short = (votes == 1).sum(axis=0), (votes == -1).sum(axis=0) - mv = int(combo_votes) - combo_arr = np.where((n_long >= mv) & (n_short == 0), 1, - np.where((n_short >= mv) & (n_long == 0), -1, 0)).astype(int) - combo_now = int(combo_arr[-1]) - - # --- indikatör bazlı SL/TP: en yakın ST / UT / VP seviyesi = invalidasyon (SL), - # önündeki Value Area / POC seviyesi >= 1 ATR uzaktaysa TP üst sınırı - ind_tpsl = bool(ind_tpsl) - if ind_tpsl: - c_, a_ = A["c"], A["a"] - vpo, vpl, vph = (vp[k].to_numpy(float) for k in ("poc", "val", "vah")) - - def nearest(levels, sign): # sign=+1: seviye fiyatın altında, -1: üstünde - d = np.full(len(c_), np.inf) - for lv in levels: - dd = sign*(c_-lv) - ok = np.isfinite(dd) & (dd > 0) - d = np.where(ok & (dd < d), dd, d) - return d - smin, smax = float(tpsl_smin), float(tpsl_smax) - sd_of = lambda d: np.clip(np.where(np.isfinite(d), d+0.1*a_, 1.5*a_), smin*a_, smax*a_) - cap_of = lambda d: np.where(d >= 1.0*a_, d, np.inf) - lv_l = [st_line, ut_stop, vpl] + ([SM["sw_lo"]] if (smc_only and SM is not None) else []) - lv_s = [st_line, ut_stop, vph] + ([SM["sw_hi"]] if (smc_only and SM is not None) else []) - cap_l, cap_s = [vpo, vph], [vpo, vpl] - if smc_lvl_on: # Order Block + önceki D/W/M high-low: SL referansı ve TP sınırı - lv_l += [SM["ob_sup"], SM["pdl"], SM["pwl"], SM["pml"]] - lv_s += [SM["ob_res"], SM["pdh"], SM["pwh"], SM["pmh"]] - cap_l += [SM["ob_res"], SM["pdh"], SM["pwh"], SM["pmh"]] - cap_s += [SM["ob_sup"], SM["pdl"], SM["pwl"], SM["pml"]] - A["ind_sd"] = {1: sd_of(nearest(lv_l, 1)), - -1: sd_of(nearest(lv_s, -1))} - A["ind_cap"] = {1: cap_of(nearest(cap_l, -1)), - -1: cap_of(nearest(cap_s, 1))} - - # --- K seçimi, kalibrasyon, güncel olasılık - best_k, k_scores = tune_k(dev_X, dev_y, h) - cal_n = min(120, max(45, int(len(dev_X)*0.20))) - fit_end = len(dev_X)-cal_n-h - if fit_end <= 100: - raise ValueError("Kalibrasyon için eğitim verisi yetersiz.") - fit_X, fit_y = dev_X.iloc[:fit_end], dev_y.iloc[:fit_end] - cal_X, cal_y = dev_X.iloc[fit_end+h:], dev_y.iloc[fit_end+h:] - cal_model = pipe(best_k).fit(fit_X, fit_y) - classes_now = cal_model.named_steps["knn"].classes_ - current_x = X_all.iloc[[-1]] - probs = calibrate_current(cal_model.predict_proba(cal_X), cal_y.to_numpy(), - cal_model.predict_proba(current_x), classes_now) - p_down, p_flat, p_up = (float(p) for p in probs) - - if side_mode == "Sadece Long": - side = 1 - elif side_mode == "Sadece Short": - side = -1 - else: - side = 1 if p_up >= p_down else -1 - if ind_only and ind_now != 0 and allow[ind_now]: - side = ind_now - if ldc_only and ldc_now != 0 and allow[ldc_now]: - side = ldc_now - if combo_only and combo_now != 0 and allow[combo_now]: - side = combo_now - if smc_only and smc_now != 0 and allow[smc_now]: - side = smc_now - p_side, p_opp = (p_up, p_down) if side == 1 else (p_down, p_up) - - imp, scaler = cal_model.named_steps["imputer"], cal_model.named_steps["scale"] - nn = NearestNeighbors(n_neighbors=best_k).fit(scaler.transform(imp.transform(fit_X))) - _, idx = nn.kneighbors(scaler.transform(imp.transform(current_x))) - agreement = float((fit_y.iloc[idx[0]].to_numpy() == side).mean()) - if ind_only: # indikatör modunda: son 10 mumda yönün sürekliliği - agreement = float((ind_arr[-10:] == side).mean()) - if ldc_only: - agreement = float((ldc_arr[-10:] == side).mean()) - if combo_only: - agreement = float((combo_arr[-10:] == side).mean()) - if smc_only: - agreement = float((smc_arr[-10:] == side).mean()) - - # --- yön bazlı TP/SL optimizasyonu (yalnızca geliştirme verisi) - current_regime = str(regimes_all.iloc[-1]) - dev_pos = pos_of(dev_X.index) - params, top, regime_used = {}, [], "—" - - def opt_pos(s_): - f_ = list(dev_pos) - if ind_only or ind_confirm: - f_ = [i for i in f_ if ind_arr[i] == s_] - if ldc_on: - f_ = [i for i in f_ if ldc_arr[i] == s_] - if combo_on: - f_ = [i for i in f_ if combo_arr[i] == s_] - if vp_on: - f_ = [i for i in f_ if vp_arr[i] == s_] - if gann_on: - f_ = [i for i in f_ if gann_arr[i] == s_] - if smc_on and not smc_only: - f_ = [i for i in f_ if smc_arr[i] == s_] - if smc_only: - f_ = [i for i in f_ if smc_entry[i] == s_] - return f_ if len(f_) >= 35 else dev_pos - for s in (1, -1): - if allow[s]: - sl_s, rr_s, top_s, used_s = optimize_barriers( - A, opt_pos(s), reg_arr, s, cost, current_regime, h) - params[s] = (sl_s, rr_s) - if s == side: - top, regime_used = top_s, used_s - sl, rr = params[side] - - # --- dokunulmamış OOS bloğu - oos_X = X.iloc[dev_end+h:] - oos_y = y.loc[oos_X.index] - oos_pred = pipe(best_k).fit(dev_X, dev_y).predict(oos_X) - oos_bacc = float(balanced_accuracy_score(oos_y, oos_pred)) - oos_acc = float(accuracy_score(oos_y, oos_pred)) - oos_pos_arr = pos_of(oos_X.index) - if ldc_only: # Lorentzian yönü OOS'ta tahmin yerine geçer - oos_pred = ldc_arr[oos_pos_arr] - oos_bacc = float(balanced_accuracy_score(oos_y, oos_pred)) - oos_acc = float(accuracy_score(oos_y, oos_pred)) - if combo_only: # birleşik sinyal OOS'ta tahmin yerine geçer - oos_pred = combo_arr[oos_pos_arr] - oos_bacc = float(balanced_accuracy_score(oos_y, oos_pred)) - oos_acc = float(accuracy_score(oos_y, oos_pred)) - if ind_only: # indikatör yönü OOS'ta tahmin yerine geçer - oos_pred = ind_arr[oos_pos_arr] - oos_bacc = float(balanced_accuracy_score(oos_y, oos_pred)) - oos_acc = float(accuracy_score(oos_y, oos_pred)) - - if smc_only: # metrik: SMC yön durumu; işlem: yapı kırılımı mumları - oos_pred = smc_arr[oos_pos_arr] - oos_bacc = float(balanced_accuracy_score(oos_y, oos_pred)) - oos_acc = float(accuracy_score(oos_y, oos_pred)) - oos_pred = smc_entry[oos_pos_arr] - rows, busy = [], -1 - for i, pred in zip(oos_pos_arr, oos_pred): - pred = int(pred) - if pred == 0 or not allow[pred]: - continue - if ind_confirm and ind_arr[i] != pred: - continue - if vp_on and vp_arr[i] != pred: - continue - if gann_on and gann_arr[i] != pred: - continue - if smc_on and smc_arr[i] != pred: - continue - if ldc_confirm and ldc_arr[i] != pred: - continue - if combo_on and combo_arr[i] != pred: - continue - if skip_overlap and i <= busy: - continue - sl_p, rr_p = params[pred] - t = barrier_trade(A, i, pred, sl_p, rr_p, h) - if t is None: - continue - ret, j, why = t - net = ret-cost - r = net/max(stop_dist(A, i, pred, sl_p)/A["c"][i], 1e-12) - rows.append((df.index[i], df.index[j], "LONG" if pred == 1 else "SHORT", - A["c"][i], why, net*100, r, reg_arr[i])) - busy = j - trades_df = pd.DataFrame(rows, columns=TRADE_COLS) - R = trades_df.R.to_numpy() if len(trades_df) else np.array([]) - expectancy_r = float(R.mean()) if len(R) else 0.0 - - recent = df.Close.tail(30) - drawdown = float(recent.iloc[-1]/recent.cummax().iloc[-1]-1) - risk_pct, _ = adaptive_risk(min_risk, max_risk, oos_bacc, agreement, - expectancy_r, len(R), drawdown) - curve, dds = simulate_equity(R, risk_pct) - stats = trade_stats(R, curve, dds, trades_df) - mc = risk_path_mc(R, risk_pct) if len(R) else None - - # --- güncel seviyeler ve pozisyon - entry, cur_atr = float(df.Close.iloc[-1]), float(atr_all.iloc[-1]) - if ind_tpsl: - stop_d = float(A["ind_sd"][side][-1]) - target_d = float(min(stop_d*rr, A["ind_cap"][side][-1])) - cl_ = float(A["c"][-1]) - cands = {"Supertrend": float(st_line[-1]), "UT trailing stop": float(ut_stop[-1]), - "VP " + ("VAL" if side == 1 else "VAH"): - float((vp.val if side == 1 else vp.vah).iloc[-1])} - if smc_lvl_on: - kk_ = ("ob_sup", "pdl", "pwl", "pml") if side == 1 else ("ob_res", "pdh", "pwh", "pmh") - for k_, nm_ in zip(kk_, ("SMC OB", "Önceki gün", "Önceki hafta", "Önceki ay")): - cands[nm_] = float(SM[k_][-1]) - if smc_only and SM is not None: - cands["SMC swing " + ("low" if side == 1 else "high")] = float(SM["sw_lo" if side == 1 else "sw_hi"][-1]) - valid = {k_: side*(cl_-v_) for k_, v_ in cands.items() if np.isfinite(v_) and side*(cl_-v_) > 0} - sl_name = min(valid, key=valid.get) if valid else "ATR varsayılan (seviye yok)" - else: - stop_d, target_d = sl*cur_atr, sl*cur_atr*rr - stop = entry-stop_d if side == 1 else entry+stop_d - target = entry+target_d if side == 1 else entry-target_d - - risk_cash = equity*risk_pct/100 - cost_per_unit = stop_d+entry*cost - qty = risk_cash/cost_per_unit if cost_per_unit > 0 else 0.0 - max_notional = equity*float(max_leverage) - capped = qty*entry > max_notional - if capped: - qty = max_notional/entry - if whole_units: - qty = float(np.floor(qty)) - notional = qty*entry - actual_risk = qty*cost_per_unit - margin = notional/max(float(max_leverage), 1) - - # --- işlem kapısı (her kontrol ayrı gösterilir) - checks = [ - ("Yön izni", side_mode, "—", allow[side]), - ("P(yön)", f"{p_side:.1%}", f"≥ {p_min:.0%}", p_side >= p_min), - ("P(yön) − P(karşı)", f"{(p_side-p_opp):.1%}", f"≥ {p_gap:.0%}", (p_side-p_opp) >= p_gap), - ("P(FLAT)", f"{p_flat:.1%}", f"< {flat_max:.0%}", p_flat < flat_max), - ("Komşu yön uyumu", f"{agreement:.1%}", f"≥ {agree_min:.0%}", agreement >= agree_min), - ("OOS balanced acc.", f"{oos_bacc:.3f}", f"≥ {bacc_min:.2f}", oos_bacc >= bacc_min), - ("OOS işlem sayısı", f"{len(R)}", f"≥ {int(min_trades)}", len(R) >= int(min_trades)), - ("OOS expectancy", f"{expectancy_r:.3f}R", "> 0", expectancy_r > 0), - ] - dname = {1: "LONG ↑", -1: "SHORT ↓", 0: "belirsiz"} - ind_check = ("İndikatör yönü (ST + UT aynı)", dname[ind_now], "yönle aynı", ind_now == side) - if ind_only or ldc_only or combo_only or smc_only: - keep = ("Yön izni", "OOS balanced acc.", "OOS işlem sayısı", "OOS expectancy") - if smc_only: - keep = tuple(k_ for k_ in keep if k_ != "OOS balanced acc.") - checks = [c for c in checks if c[0] in keep] - if ind_only or ind_confirm: - checks.insert(1, ind_check) - if ldc_on: - checks.insert(1, ("Lorentzian yönü (ML + kernel)", dname[ldc_now], "yönle aynı", - ldc_now == side)) - if combo_on: - nv = int(sum(1 for v_ in (ind_now, ldc_now, vp_now) if v_ == side)) - no_ = int(sum(1 for v_ in (ind_now, ldc_now, vp_now) if v_ == -side)) - checks.insert(1, ("Birleşik sinyal (ST+UT · Lorentzian · VP)", f"{nv}/3 oy, karşı {no_}", - f"≥ {mv} oy, karşı oy yok", combo_now == side)) - if ind_tpsl: - rr_now = target_d/stop_d if stop_d > 0 else 0.0 - checks.append(("İndikatör R:R (TP/SL)", f"{rr_now:.2f}", f"≥ {float(tpsl_minrr):g}", - rr_now >= float(tpsl_minrr))) - if vp_on: - checks.insert(1, ("Volume Profile (POC/VA konumu)", vp_names[int(vp_arr[-1])], - "yönle aynı", int(vp_arr[-1]) == side)) - if gann_on: - checks.insert(1, ("Gann Box (%50 konumu)", dname[gann_now], "yönle aynı", gann_now == side)) - if smc_on: - checks.insert(1, (f"SMC yapı yönü ({smc_src}" + (" + premium/discount)" if smc_zone else ")"), - dname[smc_now], "yönle aynı", smc_now == side)) - trade_ok = all(c[3] for c in checks) - if trade_ok: - signal = "LONG" if side == 1 else "SHORT" - else: - signal = "WAIT — İŞLEM AÇMA" - qty = notional = margin = actual_risk = 0.0 - gate_md = md_table(["Kontrol", "Değer", "Eşik", "Durum"], - [(n, v, t, "✅" if ok else "❌") for n, v, t, ok in checks]) - - # --- rapor - if stats: - pf = "∞" if np.isinf(stats["pf"]) else f"{stats['pf']:.2f}" - oos_md = md_table(["Ölçüm", "Değer"], [ - ("İşlem sayısı", stats["n"]), - ("Kazanma oranı", f"{stats['win']:.1%}"), - ("Expectancy", f"{stats['exp']:.3f}R"), - ("Profit factor", pf), - (f"Toplam getiri (risk {risk_pct:.2f}%)", f"{stats['ret']:.2%}"), - ("Maks. drawdown", f"{stats['mdd']:.2%}"), - ("Çıkış dağılımı TP / SL / süre", f"{stats['tp']} / {stats['sl']} / {stats['time']}"), - ]) - g = trades_df.groupby("regime").R.agg(["count", "mean", lambda s: (s > 0).mean()]) - reg_md = md_table(["Rejim", "İşlem", "Ort. R", "Kazanma"], - [(r, int(c), f"{m:.3f}", f"{w:.1%}") - for r, (c, m, w) in g.iterrows()]) - else: - oos_md = reg_md = "_OOS işlem yok._" - - mc_text = ( - f"100 işlem · 2.000 bootstrap senaryosu · medyan: {mc['median']:.2%} · " - f"%5 kötü sonuç: {mc['p05']:.2%} · %95 iyi sonuç: {mc['p95']:.2%} · " - f"%5 kuyruk max DD: {mc['dd95']:.2%} · " - f"%50 sermaye altına inme frekansı: {mc['ruin']:.1%}" - if mc else "Monte Carlo için yeterli OOS işlem yok.") - ind_md = md_table(["İndikatör", "Yön", "Seviye"], [ - (f"Supertrend ({int(st_period)}, {float(st_mult):g})", dname[int(st_dir[-1])], fmt_p(float(st_line[-1]))), - (f"UT Bot ({float(ut_key):g}, {int(ut_period)})", dname[int(ut_pos[-1])], fmt_p(float(ut_stop[-1]))), - ("Bileşik (ikisi aynı yönde)", dname[ind_now], "—")]) - if lp is not None: - _, _, poc_n, val_n, vah_n = lp - pos_txt = ("VA üstünde" if entry > vah_n else "VA altında" if entry < val_n - else "VA içinde · POC üstü" if entry > poc_n else "VA içinde · POC altı") - vp_md = md_table(["Seviye", "Fiyat"], [ - ("VAH (Value Area üst)", fmt_p(vah_n)), ("POC (en yüksek hacim)", fmt_p(poc_n)), - ("VAL (Value Area alt)", fmt_p(val_n)), ("Son kapanış", f"{fmt_p(entry)} — {pos_txt}"), - ("VP yön onayı", vp_names[int(vp_arr[-1])])]) - else: - vp_md = "_Profil hesaplanamadı._" - vp_note = (f"Pencere {vp_win} mum · {vp_nb} kutu · Value Area %{vp_vap*100:.0f} · " - f"uzama sınırı {float(vp_ext):g} ATR." - + (" Hacim verisi yok → zaman bazlı profil kullanıldı." if vp_no_vol else "") - + (" Mod Kapalı: yalnızca bilgi amaçlı." if not vp_on else "")) - if SM is not None: - last_ev = [e for e in SM["events"] if not e["internal"]][-1:] or [None] - last_iv = [e for e in SM["events"] if e["internal"]][-1:] or [None] - evtxt = lambda e: "—" if e is None else (f"{e['kind']} {'↑' if e['bull'] else '↓'} · {fmt_p(e['lvl'])} · " - f"{df.index[e['i']].date()}") - tp_, bt_ = float(SM["top"][-1]), float(SM["bot"][-1]) - zpos = (entry-bt_)/(tp_-bt_) if tp_ > bt_ else float("nan") - zname = ("—" if not np.isfinite(zpos) else - "Premium" if zpos >= 0.95 else - "Discount" if zpos <= 0.05 else - "Equilibrium") - obt = lambda b: [o_ for o_ in SM["obs"] if o_["bull"] == b and not o_["internal"]][-1:] - ob_txt = lambda b: (f"{fmt_p(obt(b)[0]['lo'])} – {fmt_p(obt(b)[0]['hi'])}" if obt(b) else "—") - smc_md = md_table(["Bileşen", "Değer"], [ - ("Swing yapı yönü", dname[int(SM["swing_trend"][-1])]), - ("Internal yapı yönü", dname[int(SM["internal_trend"][-1])]), - ("Son swing kırılımı", evtxt(last_ev[0])), ("Son internal kırılımı", evtxt(last_iv[0])), - ("Premium / Discount", f"{zname}" + (f" (%{zpos*100:.0f})" if np.isfinite(zpos) else "")), - ("Strong/Weak High · Low", f"{'Strong' if SM['strong_high'] else 'Weak'} {fmt_p(tp_)} · " - f"{'Strong' if SM['strong_low'] else 'Weak'} {fmt_p(bt_)}"), - ("Aktif swing OB (bull)", ob_txt(True)), ("Aktif swing OB (bear)", ob_txt(False)), - ("Önceki gün H / L", f"{fmt_p(SM['pdh'][-1])} / {fmt_p(SM['pdl'][-1])}"), - ("Önceki hafta H / L", f"{fmt_p(SM['pwh'][-1])} / {fmt_p(SM['pwl'][-1])}"), - ("Önceki ay H / L", f"{fmt_p(SM['pmh'][-1])} / {fmt_p(SM['pml'][-1])}"), - ("Açık FVG sayısı", len(SM["fvg"])), ("SMC yön onayı", dname[smc_now])]) - smc_note = (f"Swing {int(smc_swing_len)} · internal {int(smc_int_len)} mum" - + (" · premium/discount kapısı açık" if smc_zone else "") - + (" · internal confluence filtresi (LuxAlgo Pine koşulları)" if smc_conf else "") - + " · BOS/CHoCH yalnızca pivot doğrulandıktan ve kapanış seviyesi kırdıktan sonra sayılır" - + f" · OB filtresi: {smc_obf}" - + (" · OB + önceki D/W/M seviyeleri SL/TP'de" if smc_lvl_on else "") - + (" · Mod Kapalı: yalnızca bilgi/grafik." if not smc_on else "") + ".") - else: - smc_md, smc_note = "_Mod kapalı._", "" - if GB is not None: - gpos = gann_pos[-1] - gann_md = md_table(["Seviye", "Fiyat"], - [(f"{p:.3f}" + (" (orta)" if p == 0.5 else ""), fmt_p(y)) for p, y in sorted(GB["levels"].items(), reverse=True)] - + [("Son kapanış", f"{fmt_p(entry)} — kutu içi konum %{gpos*100:.0f}" if np.isfinite(gpos) else fmt_p(entry)), - ("Kutu yönü", "yükseliş (düşük → yüksek)" if GB["up"] else "düşüş (yüksek → düşük)"), - ("Gann yön onayı", dname[gann_now])]) - gann_note = (f"Pencere {int(gann_lb)} mum · yüksek {GB['hi_date'].date()} · düşük {GB['lo_date'].date()}" - + (" · yön çevrildi (reverse)" if gann_rev else "") - + (" · Mod Kapalı: yalnızca bilgi amaçlı." if not gann_on else "") + ".") - else: - gann_md, gann_note = "_Kutu hesaplanamadı._", "" - if LD is not None: - fl = lambda on, arr: "kapalı" if not on else ("✅" if bool(arr[-1]) else "❌") - ldc_md = md_table(["Bileşen", "Değer"], [ - ("Lorentzian yönü (ML + kernel)", dname[ldc_now]), - ("ML sinyali (filtreli)", dname[int(LD["signal"][-1])]), - ("ML tahmin skoru", f"{int(LD['pred'][-1]):+d} / ±{int(ldc_k)}"), - ("Kernel tahmini · eğim", f"{fmt_p(float(LD['yhat'][-1]))} · " - f"{'↑' if LD['yhat'][-1] > LD['yhat'][-2] else '↓'}"), - ("Volatilite / Rejim / ADX filtresi", - f"{fl(ldc_use_vol, LD['vol'])} / {fl(ldc_use_regime, LD['reg'])} / {fl(ldc_use_adx, LD['adx'])}")]) - ldc_note = (f"{int(ldc_k)} komşu · en fazla {int(ldc_back)} geçmiş mum · kernel " - f"({int(ldc_kern_lb)}, {float(ldc_kern_rw):g}) · rejim eşiği {float(ldc_regime):g}.") - else: - ldc_md, ldc_note = "_Mod kapalı._", "" - if combo_on: - combo_md = md_table(["Kaynak", "Oy"], [ - ("Supertrend + UT Bot", dname[ind_now]), ("Lorentzian (ML + kernel)", dname[ldc_now]), - ("Volume Profile (POC/VA)", dname[vp_now]), - ("**Birleşik sinyal**", f"**{dname[combo_now]}** (en az {mv} oy, karşı oy yok)")]) - else: - combo_md = "_SMC-only modunda devre dışı._" if smc_only else "_Mod kapalı._" - if ind_tpsl: - capped = target_d < stop_d*rr-1e-12 - tp_title = "indikatörlerden türetildi" - tpsl_note = (f"SL: en yakın invalidasyon seviyesi — **{sl_name}** (+0,1 ATR tampon, " - f"{float(tpsl_smin):g}–{float(tpsl_smax):g} ATR aralığına sıkıştırılır). " - f"TP: {rr:.2f}R katı" - + (", önündeki VA/POC/SMC OB/MTF seviyesinde sınırlandı" if capped else "") - + ". Optimize edilen tek parametre R katıdır.\n\n") - else: - tp_title, tpsl_note = "optimize", "" - k_text = " | ".join(f"K={k}: {s:.3f}" for s, k in sorted(k_scores, reverse=True)) or "—" - overlap_note = ("tek pozisyon (çakışma yok)" if skip_overlap - else "çakışan sinyaller bağımsız işlem sayıldı") - report = f""" -# {signal} - -**Son kapanmış mum:** {df.index[-1].date()} · **{ticker} referans fiyatı:** {fmt_p(entry)} · **Ufuk:** {h} gün - -## Kapı kontrolleri -{gate_md} - -## {ticker} — Kalibre edilmiş KNN -| Ölçüm | Değer | -|---|---:| -| P(UP) / P(FLAT) / P(DOWN) | {p_up:.1%} / {p_flat:.1%} / {p_down:.1%} | -| Seçilen K | {best_k} | -| OOS accuracy | {oos_acc:.3f} | -| Güncel rejim | {current_regime} | - -**K seçim skorları:** {k_text} - -## İndikatörler — mod: {ind_mode} -{ind_md} - -## Birleşik sinyal — mod: {combo_mode} -{combo_md} - -## Lorentzian Classification — mod: {ldc_mode} -{ldc_md} - -{ldc_note} - -## Volume Profile — mod: {vp_mode} -{vp_md} - -{vp_note} - -## Smart Money Concepts (LuxAlgo) — mod: {smc_mode} -{smc_md} - -{smc_note} - -## Gann Box (Zeiierman) — mod: {gann_mode} -{gann_md} - -{gann_note} - -## TP / SL ({'LONG' if side == 1 else 'SHORT'} için {tp_title}) -| Parametre | Değer | -|---|---:| -| SL | {stop_d/cur_atr:.2f} ATR | -| TP | {target_d/stop_d:.2f}R | -| Stop-loss | {fmt_p(stop)} | -| Take-profit | {fmt_p(target)} | -| ATR(14) | {fmt_p(cur_atr)} | - -{tpsl_note}TP/SL optimizasyon rejimi: {regime_used}. Yeterli rejim örneği yoksa tüm geliştirme dönemine geri dönülür. - -## OOS backtest ({overlap_note}) -{oos_md} - -**Rejim bazlı OOS:** - -{reg_md} - -## Risk-bütçeli pozisyon ({ticker}) -| Parametre | Değer | -|---|---:| -| Hesap büyüklüğü | ${equity:,.2f} | -| Adaptif risk | {risk_pct:.3f}% | -| Risk bütçesi | ${risk_cash:,.2f} | -| Varlık miktarı (birim) | {qty:.8f} | -| Nominal pozisyon | ${notional:,.2f} | -| Tahmini teminat | ${margin:,.2f} | -| Tahmini stop kaybı + maliyet | ${actual_risk:,.2f} | -| Kaldıraç sınırı | {"Uygulandı" if capped else "Uygulanmadı"} | -| Son 30 günlük kapanış DD | {drawdown:.2%} | - -## Monte Carlo -{mc_text} -""" - top_df = pd.DataFrame(top, columns=["score", "SL_ATR", "TP_R", "samples", - "mean_net_return", "win_rate"]) - if ind_tpsl and not top_df.empty: - top_df["SL_ATR"] = "indikatör" - if not top_df.empty: - top_df["score"] = top_df.score.map(lambda v: f"{v:.5f}") - top_df["mean_net_return"] = top_df.mean_net_return.map(lambda v: f"{v:.3%}") - top_df["win_rate"] = top_df.win_rate.map(lambda v: f"{v:.1%}") - - csv_path = None - if len(trades_df): - safe = re.sub(r"[^A-Za-z0-9_-]", "_", ticker) - csv_path = os.path.join(tempfile.gettempdir(), f"goshawk_{safe}_oos_trades.csv") - trades_df.to_csv(csv_path, index=False) - - price_fig = price_figure(df, ticker, trades_df, trade_ok, stop, target, - ind=(st_line, st_dir, ut_stop), - vp=vp if (vp_on or combo_on) else None, vp_prof=lp, - ldc=LD, combo=combo_arr if combo_on else None, - gann=GB, gann_fan=bool(gann_fan_on), gann_extend=bool(gann_ext_on), smc=SM) - eq_fig = equity_figure(curve, dds) if len(R) else None - pf_fig = pf_gauge(stats["pf"], stats["n"]) if stats else None - mc_fig = mc_figure(mc) if mc else None - - disclaimer = ( - "**Araştırma prototipi; otomatik emir göndermez.** KNN olasılık kalibrasyonu tek bir " - "tarihsel kalibrasyon dilimine dayanır ve küçük örneklerde ham olasılığa geri döner. " - "TP/SL optimizasyonu geliştirme döneminde ve yön bazında yapılır; son dönem OOS olarak " - "ayrılır. Rejim etiketleri geliştirme verisinin volatilite kantillerine dayanır; canlı " - "dağılım değişebilir. Adaptif risk yüzdesi OOS metriklerinden türetildiği için OOS " - "özsermaye eğrisi iyimser olabilir. Günlük mumda SL ve TP aynı mumda görülürse SL " - "varsayılır. Monte Carlo, OOS R-multiple serisini bootstrap eder; bağımsızlık varsayımı " - "seri korelasyonunu ve rejim değişimini tam modellemez. Pozisyon hesabı spot miktar " - "varsayar; CFD lot sözleşmesi, spread, funding, swap, komisyon, minimum lot, marjin ve " - "broker fiyatı farklı olabilir. Gap ve kayma kaybı planlanan riskin üstüne çıkarabilir. " - "Supertrend ve UT Bot, TradingView formüllerinin Python portudur; ATR başlangıcı ve veri kaynağı yüzünden TV değerlerinden küçük sapmalar olabilir. Birleşik sinyal; ST+UT, Lorentzian ve Volume Profile oylarının birleşimidir. İndikatör bazlı TP/SL, en yakın ST/UT/VP seviyesini invalidasyon sayar ve ATR aralığına sıkıştırır. Lorentzian Classification, jdehorty'nin TradingView göstergesinin Python portudur (ANN komşu seçimi, filtreler ve kernel regresyon); Pine kaynağıyla birebir aynı sonuç vermeyebilir ve yalnızca günlük mumda çalışır. Volume Profile, günlük OHLCV'den (her mumun hacmi high–low aralığına eşit dağıtılarak) yaklaşık hesaplanır; TradingView VPVR'nin alt-zaman dilimi verisiyle birebir aynı olmaz. Gann Box, Zeiierman göstergesinin açıklamasına göre yazılmış bir Python portudur (Pine kaynağı birebir alınmadı); kutu son N mumun en yüksek/en düşük noktasından kurulur, Gann Fan 1x1 köşegeni ve katlarıyla çizilir, Gann Curves/Angles eklenmedi. Onay filtresi, kapanışın kutu %50 çizgisine göre konumuna bakar ve geçmişe bakan (kayan) pencere kullanır. SMC, LuxAlgo göstergesinin açıklamasına göre yazılmış bir Python portudur (Pine kaynağı birebir değil): swing/internal yapı, BOS/CHoCH, order block (high/low mitigasyonu), EQH/EQL, FVG ve premium/discount hesaplanır; mum boyama yoktur; internal confluence ve OB filtresi, Pine formüllerinin hafızadan yazılmış karşılığıdır. OB ve önceki gün/hafta/ay high-low seviyeleri, «TP/SL'i indikatörlerden türet» açıkken en yakın seviye SL referansı ve TP sınırı olarak backtest'e girer. Sadece SMC modunda işlemler yalnızca seçilen yapıdaki (swing/internal) BOS/CHoCH kırılım mumunda, kırılım yönünde açılır; SL en yakın ST/UT/VP/SMC swing seviyesinden, TP R katından türetilir; diğer sinyal modları devre dışıdır. Pivotlar pencere kadar mum sonra doğrulanır, bu yüzden sinyal geç gelir. SMC kavramlarının istatistiksel geçerliliği kanıtlı değildir; OOS sonuçlarıyla doğrulayın. Canlı hesap öncesi paper trading ve broker bazlı doğrulama zorunludur." - ) - return (report, prob_figure(p_down, p_flat, p_up), price_fig, eq_fig, pf_fig, mc_fig, - top_df, csv_path, disclaimer) - - except Exception as exc: - return (f"# Hata\n{type(exc).__name__}: {exc}", None, None, None, None, None, - pd.DataFrame(), None, - "Veri bağlantısını ve parametreleri kontrol edin.") - - -# ---------------------------------------------------------------- arayüz -with gr.Blocks(title=APP_TITLE, theme=gr.themes.Soft()) as demo: - gr.Markdown( - "# GOSHAWK — Multi-Asset Adaptive Risk Engine v5\n" - "Kalibre KNN · adaptif K · yön bazlı TP/SL · bağımsız OOS backtest · " - "R-multiple Monte Carlo · drawdown bazlı risk · kapı kontrolleri." - ) + ohlcv, deep, ind = asyncio.run(run_async(symbol, timeframe, exchange, horizon, limit)) + df = to_df(ohlcv) + res = predict(df, int(horizon)) + except Exception as e: # noqa: BLE001 + return f"**Hata:** {e}", None, "" + + p = res["p_up"] + signal = "LONG" if p > 0.55 else "SHORT" if p < 0.45 else "NÖTR" + edge = "zayıf (baseline'a yakın)" if res["acc"] < res["baseline"] + 0.02 else "var" + md = f"""### {symbol} · {timeframe} · {int(horizon)} bar sonrası +| | | +|---|---| +| Son fiyat | **{res['price']:.4f}** | +| Yükseliş olasılığı | **{p:.1%}** → **{signal}** | +| Tahmini merkez | {res['center']:.4f} | +| Aralık (ATR·√h) | {res['low']:.4f} – {res['high']:.4f} | +| Test doğruluğu | {res['acc']:.1%} (baseline {res['baseline']:.1%}) → model edge: {edge} | + +*Eğitim/araştırma amaçlıdır; yatırım tavsiyesi değildir.*""" + + # Grafik + tail = df.tail(150) + step = df["ts"].diff().median() + fx = df["ts"].iloc[-1] + step * int(horizon) + fig = go.Figure(go.Candlestick(x=tail["ts"], open=tail["open"], high=tail["high"], + low=tail["low"], close=tail["close"], name="Fiyat")) + fig.add_trace(go.Scatter(x=[fx], y=[res["center"]], mode="markers", name="Tahmin", + marker=dict(size=12, symbol="diamond"), + error_y=dict(type="data", symmetric=False, + array=[res["high"] - res["center"]], + arrayminus=[res["center"] - res["low"]]))) + fig.update_layout(height=450, xaxis_rangeslider_visible=False, margin=dict(l=10, r=10, t=30, b=10)) + + raw = json.dumps({"deep_analyze": deep, "compute_indicators": ind}, + indent=2, ensure_ascii=False, default=str) + return md, fig, raw + + +with gr.Blocks(title="Tickscope Tahmin") as demo: + gr.Markdown("## Tickscope MCP → ML Tahmin") with gr.Row(): - with gr.Column(): - ticker = gr.Dropdown(ASSETS, value="BTC-USD", allow_custom_value=True, - label="Varlık (listeden seç ya da Yahoo sembolü yaz)") - period = gr.Dropdown(["2y", "5y", "10y", "max"], value="5y", label="Veri geçmişi") - horizon = gr.Dropdown([3, 5, 10], value=5, label="Tahmin ufku (gün)") - side_mode = gr.Radio(SIDE_MODES, value="Long + Short", label="İzin verilen yön") - ind_mode = gr.Radio(IND_MODES, value="Kapalı", label="İndikatör modu (Supertrend + UT Bot)") - vp_mode = gr.Radio(VP_MODES, value="Kapalı", label="Volume Profile modu") - ldc_mode = gr.Radio(LDC_MODES, value="Kapalı", label="Lorentzian Classification modu") - combo_mode = gr.Radio(COMBO_MODES, value=COMBO_MODES[2], - label="Birleşik sinyal (ST+UT · Lorentzian · Volume Profile oylaması)") - gann_mode = gr.Radio(GANN_MODES, value=GANN_MODES[0], label="Gann Box modu (Zeiierman)") - smc_mode = gr.Radio(SMC_MODES, value=SMC_MODES[0], label="Smart Money Concepts modu (LuxAlgo)") - ind_tpsl = gr.Checkbox(value=True, label="TP/SL'i indikatörlerden türet (ST / UT / VP seviyeleri)") - train_window = gr.Slider(300, 1500, value=900, step=50, label="Maksimum eğitim penceresi") - with gr.Column(): - equity = gr.Number(value=10000, label="Hesap büyüklüğü (USD)") - max_leverage = gr.Slider(1, 10, value=3, step=1, label="Azami kaldıraç") - min_risk = gr.Slider(0.05, 0.5, value=0.10, step=0.05, label="Minimum risk (%)") - max_risk = gr.Slider(0.25, 1.5, value=0.75, step=0.05, label="Maksimum risk (%)") - fee = gr.Number(value=0.05, label="Tek yön komisyon (%)") - slip = gr.Number(value=0.03, label="Tek yön slippage (%)") - with gr.Accordion("Gelişmiş: işlem kapısı eşikleri ve simülasyon", open=False): - with gr.Row(): - p_min = gr.Slider(0.34, 0.70, value=0.43, step=0.01, label="Min. P(yön)") - p_gap = gr.Slider(0.0, 0.40, value=0.12, step=0.01, label="Min. P(yön) − P(karşı)") - flat_max = gr.Slider(0.30, 0.90, value=0.60, step=0.01, label="Maks. P(FLAT)") - with gr.Row(): - agree_min = gr.Slider(0.30, 0.90, value=0.55, step=0.01, label="Min. komşu uyumu") - bacc_min = gr.Slider(0.30, 0.60, value=0.36, step=0.01, label="Min. OOS balanced acc.") - min_trades = gr.Slider(5, 50, value=15, step=1, label="Min. OOS işlem sayısı") - with gr.Row(): - st_period = gr.Slider(5, 30, value=10, step=1, label="Supertrend ATR periyodu") - st_mult = gr.Slider(1.0, 6.0, value=3.0, step=0.1, label="Supertrend çarpanı") - ut_key = gr.Slider(0.5, 4.0, value=1.0, step=0.1, label="UT Bot key value") - ut_period = gr.Slider(5, 30, value=10, step=1, label="UT Bot ATR periyodu") - with gr.Row(): - combo_votes = gr.Slider(1, 3, value=2, step=1, label="Birleşik sinyal: min. aynı yön oyu (karşı oy yok)") - tpsl_smin = gr.Slider(0.3, 2.0, value=0.8, step=0.1, label="İndikatör SL alt sınır (ATR)") - tpsl_smax = gr.Slider(1.5, 6.0, value=3.0, step=0.1, label="İndikatör SL üst sınır (ATR)") - tpsl_minrr = gr.Slider(0.5, 3.0, value=1.0, step=0.1, label="Min. R:R (kapı)") - with gr.Row(): - ldc_k = gr.Slider(1, 30, value=8, step=1, label="Lorentzian komşu sayısı") - ldc_back = gr.Slider(300, 3000, value=2000, step=100, label="Lorentzian maks. geçmiş mum") - ldc_regime = gr.Slider(-2.0, 2.0, value=-0.1, step=0.1, label="Rejim eşiği") - with gr.Row(): - ldc_kern_lb = gr.Slider(3, 50, value=8, step=1, label="Kernel lookback") - ldc_kern_rw = gr.Slider(0.25, 25, value=8, step=0.25, label="Kernel göreli ağırlık") - with gr.Row(): - ldc_use_vol = gr.Checkbox(value=True, label="Volatilite filtresi") - ldc_use_regime = gr.Checkbox(value=True, label="Rejim filtresi") - ldc_use_adx = gr.Checkbox(value=False, label="ADX filtresi (eşik 20)") - ldc_kernel = gr.Checkbox(value=True, label="Kernel ile onayla") - ldc_exact = gr.Checkbox(value=True, label="Gerçek en yakın komşu (kapalı: Pine yaklaşık-NN)") - with gr.Row(): - vp_window = gr.Slider(20, 250, value=60, step=5, label="Volume Profile penceresi (mum)") - vp_bins = gr.Slider(12, 60, value=24, step=2, label="Fiyat kutusu sayısı") - vp_va = gr.Slider(0.50, 0.90, value=0.70, step=0.05, label="Value Area oranı") - vp_ext = gr.Slider(0.5, 4.0, value=1.5, step=0.1, label="Maks. uzama (ATR) — kovalamayı engeller") - with gr.Row(): - gann_lb = gr.Slider(30, 150, value=150, step=5, label="Gann Box penceresi (mum)") - gann_fan_on = gr.Checkbox(value=True, label="Gann Fan göster") - gann_ext_on = gr.Checkbox(value=True, label="Kutuyu geleceğe uzat (Extend)") - gann_rev = gr.Checkbox(value=False, label="Reverse (yönü çevir)") - with gr.Row(): - smc_src = gr.Radio(SMC_SRC, value=SMC_SRC[0], label="SMC yön kaynağı") - smc_swing_len = gr.Slider(10, 100, value=50, step=5, label="SMC swing yapı uzunluğu") - smc_int_len = gr.Slider(3, 15, value=5, step=1, label="SMC internal yapı uzunluğu") - smc_zone = gr.Checkbox(value=False, label="Premium/Discount kapısı (long=discount, short=premium)") - smc_draw = gr.Checkbox(value=True, label="SMC katmanlarını grafikte göster") - with gr.Row(): - smc_conf = gr.Checkbox(value=False, label="Internal confluence filtresi (zayıf kırılımları ele)") - smc_obf = gr.Radio(SMC_OBF, value=SMC_OBF[0], label="Order Block volatilite filtresi") - smc_lvl = gr.Checkbox(value=True, label="SMC seviyeleri TP/SL'de kullan (Order Block + önceki gün/hafta/ay H-L)") - with gr.Row(): - skip_overlap = gr.Checkbox(value=False, label="Açık pozisyon varken yeni sinyali atla (tek pozisyon)") - whole_units = gr.Checkbox(value=False, label="Miktarı tam sayıya yuvarla (hisse)") - run = gr.Button("Öğren, optimize et ve test et", variant="primary") - - with gr.Tabs(): - with gr.Tab("Sinyal"): - output = gr.Markdown() - prob_plot = gr.Plot() - with gr.Tab("Grafik"): - chart = gr.Plot() - with gr.Tab("OOS backtest"): - pf_plot = gr.Plot() - equity_plot = gr.Plot() - with gr.Tab("Monte Carlo"): - mc_plot = gr.Plot() - with gr.Tab("TP/SL optimizasyon"): - optimization = gr.Dataframe(label="En iyi TP/SL kombinasyonları", interactive=False) - with gr.Tab("İşlem kaydı"): - csv_file = gr.File(label="OOS işlemleri (CSV)", interactive=False) - disclaimer = gr.Markdown() - - run.click( - run_engine, - inputs=[ticker, period, horizon, side_mode, equity, min_risk, max_risk, - max_leverage, fee, slip, train_window, - p_min, p_gap, flat_max, agree_min, bacc_min, min_trades, - skip_overlap, whole_units, ind_mode, st_period, st_mult, ut_key, ut_period, - vp_mode, vp_window, vp_bins, vp_va, vp_ext, - ldc_mode, ldc_k, ldc_back, ldc_regime, ldc_kern_lb, ldc_kern_rw, - ldc_use_vol, ldc_use_regime, ldc_use_adx, ldc_kernel, ldc_exact, - combo_mode, combo_votes, ind_tpsl, tpsl_smin, tpsl_smax, tpsl_minrr, - gann_mode, gann_lb, gann_fan_on, gann_ext_on, gann_rev, - smc_mode, smc_src, smc_swing_len, smc_int_len, smc_zone, smc_draw, - smc_conf, smc_obf, smc_lvl], - outputs=[output, prob_plot, chart, equity_plot, pf_plot, mc_plot, optimization, - csv_file, disclaimer], - ) + symbol = gr.Textbox("BTC/USDT", label="Sembol") + exchange = gr.Dropdown(["binance", "bybit", "okx"], value="binance", label="Borsa") + timeframe = gr.Dropdown(["5m", "15m", "1h", "4h", "1d"], value="1h", label="Timeframe") + with gr.Row(): + horizon = gr.Slider(1, 24, value=4, step=1, label="Tahmin ufku (bar)") + limit = gr.Slider(300, 1000, value=1000, step=100, label="Mum sayısı") + btn = gr.Button("Tahmin et", variant="primary") + out_md = gr.Markdown() + out_plot = gr.Plot() + with gr.Accordion("Tickscope ham çıktı (deep_analyze / indicators)", open=False): + out_raw = gr.Code(language="json") + btn.click(run, [symbol, timeframe, exchange, horizon, limit], [out_md, out_plot, out_raw]) if __name__ == "__main__": - demo.launch(server_name="0.0.0.0", server_port=7860) + demo.launch()