Skip to content

高级策略

本页面介绍了一些可以用于策略的高级概念。 如果你刚刚开始,请先了解一下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 不会保存您的策略的历史版本,所以用户需要能够最终回滚到先前版本的策略。

派生策略可以从其他策略派生出策略。这样可以避免重复编写自定义策略代码。您可以使用此技术来覆盖主策略的一小部分,同时保留其余部分不变:

user_data/strategies/myawesomestrategy.py
class MyAwesomeStrategy(IStrategy):
    ...
    stoploss = 0.13
    trailing_stop = False
    # 所有其他属性和方法都在这里,就像在任何自定义策略中一样...
    ...
user_data/strategies/MyAwesomeStrategy2.py
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()来对此进行反制 - 因此,性能影响应该很小甚至不存在。