高级策略¶
本页面介绍了一些可以用于策略的高级概念。 如果你刚刚开始,请先了解一下Freqtrade基本知识和策略定制化中描述的方法。
所述方法的调用顺序在机器人执行逻辑下进行了讲解。这些文档还有助于决定哪种方法最适合您的定制需求。
注意
只有在策略中使用回调方法时,才应实现回调方法。
小贴士
运行 freqtrade new-strategy --strategy MyAwesomeStrategy --template advanced
,可以使用包含所有可用回调方法的策略模板开始。
存储信息¶
可以通过在策略类中创建一个新的字典来存储信息。
变量的名称可以随意选择,但应以 custom_
作为前缀,以避免与预定义的策略变量发生命名冲突。
class AwesomeStrategy(IStrategy):
# 创建自定义字典
custom_info = {}
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# 检查条目是否已经存在
if not metadata["pair"] in self.custom_info:
# 为该交易对创建空白条目
self.custom_info[metadata["pair"]] = {}``` python
if "crosstime" in self.custom_info[metadata["pair"]]:
self.custom_info[metadata["pair"]]["crosstime"] += 1
else:
self.custom_info[metadata["pair"]]["crosstime"] = 1
警告
数据在机器人重新启动(或配置重新加载)后不会持久化。此外,应保持数据量较小(无DataFrames等),否则机器人将开始消耗大量内存,最终耗尽内存并崩溃。
注意
如果数据是特定于货币对的,请确保在字典中使用pair作为其中一个键。
数据帧访问¶
您可以通过从dataprovider查询来访问各种策略函数中的数据帧。
from freqtrade.exchange import timeframe_to_prev_date
class AwesomeStrategy(IStrategy):
def confirm_trade_exit(self, pair: str, trade: 'Trade', order_type: str, amount: float,
rate: float, time_in_force: str, exit_reason: str,
current_time: 'datetime', **kwargs) -> bool:
# 获取货币对的数据帧。
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
# 获取最后一个可用的蜡烛。不要使用current_time来查找最新的蜡烛,因为current_time指向当前不完整的蜡烛,其数据不可用。
last_candle = dataframe.iloc[-1].squeeze()
# <...>
# 在干燥/实时运行中,交易的开放日期将与蜡烛的开放日期不匹配,因此必须四舍五入。
trade_date = timeframe_to_prev_date(self.timeframe, trade.open_date_utc)
# 查找交易蜡烛。
trade_candle = dataframe.loc[dataframe['date'] == trade_date]
# 对于刚开启的交易,trade_candle可能为空,因为它仍然不完整。
if not trade_candle.empty:
trade_candle = trade_candle.squeeze()
# <...>
使用.iloc[-1]
这里可以使用.iloc[-1]
是因为get_analyzed_dataframe()
只返回回测允许看到的蜡烛。
在populate_*
方法中将无法正常工作,因此请确保在该区域不使用.iloc[]
。
另外,这仅适用于2021.5版本及更高版本。***
输入标签¶
当您的策略具有多个买入信号时,您可以给触发信号命名。
然后您可以在 custom_exit
中访问您的买入信号
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[
(
(dataframe['rsi'] < 35) &
(dataframe['volume'] > 0)
),
['enter_long', 'enter_tag']] = (1, 'buy_signal_rsi')
return dataframe
def custom_exit(self, pair: str, trade: Trade, current_time: datetime, current_rate: float,
current_profit: float, **kwargs):
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
last_candle = dataframe.iloc[-1].squeeze()
if trade.enter_tag == 'buy_signal_rsi' and last_candle['rsi'] > 80:
return 'sell_signal_rsi'
return None
注意
enter_tag
限制为100个字符,剩余数据将被截断。
警告
只有一个 enter_tag
列,用于长和短交易。
因此,此列必须被视为“最后写入获胜”(毕竟,它只是一个数据帧列)。
在复杂情况下,当多个信号冲突时(或者如果信号基于不同条件再次停用),这可能导致错误的标签应用于入场信号。
这些结果是策略覆盖先前标签的结果 - 最后一个标签将“粘贴”下来,成为freqtrade将使用的那一个。
出场标签与购买标签类似,您还可以指定一个卖出标签。¶
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[
(
(dataframe['rsi'] > 70) &
(dataframe['volume'] > 0)
),
['exit_long', 'exit_tag']] = (1, 'exit_rsi')
return dataframe
然后,提供的退出标签将用作卖出原因,并在回测结果中显示为此。
注意
exit_reason
限制为100个字符,多余的数据将被截断。
策略版本¶
您可以通过使用 "version" 方法来实现自定义策略版本,并返回您希望该策略具有的版本。
def version(self) -> str:
"""
返回策略的版本。
"""
return "1.1"
注意
您应确保在此之外实现适当的版本控制(如 Git 存储库),因为 freqtrade 不会保存您的策略的历史版本,所以用户需要能够最终回滚到先前版本的策略。
派生策略可以从其他策略派生出策略。这样可以避免重复编写自定义策略代码。您可以使用此技术来覆盖主策略的一小部分,同时保留其余部分不变:¶
class MyAwesomeStrategy(IStrategy):
...
stoploss = 0.13
trailing_stop = False
# 所有其他属性和方法都在这里,就像在任何自定义策略中一样...
...
from myawesomestrategy import MyAwesomeStrategy
class MyAwesomeStrategy2(MyAwesomeStrategy):
# 覆盖一些内容
stoploss = 0.08
trailing_stop = True
可以覆盖属性和方法,以便按您的需求改变原策略的行为。
虽然在同一个文件中保留子类是技术上可行的,但这可能导致一些与超参文件相关的问题,因此我们建议使用单独的策略文件,并像上面所示那样导入父策略。
嵌入策略¶
Freqtrade提供了一种将策略嵌入到配置文件中的简单方法。这是通过使用BASE64编码并在所选配置文件的策略配置字段中提供该字符串来实现的。
将字符串编码为BASE64¶
这是一个快速示例,演示如何在Python中生成BASE64字符串。
from base64 import urlsafe_b64encode
with open(file, 'r') as f:
content = f.read()
content = urlsafe_b64encode(content.encode('utf-8'))
变量 'content' 将包含以 BASE64 编码形式的策略文件。现在可以将其设置为配置文件中的如下内容
"strategy": "策略名称:BASE64字符串"
请确保 '策略名称' 与策略名称完全一致!
性能警告¶
执行策略时,有时可能在日志中看到以下内容
PerformanceWarning: DataFrame is highly fragmented.
这是 pandas
中的一个警告,并且警告还继续说:
使用 pd.concat(axis=1)
。
这可能会导致轻微的性能影响,通常只在超参数优化(优化指标时)时才能看到。
例如:
frames = [dataframe]
for val in self.buy_ema_short.range:
frames.append(DataFrame({
f'ema_short_{val}': ta.EMA(dataframe, timeperiod=val)
}))
# 合并所有数据框,并重新分配原始数据框的列
dataframe = pd.concat(frames, axis=1)
然而,Freqtrade通过在populate_indicators()
方法之后对数据框运行dataframe.copy()
来对此进行反制 - 因此,性能影响应该很小甚至不存在。