"""Benchmarks: buy-and-hold, cash, simple momentum, equal-risk (constant vol target).""" from __future__ import annotations import pandas as pd from ktrade.strategy.base import ParamSpec, Strategy, StrategyMeta, log_returns, vol_target_scale class BuyAndHold(Strategy): meta = StrategyMeta(name="buy_and_hold", label="Buy & hold (benchmark)", family="benchmark", hypothesis="Fully invested from the first bar.", experimental=False, min_bars=2, params=[]) def features(self, df: pd.DataFrame) -> pd.DataFrame: return pd.DataFrame({"ts": df["ts"].values, "close": df["close"].values}, index=df.index) def target(self, feats: pd.DataFrame) -> pd.Series: return pd.Series(1.0, index=feats.index) class Cash(Strategy): meta = StrategyMeta(name="cash", label="Cash (benchmark)", family="benchmark", hypothesis="Never invested; zero return, zero risk (ignores interest).", experimental=False, min_bars=1, params=[]) def features(self, df: pd.DataFrame) -> pd.DataFrame: return pd.DataFrame({"ts": df["ts"].values, "close": df["close"].values}, index=df.index) def target(self, feats: pd.DataFrame) -> pd.Series: return pd.Series(0.0, index=feats.index) class SimpleMomentum(Strategy): meta = StrategyMeta( name="simple_momentum", label="Simple momentum (benchmark)", family="benchmark", hypothesis="Long when the trailing N-bar return is positive, otherwise cash. No vol scaling.", experimental=False, min_bars=30, params=[ParamSpec(name="lookback", type="int", default=24, min=2, max=2000)], ) def features(self, df: pd.DataFrame) -> pd.DataFrame: f = pd.DataFrame({"ts": df["ts"].values, "close": df["close"].values}, index=df.index) f["mom"] = df["close"] / df["close"].shift(self.params["lookback"]) - 1 return f def target(self, feats: pd.DataFrame) -> pd.Series: return (feats["mom"] > 0).astype(float).where(feats["mom"].notna(), 0.0) class EqualRisk(Strategy): meta = StrategyMeta( name="equal_risk", label="Constant-vol long (benchmark)", family="benchmark", hypothesis="Always long, sized to a constant annualized volatility target (inverse-vol).", experimental=False, min_bars=60, params=[ParamSpec(name="vol_lookback", type="int", default=48, min=5, max=1000), ParamSpec(name="target_vol", type="float", default=0.40, min=0.01, max=3.0)], ) def features(self, df: pd.DataFrame) -> pd.DataFrame: f = pd.DataFrame({"ts": df["ts"].values, "close": df["close"].values}, index=df.index) f["ret"] = log_returns(df["close"]) bars_per_year = 365 * 24 * 60 / float(df.attrs.get("interval_min", 60)) f["vol_scale"] = vol_target_scale(f["ret"], self.params["vol_lookback"], self.params["target_vol"], bars_per_year) return f def target(self, feats: pd.DataFrame) -> pd.Series: return feats["vol_scale"].fillna(0.0)