Files
signal-platform/tests/unit/test_signal_context_snapshot.py
dennisthiessen 80b4113280
Deploy / lint (push) Successful in 7s
Deploy / test (push) Successful in 1m1s
Deploy / deploy (push) Successful in 33s
feat: add strategy variant lab and signal context snapshots
Backtest report now includes research-only hold-to-horizon portfolio variants comparing raw vs residual 12-1 momentum, cutoff 80 vs 90, max 10 vs 15 positions, and SPY-200 risk scaling. A dynamic research recommendation panel flags residual momentum, cutoff 90, or regime scaling only when transparent promotion rules pass.

Adds signal_context_snapshots with migration 016 and captures one point-in-time context row per newly generated TradeSetup: setup fields, composite/dimensions, latest sentiment, latest fundamentals, and strategy_version=momentum_12_1_rr_time_v1. This is forward-only; no historical sentiment/fundamental backfill is attempted.

No live gate, paper-trade exit, or production ranking behavior changes.

Verification: 458 backend tests pass, ruff check app/ clean, frontend npm run build clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 16:25:04 +02:00

111 lines
3.2 KiB
Python

"""Tests for point-in-time signal context snapshots."""
from __future__ import annotations
import json
from datetime import date, datetime, timezone
import pytest
from app.models.fundamental import FundamentalData
from app.models.score import CompositeScore, DimensionScore
from app.models.sentiment import SentimentScore
from app.models.signal_context_snapshot import SignalContextSnapshot
from app.models.ticker import Ticker
from app.models.trade_setup import TradeSetup
from app.services import rr_scanner_service as rr
from tests.conftest import _test_session_factory # type: ignore
@pytest.fixture
async def session():
async with _test_session_factory() as s:
yield s
async def test_create_signal_context_snapshot_captures_latest_context(session):
now = datetime(2026, 7, 2, 12, tzinfo=timezone.utc)
ticker = Ticker(symbol="CTX")
session.add(ticker)
await session.flush()
session.add_all([
DimensionScore(
ticker_id=ticker.id,
dimension="technical",
score=71.0,
is_stale=False,
computed_at=now,
),
DimensionScore(
ticker_id=ticker.id,
dimension="momentum",
score=82.0,
is_stale=False,
computed_at=now,
),
CompositeScore(
ticker_id=ticker.id,
score=76.5,
is_stale=False,
weights_json='{"technical": 0.25}',
computed_at=now,
),
SentimentScore(
ticker_id=ticker.id,
classification="BULLISH",
confidence=78,
source="test",
timestamp=now,
reasoning="",
citations_json="[]",
recommendation="BUY",
),
FundamentalData(
ticker_id=ticker.id,
pe_ratio=25.0,
revenue_growth=0.18,
earnings_surprise=0.05,
market_cap=1_000_000_000.0,
next_earnings_date=date(2026, 8, 1),
fetched_at=now,
unavailable_fields_json="{}",
),
])
setup = TradeSetup(
ticker_id=ticker.id,
direction="long",
entry_price=100.0,
stop_loss=95.0,
target=120.0,
rr_ratio=4.0,
composite_score=76.5,
detected_at=now,
confidence_score=64.0,
momentum_percentile=88.0,
recommended_action="LONG_HIGH",
risk_level="Low",
)
session.add(setup)
await session.flush()
await rr._create_signal_context_snapshots(session, [setup])
await session.commit()
row = (await session.get(SignalContextSnapshot, 1))
assert row is not None
assert row.trade_setup_id == setup.id
assert row.strategy_version == rr.STRATEGY_VERSION
assert row.momentum_percentile == 88.0
score = json.loads(row.score_context_json)
sentiment = json.loads(row.sentiment_context_json)
fundamental = json.loads(row.fundamental_context_json)
assert score["composite_score"] == 76.5
assert score["dimensions"]["technical"]["score"] == 71.0
assert sentiment["classification"] == "BULLISH"
assert sentiment["confidence"] == 78
assert fundamental["pe_ratio"] == 25.0
assert fundamental["next_earnings_date"] == "2026-08-01"