第 3 章 · 03 入场与出场信号


文档摘要

第 3 章 · 03 入场与出场信号 本节摘要:有了指标,接下来就是把指标组合成「买卖信号」。本节讲透 (入场)与 (出场)两个方法:信号列名的严格约定( / / / )、如何用 给某些行打标、多条件如何用 「与」组合、 如何捕捉「穿越」事件,以及一个新手必踩的坑——别忘了 。读完本节,你能把任意几个指标拼成「RSI 上穿 30 且价格在布林下轨」这类复合信号。 内容来源:原项目源码 、文档 ,汉化并套用体系化模板。 学习目标 阅读完本节,你应当能够: 列举信号列名约定: / / / 。 用 给满足条件的行打标。 用 (与)和 (或)组合多条件,并加括号保证优先级。 用 / 捕捉穿越事件。 理解布尔语义:信号列 即触发,缺省/0 即不触发。 写出带守卫条件(guard)的稳健入场逻辑。

第 3 章 · 03 入场与出场信号

本节摘要:有了指标,接下来就是把指标组合成「买卖信号」。本节讲透 populate_entry_trend(入场)与 populate_exit_trend(出场)两个方法:信号列名的严格约定(enter_long/enter_short/exit_long/exit_short)、如何用 dataframe.loc[条件, 列名] = 1 给某些行打标、多条件如何用 &「与」组合、qtpylib.crossed_above/below 如何捕捉「穿越」事件,以及一个新手必踩的坑——别忘了 & (dataframe["volume"] > 0)。读完本节,你能把任意几个指标拼成「RSI 上穿 30 且价格在布林下轨」这类复合信号。

内容来源:原项目源码 freqtrade/templates/sample_strategy.py、文档 docs/strategy-101.md,汉化并套用体系化模板。

学习目标

阅读完本节,你应当能够:

  1. 列举信号列名约定:enter_long/enter_short/exit_long/exit_short
  2. dataframe.loc[条件, 列名] = 1满足条件的行打标。
  3. &(与)和 |(或)组合多条件,并加括号保证优先级。
  4. qtpylib.crossed_above / crossed_below 捕捉穿越事件
  5. 理解布尔语义:信号列 ==1 即触发,缺省/0 即不触发。
  6. 写出带守卫条件(guard)的稳健入场逻辑。

一、信号列名:四把开关

freqtrade 通过给 DataFrame 添加约定列名来表达信号。引擎只看这四列:

列名 触发动作 前置条件
enter_long 开多仓(买入) 默认行为
enter_short 开空仓 can_short=True 且合约模式
exit_long 平多仓(卖出) 持有多仓
exit_short 平空仓 持有空仓

💡 本质:信号不是「指令」,而是「事件标记」。你在某根 K 线对应的行上把这些列设为 1,引擎就理解为「这根 K 线收盘后,执行对应动作」。设 0 或不设表示「这根 K 线什么都不做」。

二、打标语法:dataframe.loc

pandas 的 loc 是给满足条件的行赋值的标淮做法:

dataframe.loc[ (条件A) & (条件B), # 行选择器(布尔 Series) "enter_long", # 列名 ] = 1

含义:同时满足 A 和 B 的那些行,把它们的 enter_long 列设为 1

最简单的例子——「RSI 跌破 30 就买」:

def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe.loc[ (dataframe["rsi"] < 30), "enter_long", ] = 1 return dataframe

⚠️ 不要写成 dataframe["enter_long"] = ...:这是给所有行赋值,会覆盖整列。必须用 loc[条件, 列] 精准选择哪些行被打标。未满足条件的行保持原值(NaN,引擎视为 0)。

三、多条件组合:& 与 |

实战中信号从来不是单一条件,而是几个指标的「逻辑与」。pandas 用 & 表示按元素「与」、| 表示「或」,每个条件必须加括号(因为 & 优先级高于比较运算符):

# ✅ 正确:每个条件都加括号 dataframe.loc[ (dataframe["rsi"] < 30) & (dataframe["close"] < dataframe["bb_lowerband"]), "enter_long", ] = 1 # ❌ 错误:漏括号会被解析成 dataframe["rsi"] < (30 & dataframe["close"]) < ...

常见组合:

# RSI 超卖 + 价格跌破布林下轨 + MACD 柱状图转正(动量回升) dataframe.loc[ (dataframe["rsi"] < 30) & (dataframe["close"] < dataframe["bb_lowerband"]) & (dataframe["macdhist"] > 0) & (dataframe["volume"] > 0), # 守卫:排除零成交量的脏数据 "enter_long", ] = 1

💡 「与」vs「或」:几乎所有稳健的入场信号都用 &(条件同时满足);用 | 的入场会非常激进,信号太多。出场可以适度用 |(多个出场理由任一满足)。

四、穿越事件:crossed_above / crossed_below

RSI 上穿 30」和「RSI 小于 30」是两件事——前者是「事件」(只在那根 K 线触发一次),后者是「状态」(只要 RSI 还在 30 下就一直触发)。事件信号更适合做入场(避免重复进场):

from technical import qtpylib # RSI 上穿 30:上一根 ≤30,这一根 >30 qtpylib.crossed_above(dataframe["rsi"], 30) # 反过来:RSI 下穿 70 qtpylib.crossed_below(dataframe["rsi"], 70)

也可以两列互相穿越:

# EMA9 上穿 EMA21(金叉) qtpylib.crossed_above(dataframe["ema9"], dataframe["ema21"]) # EMA9 下穿 EMA21(死叉) qtpylib.crossed_below(dataframe["ema9"], dataframe["ema21"])

⚠️ 事件信号 vs 状态信号的选择:事件信号(crossed_*)只在穿越那一根触发,适合「精准进场」;状态信号(<>)在整个区间都触发,适合「条件维持时持续有效」(如趋势跟随的持仓判断)。混用要小心回测时的成交假设。

五、必备守卫:volume > 0

几乎所有教科书级的入场条件都加一句:

& (dataframe["volume"] > 0)

为什么?

  • 交易所数据里偶尔会出现零成交量的脏 K 线(数据缺失、停牌等)。
  • 这些 K 线上的价格指标可能失真(RSI 用前值、MACD 平移),容易产出虚假信号。
  • volume > 0 一刀切掉,是最便宜也最有效的护栏。

💡 建议:把 & (dataframe["volume"] > 0) 当作入场条件的默认最后一行,除非你有明确理由不加。

六、出场信号:对称的写法

populate_exit_trend 与入场完全对称——只是把 enter_long 换成 exit_long:

def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: # RSI 超买就出 dataframe.loc[ (qtpylib.crossed_above(dataframe["rsi"], 70)) & (dataframe["tema"] > dataframe["bb_middleband"]) & (dataframe["volume"] > 0), "exit_long", ] = 1 return dataframe

出场信号不是唯一的出场来源——还有 minimal_roi(达到目标利润)、stoploss(亏损止损)、custom_exit(自定义)。它们是**「或」关系**:任一触发,机器人就平仓。下一节详讲。

七、一个完整例子:RSI + BB + TEMA

把上面所有要点拼起来,就是 sample_strategy 的入场逻辑:

def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe.loc[ ( # 信号:RSI 上穿 30(超卖反弹事件) (qtpylib.crossed_above(dataframe["rsi"], self.buy_rsi.value)) # 守卫:TEMA 低于布林中轨(确认还在下行区间) & (dataframe["tema"] <= dataframe["bb_middleband"]) # 守卫:TEMA 在抬升(确认开始反弹) & (dataframe["tema"] > dataframe["tema"].shift(1)) # 守卫:零成交量剔除 & (dataframe["volume"] > 0) ), "enter_long", ] = 1 return dataframe

这套逻辑的解读:RSI 超卖反弹(信号)+ 仍在布林下半区(还在低位)+ TEMA 抬升(反弹已开始)+ 有成交量。一个事件 + 三个守卫,既保证信号清晰,又过滤掉假反弹。这是 freqtrade 教科书级的写法,第 05 节会逐行精读。

本节要点回顾

  1. 四把开关:enter_long/enter_short/exit_long/exit_short,引擎只看这四列;enter_short/exit_short 需要 can_short=True
  2. 打标语法:dataframe.loc[(条件), "enter_long"] = 1,精准选择满足条件的行;不要整列赋值。
  3. 多条件:用 &(与)、|(或),每个条件必须加括号;稳健入场通常全 &
  4. 穿越事件:qtpylib.crossed_above/below 捕捉「上/下穿」事件,只在穿越那根触发,适合精准进场。
  5. 必备守卫:& (dataframe["volume"] > 0) 几乎是入场条件的标配,过滤零成交量脏数据。
  6. 出场对称:populate_exit_trend 与入场同写法,只是列名换成 exit_long;出场信号与 ROI、止损是「或」关系。

下一节,我们看风控双雄——minimal_roi 的阶梯表机制与 stoploss,以及它们如何与出场信号协同。


发布者: 作者: 灏天文库 转发
评论区 (0)
U