from __future__ import annotations

import asyncio
import pickle
from datetime import datetime
from types import SimpleNamespace
from zoneinfo import ZoneInfo

import apps.intraday_bt.run as run_mod
from apps.intraday_bt.run import (
    _augment_momentum_seed_candidates_with_liquid_overlay,
    _chunk_trading_days_by_pairs,
    _fetch_vix_by_day,
    _latest_backtest_date,
    _latest_completed_trading_day,
    _load_vix_from_local_macro_snapshots,
    _normalize_candidate_map,
    _momentum_intraday_seed_candidates,
    _momentum_strategy_uses_candidate_stage_catalyst,
    _momentum_strategy_uses_daily_enrichment,
    _momentum_strategy_requires_regime_ticker_daily,
    _momentum_strategy_uses_attention,
    _momentum_strategy_uses_catalyst,
    _momentum_strategy_uses_sector_labels,
    _momentum_strategy_uses_sector_proxies,
    _retain_recent_intraday_shortlist,
    _recent_intraday_first_candidates,
    _strategy_for_recent_live_scan,
    _should_use_recent_live_scan,
    _should_use_recent_intraday_first_scan,
    apply_cli_overrides,
    get_trading_days,
    load_config,
)
from libs.intraday.domain import StrategyParams


def test_get_trading_days_uses_local_calendar_for_explicit_range() -> None:
    days = asyncio.run(get_trading_days(None, "2026-01-01", "2026-01-10", lookback=0))

    assert days == [
        "2026-01-02",
        "2026-01-05",
        "2026-01-06",
        "2026-01-07",
        "2026-01-08",
        "2026-01-09",
    ]


def test_get_trading_days_trims_to_lookback_when_start_not_pinned() -> None:
    days = asyncio.run(get_trading_days(None, None, "2026-01-10", lookback=3))

    assert days == [
        "2026-01-07",
        "2026-01-08",
        "2026-01-09",
    ]


def test_chunk_trading_days_by_pairs_keeps_days_contiguous_and_bounded() -> None:
    trading_days = [
        "2026-01-05",
        "2026-01-06",
        "2026-01-07",
        "2026-01-08",
    ]
    candidates = {
        "2026-01-05": ["A"] * 2000,
        "2026-01-06": ["B"] * 2000,
        "2026-01-07": ["C"] * 1500,
        "2026-01-08": ["D"] * 2500,
    }

    chunks = _chunk_trading_days_by_pairs(trading_days, candidates, max_pairs_per_chunk=3500)

    assert chunks == [
        ["2026-01-05"],
        ["2026-01-06", "2026-01-07"],
        ["2026-01-08"],
    ]


def test_normalize_candidate_map_unwraps_tuple_payload() -> None:
    payload = ({"2026-01-05": ["A", "B"], "2026-01-06": []}, {"2026-01-05": {"A"}})

    normalized = _normalize_candidate_map(payload)

    assert normalized == {"2026-01-05": ["A", "B"]}


def test_latest_backtest_date_excludes_today_before_close() -> None:
    now_et = datetime(2026, 4, 14, 12, 0, tzinfo=ZoneInfo("America/New_York"))

    latest = _latest_backtest_date(now_et)

    assert latest.isoformat() == "2026-04-13"


def test_latest_backtest_date_includes_today_after_close() -> None:
    now_et = datetime(2026, 4, 14, 16, 1, tzinfo=ZoneInfo("America/New_York"))

    latest = _latest_backtest_date(now_et)

    assert latest.isoformat() == "2026-04-14"


def test_latest_backtest_date_uses_last_session_on_weekend() -> None:
    now_et = datetime(2026, 4, 18, 10, 0, tzinfo=ZoneInfo("America/New_York"))

    latest = _latest_backtest_date(now_et)

    assert latest.isoformat() == "2026-04-18"


def test_latest_completed_trading_day_walks_back_on_weekend() -> None:
    now_et = datetime(2026, 4, 18, 10, 0, tzinfo=ZoneInfo("America/New_York"))

    latest = _latest_completed_trading_day(now_et)

    assert latest.isoformat() == "2026-04-17"


def test_load_vix_from_local_macro_snapshots_uses_covering_file(tmp_path, monkeypatch) -> None:
    parquet_dir = tmp_path / "parquet" / "sample"
    parquet_dir.mkdir(parents=True, exist_ok=True)
    payload = {
        datetime(2025, 1, 2).date(): {"VIXCLS": 17.1},
        datetime(2025, 1, 3).date(): {"VIXCLS": 18.2},
        datetime(2025, 1, 6).date(): {"VIXCLS": 19.3},
    }
    path = parquet_dir / "macro_window_2025-01-01_2025-01-10.pkl"
    with path.open("wb") as fh:
        pickle.dump(payload, fh, protocol=pickle.HIGHEST_PROTOCOL)

    monkeypatch.setattr(
        run_mod,
        "get_settings",
        lambda: SimpleNamespace(data_root=str(tmp_path)),
    )

    result = _load_vix_from_local_macro_snapshots(["2025-01-02", "2025-01-03", "2025-01-06"])

    assert result == {
        "2025-01-02": 17.1,
        "2025-01-03": 18.2,
        "2025-01-06": 19.3,
    }


def test_fetch_vix_by_day_uses_local_snapshot_when_health_check_fails(tmp_path, monkeypatch) -> None:
    parquet_dir = tmp_path / "parquet" / "sample"
    parquet_dir.mkdir(parents=True, exist_ok=True)
    payload = {
        datetime(2025, 1, 2).date(): {"VIXCLS": 17.1},
        datetime(2025, 1, 3).date(): {"VIXCLS": 18.2},
    }
    path = parquet_dir / "macro_window_2025-01-01_2025-01-10.pkl"
    with path.open("wb") as fh:
        pickle.dump(payload, fh, protocol=pickle.HIGHEST_PROTOCOL)

    monkeypatch.setattr(
        run_mod,
        "get_settings",
        lambda: SimpleNamespace(data_root=str(tmp_path)),
    )

    class _Client:
        async def health_check_fast(self, timeout: float = 3.0) -> bool:
            return False

    result = asyncio.run(_fetch_vix_by_day(_Client(), ["2025-01-02", "2025-01-03"]))

    assert result == {
        "2025-01-02": 17.1,
        "2025-01-03": 18.2,
    }


def test_load_ticker_sectors_with_oracle_backfills_missing_cache(tmp_path, monkeypatch) -> None:
    cache_path = tmp_path / "cache" / "sector_cache.json"
    cache_path.parent.mkdir(parents=True, exist_ok=True)
    cache_path.write_text('{"TSLA": "Consumer Cyclical"}')

    monkeypatch.setattr(
        run_mod,
        "get_settings",
        lambda: SimpleNamespace(data_root=str(tmp_path)),
    )

    class FakeCompanyService:
        def __init__(self, client) -> None:
            self.client = client

        async def get_company(self, symbol: str):
            if symbol == "CPRX":
                return SimpleNamespace(
                    sector="Healthcare",
                    industry="Biotechnology",
                    exchange="NASDAQ",
                    market_cap=2_979_108_098.0,
                )
            raise AssertionError(f"unexpected symbol {symbol}")

    monkeypatch.setattr(run_mod, "CompanyService", FakeCompanyService)

    result = asyncio.run(
        run_mod._load_ticker_sectors_with_oracle(["TSLA", "CPRX"], client=object())
    )

    assert result == {
        "TSLA": "Consumer Cyclical",
        "CPRX": "Healthcare",
    }
    assert cache_path.exists()
    assert '"CPRX": "Healthcare"' in cache_path.read_text()


def test_load_ticker_sectors_with_oracle_skips_placeholder_metadata(tmp_path, monkeypatch) -> None:
    cache_path = tmp_path / "cache" / "sector_cache.json"
    cache_path.parent.mkdir(parents=True, exist_ok=True)
    cache_path.write_text("{}")

    monkeypatch.setattr(
        run_mod,
        "get_settings",
        lambda: SimpleNamespace(data_root=str(tmp_path)),
    )

    class FakeCompanyService:
        def __init__(self, client) -> None:
            self.client = client

        async def get_company(self, symbol: str):
            return SimpleNamespace(
                sector="Technology",
                industry="Software",
                exchange=None,
                market_cap=None,
            )

    monkeypatch.setattr(run_mod, "CompanyService", FakeCompanyService)

    result = asyncio.run(
        run_mod._load_ticker_sectors_with_oracle(["AVGO"], client=object())
    )

    assert result == {"AVGO": "UNKNOWN"}
    assert cache_path.read_text() == "{}"


def test_should_use_recent_live_scan_only_for_small_recent_windows(monkeypatch) -> None:
    monkeypatch.setattr(
        run_mod,
        "_latest_backtest_date",
        lambda now_et=None: datetime(2026, 4, 16, tzinfo=ZoneInfo("America/New_York")).date(),
    )
    strategy = StrategyParams(recent_live_scan_days=5)

    assert _should_use_recent_live_scan(strategy, ["2026-04-15", "2026-04-16"]) is True
    assert _should_use_recent_live_scan(
        strategy,
        ["2026-04-09", "2026-04-10", "2026-04-13", "2026-04-14", "2026-04-15", "2026-04-16"],
    ) is False
    assert _should_use_recent_live_scan(strategy, ["2026-03-01"]) is False


def test_momentum_strategy_uses_intraday_event_weight_for_fetch_activation() -> None:
    strategy = StrategyParams(candidate_intraday_weight_event_score=0.05)

    assert _momentum_strategy_uses_catalyst(strategy) is True


def test_momentum_strategy_uses_seed_event_overlay_for_candidate_stage_catalyst() -> None:
    strategy = StrategyParams(candidate_seed_event_overlay_slots=2)

    assert _momentum_strategy_uses_catalyst(strategy) is True
    assert _momentum_strategy_uses_candidate_stage_catalyst(strategy) is True


def test_momentum_strategy_uses_candidate_event_type_filter_for_fetch_activation() -> None:
    strategy = StrategyParams(candidate_allowed_event_types=["earnings_release"])

    assert _momentum_strategy_uses_catalyst(strategy) is True
    assert _momentum_strategy_uses_candidate_stage_catalyst(strategy) is True


def test_momentum_strategy_uses_event_reserve_and_event_sleeve_for_fetch_activation() -> None:
    reserve_strategy = StrategyParams(candidate_intraday_event_reserve_slots=1)
    sleeve_strategy = StrategyParams(use_event_sleeve=True, event_weight=0.1)
    event_day_liquid_strategy = StrategyParams(
        use_event_day_liquid_sleeve=True,
        event_day_liquid_capital_fraction=0.1,
        event_day_liquid_max_positions=1,
    )

    assert _momentum_strategy_uses_catalyst(reserve_strategy) is True
    assert _momentum_strategy_uses_candidate_stage_catalyst(reserve_strategy) is False
    assert _momentum_strategy_uses_catalyst(sleeve_strategy) is True
    assert _momentum_strategy_uses_catalyst(event_day_liquid_strategy) is True


def test_momentum_strategy_uses_intraday_attention_weight_for_fetch_activation() -> None:
    strategy = StrategyParams(candidate_intraday_weight_attention_news=0.03)

    assert _momentum_strategy_uses_attention(strategy) is True


def test_momentum_strategy_requires_regime_ticker_daily_for_gap_meta_layer() -> None:
    strategy = StrategyParams(regime_size_scale_low=-0.01)

    assert _momentum_strategy_requires_regime_ticker_daily(strategy) is True


def test_momentum_strategy_uses_sector_metadata_and_proxy_fetch_for_overlay_engines() -> None:
    cluster_strategy = StrategyParams(use_liquid_cluster_engine=True)
    etf_strategy = StrategyParams(
        use_sector_etf_sleeve=True,
        sector_etf_capital_fraction=0.2,
        sector_etf_max_positions=1,
    )

    assert _momentum_strategy_uses_daily_enrichment(cluster_strategy) is True
    assert _momentum_strategy_uses_sector_labels(cluster_strategy) is True
    assert _momentum_strategy_uses_sector_proxies(cluster_strategy) is False
    assert _momentum_strategy_uses_sector_labels(etf_strategy) is True
    assert _momentum_strategy_uses_sector_proxies(etf_strategy) is True


def test_momentum_intraday_seed_candidates_only_apply_signal_filters_in_final_pass() -> None:
    daily_bars = {
        "AAA": [
            {"date": "2026-01-05", "open": 10.0},
        ],
        "BBB": [
            {"date": "2026-01-05", "open": 10.0},
        ],
    }
    enrichment = {
        "AAA": {
            "2026-01-05": {
                "gap_pct": 0.03,
                "event_flag": True,
                "event_score": 1.0,
                "ret_5d": 0.01,
                "entropy_20d": 0.4,
                "avg_dollar_vol_30d": 1_000_000.0,
                "atr_14": 1.0,
            }
        },
        "BBB": {
            "2026-01-05": {
                "gap_pct": 0.04,
                "event_flag": False,
                "event_score": 0.0,
                "ret_5d": 0.01,
                "entropy_20d": 0.4,
                "avg_dollar_vol_30d": 1_000_000.0,
                "atr_14": 1.0,
            }
        },
    }
    strategy = StrategyParams(
        candidate_source_mode="intraday_first",
        candidate_seed_threshold=0.02,
        candidate_seed_max_per_day=1,
        candidate_require_event_flag=True,
        candidate_weight_event_score=1.0,
    )

    preliminary = _momentum_intraday_seed_candidates(
        daily_bars,
        ["2026-01-05"],
        enrichment,
        strategy,
        default_threshold=0.02,
        use_signal_features=False,
    )
    final_seed = _momentum_intraday_seed_candidates(
        daily_bars,
        ["2026-01-05"],
        enrichment,
        strategy,
        default_threshold=0.02,
        use_signal_features=True,
    )

    assert preliminary == {"2026-01-05": ["BBB"]}
    assert final_seed == {"2026-01-05": ["AAA"]}


def test_recent_intraday_first_candidates_uses_intraday_leaders_without_static_universe() -> None:
    strategy = StrategyParams(
        entry_minutes_after_open=10,
        confirmation_minutes_after_entry=5,
        min_entry_volume=250000,
        min_entry_dollar_volume=2_000_000,
        top_n=8,
        recent_live_scan_max_candidates_per_day=10,
    )
    day = "2026-04-16"
    bars = {
        day: {
            "XNDU": [
                {"timestamp": "2026-04-16T13:30:00+00:00", "open": 2.00, "high": 2.06, "low": 1.98, "close": 2.05, "volume": 150000},
                {"timestamp": "2026-04-16T13:35:00+00:00", "open": 2.05, "high": 2.12, "low": 2.04, "close": 2.11, "volume": 175000},
                {"timestamp": "2026-04-16T13:40:00+00:00", "open": 2.11, "high": 2.15, "low": 2.10, "close": 2.14, "volume": 180000},
                {"timestamp": "2026-04-16T13:45:00+00:00", "open": 2.14, "high": 2.20, "low": 2.13, "close": 2.19, "volume": 200000},
                {"timestamp": "2026-04-16T13:50:00+00:00", "open": 2.19, "high": 2.24, "low": 2.18, "close": 2.22, "volume": 210000},
            ],
            "SLOW": [
                {"timestamp": "2026-04-16T13:30:00+00:00", "open": 20.00, "high": 20.01, "low": 19.95, "close": 19.98, "volume": 50000},
                {"timestamp": "2026-04-16T13:35:00+00:00", "open": 19.98, "high": 20.00, "low": 19.90, "close": 19.95, "volume": 50000},
                {"timestamp": "2026-04-16T13:40:00+00:00", "open": 19.95, "high": 19.99, "low": 19.92, "close": 19.97, "volume": 50000},
                {"timestamp": "2026-04-16T13:45:00+00:00", "open": 19.97, "high": 19.98, "low": 19.94, "close": 19.96, "volume": 50000},
                {"timestamp": "2026-04-16T13:50:00+00:00", "open": 19.96, "high": 19.97, "low": 19.93, "close": 19.95, "volume": 50000},
                {"timestamp": "2026-04-16T13:55:00+00:00", "open": 19.95, "high": 19.96, "low": 19.92, "close": 19.94, "volume": 50000},
            ],
            "LIQUID": [
                {"timestamp": "2026-04-16T13:30:00+00:00", "open": 400.00, "high": 401.00, "low": 399.00, "close": 400.20, "volume": 80000},
                {"timestamp": "2026-04-16T13:35:00+00:00", "open": 400.20, "high": 401.20, "low": 400.10, "close": 400.80, "volume": 90000},
                {"timestamp": "2026-04-16T13:40:00+00:00", "open": 400.80, "high": 402.50, "low": 400.70, "close": 402.20, "volume": 120000},
                {"timestamp": "2026-04-16T13:45:00+00:00", "open": 402.20, "high": 403.20, "low": 401.90, "close": 402.80, "volume": 130000},
                {"timestamp": "2026-04-16T13:50:00+00:00", "open": 402.80, "high": 404.00, "low": 402.70, "close": 403.60, "volume": 140000},
                {"timestamp": "2026-04-16T13:55:00+00:00", "open": 403.60, "high": 404.20, "low": 403.40, "close": 404.00, "volume": 150000},
            ],
            "MEGA": [
                {"timestamp": "2026-04-16T13:30:00+00:00", "open": 500.00, "high": 500.80, "low": 499.50, "close": 500.10, "volume": 120000},
                {"timestamp": "2026-04-16T13:35:00+00:00", "open": 500.10, "high": 500.90, "low": 500.00, "close": 500.40, "volume": 140000},
                {"timestamp": "2026-04-16T13:40:00+00:00", "open": 500.40, "high": 501.10, "low": 500.20, "close": 500.70, "volume": 150000},
                {"timestamp": "2026-04-16T13:45:00+00:00", "open": 500.70, "high": 501.30, "low": 500.50, "close": 500.90, "volume": 160000},
                {"timestamp": "2026-04-16T13:50:00+00:00", "open": 500.90, "high": 501.50, "low": 500.80, "close": 501.20, "volume": 180000},
                {"timestamp": "2026-04-16T13:55:00+00:00", "open": 501.20, "high": 501.70, "low": 501.00, "close": 501.40, "volume": 190000},
            ],
        }
    }

    candidates = _recent_intraday_first_candidates(bars, [day], strategy)

    assert "XNDU" in candidates[day]
    assert "LIQUID" in candidates[day]
    assert "MEGA" in candidates[day]
    assert "SLOW" not in candidates[day]


def test_recent_intraday_first_scan_only_applies_to_latest_safe_day(monkeypatch) -> None:
    assert _should_use_recent_intraday_first_scan(["2026-04-16"]) is True
    assert _should_use_recent_intraday_first_scan(["2026-04-15"]) is True


def test_recent_intraday_first_scan_treats_latest_completed_weekday_as_recent(monkeypatch) -> None:
    assert _should_use_recent_intraday_first_scan(["2026-04-17"]) is True


def test_recent_intraday_first_scan_false_for_empty_window() -> None:
    assert _should_use_recent_intraday_first_scan([]) is False


def test_liquid_seed_overlay_adds_liquid_name_without_replacing_base_seed() -> None:
    strategy = StrategyParams(
        candidate_seed_liquid_overlay_slots=1,
        candidate_seed_liquid_min_gap_pct=0.005,
        candidate_seed_liquid_max_gap_pct=0.03,
        candidate_seed_liquid_min_avg_dollar_vol_30d=500_000_000.0,
        candidate_seed_liquid_min_ret_5d=0.0,
        candidate_seed_liquid_max_entropy_20d=0.90,
    )
    daily_bars = {
        "BASE": [{"date": "2026-04-15", "open": 50.0}],
        "TSLA": [{"date": "2026-04-15", "open": 250.0}],
        "NOPE": [{"date": "2026-04-15", "open": 30.0}],
    }
    enrichment = {
        "BASE": {"2026-04-15": {"gap_pct": 0.03, "avg_dollar_vol_30d": 200_000_000.0, "ret_5d": 0.02, "entropy_20d": 0.40}},
        "TSLA": {"2026-04-15": {"gap_pct": 0.007, "avg_dollar_vol_30d": 1_200_000_000.0, "ret_5d": 0.05, "entropy_20d": 0.70}},
        "NOPE": {"2026-04-15": {"gap_pct": 0.009, "avg_dollar_vol_30d": 200_000_000.0, "ret_5d": 0.05, "entropy_20d": 0.50}},
    }
    candidates = {"2026-04-15": ["BASE"]}

    augmented, overlay = _augment_momentum_seed_candidates_with_liquid_overlay(
        candidates,
        daily_bars,
        ["2026-04-15"],
        enrichment,
        strategy,
    )

    assert augmented["2026-04-15"] == ["BASE", "TSLA"]


def test_strategy_for_recent_live_scan_applies_only_recent_overrides() -> None:
    strategy = StrategyParams(
        top_n=8,
        min_morning_gain_pct=0.015,
        max_morning_gain_pct=0.06,
        min_confirmation_return_pct=0.005,
        max_gap_pct=0.055,
        use_slow_ignite_sleeve=False,
        recent_live_scan_top_n=10,
        recent_live_scan_min_morning_gain_pct=0.01,
        recent_live_scan_max_morning_gain_pct=0.05,
        recent_live_scan_min_confirmation_return_pct=0.001,
        recent_live_scan_max_gap_pct=0.04,
        recent_live_scan_max_entropy_20d=0.9,
        recent_live_scan_use_slow_ignite_sleeve=True,
        recent_live_scan_slow_ignite_weight=0.2,
        recent_live_scan_use_liquid_largecap_sleeve=True,
        recent_live_scan_liquid_largecap_weight=0.3,
        recent_live_scan_liquid_largecap_min_gain_pct=0.004,
        recent_live_scan_liquid_largecap_max_gain_pct=0.02,
        recent_live_scan_liquid_largecap_min_confirmation_return_pct=0.0005,
        recent_live_scan_liquid_largecap_min_entry_dollar_volume=50_000_000.0,
        recent_live_scan_liquid_largecap_min_avg_dollar_vol_30d=500_000_000.0,
        recent_live_scan_liquid_largecap_max_entropy_20d=0.9,
    )

    unchanged = _strategy_for_recent_live_scan(strategy, recent_live_scan=False)
    recent = _strategy_for_recent_live_scan(strategy, recent_live_scan=True)

    assert unchanged.top_n == 8
    assert unchanged.min_morning_gain_pct == 0.015
    assert unchanged.max_morning_gain_pct == 0.06
    assert unchanged.max_gap_pct == 0.055
    assert unchanged.use_slow_ignite_sleeve is False
    assert recent.top_n == 10
    assert recent.min_morning_gain_pct == 0.01
    assert recent.max_morning_gain_pct == 0.05
    assert recent.min_confirmation_return_pct == 0.001
    assert recent.max_gap_pct == 0.04
    assert recent.max_entropy_20d == 0.9
    assert recent.use_slow_ignite_sleeve is True
    assert recent.use_liquid_largecap_sleeve is True
    assert recent.liquid_largecap_weight == 0.3
    assert recent.liquid_largecap_min_gain_pct == 0.004
    assert recent.liquid_largecap_max_gain_pct == 0.02
    assert recent.liquid_largecap_min_confirmation_return_pct == 0.0005
    assert recent.slow_ignite_weight == 0.2


def test_augment_momentum_seed_candidates_with_leader_overlay_adds_negative_gap_continuation_name() -> None:
    strategy = StrategyParams(
        candidate_seed_leader_overlay_slots=1,
        candidate_seed_leader_min_gap_pct=-0.01,
        candidate_seed_leader_max_gap_pct=0.01,
        candidate_seed_leader_min_avg_dollar_vol_30d=500_000_000.0,
        candidate_seed_leader_min_ret_5d=0.15,
        candidate_seed_leader_min_atr_pct=0.05,
        candidate_seed_leader_max_entropy_20d=0.75,
    )
    daily_bars = {
        "BASE": [{"date": "2026-04-20", "open": 50.0}],
        "CAR": [{"date": "2026-04-20", "open": 491.26}],
        "NOPE": [{"date": "2026-04-20", "open": 300.0}],
    }
    enrichment = {
        "BASE": {"2026-04-20": {"gap_pct": 0.03, "avg_dollar_vol_30d": 200_000_000.0, "ret_5d": 0.02, "entropy_20d": 0.40, "atr_14": 2.0}},
        "CAR": {"2026-04-20": {"gap_pct": -0.005, "avg_dollar_vol_30d": 730_000_000.0, "ret_5d": 0.33, "entropy_20d": 0.44, "atr_14": 51.8}},
        "NOPE": {"2026-04-20": {"gap_pct": 0.0, "avg_dollar_vol_30d": 450_000_000.0, "ret_5d": 0.18, "entropy_20d": 0.55, "atr_14": 5.0}},
    }
    candidates = {"2026-04-20": ["BASE"]}

    augmented, overlay = _augment_momentum_seed_candidates_with_liquid_overlay(
        candidates,
        daily_bars,
        ["2026-04-20"],
        enrichment,
        strategy,
    )

    assert augmented["2026-04-20"] == ["BASE", "CAR"]


def test_augment_momentum_seed_candidates_with_moderate_liquid_overlay_adds_followthrough_name() -> None:
    strategy = StrategyParams(
        candidate_seed_moderate_liquid_overlay_slots=1,
        candidate_seed_moderate_liquid_min_gap_pct=0.005,
        candidate_seed_moderate_liquid_max_gap_pct=0.025,
        candidate_seed_moderate_liquid_min_avg_dollar_vol_30d=250_000_000.0,
        candidate_seed_moderate_liquid_max_avg_dollar_vol_30d=2_000_000_000.0,
        candidate_seed_moderate_liquid_max_entropy_20d=0.86,
    )
    daily_bars = {
        "BASE": [{"date": "2026-03-13", "open": 50.0}],
        "TER": [{"date": "2026-03-13", "open": 100.0}],
        "MEGA": [{"date": "2026-03-13", "open": 250.0}],
    }
    enrichment = {
        "BASE": {"2026-03-13": {"gap_pct": 0.04, "avg_dollar_vol_30d": 150_000_000.0, "ret_5d": 0.02, "entropy_20d": 0.40}},
        "TER": {"2026-03-13": {"gap_pct": 0.012, "avg_dollar_vol_30d": 750_000_000.0, "ret_5d": -0.02, "entropy_20d": 0.79}},
        "MEGA": {"2026-03-13": {"gap_pct": 0.010, "avg_dollar_vol_30d": 5_000_000_000.0, "ret_5d": 0.01, "entropy_20d": 0.50}},
    }
    candidates = {"2026-03-13": ["BASE"]}

    augmented, overlay = _augment_momentum_seed_candidates_with_liquid_overlay(
        candidates,
        daily_bars,
        ["2026-03-13"],
        enrichment,
        strategy,
    )

    assert augmented["2026-03-13"] == ["BASE", "TER"]
    assert overlay == {"2026-03-13": {"TER"}}
    assert enrichment["TER"]["2026-03-13"]["candidate_seed_moderate_liquid_overlay"] is True


def test_augment_momentum_seed_candidates_with_event_overlay_adds_actual_catalyst_name() -> None:
    strategy = StrategyParams(
        candidate_seed_event_overlay_slots=1,
        candidate_seed_event_min_score=0.95,
        candidate_seed_event_min_gap_pct=-0.02,
        candidate_seed_event_max_gap_pct=0.08,
        candidate_seed_event_min_avg_dollar_vol_30d=100_000_000.0,
        candidate_seed_event_max_entropy_20d=0.80,
    )
    daily_bars = {
        "BASE": [{"date": "2026-02-10", "open": 50.0}],
        "CAT": [{"date": "2026-02-10", "open": 42.0}],
        "WEAK": [{"date": "2026-02-10", "open": 30.0}],
    }
    enrichment = {
        "BASE": {"2026-02-10": {"gap_pct": 0.04, "avg_dollar_vol_30d": 200_000_000.0, "ret_5d": 0.03, "entropy_20d": 0.40}},
        "CAT": {"2026-02-10": {"event_flag": True, "event_score": 1.0, "gap_pct": 0.01, "avg_dollar_vol_30d": 350_000_000.0, "ret_5d": 0.08, "entropy_20d": 0.55}},
        "WEAK": {"2026-02-10": {"event_flag": True, "event_score": 0.60, "gap_pct": 0.015, "avg_dollar_vol_30d": 120_000_000.0, "ret_5d": 0.02, "entropy_20d": 0.50}},
    }
    candidates = {"2026-02-10": ["BASE"]}

    augmented, overlay = _augment_momentum_seed_candidates_with_liquid_overlay(
        candidates,
        daily_bars,
        ["2026-02-10"],
        enrichment,
        strategy,
    )

    assert augmented["2026-02-10"] == ["BASE", "CAT"]
    assert overlay == {"2026-02-10": {"CAT"}}


def test_retain_recent_intraday_shortlist_preserves_intraday_candidates() -> None:
    candidates = {"2026-04-15": ["TSLA", "XNDU", "AXTI"]}
    daily_bars = {"TSLA": [{"date": "2026-04-15"}], "AXTI": [{"date": "2026-04-15"}]}

    retained = _retain_recent_intraday_shortlist(
        candidates,
        daily_bars,
        require_daily_features=True,
    )

    assert retained == {"2026-04-15": ["TSLA", "AXTI"]}


def test_momentum_strategy_defaults_to_simple_returns_and_cli_can_override() -> None:
    config = load_config(
        "configs/intraday/strategies/leader_intraday_momentum_high_wr_intraday_first.yaml"
    )

    assert config.strategy.compound_returns is False

    args = SimpleNamespace(
        days=None,
        start=None,
        end=None,
        universe=None,
        top_n=None,
        stop_loss=None,
        entry_min=None,
        exit_min=None,
        min_gain=None,
        no_cache=False,
        verbose=False,
        output_dir=None,
        strategy="momentum",
        compound_returns=True,
        initial_capital=None,
    )

    updated = apply_cli_overrides(config, args)

    assert updated.strategy.compound_returns is True
