The Wire
Everything the system knows, it knows for free
FutureMarket buys no data. Every price, filing, headline and indicator arrives through a free tier or an open API, and the architecture is shaped by that constraint more than by any other single decision.
services/openbb_service.py
380 lines
Provider: yfinance via OpenBB SDK
The consequence of a free-data architecture is that rate limiting, not latency, is the governing concern. It shows up everywhere: in the staggered scheduler offsets that keep thirteen jobs from hitting the same upstream simultaneously; in the half-second sleep between batches when the universe service validates several hundred tickers; in the one-second minimum interval enforced by an asyncio lock in front of the news client; and in seven separate in-process caches, no two of which share an implementation.
The data layer is a thin abstraction over the OpenBB SDK, which itself wraps yfinance. The SDK is deliberately not imported at module load. It is lazy-loaded on first property access, with the auto-build environment flag set at import time, and this is what keeps the FastAPI process from taking several seconds to start.
Five fetch paths exist, and each is written twice: a synchronous cached implementation, and a thin async wrapper that pushes it onto a worker thread. This is the dominant pattern across the whole backend — blocking network and numerical work runs in threads, the event loop stays free, and there is no true async client anywhere in the data path.
Symbol normalisation happens at exactly one point, and only when the provider is yfinance: a dot in a class-share ticker becomes a hyphen. It is a two-line function, and it is the only concession in the codebase to the fact that ticker conventions differ between vendors.
Where a provider does not supply a field, the service does one of two things, and the difference matters. For quote spreads it fabricates: when the provider returns no bid or ask — which is the normal case for the free feed — it synthesises a symmetric ten-basis-point spread around the last price. For valuation ratios it declines: market capitalisation, price/earnings, dividend yield, and beta are all returned as explicit nulls.
The second behaviour is more honest and causes more trouble. Downstream, the tool that formats fundamentals for the language model divides the market-cap field by a billion to render it in billions. A null does not divide. The resulting type error is swallowed by a broad exception handler that discards the income and balance-sheet sections it had already built correctly, and returns an error string in their place.1
Rate limiting, not latency, is the governing constraint. Seven caches exist, and no two of them share an implementation.
On the cost of building against free tiers