# Central configuration for date windows and regime thresholds used across the optimization pipeline.
# Update these when extending the training window or rolling the OOS period forward.
#
# NOTE: MVRV regime thresholds also appear as input.float() defaults in
# strategies/strategy_activation_scores.pine — update both files together if these change.

# Data file path helpers — all strategy data lives in data/mlp/ with TradingView's naming convention.
# TF → TV period string (minutes or "1D"); used to build "data/mlp/{ASSET}, {period}.csv" paths.
TF_TO_PERIOD = {"4H": "240", "6H": "360", "8H": "480", "12H": "720", "1D": "1D"}
PERIOD_TO_TF = {v: k for k, v in TF_TO_PERIOD.items()}


def mlp_data_path(asset: str, tf: str) -> str:
    """Return the canonical data/mlp/ path for an asset/TF combo."""
    return f"data/mlp/{asset}, {TF_TO_PERIOD.get(tf, tf)}.csv"


TRAIN_START = "2015-01-01"   # Earliest data loaded (TV shows full history from here)
SCORE_START = "2017-12-01"   # Trades before this date excluded from optimizer scoring
TRAIN_END   = "2026-02-28"   # Last day of in-sample training window
OOS_START   = "2026-03-01"   # Out-of-sample validation window start

# MVRV Z-Score regime thresholds (0–100 percentile scale).
# Bear < MVRV_BEAR_THRESHOLD <= Sideways < MVRV_BULL_THRESHOLD <= Bull.
# Mirror values in strategy_activation_scores.pine i_mvrv_bear_threshold / i_mvrv_bull_threshold defaults.
MVRV_BEAR_THRESHOLD = -5.0
MVRV_BULL_THRESHOLD = 30.0

# Composite metric: sqrt(Calmar × Sortino) × (1 + min(log(trades/floor), CAP)) × ln(1 + P&L/1000)
# Base uses geometric mean of Calmar and Sortino — harder to game than either alone.
# floor = max(MIN_ABS_TRADES, round(MIN_TRADES_PER_YEAR × is_years))
# GPU kernel optimises raw Calmar; composite applied at CPU ranking step only.
# Subperiod consistency (Calmar > 0 in both IS halves) is applied in auto_optimize_loop.py
# before saving — params that fail it get composite zeroed regardless of this function's output.
MIN_TRADES_PER_YEAR  = 3.0    # Lowered from 4.0: 12H/8H/1D have fewer bars, 33-trade floor was too strict
MIN_ABS_TRADES       = 10     # Absolute floor regardless of IS window length
COMPOSITE_LOG_CAP    = 1.3863 # log(4) — caps bonus at 4× the floor to prevent churn gaming
MIN_WINNER_PNL_PCT   = 1000.0 # Minimum IS total P&L % to be considered a winner
SUBPERIOD_SPLIT      = "2021-01-01"  # IS split for two-period consistency check (P1: 2017–2020, P2: 2021–2025)

import math as _math

def get_min_trades(is_years: float) -> int:
    """Dynamic IS trade floor proportional to window length."""
    return max(MIN_ABS_TRADES, round(MIN_TRADES_PER_YEAR * is_years))

def composite_score(calmar: float, sortino: float, trades: int, is_years: float, pnl_pct: float) -> float:
    """Composite: sqrt(Calmar × Sortino) × (1 + log_bonus) × ln(1 + P&L/1000).
    Returns 0 if below trade floor, either ratio ≤ 0, or P&L < MIN_WINNER_PNL_PCT."""
    floor = get_min_trades(is_years)
    if trades < floor or calmar <= 0 or sortino <= 0 or pnl_pct < MIN_WINNER_PNL_PCT:
        return 0.0
    base = _math.sqrt(calmar * sortino)          # geometric mean — requires both to be good
    log_bonus = min(_math.log(trades / floor), COMPOSITE_LOG_CAP)
    geo_factor = _math.log(1.0 + pnl_pct / 1000.0)
    return base * (1.0 + log_bonus) * geo_factor

# Non-weight optimizer params that are not i_w_* signals.
# sync_params.py propagates these to all 20 active params files automatically.
# auto_optimize_loop.py imports _NON_WEIGHT_PARAM_COLS directly (not from here),
# but adding here ensures sync_params.py keeps all params files in sync.
# Format: param_name -> default optimizer range dict (same syntax as params JSON).
NON_WEIGHT_PARAMS = {
    # MVRV bear-regime entry suppression.
    # When true, long entries are blocked when mvrv_regime == -1 (zscore < MVRV_BEAR_THRESHOLD).
    # Mirrors i_mvrv_suppress_bear in strategy_activation_scores.pine line 1419.
    "i_mvrv_suppress_bear": {"values": [False, True]},
}

# Canonical list of all signal weight params (i_w_*) — single source of truth.
# mine_sweep_db.py, shap_feature_importance.py, and auto_optimize_loop.py all import this.
# *** ADD NEW SIGNALS HERE when adding to params JSON and Pine Script. ***
# Adding a signal here automatically: writes it to the DB, includes it in dashboards, and
# catches missing CSV columns before a long run starts.
WEIGHT_COLS = [
    # Core technical signals
    "i_w_stoch", "i_w_macd_pred", "i_w_osc", "i_w_totalvol",
    "i_w_m3_momentum", "i_w_m2_tiny", "i_w_newaddr",
    "i_w_stoch_div_osc", "i_w_vwap_div_osc", "i_w_stoch_peaking",
    "i_w_sendaddr", "i_w_m3_div_osc", "i_w_m2_div_osc",
    "i_w_m2_div_osc_noOffset",
    # Candlestick patterns
    "i_w_bearish_engulfing", "i_w_bullish_hammer",
    "i_w_bullish_engulfing", "i_w_shooting_star",
    # Macro / cross-asset
    "i_w_btc_spx_corr", "i_w_dxy", "i_w_vix", "i_w_btc_dom",
    "i_w_us10y", "i_w_spy", "i_w_gold",
    # On-chain / regime
    "i_w_mvrv", "i_w_mvrv_cont", "i_w_nupl",
    "i_w_fed_net_liq", "i_w_gc_position",
    # Rates / macro
    "i_w_us2y", "i_w_yield_curve", "i_w_qqq_spy_ratio",
    # RSI divergence signals
    "i_w_rsid_reg_bull", "i_w_rsid_reg_bear",
    "i_w_rsid_hid_bull", "i_w_rsid_hid_bear",
    "i_w_rsid_rt_bull",  "i_w_rsid_rt_bear",
    "i_w_rsid_slow_bull", "i_w_rsid_slow_bear",
    "i_w_rsid_delayed_peak", "i_w_rsid_delayed_dip",
    # Derivatives / market structure
    "i_w_oi_roc", "i_w_usdt_d", "i_w_basis",
    # Sentiment / sub-TF confirmation
    "i_w_fear_greed", "i_w_btc_gold", "i_w_rsi_subtf",
]

RESULTS_DIR  = "results"           # Dashboards and sweep DB
WINNERS_DIR  = "results/winners"   # Best-params CSVs, cheatsheets, pine snippets
SWEEPS_DIR   = "results/sweeps"    # Per-run top-5000 sweep CSVs (gitignored, transient)
REPORTS_DIR  = "results/reports"   # Per-asset performance report MDs (gitignored)
OUTPUT_DIR   = "output"            # Optimization run logs (gitignored)

# ---------------------------------------------------------------------------
# Walk-Forward Optimization (WFO) fold structure.
# ---------------------------------------------------------------------------
# Each tuple: (is_end, oos_start, oos_end). IS always starts at SCORE_START.
# OOS periods cover distinct market regimes within the IS training window:
#   2021 — post-halving bull run
#   2022 — bear market / LUNA collapse / FTX
#   2023 — recovery / sideways consolidation
#   2024 H1 — ETF launch rally → correction
#   2024 H2 — political bull run (Trump/crypto legislation)
#   2025 H2 — ATH correction + tariff shock
# These are all within the IS training window (2017-2026-02-28), so WFO is a
# temporal robustness check ("does this survive different sub-periods?"), not a
# true holdout.  True holdout is still 2026-03-01+ (OOS dashboard).
WFO_FOLDS = [
    ("2020-12-31", "2021-01-01", "2021-12-31"),   # post-halving bull run
    ("2021-12-31", "2022-01-01", "2022-12-31"),   # bear / LUNA / FTX
    ("2022-12-31", "2023-01-01", "2023-12-31"),   # recovery / sideways
    ("2023-12-31", "2024-01-01", "2024-09-30"),   # ETF launch rally → correction
    ("2024-09-30", "2024-10-01", "2025-09-30"),   # political bull run (Trump/crypto legislation)
    ("2025-09-30", "2025-10-01", "2026-02-28"),   # ATH correction + tariff shock
]
WFO_MIN_OOS_TRADES = MIN_ABS_TRADES  # min OOS trades for a fold to contribute to WFO score — must match MIN_ABS_TRADES so folds below calculate_metrics' floor are excluded rather than scored as -10.0
WFO_MIN_VALID_FOLDS = 2  # min folds with enough trades; else WFO score = 0
WFO_TOP_N = 5000         # top N IS Calmar candidates to WFO-rescore per iteration
