Phase A-C complete: 4 core factors, regime detection, signal tracking, Bayesian expectancy. chanmacro/ (32 files, ~4000 lines): - models: 12 enums + 15 Pydantic v2 models (DateAwareModel, MarketStateVector, etc.) - fetchers: OHLCV + Breadth (from data_provider) + Derivatives (new endpoint) - scoring: Price Structure / Breadth (quantile buckets) / OI Matrix (5 discrete states) / Volatility Regime - regime_detector: 3-state (TREND/RANGE/PANIC), factor-locked (Price+Breadth+Vol), versioned, 2-day confirmation - expectancy: SignalTracker (record+outcomes), TimeDecay (half-life=180d), BayesianExpectancyEngine (Empirical Bayes, Leveled, SufficiencyGuard) - validation: FactorValidator (IC/ICIR/Hit Ratio), RegimeValidator (MI/KL/ANOVA), TransitionValidator (stability) - CLI: fetch|score|regime|track|backfill|expectancy|validate|serve - tests: 52 passing (models, scoring, regime, expectancy) data_provider: - /api/derivatives endpoint: funding rate, OI, OI change, basis - _derivatives storage: same persist pattern as K-line (merge→lock→snapshot→atomic write) - background refresh every 60s Co-Authored-By: Claude <noreply@anthropic.com>
225 lines
7.6 KiB
Python
225 lines
7.6 KiB
Python
"""
|
|
database.py — SQLite schema initialization and connection management.
|
|
"""
|
|
|
|
import sqlite3
|
|
import os
|
|
from pathlib import Path
|
|
|
|
SCHEMA = """
|
|
-- ═══════════════════════════════════════════════
|
|
-- L0: Raw data tables
|
|
-- ═══════════════════════════════════════════════
|
|
|
|
CREATE TABLE IF NOT EXISTS ohlcv_daily (
|
|
date TEXT NOT NULL,
|
|
symbol TEXT NOT NULL DEFAULT 'BTC/USDT:USDT',
|
|
open REAL,
|
|
high REAL,
|
|
low REAL,
|
|
close REAL,
|
|
volume REAL,
|
|
ema20 REAL,
|
|
ema60 REAL,
|
|
ema120 REAL,
|
|
atr_14 REAL,
|
|
bb_width REAL,
|
|
adx_14 REAL,
|
|
PRIMARY KEY (date, symbol)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS breadth_daily (
|
|
date TEXT PRIMARY KEY,
|
|
total_tracked INTEGER DEFAULT 50,
|
|
advance_top50 INTEGER DEFAULT 0,
|
|
decline_top50 INTEGER DEFAULT 0,
|
|
above_ema20_top50 INTEGER DEFAULT 0,
|
|
new_highs_20d_top50 INTEGER DEFAULT 0,
|
|
btc_dominance REAL,
|
|
advance_top20 INTEGER DEFAULT 0,
|
|
advance_top30 INTEGER DEFAULT 0,
|
|
above_ema20_top20 INTEGER DEFAULT 0,
|
|
above_ema20_top30 INTEGER DEFAULT 0,
|
|
new_highs_20d_top20 INTEGER DEFAULT 0,
|
|
new_highs_20d_top30 INTEGER DEFAULT 0,
|
|
fetched_at TEXT DEFAULT (datetime('now'))
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS derivatives (
|
|
date TEXT NOT NULL,
|
|
symbol TEXT NOT NULL DEFAULT 'BTC/USDT:USDT',
|
|
funding_rate REAL,
|
|
open_interest REAL,
|
|
oi_24h_change_pct REAL,
|
|
long_liquidations REAL,
|
|
short_liquidations REAL,
|
|
basis_annualised_pct REAL,
|
|
source TEXT DEFAULT 'binance',
|
|
fetched_at TEXT DEFAULT (datetime('now')),
|
|
PRIMARY KEY (date, symbol)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS etf_flow (
|
|
date TEXT NOT NULL,
|
|
product TEXT NOT NULL,
|
|
net_flow_million REAL NOT NULL,
|
|
price REAL,
|
|
source TEXT DEFAULT 'farside',
|
|
fetched_at TEXT DEFAULT (datetime('now')),
|
|
PRIMARY KEY (date, product)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS stablecoin_supply (
|
|
date TEXT NOT NULL,
|
|
token TEXT NOT NULL,
|
|
chain TEXT NOT NULL DEFAULT 'all',
|
|
supply REAL NOT NULL,
|
|
source TEXT DEFAULT 'defillama',
|
|
fetched_at TEXT DEFAULT (datetime('now')),
|
|
PRIMARY KEY (date, token, chain)
|
|
);
|
|
|
|
-- ═══════════════════════════════════════════════
|
|
-- L3: Regime history
|
|
-- ═══════════════════════════════════════════════
|
|
|
|
CREATE TABLE IF NOT EXISTS regime_history (
|
|
date TEXT PRIMARY KEY,
|
|
regime TEXT NOT NULL,
|
|
confidence REAL,
|
|
regime_version TEXT NOT NULL DEFAULT 'v1_price_breadth_vol',
|
|
maturity_score REAL DEFAULT 50.0,
|
|
all_scores_json TEXT DEFAULT '{}',
|
|
prior_regime TEXT,
|
|
confirmation_days INTEGER DEFAULT 1,
|
|
created_at TEXT DEFAULT (datetime('now'))
|
|
);
|
|
|
|
-- ═══════════════════════════════════════════════
|
|
-- ★ signal_features — THE moat
|
|
-- ═══════════════════════════════════════════════
|
|
|
|
CREATE TABLE IF NOT EXISTS signal_features (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
date TEXT NOT NULL,
|
|
signal_type TEXT NOT NULL,
|
|
signal_version TEXT NOT NULL DEFAULT 'b3_v1',
|
|
symbol TEXT DEFAULT 'BTC/USDT:USDT',
|
|
|
|
-- ★★ Version control (most important fields)
|
|
regime_version TEXT NOT NULL DEFAULT 'v1_price_breadth_vol',
|
|
signal_grade TEXT,
|
|
signal_strength REAL,
|
|
|
|
-- Market State Vector snapshot
|
|
regime TEXT NOT NULL,
|
|
regime_confidence REAL,
|
|
regime_maturity_score REAL DEFAULT 50.0,
|
|
market_state_hash TEXT,
|
|
state_embedding TEXT DEFAULT '[]',
|
|
breadth_top20 REAL,
|
|
breadth_top30 REAL,
|
|
breadth_top50 REAL,
|
|
breadth_bucket TEXT,
|
|
breadth_divergence REAL,
|
|
oi_state TEXT,
|
|
volatility_regime TEXT,
|
|
price_structure_score REAL,
|
|
|
|
-- Chan context (V5+)
|
|
chan_trend_direction TEXT,
|
|
chan_pivot_count INTEGER,
|
|
chan_divergence_type TEXT,
|
|
|
|
-- Outcomes
|
|
entry_price REAL,
|
|
result_1d REAL,
|
|
result_3d REAL,
|
|
result_5d REAL,
|
|
result_7d REAL,
|
|
result_14d REAL,
|
|
max_favorable_excursion REAL,
|
|
max_adverse_excursion REAL,
|
|
is_win_7d INTEGER,
|
|
|
|
created_at TEXT DEFAULT (datetime('now'))
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_sf_regime ON signal_features(regime);
|
|
CREATE INDEX IF NOT EXISTS idx_sf_signal ON signal_features(signal_type);
|
|
CREATE INDEX IF NOT EXISTS idx_sf_oi_state ON signal_features(oi_state);
|
|
CREATE INDEX IF NOT EXISTS idx_sf_date ON signal_features(date);
|
|
CREATE INDEX IF NOT EXISTS idx_sf_state_hash ON signal_features(market_state_hash);
|
|
CREATE INDEX IF NOT EXISTS idx_sf_regime_version ON signal_features(regime_version);
|
|
CREATE INDEX IF NOT EXISTS idx_sf_signal_version ON signal_features(signal_version);
|
|
|
|
-- ═══════════════════════════════════════════════
|
|
-- Expectancy cache (raw counts, NOT posteriors)
|
|
-- ═══════════════════════════════════════════════
|
|
|
|
CREATE TABLE IF NOT EXISTS expectancy_cache (
|
|
state_hash TEXT NOT NULL,
|
|
signal_type TEXT NOT NULL,
|
|
wins_weighted REAL DEFAULT 0,
|
|
losses_weighted REAL DEFAULT 0,
|
|
sum_return_7d REAL DEFAULT 0,
|
|
sum_return_sq_7d REAL DEFAULT 0,
|
|
effective_samples REAL DEFAULT 0,
|
|
sufficiency TEXT DEFAULT 'INSUFFICIENT',
|
|
updated_at TEXT DEFAULT (datetime('now')),
|
|
PRIMARY KEY (state_hash, signal_type)
|
|
);
|
|
|
|
-- ═══════════════════════════════════════════════
|
|
-- Similarity outcome (KNN weight learning, Phase D)
|
|
-- ═══════════════════════════════════════════════
|
|
|
|
CREATE TABLE IF NOT EXISTS similarity_outcome (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
state_a_hash TEXT,
|
|
state_b_hash TEXT,
|
|
distance REAL,
|
|
actual_return_gap REAL,
|
|
dimension_weights_json TEXT DEFAULT '{}',
|
|
created_at TEXT DEFAULT (datetime('now'))
|
|
);
|
|
|
|
-- ═══════════════════════════════════════════════
|
|
-- chan_context — Chan theory integration (V1 empty)
|
|
-- ═══════════════════════════════════════════════
|
|
|
|
CREATE TABLE IF NOT EXISTS chan_context (
|
|
date TEXT NOT NULL,
|
|
timeframe TEXT NOT NULL DEFAULT '1d',
|
|
trend_direction TEXT,
|
|
trend_strength REAL,
|
|
pivot_count INTEGER,
|
|
pivot_level TEXT,
|
|
signal_type TEXT,
|
|
signal_strength REAL,
|
|
divergence_type TEXT,
|
|
chan_structure_score REAL,
|
|
alignment_score REAL,
|
|
raw_context_json TEXT DEFAULT '{}',
|
|
PRIMARY KEY (date, timeframe)
|
|
);
|
|
"""
|
|
|
|
|
|
def init_db(db_path: str = "data/macro.db") -> sqlite3.Connection:
|
|
"""Initialize database: create directory and all tables."""
|
|
Path(db_path).parent.mkdir(parents=True, exist_ok=True)
|
|
conn = sqlite3.connect(db_path)
|
|
conn.executescript(SCHEMA)
|
|
conn.commit()
|
|
return conn
|
|
|
|
|
|
def get_connection(db_path: str = "data/macro.db") -> sqlite3.Connection:
|
|
"""Get a database connection. Creates tables if first run."""
|
|
if not os.path.exists(db_path):
|
|
return init_db(db_path)
|
|
conn = sqlite3.connect(db_path)
|
|
conn.row_factory = sqlite3.Row
|
|
return conn
|