Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Real-time and historical stock and forex market data via Twelve Data API: quotes, OHLCV time series, and symbol search.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
exports.py
1"""2TwelveData skill exports — script-mode skill.34Usage from a bash block:5python3 - <<'EOF'6import sys7sys.path.insert(0, "/data/workspace/skills/twelvedata")8from exports import twelvedata_price, twelvedata_time_series9print(twelvedata_price(symbol="AAPL"))10EOF1112Imports from sidecar.proxy_client (NOT core.http_client) so this skill13stays runnable without the agent platform's core/* modules on PYTHONPATH.14"""15import os1617# Make sidecar/ importable when the script is invoked directly via18# `python3 -c` from the agent's bash tool. /app is already on PYTHONPATH19# inside the container (set by entrypoint.sh), so `from sidecar...` works20# in production. The fallback covers local-dev runs from outside /app.21try:22from sidecar.proxy_client import proxied_get23except ImportError:24# Local dev / running outside the deployed image: fall back to25# core.http_client which is identical. Skill still works either way.26from core.http_client import proxied_get2728API_KEY = os.environ.get("TWELVEDATA_API_KEY", "")29BASE = "https://api.twelvedata.com"303132def _explain_error(code, body):33"""Map a TwelveData error code to a short, actionable message.3435Never echoes the raw JSON body back to the agent (issue #243)."""36code = str(code)37msg = ""38if isinstance(body, dict):39msg = str(body.get("message", "")).strip()40if code == "404":41return (42"Symbol not found in TwelveData. Check the spelling, or use "43"twelvedata_search to find the correct ticker."44)45if code == "401":46return "TwelveData API key is invalid or missing (TWELVEDATA_API_KEY)."47if code == "429":48return "TwelveData rate limit hit. Wait before retrying."49# Generic: include the upstream message text only (not the full JSON).50return f"TwelveData API error {code}" + (f": {msg}" if msg else "")515253def _get(endpoint, params=None):54"""Make a GET request to TwelveData API.5556TwelveData signals invalid symbols in two ways:57- an HTTP 4xx status, or58- HTTP 200 with an error envelope {"code": 404, "status": "error",59"message": ...} in the body.60Both are normalised into a clean RuntimeError so the agent sees an61actionable message instead of a raw JSON dump (issue #243)."""62if params is None:63params = {}64params["apikey"] = API_KEY65r = proxied_get(f"{BASE}/{endpoint}", params=params)6667# Non-2xx: surface a short readable error, not the raw body.68if r.status_code >= 400:69try:70body = r.json()71except Exception:72body = {}73raise RuntimeError(_explain_error(body.get("code", r.status_code), body))7475data = r.json()76# TwelveData frequently returns HTTP 200 with an error envelope.77if isinstance(data, dict) and str(data.get("status")) == "error":78raise RuntimeError(_explain_error(data.get("code", r.status_code), data))79return data808182def twelvedata_time_series(symbol, interval="1day", outputsize=30, start_date=None, end_date=None, prepost=False):83"""Get OHLCV time series data."""84params = {"symbol": symbol, "interval": interval, "outputsize": outputsize}85if start_date:86params["start_date"] = start_date87if end_date:88params["end_date"] = end_date89if prepost:90params["prepost"] = "true"91return _get("time_series", params)929394def twelvedata_price(symbol, prepost=False):95"""Get current price for a symbol."""96params = {"symbol": symbol}97if prepost:98params["prepost"] = "true"99return _get("price", params)100101102def twelvedata_eod(symbol, date=None, prepost=False):103"""Get end-of-day price."""104params = {"symbol": symbol}105if date:106params["date"] = date107if prepost:108params["prepost"] = "true"109return _get("eod", params)110111112def twelvedata_quote(symbol, prepost=False):113"""Get detailed quote (price, volume, 52w high/low, change %)."""114params = {"symbol": symbol}115if prepost:116params["prepost"] = "true"117return _get("quote", params)118119120def twelvedata_quote_batch(symbols, prepost=False):121"""Get quotes for multiple symbols. symbols: comma-separated string."""122params = {"symbol": symbols}123if prepost:124params["prepost"] = "true"125return _get("quote", params)126127128def twelvedata_price_batch(symbols, prepost=False):129"""Get prices for multiple symbols. symbols: comma-separated string."""130params = {"symbol": symbols}131if prepost:132params["prepost"] = "true"133return _get("price", params)134135136def twelvedata_search(query):137"""Search for symbols by name or ticker."""138return _get("symbol_search", {"symbol": query})139140141def twelvedata_stocks(exchange=None, country=None):142"""Get list of available stocks, optionally filtered."""143params = {}144if exchange:145params["exchange"] = exchange146if country:147params["country"] = country148return _get("stocks", params)149150151def twelvedata_forex_pairs():152"""Get all available forex pairs."""153return _get("forex_pairs")154155156def twelvedata_exchanges():157"""Get list of supported exchanges."""158return _get("exchanges")159