GoshawkVortexAI commited on
Commit
9c17ff5
·
verified ·
1 Parent(s): c57c59c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +100 -16
app.py CHANGED
@@ -2,6 +2,11 @@
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
 
 
 
 
 
5
 
6
  import os
7
  import time
@@ -26,8 +31,13 @@ warnings.filterwarnings("ignore")
26
  # SABİTLER
27
  # ---------------------------------------------------------------------------
28
 
 
29
  API_BASE = "https://europe.albion-online-data.com/api/v2"
30
 
 
 
 
 
31
  CITIES = [
32
  "Bridgewatch",
33
  "Fort Sterling",
@@ -101,17 +111,32 @@ MAX_RETRIES = 3
101
  RETRY_BACKOFF = 2
102
 
103
  # ---------------------------------------------------------------------------
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:
111
- resp = requests.get(url, params=params, timeout=REQUEST_TIMEOUT)
112
  if resp.status_code == 200:
113
  return resp.json()
114
  elif resp.status_code == 429:
 
 
 
 
 
 
 
115
  time.sleep(RETRY_BACKOFF * (attempt + 1))
116
  continue
117
  else:
@@ -119,6 +144,8 @@ def safe_request(url: str, params: Optional[dict] = None) -> Optional[list]:
119
  return None
120
  except requests.exceptions.Timeout:
121
  print(f"Zaman aşımı (deneme {attempt+1}/{MAX_RETRIES}): {url}")
 
 
122
  except requests.exceptions.RequestException as e:
123
  print(f"İstek hatası (deneme {attempt+1}/{MAX_RETRIES}): {e}")
124
  time.sleep(RETRY_BACKOFF * (attempt + 1))
@@ -126,6 +153,10 @@ def safe_request(url: str, params: Optional[dict] = None) -> Optional[list]:
126
 
127
 
128
  def fetch_current_prices(item_ids: List[str], cities: List[str], qualities: str = "1") -> pd.DataFrame:
 
 
 
 
129
  all_data = []
130
  chunk_size = 50
131
  for i in range(0, len(item_ids), chunk_size):
@@ -137,9 +168,11 @@ def fetch_current_prices(item_ids: List[str], cities: List[str], qualities: str
137
  data = safe_request(url, params)
138
  if data:
139
  all_data.extend(data)
140
- time.sleep(0.2)
 
141
  if not all_data:
142
  return pd.DataFrame()
 
143
  df = pd.DataFrame(all_data)
144
  for col in ["sell_price_min", "sell_price_max", "buy_price_min", "buy_price_max"]:
145
  if col in df.columns:
@@ -151,6 +184,11 @@ def fetch_current_prices(item_ids: List[str], cities: List[str], qualities: str
151
 
152
 
153
  def fetch_history(item_ids: List[str], cities: List[str], time_scale: int = 24, qualities: str = "1") -> pd.DataFrame:
 
 
 
 
 
154
  all_records = []
155
  chunk_size = 30
156
  for i in range(0, len(item_ids), chunk_size):
@@ -158,7 +196,11 @@ def fetch_history(item_ids: List[str], cities: List[str], time_scale: int = 24,
158
  items_str = ",".join(chunk)
159
  locations_str = ",".join(cities)
160
  url = f"{API_BASE}/stats/history/{items_str}.json"
161
- params = {"locations": locations_str, "qualities": qualities, "time-scale": time_scale}
 
 
 
 
162
  data = safe_request(url, params)
163
  if data:
164
  for entry in data:
@@ -174,9 +216,10 @@ def fetch_history(item_ids: List[str], cities: List[str], time_scale: int = 24,
174
  "avg_price": point.get("avg_price"),
175
  "item_count": point.get("item_count", 0),
176
  })
177
- time.sleep(0.3)
178
  if not all_records:
179
  return pd.DataFrame()
 
180
  df = pd.DataFrame(all_records)
181
  df["timestamp"] = pd.to_datetime(df["timestamp"], errors="coerce")
182
  df["avg_price"] = pd.to_numeric(df["avg_price"], errors="coerce").astype("float64")
@@ -184,6 +227,21 @@ def fetch_history(item_ids: List[str], cities: List[str], time_scale: int = 24,
184
  return df
185
 
186
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
187
  def clean_market_data(df: pd.DataFrame) -> pd.DataFrame:
188
  if df.empty:
189
  return df
@@ -192,6 +250,7 @@ def clean_market_data(df: pd.DataFrame) -> pd.DataFrame:
192
  if "item_count" in df.columns:
193
  df["item_count"] = pd.to_numeric(df["item_count"], errors="coerce").fillna(0).astype("float64")
194
  df = df[df["avg_price"] > 0].copy()
 
195
  if "item_id" in df.columns:
196
  for item in df["item_id"].unique():
197
  mask = df["item_id"] == item
@@ -201,6 +260,7 @@ def clean_market_data(df: pd.DataFrame) -> pd.DataFrame:
201
  upper = df.loc[mask, "avg_price"].quantile(0.995)
202
  if pd.notna(lower) and pd.notna(upper) and upper > lower:
203
  df.loc[mask, "avg_price"] = df.loc[mask, "avg_price"].clip(lower, upper)
 
204
  df = df.sort_values(["item_id", "city", "timestamp"]).reset_index(drop=True)
205
  return df
206
 
@@ -497,8 +557,9 @@ class DataPipeline:
497
  self.featured_df = pd.DataFrame()
498
  self.model = None
499
  self.last_update = None
500
- self.status_message = "Veri yüklenmedi."
501
  self._backtest_cache = None
 
502
 
503
  def fetch_data(self, item_ids: List[str] = None, cities: List[str] = None):
504
  if item_ids is None:
@@ -506,7 +567,12 @@ class DataPipeline:
506
  if cities is None:
507
  cities = CITIES
508
 
509
- self.status_message = "API'den veri çekiliyor..."
 
 
 
 
 
510
  try:
511
  hist = fetch_history(item_ids, cities)
512
  prices = fetch_current_prices(item_ids, cities)
@@ -515,12 +581,15 @@ class DataPipeline:
515
  return
516
 
517
  if hist.empty:
518
- self.status_message = "Tarihsel veri alınamadı. API bağlantısını kontrol edin."
519
  return
520
 
521
  self.history_df = clean_market_data(hist)
522
  self.prices_df = prices
523
- self.status_message = f"{len(self.history_df)} satır tarihsel veri, {len(self.prices_df)} anlık fiyat alındı."
 
 
 
524
 
525
  try:
526
  save_to_csv(self.history_df, "history")
@@ -546,7 +615,7 @@ class DataPipeline:
546
  self.status_message = "Model eğitimi başarısız."
547
  return False
548
 
549
- self.status_message = f"Model eğitildi ({len(X)} örnek)."
550
  self.last_update = dt.datetime.now()
551
  return True
552
 
@@ -592,10 +661,14 @@ pipeline = DataPipeline()
592
 
593
  def get_dashboard_metrics():
594
  if pipeline.featured_df.empty:
595
- return pd.DataFrame({"Metrik": ["Durum"], "Değer": ["Veri yok - Lütfen 'Verileri Yenile' butonuna tıklayın."]})
 
 
 
596
 
597
  df = pipeline.featured_df
598
  metrics = {
 
599
  "Toplam Gözlem": len(df),
600
  "Ürün Sayısı": df["item_id"].nunique(),
601
  "Şehir Sayısı": df["city"].nunique(),
@@ -642,7 +715,7 @@ def get_prediction_table():
642
  preds = pipeline.get_latest_predictions()
643
  if preds.empty:
644
  return pd.DataFrame({
645
- "Bilgi": ["Tahmin üretilemedi. Veri çekip model eğitin veya yeterli veri olduğundan emin olun."]
646
  })
647
 
648
  preds = preds.copy()
@@ -881,6 +954,8 @@ with gr.Blocks(title="GOSHAWK ALBION MARKET AI", theme=gr.themes.Soft(primary_hu
881
  **Profesyonel Market Intelligence Dashboard** | Avrupa Sunucusu
882
  *LightGBM tabanlı 24 saatlik fiyat tahmini ve arbitraj analizi.*
883
 
 
 
884
  **⚠️ 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.
885
  """
886
  )
@@ -896,9 +971,13 @@ with gr.Blocks(title="GOSHAWK ALBION MARKET AI", theme=gr.themes.Soft(primary_hu
896
  gr.Markdown(
897
  "### Kullanım\n"
898
  "1. 'Verileri Yenile ve Modeli Eğit' butonuna tıklayın.\n"
899
- "2. API'den gerçek piyasa verileri çekilir ve global LightGBM modeli eğitilir.\n"
900
- "3. Diğer sekmelerden analizleri inceleyebilirsiniz.\n\n"
901
- "*Veriler `gos_hawk_data/` dizininde CSV olarak saklanır.*\n\n"
 
 
 
 
902
  "### Riskler ve Belirsizlikler\n"
903
  "- Model tahminleri **olasılıksaldır**, kesin değildir.\n"
904
  "- Albion Online piyasası oyuncu davranışı, güncellemeler ve sezon değişimlerinden etkilenir.\n"
@@ -940,7 +1019,12 @@ with gr.Blocks(title="GOSHAWK ALBION MARKET AI", theme=gr.themes.Soft(primary_hu
940
  )
941
 
942
  with gr.TabItem("🗄️ Veri Yönetimi"):
943
- gr.Markdown("Veriler `gos_hawk_data/` dizininde CSV olarak saklanır.")
 
 
 
 
 
944
  csv_files = gr.Dataframe(
945
  label="Kayıtlı CSV Dosyaları (Yenile butonuna basınca güncellenir)",
946
  interactive=False,
 
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
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
+ # API key gerekmez, public endpoint'tir.
10
 
11
  import os
12
  import time
 
31
  # SABİTLER
32
  # ---------------------------------------------------------------------------
33
 
34
+ # Public AODP API - Avrupa sunucusu
35
  API_BASE = "https://europe.albion-online-data.com/api/v2"
36
 
37
+ # Diğer sunucu seçenekleri (isteğe bağlı):
38
+ # API_BASE = "https://west.albion-online-data.com/api/v2" # Americas
39
+ # API_BASE = "https://east.albion-online-data.com/api/v2" # Asia
40
+
41
  CITIES = [
42
  "Bridgewatch",
43
  "Fort Sterling",
 
111
  RETRY_BACKOFF = 2
112
 
113
  # ---------------------------------------------------------------------------
114
+ # PUBLIC API ÇAĞRILARI
115
  # ---------------------------------------------------------------------------
116
 
117
 
118
  def safe_request(url: str, params: Optional[dict] = None) -> Optional[list]:
119
+ """
120
+ Public AODP API'sine güvenli istek atar.
121
+ Retry, timeout ve rate-limit (429) koruması içerir.
122
+ """
123
+ headers = {
124
+ "User-Agent": "GOSHAWK-ALBION-MARKET-AI/1.0 (public-api-client)",
125
+ "Accept": "application/json",
126
+ }
127
  for attempt in range(MAX_RETRIES):
128
  try:
129
+ resp = requests.get(url, params=params, headers=headers, timeout=REQUEST_TIMEOUT)
130
  if resp.status_code == 200:
131
  return resp.json()
132
  elif resp.status_code == 429:
133
+ # Rate limit
134
+ wait = RETRY_BACKOFF * (attempt + 1) * 2
135
+ print(f"Rate limit (429). {wait}s bekleniyor...")
136
+ time.sleep(wait)
137
+ continue
138
+ elif resp.status_code >= 500:
139
+ print(f"Sunucu hatası {resp.status_code}. Yeniden deneniyor...")
140
  time.sleep(RETRY_BACKOFF * (attempt + 1))
141
  continue
142
  else:
 
144
  return None
145
  except requests.exceptions.Timeout:
146
  print(f"Zaman aşımı (deneme {attempt+1}/{MAX_RETRIES}): {url}")
147
+ except requests.exceptions.ConnectionError as e:
148
+ print(f"Bağlantı hatası (deneme {attempt+1}/{MAX_RETRIES}): {e}")
149
  except requests.exceptions.RequestException as e:
150
  print(f"İstek hatası (deneme {attempt+1}/{MAX_RETRIES}): {e}")
151
  time.sleep(RETRY_BACKOFF * (attempt + 1))
 
153
 
154
 
155
  def fetch_current_prices(item_ids: List[str], cities: List[str], qualities: str = "1") -> pd.DataFrame:
156
+ """
157
+ AODP /stats/prices/ endpoint'inden anlık alış/satış fiyatlarını çeker.
158
+ Public endpoint, API key gerektirmez.
159
+ """
160
  all_data = []
161
  chunk_size = 50
162
  for i in range(0, len(item_ids), chunk_size):
 
168
  data = safe_request(url, params)
169
  if data:
170
  all_data.extend(data)
171
+ # Public API'ye nazik ol
172
+ time.sleep(0.3)
173
  if not all_data:
174
  return pd.DataFrame()
175
+
176
  df = pd.DataFrame(all_data)
177
  for col in ["sell_price_min", "sell_price_max", "buy_price_min", "buy_price_max"]:
178
  if col in df.columns:
 
184
 
185
 
186
  def fetch_history(item_ids: List[str], cities: List[str], time_scale: int = 24, qualities: str = "1") -> pd.DataFrame:
187
+ """
188
+ AODP /stats/history/ endpoint'inden tarihsel ortalama fiyatları çeker.
189
+ time_scale: 1, 6, 24 saatlik bucket.
190
+ Public endpoint, API key gerektirmez.
191
+ """
192
  all_records = []
193
  chunk_size = 30
194
  for i in range(0, len(item_ids), chunk_size):
 
196
  items_str = ",".join(chunk)
197
  locations_str = ",".join(cities)
198
  url = f"{API_BASE}/stats/history/{items_str}.json"
199
+ params = {
200
+ "locations": locations_str,
201
+ "qualities": qualities,
202
+ "time-scale": time_scale,
203
+ }
204
  data = safe_request(url, params)
205
  if data:
206
  for entry in data:
 
216
  "avg_price": point.get("avg_price"),
217
  "item_count": point.get("item_count", 0),
218
  })
219
+ time.sleep(0.4)
220
  if not all_records:
221
  return pd.DataFrame()
222
+
223
  df = pd.DataFrame(all_records)
224
  df["timestamp"] = pd.to_datetime(df["timestamp"], errors="coerce")
225
  df["avg_price"] = pd.to_numeric(df["avg_price"], errors="coerce").astype("float64")
 
227
  return df
228
 
229
 
230
+ def check_api_health() -> Tuple[bool, str]:
231
+ """
232
+ Public API'nin erişilebilirliğini kontrol eder.
233
+ """
234
+ try:
235
+ url = f"{API_BASE}/stats/prices/T4_BAG.json"
236
+ params = {"locations": "Bridgewatch", "qualities": "1"}
237
+ data = safe_request(url, params)
238
+ if data is not None:
239
+ return True, "Public API erişilebilir."
240
+ return False, "Public API yanıt vermedi."
241
+ except Exception as e:
242
+ return False, f"API kontrol hatası: {e}"
243
+
244
+
245
  def clean_market_data(df: pd.DataFrame) -> pd.DataFrame:
246
  if df.empty:
247
  return df
 
250
  if "item_count" in df.columns:
251
  df["item_count"] = pd.to_numeric(df["item_count"], errors="coerce").fillna(0).astype("float64")
252
  df = df[df["avg_price"] > 0].copy()
253
+
254
  if "item_id" in df.columns:
255
  for item in df["item_id"].unique():
256
  mask = df["item_id"] == item
 
260
  upper = df.loc[mask, "avg_price"].quantile(0.995)
261
  if pd.notna(lower) and pd.notna(upper) and upper > lower:
262
  df.loc[mask, "avg_price"] = df.loc[mask, "avg_price"].clip(lower, upper)
263
+
264
  df = df.sort_values(["item_id", "city", "timestamp"]).reset_index(drop=True)
265
  return df
266
 
 
557
  self.featured_df = pd.DataFrame()
558
  self.model = None
559
  self.last_update = None
560
+ self.status_message = "Veri yüklenmedi. 'Verileri Yenile' butonuna basın."
561
  self._backtest_cache = None
562
+ self.data_source = "PUBLIC_API_EUROPE"
563
 
564
  def fetch_data(self, item_ids: List[str] = None, cities: List[str] = None):
565
  if item_ids is None:
 
567
  if cities is None:
568
  cities = CITIES
569
 
570
+ self.status_message = f"Public API ({API_BASE}) üzerinden veri çekiliyor..."
571
+ ok, msg = check_api_health()
572
+ if not ok:
573
+ self.status_message = f"API erişilemiyor: {msg}"
574
+ return
575
+
576
  try:
577
  hist = fetch_history(item_ids, cities)
578
  prices = fetch_current_prices(item_ids, cities)
 
581
  return
582
 
583
  if hist.empty:
584
+ self.status_message = "Tarihsel veri alınamadı. Public API geçici olarak yanıt vermiyor olabilir."
585
  return
586
 
587
  self.history_df = clean_market_data(hist)
588
  self.prices_df = prices
589
+ self.status_message = (
590
+ f"✓ Public API'den alındı: {len(self.history_df)} satır tarihsel, "
591
+ f"{len(self.prices_df)} anlık fiyat."
592
+ )
593
 
594
  try:
595
  save_to_csv(self.history_df, "history")
 
615
  self.status_message = "Model eğitimi başarısız."
616
  return False
617
 
618
+ self.status_message = f"Model eğitildi ({len(X)} örnek). Kaynak: Public API (EU)."
619
  self.last_update = dt.datetime.now()
620
  return True
621
 
 
661
 
662
  def get_dashboard_metrics():
663
  if pipeline.featured_df.empty:
664
+ return pd.DataFrame({
665
+ "Metrik": ["Durum", "Veri Kaynağı"],
666
+ "Değer": ["Veri yok - 'Verileri Yenile' butonuna tıklayın.", "Public AODP API (europe)"]
667
+ })
668
 
669
  df = pipeline.featured_df
670
  metrics = {
671
+ "Veri Kaynağı": "Public AODP API (europe.albion-online-data.com)",
672
  "Toplam Gözlem": len(df),
673
  "Ürün Sayısı": df["item_id"].nunique(),
674
  "Şehir Sayısı": df["city"].nunique(),
 
715
  preds = pipeline.get_latest_predictions()
716
  if preds.empty:
717
  return pd.DataFrame({
718
+ "Bilgi": ["Tahmin üretilemedi. Veri çekip model eğitin."]
719
  })
720
 
721
  preds = preds.copy()
 
954
  **Profesyonel Market Intelligence Dashboard** | Avrupa Sunucusu
955
  *LightGBM tabanlı 24 saatlik fiyat tahmini ve arbitraj analizi.*
956
 
957
+ **Veri kaynağı:** Public Albion Online Data Project API (`europe.albion-online-data.com`)
958
+
959
  **⚠️ 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.
960
  """
961
  )
 
971
  gr.Markdown(
972
  "### Kullanım\n"
973
  "1. 'Verileri Yenile ve Modeli Eğit' butonuna tıklayın.\n"
974
+ "2. Public AODP API'sinden gerçek piyasa verileri çekilir.\n"
975
+ "3. Global LightGBM modeli eğitilir.\n"
976
+ "4. Diğer sekmelerden analizleri inceleyebilirsiniz.\n\n"
977
+ "### API Notu\n"
978
+ "- Public endpoint'tir, API key gerekmez.\n"
979
+ "- Rate-limit: dakikada ~60 istek.\n"
980
+ "- Çok sık yenilemek geçici olarak bloklanmanıza neden olabilir.\n\n"
981
  "### Riskler ve Belirsizlikler\n"
982
  "- Model tahminleri **olasılıksaldır**, kesin değildir.\n"
983
  "- Albion Online piyasası oyuncu davranışı, güncellemeler ve sezon değişimlerinden etkilenir.\n"
 
1019
  )
1020
 
1021
  with gr.TabItem("🗄️ Veri Yönetimi"):
1022
+ gr.Markdown(
1023
+ "Veriler `gos_hawk_data/` dizininde CSV olarak saklanır.\n\n"
1024
+ "**API Kaynağı:** Public AODP API — `europe.albion-online-data.com/api/v2`\n"
1025
+ "**Endpoint'ler:** `/stats/prices/` (anlık), `/stats/history/` (tarihsel)\n"
1026
+ "**Rate-limit:** dakikada ~60 istek (public)"
1027
+ )
1028
  csv_files = gr.Dataframe(
1029
  label="Kayıtlı CSV Dosyaları (Yenile butonuna basınca güncellenir)",
1030
  interactive=False,