SPB Git forge
1commits 1branches 0releases
189.0 KBsize
maindefault branch
5 h agolast push
Python 54.7% TypeScript 42.5% SQL 1.9%
2.9 KB · 62 lines python
Raw Blame History
1"""Benchmarks: buy-and-hold, cash, simple momentum, equal-risk (constant vol target)."""23from __future__ import annotations45import pandas as pd67from ktrade.strategy.base import ParamSpec, Strategy, StrategyMeta, log_returns, vol_target_scale8910class BuyAndHold(Strategy):11    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=[])1213    def features(self, df: pd.DataFrame) -> pd.DataFrame:14        return pd.DataFrame({"ts": df["ts"].values, "close": df["close"].values}, index=df.index)1516    def target(self, feats: pd.DataFrame) -> pd.Series:17        return pd.Series(1.0, index=feats.index)181920class Cash(Strategy):21    meta = StrategyMeta(name="cash", label="Cash (benchmark)", family="benchmark", hypothesis="Never invested; zero return, zero risk (ignores interest).", experimental=False, min_bars=1, params=[])2223    def features(self, df: pd.DataFrame) -> pd.DataFrame:24        return pd.DataFrame({"ts": df["ts"].values, "close": df["close"].values}, index=df.index)2526    def target(self, feats: pd.DataFrame) -> pd.Series:27        return pd.Series(0.0, index=feats.index)282930class SimpleMomentum(Strategy):31    meta = StrategyMeta(32        name="simple_momentum", label="Simple momentum (benchmark)", family="benchmark",33        hypothesis="Long when the trailing N-bar return is positive, otherwise cash. No vol scaling.", experimental=False, min_bars=30,34        params=[ParamSpec(name="lookback", type="int", default=24, min=2, max=2000)],35    )3637    def features(self, df: pd.DataFrame) -> pd.DataFrame:38        f = pd.DataFrame({"ts": df["ts"].values, "close": df["close"].values}, index=df.index)39        f["mom"] = df["close"] / df["close"].shift(self.params["lookback"]) - 140        return f4142    def target(self, feats: pd.DataFrame) -> pd.Series:43        return (feats["mom"] > 0).astype(float).where(feats["mom"].notna(), 0.0)444546class EqualRisk(Strategy):47    meta = StrategyMeta(48        name="equal_risk", label="Constant-vol long (benchmark)", family="benchmark",49        hypothesis="Always long, sized to a constant annualized volatility target (inverse-vol).", experimental=False, min_bars=60,50        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)],51    )5253    def features(self, df: pd.DataFrame) -> pd.DataFrame:54        f = pd.DataFrame({"ts": df["ts"].values, "close": df["close"].values}, index=df.index)55        f["ret"] = log_returns(df["close"])56        bars_per_year = 365 * 24 * 60 / float(df.attrs.get("interval_min", 60))57        f["vol_scale"] = vol_target_scale(f["ret"], self.params["vol_lookback"], self.params["target_vol"], bars_per_year)58        return f5960    def target(self, feats: pd.DataFrame) -> pd.Series:61        return feats["vol_scale"].fillna(0.0)62