GoshawkVortexAI commited on
Commit
074a0ae
·
verified ·
1 Parent(s): f757a9f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +163 -20
app.py CHANGED
@@ -29,6 +29,7 @@ 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
 
33
  _CACHE = {}
34
  CACHE_TTL = 600 # saniye; Yahoo'yu gereksiz yere tekrar çağırmamak için
@@ -66,7 +67,7 @@ def calc_rsi(s, n=14):
66
  return 100 - 100/(1+rs)
67
 
68
 
69
- def make_features(df):
70
  c, v = df.Close, df.Volume
71
  x = pd.DataFrame(index=df.index)
72
  for n in [1, 2, 3, 5, 10, 20, 50]:
@@ -89,6 +90,10 @@ def make_features(df):
89
  x["dist_hi_20"] = c/df.High.rolling(20).max()-1
90
  x["dist_lo_20"] = c/df.Low.rolling(20).min()-1
91
  x["bb_z20"] = (c-c.rolling(20).mean())/c.rolling(20).std().replace(0, np.nan)
 
 
 
 
92
  return x.replace([np.inf, -np.inf], np.nan), atr
93
 
94
 
@@ -139,6 +144,78 @@ def ut_bot(df, key=1.0, period=10):
139
  return pos, stop
140
 
141
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
  def regime_labels(features, ref_vol):
143
  # Volatilite kantilleri yalnızca geliştirme dönemi verisinden alınır (sızıntı yok).
144
  q1, q2 = ref_vol.dropna().quantile([0.33, 0.67])
@@ -340,21 +417,44 @@ TRADE_COLS = ["entry_date", "exit_date", "side", "entry_price", "exit_reason",
340
 
341
 
342
  # ---------------------------------------------------------------- grafikler
343
- def price_figure(df, ticker, trades_df, trade_ok, stop, target, ind=None):
344
  w = df.tail(150)
345
- fig = go.Figure([go.Candlestick(x=w.index, open=w.Open, high=w.High, low=w.Low,
346
- close=w.Close, name=ticker)])
 
 
 
 
 
 
 
347
  for n, col in [(20, "#f59e0b"), (50, "#3b82f6")]:
348
  ema = df.Close.ewm(span=n, adjust=False).mean().tail(150)
349
  fig.add_trace(go.Scatter(x=w.index, y=ema, mode="lines", name=f"EMA{n}",
350
- line=dict(width=1.2, color=col)))
351
  if ind is not None:
352
  st_line, st_dir, ut_stop = (np.asarray(a)[-150:] for a in ind)
353
  for d, col, nm in [(1, "#16a34a", "Supertrend ↑"), (-1, "#dc2626", "Supertrend ↓")]:
354
  fig.add_trace(go.Scatter(x=w.index, y=np.where(st_dir == d, st_line, np.nan),
355
- mode="lines", name=nm, line=dict(width=1.5, color=col)))
356
  fig.add_trace(go.Scatter(x=w.index, y=ut_stop, mode="lines", name="UT trailing stop",
357
- line=dict(width=1, dash="dot", color="#9333ea")))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
358
  if len(trades_df):
359
  t = trades_df[trades_df.entry_date.isin(w.index)]
360
  for side, sym, nm in [("LONG", "triangle-up", "OOS long"),
@@ -366,11 +466,12 @@ def price_figure(df, ticker, trades_df, trade_ok, stop, target, ind=None):
366
  marker=dict(symbol=sym, size=9,
367
  color=["#16a34a" if r > 0 else "#dc2626" for r in s.R],
368
  line=dict(width=1, color="white")),
369
- text=[f"R={r:.2f} · {why}" for r, why in zip(s.R, s.exit_reason)]))
370
  if trade_ok:
371
- fig.add_hline(y=stop, line_dash="dash", line_color="#dc2626", annotation_text="SL")
372
- fig.add_hline(y=target, line_dash="dash", line_color="#16a34a", annotation_text="TP")
373
- fig.update_layout(title=f"{ticker} Günlük — son 150 mum (üçgenler: OOS sinyalleri)",
 
374
  height=560, xaxis_rangeslider_visible=False)
375
  return fig
376
 
@@ -434,7 +535,8 @@ def mc_figure(mc):
434
  def run_engine(ticker, period, horizon, side_mode, equity, min_risk, max_risk,
435
  max_leverage, fee, slip, train_window,
436
  p_min, p_gap, flat_max, agree_min, bacc_min, min_trades,
437
- skip_overlap, whole_units, ind_mode, st_period, st_mult, ut_key, ut_period):
 
438
  try:
439
  ticker = str(ticker).strip().upper()
440
  h = int(horizon)
@@ -449,7 +551,15 @@ def run_engine(ticker, period, horizon, side_mode, equity, min_risk, max_risk,
449
  if len(df) < 450:
450
  raise ValueError("En az 450 günlük mum gerekli.")
451
  df = df.iloc[:-1].copy() # tamamlanmamış güncel mumu çıkar
452
- X_all, atr_all = make_features(df)
 
 
 
 
 
 
 
 
453
  y_all, _ = make_target(df, atr_all, h)
454
 
455
  valid = X_all.notna().all(axis=1) & y_all.notna()
@@ -519,11 +629,12 @@ def run_engine(ticker, period, horizon, side_mode, equity, min_risk, max_risk,
519
  params, top, regime_used = {}, [], "—"
520
 
521
  def opt_pos(s_):
 
522
  if ind_only or ind_confirm:
523
- f_ = [i for i in dev_pos if ind_arr[i] == s_]
524
- if len(f_) >= 35:
525
- return f_
526
- return dev_pos
527
  for s in (1, -1):
528
  if allow[s]:
529
  sl_s, rr_s, top_s, used_s = optimize_barriers(
@@ -552,6 +663,8 @@ def run_engine(ticker, period, horizon, side_mode, equity, min_risk, max_risk,
552
  continue
553
  if ind_confirm and ind_arr[i] != pred:
554
  continue
 
 
555
  if skip_overlap and i <= busy:
556
  continue
557
  sl_p, rr_p = params[pred]
@@ -614,6 +727,9 @@ def run_engine(ticker, period, horizon, side_mode, equity, min_risk, max_risk,
614
  checks.insert(1, ind_check)
615
  elif ind_confirm:
616
  checks.insert(1, ind_check)
 
 
 
617
  trade_ok = all(c[3] for c in checks)
618
  if trade_ok:
619
  signal = "LONG" if side == 1 else "SHORT"
@@ -652,6 +768,20 @@ def run_engine(ticker, period, horizon, side_mode, equity, min_risk, max_risk,
652
  (f"Supertrend ({int(st_period)}, {float(st_mult):g})", dname[int(st_dir[-1])], fmt_p(float(st_line[-1]))),
653
  (f"UT Bot ({float(ut_key):g}, {int(ut_period)})", dname[int(ut_pos[-1])], fmt_p(float(ut_stop[-1]))),
654
  ("Bileşik (ikisi aynı yönde)", dname[ind_now], "—")])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
655
  k_text = " | ".join(f"K={k}: {s:.3f}" for s, k in sorted(k_scores, reverse=True)) or "—"
656
  overlap_note = ("tek pozisyon (çakışma yok)" if skip_overlap
657
  else "çakışan sinyaller bağımsız işlem sayıldı")
@@ -676,6 +806,11 @@ def run_engine(ticker, period, horizon, side_mode, equity, min_risk, max_risk,
676
  ## İndikatörler — mod: {ind_mode}
677
  {ind_md}
678
 
 
 
 
 
 
679
  ## TP / SL ({'LONG' if side == 1 else 'SHORT'} için optimize)
680
  | Parametre | Değer |
681
  |---|---:|
@@ -724,7 +859,8 @@ TP/SL optimizasyon rejimi: {regime_used}. Yeterli rejim örneği yoksa tüm geli
724
  trades_df.to_csv(csv_path, index=False)
725
 
726
  price_fig = price_figure(df, ticker, trades_df, trade_ok, stop, target,
727
- ind=(st_line, st_dir, ut_stop))
 
728
  eq_fig = equity_figure(curve, dds) if len(R) else None
729
  pf_fig = pf_gauge(stats["pf"], stats["n"]) if stats else None
730
  mc_fig = mc_figure(mc) if mc else None
@@ -740,7 +876,7 @@ TP/SL optimizasyon rejimi: {regime_used}. Yeterli rejim örneği yoksa tüm geli
740
  "seri korelasyonunu ve rejim değişimini tam modellemez. Pozisyon hesabı spot miktar "
741
  "varsayar; CFD lot sözleşmesi, spread, funding, swap, komisyon, minimum lot, marjin ve "
742
  "broker fiyatı farklı olabilir. Gap ve kayma kaybı planlanan riskin üstüne çıkarabilir. "
743
- "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. Canlı hesap öncesi paper trading ve broker bazlı doğrulama zorunludur."
744
  )
745
  return (report, prob_figure(p_down, p_flat, p_up), price_fig, eq_fig, pf_fig, mc_fig,
746
  top_df, csv_path, disclaimer)
@@ -766,6 +902,7 @@ with gr.Blocks(title=APP_TITLE, theme=gr.themes.Soft()) as demo:
766
  horizon = gr.Dropdown([3, 5, 10], value=5, label="Tahmin ufku (gün)")
767
  side_mode = gr.Radio(SIDE_MODES, value="Long + Short", label="İzin verilen yön")
768
  ind_mode = gr.Radio(IND_MODES, value="Kapalı", label="İndikatör modu (Supertrend + UT Bot)")
 
769
  train_window = gr.Slider(300, 1500, value=900, step=50, label="Maksimum eğitim penceresi")
770
  with gr.Column():
771
  equity = gr.Number(value=10000, label="Hesap büyüklüğü (USD)")
@@ -788,6 +925,11 @@ with gr.Blocks(title=APP_TITLE, theme=gr.themes.Soft()) as demo:
788
  st_mult = gr.Slider(1.0, 6.0, value=3.0, step=0.1, label="Supertrend çarpanı")
789
  ut_key = gr.Slider(0.5, 4.0, value=1.0, step=0.1, label="UT Bot key value")
790
  ut_period = gr.Slider(5, 30, value=10, step=1, label="UT Bot ATR periyodu")
 
 
 
 
 
791
  with gr.Row():
792
  skip_overlap = gr.Checkbox(value=False, label="Açık pozisyon varken yeni sinyali atla (tek pozisyon)")
793
  whole_units = gr.Checkbox(value=False, label="Miktarı tam sayıya yuvarla (hisse)")
@@ -815,7 +957,8 @@ with gr.Blocks(title=APP_TITLE, theme=gr.themes.Soft()) as demo:
815
  inputs=[ticker, period, horizon, side_mode, equity, min_risk, max_risk,
816
  max_leverage, fee, slip, train_window,
817
  p_min, p_gap, flat_max, agree_min, bacc_min, min_trades,
818
- skip_overlap, whole_units, ind_mode, st_period, st_mult, ut_key, ut_period],
 
819
  outputs=[output, prob_plot, chart, equity_plot, pf_plot, mc_plot, optimization,
820
  csv_file, disclaimer],
821
  )
 
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
+ VP_MODES = ["Kapalı", "Onay filtresi (POC / Value Area)", "KNN özelliği + onay filtresi"]
33
 
34
  _CACHE = {}
35
  CACHE_TTL = 600 # saniye; Yahoo'yu gereksiz yere tekrar çağırmamak için
 
67
  return 100 - 100/(1+rs)
68
 
69
 
70
+ def make_features(df, vp=None):
71
  c, v = df.Close, df.Volume
72
  x = pd.DataFrame(index=df.index)
73
  for n in [1, 2, 3, 5, 10, 20, 50]:
 
90
  x["dist_hi_20"] = c/df.High.rolling(20).max()-1
91
  x["dist_lo_20"] = c/df.Low.rolling(20).min()-1
92
  x["bb_z20"] = (c-c.rolling(20).mean())/c.rolling(20).std().replace(0, np.nan)
93
+ if vp is not None: # Volume Profile özellikleri (ileriye bakmaz)
94
+ x["vp_poc_gap"] = c/vp["poc"]-1
95
+ x["vp_va_pos"] = (c-vp["val"])/(vp["vah"]-vp["val"]).replace(0, np.nan)
96
+ x["vp_va_width"] = (vp["vah"]-vp["val"])/c
97
  return x.replace([np.inf, -np.inf], np.nan), atr
98
 
99
 
 
144
  return pos, stop
145
 
146
 
147
+ def _profile(h, l, v, bins):
148
+ """Bar hacmini high-low aralığına, kutularla örtüştüğü oranda dağıtır (VPVR mantığı)."""
149
+ lo, hi = float(np.min(l)), float(np.max(h))
150
+ if not (np.isfinite(lo) and np.isfinite(hi)) or hi <= lo:
151
+ return None
152
+ v = np.where(np.isfinite(v) & (v > 0), v, 0.0)
153
+ if v.sum() <= 0: # hacim verisi yok (ör. forex) -> zaman bazlı profil
154
+ v = np.ones_like(v)
155
+ edges = np.linspace(lo, hi, bins+1)
156
+ step = (hi-lo)/bins
157
+ rng = np.maximum(h-l, 1e-12)
158
+ ov = np.clip(np.minimum(h[:, None], edges[None, 1:]) - np.maximum(l[:, None], edges[None, :-1]), 0, None)
159
+ frac = ov/rng[:, None]
160
+ zr = (h-l) <= 1e-12
161
+ if zr.any():
162
+ frac[zr] = 0.0
163
+ frac[zr, np.clip(((h[zr]-lo)/step).astype(int), 0, bins-1)] = 1.0
164
+ return edges, (frac*v[:, None]).sum(axis=0)
165
+
166
+
167
+ def _value_area(edges, prof, va_pct):
168
+ pi = int(np.argmax(prof))
169
+ lo_i = hi_i = pi
170
+ acc, target = float(prof[pi]), va_pct*float(prof.sum())
171
+ nb = len(prof)
172
+ while acc < target and (lo_i > 0 or hi_i < nb-1):
173
+ up = prof[hi_i+1] if hi_i < nb-1 else -1.0
174
+ dn = prof[lo_i-1] if lo_i > 0 else -1.0
175
+ if up >= dn:
176
+ hi_i += 1
177
+ acc += up
178
+ else:
179
+ lo_i -= 1
180
+ acc += dn
181
+ return float((edges[pi]+edges[pi+1])/2), float(edges[lo_i]), float(edges[hi_i+1])
182
+
183
+
184
+ def volume_profile(df, window=60, bins=24, va_pct=0.70):
185
+ """Kayan Volume Profile: her mum için son `window` mumdan POC / VAL / VAH.
186
+ Yalnızca o muma kadar olan veri kullanılır (ileriye bakma yok)."""
187
+ h, l, v = (df[c].to_numpy(float) for c in ("High", "Low", "Volume"))
188
+ n = len(h)
189
+ out = np.full((n, 3), np.nan)
190
+ for i in range(window-1, n):
191
+ sl = slice(i-window+1, i+1)
192
+ p = _profile(h[sl], l[sl], v[sl], bins)
193
+ if p is None:
194
+ continue
195
+ out[i] = _value_area(p[0], p[1], va_pct)
196
+ return pd.DataFrame(out, index=df.index, columns=["poc", "val", "vah"])
197
+
198
+
199
+ def latest_profile(df, window, bins, va_pct):
200
+ w = df.tail(int(window))
201
+ p = _profile(w.High.to_numpy(float), w.Low.to_numpy(float), w.Volume.to_numpy(float), int(bins))
202
+ if p is None:
203
+ return None
204
+ poc, val, vah = _value_area(p[0], p[1], va_pct)
205
+ return p[0], p[1], poc, val, vah
206
+
207
+
208
+ def vp_signal(df, atr, vp, ext_atr):
209
+ """+1: long onaylı (kapanış POC üstünde ve VAH'ın ext_atr*ATR'den fazla üstünde değil),
210
+ -1: short onaylı (POC altında ve VAL'ın ext_atr*ATR'den fazla altında değil), 0: onay yok."""
211
+ c, a = df.Close.to_numpy(float), atr.to_numpy(float)
212
+ poc, val, vah = (vp[k].to_numpy(float) for k in ("poc", "val", "vah"))
213
+ with np.errstate(invalid="ignore"):
214
+ ok_l = (c > poc) & ((c-vah) <= ext_atr*a)
215
+ ok_s = (c < poc) & ((val-c) <= ext_atr*a)
216
+ return np.where(ok_l, 1, np.where(ok_s, -1, 0)).astype(int)
217
+
218
+
219
  def regime_labels(features, ref_vol):
220
  # Volatilite kantilleri yalnızca geliştirme dönemi verisinden alınır (sızıntı yok).
221
  q1, q2 = ref_vol.dropna().quantile([0.33, 0.67])
 
417
 
418
 
419
  # ---------------------------------------------------------------- grafikler
420
+ def price_figure(df, ticker, trades_df, trade_ok, stop, target, ind=None, vp=None, vp_prof=None):
421
  w = df.tail(150)
422
+ sub = vp is not None and vp_prof is not None
423
+ if sub:
424
+ fig = make_subplots(rows=1, cols=2, shared_yaxes=True,
425
+ column_widths=[0.86, 0.14], horizontal_spacing=0.005)
426
+ else:
427
+ fig = go.Figure()
428
+ kw = dict(row=1, col=1) if sub else {}
429
+ fig.add_trace(go.Candlestick(x=w.index, open=w.Open, high=w.High, low=w.Low,
430
+ close=w.Close, name=ticker), **kw)
431
  for n, col in [(20, "#f59e0b"), (50, "#3b82f6")]:
432
  ema = df.Close.ewm(span=n, adjust=False).mean().tail(150)
433
  fig.add_trace(go.Scatter(x=w.index, y=ema, mode="lines", name=f"EMA{n}",
434
+ line=dict(width=1.2, color=col)), **kw)
435
  if ind is not None:
436
  st_line, st_dir, ut_stop = (np.asarray(a)[-150:] for a in ind)
437
  for d, col, nm in [(1, "#16a34a", "Supertrend ↑"), (-1, "#dc2626", "Supertrend ↓")]:
438
  fig.add_trace(go.Scatter(x=w.index, y=np.where(st_dir == d, st_line, np.nan),
439
+ mode="lines", name=nm, line=dict(width=1.5, color=col)), **kw)
440
  fig.add_trace(go.Scatter(x=w.index, y=ut_stop, mode="lines", name="UT trailing stop",
441
+ line=dict(width=1, dash="dot", color="#9333ea")), **kw)
442
+ if sub:
443
+ vw = vp.tail(150)
444
+ for k, col, dash, nm in [("poc", "#f59e0b", "solid", "POC"),
445
+ ("vah", "#64748b", "dash", "VAH"),
446
+ ("val", "#64748b", "dash", "VAL")]:
447
+ fig.add_trace(go.Scatter(x=w.index, y=vw[k], mode="lines", name=nm,
448
+ line=dict(width=1.4, color=col, dash=dash)), **kw)
449
+ edges, prof, poc, val, vah = vp_prof
450
+ mid = (edges[:-1]+edges[1:])/2
451
+ colors = ["#f59e0b" if edges[k] <= poc <= edges[k+1]
452
+ else ("#60a5fa" if val <= mid[k] <= vah else "#cbd5e1")
453
+ for k in range(len(mid))]
454
+ fig.add_trace(go.Bar(x=prof, y=mid, orientation="h", width=(edges[1]-edges[0])*0.95,
455
+ marker_color=colors, name="Volume Profile", showlegend=False,
456
+ hoverinfo="skip"), row=1, col=2)
457
+ fig.update_xaxes(showticklabels=False, row=1, col=2)
458
  if len(trades_df):
459
  t = trades_df[trades_df.entry_date.isin(w.index)]
460
  for side, sym, nm in [("LONG", "triangle-up", "OOS long"),
 
466
  marker=dict(symbol=sym, size=9,
467
  color=["#16a34a" if r > 0 else "#dc2626" for r in s.R],
468
  line=dict(width=1, color="white")),
469
+ text=[f"R={r:.2f} · {why}" for r, why in zip(s.R, s.exit_reason)]), **kw)
470
  if trade_ok:
471
+ fig.add_hline(y=stop, line_dash="dash", line_color="#dc2626", annotation_text="SL", **kw)
472
+ fig.add_hline(y=target, line_dash="dash", line_color="#16a34a", annotation_text="TP", **kw)
473
+ fig.update_layout(title=f"{ticker} Günlük — son 150 mum (üçgenler: OOS sinyalleri)"
474
+ + (" + Volume Profile" if sub else ""),
475
  height=560, xaxis_rangeslider_visible=False)
476
  return fig
477
 
 
535
  def run_engine(ticker, period, horizon, side_mode, equity, min_risk, max_risk,
536
  max_leverage, fee, slip, train_window,
537
  p_min, p_gap, flat_max, agree_min, bacc_min, min_trades,
538
+ skip_overlap, whole_units, ind_mode, st_period, st_mult, ut_key, ut_period,
539
+ vp_mode, vp_window, vp_bins, vp_va, vp_ext):
540
  try:
541
  ticker = str(ticker).strip().upper()
542
  h = int(horizon)
 
551
  if len(df) < 450:
552
  raise ValueError("En az 450 günlük mum gerekli.")
553
  df = df.iloc[:-1].copy() # tamamlanmamış güncel mumu çıkar
554
+ vp_on = vp_mode != VP_MODES[0]
555
+ vp_feat = vp_mode == VP_MODES[2]
556
+ vp_win, vp_nb, vp_vap = int(vp_window), int(vp_bins), float(vp_va)
557
+ vp = volume_profile(df, vp_win, vp_nb, vp_vap)
558
+ X_all, atr_all = make_features(df, vp if vp_feat else None)
559
+ vp_arr = vp_signal(df, atr_all, vp, float(vp_ext))
560
+ vp_names = {1: "LONG onaylı", -1: "SHORT onaylı", 0: "onay yok"}
561
+ vp_no_vol = float(df.Volume.tail(vp_win).sum()) <= 0
562
+ lp = latest_profile(df, vp_win, vp_nb, vp_vap)
563
  y_all, _ = make_target(df, atr_all, h)
564
 
565
  valid = X_all.notna().all(axis=1) & y_all.notna()
 
629
  params, top, regime_used = {}, [], "—"
630
 
631
  def opt_pos(s_):
632
+ f_ = list(dev_pos)
633
  if ind_only or ind_confirm:
634
+ f_ = [i for i in f_ if ind_arr[i] == s_]
635
+ if vp_on:
636
+ f_ = [i for i in f_ if vp_arr[i] == s_]
637
+ return f_ if len(f_) >= 35 else dev_pos
638
  for s in (1, -1):
639
  if allow[s]:
640
  sl_s, rr_s, top_s, used_s = optimize_barriers(
 
663
  continue
664
  if ind_confirm and ind_arr[i] != pred:
665
  continue
666
+ if vp_on and vp_arr[i] != pred:
667
+ continue
668
  if skip_overlap and i <= busy:
669
  continue
670
  sl_p, rr_p = params[pred]
 
727
  checks.insert(1, ind_check)
728
  elif ind_confirm:
729
  checks.insert(1, ind_check)
730
+ if vp_on:
731
+ checks.insert(1, ("Volume Profile (POC/VA konumu)", vp_names[int(vp_arr[-1])],
732
+ "yönle aynı", int(vp_arr[-1]) == side))
733
  trade_ok = all(c[3] for c in checks)
734
  if trade_ok:
735
  signal = "LONG" if side == 1 else "SHORT"
 
768
  (f"Supertrend ({int(st_period)}, {float(st_mult):g})", dname[int(st_dir[-1])], fmt_p(float(st_line[-1]))),
769
  (f"UT Bot ({float(ut_key):g}, {int(ut_period)})", dname[int(ut_pos[-1])], fmt_p(float(ut_stop[-1]))),
770
  ("Bileşik (ikisi aynı yönde)", dname[ind_now], "—")])
771
+ if lp is not None:
772
+ _, _, poc_n, val_n, vah_n = lp
773
+ pos_txt = ("VA üstünde" if entry > vah_n else "VA altında" if entry < val_n
774
+ else "VA içinde · POC üstü" if entry > poc_n else "VA içinde · POC altı")
775
+ vp_md = md_table(["Seviye", "Fiyat"], [
776
+ ("VAH (Value Area üst)", fmt_p(vah_n)), ("POC (en yüksek hacim)", fmt_p(poc_n)),
777
+ ("VAL (Value Area alt)", fmt_p(val_n)), ("Son kapanış", f"{fmt_p(entry)} — {pos_txt}"),
778
+ ("VP yön onayı", vp_names[int(vp_arr[-1])])])
779
+ else:
780
+ vp_md = "_Profil hesaplanamadı._"
781
+ vp_note = (f"Pencere {vp_win} mum · {vp_nb} kutu · Value Area %{vp_vap*100:.0f} · "
782
+ f"uzama sınırı {float(vp_ext):g} ATR."
783
+ + (" Hacim verisi yok → zaman bazlı profil kullanıldı." if vp_no_vol else "")
784
+ + (" Mod Kapalı: yalnızca bilgi amaçlı." if not vp_on else ""))
785
  k_text = " | ".join(f"K={k}: {s:.3f}" for s, k in sorted(k_scores, reverse=True)) or "—"
786
  overlap_note = ("tek pozisyon (çakışma yok)" if skip_overlap
787
  else "çakışan sinyaller bağımsız işlem sayıldı")
 
806
  ## İndikatörler — mod: {ind_mode}
807
  {ind_md}
808
 
809
+ ## Volume Profile — mod: {vp_mode}
810
+ {vp_md}
811
+
812
+ {vp_note}
813
+
814
  ## TP / SL ({'LONG' if side == 1 else 'SHORT'} için optimize)
815
  | Parametre | Değer |
816
  |---|---:|
 
859
  trades_df.to_csv(csv_path, index=False)
860
 
861
  price_fig = price_figure(df, ticker, trades_df, trade_ok, stop, target,
862
+ ind=(st_line, st_dir, ut_stop),
863
+ vp=vp if vp_on else None, vp_prof=lp)
864
  eq_fig = equity_figure(curve, dds) if len(R) else None
865
  pf_fig = pf_gauge(stats["pf"], stats["n"]) if stats else None
866
  mc_fig = mc_figure(mc) if mc else None
 
876
  "seri korelasyonunu ve rejim değişimini tam modellemez. Pozisyon hesabı spot miktar "
877
  "varsayar; CFD lot sözleşmesi, spread, funding, swap, komisyon, minimum lot, marjin ve "
878
  "broker fiyatı farklı olabilir. Gap ve kayma kaybı planlanan riskin üstüne çıkarabilir. "
879
+ "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. 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. Canlı hesap öncesi paper trading ve broker bazlı doğrulama zorunludur."
880
  )
881
  return (report, prob_figure(p_down, p_flat, p_up), price_fig, eq_fig, pf_fig, mc_fig,
882
  top_df, csv_path, disclaimer)
 
902
  horizon = gr.Dropdown([3, 5, 10], value=5, label="Tahmin ufku (gün)")
903
  side_mode = gr.Radio(SIDE_MODES, value="Long + Short", label="İzin verilen yön")
904
  ind_mode = gr.Radio(IND_MODES, value="Kapalı", label="İndikatör modu (Supertrend + UT Bot)")
905
+ vp_mode = gr.Radio(VP_MODES, value="Kapalı", label="Volume Profile modu")
906
  train_window = gr.Slider(300, 1500, value=900, step=50, label="Maksimum eğitim penceresi")
907
  with gr.Column():
908
  equity = gr.Number(value=10000, label="Hesap büyüklüğü (USD)")
 
925
  st_mult = gr.Slider(1.0, 6.0, value=3.0, step=0.1, label="Supertrend çarpanı")
926
  ut_key = gr.Slider(0.5, 4.0, value=1.0, step=0.1, label="UT Bot key value")
927
  ut_period = gr.Slider(5, 30, value=10, step=1, label="UT Bot ATR periyodu")
928
+ with gr.Row():
929
+ vp_window = gr.Slider(20, 250, value=60, step=5, label="Volume Profile penceresi (mum)")
930
+ vp_bins = gr.Slider(12, 60, value=24, step=2, label="Fiyat kutusu sayısı")
931
+ vp_va = gr.Slider(0.50, 0.90, value=0.70, step=0.05, label="Value Area oranı")
932
+ vp_ext = gr.Slider(0.5, 4.0, value=1.5, step=0.1, label="Maks. uzama (ATR) — kovalamayı engeller")
933
  with gr.Row():
934
  skip_overlap = gr.Checkbox(value=False, label="Açık pozisyon varken yeni sinyali atla (tek pozisyon)")
935
  whole_units = gr.Checkbox(value=False, label="Miktarı tam sayıya yuvarla (hisse)")
 
957
  inputs=[ticker, period, horizon, side_mode, equity, min_risk, max_risk,
958
  max_leverage, fee, slip, train_window,
959
  p_min, p_gap, flat_max, agree_min, bacc_min, min_trades,
960
+ skip_overlap, whole_units, ind_mode, st_period, st_mult, ut_key, ut_period,
961
+ vp_mode, vp_window, vp_bins, vp_va, vp_ext],
962
  outputs=[output, prob_plot, chart, equity_plot, pf_plot, mc_plot, optimization,
963
  csv_file, disclaimer],
964
  )