Repository Public
1"""Donchian channel breakout: enter on a new N-bar high, leave on a new M-bar low."""23import numpy as np45META = {6 "name": "donchian_breakout",7 "version": "2",8 "universe": ["ETH", "LINK", "AAVE"],9 "interval": "1h",10 "lookback": 2200,11 "params": {"entry": 480, "exit": 240},12}131415def _is_long(df, entry, exit_):16 """Replay the channel over the window: long if the most recent event is an upside breakout."""17 close = df["close"]18 upper = df["high"].rolling(entry).max().shift(1) # highest high of the previous `entry` bars19 lower = df["low"].rolling(exit_).min().shift(1) # lowest low of the previous `exit_` bars20 up = np.nonzero((close > upper).to_numpy())[0]21 down = np.nonzero((close < lower).to_numpy())[0]22 if len(up) == 0:23 return False24 return len(down) == 0 or up[-1] > down[-1]252627def target_weights(data, params):28 entry, exit_ = int(params["entry"]), int(params["exit"])29 n = len(data)30 weights = {}31 for symbol, df in data.items():32 if len(df) <= max(entry, exit_) or entry < 1 or exit_ < 1:33 weights[symbol] = 0.034 continue35 weights[symbol] = 1.0 / n if _is_long(df, entry, exit_) else 0.036 return weights37