135 lines
4.3 KiB
Python
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
|