300 lines
11 KiB
Markdown
300 lines
11 KiB
Markdown
|
|
# Design: Extended Strategies + Fundamental Data
|
|||
|
|
|
|||
|
|
**Date:** 2026-02-23
|
|||
|
|
**Status:** Approved
|
|||
|
|
|
|||
|
|
## Summary
|
|||
|
|
|
|||
|
|
Add 6 new trading strategies and a fundamental data pipeline to the existing 3-strategy ensemble. Introduces fundamental analysis data (EPS, P/E, PEG, etc.) from three providers with rotation and DB-backed caching, plus additional technical indicators computed from existing OHLCV bars.
|
|||
|
|
|
|||
|
|
**Result:** 9-strategy weighted ensemble (3 existing + 6 new).
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 1. Fundamental Data Provider System
|
|||
|
|
|
|||
|
|
### 1.1 Architecture
|
|||
|
|
|
|||
|
|
New module `shared/fundamentals/` with a provider abstraction:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
FundamentalsProvider (ABC)
|
|||
|
|
├── AlphaVantageProvider (25 req/day free tier)
|
|||
|
|
├── FMPProvider (250 req/day free tier)
|
|||
|
|
├── YahooFinanceProvider (no API key, yfinance library)
|
|||
|
|
└── RotatingProvider (wraps all three, rotates on rate limit)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 1.2 Provider Interface
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
class FundamentalsProvider(ABC):
|
|||
|
|
@abstractmethod
|
|||
|
|
async def fetch(self, ticker: str) -> FundamentalsSnapshot | None:
|
|||
|
|
"""Fetch fundamental data for a single ticker."""
|
|||
|
|
|
|||
|
|
class RotatingProvider(FundamentalsProvider):
|
|||
|
|
"""Tries providers in order, rotating to the next on rate limit or error."""
|
|||
|
|
def __init__(self, providers: list[FundamentalsProvider]): ...
|
|||
|
|
async def fetch(self, ticker: str) -> FundamentalsSnapshot | None: ...
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 1.3 Data Model
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
class FundamentalsSnapshot(BaseModel):
|
|||
|
|
ticker: str
|
|||
|
|
eps_ttm: float | None = None # Earnings per share (trailing 12 months)
|
|||
|
|
pe_ratio: float | None = None # Price-to-earnings ratio
|
|||
|
|
peg_ratio: float | None = None # Price/earnings-to-growth ratio
|
|||
|
|
revenue_growth_yoy: float | None = None # Year-over-year revenue growth (decimal)
|
|||
|
|
profit_margin: float | None = None # Net profit margin (decimal)
|
|||
|
|
debt_to_equity: float | None = None # Total debt / shareholder equity
|
|||
|
|
market_cap: float | None = None # Total market capitalization
|
|||
|
|
fetched_at: datetime # When this data was fetched
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 1.4 DB-Backed Caching
|
|||
|
|
|
|||
|
|
- New `fundamentals` table (Alembic migration required).
|
|||
|
|
- On startup: load from DB. If `fetched_at` is within 24h, skip API calls.
|
|||
|
|
- Daily refresh: background task re-fetches and updates DB.
|
|||
|
|
- Prevents wasting free-tier API calls during local dev / restarts.
|
|||
|
|
|
|||
|
|
### 1.5 Configuration
|
|||
|
|
|
|||
|
|
New `.env` variables:
|
|||
|
|
```
|
|||
|
|
TRADING_ALPHA_VANTAGE_API_KEY=<key>
|
|||
|
|
TRADING_FMP_API_KEY=<key>
|
|||
|
|
TRADING_FUNDAMENTALS_CACHE_TTL_HOURS=24
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
No API key needed for Yahoo Finance (yfinance library).
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 2. New Technical Indicators
|
|||
|
|
|
|||
|
|
Computed in `MarketDataManager.get_snapshot()` from the existing OHLCV rolling window. No new data source needed.
|
|||
|
|
|
|||
|
|
### 2.1 Indicators
|
|||
|
|
|
|||
|
|
| Indicator | Computation | Added to MarketSnapshot |
|
|||
|
|
|-----------|-------------|------------------------|
|
|||
|
|
| MACD | EMA-12 minus EMA-26 | `macd: float \| None` |
|
|||
|
|
| MACD Signal | EMA-9 of MACD line | `macd_signal: float \| None` |
|
|||
|
|
| MACD Histogram | MACD minus signal | `macd_histogram: float \| None` |
|
|||
|
|
| Bollinger Upper | SMA-20 + 2σ | `bollinger_upper: float \| None` |
|
|||
|
|
| Bollinger Mid | SMA-20 | `bollinger_mid: float \| None` |
|
|||
|
|
| Bollinger Lower | SMA-20 − 2σ | `bollinger_lower: float \| None` |
|
|||
|
|
| VWAP | Cumulative(price × volume) / cumulative(volume) | `vwap: float \| None` |
|
|||
|
|
| ATR (14) | Average true range, 14-period | `atr: float \| None` |
|
|||
|
|
| EMA-9 | 9-period exponential moving average | `ema_9: float \| None` |
|
|||
|
|
| EMA-21 | 21-period exponential moving average | `ema_21: float \| None` |
|
|||
|
|
| SMA-200 | 200-period simple moving average | `sma_200: float \| None` |
|
|||
|
|
|
|||
|
|
### 2.2 Historical Bars Increase
|
|||
|
|
|
|||
|
|
`TRADING_HISTORICAL_BARS` bumped from 100 → 250 to support SMA-200 computation.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 3. New Strategies (6 total)
|
|||
|
|
|
|||
|
|
All strategies implement `BaseStrategy.evaluate(ticker, market, sentiment) -> TradeSignal | None`.
|
|||
|
|
|
|||
|
|
### 3.1 Value Strategy (fundamental)
|
|||
|
|
|
|||
|
|
**File:** `shared/strategies/value.py`
|
|||
|
|
**Name:** `"value"`
|
|||
|
|
|
|||
|
|
Uses fundamental data (EPS, P/E, PEG, profit margin, debt-to-equity).
|
|||
|
|
|
|||
|
|
| Condition | Signal |
|
|||
|
|
|-----------|--------|
|
|||
|
|
| PEG < 1.0 AND P/E < 25 AND EPS growth > 0 | LONG |
|
|||
|
|
| PEG > 3.0 OR (P/E > 50 AND negative EPS growth) | SHORT |
|
|||
|
|
| Strength: composite of how far metrics deviate from thresholds | 0–1 |
|
|||
|
|
|
|||
|
|
Fundamentals data passed via a new optional `fundamentals` field on `MarketSnapshot`:
|
|||
|
|
```python
|
|||
|
|
fundamentals: FundamentalsSnapshot | None = None
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3.2 MACD Crossover Strategy (technical)
|
|||
|
|
|
|||
|
|
**File:** `shared/strategies/macd_crossover.py`
|
|||
|
|
**Name:** `"macd_crossover"`
|
|||
|
|
|
|||
|
|
| Condition | Signal |
|
|||
|
|
|-----------|--------|
|
|||
|
|
| MACD crosses above signal line AND histogram > 0 | LONG |
|
|||
|
|
| MACD crosses below signal line AND histogram < 0 | SHORT |
|
|||
|
|
| Strength: abs(histogram) normalized by ATR | 0–1 |
|
|||
|
|
|
|||
|
|
Requires tracking previous MACD/signal values to detect crossovers. Stores last state per ticker in instance dict.
|
|||
|
|
|
|||
|
|
### 3.3 Bollinger Bands Breakout Strategy (technical)
|
|||
|
|
|
|||
|
|
**File:** `shared/strategies/bollinger_breakout.py`
|
|||
|
|
**Name:** `"bollinger_breakout"`
|
|||
|
|
|
|||
|
|
| Condition | Signal |
|
|||
|
|
|-----------|--------|
|
|||
|
|
| Price above upper band + volume > 1.5× average | LONG (momentum breakout) |
|
|||
|
|
| Price below lower band | LONG (mean reversion bounce) |
|
|||
|
|
| Price was above upper band, now back inside | SHORT (failed breakout) |
|
|||
|
|
| Strength: distance from band / band width | 0–1 |
|
|||
|
|
|
|||
|
|
### 3.4 VWAP Strategy (technical, intraday)
|
|||
|
|
|
|||
|
|
**File:** `shared/strategies/vwap.py`
|
|||
|
|
**Name:** `"vwap"`
|
|||
|
|
|
|||
|
|
| Condition | Signal |
|
|||
|
|
|-----------|--------|
|
|||
|
|
| Price crosses above VWAP with increasing volume | LONG |
|
|||
|
|
| Price crosses below VWAP with increasing volume | SHORT |
|
|||
|
|
| Strength: distance from VWAP as % of price, weighted by volume ratio | 0–1 |
|
|||
|
|
|
|||
|
|
### 3.5 Liquidity Strategy (volume-based)
|
|||
|
|
|
|||
|
|
**File:** `shared/strategies/liquidity.py`
|
|||
|
|
**Name:** `"liquidity"`
|
|||
|
|
|
|||
|
|
| Indicator | Computation |
|
|||
|
|
|-----------|-------------|
|
|||
|
|
| Relative Volume | Current volume / 20-bar average volume |
|
|||
|
|
| Volume Trend | Slope of volume over recent bars |
|
|||
|
|
| Volume-Price Divergence | Price moving without volume confirmation |
|
|||
|
|
|
|||
|
|
| Condition | Signal |
|
|||
|
|
|-----------|--------|
|
|||
|
|
| Relative volume > 2.0 AND price rising AND volume trend increasing | LONG |
|
|||
|
|
| Relative volume > 2.0 AND price falling AND volume trend increasing | SHORT |
|
|||
|
|
| Relative volume < 0.5 (thin liquidity) | Neutral — skip |
|
|||
|
|
| Price rising on declining volume (divergence) | SHORT (weak rally) |
|
|||
|
|
| Strength: relative volume magnitude, clamped to [0, 1] | 0–1 |
|
|||
|
|
|
|||
|
|
### 3.6 MA Stack Strategy (trend alignment)
|
|||
|
|
|
|||
|
|
**File:** `shared/strategies/ma_stack.py`
|
|||
|
|
**Name:** `"ma_stack"`
|
|||
|
|
|
|||
|
|
Reads the full 5-MA stack: EMA-9, EMA-21, SMA-50, SMA-200 (plus price).
|
|||
|
|
|
|||
|
|
| Condition | Signal |
|
|||
|
|
|-----------|--------|
|
|||
|
|
| Full bull alignment: price > EMA-9 > EMA-21 > SMA-50 > SMA-200 | LONG, high strength |
|
|||
|
|
| Partial bull: price > EMA-9 > EMA-21, below SMA-200 | LONG, lower strength |
|
|||
|
|
| Full bear alignment: price < EMA-9 < EMA-21 < SMA-50 < SMA-200 | SHORT, high strength |
|
|||
|
|
| Golden cross: SMA-50 crosses above SMA-200 | LONG boost |
|
|||
|
|
| Death cross: SMA-50 crosses below SMA-200 | SHORT boost |
|
|||
|
|
| MAs tangled / no clear order | Neutral — no signal |
|
|||
|
|
| Strength: alignment count / 5, adjusted by fan spread | 0–1 |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 4. Updated Ensemble
|
|||
|
|
|
|||
|
|
### 4.1 Default Weights (equal, 9 strategies)
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
_DEFAULT_WEIGHTS = {
|
|||
|
|
"momentum": 0.111,
|
|||
|
|
"mean_reversion": 0.111,
|
|||
|
|
"news_driven": 0.111,
|
|||
|
|
"value": 0.111,
|
|||
|
|
"macd_crossover": 0.111,
|
|||
|
|
"bollinger_breakout": 0.111,
|
|||
|
|
"vwap": 0.111,
|
|||
|
|
"liquidity": 0.112,
|
|||
|
|
"ma_stack": 0.111,
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4.2 Learning Engine
|
|||
|
|
|
|||
|
|
The existing learning engine (multi-armed bandit) will automatically adjust weights for the new strategies based on trade outcomes. No changes needed to the learning engine itself — it already handles arbitrary strategy names.
|
|||
|
|
|
|||
|
|
### 4.3 Strategy Seeding
|
|||
|
|
|
|||
|
|
`scripts/seed_strategies.py` updated to seed all 9 strategies into the `strategies` DB table.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 5. Integration Points
|
|||
|
|
|
|||
|
|
### 5.1 Signal Generator Changes
|
|||
|
|
|
|||
|
|
1. Import and instantiate all 6 new strategies in `run()`.
|
|||
|
|
2. Add fundamentals fetching as a background task (daily refresh).
|
|||
|
|
3. Inject `FundamentalsSnapshot` into `MarketSnapshot.fundamentals` before calling ensemble.
|
|||
|
|
4. Update `_DEFAULT_WEIGHTS` to include all 9 strategies.
|
|||
|
|
|
|||
|
|
### 5.2 MarketDataManager Changes
|
|||
|
|
|
|||
|
|
1. Add EMA computation method (exponential moving average).
|
|||
|
|
2. Compute MACD, Bollinger, VWAP, ATR, EMA-9, EMA-21, SMA-200 in `get_snapshot()`.
|
|||
|
|
3. Track previous MACD state for crossover detection (or let strategy handle it).
|
|||
|
|
|
|||
|
|
### 5.3 Schema Changes
|
|||
|
|
|
|||
|
|
1. Add new optional fields to `MarketSnapshot` (all technical indicators + fundamentals).
|
|||
|
|
2. Add `FundamentalsSnapshot` Pydantic model.
|
|||
|
|
|
|||
|
|
### 5.4 Database Changes
|
|||
|
|
|
|||
|
|
1. New `fundamentals` table (Alembic migration).
|
|||
|
|
2. Seed 6 new strategies via `seed_strategies.py`.
|
|||
|
|
|
|||
|
|
### 5.5 Configuration Changes
|
|||
|
|
|
|||
|
|
```env
|
|||
|
|
TRADING_ALPHA_VANTAGE_API_KEY=M0I3TWB6VKU0UF51
|
|||
|
|
TRADING_FMP_API_KEY=34zqbQFeRxYvPtzp3Y5QLKPVPztkZyfK
|
|||
|
|
TRADING_FUNDAMENTALS_CACHE_TTL_HOURS=24
|
|||
|
|
TRADING_HISTORICAL_BARS=250
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 5.6 Dependencies
|
|||
|
|
|
|||
|
|
```toml
|
|||
|
|
# pyproject.toml — new deps
|
|||
|
|
yfinance # Yahoo Finance fundamentals (no API key)
|
|||
|
|
# httpx/aiohttp already available for Alpha Vantage + FMP REST calls
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 6. File Changes Summary
|
|||
|
|
|
|||
|
|
### New Files
|
|||
|
|
- `shared/fundamentals/__init__.py`
|
|||
|
|
- `shared/fundamentals/base.py` — `FundamentalsProvider` ABC + `FundamentalsSnapshot`
|
|||
|
|
- `shared/fundamentals/alpha_vantage.py` — Alpha Vantage provider
|
|||
|
|
- `shared/fundamentals/fmp.py` — Financial Modeling Prep provider
|
|||
|
|
- `shared/fundamentals/yahoo.py` — Yahoo Finance provider (yfinance)
|
|||
|
|
- `shared/fundamentals/rotating.py` — `RotatingProvider` with failover
|
|||
|
|
- `shared/strategies/value.py` — Value strategy
|
|||
|
|
- `shared/strategies/macd_crossover.py` — MACD Crossover strategy
|
|||
|
|
- `shared/strategies/bollinger_breakout.py` — Bollinger Breakout strategy
|
|||
|
|
- `shared/strategies/vwap.py` — VWAP strategy
|
|||
|
|
- `shared/strategies/liquidity.py` — Liquidity strategy
|
|||
|
|
- `shared/strategies/ma_stack.py` — MA Stack strategy
|
|||
|
|
- `alembic/versions/xxx_add_fundamentals_table.py` — Migration
|
|||
|
|
- `tests/test_fundamentals.py` — Unit tests for providers
|
|||
|
|
- `tests/test_new_strategies.py` — Unit tests for 6 new strategies
|
|||
|
|
- `tests/test_indicators.py` — Unit tests for technical indicator computations
|
|||
|
|
|
|||
|
|
### Modified Files
|
|||
|
|
- `shared/schemas/trading.py` — Add indicator fields to `MarketSnapshot`
|
|||
|
|
- `services/signal_generator/market_data.py` — Add EMA, MACD, Bollinger, VWAP, ATR, SMA-200 computations
|
|||
|
|
- `services/signal_generator/main.py` — Import new strategies, add fundamentals fetching, update weights
|
|||
|
|
- `shared/strategies/__init__.py` — Export new strategies
|
|||
|
|
- `scripts/seed_strategies.py` — Seed 6 new strategies
|
|||
|
|
- `.env` — Add API keys and config
|
|||
|
|
- `pyproject.toml` — Add yfinance dependency
|
|||
|
|
- `shared/models/trading.py` — Add `Fundamentals` DB model (or new file in models/)
|