feat: saxo price

This commit is contained in:
OpenSquared
2026-07-23 17:48:38 +02:00
parent 151c347ab1
commit 619f521b32

View File

@@ -242,13 +242,13 @@ def get_price_quote(symbol: str, asset_type: str = "FxSpot", amount: int = 10000
def get_price_history(symbol: str, asset_type: str = "FxSpot", days: int = 90) -> List[Dict[str, Any]]: def get_price_history(symbol: str, asset_type: str = "FxSpot", days: int = 90) -> List[Dict[str, Any]]:
"""Daily OHLC bars via Saxo's Chart API (GET /chart/v3/charts, Horizon=1440 = daily """Daily OHLC bars via Saxo's Chart API (GET /chart/v3/charts, Horizon=1440 = daily
bars). v1 confirmed dead (plain HTML 404, not a Saxo JSON error — 2026-07-23 live bars). v1 confirmed dead (plain HTML 404, not a Saxo JSON error); v3 confirmed live
test) — Saxo has iterated this service's version over time, v3 is the current best 2026-07-23 (returns real bars — ContractFutures symbols worked immediately). FX Spot
guess, NOT yet verified against a live account (unlike get_price_quote/ bars quote Bid/Ask rather than a single traded Close (no single last-traded price for
snapshot_options_chain, which were built against confirmed real responses) — this OTC FX) — CloseMid/mid-of-CloseBid+CloseAsk is used as the close price in that case.
follows Saxo's documented chart endpoint shape but needs a live check once deployed. Callers should treat any failure here (entitlement gap, still-wrong field names for
Callers should treat any failure here (wrong field names, entitlement gap, etc.) as some other asset type, etc.) as routine and fall back to another source, not surface
routine and fall back to another source, not surface it as a hard error. it as a hard error.
Returns oldest-first [{"date": "YYYY-MM-DD", "close": float}, ...]. Returns oldest-first [{"date": "YYYY-MM-DD", "close": float}, ...].
""" """
instrument = resolve_instrument(symbol, asset_types=asset_type) instrument = resolve_instrument(symbol, asset_types=asset_type)
@@ -263,13 +263,17 @@ def get_price_history(symbol: str, asset_type: str = "FxSpot", days: int = 90) -
raise ValueError(f"No chart data returned for '{symbol}' ({asset_type})") raise ValueError(f"No chart data returned for '{symbol}' ({asset_type})")
out = [] out = []
for bar in bars: for bar in bars:
close = bar.get("Close")
time_str = bar.get("Time") time_str = bar.get("Time")
close = bar.get("Close")
if close is None:
close = bar.get("CloseMid")
if close is None and bar.get("CloseBid") is not None and bar.get("CloseAsk") is not None:
close = (bar["CloseBid"] + bar["CloseAsk"]) / 2
if close is None or not time_str: if close is None or not time_str:
continue continue
out.append({"date": str(time_str)[:10], "close": float(close)}) out.append({"date": str(time_str)[:10], "close": float(close)})
if not out: if not out:
raise ValueError(f"Chart data for '{symbol}' had no usable Close/Time fields") raise ValueError(f"Chart data for '{symbol}' had no usable Close/CloseMid/CloseBid+CloseAsk/Time fields — raw bar keys: {list(bars[0].keys()) if bars else []}")
return out return out