File size: 9,967 Bytes
32f16c2
 
 
 
 
 
 
7aea037
 
 
 
32f16c2
7aea037
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32f16c2
7aea037
32f16c2
 
7aea037
 
32f16c2
 
7aea037
32f16c2
7aea037
 
32f16c2
 
 
7aea037
32f16c2
7aea037
32f16c2
7aea037
32f16c2
7aea037
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32f16c2
 
7aea037
 
 
 
 
 
 
32f16c2
7aea037
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32f16c2
 
 
 
 
 
 
 
 
 
 
7aea037
32f16c2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7aea037
 
 
 
32f16c2
7aea037
 
 
 
3452823
7aea037
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
"""
API-ready versions of stock data functions
Streamlit bağımlılıkları olmayan versiyonlar
"""
import pandas as pd
from datetime import datetime, timedelta
import numpy as np
from typing import Any, Dict, Optional, Tuple
import json
import urllib.request
import urllib.error


def _get_yfinance():
    try:
        import yfinance as yf  # type: ignore

        return yf
    except Exception as exc:
        raise RuntimeError(
            "yfinance is not available or is incompatible with this Python runtime"
        ) from exc


def _normalize_yahoo_symbol(symbol: str) -> str:
    sym = str(symbol or "").strip().upper()
    if not sym:
        return ""
    if sym.endswith(".IS"):
        return sym
    return f"{sym}.IS"


def _probe_yahoo_chart_status(
    yahoo_symbol: str,
    period: str,
    interval: str,
    timeout_seconds: int = 10,
) -> Dict[str, Any]:
    url = (
        f"https://query1.finance.yahoo.com/v8/finance/chart/{yahoo_symbol}"
        f"?range={period}&interval={interval}"
    )
    req = urllib.request.Request(
        url,
        headers={
            "User-Agent": "Mozilla/5.0",
            "Accept": "application/json,text/plain;q=0.9,*/*;q=0.8",
        },
        method="GET",
    )

    try:
        with urllib.request.urlopen(req, timeout=timeout_seconds) as resp:
            status = int(getattr(resp, "status", 200))
            ct = str(resp.headers.get("content-type") or "")
            body = resp.read(512) or b""
            body_preview = body.decode("utf-8", errors="replace")
            parsed_ok = False
            if "json" in ct.lower():
                try:
                    json.loads(body_preview)
                    parsed_ok = True
                except Exception:
                    parsed_ok = False

            return {
                "ok": status == 200,
                "http_status": status,
                "content_type": ct,
                "parsed_json_preview": parsed_ok,
                "body_preview": body_preview,
                "url": url,
            }
    except urllib.error.HTTPError as e:
        body = (e.read(512) or b"") if hasattr(e, "read") else b""
        body_preview = body.decode("utf-8", errors="replace")
        return {
            "ok": False,
            "http_status": int(getattr(e, "code", 0) or 0),
            "content_type": str(getattr(e, "headers", {}).get("content-type") if getattr(e, "headers", None) else ""),
            "body_preview": body_preview,
            "url": url,
        }
    except Exception as e:
        return {
            "ok": False,
            "http_status": None,
            "error": f"{type(e).__name__}: {e}",
            "url": url,
        }


def _fetch_yahoo_chart_df(
    yahoo_symbol: str,
    period: str,
    interval: str,
    timeout_seconds: int = 20,
) -> Optional[pd.DataFrame]:
    url = (
        f"https://query1.finance.yahoo.com/v8/finance/chart/{yahoo_symbol}"
        f"?range={period}&interval={interval}"
    )
    req = urllib.request.Request(
        url,
        headers={
            "User-Agent": "Mozilla/5.0",
            "Accept": "application/json,text/plain;q=0.9,*/*;q=0.8",
        },
        method="GET",
    )

    with urllib.request.urlopen(req, timeout=timeout_seconds) as resp:
        raw = resp.read() or b""
    payload = json.loads(raw.decode("utf-8", errors="strict"))

    chart = (payload or {}).get("chart") or {}
    results = chart.get("result") or []
    if not results:
        return None
    r0 = results[0] or {}

    timestamps = r0.get("timestamp") or []
    indicators = r0.get("indicators") or {}
    quotes = indicators.get("quote") or []
    if not timestamps or not quotes:
        return None
    q0 = quotes[0] or {}

    open_ = q0.get("open") or []
    high = q0.get("high") or []
    low = q0.get("low") or []
    close = q0.get("close") or []
    volume = q0.get("volume") or []

    df = pd.DataFrame(
        {
            "Open": open_,
            "High": high,
            "Low": low,
            "Close": close,
            "Volume": volume,
        }
    )
    df.index = pd.to_datetime(pd.Series(timestamps, dtype="int64"), unit="s", utc=True)

    for col in ["Open", "High", "Low", "Close", "Volume"]:
        df[col] = pd.to_numeric(df[col], errors="coerce")
    df = df.dropna(subset=["Close"])
    df["Volume"] = df["Volume"].fillna(0.0)
    if df.empty:
        return None
    return df


def get_stock_data_with_status_for_api(
    symbol: str,
    period: str = "1y",
    interval: str = "1d",
) -> Tuple[Optional[pd.DataFrame], Dict[str, Any]]:
    yahoo_symbol = _normalize_yahoo_symbol(symbol)
    if not yahoo_symbol:
        return None, {"ok": False, "error_type": "invalid_symbol"}

    status: Dict[str, Any] = {
        "ok": False,
        "symbol": str(symbol or ""),
        "yahoo_symbol": yahoo_symbol,
        "period": period,
        "interval": interval,
        "source": "yahoo_finance",
    }

    try:
        yf = _get_yfinance()
        stock = yf.Ticker(yahoo_symbol)
        data = stock.history(period=period, interval=interval)

        if data is not None and len(data) > 0:
            try:
                fast_info = stock.fast_info
                if hasattr(fast_info, "last_price") and fast_info.last_price:
                    last_idx = data.index[-1]
                    data.loc[last_idx, "Close"] = fast_info.last_price
            except Exception:
                try:
                    info = stock.info
                    last_idx = data.index[-1]
                    for key in ["regularMarketPrice", "currentPrice", "previousClose"]:
                        if key in info and info[key]:
                            data.loc[last_idx, "Close"] = info[key]
                            break
                except Exception:
                    pass

            status["ok"] = True
            status["error_type"] = None
            return data, status

        probe = _probe_yahoo_chart_status(yahoo_symbol, period=period, interval=interval)
        status["probe"] = {
            k: probe.get(k)
            for k in ["ok", "http_status", "content_type", "parsed_json_preview", "body_preview", "url", "error"]
            if k in probe
        }
        http_status = probe.get("http_status")
        if http_status == 429:
            status["error_type"] = "rate_limited"
            return None, status

        try:
            chart_df = _fetch_yahoo_chart_df(yahoo_symbol, period=period, interval=interval)
            if chart_df is not None and not chart_df.empty:
                status["ok"] = True
                status["error_type"] = None
                status["source"] = "yahoo_chart"
                return chart_df, status
        except Exception as e:
            status["chart_fallback_error"] = f"{type(e).__name__}: {e}"

        for backup_period in ["6mo", "3mo", "1mo"]:
            try:
                data2 = stock.history(period=backup_period, interval=interval)
                if data2 is not None and not data2.empty:
                    status["ok"] = True
                    status["error_type"] = None
                    status["period"] = backup_period
                    return data2, status
            except Exception:
                continue

            try:
                chart_df2 = _fetch_yahoo_chart_df(yahoo_symbol, period=backup_period, interval=interval)
                if chart_df2 is not None and not chart_df2.empty:
                    status["ok"] = True
                    status["error_type"] = None
                    status["source"] = "yahoo_chart"
                    status["period"] = backup_period
                    return chart_df2, status
            except Exception:
                continue

        status["error_type"] = status.get("error_type") or "no_data"
        return None, status
    except Exception as e:
        status["error_type"] = "exception"
        status["error"] = f"{type(e).__name__}: {e}"
        return None, status

def get_stock_data_for_api(symbol, period="1y", interval="1d"):
    """
    API için cache'siz veri çekme fonksiyonu
    """
    try:
        data, _status = get_stock_data_with_status_for_api(symbol, period=period, interval=interval)
        return data
            
    except Exception as e:
        print(f"API veri hatası ({symbol}): {e}")
        return None


def get_market_summary_for_api():
    """
    API için BIST 100 özet bilgisi
    """
    try:
        yf = _get_yfinance()
        xu100 = yf.Ticker("XU100.IS")
        data = xu100.history(period="5d")
        
        if data.empty:
            return None
            
        latest = data.iloc[-1]
        previous = data.iloc[-2] if len(data) > 1 else latest
        
        change = latest['Close'] - previous['Close']
        change_pct = (change / previous['Close']) * 100
        
        # Safely format date
        try:
            date_str = data.index[-1].strftime('%Y-%m-%d')  # type: ignore
        except (AttributeError, TypeError):
            date_str = str(data.index[-1])[:10]
        
        return {
            'index': 'XU100',
            'value': float(latest['Close']),
            'change': float(change),
            'change_percent': float(change_pct),
            'high': float(data['High'].max()),
            'low': float(data['Low'].min()),
            'volume': int(latest['Volume']),
            'date': date_str
        }
    except Exception as e:
        print(f"Market summary hatası: {e}")
        return None


def get_popular_stocks():
    """
    Popüler/likit hisseleri döndürür.

    NOTE: Hardcoded/toy list is intentionally avoided.
    We derive a bounded universe from official BIST index constituents.
    """
    try:
        from data.index_constituents import get_index_constituents

        res = get_index_constituents("bist30")
        return res.symbols or []
    except Exception:
        return []