Files
momentum-system/tests/synthetic.py
momentum devandClaude Opus 4.8 007cd95c79 Baseline before Postgres migration (WS0)
Snapshot before the DuckDB->PostgreSQL migration. data/, archive/, the 232MB
rebuild zip, the Windows .venv, and .env are excluded via .gitignore.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 23:39:29 -04:00

91 lines
3.1 KiB
Python

"""Synthetic minute-bar builders shared across the test suite."""
from __future__ import annotations
from datetime import datetime, timedelta
import pandas as pd
SESSION_DATE = datetime(2026, 6, 11).date()
# Bars are stored UTC-naive throughout the pipeline (ingestion converts to UTC
# then drops tzinfo). 13:30 UTC == 09:30 America/New_York in June (EDT), so this
# fixture exercises the same timezone path as live data: the watcher converts
# the bar timestamp UTC -> Eastern before the entry-cutoff check.
SESSION_OPEN = datetime(2026, 6, 11, 13, 30) # 09:30 ET expressed in UTC
def make_bars(specs: list[tuple[float, float, float, float, int]],
start: datetime | None = None,
symbol: str = "TEST") -> pd.DataFrame:
"""Build a minute-bar frame from (open, high, low, close, volume) tuples."""
start = start or SESSION_OPEN
rows = []
cum_pv = 0.0
cum_v = 0
for i, (o, h, l, c, v) in enumerate(specs):
ts = start + timedelta(minutes=i)
typical = (h + l + c) / 3.0
cum_pv += typical * v
cum_v += v
rows.append(
{
"symbol": symbol,
"timestamp": ts,
"session_date": SESSION_DATE,
"is_premarket": False,
"is_regular_hours": True,
"open": o,
"high": h,
"low": l,
"close": c,
"volume": v,
"vwap": cum_pv / cum_v if cum_v else c,
"quality_score": 1.0,
}
)
return pd.DataFrame(rows)
def bull_flag_bars(symbol: str = "TEST") -> pd.DataFrame:
"""Gap-up + impulse + light pullback + breakout: should evaluate ready.
Pair with previous_close=10.0 and avg_daily_volume=500_000 so the gap
(~25%) and relative volume criteria pass.
"""
specs = [
# base
(12.50, 12.60, 12.45, 12.55, 60_000),
(12.55, 12.65, 12.50, 12.60, 55_000),
(12.60, 12.70, 12.55, 12.65, 50_000),
# impulse leg on expanding volume
(12.65, 12.95, 12.62, 12.92, 120_000),
(12.92, 13.25, 12.90, 13.20, 150_000),
(13.20, 13.55, 13.18, 13.50, 180_000),
(13.50, 13.85, 13.48, 13.80, 200_000),
# shallow pullback on lighter volume (holds above vwap)
(13.80, 13.82, 13.62, 13.66, 70_000),
(13.66, 13.70, 13.55, 13.60, 55_000),
(13.60, 13.66, 13.52, 13.58, 45_000),
# breakout bar through pullback high
(13.58, 13.95, 13.56, 13.92, 220_000),
(13.92, 14.05, 13.88, 14.00, 190_000),
]
return make_bars(specs, symbol=symbol)
def fading_bars(symbol: str = "FADE") -> pd.DataFrame:
"""Steady downtrend below vwap: should evaluate blocked."""
specs = []
price = 10.0
for i in range(15):
o = price
c = price - 0.08
specs.append((o, o + 0.02, c - 0.03, c, 40_000))
price = c
return make_bars(specs, symbol=symbol)
def tiny_bars(symbol: str = "TINY") -> pd.DataFrame:
"""Too few bars: should block with insufficient_data."""
return make_bars([(5.0, 5.1, 4.9, 5.05, 10_000)] * 3, symbol=symbol)