策略自定义¶
本页面介绍如何自定义策略、添加新指标以及设置交易规则。
如果您还没有了解,请先熟悉以下内容:
- 《Freqtrade 策略入门指南》,该指南帮助您快速开始策略开发
- 《Freqtrade 机器人基础》,提供机器人运行机制的总体信息
开发您自己的策略¶
机器人包含一个默认的策略文件。
此外,还有多个其他策略可在策略仓库中找到。
但您很可能有自己独特的策略想法。
本文档旨在帮助您将想法转化为可运行的策略。
生成策略模板¶
要开始使用,可以运行以下命令:
freqtrade new-strategy --strategy AwesomeStrategy
这将基于模板创建一个名为 AwesomeStrategy 的新策略,该策略的文件路径为 user_data/strategies/AwesomeStrategy.py。
注意
策略名称 和 文件名 之间存在区别。在大多数命令中,Freqtrade 使用的是策略的 名称,而不是文件名。
注意
new-strategy 命令生成的初始示例策略默认不会直接盈利。
不同模板级别
freqtrade new-strategy 命令包含一个额外参数 --template,用于控制所创建策略中预置内容的多少。使用 --template minimal 可生成一个不含任何指标示例的空策略;使用 --template advanced 则可生成包含更复杂功能定义的模板。
策略结构解析¶
策略文件包含构建策略逻辑所需的所有信息:
- OHLCV 格式的 K 线数据
- 技术指标
- 入场逻辑
- 信号
- 退出逻辑
- 信号
- 最低投资回报率(ROI)
- 回调函数("自定义函数")
- 止损设置
- 固定/绝对值
- 追踪止损
- 回调函数("自定义函数")
- 定价 [可选]
- 仓位调整 [可选]
机器人内置了一个名为 SampleStrategy 的示例策略,可作为你编写策略的基础:user_data/strategies/sample_strategy.py。你可以通过参数 --strategy SampleStrategy 来测试它。请记住,这里使用的是策略类名,而非文件名。
此外,还有一个名为 INTERFACE_VERSION 的属性,用于定义机器人应使用的策略接口版本。当前版本为 3,这也是未显式设置时的默认版本。
你可能会看到一些旧策略设置为接口版本 2,这些策略需要更新为 v3 的术语,因为未来的版本将要求必须设置此属性。
通过 trade 命令可启动机器人进入模拟或实盘模式:
freqtrade trade --strategy AwesomeStrategy
机器人模式¶
Freqtrade 策略可在 Freqtrade 机器人的 5 种主要模式下运行:
- 回测
- 超参数优化(hyperopt)
- 模拟交易("前向测试")
- 实盘交易
- FreqAI(本文不涉及)
请查阅 配置文档 了解如何设置机器人为模拟或实盘模式。
测试时请始终使用模拟模式,这能让你了解策略在真实环境中的表现,同时无需承担资金风险。
深入探索¶
在接下来的章节中,我们将以 user_data/strategies/sample_strategy.py 文件作为参考。
策略与回测
为了避免回测与模拟/实盘模式之间出现意外差异,请注意:在回测过程中,整个时间范围的数据会一次性传递给 populate_*() 方法。因此,最好使用向量化操作(在整个数据框上进行,而非使用循环),并避免索引引用(如 df.iloc[-1]),而应使用 df.shift() 来获取前一根 K 线的数据。
警告:使用了未来数据
由于回测会将整个时间范围的数据一次性传入 populate_*() 方法,策略编写者必须注意避免策略使用到未来数据。本文档的 常见错误 部分列出了此类问题的一些典型模式。
前视偏差与递归分析
Freqtrade 提供了两个有用的命令,用于检测常见的前视偏差(使用未来数据)和递归偏差(指标值的不一致性)问题。在将策略用于模拟或实盘交易之前,您应始终先运行这两个命令。请查阅有关 前视偏差 和 递归 分析的相关文档。
数据框¶
Freqtrade 使用 pandas 来存储和提供 K 线(OHLCV)数据。Pandas 是一个用于处理表格格式大量数据的强大库。
数据框中的每一行对应图表上的一根 K 线,最新完成的 K 线始终位于数据框的末尾(按日期排序)。
如果我们使用 pandas 的 head() 函数查看主数据框的前几行,将会看到如下内容:
> dataframe.head()
date open high low close volume
0 2021-11-09 23:25:00+00:00 67279.67 67321.84 67255.01 67300.97 44.62253
1 2021-11-09 23:30:00+00:00 67300.97 67301.34 67183.03 67187.01 61.38076
2 2021-11-09 23:35:00+00:00 67187.02 67187.02 67031.93 67123.81 113.42728
3 2021-11-09 23:40:00+00:00 67123.80 67222.40 67080.33 67160.48 78.96008
4 2021-11-09 23:45:00+00:00 67160.48 67160.48 66901.26 66943.37 111.39292
数据框是一种表格,其中的列不是单个值,而是一系列数据值。因此,像下面这样的简单 Python 比较将无法正常工作:
if dataframe['rsi'] > 30:
dataframe['enter_long'] = 1
上述代码会报错:The truth value of a Series is ambiguous [...](Series 的布尔值不明确)。
这必须改写为符合 pandas 规范的方式,使操作在整个数据框上执行,即实现向量化。
dataframe.loc[
(dataframe['rsi'] > 30)
, 'enter_long'] = 1
通过这一操作,您的数据框中会新增一列,当 RSI 大于 30 时,该列的值为 1。
Freqtrade 使用这一新列作为入场信号,表示接下来将在下一根 K 线开盘时尽快开仓。
Pandas 提供了快速计算指标的方法,即“向量化”。为了充分利用这种速度优势,建议不要使用循环,而应使用向量化方法。
向量化操作会对整个数据范围进行计算,因此在计算指标时,相比逐行循环,其速度要快得多。
信号与交易
- 信号由 K 线收盘时的指标生成,表示开仓的意图。
- 交易是指已执行的订单(在实盘模式下于交易所成交),交易将在下一根 K 线开盘时尽可能快地建立。
交易订单假设
在回测中,信号在 K 线收盘时生成,交易则立即在下一根 K 线开盘时启动。
在模拟和实盘模式中,可能会有延迟,因为需要先分析所有交易对的数据框,然后依次处理每个交易对的交易。这意味着在模拟/实盘模式中,您需要尽量减少计算延迟,通常可通过减少交易对数量以及使用高主频的 CPU 来实现。
为什么我看不到“实时”K 线数据?¶
Freqtrade 不会将不完整/未完成的 K 线存储在数据框中。
使用不完整数据进行策略决策的行为被称为“重绘”(repainting),你可能会发现其他平台允许这种做法。
但 Freqtrade 不会。数据框中仅包含完整/已完成的 K 线数据。
自定义指标¶
入场和出场信号需要依赖指标。你可以通过扩展策略文件中 populate_indicators() 方法内的列表来添加更多指标。
你应该只添加在 populate_entry_trend()、populate_exit_trend() 中使用到的指标,或用于计算其他指标的指标,否则可能会影响性能。
重要的是,始终要确保这三个函数返回的数据框不删除或修改 "open", "high", "low", "close", "volume" 这些列,否则这些字段的内容可能会变得不可预期。
示例:
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Adds several different TA indicators to the given DataFrame
Performance Note: For the best performance be frugal on the number of indicators
you are using. Let uncomment only the indicator you are using in your strategies
or your hyperopt configuration, otherwise you will waste your memory and CPU usage.
:param dataframe: Dataframe with data from the exchange
:param metadata: Additional information, like the currently traded pair
:return: a Dataframe with all mandatory indicators for the strategies
"""
dataframe['sar'] = ta.SAR(dataframe)
dataframe['adx'] = ta.ADX(dataframe)
stoch = ta.STOCHF(dataframe)
dataframe['fastd'] = stoch['fastd']
dataframe['fastk'] = stoch['fastk']
dataframe['bb_lower'] = ta.BBANDS(dataframe, nbdevup=2, nbdevdn=2)['lowerband']
dataframe['sma'] = ta.SMA(dataframe, timeperiod=40)
dataframe['tema'] = ta.TEMA(dataframe, timeperiod=9)
dataframe['mfi'] = ta.MFI(dataframe)
dataframe['rsi'] = ta.RSI(dataframe)
dataframe['ema5'] = ta.EMA(dataframe, timeperiod=5)
dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10)
dataframe['ema50'] = ta.EMA(dataframe, timeperiod=50)
dataframe['ema100'] = ta.EMA(dataframe, timeperiod=100)
dataframe['ao'] = awesome_oscillator(dataframe)
macd = ta.MACD(dataframe)
dataframe['macd'] = macd['macd']
dataframe['macdsignal'] = macd['macdsignal']
dataframe['macdhist'] = macd['macdhist']
hilbert = ta.HT_SINE(dataframe)
dataframe['htsine'] = hilbert['sine']
dataframe['htleadsine'] = hilbert['leadsine']
dataframe['plus_dm'] = ta.PLUS_DM(dataframe)
dataframe['plus_di'] = ta.PLUS_DI(dataframe)
dataframe['minus_dm'] = ta.MINUS_DM(dataframe)
dataframe['minus_di'] = ta.MINUS_DI(dataframe)
# remember to always return the dataframe
return dataframe
想要更多指标示例吗?
请查看 user_data/strategies/sample_strategy.py 文件,然后取消注释你需要的指标。
指标库¶
开箱即用,freqtrade 安装了以下技术分析库:
可根据需要安装额外的技术分析库,或由策略作者自行编写/发明自定义指标。
策略启动周期¶
某些指标在初始阶段存在不稳定期,此时由于 K 线数据不足,无法计算出有效值(即 NaN),或计算结果不准确。这可能导致不一致问题,因为 Freqtrade 无法知道这个不稳定期有多长,只能使用数据框中现有的指标值。
为解决此问题,可以在策略中设置 startup_candle_count 属性。
该值应设为策略计算稳定指标所需的最大 K 线数量。如果用户使用了包含多个时间帧的信息对(informative pairs),startup_candle_count 不一定随之改变。其值应为所有信息对时间帧中计算稳定指标所需的最长周期(以 K 线数量计)。
你可以使用 recursive-analysis 来检查并确定正确的 startup_candle_count 值。当递归分析显示方差为 0% 时,说明你已拥有足够的启动 K 线数据。
在本示例策略中,该值应设为 400(startup_candle_count = 400),因为 EMA100 指标要获得准确值,至少需要 400 根 K 线的历史数据。
dataframe['ema100'] = ta.EMA(dataframe, timeperiod=100)
通过告知机器人所需的历史数据量,回测和超参数优化时的交易可以准确地从指定时间范围开始。
使用多次调用获取 OHLCV 数据
如果你收到类似 WARNING - Using 3 calls to get OHLCV. This can result in slower operations for the bot. Please check if you really need 1500 candles for your strategy 的警告,你应该考虑是否真的需要这么多历史数据来生成信号。如果需要,Freqtrade 将对同一交易对发起多次请求,这显然比单次网络请求更慢。因此,这会导致 Freqtrade 更新 K 线的时间变长,应尽可能避免。为防止过度请求交易所或使 freqtrade 变得太慢,此类调用最多限制为 5 次。
警告
startup_candle_count 应小于 ohlcv_candle_limit * 5(对于大多数交易所而言是 500 * 5),因为在模拟运行/实盘交易期间仅能获取到该数量的 K 线数据。
示例¶
让我们尝试使用上面提到的 EMA100 示例策略,对 2019 年 1 月一个月的 5 分钟 K 线进行回测。
freqtrade backtesting --timerange 20190101-20190201 --timeframe 5m
假设 startup_candle_count 设置为 400,回测系统会知道需要 400 根 K 线来生成有效的入场信号。它将从 20190101 - (400 * 5m) 开始加载数据,即大约 2018-12-30 11:40:00。
如果这些数据可用,则指标将基于扩展的时间范围进行计算。在开始回测前,不稳定的启动期(直到 2019-01-01 00:00:00)的数据将被移除。
启动 K 线数据不可用
如果启动期的数据不可用,则时间范围将被调整以弥补该启动期。在我们的示例中,回测将从 2019-01-02 09:20:00 开始。
入场信号规则¶
在策略文件中修改 populate_entry_trend() 方法以更新你的入场策略。
必须始终返回原始的 dataframe,不得删除或修改 "open", "high", "low", "close", "volume" 这些列,否则这些字段可能包含意外内容,导致策略产生无效值,甚至完全失效。
该方法还需定义一个新列 "enter_long"(做空时为 "enter_short"),该列中 1 表示入场,0 表示“无操作”。即使策略仅做空,enter_long 也是必须设置的列。
你可以通过使用 "enter_tag" 列为入场信号命名,这有助于后续调试和评估你的策略。
来自 user_data/strategies/sample_strategy.py 的示例:
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Based on TA indicators, populates the buy signal for the given dataframe
:param dataframe: DataFrame populated with indicators
:param metadata: Additional information, like the currently traded pair
:return: DataFrame with buy column
"""
dataframe.loc[
(
(qtpylib.crossed_above(dataframe['rsi'], 30)) & # Signal: RSI crosses above 30
(dataframe['tema'] <= dataframe['bb_middleband']) & # Guard
(dataframe['tema'] > dataframe['tema'].shift(1)) & # Guard
(dataframe['volume'] > 0) # Make sure Volume is not 0
),
['enter_long', 'enter_tag']] = (1, 'rsi_cross')
return dataframe
进入空头交易
通过设置 enter_short(对应做多时的 enter_long)可创建空头入场信号。enter_tag 列保持不变。请注意,做空需要你的交易所和市场配置支持!此外,如果你打算做空,请确保在策略中正确设置 can_short。
# allow both long and short trades
can_short = True
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[
(
(qtpylib.crossed_above(dataframe['rsi'], 30)) & # Signal: RSI crosses above 30
(dataframe['tema'] <= dataframe['bb_middleband']) & # Guard
(dataframe['tema'] > dataframe['tema'].shift(1)) & # Guard
(dataframe['volume'] > 0) # Make sure Volume is not 0
),
['enter_long', 'enter_tag']] = (1, 'rsi_cross')
dataframe.loc[
(
(qtpylib.crossed_below(dataframe['rsi'], 70)) & # Signal: RSI crosses below 70
(dataframe['tema'] > dataframe['bb_middleband']) & # Guard
(dataframe['tema'] < dataframe['tema'].shift(1)) & # Guard
(dataframe['volume'] > 0) # Make sure Volume is not 0
),
['enter_short', 'enter_tag']] = (1, 'rsi_cross')
return dataframe
注意
买入需要有卖家存在。因此,必须确保成交量大于 0(dataframe['volume'] > 0),以避免机器人在无交易活动的时段进行买卖。
出场信号规则¶
在策略文件中添加或修改 populate_exit_trend() 方法以更新你的出场策略。
可以通过在配置文件或策略中将 use_exit_signal 设置为 false 来禁用出场信号。
use_exit_signal 不会影响 信号冲突规则——这些规则仍会生效,并可能阻止新的入场。
必须始终返回原始的 dataframe,不得删除或修改 "open", "high", "low", "close", "volume" 这些列,否则这些字段可能包含意外内容,导致策略产生无效值,甚至完全失效。
该方法还需定义一个新列 "exit_long"(做空时为 "exit_short"),该列中 1 表示出场,0 表示“无操作”。
你可以通过使用 "exit_tag" 列为出场信号命名,这有助于后续调试和评估你的策略。
来自 user_data/strategies/sample_strategy.py 的示例:
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Based on TA indicators, populates the exit signal for the given dataframe
:param dataframe: DataFrame populated with indicators
:param metadata: Additional information, like the currently traded pair
:return: DataFrame with buy column
"""
dataframe.loc[
(
(qtpylib.crossed_above(dataframe['rsi'], 70)) & # Signal: RSI crosses above 70
(dataframe['tema'] > dataframe['bb_middleband']) & # Guard
(dataframe['tema'] < dataframe['tema'].shift(1)) & # Guard
(dataframe['volume'] > 0) # Make sure Volume is not 0
),
['exit_long', 'exit_tag']] = (1, 'rsi_too_high')
return dataframe
退出空头交易
可以通过设置 exit_short(对应于 exit_long)来创建空头平仓。exit_tag 列保持不变。做空需要您的交易所和市场配置支持!此外,如果您打算进行做空操作,请确保在策略中正确设置了 can_short。
# allow both long and short trades
can_short = True
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[
(
(qtpylib.crossed_above(dataframe['rsi'], 70)) & # Signal: RSI crosses above 70
(dataframe['tema'] > dataframe['bb_middleband']) & # Guard
(dataframe['tema'] < dataframe['tema'].shift(1)) & # Guard
(dataframe['volume'] > 0) # Make sure Volume is not 0
),
['exit_long', 'exit_tag']] = (1, 'rsi_too_high')
dataframe.loc[
(
(qtpylib.crossed_below(dataframe['rsi'], 30)) & # Signal: RSI crosses below 30
(dataframe['tema'] < dataframe['bb_middleband']) & # Guard
(dataframe['tema'] > dataframe['tema'].shift(1)) & # Guard
(dataframe['volume'] > 0) # Make sure Volume is not 0
),
['exit_short', 'exit_tag']] = (1, 'rsi_too_low')
return dataframe
最小 ROI¶
策略变量 minimal_roi 定义了一笔交易在退出前应达到的最低投资回报率(ROI),该条件独立于退出信号。
其格式如下所示,即一个 Python dict,字典的键(冒号左侧)表示自交易开仓以来经过的分钟数,值(冒号右侧)表示收益率百分比。
minimal_roi = {
"40": 0.0,
"30": 0.01,
"20": 0.02,
"0": 0.04
}
因此,上述配置的含义是:
- 当收益达到 4% 时退出
- 当收益达到 2% 时退出(在持仓 20 分钟后生效)
- 当收益达到 1% 时退出(在持仓 30 分钟后生效)
- 当交易不再亏损时退出(在持仓 40 分钟后生效)
计算中已包含手续费。
禁用最小 ROI¶
要完全禁用 ROI,请将其设置为空字典:
minimal_roi = {}
在最小 ROI 中使用计算¶
若要基于 K 线周期(时间框架)设置时间,以下代码片段会很有帮助。
这样,即使您更改策略的时间框架,最小 ROI 的时间仍将以 K 线根数为单位,例如在 3 根 K 线后。
from freqtrade.exchange import timeframe_to_minutes
class AwesomeStrategy(IStrategy):
timeframe = "1d"
timeframe_mins = timeframe_to_minutes(timeframe)
minimal_roi = {
"0": 0.05, # 5% for the first 3 candles
str(timeframe_mins * 3): 0.02, # 2% after 3 candles
str(timeframe_mins * 6): 0.01, # 1% After 6 candles
}
未立即成交的订单
minimal_roi 会以 trade.open_date 作为参考时间,即交易初始化的时间,也就是为此交易下达第一个订单的时间。对于未立即成交的限价单(通常通过 custom_entry_price() 设置“非实时”价格),以及初始订单价格被 adjust_entry_price() 替换的情况,该规则仍然适用。所使用的时间仍为最初的 trade.open_date(即首次下单的时间),而不是新下单或调整订单的时间。
止损¶
强烈建议设置止损,以保护您的资金免受不利行情的剧烈冲击。
设置 10% 止损的示例:
stoploss = -0.10
有关止损功能的完整文档,请参阅专门的 止损页面。
时间框架¶
这是机器人在策略中应使用的 K 线周期。
常见值包括 "1m"、"5m"、"15m"、"1h",但您的交易所支持的所有时间周期都可使用。
请注意,相同的入场/出场信号可能在某个时间框架下表现良好,但在其他框架下则不然。
此设置可在策略方法中通过 self.timeframe 属性访问。
是否可做空¶
要在期货市场中使用做空信号,您必须设置 can_short = True。
启用此选项的策略在现货市场中将无法加载。
如果 enter_short 列中包含值为 1 的信号以触发做空,而您将 can_short = False(默认值)保持不变,则这些做空信号将被忽略,即使您已在配置中指定了期货市场。
元数据字典¶
metadata 字典(可用于 populate_entry_trend、populate_exit_trend 和 populate_indicators)包含额外的信息。目前包括 pair,可通过 metadata['pair'] 访问,返回格式为 XRP/BTC 的交易对(期货市场则为 XRP/BTC:BTC)。
不应修改元数据字典,且该字典中的信息不会在策略的多个函数之间持久保留。
请参阅下方的 存储信息 部分。
策略所需导入¶
创建策略时,您需要导入必要的模块和类。以下导入是策略所必需的:
默认情况下,我们建议将以下导入作为策略的基础:这将涵盖使 freqtrade 功能正常运行所需的所有导入。显然,您可以根据策略需要添加更多导入。
# flake8: noqa: F401
# isort: skip_file
# --- Do not remove these imports ---
import numpy as np
import pandas as pd
from datetime import datetime, timedelta, timezone
from pandas import DataFrame
from typing import Dict, Optional, Union, Tuple
from freqtrade.strategy import (
IStrategy,
Trade,
Order,
PairLocks,
informative, # @informative decorator
# Hyperopt Parameters
BooleanParameter,
CategoricalParameter,
DecimalParameter,
IntParameter,
RealParameter,
# timeframe helpers
timeframe_to_minutes,
timeframe_to_next_date,
timeframe_to_prev_date,
# Strategy helper functions
merge_informative_pair,
stoploss_from_absolute,
stoploss_from_open,
)
# --------------------------------
# Add your lib to import here
import talib.abstract as ta
from technical import qtpylib
策略文件加载¶
默认情况下,Freqtrade 会尝试从 userdir(默认路径为 user_data/strategies)目录下的所有 .py 文件中加载策略。
假设你的策略名为 AwesomeStrategy,并保存在文件 user_data/strategies/AwesomeStrategy.py 中,那么你可以通过以下命令以模拟(或实盘,取决于配置)模式启动 Freqtrade:
freqtrade trade --strategy AwesomeStrategy
请注意,我们使用的是类名,而不是文件名。
你可以使用 freqtrade list-strategies 命令查看 Freqtrade 能够加载的所有策略(即正确文件夹中的所有策略)。该命令还会包含一个“状态”字段,用于提示潜在问题。
自定义策略目录
你可以通过 --strategy-path user_data/otherPath 使用不同的目录。此参数适用于所有需要指定策略的命令。
辅助参考交易对¶
获取不可交易交易对的数据¶
某些策略可能需要更广泛时间范围的数据,此时获取额外的辅助参考交易对(informative pairs)数据会非常有帮助。
这些交易对的 OHLCV 数据将在常规白名单刷新过程中一并下载,并可通过 DataProvider 获取,方式与其他交易对相同(见下文)。
除非这些交易对也被列入交易对白名单,或被动态白名单机制(如 VolumePairlist)选中,否则不会参与实际交易。
这些交易对应以元组形式指定,格式为 ("交易对", "时间周期"),其中交易对为第一个参数,时间周期为第二个参数。
示例:
def informative_pairs(self):
return [("ETH/USDT", "5m"),
("BTC/TUSD", "15m"),
]
完整示例可参见 DataProvider 章节。
警告
由于这些交易对会在白名单刷新时一同更新,建议尽量保持列表简短。只要所用交易所支持且活跃,任何时间周期和交易对均可指定。但尽可能使用重采样生成较长周期的 K 线,以避免向交易所发送过多请求而导致被封禁。
替代 K 线类型
informative_pairs 还可以提供第三个元组元素,用于显式定义 K 线类型。替代 K 线类型的可用性取决于交易模式和交易所。通常,现货交易对不能用于期货市场,而期货 K 线也不能作为现货机器人的辅助参考对。具体细节可能因交易所而异,相关信息可在交易所文档中找到。
def informative_pairs(self):
return [
("ETH/USDT", "5m", ""), # Uses default candletype, depends on trading_mode (recommended)
("ETH/USDT", "5m", "spot"), # Forces usage of spot candles (only valid for bots running on spot markets).
("BTC/TUSD", "15m", "futures"), # Uses futures candles (only bots with `trading_mode=futures`)
("BTC/TUSD", "15m", "mark"), # Uses mark candles (only bots with `trading_mode=futures`)
]
辅助参考对装饰器 (@informative())¶
要轻松定义信息对,请使用 @informative 装饰器。所有被装饰的 populate_indicators_* 方法都会独立运行,无法访问其他信息对的数据。然而,每个交易对的所有信息性数据框会被合并,并传递给主 populate_indicators() 方法。
注意
如果你在生成一个信息对时需要使用另一个信息对的数据,则不要使用 @informative 装饰器。请参考DataProvider 章节中描述的手动方式来定义信息对。
在进行超参数优化(hyperopt)时,不支持使用可优化参数的 .value 属性。请改用 .range 属性。更多信息请参见优化指标参数。
完整文档
def informative(
timeframe: str,
asset: str = "",
fmt: str | Callable[[Any], str] | None = None,
*,
candle_type: CandleType | str | None = None,
ffill: bool = True,
) -> Callable[[PopulateIndicators], PopulateIndicators]:
"""
A decorator for populate_indicators_Nn(self, dataframe, metadata), allowing these functions to
define informative indicators.
Example usage:
@informative('1h')
def populate_indicators_1h(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)
return dataframe
:param timeframe: Informative timeframe. Must always be equal or higher than strategy timeframe.
:param asset: Informative asset, for example BTC, BTC/USDT, ETH/BTC. Do not specify to use
current pair. Also supports limited pair format strings (see below)
:param fmt: Column format (str) or column formatter (callable(name, asset, timeframe)). When not
specified, defaults to:
* {base}_{quote}_{column}_{timeframe} if asset is specified.
* {column}_{timeframe} if asset is not specified.
Pair format supports these format variables:
* {base} - base currency in lower case, for example 'eth'.
* {BASE} - same as {base}, except in upper case.
* {quote} - quote currency in lower case, for example 'usdt'.
* {QUOTE} - same as {quote}, except in upper case.
Format string additionally supports this variables.
* {asset} - full name of the asset, for example 'BTC/USDT'.
* {column} - name of dataframe column.
* {timeframe} - timeframe of informative dataframe.
:param ffill: ffill dataframe after merging informative pair.
:param candle_type: '', mark, index, premiumIndex, or funding_rate
"""
快速简便地定义信息对的方法
大多数情况下,我们并不需要 merge_informative_pair() 提供的强大功能和灵活性,因此可以使用装饰器快速定义信息对。
from datetime import datetime
from freqtrade.persistence import Trade
from freqtrade.strategy import IStrategy, informative
class AwesomeStrategy(IStrategy):
# This method is not required.
# def informative_pairs(self): ...
# Define informative upper timeframe for each pair. Decorators can be stacked on same
# method. Available in populate_indicators as 'rsi_30m' and 'rsi_1h'.
@informative('30m')
@informative('1h')
def populate_indicators_1h(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)
return dataframe
# Define BTC/STAKE informative pair. Available in populate_indicators and other methods as
# 'btc_rsi_1h'. Current stake currency should be specified as {stake} format variable
# instead of hard-coding actual stake currency. Available in populate_indicators and other
# methods as 'btc_usdt_rsi_1h' (when stake currency is USDT).
@informative('1h', 'BTC/{stake}')
def populate_indicators_btc_1h(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)
return dataframe
# Define BTC/ETH informative pair. You must specify quote currency if it is different from
# stake currency. Available in populate_indicators and other methods as 'eth_btc_rsi_1h'.
@informative('1h', 'ETH/BTC')
def populate_indicators_eth_btc_1h(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)
return dataframe
# Define BTC/STAKE informative pair. A custom formatter may be specified for formatting
# column names. A callable `fmt(**kwargs) -> str` may be specified, to implement custom
# formatting. Available in populate_indicators and other methods as 'rsi_upper_1h'.
@informative('1h', 'BTC/{stake}', '{column}_{timeframe}')
def populate_indicators_btc_1h_2(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe['rsi_upper'] = ta.RSI(dataframe, timeperiod=14)
return dataframe
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# Strategy timeframe indicators for current pair.
dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)
# Informative pairs are available in this method.
dataframe['rsi_less'] = dataframe['rsi'] < dataframe['rsi_1h']
return dataframe
注意
在访问其他交易对的信息性数据框时,请使用字符串格式化。这样可以在配置中轻松更改质押货币(stake currency),而无需调整策略代码。
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
stake = self.config['stake_currency']
dataframe.loc[
(
(dataframe[f'btc_{stake}_rsi_1h'] < 35)
&
(dataframe['volume'] > 0)
),
['enter_long', 'enter_tag']] = (1, 'buy_signal_rsi')
return dataframe
或者,也可以通过重命名列来从列名中移除质押货币:@informative('1h', 'BTC/{stake}', fmt='{base}_{column}_{timeframe}')。
方法名称重复
使用 @informative() 装饰器标记的方法必须始终具有唯一名称!重复使用相同名称(例如复制粘贴已定义的信息方法)会覆盖之前定义的方法,由于 Python 编程语言的限制,这不会产生任何错误。在这种情况下,你会发现策略文件中靠前定义的方法所创建的指标在数据框中不可用。请仔细检查方法名称,确保它们是唯一的!
merge_informative_pair()¶
此方法可帮助你安全且一致地将信息对合并到常规主数据框中,避免引入前瞻偏差(lookahead bias)。
选项:
- 重命名列为唯一列名
- 合并数据框并避免前瞻偏差
- 前向填充(可选)
完整示例,请参见下方的完整数据提供者示例。
信息性数据框中的所有列将以重命名的形式出现在返回的数据框中:
列重命名
假设 inf_tf = '1d',结果列名为:
'date', 'open', 'high', 'low', 'close', 'rsi' # from the original dataframe
'date_1d', 'open_1d', 'high_1d', 'low_1d', 'close_1d', 'rsi_1d' # from the informative dataframe
列重命名 - 1 小时
假设 inf_tf = '1h',结果列名为:
'date', 'open', 'high', 'low', 'close', 'rsi' # from the original dataframe
'date_1h', 'open_1h', 'high_1h', 'low_1h', 'close_1h', 'rsi_1h' # from the informative dataframe
自定义实现
可以自定义实现,方法如下:
# Shift date by 1 candle
# This is necessary since the data is always the "open date"
# and a 15m candle starting at 12:15 should not know the close of the 1h candle from 12:00 to 13:00
minutes = timeframe_to_minutes(inf_tf)
# Only do this if the timeframes are different:
informative['date_merge'] = informative["date"] + pd.to_timedelta(minutes, 'm')
# Rename columns to be unique
informative.columns = [f"{col}_{inf_tf}" for col in informative.columns]
# Assuming inf_tf = '1d' - then the columns will now be:
# date_1d, open_1d, high_1d, low_1d, close_1d, rsi_1d
# Combine the 2 dataframes
# all indicators on the informative sample MUST be calculated before this point
dataframe = pd.merge(dataframe, informative, left_on='date', right_on=f'date_merge_{inf_tf}', how='left')
# FFill to have the 1d value available in every row throughout the day.
# Without this, comparisons would only work once per day.
dataframe = dataframe.ffill()
信息时间周期 < 主时间周期
不建议在此方法中使用比主数据框时间周期更小的信息时间周期,因为这将无法利用额外提供的信息。若要正确使用更详细的信息,应采用更高级的方法(超出本文档范围)。
附加数据(DataProvider)¶
策略提供了对 DataProvider 的访问,使你能够获取更多可用于策略的数据。
所有方法在失败时均返回 None,即失败不会抛出异常。
请始终检查操作模式,以选择正确的方法来获取数据(示例见下文)。
Hyperopt 的限制
在 hyperopt 期间可以使用 DataProvider,但它只能在策略内部的 populate_indicators() 方法中使用,不能在 hyperopt 类文件中使用。它也无法在 populate_entry_trend() 和 populate_exit_trend() 方法中使用。
DataProvider 的可用选项¶
available_pairs- 一个属性,包含缓存的交易对及其时间周期的元组列表(交易对,时间周期)。current_whitelist()- 返回当前白名单交易对的列表。适用于访问动态白名单(例如 VolumePairlist)。get_pair_dataframe(pair, timeframe)- 这是一个通用方法,返回历史数据(用于回测)或缓存的实时数据(用于模拟运行和实盘运行模式)。get_analyzed_dataframe(pair, timeframe)- 返回已分析的数据帧(即调用populate_indicators()、populate_buy()、populate_sell()后的结果)以及最新分析的时间。historic_ohlcv(pair, timeframe)- 返回存储在磁盘上的历史数据。market(pair)- 返回该交易对的市场数据:手续费、限制、精度、活跃状态标志等。更多关于市场数据结构的信息,请参见ccxt 文档。ohlcv(pair, timeframe)- 当前交易对的缓存 K 线(OHLCV)数据,返回 DataFrame 或空的 DataFrame。orderbook(pair, maximum)- 返回该交易对最新的订单簿数据,返回一个包含买盘/卖盘的字典,总计包含maximum条记录。ticker(pair)- 返回该交易对的当前行情数据。更多关于 Ticker 数据结构的信息,请参见ccxt 文档。check_delisting(pair)- 如果该交易对有退市计划,则返回其退市时间;否则返回 None。funding_rate(pair)- 返回该交易对当前的资金费率数据。runmode- 包含当前运行模式的属性。
使用示例¶
available_pairs¶
for pair, timeframe in self.dp.available_pairs:
print(f"available {pair}, {timeframe}")
current_whitelist()¶
假设你开发了一个策略,使用按交易量排名前 10 的交易所交易对,基于 1d 时间周期生成信号,但在 5m 时间周期上进行交易。
该策略的逻辑可能如下所示:
每 5 分钟使用 VolumePairList 扫描一次交易量排名前 10 的交易对,并使用 14 日 RSI 进行入场和离场。
由于可用数据有限,很难将 5m K 线重采样为日线以用于 14 日 RSI 计算。大多数交易所仅允许用户获取 500-1000 根 K 线,这实际上只提供约 1.74 天的日线数据。而我们至少需要 14 天!
由于我们无法对数据进行重采样,必须使用信息对(informative pair),但白名单是动态的,我们无法提前知道应使用哪些交易对!这就成了一个问题!
这时调用 self.dp.current_whitelist() 就非常有用,可以获取当前白名单中的交易对。
def informative_pairs(self):
# get access to all pairs available in whitelist.
pairs = self.dp.current_whitelist()
# Assign timeframe to each pair so they can be downloaded and cached for strategy.
informative_pairs = [(pair, '1d') for pair in pairs]
return informative_pairs
使用 current_whitelist 进行绘图
plot-dataframe 不支持当前的白名单,因为该命令通常通过显式提供交易对列表(pairlist)来使用,这会导致此方法的返回值产生误导。在 webserver 模式 下的 FreqUI 可视化中也不支持此功能,因为 webserver 模式的配置不需要设置交易对列表。
get_pair_dataframe(pair, timeframe)¶
# fetch live / historical candle (OHLCV) data for the first informative pair
inf_pair, inf_timeframe = self.informative_pairs()[0]
informative = self.dp.get_pair_dataframe(pair=inf_pair,
timeframe=inf_timeframe)
关于回测的警告
在回测中,dp.get_pair_dataframe() 的行为取决于其调用位置。在 populate_*() 方法中,dp.get_pair_dataframe() 会返回完整的时间范围。请确保不要“窥探未来”数据,以免在模拟或实盘运行时出现意外结果。在 回调函数 中,你将获得截至当前(模拟)K 线的完整时间范围。
get_analyzed_dataframe(交易对, 时间周期)¶
该方法被 Freqtrade 内部用于确定最新的交易信号。它也可以在特定的回调函数中使用,以获取触发操作的信号(详见 高级策略文档 中关于可用回调函数的更多细节)。
# fetch current dataframe
dataframe, last_updated = self.dp.get_analyzed_dataframe(pair=metadata['pair'],
timeframe=self.timeframe)
暂无数据
如果请求的交易对未被缓存,则返回一个空的 DataFrame。你可以通过 if dataframe.empty: 来检查这种情况,并相应地处理。使用白名单中的交易对时,这种情况不应发生。
orderbook(交易对, 最大值)¶
获取指定交易对的当前订单簿。
if self.dp.runmode.value in ('live', 'dry_run'):
ob = self.dp.orderbook(metadata['pair'], 1)
dataframe['best_bid'] = ob['bids'][0][0]
dataframe['best_ask'] = ob['asks'][0][0]
订单簿结构与 ccxt 的订单结构保持一致,因此返回结果的格式如下所示:
{
'bids': [
[ price, amount ], // [ float, float ]
[ price, amount ],
...
],
'asks': [
[ price, amount ],
[ price, amount ],
//...
],
//...
}
因此,如上所示使用 ob['bids'][0][0] 将获取最优买价。ob['bids'][0][1] 则会查看该订单簿位置上的挂单数量。
关于回测的警告
订单簿不属于历史数据的一部分,这意味着如果在回测或超参数优化中使用此方法,结果将不准确,因为该方法返回的是实时更新的值。
ticker(交易对)¶
if self.dp.runmode.value in ('live', 'dry_run'):
ticker = self.dp.ticker(metadata['pair'])
dataframe['last_price'] = ticker['last']
dataframe['volume24h'] = ticker['quoteVolume']
dataframe['vwap'] = ticker['vwap']
警告
尽管 ticker 数据结构是 ccxt 统一接口的一部分,但不同交易所返回的值可能有所不同。例如,许多交易所不提供 vwap 值,某些交易所并不总是填充 last 字段(因此其值可能为 None)等。因此,你需要仔细验证交易所返回的 ticker 数据,并添加适当的错误处理或默认值。
关于回测的警告
此方法始终返回最新/实时的数值。因此,在回测/超参数优化中使用此方法而未进行运行模式检查,将导致错误的结果,例如你的整个 DataFrame 所有行都会包含相同的单个值。
check_delisting(交易对)¶
def custom_exit(self, pair: str, trade: Trade, current_time: datetime, current_rate: float, current_profit: float, **kwargs):
if self.dp.runmode.value in ('live', 'dry_run'):
delisting_dt = self.dp.check_delisting(pair)
if delisting_dt is not None:
return "delist"
退市信息的可用性
此方法仅对部分交易所可用,当该功能不可用或交易对未计划退市时,将返回 None。
关于回测的警告
此方法始终返回最新/实时的数值。因此,在回测/超参数优化中使用此方法而未进行运行模式检查,将导致错误的结果,例如你的整个 DataFrame 所有行都会包含相同的单个值。
funding_rate(交易对)¶
获取指定交易对的当前资金费率,仅适用于以 base/quote:settle 格式表示的期货交易对(例如 ETH/USDT:USDT)。
if self.dp.runmode.value in ('live', 'dry_run'):
funding_rate = self.dp.funding_rate(metadata['pair'])
dataframe['current_funding_rate'] = funding_rate['fundingRate']
dataframe['next_funding_timestamp'] = funding_rate['fundingTimestamp']
dataframe['next_funding_datetime'] = funding_rate['fundingDatetime']
资金费率结构与 ccxt 的资金费率结构保持一致,因此返回结果的格式如下所示:
{
"info": {
# ...
},
"symbol": "BTC/USDT:USDT",
"markPrice": 110730.7,
"indexPrice": 110782.52,
"interestRate": 0.0001,
"estimatedSettlePrice": 110822.67200153,
"timestamp": 1757146321001,
"datetime": "2025-09-06T08:12:01.001Z",
"fundingRate": 5.609e-05,
"fundingTimestamp": 1757174400000,
"fundingDatetime": "2025-09-06T16:00:00.000Z",
"nextFundingRate": None,
"nextFundingTimestamp": None,
"nextFundingDatetime": None,
"previousFundingRate": None,
"previousFundingTimestamp": None,
"previousFundingDatetime": None,
"interval": None,
}
因此,如上所示使用 funding_rate['fundingRate'] 将获取当前的资金费率。实际可用的数据在不同交易所之间可能有所差异,因此此代码可能在不同交易所上无法按预期工作。
关于回测的警告
当前资金费率不属于历史数据,这意味着如果使用此方法,回测和超参数优化将无法正常工作,因为该方法返回的是实时更新的值。我们建议在回测时使用历史可用的资金费率(该数据会自动下载,频率由交易所提供,通常为 4 小时或 8 小时)。self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe='8h', candle_type="funding_rate")
发送通知¶
数据提供器 .send_msg() 函数允许你从策略中发送自定义通知。相同的 notification 在每个 K 线周期内只会发送一次,除非第二个参数 (always_send) 设置为 True。
self.dp.send_msg(f"{metadata['pair']} just got hot!")
# Force send this notification, avoid caching (Please read warning below!)
self.dp.send_msg(f"{metadata['pair']} just got hot!", always_send=True)
通知仅在交易模式下(实盘/模拟运行)发送——因此在回测时可以无条件调用此方法。
消息刷屏
在此方法中设置 always_send=True 可能会导致频繁的消息推送。请谨慎使用,并仅在确定不会在单个 K 线周期内频繁触发的条件下启用,以避免每 5 秒就发送一条消息。
完整的 DataProvider 示例¶
from freqtrade.strategy import IStrategy, merge_informative_pair
from pandas import DataFrame
class SampleStrategy(IStrategy):
# strategy init stuff...
timeframe = '5m'
# more strategy init stuff..
def informative_pairs(self):
# get access to all pairs available in whitelist.
pairs = self.dp.current_whitelist()
# Assign tf to each pair so they can be downloaded and cached for strategy.
informative_pairs = [(pair, '1d') for pair in pairs]
# Optionally Add additional "static" pairs
informative_pairs += [("ETH/USDT", "5m"),
("BTC/TUSD", "15m"),
]
return informative_pairs
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
if not self.dp:
# Don't do anything if DataProvider is not available.
return dataframe
inf_tf = '1d'
# Get the informative pair
informative = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe=inf_tf)
# Get the 14 day rsi
informative['rsi'] = ta.RSI(informative, timeperiod=14)
# Use the helper function merge_informative_pair to safely merge the pair
# Automatically renames the columns and merges a shorter timeframe dataframe and a longer timeframe informative pair
# use ffill to have the 1d value available in every row throughout the day.
# Without this, comparisons between columns of the original and the informative pair would only work once per day.
# Full documentation of this method, see below
dataframe = merge_informative_pair(dataframe, informative, self.timeframe, inf_tf, ffill=True)
# Calculate rsi of the original dataframe (5m timeframe)
dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)
# Do other stuff
# ...
return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[
(
(qtpylib.crossed_above(dataframe['rsi'], 30)) & # Signal: RSI crosses above 30
(dataframe['rsi_1d'] < 30) & # Ensure daily RSI is < 30
(dataframe['volume'] > 0) # Ensure this candle had volume (important for backtesting)
),
['enter_long', 'enter_tag']] = (1, 'rsi_cross')
附加数据(钱包余额)¶
策略中提供了访问 wallets 对象的功能。该对象包含你在交易所账户中的当前余额信息。
回测 / 超参数优化
钱包对象的行为取决于调用它的函数。在 populate_*() 方法中,它将返回配置中的完整钱包信息;而在回调函数中,你将获得模拟过程中对应时间点的实际钱包状态。
请始终检查 wallets 是否可用,以避免在回测期间出现错误。
if self.wallets:
free_eth = self.wallets.get_free('ETH')
used_eth = self.wallets.get_used('ETH')
total_eth = self.wallets.get_total('ETH')
钱包对象的可用方法¶
get_free(asset)- 当前可用于交易的余额get_used(asset)- 当前已被占用的余额(挂单)get_total(asset)- 总余额 — 上述两项之和
附加数据(交易记录)¶
可以通过查询数据库在策略中获取交易历史记录。
在文件顶部导入所需对象:
from freqtrade.persistence import Trade
以下示例查询了当天当前交易对(metadata['pair'])的交易记录。也可以轻松添加其他过滤条件。
trades = Trade.get_trades_proxy(pair=metadata['pair'],
open_date=datetime.now(timezone.utc) - timedelta(days=1),
is_open=False,
]).order_by(Trade.close_date).all()
# Summarize profit for this pair.
curdayprofit = sum(trade.close_profit for trade in trades)
有关可用方法的完整列表,请参考交易对象文档。
警告
在回测或超参数优化期间,populate_* 方法中无法获取交易历史,结果将为空。
阻止特定交易对的交易¶
当某个交易对退出后,Freqtrade 会自动锁定该交易对当前的 K 线周期(直到该 K 线结束),防止立即重新入场。
这是为了防止在单个 K 线内发生大量且频繁的“瀑布式”交易。
被锁定的交易对将显示消息:交易对 <pair> 当前已被锁定。
在策略内部锁定交易对¶
有时可能希望在某些事件发生后锁定某个交易对(例如连续多次亏损交易)。
Freqtrade 提供了一种简单的方法,可在策略内部通过调用 self.lock_pair(pair, until, [reason]) 来实现锁定交易对。until 必须是一个未来的 datetime 对象,在此时间之后该交易对的交易将重新启用;而 reason 是一个可选字符串,用于说明锁定该交易对的原因。
也可以手动解除锁定,方法是调用 self.unlock_pair(pair) 或提供解锁原因的 self.unlock_reason(<reason>)。self.unlock_reason(<reason>) 将会解除所有当前以指定原因为由被锁定的交易对。
要检查某个交易对是否已被锁定,请使用 self.is_pair_locked(pair)。
注意
被锁定的交易对的时间总是向上取整到下一个 K 线周期。例如,假设时间框架为 5m,若设置 until 为 10:18,则该交易对将被锁定至 10:15–10:20 的 K 线完成为止。
警告
在回测期间无法手动锁定交易对,仅允许通过防护机制(Protections)进行锁定。
交易对锁定示例¶
from freqtrade.persistence import Trade
from datetime import timedelta, datetime, timezone
# Put the above lines at the top of the strategy file, next to all the other imports
# --------
# Within populate indicators (or populate_entry_trend):
if self.config['runmode'].value in ('live', 'dry_run'):
# fetch closed trades for the last 2 days
trades = Trade.get_trades_proxy(
pair=metadata['pair'], is_open=False,
open_date=datetime.now(timezone.utc) - timedelta(days=2))
# Analyze the conditions you'd like to lock the pair .... will probably be different for every strategy
sumprofit = sum(trade.close_profit for trade in trades)
if sumprofit < 0:
# Lock pair for 12 hours
self.lock_pair(metadata['pair'], until=datetime.now(timezone.utc) + timedelta(hours=12))
打印主数据框¶
要查看当前的主数据框,可以在 populate_entry_trend() 或 populate_exit_trend() 中插入打印语句。你也可以同时打印交易对名称,以便清楚地知道当前显示的是哪个交易对的数据。
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[
(
#>> whatever condition<<<
),
['enter_long', 'enter_tag']] = (1, 'somestring')
# Print the Analyzed pair
print(f"result for {metadata['pair']}")
# Inspect the last 5 rows
print(dataframe.tail())
return dataframe
通过使用 print(dataframe) 而不是 print(dataframe.tail()),也可以打印多行数据。但不建议这样做,因为它可能导致大量输出(每 5 秒每个交易对约 500 行)。
编写策略时的常见错误¶
回测时窥探未来数据¶
出于性能考虑,回测会一次性分析整个数据框的时间范围。因此,策略编写者必须确保策略不会向前看(lookahead),即不能使用在实盘或模拟交易模式下无法获得的数据。
这是一个常见的痛点,可能导致回测结果与模拟/实盘运行之间存在巨大差异。那些窥探未来数据的策略在回测中可能表现极佳,常常产生惊人的利润或胜率,但在真实条件下却会失败或表现不佳。
以下是一些应避免的常见模式,以防止出现此类问题:
- 不要使用
shift(-1)或其他负值参数。这会在回测中使用来自未来的数据,而在模拟或实盘模式下这些数据是不可用的。 - 不要在
populate_函数中使用.iloc[-1]或数据框中的任何其他绝对位置索引,因为在模拟运行和回测之间的行为会有所不同。不过,在回调函数中使用绝对iloc索引是安全的——参见 策略回调函数。 - 不要使用涉及整个数据框或列的所有值的函数,例如
dataframe['mean_volume'] = dataframe['volume'].mean()。由于回测使用的是完整数据框,在数据框的任意时刻,'mean_volume'序列都会包含来自未来的数据。应改用 rolling() 计算,例如dataframe['volume'].rolling(<window>).mean()。 - 不要使用
.resample('1h')。这会使用时间段区间的左边界,从而将数据从小时边界移动到小时的开始处。请改用.resample('1h', label='right')。 - 不要使用
.merge()将较长的时间框架合并到较短的时间框架上。相反,请使用信息对辅助工具。(简单的合并可能会隐式地导致前瞻偏差,因为日期指的是开盘时间,而不是收盘时间。)
识别问题
你应该始终使用两个辅助命令:lookahead-analysis 和 recursive-analysis,它们各自能以不同的方式帮助你发现策略中的问题。请将它们视为其本质——用于识别最常见问题的辅助工具。每个命令的负面结果并不能保证上述错误完全不存在。
信号冲突¶
当冲突信号发生碰撞时(例如,同时将 'enter_long' 和 'exit_long' 设置为 1),Freqtrade 将不采取任何操作并忽略入场信号。这将避免出现刚入场就立即退出的交易。显然,这可能导致错失入场机会。
以下规则适用,当 3 个信号中有超过一个被设置时,入场信号将被忽略:
enter_long->exit_long,enter_shortenter_short->exit_short,enter_long
更多策略思路¶
如需获取更多策略思路,可前往策略仓库。欢迎将其作为示例参考,但结果将取决于当前市场状况、交易对等因素。因此,这些策略仅应用于学习目的,不可直接用于实盘交易。请先针对你的交易所/目标交易对进行回测,再通过模拟交易仔细评估,风险自负。
欢迎将其中任意策略作为你自研策略的灵感来源。我们也乐于接受包含新策略的 Pull Request 提交至仓库。
下一步¶
现在你已经有了一个完美的策略,可能想要进行回测。接下来的步骤是学习如何使用回测功能。