💬 Forum

GridStrategy

🏆 League #1862 / 1939

Meiyerlove/lhjy/freqtrade_bot/user_data/strategies/GridStrategy.py · first seen 2026-07-16 · repo updated 2026-04-29 · ⬇ 1 download

Basics mode: spot timeframe: 1h interface version: 3
Settings stoploss: -0.25 has minimal roi dca startup candle count: 50 hyperopt hyperopt params: 4
Indicators ADX ATR Bollinger_Bands EMA RSI talib
Concepts dca grid
15 related strategies ( identical code, similar name)

Each tile is a different kind of check — from an instant code lint to full sandboxed backtests and forward tests on recent data. Not sure what a check actually proves? See the FAQ →

Source

Download Raw
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
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

    # ── 动态止盈 ─────────────────────────────────────────────────────
    FEE_RATE = 0.001  # OKX 现货挂单费率 0.10%

    def custom_exit(self, pair, trade, current_time, current_rate,
                    current_profit, **kwargs):
        """
        均价回升 tp_atr_mult × ATR 时止盈出场。
        最小止盈随 DCA 档数动态提高,确保每笔交易扣完手续费后都真正盈利。
        """
        atr = self._atr_cache.get(pair)
        if atr is None or trade.open_rate == 0:
            return None

        filled = trade.nr_of_successful_entries
        # DCA 每加一档多一笔入场手续费;最小止盈 = 累计入场费 + 0.2% 净利缓冲
        fee_buffer = self.FEE_RATE * filled + 0.002
        atr_target  = (self.tp_atr_mult.value * atr) / trade.open_rate
        profit_target = max(atr_target, fee_buffer)

        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