GoshawkVortexAI commited on
Commit
a138dad
·
verified ·
1 Parent(s): a719d79

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +1768 -0
app.py ADDED
@@ -0,0 +1,1768 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import warnings
2
+ warnings.filterwarnings("ignore")
3
+
4
+ import os
5
+ import re
6
+ import time
7
+ import tempfile
8
+
9
+ import numpy as np
10
+ import pandas as pd
11
+ import yfinance as yf
12
+ import gradio as gr
13
+ import plotly.graph_objects as go
14
+ from plotly.subplots import make_subplots
15
+
16
+ from sklearn.impute import SimpleImputer
17
+ from sklearn.preprocessing import StandardScaler
18
+ from sklearn.pipeline import Pipeline
19
+ from sklearn.neighbors import KNeighborsClassifier, NearestNeighbors
20
+ from sklearn.isotonic import IsotonicRegression
21
+ from sklearn.metrics import balanced_accuracy_score, accuracy_score
22
+
23
+ APP_TITLE = "Goshawk Adaptive Risk Engine v5"
24
+ ASSETS = ["BTC-USD", "ETH-USD", "SOL-USD", "AAPL", "MSFT", "NVDA", "SPY", "QQQ",
25
+ "GC=F", "EURUSD=X"]
26
+ K_GRID = [5, 7, 9, 11, 15, 21]
27
+ SL_GRID = [0.8, 1.0, 1.2, 1.5, 1.8, 2.0, 2.5, 3.0]
28
+ RR_GRID = [1.0, 1.25, 1.5, 2.0, 2.5, 3.0, 4.0]
29
+ CLASSES = [-1, 0, 1]
30
+ SIDE_MODES = ["Long + Short", "Sadece Long", "Sadece Short"]
31
+ IND_MODES = ["Kapalı", "Onay filtresi (KNN + ST/UT)", "Sadece indikatör"]
32
+ COMBO_MODES = ["Kapalı", "Onay filtresi (KNN + birleşik)", "Sadece birleşik sinyal"]
33
+ LDC_MODES = ["Kapalı", "Onay filtresi (Lorentzian)", "Sadece Lorentzian"]
34
+ VP_MODES = ["Kapalı", "Onay filtresi (POC / Value Area)", "KNN özelliği + onay filtresi"]
35
+ GANN_MODES = ["Kapalı", "Onay filtresi (Gann Box %50)"]
36
+ SMC_MODES = ["Kapalı", "Onay filtresi (SMC yapı yönü)", "Sadece SMC (yapı kırılımı girişi)"]
37
+ SMC_SRC = ["Swing yapı", "Internal yapı"]
38
+ SMC_OBF = ["Atr", "Cumulative Mean Range"]
39
+ GANN_LEVELS = [0.0, 0.236, 0.382, 0.5, 0.618, 0.786, 1.0]
40
+ GANN_FAN = [(8, "8x1"), (4, "4x1"), (3, "3x1"), (2, "2x1"), (1, "1x1"),
41
+ (1/2, "1x2"), (1/3, "1x3"), (1/4, "1x4"), (1/8, "1x8")]
42
+
43
+ _CACHE = {}
44
+ CACHE_TTL = 600 # saniye; Yahoo'yu gereksiz yere tekrar çağırmamak için
45
+
46
+
47
+ # ---------------------------------------------------------------- veri / özellik
48
+ def get_market_data(ticker, period):
49
+ key, now = (ticker, period), time.time()
50
+ if key in _CACHE and now - _CACHE[key][0] < CACHE_TTL:
51
+ return _CACHE[key][1].copy()
52
+ df = yf.download(ticker, period=period, interval="1d",
53
+ auto_adjust=True, progress=False)
54
+ if df is None or df.empty:
55
+ raise ValueError(f"Yahoo Finance {ticker} verisi döndürmedi.")
56
+ if isinstance(df.columns, pd.MultiIndex):
57
+ df.columns = df.columns.get_level_values(0)
58
+ df = df[["Open", "High", "Low", "Close", "Volume"]].dropna().copy()
59
+ df.index = pd.to_datetime(df.index)
60
+ _CACHE[key] = (now, df)
61
+ return df.copy()
62
+
63
+
64
+ def calc_atr(df, n=14):
65
+ pc = df.Close.shift(1)
66
+ tr = pd.concat([(df.High - df.Low), (df.High - pc).abs(),
67
+ (df.Low - pc).abs()], axis=1).max(axis=1)
68
+ return tr.ewm(alpha=1/n, adjust=False).mean()
69
+
70
+
71
+ def calc_rsi(s, n=14):
72
+ d = s.diff()
73
+ up = d.clip(lower=0).ewm(alpha=1/n, adjust=False).mean()
74
+ dn = (-d.clip(upper=0)).ewm(alpha=1/n, adjust=False).mean()
75
+ rs = up / dn.replace(0, np.nan)
76
+ return 100 - 100/(1+rs)
77
+
78
+
79
+ def make_features(df, vp=None):
80
+ c, v = df.Close, df.Volume
81
+ x = pd.DataFrame(index=df.index)
82
+ for n in [1, 2, 3, 5, 10, 20, 50]:
83
+ x[f"ret_{n}"] = c.pct_change(n)
84
+ for n in [5, 10, 20, 50]:
85
+ x[f"ema_gap_{n}"] = c/c.ewm(span=n, adjust=False).mean()-1
86
+ x["rsi_14"] = calc_rsi(c)/100
87
+ atr = calc_atr(df)
88
+ x["atr_pct"] = atr/c
89
+ x["range_pct"] = (df.High-df.Low)/c
90
+ x["body_pct"] = (df.Close-df.Open)/df.Open
91
+ daily_ret = c.pct_change()
92
+ x["vol_10"] = daily_ret.rolling(10).std()
93
+ x["vol_20"] = daily_ret.rolling(20).std()
94
+ x["volume_z20"] = (v-v.rolling(20).mean())/v.rolling(20).std().replace(0, np.nan)
95
+ x["volume_change"] = v.pct_change()
96
+ macd = c.ewm(span=12, adjust=False).mean()-c.ewm(span=26, adjust=False).mean()
97
+ x["macd_pct"] = macd/c
98
+ # v5: fiyat kanalı konumu ve Bollinger z-skoru
99
+ x["dist_hi_20"] = c/df.High.rolling(20).max()-1
100
+ x["dist_lo_20"] = c/df.Low.rolling(20).min()-1
101
+ x["bb_z20"] = (c-c.rolling(20).mean())/c.rolling(20).std().replace(0, np.nan)
102
+ if vp is not None: # Volume Profile özellikleri (ileriye bakmaz)
103
+ x["vp_poc_gap"] = c/vp["poc"]-1
104
+ x["vp_va_pos"] = (c-vp["val"])/(vp["vah"]-vp["val"]).replace(0, np.nan)
105
+ x["vp_va_width"] = (vp["vah"]-vp["val"])/c
106
+ return x.replace([np.inf, -np.inf], np.nan), atr
107
+
108
+
109
+ def supertrend(df, period=10, mult=3.0):
110
+ """TradingView ta.supertrend portu. dir: +1 yükseliş, -1 düşüş."""
111
+ atr = calc_atr(df, period).to_numpy(float)
112
+ h, l, c = df.High.to_numpy(float), df.Low.to_numpy(float), df.Close.to_numpy(float)
113
+ hl2 = (h+l)/2
114
+ n = len(c)
115
+ up, dn = hl2-mult*atr, hl2+mult*atr
116
+ trend = np.ones(n, dtype=int)
117
+ for i in range(1, n):
118
+ if c[i-1] > up[i-1]:
119
+ up[i] = max(up[i], up[i-1])
120
+ if c[i-1] < dn[i-1]:
121
+ dn[i] = min(dn[i], dn[i-1])
122
+ trend[i] = trend[i-1]
123
+ if trend[i-1] == -1 and c[i] > dn[i-1]:
124
+ trend[i] = 1
125
+ elif trend[i-1] == 1 and c[i] < up[i-1]:
126
+ trend[i] = -1
127
+ return trend, np.where(trend == 1, up, dn)
128
+
129
+
130
+ def ut_bot(df, key=1.0, period=10):
131
+ """UT Bot Alerts (QuantNomad) portu. pos: +1 al, -1 sat, 0 henüz sinyal yok."""
132
+ c = df.Close.to_numpy(float)
133
+ nloss = key*calc_atr(df, period).to_numpy(float)
134
+ n = len(c)
135
+ stop, pos = np.zeros(n), np.zeros(n, dtype=int)
136
+ stop[0] = c[0]-nloss[0]
137
+ for i in range(1, n):
138
+ prev = stop[i-1]
139
+ if c[i] > prev and c[i-1] > prev:
140
+ stop[i] = max(prev, c[i]-nloss[i])
141
+ elif c[i] < prev and c[i-1] < prev:
142
+ stop[i] = min(prev, c[i]+nloss[i])
143
+ elif c[i] > prev:
144
+ stop[i] = c[i]-nloss[i]
145
+ else:
146
+ stop[i] = c[i]+nloss[i]
147
+ if c[i-1] < prev and c[i] > prev:
148
+ pos[i] = 1
149
+ elif c[i-1] > prev and c[i] < prev:
150
+ pos[i] = -1
151
+ else:
152
+ pos[i] = pos[i-1]
153
+ return pos, stop
154
+
155
+
156
+
157
+
158
+ # ------------------------------------------- Smart Money Concepts (LuxAlgo) Python portu
159
+ # Based on LuxAlgo Smart Money Concepts; attribution retained.
160
+ # Original license: CC BY-NC-SA 4.0 — https://creativecommons.org/licenses/by-nc-sa/4.0/
161
+ def mtf_prev_levels(df):
162
+ """Önceki gün / hafta / ay high-low (tamamlanmış dönem; ileriye bakma yok), mum bazında dizi."""
163
+ out = {}
164
+ for key, rule in (("d", "D"), ("w", "W"), ("m", "M")):
165
+ per = df.index.to_period(rule)
166
+ hi = df.High.groupby(per).max().shift(1)
167
+ lo = df.Low.groupby(per).min().shift(1)
168
+ out["p"+key+"h"] = hi.reindex(per).to_numpy(float)
169
+ out["p"+key+"l"] = lo.reindex(per).to_numpy(float)
170
+ return out
171
+
172
+
173
+ def _smc_pivots(h, l, size):
174
+ """Pine leg(size) mantığı: pivotlar `size` mum sonra doğrulanır (ileriye bakma yok).
175
+ Dönüş: {doğrulama_mumu: [("H"|"L", seviye, pivot_mumu)]}"""
176
+ n = len(h)
177
+ hs, ls = pd.Series(h), pd.Series(l)
178
+ hmax = hs.rolling(size).max().to_numpy(float)
179
+ lmin = ls.rolling(size).min().to_numpy(float)
180
+ out, leg = {}, 0
181
+ for i in range(size, n):
182
+ new_leg = leg
183
+ if h[i-size] > hmax[i]:
184
+ new_leg = 0
185
+ elif l[i-size] < lmin[i]:
186
+ new_leg = 1
187
+ if new_leg != leg:
188
+ out.setdefault(i, []).append(("L", float(l[i-size]), i-size) if new_leg == 1
189
+ else ("H", float(h[i-size]), i-size))
190
+ leg = new_leg
191
+ return out
192
+
193
+
194
+ def smc_analyze(df, swing_len=50, int_len=5, eq_len=3, eq_thr=0.1, ob_keep=5,
195
+ confluence=False, ob_filter="Atr"):
196
+ """LuxAlgo 'Smart Money Concepts' göstergesinin Python portu: swing/internal yapı (BOS, CHoCH),
197
+ order block, EQH/EQL, Fair Value Gap, premium/discount ve strong/weak high-low.
198
+ Pine kaynağından birebir değil; her mum yalnızca kendisine kadarki veriyi kullanır."""
199
+ n = len(df)
200
+ o, h, l, c = (df[k].to_numpy(float) for k in ("Open", "High", "Low", "Close"))
201
+ atr = calc_atr(df, 200).to_numpy(float)
202
+ pc_ = np.r_[c[0], c[:-1]]
203
+ tr_ = np.maximum(h-l, np.maximum(np.abs(h-pc_), np.abs(l-pc_)))
204
+ vm = (np.cumsum(tr_)/np.arange(1, n+1)) if ob_filter == "Cumulative Mean Range" else atr
205
+ hv = (h-l) >= 2*vm # yüksek volatilite mumları: OB için high/low yer değiştirir
206
+ # LuxAlgo Pine ile aynı gövde/fitil konfluens koşulları.
207
+ # Bearish koşul önceki portta ters yazılmıştı; burada Pine ifadesi birebir korunur.
208
+ bull_bar = (h-np.maximum(c, o)) > np.minimum(c, o-l) if confluence else np.ones(n, bool)
209
+ bear_bar = (h-np.maximum(c, o)) < np.minimum(c, o-l) if confluence else np.ones(n, bool)
210
+ ph, pl = np.where(hv, l, h), np.where(hv, h, l)
211
+ swing_len = int(min(max(swing_len, 3), max(n//4, 3)))
212
+ int_len = int(min(max(int_len, 2), swing_len))
213
+ P_s, P_i, P_e = (_smc_pivots(h, l, k) for k in (swing_len, int_len, int(eq_len)))
214
+
215
+ new = lambda: {"lvl": np.nan, "crossed": True, "idx": -1}
216
+ sh, sl_, ih, il = new(), new(), new(), new()
217
+ s_tr = np.zeros(n, dtype=int)
218
+ i_tr = np.zeros(n, dtype=int)
219
+ top_a, bot_a = np.full(n, np.nan), np.full(n, np.nan)
220
+ swh_a, swl_a = np.full(n, np.nan), np.full(n, np.nan)
221
+ st = it = 0
222
+ top, bot = h[0], l[0]
223
+ events, obs, points, eqs, fvgs = [], [], [], [], []
224
+ act = []
225
+ ob_sup_a, ob_res_a = np.full(n, np.nan), np.full(n, np.nan)
226
+ def _new_ob(d):
227
+ obs.append(d)
228
+ act.append(d)
229
+ if len(act) > 60:
230
+ act.pop(0)
231
+ prev_hi = prev_lo = np.nan
232
+ eq_h = eq_l = None
233
+ dsum = 0.0
234
+ for i in range(n):
235
+ # --- swing / internal pivotlar
236
+ for kind, lvl, bi in P_s.get(i, []):
237
+ if kind == "H":
238
+ points.append((bi, lvl, "HH" if (np.isfinite(prev_hi) and lvl > prev_hi) else "LH", "H"))
239
+ prev_hi = lvl
240
+ sh.update(lvl=lvl, crossed=False, idx=bi)
241
+ top = lvl
242
+ else:
243
+ points.append((bi, lvl, "LL" if (np.isfinite(prev_lo) and lvl < prev_lo) else "HL", "L"))
244
+ prev_lo = lvl
245
+ sl_.update(lvl=lvl, crossed=False, idx=bi)
246
+ bot = lvl
247
+ for kind, lvl, bi in P_i.get(i, []):
248
+ (ih if kind == "H" else il).update(lvl=lvl, crossed=False, idx=bi)
249
+ # --- EQH / EQL
250
+ for kind, lvl, bi in P_e.get(i, []):
251
+ thr = eq_thr*atr[i] if np.isfinite(atr[i]) else 0.0
252
+ if kind == "H":
253
+ if eq_h is not None and abs(eq_h[0]-lvl) < thr:
254
+ eqs.append(("EQH", eq_h[1], eq_h[0], bi, lvl, i))
255
+ eq_h = (lvl, bi)
256
+ else:
257
+ if eq_l is not None and abs(eq_l[0]-lvl) < thr:
258
+ eqs.append(("EQL", eq_l[1], eq_l[0], bi, lvl, i))
259
+ eq_l = (lvl, bi)
260
+ # --- order block mitigasyonu (High/Low)
261
+ for ob in act:
262
+ if ob["on"] and ((ob["bull"] and l[i] < ob["lo"]) or (not ob["bull"] and h[i] > ob["hi"])):
263
+ ob["on"] = False
264
+ act[:] = [o_ for o_ in act if o_["on"]]
265
+ # --- yapı kırılımları
266
+ if i > 0:
267
+ for internal, hp, lp in ((True, ih, il), (False, sh, sl_)):
268
+ tr = it if internal else st
269
+ extra_u = (not internal) or (ih["lvl"] != sh["lvl"] and bool(bull_bar[i]))
270
+ extra_d = (not internal) or (il["lvl"] != sl_["lvl"] and bool(bear_bar[i]))
271
+ if (np.isfinite(hp["lvl"]) and not hp["crossed"] and c[i] > hp["lvl"] >= c[i-1]
272
+ and extra_u):
273
+ events.append({"i": i, "kind": "CHoCH" if tr == -1 else "BOS", "bull": True,
274
+ "internal": internal, "lvl": hp["lvl"], "from": hp["idx"]})
275
+ hp["crossed"] = True
276
+ seg = pl[hp["idx"]:i]
277
+ if len(seg):
278
+ j = hp["idx"] + int(np.argmin(seg))
279
+ _new_ob({"bull": True, "internal": internal, "hi": float(ph[j]),
280
+ "lo": float(pl[j]), "idx": j, "on": True})
281
+ if internal:
282
+ it = 1
283
+ else:
284
+ st = 1
285
+ if (np.isfinite(lp["lvl"]) and not lp["crossed"] and c[i] < lp["lvl"] <= c[i-1]
286
+ and extra_d):
287
+ events.append({"i": i, "kind": "CHoCH" if tr == 1 else "BOS", "bull": False,
288
+ "internal": internal, "lvl": lp["lvl"], "from": lp["idx"]})
289
+ lp["crossed"] = True
290
+ seg = ph[lp["idx"]:i]
291
+ if len(seg):
292
+ j = lp["idx"] + int(np.argmax(seg))
293
+ _new_ob({"bull": False, "internal": internal, "hi": float(ph[j]),
294
+ "lo": float(pl[j]), "idx": j, "on": True})
295
+ if internal:
296
+ it = -1
297
+ else:
298
+ st = -1
299
+ s_tr[i], i_tr[i] = st, it
300
+ top, bot = max(top, h[i]), min(bot, l[i])
301
+ top_a[i], bot_a[i] = top, bot
302
+ swh_a[i], swl_a[i] = sh["lvl"], sl_["lvl"]
303
+ ob_sup_a[i] = max((o_["lo"] for o_ in act if o_["bull"] and o_["lo"] < c[i]), default=np.nan)
304
+ ob_res_a[i] = min((o_["hi"] for o_ in act if (not o_["bull"]) and o_["hi"] > c[i]), default=np.nan)
305
+ # --- Fair Value Gap
306
+ if i >= 2:
307
+ delta = (c[i-1]-o[i-1])/(o[i-1]*100) if o[i-1] else 0.0
308
+ dsum += abs(delta)
309
+ thr = dsum/max(i, 1)*2
310
+ for g in fvgs:
311
+ if g["on"] and ((g["bull"] and l[i] < g["bot"]) or (not g["bull"] and h[i] > g["top"])):
312
+ g["on"] = False
313
+ if l[i] > h[i-2] and c[i-1] > h[i-2] and delta > thr:
314
+ fvgs.append({"bull": True, "top": float(l[i]), "bot": float(h[i-2]), "idx": i-1, "on": True})
315
+ elif h[i] < l[i-2] and c[i-1] < l[i-2] and -delta > thr:
316
+ fvgs.append({"bull": False, "top": float(l[i-2]), "bot": float(h[i]), "idx": i-1, "on": True})
317
+ live = lambda internal, bull: [o_ for o_ in obs if o_["on"] and o_["internal"] == internal
318
+ and o_["bull"] == bull][-int(ob_keep):]
319
+ 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),
320
+ "events": events, "points": points, "eq": eqs,
321
+ "obs": live(True, True)+live(True, False)+live(False, True)+live(False, False),
322
+ "fvg": [g for g in fvgs if g["on"]][-8:], "strong_high": st == -1, "strong_low": st == 1}
323
+
324
+
325
+ def add_smc_traces(fig, S, index, last_n=150, kw=None):
326
+ """SMC katmanlarını son `last_n` mumluk grafiğe ekler."""
327
+ kw = kw or {}
328
+ n = len(index)
329
+ w0 = max(0, n-int(last_n))
330
+ xi = lambda k: index[min(max(int(k), w0), n-1)]
331
+ for e in S["events"]:
332
+ if e["i"] < w0:
333
+ continue
334
+ col = "#16a34a" if e["bull"] else "#dc2626"
335
+ xa, xb = xi(e["from"]), xi(e["i"])
336
+ fig.add_trace(go.Scatter(x=[xa, xb], y=[e["lvl"], e["lvl"]], mode="lines",
337
+ line=dict(width=1 if e["internal"] else 2, color=col,
338
+ dash="dash" if e["internal"] else "solid"),
339
+ showlegend=False, hoverinfo="skip"), **kw)
340
+ mid = xa + (xb-xa)/2
341
+ fig.add_trace(go.Scatter(x=[mid], y=[e["lvl"]], mode="text", text=[e["kind"]],
342
+ textposition="top center" if e["bull"] else "bottom center",
343
+ textfont=dict(size=9 if e["internal"] else 11, color=col),
344
+ showlegend=False, hoverinfo="skip"), **kw)
345
+ for ob in S["obs"]:
346
+ col = "34,197,94" if ob["bull"] else "239,68,68"
347
+ fig.add_shape(type="rect", x0=xi(ob["idx"]), x1=index[-1], y0=ob["lo"], y1=ob["hi"],
348
+ line=dict(width=1 if ob["internal"] else 0, color=f"rgb({col})"),
349
+ fillcolor=f"rgba({col},{0.12 if ob['internal'] else 0.22})", layer="below", **kw)
350
+ for g in S["fvg"]:
351
+ if g["idx"] < w0:
352
+ continue
353
+ col = "34,197,94" if g["bull"] else "239,68,68"
354
+ fig.add_shape(type="rect", x0=xi(g["idx"]), x1=index[-1], y0=g["bot"], y1=g["top"],
355
+ line=dict(width=0), fillcolor=f"rgba({col},0.10)", layer="below", **kw)
356
+ for kind, xa_i, la, xb_i, lb, _ in S["eq"]:
357
+ if xb_i < w0:
358
+ continue
359
+ fig.add_trace(go.Scatter(x=[xi(xa_i), xi(xb_i)], y=[la, lb], mode="lines+text",
360
+ text=["", kind], textposition="top center" if kind == "EQH" else "bottom center",
361
+ line=dict(width=1, dash="dot", color="#64748b"),
362
+ textfont=dict(size=10, color="#64748b"), showlegend=False,
363
+ hoverinfo="skip"), **kw)
364
+ for bi, lvl, lab, kd in S["points"]:
365
+ if bi >= w0:
366
+ fig.add_trace(go.Scatter(x=[index[bi]], y=[lvl], mode="text", text=[lab],
367
+ textposition="top center" if kd == "H" else "bottom center",
368
+ textfont=dict(size=9, color="#475569"), showlegend=False,
369
+ hoverinfo="skip"), **kw)
370
+ if S.get("show_mtf"):
371
+ for k_, nm_ in (("pdh", "PDH"), ("pdl", "PDL"), ("pwh", "PWH"), ("pwl", "PWL"),
372
+ ("pmh", "PMH"), ("pml", "PML")):
373
+ y_ = S[k_][-1]
374
+ if np.isfinite(y_):
375
+ fig.add_trace(go.Scatter(x=[index[w0], index[-1]], y=[y_, y_], mode="lines+text",
376
+ text=["", nm_], textposition="top left",
377
+ line=dict(width=1, dash="dashdot", color="#0ea5e9"),
378
+ textfont=dict(size=9, color="#0ea5e9"), showlegend=False,
379
+ hoverinfo="skip"), **kw)
380
+ top, bot = float(S["top"][-1]), float(S["bot"][-1])
381
+ if np.isfinite(top) and np.isfinite(bot) and top > bot:
382
+ x0 = index[max(0, n-35)]
383
+ rng_ = top-bot
384
+ for y0, y1, col, nm in [(top, top-0.05*rng_, "239,68,68", "Premium"),
385
+ (bot+0.525*rng_, bot+0.475*rng_, "148,163,184", "Equilibrium"),
386
+ (bot+0.05*rng_, bot, "34,197,94", "Discount")]:
387
+ fig.add_shape(type="rect", x0=x0, x1=index[-1], y0=y0, y1=y1, line=dict(width=0),
388
+ fillcolor=f"rgba({col},0.16)", layer="below", **kw)
389
+ for y, nm in [(top, "Strong High" if S["strong_high"] else "Weak High"),
390
+ (bot, "Strong Low" if S["strong_low"] else "Weak Low")]:
391
+ fig.add_trace(go.Scatter(x=[x0, index[-1]], y=[y, y], mode="lines+text", text=["", nm],
392
+ textposition="top left", line=dict(width=1, color="#64748b"),
393
+ textfont=dict(size=9, color="#64748b"), showlegend=False,
394
+ hoverinfo="skip"), **kw)
395
+
396
+ # ------------------------------------------- Gann Box (Zeiierman) Python portu
397
+ def gann_box(df, lookback=150, reverse=False, levels=GANN_LEVELS):
398
+ """Görünen aralıktaki (son `lookback` mum) en yüksek / en düşük noktadan Gann Box kurar.
399
+ Zeiierman'ın TradingView göstergesinin açıklamasına göre yazıldı (Pine kaynağı birebir değil).
400
+ up: düşük, yüksekten ÖNCE geldiyse True (yükseliş kutusu). reverse: yönü çevirir."""
401
+ w = df.tail(int(lookback))
402
+ if len(w) < 10:
403
+ return None
404
+ hi_i, lo_i = int(np.argmax(w.High.to_numpy(float))), int(np.argmin(w.Low.to_numpy(float)))
405
+ hi, lo = float(w.High.iloc[hi_i]), float(w.Low.iloc[lo_i])
406
+ if hi <= lo:
407
+ return None
408
+ up = lo_i < hi_i
409
+ if reverse:
410
+ up = not up
411
+ # kutu: pencerenin başı -> sonu; seviyeler lo + p*(hi-lo)
412
+ lv = {float(p): lo + float(p)*(hi-lo) for p in levels}
413
+ return {"x0": w.index[0], "x1": w.index[-1], "n": len(w), "hi": hi, "lo": lo,
414
+ "up": up, "levels": lv, "hi_date": w.index[hi_i], "lo_date": w.index[lo_i]}
415
+
416
+
417
+ def gann_signal(df, lookback=150):
418
+ """Her mum için yalnızca geçmişe bakan Gann Box %50 konumu: kapanış orta çizgi üstünde +1,
419
+ altında -1 (kutu o muma kadarki son `lookback` mumdan kurulur)."""
420
+ lb = int(lookback)
421
+ hi = df.High.rolling(lb, min_periods=lb).max()
422
+ lo = df.Low.rolling(lb, min_periods=lb).min()
423
+ mid = (hi+lo)/2
424
+ c = df.Close
425
+ out = np.where(c > mid, 1, np.where(c < mid, -1, 0))
426
+ out = np.where(mid.isna(), 0, out)
427
+ return out.astype(int), ((c-lo)/(hi-lo).replace(0, np.nan)).to_numpy(float)
428
+
429
+
430
+ def add_gann_traces(fig, gb, df_index, extend=True, fan=True, kw=None):
431
+ """Kutu, yüzde seviyeleri ve (isteğe bağlı) Gann Fan çizgilerini Plotly grafiğine ekler."""
432
+ kw = kw or {}
433
+ x0, x1, n = gb["x0"], gb["x1"], gb["n"]
434
+ step = (df_index[-1]-df_index[-2]) if len(df_index) > 1 else pd.Timedelta(days=1)
435
+ x_end = x1 + step*int(n*0.5) if extend else x1
436
+ hi, lo = gb["hi"], gb["lo"]
437
+ fig.add_shape(type="rect", x0=x0, x1=x1, y0=lo, y1=hi, line=dict(width=1, color="#64748b"),
438
+ fillcolor="rgba(100,116,139,0.05)", layer="below", **kw)
439
+ pal = {0.0: "#16a34a", 0.236: "#84cc16", 0.382: "#eab308", 0.5: "#f59e0b",
440
+ 0.618: "#f97316", 0.786: "#ef4444", 1.0: "#dc2626"}
441
+ for p, y in gb["levels"].items():
442
+ fig.add_trace(go.Scatter(x=[x0, x_end], y=[y, y], mode="lines+text",
443
+ text=["", f"Gann {p:.3f} · {fmt_p(y)}"], textposition="middle right",
444
+ line=dict(width=1, dash="dot", color=pal.get(p, "#94a3b8")),
445
+ name=f"Gann {p:.3f}", showlegend=False, hoverinfo="name+y"), **kw)
446
+ if fan:
447
+ # köşe: yükseliş kutusunda düşük (sol-alt), düşüşte yüksek (sol-üst); 1x1 = karşı köşeye köşegen
448
+ y_start = lo if gb["up"] else hi
449
+ sign = 1 if gb["up"] else -1
450
+ base = (hi-lo)/max(gb["n"]-1, 1) # 1x1 eğimi (fiyat/mum)
451
+ steps = (x_end - x0)/step
452
+ for mult, nm in GANN_FAN:
453
+ y_end = y_start + sign*base*mult*steps
454
+ ya, yb = (y_start, y_end)
455
+ # kutu dışına taşan kısmı kes
456
+ lo_c, hi_c = lo - (hi-lo)*0.5, hi + (hi-lo)*0.5
457
+ if ya == yb:
458
+ continue
459
+ t_hit = min(1.0, max(0.0, ((hi_c if yb > ya else lo_c)-ya)/(yb-ya)))
460
+ xe = x0 + (x_end-x0)*t_hit
461
+ ye = ya + (yb-ya)*t_hit
462
+ fig.add_trace(go.Scatter(x=[x0, xe], y=[ya, ye], mode="lines",
463
+ line=dict(width=1.6 if mult == 1 else 0.8,
464
+ color="#2563eb" if mult == 1 else "#93c5fd"),
465
+ name=f"Gann Fan {nm}", showlegend=(mult == 1),
466
+ hoverinfo="name"), **kw)
467
+
468
+
469
+ def _profile(h, l, v, bins):
470
+ """Bar hacmini high-low aralığına, kutularla örtüştüğü oranda dağıtır (VPVR mantığı)."""
471
+ lo, hi = float(np.min(l)), float(np.max(h))
472
+ if not (np.isfinite(lo) and np.isfinite(hi)) or hi <= lo:
473
+ return None
474
+ v = np.where(np.isfinite(v) & (v > 0), v, 0.0)
475
+ if v.sum() <= 0: # hacim verisi yok (ör. forex) -> zaman bazlı profil
476
+ v = np.ones_like(v)
477
+ edges = np.linspace(lo, hi, bins+1)
478
+ step = (hi-lo)/bins
479
+ rng = np.maximum(h-l, 1e-12)
480
+ ov = np.clip(np.minimum(h[:, None], edges[None, 1:]) - np.maximum(l[:, None], edges[None, :-1]), 0, None)
481
+ frac = ov/rng[:, None]
482
+ zr = (h-l) <= 1e-12
483
+ if zr.any():
484
+ frac[zr] = 0.0
485
+ frac[zr, np.clip(((h[zr]-lo)/step).astype(int), 0, bins-1)] = 1.0
486
+ return edges, (frac*v[:, None]).sum(axis=0)
487
+
488
+
489
+ def _value_area(edges, prof, va_pct):
490
+ pi = int(np.argmax(prof))
491
+ lo_i = hi_i = pi
492
+ acc, target = float(prof[pi]), va_pct*float(prof.sum())
493
+ nb = len(prof)
494
+ while acc < target and (lo_i > 0 or hi_i < nb-1):
495
+ up = prof[hi_i+1] if hi_i < nb-1 else -1.0
496
+ dn = prof[lo_i-1] if lo_i > 0 else -1.0
497
+ if up >= dn:
498
+ hi_i += 1
499
+ acc += up
500
+ else:
501
+ lo_i -= 1
502
+ acc += dn
503
+ return float((edges[pi]+edges[pi+1])/2), float(edges[lo_i]), float(edges[hi_i+1])
504
+
505
+
506
+ def volume_profile(df, window=60, bins=24, va_pct=0.70):
507
+ """Kayan Volume Profile: her mum için son `window` mumdan POC / VAL / VAH.
508
+ Yalnızca o muma kadar olan veri kullanılır (ileriye bakma yok)."""
509
+ h, l, v = (df[c].to_numpy(float) for c in ("High", "Low", "Volume"))
510
+ n = len(h)
511
+ out = np.full((n, 3), np.nan)
512
+ for i in range(window-1, n):
513
+ sl = slice(i-window+1, i+1)
514
+ p = _profile(h[sl], l[sl], v[sl], bins)
515
+ if p is None:
516
+ continue
517
+ out[i] = _value_area(p[0], p[1], va_pct)
518
+ return pd.DataFrame(out, index=df.index, columns=["poc", "val", "vah"])
519
+
520
+
521
+ def latest_profile(df, window, bins, va_pct):
522
+ w = df.tail(int(window))
523
+ p = _profile(w.High.to_numpy(float), w.Low.to_numpy(float), w.Volume.to_numpy(float), int(bins))
524
+ if p is None:
525
+ return None
526
+ poc, val, vah = _value_area(p[0], p[1], va_pct)
527
+ return p[0], p[1], poc, val, vah
528
+
529
+
530
+ def vp_signal(df, atr, vp, ext_atr):
531
+ """+1: long onaylı (kapanış POC üstünde ve VAH'ın ext_atr*ATR'den fazla üstünde değil),
532
+ -1: short onaylı (POC altında ve VAL'ın ext_atr*ATR'den fazla altında değil), 0: onay yok."""
533
+ c, a = df.Close.to_numpy(float), atr.to_numpy(float)
534
+ poc, val, vah = (vp[k].to_numpy(float) for k in ("poc", "val", "vah"))
535
+ with np.errstate(invalid="ignore"):
536
+ ok_l = (c > poc) & ((c-vah) <= ext_atr*a)
537
+ ok_s = (c < poc) & ((val-c) <= ext_atr*a)
538
+ return np.where(ok_l, 1, np.where(ok_s, -1, 0)).astype(int)
539
+
540
+
541
+ # ------------------------------------------- Lorentzian Classification (jdehorty) portu
542
+ def adx_wilder(df, n=14):
543
+ h, l, c = df.High, df.Low, df.Close
544
+ up, dn = h.diff(), -l.diff()
545
+ plus = pd.Series(np.where((up > dn) & (up > 0), up, 0.0), index=df.index)
546
+ minus = pd.Series(np.where((dn > up) & (dn > 0), dn, 0.0), index=df.index)
547
+ tr = pd.concat([(h-l), (h-c.shift(1)).abs(), (l-c.shift(1)).abs()], axis=1).max(axis=1)
548
+ a = 1/n
549
+ atr = tr.ewm(alpha=a, adjust=False).mean()
550
+ pdi = 100*plus.ewm(alpha=a, adjust=False).mean()/atr
551
+ mdi = 100*minus.ewm(alpha=a, adjust=False).mean()/atr
552
+ dx = 100*(pdi-mdi).abs()/(pdi+mdi).replace(0, np.nan)
553
+ return dx.ewm(alpha=a, adjust=False).mean().to_numpy(float)
554
+
555
+
556
+ def _norm01(x):
557
+ """Geçmiş min/max'a göre 0-1 ölçekleme (TV 'normalize'); yalnızca geçmişe bakar."""
558
+ s = pd.Series(x)
559
+ lo, hi = s.cummin(), s.cummax()
560
+ return ((s-lo)/(hi-lo).replace(0, np.nan)).to_numpy(float)
561
+
562
+
563
+ def _ldc_features(df):
564
+ src = (df.High+df.Low+df.Close)/3
565
+ ema = lambda s, n: s.ewm(span=max(int(n), 1), adjust=False).mean()
566
+ rsi = lambda n: ema(calc_rsi(src, n)/100, 1).to_numpy(float)
567
+ e1 = ema(src, 10)
568
+ e2 = ema((src-e1).abs(), 10)
569
+ wt1 = ema((src-e1)/(0.015*e2).replace(0, np.nan), 11)
570
+ wt = _norm01((wt1-wt1.rolling(4).mean()).to_numpy(float))
571
+ ma = src.rolling(20).mean()
572
+ md = src.rolling(20).apply(lambda a: np.abs(a-a.mean()).mean(), raw=True)
573
+ cci = _norm01(((src-ma)/(0.015*md.replace(0, np.nan))).to_numpy(float))
574
+ adx = adx_wilder(df, 20)/100
575
+ # varsayılan öznitelikler: RSI(14,1), WT(10,11), CCI(20,1), ADX(20,2), RSI(9,1)
576
+ return np.column_stack([rsi(14), wt, cci, adx, rsi(9)]), src.to_numpy(float)
577
+
578
+
579
+ def ldc_classify(df, k=8, max_back=2000, regime_thr=-0.1, kern_lb=8, kern_rw=8.0, x0=25,
580
+ use_vol=True, use_regime=True, use_adx=False, adx_thr=20, use_kernel=True, exact=True):
581
+ """Machine Learning: Lorentzian Classification (jdehorty) Python portu.
582
+ Lorentzian mesafe = sum(log(1+|x-y|)); exact=True: gerçek k-NN, False: Pine'ın yaklaşık-NN döngüsü;
583
+ etiket = 4 bar sonraki yön. Filtreler: volatilite, rejim (Kalman eğimi), ADX; kernel:
584
+ Nadaraya-Watson rational quadratic. Her mum yalnızca kendisine kadarki veriyi kullanır.
585
+ state: +1 / -1 (ML sinyali + kernel eğimi aynı yönde), 0 onay yok."""
586
+ n = len(df)
587
+ F, s = _ldc_features(df)
588
+ y = np.full(n, np.nan)
589
+ y[:-4] = np.sign(s[4:]-s[:-4])
590
+ ok_f = np.isfinite(F).all(axis=1)
591
+ cand = ok_f & np.isfinite(y) & (np.arange(n) % 4 != 0)
592
+ kk = int(k)
593
+ cut = int(np.floor(kk*0.75+0.5))
594
+ pred = np.zeros(n)
595
+ for t in range(60, n):
596
+ if not ok_f[t]:
597
+ continue
598
+ hi_j = t-4 # etiketi t'de bilinen son örnek
599
+ lo_j = max(0, hi_j-int(max_back)+1)
600
+ sel = np.flatnonzero(cand[lo_j:hi_j+1]) + lo_j
601
+ if len(sel) < kk:
602
+ continue
603
+ D = np.log1p(np.abs(F[sel]-F[t])).sum(axis=1)
604
+ if exact: # gerçek k en yakın komşu (Lorentzian mesafe)
605
+ nn_ = np.argpartition(D, kk-1)[:kk]
606
+ pred[t] = y[sel][nn_].sum()
607
+ continue
608
+ last, dist, pr = -1.0, [], [] # Pine'ın kronolojik yaklaşık-NN döngüsü
609
+ for d, yv in zip(D.tolist(), y[sel].tolist()):
610
+ if d >= last:
611
+ dist.append(d)
612
+ pr.append(yv)
613
+ if len(dist) > kk:
614
+ last = dist[cut]
615
+ dist.pop(0)
616
+ pr.pop(0)
617
+ pred[t] = sum(pr)
618
+
619
+ # --- filtreler
620
+ f_vol = (calc_atr(df, 1).to_numpy(float) > calc_atr(df, 10).to_numpy(float)) if use_vol \
621
+ else np.ones(n, bool)
622
+ f_reg = np.ones(n, bool)
623
+ if use_regime:
624
+ o4 = ((df.Open+df.High+df.Low+df.Close)/4).to_numpy(float)
625
+ hl = (df.High-df.Low).to_numpy(float)
626
+ v1 = v2 = kl = 0.0
627
+ klmf = np.zeros(n)
628
+ for i in range(n):
629
+ v1 = 0.2*(o4[i]-(o4[i-1] if i else o4[i]))+0.8*v1
630
+ v2 = 0.1*hl[i]+0.8*v2
631
+ om = abs(v1/v2) if v2 else 0.0
632
+ al = (-om**2+np.sqrt(om**4+16*om**2))/8
633
+ kl = al*o4[i]+(1-al)*kl
634
+ klmf[i] = kl
635
+ slope = pd.Series(np.abs(np.diff(klmf, prepend=klmf[0])))
636
+ ema_s = slope.ewm(span=200, adjust=False).mean()
637
+ f_reg = (((slope-ema_s)/ema_s.replace(0, np.nan)) >= float(regime_thr)).fillna(False).to_numpy(bool)
638
+ f_adx = (adx_wilder(df, 14) > float(adx_thr)) if use_adx else np.ones(n, bool)
639
+ filt = f_vol & f_reg & f_adx
640
+
641
+ sig = np.zeros(n, dtype=int)
642
+ for t in range(1, n):
643
+ sig[t] = 1 if (pred[t] > 0 and filt[t]) else -1 if (pred[t] < 0 and filt[t]) else sig[t-1]
644
+
645
+ # --- kernel regresyon (rational quadratic), yalnızca geçmiş barlar
646
+ L = int(kern_lb)+int(x0)
647
+ i_ = np.arange(L+1)
648
+ w = (1+i_**2/(float(kern_lb)**2*2*float(kern_rw)))**(-float(kern_rw))
649
+ yhat = np.full(n, np.nan)
650
+ for t in range(n):
651
+ m = min(L+1, t+1)
652
+ yhat[t] = (w[:m]*s[t-np.arange(m)]).sum()/w[:m].sum()
653
+ bull = np.r_[False, yhat[1:] > yhat[:-1]]
654
+ bear = np.r_[False, yhat[1:] < yhat[:-1]]
655
+ if use_kernel:
656
+ state = np.where((sig == 1) & bull, 1, np.where((sig == -1) & bear, -1, 0)).astype(int)
657
+ else:
658
+ state = sig.copy()
659
+ state[:60] = 0
660
+ return {"state": state, "signal": sig, "pred": pred, "yhat": yhat,
661
+ "vol": f_vol, "reg": f_reg, "adx": f_adx}
662
+
663
+
664
+ def regime_labels(features, ref_vol):
665
+ # Volatilite kantilleri yalnızca geliştirme dönemi verisinden alınır (sızıntı yok).
666
+ q1, q2 = ref_vol.dropna().quantile([0.33, 0.67])
667
+ vol, trend = features["vol_20"], features["ema_gap_20"]
668
+ vname = np.where(vol <= q1, "LOW_VOL", np.where(vol >= q2, "HIGH_VOL", "MID_VOL"))
669
+ tname = np.where(trend > 0.02, "UP", np.where(trend < -0.02, "DOWN", "RANGE"))
670
+ return pd.Series([f"{t}_{v}" for t, v in zip(tname, vname)], index=features.index)
671
+
672
+
673
+ def make_target(df, atr, horizon, threshold=0.20):
674
+ fwd = df.Close.shift(-horizon)/df.Close-1
675
+ band = threshold*(atr/df.Close)*np.sqrt(horizon)
676
+ y = pd.Series(np.where(fwd > band, 1, np.where(fwd < -band, -1, 0)), index=df.index)
677
+ return y.where(fwd.notna() & band.notna()), fwd
678
+
679
+
680
+ # ---------------------------------------------------------------- model
681
+ def pipe(k):
682
+ return Pipeline([
683
+ ("imputer", SimpleImputer(strategy="median")),
684
+ ("scale", StandardScaler()),
685
+ ("knn", KNeighborsClassifier(n_neighbors=int(k), weights="distance"))
686
+ ])
687
+
688
+
689
+ def tune_k(X, y, h):
690
+ # İç kronolojik doğrulama; örtüşen etiketleri önlemek için ufuk kadar purge.
691
+ cut = int(len(X)*0.80)
692
+ tr_end = cut-h
693
+ if tr_end <= max(K_GRID)+20 or len(X)-cut < 25:
694
+ return 9, []
695
+ scores = []
696
+ for k in K_GRID:
697
+ model = pipe(k).fit(X.iloc[:tr_end], y.iloc[:tr_end])
698
+ pred = model.predict(X.iloc[cut:])
699
+ scores.append((float(balanced_accuracy_score(y.iloc[cut:], pred)), int(k)))
700
+ scores.sort(reverse=True)
701
+ return scores[0][1], scores
702
+
703
+
704
+ def calibrate_current(raw_cal, y_cal, raw_cur, classes):
705
+ # Bire-karşı-hepsi isotonic kalibrasyon; küçük kalibrasyon setinde ham olasılık.
706
+ classes = list(classes)
707
+ raw_vec = np.zeros(len(CLASSES))
708
+ out = np.zeros(len(CLASSES))
709
+ for j, cls in enumerate(CLASSES):
710
+ if cls not in classes:
711
+ continue
712
+ ci = classes.index(cls)
713
+ val = raw_cur[0, ci]
714
+ raw_vec[j] = val
715
+ truth = (np.asarray(y_cal) == cls).astype(int)
716
+ if len(truth) >= 35 and len(np.unique(truth)) == 2:
717
+ iso = IsotonicRegression(out_of_bounds="clip").fit(raw_cal[:, ci], truth)
718
+ out[j] = iso.predict([val])[0]
719
+ else:
720
+ out[j] = val
721
+ if out.sum() > 0:
722
+ return out/out.sum()
723
+ if raw_vec.sum() > 0:
724
+ return raw_vec/raw_vec.sum()
725
+ return np.full(len(CLASSES), 1/3)
726
+
727
+
728
+ # ---------------------------------------------------------------- bariyer / R
729
+ def stop_dist(A, i, side, sl_atr):
730
+ return A["ind_sd"][side][i] if "ind_sd" in A else sl_atr*A["a"][i]
731
+
732
+
733
+ def barrier_trade(A, i, side, sl_atr, rr, h):
734
+ """(getiri, çıkış_pozisyonu, neden) döndürür. Aynı mumda SL+TP varsa SL kabul edilir."""
735
+ entry, av = A["c"][i], A["a"][i]
736
+ if not np.isfinite(av) or av <= 0 or not np.isfinite(entry):
737
+ return None
738
+ if "ind_sd" in A: # SL/TP indikatör seviyelerinden (ST / UT / Volume Profile)
739
+ sd = A["ind_sd"][side][i]
740
+ if not np.isfinite(sd) or sd <= 0:
741
+ return None
742
+ td = min(sd*rr, A["ind_cap"][side][i])
743
+ else:
744
+ sd = sl_atr*av
745
+ td = sd*rr
746
+ end = min(i+h, len(A["c"])-1)
747
+ for j in range(i+1, end+1):
748
+ hi, lo = A["h"][j], A["l"][j]
749
+ if side == 1:
750
+ sl_hit, tp_hit = lo <= entry-sd, hi >= entry+td
751
+ else:
752
+ sl_hit, tp_hit = hi >= entry+sd, lo <= entry-td
753
+ if sl_hit:
754
+ return -sd/entry, j, "SL"
755
+ if tp_hit:
756
+ return td/entry, j, "TP"
757
+ return side*(A["c"][end]-entry)/entry, end, "TIME"
758
+
759
+
760
+ def optimize_barriers(A, pos, regimes_arr, side, cost, current_regime, h):
761
+ pos = list(pos)[-650:]
762
+ sel = [i for i in pos if regimes_arr[i] == current_regime]
763
+ if len(sel) < 35:
764
+ sel, used = pos, "GLOBAL_FALLBACK"
765
+ else:
766
+ used = str(current_regime)
767
+ results = []
768
+ for sl in ([1.0] if "ind_sd" in A else SL_GRID):
769
+ for rr in RR_GRID:
770
+ vals = []
771
+ for i in sel:
772
+ t = barrier_trade(A, i, side, sl, rr, h)
773
+ if t is not None:
774
+ vals.append(t[0]-cost)
775
+ if len(vals) >= 35:
776
+ a = np.asarray(vals)
777
+ downside = np.sqrt(np.mean(np.minimum(a, 0)**2))
778
+ results.append((a.mean()-0.35*downside, sl, rr, len(a),
779
+ a.mean(), (a > 0).mean()))
780
+ if not results:
781
+ return 1.5, 2.0, [], used
782
+ results.sort(reverse=True)
783
+ return float(results[0][1]), float(results[0][2]), results[:10], used
784
+
785
+
786
+ # ---------------------------------------------------------------- risk / MC
787
+ def dd_mult(dd):
788
+ return np.select([dd <= -0.20, dd <= -0.15, dd <= -0.10, dd <= -0.06],
789
+ [0.20, 0.35, 0.50, 0.75], 1.0)
790
+
791
+
792
+ def adaptive_risk(min_risk, max_risk, bacc, agreement, expectancy_r, n_trades, drawdown):
793
+ # Açık, deterministik politika (pekiştirmeli öğrenme değildir).
794
+ quality = np.clip((bacc-0.33)/0.20, 0, 1)
795
+ agree = np.clip((agreement-0.33)/0.50, 0, 1)
796
+ edge = np.clip(expectancy_r/0.30, 0, 1)
797
+ sample = np.clip(n_trades/40, 0, 1)
798
+ score = 0.30*quality+0.25*agree+0.30*edge+0.15*sample
799
+ risk = min_risk+(max_risk-min_risk)*score
800
+ if drawdown <= -0.20:
801
+ risk = min_risk
802
+ elif drawdown <= -0.15:
803
+ risk = min_risk+(risk-min_risk)*0.20
804
+ elif drawdown <= -0.10:
805
+ risk = min_risk+(risk-min_risk)*0.40
806
+ elif drawdown <= -0.06:
807
+ risk = min_risk+(risk-min_risk)*0.70
808
+ return float(np.clip(risk, min_risk, max_risk)), float(score)
809
+
810
+
811
+ def simulate_equity(r_values, base_risk_pct):
812
+ eq, peak = 1.0, 1.0
813
+ curve, dds = [1.0], [0.0]
814
+ for r in r_values:
815
+ risk = base_risk_pct/100*float(dd_mult(eq/peak-1))
816
+ eq *= max(0.01, 1+risk*float(r))
817
+ peak = max(peak, eq)
818
+ curve.append(eq)
819
+ dds.append(eq/peak-1)
820
+ return np.array(curve), np.array(dds)
821
+
822
+
823
+ def risk_path_mc(r_multiples, base_risk_pct, sims=2000, n_trades=100):
824
+ # R biriminde bootstrap; kayıp 1R ile sınırlanmaz (maliyet nedeniyle ~-1.05R olabilir).
825
+ if len(r_multiples) < 10:
826
+ return None
827
+ rng = np.random.default_rng(20261103)
828
+ S = rng.choice(np.asarray(r_multiples, float), size=(sims, n_trades), replace=True)
829
+ eq, peak, mdd = np.ones(sims), np.ones(sims), np.zeros(sims)
830
+ paths = np.empty((n_trades+1, sims))
831
+ paths[0] = 1.0
832
+ for t in range(n_trades):
833
+ risk = base_risk_pct/100*dd_mult(eq/peak-1)
834
+ eq = eq*np.maximum(0.01, 1+risk*S[:, t])
835
+ peak = np.maximum(peak, eq)
836
+ mdd = np.minimum(mdd, eq/peak-1)
837
+ paths[t+1] = eq
838
+ fin = eq-1
839
+ return {"median": float(np.median(fin)), "p05": float(np.percentile(fin, 5)),
840
+ "p95": float(np.percentile(fin, 95)), "dd95": float(np.percentile(mdd, 5)),
841
+ "ruin": float(np.mean(eq < 0.5)),
842
+ "pct": np.percentile(paths, [5, 25, 50, 75, 95], axis=1)-1.0}
843
+
844
+
845
+ # ---------------------------------------------------------------- yardımcılar
846
+ def fmt_p(x):
847
+ return f"{x:,.2f}" if abs(x) >= 100 else f"{x:,.4f}"
848
+
849
+
850
+ def md_table(header, rows):
851
+ if not rows:
852
+ return "_Veri yok._"
853
+ out = ["| " + " | ".join(header) + " |", "|" + "|".join(["---"]*len(header)) + "|"]
854
+ out += ["| " + " | ".join(str(c) for c in r) + " |" for r in rows]
855
+ return "\n".join(out)
856
+
857
+
858
+ def trade_stats(R, curve, dds, trades_df):
859
+ if len(R) == 0:
860
+ return None
861
+ wins, losses = R[R > 0].sum(), -R[R < 0].sum()
862
+ reasons = trades_df.exit_reason.value_counts().to_dict()
863
+ return {"n": len(R), "win": float((R > 0).mean()), "exp": float(R.mean()),
864
+ "pf": float(wins/losses) if losses > 0 else float("inf"),
865
+ "ret": float(curve[-1]-1), "mdd": float(dds.min()),
866
+ "tp": reasons.get("TP", 0), "sl": reasons.get("SL", 0),
867
+ "time": reasons.get("TIME", 0)}
868
+
869
+
870
+ TRADE_COLS = ["entry_date", "exit_date", "side", "entry_price", "exit_reason",
871
+ "net_return_pct", "R", "regime"]
872
+
873
+
874
+ # ---------------------------------------------------------------- grafikler
875
+ def price_figure(df, ticker, trades_df, trade_ok, stop, target, ind=None, vp=None, vp_prof=None,
876
+ ldc=None, combo=None, gann=None, gann_fan=True, gann_extend=True, smc=None):
877
+ w = df.tail(150)
878
+ sub = vp is not None and vp_prof is not None
879
+ if sub:
880
+ fig = make_subplots(rows=1, cols=2, shared_yaxes=True,
881
+ column_widths=[0.86, 0.14], horizontal_spacing=0.005)
882
+ else:
883
+ fig = go.Figure()
884
+ kw = dict(row=1, col=1) if sub else {}
885
+ fig.add_trace(go.Candlestick(x=w.index, open=w.Open, high=w.High, low=w.Low,
886
+ close=w.Close, name=ticker), **kw)
887
+ for n, col in [(20, "#f59e0b"), (50, "#3b82f6")]:
888
+ ema = df.Close.ewm(span=n, adjust=False).mean().tail(150)
889
+ fig.add_trace(go.Scatter(x=w.index, y=ema, mode="lines", name=f"EMA{n}",
890
+ line=dict(width=1.2, color=col)), **kw)
891
+ if ind is not None:
892
+ st_line, st_dir, ut_stop = (np.asarray(a)[-150:] for a in ind)
893
+ for d, col, nm in [(1, "#16a34a", "Supertrend ↑"), (-1, "#dc2626", "Supertrend ↓")]:
894
+ fig.add_trace(go.Scatter(x=w.index, y=np.where(st_dir == d, st_line, np.nan),
895
+ mode="lines", name=nm, line=dict(width=1.5, color=col)), **kw)
896
+ fig.add_trace(go.Scatter(x=w.index, y=ut_stop, mode="lines", name="UT trailing stop",
897
+ line=dict(width=1, dash="dot", color="#9333ea")), **kw)
898
+ if sub:
899
+ vw = vp.tail(150)
900
+ for k, col, dash, nm in [("poc", "#f59e0b", "solid", "POC"),
901
+ ("vah", "#64748b", "dash", "VAH"),
902
+ ("val", "#64748b", "dash", "VAL")]:
903
+ fig.add_trace(go.Scatter(x=w.index, y=vw[k], mode="lines", name=nm,
904
+ line=dict(width=1.4, color=col, dash=dash)), **kw)
905
+ edges, prof, poc, val, vah = vp_prof
906
+ mid = (edges[:-1]+edges[1:])/2
907
+ colors = ["#f59e0b" if edges[k] <= poc <= edges[k+1]
908
+ else ("#60a5fa" if val <= mid[k] <= vah else "#cbd5e1")
909
+ for k in range(len(mid))]
910
+ fig.add_trace(go.Bar(x=prof, y=mid, orientation="h", width=(edges[1]-edges[0])*0.95,
911
+ marker_color=colors, name="Volume Profile", showlegend=False,
912
+ hoverinfo="skip"), row=1, col=2)
913
+ fig.update_xaxes(showticklabels=False, row=1, col=2)
914
+ if ldc is not None:
915
+ yh = np.asarray(ldc["yhat"])[-150:]
916
+ rising = np.r_[False, np.diff(yh) > 0]
917
+ for flag, col, nm in [(True, "#16a34a", "Lorentzian kernel ↑"), (False, "#dc2626", "Lorentzian kernel ↓")]:
918
+ fig.add_trace(go.Scatter(x=w.index, y=np.where(rising == flag, yh, np.nan), mode="lines",
919
+ name=nm, line=dict(width=2, color=col)), **kw)
920
+ st_ = np.asarray(ldc["state"])[-150:]
921
+ prev = np.r_[0, st_[:-1]]
922
+ for d, nm, col, ycol, off in [(1, "LDC long", "#15803d", "Low", 0.995),
923
+ (-1, "LDC short", "#b91c1c", "High", 1.005)]:
924
+ m_ = (st_ == d) & (prev != d)
925
+ if m_.any():
926
+ fig.add_trace(go.Scatter(x=w.index[m_], y=w[ycol].to_numpy()[m_]*off, mode="markers",
927
+ name=nm, marker=dict(symbol="diamond", size=8, color=col,
928
+ line=dict(width=1, color="white"))), **kw)
929
+ if combo is not None:
930
+ cs = np.asarray(combo)[-150:]
931
+ pv = np.r_[0, cs[:-1]]
932
+ for d, nm, col, ycol, off in [(1, "Birleşik LONG", "#f59e0b", "Low", 0.985),
933
+ (-1, "Birleşik SHORT", "#7c3aed", "High", 1.015)]:
934
+ m_ = (cs == d) & (pv != d)
935
+ if m_.any():
936
+ fig.add_trace(go.Scatter(x=w.index[m_], y=w[ycol].to_numpy()[m_]*off, mode="markers",
937
+ name=nm, marker=dict(symbol="star", size=12, color=col,
938
+ line=dict(width=1, color="white"))), **kw)
939
+ if len(trades_df):
940
+ t = trades_df[trades_df.entry_date.isin(w.index)]
941
+ for side, sym, nm in [("LONG", "triangle-up", "OOS long"),
942
+ ("SHORT", "triangle-down", "OOS short")]:
943
+ s = t[t.side == side]
944
+ if len(s):
945
+ fig.add_trace(go.Scatter(
946
+ x=s.entry_date, y=s.entry_price, mode="markers", name=nm,
947
+ marker=dict(symbol=sym, size=9,
948
+ color=["#16a34a" if r > 0 else "#dc2626" for r in s.R],
949
+ line=dict(width=1, color="white")),
950
+ text=[f"R={r:.2f} · {why}" for r, why in zip(s.R, s.exit_reason)]), **kw)
951
+ if smc is not None:
952
+ add_smc_traces(fig, smc, df.index, last_n=150, kw=kw)
953
+ if gann is not None:
954
+ add_gann_traces(fig, gann, df.index, extend=gann_extend, fan=gann_fan, kw=kw)
955
+ if trade_ok:
956
+ fig.add_hline(y=stop, line_dash="dash", line_color="#dc2626", annotation_text="SL", **kw)
957
+ fig.add_hline(y=target, line_dash="dash", line_color="#16a34a", annotation_text="TP", **kw)
958
+ fig.update_layout(title=f"{ticker} Günlük — son 150 mum (üçgenler: OOS sinyalleri)"
959
+ + (" + Volume Profile" if sub else "") + (" + Gann Box" if gann is not None else "") + (" + SMC" if smc is not None else ""),
960
+ height=560, xaxis_rangeslider_visible=False)
961
+ return fig
962
+
963
+
964
+ def prob_figure(p_down, p_flat, p_up):
965
+ fig = go.Figure(go.Bar(x=["DOWN", "FLAT", "UP"], y=[p_down*100, p_flat*100, p_up*100],
966
+ marker_color=["#dc2626", "#9ca3af", "#16a34a"],
967
+ text=[f"{p*100:.1f}%" for p in (p_down, p_flat, p_up)],
968
+ textposition="outside"))
969
+ fig.update_layout(title="Kalibre edilmiş olasılıklar", height=300,
970
+ yaxis=dict(range=[0, 100], title="%"), margin=dict(t=50, b=30))
971
+ return fig
972
+
973
+
974
+ def equity_figure(curve, dds):
975
+ x = np.arange(len(curve))
976
+ fig = make_subplots(rows=2, cols=1, shared_xaxes=True, row_heights=[0.7, 0.3],
977
+ vertical_spacing=0.06)
978
+ fig.add_trace(go.Scatter(x=x, y=(curve-1)*100, mode="lines", name="Kümülatif getiri %"), 1, 1)
979
+ fig.add_trace(go.Scatter(x=x, y=dds*100, fill="tozeroy", name="Drawdown %",
980
+ line=dict(color="#dc2626")), 2, 1)
981
+ fig.update_xaxes(title_text="İşlem #", row=2, col=1)
982
+ fig.update_layout(title="OOS backtest — adaptif risk ile bileşik özsermaye", height=520)
983
+ return fig
984
+
985
+
986
+ def pf_gauge(pf, n):
987
+ shown = 3.0 if np.isinf(pf) else float(min(pf, 3.0))
988
+ fig = go.Figure(go.Indicator(
989
+ mode="gauge+number", value=shown,
990
+ number=dict(valueformat=".2f", suffix=" (∞)" if np.isinf(pf) else ""),
991
+ title=dict(text=f"Profit Factor — OOS ({n} işlem)"),
992
+ gauge=dict(
993
+ axis=dict(range=[0, 3]),
994
+ bar=dict(color="#1d4ed8"),
995
+ steps=[dict(range=[0, 1], color="#fecaca"),
996
+ dict(range=[1, 1.5], color="#fef08a"),
997
+ dict(range=[1.5, 3], color="#bbf7d0")],
998
+ threshold=dict(line=dict(color="black", width=3), thickness=0.8, value=1.0))))
999
+ fig.update_layout(height=320, margin=dict(t=70, b=20))
1000
+ return fig
1001
+
1002
+
1003
+ def mc_figure(mc):
1004
+ p = mc["pct"]*100
1005
+ x = np.arange(p.shape[1])
1006
+ fig = go.Figure()
1007
+ fig.add_trace(go.Scatter(x=x, y=p[0], line=dict(width=0), showlegend=False, hoverinfo="skip"))
1008
+ fig.add_trace(go.Scatter(x=x, y=p[4], fill="tonexty", line=dict(width=0),
1009
+ fillcolor="rgba(59,130,246,0.15)", name="%5–%95"))
1010
+ fig.add_trace(go.Scatter(x=x, y=p[1], line=dict(width=0), showlegend=False, hoverinfo="skip"))
1011
+ fig.add_trace(go.Scatter(x=x, y=p[3], fill="tonexty", line=dict(width=0),
1012
+ fillcolor="rgba(59,130,246,0.30)", name="%25–%75"))
1013
+ fig.add_trace(go.Scatter(x=x, y=p[2], line=dict(color="#1d4ed8", width=2), name="Medyan"))
1014
+ fig.update_layout(title="Monte Carlo — 100 işlemlik getiri yelpazesi (%)",
1015
+ xaxis_title="İşlem #", yaxis_title="Getiri %", height=460)
1016
+ return fig
1017
+
1018
+
1019
+ # ---------------------------------------------------------------- ana motor
1020
+ def run_engine(ticker, period, horizon, side_mode, equity, min_risk, max_risk,
1021
+ max_leverage, fee, slip, train_window,
1022
+ p_min, p_gap, flat_max, agree_min, bacc_min, min_trades,
1023
+ skip_overlap, whole_units, ind_mode, st_period, st_mult, ut_key, ut_period,
1024
+ vp_mode, vp_window, vp_bins, vp_va, vp_ext,
1025
+ ldc_mode, ldc_k, ldc_back, ldc_regime, ldc_kern_lb, ldc_kern_rw,
1026
+ ldc_use_vol, ldc_use_regime, ldc_use_adx, ldc_kernel, ldc_exact,
1027
+ combo_mode, combo_votes, ind_tpsl, tpsl_smin, tpsl_smax, tpsl_minrr,
1028
+ gann_mode, gann_lb, gann_fan_on, gann_ext_on, gann_rev,
1029
+ smc_mode, smc_src, smc_swing_len, smc_int_len, smc_zone, smc_draw,
1030
+ smc_conf, smc_obf, smc_lvl):
1031
+ try:
1032
+ ticker = str(ticker).strip().upper()
1033
+ h = int(horizon)
1034
+ equity, fee, slip = float(equity), float(fee), float(slip)
1035
+ min_risk, max_risk = float(min_risk), float(max_risk)
1036
+ if max_risk < min_risk:
1037
+ raise ValueError("Maksimum risk, minimum riskten küçük olamaz.")
1038
+ cost = 2*(fee+slip)/100
1039
+ allow = {1: side_mode != "Sadece Short", -1: side_mode != "Sadece Long"}
1040
+
1041
+ df = get_market_data(ticker, period)
1042
+ if len(df) < 450:
1043
+ raise ValueError("En az 450 günlük mum gerekli.")
1044
+ df = df.iloc[:-1].copy() # tamamlanmamış güncel mumu çıkar
1045
+ vp_on = vp_mode != VP_MODES[0]
1046
+ vp_feat = vp_mode == VP_MODES[2]
1047
+ vp_win, vp_nb, vp_vap = int(vp_window), int(vp_bins), float(vp_va)
1048
+ vp = volume_profile(df, vp_win, vp_nb, vp_vap)
1049
+ X_all, atr_all = make_features(df, vp if vp_feat else None)
1050
+ vp_arr = vp_signal(df, atr_all, vp, float(vp_ext))
1051
+ vp_names = {1: "LONG onaylı", -1: "SHORT onaylı", 0: "onay yok"}
1052
+ vp_no_vol = float(df.Volume.tail(vp_win).sum()) <= 0
1053
+ lp = latest_profile(df, vp_win, vp_nb, vp_vap)
1054
+ y_all, _ = make_target(df, atr_all, h)
1055
+ gann_on = gann_mode != GANN_MODES[0]
1056
+ gann_arr, gann_pos = gann_signal(df, int(gann_lb))
1057
+ gann_now = int(gann_arr[-1])
1058
+ GB = gann_box(df, int(gann_lb), bool(gann_rev))
1059
+ smc_on = smc_mode != SMC_MODES[0]
1060
+ smc_only = smc_mode == SMC_MODES[2]
1061
+ SM = smc_analyze(df, int(smc_swing_len), int(smc_int_len), confluence=bool(smc_conf),
1062
+ ob_filter=smc_obf) if (smc_on or smc_draw) else None
1063
+ smc_lvl_on = bool(smc_lvl) and SM is not None and smc_on
1064
+ if SM is not None:
1065
+ SM["show_mtf"] = bool(smc_lvl)
1066
+ smc_arr = np.zeros(len(df), dtype=int)
1067
+ if SM is not None:
1068
+ smc_arr = (SM["swing_trend"] if smc_src == SMC_SRC[0] else SM["internal_trend"]).copy()
1069
+ if smc_zone: # LuxAlgo Premium/Discount kutuları: uçlarda %5, equilibrium %47.5-%52.5
1070
+ top_, bot_ = SM["top"], SM["bot"]
1071
+ rng_ = top_ - bot_
1072
+ close_ = df.Close.to_numpy(float)
1073
+ discount_edge = bot_ + 0.05*rng_
1074
+ premium_edge = top_ - 0.05*rng_
1075
+ zone_ = np.where(close_ <= discount_edge, 1,
1076
+ np.where(close_ >= premium_edge, -1, 0))
1077
+ smc_arr = np.where(zone_ == smc_arr, smc_arr, 0).astype(int)
1078
+ smc_now = int(smc_arr[-1])
1079
+ smc_entry = np.zeros(len(df), dtype=int) # yalnızca yapı kırılımı mumlarında giriş
1080
+ if SM is not None:
1081
+ want_internal = smc_src == SMC_SRC[1]
1082
+ ev_arr = np.zeros(len(df), dtype=int)
1083
+ for e_ in SM["events"]:
1084
+ if e_["internal"] == want_internal:
1085
+ ev_arr[e_["i"]] = 1 if e_["bull"] else -1
1086
+ smc_entry = np.where(ev_arr == smc_arr, ev_arr, 0).astype(int)
1087
+
1088
+ valid = X_all.notna().all(axis=1) & y_all.notna()
1089
+ X = X_all.loc[valid].copy()
1090
+ y = y_all.loc[X.index].astype(int)
1091
+ if len(X) < 300:
1092
+ raise ValueError("Temiz özelliklerden sonra yeterli veri kalmadı.")
1093
+
1094
+ oos_n = min(180, max(75, int(len(X)*0.22)))
1095
+ dev_end = len(X)-oos_n-h
1096
+ if dev_end < 200:
1097
+ raise ValueError("OOS ayrımı için yeterli geliştirme verisi yok.")
1098
+ tw = int(train_window)
1099
+ dev_X, dev_y = X.iloc[:dev_end], y.iloc[:dev_end]
1100
+ if len(dev_X) > tw:
1101
+ dev_X, dev_y = dev_X.iloc[-tw:], dev_y.iloc[-tw:]
1102
+
1103
+ regimes_all = regime_labels(X_all, X_all.loc[dev_X.index, "vol_20"])
1104
+ reg_arr = regimes_all.to_numpy()
1105
+ A = {"h": df.High.to_numpy(float), "l": df.Low.to_numpy(float),
1106
+ "c": df.Close.to_numpy(float), "a": atr_all.to_numpy(float)}
1107
+ pos_of = lambda idx: df.index.get_indexer(idx)
1108
+
1109
+ # --- indikatörler (Supertrend + UT Bot), tamamen Python'da
1110
+ st_dir, st_line = supertrend(df, int(st_period), float(st_mult))
1111
+ ut_pos, ut_stop = ut_bot(df, float(ut_key), int(ut_period))
1112
+ ind_arr = np.where((st_dir == ut_pos) & (ut_pos != 0), ut_pos, 0).astype(int)
1113
+ ind_only = ind_mode == "Sadece indikatör"
1114
+ ind_confirm = ind_mode == IND_MODES[1]
1115
+ ind_now = int(ind_arr[-1])
1116
+
1117
+ # --- Lorentzian Classification (ML + kernel) ve birleşik sinyal (oylama)
1118
+ combo_on = combo_mode != COMBO_MODES[0]
1119
+ combo_only = combo_mode == COMBO_MODES[2]
1120
+ ldc_on = ldc_mode != LDC_MODES[0] and not combo_only
1121
+ ldc_only = ldc_mode == LDC_MODES[2] and not combo_only
1122
+ ldc_confirm = ldc_mode == LDC_MODES[1] and not combo_only
1123
+ if combo_only: # oylar zaten birleşik sinyale girer; tekil filtreler çift sayılmasın
1124
+ ind_only = ind_confirm = vp_on = False
1125
+ ind_only = ind_only and not ldc_only # iki "Sadece" modu birlikteyse Lorentzian öncelikli
1126
+ if smc_only: # backtest yalnızca SMC girişlerine göre; diğer sinyal/filtre modları devre dışı
1127
+ ind_only = ind_confirm = vp_on = ldc_on = ldc_only = ldc_confirm = False
1128
+ combo_on = combo_only = False
1129
+ need_ldc = ldc_mode != LDC_MODES[0] or combo_on
1130
+ LD = ldc_classify(df, ldc_k, ldc_back, ldc_regime, ldc_kern_lb, ldc_kern_rw,
1131
+ use_vol=bool(ldc_use_vol), use_regime=bool(ldc_use_regime),
1132
+ use_adx=bool(ldc_use_adx), use_kernel=bool(ldc_kernel),
1133
+ exact=bool(ldc_exact)) if need_ldc else None
1134
+ ldc_arr = LD["state"] if LD else np.zeros(len(df), dtype=int)
1135
+ ldc_now = int(ldc_arr[-1])
1136
+ vp_now = int(vp_arr[-1])
1137
+ votes = np.vstack([ind_arr, ldc_arr, vp_arr]) # ST+UT, Lorentzian, Volume Profile
1138
+ n_long, n_short = (votes == 1).sum(axis=0), (votes == -1).sum(axis=0)
1139
+ mv = int(combo_votes)
1140
+ combo_arr = np.where((n_long >= mv) & (n_short == 0), 1,
1141
+ np.where((n_short >= mv) & (n_long == 0), -1, 0)).astype(int)
1142
+ combo_now = int(combo_arr[-1])
1143
+
1144
+ # --- indikatör bazlı SL/TP: en yakın ST / UT / VP seviyesi = invalidasyon (SL),
1145
+ # önündeki Value Area / POC seviyesi >= 1 ATR uzaktaysa TP üst sınırı
1146
+ ind_tpsl = bool(ind_tpsl)
1147
+ if ind_tpsl:
1148
+ c_, a_ = A["c"], A["a"]
1149
+ vpo, vpl, vph = (vp[k].to_numpy(float) for k in ("poc", "val", "vah"))
1150
+
1151
+ def nearest(levels, sign): # sign=+1: seviye fiyatın altında, -1: üstünde
1152
+ d = np.full(len(c_), np.inf)
1153
+ for lv in levels:
1154
+ dd = sign*(c_-lv)
1155
+ ok = np.isfinite(dd) & (dd > 0)
1156
+ d = np.where(ok & (dd < d), dd, d)
1157
+ return d
1158
+ smin, smax = float(tpsl_smin), float(tpsl_smax)
1159
+ sd_of = lambda d: np.clip(np.where(np.isfinite(d), d+0.1*a_, 1.5*a_), smin*a_, smax*a_)
1160
+ cap_of = lambda d: np.where(d >= 1.0*a_, d, np.inf)
1161
+ lv_l = [st_line, ut_stop, vpl] + ([SM["sw_lo"]] if (smc_only and SM is not None) else [])
1162
+ lv_s = [st_line, ut_stop, vph] + ([SM["sw_hi"]] if (smc_only and SM is not None) else [])
1163
+ cap_l, cap_s = [vpo, vph], [vpo, vpl]
1164
+ if smc_lvl_on: # Order Block + önceki D/W/M high-low: SL referansı ve TP sınırı
1165
+ lv_l += [SM["ob_sup"], SM["pdl"], SM["pwl"], SM["pml"]]
1166
+ lv_s += [SM["ob_res"], SM["pdh"], SM["pwh"], SM["pmh"]]
1167
+ cap_l += [SM["ob_res"], SM["pdh"], SM["pwh"], SM["pmh"]]
1168
+ cap_s += [SM["ob_sup"], SM["pdl"], SM["pwl"], SM["pml"]]
1169
+ A["ind_sd"] = {1: sd_of(nearest(lv_l, 1)),
1170
+ -1: sd_of(nearest(lv_s, -1))}
1171
+ A["ind_cap"] = {1: cap_of(nearest(cap_l, -1)),
1172
+ -1: cap_of(nearest(cap_s, 1))}
1173
+
1174
+ # --- K seçimi, kalibrasyon, güncel olasılık
1175
+ best_k, k_scores = tune_k(dev_X, dev_y, h)
1176
+ cal_n = min(120, max(45, int(len(dev_X)*0.20)))
1177
+ fit_end = len(dev_X)-cal_n-h
1178
+ if fit_end <= 100:
1179
+ raise ValueError("Kalibrasyon için eğitim verisi yetersiz.")
1180
+ fit_X, fit_y = dev_X.iloc[:fit_end], dev_y.iloc[:fit_end]
1181
+ cal_X, cal_y = dev_X.iloc[fit_end+h:], dev_y.iloc[fit_end+h:]
1182
+ cal_model = pipe(best_k).fit(fit_X, fit_y)
1183
+ classes_now = cal_model.named_steps["knn"].classes_
1184
+ current_x = X_all.iloc[[-1]]
1185
+ probs = calibrate_current(cal_model.predict_proba(cal_X), cal_y.to_numpy(),
1186
+ cal_model.predict_proba(current_x), classes_now)
1187
+ p_down, p_flat, p_up = (float(p) for p in probs)
1188
+
1189
+ if side_mode == "Sadece Long":
1190
+ side = 1
1191
+ elif side_mode == "Sadece Short":
1192
+ side = -1
1193
+ else:
1194
+ side = 1 if p_up >= p_down else -1
1195
+ if ind_only and ind_now != 0 and allow[ind_now]:
1196
+ side = ind_now
1197
+ if ldc_only and ldc_now != 0 and allow[ldc_now]:
1198
+ side = ldc_now
1199
+ if combo_only and combo_now != 0 and allow[combo_now]:
1200
+ side = combo_now
1201
+ if smc_only and smc_now != 0 and allow[smc_now]:
1202
+ side = smc_now
1203
+ p_side, p_opp = (p_up, p_down) if side == 1 else (p_down, p_up)
1204
+
1205
+ imp, scaler = cal_model.named_steps["imputer"], cal_model.named_steps["scale"]
1206
+ nn = NearestNeighbors(n_neighbors=best_k).fit(scaler.transform(imp.transform(fit_X)))
1207
+ _, idx = nn.kneighbors(scaler.transform(imp.transform(current_x)))
1208
+ agreement = float((fit_y.iloc[idx[0]].to_numpy() == side).mean())
1209
+ if ind_only: # indikatör modunda: son 10 mumda yönün sürekliliği
1210
+ agreement = float((ind_arr[-10:] == side).mean())
1211
+ if ldc_only:
1212
+ agreement = float((ldc_arr[-10:] == side).mean())
1213
+ if combo_only:
1214
+ agreement = float((combo_arr[-10:] == side).mean())
1215
+ if smc_only:
1216
+ agreement = float((smc_arr[-10:] == side).mean())
1217
+
1218
+ # --- yön bazlı TP/SL optimizasyonu (yalnızca geliştirme verisi)
1219
+ current_regime = str(regimes_all.iloc[-1])
1220
+ dev_pos = pos_of(dev_X.index)
1221
+ params, top, regime_used = {}, [], "—"
1222
+
1223
+ def opt_pos(s_):
1224
+ f_ = list(dev_pos)
1225
+ if ind_only or ind_confirm:
1226
+ f_ = [i for i in f_ if ind_arr[i] == s_]
1227
+ if ldc_on:
1228
+ f_ = [i for i in f_ if ldc_arr[i] == s_]
1229
+ if combo_on:
1230
+ f_ = [i for i in f_ if combo_arr[i] == s_]
1231
+ if vp_on:
1232
+ f_ = [i for i in f_ if vp_arr[i] == s_]
1233
+ if gann_on:
1234
+ f_ = [i for i in f_ if gann_arr[i] == s_]
1235
+ if smc_on and not smc_only:
1236
+ f_ = [i for i in f_ if smc_arr[i] == s_]
1237
+ if smc_only:
1238
+ f_ = [i for i in f_ if smc_entry[i] == s_]
1239
+ return f_ if len(f_) >= 35 else dev_pos
1240
+ for s in (1, -1):
1241
+ if allow[s]:
1242
+ sl_s, rr_s, top_s, used_s = optimize_barriers(
1243
+ A, opt_pos(s), reg_arr, s, cost, current_regime, h)
1244
+ params[s] = (sl_s, rr_s)
1245
+ if s == side:
1246
+ top, regime_used = top_s, used_s
1247
+ sl, rr = params[side]
1248
+
1249
+ # --- dokunulmamış OOS bloğu
1250
+ oos_X = X.iloc[dev_end+h:]
1251
+ oos_y = y.loc[oos_X.index]
1252
+ oos_pred = pipe(best_k).fit(dev_X, dev_y).predict(oos_X)
1253
+ oos_bacc = float(balanced_accuracy_score(oos_y, oos_pred))
1254
+ oos_acc = float(accuracy_score(oos_y, oos_pred))
1255
+ oos_pos_arr = pos_of(oos_X.index)
1256
+ if ldc_only: # Lorentzian yönü OOS'ta tahmin yerine geçer
1257
+ oos_pred = ldc_arr[oos_pos_arr]
1258
+ oos_bacc = float(balanced_accuracy_score(oos_y, oos_pred))
1259
+ oos_acc = float(accuracy_score(oos_y, oos_pred))
1260
+ if combo_only: # birleşik sinyal OOS'ta tahmin yerine geçer
1261
+ oos_pred = combo_arr[oos_pos_arr]
1262
+ oos_bacc = float(balanced_accuracy_score(oos_y, oos_pred))
1263
+ oos_acc = float(accuracy_score(oos_y, oos_pred))
1264
+ if ind_only: # indikatör yönü OOS'ta tahmin yerine geçer
1265
+ oos_pred = ind_arr[oos_pos_arr]
1266
+ oos_bacc = float(balanced_accuracy_score(oos_y, oos_pred))
1267
+ oos_acc = float(accuracy_score(oos_y, oos_pred))
1268
+
1269
+ if smc_only: # metrik: SMC yön durumu; işlem: yapı kırılımı mumları
1270
+ oos_pred = smc_arr[oos_pos_arr]
1271
+ oos_bacc = float(balanced_accuracy_score(oos_y, oos_pred))
1272
+ oos_acc = float(accuracy_score(oos_y, oos_pred))
1273
+ oos_pred = smc_entry[oos_pos_arr]
1274
+ rows, busy = [], -1
1275
+ for i, pred in zip(oos_pos_arr, oos_pred):
1276
+ pred = int(pred)
1277
+ if pred == 0 or not allow[pred]:
1278
+ continue
1279
+ if ind_confirm and ind_arr[i] != pred:
1280
+ continue
1281
+ if vp_on and vp_arr[i] != pred:
1282
+ continue
1283
+ if gann_on and gann_arr[i] != pred:
1284
+ continue
1285
+ if smc_on and smc_arr[i] != pred:
1286
+ continue
1287
+ if ldc_confirm and ldc_arr[i] != pred:
1288
+ continue
1289
+ if combo_on and combo_arr[i] != pred:
1290
+ continue
1291
+ if skip_overlap and i <= busy:
1292
+ continue
1293
+ sl_p, rr_p = params[pred]
1294
+ t = barrier_trade(A, i, pred, sl_p, rr_p, h)
1295
+ if t is None:
1296
+ continue
1297
+ ret, j, why = t
1298
+ net = ret-cost
1299
+ r = net/max(stop_dist(A, i, pred, sl_p)/A["c"][i], 1e-12)
1300
+ rows.append((df.index[i], df.index[j], "LONG" if pred == 1 else "SHORT",
1301
+ A["c"][i], why, net*100, r, reg_arr[i]))
1302
+ busy = j
1303
+ trades_df = pd.DataFrame(rows, columns=TRADE_COLS)
1304
+ R = trades_df.R.to_numpy() if len(trades_df) else np.array([])
1305
+ expectancy_r = float(R.mean()) if len(R) else 0.0
1306
+
1307
+ recent = df.Close.tail(30)
1308
+ drawdown = float(recent.iloc[-1]/recent.cummax().iloc[-1]-1)
1309
+ risk_pct, _ = adaptive_risk(min_risk, max_risk, oos_bacc, agreement,
1310
+ expectancy_r, len(R), drawdown)
1311
+ curve, dds = simulate_equity(R, risk_pct)
1312
+ stats = trade_stats(R, curve, dds, trades_df)
1313
+ mc = risk_path_mc(R, risk_pct) if len(R) else None
1314
+
1315
+ # --- güncel seviyeler ve pozisyon
1316
+ entry, cur_atr = float(df.Close.iloc[-1]), float(atr_all.iloc[-1])
1317
+ if ind_tpsl:
1318
+ stop_d = float(A["ind_sd"][side][-1])
1319
+ target_d = float(min(stop_d*rr, A["ind_cap"][side][-1]))
1320
+ cl_ = float(A["c"][-1])
1321
+ cands = {"Supertrend": float(st_line[-1]), "UT trailing stop": float(ut_stop[-1]),
1322
+ "VP " + ("VAL" if side == 1 else "VAH"):
1323
+ float((vp.val if side == 1 else vp.vah).iloc[-1])}
1324
+ if smc_lvl_on:
1325
+ kk_ = ("ob_sup", "pdl", "pwl", "pml") if side == 1 else ("ob_res", "pdh", "pwh", "pmh")
1326
+ for k_, nm_ in zip(kk_, ("SMC OB", "Önceki gün", "Önceki hafta", "Önceki ay")):
1327
+ cands[nm_] = float(SM[k_][-1])
1328
+ if smc_only and SM is not None:
1329
+ cands["SMC swing " + ("low" if side == 1 else "high")] = float(SM["sw_lo" if side == 1 else "sw_hi"][-1])
1330
+ valid = {k_: side*(cl_-v_) for k_, v_ in cands.items() if np.isfinite(v_) and side*(cl_-v_) > 0}
1331
+ sl_name = min(valid, key=valid.get) if valid else "ATR varsayılan (seviye yok)"
1332
+ else:
1333
+ stop_d, target_d = sl*cur_atr, sl*cur_atr*rr
1334
+ stop = entry-stop_d if side == 1 else entry+stop_d
1335
+ target = entry+target_d if side == 1 else entry-target_d
1336
+
1337
+ risk_cash = equity*risk_pct/100
1338
+ cost_per_unit = stop_d+entry*cost
1339
+ qty = risk_cash/cost_per_unit if cost_per_unit > 0 else 0.0
1340
+ max_notional = equity*float(max_leverage)
1341
+ capped = qty*entry > max_notional
1342
+ if capped:
1343
+ qty = max_notional/entry
1344
+ if whole_units:
1345
+ qty = float(np.floor(qty))
1346
+ notional = qty*entry
1347
+ actual_risk = qty*cost_per_unit
1348
+ margin = notional/max(float(max_leverage), 1)
1349
+
1350
+ # --- işlem kapısı (her kontrol ayrı gösterilir)
1351
+ checks = [
1352
+ ("Y��n izni", side_mode, "—", allow[side]),
1353
+ ("P(yön)", f"{p_side:.1%}", f"≥ {p_min:.0%}", p_side >= p_min),
1354
+ ("P(yön) − P(karşı)", f"{(p_side-p_opp):.1%}", f"≥ {p_gap:.0%}", (p_side-p_opp) >= p_gap),
1355
+ ("P(FLAT)", f"{p_flat:.1%}", f"< {flat_max:.0%}", p_flat < flat_max),
1356
+ ("Komşu yön uyumu", f"{agreement:.1%}", f"≥ {agree_min:.0%}", agreement >= agree_min),
1357
+ ("OOS balanced acc.", f"{oos_bacc:.3f}", f"≥ {bacc_min:.2f}", oos_bacc >= bacc_min),
1358
+ ("OOS işlem sayısı", f"{len(R)}", f"≥ {int(min_trades)}", len(R) >= int(min_trades)),
1359
+ ("OOS expectancy", f"{expectancy_r:.3f}R", "> 0", expectancy_r > 0),
1360
+ ]
1361
+ dname = {1: "LONG ↑", -1: "SHORT ↓", 0: "belirsiz"}
1362
+ ind_check = ("İndikatör yönü (ST + UT aynı)", dname[ind_now], "yönle aynı", ind_now == side)
1363
+ if ind_only or ldc_only or combo_only or smc_only:
1364
+ keep = ("Yön izni", "OOS balanced acc.", "OOS işlem sayısı", "OOS expectancy")
1365
+ if smc_only:
1366
+ keep = tuple(k_ for k_ in keep if k_ != "OOS balanced acc.")
1367
+ checks = [c for c in checks if c[0] in keep]
1368
+ if ind_only or ind_confirm:
1369
+ checks.insert(1, ind_check)
1370
+ if ldc_on:
1371
+ checks.insert(1, ("Lorentzian yönü (ML + kernel)", dname[ldc_now], "yönle aynı",
1372
+ ldc_now == side))
1373
+ if combo_on:
1374
+ nv = int(sum(1 for v_ in (ind_now, ldc_now, vp_now) if v_ == side))
1375
+ no_ = int(sum(1 for v_ in (ind_now, ldc_now, vp_now) if v_ == -side))
1376
+ checks.insert(1, ("Birleşik sinyal (ST+UT · Lorentzian · VP)", f"{nv}/3 oy, karşı {no_}",
1377
+ f"≥ {mv} oy, karşı oy yok", combo_now == side))
1378
+ if ind_tpsl:
1379
+ rr_now = target_d/stop_d if stop_d > 0 else 0.0
1380
+ checks.append(("İndikatör R:R (TP/SL)", f"{rr_now:.2f}", f"≥ {float(tpsl_minrr):g}",
1381
+ rr_now >= float(tpsl_minrr)))
1382
+ if vp_on:
1383
+ checks.insert(1, ("Volume Profile (POC/VA konumu)", vp_names[int(vp_arr[-1])],
1384
+ "yönle aynı", int(vp_arr[-1]) == side))
1385
+ if gann_on:
1386
+ checks.insert(1, ("Gann Box (%50 konumu)", dname[gann_now], "yönle aynı", gann_now == side))
1387
+ if smc_on:
1388
+ checks.insert(1, (f"SMC yapı yönü ({smc_src}" + (" + premium/discount)" if smc_zone else ")"),
1389
+ dname[smc_now], "yönle aynı", smc_now == side))
1390
+ trade_ok = all(c[3] for c in checks)
1391
+ if trade_ok:
1392
+ signal = "LONG" if side == 1 else "SHORT"
1393
+ else:
1394
+ signal = "WAIT — İŞLEM AÇMA"
1395
+ qty = notional = margin = actual_risk = 0.0
1396
+ gate_md = md_table(["Kontrol", "Değer", "Eşik", "Durum"],
1397
+ [(n, v, t, "✅" if ok else "❌") for n, v, t, ok in checks])
1398
+
1399
+ # --- rapor
1400
+ if stats:
1401
+ pf = "∞" if np.isinf(stats["pf"]) else f"{stats['pf']:.2f}"
1402
+ oos_md = md_table(["Ölçüm", "Değer"], [
1403
+ ("İşlem sayısı", stats["n"]),
1404
+ ("Kazanma oranı", f"{stats['win']:.1%}"),
1405
+ ("Expectancy", f"{stats['exp']:.3f}R"),
1406
+ ("Profit factor", pf),
1407
+ (f"Toplam getiri (risk {risk_pct:.2f}%)", f"{stats['ret']:.2%}"),
1408
+ ("Maks. drawdown", f"{stats['mdd']:.2%}"),
1409
+ ("Çıkış dağılımı TP / SL / süre", f"{stats['tp']} / {stats['sl']} / {stats['time']}"),
1410
+ ])
1411
+ g = trades_df.groupby("regime").R.agg(["count", "mean", lambda s: (s > 0).mean()])
1412
+ reg_md = md_table(["Rejim", "İşlem", "Ort. R", "Kazanma"],
1413
+ [(r, int(c), f"{m:.3f}", f"{w:.1%}")
1414
+ for r, (c, m, w) in g.iterrows()])
1415
+ else:
1416
+ oos_md = reg_md = "_OOS işlem yok._"
1417
+
1418
+ mc_text = (
1419
+ f"100 işlem · 2.000 bootstrap senaryosu · medyan: {mc['median']:.2%} · "
1420
+ f"%5 kötü sonuç: {mc['p05']:.2%} · %95 iyi sonuç: {mc['p95']:.2%} · "
1421
+ f"%5 kuyruk max DD: {mc['dd95']:.2%} · "
1422
+ f"%50 sermaye altına inme frekansı: {mc['ruin']:.1%}"
1423
+ if mc else "Monte Carlo için yeterli OOS işlem yok.")
1424
+ ind_md = md_table(["İndikatör", "Yön", "Seviye"], [
1425
+ (f"Supertrend ({int(st_period)}, {float(st_mult):g})", dname[int(st_dir[-1])], fmt_p(float(st_line[-1]))),
1426
+ (f"UT Bot ({float(ut_key):g}, {int(ut_period)})", dname[int(ut_pos[-1])], fmt_p(float(ut_stop[-1]))),
1427
+ ("Bileşik (ikisi aynı yönde)", dname[ind_now], "—")])
1428
+ if lp is not None:
1429
+ _, _, poc_n, val_n, vah_n = lp
1430
+ pos_txt = ("VA üstünde" if entry > vah_n else "VA altında" if entry < val_n
1431
+ else "VA içinde · POC üstü" if entry > poc_n else "VA içinde · POC altı")
1432
+ vp_md = md_table(["Seviye", "Fiyat"], [
1433
+ ("VAH (Value Area üst)", fmt_p(vah_n)), ("POC (en yüksek hacim)", fmt_p(poc_n)),
1434
+ ("VAL (Value Area alt)", fmt_p(val_n)), ("Son kapanış", f"{fmt_p(entry)} — {pos_txt}"),
1435
+ ("VP yön onayı", vp_names[int(vp_arr[-1])])])
1436
+ else:
1437
+ vp_md = "_Profil hesaplanamadı._"
1438
+ vp_note = (f"Pencere {vp_win} mum · {vp_nb} kutu · Value Area %{vp_vap*100:.0f} · "
1439
+ f"uzama sınırı {float(vp_ext):g} ATR."
1440
+ + (" Hacim verisi yok → zaman bazlı profil kullanıldı." if vp_no_vol else "")
1441
+ + (" Mod Kapalı: yalnızca bilgi amaçlı." if not vp_on else ""))
1442
+ if SM is not None:
1443
+ last_ev = [e for e in SM["events"] if not e["internal"]][-1:] or [None]
1444
+ last_iv = [e for e in SM["events"] if e["internal"]][-1:] or [None]
1445
+ evtxt = lambda e: "—" if e is None else (f"{e['kind']} {'↑' if e['bull'] else '↓'} · {fmt_p(e['lvl'])} · "
1446
+ f"{df.index[e['i']].date()}")
1447
+ tp_, bt_ = float(SM["top"][-1]), float(SM["bot"][-1])
1448
+ zpos = (entry-bt_)/(tp_-bt_) if tp_ > bt_ else float("nan")
1449
+ zname = ("—" if not np.isfinite(zpos) else
1450
+ "Premium" if zpos >= 0.95 else
1451
+ "Discount" if zpos <= 0.05 else
1452
+ "Equilibrium")
1453
+ obt = lambda b: [o_ for o_ in SM["obs"] if o_["bull"] == b and not o_["internal"]][-1:]
1454
+ ob_txt = lambda b: (f"{fmt_p(obt(b)[0]['lo'])} – {fmt_p(obt(b)[0]['hi'])}" if obt(b) else "—")
1455
+ smc_md = md_table(["Bileşen", "Değer"], [
1456
+ ("Swing yapı yönü", dname[int(SM["swing_trend"][-1])]),
1457
+ ("Internal yapı yönü", dname[int(SM["internal_trend"][-1])]),
1458
+ ("Son swing kırılımı", evtxt(last_ev[0])), ("Son internal kırılımı", evtxt(last_iv[0])),
1459
+ ("Premium / Discount", f"{zname}" + (f" (%{zpos*100:.0f})" if np.isfinite(zpos) else "")),
1460
+ ("Strong/Weak High · Low", f"{'Strong' if SM['strong_high'] else 'Weak'} {fmt_p(tp_)} · "
1461
+ f"{'Strong' if SM['strong_low'] else 'Weak'} {fmt_p(bt_)}"),
1462
+ ("Aktif swing OB (bull)", ob_txt(True)), ("Aktif swing OB (bear)", ob_txt(False)),
1463
+ ("Önceki gün H / L", f"{fmt_p(SM['pdh'][-1])} / {fmt_p(SM['pdl'][-1])}"),
1464
+ ("Önceki hafta H / L", f"{fmt_p(SM['pwh'][-1])} / {fmt_p(SM['pwl'][-1])}"),
1465
+ ("Önceki ay H / L", f"{fmt_p(SM['pmh'][-1])} / {fmt_p(SM['pml'][-1])}"),
1466
+ ("Açık FVG sayısı", len(SM["fvg"])), ("SMC yön onayı", dname[smc_now])])
1467
+ smc_note = (f"Swing {int(smc_swing_len)} · internal {int(smc_int_len)} mum"
1468
+ + (" · premium/discount kapısı açık" if smc_zone else "")
1469
+ + (" · internal confluence filtresi (LuxAlgo Pine koşulları)" if smc_conf else "")
1470
+ + " · BOS/CHoCH yalnızca pivot doğrulandıktan ve kapanış seviyesi kırdıktan sonra sayılır"
1471
+ + f" · OB filtresi: {smc_obf}"
1472
+ + (" · OB + önceki D/W/M seviyeleri SL/TP'de" if smc_lvl_on else "")
1473
+ + (" · Mod Kapalı: yalnızca bilgi/grafik." if not smc_on else "") + ".")
1474
+ else:
1475
+ smc_md, smc_note = "_Mod kapalı._", ""
1476
+ if GB is not None:
1477
+ gpos = gann_pos[-1]
1478
+ gann_md = md_table(["Seviye", "Fiyat"],
1479
+ [(f"{p:.3f}" + (" (orta)" if p == 0.5 else ""), fmt_p(y)) for p, y in sorted(GB["levels"].items(), reverse=True)]
1480
+ + [("Son kapanış", f"{fmt_p(entry)} — kutu içi konum %{gpos*100:.0f}" if np.isfinite(gpos) else fmt_p(entry)),
1481
+ ("Kutu yönü", "yükseliş (düşük → yüksek)" if GB["up"] else "düşüş (yüksek → düşük)"),
1482
+ ("Gann yön onayı", dname[gann_now])])
1483
+ gann_note = (f"Pencere {int(gann_lb)} mum · yüksek {GB['hi_date'].date()} · düşük {GB['lo_date'].date()}"
1484
+ + (" · yön çevrildi (reverse)" if gann_rev else "")
1485
+ + (" · Mod Kapalı: yalnızca bilgi amaçlı." if not gann_on else "") + ".")
1486
+ else:
1487
+ gann_md, gann_note = "_Kutu hesaplanamadı._", ""
1488
+ if LD is not None:
1489
+ fl = lambda on, arr: "kapalı" if not on else ("✅" if bool(arr[-1]) else "❌")
1490
+ ldc_md = md_table(["Bileşen", "Değer"], [
1491
+ ("Lorentzian yönü (ML + kernel)", dname[ldc_now]),
1492
+ ("ML sinyali (filtreli)", dname[int(LD["signal"][-1])]),
1493
+ ("ML tahmin skoru", f"{int(LD['pred'][-1]):+d} / ±{int(ldc_k)}"),
1494
+ ("Kernel tahmini · eğim", f"{fmt_p(float(LD['yhat'][-1]))} · "
1495
+ f"{'↑' if LD['yhat'][-1] > LD['yhat'][-2] else '↓'}"),
1496
+ ("Volatilite / Rejim / ADX filtresi",
1497
+ f"{fl(ldc_use_vol, LD['vol'])} / {fl(ldc_use_regime, LD['reg'])} / {fl(ldc_use_adx, LD['adx'])}")])
1498
+ ldc_note = (f"{int(ldc_k)} komşu · en fazla {int(ldc_back)} geçmiş mum · kernel "
1499
+ f"({int(ldc_kern_lb)}, {float(ldc_kern_rw):g}) · rejim eşiği {float(ldc_regime):g}.")
1500
+ else:
1501
+ ldc_md, ldc_note = "_Mod kapalı._", ""
1502
+ if combo_on:
1503
+ combo_md = md_table(["Kaynak", "Oy"], [
1504
+ ("Supertrend + UT Bot", dname[ind_now]), ("Lorentzian (ML + kernel)", dname[ldc_now]),
1505
+ ("Volume Profile (POC/VA)", dname[vp_now]),
1506
+ ("**Birleşik sinyal**", f"**{dname[combo_now]}** (en az {mv} oy, karşı oy yok)")])
1507
+ else:
1508
+ combo_md = "_SMC-only modunda devre dışı._" if smc_only else "_Mod kapalı._"
1509
+ if ind_tpsl:
1510
+ capped = target_d < stop_d*rr-1e-12
1511
+ tp_title = "indikatörlerden türetildi"
1512
+ tpsl_note = (f"SL: en yakın invalidasyon seviyesi — **{sl_name}** (+0,1 ATR tampon, "
1513
+ f"{float(tpsl_smin):g}–{float(tpsl_smax):g} ATR aralığına sıkıştırılır). "
1514
+ f"TP: {rr:.2f}R katı"
1515
+ + (", önündeki VA/POC/SMC OB/MTF seviyesinde sınırlandı" if capped else "")
1516
+ + ". Optimize edilen tek parametre R katıdır.\n\n")
1517
+ else:
1518
+ tp_title, tpsl_note = "optimize", ""
1519
+ k_text = " | ".join(f"K={k}: {s:.3f}" for s, k in sorted(k_scores, reverse=True)) or "—"
1520
+ overlap_note = ("tek pozisyon (çakışma yok)" if skip_overlap
1521
+ else "çakışan sinyaller bağımsız işlem sayıldı")
1522
+ report = f"""
1523
+ # {signal}
1524
+
1525
+ **Son kapanmış mum:** {df.index[-1].date()} · **{ticker} referans fiyatı:** {fmt_p(entry)} · **Ufuk:** {h} gün
1526
+
1527
+ ## Kapı kontrolleri
1528
+ {gate_md}
1529
+
1530
+ ## {ticker} — Kalibre edilmiş KNN
1531
+ | Ölçüm | Değer |
1532
+ |---|---:|
1533
+ | P(UP) / P(FLAT) / P(DOWN) | {p_up:.1%} / {p_flat:.1%} / {p_down:.1%} |
1534
+ | Seçilen K | {best_k} |
1535
+ | OOS accuracy | {oos_acc:.3f} |
1536
+ | Güncel rejim | {current_regime} |
1537
+
1538
+ **K seçim skorları:** {k_text}
1539
+
1540
+ ## İndikatörler — mod: {ind_mode}
1541
+ {ind_md}
1542
+
1543
+ ## Birleşik sinyal — mod: {combo_mode}
1544
+ {combo_md}
1545
+
1546
+ ## Lorentzian Classification — mod: {ldc_mode}
1547
+ {ldc_md}
1548
+
1549
+ {ldc_note}
1550
+
1551
+ ## Volume Profile — mod: {vp_mode}
1552
+ {vp_md}
1553
+
1554
+ {vp_note}
1555
+
1556
+ ## Smart Money Concepts (LuxAlgo) — mod: {smc_mode}
1557
+ {smc_md}
1558
+
1559
+ {smc_note}
1560
+
1561
+ ## Gann Box (Zeiierman) — mod: {gann_mode}
1562
+ {gann_md}
1563
+
1564
+ {gann_note}
1565
+
1566
+ ## TP / SL ({'LONG' if side == 1 else 'SHORT'} için {tp_title})
1567
+ | Parametre | Değer |
1568
+ |---|---:|
1569
+ | SL | {stop_d/cur_atr:.2f} ATR |
1570
+ | TP | {target_d/stop_d:.2f}R |
1571
+ | Stop-loss | {fmt_p(stop)} |
1572
+ | Take-profit | {fmt_p(target)} |
1573
+ | ATR(14) | {fmt_p(cur_atr)} |
1574
+
1575
+ {tpsl_note}TP/SL optimizasyon rejimi: {regime_used}. Yeterli rejim örneği yoksa tüm geliştirme dönemine geri dönülür.
1576
+
1577
+ ## OOS backtest ({overlap_note})
1578
+ {oos_md}
1579
+
1580
+ **Rejim bazlı OOS:**
1581
+
1582
+ {reg_md}
1583
+
1584
+ ## Risk-bütçeli pozisyon ({ticker})
1585
+ | Parametre | Değer |
1586
+ |---|---:|
1587
+ | Hesap büyüklüğü | ${equity:,.2f} |
1588
+ | Adaptif risk | {risk_pct:.3f}% |
1589
+ | Risk bütçesi | ${risk_cash:,.2f} |
1590
+ | Varlık miktarı (birim) | {qty:.8f} |
1591
+ | Nominal pozisyon | ${notional:,.2f} |
1592
+ | Tahmini teminat | ${margin:,.2f} |
1593
+ | Tahmini stop kaybı + maliyet | ${actual_risk:,.2f} |
1594
+ | Kaldıraç sınırı | {"Uygulandı" if capped else "Uygulanmadı"} |
1595
+ | Son 30 günlük kapanış DD | {drawdown:.2%} |
1596
+
1597
+ ## Monte Carlo
1598
+ {mc_text}
1599
+ """
1600
+ top_df = pd.DataFrame(top, columns=["score", "SL_ATR", "TP_R", "samples",
1601
+ "mean_net_return", "win_rate"])
1602
+ if ind_tpsl and not top_df.empty:
1603
+ top_df["SL_ATR"] = "indikatör"
1604
+ if not top_df.empty:
1605
+ top_df["score"] = top_df.score.map(lambda v: f"{v:.5f}")
1606
+ top_df["mean_net_return"] = top_df.mean_net_return.map(lambda v: f"{v:.3%}")
1607
+ top_df["win_rate"] = top_df.win_rate.map(lambda v: f"{v:.1%}")
1608
+
1609
+ csv_path = None
1610
+ if len(trades_df):
1611
+ safe = re.sub(r"[^A-Za-z0-9_-]", "_", ticker)
1612
+ csv_path = os.path.join(tempfile.gettempdir(), f"goshawk_{safe}_oos_trades.csv")
1613
+ trades_df.to_csv(csv_path, index=False)
1614
+
1615
+ price_fig = price_figure(df, ticker, trades_df, trade_ok, stop, target,
1616
+ ind=(st_line, st_dir, ut_stop),
1617
+ vp=vp if (vp_on or combo_on) else None, vp_prof=lp,
1618
+ ldc=LD, combo=combo_arr if combo_on else None,
1619
+ gann=GB, gann_fan=bool(gann_fan_on), gann_extend=bool(gann_ext_on), smc=SM)
1620
+ eq_fig = equity_figure(curve, dds) if len(R) else None
1621
+ pf_fig = pf_gauge(stats["pf"], stats["n"]) if stats else None
1622
+ mc_fig = mc_figure(mc) if mc else None
1623
+
1624
+ disclaimer = (
1625
+ "**Araştırma prototipi; otomatik emir göndermez.** KNN olasılık kalibrasyonu tek bir "
1626
+ "tarihsel kalibrasyon dilimine dayanır ve küçük örneklerde ham olasılığa geri döner. "
1627
+ "TP/SL optimizasyonu geliştirme döneminde ve yön bazında yapılır; son dönem OOS olarak "
1628
+ "ayrılır. Rejim etiketleri geliştirme verisinin volatilite kantillerine dayanır; canlı "
1629
+ "dağılım değişebilir. Adaptif risk yüzdesi OOS metriklerinden türetildiği için OOS "
1630
+ "özsermaye eğrisi iyimser olabilir. Günlük mumda SL ve TP aynı mumda görülürse SL "
1631
+ "varsayılır. Monte Carlo, OOS R-multiple serisini bootstrap eder; bağımsızlık varsayımı "
1632
+ "seri korelasyonunu ve rejim değişimini tam modellemez. Pozisyon hesabı spot miktar "
1633
+ "varsayar; CFD lot sözleşmesi, spread, funding, swap, komisyon, minimum lot, marjin ve "
1634
+ "broker fiyatı farklı olabilir. Gap ve kayma kaybı planlanan riskin üstüne çıkarabilir. "
1635
+ "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."
1636
+ )
1637
+ return (report, prob_figure(p_down, p_flat, p_up), price_fig, eq_fig, pf_fig, mc_fig,
1638
+ top_df, csv_path, disclaimer)
1639
+
1640
+ except Exception as exc:
1641
+ return (f"# Hata\n{type(exc).__name__}: {exc}", None, None, None, None, None,
1642
+ pd.DataFrame(), None,
1643
+ "Veri bağlantısını ve parametreleri kontrol edin.")
1644
+
1645
+
1646
+ # ---------------------------------------------------------------- arayüz
1647
+ with gr.Blocks(title=APP_TITLE, theme=gr.themes.Soft()) as demo:
1648
+ gr.Markdown(
1649
+ "# GOSHAWK — Multi-Asset Adaptive Risk Engine v5\n"
1650
+ "Kalibre KNN · adaptif K · yön bazlı TP/SL · bağımsız OOS backtest · "
1651
+ "R-multiple Monte Carlo · drawdown bazlı risk · kapı kontrolleri."
1652
+ )
1653
+ with gr.Row():
1654
+ with gr.Column():
1655
+ ticker = gr.Dropdown(ASSETS, value="BTC-USD", allow_custom_value=True,
1656
+ label="Varlık (listeden seç ya da Yahoo sembolü yaz)")
1657
+ period = gr.Dropdown(["2y", "5y", "10y", "max"], value="5y", label="Veri geçmişi")
1658
+ horizon = gr.Dropdown([3, 5, 10], value=5, label="Tahmin ufku (gün)")
1659
+ side_mode = gr.Radio(SIDE_MODES, value="Long + Short", label="İzin verilen yön")
1660
+ ind_mode = gr.Radio(IND_MODES, value="Kapalı", label="İndikatör modu (Supertrend + UT Bot)")
1661
+ vp_mode = gr.Radio(VP_MODES, value="Kapalı", label="Volume Profile modu")
1662
+ ldc_mode = gr.Radio(LDC_MODES, value="Kapalı", label="Lorentzian Classification modu")
1663
+ combo_mode = gr.Radio(COMBO_MODES, value=COMBO_MODES[2],
1664
+ label="Birleşik sinyal (ST+UT · Lorentzian · Volume Profile oylaması)")
1665
+ gann_mode = gr.Radio(GANN_MODES, value=GANN_MODES[0], label="Gann Box modu (Zeiierman)")
1666
+ smc_mode = gr.Radio(SMC_MODES, value=SMC_MODES[0], label="Smart Money Concepts modu (LuxAlgo)")
1667
+ ind_tpsl = gr.Checkbox(value=True, label="TP/SL'i indikatörlerden türet (ST / UT / VP seviyeleri)")
1668
+ train_window = gr.Slider(300, 1500, value=900, step=50, label="Maksimum eğitim penceresi")
1669
+ with gr.Column():
1670
+ equity = gr.Number(value=10000, label="Hesap büyüklüğü (USD)")
1671
+ max_leverage = gr.Slider(1, 10, value=3, step=1, label="Azami kaldıraç")
1672
+ min_risk = gr.Slider(0.05, 0.5, value=0.10, step=0.05, label="Minimum risk (%)")
1673
+ max_risk = gr.Slider(0.25, 1.5, value=0.75, step=0.05, label="Maksimum risk (%)")
1674
+ fee = gr.Number(value=0.05, label="Tek yön komisyon (%)")
1675
+ slip = gr.Number(value=0.03, label="Tek yön slippage (%)")
1676
+ with gr.Accordion("Gelişmiş: işlem kapısı eşikleri ve simülasyon", open=False):
1677
+ with gr.Row():
1678
+ p_min = gr.Slider(0.34, 0.70, value=0.43, step=0.01, label="Min. P(yön)")
1679
+ p_gap = gr.Slider(0.0, 0.40, value=0.12, step=0.01, label="Min. P(yön) − P(karşı)")
1680
+ flat_max = gr.Slider(0.30, 0.90, value=0.60, step=0.01, label="Maks. P(FLAT)")
1681
+ with gr.Row():
1682
+ agree_min = gr.Slider(0.30, 0.90, value=0.55, step=0.01, label="Min. komşu uyumu")
1683
+ bacc_min = gr.Slider(0.30, 0.60, value=0.36, step=0.01, label="Min. OOS balanced acc.")
1684
+ min_trades = gr.Slider(5, 50, value=15, step=1, label="Min. OOS işlem sayısı")
1685
+ with gr.Row():
1686
+ st_period = gr.Slider(5, 30, value=10, step=1, label="Supertrend ATR periyodu")
1687
+ st_mult = gr.Slider(1.0, 6.0, value=3.0, step=0.1, label="Supertrend çarpanı")
1688
+ ut_key = gr.Slider(0.5, 4.0, value=1.0, step=0.1, label="UT Bot key value")
1689
+ ut_period = gr.Slider(5, 30, value=10, step=1, label="UT Bot ATR periyodu")
1690
+ with gr.Row():
1691
+ combo_votes = gr.Slider(1, 3, value=2, step=1, label="Birleşik sinyal: min. aynı yön oyu (karşı oy yok)")
1692
+ tpsl_smin = gr.Slider(0.3, 2.0, value=0.8, step=0.1, label="İndikatör SL alt sınır (ATR)")
1693
+ tpsl_smax = gr.Slider(1.5, 6.0, value=3.0, step=0.1, label="İndikatör SL üst sınır (ATR)")
1694
+ tpsl_minrr = gr.Slider(0.5, 3.0, value=1.0, step=0.1, label="Min. R:R (kapı)")
1695
+ with gr.Row():
1696
+ ldc_k = gr.Slider(1, 30, value=8, step=1, label="Lorentzian komşu sayısı")
1697
+ ldc_back = gr.Slider(300, 3000, value=2000, step=100, label="Lorentzian maks. geçmiş mum")
1698
+ ldc_regime = gr.Slider(-2.0, 2.0, value=-0.1, step=0.1, label="Rejim eşiği")
1699
+ with gr.Row():
1700
+ ldc_kern_lb = gr.Slider(3, 50, value=8, step=1, label="Kernel lookback")
1701
+ ldc_kern_rw = gr.Slider(0.25, 25, value=8, step=0.25, label="Kernel göreli ağırlık")
1702
+ with gr.Row():
1703
+ ldc_use_vol = gr.Checkbox(value=True, label="Volatilite filtresi")
1704
+ ldc_use_regime = gr.Checkbox(value=True, label="Rejim filtresi")
1705
+ ldc_use_adx = gr.Checkbox(value=False, label="ADX filtresi (eşik 20)")
1706
+ ldc_kernel = gr.Checkbox(value=True, label="Kernel ile onayla")
1707
+ ldc_exact = gr.Checkbox(value=True, label="Gerçek en yakın komşu (kapalı: Pine yaklaşık-NN)")
1708
+ with gr.Row():
1709
+ vp_window = gr.Slider(20, 250, value=60, step=5, label="Volume Profile penceresi (mum)")
1710
+ vp_bins = gr.Slider(12, 60, value=24, step=2, label="Fiyat kutusu sayısı")
1711
+ vp_va = gr.Slider(0.50, 0.90, value=0.70, step=0.05, label="Value Area oranı")
1712
+ vp_ext = gr.Slider(0.5, 4.0, value=1.5, step=0.1, label="Maks. uzama (ATR) — kovalamayı engeller")
1713
+ with gr.Row():
1714
+ gann_lb = gr.Slider(30, 150, value=150, step=5, label="Gann Box penceresi (mum)")
1715
+ gann_fan_on = gr.Checkbox(value=True, label="Gann Fan göster")
1716
+ gann_ext_on = gr.Checkbox(value=True, label="Kutuyu geleceğe uzat (Extend)")
1717
+ gann_rev = gr.Checkbox(value=False, label="Reverse (yönü çevir)")
1718
+ with gr.Row():
1719
+ smc_src = gr.Radio(SMC_SRC, value=SMC_SRC[0], label="SMC yön kaynağı")
1720
+ smc_swing_len = gr.Slider(10, 100, value=50, step=5, label="SMC swing yapı uzunluğu")
1721
+ smc_int_len = gr.Slider(3, 15, value=5, step=1, label="SMC internal yapı uzunluğu")
1722
+ smc_zone = gr.Checkbox(value=False, label="Premium/Discount kapısı (long=discount, short=premium)")
1723
+ smc_draw = gr.Checkbox(value=True, label="SMC katmanlarını grafikte göster")
1724
+ with gr.Row():
1725
+ smc_conf = gr.Checkbox(value=False, label="Internal confluence filtresi (zayıf kırılımları ele)")
1726
+ smc_obf = gr.Radio(SMC_OBF, value=SMC_OBF[0], label="Order Block volatilite filtresi")
1727
+ smc_lvl = gr.Checkbox(value=True, label="SMC seviyeleri TP/SL'de kullan (Order Block + önceki gün/hafta/ay H-L)")
1728
+ with gr.Row():
1729
+ skip_overlap = gr.Checkbox(value=False, label="Açık pozisyon varken yeni sinyali atla (tek pozisyon)")
1730
+ whole_units = gr.Checkbox(value=False, label="Miktarı tam sayıya yuvarla (hisse)")
1731
+ run = gr.Button("Öğren, optimize et ve test et", variant="primary")
1732
+
1733
+ with gr.Tabs():
1734
+ with gr.Tab("Sinyal"):
1735
+ output = gr.Markdown()
1736
+ prob_plot = gr.Plot()
1737
+ with gr.Tab("Grafik"):
1738
+ chart = gr.Plot()
1739
+ with gr.Tab("OOS backtest"):
1740
+ pf_plot = gr.Plot()
1741
+ equity_plot = gr.Plot()
1742
+ with gr.Tab("Monte Carlo"):
1743
+ mc_plot = gr.Plot()
1744
+ with gr.Tab("TP/SL optimizasyon"):
1745
+ optimization = gr.Dataframe(label="En iyi TP/SL kombinasyonları", interactive=False)
1746
+ with gr.Tab("İşlem kaydı"):
1747
+ csv_file = gr.File(label="OOS işlemleri (CSV)", interactive=False)
1748
+ disclaimer = gr.Markdown()
1749
+
1750
+ run.click(
1751
+ run_engine,
1752
+ inputs=[ticker, period, horizon, side_mode, equity, min_risk, max_risk,
1753
+ max_leverage, fee, slip, train_window,
1754
+ p_min, p_gap, flat_max, agree_min, bacc_min, min_trades,
1755
+ skip_overlap, whole_units, ind_mode, st_period, st_mult, ut_key, ut_period,
1756
+ vp_mode, vp_window, vp_bins, vp_va, vp_ext,
1757
+ ldc_mode, ldc_k, ldc_back, ldc_regime, ldc_kern_lb, ldc_kern_rw,
1758
+ ldc_use_vol, ldc_use_regime, ldc_use_adx, ldc_kernel, ldc_exact,
1759
+ combo_mode, combo_votes, ind_tpsl, tpsl_smin, tpsl_smax, tpsl_minrr,
1760
+ gann_mode, gann_lb, gann_fan_on, gann_ext_on, gann_rev,
1761
+ smc_mode, smc_src, smc_swing_len, smc_int_len, smc_zone, smc_draw,
1762
+ smc_conf, smc_obf, smc_lvl],
1763
+ outputs=[output, prob_plot, chart, equity_plot, pf_plot, mc_plot, optimization,
1764
+ csv_file, disclaimer],
1765
+ )
1766
+
1767
+ if __name__ == "__main__":
1768
+ demo.launch(server_name="0.0.0.0", server_port=7860)