Files
microdao-daarion/services/senpai-md-consumer/README.md
Apple 09dee24342 feat: MD pipeline — market-data-service hardening + SenpAI NATS consumer
Producer (market-data-service):
- Backpressure: smart drop policy (heartbeats→quotes→trades preserved)
- Heartbeat monitor: synthetic HeartbeatEvent on provider silence
- Graceful shutdown: WS→bus→storage→DB engine cleanup sequence
- Bybit V5 public WS provider (backup for Binance, no API key needed)
- FailoverManager: health-based provider switching with recovery
- NATS output adapter: md.events.{type}.{symbol} for SenpAI
- /bus-stats endpoint for backpressure monitoring
- Dockerfile + docker-compose.node1.yml integration
- 36 tests (parsing + bus + failover), requirements.lock

Consumer (senpai-md-consumer):
- NATSConsumer: subscribe md.events.>, queue group senpai-md, backpressure
- State store: LatestState + RollingWindow (deque, 60s)
- Feature engine: 11 features (mid, spread, VWAP, return, vol, latency)
- Rule-based signals: long/short on return+volume+spread conditions
- Publisher: rate-limited features + signals + alerts to NATS
- HTTP API: /health, /metrics, /state/latest, /features/latest, /stats
- 10 Prometheus metrics
- Dockerfile + docker-compose.senpai.yml
- 41 tests (parsing + state + features + rate-limit), requirements.lock

CI: ruff + pytest + smoke import for both services
Tests: 77 total passed, lint clean
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-09 11:46:15 -08:00

243 lines
7.7 KiB
Markdown

# SenpAI Market-Data Consumer
NATS subscriber + feature engine + signal bus for the SenpAI/Gordon trading agent.
Consumes normalised events from `market-data-service`, computes real-time features, and publishes signals back to NATS.
## Architecture
```
market-data-service SenpAI MD Consumer
┌──────────────┐ ┌────────────────────────────────┐
│ Binance WS │ │ │
│ Bybit WS │──► NATS ──────────► NATSConsumer │
│ Alpaca WS │ md.events.> │ ↓ (bounded queue) │
└──────────────┘ │ State Store │
│ ├─ LatestState (trade/quote)│
│ └─ RollingWindow (60s deque)│
│ ↓ │
│ Feature Engine │
│ ├─ mid, spread, vwap │
│ ├─ return_10s, vol_60s │
│ └─ latency p50/p95 │
│ ↓ │
│ Publisher ──► NATS │
│ ├─ senpai.features.{symbol} │
│ ├─ senpai.signals.{symbol} │
│ └─ senpai.alerts │
│ │
│ HTTP API (:8892) │
│ /health /metrics /stats │
│ /state/latest /features │
└────────────────────────────────┘
```
## Quick Start
### 1. Install
```bash
cd services/senpai-md-consumer
pip install -r requirements.txt
cp .env.example .env
```
### 2. Start NATS (if not running)
```bash
docker run -d --name nats -p 4222:4222 -p 8222:8222 nats:2.10-alpine --js -m 8222
```
### 3. Start market-data-service (producer)
```bash
cd ../market-data-service
python -m app run --provider binance --symbols BTCUSDT,ETHUSDT
```
### 4. Start SenpAI MD Consumer
```bash
cd ../senpai-md-consumer
python -m senpai.md_consumer
```
### 5. Verify
```bash
# Health
curl http://localhost:8892/health
# Stats
curl http://localhost:8892/stats
# Latest state
curl "http://localhost:8892/state/latest?symbol=BTCUSDT"
# Computed features
curl "http://localhost:8892/features/latest?symbol=BTCUSDT"
# Prometheus metrics
curl http://localhost:8892/metrics
```
## Docker
### Standalone (with NATS)
```bash
docker-compose -f docker-compose.senpai.yml up -d
```
### Part of NODE1 stack
```bash
docker-compose -f docker-compose.node1.yml up -d market-data-service senpai-md-consumer
```
## NATS Subjects
### Consumed (from market-data-service)
| Subject | Description |
|---|---|
| `md.events.trade.{symbol}` | Trade events |
| `md.events.quote.{symbol}` | Quote events |
| `md.events.book_l2.{symbol}` | L2 book snapshots |
| `md.events.heartbeat.__system__` | Provider heartbeats |
### Published (for SenpAI/other consumers)
| Subject | Description |
|---|---|
| `senpai.features.{symbol}` | Feature snapshots (rate-limited to 10Hz/symbol) |
| `senpai.signals.{symbol}` | Trade signals (long/short) |
| `senpai.alerts` | System alerts (latency, gaps, backpressure) |
## Features Computed
| Feature | Description |
|---|---|
| `mid` | (bid + ask) / 2 |
| `spread_abs` | ask - bid |
| `spread_bps` | spread in basis points |
| `trade_vwap_10s` | VWAP over 10 seconds |
| `trade_vwap_60s` | VWAP over 60 seconds |
| `trade_count_10s` | Number of trades in 10s |
| `trade_volume_10s` | Total volume in 10s |
| `return_10s` | Price return over 10 seconds |
| `realized_vol_60s` | Realised volatility (60s log-return std) |
| `latency_ms_p50` | p50 exchange-to-receive latency |
| `latency_ms_p95` | p95 exchange-to-receive latency |
## Signal Rules (MVP)
**Long signal** emitted when ALL conditions met:
- `return_10s > 0.3%` (configurable)
- `trade_volume_10s > 1.0` (configurable)
- `spread_bps < 20` (configurable)
**Short signal**: same but `return_10s < -0.3%`
## Backpressure Policy
- Queue < 90% → accept all events
- Queue >= 90% → drop heartbeats, quotes, book snapshots
- **Trades are NEVER dropped**
## HTTP Endpoints
| Endpoint | Description |
|---|---|
| `GET /health` | Service health + tracked symbols |
| `GET /metrics` | Prometheus metrics |
| `GET /state/latest?symbol=` | Latest trade + quote |
| `GET /features/latest?symbol=` | Current computed features |
| `GET /stats` | Queue fill, drops, events/sec |
## Prometheus Metrics
| Metric | Type | Description |
|---|---|---|
| `senpai_events_in_total` | Counter | Events received {type, provider} |
| `senpai_events_dropped_total` | Counter | Dropped events {reason, type} |
| `senpai_queue_fill_ratio` | Gauge | Queue fill 0..1 |
| `senpai_processing_latency_ms` | Histogram | Processing latency |
| `senpai_feature_publish_total` | Counter | Feature publishes {symbol} |
| `senpai_signals_emitted_total` | Counter | Signals {symbol, direction} |
| `senpai_nats_connected` | Gauge | NATS connection status |
## Tests
```bash
pytest tests/ -v
```
41 tests:
- 11 model parsing tests (tolerant parsing, edge cases)
- 10 state/rolling window tests (eviction, lookup)
- 16 feature math tests (VWAP, vol, signals, percentile)
- 5 rate-limit tests (publish throttling, error handling)
## Troubleshooting
### NATS connection refused
```
nats.error: error=could not connect to server
```
Ensure NATS is running:
```bash
docker run -d --name nats -p 4222:4222 nats:2.10-alpine --js
```
Or check `NATS_URL` in `.env`.
### No events arriving (queue stays at 0)
1. Verify `market-data-service` is running and `NATS_ENABLED=true`
2. Check subject match: producer publishes to `md.events.trade.BTCUSDT`, consumer subscribes to `md.events.>`
3. Check NATS monitoring: `curl http://localhost:8222/connz` — both services should appear
### JetStream errors
If `USE_JETSTREAM=true` but NATS started without `--js`:
```bash
# Restart NATS with JetStream
docker rm -f nats
docker run -d -p 4222:4222 -p 8222:8222 nats:2.10-alpine --js -m 8222
```
Or set `USE_JETSTREAM=false` for core NATS (simpler, works for MVP).
### Port 8892 already in use
```bash
lsof -ti:8892 | xargs kill -9
```
### Features show `null` for all values
Normal on startup — features populate after first trade+quote arrive. Wait a few seconds for Binance data to flow through.
### No signals emitted
Signal rules require ALL conditions simultaneously:
- `return_10s > 0.3%` — needs price movement
- `volume_10s > 1.0` — needs trading activity
- `spread_bps < 20` — needs tight spread
In low-volatility markets, signals may be rare. Lower thresholds in `.env` for testing:
```env
SIGNAL_RETURN_THRESHOLD=0.001
SIGNAL_VOLUME_THRESHOLD=0.1
```
### High memory usage
Rolling windows grow per symbol. With many symbols, reduce window:
```env
ROLLING_WINDOW_SECONDS=30
```
## Configuration (ENV)
See `.env.example` for all available settings.
Key settings:
- `NATS_URL` — NATS server URL
- `FEATURES_PUB_RATE_HZ` — max feature publishes per symbol per second
- `SIGNAL_RETURN_THRESHOLD` — min return for signal trigger
- `ROLLING_WINDOW_SECONDS` — rolling window duration