GoshawkVortexAI commited on
Commit
00d4a0f
·
verified ·
1 Parent(s): 57861f1

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +104 -90
app.py CHANGED
@@ -2,10 +2,6 @@
2
  # GOSHAWK ALBION MARKET AI
3
  # Tek dosyalık, çalıştırılabilir Market Intelligence uygulaması.
4
  # Gerekli paketler: gradio, lightgbm, pandas, numpy, plotly, requests, scikit-learn, joblib
5
- #
6
- # NOT: Bu uygulama Albion Online Data Project (AODP) public API'sini kullanır.
7
- # Varsayılan sunucu: Avrupa (europe).
8
- # API dokümantasyonu: https://www.albion-online-data.com/
9
 
10
  import os
11
  import time
@@ -37,9 +33,6 @@ warnings.filterwarnings("ignore")
37
  # ---------------------------------------------------------------------------
38
 
39
  API_BASE = "https://europe.albion-online-data.com/api/v2"
40
- # Alternatifler:
41
- # API_BASE = "https://west.albion-online-data.com/api/v2" # Americas
42
- # API_BASE = "https://east.albion-online-data.com/api/v2" # Asia
43
 
44
  CITIES = [
45
  "Bridgewatch",
@@ -108,16 +101,29 @@ MODEL_PATH = os.path.join(MODEL_DIR, "gos_hawk_model.pkl")
108
 
109
  PURGE_HOURS = 24
110
  EMBARGO_HOURS = 24
111
- # Eşik düşürüldü: public API'den gelen gerçek veri hacmi genellikle sınırlıdır
112
  MIN_TRAIN_SAMPLES = 150
113
 
114
  REQUEST_TIMEOUT = 30
115
  MAX_RETRIES = 3
116
  RETRY_BACKOFF = 2
117
 
118
- # Public API zaman ölçeği (saat): 6 → 6 saatlik bucket → 4× daha fazla veri noktası
119
  HISTORY_TIME_SCALE = 6
120
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
  # ---------------------------------------------------------------------------
122
  # PUBLIC API ÇAĞRILARI
123
  # ---------------------------------------------------------------------------
@@ -125,7 +131,7 @@ HISTORY_TIME_SCALE = 6
125
 
126
  def safe_request(url: str, params: Optional[dict] = None) -> Optional[list]:
127
  headers = {
128
- "User-Agent": "GOSHAWK-ALBION-MARKET-AI/1.1 (public-api-client)",
129
  "Accept": "application/json",
130
  }
131
  for attempt in range(MAX_RETRIES):
@@ -181,16 +187,7 @@ def fetch_current_prices(item_ids: List[str], cities: List[str], qualities: str
181
  return df
182
 
183
 
184
- def fetch_history(
185
- item_ids: List[str],
186
- cities: List[str],
187
- time_scale: int = 6,
188
- qualities: str = "1",
189
- ) -> pd.DataFrame:
190
- """
191
- AODP /stats/history/ endpoint'inden tarihsel ortalama fiyatları çeker.
192
- time_scale=6 → 6 saatlik bucket → 24h'ye göre 4 kat daha fazla nokta.
193
- """
194
  all_records = []
195
  chunk_size = 30
196
  for i in range(0, len(item_ids), chunk_size):
@@ -198,11 +195,7 @@ def fetch_history(
198
  items_str = ",".join(chunk)
199
  locations_str = ",".join(cities)
200
  url = f"{API_BASE}/stats/history/{items_str}.json"
201
- params = {
202
- "locations": locations_str,
203
- "qualities": qualities,
204
- "time-scale": time_scale,
205
- }
206
  data = safe_request(url, params)
207
  if data:
208
  for entry in data:
@@ -304,10 +297,6 @@ def build_features(df: pd.DataFrame) -> pd.DataFrame:
304
 
305
  group_cols = ["item_id", "city"]
306
 
307
- # time_scale=6 ile saatlik değil 6-saatlik bucket kullanıyoruz.
308
- # 1 birim = 6 saat. Bu yüzden lag/chg/ma pencerelerini "bucket" cinsinden
309
- # ifade ediyoruz: 1h→1 bucket, 24h→4 bucket, 48h→8 bucket.
310
- # Yine de isimleri saat cinsinden bırakıyoruz (anlamlılık için).
311
  for lag in [1, 3, 6, 12, 24, 48]:
312
  df[f"lag_{lag}h"] = df.groupby(group_cols)["avg_price"].shift(lag)
313
 
@@ -315,7 +304,6 @@ def build_features(df: pd.DataFrame) -> pd.DataFrame:
315
  for chg in [1, 6, 12, 24]:
316
  df[f"chg_{chg}h"] = df.groupby(group_cols)["log_price"].shift(chg)
317
 
318
- # min_periods yumuşatıldı: daha az NaN, daha çok eğitim örneği
319
  for window in [6, 12, 24, 48]:
320
  min_p = max(3, window // 4)
321
  df[f"ma_{window}h"] = df.groupby(group_cols)["avg_price"].transform(
@@ -336,7 +324,8 @@ def build_features(df: pd.DataFrame) -> pd.DataFrame:
336
  city_avg = df.groupby(["item_id", "timestamp"])["avg_price"].transform("mean")
337
  df["city_price_deviation"] = (df["avg_price"] - city_avg) / city_avg.replace(0, np.nan)
338
 
339
- df["target_return_24h"] = df.groupby(group_cols)["log_price"].shift(-4) # 4 bucket = 24h
 
340
 
341
  return df
342
 
@@ -358,10 +347,6 @@ FEATURE_COLS = [
358
 
359
 
360
  def prepare_model_data(featured_df: pd.DataFrame) -> Tuple[pd.DataFrame, pd.Series, pd.DataFrame]:
361
- """
362
- Eğitim verisini haz��rlar. 48h lag/ma NaN ise daha kısa pencereden doldurur
363
- ki örnek sayısı düşmesin.
364
- """
365
  if featured_df.empty:
366
  return pd.DataFrame(), pd.Series(dtype=float), pd.DataFrame()
367
 
@@ -369,10 +354,8 @@ def prepare_model_data(featured_df: pd.DataFrame) -> Tuple[pd.DataFrame, pd.Seri
369
  df["item_code"] = df["item_id"].astype("category").cat.codes
370
  df["city_code"] = df["city"].astype("category").cat.codes
371
 
372
- # Target ve temel lag'ler zorunlu
373
  df = df.dropna(subset=["target_return_24h", "lag_1h", "lag_24h"]).copy()
374
 
375
- # Uzun pencereleri kısa pencereden doldur
376
  if "lag_48h" in df.columns:
377
  df["lag_48h"] = df["lag_48h"].fillna(df["lag_24h"])
378
  if "ma_48h" in df.columns:
@@ -380,13 +363,13 @@ def prepare_model_data(featured_df: pd.DataFrame) -> Tuple[pd.DataFrame, pd.Seri
380
  if "ma_24h" in df.columns:
381
  df["ma_24h"] = df["ma_24h"].fillna(df["lag_24h"])
382
  if "ma_12h" in df.columns:
383
- df["ma_12h"] = df["ma_12h"].fillna(df["lag_12h"] if "lag_12h" in df.columns else df["lag_24h"])
384
  if "ma_6h" in df.columns:
385
- df["ma_6h"] = df["ma_6h"].fillna(df["lag_6h"] if "lag_6h" in df.columns else df["lag_24h"])
386
  if "lag_3h" in df.columns:
387
- df["lag_3h"] = df["lag_3h"].fillna(df["lag_6h"] if "lag_6h" in df.columns else df["lag_24h"])
388
  if "lag_6h" in df.columns:
389
- df["lag_6h"] = df["lag_6h"].fillna(df["lag_12h"] if "lag_12h" in df.columns else df["lag_24h"])
390
  if "lag_12h" in df.columns:
391
  df["lag_12h"] = df["lag_12h"].fillna(df["lag_24h"])
392
 
@@ -400,13 +383,15 @@ def prepare_model_data(featured_df: pd.DataFrame) -> Tuple[pd.DataFrame, pd.Seri
400
  df["volume_ratio"] = df["volume_ratio"].fillna(1.0)
401
  df["city_price_deviation"] = df["city_price_deviation"].fillna(0.0)
402
 
403
- # Kalan NaN'ları temizle
404
  model_df = df.dropna(subset=FEATURE_COLS + ["target_return_24h"]).copy()
405
 
 
406
  if len(model_df) > 100:
407
- lower = model_df["target_return_24h"].quantile(0.001)
408
- upper = model_df["target_return_24h"].quantile(0.999)
409
- model_df["target_return_24h"] = model_df["target_return_24h"].clip(lower, upper)
 
 
410
 
411
  X = model_df[FEATURE_COLS].astype("float64")
412
  y = model_df["target_return_24h"].astype("float64")
@@ -424,23 +409,28 @@ def train_global_model(X: pd.DataFrame, y: pd.Series) -> Optional[lgb.LGBMRegres
424
  X_train, X_val = X.iloc[:split_idx], X.iloc[split_idx:]
425
  y_train, y_val = y.iloc[:split_idx], y.iloc[split_idx:]
426
 
 
427
  model = lgb.LGBMRegressor(
428
  objective="regression",
429
  metric="mae",
430
  boosting_type="gbdt",
431
- num_leaves=31,
432
- learning_rate=0.05,
433
- n_estimators=500,
434
- feature_fraction=0.8,
435
- bagging_fraction=0.8,
 
 
436
  bagging_freq=5,
 
 
437
  verbose=-1,
438
  random_state=42,
439
  n_jobs=-1,
440
  )
441
 
442
  callbacks = [
443
- lgb.early_stopping(stopping_rounds=50, verbose=False),
444
  lgb.log_evaluation(period=0),
445
  ]
446
 
@@ -455,6 +445,12 @@ def train_global_model(X: pd.DataFrame, y: pd.Series) -> Optional[lgb.LGBMRegres
455
 
456
 
457
  def predict_24h_returns(model: lgb.LGBMRegressor, latest_features: pd.DataFrame) -> pd.DataFrame:
 
 
 
 
 
 
458
  if latest_features.empty or model is None:
459
  return pd.DataFrame()
460
 
@@ -467,11 +463,23 @@ def predict_24h_returns(model: lgb.LGBMRegressor, latest_features: pd.DataFrame)
467
  if X_pred.empty:
468
  return pd.DataFrame()
469
 
470
- preds = model.predict(X_pred)
471
- result = latest_features.loc[X_pred.index, ["item_id", "city", "avg_price", "item_count", "volatility_24h"]].copy()
472
- result["predicted_log_return_24h"] = preds
473
- result["predicted_return_pct"] = (np.exp(preds) - 1) * 100
474
- result["predicted_price_24h"] = result["avg_price"] * np.exp(preds)
 
 
 
 
 
 
 
 
 
 
 
 
475
 
476
  return result
477
 
@@ -496,12 +504,12 @@ def walk_forward_backtest(featured_df: pd.DataFrame, n_splits: int = 3) -> Dict:
496
  all_predictions = []
497
 
498
  for i in range(n_splits):
499
- test_start_idx = test_size * (i + 1) + 4 # 4 bucket purge
500
  test_end_idx = test_start_idx + test_size
501
  if test_end_idx > len(unique_times):
502
  break
503
 
504
- train_end_idx = test_start_idx - 8 # 8 bucket embargo+purge
505
  if train_end_idx <= 0:
506
  continue
507
 
@@ -523,16 +531,21 @@ def walk_forward_backtest(featured_df: pd.DataFrame, n_splits: int = 3) -> Dict:
523
  model = lgb.LGBMRegressor(
524
  objective="regression",
525
  metric="mae",
526
- num_leaves=31,
527
- learning_rate=0.05,
 
 
528
  n_estimators=300,
 
 
529
  verbose=-1,
530
  random_state=42,
531
  n_jobs=-1,
532
  )
533
  model.fit(X_train, y_train)
534
 
535
- preds = model.predict(X_test)
 
536
 
537
  mae = mean_absolute_error(y_test, preds)
538
  rmse = np.sqrt(mean_squared_error(y_test, preds))
@@ -566,7 +579,7 @@ def walk_forward_backtest(featured_df: pd.DataFrame, n_splits: int = 3) -> Dict:
566
  sim_df = pd.concat(all_predictions)
567
  total_cost_rate = SETUP_FEE_RATE + TRANSACTION_TAX_RATE
568
  sim_df["net_return"] = sim_df["actual_return"] - total_cost_rate
569
- sim_df["trade_signal"] = sim_df["predicted_return"] > 0
570
  sim_trades = sim_df[sim_df["trade_signal"]].copy()
571
 
572
  if len(sim_trades) > 0:
@@ -606,7 +619,6 @@ class DataPipeline:
606
  self._backtest_cache = None
607
  self.data_source = "PUBLIC_API_EUROPE"
608
 
609
- # Diskte kayıtlı model varsa yükle
610
  if _HAS_JOBLIB and os.path.exists(MODEL_PATH):
611
  try:
612
  self.model = joblib.load(MODEL_PATH)
@@ -661,7 +673,7 @@ class DataPipeline:
661
  self.status_message = (
662
  f"Yetersiz örnek: {len(X)} (min {MIN_TRAIN_SAMPLES}). "
663
  f"Toplam tarihsel satır: {len(self.featured_df)}. "
664
- f"Daha fazla veri için HISTORY_TIME_SCALE=1 deneyin."
665
  )
666
  return False
667
 
@@ -670,17 +682,17 @@ class DataPipeline:
670
  self.status_message = "Model eğitimi başarısız."
671
  return False
672
 
673
- # Kaydet
674
  if _HAS_JOBLIB:
675
  try:
676
  joblib.dump(self.model, MODEL_PATH)
677
  self.status_message = (
678
- f"Model eğitildi ve kaydedildi ({len(X)} örnek). Kaynak: Public API (EU)."
 
679
  )
680
  except Exception as e:
681
  self.status_message = f"Model eğitildi ancak kaydedilemedi: {e}"
682
  else:
683
- self.status_message = f"Model eğitildi ({len(X)} örnek). Kaynak: Public API (EU)."
684
 
685
  self.last_update = dt.datetime.now()
686
  return True
@@ -693,7 +705,6 @@ class DataPipeline:
693
  latest["item_code"] = latest["item_id"].astype("category").cat.codes
694
  latest["city_code"] = latest["city"].astype("category").cat.codes
695
 
696
- # En son satırlar için feature NaN olabilir → kısmi doldur
697
  for col in FEATURE_COLS:
698
  if col not in latest.columns:
699
  latest[col] = 0.0
@@ -709,7 +720,15 @@ class DataPipeline:
709
  else:
710
  preds["spread"] = np.nan
711
 
712
- preds["confidence"] = (1.0 - preds["volatility_24h"].clip(0, 0.2) / 0.2).fillna(0.5).clip(0, 1)
 
 
 
 
 
 
 
 
713
  total_cost = SETUP_FEE_RATE + TRANSACTION_TAX_RATE
714
  preds["net_return_pct"] = preds["predicted_return_pct"] - (total_cost * 100)
715
 
@@ -751,6 +770,7 @@ def get_dashboard_metrics():
751
  "Ortalama Fiyat": f"{df['avg_price'].mean():.0f} silver",
752
  "Ortalama Hacim": f"{df['item_count'].mean():.1f}",
753
  "Model Durumu": "Eğitildi" if pipeline.model else "Eğitilmedi",
 
754
  "Son Güncelleme": pipeline.last_update.strftime("%Y-%m-%d %H:%M") if pipeline.last_update else "—",
755
  }
756
  return pd.DataFrame(list(metrics.items()), columns=["Metrik", "Değer"])
@@ -807,11 +827,13 @@ def get_prediction_table():
807
  bins=[-1, 10, 50, 200, np.inf],
808
  labels=["Düşük", "Orta", "İyi", "Yüksek"]
809
  ).astype(str)
 
 
810
 
811
  cols = [
812
  "Ürün", "Şehir", "Mevcut Fiyat", "Tahmini Fiyat (24s)",
813
  "Tahmini Değişim %", "Güvenilirlik", "Spread", "Likidite",
814
- "Net Getiri % (maliyet sonrası)"
815
  ]
816
  return preds[cols].sort_values("Tahmini Değişim %", ascending=False)
817
 
@@ -1029,7 +1051,7 @@ with gr.Blocks(title="GOSHAWK ALBION MARKET AI", theme=gr.themes.Soft(primary_hu
1029
  *LightGBM tabanlı 24 saatlik fiyat tahmini ve arbitraj analizi.*
1030
 
1031
  **Veri kaynağı:** Public Albion Online Data Project API (`europe.albion-online-data.com`)
1032
- **Tarihsel veri çözünürlüğü:** 6 saatlik bucket (daha fazla örnek için)
1033
 
1034
  **⚠️ Risk Uyarısı:** Bu uygulama yatırım tavsiyesi değildir. Model tahminleri belirsizlik içerir ve kesin kazanç garantisi sunmaz. Geçmiş performans gelecek sonuçların göstergesi değildir.
1035
  """
@@ -1044,18 +1066,13 @@ with gr.Blocks(title="GOSHAWK ALBION MARKET AI", theme=gr.themes.Soft(primary_hu
1044
  with gr.TabItem("📊 Dashboard"):
1045
  dashboard_table = gr.Dataframe(label="Özet Metrikler", interactive=False, wrap=True)
1046
  gr.Markdown(
1047
- "### Kullanım\n"
1048
- "1. 'Verileri Yenile ve Modeli Eğit' butonuna tıklayın.\n"
1049
- "2. Public AODP API'sinden gerçek piyasa verileri çekilir.\n"
1050
- "3. Global LightGBM modeli eğitilir ve `gos_hawk_data/models/` altına kaydedilir.\n"
1051
- "4. Diğer sekmelerden analizleri inceleyebilirsiniz.\n\n"
1052
- "### Veri hacmi hakkında\n"
1053
- "- `time-scale=6` → 6 saatlik bucket kullanılır, 24h'ye göre 4× daha fazla nokta.\n"
1054
- "- `MIN_TRAIN_SAMPLES=150` → düşük hacimli ürünlerde de model eğitilebilir.\n"
1055
- "- Hâlâ 'Yetersiz örnek' alıyorsanız `HISTORY_TIME_SCALE=1` (saatlik) deneyin.\n\n"
1056
- "### Riskler ve Belirsizlikler\n"
1057
  "- Model tahminleri **olasılıksaldır**, kesin değildir.\n"
1058
- "- Albion Online piyasası oyuncu davranışı, güncellemeler ve sezon değişimlerinden etkilenir.\n"
1059
  "- Backtest sonuçları geçmiş performansı yansıtır; gelecek performansı garanti etmez.\n"
1060
  "- Spread ve likidite düşükse gerçekleşme fiyatı tahminden sapabilir.\n"
1061
  )
@@ -1066,9 +1083,8 @@ with gr.Blocks(title="GOSHAWK ALBION MARKET AI", theme=gr.themes.Soft(primary_hu
1066
  with gr.TabItem("🔮 24 Saatlik Fiyat Tahmini"):
1067
  prediction_table = gr.Dataframe(label="Tahminler (24 saatlik log getiri modeli)", interactive=False)
1068
  gr.Markdown(
1069
- "*Güvenilirlik, volatilite tabanlı bir göstergedir. "
1070
- "Net getiri, %2.5 setup + %8 vergi düşülerek hesaplanmıştır. "
1071
- "Spread ve likidite düşükse işlem maliyeti artabilir. "
1072
  "Bu tahminler garanti değildir.*"
1073
  )
1074
 
@@ -1076,7 +1092,7 @@ with gr.Blocks(title="GOSHAWK ALBION MARKET AI", theme=gr.themes.Soft(primary_hu
1076
  arbitrage_table = gr.Dataframe(label="Arbitraj Fırsatları (işlem maliyetleri dahil)", interactive=False)
1077
  gr.Markdown(
1078
  "*Net kâr; alışta %2.5 setup fee, satışta %2.5 setup + %8 transaction tax düşülerek hesaplanmıştır. "
1079
- "Seyahat maliyeti ve zaman riski dahil değildir. Caerleon'a giriş/çıkış riski ayrıca değerlendirilmelidir.*"
1080
  )
1081
 
1082
  with gr.TabItem("📈 Model Performansı"):
@@ -1088,9 +1104,8 @@ with gr.Blocks(title="GOSHAWK ALBION MARKET AI", theme=gr.themes.Soft(primary_hu
1088
  simulation_table = gr.Dataframe(label="İşlem Simülasyonu (maliyetler sonrası)", interactive=False)
1089
  gr.Markdown(
1090
  "*Walk-forward backtest kronolojik split kullanır; purge ve embargo uygulanır. "
1091
- "Baseline, 'fiyat değişmez' (getiri=0) varsayımıdır. "
1092
- "Simülasyon yalnızca modelin pozitif getiri öngördüğü işlemleri içerir. "
1093
- "Gerçek işlemlerde slippage ve rekabet nedeniyle sonuçlar farklı olabilir.*"
1094
  )
1095
 
1096
  with gr.TabItem("🗄️ Veri Yönetimi"):
@@ -1098,7 +1113,6 @@ with gr.Blocks(title="GOSHAWK ALBION MARKET AI", theme=gr.themes.Soft(primary_hu
1098
  "Veriler `gos_hawk_data/` dizininde CSV olarak saklanır.\n\n"
1099
  "**API Kaynağı:** Public AODP API — `europe.albion-online-data.com/api/v2`\n"
1100
  "**Endpoint'ler:** `/stats/prices/` (anlık), `/stats/history/` (tarihsel)\n"
1101
- "**Rate-limit:** dakikada ~60 istek (public)\n"
1102
  "**Model dosyası:** `gos_hawk_data/models/gos_hawk_model.pkl`\n"
1103
  )
1104
  csv_files = gr.Dataframe(
 
2
  # GOSHAWK ALBION MARKET AI
3
  # Tek dosyalık, çalıştırılabilir Market Intelligence uygulaması.
4
  # Gerekli paketler: gradio, lightgbm, pandas, numpy, plotly, requests, scikit-learn, joblib
 
 
 
 
5
 
6
  import os
7
  import time
 
33
  # ---------------------------------------------------------------------------
34
 
35
  API_BASE = "https://europe.albion-online-data.com/api/v2"
 
 
 
36
 
37
  CITIES = [
38
  "Bridgewatch",
 
101
 
102
  PURGE_HOURS = 24
103
  EMBARGO_HOURS = 24
 
104
  MIN_TRAIN_SAMPLES = 150
105
 
106
  REQUEST_TIMEOUT = 30
107
  MAX_RETRIES = 3
108
  RETRY_BACKOFF = 2
109
 
 
110
  HISTORY_TIME_SCALE = 6
111
 
112
+ # ---------------------------------------------------------------------------
113
+ # TAHMİN SINIRLARI (aşırı tahminleri engelle)
114
+ # ---------------------------------------------------------------------------
115
+
116
+ # 24h'lik gerçekçi max log getiri aralığı (yaklaşık ±%15)
117
+ MAX_LOG_RETURN = 0.15
118
+ MIN_LOG_RETURN = -0.15
119
+
120
+ # Eğitim target clip (log uzayında)
121
+ TARGET_CLIP_LOWER_Q = 0.02
122
+ TARGET_CLIP_UPPER_Q = 0.98
123
+
124
+ # Güven eşiği: bu log getirinin altındaki tahminler "belirsiz" sayılır
125
+ LOW_CONFIDENCE_THRESHOLD = 0.02
126
+
127
  # ---------------------------------------------------------------------------
128
  # PUBLIC API ÇAĞRILARI
129
  # ---------------------------------------------------------------------------
 
131
 
132
  def safe_request(url: str, params: Optional[dict] = None) -> Optional[list]:
133
  headers = {
134
+ "User-Agent": "GOSHAWK-ALBION-MARKET-AI/1.2 (public-api-client)",
135
  "Accept": "application/json",
136
  }
137
  for attempt in range(MAX_RETRIES):
 
187
  return df
188
 
189
 
190
+ def fetch_history(item_ids: List[str], cities: List[str], time_scale: int = 6, qualities: str = "1") -> pd.DataFrame:
 
 
 
 
 
 
 
 
 
191
  all_records = []
192
  chunk_size = 30
193
  for i in range(0, len(item_ids), chunk_size):
 
195
  items_str = ",".join(chunk)
196
  locations_str = ",".join(cities)
197
  url = f"{API_BASE}/stats/history/{items_str}.json"
198
+ params = {"locations": locations_str, "qualities": qualities, "time-scale": time_scale}
 
 
 
 
199
  data = safe_request(url, params)
200
  if data:
201
  for entry in data:
 
297
 
298
  group_cols = ["item_id", "city"]
299
 
 
 
 
 
300
  for lag in [1, 3, 6, 12, 24, 48]:
301
  df[f"lag_{lag}h"] = df.groupby(group_cols)["avg_price"].shift(lag)
302
 
 
304
  for chg in [1, 6, 12, 24]:
305
  df[f"chg_{chg}h"] = df.groupby(group_cols)["log_price"].shift(chg)
306
 
 
307
  for window in [6, 12, 24, 48]:
308
  min_p = max(3, window // 4)
309
  df[f"ma_{window}h"] = df.groupby(group_cols)["avg_price"].transform(
 
324
  city_avg = df.groupby(["item_id", "timestamp"])["avg_price"].transform("mean")
325
  df["city_price_deviation"] = (df["avg_price"] - city_avg) / city_avg.replace(0, np.nan)
326
 
327
+ # Hedef: 4 bucket sonraki log getiri (time_scale=6h → 4*6=24h)
328
+ df["target_return_24h"] = df.groupby(group_cols)["log_price"].shift(-4)
329
 
330
  return df
331
 
 
347
 
348
 
349
  def prepare_model_data(featured_df: pd.DataFrame) -> Tuple[pd.DataFrame, pd.Series, pd.DataFrame]:
 
 
 
 
350
  if featured_df.empty:
351
  return pd.DataFrame(), pd.Series(dtype=float), pd.DataFrame()
352
 
 
354
  df["item_code"] = df["item_id"].astype("category").cat.codes
355
  df["city_code"] = df["city"].astype("category").cat.codes
356
 
 
357
  df = df.dropna(subset=["target_return_24h", "lag_1h", "lag_24h"]).copy()
358
 
 
359
  if "lag_48h" in df.columns:
360
  df["lag_48h"] = df["lag_48h"].fillna(df["lag_24h"])
361
  if "ma_48h" in df.columns:
 
363
  if "ma_24h" in df.columns:
364
  df["ma_24h"] = df["ma_24h"].fillna(df["lag_24h"])
365
  if "ma_12h" in df.columns:
366
+ df["ma_12h"] = df["ma_12h"].fillna(df.get("lag_12h", df["lag_24h"]))
367
  if "ma_6h" in df.columns:
368
+ df["ma_6h"] = df["ma_6h"].fillna(df.get("lag_6h", df["lag_24h"]))
369
  if "lag_3h" in df.columns:
370
+ df["lag_3h"] = df["lag_3h"].fillna(df.get("lag_6h", df["lag_24h"]))
371
  if "lag_6h" in df.columns:
372
+ df["lag_6h"] = df["lag_6h"].fillna(df.get("lag_12h", df["lag_24h"]))
373
  if "lag_12h" in df.columns:
374
  df["lag_12h"] = df["lag_12h"].fillna(df["lag_24h"])
375
 
 
383
  df["volume_ratio"] = df["volume_ratio"].fillna(1.0)
384
  df["city_price_deviation"] = df["city_price_deviation"].fillna(0.0)
385
 
 
386
  model_df = df.dropna(subset=FEATURE_COLS + ["target_return_24h"]).copy()
387
 
388
+ # KRİTİK DÜZELTME: target clip çok daha dar
389
  if len(model_df) > 100:
390
+ lower = model_df["target_return_24h"].quantile(TARGET_CLIP_LOWER_Q)
391
+ upper = model_df["target_return_24h"].quantile(TARGET_CLIP_UPPER_Q)
392
+ model_df["target_return_24h"] = model_df["target_return_24h"].clip(
393
+ max(lower, MIN_LOG_RETURN), min(upper, MAX_LOG_RETURN)
394
+ )
395
 
396
  X = model_df[FEATURE_COLS].astype("float64")
397
  y = model_df["target_return_24h"].astype("float64")
 
409
  X_train, X_val = X.iloc[:split_idx], X.iloc[split_idx:]
410
  y_train, y_val = y.iloc[:split_idx], y.iloc[split_idx:]
411
 
412
+ # KRİTİK DÜZELTME: daha güçlü regülarizasyon → overfit önleme
413
  model = lgb.LGBMRegressor(
414
  objective="regression",
415
  metric="mae",
416
  boosting_type="gbdt",
417
+ num_leaves=15, # 31 → 15
418
+ max_depth=6, # eklendi
419
+ min_child_samples=50, # eklendi
420
+ learning_rate=0.03, # 0.05 → 0.03
421
+ n_estimators=400,
422
+ feature_fraction=0.7,
423
+ bagging_fraction=0.7,
424
  bagging_freq=5,
425
+ reg_alpha=0.1, # L1
426
+ reg_lambda=1.0, # L2
427
  verbose=-1,
428
  random_state=42,
429
  n_jobs=-1,
430
  )
431
 
432
  callbacks = [
433
+ lgb.early_stopping(stopping_rounds=30, verbose=False),
434
  lgb.log_evaluation(period=0),
435
  ]
436
 
 
445
 
446
 
447
  def predict_24h_returns(model: lgb.LGBMRegressor, latest_features: pd.DataFrame) -> pd.DataFrame:
448
+ """
449
+ KRİTİK DÜZELTME:
450
+ 1. Ham tahmin clip'lenir (aşırı değerleri engelle).
451
+ 2. Tahminin mutlak değeri çok küçükse "belirsiz" işaretlenir.
452
+ 3. exp() güvenli aralıkta uygulanır.
453
+ """
454
  if latest_features.empty or model is None:
455
  return pd.DataFrame()
456
 
 
463
  if X_pred.empty:
464
  return pd.DataFrame()
465
 
466
+ raw_preds = model.predict(X_pred)
467
+
468
+ # Ham tahmini güvenli aralığa clip'le
469
+ clipped_preds = np.clip(raw_preds, MIN_LOG_RETURN, MAX_LOG_RETURN)
470
+
471
+ # Yüzdeye çevir
472
+ pred_return_pct = (np.exp(clipped_preds) - 1) * 100
473
+
474
+ result = latest_features.loc[X_pred.index, ["item_id", "city", "avg_price", "item_count", "volatility_24h", "rsi_14", "volume_ratio"]].copy()
475
+ result["raw_log_pred"] = raw_preds
476
+ result["clipped_log_pred"] = clipped_preds
477
+ result["predicted_log_return_24h"] = clipped_preds
478
+ result["predicted_return_pct"] = pred_return_pct
479
+ result["predicted_price_24h"] = result["avg_price"] * np.exp(clipped_preds)
480
+
481
+ # Belirsizlik göstergesi: |tahmin| < eşik ise "belirsiz"
482
+ result["is_uncertain"] = np.abs(clipped_preds) < LOW_CONFIDENCE_THRESHOLD
483
 
484
  return result
485
 
 
504
  all_predictions = []
505
 
506
  for i in range(n_splits):
507
+ test_start_idx = test_size * (i + 1) + 4
508
  test_end_idx = test_start_idx + test_size
509
  if test_end_idx > len(unique_times):
510
  break
511
 
512
+ train_end_idx = test_start_idx - 8
513
  if train_end_idx <= 0:
514
  continue
515
 
 
531
  model = lgb.LGBMRegressor(
532
  objective="regression",
533
  metric="mae",
534
+ num_leaves=15,
535
+ max_depth=6,
536
+ min_child_samples=50,
537
+ learning_rate=0.03,
538
  n_estimators=300,
539
+ reg_alpha=0.1,
540
+ reg_lambda=1.0,
541
  verbose=-1,
542
  random_state=42,
543
  n_jobs=-1,
544
  )
545
  model.fit(X_train, y_train)
546
 
547
+ raw_preds = model.predict(X_test)
548
+ preds = np.clip(raw_preds, MIN_LOG_RETURN, MAX_LOG_RETURN)
549
 
550
  mae = mean_absolute_error(y_test, preds)
551
  rmse = np.sqrt(mean_squared_error(y_test, preds))
 
579
  sim_df = pd.concat(all_predictions)
580
  total_cost_rate = SETUP_FEE_RATE + TRANSACTION_TAX_RATE
581
  sim_df["net_return"] = sim_df["actual_return"] - total_cost_rate
582
+ sim_df["trade_signal"] = sim_df["predicted_return"] > LOW_CONFIDENCE_THRESHOLD
583
  sim_trades = sim_df[sim_df["trade_signal"]].copy()
584
 
585
  if len(sim_trades) > 0:
 
619
  self._backtest_cache = None
620
  self.data_source = "PUBLIC_API_EUROPE"
621
 
 
622
  if _HAS_JOBLIB and os.path.exists(MODEL_PATH):
623
  try:
624
  self.model = joblib.load(MODEL_PATH)
 
673
  self.status_message = (
674
  f"Yetersiz örnek: {len(X)} (min {MIN_TRAIN_SAMPLES}). "
675
  f"Toplam tarihsel satır: {len(self.featured_df)}. "
676
+ f"HISTORY_TIME_SCALE=1 deneyin."
677
  )
678
  return False
679
 
 
682
  self.status_message = "Model eğitimi başarısız."
683
  return False
684
 
 
685
  if _HAS_JOBLIB:
686
  try:
687
  joblib.dump(self.model, MODEL_PATH)
688
  self.status_message = (
689
+ f"Model eğitildi ve kaydedildi ({len(X)} örnek). "
690
+ f"Tahminler ±%15 aralığına sınırlandırıldı."
691
  )
692
  except Exception as e:
693
  self.status_message = f"Model eğitildi ancak kaydedilemedi: {e}"
694
  else:
695
+ self.status_message = f"Model eğitildi ({len(X)} örnek). Tahminler ±%15 aralığına sınırlandırıldı."
696
 
697
  self.last_update = dt.datetime.now()
698
  return True
 
705
  latest["item_code"] = latest["item_id"].astype("category").cat.codes
706
  latest["city_code"] = latest["city"].astype("category").cat.codes
707
 
 
708
  for col in FEATURE_COLS:
709
  if col not in latest.columns:
710
  latest[col] = 0.0
 
720
  else:
721
  preds["spread"] = np.nan
722
 
723
+ # Gelişmi�� güvenilirlik: volatilite + RSI aşırı bölge + volume
724
+ vol_score = (1.0 - preds["volatility_24h"].clip(0, 0.2) / 0.2).fillna(0.5)
725
+ rsi_penalty = ((preds["rsi_14"] - 50).abs() / 50).clip(0, 1).fillna(0.5) * 0.2
726
+ vol_ratio_score = preds["volume_ratio"].clip(0, 2).fillna(1.0) / 2
727
+ base_conf = vol_score * 0.6 + vol_ratio_score * 0.4 - rsi_penalty
728
+ # Belirsiz tahminleri cezalandır
729
+ certainty_penalty = preds["is_uncertain"].astype(float) * 0.4
730
+ preds["confidence"] = (base_conf - certainty_penalty).clip(0, 1).fillna(0.3)
731
+
732
  total_cost = SETUP_FEE_RATE + TRANSACTION_TAX_RATE
733
  preds["net_return_pct"] = preds["predicted_return_pct"] - (total_cost * 100)
734
 
 
770
  "Ortalama Fiyat": f"{df['avg_price'].mean():.0f} silver",
771
  "Ortalama Hacim": f"{df['item_count'].mean():.1f}",
772
  "Model Durumu": "Eğitildi" if pipeline.model else "Eğitilmedi",
773
+ "Tahmin Sınırı": f"±%{MAX_LOG_RETURN * 100:.0f}",
774
  "Son Güncelleme": pipeline.last_update.strftime("%Y-%m-%d %H:%M") if pipeline.last_update else "—",
775
  }
776
  return pd.DataFrame(list(metrics.items()), columns=["Metrik", "Değer"])
 
827
  bins=[-1, 10, 50, 200, np.inf],
828
  labels=["Düşük", "Orta", "İyi", "Yüksek"]
829
  ).astype(str)
830
+ # Belirsiz tahminleri işaretle
831
+ preds["Not"] = np.where(preds["is_uncertain"], "⚠️ Belirsiz", "")
832
 
833
  cols = [
834
  "Ürün", "Şehir", "Mevcut Fiyat", "Tahmini Fiyat (24s)",
835
  "Tahmini Değişim %", "Güvenilirlik", "Spread", "Likidite",
836
+ "Net Getiri % (maliyet sonrası)", "Not"
837
  ]
838
  return preds[cols].sort_values("Tahmini Değişim %", ascending=False)
839
 
 
1051
  *LightGBM tabanlı 24 saatlik fiyat tahmini ve arbitraj analizi.*
1052
 
1053
  **Veri kaynağı:** Public Albion Online Data Project API (`europe.albion-online-data.com`)
1054
+ **Tahmin sınırı:** ±%15 (aşırı tahminler engellendi)
1055
 
1056
  **⚠️ Risk Uyarısı:** Bu uygulama yatırım tavsiyesi değildir. Model tahminleri belirsizlik içerir ve kesin kazanç garantisi sunmaz. Geçmiş performans gelecek sonuçların göstergesi değildir.
1057
  """
 
1066
  with gr.TabItem("📊 Dashboard"):
1067
  dashboard_table = gr.Dataframe(label="Özet Metrikler", interactive=False, wrap=True)
1068
  gr.Markdown(
1069
+ "### Tahmin Kalibrasyonu\n"
1070
+ "- Model log getiri tahminleri **±0.15 log birimi** (yaklaşık ±%16) ile sınırlandırılmıştır.\n"
1071
+ "- Bu aralığın dışındaki ham tahminler `clip`lenir.\n"
1072
+ "- Tahmin mutlak değeri < 0.02 ise **belirsiz** olarak işaretlenir.\n"
1073
+ "- Güvenilirlik; volatilite, RSI aşırı bölge ve hacim oranına göre hesaplanır.\n\n"
1074
+ "### Riskler\n"
 
 
 
 
1075
  "- Model tahminleri **olasılıksaldır**, kesin değildir.\n"
 
1076
  "- Backtest sonuçları geçmiş performansı yansıtır; gelecek performansı garanti etmez.\n"
1077
  "- Spread ve likidite düşükse gerçekleşme fiyatı tahminden sapabilir.\n"
1078
  )
 
1083
  with gr.TabItem("🔮 24 Saatlik Fiyat Tahmini"):
1084
  prediction_table = gr.Dataframe(label="Tahminler (24 saatlik log getiri modeli)", interactive=False)
1085
  gr.Markdown(
1086
+ "*Net getiri %2.5 setup + %8 vergi düşülerek hesaplanır. "
1087
+ "**Not** sütunundaki ⚠️ Belirsiz işareti, modelin sinyal üretmediği durumları gösterir. "
 
1088
  "Bu tahminler garanti değildir.*"
1089
  )
1090
 
 
1092
  arbitrage_table = gr.Dataframe(label="Arbitraj Fırsatları (işlem maliyetleri dahil)", interactive=False)
1093
  gr.Markdown(
1094
  "*Net kâr; alışta %2.5 setup fee, satışta %2.5 setup + %8 transaction tax düşülerek hesaplanmıştır. "
1095
+ "Seyahat maliyeti ve zaman riski dahil değildir.*"
1096
  )
1097
 
1098
  with gr.TabItem("📈 Model Performansı"):
 
1104
  simulation_table = gr.Dataframe(label="İşlem Simülasyonu (maliyetler sonrası)", interactive=False)
1105
  gr.Markdown(
1106
  "*Walk-forward backtest kronolojik split kullanır; purge ve embargo uygulanır. "
1107
+ "Baseline 'fiyat değişmez' varsayımıdır. Simülasyon yalnızca modelin güven eşiğinin üzerinde "
1108
+ "pozitif getiri öngördüğü işlemleri içerir.*"
 
1109
  )
1110
 
1111
  with gr.TabItem("🗄️ Veri Yönetimi"):
 
1113
  "Veriler `gos_hawk_data/` dizininde CSV olarak saklanır.\n\n"
1114
  "**API Kaynağı:** Public AODP API — `europe.albion-online-data.com/api/v2`\n"
1115
  "**Endpoint'ler:** `/stats/prices/` (anlık), `/stats/history/` (tarihsel)\n"
 
1116
  "**Model dosyası:** `gos_hawk_data/models/gos_hawk_model.pkl`\n"
1117
  )
1118
  csv_files = gr.Dataframe(