kaan1233/bistelligence-api
0
1from fastapi import FastAPI
2from fastapi.staticfiles import StaticFiles
3from fastapi.responses import JSONResponse, StreamingResponse
4from fastapi.middleware.cors import CORSMiddleware
5from fastapi.encoders import jsonable_encoder
6from pydantic import BaseModel
7import pandas as pd
8import json
9import asyncio
10from datetime import datetime, timedelta
11import os
12import firebase_admin
13from firebase_admin import credentials, firestore, auth
14from fastapi import Request, HTTPException, Security, Depends
15from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
16
17# --- FIREBASE SETUP ---
18firebase_db = None
19firebase_creds_env = os.getenv("FIREBASE_CREDENTIALS")
20
21try:
22 if firebase_creds_env:
23 # Render'da ├çevre De─şi┼şkeni olarak verilen JSON string'i y├╝kle
24 cred_dict = json.loads(firebase_creds_env)
25 cred = credentials.Certificate(cred_dict)
26 firebase_admin.initialize_app(cred)
27 firebase_db = firestore.client()
28 print("[S─░STEM] Firebase Admin SDK (Env Var) ba┼şar─▒yla ba┼şlat─▒ld─▒.")
29 elif os.path.exists("firebase-adminsdk.json"):
30 # Lokal geli┼ştirme ortam─▒ i├ğin dosya y├╝kle
31 cred = credentials.Certificate("firebase-adminsdk.json")
32 firebase_admin.initialize_app(cred)
33 firebase_db = firestore.client()
34 print("[S─░STEM] Firebase Admin SDK (Dosya) ba┼şar─▒yla ba┼şlat─▒ld─▒.")
35 else:
36 print("[UYARI] Firebase Credentials bulunamad─▒. Bulut veritaban─▒ aktif de─şil.")
37except Exception as e:
38 print(f"[HATA] Firebase ba┼şlat─▒lamad─▒: {e}")
39
40security = HTTPBearer(auto_error=False)
41
42def get_current_user(credentials: HTTPAuthorizationCredentials = Security(security)):
43 if not credentials:
44 # E─şer hen├╝z frontend'den token g├Ânderilmiyorsa ge├ğici lokal kullan─▒c─▒ d├Ând├╝r
45 return {"uid": "local_user"}
46
47 if firebase_db is None:
48 # Backend'de Firebase aktif de─şilse token do─şrulama yap─▒lamaz, direkt izin ver
49 return {"uid": "local_user"}
50
51 token = credentials.credentials
52 try:
53 decoded_token = auth.verify_id_token(token)
54 return decoded_token
55 except Exception as e:
56 raise HTTPException(status_code=401, detail="Ge├ğersiz yetkilendirme")
57
58from data_fetcher import fetch_stock_data, fetch_market_index, BIST100_TICKERS, ALL_BIST_TICKERS
59TARGET_TICKERS = ALL_BIST_TICKERS[:250]
60
61from analyzer import calculate_technical_score, check_market_regime, get_signal_label, analyze_news_sentiment
62import yfinance as yf
63from ml_model import train_model, predict_confidence, predict_multi_horizon
64from portfolio_manager import load_portfolio, add_position, remove_position
65from watchlist_manager import load_watchlist, add_to_watchlist, remove_from_watchlist
66from gemini_advisor import fetch_gemini_response, fetch_elite_report
67from markowitz import calculate_correlation_matrix, analyze_portfolio_risk, get_balancing_suggestions
68from backtester import run_backtest
69from sector_analyzer import analyze_sectors, get_sector_rotation_summary, get_ticker_sector
70import model_portfolio_bot
71import algo_config_manager
72
73app = FastAPI(title="BISTelligence API")
74
75# Geli┼ştirme a┼şamas─▒nda CORS sorunlar─▒n─▒ ├Ânlemek i├ğin
76app.add_middleware(
77 CORSMiddleware,
78 allow_origins=[
79 "http://localhost:3000",
80 "http://localhost:8000",
81 "http://127.0.0.1:5500",
82 "https://bistelligence-ui.onrender.com",
83 "*" # Sadece test i├ğin, allow_credentials=False yap─▒yoruz ki wildcard ├ğal─▒┼şs─▒n
84 ],
85 allow_credentials=False,
86 allow_methods=["*"],
87 allow_headers=["*"],
88)
89
90# Global Cache
91market_regime = {}
92stock_data = {}
93recommendations = []
94elite_ai_report = "Yapay zeka analizi hen├╝z tamamlanmad─▒."
95last_ai_report_date = None
96currency_data = None
97ai_refresh_limits = {}
98def run_analysis():
99 global market_regime, stock_data, recommendations, elite_ai_report, currency_data, last_ai_report_date
100 import time
101 from datetime import datetime
102
103 print("=" * 60)
104 print(f"B─░ST Finansal API v2 - Saatlik G├╝ncelleme ({datetime.now().strftime('%H:%M:%S')})")
105 print("=" * 60)
106
107 print("\n[1/3] Endeks verisi ├ğekiliyor...")
108 idx = fetch_market_index()
109 market_regime = check_market_regime(idx)
110 print(f" Piyasa Rejimi: {market_regime.get('regime', 'Bilinmiyor')}")
111
112 print(f"\n[2/3] {len(TARGET_TICKERS)} hisse verisi ├ğekiliyor...")
113 stock_data = fetch_stock_data(TARGET_TICKERS)
114 print(f" {len(stock_data)} hisse ba┼şar─▒yla indirildi.")
115
116 try:
117 print(" Alternatif yat─▒r─▒m verileri (D├Âviz/Alt─▒n) ├ğekiliyor...")
118 currency_data = yf.download(["TRY=X", "EURTRY=X", "GC=F"], period="5y")["Close"].ffill().bfill()
119 if currency_data.index.tzinfo is not None:
120 currency_data.index = currency_data.index.tz_localize(None)
121 except Exception as e:
122 print(f"[UYARI] Currency data fetch failed: {e}")
123
124 print(f"\n[3/3] ┼Şelale Filtreleme (TA -> ML -> AI) ba┼şl─▒yor...")
125 res = []
126 analyzed = 0
127 failed = 0
128 elite_stocks_str = ""
129
130 for ticker in TARGET_TICKERS:
131 if ticker not in stock_data:
132 continue
133 try:
134 df = stock_data[ticker]
135 # A┼ŞAMA 1: Kaba Filtre (Teknik Analiz)
136 score, details = calculate_technical_score(df)
137 reasons = [r for r in details.get("reasons", [])]
138
139 ml_conf = 0
140 gb_conf = 0
141 rf_conf = 0
142 model_acc = 0
143
144 # Sadece skoru 50 ve ├╝zeri olanlara ML uygula (Yar─▒-Elit)
145 if score >= 50:
146 # A┼ŞAMA 2: ─░nce Filtre (Makine ├û─şrenmesi)
147 model = train_model(df, ticker)
148 ml_result = predict_confidence(model, df)
149
150 ml_conf = ml_result["confidence"]
151 gb_conf = ml_result["gb_confidence"]
152 rf_conf = ml_result["rf_confidence"]
153 model_acc = ml_result["model_accuracy"]
154
155 if model_acc < 0.55:
156 score = 49
157 reasons.append("Yapay Zeka Baraj─▒ a┼ş─▒lamad─▒ (Model Do─şrulu─şu D├╝┼ş├╝k)")
158 elif ml_conf < 50:
159 score = 49
160 reasons.append("Yapay Zeka Baraj─▒ a┼ş─▒lamad─▒ (D├╝┼ş├╝k G├╝ven Skoru)")
161 else:
162 reasons.append("TA Filtresine Tak─▒ld─▒ (Zay─▒f Trend)")
163
164 signal_info = get_signal_label(score)
165
166 res.append({
167 "ticker": ticker,
168 "price": round(details.get("current_price", 0), 2),
169 "score": score,
170 "signal": signal_info,
171 "ml_conf": ml_conf,
172 "ml_detail": {
173 "gb_confidence": gb_conf,
174 "rf_confidence": rf_conf,
175 "model_accuracy": model_acc,
176 },
177 "stop_loss": round(details.get("stop_loss", 0), 2),
178 "target_price": round(details.get("target_price", 0), 2),
179 "risk_reward": details.get("risk_reward", 0),
180 "categories": details.get("categories", {}),
181 "reasons": reasons,
182 "rsi": round(details.get("rsi", 0), 1) if details.get("rsi") is not None else None,
183 "sector": get_ticker_sector(ticker),
184 })
185 analyzed += 1
186 if analyzed % 10 == 0:
187 print(f" {analyzed} hisse analiz edildi...")
188 except Exception as e:
189 failed += 1
190
191 recommendations = sorted(res, key=lambda x: (x['score'], x['ml_conf']), reverse=True)
192
193 # A┼ŞAMA 3: Nihai Karar (Gemini LLM)
194 print(f"\n[4/4] Elit Hisseler Yapay Zekaya G├Ânderiliyor...")
195 top_elites = [r for r in recommendations if r['score'] >= 50 and r['ml_conf'] >= 50][:5]
196
197 today_str = datetime.now().strftime("%Y-%m-%d")
198 if last_ai_report_date != today_str:
199 if top_elites:
200 elite_text = ", ".join([f"{r['ticker']} (Puan: {r['score']}, ML G├╝ven: %{r['ml_conf']})" for r in top_elites])
201 elite_ai_report = fetch_elite_report(elite_text, market_regime)
202 print(" Gemini raporu ba┼şar─▒yla al─▒nd─▒.")
203 else:
204 elite_ai_report = "Bug├╝n piyasada hem Teknik Analiz hem de Yapay Zeka baraj─▒n─▒ ge├ğebilen 'Elit' bir hisse bulunamad─▒. Nakitte beklemek en g├╝venli se├ğenek olabilir."
205 print(" Uygun elit hisse bulunamad─▒.")
206 last_ai_report_date = today_str
207 else:
208 print(" Gemini AI Raporu bug├╝n zaten ├ğekildi, eski rapor kullan─▒l─▒yor.")
209
210 print("\n[5/5] AI Model Portf├Ây├╝ ─░┼şlemleri Manuel Olarak Tetiklenmeyi Bekliyor...")
211
212 print(f"\n{'=' * 60}")
213 print(f"Sistem Haz─▒r! {analyzed} hisse analiz edildi. ({failed} hata)")
214 print(f"http://127.0.0.1:8000 adresinden eri┼şebilirsiniz.")
215
216 print(f"{'=' * 60}")
217
218 # ├ûnbelle─şe Kaydet
219 try:
220 def convert_numpy(obj):
221 import numpy as np
222 if isinstance(obj, np.bool_): return bool(obj)
223 if isinstance(obj, np.integer): return int(obj)
224 if isinstance(obj, np.floating): return float(obj)
225 if isinstance(obj, np.ndarray): return obj.tolist()
226 if isinstance(obj, dict): return {k: convert_numpy(v) for k, v in obj.items()}
227 if isinstance(obj, list): return [convert_numpy(v) for v in obj]
228 return obj
229
230 cache_data = {
231 "timestamp": time.time(),
232 "market_regime": convert_numpy(market_regime),
233 "recommendations": convert_numpy(recommendations),
234 "elite_ai_report": elite_ai_report
235 }
236 if firebase_db:
237 firebase_db.collection("system_data").document("ai_cache").set(cache_data)
238 print(" Sonu├ğlar ba┼şar─▒yla Firebase ai_cache belgesine kaydedildi.")
239 except Exception as e:
240 print(f" Firebase Cache kaydedilemedi: {e}")
241
242@app.get("/api/dashboard")
243def get_dashboard():
244 stats = {
245 "analyzed_count": len(stock_data) if stock_data else 0,
246 "strong_signals": len([r for r in recommendations if r['score'] >= 60]),
247 "total_bullish": len([r for r in recommendations if r['score'] >= 50]),
248 "elite_ai_report": elite_ai_report,
249 }
250 return {"status": "success", "regime": market_regime, "stats": stats, "recommendations": recommendations[:20]}
251
252@app.get("/api/macro-data")
253def get_macro_data():
254 try:
255 if currency_data is None or currency_data.empty:
256 return {"status": "error"}
257
258 idx_val = "N/A"
259 idx = fetch_market_index()
260 if idx is not None and not idx.empty:
261 idx_val = f"{idx['Close'].iloc[-1]:.2f}"
262
263 usd = currency_data["TRY=X"].iloc[-1] if "TRY=X" in currency_data else 0
264 eur = currency_data["EURTRY=X"].iloc[-1] if "EURTRY=X" in currency_data else 0
265 gold = currency_data["GC=F"].iloc[-1] if "GC=F" in currency_data else 0
266
267 return {
268 "status": "success",
269 "data": {
270 "xu100": idx_val,
271 "usd": f"{usd:.2f}" if usd else "N/A",
272 "eur": f"{eur:.2f}" if eur else "N/A",
273 "gold": f"${gold:.2f}" if gold else "N/A"
274 }
275 }
276 except Exception as e:
277 return {"status": "error", "message": str(e)}
278
279@app.on_event("startup")
280async def startup_event():
281 # ─░lk analiz i┼şlemi ve zamanlanm─▒┼ş g├╝ncelleme
282 run_analysis()
283 schedule_hourly_update()
284
285import threading
286
287def schedule_hourly_update():
288 def update_job():
289 while True:
290 import time
291 time.sleep(3600) # Her 1 saatte bir
292 try:
293 run_analysis()
294 except Exception as e:
295 print(f"[UYARI] Saatlik g├╝ncelleme ba┼şar─▒s─▒z: {e}")
296
297 t = threading.Thread(target=update_job, daemon=True)
298 t.start()
299
300
301
302# =======================================================
303# ELITE AI REPORT
304
305# =======================================================
306@app.get("/api/elite-report")
307def get_elite_report(refresh: bool = False, user: dict = Depends(get_current_user)):
308 global elite_ai_report
309 if refresh:
310 uid = user.get("uid", "local_user")
311 today_str = datetime.now().strftime("%Y-%m-%d")
312
313 # Kullan─▒c─▒n─▒n limit bilgilerini al veya olu┼ştur
314 user_limit = ai_refresh_limits.get(uid, {"date": today_str, "count": 0})
315
316 if user_limit["date"] != today_str:
317 user_limit = {"date": today_str, "count": 0}
318
319 if user_limit["count"] >= 3:
320 return {"status": "error", "message": "G├╝nl├╝k yenileme limitinize (3/3) ula┼şt─▒n─▒z. L├╝tfen yar─▒n tekrar deneyin.", "report": elite_ai_report}
321
322 # Hak varsa count art─▒r ve AI'yi ├ğal─▒┼şt─▒r
323 user_limit["count"] += 1
324 ai_refresh_limits[uid] = user_limit
325
326 # Elite report ├╝ret
327 elite_text = ""
328 for r in recommendations[:10]:
329 if r['score'] >= 60:
330 elite_text += f"{r['ticker']}: Skor {r['score']} - {r['signal']}\n"
331
332 if elite_text:
333 elite_ai_report = fetch_elite_report(elite_text, market_regime)
334 else:
335 elite_ai_report = "Bug├╝n piyasada hem Teknik Analiz hem de Yapay Zeka baraj─▒n─▒ ge├ğebilen 'Elit' bir hisse bulunamad─▒. Nakitte beklemek en g├╝venli se├ğenek olabilir."
336
337 return {"status": "success", "report": elite_ai_report}
338
339@app.get("/api/market-movers")
340def get_market_movers():
341 daily = []
342 weekly = []
343 volume = []
344
345 for ticker, df in stock_data.items():
346 if len(df) < 6:
347 continue
348 try:
349 curr_price = float(df['Close'].iloc[-1])
350 prev_price = float(df['Close'].iloc[-2])
351 week_price = float(df['Close'].iloc[-6])
352 vol = float(df['Volume'].iloc[-1])
353
354 daily_pct = ((curr_price - prev_price) / prev_price) * 100 if prev_price > 0 else 0
355 weekly_pct = ((curr_price - week_price) / week_price) * 100 if week_price > 0 else 0
356
357 # Hacmi milyon TL cinsinden hesapla
358 vol_tl = (vol * curr_price) / 1_000_000
359
360 daily.append({"ticker": ticker, "change": daily_pct, "price": curr_price})
361 weekly.append({"ticker": ticker, "change": weekly_pct, "price": curr_price})
362 volume.append({"ticker": ticker, "volume": vol_tl, "price": curr_price})
363 except Exception:
364 continue
365
366 # S─▒ralamalar
367 daily_sorted = sorted(daily, key=lambda x: x["change"], reverse=True)
368 weekly_sorted = sorted(weekly, key=lambda x: x["change"], reverse=True)
369 volume_sorted = sorted(volume, key=lambda x: x["volume"], reverse=True)
370
371 return {
372 "status": "success",
373 "daily_gainers": [x for x in daily_sorted if x["change"] >= 5][:10],
374 "daily_losers": [x for x in reversed(daily_sorted) if x["change"] <= -5][:10],
375 "weekly_gainers": weekly_sorted[:10],
376 "weekly_losers": list(reversed(weekly_sorted))[:10],
377 "volume_leaders": volume_sorted[:10]
378 }
379
380
381# =======================================================
382# MARKET
383# =======================================================
384@app.get("/api/market")
385def get_market():
386 return {
387 "regime": market_regime.get("regime", "Bilinmiyor"),
388 "description": market_regime.get("description", ""),
389 "level": market_regime.get("level", "unknown"),
390 "volatility": market_regime.get("volatility", None),
391 "trend_strength": market_regime.get("trend_strength", None),
392 "total_tickers": len(TARGET_TICKERS),
393 }
394
395
396# =======================================================
397# RECOMMENDATIONS
398# =======================================================
399@app.get("/api/recommendations")
400def get_recs():
401 return recommendations
402
403
404# =======================================================
405# STOCK DETAIL (Tekil hisse detay─▒)
406# =======================================================
407@app.get("/api/stock/{ticker}")
408def get_stock_detail(ticker: str):
409 """Tek bir hisse i├ğin detayl─▒ analiz verileri d├Ând├╝r├╝r."""
410 if ticker not in stock_data:
411 new_data = fetch_stock_data([ticker])
412 if ticker in new_data:
413 stock_data[ticker] = new_data[ticker]
414 else:
415 return {"status": "error", "message": f"{ticker} i├ğin veri bulunamad─▒."}
416
417 df = stock_data[ticker]
418 score, details = calculate_technical_score(df)
419 model = train_model(df, ticker)
420 ml_result = predict_confidence(model, df)
421 multi_horizon = predict_multi_horizon(model, df)
422
423 # --- YAPAY ZEKA BARAJI ---
424 reasons = details.get("reasons", [])
425 if ml_result["model_accuracy"] < 0.55:
426 if score >= 50:
427 score = 49
428 reasons.append("Yapay Zeka Baraj─▒ a┼ş─▒lamad─▒ (Model Do─şrulu─şu D├╝┼ş├╝k)")
429 elif ml_result["confidence"] < 50:
430 if score >= 50:
431 score = 49
432 reasons.append("Yapay Zeka Baraj─▒ a┼ş─▒lamad─▒ (D├╝┼ş├╝k G├╝ven Skoru)")
433
434 signal_info = get_signal_label(score)
435
436 # Feature importance
437 feature_imp = ml_result.get("feature_importance", [])
438 feature_imp_serialized = [{"name": f[0], "importance": round(f[1] * 100, 1)} for f in feature_imp]
439
440 # Son 100 g├╝nl├╝k fiyat ge├ğmi┼şi (grafik i├ğin daha fazla veri)
441 price_history = []
442 last_100 = df.tail(100)
443 for idx, row in last_100.iterrows():
444 date_str = str(idx.date()) if hasattr(idx, 'date') else str(idx)
445 price_history.append({
446 "date": date_str,
447 "open": round(float(row['Open']), 2) if 'Open' in row else round(float(row['Close']), 2),
448 "high": round(float(row['High']), 2) if 'High' in row else round(float(row['Close']), 2),
449 "low": round(float(row['Low']), 2) if 'Low' in row else round(float(row['Close']), 2),
450 "close": round(float(row['Close']), 2),
451 "volume": int(row['Volume']) if 'Volume' in row else 0,
452 })
453
454 return {
455 "status": "success",
456 "ticker": ticker,
457 "sector": get_ticker_sector(ticker),
458 "price": round(details.get("current_price", 0), 2),
459 "score": score,
460 "signal": signal_info,
461 "categories": details.get("categories", {}),
462 "reasons": reasons,
463 "stop_loss": round(details.get("stop_loss", 0), 2),
464 "target_price": round(details.get("target_price", 0), 2),
465 "risk_reward": details.get("risk_reward", 0),
466 "rsi": round(details.get("rsi", 0), 1) if details.get("rsi") is not None else None,
467 "fibonacci": details.get("fibonacci", {}),
468 "ml": {
469 "confidence": ml_result["confidence"],
470 "gb_confidence": ml_result["gb_confidence"],
471 "rf_confidence": ml_result["rf_confidence"],
472 "model_accuracy": ml_result["model_accuracy"],
473 "feature_importance": feature_imp_serialized,
474 },
475 "multi_horizon": multi_horizon,
476 "price_history": price_history,
477 }
478
479# =======================================================
480# NEWS & SENTIMENT
481# =======================================================
482@app.get("/api/news/{ticker}")
483def get_news(ticker: str):
484 try:
485 t = yf.Ticker(ticker)
486 raw_news = t.news
487 if not raw_news:
488 return {"status": "success", "news": []}
489
490 formatted_news = []
491 for n in raw_news:
492 title = n.get("title", "")
493 sentiment = analyze_news_sentiment(title)
494
495 # yfinance providerPublishTime is timestamp
496 pub_time = n.get("providerPublishTime", 0)
497 if pub_time > 0:
498 dt = datetime.fromtimestamp(pub_time).strftime("%Y-%m-%d %H:%M")
499 else:
500 dt = "Bilinmiyor"
501
502 formatted_news.append({
503 "title": title,
504 "link": n.get("link", "#"),
505 "publisher": n.get("publisher", "Bilinmiyor"),
506 "time": dt,
507 "sentiment": sentiment
508 })
509
510 return {"status": "success", "news": formatted_news}
511 except Exception as e:
512 return {"status": "error", "message": str(e)}
513
514# =======================================================
515# BACKTEST
516# =======================================================
517@app.post("/api/run-backtest")
518def api_run_backtest(req: dict):
519 try:
520 tickers = req.get("tickers", ["THYAO.IS"])
521 lookback = int(req.get("lookback_days", 400))
522 threshold = int(req.get("buy_threshold", 50))
523
524 # Ensure we have data for these tickers
525 missing = [t for t in tickers if t not in stock_data]
526 if missing:
527 new_data = fetch_stock_data(missing)
528 for mt in new_data:
529 stock_data[mt] = new_data[mt]
530
531 gen = run_backtest(stock_data, tickers, lookback, threshold)
532
533 final_result = None
534 try:
535 for res in gen:
536 if res["type"] == "result":
537 final_result = res["data"]
538 except Exception as e:
539 import traceback
540 traceback.print_exc()
541 return {"status": "error", "message": f"Backtest motorunda i├ğ hata: {str(e)}"}
542
543 if final_result:
544 return {"status": "success", "data": final_result}
545 else:
546 return {"status": "error", "message": "Backtest tamamlanamad─▒ (sonu├ğ d├Ând├╝r├╝lmedi)."}
547 except Exception as e:
548 import traceback
549 traceback.print_exc()
550 return {"status": "error", "message": f"Backtest ba┼şlat─▒lamad─▒: {str(e)}"}
551
552# =======================================================
553# PORTFOLIO
554# =======================================================
555@app.get("/api/portfolio")
556def get_portfolio(user: dict = Depends(get_current_user)):
557 pf = load_portfolio(user.get("uid", "local_user"), firebase_db)
558
559 # Eksik hisseleri ├ğek
560 missing = [item['ticker'] for item in pf if item['ticker'] not in stock_data]
561 if missing:
562 new_data = fetch_stock_data(missing)
563 for mt in new_data:
564 stock_data[mt] = new_data[mt]
565
566 enriched_pf = []
567 total_cost = 0
568 total_val = 0
569 for item in pf:
570 t = item['ticker']
571 curr_price = round(stock_data[t]['Close'].iloc[-1], 2) if t in stock_data else item['cost']
572 val = item['amount'] * curr_price
573 cost = item['amount'] * item['cost']
574 pnl_tl = val - cost
575 pnl_pct = round((pnl_tl / cost) * 100, 2) if cost > 0 else 0
576
577 total_cost += cost
578 total_val += val
579
580 # Hisse skoru ve sinyali
581 ticker_score = 0
582 ticker_signal = get_signal_label(0)
583 for r in recommendations:
584 if r['ticker'] == t:
585 ticker_score = r['score']
586 ticker_signal = r['signal']
587 break
588
589 enriched_pf.append({
590 "ticker": t,
591 "sector": get_ticker_sector(t),
592 "amount": item['amount'],
593 "cost": item['cost'],
594 "current_price": curr_price,
595 "total_value": round(val, 2),
596 "pnl_tl": round(pnl_tl, 2),
597 "pnl_pct": pnl_pct,
598 "score": ticker_score,
599 "signal": ticker_signal,
600 })
601
602 global_pnl = total_val - total_cost
603 global_pnl_pct = round((global_pnl / total_cost) * 100, 2) if total_cost > 0 else 0
604
605 return {
606 "items": enriched_pf,
607 "summary": {
608 "total_value": round(total_val, 2),
609 "total_cost": round(total_cost, 2),
610 "global_pnl": round(global_pnl, 2),
611 "global_pnl_pct": global_pnl_pct
612 }
613 }
614
615class PortfolioItem(BaseModel):
616 ticker: str
617 amount: int
618 cost: float
619 purchase_date: str = None
620
621@app.post("/api/portfolio")
622def add_to_pf(item: PortfolioItem, user: dict = Depends(get_current_user)):
623 add_position(item.ticker, item.amount, item.cost, item.purchase_date, user.get("uid", "local_user"), firebase_db)
624 return {"status": "success"}
625
626@app.delete("/api/portfolio/{ticker}")
627def rm_pf(ticker: str, user: dict = Depends(get_current_user)):
628 remove_position(ticker, user.get("uid", "local_user"), firebase_db)
629 return {"status": "success"}
630
631@app.get("/api/portfolio/opportunity")
632def get_opportunity_cost(user: dict = Depends(get_current_user)):
633 pf = load_portfolio(user.get("uid", "local_user"), firebase_db)
634 items_with_date = [item for item in pf if item.get("purchase_date")]
635 if not items_with_date:
636 return {"status": "error", "message": "Portf├Ây├╝n├╝zde al─▒┼ş tarihi girilmi┼ş hisse bulunamad─▒. L├╝tfen 'D├╝zenle' butonuna basarak al─▒┼ş tarihi ekleyin."}
637
638 try:
639 unique_dates = [datetime.strptime(item["purchase_date"], "%Y-%m-%d") for item in items_with_date]
640 min_date = min(unique_dates)
641
642 global currency_data
643 if currency_data is None or currency_data.empty:
644 return {"status": "error", "message": "Alternatif yat─▒r─▒m verileri hen├╝z sunucuda haz─▒r de─şil, l├╝tfen 1 dakika sonra tekrar deneyin."}
645
646 df_alt = currency_data
647
648 curr_usd = float(df_alt["TRY=X"].iloc[-1])
649 curr_eur = float(df_alt["EURTRY=X"].iloc[-1])
650 curr_gold_usd = float(df_alt["GC=F"].iloc[-1])
651 curr_gold_tl = (curr_gold_usd * curr_usd) / 31.1034768
652
653 results = []
654 for item in items_with_date:
655 target_date = pd.to_datetime(item["purchase_date"])
656 # En yak─▒n tarihi bul (hafta sonu vs i├ğin)
657 idx = df_alt.index.get_indexer([target_date], method="nearest")[0]
658 row = df_alt.iloc[idx]
659
660 hist_usd = float(row["TRY=X"])
661 hist_eur = float(row["EURTRY=X"])
662 hist_gold_usd = float(row["GC=F"])
663 hist_gold_tl = (hist_gold_usd * hist_usd) / 31.1034768
664
665 invested_tl = item["amount"] * item["cost"]
666
667 usd_amount = invested_tl / hist_usd
668 eur_amount = invested_tl / hist_eur
669 gold_amount = invested_tl / hist_gold_tl
670
671 usd_current_tl = usd_amount * curr_usd
672 eur_current_tl = eur_amount * curr_eur
673 gold_current_tl = gold_amount * curr_gold_tl
674
675 # Hisse g├╝ncel de─şer
676 current_stock_price = float(stock_data[item["ticker"]]['Close'].iloc[-1]) if item["ticker"] in stock_data else item["cost"]
677 current_stock_value = item["amount"] * current_stock_price
678
679 results.append({
680 "ticker": item["ticker"],
681 "purchase_date": item["purchase_date"],
682 "invested_tl": invested_tl,
683 "current_stock_value": current_stock_value,
684 "usd_value": usd_current_tl,
685 "eur_value": eur_current_tl,
686 "gold_value": gold_current_tl
687 })
688
689 return {"status": "success", "data": results}
690 except Exception as e:
691 import traceback
692 traceback.print_exc()
693 return {"status": "error", "message": f"Hesaplama hatas─▒: {str(e)}"}
694
695# =======================================================
696# MARKOWITZ
697# =======================================================
698@app.get("/api/portfolio/markowitz")
699def get_markowitz_analysis(user: dict = Depends(get_current_user)):
700 pf = load_portfolio(user.get("uid", "local_user"), firebase_db)
701 if not pf:
702 return {"status": "error", "message": "Portf├Ây bo┼ş."}
703 pf_df = pd.DataFrame(pf)
704 corr_matrix, err = calculate_correlation_matrix(stock_data, pf_df)
705 if err:
706 return {"status": "error", "message": err}
707
708 warnings = analyze_portfolio_risk(corr_matrix)
709 suggestions = get_balancing_suggestions(stock_data, pf_df, TARGET_TICKERS)
710
711 tickers = corr_matrix.columns.tolist()
712 matrix_data = corr_matrix.values.tolist()
713
714 return {
715 "status": "success",
716 "warnings": warnings,
717 "suggestions": suggestions,
718 "correlation": {
719 "tickers": tickers,
720 "matrix": matrix_data
721 }
722 }
723
724
725# =======================================================
726# WATCHLIST
727# =======================================================
728@app.get("/api/watchlist")
729def get_watchlist(user: dict = Depends(get_current_user)):
730 wl = load_watchlist(user.get("uid", "local_user"), firebase_db)
731 res = []
732
733 # Fetch data for missing tickers
734 missing_tickers = [t for t in wl if t not in stock_data]
735 if missing_tickers:
736 new_data = fetch_stock_data(missing_tickers)
737 for mt in new_data:
738 stock_data[mt] = new_data[mt]
739
740 for t in wl:
741 if t in stock_data:
742 curr_p = round(stock_data[t]['Close'].iloc[-1], 2)
743 prev_p = round(stock_data[t]['Close'].iloc[-2], 2) if len(stock_data[t]) > 1 else curr_p
744 change = round(((curr_p - prev_p)/prev_p)*100, 2)
745
746 # Puan─▒ ve sinyali bul
747 score = 0
748 signal = get_signal_label(0)
749 for r in recommendations:
750 if r['ticker'] == t:
751 score = r['score']
752 signal = r['signal']
753 break
754
755 # Dinamik Hesaplama
756 if score == 0:
757 df = stock_data[t]
758 dyn_score, dyn_details = calculate_technical_score(df)
759 score = dyn_score
760 signal = dyn_details.get("signal", get_signal_label(dyn_score))
761
762 res.append({
763 "ticker": t,
764 "sector": get_ticker_sector(t),
765 "price": curr_p,
766 "change": change,
767 "score": score,
768 "signal": signal,
769 })
770 return res
771
772class WatchlistItem(BaseModel):
773 ticker: str
774
775@app.post("/api/watchlist")
776def add_wl(item: WatchlistItem, user: dict = Depends(get_current_user)):
777 add_to_watchlist(item.ticker, user.get("uid", "local_user"), firebase_db)
778 return {"status": "success"}
779
780@app.delete("/api/watchlist/{ticker}")
781def rm_wl(ticker: str, user: dict = Depends(get_current_user)):
782 remove_from_watchlist(ticker, user.get("uid", "local_user"), firebase_db)
783 return {"status": "success"}
784
785
786# =======================================================
787# GEMINI AI
788# =======================================================
789class GeminiRequest(BaseModel):
790 kap_text: str
791
792@app.post("/api/gemini")
793def get_ai_advice(req: GeminiRequest, user: dict = Depends(get_current_user)):
794 pf_str = str(load_portfolio(user.get("uid", "local_user"), firebase_db))
795 top_recs = str([{"ticker": r['ticker'], "score": r['score'], "signal": r['signal']['label']} for r in recommendations[:5]])
796
797 # Piyasa rejimi detayl─▒ string
798 regime_str = f"{market_regime.get('regime', 'Bilinmiyor')} (Volatilite: %{market_regime.get('volatility', '?')}, Trend G├╝c├╝: {market_regime.get('trend_strength', '?')})"
799
800 try:
801 advice = fetch_gemini_response(pf_str, regime_str, top_recs, req.kap_text)
802 return {"advice": advice}
803 except Exception as e:
804 return {"advice": f"Hata olu┼ştu: {str(e)}"}
805
806
807# =======================================================
808# BACKTEST
809# =======================================================
810@app.get("/api/backtest")
811def get_backtest():
812 """Algoritman─▒n ge├ğmi┼ş performans analizi. SSE ile progres g├Ânderir."""
813 def event_generator():
814 for update in run_backtest(stock_data, TARGET_TICKERS):
815 encoded = jsonable_encoder(update)
816 yield f"data: {json.dumps(encoded)}\n\n"
817 return StreamingResponse(
818 event_generator(),
819 media_type="text/event-stream",
820 headers={
821 "Cache-Control": "no-cache",
822 "Connection": "keep-alive",
823 "X-Accel-Buffering": "no"
824 }
825 )
826
827@app.get("/api/backtest/single/{ticker}")
828def get_single_backtest(ticker: str):
829 """Tekil bir hisse i├ğin detayl─▒ backtest ve grafik verisi d├Ând├╝r├╝r."""
830 if ticker not in stock_data:
831 new_data = fetch_stock_data([ticker])
832 if ticker in new_data:
833 stock_data[ticker] = new_data[ticker]
834 else:
835 return {"status": "error", "message": f"{ticker} i├ğin veri bulunamad─▒."}
836
837 df = stock_data[ticker]
838 # Sadece algoritman─▒n test etti─şi son 400 g├╝n├╝ alal─▒m
839 # G├Âstergeleri hesapla
840 import pandas_ta as ta
841 df_ind = df.copy()
842 df_ind['SMA_50'] = ta.sma(df_ind['Close'], length=50)
843 df_ind['SMA_200'] = ta.sma(df_ind['Close'], length=200)
844 bbands = ta.bbands(df_ind['Close'], length=20, std=2.0)
845 if bbands is not None and not bbands.empty:
846 df_ind['BBL'] = bbands.iloc[:, 0]
847 df_ind['BBU'] = bbands.iloc[:, 2]
848 else:
849 df_ind['BBL'] = None
850 df_ind['BBU'] = None
851
852 prices = []
853 for idx, row in df_ind.tail(400).iterrows():
854 prices.append({
855 "date": str(idx.date()) if hasattr(idx, 'date') else str(idx),
856 "close": round(float(row['Close']), 2),
857 "sma50": round(float(row['SMA_50']), 2) if not pd.isna(row['SMA_50']) else None,
858 "sma200": round(float(row['SMA_200']), 2) if not pd.isna(row['SMA_200']) else None,
859 "bbl": round(float(row['BBL']), 2) if not pd.isna(row['BBL']) else None,
860 "bbu": round(float(row['BBU']), 2) if not pd.isna(row['BBU']) else None
861 })
862
863 bt_gen = run_backtest(stock_data, [ticker])
864 result = None
865 for item in bt_gen:
866 if item["type"] == "result":
867 result = item["data"]
868 break
869
870 # E─şer o hissede hi├ğ i┼şlem yap─▒lmam─▒┼şsa result['status'] == 'no_data' d├Ânebilir.
871 trades = []
872 summary = {}
873 if result and result.get("status") == "success":
874 trades = result.get("recent_trades", [])
875 summary = result.get("ticker_results", {}).get(ticker, {})
876
877 encoded_data = jsonable_encoder({
878 "status": "success",
879 "ticker": ticker,
880 "prices": prices,
881 "trades": trades,
882 "summary": summary
883 })
884 return JSONResponse(encoded_data)
885
886
887# =======================================================
888# SECTORS
889# =======================================================
890@app.get("/api/sectors")
891def get_sectors():
892 """Sekt├Âr analizi ve rotasyon sinyalleri."""
893 sectors = analyze_sectors(stock_data)
894 rotation = get_sector_rotation_summary(sectors)
895
896 encoded_data = jsonable_encoder({
897 "sectors": sectors,
898 "rotation": rotation,
899 })
900 return JSONResponse(encoded_data)
901
902
903# =======================================================
904# TICKERS
905# =======================================================
906@app.get("/api/tickers")
907def get_tickers():
908 return ALL_BIST_TICKERS
909
910# =======================================================
911# SCREENER
912# =======================================================
913@app.get("/api/screener")
914def get_screener():
915 """Hisse tarama ve filtreleme i├ğin teknik ve ML verilerini d├Ând├╝r├╝r."""
916 results = []
917
918 for ticker, df in stock_data.items():
919 if df is None or len(df) < 200:
920 continue
921
922 try:
923 # Sadece son sat─▒r─▒ ve ML skorunu al
924 df_ml = _build_features(df)
925 last_row = df_ml.iloc[-1]
926
927 # ML Modeli (E─şitilmi┼şse)
928 model_bundle = train_model(df_ml, ticker)
929 ml_score = predict_confidence(model_bundle, df_ml) * 100 if model_bundle else 0
930
931 # Son veriler
932 close_price = last_row.get('Close', 0)
933 rsi = last_row.get('RSI', 0)
934 macd = last_row.get('MACD', 0)
935 macd_signal = last_row.get('MACD_Signal', 0)
936 adx = last_row.get('ADX', 0)
937 volume_ratio = last_row.get('Volume_Ratio', 0)
938
939 macd_status = "Al" if macd > macd_signal else "Sat"
940
941 # Sekt├Âr bilgisi
942 from sector_analyzer import SECTOR_MAP
943 sector = SECTOR_MAP.get(ticker.replace('.IS', ''), "Di─şer")
944
945 results.append({
946 "ticker": ticker,
947 "sector": sector,
948 "close": round(float(close_price), 2),
949 "rsi": round(float(rsi), 2),
950 "macd": round(float(macd), 2),
951 "macd_status": macd_status,
952 "adx": round(float(adx), 2),
953 "volume_ratio": round(float(volume_ratio), 2),
954 "ml_score": round(float(ml_score), 2)
955 })
956 except Exception as e:
957 continue
958
959 return JSONResponse(jsonable_encoder({"status": "success", "data": results}))
960
961# =======================================================
962# PORTFOLIO ANALYTICS
963# =======================================================
964class PortfolioItem(BaseModel):
965 ticker: str
966 amount: float
967
968class PortfolioPayload(BaseModel):
969 items: list[PortfolioItem]
970
971@app.post("/api/portfolio/analyze")
972def analyze_portfolio(payload: PortfolioPayload):
973 """Kullan─▒c─▒n─▒n portf├Ây├╝ndeki hisselerin g├╝ncel durumunu ve sekt├Ârel da─ş─▒l─▒m─▒n─▒ analiz eder."""
974 total_value = 0
975 daily_pnl = 0
976 sector_distribution = {}
977
978 from sector_analyzer import SECTOR_MAP
979
980 details = []
981
982 for item in payload.items:
983 ticker = item.ticker
984 amount = item.amount
985
986 if ticker in stock_data and not stock_data[ticker].empty:
987 df = stock_data[ticker]
988 current_price = float(df['Close'].iloc[-1])
989 prev_price = float(df['Close'].iloc[-2]) if len(df) > 1 else current_price
990
991 value = current_price * amount
992 prev_value = prev_price * amount
993
994 total_value += value
995 daily_pnl += (value - prev_value)
996
997 sector = SECTOR_MAP.get(ticker.replace('.IS', ''), "Di─şer")
998 sector_distribution[sector] = sector_distribution.get(sector, 0) + value
999
1000 details.append({
1001 "ticker": ticker,
1002 "price": round(current_price, 2),
1003 "amount": amount,
1004 "value": round(value, 2),
1005 "daily_change_pct": round(((current_price - prev_price) / prev_price) * 100, 2)
1006 })
1007 return {"advice": f"Hata olu┼ştu: {str(e)}"}
1008
1009
1010# =======================================================
1011# BACKTEST
1012# =======================================================
1013@app.get("/api/backtest")
1014# AI MODEL PORTFOLIO
1015# =======================================================
1016@app.get("/api/ai-portfolio")
1017def get_ai_portfolio():
1018 try:
1019 data = model_portfolio_bot.get_bot_dashboard(recommendations, firebase_db)
1020 return {"status": "success", "data": data}
1021 except Exception as e:
1022 return {"status": "error", "message": str(e)}
1023
1024class ResetPortfolioRequest(BaseModel):
1025 capital: float
1026
1027@app.post("/api/ai-portfolio/reset")
1028def reset_ai_portfolio(req: ResetPortfolioRequest):
1029 try:
1030 data = model_portfolio_bot.reset_bot_portfolio(req.capital, firebase_db)
1031 return {"status": "success", "message": f"Portf├Ây {req.capital} TL ile s─▒f─▒rland─▒."}
1032 except Exception as e:
1033 return {"status": "error", "message": str(e)}
1034
1035@app.post("/api/ai-portfolio/run")
1036def run_ai_portfolio_bot():
1037 try:
1038 model_portfolio_bot.run_trading_bot(recommendations, firebase_db)
1039 return {"status": "success", "message": "Bot i┼şlemleri ba┼şar─▒yla tamamland─▒."}
1040 except Exception as e:
1041 return {"status": "error", "message": str(e)}
1042
1043# =======================================================
1044# ALGORITHM SETTINGS
1045# =======================================================
1046@app.get("/api/algo-config")
1047def get_algo_config():
1048 try:
1049 data = algo_config_manager.get_config(firebase_db)
1050 return {"status": "success", "data": data}
1051 except Exception as e:
1052 return {"status": "error", "message": str(e)}
1053
1054@app.post("/api/algo-config")
1055def save_algo_config(req: dict):
1056 try:
1057 algo_config_manager.save_config(req.dict() if hasattr(req, 'dict') else req, firebase_db)
1058 asyncio.create_task(trigger_reanalysis())
1059 return {"status": "success", "message": "Algoritma ayarlar─▒ kaydedildi. Analiz yeniden ba┼şlat─▒l─▒yor..."}
1060 except Exception as e:
1061 return {"status": "error", "message": str(e)}
1062
1063async def trigger_reanalysis():
1064 print("\n[S─░STEM] Kullan─▒c─▒ algoritma ayarlar─▒n─▒ de─şi┼ştirdi, analiz ba┼ştan ba┼şl─▒yor...")
1065 # yield context
1066 await asyncio.sleep(1)
1067 run_analysis()
1068
1069# Statik dosyalar─▒ (HTML/CSS/JS) sunma
1070app.mount("/", StaticFiles(directory="frontend", html=True), name="frontend")
1071
1072if __name__ == "__main__":
1073 import uvicorn
1074 uvicorn.run("server:app", host="127.0.0.1", port=8000, reload=True)
1075 