V2 与 V3 之间的策略迁移¶
为了支持新市场和交易类型(主要是做空交易/杠杆交易),接口中的一些内容必须进行更改。如果您打算使用现货市场以外的市场,请将您的策略迁移到新格式。
我们已尽全力保持与现有策略的兼容性,因此如果您只是想继续在现货市场使用 freqtrade,目前无需进行任何更改。
您可以使用快速摘要作为检查清单。请参考下方的详细章节以获取完整的迁移信息。
快速摘要 / 迁移检查清单¶
注意:forcesell、forcebuy、emergencysell 分别更改为 force_exit、force_enter、emergency_exit。
- 策略方法:
- 数据框列:
- 交易对象(trade-object)现在具有以下新属性:
is_shortentry_sideexit_sidetrade_direction- 已重命名:
sell_reason->exit_reason
- 将
trade.nr_of_successful_buys重命名为trade.nr_of_successful_entries(主要与adjust_trade_position()相关) - 引入了新的
leverage回调函数。 - 信息对(informative pairs)现在可以在元组中传递第三个元素,用于定义蜡烛图类型。
@informative装饰器现在接受一个可选的candle_type参数。- 辅助方法
stoploss_from_open和stoploss_from_absolute现在新增is_short作为额外参数。 INTERFACE_VERSION应设置为 3。- 策略/配置设置。
order_time_in_forcebuy -> entry,sell -> exit。order_typesbuy -> entry,sell -> exit。unfilledtimeoutbuy -> entry,sell -> exit。ignore_buying_expired_candle_after-> 已移至根级别,而非位于 "ask_strategy/exit_pricing" 中
- 术语变更
- 卖出原因已更改,以反映新命名的“退出”(exit)替代“卖出”(sells)。如果你在策略中使用了
exit_reason检查,请务必注意并及时更新你的策略。sell_signal->exit_signalcustom_sell->custom_exitforce_sell->force_exitemergency_sell->emergency_exit
- 订单定价
bid_strategy->entry_pricingask_strategy->exit_pricingask_last_balance->price_last_balancebid_last_balance->price_last_balance
- Webhook 术语从“sell”更改为“exit”,从“buy”更改为“entry”
webhookbuy->entrywebhookbuyfill->entry_fillwebhookbuycancel->entry_cancelwebhooksell->exitwebhooksellfill->exit_fillwebhooksellcancel->exit_cancel
- Telegram 通知设置
buy->entrybuy_fill->entry_fillbuy_cancel->entry_cancelsell->exitsell_fill->exit_fillsell_cancel->exit_cancel
- 策略/配置设置:
use_sell_signal->use_exit_signalsell_profit_only->exit_profit_onlysell_profit_offset->exit_profit_offsetignore_roi_if_buy_signal->ignore_roi_if_entry_signalforcebuy_enable->force_entry_enable
- 卖出原因已更改,以反映新命名的“退出”(exit)替代“卖出”(sells)。如果你在策略中使用了
详细说明¶
populate_buy_trend¶
在 populate_buy_trend() 中,你需要将赋值的列从 'buy' 更改为 'enter_long',同时将方法名从 populate_buy_trend 改为 populate_entry_trend。
def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[
(
(qtpylib.crossed_above(dataframe['rsi'], 30)) & # Signal: RSI crosses above 30
(dataframe['tema'] <= dataframe['bb_middleband']) & # Guard
(dataframe['tema'] > dataframe['tema'].shift(1)) & # Guard
(dataframe['volume'] > 0) # Make sure Volume is not 0
),
['buy', 'buy_tag']] = (1, 'rsi_cross')
return dataframe
修改后:
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[
(
(qtpylib.crossed_above(dataframe['rsi'], 30)) & # Signal: RSI crosses above 30
(dataframe['tema'] <= dataframe['bb_middleband']) & # Guard
(dataframe['tema'] > dataframe['tema'].shift(1)) & # Guard
(dataframe['volume'] > 0) # Make sure Volume is not 0
),
['enter_long', 'enter_tag']] = (1, 'rsi_cross')
return dataframe
请参考 策略文档 了解如何进入和退出空头交易。
populate_sell_trend¶
类似于 populate_buy_trend,populate_sell_trend() 将被重命名为 populate_exit_trend()。我们还将列名从 'sell' 更改为 'exit_long'。
def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[
(
(qtpylib.crossed_above(dataframe['rsi'], 70)) & # Signal: RSI crosses above 70
(dataframe['tema'] > dataframe['bb_middleband']) & # Guard
(dataframe['tema'] < dataframe['tema'].shift(1)) & # Guard
(dataframe['volume'] > 0) # Make sure Volume is not 0
),
['sell', 'exit_tag']] = (1, 'some_exit_tag')
return dataframe
之后
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[
(
(qtpylib.crossed_above(dataframe['rsi'], 70)) & # Signal: RSI crosses above 70
(dataframe['tema'] > dataframe['bb_middleband']) & # Guard
(dataframe['tema'] < dataframe['tema'].shift(1)) & # Guard
(dataframe['volume'] > 0) # Make sure Volume is not 0
),
['exit_long', 'exit_tag']] = (1, 'some_exit_tag')
return dataframe
请参考 策略文档 了解如何进入和退出空头交易。
custom_sell¶
custom_sell 已重命名为 custom_exit。现在它会在每次迭代时都被调用,不再受当前利润和 exit_profit_only 设置的影响。
class AwesomeStrategy(IStrategy):
def custom_sell(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()
# ...
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()
# ...
custom_entry_timeout¶
check_buy_timeout() 已重命名为 check_entry_timeout(),而 check_sell_timeout() 已重命名为 check_exit_timeout()。
class AwesomeStrategy(IStrategy):
def check_buy_timeout(self, pair: str, trade: 'Trade', order: dict,
current_time: datetime, **kwargs) -> bool:
return False
def check_sell_timeout(self, pair: str, trade: 'Trade', order: dict,
current_time: datetime, **kwargs) -> bool:
return False
class AwesomeStrategy(IStrategy):
def check_entry_timeout(self, pair: str, trade: 'Trade', order: 'Order',
current_time: datetime, **kwargs) -> bool:
return False
def check_exit_timeout(self, pair: str, trade: 'Trade', order: 'Order',
current_time: datetime, **kwargs) -> bool:
return False
custom_stake_amount¶
新增字符串参数 side,其值可以是 "long" 或 "short"。
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,
entry_tag: Optional[str], **kwargs) -> float:
# ...
return proposed_stake
class AwesomeStrategy(IStrategy):
def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float,
proposed_stake: float, min_stake: float | None, max_stake: float,
entry_tag: str | None, side: str, **kwargs) -> float:
# ...
return proposed_stake
confirm_trade_entry¶
新增字符串参数 side,其值可以是 "long" 或 "short"。
class AwesomeStrategy(IStrategy):
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],
**kwargs) -> bool:
return True
修改后:
class AwesomeStrategy(IStrategy):
def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float,
time_in_force: str, current_time: datetime, entry_tag: str | None,
side: str, **kwargs) -> bool:
return True
confirm_trade_exit¶
将参数 sell_reason 更改为 exit_reason。为了兼容性,sell_reason 在一段时间内仍会被提供。
class AwesomeStrategy(IStrategy):
def confirm_trade_exit(self, pair: str, trade: Trade, order_type: str, amount: float,
rate: float, time_in_force: str, sell_reason: str,
current_time: datetime, **kwargs) -> bool:
return True
修改后:
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:
return True
custom_entry_price¶
新增字符串参数 side,其值可以是 "long" 或 "short"。
class AwesomeStrategy(IStrategy):
def custom_entry_price(self, pair: str, current_time: datetime, proposed_rate: float,
entry_tag: Optional[str], **kwargs) -> float:
return proposed_rate
修改后:
class AwesomeStrategy(IStrategy):
def custom_entry_price(self, pair: str, trade: Trade | None, current_time: datetime, proposed_rate: float,
entry_tag: str | None, side: str, **kwargs) -> float:
return proposed_rate
调整交易仓位变更¶
虽然 adjust-trade-position 本身没有变化,但你不应再使用 trade.nr_of_successful_buys,而应改用 trade.nr_of_successful_entries,后者还会包含空头建仓。
辅助方法¶
为 stoploss_from_open 和 stoploss_from_absolute 添加了参数 "is_short",该参数应传入 trade.is_short 的值。
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
current_rate: float, current_profit: float, **kwargs) -> float:
# once the profit has risen above 10%, keep the stoploss at 7% above the open price
if current_profit > 0.10:
return stoploss_from_open(0.07, current_profit)
return stoploss_from_absolute(current_rate - (candle['atr'] * 2), current_rate)
return 1
修改后:
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
current_rate: float, current_profit: float, after_fill: bool,
**kwargs) -> float | None:
# once the profit has risen above 10%, keep the stoploss at 7% above the open price
if current_profit > 0.10:
return stoploss_from_open(0.07, current_profit, is_short=trade.is_short)
return stoploss_from_absolute(current_rate - (candle['atr'] * 2), current_rate, is_short=trade.is_short, leverage=trade.leverage)
策略/配置设置¶
order_time_in_force¶
order_time_in_force 属性已从 "buy" 更改为 "entry","sell" 更改为 "exit"。
order_time_in_force: dict = {
"buy": "gtc",
"sell": "gtc",
}
修改后:
order_time_in_force: dict = {
"entry": "GTC",
"exit": "GTC",
}
order_types¶
order_types 中的所有术语已从 buy 改为 entry,sell 改为 exit,并且两个单词之间使用 _ 连接。
order_types = {
"buy": "limit",
"sell": "limit",
"emergencysell": "market",
"forcesell": "market",
"forcebuy": "market",
"stoploss": "market",
"stoploss_on_exchange": false,
"stoploss_on_exchange_interval": 60
}
修改后:
order_types = {
"entry": "limit",
"exit": "limit",
"emergency_exit": "market",
"force_exit": "market",
"force_entry": "market",
"stoploss": "market",
"stoploss_on_exchange": false,
"stoploss_on_exchange_interval": 60
}
策略级别设置¶
use_sell_signal->use_exit_signalsell_profit_only->exit_profit_onlysell_profit_offset->exit_profit_offsetignore_roi_if_buy_signal->ignore_roi_if_entry_signal
# These values can be overridden in the config.
use_sell_signal = True
sell_profit_only = True
sell_profit_offset: 0.01
ignore_roi_if_buy_signal = False
修改后:
# These values can be overridden in the config.
use_exit_signal = True
exit_profit_only = True
exit_profit_offset: 0.01
ignore_roi_if_entry_signal = False
unfilledtimeout¶
unfilledtimeout 中的所有术语已从 buy 改为 entry,sell 改为 exit。
unfilledtimeout = {
"buy": 10,
"sell": 10,
"exit_timeout_count": 0,
"unit": "minutes"
}
修改后:
unfilledtimeout = {
"entry": 10,
"exit": 10,
"exit_timeout_count": 0,
"unit": "minutes"
}
订单定价¶
订单定价方式发生了两项变更:bid_strategy 已重命名为 entry_pricing,ask_strategy 已重命名为 exit_pricing。同时,属性 ask_last_balance 和 bid_last_balance 均被重命名为 price_last_balance。此外,现在价格侧可以定义为 ask、bid、same 或 other。更多信息请参考 定价文档。
{
"bid_strategy": {
"price_side": "bid",
"use_order_book": true,
"order_book_top": 1,
"ask_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"ask_strategy":{
"price_side": "ask",
"use_order_book": true,
"order_book_top": 1,
"bid_last_balance": 0.0
"ignore_buying_expired_candle_after": 120
}
}
修改后:
{
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing":{
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0
},
"ignore_buying_expired_candle_after": 120
}
FreqAI 策略¶
populate_any_indicators() 方法已被拆分为 feature_engineering_expand_all()、feature_engineering_expand_basic()、feature_engineering_standard() 和 set_freqai_targets()。
对于每个新函数,交易对(以及必要时的时间周期)将自动添加到列中。因此,使用新逻辑后特征的定义变得更加简单。
有关每个方法的完整说明,请参阅对应的 freqAI 文档页面。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | |
- 特征 — 移动到
feature_engineering_expand_all - 基础特征,不需在
indicator_periods_candles上扩展 — 移动到feature_engineering_expand_basic()。 - 标准特征,无需扩展 — 移动到
feature_engineering_standard()。 - 目标 — 将此部分移动到
set_freqai_targets()。
freqai - 特征工程扩展全部¶
特征现在将自动扩展。因此,需要移除原有的扩展循环以及 {pair} / {timeframe} 部分。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | |
Freqai - 特征工程基础¶
基础特征。请确保从特征中移除 {pair} 部分。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | |
FreqAI - 特征工程标准¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | |
FreqAI - 设置目标¶
目标现在拥有独立的专用方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | |
FreqAI - 新的数据管道¶
如果你创建了自己的自定义 IFreqaiModel,并在其中使用了自定义的 train()/predict() 函数,并且 仍然依赖 data_cleaning_train/predict(),那么你需要迁移到新的数据处理流程。如果你的模型不依赖 data_cleaning_train/predict(),则无需担心此次迁移。这意味着本迁移指南仅适用于极少数高级用户。如果你误入此指南,可前往 Freqtrade 的 Discord 服务器深入咨询你的问题。
迁移过程首先需要移除 data_cleaning_train/predict(),并在你的 IFreqaiModel 类中添加 define_data_pipeline() 和 define_label_pipeline() 函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | |
- 数据归一化和清洗现在通过新的管道定义实现统一处理。该功能在新增的
define_data_pipeline()和define_label_pipeline()函数中完成。data_cleaning_train()和data_cleaning_predict()函数已不再使用。如有需要,你可以重写define_data_pipeline()来创建自己的自定义处理流程。 - 数据归一化和清洗现在通过新的管道定义实现统一处理。该功能在新增的
define_data_pipeline()和define_label_pipeline()函数中完成。data_cleaning_train()和data_cleaning_predict()函数已不再使用。如有需要,你可以重写define_data_pipeline()来创建自己的自定义处理流程。 - 数据反归一化也通过新管道完成,请使用以下代码替换原有逻辑。