GoshawkVortexAI commited on
Commit
ff97b10
·
verified ·
1 Parent(s): 0d5181a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +217 -113
app.py CHANGED
@@ -50,7 +50,7 @@ FEATURES = [
50
 
51
 
52
  # =========================================================
53
- # OKX DATA
54
  # =========================================================
55
 
56
  def fetch_data(timeframe="1h", candles=2000):
@@ -67,56 +67,63 @@ def fetch_data(timeframe="1h", candles=2000):
67
 
68
  if SYMBOL not in exchange.markets:
69
  raise ValueError(
70
- f"OKX sembolü bulunamadı: {SYMBOL}"
71
  )
72
 
73
  timeframe_ms = exchange.parse_timeframe(timeframe) * 1000
74
 
75
  since = (
76
  exchange.milliseconds()
77
- - candles * timeframe_ms
78
  )
79
 
80
- all_rows = []
81
- last_timestamp = None
 
82
 
83
- while len(all_rows) < candles:
84
 
85
- limit = min(100, candles - len(all_rows))
86
 
87
  batch = exchange.fetch_ohlcv(
88
  SYMBOL,
89
  timeframe=timeframe,
90
  since=since,
91
- limit=limit
92
  )
93
 
94
  if not batch:
95
  break
96
 
 
 
97
  for row in batch:
 
 
 
98
 
99
- if last_timestamp is None or row[0] > last_timestamp:
100
- all_rows.append(row)
101
- last_timestamp = row[0]
102
 
103
- new_since = batch[-1][0] + timeframe_ms
104
 
105
  if new_since <= since:
106
  break
107
 
108
  since = new_since
109
 
110
- if len(batch) < limit:
 
 
 
111
  break
112
 
113
  time.sleep(exchange.rateLimit / 1000)
114
 
115
  if not all_rows:
116
- raise RuntimeError("OKX veri döndürmedi.")
117
 
118
  df = pd.DataFrame(
119
- all_rows,
120
  columns=[
121
  "timestamp",
122
  "open",
@@ -127,8 +134,8 @@ def fetch_data(timeframe="1h", candles=2000):
127
  ]
128
  )
129
 
130
- df = df.drop_duplicates("timestamp")
131
  df = df.sort_values("timestamp")
 
132
 
133
  df["timestamp"] = pd.to_datetime(
134
  df["timestamp"],
@@ -152,11 +159,19 @@ def fetch_data(timeframe="1h", candles=2000):
152
 
153
  df = df.dropna()
154
 
155
- # Tamamlanmamış son mumu çıkar.
156
  if len(df) > 1:
157
  df = df.iloc[:-1]
158
 
159
- return df.tail(candles)
 
 
 
 
 
 
 
 
160
 
161
 
162
  # =========================================================
@@ -167,7 +182,7 @@ def calculate_indicators(df):
167
 
168
  df = df.copy()
169
 
170
- # ATR
171
  previous_close = df["close"].shift(1)
172
 
173
  tr = pd.concat([
@@ -290,7 +305,7 @@ def calculate_indicators(df):
290
  df["supertrend"] = supertrend
291
  df["supertrend_direction"] = direction
292
 
293
- # UTC DAILY VWAP
294
  typical_price = (
295
  df["high"] + df["low"] + df["close"]
296
  ) / 3
@@ -379,7 +394,7 @@ def prepare_dataset(raw, horizon):
379
  future_close > data["close"]
380
  ).astype(int)
381
 
382
- # Bilinmeyen gelecek hedeflerini çıkar.
383
  data = data.iloc[:-horizon].copy()
384
 
385
  data = data.replace(
@@ -391,9 +406,11 @@ def prepare_dataset(raw, horizon):
391
  subset=FEATURES + ["target", "atr"]
392
  )
393
 
394
- if len(data) < 500:
395
  raise ValueError(
396
- "Model için yeterli temiz veri bulunamadı."
 
 
397
  )
398
 
399
  return data
@@ -436,12 +453,7 @@ def create_models(params):
436
  return [extra, hist, logistic]
437
 
438
 
439
- def ensemble_predict(
440
- X_train,
441
- y_train,
442
- X_test,
443
- params
444
- ):
445
 
446
  probabilities = []
447
 
@@ -449,15 +461,15 @@ def ensemble_predict(
449
 
450
  model.fit(X_train, y_train)
451
 
452
- p = model.predict_proba(X_test)[:, 1]
453
 
454
- probabilities.append(p)
455
 
456
  return np.mean(probabilities, axis=0)
457
 
458
 
459
  # =========================================================
460
- # BACKTEST ENGINE
461
  # =========================================================
462
 
463
  def run_backtest(
@@ -529,8 +541,6 @@ def run_backtest(
529
 
530
  if side == "LONG":
531
 
532
- # Aynı mumda stop ve hedef görülürse
533
- # ihtiyatlı bi��imde stop önce kabul edilir.
534
  if low <= stop:
535
  exit_price = stop
536
  exit_reason = "STOP"
@@ -607,24 +617,16 @@ def run_backtest(
607
  else:
608
  pf = 0
609
 
610
- winrate = (
611
- len(wins) / len(trade_df)
612
- ) * 100
613
 
614
- # İşlemler sırasıyla bileşik getiri
615
  returns = trade_df["net_pct"].values / 100
616
 
617
  equity = np.cumprod(1 + returns)
618
-
619
  peak = np.maximum.accumulate(equity)
620
 
621
- drawdown = (
622
- (equity - peak) / peak
623
- ) * 100
624
 
625
- total_return = (
626
- equity[-1] - 1
627
- ) * 100
628
 
629
  return {
630
  "count": len(trade_df),
@@ -637,7 +639,7 @@ def run_backtest(
637
 
638
 
639
  # =========================================================
640
- # OPTUNA OPTIMIZATION
641
  # =========================================================
642
 
643
  def optimize_model(
@@ -649,7 +651,16 @@ def optimize_model(
649
  fee_pct
650
  ):
651
 
652
- splitter = TimeSeriesSplit(n_splits=3)
 
 
 
 
 
 
 
 
 
653
 
654
  splits = list(splitter.split(X))
655
 
@@ -657,22 +668,22 @@ def optimize_model(
657
 
658
  params = {
659
  "extra_trees": trial.suggest_int(
660
- "extra_trees", 100, 350
661
  ),
662
  "extra_depth": trial.suggest_int(
663
- "extra_depth", 3, 14
664
  ),
665
  "extra_leaf": trial.suggest_int(
666
- "extra_leaf", 2, 12
667
  ),
668
  "hgb_iter": trial.suggest_int(
669
- "hgb_iter", 80, 250
670
  ),
671
  "hgb_lr": trial.suggest_float(
672
  "hgb_lr", 0.01, 0.15, log=True
673
  ),
674
  "hgb_leaves": trial.suggest_int(
675
- "hgb_leaves", 7, 25
676
  ),
677
  "hgb_l2": trial.suggest_float(
678
  "hgb_l2", 0.1, 10, log=True
@@ -731,8 +742,8 @@ def optimize_model(
731
  fee_pct=fee_pct
732
  )
733
 
734
- if result["count"] < 5:
735
- fold_scores.append(-5.0)
736
  continue
737
 
738
  pf = result["profit_factor"]
@@ -740,7 +751,6 @@ def optimize_model(
740
  if np.isinf(pf):
741
  pf = 3.0
742
 
743
- # Getiri + PF - aşırı düşüş cezası
744
  score = (
745
  result["total_return"]
746
  + min(pf, 3.0) * 3
@@ -756,9 +766,7 @@ def optimize_model(
756
 
757
  study = optuna.create_study(
758
  direction="maximize",
759
- sampler=optuna.samplers.TPESampler(
760
- seed=42
761
- )
762
  )
763
 
764
  optuna.logging.set_verbosity(
@@ -794,12 +802,15 @@ def optimize_model(
794
 
795
 
796
  # =========================================================
797
- # WALK FORWARD VALIDATION
798
  # =========================================================
799
 
800
- def walk_forward_validation(X, y, params):
801
 
802
- splitter = TimeSeriesSplit(n_splits=4)
 
 
 
803
 
804
  scores = []
805
 
@@ -814,7 +825,7 @@ def walk_forward_validation(X, y, params):
814
  if y_train.nunique() < 2:
815
  continue
816
 
817
- probabilities = ensemble_predict(
818
  X_train,
819
  y_train,
820
  X_test,
@@ -822,7 +833,7 @@ def walk_forward_validation(X, y, params):
822
  )
823
 
824
  predictions = (
825
- probabilities >= 0.5
826
  ).astype(int)
827
 
828
  scores.append(
@@ -832,10 +843,35 @@ def walk_forward_validation(X, y, params):
832
  )
833
  )
834
 
835
- if not scores:
836
- return 0
837
 
838
- return float(np.mean(scores))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
839
 
840
 
841
  # =========================================================
@@ -863,13 +899,13 @@ def analyze(
863
  if horizon < 1 or horizon > 48:
864
  return "Horizon 1-48 arasında olmalı."
865
 
866
- if trials < 5 or trials > 100:
867
- return "Optuna trial sayısı 5-100 arasında olmalı."
868
 
869
  if fee_pct < 0 or fee_pct > 1:
870
  return "Komisyon değeri geçersiz."
871
 
872
- # DATA
873
  raw = fetch_data(
874
  timeframe=timeframe,
875
  candles=candles
@@ -886,19 +922,32 @@ def analyze(
886
  y = data["target"].astype(int)
887
 
888
  if y.nunique() < 2:
889
- return "Veri setinde yalnızca tek hedef sınıfı var."
890
 
891
- # CHRONOLOGICAL HOLDOUT
892
  split = int(len(data) * 0.80)
893
 
894
- X_dev = X.iloc[:split]
895
- y_dev = y.iloc[:split]
 
 
 
 
 
 
 
 
 
 
896
 
897
  X_test = X.iloc[split:]
898
  y_test = y.iloc[split:]
899
 
900
- if y_dev.nunique() < 2:
901
- return "Eğitim verisinde iki sınıf da bulunmalı."
 
 
 
902
 
903
  # OPTUNA
904
  params, threshold, atr_mult, best_score, n_trials = (
@@ -916,7 +965,8 @@ def analyze(
916
  wf_score = walk_forward_validation(
917
  X_dev,
918
  y_dev,
919
- params
 
920
  )
921
 
922
  # HOLDOUT PREDICTION
@@ -972,8 +1022,19 @@ def analyze(
972
  fee_pct=fee_pct
973
  )
974
 
975
- # LIVE MODEL: development data only
976
- # Holdout is not used for final model fitting.
 
 
 
 
 
 
 
 
 
 
 
977
  latest_indicators = indicators.dropna(
978
  subset=FEATURES + ["atr"]
979
  )
@@ -985,14 +1046,17 @@ def analyze(
985
  columns=FEATURES
986
  )
987
 
988
- latest_probability = ensemble_predict(
989
- X_dev,
990
- y_dev,
991
- latest_X,
992
- params
993
- )[0]
994
 
995
- p_up = float(latest_probability)
 
 
 
 
 
 
996
  p_down = 1 - p_up
997
 
998
  if p_up >= threshold:
@@ -1007,11 +1071,11 @@ def analyze(
1007
 
1008
  if signal == "LONG":
1009
  stop = entry - atr * atr_mult
1010
- target = entry + atr * atr_mult * 2
1011
 
1012
  elif signal == "SHORT":
1013
  stop = entry + atr * atr_mult
1014
- target = entry - atr * atr_mult * 2
1015
 
1016
  else:
1017
  stop = None
@@ -1038,6 +1102,24 @@ def analyze(
1038
  else "ALTINDA"
1039
  )
1040
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1041
  pf = backtest["profit_factor"]
1042
 
1043
  pf_text = (
@@ -1045,7 +1127,15 @@ def analyze(
1045
  else f"{pf:.2f}"
1046
  )
1047
 
1048
- trade_table = ""
 
 
 
 
 
 
 
 
1049
 
1050
  if backtest["trades"]:
1051
 
@@ -1057,13 +1147,6 @@ def analyze(
1057
  index=False
1058
  )
1059
 
1060
- if stop is None:
1061
- stop_text = "—"
1062
- target_text = "—"
1063
- else:
1064
- stop_text = f"{stop:,.2f}"
1065
- target_text = f"{target:,.2f}"
1066
-
1067
  output = f"""
1068
  # OKX BTC/USDT AI ANALİZİ
1069
 
@@ -1078,14 +1161,15 @@ def analyze(
1078
  | Zaman dilimi | {timeframe} |
1079
  | Tahmin horizon | {horizon} mum |
1080
 
1081
- ## TradingView uyumlu indikatörler
1082
 
1083
- | İndikatör | Değer |
1084
- |---|---:|
1085
  | RSI 14 | {rsi:.2f} |
1086
  | MACD | {macd_state} |
1087
  | Supertrend 10,3 | {supertrend_state} |
1088
  | VWAP | {vwap_state} |
 
1089
  | VWAP uzaklığı | {latest['vwap_distance']:.3f}% |
1090
 
1091
  ## İşlem seviyeleri
@@ -1097,14 +1181,14 @@ def analyze(
1097
  | 2R hedef | {target_text} |
1098
  | ATR çarpanı | {atr_mult:.2f} |
1099
 
1100
- ## Optuna optimizasyonu
1101
 
1102
  | Metrik | Sonuç |
1103
  |---|---:|
1104
- | Tamamlanan trial | {n_trials} |
1105
  | En iyi objective | {best_score:.4f} |
1106
- | Optimize edilen threshold | {threshold:.3f} |
1107
- | Optimize edilen ATR | {atr_mult:.2f} |
1108
 
1109
  ## Bağımsız hold-out test
1110
 
@@ -1115,6 +1199,22 @@ def analyze(
1115
  | AUC | {auc:.4f} |
1116
  | Walk-forward Balanced Accuracy | {wf_score * 100:.2f}% |
1117
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1118
  ## Hold-out backtest
1119
 
1120
  | Metrik | Sonuç |
@@ -1126,17 +1226,21 @@ def analyze(
1126
  | Maksimum drawdown | {backtest['max_drawdown']:.2f}% |
1127
  | Komisyon | %{fee_pct:.3f} / taraf |
1128
 
1129
- ## Son işlemler
1130
 
1131
- {trade_table if trade_table else "Backtest döneminde işlem oluşmadı."}
1132
 
1133
- ## Optuna'nın seçtiği model parametreleri
1134
 
1135
  ```text
1136
  {params}
1137
  ```
1138
 
1139
- **Not:** Optuna yalnızca ilk %80'lik geliştirme verisinde optimize edildi. Son %20 hold-out test optimizasyona dahil edilmedi. Hold-out sonucu geçmiş performanstır; gelecekteki performans garantisi değildir.
 
 
 
 
1140
  """
1141
 
1142
  return output
@@ -1146,7 +1250,7 @@ def analyze(
1146
  return (
1147
  "# ANALİZ HATASI\n\n"
1148
  f"`{type(e).__name__}: {str(e)}`\n\n"
1149
- "OKX bağlantısını, sembolü ve paketleri kontrol et."
1150
  )
1151
 
1152
 
@@ -1155,16 +1259,16 @@ def analyze(
1155
  # =========================================================
1156
 
1157
  with gr.Blocks(
1158
- title="OKX BTC AI Optuna Backtest"
1159
  ) as app:
1160
 
1161
  gr.Markdown(
1162
  """
1163
- # OKX BTC AI — OPTUNA + BACKTEST
1164
 
1165
- OKX perpetual swap verisiyle çalışan makine öğrenmesi sistemi.
1166
 
1167
- **Supertrend + RSI + MACD + VWAP + Ensemble ML + Optuna**
1168
  """
1169
  )
1170
 
@@ -1207,10 +1311,10 @@ OKX perpetual swap verisiyle çalışan makine öğrenmesi sistemi.
1207
  )
1208
 
1209
  trials = gr.Slider(
1210
- minimum=5,
1211
  maximum=100,
1212
  value=20,
1213
- step=5,
1214
  label="Optuna trial sayısı"
1215
  )
1216
 
 
50
 
51
 
52
  # =========================================================
53
+ # OKX DATA FETCH
54
  # =========================================================
55
 
56
  def fetch_data(timeframe="1h", candles=2000):
 
67
 
68
  if SYMBOL not in exchange.markets:
69
  raise ValueError(
70
+ f"OKX perpetual sembolü bulunamadı: {SYMBOL}"
71
  )
72
 
73
  timeframe_ms = exchange.parse_timeframe(timeframe) * 1000
74
 
75
  since = (
76
  exchange.milliseconds()
77
+ - (candles + 10) * timeframe_ms
78
  )
79
 
80
+ all_rows = {}
81
+ requests = 0
82
+ max_requests = max(20, candles // 80 + 10)
83
 
84
+ while len(all_rows) < candles and requests < max_requests:
85
 
86
+ requests += 1
87
 
88
  batch = exchange.fetch_ohlcv(
89
  SYMBOL,
90
  timeframe=timeframe,
91
  since=since,
92
+ limit=100
93
  )
94
 
95
  if not batch:
96
  break
97
 
98
+ old_count = len(all_rows)
99
+
100
  for row in batch:
101
+ all_rows[int(row[0])] = row
102
+
103
+ new_count = len(all_rows)
104
 
105
+ last_timestamp = max(int(row[0]) for row in batch)
 
 
106
 
107
+ new_since = last_timestamp + timeframe_ms
108
 
109
  if new_since <= since:
110
  break
111
 
112
  since = new_since
113
 
114
+ if new_count == old_count:
115
+ break
116
+
117
+ if len(batch) < 100:
118
  break
119
 
120
  time.sleep(exchange.rateLimit / 1000)
121
 
122
  if not all_rows:
123
+ raise ValueError("OKX veri döndürmedi.")
124
 
125
  df = pd.DataFrame(
126
+ list(all_rows.values()),
127
  columns=[
128
  "timestamp",
129
  "open",
 
134
  ]
135
  )
136
 
 
137
  df = df.sort_values("timestamp")
138
+ df = df.drop_duplicates("timestamp")
139
 
140
  df["timestamp"] = pd.to_datetime(
141
  df["timestamp"],
 
159
 
160
  df = df.dropna()
161
 
162
+ # Henüz tamamlanmamış mum
163
  if len(df) > 1:
164
  df = df.iloc[:-1]
165
 
166
+ df = df.tail(candles)
167
+
168
+ if len(df) < 200:
169
+ raise ValueError(
170
+ f"OKX yalnızca {len(df)} mum döndürdü. "
171
+ "Daha yüksek mum sayısı veya farklı timeframe deneyin."
172
+ )
173
+
174
+ return df
175
 
176
 
177
  # =========================================================
 
182
 
183
  df = df.copy()
184
 
185
+ # ATR 10
186
  previous_close = df["close"].shift(1)
187
 
188
  tr = pd.concat([
 
305
  df["supertrend"] = supertrend
306
  df["supertrend_direction"] = direction
307
 
308
+ # VWAP - UTC daily reset
309
  typical_price = (
310
  df["high"] + df["low"] + df["close"]
311
  ) / 3
 
394
  future_close > data["close"]
395
  ).astype(int)
396
 
397
+ # Gelecek hedefi bulunmayan son mumları çıkar.
398
  data = data.iloc[:-horizon].copy()
399
 
400
  data = data.replace(
 
406
  subset=FEATURES + ["target", "atr"]
407
  )
408
 
409
+ if len(data) < 150:
410
  raise ValueError(
411
+ f"Temiz veri sayısı {len(data)}. "
412
+ "En az 150 örnek gerekli. Mum sayısını artırın "
413
+ "veya daha büyük timeframe deneyin."
414
  )
415
 
416
  return data
 
453
  return [extra, hist, logistic]
454
 
455
 
456
+ def ensemble_predict(X_train, y_train, X_test, params):
 
 
 
 
 
457
 
458
  probabilities = []
459
 
 
461
 
462
  model.fit(X_train, y_train)
463
 
464
+ probability = model.predict_proba(X_test)[:, 1]
465
 
466
+ probabilities.append(probability)
467
 
468
  return np.mean(probabilities, axis=0)
469
 
470
 
471
  # =========================================================
472
+ # BACKTEST
473
  # =========================================================
474
 
475
  def run_backtest(
 
541
 
542
  if side == "LONG":
543
 
 
 
544
  if low <= stop:
545
  exit_price = stop
546
  exit_reason = "STOP"
 
617
  else:
618
  pf = 0
619
 
620
+ winrate = len(wins) / len(trade_df) * 100
 
 
621
 
 
622
  returns = trade_df["net_pct"].values / 100
623
 
624
  equity = np.cumprod(1 + returns)
 
625
  peak = np.maximum.accumulate(equity)
626
 
627
+ drawdown = (equity - peak) / peak * 100
 
 
628
 
629
+ total_return = (equity[-1] - 1) * 100
 
 
630
 
631
  return {
632
  "count": len(trade_df),
 
639
 
640
 
641
  # =========================================================
642
+ # OPTUNA
643
  # =========================================================
644
 
645
  def optimize_model(
 
651
  fee_pct
652
  ):
653
 
654
+ if len(X) < 200:
655
+ raise ValueError(
656
+ "Optuna için yeterli geliştirme verisi yok. "
657
+ "Mum sayısını artırın."
658
+ )
659
+
660
+ splitter = TimeSeriesSplit(
661
+ n_splits=3,
662
+ gap=horizon
663
+ )
664
 
665
  splits = list(splitter.split(X))
666
 
 
668
 
669
  params = {
670
  "extra_trees": trial.suggest_int(
671
+ "extra_trees", 100, 300
672
  ),
673
  "extra_depth": trial.suggest_int(
674
+ "extra_depth", 3, 12
675
  ),
676
  "extra_leaf": trial.suggest_int(
677
+ "extra_leaf", 2, 10
678
  ),
679
  "hgb_iter": trial.suggest_int(
680
+ "hgb_iter", 80, 200
681
  ),
682
  "hgb_lr": trial.suggest_float(
683
  "hgb_lr", 0.01, 0.15, log=True
684
  ),
685
  "hgb_leaves": trial.suggest_int(
686
+ "hgb_leaves", 7, 20
687
  ),
688
  "hgb_l2": trial.suggest_float(
689
  "hgb_l2", 0.1, 10, log=True
 
742
  fee_pct=fee_pct
743
  )
744
 
745
+ if result["count"] < 3:
746
+ fold_scores.append(-10.0)
747
  continue
748
 
749
  pf = result["profit_factor"]
 
751
  if np.isinf(pf):
752
  pf = 3.0
753
 
 
754
  score = (
755
  result["total_return"]
756
  + min(pf, 3.0) * 3
 
766
 
767
  study = optuna.create_study(
768
  direction="maximize",
769
+ sampler=optuna.samplers.TPESampler(seed=42)
 
 
770
  )
771
 
772
  optuna.logging.set_verbosity(
 
802
 
803
 
804
  # =========================================================
805
+ # WALK-FORWARD VALIDATION
806
  # =========================================================
807
 
808
+ def walk_forward_validation(X, y, params, horizon):
809
 
810
+ splitter = TimeSeriesSplit(
811
+ n_splits=3,
812
+ gap=horizon
813
+ )
814
 
815
  scores = []
816
 
 
825
  if y_train.nunique() < 2:
826
  continue
827
 
828
+ probs = ensemble_predict(
829
  X_train,
830
  y_train,
831
  X_test,
 
833
  )
834
 
835
  predictions = (
836
+ probs >= 0.5
837
  ).astype(int)
838
 
839
  scores.append(
 
843
  )
844
  )
845
 
846
+ return float(np.mean(scores)) if scores else 0.5
847
+
848
 
849
+ # =========================================================
850
+ # OVERFIT RISK INDEX
851
+ # =========================================================
852
+
853
+ def calculate_overfit_index(walk_forward_score, holdout_score):
854
+
855
+ # Pozitif fark, geliştirme döneminin test döneminden
856
+ # daha iyi görünmesi anlamına gelir.
857
+ gap = max(
858
+ 0.0,
859
+ walk_forward_score - holdout_score
860
+ )
861
+
862
+ # 20 yüzde puanlık farkta 100 puan risk.
863
+ risk = min(100.0, gap * 500)
864
+
865
+ if risk < 15:
866
+ label = "DÜŞÜK"
867
+ elif risk < 35:
868
+ label = "ORTA"
869
+ elif risk < 60:
870
+ label = "YÜKSEK"
871
+ else:
872
+ label = "ÇOK YÜKSEK"
873
+
874
+ return risk, label, gap
875
 
876
 
877
  # =========================================================
 
899
  if horizon < 1 or horizon > 48:
900
  return "Horizon 1-48 arasında olmalı."
901
 
902
+ if trials < 3 or trials > 100:
903
+ return "Trial sayısı 3-100 arasında olmalı."
904
 
905
  if fee_pct < 0 or fee_pct > 1:
906
  return "Komisyon değeri geçersiz."
907
 
908
+ # FETCH
909
  raw = fetch_data(
910
  timeframe=timeframe,
911
  candles=candles
 
922
  y = data["target"].astype(int)
923
 
924
  if y.nunique() < 2:
925
+ return "Veri setinde iki hedef sınıfı bulunmalı."
926
 
927
+ # HOLDOUT SPLIT
928
  split = int(len(data) * 0.80)
929
 
930
+ # Horizon kadar purge: train etiketleri
931
+ # holdout dönemine taşmasın.
932
+ dev_end = split - horizon
933
+
934
+ if dev_end < 200:
935
+ return (
936
+ "Geliştirme verisi yetersiz. "
937
+ "Mum sayısını artırın veya horizon'u azaltın."
938
+ )
939
+
940
+ X_dev = X.iloc[:dev_end]
941
+ y_dev = y.iloc[:dev_end]
942
 
943
  X_test = X.iloc[split:]
944
  y_test = y.iloc[split:]
945
 
946
+ if y_dev.nunique() < 2 or y_test.nunique() < 2:
947
+ return (
948
+ "Eğitim veya hold-out döneminde iki sınıf da "
949
+ "bulunmalı. Daha fazla mum deneyin."
950
+ )
951
 
952
  # OPTUNA
953
  params, threshold, atr_mult, best_score, n_trials = (
 
965
  wf_score = walk_forward_validation(
966
  X_dev,
967
  y_dev,
968
+ params,
969
+ horizon
970
  )
971
 
972
  # HOLDOUT PREDICTION
 
1022
  fee_pct=fee_pct
1023
  )
1024
 
1025
+ # OVERFIT INDEX
1026
+ overfit_score, overfit_label, overfit_gap = (
1027
+ calculate_overfit_index(
1028
+ wf_score,
1029
+ balanced
1030
+ )
1031
+ )
1032
+
1033
+ # FINAL MODEL
1034
+ # Holdout evaluation tamamlandıktan sonra
1035
+ # güncel sinyal için tüm bilinen etiketler kullanılır.
1036
+ final_models = create_models(params)
1037
+
1038
  latest_indicators = indicators.dropna(
1039
  subset=FEATURES + ["atr"]
1040
  )
 
1046
  columns=FEATURES
1047
  )
1048
 
1049
+ final_probabilities = []
1050
+
1051
+ for model in final_models:
 
 
 
1052
 
1053
+ model.fit(X, y)
1054
+
1055
+ final_probabilities.append(
1056
+ model.predict_proba(latest_X)[:, 1][0]
1057
+ )
1058
+
1059
+ p_up = float(np.mean(final_probabilities))
1060
  p_down = 1 - p_up
1061
 
1062
  if p_up >= threshold:
 
1071
 
1072
  if signal == "LONG":
1073
  stop = entry - atr * atr_mult
1074
+ target = entry + 2 * atr * atr_mult
1075
 
1076
  elif signal == "SHORT":
1077
  stop = entry + atr * atr_mult
1078
+ target = entry - 2 * atr * atr_mult
1079
 
1080
  else:
1081
  stop = None
 
1102
  else "ALTINDA"
1103
  )
1104
 
1105
+ # 4 INDICATOR CONFLUENCE
1106
+ votes = [
1107
+ 1 if latest["supertrend_direction"] == 1 else -1,
1108
+ 1 if latest["macd"] > latest["macd_signal"] else -1,
1109
+ 1 if rsi >= 50 else -1,
1110
+ 1 if latest["vwap_distance"] > 0 else -1
1111
+ ]
1112
+
1113
+ bullish_votes = sum(v == 1 for v in votes)
1114
+ bearish_votes = 4 - bullish_votes
1115
+
1116
+ if bullish_votes >= 3:
1117
+ confluence = "GÜÇLÜ BOĞA UYUMU"
1118
+ elif bearish_votes >= 3:
1119
+ confluence = "GÜÇLÜ AYI UYUMU"
1120
+ else:
1121
+ confluence = "KARARSIZ / KARIŞIK"
1122
+
1123
  pf = backtest["profit_factor"]
1124
 
1125
  pf_text = (
 
1127
  else f"{pf:.2f}"
1128
  )
1129
 
1130
+ stop_text = (
1131
+ f"{stop:,.2f}" if stop is not None else "—"
1132
+ )
1133
+
1134
+ target_text = (
1135
+ f"{target:,.2f}" if target is not None else "—"
1136
+ )
1137
+
1138
+ trade_table = "İşlem oluşmadı."
1139
 
1140
  if backtest["trades"]:
1141
 
 
1147
  index=False
1148
  )
1149
 
 
 
 
 
 
 
 
1150
  output = f"""
1151
  # OKX BTC/USDT AI ANALİZİ
1152
 
 
1161
  | Zaman dilimi | {timeframe} |
1162
  | Tahmin horizon | {horizon} mum |
1163
 
1164
+ ## Dört indikatör
1165
 
1166
+ | İndikatör | Durum |
1167
+ |---|---|
1168
  | RSI 14 | {rsi:.2f} |
1169
  | MACD | {macd_state} |
1170
  | Supertrend 10,3 | {supertrend_state} |
1171
  | VWAP | {vwap_state} |
1172
+ | İndikatör uyumu | {confluence} |
1173
  | VWAP uzaklığı | {latest['vwap_distance']:.3f}% |
1174
 
1175
  ## İşlem seviyeleri
 
1181
  | 2R hedef | {target_text} |
1182
  | ATR çarpanı | {atr_mult:.2f} |
1183
 
1184
+ ## Optuna
1185
 
1186
  | Metrik | Sonuç |
1187
  |---|---:|
1188
+ | Trial sayısı | {n_trials} |
1189
  | En iyi objective | {best_score:.4f} |
1190
+ | Sinyal eşiği | {threshold:.3f} |
1191
+ | ATR çarpanı | {atr_mult:.2f} |
1192
 
1193
  ## Bağımsız hold-out test
1194
 
 
1199
  | AUC | {auc:.4f} |
1200
  | Walk-forward Balanced Accuracy | {wf_score * 100:.2f}% |
1201
 
1202
+ ## OVERFIT RISK INDEX
1203
+
1204
+ # {overfit_score:.1f} / 100 — {overfit_label}
1205
+
1206
+ | Ölçüm | Sonuç |
1207
+ |---|---:|
1208
+ | Walk-forward başarı | {wf_score * 100:.2f}% |
1209
+ | Hold-out başarı | {balanced * 100:.2f}% |
1210
+ | Başarı farkı | {overfit_gap * 100:.2f} yüzde puan |
1211
+
1212
+ Risk yorumu:
1213
+ - 0–14: Düşük gözlenen genelleme farkı
1214
+ - 15–34: Orta
1215
+ - 35–59: Yüksek
1216
+ - 60–100: Çok yüksek
1217
+
1218
  ## Hold-out backtest
1219
 
1220
  | Metrik | Sonuç |
 
1226
  | Maksimum drawdown | {backtest['max_drawdown']:.2f}% |
1227
  | Komisyon | %{fee_pct:.3f} / taraf |
1228
 
1229
+ ## Son backtest işlemleri
1230
 
1231
+ {trade_table}
1232
 
1233
+ ## Optuna model parametreleri
1234
 
1235
  ```text
1236
  {params}
1237
  ```
1238
 
1239
+ ---
1240
+
1241
+ **Overfit uyarısı:** Bu risk endeksi kesin bir overfit testi değildir. Walk-forward ve hold-out Balanced Accuracy arasındaki farktan türetilen bir göstergedir. Küçük fark, modelin overfit olmadığını kanıtlamaz.
1242
+
1243
+ **Backtest uyarısı:** Mum içindeki fiyat sırası bilinmediği için aynı mumda stop ve hedef görülürse stop önce varsayılır. Funding, spread, slippage ve gerçek emir gerçekleşmesi dahil değildir. Sonuçlar canlı kazanç garantisi değildir.
1244
  """
1245
 
1246
  return output
 
1250
  return (
1251
  "# ANALİZ HATASI\n\n"
1252
  f"`{type(e).__name__}: {str(e)}`\n\n"
1253
+ "OKX bağlantısını, mum sayısını ve timeframe ayarını kontrol et."
1254
  )
1255
 
1256
 
 
1259
  # =========================================================
1260
 
1261
  with gr.Blocks(
1262
+ title="OKX BTC AI Optuna Overfit Monitor"
1263
  ) as app:
1264
 
1265
  gr.Markdown(
1266
  """
1267
+ # OKX BTC AI — OPTUNA + BACKTEST + OVERFIT MONITOR
1268
 
1269
+ OKX perpetual swap verisiyle çalışan ML ensemble.
1270
 
1271
+ **Supertrend + RSI + MACD + VWAP + Optuna + Walk-forward**
1272
  """
1273
  )
1274
 
 
1311
  )
1312
 
1313
  trials = gr.Slider(
1314
+ minimum=3,
1315
  maximum=100,
1316
  value=20,
1317
+ step=1,
1318
  label="Optuna trial sayısı"
1319
  )
1320