"""Visualization helpers for skew mispricing research notebooks."""

import matplotlib.pyplot as plt
import numpy as np

from volatility_trading.config.constants import OPTION_TYPES


def plot_iv_smiles(iv_smiles, ticker):
    """Plot implied-volatility smiles across selected DTE buckets."""
    plt.figure(figsize=(12, 6))

    for dte, iv_smile in iv_smiles.items():
        if iv_smile is not None and not iv_smile.empty:
            plt.scatter(iv_smile.index, iv_smile.values, marker="o", label=int(dte))

    plt.xlabel("Strike", fontsize=14)
    plt.ylabel("Implied Volatility", fontsize=14)
    plt.title(f"Implied Volatility Smiles for {ticker} Options", fontsize=16)
    plt.legend(title="Days to Expiry", fontsize=10)
    plt.grid(True)
    plt.tight_layout()
    plt.show()


def plot_volume_filter(options, log_volumes):
    """Plot volume distribution diagnostics used for liquidity filtering."""
    plt.figure(figsize=(10, 5))

    for opt_type, color in zip(OPTION_TYPES, ["blue", "orange"]):
        subset = options[options["option_type"] == opt_type]
        log_volumes = (subset["volume"] + 1).apply(np.log)
        log_volumes.hist(bins=100, alpha=0.5, label=f"{opt_type} options", color=color)

    plt.axvline(np.log(2), color="r", linestyle="--", label="Volume ≥ 1")
    plt.title("Option Volume Distribution (log scale)", fontsize=16)
    plt.xlabel("log(volume + 1)", fontsize=14)
    plt.ylabel("Frequency", fontsize=14)
    plt.legend()
    plt.grid(True)
    plt.tight_layout()
    plt.show()


def plot_bid_ask_filter(options):
    """Plot relative bid/ask spread diagnostics by option type."""
    plt.figure(figsize=(10, 6))

    for opt_type, color in zip(OPTION_TYPES, ["blue", "orange"]):
        subset = options[options["option_type"] == opt_type]
        log_rel_spread = (subset["rel_spread"] + 1e-6).apply(np.log)
        log_rel_spread.hist(
            bins=100, alpha=0.5, label=f"{opt_type} options", color=color
        )

    plt.axvline(np.log(0.25), color="red", linestyle="--", label="25% max spread")
    plt.xlabel("log(Relative Bid-Ask Spread)", fontsize=14)
    plt.ylabel("Frequency", fontsize=14)
    plt.title("Distribution of Relative Bid-Ask Spread (Calls vs Puts)", fontsize=16)
    plt.legend()
    plt.grid(True)
    plt.tight_layout()
    plt.show()


def plot_moneyness_filter(avg_vol):
    """Plot average traded volume by moneyness bucket and option type."""
    plt.figure(figsize=(12, 6))

    for option_type in OPTION_TYPES:
        subset = avg_vol[avg_vol["option_type"] == option_type]
        plt.plot(
            subset["moneyness_bin"].astype(str),
            subset["volume"],
            label=f"{option_type} volume",
        )

    plt.axvline("(0.8, 0.85]", color="red", linestyle="--", label="0.8 Moneyness")
    plt.axvline("(1.2, 1.25]", color="red", linestyle="--", label="1.2 Moneyness")
    plt.xticks(rotation=45)
    plt.xlabel("Moneyness (strike / underlying)", fontsize=14)
    plt.ylabel("Average Volume", fontsize=14)
    plt.title("Average Option Volume by Moneyness", fontsize=16)
    plt.legend()
    plt.grid(True)
    plt.tight_layout()
    plt.show()


def plot_synthetic_ivs(synthetic_skew):
    """Plot synthetic 30-DTE call/put/ATM implied-volatility series."""
    plt.figure(figsize=(12, 6))
    plt.plot(synthetic_skew.index, synthetic_skew["iv_put_30"], label="25Δ Put IV")
    plt.plot(synthetic_skew.index, synthetic_skew["iv_call_30"], label="25Δ Call IV")
    plt.plot(synthetic_skew.index, synthetic_skew["iv_atm_30"], label="ATM IV")

    plt.title("Synthetic 30-DTE Implied Volatilities", fontsize=16)
    plt.xlabel("Date", fontsize=14)
    plt.ylabel("Implied Volatility", fontsize=14)
    plt.legend()
    plt.grid(True)
    plt.tight_layout()
    plt.show()


def plot_norm_abs_skew(synthetic_skew):
    """Plot normalized and absolute skew on dual y-axes."""
    fig, ax1 = plt.subplots(figsize=(14, 5))

    ax1.plot(synthetic_skew["skew_norm"], color="red", label="Normalized Skew")
    ax1.set_ylabel("Normalized Skew", color="red", fontsize=14)
    ax1.tick_params(axis="y", labelcolor="red")

    ax2 = ax1.twinx()
    ax2.plot(synthetic_skew["skew_abs"], color="navy", label="Absolute Skew")
    ax2.set_ylabel("Absolute Skew", color="navy", fontsize=14)
    ax2.tick_params(axis="y", labelcolor="navy")

    ax1.set_title("Absolute vs Normalized 30-DTE 25Δ Skew", fontsize=16)
    ax1.set_xlabel("Date", fontsize=14)

    lines_1, labels_1 = ax1.get_legend_handles_labels()
    lines_2, labels_2 = ax2.get_legend_handles_labels()
    ax1.legend(lines_1 + lines_2, labels_1 + labels_2, loc="upper right")

    plt.tight_layout()
    plt.show()


def plot_skew_vs_spy(synthetic_skew, spy):
    """Plot SPY against absolute and normalized skew time series."""
    fig, axes = plt.subplots(2, 1, figsize=(14, 8), sharex=True)

    # --- First subplot: SPY vs Absolute Skew ---
    ax1 = axes[0]
    ax1.plot(spy["Close"], color="purple", label="SPY")
    ax1.set_ylabel("SPY", color="purple", fontsize=12)
    ax1.tick_params(axis="y", labelcolor="purple")
    ax1.set_title("SPY vs 30-DTE 25Δ Absolute Skew", fontsize=14)

    ax1b = ax1.twinx()
    ax1b.plot(synthetic_skew["skew_abs"], color="blue", label="Absolute Skew")
    ax1b.set_ylabel("Abs Skew", color="blue", fontsize=12)
    ax1b.tick_params(axis="y", labelcolor="blue")

    lines1, labels1 = ax1.get_legend_handles_labels()
    lines1b, labels1b = ax1b.get_legend_handles_labels()
    ax1.legend(lines1 + lines1b, labels1 + labels1b, loc="upper left")

    # --- Second subplot: SPY vs Normalized Skew ---
    ax2 = axes[1]
    ax2.plot(spy["Close"], color="purple", label="SPY")
    ax2.set_ylabel("SPY", color="purple", fontsize=12)
    ax2.tick_params(axis="y", labelcolor="purple")
    ax2.set_title("SPY vs 30-DTE 25Δ Normalized Skew", fontsize=14)
    ax2.set_xlabel("Date", fontsize=12)

    ax2b = ax2.twinx()
    ax2b.plot(synthetic_skew["skew_norm"], color="red", label="Normalized Skew")
    ax2b.set_ylabel("Norm Skew", color="red", fontsize=12)
    ax2b.tick_params(axis="y", labelcolor="red")

    lines2, labels2 = ax2.get_legend_handles_labels()
    lines2b, labels2b = ax2b.get_legend_handles_labels()
    ax2.legend(lines2 + lines2b, labels2 + labels2b, loc="upper left")

    plt.tight_layout()
    plt.show()


def plot_risk_reversal_payoff(
    spot_price=100, strike_put=95, strike_call=105, premium_put=3, premium_call=2
):
    """Plot expiry payoff for a long-call/short-put risk reversal."""
    price_range = np.linspace(80, 120, 500)

    # Compute payoff
    payoff_call = np.maximum(price_range - strike_call, 0) - premium_call
    payoff_put = -np.maximum(strike_put - price_range, 0) + premium_put
    risk_reversal_pnl = payoff_call + payoff_put

    # Plot
    plt.figure(figsize=(12, 6))
    plt.plot(
        price_range,
        risk_reversal_pnl,
        label="Risk Reversal Payoff (Buy Call, Sell Put)",
        linewidth=2,
    )
    plt.axhline(0, color="gray", linestyle="--", linewidth=1)
    plt.axvline(spot_price, color="black", linestyle=":", label="Spot Price")
    plt.xlabel("Underlying Price at Expiry", fontsize=14)
    plt.ylabel("P&L", fontsize=14)
    plt.title("Risk Reversal Payoff Diagram", fontsize=16)
    plt.legend()
    plt.grid(True)
    plt.show()


def plot_skew_signals(
    skew,
    signals,
    lower_threshold,
    upper_threshold,
    title="Skew with Entry/Exit Signals",
):
    """Overlay long/short/exit skew signals on raw skew levels."""
    plt.figure(figsize=(14, 5))
    plt.plot(skew, label="Skew", color="blue")

    plt.axhline(upper_threshold, color="red", linestyle="--", label="Upper Threshold")
    plt.axhline(lower_threshold, color="green", linestyle="--", label="Lower Threshold")

    # Entry/Exit markers
    plt.scatter(
        signals[signals["long"]].index,
        skew[signals["long"]],
        color="green",
        marker="^",
        label="Long Signal",
    )
    plt.scatter(
        signals[signals["short"]].index,
        skew[signals["short"]],
        color="red",
        marker="v",
        label="Short Signal",
    )
    plt.scatter(
        signals[signals["exit"]].index,
        skew[signals["exit"]],
        color="purple",
        marker="D",
        label="Exit Signal",
    )

    plt.title(title, fontsize=16)
    plt.xlabel("Date", fontsize=14)
    plt.ylabel("Skew", fontsize=14)
    plt.grid(True)
    plt.legend()
    plt.tight_layout()
    plt.show()


def plot_zscore_signals(
    z_score,
    signals,
    entry_threshold,
    exit_threshold,
    title="Skew Z-Score with Entry/Exit Signals",
):
    """Overlay long/short/exit signals on skew z-score series."""
    plt.figure(figsize=(14, 5))

    plt.plot(z_score, label="Z-Score", color="blue")
    plt.axhline(entry_threshold, color="red", linestyle="--", label="Entry Threshold")
    plt.axhline(-entry_threshold, color="red", linestyle="--")
    plt.axhline(exit_threshold, color="green", linestyle="--", label="Exit Threshold")
    plt.axhline(-exit_threshold, color="green", linestyle="--")

    plt.scatter(
        signals[signals["long"]].index,
        z_score[signals["long"]],
        color="green",
        marker="^",
        label="Long Entry",
    )
    plt.scatter(
        signals[signals["short"]].index,
        z_score[signals["short"]],
        color="red",
        marker="v",
        label="Short Entry",
    )
    plt.scatter(
        signals[signals["exit"]].index,
        z_score[signals["exit"]],
        color="purple",
        marker="D",
        label="Exit",
    )

    plt.title(title, fontsize=16)
    plt.xlabel("Date", fontsize=14)
    plt.ylabel("Z-Score", fontsize=14)
    plt.grid(True)
    plt.legend()
    plt.tight_layout()
    plt.show()


def plot_skew_vs_zscore(synthetic_skew):
    """Plot absolute skew and skew z-score on dual y-axes."""
    fig, ax1 = plt.subplots(figsize=(14, 6))

    zscore = synthetic_skew["skew_zscore"].dropna()
    skew = synthetic_skew["skew_abs"].loc[zscore.index]

    ax1.plot(skew, label="Skew", color="blue")
    ax1.set_ylabel("Skew", color="blue", fontsize=14)
    ax1.tick_params(axis="y", labelcolor="blue")

    ax2 = ax1.twinx()
    ax2.plot(zscore, label="Z-Score", color="orange")
    ax2.set_ylabel("Z-Score", color="orange", fontsize=14)
    ax2.tick_params(axis="y", labelcolor="orange")

    lines1, labels1 = ax1.get_legend_handles_labels()
    lines2, labels2 = ax2.get_legend_handles_labels()
    ax1.legend(lines1 + lines2, labels1 + labels2, loc="lower left")

    plt.title("Raw Skew and Z-Score of Skew", fontsize=16)
    plt.grid(True)
    plt.tight_layout()
    plt.show()


def plot_boll_bands(synthetic_skew, signals):
    """Plot skew with Bollinger-style entry/exit bands and signal markers."""
    window = 60  # e.g. 60-day rolling
    k_entry = 1.5  # entry at ±1.5σ
    k_exit = 0.5  # exit at ±0.5σ

    # rolling stats
    m = synthetic_skew["skew_abs"].rolling(window).mean().dropna()
    s = synthetic_skew["skew_abs"].rolling(window).std().dropna()
    skew = synthetic_skew["skew_abs"].loc[m.index]

    # bands
    upper_entry = m + k_entry * s
    lower_entry = m - k_entry * s
    upper_exit = m + k_exit * s
    lower_exit = m - k_exit * s

    plt.figure(figsize=(14, 5))
    plt.plot(skew, label="Skew", color="blue")
    plt.plot(m, label="Rolling Mean", color="black")
    plt.plot(upper_entry, "--", label=f"{k_entry}σ Entry", color="red")
    plt.plot(lower_exit, ":", label=f"{k_exit}σ Exit", color="green")
    plt.plot(lower_entry, "--", color="red")
    plt.plot(upper_exit, ":", color="green")

    # overlay your signals
    plt.scatter(
        signals[signals.long].index,
        synthetic_skew.loc[signals.long, "skew_abs"],
        marker="^",
        color="green",
        label="Long Entry",
    )
    plt.scatter(
        signals[signals.short].index,
        synthetic_skew.loc[signals.short, "skew_abs"],
        marker="v",
        color="red",
        label="Short Entry",
    )
    plt.scatter(
        signals[signals.exit].index,
        synthetic_skew.loc[signals.exit, "skew_abs"],
        marker="D",
        color="purple",
        label="Exit",
    )

    plt.title("Skew with Bollinger-Style Bands and Signals", fontsize=16)
    plt.xlabel("Date", fontsize=14)
    plt.ylabel("Skew", fontsize=14)
    plt.legend()
    plt.grid(True)
    plt.tight_layout()
    plt.show()


def plot_zscore_signals_with_vix(
    z_score, signals, vix, entry_threshold, exit_threshold, title, vix_filter=20
):
    """Plot skew z-score signals with shaded VIX regime filter."""
    fig, ax = plt.subplots(figsize=(14, 5))

    z_score = z_score.copy()
    signals = signals.copy()
    vix = vix.copy()

    common_dates = z_score.index.intersection(vix.index).intersection(signals.index)
    z_score = z_score.loc[common_dates]
    signals = signals.loc[common_dates]
    vix = vix.loc[common_dates]

    # Plot Skew (Left Axis)
    ax.plot(z_score, label="Skew Z-Score", color="blue")

    ax.axhline(entry_threshold, color="red", linestyle="--")
    ax.axhline(-entry_threshold, color="red", linestyle="--")
    ax.axhline(exit_threshold, color="green", linestyle="--")
    ax.axhline(-exit_threshold, color="green", linestyle="--")

    # Entry and Exit markers
    ax.scatter(
        signals[signals["long"]].index,
        z_score[signals["long"]],
        color="green",
        marker="^",
        s=50,
        label="Long Entry",
    )
    ax.scatter(
        signals[signals["short"]].index,
        z_score[signals["short"]],
        color="red",
        marker="v",
        s=50,
        label="Short Entry",
    )
    ax.scatter(
        signals[signals["exit"]].index,
        z_score[signals["exit"]],
        color="purple",
        marker="D",
        s=50,
        label="Exit",
    )

    ax.set_ylabel("Z-Score", color="blue")
    ax.set_xlabel("Date")
    ax.set_title(title, fontsize=16)

    # Create masks
    high_vix = vix > vix_filter
    low_vix = ~high_vix

    def shade_regions(mask, color, alpha, label):
        start = None
        for date, active in mask.items():
            if active and start is None:
                start = date
            elif not active and start is not None:
                ax.axvspan(
                    start,
                    date,
                    color=color,
                    alpha=alpha,
                    label=label
                    if label not in ax.get_legend_handles_labels()[1]
                    else None,
                )
                start = None
        if start is not None:
            ax.axvspan(
                start,
                mask.index[-1],
                color=color,
                alpha=alpha,
                label=label if label not in ax.get_legend_handles_labels()[1] else None,
            )

    # Shade regions
    shade_regions(high_vix, "red", 0.15, f"VIX > {vix_filter}")
    shade_regions(low_vix, "green", 0.1, f"VIX ≤ {vix_filter}")

    ax.legend(loc="upper left")
    ax.grid(True)
    plt.tight_layout()
    plt.show()


def plot_vix(vix, vix_threshold=20):
    """Plot VIX series with threshold-based regime shading."""
    plt.figure(figsize=(12, 6))
    plt.plot(vix, label="VIX", color="blue")
    plt.axhline(
        vix_threshold, color="red", linestyle="--", label=f"VIX = {vix_threshold}"
    )

    vix = vix.copy()
    # vix = vix.iloc[:, 0]
    # Fill above threshold
    above = vix > vix_threshold

    plt.fill_between(
        vix.index,
        vix_threshold,
        np.where(above, vix, np.nan),  # fill only where VIX > threshold
        color="red",
        alpha=0.2,
    )
    plt.fill_between(
        vix.index,
        vix_threshold,
        np.where(~above, vix, np.nan),  # fill only where VIX > threshold
        color="green",
        alpha=0.2,
    )

    plt.title("VIX Time Series with Regime Threshold", fontsize=16)
    plt.ylabel("VIX", fontsize=14)
    plt.xlabel("Date", fontsize=14)
    plt.legend()
    plt.show()


def plot_ivp(ivp, ivp_lower_threshold=30, ivp_higher_threshold=70):
    """Plot IV percentile series with normal/extreme regime shading."""
    ivp = ivp.copy()
    ivp = ivp.dropna()

    ivp_lower_threshold /= 100
    ivp_higher_threshold /= 100

    # Fill above threshold
    plt.figure(figsize=(12, 6))
    plt.plot(ivp, label="IVP", color="blue")
    plt.axhline(ivp_lower_threshold, color="red", linestyle="--")
    plt.axhline(ivp_higher_threshold, color="red", linestyle="--")

    plt.fill_between(
        ivp.index,
        ivp_higher_threshold,
        ivp,
        where=(ivp >= ivp_higher_threshold),
        color="red",
        alpha=0.2,
        interpolate=True,
        label=f"Extreme regime (IVP <= {ivp_lower_threshold}% & IVP >= {ivp_higher_threshold}%)",
    )

    plt.fill_between(
        ivp.index,
        ivp_lower_threshold,
        ivp,
        where=(ivp <= ivp_lower_threshold),
        color="red",
        alpha=0.2,
        interpolate=True,
    )

    plt.fill_between(
        ivp.index,
        ivp_lower_threshold,
        ivp_higher_threshold,
        color="green",
        alpha=0.2,
        interpolate=True,
        label=f"Normal regime ({ivp_lower_threshold}% ≤ IVP < {ivp_higher_threshold}%)",
    )

    plt.title("IV Percentile (IVP) Regimes", fontsize=16)
    plt.ylabel("IVP", fontsize=14)
    plt.xlabel("Date", fontsize=14)
    plt.legend(loc="upper left")
    plt.grid(True)
    plt.tight_layout()
    plt.show()


def plot_zscore_signals_with_ivp(
    z_score,
    signals,
    ivp,
    entry_threshold,
    exit_threshold,
    ivp_lower_threshold,
    ivp_higher_threshold,
):
    """Plot skew z-score signals with IVP-based regime overlays."""
    z_score = z_score.copy()
    ivp = ivp.copy()
    signals = signals.copy()

    ivp = ivp.dropna()
    z_score = z_score.dropna()

    common_dates = z_score.index.intersection(ivp.index).intersection(signals.index)
    z_score = z_score.loc[common_dates]
    signals = signals.loc[common_dates]
    ivp = ivp.loc[common_dates]

    fig, ax = plt.subplots(figsize=(14, 5))
    # Plot Skew (Left Axis)
    ax.plot(z_score, label="Skew Z-Score", color="blue")

    ax.axhline(entry_threshold, color="red", linestyle="--")
    ax.axhline(-entry_threshold, color="red", linestyle="--")
    # Exit thresholds
    ax.axhline(exit_threshold, color="green", linestyle="--")
    ax.axhline(-exit_threshold, color="green", linestyle="--")

    # Entry and Exit markers
    ax.scatter(
        signals[signals["long"]].index,
        z_score[signals["long"]],
        color="green",
        marker="^",
        s=50,
    )
    ax.scatter(
        signals[signals["short"]].index,
        z_score[signals["short"]],
        color="red",
        marker="v",
        s=50,
    )
    ax.scatter(
        signals[signals["exit"]].index,
        z_score[signals["exit"]],
        color="purple",
        marker="D",
        s=50,
    )

    # Build boolean masks
    normal_band = (ivp_lower_threshold / 100 <= ivp) & (
        ivp <= ivp_higher_threshold / 100
    )
    extreme_band = ~normal_band

    # Helper to shade a mask with a given color
    def shade(mask, color, alpha, label):
        start = None
        for date, val in mask.items():
            if val and start is None:
                start = date
            if start is not None and (not val):
                ax.axvspan(
                    start,
                    date,
                    color=color,
                    alpha=alpha,
                    label=label
                    if label not in ax.get_legend_handles_labels()[1]
                    else None,
                )
                start = None
        if start is not None:
            ax.axvspan(
                start,
                mask.index[-1],
                color=color,
                alpha=alpha,
                label=label if label not in ax.get_legend_handles_labels()[1] else None,
            )

    # Shade outside/mid in orange, panic in red
    shade(
        normal_band,
        "green",
        0.15,
        f"IVP inside [{ivp_lower_threshold}%,{ivp_higher_threshold}%]",
    )
    shade(
        extreme_band,
        "red",
        0.15,
        f"IVP >= outside [{ivp_lower_threshold}%,{ivp_higher_threshold}%]",
    )

    ax.grid(True)
    ax.set_ylabel("Z-Score", color="blue")
    ax.set_xlabel("Date")
    ax.set_title("Skew with Signals and IVP Filter", fontsize=16)
    ax.legend(loc="lower left")
    plt.tight_layout()

    plt.show()


def plot_skew_percentile(skew_perc, lower_threshold=20, upper_threshold=80):
    """Plot skew percentile with lower/upper threshold regimes."""
    skew_perc = skew_perc.copy().dropna()

    lower_threshold /= 100
    upper_threshold /= 100

    plt.figure(figsize=(12, 6))
    plt.plot(skew_perc, label="Skew Percentile", color="blue")
    plt.axhline(lower_threshold, color="red", linestyle="--")
    plt.axhline(upper_threshold, color="red", linestyle="--")

    plt.fill_between(
        skew_perc.index,
        upper_threshold,
        skew_perc,
        where=(skew_perc >= upper_threshold),
        color="red",
        alpha=0.2,
        interpolate=True,
        label=f"Extreme regime (≤ {lower_threshold * 100:.0f}% or ≥ {upper_threshold * 100:.0f}%)",
    )

    plt.fill_between(
        skew_perc.index,
        lower_threshold,
        skew_perc,
        where=(skew_perc <= lower_threshold),
        color="red",
        alpha=0.2,
        interpolate=True,
    )

    plt.fill_between(
        skew_perc.index,
        lower_threshold,
        upper_threshold,
        color="green",
        alpha=0.2,
        interpolate=True,
        label=f"Normal regime ({lower_threshold * 100:.0f}% – {upper_threshold * 100:.0f}%)",
    )

    plt.title("Skew Percentile Regimes", fontsize=16)
    plt.ylabel("Skew Percentile", fontsize=14)
    plt.xlabel("Date", fontsize=14)
    plt.legend(loc="lower right")
    plt.grid(True)
    plt.tight_layout()
    plt.show()


def plot_zscore_signals_with_skew_percentile(
    z_score,
    signals,
    skew_perc,
    entry_threshold,
    exit_threshold,
    lower_threshold=20,
    upper_threshold=80,
    title="Z-Score and Skew Percentile Regimes",
):
    """Plot z-score signals with skew-percentile regime shading."""
    lower_threshold /= 100
    upper_threshold /= 100

    z_score = z_score.copy().dropna()
    skew_perc = skew_perc.copy().dropna()
    signals = signals.copy()

    common_dates = z_score.index.intersection(skew_perc.index).intersection(
        signals.index
    )
    z_score = z_score.loc[common_dates]
    signals = signals.loc[common_dates]
    skew_perc = skew_perc.loc[common_dates]

    fig, ax = plt.subplots(figsize=(14, 5))
    ax.plot(z_score, label="Skew Z-Score", color="blue")

    ax.axhline(entry_threshold, color="red", linestyle="--")
    ax.axhline(-entry_threshold, color="red", linestyle="--")
    ax.axhline(exit_threshold, color="green", linestyle="--")
    ax.axhline(-exit_threshold, color="green", linestyle="--")

    # Entry/Exit Markers
    ax.scatter(
        signals[signals["long"]].index,
        z_score[signals["long"]],
        color="green",
        marker="^",
        s=50,
    )
    ax.scatter(
        signals[signals["short"]].index,
        z_score[signals["short"]],
        color="red",
        marker="v",
        s=50,
    )
    ax.scatter(
        signals[signals["exit"]].index,
        z_score[signals["exit"]],
        color="purple",
        marker="D",
        s=50,
        label="Exit",
    )

    # Define extreme and normal regimes
    extreme_band = (skew_perc <= lower_threshold) | (skew_perc >= upper_threshold)
    normal_band = ~extreme_band

    def shade(mask, color, alpha, label):
        start = None
        for date, val in mask.items():
            if val and start is None:
                start = date
            elif not val and start is not None:
                ax.axvspan(
                    start,
                    date,
                    color=color,
                    alpha=alpha,
                    label=label
                    if label not in ax.get_legend_handles_labels()[1]
                    else None,
                )
                start = None
        if start is not None:
            ax.axvspan(
                start,
                mask.index[-1],
                color=color,
                alpha=alpha,
                label=label if label not in ax.get_legend_handles_labels()[1] else None,
            )

    shade(
        extreme_band,
        "green",
        0.15,
        f"Skew Percentile ≤ {lower_threshold * 100:.0f}% or ≥ {upper_threshold * 100:.0f}%",
    )
    shade(
        normal_band,
        "red",
        0.15,
        f"Skew Percentile in ({lower_threshold * 100:.0f}%, {upper_threshold * 100:.0f}%)",
    )
    ax.set_ylabel("Z-Score", color="blue")
    ax.set_xlabel("Date")
    ax.set_title(title, fontsize=16)
    ax.grid(True)
    ax.legend(loc="lower left")
    plt.tight_layout()
    plt.show()
