Spaces:
Sleeping
Sleeping
| import os | |
| from typing import Optional | |
| import pandas as pd | |
| import datetime as dt | |
| import time | |
| try: | |
| import requests # for Alpha Vantage fallback | |
| except Exception: | |
| requests = None | |
| try: | |
| import yfinance as yf # lightweight polling, near real-time for popular tickers | |
| except Exception: | |
| yf = None | |
| def fetch_candles_yf(symbol: str, period: str = '1d', interval: str = '1m') -> pd.DataFrame: | |
| if yf is None: | |
| raise RuntimeError("yfinance is not installed. Please install yfinance.") | |
| ticker = yf.Ticker(symbol) | |
| df = ticker.history(period=period, interval=interval, auto_adjust=False) | |
| df = df.rename(columns={ | |
| 'Open': 'Open', 'High': 'High', 'Low': 'Low', 'Close': 'Close', 'Volume': 'Volume' | |
| }) | |
| # Ensure index is datetime and sorted | |
| # yfinance can return either Datetime or Date in index/columns. Normalize to Date index | |
| if 'Datetime' in df.columns: | |
| df = df.rename(columns={'Datetime': 'Date'}) | |
| df = df.reset_index().rename(columns={'Date': 'Date'}) | |
| if 'Date' not in df.columns: | |
| df = df.rename(columns={'index': 'Date'}) | |
| df = df.set_index('Date') | |
| df = df.sort_index() | |
| return df[['Open', 'High', 'Low', 'Close']] | |
| def get_returns(symbol): | |
| return [0.01] * 60 | |
| def _map_interval_to_alpha_vantage(interval: str) -> Optional[str]: | |
| mapping = { | |
| '1m': '1min', | |
| '5m': '5min', | |
| '15m': '15min', | |
| '60m': '60min' | |
| } | |
| return mapping.get(interval) | |
| def fetch_candles_alpha_vantage(symbol: str, interval: str, api_key: str) -> pd.DataFrame: | |
| if requests is None: | |
| raise RuntimeError("requests n'est pas installé. Veuillez installer requests.") | |
| av_interval = _map_interval_to_alpha_vantage(interval) or '1min' | |
| params = { | |
| 'function': 'TIME_SERIES_INTRADAY', | |
| 'symbol': symbol, | |
| 'interval': av_interval, | |
| 'apikey': api_key, | |
| 'outputsize': 'compact' | |
| } | |
| url = 'https://www.alphavantage.co/query' | |
| resp = requests.get(url, params=params, timeout=15) | |
| resp.raise_for_status() | |
| data = resp.json() | |
| key = f'Time Series ({av_interval})' | |
| if key not in data: | |
| # Alpha Vantage rate limit or unsupported symbol | |
| raise RuntimeError(f"Alpha Vantage réponse invalide: {list(data.keys())[:3]}") | |
| ts = data[key] | |
| rows = [] | |
| for ts_str, ohlc in ts.items(): | |
| # parse timestamps as local naive datetime | |
| try: | |
| ts_dt = dt.datetime.fromisoformat(ts_str) | |
| except Exception: | |
| # fallback format | |
| ts_dt = dt.datetime.strptime(ts_str, '%Y-%m-%d %H:%M:%S') | |
| rows.append({ | |
| 'Date': ts_dt, | |
| 'Open': float(ohlc['1. open']), | |
| 'High': float(ohlc['2. high']), | |
| 'Low': float(ohlc['3. low']), | |
| 'Close': float(ohlc['4. close']) | |
| }) | |
| df = pd.DataFrame(rows).set_index('Date').sort_index() | |
| return df[['Open', 'High', 'Low', 'Close']] | |
| def fetch_latest_candles(symbol: str, period: str = '1d', interval: str = '1m') -> pd.DataFrame: | |
| # If Alpha Vantage key is present and symbol looks supported, try AV first | |
| av_key = os.getenv('ALPHAVANTAGE_API_KEY') | |
| # Simple heuristic: AV does not support indices like ^GSPC, ^FCHI directly | |
| looks_index = symbol.startswith('^') | |
| if av_key and not looks_index: | |
| try: | |
| return fetch_candles_alpha_vantage(symbol, interval=interval, api_key=av_key) | |
| except Exception: | |
| # fall back to yfinance below | |
| pass | |
| # Retry yfinance a couple of times in case of DNS hiccups | |
| last_err = None | |
| for _ in range(2): | |
| try: | |
| return fetch_candles_yf(symbol, period=period, interval=interval) | |
| except Exception as e: | |
| last_err = e | |
| time.sleep(1.0) | |
| # final raise | |
| raise last_err if last_err else RuntimeError('Unknown data fetch error') | |