# 來源註記：這是 Codex 弄的。
"""可重現的教學用 SMA baseline；不下載真實市場資料。"""

from __future__ import annotations

import numpy as np
import pandas as pd


def run_backtest(
    close: pd.Series,
    window: int = 20,
    cost_bps: float = 5.0,
) -> pd.DataFrame:
    if window < 2:
        raise ValueError("window 必須 >= 2")
    close = close.dropna().astype(float).sort_index()
    sma = close.rolling(window, min_periods=window).mean()
    signal = (close > sma).astype(float)
    position = signal.shift(1).fillna(0.0)  # 今日 close 訊號，下一期才持有
    asset_return = close.pct_change().fillna(0.0)
    gross_return = position * asset_return
    turnover = position.diff().abs().fillna(position.abs())
    cost = turnover * (cost_bps / 10_000)
    net_return = gross_return - cost
    equity = 100_000 * (1 + net_return).cumprod()
    return pd.DataFrame(
        {
            "close": close,
            "sma": sma,
            "signal": signal,
            "position": position,
            "asset_return": asset_return,
            "turnover": turnover,
            "cost": cost,
            "net_return": net_return,
            "equity": equity,
        }
    )


def _synthetic_close() -> pd.Series:
    rng = np.random.default_rng(7)
    dates = pd.bdate_range("2023-01-02", periods=500)
    regime_drift = np.where(np.arange(len(dates)) < 250, 0.0005, -0.00005)
    returns = regime_drift + rng.normal(0, 0.012, len(dates))
    return pd.Series(100 * np.cumprod(1 + returns), index=dates, name="close")


def _demo() -> None:
    close = _synthetic_close()
    baseline = run_backtest(close, window=20, cost_bps=5)
    expensive = run_backtest(close, window=20, cost_bps=50)
    print(f"Baseline final equity: {baseline['equity'].iloc[-1]:,.2f}")
    print(f"50 bps final equity:   {expensive['equity'].iloc[-1]:,.2f}")
    assert expensive["equity"].iloc[-1] <= baseline["equity"].iloc[-1]
    changed = baseline.index[baseline["signal"].diff().fillna(0).ne(0)]
    for timestamp in changed[:10]:
        location = baseline.index.get_loc(timestamp)
        if location + 1 < len(baseline):
            assert baseline["position"].iloc[location + 1] == baseline["signal"].iloc[location]


if __name__ == "__main__":
    _demo()

