import talib.abstract as ta from pandas import DataFrame from freqtrade.strategy import IStrategy, DecimalParameter, IntParameter from freqtrade.persistence import Trade import logging logger = logging.getLogger(__name__) class GridStrategy(IStrategy): """ 专业自适应网格策略 核心改进(相比交易所内置网格): 1. ATR 动态间距:波动大 → 格子自动拉宽,避免过早加仓;波动小 → 格子收窄,提高资金效率 2. ADX 趋势过滤:ADX > 阈值说明是趋势市,不开新仓;只在震荡市运行 3. 递增仓位:第1次加仓1倍,第2次1.5倍,第3次2倍 → 越跌买越多,更快摊低均价 4. ATR 动态止盈:回升 N×ATR 就出场,不用死板的固定百分比 5. EMA200 趋势方向:只在大方向向上时做多,避免逆势抄底 """ INTERFACE_VERSION = 3 timeframe = "1h" can_short = False position_adjustment_enable = True max_entry_position_adjustment = 4 # 最多加仓4次(共5档) # ROI 设宽,主要由 custom_exit 控制精确出场 minimal_roi = { "0": 0.10, "2880": 0.02, # 持仓 120 天还没涨,降到 2% 接受出场 "5760": 0.005, } # 止损设宽:覆盖5档×ATR的加仓空间 stoploss = -0.25 trailing_stop = False startup_candle_count = 50 # ── Hyperopt 参数 ──────────────────────────────────────────────── # 网格间距倍数(间距 = atr_mult × ATR) atr_mult = DecimalParameter(0.5, 2.5, default=1.2, decimals=1, space="buy") # ADX 阈值:低于此值认为是震荡市,才允许入场 adx_max = IntParameter(15, 40, default=28, space="buy") # 布林带宽度上限:太宽说明正在剧烈波动,不入场 bb_width_max = DecimalParameter(0.04, 0.20, default=0.10, decimals=2, space="buy") # 止盈触发:均价回升 tp_atr_mult × ATR 就出场 tp_atr_mult = DecimalParameter(0.5, 2.0, default=1.0, decimals=1, space="sell") # ATR 缓存:populate_indicators 中更新,custom_exit/adjust_entry 中读取 _atr_cache: dict = {} # ── 指标计算 ───────────────────────────────────────────────────── def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe["atr"] = ta.ATR(dataframe, timeperiod=14) dataframe["adx"] = ta.ADX(dataframe, timeperiod=14) dataframe["ema200"] = ta.EMA(dataframe, timeperiod=200) dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14) upper, mid, lower = ta.BBANDS(dataframe["close"], timeperiod=20, nbdevup=2.0, nbdevdn=2.0) dataframe["bb_upper"] = upper dataframe["bb_mid"] = mid dataframe["bb_lower"] = lower # BB宽度归一化((上轨-下轨)/中轨),代表相对波动幅度 dataframe["bb_width"] = (upper - lower) / mid # 缓存最新 ATR,供 custom_exit 和 adjust_trade_entry 使用 if len(dataframe) > 0: self._atr_cache[metadata["pair"]] = float(dataframe["atr"].iloc[-1]) return dataframe # ── 入场信号 ───────────────────────────────────────────────────── def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe.loc[ ( # 大方向:价格在 EMA200 之上(长期上升趋势) (dataframe["close"] > dataframe["ema200"]) & # 趋势强度低:ADX 小 → 震荡市,适合网格 (dataframe["adx"] < self.adx_max.value) & # 波动幅度不过大:BB 宽度在合理范围内 (dataframe["bb_width"] < self.bb_width_max.value) & # 入场位置:价格不高于 BB 中轨太多(不在高位追入) (dataframe["close"] <= dataframe["bb_mid"] * 1.01) & # 成交量有效 (dataframe["volume"] > 0) ), "enter_long", ] = 1 return dataframe def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: # 不设退出信号,全部由 custom_exit + minimal_roi 控制 return dataframe # ── 动态止盈 ───────────────────────────────────────────────────── def custom_exit(self, pair, trade, current_time, current_rate, current_profit, **kwargs): """ 均价回升 tp_atr_mult × ATR 时止盈出场。 ATR 随波动率变化,止盈目标也自动适配——高波动期目标更高,低波动期更敏感。 """ atr = self._atr_cache.get(pair) if atr is None or trade.open_rate == 0: return None # 动态止盈百分比:ATR / 均价 × 止盈倍数 profit_target = (self.tp_atr_mult.value * atr) / trade.open_rate profit_target = max(profit_target, 0.008) # 最低 0.8%,覆盖双边手续费 if current_profit >= profit_target: filled = trade.nr_of_successful_entries return f"grid_tp_lvl{filled}_{current_profit:.3f}" return None # ── 网格加仓逻辑 ───────────────────────────────────────────────── def adjust_trade_entry( self, trade: Trade, current_time, current_rate: float, current_profit: float, min_stake, max_stake: float, current_entry_rate: float, current_exit_rate: float, current_entry_profit: float, current_exit_profit: float, **kwargs, ): """ 自适应网格加仓: - 间距 = atr_mult × ATR(随市场波动自动调整) - 仓位递增:填入第N档时,加仓 = 初始仓 × (1 + (N-1)×0.5) 档位 加仓倍数 资金比例 1 1.0× 第1次同等金额 2 1.5× 价格再跌一格 3 2.0× ... 4 2.5× 第4次加仓 """ filled = trade.nr_of_successful_entries if filled >= (self.max_entry_position_adjustment + 1): return None # 动态间距:ATR占当前价的比例 atr = self._atr_cache.get(trade.pair) if atr: grid_step = (self.atr_mult.value * atr) / current_rate grid_step = max(grid_step, 0.01) # 最小1% grid_step = min(grid_step, 0.08) # 最大8%(防极端行情) else: grid_step = 0.025 # 无ATR时降级到2.5%固定间距 # 达到触发阈值才加仓 threshold = -(grid_step * filled) if current_profit > threshold: return None # 递增仓位:第1档加1倍,第2档加1.5倍,依此类推 multiplier = 1.0 + max(filled - 1, 0) * 0.5 stake = trade.stake_amount * multiplier stake = max(stake, min_stake or 0) stake = min(stake, max_stake) logger.info( f"[Grid] {trade.pair} 第{filled}次加仓 | " f"间距={grid_step:.2%} 阈值={threshold:.2%} 当前={current_profit:.2%} | " f"加仓={stake:.2f} USDT ({multiplier}x)" ) return stake