Files
Chan/ChanMacro/tests/conftest.py
T
jackyu66gitandClaude 71951019fb chanmacro: Signal Expectancy Engine V1 — Market Memory System
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>
2026-06-24 17:44:55 +08:00

135 lines
4.3 KiB
Python

"""
tests/conftest.py — Shared fixtures for ChanMacro tests.
"""
import os
import sys
import pytest
import sqlite3
import numpy as np
import pandas as pd
from datetime import date, timedelta
from pathlib import Path
# Ensure package root on path
sys.path.insert(0, str(Path(__file__).parent.parent))
@pytest.fixture
def db_path(tmp_path):
"""Create a temporary SQLite database with full mock data."""
db = str(tmp_path / "test_macro.db")
from database import init_db
conn = init_db(db)
np.random.seed(42)
base = date(2025, 9, 1)
n_days = 300
# Generate realistic price series with 3 regime periods
prices = [90000]
regimes = []
for i in range(n_days):
if i < 100:
ret = np.random.normal(0.003, 0.015)
regime = "TREND"
elif i < 200:
ret = np.random.normal(0.000, 0.012)
regime = "RANGE"
else:
ret = np.random.normal(-0.003, 0.025)
regime = "PANIC"
prices.append(prices[-1] * (1 + ret))
regimes.append(regime)
for i in range(n_days):
d = base + timedelta(days=i)
c = prices[i]
r = regimes[i]
# OHLCV
conn.execute("""
INSERT OR REPLACE INTO ohlcv_daily
(date,symbol,open,high,low,close,volume,ema20,ema60,ema120,atr_14,bb_width,adx_14)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)
""", (
d.strftime("%Y-%m-%d"), "BTC/USDT:USDT",
c * 0.99, c * 1.03, c * 0.97, c, 1000,
c * (0.98 if r == "TREND" else 1.02 if r == "PANIC" else 1.0),
c * (0.95 if r == "TREND" else 1.05 if r == "PANIC" else 1.0),
c * (0.90 if r == "TREND" else 1.10 if r == "PANIC" else 1.0),
c * (0.02 if r == "PANIC" else 0.015),
4.5, 28.0 if r == "TREND" else 18.0,
))
# Breadth
adv = 42 if r == "TREND" else 25 if r == "RANGE" else 8
conn.execute("""
INSERT OR REPLACE INTO breadth_daily
(date,total_tracked,advance_top50,decline_top50,above_ema20_top50,
new_highs_20d_top50,advance_top30,advance_top20,
above_ema20_top30,above_ema20_top20,new_highs_20d_top30,new_highs_20d_top20)
VALUES (?,50,?,?,?,?,?,?,?,?,?,?)
""", (
d.strftime("%Y-%m-%d"), adv, 50 - adv, adv, min(adv, 15),
int(adv * 0.7), int(adv * 0.5), int(adv * 0.7), int(adv * 0.5),
min(int(adv * 0.7), 12), min(int(adv * 0.5), 8),
))
# Derivatives
oi_chg = 3.5 if r == "TREND" else 0.5 if r == "RANGE" else -2.0
conn.execute("""
INSERT OR REPLACE INTO derivatives
(date,symbol,funding_rate,open_interest,oi_24h_change_pct,
long_liquidations,short_liquidations,basis_annualised_pct)
VALUES (?,?,?,?,?,?,?,?)
""", (
d.strftime("%Y-%m-%d"), "BTC/USDT:USDT",
0.0001 + np.random.normal(0, 0.0002),
35e9, oi_chg + np.random.normal(0, 1.0),
50e6 * np.random.random(), 30e6 * np.random.random(),
8.5 if r == "TREND" else 3.0,
))
# Regime history
conn.execute("""
INSERT OR REPLACE INTO regime_history
(date,regime,confidence,regime_version,maturity_score,all_scores_json,confirmation_days)
VALUES (?,?,?,?,?,?,?)
""", (d.strftime("%Y-%m-%d"), r, 0.75, "v1_price_breadth_vol", 50, "{}", 1))
conn.commit()
conn.close()
# Override config to use test DB
from config import config
old_db = config.db_path
config.db_path = db
yield db
config.db_path = old_db
@pytest.fixture
def sample_state(db_path):
"""Build a MarketStateVector for a known test date."""
from models import (
MarketStateVector, MarketRegime, BreadthBucket,
OIState, VolRegime,
)
state = MarketStateVector(
date=date(2026, 3, 15),
regime=MarketRegime.TREND,
regime_confidence=0.82,
regime_version="v1_price_breadth_vol",
regime_maturity_score=55.0,
breadth_top20=82.0,
breadth_top30=78.0,
breadth_top50=74.0,
breadth_bucket=BreadthBucket.STRONG,
breadth_divergence=8.0,
oi_state=OIState.NEW_LONGS,
volatility_regime=VolRegime.NORMAL_VOL,
)
state.market_state_hash = state.compute_hash()
return state