第 3 章 · 03 入场与出场信号 本节摘要:有了指标,接下来就是把指标组合成「买卖信号」。本节讲透 (入场)与 (出场)两个方法:信号列名的严格约定( / / / )、如何用 给某些行打标、多条件如何用 「与」组合、 如何捕捉「穿越」事件,以及一个新手必踩的坑——别忘了 。读完本节,你能把任意几个指标拼成「RSI 上穿 30 且价格在布林下轨」这类复合信号。 内容来源:原项目源码 、文档 ,汉化并套用体系化模板。 学习目标 阅读完本节,你应当能够: 列举信号列名约定: / / / 。 用 给满足条件的行打标。 用 (与)和 (或)组合多条件,并加括号保证优先级。 用 / 捕捉穿越事件。 理解布尔语义:信号列 即触发,缺省/0 即不触发。 写出带守卫条件(guard)的稳健入场逻辑。
本节摘要:有了指标,接下来就是把指标组合成「买卖信号」。本节讲透
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,汉化并套用体系化模板。
阅读完本节,你应当能够:
enter_long/enter_short/exit_long/exit_short。dataframe.loc[条件, 列名] = 1 给满足条件的行打标。&(与)和 |(或)组合多条件,并加括号保证优先级。qtpylib.crossed_above / crossed_below 捕捉穿越事件。==1 即触发,缺省/0 即不触发。freqtrade 通过给 DataFrame 添加约定列名来表达信号。引擎只看这四列:
| 列名 | 触发动作 | 前置条件 |
|---|---|---|
enter_long |
开多仓(买入) | 默认行为 |
enter_short |
开空仓 | 需 can_short=True 且合约模式 |
exit_long |
平多仓(卖出) | 持有多仓 |
exit_short |
平空仓 | 持有空仓 |
💡 本质:信号不是「指令」,而是「事件标记」。你在某根 K 线对应的行上把这些列设为 1,引擎就理解为「这根 K 线收盘后,执行对应动作」。设 0 或不设表示「这根 K 线什么都不做」。
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「或」:几乎所有稳健的入场信号都用
&(条件同时满足);用|的入场会非常激进,信号太多。出场可以适度用|(多个出场理由任一满足)。
「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_*)只在穿越那一根触发,适合「精准进场」;状态信号(<、>)在整个区间都触发,适合「条件维持时持续有效」(如趋势跟随的持仓判断)。混用要小心回测时的成交假设。
几乎所有教科书级的入场条件都加一句:
& (dataframe["volume"] > 0)
为什么?
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(自定义)。它们是**「或」关系**:任一触发,机器人就平仓。下一节详讲。
把上面所有要点拼起来,就是 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 节会逐行精读。
enter_long/enter_short/exit_long/exit_short,引擎只看这四列;enter_short/exit_short 需要 can_short=True。dataframe.loc[(条件), "enter_long"] = 1,精准选择满足条件的行;不要整列赋值。&(与)、|(或),每个条件必须加括号;稳健入场通常全 &。qtpylib.crossed_above/below 捕捉「上/下穿」事件,只在穿越那根触发,适合精准进场。& (dataframe["volume"] > 0) 几乎是入场条件的标配,过滤零成交量脏数据。populate_exit_trend 与入场同写法,只是列名换成 exit_long;出场信号与 ROI、止损是「或」关系。下一节,我们看风控双雄——
minimal_roi的阶梯表机制与stoploss,以及它们如何与出场信号协同。