Ingestion Worker
The Ingestion Worker is a standalone service responsible for real-time asset price tracking, data streaming, and database candle aggregation. It operates on a continuous async loop to fetch ticker ticks and publish them to a message broker.
Technical Architecture
1. Multi-Threaded yfinance Ingestion
The Yahoo Finance (yfinance) library runs synchronous blocking network calls when querying tickers. To prevent blocking the main asyncio event loop, the worker runs all fast_info metrics retrievals inside a thread pool executor using loop.run_in_executor(None, ...) inside get_latest_stock_data.
loop = asyncio.get_event_loop()
yf_ticker = yf.Ticker(ticker)
info = await loop.run_in_executor(None, lambda: yf_ticker.fast_info)
This ensures high concurrency and prevents websocket read latency on the frontend during polling cycles.
2. Exponential Rate-Limit Cooldown
To bypass strict IP rate-limiting blocks from third-party APIs:
- If a
429 Too Many Requestsor rate-limiting exception string is caught during execution, the worker sets a global cooldown timestamp (COOLDOWN_UNTIL). - The rate-limiting cooldown backoff starts at 60 seconds.
- On consecutive rate-limit violations, the backoff doubles exponentially up to a maximum limit of 15 minutes (900 seconds).
- Once the cooldown timer expires, the backoff resets to 60 seconds on the first successful fetch.
3. Redpanda Event Streaming
All successfully fetched ticker events are formatted as JSON payloads containing the ticker name, latest price, cumulative volume, and UTC timestamp, then published to the Redpanda Cloud topic stock-ticks.
- The worker uses the asynchronous
aiokafkaclient (AIOKafkaProducer). - Payload serialization is handled using a native UTF-8 JSON encoder.
4. 1-Minute Candle Aggregation
In addition to streaming, the worker buffers price and volume data points in memory to calculate 1-minute OHLCV candles:
- Open: The first price point of the minute.
- High: The maximum price point recorded in the minute.
- Low: The minimum price point recorded in the minute.
- Close: The last price point of the minute.
- Volume: The difference between the maximum and minimum cumulative volumes recorded in that minute.
Closed-Market Skipping Rules:
To avoid bloat in the database (e.g. NeonDB storage limitations), the worker filters out flat, inactive closed-market candles (where open equals high, low, and close, and the volume difference is zero).
- Non-crypto tickers (stocks and indices) will not be committed to the database during closed hours.
- Crypto tickers (e.g. ending in
-USDor-BTC) bypass this rule and are saved 24/7 since cryptocurrency markets never close.
Service Operations
Core Main Loop
The worker queries the database dynamically on each cycle to fetch active tickers from users' watchlists (select(Watchlist.ticker).distinct()). This guarantees that:
- The worker only polls tickers that users are actively monitoring.
- Adding a ticker to a watchlist dynamically begins polling it without requiring a worker restart.
- Polling calls are staggered with a 1.5-second sleep interval to spread load evenly.