Repository Public
1"""Long/short moving-average trend with volatility targeting: side from two means, size from realised volatility."""23import numpy as np45META = {6 "name": "ma_trend_perp",7 "version": "1",8 "kind": "perp",9 "universe": ["BTC", "ETH", "SOL", "HYPE"],10 "interval": "1h",11 "lookback": 400,12 "max_leverage": 2.0,13 "max_gross_exposure": 4.0,14 "params": {"fast": 24, "slow": 120, "vol_window": 168, "target_vol": 0.6, "max_weight": 0.5},15}1617SECONDS_PER_YEAR = 365.0 * 86400.0181920def target_weights(data, params):21 fast, slow, vol_window = int(params["fast"]), int(params["slow"]), int(params["vol_window"])22 target_vol, max_weight = float(params["target_vol"]), min(float(params["max_weight"]), 1.0)23 n = len(data)24 weights = {}25 for symbol, df in data.items():26 close = df["close"].to_numpy()27 if len(close) < max(slow, vol_window + 1) or fast >= slow or fast < 1 or vol_window < 2:28 weights[symbol] = 0.029 continue30 side = float(np.sign(close[-fast:].mean() - close[-slow:].mean()))31 bar_seconds = (df.index[-1] - df.index[-2]).total_seconds()32 log_returns = np.diff(np.log(close[-(vol_window + 1):]))33 realised = log_returns.std(ddof=1) * np.sqrt(SECONDS_PER_YEAR / bar_seconds)34 if not realised > 0:35 weights[symbol] = 0.036 continue37 weights[symbol] = side * min(max_weight, target_vol / n / realised)38 return weights39