Skip to content

策略回调函数

主要的策略函数 (populate_indicators(), populate_entry_trend(), populate_exit_trend()) 应该以向量化的方式使用,并且仅在回测期间一次调用,回调函数在需要时被调用。

因此,应避免在回调函数中进行复杂的计算,以免在操作期间出现延迟。 根据所使用的回调函数,它们可能在进入/退出交易时调用,或在交易持续期间调用。

当前可用的回调函数:

回调函数调用顺序

您可以在bot-basics中找到回调函数的调用顺序。

Bot start

这是一个简单的回调函数,它在加载策略时只调用一次。 这可以用于执行仅需执行一次的操作,并在设置 dataprovider 和 wallet 后运行。

import requests

class AwesomeStrategy(IStrategy):

    # ... populate_*方法

    def bot_start(self, **kwargs) -> None:
        """
        在机器人实例化后仅调用一次。
        :param **kwargs: 请确保保留此参数,以免破坏您的策略更新。
        """
        if self.config['runmode'].value in ('live', 'dry_run'):
            # 通过使用self.*将其分配给类
            # 然后可以由populate_*方法使用
            self.custom_remote_data = requests.get('https://some_remote_source.example.com')

在hyperopt期间,这将只在启动时运行一次。

机器人循环开始

在每个机器人限制迭代的开始调用的简单回调函数(在dry/live模式下大约每5秒钟调用一次,除非另有配置),或在回测/hyperopt模式下每个蜡烛一次。 这可用于执行与交易对无关的计算(适用于所有交易对),加载外部数据等。

import requests

class AwesomeStrategy(IStrategy):

    # ... populate_*方法

    def bot_loop_start(self, current_time: datetime, **kwargs) -> None:
        """
        在机器人迭代(一个循环)开始时调用。
        可以用于执行与交易对无关的任务
        (例如,收集一些用于比较的远程资源)
        :param current_time: 包含当前日期时间的datetime对象
        :param **kwargs: 请确保保留此参数,以免破坏您的策略更新。
        """
        if self.config['runmode'].value in ('live', 'dry_run'):
            # 通过使用self.*将其分配给类
            # 然后可以由populate_*方法使用
            self.remote_data = requests.get('https://some_remote_source.example.com')

投注大小管理

在进入交易之前调用,可以在下单时管理您的仓位大小。

class AwesomeStrategy(IStrategy):
    def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float,
                            proposed_stake: float, min_stake: Optional[float], max_stake: float,
                            leverage: float, entry_tag: Optional[str], side: str,
                            **kwargs) -> float:

        dataframe, _ = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe)
        current_candle = dataframe.iloc[-1].squeeze()

        if current_candle['fastk_rsi_1h'] > current_candle['fastd_rsi_1h']:
            if self.config['stake_amount'] == 'unlimited':
                # 在复利模式下有利条件时使用整个可用钱包。
                return max_stake
            else:
                # 在有利条件下复合利润,而不是使用静态投注额。
                return self.wallets.get_total_stake_amount() / self.config['max_open_trades']

        # 使用默认的投注额。
        return proposed_stake

如果您的代码引发异常,Freqtrade将回退到proposed_stake的值。异常本身将被记录。

Tip

您不必确保 min_stake <= returned_value <= max_stake。交易将成功进行,因为返回值将被夹在支持的范围内,并且此操作将被记录。

Tip

返回 0None 将阻止进行交易。

自定义退出信号

在每次节流迭代(大约每5秒)时,对开放交易调用,直到交易关闭。

允许定义自定义退出信号,表示应该卖出指定的头寸。当我们需要为每个单独的交易自定义退出条件,或者如果您需要交易数据来做出退出决策时,这非常有用。

例如,您可以使用custom_exit()来实现1:2的风险回报ROI。

不建议使用custom_exit()信号来替代止损。在这方面,使用custom_stoploss()是一种更好的方法,它还允许您在交易所上保留止损。

注意

从这个方法返回一个(非空)stringTrue等效于在指定时间的蜡烛上设置退出信号。当已经设置退出信号时,或者退出信号被禁用(use_exit_signal=False),不会调用此方法。string的最大长度为64个字符。超过这个限制将导致消息被截断为64个字符。 custom_exit()将忽略exit_profit_only,并且将始终被调用,除非use_exit_signal=False,即使有新的进入信号。

下面是一个示例,演示如何根据当前利润使用不同的指标,并退出持续一天以上的交易:

class AwesomeStrategy(IStrategy):
    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()

        # 超过20%利润,当RSI < 80时卖出
        if current_profit > 0.2:
            if last_candle['rsi'] < 80:
                return 'rsi_below_80'

        # 在2%和10%之间,如果长期EMA高于短期EMA,则卖出
        if 0.02 < current_profit < 0.1:
            if last_candle['emalong'] > last_candle['emashort']:
                return 'ema_long_below_80'

        # 如果持有的头寸超过一天,就以损失出售
        if current_profit < 0.0 and (current_time - trade.open_date_utc).days >= 1:
            return 'unclog'

有关策略回调中数据帧使用的更多信息,请参阅 数据帧访问

自定义止损

在每次迭代(大约每5秒)期间,为打开的交易调用。

必须通过在策略对象上设置 use_custom_stoploss=True 来启用自定义止损方法的使用。

止损价格只能向上移动 - 如果从 custom_stoploss 返回的止损值会导致较低的止损价格比之前设定的低,它将被忽略。传统的 stoploss 值作为绝对较低级别,并将作为初始止损(在首次为交易调用此方法之前),仍然是强制性的。

该方法必须返回一个止损值(float / number),作为当前价格的百分比。例如,如果 current_rate 是200美元,则返回 0.02 将将止损价设置为低于2%,即196美元。 在回测期间,根据蜡烛的高点(或空头交易的低点)提供 current_rate(和 current_profit)- 而获得的止损会与蜡烛的低点(或空头交易的高点)进行评估。

使用返回值的绝对值(忽略符号),所以返回 0.05-0.05 具有相同的结果,即当前价格下跌5%的止损。 返回 None 将被解释为“无需更改”,这是当你不希望修改止损时唯一安全的方式。

交易所上的止损工作方式类似于 跟踪止损,并且交易所上的止损会根据 stoploss_on_exchange_interval 中的配置进行更新(有关交易所上的止损的更多详细信息,请参阅 有关交易所上的止损的更多详细信息)。

日期的使用

所有基于时间的计算都应基于 current_time 进行 - 不建议使用 datetime.now()datetime.utcnow(),因为这将破坏回测支持。!!! Tip "跟踪止损" 当使用自定义止损值时,建议禁用 trailing_stop。两者可以一起工作,但是您可能会遇到跟踪止损会将价格推高,而您的自定义函数可能不希望如此,从而导致冲突行为。

调整持仓调整后的止损

根据您的策略,您可能需要在 持仓调整 后在两个方向上调整止损。 为此,freqtrade 将在订单成交后使用 after_fill=True 进行额外的调用,这将允许策略在任何方向上移动止损(还可以扩大止损和当前价格之间的差距,否则是禁止的)。

向后兼容性

仅当 after_fill 参数是您的 custom_stoploss 函数的函数定义的一部分时,才会进行此调用。 因此,这不会影响(也不会让人惊讶)现有并运行的策略。

自定义止损示例

下一节将展示使用自定义止损函数可能的一些示例。 当然,还有许多其他可能性,并且可以任意组合所有示例。

通过自定义止损实现跟踪止损

要模拟常规的跟踪止损(距离最高价格的4%),您可以使用以下非常简单的方法:

# 需要额外导入
from datetime import datetime
from freqtrade.persistence import Trade

class AwesomeStrategy(IStrategy):

    # ... 填充_* 方法

    use_custom_stoploss = True

    def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
                    current_rate: float, current_profit: float, after_fill: bool,
                    **kwargs) -> Optional[float]:
        """
        自定义止损逻辑,返回相对于 current_rate 的新距离(作为比率)。
        例如,返回 -0.05 会在 current_rate 的下方5% 处设定一个止损。
        自定义止损不能低于 self.stoploss,这作为最大亏损。

        完整文档请参阅 https://www.freqtrade.io/zh/latest/strategy-advanced/

        如果策略未实现该方法,则返回初始止损值。
        仅当 use_custom_stoploss 设置为 True 时才会调用。

        :param pair: 当前分析的交易对
        :param trade: 交易对象。
        :param current_time: datetime 对象,包含当前日期时间
        :param current_rate: 根据退出定价设置计算得到的汇率。
        :param current_profit: 当前利润(作为比率),根据 current_rate 计算得到。
        :param after_fill: 如果止损在订单被填充后调用,则为 True。
        :param **kwargs: 请确保保留此参数,以防止该方法的更新破坏您的策略。
        :return float: 相对于 current_rate 的新止损值
        """
        return -0.04

基于时间的追踪止损

在前60分钟使用初始止损,之后改为10%的追踪止损,然后在2小时(120分钟)之后使用5%的追踪止损。

from datetime import datetime, timedelta
from freqtrade.persistence import Trade

class AwesomeStrategy(IStrategy):

    # ... populate_* methods

    use_custom_stoploss = True

    def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
                        current_rate: float, current_profit: float, after_fill: bool, 
                        **kwargs) -> Optional[float]:

        # 确保将最长的时间间隔放在最前面 - 这些条件从上到下逐一评估。
        if current_time - timedelta(minutes=120) > trade.open_date_utc:
            return -0.05
        elif current_time - timedelta(minutes=60) > trade.open_date_utc:
            return -0.10
        return None

基于时间的跟踪停损,在成交后进行调整

在最开始的 60 分钟内使用初始停损,在此之后更改为 10% 的跟踪停损,在 2 小时后(120 分钟)我们使用 5% 的跟踪停损。 如果另外一个订单被成交,将停损设置为新的 open_rate 下方的 -10%(基于所有成交平均计算)。

from datetime import datetime, timedelta
from freqtrade.persistence import Trade

class AwesomeStrategy(IStrategy):

    # ... populate_* methods

    use_custom_stoploss = True

    def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
                        current_rate: float, current_profit: float, after_fill: bool,
                        **kwargs) -> Optional[float]:

        if after_fill:
            # 在额外的订单之后,从新的开盘率开始设置一个跟踪止损,比新开盘率低10%
            return stoploss_from_open(0.10, current_profit, is_short=trade.is_short, leverage=trade.leverage)
        # 确保首先使用最长的间隔-这些条件从上到下进行评估。
        if current_time - timedelta(minutes=120) > trade.open_date_utc:
            return -0.05
        elif current_time - timedelta(minutes=60) > trade.open_date_utc:
            return -0.10
        return None

不同的交易对使用不同的止损

根据交易对使用不同的止损。 在这个例子中,对于 ETH/BTCXRP/BTC,我们将使用10%的跟踪止损追踪最高价格,对于 LTC/BTC,我们将使用5%的跟踪止损,对于其他所有交易对,我们将使用15%的跟踪止损。

from datetime import datetime
from freqtrade.persistence import Trade

class AwesomeStrategy(IStrategy):

    # ... populate_* methods

    use_custom_stoploss = True

    def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
                        current_rate: float, current_profit: float, after_fill: bool,
                        **kwargs) -> Optional[float]:

        if pair in ('ETH/BTC', 'XRP/BTC'):
            return -0.10
        elif pair in ('LTC/BTC'):
            return -0.05
        return -0.15

带有正增益的移动止损

在利润高于4%之前使用初始止损,然后使用当前利润的50%作为移动止损,最低为2.5%,最高为5%。

请注意,止损只能增加,低于当前止损的值将被忽略。

from datetime import datetime, timedelta
from freqtrade.persistence import Trade

class AwesomeStrategy(IStrategy):

    # ... populate_* methods

    use_custom_stoploss = True

    def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
                        current_rate: float, current_profit: float, after_fill: bool,
                        **kwargs) -> Optional[float]:

        if current_profit < 0.04:
            return -1  # 返回一个比初始止损更大的值,以保持使用初始止损

        # 达到所需的增益后,将止损设置为利润的一半
        desired_stoploss = current_profit / 2

分阶段止损

这个例子不是持续追踪当前价格,而是根据当前的盈利设置固定的止损价格水平。

  • 在达到20%盈利之前使用常规止损。
  • 一旦盈利超过20% - 将止损设置为开仓价的7%以上。
  • 一旦盈利超过25% - 将止损设置为开仓价的15%以上。
  • 一旦盈利超过40% - 将止损设置为开仓价的25%以上。
from datetime import datetime
from freqtrade.persistence import Trade
from freqtrade.strategy import stoploss_from_open

class AwesomeStrategy(IStrategy):

    # ... populate_* methods

    use_custom_stoploss = True

    def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
                        current_rate: float, current_profit: float, after_fill: bool,
                        **kwargs) -> Optional[float]:

        # 从高到低评估,以使用可能的最高止损
        if current_profit > 0.40:
            return stoploss_from_open(0.25, current_profit, is_short=trade.is_short, leverage=trade.leverage)
        elif current_profit > 0.25:
            return stoploss_from_open(0.15, current_profit, is_short=trade.is_short, leverage=trade.leverage)
        elif current_profit > 0.20:
            return stoploss_from_open(0.07, current_profit, is_short=trade.is_short, leverage=trade.leverage)

        ## 返回最大的止损值,保持当前止损价不变
        return None

使用数据框中的指标自定义止损例子

绝对止损价可以从存储在数据框中的指标推导得出。下面的示例使用抛物线SAR作为止损。

class AwesomeStrategy(IStrategy):

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        # <...>
        dataframe['sar'] = ta.SAR(dataframe)

    use_custom_stoploss = True

    def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
                        current_rate: float, current_profit: float, after_fill: bool,
                        **kwargs) -> Optional[float]:

        dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
        last_candle = dataframe.iloc[-1].squeeze()

        # 使用抛物线SAR作为绝对止损价
        stoploss_price = last_candle['sar']

        # 将绝对价格转换为相对于当前汇率的百分比
        if stoploss_price < current_rate:
            return stoploss_from_absolute(stoploss_price, current_rate, is_short=trade.is_short)
        # 返回最大的止损值,保持当前止损价不变
        return None

查看有关策略回调函数中数据帧使用的更多信息,请参阅数据帧访问


停止损益计算的常用助手函数

相对于开盘价的止损

custom_stoploss() 返回的止损值必须指定相对于 current_rate 的百分比,但有时您可能希望指定相对于 入场 价格的止损。 stoploss_from_open() 是一个辅助函数,用于计算一个可以从 custom_stoploss 返回的止损值,该值与所需交易利润等同于入场点上方的值。

从自定义止损函数返回相对于开盘价的止损的示例

假设开盘价为 100 美元,current_price 是 121 美元(current_profit 将是 0.21)。

如果我们希望止损价格在开盘价上方 7%,我们可以调用 stoploss_from_open(0.07, current_profit, False),它将返回 0.1157024793。$121 下方 11.57% 是 $107,这与 $100 上方 7% 是相同的。

此函数会考虑杠杆 - 因此,在 10 倍杠杆下,实际的止损将比 $100 高出 0.7%(0.7% * 10 倍 = 7%)。

from datetime import datetime
from freqtrade.persistence import Trade
from freqtrade.strategy import IStrategy, stoploss_from_open

class AwesomeStrategy(IStrategy):

    # ... populate_* methods

    use_custom_stoploss = True

    def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
                        current_rate: float, current_profit: float, after_fill: bool,
                        **kwargs) -> Optional[float]:

        # 一旦利润上涨超过10%,将止损设置为离开价格的7%以上
        if current_profit > 0.10:
            return stoploss_from_open(0.07, current_profit, is_short=trade.is_short, leverage=trade.leverage)

        return 1

完整示例可在自定义止损部分的文档中找到。

注意

stoploss_from_open()提供无效的输入可能会产生“CustomStoploss function did not return valid stoploss”的警告。 如果current_profit参数低于指定的open_relative_stop,可能会出现这种情况。当关闭交易由confirm_trade_exit()方法阻止时,可能会出现这种情况。 可以通过在confirm_trade_exit()中检查exit_reason以解决警告,从而永远不会阻止止损售出,或者使用return stoploss_from_open(...) or 1习语, 这将请求在current_profit < open_relative_stop时不更改止损。

从绝对价格计算止损比例

custom_stoploss() 返回的止损值始终是相对于 current_rate 的百分比。为了设置一个指定的绝对价格级别的止损,我们需要使用 stop_rate 来计算相对于开盘价格指定的百分比会得到与绝对价格相同的结果。

辅助函数 stoploss_from_absolute() 可以用于将绝对价格转换为相对于当前价格的止损,然后可以从 custom_stoploss() 中返回。

从自定义止损函数中返回使用绝对价格的止损示例

如果我们想要在当前价格下方的2倍ATR处设置一个移动止损,我们可以调用 stoploss_from_absolute(current_rate + (side * candle['atr'] * 2), current_rate, is_short=trade.is_short, leverage=trade.leverage)。 对于期货来说,我们需要调整方向(涨或跌),以及根据杠杆进行调整,因为 custom_stoploss 回调函数返回的是该交易的"风险",而不是相对价格的变动。

from datetime import datetime
from freqtrade.persistence import Trade
from freqtrade.strategy import IStrategy, stoploss_from_absolute, timeframe_to_prev_date

class AwesomeStrategy(IStrategy):

    use_custom_stoploss = True

    def populate_indicators_1h(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe['atr'] = ta.ATR(dataframe, timeperiod=14)
        return dataframedef custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
                        current_rate: float, current_profit: float, after_fill: bool,
                        **kwargs) -> Optional[float]:
        dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
        trade_date = timeframe_to_prev_date(self.timeframe, trade.open_date_utc)
        candle = dataframe.iloc[-1].squeeze()
        side = 1 if trade.is_short else -1
        return stoploss_from_absolute(current_rate + (side * candle['atr'] * 2), 
                                          current_rate, is_short=trade.is_short,
                                          leverage=trade.leverage)

自定义订单价格规则

默认情况下,freqtrade使用订单簿来自动设置订单价格(有关文档,请参阅配置.md#用于订单的价格),您还可以根据您的策略创建自定义订单价格的选项。

您可以通过在策略文件中创建custom_entry_price()函数来使用此功能来自定义进入价格,并使用custom_exit_price()来自定义退出价格。

这些方法在将订单放在交易所之前调用。

提示: 如果您的自定义定价函数返回None或无效值,则价格将回退到基于常规定价配置的proposed_rate

提示: 使用custom_entry_price时,一旦与交易关联的第一个进入订单创建,Trade对象将可用,对于第一个进入,trade参数的值将为None

自定义订单进出价格示例

from datetime import datetime, timedelta, timezone
from freqtrade.persistence import Trade

class AwesomeStrategy(IStrategy):

    # ... populate_* methods

    def custom_entry_price(self, pair: str, trade: Optional['Trade'], current_time: datetime, proposed_rate: float,
                           entry_tag: Optional[str], side: str, **kwargs) -> float:

        dataframe, last_updated = self.dp.get_analyzed_dataframe(pair=pair,
                                                                timeframe=self.timeframe)
        new_entryprice = dataframe['bollinger_10_lowerband'].iat[-1]

        return new_entryprice

    def custom_exit_price(self, pair: str, trade: Trade,
                          current_time: datetime, proposed_rate: float,
                          current_profit: float, exit_tag: Optional[str], **kwargs) -> float:

        dataframe, last_updated = self.dp.get_analyzed_dataframe(pair=pair,
                                                                timeframe=self.timeframe)
        new_exitprice = dataframe['bollinger_10_upperband'].iat[-1]

        return new_exitprice

警告

修改进入和退出价格仅适用于限价订单。根据选择的价格,这可能导致大量未成交的订单。默认情况下,当前价格和自定义价格之间允许的最大距离为2%,此值可以在配置中使用 custom_price_max_distance_ratio 参数进行更改。 示例: 如果新的进入价格为97,建议的价格为100,并且将 custom_price_max_distance_ratio 设置为2%,则保留的有效自定义进入价格将是98,即当前(建议的)价格下方2%的价格。

回测

自定义价格在回测中受到支持(从2021.12版开始),如果价格在K线的最低价和最高价范围内,订单将成交。 未立即成交的订单将受到常规超时处理的影响,每个(详细)蜡烛会发生一次超时处理。 custom_exit_price() 仅会在退出类型为 exit_signal、Custom exit 和 partial exits 的卖出时调用。所有其他退出类型将使用常规的回测价格。

自定义订单超时规则

可以通过策略或配置文件中的 unfilledtimeout 部分来配置简单的基于时间的订单超时。

然而,freqtrade 还提供了自定义回调函数来判断订单是否超时,您可以根据自定义标准来判断订单是否超时。

注意

回测会在价格处于K线的最低价和最高价范围内时成交订单。 对于未立即成交的订单(使用自定义定价),下面的回调函数将在每个(详细)蜡烛中调用。

自定义订单超时示例

对每个未成交的订单进行处理,直到该订单被成交或取消为止。 check_entry_timeout() 用于交易进入订单,而 check_exit_timeout() 用于交易退出订单。

下面是一个简单的示例,根据资产的价格应用不同的未成交超时时间。对于价格较高的资产,应用较短的超时时间;而对于价格较低的币种,允许更长的时间来成交。

该函数必须返回 True(取消订单)或 False(保持订单有效)。

from datetime import datetime, timedelta
from freqtrade.persistence import Trade, Order

class AwesomeStrategy(IStrategy):

    # ... populate_* methods

    # 将未成交超时设置为25小时,因为下面的最大超时为24小时。
    unfilledtimeout = {
        'entry': 60 * 25,
        'exit': 60 * 25
    }

    def check_entry_timeout(self, pair: str, trade: 'Trade', order: 'Order',
                            current_time: datetime, **kwargs) -> bool:
        if trade.open_rate > 100 and trade.open_date_utc < current_time - timedelta(minutes=5):
            return True
        elif trade.open_rate > 10 and trade.open_date_utc < current_time - timedelta(minutes=3):
            return True
        elif trade.open_rate < 1 and trade.open_date_utc < current_time - timedelta(hours=24):
           return True
        return False


    def check_exit_timeout(self, pair: str, trade: Trade, order: 'Order',
                           current_time: datetime, **kwargs) -> bool:
        if trade.open_rate > 100 and trade.open_date_utc < current_time - timedelta(minutes=5):
            return True
        elif trade.open_rate > 10 and trade.open_date_utc < current_time - timedelta(minutes=3):
            return True
        elif trade.open_rate < 1 and trade.open_date_utc < current_time - timedelta(hours=24):
           return True
        return False

注意

对于上面的示例,unfilledtimeout 必须设置为大于24小时的某个值,否则该类型的超时将首先应用。

自定义订单超时示例(使用附加数据)

from datetime import datetime
from freqtrade.persistence import Trade, Order

class AwesomeStrategy(IStrategy):

    # ... populate_* methods

    # Set unfilledtimeout to 25 hours, since the maximum timeout from below is 24 hours.
    unfilledtimeout = {
        'entry': 60 * 25,
        'exit': 60 * 25
    }

    def check_entry_timeout(self, pair: str, trade: 'Trade', order: 'Order',
                            current_time: datetime, **kwargs) -> bool:
        ob = self.dp.orderbook(pair, 1)
        current_price = ob['bids'][0][0]
        # 如果当前价格比订单价格的 1.02 倍还要高 2%,则取消购买订单。
        if current_price > order.price * 1.02:
            return True
        return False


    def check_exit_timeout(self, pair: str, trade: 'Trade', order: 'Order',
                           current_time: datetime, **kwargs) -> bool:
        ob = self.dp.orderbook(pair, 1)
        current_price = ob['asks'][0][0]
        # 如果当前价格比订单价格的 0.98 倍还要低 2%,则取消出售订单。
        if current_price < order.price * 0.98:
            return True
        return False

交易机器人订单确认

确认交易的进场和退场。 这是在下订单之前会被调用的最后两个方法。

交易进场(买单)确认

confirm_trade_entry() 可以用于在最后一秒中止交易进场(也许是因为价格不符合预期)。

class AwesomeStrategy(IStrategy):

    # ... populate_*方法

    def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float,
                            time_in_force: str, current_time: datetime, entry_tag: Optional[str],
                            side: str, **kwargs) -> bool:
        """
        在下单前调用。
        此函数的时间非常关键,要避免在这个方法中进行繁重的计算或网络请求。

        欲了解完整文档,请访问 https://www.freqtrade.io/zh/latest/strategy-advanced/

        如果策略未实现此方法,则始终返回True(始终确认)。

        :param pair: 即将购买/做空的交易对。
        :param order_type: 订单类型(如在order_types中配置的)。通常为limit或market。
        :param amount: 将要交易的目标(基准)货币数量。
        :param rate: 在使用限价订单时将要使用的价格,或在市价订单中使用的当前价格。
        :param time_in_force: 有效时间。默认为GTC(Good-til-cancelled,长期有效)。
        :param current_time: 包含当前日期和时间的datetime对象。
        :param entry_tag: 可选的入场标签(buy_tag),如果与买入信号一起提供。
        :param side: 'long'或'short' - 指示拟议交易的方向
        :param **kwargs: 请确保将其保留在此处,以防此处的更新会破坏您的策略。
        :return bool: 当返回True时,交易买单将在交易所上进行。
            返回False将中止进程
        """
        return True

交易退出(卖单)确认

confirm_trade_exit()可用于在最后一刻中止交易退出(卖出)(例如因为价格不符预期)。

如果有不同的退出原因适用,confirm_trade_exit()可能在同一个交易的同一次迭代中被调用多次。 退出原因(如果适用)将按以下顺序列出:

  • exit_signal / custom_exit
  • stop_loss
  • roi
  • trailing_stop_loss
from freqtrade.persistence import Trade


class AwesomeStrategy(IStrategy):

    # ... populate_* methods

    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:
        """
        在放置常规退出订单之前调用的函数。
        该函数的时间安排非常关键,因此请避免在此方法中进行繁重的计算或网络请求。

        有关完整的文档,请访问 https://www.freqtrade.io/en/latest/strategy-advanced/

        如果策略未实现此函数,则始终返回 True(始终确认)。

        :param pair: 即将退出的交易对。
        :param trade: 交易对象。
        :param order_type: 订单类型(根据配置在 order_types 中设置),通常为限价或市价。
        :param amount: 基础币种的数量。
        :param rate: 当使用限价订单时将使用的价格,
                     或当使用市价订单时将使用的当前价格。
        :param time_in_force: 有效时间,默认为 GTC(Good-til-cancelled)。
        :param exit_reason: 退出原因。
            可以是以下任何选项:['roi', 'stop_loss', 'stoploss_on_exchange', 'trailing_stop_loss',
                           'exit_signal', 'force_exit', 'emergency_exit']
        :param current_time: 包含当前日期时间的 datetime 对象。
        :param **kwargs: 确保保留此参数,以免更新此参数会破坏您的策略。
        :return bool: 当为 True 时,则在交易所上下订单。
            当为 False 时,则中止流程。
        """
        if exit_reason == 'force_exit' and trade.calc_profit_ratio(rate) < 0:
            # 拒绝具有负利润的强制卖出
            # 这只是个示例,请根据您的需求进行调整
            # (这并不一定合乎逻辑,假设您知道何时进行强制卖出)
            return False
        return True

警告

confirm_trade_exit() 可能会阻止止损退出,导致重大损失,因为这将忽略止损退出。 confirm_trade_exit() 对于清算不会被调用 - 清算是由交易所强制执行的,因此无法被拒绝。

调整交易位置

position_adjustment_enable 策略属性允许在策略中使用 adjust_trade_position() 回调函数。 出于性能原因,默认情况下它是禁用的,如果启用了,freqtrade 在启动时会显示一个警告消息。 adjust_trade_position() 可以用于执行额外的订单,例如使用 DCA(逐渐买入)来管理风险,或增加或减少持仓。

额外订单也会导致额外费用,这些订单不计入 max_open_trades

当有待执行的订单(买入或卖出)时,不会调用此回调函数。

adjust_trade_position() 在整个交易过程中会被频繁调用,因此必须尽可能保持高性能的实现。

交易方向的调整将始终应用于交易方向中,因此正值将始终增加您的持仓(负值将减少您的持仓),无论是长周期交易还是短周期交易。

无法修改杠杆,并且返回的抵押金额假定为在应用杠杆之前。

增加持仓

如果策略希望进行额外的入场订单(增加持仓 -> 长周期交易的买单,短周期交易的卖单),则预期策略返回一个介于 min_stakemax_stake 之间的正 stake_amount(以抵押货币表示)。

如果钱包中没有足够的资金(返回值大于 max_stake),则信号将被忽略。 max_entry_position_adjustment 属性用于限制 bot 可以执行的每笔交易的额外入场订单数量(除了第一笔入场订单)。默认情况下,值为 -1,这意味着 bot 对调整入场订单数量没有限制。

一旦达到了您在 max_entry_position_adjustment 上设置的额外入场订单的最大数量,将忽略其他额外入场订单,但回调函数仍然会被调用,以寻找部分退出机会。### 减少仓位

该策略预计在部分退出时返回一个负的投注金额(以投注货币计量)。根据当前价格,返回该点的全部所持投注(-(trade.amount / trade.leverage) * current_exit_rate)将导致完全退出。 返回一个大于上述值的金额(以便使剩余投注金额变为负数)将导致机器人忽略信号。

关于投注金额

使用固定的投注金额意味着该金额将用于第一笔订单,就像没有进行仓位调整一样。 如果您希望使用DCA购买额外的订单,请确保在钱包中留有足够的资金。 使用“无限”投注金额和DCA订单需要您还要实现custom_stake_amount()回调函数,以避免将所有资金分配给初始订单。

警告

止损仍然是根据初始开盘价而不是平均价计算的。 常规的止损规则仍然适用(不能下降)。

虽然/stopentry命令会阻止机器人进入新的交易,但仓位调整功能将继续在现有交易上购买新的订单。

回测

在回测期间,该回调函数针对timeframetimeframe_detail中的每个K线调用,因此运行时性能会受到影响。 这也可能导致实时交易和回测之间出现不同的结果,因为回测每蜡烛只会调整交易一次,而实时交易每蜡烛可以多次调整交易。

from freqtrade.persistence import Trade


class DigDeeperStrategy(IStrategy):

    position_adjustment_enable = True

    # 尝试使用DCA处理大幅下跌。需要设置较高的止损。
    stoploss = -0.30# ... populate_* 方法

    # 例子特定变量
    max_entry_position_adjustment = 3
    # 该数字在下面有进一步说明
    max_dca_multiplier = 5.5

    # 在下单时(开放式交易)调用此方法
    def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float,
                            proposed_stake: float, min_stake: Optional[float], max_stake: float,
                            leverage: float, entry_tag: Optional[str], side: str,
                            **kwargs) -> float:

        # 我们需要留下大部分资金用于可能的进一步 DCA 订单
        # 这同样适用于固定投注
        return proposed_stake / self.max_dca_multiplier

    def adjust_trade_position(self, trade: Trade, current_time: datetime,
                              current_rate: float, current_profit: float,
                              min_stake: Optional[float], max_stake: float,
                              current_entry_rate: float, current_exit_rate: float,
                              current_entry_profit: float, current_exit_profit: float,
                              **kwargs) -> Optional[float]:
        """
        自定义交易调整逻辑,返回应增加或减少的下注金额。
        这意味着额外的入场或退出订单,带有额外的费用。
        仅当`position_adjustment_enable`设置为True时才会调用。

        有关完整文档,请访问 https://www.freqtrade.io/zh/latest/strategy-advanced/

        当策略未实现时,返回 None

        :param trade: 交易对象。
        :param current_time: 包含当前日期和时间的日期时间对象
        :param current_rate: 当前入场报价(与 current_entry_profit 相同)
        :param current_profit: 当前利润(作为比例),基于 current_rate 计算(与 current_entry_profit 相同)。
        :param min_stake: 交易所允许的最小投注额(适用于入场和退出)
        :param max_stake: 允许的最大投注额(通过余额或交易所限制)
        :param current_entry_rate: 使用入场定价的当前汇率。
        :param current_exit_rate: 使用退出定价的当前汇率。
        :param current_entry_profit: 使用入场定价的当前利润。
        :param current_exit_profit: 使用退出定价的当前利润。
        :param **kwargs: 请确保将其保留在此处,以免破坏您的策略。
        :return float: 调整交易的下注金额,
                       正值为增加仓位,负值为减少仓位。
                       返回 None 表示不采取任何操作。
        """

        if current_profit > 0.05 and trade.nr_of_successful_exits == 0:
            # 当利润达到 +5% 时,取利润的一半
            return -(trade.stake_amount / 2)

        if current_profit > -0.05:
            return None

        # 获取双向数据框(仅用于展示访问方法)
        dataframe _ = self.dp.get_analyzed_dataframe(trade.pair, self.timeframe)
        # 当价格不活跃下跌时才购买。
        last_candle = dataframe.iloc[-1].squeeze()
        previous_candle = dataframe.iloc[-2].squeeze()
        if last_candle['close'] < previous_candle['close']:
            return None

        filled_entries = trade.select_filled_orders(trade.entry_side)
        count_of_entries = trade.nr_of_successful_entries
        # 允许最多3次逐渐增加的买单(总共4次)
        # 初始购买是1倍
        # 如果利润下降到-5%,我们会购买更多的1.25倍,平均利润应该增加到大约-2.2%
        # 如果再次下降到-5%,我们会再买1.5倍
        # 如果再次下降到-5%,我们会再买1.75倍
        # 此交易的总投注额将是初始允许的投注额的5.5倍。
        # 这就是为什么max_dca_multiplier是5.5
        # 希望你有一个深厚的钱包!
        try:
            # 这返回第一个订单的投注金额
            stake_amount = filled_entries[0].stake_amount
            # 这将计算当前安全订单的大小
            stake_amount = stake_amount * (1 + (count_of_entries * 0.25))
            return stake_amount
        except Exception as exception:
            return None

        return None

位置调整计算

  • 入场价格使用加权平均值计算。
  • 退出不会影响平均入场价格。
  • 部分退出相对利润相对于此时的平均入场价格计算。
  • 最终退出相对利润基于总投资资本计算(见下面的示例)
计算示例

出于简单起见,此示例假设没有费用,并且在虚构硬币上有一个多头仓位。

  • 以8$购买100股
  • 以9\(购买100股 -> 平均价格:8.5\)
  • 以10\(卖出100股 -> 平均价格:8.5\),实现利润150$,17.65%
  • 以11\(购买150股 -> 平均价格:10\),实现利润150$,17.65%
  • 以12\(卖出100股 -> 平均价格:10\),总实现利润350$,20%
  • 以14\(卖出150股 -> 平均价格:10\),总实现利润950$,40% <- 这将是最后一个"出口"消息

这次交易的总利润是950\(,投资了3350\)100@8$ + 100@9$ + 150@11$)。因此,最终相对利润为28.35%(950 / 3350)。

调整入场价格

策略开发者可以使用adjust_entry_price()回调在新蜡烛出现时刷新/替换限价单。 请注意,在进入触发时,仍然由custom_entry_price()指示初始入场限价单价格目标。回调函数可以通过返回 None 来取消订单。

返回 current_order_rate 将保持交易所上的订单状态。 返回任何其他价格将取消现有订单,并用新订单替代。

交易开放日期(trade.open_date_utc)将保持为首次下单的时间点。 请确保注意这一点,并适时调整其他回调中的逻辑以考虑这一点,并改用第一个成交订单的日期。

如果原始订单取消失败,则订单将不会被替换 —— 虽然该订单很可能已被交易所取消。如果这种情况发生在初始进入订单上,将导致订单被删除;而如果发生在位置调整订单上,将导致交易规模保持不变。

常规超时

未成交超时(unfilledtimeout) 机制(以及 check_entry_timeout())优先于此机制。 通过上述方法取消的进入订单将不会调用此回调函数。请确保更新超时值以符合您的期望。

from freqtrade.persistence import Trade
from datetime import timedelta

class AwesomeStrategy(IStrategy):

    # ... populate_* methods

    def adjust_entry_price(self, trade: Trade, order: Optional[Order], pair: str,
                           current_time: datetime, proposed_rate: float, current_order_rate: float,
                           entry_tag: Optional[str], side: str, **kwargs) -> float:
        """
        重新调整进入价格的逻辑,返回用户期望的限价。
        只有当已经下了订单,订单仍然未执行(完全或部分未成交)
        并且在进入触发后的后续蜡烛图上没有超时时才会执行此操作。

        当策略未实现此方法时,默认返回 current_order_rate。
        如果返回 current_order_rate,则保持现有订单。
        如果返回 None,则取消订单但不替换为新订单。
        ```        :param pair: 当前分析的交易对
        :param trade: 交易对象。
        :param order: 订单对象
        :param current_time: 当前日期时间的日期对象
        :param proposed_rate: 根据入场定价设置计算得出的汇率。
        :param current_order_rate: 现有订单的汇率。
        :param entry_tag: 可选的入场标识(buy_tag),如果与买入信号一起提供。
        :param side: 'long' 或 'short' - 表示建议交易的方向
        :param **kwargs: 确保保留此处,以防止对此的更新破坏了您的策略。
        :return float: 如果提供了,则是新的入场价格值

        """
        # 将限价单用作 BTC/USDT 交易对进入触发器 10 分钟以内的价格目标 SMA200。
        if pair == 'BTC/USDT' and entry_tag == 'long_sma200' and side == 'long' and (current_time - timedelta(minutes=10)) > trade.open_date_utc:
            # 如果订单已填充超过一半的数量,只需取消订单
            if order.filled > order.remaining:
                return None
            else:
                dataframe, _ = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe)
                current_candle = dataframe.iloc[-1].squeeze()
                # 期望的价格
                return current_candle['sma_200']
        # 默认情况下:保持现有订单
        return current_order_rate

杠杆回调

在允许杠杆交易的市场上交易时,此方法必须返回所需的杠杆倍数(默认为1 -> 无杠杆)。

假设资本为500 USDT,使用3倍杠杆进行交易将导致带有杠杆=3的头寸,总价值为500 x 3 = 1500 USDT。

大于max_leverage的值将调整为max_leverage。 对于不支持杠杆的市场/交易所,此方法将被忽略。

class AwesomeStrategy(IStrategy):
    def leverage(self, pair: str, current_time: datetime, current_rate: float,
                 proposed_leverage: float, max_leverage: float, entry_tag: Optional[str], side: str,
                 **kwargs) -> float:
        """
        为每笔新交易自定义杠杆。此方法仅在期货模式下调用。

        :param pair: 当前分析的交易对
        :param current_time: 当前日期时间的日期对象
        :param current_rate: 根据退出定价设置计算得出的汇率。
        :param proposed_leverage: 机器人提议的杠杆。
        :param max_leverage: 在此交易对上允许的最大杠杆
        :param entry_tag: 可选的入场标识(buy_tag),如果与买入信号一起提供。
        :param side: 'long' 或 'short' - 表示建议交易的方向
        :return: 杠杆金额,介于1.0和max_leverage之间。
        """
        return 1.0

所有利润计算包括杠杆。止损/投资回报率也包括杠杆在其计算中。 以10倍杠杆设定10%的止损将会在价格下跌1%时触发止损。