Repository Public
1"""Delta-neutral cash-and-carry: spot long + perpetual short of the same asset while funding is persistently high."""23import numpy as np45META = {6 "name": "funding_carry_neutral_perp",7 "version": "1",8 "kind": "perp",9 "universe": ["BTC", "ETH", "SOL", "HYPE", "BTC.SPOT", "ETH.SPOT", "SOL.SPOT", "HYPE.SPOT"],10 "interval": "1h",11 "lookback": 1100,12 "max_leverage": 1.0,13 "max_gross_exposure": 1.0,14 "params": {"window": 72, "z_window": 504, "min_rate_apr": 0.15, "persistence": 0.75, "z_full": 2.0,15 "gross": 0.9},16}1718SECONDS_PER_YEAR = 365.0 * 86400.019SPOT = ".SPOT"202122def carry_strength(df, window, z_window, min_rate_apr, persistence, z_full):23 """0..1: how strongly funding says 'longs are paying, persistently, more than usual'. 0 when it does not."""24 if "funding" not in df.columns or window < 2 or z_window < 2 * window or z_full <= 0:25 return 0.026 rate = df["funding"].to_numpy()27 if len(rate) < z_window or len(df) < 2:28 return 0.029 rate = rate[-z_window:]30 if np.isnan(rate).mean() > 0.1: # too many settlements missing to say anything31 return 0.032 rate = np.where(np.isnan(rate), 0.0, rate)33 recent = rate[-window:]34 bar_seconds = (df.index[-1] - df.index[-2]).total_seconds()35 apr = recent.mean() * SECONDS_PER_YEAR / bar_seconds36 if apr < min_rate_apr or (recent > 0).mean() < persistence:37 return 0.038 means = np.lib.stride_tricks.sliding_window_view(rate, window).mean(axis=1)39 spread = means.std(ddof=1)40 if not spread > 0:41 return 0.042 z = (means[-1] - means.mean()) / spread43 return float(min(max(z / z_full, 0.0), 1.0))444546def target_weights(data, params):47 window, z_window = int(params["window"]), int(params["z_window"])48 gross = min(float(params["gross"]), 1.0) # half in spot, half short: never more than the equity49 weights = {symbol: 0.0 for symbol in data}50 pairs = [s for s in data if not s.endswith(SPOT) and s + SPOT in data]51 if not pairs:52 return weights53 leg = gross / (2.0 * len(pairs))54 for symbol in pairs:55 strength = carry_strength(data[symbol], window, z_window, float(params["min_rate_apr"]),56 float(params["persistence"]), float(params["z_full"]))57 weights[symbol] = -strength * leg # short the perpetual58 weights[symbol + SPOT] = strength * leg # hold the same notional of the asset59 return weights60