Timeframe
1h
Direction
Long Only
Stoploss
-25.0%
Trailing Stop
No
ROI
0m: 10.0%, 2880m: 2.0%, 5760m: 0.5%
Interface Version
3
Startup Candles
50
Indicators
5
freqtrade/freqtrade-strategies
Strategy 003 author@: Gerald Lonlas github@: https://github.com/freqtrade/freqtrade-strategies
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