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

Delete app.py

Browse files
Files changed (1) hide show
  1. app.py +0 -1768
app.py DELETED
@@ -1,1768 +0,0 @@
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)