CoolFace
Apppublic

kaan1233/bistelligence-api

sourceHugging Faceupdated 2mo agoView on Hugging Face
0likes
old_server.py1075 linesDownload Raw Back to root
1��from 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