策略回调函数¶
主要的策略函数 (populate_indicators()
, populate_entry_trend()
, populate_exit_trend()
) 应该以向量化的方式使用,并且仅在回测期间一次调用,回调函数在需要时被调用。
因此,应避免在回调函数中进行复杂的计算,以免在操作期间出现延迟。 根据所使用的回调函数,它们可能在进入/退出交易时调用,或在交易持续期间调用。
当前可用的回调函数:
bot_start()
bot_loop_start()
custom_stake_amount()
custom_exit()
custom_stoploss()
custom_entry_price()
和custom_exit_price()
check_entry_timeout()
和check_exit_timeout()
confirm_trade_entry()
confirm_trade_exit()
adjust_trade_position()
adjust_entry_price()
leverage()
回调函数调用顺序
您可以在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
返回 0
或 None
将阻止进行交易。
自定义退出信号¶
在每次节流迭代(大约每5秒)时,对开放交易调用,直到交易关闭。
允许定义自定义退出信号,表示应该卖出指定的头寸。当我们需要为每个单独的交易自定义退出条件,或者如果您需要交易数据来做出退出决策时,这非常有用。
例如,您可以使用custom_exit()
来实现1:2的风险回报ROI。
不建议使用custom_exit()
信号来替代止损。在这方面,使用custom_stoploss()
是一种更好的方法,它还允许您在交易所上保留止损。
注意
从这个方法返回一个(非空)string
或True
等效于在指定时间的蜡烛上设置退出信号。当已经设置退出信号时,或者退出信号被禁用(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/BTC
和 XRP/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_stake
和 max_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
命令会阻止机器人进入新的交易,但仓位调整功能将继续在现有交易上购买新的订单。
回测
在回测期间,该回调函数针对timeframe
或timeframe_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%时触发止损。