diff --git a/server.py b/server.py index 9bae643..39eeba9 100644 --- a/server.py +++ b/server.py @@ -296,36 +296,100 @@ def _resolve_symbol(ticker: str, market_type: str) -> str: return ticker +def _to_yahoo_ticker(ticker: str) -> str: + """Convert various ticker formats to Yahoo Finance format.""" + if ".JK" in ticker or ".JK" in ticker.upper(): + return ticker.replace(".jk", ".JK").replace(".Jk", ".JK") + if ticker.startswith("IDX:"): + return ticker.replace("IDX:", "") + ".JK" + if ":" in ticker: + return ticker.split(":", 1)[1] + return ticker + + def _fetch_candles( ticker: str, resolution: str = "D", count: int = 30, ) -> list[dict[str, Any]]: + # Try TradingView chart API first sym = _resolve_symbol(ticker, "stocks") to_ts = int(time.time()) resolution_seconds = {"1": 60, "5": 300, "15": 900, "30": 1800, "60": 3600, "240": 14400, "D": 86400, "W": 604800, "M": 2592000} from_ts = to_ts - (count * resolution_seconds.get(resolution, 86400)) - url = "https://chart-data.tradingview.com/history" - params = {"symbol": sym, "resolution": resolution, "from": from_ts, "to": to_ts} - resp = requests.get(url, headers=TV_CHART_HEADERS, params=params, timeout=15) - data = resp.json() + tv_urls = [ + "https://chart-data.tradingview.com/history", + "https://scanner.tradingview.com/history", + ] + for url in tv_urls: + try: + params = {"symbol": sym, "resolution": resolution, "from": from_ts, "to": to_ts} + resp = requests.get(url, headers=TV_CHART_HEADERS, params=params, timeout=10) + data = resp.json() + if data.get("s") == "ok" and len(data.get("t", [])) > 0: + candles = [] + for i in range(len(data["t"])): + candles.append({ + "time": data["t"][i], + "open": data["o"][i], + "high": data["h"][i], + "low": data["l"][i], + "close": data["c"][i], + "volume": data["v"][i], + }) + return candles + except requests.RequestException: + continue - if data.get("s") != "ok": - return [] + # Fallback: Yahoo Finance API + try: + yahoo_sym = _to_yahoo_ticker(ticker) + interval_map = {"1": "1m", "5": "5m", "15": "15m", "30": "30m", + "60": "60m", "240": "4h", "D": "1d", "W": "1wk", "M": "1mo"} + interval = interval_map.get(resolution, "1d") + range_map = {30: "1mo", 60: "3mo", 120: "6mo", 250: "1y", 500: "2y"} + y_range = "1mo" + for cnt, r in sorted(range_map.items()): + if count <= cnt: + y_range = r + break + else: + y_range = "5y" - candles = [] - for i in range(len(data["t"])): - candles.append({ - "time": data["t"][i], - "open": data["o"][i], - "high": data["h"][i], - "low": data["l"][i], - "close": data["c"][i], - "volume": data["v"][i], - }) - return candles + url = f"https://query1.finance.yahoo.com/v8/finance/chart/{yahoo_sym}" + params = {"interval": interval, "range": y_range} + headers = {"User-Agent": TV_CHART_HEADERS["User-Agent"]} + resp = requests.get(url, headers=headers, params=params, timeout=15) + data = resp.json() + + result = data.get("chart", {}).get("result", [None])[0] + if not result: + result = data.get("chart", {}).get("result", [None])[0] + if result: + timestamps = result.get("timestamp", []) + quotes = result.get("indicators", {}).get("quote", [None])[0] + if timestamps and quotes: + opens = quotes.get("open", []) + highs = quotes.get("high", []) + lows = quotes.get("low", []) + closes = quotes.get("close", []) + volumes = quotes.get("volume", []) + candles = [] + for i in range(len(timestamps)): + o, h, l, c, v = opens[i], highs[i], lows[i], closes[i], volumes[i] + if None in (o, h, l, c, v): + continue + candles.append({ + "time": timestamps[i], + "open": o, "high": h, "low": l, "close": c, "volume": v, + }) + return candles[-count:] + except requests.RequestException: + pass + + return [] def _detect_candle_patterns(candles: list[dict[str, Any]]) -> list[str]: