spb/crypto-trade-bot
Public
Python 54.7%
TypeScript 42.5%
SQL 1.9%
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