real-time data = critical for algo trading.
redis = in-memory cache for speed.
python pipeline implementation.
the latency problem #
pulling data every request:
query Polygon API → 50-100ms latency.
multiply by 10 strategies → 500ms-1s total.
too slow for real-time trading.
learned this architecture from NexusFi infrastructure discussions on building low-latency data pipelines for automated trading.
redis solution #
architecture:
- background process streams data → redis
- strategies read from redis (< 1ms)
- single API connection, multiple consumers
speed improvement:
before: 50-100ms per strategy after: <1ms per strategy
50-100x faster.
python implementation #
basic redis data pipeline:
import redis
import json
import asyncio
from polygon import WebSocketClient
from typing import Dict, List
import time
class MarketDataPipeline:
def __init__(self, polygon_api_key: str, redis_host: str = 'localhost', redis_port: int = 6379):
"""
Initialize market data pipeline
Args:
polygon_api_key: Polygon.io API key
redis_host: Redis server host
redis_port: Redis server port
"""
self.polygon_key = polygon_api_key
self.redis_client = redis.Redis(
host=redis_host,
port=redis_port,
db=0,
decode_responses=True
)
self.ws_client = None
self.subscriptions = set()
async def start_stream(self, symbols: List[str]):
"""Start WebSocket stream for symbols"""
self.subscriptions = set(symbols)
# Polygon WebSocket client
self.ws_client = WebSocketClient(
api_key=self.polygon_key,
feed='stocks', # or 'crypto', 'forex'
market='stocks'
)
# Subscribe to quote updates
for symbol in symbols:
self.ws_client.subscribe_quotes(symbol, self._handle_quote)
# Run WebSocket client
self.ws_client.run_async()
def _handle_quote(self, msg: Dict):
"""
Handle incoming quote update
Stores in Redis with key pattern: quote:{symbol}
"""
try:
symbol = msg.get('symbol')
if not symbol:
return
# Build quote data
quote_data = {
'symbol': symbol,
'bid': msg.get('bid_price'),
'ask': msg.get('ask_price'),
'bid_size': msg.get('bid_size'),
'ask_size': msg.get('ask_size'),
'timestamp': msg.get('timestamp'),
'exchange': msg.get('exchange'),
'received_at': time.time() * 1000 # Local timestamp
}
# Store in Redis
key = f"quote:{symbol}"
self.redis_client.setex(
key,
60, # Expire after 60 seconds
json.dumps(quote_data)
)
# Update last price for quick access
self.redis_client.setex(
f"price:{symbol}",
60,
quote_data['ask'] # or midpoint
)
except Exception as e:
print(f"Error handling quote: {e}")
def get_quote(self, symbol: str) -> Dict:
"""
Get latest quote from Redis
Args:
symbol: Stock symbol
Returns:
Quote data dict or None
"""
key = f"quote:{symbol}"
data = self.redis_client.get(key)
if data:
return json.loads(data)
return None
def get_price(self, symbol: str) -> float:
"""
Get latest price quickly
Args:
symbol: Stock symbol
Returns:
Latest price or None
"""
key = f"price:{symbol}"
price = self.redis_client.get(key)
return float(price) if price else None
def get_multiple_prices(self, symbols: List[str]) -> Dict[str, float]:
"""
Get multiple prices in single Redis call
Args:
symbols: List of symbols
Returns:
Dict mapping symbol to price
"""
# Use Redis pipeline for batch get
pipe = self.redis_client.pipeline()
for symbol in symbols:
pipe.get(f"price:{symbol}")
results = pipe.execute()
return {
symbol: float(price) if price else None
for symbol, price in zip(symbols, results)
}
# Example usage
if __name__ == "__main__":
# Initialize pipeline
pipeline = MarketDataPipeline(polygon_api_key='YOUR_API_KEY')
# Start streaming (async)
symbols = ['SPY', 'QQQ', 'AAPL', 'MSFT']
asyncio.run(pipeline.start_stream(symbols))
# In separate process/thread, strategies read from Redis:
quote = pipeline.get_quote('SPY')
print(f"SPY Quote: Bid {quote['bid']} | Ask {quote['ask']}")
# Or get just price for speed
price = pipeline.get_price('SPY')
print(f"SPY Price: ${price:.2f}")
# Batch get multiple prices
prices = pipeline.get_multiple_prices(['SPY', 'QQQ', 'AAPL'])
print(f"Prices: {prices}")
advanced: historical candles caching #
cache 1-minute candles for backtesting:
class CandleCache:
def __init__(self, redis_client: redis.Redis):
"""Initialize candle cache"""
self.redis = redis_client
def store_candle(self, symbol: str, timeframe: str, candle: Dict):
"""
Store candle in Redis sorted set
Args:
symbol: Stock symbol
timeframe: '1m', '5m', '1h', etc.
candle: Dict with open, high, low, close, volume, timestamp
"""
key = f"candles:{symbol}:{timeframe}"
# Use timestamp as score for sorting
score = candle['timestamp']
# Store candle as JSON
member = json.dumps(candle)
# Add to sorted set
self.redis.zadd(key, {member: score})
# Keep only last 1000 candles
self.redis.zremrangebyrank(key, 0, -1001)
# Set expiration (7 days)
self.redis.expire(key, 60 * 60 * 24 * 7)
def get_candles(
self,
symbol: str,
timeframe: str,
start_time: int = None,
end_time: int = None,
limit: int = 100
) -> List[Dict]:
"""
Retrieve candles from Redis
Args:
symbol: Stock symbol
timeframe: Timeframe
start_time: Start timestamp (ms)
end_time: End timestamp (ms)
limit: Max candles to return
Returns:
List of candle dicts
"""
key = f"candles:{symbol}:{timeframe}"
# Get from sorted set
if start_time and end_time:
# Range query
results = self.redis.zrangebyscore(
key,
start_time,
end_time,
start=0,
num=limit
)
else:
# Get latest N candles
results = self.redis.zrevrange(key, 0, limit - 1)
# Parse JSON
candles = [json.loads(r) for r in results]
return candles
# Example usage
if __name__ == "__main__":
redis_client = redis.Redis(host='localhost', port=6379, decode_responses=True)
cache = CandleCache(redis_client)
# Store candle
candle = {
'timestamp': 1709049600000, # ms
'open': 450.20,
'high': 451.80,
'low': 449.90,
'close': 451.20,
'volume': 125000
}
cache.store_candle('SPY', '1m', candle)
# Retrieve last 100 candles
candles = cache.get_candles('SPY', '1m', limit=100)
print(f"Retrieved {len(candles)} candles")
my current setup #
infrastructure:
redis server on san diego rack (same machine as strategies).
polygon websocket → redis pipeline.
10 strategies reading from redis simultaneously.
symbols tracked:
SPY, QQQ, IWM (index ETFs)
ES, NQ (futures)
BTC, ETH (crypto)
total: 7 symbols, <1MB memory usage
latency:
polygon → redis: ~15ms
strategies → redis: <1ms
total roundtrip: ~16ms
vs previous 50-100ms per strategy.
monitoring redis #
simple monitoring script:
def monitor_redis_pipeline():
"""Monitor Redis data pipeline health"""
r = redis.Redis(host='localhost', port=6379, decode_responses=True)
# Check symbol freshness
symbols = ['SPY', 'QQQ', 'ES', 'NQ', 'BTC', 'ETH']
for symbol in symbols:
quote = r.get(f"quote:{symbol}")
if quote:
data = json.loads(quote)
age = (time.time() * 1000) - data['received_at']
print(f"{symbol}: {age:.0f}ms old")
if age > 5000: # 5 seconds
print(f" ⚠️ STALE DATA for {symbol}")
else:
print(f"{symbol}: NO DATA")
# Check Redis memory usage
info = r.info('memory')
mem_used = info['used_memory_human']
print(f"\nRedis memory: {mem_used}")
# Run every 60 seconds
while True:
monitor_redis_pipeline()
time.sleep(60)
why redis over database #
postgres/mysql:
disk-based storage.
query latency 10-50ms.
redis:
in-memory storage.
query latency <1ms.
for real-time trading:
redis 10-50x faster.
critical for millisecond-sensitive strategies.
cost #
redis cloud: $0
self-hosted on my server rack.
alternative (redis cloud):
$10-30/month for 1GB.
worth it if don’t have own server.
resources #
redis docs:
thorough examples.
polygon websocket:
https://polygon.io/docs/websockets
real-time market data streams.
NexusFi infrastructure discussions:
learned this architecture from experienced algo traders.
tonight (february 26, 2:38am) #
redis = 50-100x faster than direct API calls.
architecture:
polygon websocket → redis → strategies.
latency:
before: 50-100ms per strategy.
after: <1ms per strategy.
my setup:
7 symbols tracked.
10 strategies reading simultaneously.
self-hosted redis on san diego rack.
this is production algo trading infrastructure.
2:38am wednesday. real-time market data pipeline with python and redis. polygon websocket streams to redis cache, strategies read <1ms latency vs 50-100ms direct API. architecture: single websocket connection, multiple strategy consumers. 50-100x speedup. my setup: 7 symbols (SPY/QQQ/ES/NQ/BTC/ETH/IWM), 10 strategies, self-hosted redis. learned from NexusFi infrastructure discussions. production-grade low-latency data pipeline.
-AK