feat: saxo price
This commit is contained in:
@@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user