无噪 logo
项目 速读教程 文档
  1. freqtrade 速读指南
  2. 一个简单策略

5. 一个简单策略

本文将详细介绍如何在 Freqtrade 中创建第一个交易策略,包括策略的基本结构、买入和卖出条件的设计,以及如何对策略进行测试和优化。我们会从基础概念讲起,逐步构建一个可实际运行的简单策略,并解释其中每一个细节的作用和原理。

策略开发前的准备

在开始编写策略之前,我们需要明确几个核心概念。Freqtrade 的策略本质上是一个 Python 类,它定义了如何根据市场数据生成交易信号。这些信号基于技术指标计算得出,而技术指标则来自于交易所提供的 K 线数据(OHLCV 数据:开盘价、最高价、最低价、收盘价和成交量)。

作为开发者,需要了解策略的基本构成部分:

  • 时间框架(timeframe):策略分析的 K 线周期,如 15 分钟、1 小时等
  • 指标计算(populate_indicators):添加技术指标到数据中
  • 买入信号(populate_entry_trend):定义何时买入的条件
  • 卖出信号(populate_exit_trend):定义何时卖出的条件
  • 风险控制参数:如止损(stoploss)、最小收益(minimal_roi)等

策略的基本结构

Freqtrade 策略需要继承 IStrategy 接口,并实现几个核心方法。让我们从一个最简单的策略模板开始,看看它的基本结构:

from freqtrade.strategy import IStrategy
from pandas import DataFrame
import talib.abstract as ta

class MyFirstStrategy(IStrategy):
    # 策略的时间框架,这里使用 15 分钟 K 线
    timeframe = '15m'

    # 止损设置为 -10%,意味着价格下跌 10% 时自动平仓
    stoploss = -0.10

    # 最小收益目标:当盈利达到 1% 时立即平仓
    minimal_roi = {
        "0": 0.01  # 0 分钟(即立即)达到 1% 盈利就平仓
    }

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # 计算 RSI 指标,时间周期为 14
        dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)
        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # 当 RSI 小于 30 时产生买入信号
        dataframe.loc[
            (dataframe['rsi'] < 30),
            'enter_long'] = 1
        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # 当 RSI 大于 70 时产生卖出信号
        dataframe.loc[
            (dataframe['rsi'] > 70),
            'exit_long'] = 1
        return dataframe

核心属性解析

上面的代码定义了一个名为 MyFirstStrategy 的策略类,它包含了几个关键属性:

  • timeframe:指定策略使用的 K 线周期。这里设置为 '15m',表示使用 15 分钟 K 线数据进行分析。选择合适的时间框架很重要,短线策略通常使用 5-15 分钟周期,而中长线策略可能使用 1 小时或 1 天周期。

  • stoploss:设置固定止损比例。这里的 -0.10 表示当价格从买入价下跌 10% 时,系统会自动平仓以限制损失。这是风险控制的重要手段,避免单一交易亏损过大。

  • minimal_roi:定义最小收益目标。这个字典的键是时间(分钟),值是收益比例。示例中 "0": 0.01 表示无论持仓多久,只要盈利达到 1% 就立即平仓。也可以设置更复杂的条件,比如:

    minimal_roi = {
        "60": 0.02,  # 持仓 60 分钟后,最小收益目标为 2%
        "30": 0.01,  # 持仓 30 分钟后,最小收益目标为 1%
        "0": 0.005   # 立即平仓的最小收益为 0.5%
    }
    

    系统会根据当前持仓时间选择对应的最小收益目标,当达到目标时自动平仓。

核心方法解析

策略类需要实现三个核心方法,分别用于计算指标、生成买入信号和生成卖出信号:

populate_indicators 方法

这个方法负责计算技术指标并添加到数据框(dataframe)中。数据框包含了 K 线数据,我们需要通过技术分析库(如 TA-Lib)计算指标值,并将结果存储为新的列。

示例中计算了 RSI(相对强弱指数)指标:

dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)

这里使用了 TA-Lib 的 RSI 函数,时间周期为 14(默认值)。计算结果存储在 dataframe 的 'rsi' 列中,供后续的信号生成使用。

populate_entry_trend 方法

这个方法定义买入信号的生成逻辑。通过对指标值设置条件,当条件满足时,在 'enter_long' 列中标记 1,表示产生买入信号。

示例中的条件是 RSI 小于 30:

dataframe.loc[
    (dataframe['rsi'] < 30),
    'enter_long'] = 1

这里使用 pandas 的 loc 方法进行条件筛选,当 'rsi' 列的值小于 30 时,将 'enter_long' 列对应行的值设为 1。Freqtrade 会检测到这个信号,并尝试执行买入操作。

populate_exit_trend 方法

与买入信号类似,这个方法定义卖出信号的生成逻辑,通过设置条件在 'exit_long' 列中标记 1。

示例中的条件是 RSI 大于 70:

dataframe.loc[
    (dataframe['rsi'] > 70),
    'exit_long'] = 1

当 RSI 指标超过 70 时,产生卖出信号,系统会尝试平仓。

设计买入条件

买入条件是策略的核心,它决定了何时进场。一个好的买入条件应该能过滤掉大部分无效信号,提高胜率。除了简单的 RSI 小于 30,我们还可以结合多个指标来设计更稳健的买入条件。

单一指标策略

最简单的买入条件基于单一指标,比如 RSI 超卖(低于 30)、MACD 金叉、布林带下轨反弹等。以布林带(BBANDS)为例,我们可以在价格触及下轨时考虑买入:

首先在 populate_indicators 中添加布林带指标:

def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
    # 计算布林带指标,默认参数(20 周期,2 倍标准差)
    bollinger = ta.BBANDS(dataframe)
    dataframe['bb_lower'] = bollinger['lowerband']
    dataframe['bb_middle'] = bollinger['middleband']
    dataframe['bb_upper'] = bollinger['upperband']
    return dataframe

然后在 populate_entry_trend 中设置买入条件为价格触及下轨:

def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
    dataframe.loc[
        (dataframe['close'] <= dataframe['bb_lower']),  # 收盘价小于等于布林带下轨
        'enter_long'] = 1
    return dataframe

多指标组合策略

单一指标容易产生假信号,实际策略中通常会组合多个指标来提高信号质量。例如,我们可以要求同时满足 RSI 超卖和价格触及布林带下轨:

def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
    dataframe.loc[
        (
            (dataframe['rsi'] < 30) &  # RSI 小于 30(超卖)
            (dataframe['close'] <= dataframe['bb_lower'])  # 价格触及布林带下轨
        ),
        'enter_long'] = 1
    return dataframe

这里使用 & 运算符组合两个条件,只有当两个条件同时满足时才产生买入信号。还可以添加更多条件,比如成交量放大(确认趋势强度):

def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
    # 计算 20 周期平均成交量
    dataframe['volume_mean'] = dataframe['volume'].rolling(window=20).mean()

    dataframe.loc[
        (
            (dataframe['rsi'] < 30) &  # RSI 超卖
            (dataframe['close'] <= dataframe['bb_lower']) &  # 价格触及布林带下轨
            (dataframe['volume'] > 1.5 * dataframe['volume_mean'])  # 成交量大于平均的 1.5 倍
        ),
        'enter_long'] = 1
    return dataframe

这样的组合条件可以过滤掉那些成交量不足、可能是假突破的信号。

设计卖出条件

卖出条件决定了何时离场,它直接影响策略的盈利能力。除了简单的 RSI 超买(大于 70),我们还可以结合止盈、止损和 trailing stop(移动止损)等机制。

基于指标的卖出条件

与买入条件类似,我们可以使用指标来判断卖出时机。例如,当价格触及布林带上轨时卖出:

def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
    dataframe.loc[
        (dataframe['close'] >= dataframe['bb_upper']),  # 收盘价大于等于布林带上轨
        'exit_long'] = 1
    return dataframe

或者结合 RSI 超买和价格突破上轨:

def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
    dataframe.loc[
        (
            (dataframe['rsi'] > 70) &  # RSI 超买
            (dataframe['close'] >= dataframe['bb_upper'])  # 价格触及布林带上轨
        ),
        'exit_long'] = 1
    return dataframe

结合 minimal_roi 和 stoploss

除了指标信号,Freqtrade 还提供了 minimal_roistoploss 机制来控制卖出。这两个参数可以与指标信号结合使用,形成多重保护。

例如,我们可以设置一个更灵活的 minimal_roi

minimal_roi = {
    "120": 0.05,  # 持仓 120 分钟后,最小收益目标为 5%
    "60": 0.03,   # 持仓 60 分钟后,最小收益目标为 3%
    "30": 0.01,   # 持仓 30 分钟后,最小收益目标为 1%
    "0": 0.005    # 立即平仓的最小收益为 0.5%
}

这意味着:

  • 如果持仓不到 30 分钟,只要盈利达到 0.5% 就平仓
  • 持仓 30-60 分钟,盈利达到 1% 就平仓
  • 持仓 60-120 分钟,盈利达到 3% 就平仓
  • 持仓超过 120 分钟,盈利达到 5% 就平仓

stoploss 则提供了下行保护,当价格下跌到设定比例时自动平仓。例如 stoploss = -0.05 表示下跌 5% 时止损。

移动止损(Trailing Stop)

移动止损是一种更高级的止损策略,它会随着价格上涨而提高止损位,锁定部分利润。要使用移动止损,需要在策略中设置 trailing_stop=True 并配置相关参数:

trailing_stop = True
# 当价格从最高点回撤 2% 时触发止损
 trailing_stop_positive_offset = 0.02
# 止损位每次提高的最小幅度
 trailing_stop_positive = 0.01

例如,当买入价为 100 美元,价格上涨到 110 美元时,移动止损位会从 95 美元(100 * (1 - 0.05))上调到 107.8 美元(110 * (1 - 0.02))。如果价格继续上涨到 120 美元,止损位会进一步上调到 117.6 美元(120 * (1 - 0.02)),从而锁定更多利润。

策略测试与优化

编写完策略后,不能直接用于实盘交易,必须经过严格的测试和优化。Freqtrade 提供了多种测试工具,帮助评估策略的表现。

回测(Backtesting)

回测是使用历史数据评估策略表现的过程。首先需要下载历史数据(参考第 4 章),然后使用 backtesting 命令运行回测:

freqtrade backtesting --strategy MyFirstStrategy --timerange 20230101-20230630

这个命令会使用 2023 年 1 月 1 日至 2023 年 6 月 30 日的历史数据,测试 MyFirstStrategy 的表现,并生成详细的回测报告。

回测报告包含以下关键指标:

  • 总交易次数
  • 胜率(盈利交易占比)
  • 平均盈亏比
  • 最大回撤(最大连续亏损幅度)
  • 总收益率

需要注意的是,回测结果可能存在偏差,因为它假设所有订单都能以理想价格成交,而实际市场中存在滑点和流动性问题。因此,回测结果只能作为策略初步评估的参考,不能直接代表实盘表现。

前向测试(Dry Run)

前向测试(Dry Run)是在模拟环境中使用实时市场数据测试策略,它更接近实盘交易的情况。要启用前向测试,需要在配置文件中设置 dry_run: true,然后启动交易命令:

freqtrade trade --strategy MyFirstStrategy

前向测试会模拟真实交易流程,但不会实际执行订单,不会产生真实资金变动。通过前向测试,可以观察策略在实际市场条件下的表现,包括信号生成的及时性、订单执行的延迟等。

常见问题与避免方法

在策略开发和测试过程中,有几个常见问题需要特别注意:

1. 未来数据偏差(Lookahead Bias)

未来数据偏差是指策略在回测中使用了当时无法获得的数据,导致回测结果过于乐观。例如,使用收盘价计算指标后,又用同一个收盘价产生信号,这在实际交易中是可行的;但如果使用了下一根 K 线的数据,就会导致偏差。

Freqtrade 提供了 lookahead-analysis 命令来检测这种问题:

freqtrade lookahead-analysis --strategy MyFirstStrategy

2. 过度拟合(Overfitting)

过度拟合是指策略过度适应历史数据,在回测中表现很好,但在实际交易中却亏损。避免过度拟合的方法包括:

  • 不要为了优化回测结果而过度调整参数
  • 使用多个时间段的数据进行测试
  • 在不同的市场条件(牛市、熊市、震荡市)下测试策略

3. 信号冲突(Colliding Signals)

如果在同一根 K 线上同时出现买入和卖出信号,Freqtrade 会忽略这些信号,不执行任何交易。这通常是由于指标计算错误或条件设置不当导致的,需要检查策略逻辑。

策略模板生成与扩展

手动编写策略框架比较繁琐,Freqtrade 提供了 new-strategy 命令来生成策略模板:

freqtrade new-strategy --strategy AwesomeStrategy

这个命令会在 user_data/strategies/ 目录下生成一个名为 AwesomeStrategy.py 的策略文件。可以通过 --template 参数选择不同的模板级别:

  • --template minimal:生成最简化的策略模板
  • --template advanced:生成包含更多高级功能的模板

生成的模板包含了策略的基本结构和常用指标示例,可以在此基础上进行修改和扩展。例如,添加更多指标(如 MACD、RSI、布林带),设计更复杂的买入卖出条件,或实现自定义的止损逻辑。

总结

本章介绍了 Freqtrade 策略的基本结构、买入和卖出条件的设计方法,以及策略测试的流程。我们从一个简单的 RSI 策略开始,逐步学习了如何结合多个指标、设置风险控制参数,以及使用回测和前向测试评估策略。

创建一个盈利的交易策略是一个迭代的过程,需要不断测试、优化和调整。建议从简单策略开始,逐步添加复杂度,同时始终保持风险控制意识。记住,即使是最复杂的策略也无法保证 100% 盈利,风险管理才是长期生存的关键。

下一章,我们将深入探讨策略的高级特性,包括自定义回调函数、仓位调整和策略优化技巧。