Repository Public
1"""Funding carry, perp leg only: short a perpetual whose funding is persistently positive and large (directional)."""23import numpy as np45META = {6 "name": "funding_carry_perp",7 "version": "1",8 "kind": "perp",9 "universe": ["BTC", "ETH", "SOL", "HYPE"],10 "interval": "1h",11 "lookback": 1100,12 "max_leverage": 2.0,13 "max_gross_exposure": 2.0,14 "params": {"window": 72, "z_window": 504, "min_rate_apr": 0.15, "persistence": 0.75, "z_full": 2.0,15 "gross": 1.0},16}1718SECONDS_PER_YEAR = 365.0 * 86400.0192021def carry_strength(df, window, z_window, min_rate_apr, persistence, z_full):22 """0..1: how strongly funding says 'longs are paying, persistently, more than usual'. 0 when it does not."""23 if "funding" not in df.columns or window < 2 or z_window < 2 * window or z_full <= 0:24 return 0.025 rate = df["funding"].to_numpy()26 if len(rate) < z_window or len(df) < 2:27 return 0.028 rate = rate[-z_window:]29 if np.isnan(rate).mean() > 0.1: # too many settlements missing to say anything30 return 0.031 rate = np.where(np.isnan(rate), 0.0, rate)32 recent = rate[-window:]33 bar_seconds = (df.index[-1] - df.index[-2]).total_seconds()34 apr = recent.mean() * SECONDS_PER_YEAR / bar_seconds35 if apr < min_rate_apr or (recent > 0).mean() < persistence:36 return 0.037 means = np.lib.stride_tricks.sliding_window_view(rate, window).mean(axis=1)38 spread = means.std(ddof=1)39 if not spread > 0:40 return 0.041 z = (means[-1] - means.mean()) / spread42 return float(min(max(z / z_full, 0.0), 1.0))434445def target_weights(data, params):46 window, z_window = int(params["window"]), int(params["z_window"])47 gross = float(params["gross"])48 n = len(data)49 weights = {}50 for symbol, df in data.items():51 strength = carry_strength(df, window, z_window, float(params["min_rate_apr"]), float(params["persistence"]),52 float(params["z_full"]))53 weights[symbol] = -strength * gross / n54 return weights55