š
Hedge Sprint 2 Spec: Strategy Ensemble System
P3 - LowSpec Hedge
Hedge Sprint 2: Strategy Ensemble System
Priority: CRITICAL ā Core value prop of Hedge
Owner: Bob coordinating, Atlas (backend), Kai (frontend)
Architecture Overview
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā STRATEGY LIBRARY ā
ā [MA Crossover] [RSI(2)] [MACD+RSI] [Bollinger] [Momentum] ... ā
ā [HMM Regime] [VIX Overlay] [Sector Rotation] [Gap Fill] ... ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā
ā¼
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā NIGHTLY SIGNAL SCANNER ā
ā Run ALL strategies against ALL tickers in screener universe ā
ā ā Store: {ticker, strategy, signal, confidence, reasoning} ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā
ā¼
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā COMPOSITE SCORING ENGINE ā
ā composite = Ī£(signal Ć confidence Ć regime_weight) / N ā
ā Rank all tickers by composite score ā
ā Filter by current HMM regime ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā
ā¼
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā AI OPPORTUNITY FINDER ā
ā Mode 1: "Analyze AAPL" ā All strategy signals + reasoning ā
ā Mode 2: "Scan watchlist" ā Top 10 ranked opportunities ā
ā Mode 3: "401k rebalance" ā Monthly fund rotation suggestions ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Phase 1: Strategy Library (Atlas)
Database Schema
-- Strategy definitions (templates + user customized)
CREATE TABLE strategy_definitions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL,
slug TEXT UNIQUE NOT NULL, -- 'rsi_2_mean_reversion'
description TEXT,
category TEXT NOT NULL, -- 'trend', 'mean_reversion', 'momentum', 'regime', 'risk_mgmt'
timeframe TEXT NOT NULL, -- 'intraday', 'daily', 'weekly', 'monthly'
complexity TEXT NOT NULL, -- 'easy', 'medium', 'hard'
parameters JSONB NOT NULL DEFAULT '{}', -- configurable params
default_params JSONB NOT NULL DEFAULT '{}',
is_system BOOLEAN DEFAULT true, -- system template vs user-created
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Strategy signals (generated by scanner)
CREATE TABLE strategy_signals (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
ticker TEXT NOT NULL,
strategy_id UUID REFERENCES strategy_definitions(id),
signal TEXT NOT NULL, -- 'LONG', 'SHORT', 'NEUTRAL'
confidence DECIMAL(5,2) NOT NULL, -- 0-100
reasoning TEXT,
price_at_signal DECIMAL(12,2),
generated_at TIMESTAMPTZ DEFAULT NOW(),
expires_at TIMESTAMPTZ, -- signal validity
UNIQUE(ticker, strategy_id, generated_at::date)
);
-- Composite scores (aggregated)
CREATE TABLE composite_scores (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
ticker TEXT NOT NULL,
score DECIMAL(5,2) NOT NULL, -- -100 to +100
regime TEXT, -- current HMM regime when scored
signal_count INTEGER,
bullish_count INTEGER,
bearish_count INTEGER,
top_signals JSONB, -- [{strategy, signal, confidence}]
scored_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(ticker, scored_at::date)
);
CREATE INDEX idx_signals_ticker ON strategy_signals(ticker);
CREATE INDEX idx_signals_date ON strategy_signals(generated_at);
CREATE INDEX idx_composite_score ON composite_scores(score DESC);
10 Core Strategy Implementations
Each strategy must implement:
class BaseStrategy:
def generate_signal(self, ticker: str, data: pd.DataFrame) -> Signal:
"""Returns Signal(direction, confidence, reasoning)"""
pass
def backtest(self, ticker: str, start_date, end_date) -> BacktestResult:
"""Returns performance metrics"""
pass
| # | Strategy | Class Name | Key Parameters |
|---|---|---|---|
| 1 | Dual MA Crossover | DualMAStrategy |
fast_period=9, slow_period=21 |
| 2 | RSI(2) Mean Reversion | RSI2Strategy |
period=2, oversold=10, overbought=90 |
| 3 | MACD + RSI Combo | MACDRSIStrategy |
macd_fast=12, macd_slow=26, rsi_period=14 |
| 4 | Bollinger Band Reversion | BollingerStrategy |
period=20, std_dev=2 |
| 5 | ETF Momentum Rotation | MomentumRotationStrategy |
lookback=63, top_n=3 |
| 6 | Opening Range Breakout | ORBStrategy |
range_minutes=30 |
| 7 | ATR Trailing Stop (Turtle) | TurtleStrategy |
atr_period=20, atr_mult=2 |
| 8 | VIX Risk Overlay | VIXOverlayStrategy |
low_vix=15, high_vix=25 |
| 9 | Gap Fill | GapFillStrategy |
min_gap_pct=1.0 |
| 10 | 200-Day SMA Timing | SMA200Strategy |
period=200 |
| 11 | HMM Regime | HMMRegimeStrategy |
(existing) |
API Endpoints
GET /api/strategies/library - List all strategy definitions
GET /api/strategies/library/{slug} - Get strategy details + params
POST /api/strategies/library/{slug}/backtest - Run backtest
body: {ticker, start_date, end_date, params}
GET /api/strategies/signals/{ticker} - Get all signals for ticker
GET /api/strategies/signals/latest - Get today's signals (all tickers)
POST /api/strategies/scan - Trigger manual scan
body: {tickers: [...], strategies: [...]}
GET /api/strategies/composite/{ticker} - Get composite score for ticker
GET /api/strategies/composite/top - Top N by composite score
GET /api/strategies/composite/opportunities - AI-filtered opportunities
Phase 2: Nightly Scanner (Atlas)
Scanner Service
class NightlyScanner:
async def run(self):
"""Run after market close (4:30 PM PT)"""
# 1. Get universe from screener_stocks
tickers = await self.get_screener_universe()
# 2. Get current HMM regime
regime = await self.hmm_detector.predict("SPY")
# 3. Run each strategy against each ticker
for ticker in tickers:
data = await self.fetch_price_data(ticker)
for strategy in self.strategies:
signal = strategy.generate_signal(ticker, data)
await self.store_signal(ticker, strategy, signal)
# 4. Calculate composite scores
await self.calculate_composites(regime)
# 5. Notify user of opportunities
await self.send_summary()
Composite Scoring Formula
def calculate_composite(ticker, signals, regime):
total_score = 0
Created: Wed, Feb 18, 2026, 8:28 PM by bob
Updated: Wed, Feb 18, 2026, 8:28 PM
Last accessed: Wed, Apr 1, 2026, 11:52 PM
ID: e66f83fe-25d5-49ce-b4e3-b97d4b17b075