第 4 章 · 02 回调函数全解 本节摘要:IStrategy 的三个 方法是「机械规则」——基于 DataFrame 列算信号,粒度是「K 线」。但实战中很多决策需要「持仓感知」(知道当前浮盈多少)、「时间感知」(持仓多久了)、「事件感知」(订单刚成交)。freqtrade 用回调函数(callbacks)满足这类需求——引擎在交易流程的关键节点上调用你重写的方法。本节把最常用的几个回调一次讲透: 、 、 、 、 、 、 ,以及 DCA 加仓用的 。读完本节,你能写出「持仓 30 分钟后止损上移」「浮盈 5% 后启动追踪止盈」「只在订单簿合理时入场」这类非机械逻辑。 内容来源:原项目文档 ,汉化并套用体系化模板。
本节摘要:IStrategy 的三个
populate_*方法是「机械规则」——基于 DataFrame 列算信号,粒度是「K 线」。但实战中很多决策需要「持仓感知」(知道当前浮盈多少)、「时间感知」(持仓多久了)、「事件感知」(订单刚成交)。freqtrade 用**回调函数(callbacks)**满足这类需求——引擎在交易流程的关键节点上调用你重写的方法。本节把最常用的几个回调一次讲透:custom_stoploss、custom_exit、custom_entry_price、custom_exit_price、confirm_trade_entry/exit、bot_loop_start、confirm_trade_entry,以及 DCA 加仓用的adjust_trade_position。读完本节,你能写出「持仓 30 分钟后止损上移」「浮盈 5% 后启动追踪止盈」「只在订单簿合理时入场」这类非机械逻辑。
内容来源:原项目文档
docs/strategy-callbacks.md,汉化并套用体系化模板。
⚠️ 风险提示:回调用错地方比不用更糟——比如
confirm_trade_entry里基于未来数据决策、或custom_stoploss返回过松的值,都可能让单笔亏损失控。回调在实盘前必须经过回测 + Dry-Run。
阅读完本节,你应当能够:
custom_stoploss 做动态止损。custom_entry_price / custom_exit_price 做自定义定价。confirm_trade_entry / confirm_trade_exit 做最后一关确认。custom_exit 做基于持仓状态的出场。bot_loop_start 做每轮起始的预处理。回忆第 2 章第 01 节的 process() 八步,回调就嵌在这些步骤里:
每个回调都是 IStrategy 上的可选方法——不重写就用默认行为,重写就接管那一步。
签名:
def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime, current_rate: float, current_profit: float, **kwargs) -> float: return None # None 表示用默认 stoploss
None 表示保持默认。def custom_stoploss(self, pair, trade, current_time, current_rate, current_profit, **kwargs): # 浮盈 5% 后,把止损上移到 +2%(保证不亏) if current_profit > 0.05: return -0.02 # 注意:相对当前价,不是相对开仓价 # 默认 -10% return None
💡 返回值的语义:返回的是「相对当前价的止损距离」,不是「止损价」。
-0.02意思是「比当前价低 2% 的位置止损」。这与类属性stoploss = -0.10(相对开仓价)的语义不同,容易混淆。第 04 节详讲。
签名:
def custom_exit(self, pair: str, trade: Trade, current_time: datetime, current_rate: float, current_profit: float, **kwargs) -> bool | str: return False # False/None 表示不出场
True 或字符串(出场理由,会写入 exit_reason);False/None 不出场。trade 的字段(持仓时长、入场价、enter_tag)做复杂判断。def custom_exit(self, pair, trade, current_time, current_rate, current_profit, **kwargs): # 持仓超过 2 小时且未盈利,走人 if (current_time - trade.open_date_utc).total_seconds() > 2 * 3600 and current_profit < 0: return "timeout_no_profit" return None
与 populate_exit_trend 的区别:populate_exit_trend 基于 DataFrame 列(K 线指标),custom_exit 基于 trade 状态(持仓信息)。两者「或」关系。
签名:
def custom_entry_price(self, pair: str, current_time: datetime, proposed_rate: float, entry_tag: str | None, side: str, **kwargs) -> float: return proposed_rate # 默认用 proposed_rate
custom_entry_price)、平仓前(custom_exit_price)。proposed_rate 是引擎基于 entry_pricing/exit_pricing 算出的「建议价」。典型用法——「只在订单簿最佳买盘的基础上再低 0.5% 挂买」:
def custom_entry_price(self, pair, current_time, proposed_rate, entry_tag, side, **kwargs): orderbook = self.dp.orderbook(pair, 1) if not orderbook: return proposed_rate best_bid = orderbook["bids"][0][0] return min(best_bid * 0.995, proposed_rate) # 更低一点
⚠️ 回测限制:回测中没有真实订单簿,
self.dp.orderbook返回空。涉及订单簿的回调只在 live/dry_run 有意义,回测会回退到proposed_rate。
签名:
def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float, time_in_force: str, current_time: datetime, entry_tag: str | None, side: str, **kwargs) -> bool: return True # True 确认下单,False 取消
True 放行,False 取消。def confirm_trade_entry(self, pair, order_type, amount, rate, time_in_force, current_time, entry_tag, side, **kwargs): # 5 分钟内同对进场超过 3 次,怀疑异常,取消 recent = Trade.get_trades_proxy(pair=pair, is_open=True, open_date=current_time - timedelta(minutes=5)) if len(recent) >= 3: return False return True
💡 confirm 与 custom_exit 的分工:
custom_exit决定「要不要出」;confirm_trade_exit决定「已经决定出了,最后确认下」。前者是发起,后者是审批。
def bot_loop_start(self, current_time: datetime, **kwargs) -> None: # 每次主循环开始时跑一次(对所有对) pass
def bot_loop_start(self, current_time, **kwargs): # 计算今日已平仓的总盈利,用于动态调整仓位 today = current_time.date() closed = Trade.get_trades_proxy(is_open=False, close_date=datetime(today.year, today.month, today.day)) self.today_profit = sum(t.close_profit_abs for t in closed)
def adjust_trade_position(self, trade: Trade, current_time: datetime, current_rate: float, current_profit: float, min_stake: float | None, max_stake: float | None, **kwargs) -> float | None | list: return None
position_adjustment_enable: true)。None → 不动。经典 DCA——「浮亏 5% 时补一仓」:
def adjust_trade_position(self, trade, current_time, current_rate, current_profit, min_stake, max_stake, **kwargs): if current_profit < -0.05 and trade.nr_of_successful_entries == 1: return stake_amount_for_dca # 加一仓 return None
第 05 节会详讲 DCA、partial exit 与杠杆相关。
⚠️ 关键警告:这是回测与实盘最容易失配的地方。
| 模式 | 回调调用频率 |
|---|---|
| Live | 大多数回调每次迭代(~5 秒) 调用一次 |
| Backtesting | 大多数回调每根 K 线调用一次(除非用 --timeframe-detail) |
含义:
custom_stoploss 每 5 秒跑一次,能在 5m K 线内多次反应。custom_stoploss 每根 K 线只跑一次,反应粒度更粗。💡 对策:对价格敏感的回调(止损、定价),回测时用
--timeframe-detail 1m把回调调用频率提到 1m 粒度,缩小回测-实盘差距(第 6 章详讲)。
bot_loop_start(开始)、order_filled(成交)、adjust_order_price(改价)、custom_stoploss/custom_exit(持仓管理)、custom_entry/exit_price(定价)、confirm_trade_entry/exit(最后确认)、adjust_trade_position(加减仓)。None 用默认;典型做「浮盈越大止损越紧」。trade 状态返回是否出场 + 理由;与 populate_exit_trend「或」关系。self.dp.orderbook 拿盘口(回测无效)。True 放行 False 取消;用于冷静检查。position_adjustment_enable。--timeframe-detail 缩小差距。下一节,我们认识回调里反复出现的
Trade对象——它的字段、方法,以及在回调里如何安全地读它。