策略自定义¶
本页将介绍如何自定义你的策略、添加新指标以及设置交易规则。
如果你还没有了解过,建议先阅读:
- Freqtrade 策略 101,快速入门策略开发
- Freqtrade 机器人基础,了解机器人整体运行机制
开发你自己的策略¶
机器人自带了一个默认策略文件。
此外,策略仓库中还提供了其他策略。
不过你很可能有自己的策略想法。本文档旨在帮助你将想法转化为可运行的策略。
生成策略模板¶
你可以通过以下命令快速开始:
freqtrade new-strategy --strategy AwesomeStrategy
这会基于模板创建一个名为 AwesomeStrategy
的新策略,文件路径为 user_data/strategies/AwesomeStrategy.py
。
策略结构剖析¶
一个策略文件包含构建策略逻辑所需的全部信息:
- K线数据(OHLCV 格式)
- 各类指标
- 入场逻辑
- 信号
- 出场逻辑
- 信号
- 最小收益率(ROI)
- 回调函数(自定义函数)
- 止损
- 固定/绝对止损
- 跟踪止损
- 回调函数(自定义函数)
- 定价(可选)
- 持仓调整(可选)
机器人自带一个名为 SampleStrategy
的示例策略,可作为参考:user_data/strategies/sample_strategy.py
。
你可以用参数 --strategy SampleStrategy
进行测试。注意这里用的是策略类名,而不是文件名。
此外,还有一个名为 INTERFACE_VERSION
的属性,用于定义策略接口的版本。当前版本为 3
,如果未在策略中显式设置,则默认为 3
。
你可能会看到旧策略设置为接口版本 2
,未来版本会要求升级到 v3
。
用 trade
命令即可启动机器人进入 dry
或 live
模式:
freqtrade trade --strategy AwesomeStrategy
机器人运行模式¶
Freqtrade 策略可在 5
种主要模式下被机器人处理:
- 回测(backtesting)
- 超参优化(hyperopting)
- 模拟盘(dry/forward testing)
- 实盘(live)
- FreqAI(本页不涉及)
关于如何设置 dry
或 live
模式,请查阅配置文档。
测试时请始终使用 dry 模式,这样可以在不冒资金风险的情况下了解策略实际表现。
深入剖析¶
以下内容将以 user
DataFrame¶
Freqtrade 使用 pandas 存储/提供 K 线(OHLCV)数据。 Pandas 是处理表格数据的强大库。
DataFrame 的每一行对应一根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
DataFrame 是一个表格,列不是单一值,而是一组数据。因此,像下面这样直接用 Python 比较会报错:
if dataframe['rsi'] > 30:
dataframe['enter_long'] = 1
上述写法会报错:The truth value of a Series is ambiguous [...]
。
应改为 pandas 向量化写法,对整个 dataframe 执行操作:
dataframe.loc[
(dataframe['rsi'] > 30)
, 'enter_long'] = 1
这样会在 RSI 大于 30 时,为新列 enter_long
赋值 1。
Freqtrade 会用这个新列作为入场信号,假定下一根K线开盘时开仓。
Pandas 支持高效的向量化计算,建议尽量避免循环,直接用向量化方法。
向量化操作会对整列数据进行计算,比逐行循环快得多。
为什么看不到"实时"K线数据?¶
Freqtrade 不会在 dataframe 中存储未完成/未收盘的K线。
用未完成数据做决策叫"重绘",有些平台允许,但 Freqtrade 不支持。只有完整K线数据可用。
自定义指标¶
入场和出场信号需要用到指标。你可以在策略文件的 populate_indicators()
方法中添加更多指标。
只应添加在 populate_entry_trend()
、populate_exit_trend()
或用于生成其他指标的指标,否则会影响性能。
务必返回 dataframe,且不要删除/修改 “open”, “high”, “low”, “close”, “volume” 这几列,否则会导致异常。
示例:
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
为给定 DataFrame 添加多种技术指标
性能提示:为获得最佳性能,请只用你策略或超参优化用到的指标,否则会浪费内存和 CPU。
:param dataframe: 交易所数据 DataFrame
:param metadata: 额外信息,如当前交易对
:return: 包含所有策略所需指标的 DataFrame
"""
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)
# 记得始终返回 dataframe
return dataframe
指标库¶
Freqtrade 默认安装了以下技术指标库:
如有需要可安装其他技术指标库,或自行编写自定义指标。
策略启动期¶
部分指标在启动初期因数据不足会出现 NaN 或计算不准确。Freqtrade 无法自动判断不稳定期长度,会直接用 dataframe 中的指标值。
为此,策略可设置 startup_candle_count
属性。
该值应设为策略中所有指标所需的最大历史K线数。若用到高阶时间周期 informative pair,startup_candle_count
也无需改变,只需用最大周期。
可用 recursive-analysis 检查合适的 startup_candle_count
。当递归分析显示方差为 0% 时,说明历史数据已足够。
如本例策略用到 ema100,则应设为 400(startup_candle_count = 400
),以保证 ema100 计算准确。
dataframe['ema100'] = ta.EMA(dataframe, timeperiod=100)
告知机器人所需历史长度后,回测和超参优化可从指定时间点开始。
示例¶
假设用 EMA100 策略回测 2019 年 1 月的 5m 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)会被剔除。
入场信号规则¶
编辑策略文件中的 populate_entry_trend()
方法,更新入场逻辑。
务必返回 dataframe,且不要删除/修改 “open”, “high”, “low”, “close”, “volume” 这几列,否则会导致异常。
该方法还需定义新列 enter_long
(做空为 enter_short
),入场时为 1,无操作为 0。即使只做空也必须设置 enter_long
。
可用 enter_tag
列为信号命名,便于后续调试和分析。
示例(来自 sample_strategy.py):
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
基于技术指标,为给定 dataframe 生成买入信号
:param dataframe: 已填充指标的 DataFrame
:param metadata: 额外信息,如当前交易对
:return: 包含买入信号的 DataFrame
"""
dataframe.loc[
(
(qtpylib.crossed_above(dataframe['rsi'], 30)) & # RSI 上穿 30
(dataframe['tema'] <= dataframe['bb_middleband']) & # 条件
(dataframe['tema'] > dataframe['tema'].shift(1)) & # 条件
(dataframe['volume'] > 0) # 确保有成交量
),
['enter_long', 'enter_tag']] = (1, 'rsi_cross')
return dataframe
出场信号规则¶
编辑策略文件中的 populate_exit_trend()
方法,更新出场逻辑。
可通过在配置或策略中设置 use_exit_signal
为 false 禁用出场信号。
use_exit_signal
不影响信号冲突规则,后者仍然生效,可能阻止入场。
务必返回 dataframe,且不要删除/修改 “open”, “high”, “low”, “close”, “volume” 这几列,否则会导致异常。
该方法还需定义新列 exit_long
(做空为 exit_short
),出场时为 1,无操作为 0。
可用 exit_tag
列为信号命名,便于后续调试和分析。
示例(来自 sample_strategy.py):
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
基于技术指标,为给定 dataframe 生成卖出信号
:param dataframe: 已填充指标的 DataFrame
:param metadata: 额外信息,如当前交易对
:return: 包含卖出信号的 DataFrame
"""
dataframe.loc[
(
(qtpylib.crossed_above(dataframe['rsi'], 70)) & # RSI 上穿 70
(dataframe['tema'] > dataframe['bb_middleband']) & # 条件
(dataframe['tema'] < dataframe['tema'].shift(1)) & # 条件
(dataframe['volume'] > 0) # 确保有成交量
),
['exit_long', 'exit_tag']] = (1, 'rsi_too_high')
return dataframe
最小收益率(ROI)¶
minimal_roi
策略变量定义了交易在退出前应达到的最小收益率,与出场信号无关。
格式如下,是一个 python 字典,键为开仓后经过的分钟数,值为百分比:
minimal_roi = {
"40": 0.0,
"30": 0.01,
"20": 0.02,
"0": 0.04
}
上述配置含义:
- 达到 4% 盈利时退出
- 20 分钟后达到 2% 盈利时退出
- 30 分钟后达到 1% 盈利时退出
- 40 分钟后只要不亏损就退出
计算时包含手续费。
禁用最小 ROI¶
如需完全禁用 ROI,将其设为空字典:
minimal_roi = {}
ROI 中用计算表达式¶
如需按K线周期(timeframe)设置时间,可用如下写法:
from freqtrade.exchange import timeframe_to_minutes
class AwesomeStrategy(IStrategy):
timeframe = "1d"
timeframe_mins = timeframe_to_minutes(timeframe)
minimal_roi = {
"0": 0.05, # 前 3 根 K 线 5%
str(timeframe_mins * 3): 0.02, # 3 根 K 线后 2%
str(timeframe_mins * 6): 0.01, # 6 根 K 线后 1%
}
止损¶
强烈建议设置止损,以保护资金免受极端行情影响。
如设置 10% 止损:
stoploss = -0.10
更多止损功能详见止损专页。
时间周期(Timeframe)¶
即策略用的K线周期。
常见值有 “1m”、“5m”、“15m”、“1h”,也可用交易所支持的其他周期。
同一入场/出场信号在不同周期下效果可能完全不同。
在策略方法中可通过 self.timeframe
访问。
是否支持做空¶
如需在合约市场做空,需设置 can_short = True
。
启用后,策略在现货市场会加载失败。
如 enter_short
列有 1,但 can_short = False
(默认),则即使配置了合约市场也不会做空。
Metadata 字典¶
metadata
字典(populate_entry_trend
、populate_exit_trend
、populate_indicators
可用)包含额外信息。
目前有 pair
,可通过 metadata['pair']
获取,如 XRP/BTC
(合约市场为 XRP/BTC:BTC
)。
metadata 不应被修改,也不会在策略函数间持久化。
如需持久化信息,请查阅信息存储。
策略所需的导入¶
在创建策略时,你需要导入必要的模块和类。以下是一个策略所需的导入:
默认情况下,我们建议使用以下导入作为策略的基础: 这将涵盖 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
,可用如下命令 dry(或 live,视配置而定)运行:
freqtrade trade --strategy AwesomeStrategy
注意这里用的是类名,不是文件名。
用 freqtrade list-strategies
可查看所有可加载的策略(正确目录下的所有策略)。会有"状态"字段,提示潜在问题。
辅助交易对(Informative Pairs)¶
获取非交易对的数据¶
有些策略需要参考更大周期的其他交易对数据。
这些辅助对的 OHLCV 数据会在常规白名单刷新时一并下载,可通过 DataProvider
获取。
这些对不会被交易,除非也在白名单或被动态白名单(如 VolumePairlist
)选中。
需用元组指定,格式为 ("pair", "timeframe")
,第一个为交易对,第二个为周期。
示例:
def informative_pairs(self):
return [("ETH/USDT", "5m"),
("BTC/TUSD", "15m"),
]
完整示例见DataProvider 部分。
def informative_pairs(self):
return [
("ETH/USDT", "5m", ""), # 默认 K 线类型,随 trading_mode 变化(推荐)
("ETH/USDT", "5m", "spot"), # 强制用现货 K 线(仅现货机器人)
("BTC/TUSD", "15m", "futures"), # 用合约 K 线(仅合约机器人)
("BTC/TUSD", "15m", "mark"), # 用标记 K 线(仅合约机器人)
]
Informative pairs 装饰器(@informative()
)¶
可用 @informative
装饰器快速定义 informative pair。所有被装饰的 populate_indicators_*
方法独立运行,不能访问其他 informative pair 的数据。但所有 informative dataframe 会合并传递给主 populate_indicators()
。
超参优化时,hyperoptable 参数不支持 .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]:
"""
用于 populate_indicators_Nn(self, dataframe, metadata) 的装饰器,定义 informative 指标。
用法示例:
@informative('1h')
def populate_indicators_1h(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)
return dataframe
:param timeframe: 辅助K线周期,必须大于等于主策略周期。
:param asset: 辅助资产,如 BTC、BTC/USDT、ETH/BTC。不指定则用当前对。支持格式化字符串。
:param fmt: 列名格式(str)或格式化函数。默认:
* {base}_{quote}_{column}_{timeframe}(指定 asset 时)
* {column}_{timeframe}(未指定 asset 时)
支持变量:{base}、{BASE}、{quote}、{QUOTE}、{asset}、{column}、{timeframe}
:param ffill: informative pair 合并后是否前向填充。
:param candle_type: '', mark, index, premiumIndex, funding_rate
"""
merge_informative_pair()¶
该方法可安全、无前视偏差地将 informative pair 合并到主 dataframe。
功能:
- 列重命名,避免冲突
- 无前视偏差合并
- 可选前向填充
完整示例见完整数据提供者示例。
所有 informative dataframe 列会以新名字出现在主 dataframe:
额外数据(DataProvider)¶
策略可通过 DataProvider
获取更多数据。
所有方法失败时返回 None,不抛异常。
请根据运行模式选择合适方法(见下文示例)。
DataProvider 可用方法¶
available_pairs
- 返回缓存的所有对及其周期(pair, timeframe)元组current_whitelist()
- 返回当前白名单对列表,适合动态白名单(如 VolumePairlist)get_pair_dataframe(pair, timeframe)
- 通用方法,回测时返回历史数据,dry/live 时返回缓存数据get_analyzed_dataframe(pair, timeframe)
- 返回分析后的 dataframe 及最新分析时间historic_ohlcv(pair, timeframe)
- 返回磁盘上的历史数据market(pair)
- 返回交易对市场数据(手续费、限额、精度、活跃标志等)ohlcv(pair, timeframe)
- 返回当前缓存的K线数据orderbook(pair, maximum)
- 返回最新 orderbook 数据(bids/asks)ticker(pair)
- 返回当前 ticker 数据runmode
- 当前运行模式
示例用法¶
available_pairs¶
for pair, timeframe in self.dp.available_pairs:
print(f"available {pair}, {timeframe}")
current_whitelist()¶
假设你开发了一个用 5m
周期、用 1d
周期信号的策略,且用 VolumePairList 动态选前 10 个交易对。
策略逻辑如下:
每 5 分钟扫描前 10 个交易对,用 14 日 RSI 生成信号。
因数据有限,无法用 5m K 线重采样成日线。大多数交易所只给 500-1000 根K线,约等于 1.74 天。我们需要至少 14 天!
因此需用 informative pair,且因白名单动态变化,不知用哪些对!
此时可用 self.dp.current_whitelist()
获取当前白名单对。
def informative_pairs(self):
# 获取白名单所有对
pairs = self.dp.current_whitelist()
# 为每个对分配周期
informative_pairs = [(pair, '1d') for pair in pairs]
return informative_pairs
get_pair_dataframe(pair, timeframe)¶
# 获取第一个 informative pair 的K线数据
inf_pair, inf_timeframe = self.informative_pairs()[0]
informative = self.dp.get_pair_dataframe(pair=inf_pair,
timeframe=inf_timeframe)
get_analyzed_dataframe(pair, timeframe)¶
该方法供 freqtrade 内部用来判断最后信号,也可在特定回调中用来获取触发操作的信号(见高级策略文档)。
# 获取当前 dataframe
dataframe, last_updated = self.dp.get_analyzed_dataframe(pair=metadata['pair'],
timeframe=self.timeframe)
orderbook(pair, maximum)¶
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]
orderbook 结构与 ccxt 一致:
{
'bids': [
[ price, amount ],
...
],
'asks': [
[ price, amount ],
...
],
}
用 ob['bids'][0][0]
可取最优买价,ob['bids'][0][1]
为该价位数量。
ticker(pair)¶
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']
发送通知¶
dataprovider 的 .send_msg()
可在策略中发送自定义通知。
相同的通知在每个蜡烛图期间只会发送一次,除非第二个参数(always_send
)设置为 True。
self.dp.send_msg(f"{metadata['pair']} just got hot!")
# 强制发送此通知,避免缓存(请阅读下面的警告!)
self.dp.send_msg(f"{metadata['pair']} just got hot!", always_send=True)
通知只会在交易模式(实盘/模拟盘)中发送 - 因此可以在回测中无条件调用此方法。
完整 DataProvider 示例¶
from freqtrade.strategy import IStrategy, merge_informative_pair
from pandas import DataFrame
class SampleStrategy(IStrategy):
# 策略初始化...
timeframe = '5m'
# ...
def informative_pairs(self):
# 获取白名单所有对
pairs = self.dp.current_whitelist()
# 为每个对分配周期
informative_pairs = [(pair, '1d') for pair in 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:
# DataProvider 不可用时不做任何操作
return dataframe
inf_tf = '1d'
# 获取信息对
informative = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe=inf_tf)
# 获取14天RSI
informative['rsi'] = ta.RSI(informative, timeperiod=14)
# 使用辅助函数 merge_informative_pair 安全地合并交易对
# 自动重命名列并合并较短时间周期的 dataframe 和较长时间周期的信息对
# 使用 ffill 使1天的值在一天中的每一行都可用
# 如果没有这个,原始数据框和信息对的列之间的比较每天只能进行一次
# 此方法的完整文档,见下文
dataframe = merge_informative_pair(dataframe, informative, self.timeframe, inf_tf, ffill=True)
# 计算原始数据框的RSI(5分钟时间周期)
dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)
# 其他操作
# ...
return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[
(
(qtpylib.crossed_above(dataframe['rsi'], 30)) & # RSI 上穿 30
(dataframe['rsi_1d'] < 30) & # 日线 RSI < 30
(dataframe['volume'] > 0) # 有成交量
),
['enter_long', 'enter_tag']] = (1, 'rsi_cross')
额外数据(钱包 Wallets)¶
策略可通过 wallets
对象获取当前钱包/账户余额。
调用前请检查 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')
Wallets 可用方法¶
get_free(asset)
- 当前可用余额get_used(asset)
- 当前被占用余额(挂单)get_total(asset)
- 总余额(前两者之和)
额外数据(交易记录 Trades)¶
可在策略中通过数据库查询历史交易。
在文件顶部,导入所需对象:
from freqtrade.persistence import Trade
以下示例查询当天当前对的已平仓交易(可加其他过滤条件):
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()
# 汇总该交易对的利润
curdayprofit = sum(trade.close_profit for trade in trades)
更多方法请查阅 Trade 对象 文档。
阻止特定对交易¶
Freqtrade 会在某对平仓后自动锁定该对至当前K线结束,防止同一K线内频繁交易。
锁定对时会提示 Pair <pair> is currently locked.
。
在策略中锁定对¶
有时希望在特定事件后锁定某对(如连续亏损)。
可用 self.lock_pair(pair, until, [reason])
实现,until
为未来时间点,reason
为可选说明。
可用 self.unlock_pair(pair)
或 self.unlock_reason(<reason>)
手动解锁,后者会解锁所有因该原因锁定的对。
用 self.is_pair_locked(pair)
检查对是否被锁定。
锁定对示例¶
from freqtrade.persistence import Trade
from datetime import timedelta, datetime, timezone
# 放在策略文件顶部
# --------
# 在 populate_indicators 或 populate_entry_trend 中:
if self.config['runmode'].value in ('live', 'dry_run'):
# 查询近两天已平仓交易
trades = Trade.get_trades_proxy(
pair=metadata['pair'], is_open=False,
open_date=datetime.now(timezone.utc) - timedelta(days=2))
# 分析是否需要锁定
sumprofit = sum(trade.close_profit for trade in trades)
if sumprofit < 0:
# 锁定 12 小时
self.lock_pair(metadata['pair'], until=datetime.now(timezone.utc) + timedelta(hours=12))
打印主 dataframe¶
可在 populate_entry_trend()
或 populate_exit_trend()
中打印当前主 dataframe,便于调试。
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[
(
#>> 你的条件 <<<
),
['enter_long', 'enter_tag']] = (1, 'somestring')
# 打印分析的对
print(f"result for {metadata['pair']}")
# 打印最后 5 行
print(dataframe.tail())
return dataframe
如需打印更多行可用 print(dataframe)
,但不建议,否则每对每 5 秒会输出约 500 行。
开发策略时的常见错误¶
回测时"看未来"¶
回测为提升性能会一次性分析整个 dataframe。策略作者需确保不"看未来",即不使用 dry/live 时不可用的数据。
这是常见痛点,易导致回测与 dry/live 巨大差异。看未来的策略回测时表现极佳,实盘却很差。
常见错误包括:
- 不要用
shift(-1)
或负数,这会用到未来数据 - 不要在
populate_
函数中用.iloc[-1]
或其他绝对位置,dry-run 和回测时表现不同。回调函数中可安全用 iloc - 不要用
dataframe['mean_volume'] = dataframe['volume'].mean()
这类全列函数,回测时会用到未来数据。应用 rolling() 计算,如dataframe['volume'].rolling(<window>).mean()
- 不要用
.resample('1h')
,这会用左边界,应用.resample('1h', label='right')
- 不要用
.merge()
合并大周期到小周期,应用 informative pair 工具。普通 merge 会有前视偏差
信号冲突¶
当信号冲突(如 enter_long
和 exit_long
同时为 1)时,freqtrade 会忽略入场信号,避免刚进场就立刻出场。
规则如下,若 3 个信号中有多个为 1,则忽略入场:
enter_long
->exit_long
,enter_short
enter_short
->exit_short
,enter_long
更多策略思路¶
如需更多策略思路,请访问 策略仓库。可作为学习参考,实际效果取决于市场、交易对等。请务必先回测,再 dry-run,谨慎实盘。
欢迎参考、改编,也欢迎向仓库提交新策略 PR。
下一步,回测¶
现在你已经有了完美的策略,下一步请学习如何回测。