Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -37,7 +37,6 @@ CITIES = [
|
|
| 37 |
"Caerleon",
|
| 38 |
]
|
| 39 |
|
| 40 |
-
# 100 popüler ürün (T4-T6 kaynaklar, ekipmanlar, tüketim malları)
|
| 41 |
_ITEM_POOL = [
|
| 42 |
"T4_BAG", "T5_BAG", "T6_BAG", "T7_BAG", "T8_BAG",
|
| 43 |
"T4_CAPE", "T5_CAPE", "T6_CAPE", "T7_CAPE", "T8_CAPE",
|
|
@@ -79,9 +78,11 @@ _ITEM_POOL = [
|
|
| 79 |
]
|
| 80 |
ITEM_IDS = list(dict.fromkeys(_ITEM_POOL))[:100]
|
| 81 |
|
|
|
|
| 82 |
def item_display_name(item_id: str) -> str:
|
| 83 |
return item_id.replace("_", " ").title()
|
| 84 |
|
|
|
|
| 85 |
SETUP_FEE_RATE = 0.025 # %2.5 listeleme ücreti
|
| 86 |
TRANSACTION_TAX_RATE = 0.08 # %8 satış vergisi (Premium olmayan varsayılan)
|
| 87 |
|
|
@@ -103,6 +104,7 @@ RETRY_BACKOFF = 2
|
|
| 103 |
# YARDIMCI FONKSİYONLAR
|
| 104 |
# ---------------------------------------------------------------------------
|
| 105 |
|
|
|
|
| 106 |
def safe_request(url: str, params: Optional[dict] = None) -> Optional[list]:
|
| 107 |
for attempt in range(MAX_RETRIES):
|
| 108 |
try:
|
|
@@ -207,6 +209,7 @@ def save_to_csv(df: pd.DataFrame, name: str) -> str:
|
|
| 207 |
# FEATURE ENGINEERING
|
| 208 |
# ---------------------------------------------------------------------------
|
| 209 |
|
|
|
|
| 210 |
def compute_rsi(series: pd.Series, period: int = 14) -> pd.Series:
|
| 211 |
delta = series.diff()
|
| 212 |
gain = delta.clip(lower=0).rolling(period).mean()
|
|
@@ -350,7 +353,7 @@ def predict_24h_returns(model: lgb.LGBMRegressor, latest_features: pd.DataFrame)
|
|
| 350 |
return pd.DataFrame()
|
| 351 |
|
| 352 |
preds = model.predict(X_pred)
|
| 353 |
-
result = latest_features.loc[X_pred.index, ["item_id", "city", "avg_price", "item_count"]].copy()
|
| 354 |
result["predicted_log_return_24h"] = preds
|
| 355 |
result["predicted_return_pct"] = (np.exp(preds) - 1) * 100
|
| 356 |
result["predicted_price_24h"] = result["avg_price"] * np.exp(preds)
|
|
@@ -362,6 +365,7 @@ def predict_24h_returns(model: lgb.LGBMRegressor, latest_features: pd.DataFrame)
|
|
| 362 |
# WALK-FORWARD BACKTEST
|
| 363 |
# ---------------------------------------------------------------------------
|
| 364 |
|
|
|
|
| 365 |
def walk_forward_backtest(featured_df: pd.DataFrame, n_splits: int = 3) -> Dict:
|
| 366 |
if featured_df.empty:
|
| 367 |
return {"error": "Veri yok."}
|
|
@@ -447,20 +451,17 @@ def walk_forward_backtest(featured_df: pd.DataFrame, n_splits: int = 3) -> Dict:
|
|
| 447 |
sim_df = pd.concat(all_predictions)
|
| 448 |
total_cost_rate = SETUP_FEE_RATE + TRANSACTION_TAX_RATE
|
| 449 |
sim_df["net_return"] = sim_df["actual_return"] - total_cost_rate
|
| 450 |
-
sim_df["gross_return"] = sim_df["actual_return"]
|
| 451 |
sim_df["trade_signal"] = sim_df["predicted_return"] > 0
|
| 452 |
sim_trades = sim_df[sim_df["trade_signal"]].copy()
|
| 453 |
|
| 454 |
if len(sim_trades) > 0:
|
| 455 |
avg_net_return = sim_trades["net_return"].mean()
|
| 456 |
win_rate = (sim_trades["net_return"] > 0).mean()
|
| 457 |
-
total_trades = len(sim_trades)
|
| 458 |
-
profitable_trades = int((sim_trades["net_return"] > 0).sum())
|
| 459 |
simulation = {
|
| 460 |
-
"total_trades": int(
|
| 461 |
"avg_net_return_per_trade_pct": round(float(avg_net_return * 100), 4),
|
| 462 |
"win_rate_pct": round(float(win_rate * 100), 2),
|
| 463 |
-
"profitable_trades":
|
| 464 |
}
|
| 465 |
else:
|
| 466 |
simulation = {"total_trades": 0, "avg_net_return_per_trade_pct": 0.0, "win_rate_pct": 0.0, "profitable_trades": 0}
|
|
@@ -478,6 +479,7 @@ def walk_forward_backtest(featured_df: pd.DataFrame, n_splits: int = 3) -> Dict:
|
|
| 478 |
# VERİ HATTI
|
| 479 |
# ---------------------------------------------------------------------------
|
| 480 |
|
|
|
|
| 481 |
class DataPipeline:
|
| 482 |
def __init__(self):
|
| 483 |
self.history_df = pd.DataFrame()
|
|
@@ -495,8 +497,12 @@ class DataPipeline:
|
|
| 495 |
cities = CITIES
|
| 496 |
|
| 497 |
self.status_message = "API'den veri çekiliyor..."
|
| 498 |
-
|
| 499 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 500 |
|
| 501 |
if hist.empty:
|
| 502 |
self.status_message = "Tarihsel veri alınamadı. API bağlantısını kontrol edin."
|
|
@@ -568,9 +574,10 @@ pipeline = DataPipeline()
|
|
| 568 |
# GRADIO YARDIMCI FONKSİYONLARI
|
| 569 |
# ---------------------------------------------------------------------------
|
| 570 |
|
|
|
|
| 571 |
def get_dashboard_metrics():
|
| 572 |
if pipeline.featured_df.empty:
|
| 573 |
-
return pd.DataFrame({"Metrik": ["Durum"], "Değer": ["Veri yok"]})
|
| 574 |
|
| 575 |
df = pipeline.featured_df
|
| 576 |
metrics = {
|
|
@@ -581,6 +588,7 @@ def get_dashboard_metrics():
|
|
| 581 |
"Ortalama Fiyat": f"{df['avg_price'].mean():.0f} silver",
|
| 582 |
"Ortalama Hacim": f"{df['item_count'].mean():.1f}",
|
| 583 |
"Model Durumu": "Eğitildi" if pipeline.model else "Eğitilmedi",
|
|
|
|
| 584 |
}
|
| 585 |
return pd.DataFrame(list(metrics.items()), columns=["Metrik", "Değer"])
|
| 586 |
|
|
@@ -596,13 +604,20 @@ def get_analysis_table():
|
|
| 596 |
pipeline.prices_df[["item_id", "city", "sell_price_min", "buy_price_max"]],
|
| 597 |
on=["item_id", "city"], how="left"
|
| 598 |
)
|
|
|
|
|
|
|
|
|
|
| 599 |
|
| 600 |
latest["Ürün"] = latest["item_id"].apply(item_display_name)
|
| 601 |
latest["Şehir"] = latest["city"]
|
| 602 |
latest["Mevcut Fiyat"] = latest["avg_price"].round(0)
|
| 603 |
latest["Hacim (24s)"] = latest["item_count"].fillna(0).astype(int)
|
| 604 |
-
latest["Spread"] = (latest
|
| 605 |
-
latest["Likidite"] = pd.cut(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 606 |
|
| 607 |
cols = ["Ürün", "Şehir", "Mevcut Fiyat", "Hacim (24s)", "Spread", "Likidite"]
|
| 608 |
return latest[cols].sort_values("Hacim (24s)", ascending=False).head(100)
|
|
@@ -611,8 +626,11 @@ def get_analysis_table():
|
|
| 611 |
def get_prediction_table():
|
| 612 |
preds = pipeline.get_latest_predictions()
|
| 613 |
if preds.empty:
|
| 614 |
-
return pd.DataFrame(
|
|
|
|
|
|
|
| 615 |
|
|
|
|
| 616 |
preds["Ürün"] = preds["item_id"].apply(item_display_name)
|
| 617 |
preds["Şehir"] = preds["city"]
|
| 618 |
preds["Mevcut Fiyat"] = preds["avg_price"].round(0)
|
|
@@ -621,7 +639,11 @@ def get_prediction_table():
|
|
| 621 |
preds["Güvenilirlik"] = (preds["confidence"] * 100).round(0).astype(int).astype(str) + "%"
|
| 622 |
preds["Spread"] = preds["spread"].round(0)
|
| 623 |
preds["Net Getiri % (maliyet sonrası)"] = preds["net_return_pct"].round(2)
|
| 624 |
-
preds["Likidite"] = pd.cut(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 625 |
|
| 626 |
cols = [
|
| 627 |
"Ürün", "Şehir", "Mevcut Fiyat", "Tahmini Fiyat (24s)",
|
|
@@ -633,11 +655,14 @@ def get_prediction_table():
|
|
| 633 |
|
| 634 |
def get_arbitrage_table():
|
| 635 |
if pipeline.prices_df.empty:
|
| 636 |
-
return pd.DataFrame()
|
| 637 |
|
| 638 |
prices = pipeline.prices_df.copy()
|
| 639 |
prices = prices[prices["sell_price_min"] > 0]
|
| 640 |
|
|
|
|
|
|
|
|
|
|
| 641 |
buy = prices.loc[prices.groupby("item_id")["sell_price_min"].idxmin()][["item_id", "city", "sell_price_min"]]
|
| 642 |
buy.columns = ["item_id", "buy_city", "buy_price"]
|
| 643 |
|
|
@@ -647,7 +672,9 @@ def get_arbitrage_table():
|
|
| 647 |
arb = buy.merge(sell, on="item_id")
|
| 648 |
arb = arb[arb["sell_price"] > arb["buy_price"]].copy()
|
| 649 |
|
| 650 |
-
|
|
|
|
|
|
|
| 651 |
arb["brüt_kâr"] = arb["sell_price"] - arb["buy_price"]
|
| 652 |
arb["toplam_maliyet"] = (
|
| 653 |
arb["buy_price"] * SETUP_FEE_RATE
|
|
@@ -658,6 +685,9 @@ def get_arbitrage_table():
|
|
| 658 |
|
| 659 |
arb = arb[arb["net_kâr"] > 0].sort_values("kâr_marjı_%", ascending=False)
|
| 660 |
|
|
|
|
|
|
|
|
|
|
| 661 |
arb["Ürün"] = arb["item_id"].apply(item_display_name)
|
| 662 |
arb["Alış Şehri"] = arb["buy_city"]
|
| 663 |
arb["Satış Şehri"] = arb["sell_city"]
|
|
@@ -672,13 +702,18 @@ def get_arbitrage_table():
|
|
| 672 |
|
| 673 |
def get_model_performance_plot():
|
| 674 |
if pipeline.model is None:
|
| 675 |
-
return go.Figure().update_layout(
|
|
|
|
|
|
|
|
|
|
| 676 |
|
| 677 |
try:
|
| 678 |
importance = pipeline.model.feature_importances_
|
| 679 |
feature_names = pipeline.model.feature_name_
|
| 680 |
df_imp = pd.DataFrame({"Özellik": feature_names, "Önem": importance}).sort_values("Önem", ascending=True).tail(20)
|
| 681 |
-
fig = px.bar(df_imp, x="Önem", y="Özellik", orientation="h",
|
|
|
|
|
|
|
| 682 |
return fig
|
| 683 |
except Exception as e:
|
| 684 |
return go.Figure().update_layout(title=f"Grafik hatası: {e}", template="plotly_dark")
|
|
@@ -688,7 +723,8 @@ def get_backtest_outputs():
|
|
| 688 |
bt = pipeline.get_backtest()
|
| 689 |
|
| 690 |
if "error" in bt:
|
| 691 |
-
|
|
|
|
| 692 |
|
| 693 |
results_df = pd.DataFrame(bt["results"])
|
| 694 |
|
|
@@ -711,6 +747,23 @@ def get_backtest_outputs():
|
|
| 711 |
return results_df, fig, sim_df
|
| 712 |
|
| 713 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 714 |
def refresh_data_ui():
|
| 715 |
pipeline.fetch_data()
|
| 716 |
if not pipeline.featured_df.empty:
|
|
@@ -728,6 +781,7 @@ def refresh_data_ui():
|
|
| 728 |
bt_table,
|
| 729 |
bt_fig,
|
| 730 |
sim_table,
|
|
|
|
| 731 |
)
|
| 732 |
|
| 733 |
|
|
@@ -741,6 +795,7 @@ def initial_load():
|
|
| 741 |
pd.DataFrame(),
|
| 742 |
go.Figure().update_layout(title="Verileri yenilemek için butona tıklayın", template="plotly_dark"),
|
| 743 |
pd.DataFrame(),
|
|
|
|
| 744 |
)
|
| 745 |
|
| 746 |
|
|
@@ -748,6 +803,7 @@ def initial_load():
|
|
| 748 |
# GRADIO ARAYÜZÜ
|
| 749 |
# ---------------------------------------------------------------------------
|
| 750 |
|
|
|
|
| 751 |
with gr.Blocks(title="GOSHAWK ALBION MARKET AI", theme=gr.themes.Soft(primary_hue="cyan", secondary_hue="slate")) as demo:
|
| 752 |
|
| 753 |
gr.Markdown(
|
|
@@ -762,18 +818,23 @@ with gr.Blocks(title="GOSHAWK ALBION MARKET AI", theme=gr.themes.Soft(primary_hu
|
|
| 762 |
|
| 763 |
with gr.Row():
|
| 764 |
refresh_btn = gr.Button("🔄 Verileri Yenile ve Modeli Eğit", variant="primary", scale=1)
|
| 765 |
-
status_text = gr.Textbox(label="Durum", interactive=False, scale=2)
|
| 766 |
|
| 767 |
with gr.Tabs():
|
| 768 |
|
| 769 |
with gr.TabItem("📊 Dashboard"):
|
| 770 |
-
dashboard_table = gr.Dataframe(label="Özet Metrikler", interactive=False)
|
| 771 |
gr.Markdown(
|
| 772 |
"### Kullanım\n"
|
| 773 |
"1. 'Verileri Yenile ve Modeli Eğit' butonuna tıklayın.\n"
|
| 774 |
"2. API'den gerçek piyasa verileri çekilir ve global LightGBM modeli eğitilir.\n"
|
| 775 |
"3. Diğer sekmelerden analizleri inceleyebilirsiniz.\n\n"
|
| 776 |
-
"*Veriler `gos_hawk_data/` dizininde CSV olarak saklanır.*"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 777 |
)
|
| 778 |
|
| 779 |
with gr.TabItem("📦 100 Ürün Analizi"):
|
|
@@ -782,9 +843,10 @@ with gr.Blocks(title="GOSHAWK ALBION MARKET AI", theme=gr.themes.Soft(primary_hu
|
|
| 782 |
with gr.TabItem("🔮 24 Saatlik Fiyat Tahmini"):
|
| 783 |
prediction_table = gr.Dataframe(label="Tahminler (24 saatlik log getiri modeli)", interactive=False)
|
| 784 |
gr.Markdown(
|
| 785 |
-
"*Güvenilirlik, volatilite
|
| 786 |
"Net getiri, %2.5 setup + %8 vergi düşülerek hesaplanmıştır. "
|
| 787 |
-
"Spread ve likidite düşükse işlem maliyeti artabilir.
|
|
|
|
| 788 |
)
|
| 789 |
|
| 790 |
with gr.TabItem("🚚 Şehirler Arası Arbitraj"):
|
|
@@ -798,18 +860,23 @@ with gr.Blocks(title="GOSHAWK ALBION MARKET AI", theme=gr.themes.Soft(primary_hu
|
|
| 798 |
performance_plot = gr.Plot(label="Feature Importance")
|
| 799 |
|
| 800 |
with gr.TabItem("🧪 Walk-forward Backtest"):
|
| 801 |
-
backtest_table = gr.Dataframe(label="Split Sonuçları")
|
| 802 |
backtest_plot = gr.Plot(label="MAE Karşılaştırması")
|
| 803 |
-
simulation_table = gr.Dataframe(label="İşlem Simülasyonu (maliyetler sonrası)")
|
| 804 |
gr.Markdown(
|
| 805 |
"*Walk-forward backtest kronolojik split kullanır; purge (24s) ve embargo (24s) uygulanır. "
|
| 806 |
-
"Baseline, 'fiyat değişmez' (getiri=0) varsayımıdır.
|
|
|
|
|
|
|
| 807 |
)
|
| 808 |
|
| 809 |
with gr.TabItem("🗄️ Veri Yönetimi"):
|
| 810 |
gr.Markdown("Veriler `gos_hawk_data/` dizininde CSV olarak saklanır.")
|
| 811 |
-
|
| 812 |
-
|
|
|
|
|
|
|
|
|
|
| 813 |
|
| 814 |
refresh_btn.click(
|
| 815 |
fn=refresh_data_ui,
|
|
@@ -824,6 +891,7 @@ with gr.Blocks(title="GOSHAWK ALBION MARKET AI", theme=gr.themes.Soft(primary_hu
|
|
| 824 |
backtest_table,
|
| 825 |
backtest_plot,
|
| 826 |
simulation_table,
|
|
|
|
| 827 |
],
|
| 828 |
)
|
| 829 |
|
|
@@ -839,6 +907,7 @@ with gr.Blocks(title="GOSHAWK ALBION MARKET AI", theme=gr.themes.Soft(primary_hu
|
|
| 839 |
backtest_table,
|
| 840 |
backtest_plot,
|
| 841 |
simulation_table,
|
|
|
|
| 842 |
],
|
| 843 |
)
|
| 844 |
|
|
|
|
| 37 |
"Caerleon",
|
| 38 |
]
|
| 39 |
|
|
|
|
| 40 |
_ITEM_POOL = [
|
| 41 |
"T4_BAG", "T5_BAG", "T6_BAG", "T7_BAG", "T8_BAG",
|
| 42 |
"T4_CAPE", "T5_CAPE", "T6_CAPE", "T7_CAPE", "T8_CAPE",
|
|
|
|
| 78 |
]
|
| 79 |
ITEM_IDS = list(dict.fromkeys(_ITEM_POOL))[:100]
|
| 80 |
|
| 81 |
+
|
| 82 |
def item_display_name(item_id: str) -> str:
|
| 83 |
return item_id.replace("_", " ").title()
|
| 84 |
|
| 85 |
+
|
| 86 |
SETUP_FEE_RATE = 0.025 # %2.5 listeleme ücreti
|
| 87 |
TRANSACTION_TAX_RATE = 0.08 # %8 satış vergisi (Premium olmayan varsayılan)
|
| 88 |
|
|
|
|
| 104 |
# YARDIMCI FONKSİYONLAR
|
| 105 |
# ---------------------------------------------------------------------------
|
| 106 |
|
| 107 |
+
|
| 108 |
def safe_request(url: str, params: Optional[dict] = None) -> Optional[list]:
|
| 109 |
for attempt in range(MAX_RETRIES):
|
| 110 |
try:
|
|
|
|
| 209 |
# FEATURE ENGINEERING
|
| 210 |
# ---------------------------------------------------------------------------
|
| 211 |
|
| 212 |
+
|
| 213 |
def compute_rsi(series: pd.Series, period: int = 14) -> pd.Series:
|
| 214 |
delta = series.diff()
|
| 215 |
gain = delta.clip(lower=0).rolling(period).mean()
|
|
|
|
| 353 |
return pd.DataFrame()
|
| 354 |
|
| 355 |
preds = model.predict(X_pred)
|
| 356 |
+
result = latest_features.loc[X_pred.index, ["item_id", "city", "avg_price", "item_count", "volatility_24h"]].copy()
|
| 357 |
result["predicted_log_return_24h"] = preds
|
| 358 |
result["predicted_return_pct"] = (np.exp(preds) - 1) * 100
|
| 359 |
result["predicted_price_24h"] = result["avg_price"] * np.exp(preds)
|
|
|
|
| 365 |
# WALK-FORWARD BACKTEST
|
| 366 |
# ---------------------------------------------------------------------------
|
| 367 |
|
| 368 |
+
|
| 369 |
def walk_forward_backtest(featured_df: pd.DataFrame, n_splits: int = 3) -> Dict:
|
| 370 |
if featured_df.empty:
|
| 371 |
return {"error": "Veri yok."}
|
|
|
|
| 451 |
sim_df = pd.concat(all_predictions)
|
| 452 |
total_cost_rate = SETUP_FEE_RATE + TRANSACTION_TAX_RATE
|
| 453 |
sim_df["net_return"] = sim_df["actual_return"] - total_cost_rate
|
|
|
|
| 454 |
sim_df["trade_signal"] = sim_df["predicted_return"] > 0
|
| 455 |
sim_trades = sim_df[sim_df["trade_signal"]].copy()
|
| 456 |
|
| 457 |
if len(sim_trades) > 0:
|
| 458 |
avg_net_return = sim_trades["net_return"].mean()
|
| 459 |
win_rate = (sim_trades["net_return"] > 0).mean()
|
|
|
|
|
|
|
| 460 |
simulation = {
|
| 461 |
+
"total_trades": int(len(sim_trades)),
|
| 462 |
"avg_net_return_per_trade_pct": round(float(avg_net_return * 100), 4),
|
| 463 |
"win_rate_pct": round(float(win_rate * 100), 2),
|
| 464 |
+
"profitable_trades": int((sim_trades["net_return"] > 0).sum()),
|
| 465 |
}
|
| 466 |
else:
|
| 467 |
simulation = {"total_trades": 0, "avg_net_return_per_trade_pct": 0.0, "win_rate_pct": 0.0, "profitable_trades": 0}
|
|
|
|
| 479 |
# VERİ HATTI
|
| 480 |
# ---------------------------------------------------------------------------
|
| 481 |
|
| 482 |
+
|
| 483 |
class DataPipeline:
|
| 484 |
def __init__(self):
|
| 485 |
self.history_df = pd.DataFrame()
|
|
|
|
| 497 |
cities = CITIES
|
| 498 |
|
| 499 |
self.status_message = "API'den veri çekiliyor..."
|
| 500 |
+
try:
|
| 501 |
+
hist = fetch_history(item_ids, cities)
|
| 502 |
+
prices = fetch_current_prices(item_ids, cities)
|
| 503 |
+
except Exception as e:
|
| 504 |
+
self.status_message = f"API bağlantı hatası: {e}"
|
| 505 |
+
return
|
| 506 |
|
| 507 |
if hist.empty:
|
| 508 |
self.status_message = "Tarihsel veri alınamadı. API bağlantısını kontrol edin."
|
|
|
|
| 574 |
# GRADIO YARDIMCI FONKSİYONLARI
|
| 575 |
# ---------------------------------------------------------------------------
|
| 576 |
|
| 577 |
+
|
| 578 |
def get_dashboard_metrics():
|
| 579 |
if pipeline.featured_df.empty:
|
| 580 |
+
return pd.DataFrame({"Metrik": ["Durum"], "Değer": ["Veri yok - Lütfen 'Verileri Yenile' butonuna tıklayın."]})
|
| 581 |
|
| 582 |
df = pipeline.featured_df
|
| 583 |
metrics = {
|
|
|
|
| 588 |
"Ortalama Fiyat": f"{df['avg_price'].mean():.0f} silver",
|
| 589 |
"Ortalama Hacim": f"{df['item_count'].mean():.1f}",
|
| 590 |
"Model Durumu": "Eğitildi" if pipeline.model else "Eğitilmedi",
|
| 591 |
+
"Son Güncelleme": pipeline.last_update.strftime("%Y-%m-%d %H:%M") if pipeline.last_update else "—",
|
| 592 |
}
|
| 593 |
return pd.DataFrame(list(metrics.items()), columns=["Metrik", "Değer"])
|
| 594 |
|
|
|
|
| 604 |
pipeline.prices_df[["item_id", "city", "sell_price_min", "buy_price_max"]],
|
| 605 |
on=["item_id", "city"], how="left"
|
| 606 |
)
|
| 607 |
+
else:
|
| 608 |
+
latest["sell_price_min"] = np.nan
|
| 609 |
+
latest["buy_price_max"] = np.nan
|
| 610 |
|
| 611 |
latest["Ürün"] = latest["item_id"].apply(item_display_name)
|
| 612 |
latest["Şehir"] = latest["city"]
|
| 613 |
latest["Mevcut Fiyat"] = latest["avg_price"].round(0)
|
| 614 |
latest["Hacim (24s)"] = latest["item_count"].fillna(0).astype(int)
|
| 615 |
+
latest["Spread"] = (latest["sell_price_min"] - latest["buy_price_max"]).round(0)
|
| 616 |
+
latest["Likidite"] = pd.cut(
|
| 617 |
+
latest["item_count"],
|
| 618 |
+
bins=[-1, 10, 50, 200, np.inf],
|
| 619 |
+
labels=["Düşük", "Orta", "İyi", "Yüksek"]
|
| 620 |
+
).astype(str)
|
| 621 |
|
| 622 |
cols = ["Ürün", "Şehir", "Mevcut Fiyat", "Hacim (24s)", "Spread", "Likidite"]
|
| 623 |
return latest[cols].sort_values("Hacim (24s)", ascending=False).head(100)
|
|
|
|
| 626 |
def get_prediction_table():
|
| 627 |
preds = pipeline.get_latest_predictions()
|
| 628 |
if preds.empty:
|
| 629 |
+
return pd.DataFrame({
|
| 630 |
+
"Bilgi": ["Tahmin üretilemedi. Veri çekip model eğitin veya yeterli veri olduğundan emin olun."]
|
| 631 |
+
})
|
| 632 |
|
| 633 |
+
preds = preds.copy()
|
| 634 |
preds["Ürün"] = preds["item_id"].apply(item_display_name)
|
| 635 |
preds["Şehir"] = preds["city"]
|
| 636 |
preds["Mevcut Fiyat"] = preds["avg_price"].round(0)
|
|
|
|
| 639 |
preds["Güvenilirlik"] = (preds["confidence"] * 100).round(0).astype(int).astype(str) + "%"
|
| 640 |
preds["Spread"] = preds["spread"].round(0)
|
| 641 |
preds["Net Getiri % (maliyet sonrası)"] = preds["net_return_pct"].round(2)
|
| 642 |
+
preds["Likidite"] = pd.cut(
|
| 643 |
+
preds["item_count"],
|
| 644 |
+
bins=[-1, 10, 50, 200, np.inf],
|
| 645 |
+
labels=["Düşük", "Orta", "İyi", "Yüksek"]
|
| 646 |
+
).astype(str)
|
| 647 |
|
| 648 |
cols = [
|
| 649 |
"Ürün", "Şehir", "Mevcut Fiyat", "Tahmini Fiyat (24s)",
|
|
|
|
| 655 |
|
| 656 |
def get_arbitrage_table():
|
| 657 |
if pipeline.prices_df.empty:
|
| 658 |
+
return pd.DataFrame({"Bilgi": ["Anlık fiyat verisi yok."]})
|
| 659 |
|
| 660 |
prices = pipeline.prices_df.copy()
|
| 661 |
prices = prices[prices["sell_price_min"] > 0]
|
| 662 |
|
| 663 |
+
if prices.empty:
|
| 664 |
+
return pd.DataFrame({"Bilgi": ["Geçerli anlık fiyat yok."]})
|
| 665 |
+
|
| 666 |
buy = prices.loc[prices.groupby("item_id")["sell_price_min"].idxmin()][["item_id", "city", "sell_price_min"]]
|
| 667 |
buy.columns = ["item_id", "buy_city", "buy_price"]
|
| 668 |
|
|
|
|
| 672 |
arb = buy.merge(sell, on="item_id")
|
| 673 |
arb = arb[arb["sell_price"] > arb["buy_price"]].copy()
|
| 674 |
|
| 675 |
+
if arb.empty:
|
| 676 |
+
return pd.DataFrame({"Bilgi": ["Kârlı arbitraj fırsatı bulunamadı."]})
|
| 677 |
+
|
| 678 |
arb["brüt_kâr"] = arb["sell_price"] - arb["buy_price"]
|
| 679 |
arb["toplam_maliyet"] = (
|
| 680 |
arb["buy_price"] * SETUP_FEE_RATE
|
|
|
|
| 685 |
|
| 686 |
arb = arb[arb["net_kâr"] > 0].sort_values("kâr_marjı_%", ascending=False)
|
| 687 |
|
| 688 |
+
if arb.empty:
|
| 689 |
+
return pd.DataFrame({"Bilgi": ["İşlem maliyetleri sonrası kârlı arbitraj yok."]})
|
| 690 |
+
|
| 691 |
arb["Ürün"] = arb["item_id"].apply(item_display_name)
|
| 692 |
arb["Alış Şehri"] = arb["buy_city"]
|
| 693 |
arb["Satış Şehri"] = arb["sell_city"]
|
|
|
|
| 702 |
|
| 703 |
def get_model_performance_plot():
|
| 704 |
if pipeline.model is None:
|
| 705 |
+
return go.Figure().update_layout(
|
| 706 |
+
title="Model eğitilmedi - Lütfen 'Verileri Yenile' butonuna tıklayın",
|
| 707 |
+
template="plotly_dark"
|
| 708 |
+
)
|
| 709 |
|
| 710 |
try:
|
| 711 |
importance = pipeline.model.feature_importances_
|
| 712 |
feature_names = pipeline.model.feature_name_
|
| 713 |
df_imp = pd.DataFrame({"Özellik": feature_names, "Önem": importance}).sort_values("Önem", ascending=True).tail(20)
|
| 714 |
+
fig = px.bar(df_imp, x="Önem", y="Özellik", orientation="h",
|
| 715 |
+
title="Feature Importance (Top 20)", template="plotly_dark")
|
| 716 |
+
fig.update_layout(height=600)
|
| 717 |
return fig
|
| 718 |
except Exception as e:
|
| 719 |
return go.Figure().update_layout(title=f"Grafik hatası: {e}", template="plotly_dark")
|
|
|
|
| 723 |
bt = pipeline.get_backtest()
|
| 724 |
|
| 725 |
if "error" in bt:
|
| 726 |
+
empty_fig = go.Figure().update_layout(title=f"Backtest hatası: {bt['error']}", template="plotly_dark")
|
| 727 |
+
return pd.DataFrame({"Bilgi": [bt["error"]]}), empty_fig, pd.DataFrame()
|
| 728 |
|
| 729 |
results_df = pd.DataFrame(bt["results"])
|
| 730 |
|
|
|
|
| 747 |
return results_df, fig, sim_df
|
| 748 |
|
| 749 |
|
| 750 |
+
def list_csv_files() -> pd.DataFrame:
|
| 751 |
+
rows = []
|
| 752 |
+
if os.path.isdir(DATA_DIR):
|
| 753 |
+
for fname in sorted(os.listdir(DATA_DIR)):
|
| 754 |
+
if fname.endswith(".csv"):
|
| 755 |
+
fpath = os.path.join(DATA_DIR, fname)
|
| 756 |
+
try:
|
| 757 |
+
size_kb = round(os.path.getsize(fpath) / 1024, 2)
|
| 758 |
+
mtime = dt.datetime.fromtimestamp(os.path.getmtime(fpath)).strftime("%Y-%m-%d %H:%M:%S")
|
| 759 |
+
rows.append({"Dosya": fname, "Boyut (KB)": size_kb, "Son Değişiklik": mtime})
|
| 760 |
+
except OSError:
|
| 761 |
+
continue
|
| 762 |
+
if not rows:
|
| 763 |
+
return pd.DataFrame(columns=["Dosya", "Boyut (KB)", "Son Değişiklik"])
|
| 764 |
+
return pd.DataFrame(rows)
|
| 765 |
+
|
| 766 |
+
|
| 767 |
def refresh_data_ui():
|
| 768 |
pipeline.fetch_data()
|
| 769 |
if not pipeline.featured_df.empty:
|
|
|
|
| 781 |
bt_table,
|
| 782 |
bt_fig,
|
| 783 |
sim_table,
|
| 784 |
+
list_csv_files(),
|
| 785 |
)
|
| 786 |
|
| 787 |
|
|
|
|
| 795 |
pd.DataFrame(),
|
| 796 |
go.Figure().update_layout(title="Verileri yenilemek için butona tıklayın", template="plotly_dark"),
|
| 797 |
pd.DataFrame(),
|
| 798 |
+
list_csv_files(),
|
| 799 |
)
|
| 800 |
|
| 801 |
|
|
|
|
| 803 |
# GRADIO ARAYÜZÜ
|
| 804 |
# ---------------------------------------------------------------------------
|
| 805 |
|
| 806 |
+
|
| 807 |
with gr.Blocks(title="GOSHAWK ALBION MARKET AI", theme=gr.themes.Soft(primary_hue="cyan", secondary_hue="slate")) as demo:
|
| 808 |
|
| 809 |
gr.Markdown(
|
|
|
|
| 818 |
|
| 819 |
with gr.Row():
|
| 820 |
refresh_btn = gr.Button("🔄 Verileri Yenile ve Modeli Eğit", variant="primary", scale=1)
|
| 821 |
+
status_text = gr.Textbox(label="Durum", interactive=False, scale=2, value=pipeline.status_message)
|
| 822 |
|
| 823 |
with gr.Tabs():
|
| 824 |
|
| 825 |
with gr.TabItem("📊 Dashboard"):
|
| 826 |
+
dashboard_table = gr.Dataframe(label="Özet Metrikler", interactive=False, wrap=True)
|
| 827 |
gr.Markdown(
|
| 828 |
"### Kullanım\n"
|
| 829 |
"1. 'Verileri Yenile ve Modeli Eğit' butonuna tıklayın.\n"
|
| 830 |
"2. API'den gerçek piyasa verileri çekilir ve global LightGBM modeli eğitilir.\n"
|
| 831 |
"3. Diğer sekmelerden analizleri inceleyebilirsiniz.\n\n"
|
| 832 |
+
"*Veriler `gos_hawk_data/` dizininde CSV olarak saklanır.*\n\n"
|
| 833 |
+
"### Riskler ve Belirsizlikler\n"
|
| 834 |
+
"- Model tahminleri **olasılıksaldır**, kesin değildir.\n"
|
| 835 |
+
"- Albion Online piyasası oyuncu davranışı, güncellemeler ve sezon değişimlerinden etkilenir.\n"
|
| 836 |
+
"- Backtest sonuçları geçmiş performansı yansıtır; gelecek performansı garanti etmez.\n"
|
| 837 |
+
"- Spread ve likidite düşükse gerçekleşme fiyatı tahminden sapabilir.\n"
|
| 838 |
)
|
| 839 |
|
| 840 |
with gr.TabItem("📦 100 Ürün Analizi"):
|
|
|
|
| 843 |
with gr.TabItem("🔮 24 Saatlik Fiyat Tahmini"):
|
| 844 |
prediction_table = gr.Dataframe(label="Tahminler (24 saatlik log getiri modeli)", interactive=False)
|
| 845 |
gr.Markdown(
|
| 846 |
+
"*Güvenilirlik, volatilite tabanlı bir göstergedir. "
|
| 847 |
"Net getiri, %2.5 setup + %8 vergi düşülerek hesaplanmıştır. "
|
| 848 |
+
"Spread ve likidite düşükse işlem maliyeti artabilir. "
|
| 849 |
+
"Bu tahminler garanti değildir.*"
|
| 850 |
)
|
| 851 |
|
| 852 |
with gr.TabItem("🚚 Şehirler Arası Arbitraj"):
|
|
|
|
| 860 |
performance_plot = gr.Plot(label="Feature Importance")
|
| 861 |
|
| 862 |
with gr.TabItem("🧪 Walk-forward Backtest"):
|
| 863 |
+
backtest_table = gr.Dataframe(label="Split Sonuçları", interactive=False)
|
| 864 |
backtest_plot = gr.Plot(label="MAE Karşılaştırması")
|
| 865 |
+
simulation_table = gr.Dataframe(label="İşlem Simülasyonu (maliyetler sonrası)", interactive=False)
|
| 866 |
gr.Markdown(
|
| 867 |
"*Walk-forward backtest kronolojik split kullanır; purge (24s) ve embargo (24s) uygulanır. "
|
| 868 |
+
"Baseline, 'fiyat değişmez' (getiri=0) varsayımıdır. "
|
| 869 |
+
"Simülasyon yalnızca modelin pozitif getiri öngördüğü işlemleri içerir. "
|
| 870 |
+
"Gerçek işlemlerde slippage ve rekabet nedeniyle sonuçlar farklı olabilir.*"
|
| 871 |
)
|
| 872 |
|
| 873 |
with gr.TabItem("🗄️ Veri Yönetimi"):
|
| 874 |
gr.Markdown("Veriler `gos_hawk_data/` dizininde CSV olarak saklanır.")
|
| 875 |
+
csv_files = gr.Dataframe(
|
| 876 |
+
label="Kayıtlı CSV Dosyaları (Yenile butonuna basınca güncellenir)",
|
| 877 |
+
interactive=False,
|
| 878 |
+
headers=["Dosya", "Boyut (KB)", "Son Değişiklik"],
|
| 879 |
+
)
|
| 880 |
|
| 881 |
refresh_btn.click(
|
| 882 |
fn=refresh_data_ui,
|
|
|
|
| 891 |
backtest_table,
|
| 892 |
backtest_plot,
|
| 893 |
simulation_table,
|
| 894 |
+
csv_files,
|
| 895 |
],
|
| 896 |
)
|
| 897 |
|
|
|
|
| 907 |
backtest_table,
|
| 908 |
backtest_plot,
|
| 909 |
simulation_table,
|
| 910 |
+
csv_files,
|
| 911 |
],
|
| 912 |
)
|
| 913 |
|