SPB Git

Repository Public

strategy.py 2.5 KB 60 lines python

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
Files main
Menu

Theme