V2和V3之间的策略迁移¶
为了支持新的市场和交易类型(即带杠杆的短线交易),界面上必须进行一些更改。 如果您打算使用除现货市场之外的其他市场,请将策略迁移到新的格式。
我们花费了大量的精力来保持与现有策略的兼容性,因此如果您只想继续在现货市场中使用freqtrade,则目前不需要进行任何更改。
您可以使用快速摘要作为检查清单。请查阅下面的详细部分获取完整的迁移详细信息。
快速摘要/迁移清单¶
注意:forcesell
、forcebuy
、emergencysell
已更改为 force_exit
、force_enter
、emergency_exit
。
- 策略方法:
- 数据框列:
buy
->enter_long
sell
->exit_long
buy_tag
->enter_tag
(用于长期交易和短期交易)- 新的列
enter_short
和相应的新列exit_short
(用于长期交易和短期交易)](#populate_sell_trend)
- trade-object 现在具有以下新属性:
is_short
entry_side
exit_side
trade_direction
- 重命名:
sell_reason
->exit_reason
- 将
trade.nr_of_successful_buys
重命名为trade.nr_of_successful_entries
(对于adjust_trade_position()
很重要) - 引入了新的
leverage
回调. - 现在,信息对可以在元组中传递第三个元素,以定义蜡烛类型。
@informitive
装饰器现在接受一个可选的candle_type
参数。- 辅助方法
stoploss_from_open
和stoploss_from_absolute
现在接受额外的is_short
参数。 INTERFACE_VERSION
应设置为 3。- 策略/配置设置。
order_time_in_force
buy -> entry, sell -> exit。order_types
buy -> entry, sell -> exit。unfilledtimeout
buy -> entry, sell -> exit。ignore_buying_expired_candle_after
-> 移至根级别而不是 "ask_strategy/exit_pricing"
- 术语更改
- 出售原因改为反映新的出口命名而不是出售。如果您的策略使用
exit_reason
检查,请小心并更新策略。sell_signal
->exit_signal
custom_sell
->custom_exit
force_sell
->force_exit
emergency_sell
->emergency_exit
- 订单定价
bid_strategy
->entry_pricing
ask_strategy
->exit_pricing
ask_last_balance
->price_last_balance
bid_last_balance
->price_last_balance
- Webhook 术语从 "sell" 更改为 "exit",从 "buy" 更改为 "entry"
webhookbuy
->entry
webhookbuyfill
->entry_fill
webhookbuycancel
->entry_cancel
webhooksell
->exit
webhooksellfill
->exit_fill
webhooksellcancel
->exit_cancel
- 电报通知设置
buy
->entry
buy_fill
->entry_fill
buy_cancel
->entry_cancel
sell
->exit
sell_fill
->exit_fill
sell_cancel
->exit_cancel
- 策略/配置设置:
use_sell_signal
->use_exit_signal
sell_profit_only
->exit_profit_only
sell_profit_offset
->exit_profit_offset
ignore_roi_if_buy_signal
->ignore_roi_if_entry_signal
forcebuy_enable
->force_entry_enable
- 出售原因改为反映新的出口命名而不是出售。如果您的策略使用
详细说明¶
populate_buy_trend
¶
在 populate_buy_trend()
中,您需要将分配的列从 'buy'
更改为 'enter_long'
,方法名称也应从 populate_buy_trend
更改为 populate_entry_trend
。
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[
(
(qtpylib.crossed_above(dataframe['rsi'], 30)) & # 信号:RSI 上穿 30
(dataframe['tema'] <= dataframe['bb_middleband']) & # 保护条件
(dataframe['tema'] > dataframe['tema'].shift(1)) & # 保护条件
(dataframe['volume'] > 0) # 确保交易量不为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_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[
(
(qtpylib.crossed_above(dataframe['rsi'], 70)) & # 信号:RSI 上穿 70
(dataframe['tema'] > dataframe['bb_middleband']) & # 保护条件
(dataframe['tema'] < dataframe['tema'].shift(1)) & # 保护条件
(dataframe['volume'] > 0) # 确保交易量不为0
),
['exit_long', '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)) & # 信号:RSI 上穿70
(dataframe['tema'] > dataframe['bb_middleband']) & # 保护条件
(dataframe['tema'] < dataframe['tema'].shift(1)) & # 保护条件
(dataframe['volume'] > 0) # 确保交易量不为0
),
['exit_long', 'exit_tag']] = (1, '某个退出标签')
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: Optional[float], max_stake: float,
entry_tag: Optional[str], 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: Optional[str],
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
After:
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
After:
class AwesomeStrategy(IStrategy):
def custom_entry_price(self, pair: str, trade: Optional[Trade], current_time: datetime, proposed_rate: float,
entry_tag: Optional[str], 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:
# 一旦利润上涨超过10%,将止损设置为开仓价的上方7%
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) -> Optional[float]:
# 一旦利润超过10%,将止损保持在开盘价上涨7%的水平
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",
}
After:
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_signal
sell_profit_only
->exit_profit_only
sell_profit_offset
->exit_profit_offset
ignore_roi_if_buy_signal
->ignore_roi_if_entry_signal
# 这些值可以在配置中被重新定义。
use_sell_signal = True
sell_profit_only = True
sell_profit_offset: 0.01
ignore_roi_if_buy_signal = False
之后:
# 这些值可以在配置中被重新定义。
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 = {
"entry": 10,
"exit": 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
属性更名为price_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 |
|
- 特征 - 移到
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 |
|
Freqai - feature engineering basic¶
Basic features. Make sure to remove the {pair}
part from your features.
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 |
|
FreqAI - 特征工程标准¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
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()
,并将它们替换为 define_data_pipeline()
和 define_label_pipeline()
函数到您的 IFreqaiModel
类中:```python linenums="1" hl_lines="11-14 47-49 55-57"
class MyCoolFreqaiModel(BaseRegressionModel):
"""
你在 Freqtrade 版本 2023.6 之前创建的一些酷炫的自定义 IFreqaiModel
"""
def train(
self, unfiltered_df: DataFrame, pair: str, dk: FreqaiDataKitchen, **kwargs
) -> Any:
# ... 你自定义的内容
# 删除这些行
# data_dictionary = dk.make_train_test_datasets(features_filtered, labels_filtered)
# self.data_cleaning_train(dk)
# data_dictionary = dk.normalize_data(data_dictionary)
# (1)
# 添加这些行。现在我们自己控制管道的拟合/转换
dd = dk.make_train_test_datasets(features_filtered, labels_filtered)
dk.feature_pipeline = self.define_data_pipeline(threads=dk.thread_count)
dk.label_pipeline = self.define_label_pipeline(threads=dk.thread_count)
(dd["train_features"],
dd["train_labels"],
dd["train_weights"]) = dk.feature_pipeline.fit_transform(dd["train_features"],
dd["train_labels"],
dd["train_weights"])
(dd["test_features"],
dd["test_labels"],
dd["test_weights"]) = dk.feature_pipeline.transform(dd["test_features"],
dd["test_labels"],
dd["test_weights"])
dd["train_labels"], _, _ = dk.label_pipeline.fit_transform(dd["train_labels"])
dd["test_labels"], _, _ = dk.label_pipeline.transform(dd["test_labels"])
# ... 你自定义的代码
return model
def predict(
self, unfiltered_df: DataFrame, dk: FreqaiDataKitchen, **kwargs
) -> Tuple[DataFrame, npt.NDArray[np.int_]]:
# ...你的自己的代码
# 删除这些行:
# self.data_cleaning_predict(dk)
# (2)
# 添加这些行:
dk.data_dictionary["prediction_features"], outliers, _ = dk.feature_pipeline.transform(
dk.data_dictionary["prediction_features"], outlier_check=True)
# 删除这行
# pred_df = dk.denormalize_labels_from_metadata(pred_df)
# (3)
# 替换为以下的代码块
pred_df, _, _ = dk.label_pipeline.inverse_transform(pred_df)
if self.freqai_info.get("DI_threshold", 0) > 0:
dk.DI_values = dk.feature_pipeline["di"].di_values
else:
dk.DI_values = np.zeros(outliers.shape[0])
dk.do_predict = outliers
# ...你的自己的代码
return (pred_df, dk.do_predict)
```
- 数据标准化和清洗现在与新的管道定义相一致。这是在新的
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()
来创建你自己的自定义管道。 - 使用新的管道进行数据反标准化。使用以下代码块替换这段代码。