from __future__ import annotations

import asyncio

from libs.backtest.domain import WalkForwardAggregate, WalkForwardGapStats, WalkForwardSummary
from libs.intraday.domain import CacheParams, IntradayConfig, IntradayMetrics, StrategyParams

from apps.intraday_bt.momentum_research import (
    _normalize_momentum_research_strategy,
    _quarterly_score_payload,
    _wfv_score_payload,
    MomentumResearchContext,
    MomentumResearchSnapshotStore,
    build_momentum_strategy,
    build_momentum_research_context,
    group_trading_days_by_quarter,
    intraday_metrics_to_momentum_split_result,
    simulate_momentum_params,
)


def _summary(
    *,
    mean_return: float,
    positive_rate: float,
    worst_return: float,
) -> WalkForwardSummary:
    return WalkForwardSummary(
        train_days=84,
        test_days=21,
        step_days=21,
        fold_count=5,
        folds=[],
        train_aggregate=WalkForwardAggregate(mean_return_pct=4.0),
        test_aggregate=WalkForwardAggregate(
            mean_return_pct=mean_return,
            median_return_pct=mean_return,
            worst_return_pct=worst_return,
            positive_fold_rate_pct=positive_rate,
            mean_profit_factor=1.5,
            mean_max_drawdown_pct=4.0,
            mean_trade_count=40.0,
            mean_win_rate=0.52,
        ),
        gap_stats=WalkForwardGapStats(
            mean_train_test_return_gap_pct=2.0,
            worst_train_test_return_gap_pct=4.0,
            fold_return_cv=0.4,
        ),
        engine_reliability_ratio=1.0,
    )


def test_wfv_score_prefers_better_walk_forward_and_holdout() -> None:
    strong = _wfv_score_payload(
        _summary(mean_return=2.5, positive_rate=80.0, worst_return=-1.0),
        IntradayMetrics(total_return_pct=0.10, sharpe_ratio=1.4),
    )
    weak = _wfv_score_payload(
        _summary(mean_return=0.3, positive_rate=40.0, worst_return=-6.0),
        IntradayMetrics(total_return_pct=0.01, sharpe_ratio=0.2),
    )

    assert strong["selection_score"] > weak["selection_score"]
    assert strong["holdout_return_pct"] > weak["holdout_return_pct"]


def test_wfv_score_rewards_better_holdout_loss_containment() -> None:
    summary = _summary(mean_return=1.2, positive_rate=60.0, worst_return=-2.0)
    strong = _wfv_score_payload(
        summary,
        IntradayMetrics(
            total_return_pct=0.06,
            sharpe_ratio=0.9,
            avg_loss_day_pct=-0.004,
            tail_loss_20_pct=-0.009,
            loss_containment_score=89.0,
        ),
    )
    weak = _wfv_score_payload(
        summary,
        IntradayMetrics(
            total_return_pct=0.06,
            sharpe_ratio=0.9,
            avg_loss_day_pct=-0.012,
            tail_loss_20_pct=-0.026,
            loss_containment_score=54.0,
        ),
    )

    assert strong["selection_score"] > weak["selection_score"]
    assert strong["holdout_loss_containment_score"] > weak["holdout_loss_containment_score"]


def test_group_trading_days_by_quarter_preserves_calendar_order() -> None:
    grouped = group_trading_days_by_quarter(
        [
            "2025-01-02",
            "2025-03-31",
            "2025-04-01",
            "2025-07-01",
            "2025-10-01",
        ]
    )
    assert grouped == [
        ("2025Q1", ["2025-01-02", "2025-03-31"]),
        ("2025Q2", ["2025-04-01"]),
        ("2025Q3", ["2025-07-01"]),
        ("2025Q4", ["2025-10-01"]),
    ]


def test_quarterly_score_prefers_stable_positive_quarters() -> None:
    wfv = _wfv_score_payload(
        _summary(mean_return=1.8, positive_rate=80.0, worst_return=-1.0),
        IntradayMetrics(total_return_pct=0.08, sharpe_ratio=1.1),
    )
    strong, _ = _quarterly_score_payload(
        wfv,
        [
            ("2025Q1", IntradayMetrics(total_return_pct=0.08, sharpe_ratio=1.2, max_drawdown_pct=-0.03)),
            ("2025Q2", IntradayMetrics(total_return_pct=0.06, sharpe_ratio=1.0, max_drawdown_pct=-0.02)),
            ("2025Q3", IntradayMetrics(total_return_pct=0.07, sharpe_ratio=1.1, max_drawdown_pct=-0.03)),
            ("2025Q4", IntradayMetrics(total_return_pct=0.05, sharpe_ratio=0.9, max_drawdown_pct=-0.02)),
        ],
    )
    weak, _ = _quarterly_score_payload(
        wfv,
        [
            ("2025Q1", IntradayMetrics(total_return_pct=0.16, sharpe_ratio=1.8, max_drawdown_pct=-0.08)),
            ("2025Q2", IntradayMetrics(total_return_pct=-0.09, sharpe_ratio=-0.7, max_drawdown_pct=-0.10)),
            ("2025Q3", IntradayMetrics(total_return_pct=0.02, sharpe_ratio=0.2, max_drawdown_pct=-0.05)),
            ("2025Q4", IntradayMetrics(total_return_pct=-0.03, sharpe_ratio=-0.3, max_drawdown_pct=-0.06)),
        ],
    )

    assert strong["quarter_mean_return_pct"] > weak["quarter_mean_return_pct"]
    assert strong["quarter_worst_return_pct"] > weak["quarter_worst_return_pct"]
    assert strong["quarterly_selection_score"] > weak["quarterly_selection_score"]


def test_build_momentum_strategy_applies_overrides_without_mutating_base() -> None:
    config = IntradayConfig(strategy_mode="momentum", strategy=StrategyParams(top_n=5, max_vix=30.0))
    updated = build_momentum_strategy(config, {"top_n": 4, "max_vix": 28.0})

    assert updated.top_n == 4
    assert updated.max_vix == 28.0
    assert config.strategy.top_n == 5
    assert config.strategy.max_vix == 30.0


def test_normalize_momentum_research_strategy_forces_reset_simple_mode() -> None:
    strategy = StrategyParams(
        top_n=5,
        compound_returns=True,
        daily_budget_reset=False,
    )

    normalized = _normalize_momentum_research_strategy(strategy)

    assert normalized.compound_returns is False
    assert normalized.daily_budget_reset is True
    assert strategy.compound_returns is True
    assert strategy.daily_budget_reset is False


def test_intraday_metrics_to_momentum_split_result_maps_simple_returns() -> None:
    strategy = StrategyParams(top_n=5)
    result = intraday_metrics_to_momentum_split_result(
        IntradayMetrics(
            run_id="mwf",
            trading_days=20,
            days_with_trades=8,
            total_trades=12,
            total_return_pct=0.1234,
            annualized_return_pct=0.4567,
            max_drawdown_pct=-0.089,
            sharpe_ratio=1.8,
            profit_factor=1.4,
            win_rate=0.55,
        ),
        strategy,
    )

    assert result.run_id == "mwf"
    assert result.trade_count == 12
    assert result.total_return_pct == 12.34
    assert result.annualized_return_pct == 45.67
    assert result.max_drawdown_pct == 8.9
    assert result.avg_gross_exposure_pct == 100.0
    assert result.days_in_market_pct == 40.0


def test_build_momentum_research_context_uses_snapshot_cache(tmp_path, monkeypatch) -> None:
    config = IntradayConfig(
        strategy_mode="momentum",
        strategy=StrategyParams(top_n=5),
        cache=CacheParams(enabled=True, dir=str(tmp_path / "intraday")),
    )

    trading_days = ["2025-01-02", "2025-01-03"]
    daily_bars = {
        "AAA": [{"date": "2025-01-02", "close": 10.0}],
        "BBB": [{"date": "2025-01-02", "close": 11.0}],
    }
    candidates = {"2025-01-02": ["AAA"], "2025-01-03": ["BBB"]}
    all_intraday = {
        "2025-01-02": {"AAA": [{"timestamp": "2025-01-02T14:30:00+00:00", "open": 10.0, "high": 10.5, "low": 9.9, "close": 10.3, "volume": 1000}]},
        "2025-01-03": {"BBB": [{"timestamp": "2025-01-03T14:30:00+00:00", "open": 11.0, "high": 11.4, "low": 10.8, "close": 11.2, "volume": 1200}]},
    }

    async def _resolve_universe(_universe, _client):
        return ["AAA", "BBB"]

    async def _get_trading_days(_client, _start, _end, lookback=0):
        assert lookback == 0
        return trading_days

    async def _fetch_daily_bars_bulk(*args, **kwargs):
        return daily_bars

    def _pre_screen_candidates(*args, **kwargs):
        return candidates

    async def _fetch_intraday_bulk(*args, **kwargs):
        return all_intraday

    monkeypatch.setattr("apps.intraday_bt.momentum_research.resolve_universe", _resolve_universe)
    monkeypatch.setattr("apps.intraday_bt.momentum_research.get_trading_days", _get_trading_days)
    monkeypatch.setattr("apps.intraday_bt.momentum_research.fetch_daily_bars_bulk", _fetch_daily_bars_bulk)
    monkeypatch.setattr("apps.intraday_bt.momentum_research.momentum_pre_screen_candidates", _pre_screen_candidates)
    monkeypatch.setattr("apps.intraday_bt.momentum_research.fetch_intraday_bulk", _fetch_intraday_bulk)

    first = asyncio.run(build_momentum_research_context(config, "2025-01-02", "2025-01-03", client=None))
    assert first.candidate_pairs == 2
    assert first.research_snapshot_key is not None

    async def _should_not_fetch(*args, **kwargs):
        raise AssertionError("fetch path should not run after snapshot is saved")

    def _should_not_screen(*args, **kwargs):
        raise AssertionError("screen path should not run after snapshot is saved")

    monkeypatch.setattr("apps.intraday_bt.momentum_research.fetch_daily_bars_bulk", _should_not_fetch)
    monkeypatch.setattr("apps.intraday_bt.momentum_research.momentum_pre_screen_candidates", _should_not_screen)
    monkeypatch.setattr("apps.intraday_bt.momentum_research.fetch_intraday_bulk", _should_not_fetch)

    second = asyncio.run(build_momentum_research_context(config, "2025-01-02", "2025-01-03", client=None))
    assert second.research_snapshot_key == first.research_snapshot_key
    assert second.candidates == candidates
    assert second.all_intraday == all_intraday


def test_momentum_research_snapshot_key_changes_when_seed_overlay_changes(tmp_path) -> None:
    config_a = IntradayConfig(
        strategy_mode="momentum",
        strategy=StrategyParams(top_n=5, candidate_source_mode="intraday_first"),
        cache=CacheParams(enabled=True, dir=str(tmp_path / "intraday")),
    )
    config_b = IntradayConfig(
        strategy_mode="momentum",
        strategy=StrategyParams(
            top_n=5,
            candidate_source_mode="intraday_first",
            candidate_seed_liquid_overlay_slots=1,
        ),
        cache=CacheParams(enabled=True, dir=str(tmp_path / "intraday")),
    )

    key_a = MomentumResearchSnapshotStore(tmp_path / "snapshots").build_key(
        config_a,
        start_date="2025-01-02",
        end_date="2025-01-03",
        tickers=["AAA"],
        trading_days=["2025-01-02", "2025-01-03"],
    )
    key_b = MomentumResearchSnapshotStore(tmp_path / "snapshots").build_key(
        config_b,
        start_date="2025-01-02",
        end_date="2025-01-03",
        tickers=["AAA"],
        trading_days=["2025-01-02", "2025-01-03"],
    )

    assert key_a != key_b


def test_build_momentum_research_context_applies_seed_overlay_before_intraday_fetch(
    tmp_path,
    monkeypatch,
) -> None:
    config = IntradayConfig(
        strategy_mode="momentum",
        strategy=StrategyParams(
            top_n=5,
            candidate_source_mode="intraday_first",
            candidate_seed_liquid_overlay_slots=1,
        ),
        cache=CacheParams(enabled=False, dir=str(tmp_path / "intraday")),
    )

    async def _resolve_universe(_universe, _client):
        return ["AAA", "BBB"]

    async def _get_trading_days(_client, _start, _end, lookback=0):
        assert lookback == 0
        return ["2025-01-02"]

    async def _fetch_daily_bars_bulk(*args, **kwargs):
        return {"AAA": [{"date": "2025-01-02", "close": 10.0}], "BBB": [{"date": "2025-01-02", "close": 11.0}]}

    def _enrichment(*args, **kwargs):
        return {}

    def _seed_candidates(*args, **kwargs):
        return {"2025-01-02": ["AAA"]}

    def _augment(candidates, *args, **kwargs):
        assert candidates == {"2025-01-02": ["AAA"]}
        return {"2025-01-02": ["AAA", "BBB"]}, {}

    captured: dict[str, object] = {}

    async def _fetch_intraday_bulk(candidates, *args, **kwargs):
        captured["candidates"] = candidates
        return {
            "2025-01-02": {
                "AAA": [{"timestamp": "2025-01-02T14:30:00+00:00", "open": 10.0, "high": 10.2, "low": 9.9, "close": 10.1, "volume": 1000}],
                "BBB": [{"timestamp": "2025-01-02T14:30:00+00:00", "open": 11.0, "high": 11.2, "low": 10.9, "close": 11.1, "volume": 1000}],
            }
        }

    def _intraday_first_candidates(*args, **kwargs):
        return {"2025-01-02": ["AAA", "BBB"]}

    monkeypatch.setattr("apps.intraday_bt.momentum_research.resolve_universe", _resolve_universe)
    monkeypatch.setattr("apps.intraday_bt.momentum_research.get_trading_days", _get_trading_days)
    monkeypatch.setattr("apps.intraday_bt.momentum_research.fetch_daily_bars_bulk", _fetch_daily_bars_bulk)
    monkeypatch.setattr("apps.intraday_bt.momentum_research._momentum_enrichment_for_days", _enrichment)
    monkeypatch.setattr("apps.intraday_bt.momentum_research._momentum_intraday_seed_candidates", _seed_candidates)
    monkeypatch.setattr(
        "apps.intraday_bt.momentum_research._augment_momentum_seed_candidates_with_liquid_overlay",
        _augment,
    )
    monkeypatch.setattr("apps.intraday_bt.momentum_research.fetch_intraday_bulk", _fetch_intraday_bulk)
    monkeypatch.setattr(
        "apps.intraday_bt.momentum_research.momentum_intraday_first_candidates",
        _intraday_first_candidates,
    )

    context = asyncio.run(build_momentum_research_context(config, "2025-01-02", "2025-01-02", client=None))

    assert captured["candidates"] == {"2025-01-02": ["AAA", "BBB"]}
    assert context.candidates == {"2025-01-02": ["AAA", "BBB"]}


def test_build_momentum_research_context_fetches_all_tickers_for_candidate_stage_catalyst(
    tmp_path,
    monkeypatch,
) -> None:
    config = IntradayConfig(
        strategy_mode="momentum",
        strategy=StrategyParams(
            top_n=5,
            candidate_source_mode="intraday_first",
            candidate_seed_event_overlay_slots=1,
        ),
        cache=CacheParams(enabled=False, dir=str(tmp_path / "intraday")),
    )

    async def _resolve_universe(_universe, _client):
        return ["AAA", "BBB", "CCC"]

    async def _get_trading_days(_client, _start, _end, lookback=0):
        assert lookback == 0
        return ["2025-01-02"]

    async def _fetch_daily_bars_bulk(*args, **kwargs):
        return {
            "AAA": [{"date": "2025-01-02", "close": 10.0}],
            "BBB": [{"date": "2025-01-02", "close": 11.0}],
            "CCC": [{"date": "2025-01-02", "close": 12.0}],
        }

    def _enrichment(*args, **kwargs):
        return {
            "AAA": {"2025-01-02": {"gap_pct": 0.03}},
            "BBB": {"2025-01-02": {"gap_pct": 0.02}},
            "CCC": {"2025-01-02": {"gap_pct": 0.01}},
        }

    def _seed_candidates(*args, **kwargs):
        return {"2025-01-02": ["AAA"]}

    async def _fetch_filing_event_features_bulk(tickers, *args, **kwargs):
        assert tickers == ["AAA", "BBB", "CCC"]
        return {}

    async def _fetch_intraday_bulk(*args, **kwargs):
        return {}

    monkeypatch.setattr("apps.intraday_bt.momentum_research.resolve_universe", _resolve_universe)
    monkeypatch.setattr("apps.intraday_bt.momentum_research.get_trading_days", _get_trading_days)
    monkeypatch.setattr("apps.intraday_bt.momentum_research.fetch_daily_bars_bulk", _fetch_daily_bars_bulk)
    monkeypatch.setattr("apps.intraday_bt.momentum_research._momentum_enrichment_for_days", _enrichment)
    monkeypatch.setattr("apps.intraday_bt.momentum_research._momentum_intraday_seed_candidates", _seed_candidates)
    monkeypatch.setattr(
        "apps.intraday_bt.momentum_research.fetch_filing_event_features_bulk",
        _fetch_filing_event_features_bulk,
    )
    monkeypatch.setattr("apps.intraday_bt.momentum_research.fetch_intraday_bulk", _fetch_intraday_bulk)
    monkeypatch.setattr(
        "apps.intraday_bt.momentum_research.momentum_intraday_first_candidates",
        lambda *args, **kwargs: {},
    )

    context = asyncio.run(build_momentum_research_context(config, "2025-01-02", "2025-01-02", client=None))

    assert context.candidates == {}


def test_simulate_momentum_params_recomputes_candidates_for_strategy() -> None:
    context = MomentumResearchContext(
        config=IntradayConfig(
            strategy_mode="momentum",
            strategy=StrategyParams(top_n=2),
        ),
        tickers=["AAA", "BBB"],
        ticker_sectors={},
        trading_days=["2026-01-05"],
        daily_bars={
            "AAA": [
                {"date": "2026-01-02", "open": 10.0, "high": 10.2, "low": 9.8, "close": 10.0, "volume": 1000},
                {"date": "2026-01-05", "open": 10.3, "high": 10.8, "low": 10.2, "close": 10.6, "volume": 2000},
            ],
            "BBB": [
                {"date": "2026-01-02", "open": 11.0, "high": 11.1, "low": 10.9, "close": 11.0, "volume": 1000},
                {"date": "2026-01-05", "open": 11.4, "high": 11.9, "low": 11.3, "close": 11.7, "volume": 2000},
            ],
        },
        all_intraday={
            "2026-01-05": {
                "AAA": [
                    {"timestamp": "2026-01-05T14:30:00+00:00", "open": 10.3, "high": 10.5, "low": 10.2, "close": 10.4, "volume": 50000},
                    {"timestamp": "2026-01-05T14:35:00+00:00", "open": 10.4, "high": 10.6, "low": 10.3, "close": 10.5, "volume": 50000},
                    {"timestamp": "2026-01-05T14:40:00+00:00", "open": 10.5, "high": 10.7, "low": 10.4, "close": 10.6, "volume": 50000},
                    {"timestamp": "2026-01-05T14:45:00+00:00", "open": 10.6, "high": 10.8, "low": 10.5, "close": 10.7, "volume": 50000},
                    {"timestamp": "2026-01-05T14:50:00+00:00", "open": 10.7, "high": 10.9, "low": 10.6, "close": 10.8, "volume": 50000},
                    {"timestamp": "2026-01-05T20:55:00+00:00", "open": 10.9, "high": 11.0, "low": 10.8, "close": 10.95, "volume": 50000},
                ],
                "BBB": [
                    {"timestamp": "2026-01-05T14:30:00+00:00", "open": 11.4, "high": 11.5, "low": 11.3, "close": 11.45, "volume": 50000},
                    {"timestamp": "2026-01-05T14:35:00+00:00", "open": 11.45, "high": 11.6, "low": 11.4, "close": 11.55, "volume": 50000},
                    {"timestamp": "2026-01-05T14:40:00+00:00", "open": 11.55, "high": 11.8, "low": 11.5, "close": 11.75, "volume": 50000},
                    {"timestamp": "2026-01-05T14:45:00+00:00", "open": 11.75, "high": 11.9, "low": 11.7, "close": 11.85, "volume": 50000},
                    {"timestamp": "2026-01-05T14:50:00+00:00", "open": 11.85, "high": 12.0, "low": 11.8, "close": 11.95, "volume": 50000},
                    {"timestamp": "2026-01-05T20:55:00+00:00", "open": 11.9, "high": 12.0, "low": 11.8, "close": 11.92, "volume": 50000},
                ],
            }
        },
        daily_enrichment={
            "AAA": {"2026-01-05": {"gap_pct": 0.03, "ret_5d": 0.01, "entropy_20d": 0.8, "avg_dollar_vol_30d": 20_000_000.0, "atr_14": 1.0, "event_flag": False}},
            "BBB": {"2026-01-05": {"gap_pct": 0.03, "ret_5d": 0.02, "entropy_20d": 0.7, "avg_dollar_vol_30d": 25_000_000.0, "atr_14": 1.1, "event_flag": True, "event_score": 1.0}},
        },
        vix_by_day=None,
        candidates={"2026-01-05": ["AAA", "BBB"]},
        candidate_pairs=2,
        research_snapshot_key=None,
    )

    strategy = StrategyParams(
        top_n=2,
        entry_minutes_after_open=20,
        min_morning_gain_pct=0.0,
        min_entry_volume=0,
        candidate_require_event_flag=True,
        exit_minutes_before_close=5,
    )

    day_results, metrics = simulate_momentum_params(context, strategy, ["2026-01-05"], run_id="test")

    assert metrics.total_trades == 1
    assert len(day_results) == 1
    assert day_results[0].trades[0].ticker == "BBB"


def test_simulate_momentum_params_recomputes_intraday_first_candidates_for_strategy() -> None:
    context = MomentumResearchContext(
        config=IntradayConfig(
            strategy_mode="momentum",
            strategy=StrategyParams(top_n=2, candidate_source_mode="intraday_first"),
        ),
        tickers=["AAA", "BBB"],
        ticker_sectors={},
        trading_days=["2026-01-05"],
        daily_bars={},
        all_intraday={
            "2026-01-05": {
                "AAA": [
                    {"timestamp": "2026-01-05T14:30:00+00:00", "open": 10.0, "high": 10.1, "low": 9.9, "close": 10.0, "volume": 60_000},
                    {"timestamp": "2026-01-05T14:35:00+00:00", "open": 10.0, "high": 10.2, "low": 9.9, "close": 10.1, "volume": 60_000},
                    {"timestamp": "2026-01-05T14:40:00+00:00", "open": 10.1, "high": 10.5, "low": 10.0, "close": 10.3, "volume": 60_000},
                    {"timestamp": "2026-01-05T14:45:00+00:00", "open": 10.3, "high": 10.4, "low": 10.1, "close": 10.2, "volume": 60_000},
                    {"timestamp": "2026-01-05T14:50:00+00:00", "open": 10.2, "high": 10.3, "low": 10.1, "close": 10.2, "volume": 60_000},
                    {"timestamp": "2026-01-05T20:55:00+00:00", "open": 10.2, "high": 10.3, "low": 10.0, "close": 10.1, "volume": 60_000},
                ],
                "BBB": [
                    {"timestamp": "2026-01-05T14:30:00+00:00", "open": 11.0, "high": 11.1, "low": 10.9, "close": 11.0, "volume": 70_000},
                    {"timestamp": "2026-01-05T14:35:00+00:00", "open": 11.0, "high": 11.2, "low": 10.9, "close": 11.1, "volume": 70_000},
                    {"timestamp": "2026-01-05T14:40:00+00:00", "open": 11.1, "high": 11.6, "low": 11.0, "close": 11.4, "volume": 70_000},
                    {"timestamp": "2026-01-05T14:45:00+00:00", "open": 11.4, "high": 11.8, "low": 11.3, "close": 11.7, "volume": 70_000},
                    {"timestamp": "2026-01-05T14:50:00+00:00", "open": 11.7, "high": 11.9, "low": 11.6, "close": 11.8, "volume": 70_000},
                    {"timestamp": "2026-01-05T20:55:00+00:00", "open": 11.8, "high": 11.9, "low": 11.7, "close": 11.85, "volume": 70_000},
                ],
            }
        },
        daily_enrichment={
            "AAA": {"2026-01-05": {"gap_pct": 0.01, "avg_daily_vol_14d": 1_000_000.0}},
            "BBB": {"2026-01-05": {"gap_pct": 0.01, "avg_daily_vol_14d": 1_000_000.0}},
        },
        vix_by_day=None,
        candidates={"2026-01-05": ["AAA", "BBB"]},
        candidate_pairs=2,
        research_snapshot_key=None,
    )

    strategy = StrategyParams(
        top_n=2,
        candidate_source_mode="intraday_first",
        candidate_final_max_per_day=1,
        entry_minutes_after_open=10,
        confirmation_minutes_after_entry=5,
        min_confirmation_return_pct=0.0,
        min_morning_gain_pct=0.01,
        min_entry_volume=0,
        exit_minutes_before_close=5,
    )

    day_results, metrics = simulate_momentum_params(context, strategy, ["2026-01-05"], run_id="test_if")

    assert metrics.total_trades == 1
    assert len(day_results) == 1
    assert day_results[0].trades[0].ticker == "BBB"
