:root{--brand-1:#18794e;--brand-2:#3dd68c;--brand-3:#2bb673;--brand-soft:#3dd68c22;} .hero .kicker{color:var(--brand-2)} .nav .ep{color:var(--brand-2)} .sec h2 .num{color:var(--brand-2)} .quote::before{background:var(--brand-1)} .quote .q-text .em{color:var(--brand-2)} .quote .q-water .pg{color:var(--brand-2)} .widget .w-title{color:var(--brand-2)} .takeaway h3{color:var(--brand-2)} .takeaway ol li::marker{color:var(--brand-2)} .next .nx-label{color:var(--brand-2)} .formula .eq{color:var(--brand-2)} a{color:var(--brand-2)} .comp.c1 .tag{background:var(--brand-soft);color:var(--brand-2)} /* 策略模板填空 */ .strattmpl .code{background:#0a0e14;border:1px solid var(--divider);border-radius:8px;padding:14px 16px;margin:12px 0;font-family:var(--mono);font-size:12.5px;line-height:1.85;color:var(--t2);overflow-x:auto;white-space:pre-wrap} .strattmpl .code .kw{color:#c792ea} .strattmpl .code .fn{color:var(--brand-2)} .strattmpl .code .str{color:var(--yellow-1)} .strattmpl .code .cm{color:var(--t3)} .strattmpl .code .blank{display:inline-block;min-width:140px;background:var(--brand-soft);border:1px dashed var(--brand-2);border-radius:4px;padding:0 8px;color:var(--brand-2);cursor:pointer;font-style:italic} .strattmpl .code .blank.filled{background:var(--bg-mute);border-style:solid;color:var(--t1);font-style:normal} .strattmpl .opts{display:flex;flex-wrap:wrap;gap:8px;margin:12px 0} .strattmpl .opts .opt{background:var(--bg-mute);border:1px solid var(--divider);border-radius:6px;padding:7px 12px;font-size:12.5px;cursor:pointer;color:var(--t2);font-family:var(--mono)} .strattmpl .opts .opt:hover{border-color:var(--brand-1);color:var(--brand-2)} .strattmpl .opts .opt.used{opacity:.4;text-decoration:line-through;cursor:not-allowed} .strattmpl .result{margin-top:14px;padding:14px;border-radius:8px;font-size:13.5px} .strattmpl .result.ok{background:var(--green-soft);color:var(--green-1);border:1px solid var(--green-1)} .strattmpl .result.bad{background:var(--red-soft);color:var(--red-1);border:1px solid var(--red-1)} .strattmpl .result.warn{background:var(--yellow-soft);color:var(--yellow-1);border:1px solid var(--yellow-1)} .strattmpl .target{font-size:12px;color:var(--t3);margin-top:6px} .strattmpl .target b{color:var(--brand-2)}
量化交易实战 · 第 11 期 · 第二季

freqtrade 策略就是填三个方法——这么简单?

freqtrade IStrategy 接口 · strategy as a class

vnpy 是事件回调,freqtrade 是另一套范式:继承 IStrategy,填三个方法就是完整策略。populate_indicators 算指标(RSI、EMA、布林带),populate_entry_trend 定进场条件,populate_exit_trend 定出场条件。框架管回测、下单、风控,你只管"什么时候买、什么时候卖"。声明式、可读、可回测——这就是为什么 freqtrade 适合新手。
⏱ 约 9 分钟 🎯 想快速写策略、用 freqtrade 的人 📦 源:freqtrade §3

01三个方法,一个策略

freqtrade 的策略是一个 Python 类,继承 IStrategy。你不用管事件、不用管下单、不用管回测引擎——只填三个方法。

方法 1

populate_indicators

给 dataframe 加指标列:RSI、EMA、ATR、布林带……用 ta-lib 或 pandas-ta。输入是 OHLCV,输出是加了列的 df。

方法 2

populate_entry_trend

给 df 加 enter_long 列:满足条件设 1,否则 0。比如 RSI<30 且 close>EMA20 就进场。

方法 3

populate_exit_trend

给 df 加 exit_long 列:满足出场条件设 1。比如 RSI>70 或止损触发。

框架管

其余全是框架

回测、滑点、手续费、下单、仓位、止损、止盈、Telegram 通知——框架全包,你不用写。

这种"声明式"写法的好处:策略逻辑就是几行布尔条件,读代码就知道在干什么。坏处:灵活性不如 vnpy——你想做"根据持仓时间动态调止损"这种复杂逻辑,得绕一点(用 custom_stoploss)。

vnpy 给你自由,
freqtrade 给你护栏。
灏天文库 · 量化交易实战 P.33

02策略模板填空:补全这个 RSI 策略

下面是一个 RSI 均线策略的模板,挖了 4 个空。从下方选项里挑合适的填进去(点空格再点选项),全部填对策略就能跑。

🧩 策略模板填空
补全指标计算和进出场条件,看策略能不能通过校验。
class RsiStrategy(IStrategy): def populate_indicators(self, df, metadata): df['rsi'] = ta.RSI(df['close'], timeperiod=14) df['ema'] = ta.EMA(df['close'], timeperiod=20) return df def populate_entry_trend(self, df, metadata): df.loc[ (df['rsi'] < 30) # RSI 超卖 & (df['close'] > df['ema']), # 趋势向上 'enter_long' ] = 1 return df def populate_exit_trend(self, df, metadata): df.loc[ (df['rsi'] > 70), # RSI 超买 'exit_long' ] = 1 return df
目标:4 个空都填对(RSI 周期、超卖阈值、enter_long 值、超买阈值)
14(RSI 默认周期) 30(超卖线) 70(超买线) 1(触发进场) 0(不触发) 50(中线) 7(短周期) 100(极值)
点空格再点下方选项填入。4 个空全对策略才能跑。

03回测怎么跑:一行命令的事

策略写好后,回测就一行命令:freqtrade backtesting --strategy RsiStrategy --timerange 20230101-20240101。框架会拿历史 OHLCV,按你的三个方法生成信号,模拟下单,算收益、夏普、最大回撤、胜率。

关键概念:dataframe 是逐行推进的。框架从第一根 bar 走到最后一根,每根都跑你的三个方法。但要注意——populate_indicators 算 RSI 时,第 14 根 bar 之前 RSI 是 NaN(数据不够)。框架会自动跳过这些 bar,不会假装有信号。

另一个高频踩坑:未来函数。你的进场条件用了未来才有的数据(比如用 df['close'].shift(-1) 看明天),回测会"作弊"——胜率虚高。freqtrade 默认不让你这么干,但你自己写指标时要小心。规则:第 i 根 bar 的信号,只能用第 i 根及之前的数据。

第 i 根 bar 的信号,
只能用第 i 根及之前的数据。
灏天文库 · 量化交易实战 P.34

04带走这套清单

✅ freqtrade 策略 6 条可执行规则

  1. 三个方法就是策略:populate_indicators / populate_entry_trend / populate_exit_trend。
  2. 进场用 enter_long=1,出场用 exit_long=1:布尔条件,框架管下单。
  3. 指标用 ta-lib 或 pandas-ta:算完加列到 df,return df。
  4. 别用未来函数:第 i 根 bar 的信号只用第 i 根及之前的数据。
  5. 止损用 stoploss 参数或 custom_stoploss:动态止损写 custom_stoploss。
  6. 回测一行命令:freqtrade backtesting --strategy Xxx --timerange ...
声明式策略,
读代码就是读逻辑。
灏天文库 · 量化交易实战 P.35