Repository Public
1"""Long/short z-score mean reversion with a stop: fade large deviations from the short-term mean."""23import numpy as np45META = {6 "name": "zscore_reversion_perp",7 "version": "1",8 "kind": "perp",9 "universe": ["BTC", "ETH", "SOL", "HYPE"],10 "interval": "1h",11 "lookback": 300,12 "max_leverage": 2.0,13 "max_gross_exposure": 2.0,14 "params": {"window": 48, "entry_z": 2.0, "exit_z": 0.5, "stop_z": 4.0, "gross": 1.0},15}161718def _zscores(close, window):19 """z of each close against the mean and standard deviation of the `window` closes ending on it."""20 views = np.lib.stride_tricks.sliding_window_view(close, window)21 mean = views.mean(axis=1)22 std = views.std(axis=1, ddof=1)23 z = np.zeros(len(mean))24 ok = std > 025 z[ok] = (close[window - 1:][ok] - mean[ok]) / std[ok]26 return z272829def _state(z, entry_z, exit_z, stop_z):30 """Replay the rule over the window (the function keeps no memory): +1 long, -1 short, 0 flat."""31 state, blocked = 0, False32 for value in z:33 if blocked and abs(value) < exit_z:34 blocked = False35 if state == 0:36 if not blocked and entry_z < abs(value) < stop_z:37 state = -1 if value > 0 else 138 elif abs(value) > stop_z: # the deviation kept growing: stop, wait for a return39 state, blocked = 0, True40 elif (state == 1 and value > -exit_z) or (state == -1 and value < exit_z):41 state = 042 return state434445def target_weights(data, params):46 window = int(params["window"])47 entry_z, exit_z, stop_z = float(params["entry_z"]), float(params["exit_z"]), float(params["stop_z"])48 gross = float(params["gross"])49 n = len(data)50 weights = {}51 for symbol, df in data.items():52 close = df["close"].to_numpy()53 if window < 3 or len(close) < window + 1 or not exit_z < entry_z < stop_z:54 weights[symbol] = 0.055 continue56 weights[symbol] = _state(_zscores(close, window), entry_z, exit_z, stop_z) * gross / n57 return weights58