Verified against a live account before and after writing it. CHART_FUTURES delivers one true-OHLCV minute bar per symbol per minute, LEVEL_ONE_FUTURES reports delayed: false, and consecutive bars arrived sixty seconds apart through the production code path. Yahoo stays. Schwab serves no futures history whatever, so seed_source resolves to Yahoo even when SEED_SOURCE=schwab is asked for — the pairing is the intended configuration rather than a fallback. The symbols differ, ES=F against /ES, so Settings.live_symbol picks the live one while seeding always uses Yahoo's. Three findings worth keeping, each of which cost a round trip: - get_quote() singular returns the wrong instrument entirely. It puts the symbol in the URL path, where the leading slash is normalised away, so /ES resolves to Eversource Energy at $72 and returns HTTP 200 with a populated body. Only get_quotes() plural, which passes symbols as a query parameter, returns the future. A 200 is not evidence; assetMainType is. - Streaming requires the Accounts and Trading product. StreamClient.login() reads /trader/v1/userPreference for its socket URL, and that path does not exist in Market Data Production. - /ES resolves to the active contract on Schwab's side, so the contract roll handling the plan left open needs no code. The stream drops the oldest queued message rather than stalling the socket, and surfaces a dead pump task instead of waiting forever on a queue nothing fills. schwab-py moves into requirements.txt, imported only when LIVE_SOURCE=schwab. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
34 lines
1.4 KiB
Python
34 lines
1.4 KiB
Python
from app.config import Settings
|
|
from app.market.base import MarketDataSource
|
|
from app.market.replay import ReplaySource
|
|
from app.market.yahoo import YahooSource
|
|
|
|
|
|
def live_source(settings: Settings) -> MarketDataSource:
|
|
if settings.live_source == "yahoo":
|
|
return YahooSource(settings.yahoo_poll_seconds)
|
|
if settings.live_source == "schwab":
|
|
# Imported here so schwab-py stays optional while the live source is
|
|
# Yahoo, which is still the default.
|
|
from app.market.schwab import SchwabSource
|
|
|
|
return SchwabSource(settings)
|
|
if settings.live_source == "replay" and settings.replay_file:
|
|
return ReplaySource(settings.replay_file)
|
|
raise ValueError(f"Unsupported LIVE_SOURCE: {settings.live_source}")
|
|
|
|
|
|
def seed_source(settings: Settings) -> MarketDataSource | None:
|
|
"""The source of history, which is never Schwab.
|
|
|
|
Schwab serves price history for equities and ETFs only, so with
|
|
LIVE_SOURCE=schwab the seed stays on Yahoo. That pairing is the intended
|
|
configuration, not a fallback: Yahoo supplies the past, Schwab the present.
|
|
"""
|
|
if settings.seed_source == "none":
|
|
return None
|
|
if settings.seed_source in ("yahoo", "schwab"):
|
|
return YahooSource(settings.yahoo_poll_seconds)
|
|
if settings.seed_source == "replay" and settings.replay_file:
|
|
return ReplaySource(settings.replay_file)
|
|
raise ValueError(f"Unsupported SEED_SOURCE: {settings.seed_source}")
|