第 3 章 · 05 第一个示例策略 本节摘要:前三节我们把指标、信号、风控拆开学了,现在把它们拼起来,逐段精读 freqtrade 官方模板 ——它就是 不加 时生成的完整骨架。这套策略的逻辑是教科书级的「RSI 超卖反弹 + 布林带 + TEMA」:用 RSI 上穿 30 捕捉超卖反转事件,用 TEMA 与布林中轨的相对位置做守卫,用 minimalroi 阶梯表与 10% 止损做风控。读完本节,你能完整理解这份模板每一行的作用,并知道如何在其上改成自己的策略——同时牢记:它是「怎么写」的范例,不是「赚钱」的印钞机。 内容来源:原项目源码 、文档 ,汉化并套用体系化模板。 ⚠️ 风险提示:官方明说 SampleStrategy 不是用来赚钱的。直接拿它上实盘大概率亏损。
本节摘要:前三节我们把指标、信号、风控拆开学了,现在把它们拼起来,逐段精读 freqtrade 官方模板
freqtrade/templates/sample_strategy.py——它就是freqtrade new-strategy不加--template minimal时生成的完整骨架。这套策略的逻辑是教科书级的「RSI 超卖反弹 + 布林带 + TEMA」:用 RSI 上穿 30 捕捉超卖反转事件,用 TEMA 与布林中轨的相对位置做守卫,用 minimal_roi 阶梯表与 10% 止损做风控。读完本节,你能完整理解这份模板每一行的作用,并知道如何在其上改成自己的策略——同时牢记:它是「怎么写」的范例,不是「赚钱」的印钞机。
内容来源:原项目源码
freqtrade/templates/sample_strategy.py、文档docs/strategy-101.md,汉化并套用体系化模板。
⚠️ 风险提示:官方明说 SampleStrategy 不是用来赚钱的。直接拿它上实盘大概率亏损。它是结构范例,真正实盘前必须回测 + Dry-Run + 自己理解每一行逻辑。
阅读完本节,你应当能够:
IntParameter)如何让 RSI 阈值可调。freqtrade new-strategy 生成模板并跑起来。打开 sample_strategy.py,去掉注释后骨架是这样:
import talib.abstract as ta from technical import qtpylib from freqtrade.strategy import IStrategy, IntParameter, informative from pandas import DataFrame class SampleStrategy(IStrategy): # —— 第一块:类配置 —— INTERFACE_VERSION = 3 timeframe = "5m" can_short = False minimal_roi = {"60": 0.01, "30": 0.02, "0": 0.04} stoploss = -0.10 trailing_stop = False process_only_new_candles = True startup_candle_count = 200 order_types = {...} use_exit_signal = True # —— 第二块:Hyperopt 参数 —— buy_rsi = IntParameter(1, 50, default=30, space="buy") sell_rsi = IntParameter(50, 100, default=70, space="sell") # —— 第三块:三个必须方法 —— def populate_indicators(self, dataframe, metadata): ... def populate_entry_trend(self, dataframe, metadata): ... def populate_exit_trend(self, dataframe, metadata): ... # —— 第四块:可视化配置(可选) —— plot_config = {...}
INTERFACE_VERSION = 3 # 接口版本,目前固定 3 timeframe = "5m" # 5 分钟 K 线 can_short: bool = False # 仅做多 minimal_roi = { # 阶梯 ROI(第 04 节) "60": 0.01, # 1 小时后 1% 就平 "30": 0.02, # 30 分钟后 2% "0": 0.04, # 立即要 4% } stoploss = -0.10 # 亏损 10% 止损 trailing_stop = False # 不开启追踪止损(第 4 章详讲) process_only_new_candles = True # 只在新 K 线收盘跑 startup_candle_count: int = 200 # 前 200 根忽略 order_types = { "entry": "limit", # 入场用限价单 "exit": "limit", # 出场用限价单 "stoploss": "market", # 止损用市价单(确保成交) "stoploss_on_exchange": False, } use_exit_signal = True # 启用 populate_exit_trend exit_profit_only = False # 任何盈亏都可用信号出场 ignore_roi_if_entry_signal = False # 达 ROI 就平,不等信号
每一行都在前三节讲过。注意 startup_candle_count = 200 是因为指标里最长的周期接近 200(虽然示例实际只用短周期,但留 200 是给后续扩展留余地)。
💡 改自己的策略从这里开始:
timeframe、minimal_roi、stoploss是最先该调整的三个。但记住——JSON 配置会覆盖它们,所以实盘时检查 JSON。
buy_rsi = IntParameter(low=1, high=50, default=30, space="buy", optimize=True, load=True) sell_rsi = IntParameter(low=50, high=100, default=70, space="sell", optimize=True, load=True) short_rsi = IntParameter(low=51, high=100, default=70, space="sell", optimize=True, load=True) exit_short_rsi = IntParameter(low=1, high=50, default=30, space="exit", optimize=True, load=True)
IntParameter 声明一个可优化参数:
low/high:搜索范围。default:不用 Hyperopt 时的默认值。space:"buy"(买空间)、"sell"(卖空间)、"roi"、"stoploss"、"exit" 等,Hyperopt 可分空间优化。optimize:是否参与优化。load:是否从 Hyperopt 结果文件加载。代码里用 self.buy_rsi.value 取当前值。第 7 章详讲。
去掉所有注释行,sample 实际计算的指标就这几个(注意:不是注释里的全部):
def populate_indicators(self, dataframe, metadata): dataframe["adx"] = ta.ADX(dataframe) # 趋势强度 dataframe["rsi"] = ta.RSI(dataframe) # 动量 stoch_fast = ta.STOCHF(dataframe) # 快随机 dataframe["fastd"] = stoch_fast["fastd"] dataframe["fastk"] = stoch_fast["fastk"] macd = ta.MACD(dataframe) # MACD dataframe["macd"] = macd["macd"] dataframe["macdsignal"] = macd["macdsignal"] dataframe["macdhist"] = macd["macdhist"] dataframe["mfi"] = ta.MFI(dataframe) # 资金流量 bb = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2) dataframe["bb_lowerband"] = bb["lower"] dataframe["bb_middleband"] = bb["mid"] dataframe["bb_upperband"] = bb["upper"] dataframe["sar"] = ta.SAR(dataframe) # 抛物线 dataframe["tema"] = ta.TEMA(dataframe, timeperiod=9) # 三重 EMA hilbert = ta.HT_SINE(dataframe) # 希尔伯特正弦 dataframe["htsine"] = hilbert["sine"] dataframe["htleadsine"] = hilbert["leadsine"] return dataframe
⚠️ 观察:sample 里
populate_indicators算了 ~15 个指标,但populate_entry_trend实际只用到rsi、tema、bb_middleband三个。这正是反面教材——按第 02 节的性能铁律,实战应该删掉没用的。sample 保留它们是「目录」用途,让你看到 TA-Lib 怎么调。
def populate_entry_trend(self, dataframe, metadata): # 做多信号 dataframe.loc[ ( # 信号:RSI 上穿 buy_rsi(默认 30)——超卖反弹事件 (qtpylib.crossed_above(dataframe["rsi"], self.buy_rsi.value)) # 守卫 1:TEMA ≤ 布林中轨(确认还在低位区间) & (dataframe["tema"] <= dataframe["bb_middleband"]) # 守卫 2:TEMA 在抬升(反弹已启动) & (dataframe["tema"] > dataframe["tema"].shift(1)) # 守卫 3:成交量 > 0 & (dataframe["volume"] > 0) ), "enter_long", ] = 1 # 做空信号(can_short=False 时不会触发) dataframe.loc[ ( (qtpylib.crossed_above(dataframe["rsi"], self.short_rsi.value)) & (dataframe["tema"] > dataframe["bb_middleband"]) & (dataframe["tema"] < dataframe["tema"].shift(1)) & (dataframe["volume"] > 0) ), "enter_short", ] = 1 return dataframe
这套「RSI + BB + TEMA」逻辑的解读(第 03 节已铺垫):
| 条件 | 角色 | 解读 |
|---|---|---|
crossed_above(rsi, 30) |
信号 | RSI 从超卖区上穿 30,反弹事件 |
tema <= bb_middleband |
守卫 | TEMA 还在布林下半区,确认低位 |
tema > tema.shift(1) |
守卫 | TEMA 在抬升,反弹已启动而非刚开始 |
volume > 0 |
守卫 | 排除零成交量脏数据 |
💡 为什么用 crossed_above 而非 <:RSI 跌到 25 然后反弹到 28,这一路上
< 30都为真——会重复触发;而crossed_above(rsi, 30)只在「从下方穿越到上方」那一根 K 线触发,信号干净。
出场逻辑与入场镜像对称——只是把阈值和方向反过来:
def populate_exit_trend(self, dataframe, metadata): # 平多:RSI 上穿 70(超买回落)+ TEMA 在高位 + TEMA 在下降 dataframe.loc[ ( (qtpylib.crossed_above(dataframe["rsi"], self.sell_rsi.value)) & (dataframe["tema"] > dataframe["bb_middleband"]) & (dataframe["tema"] < dataframe["tema"].shift(1)) & (dataframe["volume"] > 0) ), "exit_long", ] = 1 # 平空:略,逻辑对称 return dataframe
注意出场还会叠加 minimal_roi(4%/2%/1% 阶梯)和 stoploss(-10%),三者「或」关系。
plot_config = { "main_plot": {"tema": {}, "sar": {"color": "white"}}, "subplots": { "MACD": {"macd": {"color": "blue"}, "macdsignal": {"color": "orange"}}, "RSI": {"rsi": {"color": "red"}}, }, }
main_plot:画在主图(K 线图)上的指标,通常是价格类(均线、SAR)。subplots:画在副图上的指标,通常是震荡类(RSI、MACD)。不影响交易,只影响 freqtrade plot-dataframe 的输出。
实战路径:
cp sample_strategy.py MyStrategy.py,把类名 SampleStrategy 改成 MyStrategy。populate_indicators 里入场/出场没用到的列。buy_rsi、sell_rsi 的 default 改成你回测最优的值。can_short=False 时,enter_short/exit_short 块可删。freqtrade backtesting -c config.json -s MyStrategy。⚠️ 必须保留:
INTERFACE_VERSION、populate_indicators、populate_entry_trend、populate_exit_trend、timeframe、minimal_roi、stoploss——这些是引擎的硬性约定。其余(plot_config、Hyperopt 参数、informative_pairs)可选。
# 生成完整版(就是 sample_strategy) freqtrade new-strategy --strategy MyFirst # 生成最小骨架 freqtrade new-strategy --strategy MyFirst --template minimal # 回测 freqtrade backtesting -c user_data/config.json -s MyFirst # Dry-Run freqtrade trade -c user_data/config.json -s MyFirst --strategy-path user_data/strategies/
下一章进入「策略深度定制」——先看
@informative装饰器,如何在一个策略里融合多周期(如 1h 趋势 + 5m 入场)的数据。