mirror of
https://github.com/freqtrade/freqtrade.git
synced 2024-11-10 10:21:59 +00:00
Allow user to go live and start from pretrained models (after a completed backtest) by simply reusing the identifier
config parameter while dry/live.
This commit is contained in:
parent
7486d9d9e2
commit
b79d4e8876
|
@ -55,10 +55,9 @@
|
||||||
"15m"
|
"15m"
|
||||||
],
|
],
|
||||||
"train_period": 30,
|
"train_period": 30,
|
||||||
"backtest_period": 7,
|
"backtest_period": 10,
|
||||||
"identifier": "example",
|
"identifier": "example",
|
||||||
"live_trained_timerange": "",
|
"live_trained_timestamp": 0,
|
||||||
"live_full_backtestrange": "",
|
|
||||||
"corr_pairlist": [
|
"corr_pairlist": [
|
||||||
"BTC/USDT",
|
"BTC/USDT",
|
||||||
"ETH/USDT",
|
"ETH/USDT",
|
||||||
|
|
|
@ -158,7 +158,7 @@ a specific pair or timeframe, they should use the following structure inside `po
|
||||||
if pair == metadata['pair'] and tf == self.timeframe:
|
if pair == metadata['pair'] and tf == self.timeframe:
|
||||||
df['%-day_of_week'] = (df["date"].dt.dayofweek + 1) / 7
|
df['%-day_of_week'] = (df["date"].dt.dayofweek + 1) / 7
|
||||||
df['%-hour_of_day'] = (df['date'].dt.hour + 1) / 25
|
df['%-hour_of_day'] = (df['date'].dt.hour + 1) / 25
|
||||||
|
```
|
||||||
|
|
||||||
(Please see the example script located in `freqtrade/templates/FreqaiExampleStrategy.py` for a full example of `populate_any_indicators()`)
|
(Please see the example script located in `freqtrade/templates/FreqaiExampleStrategy.py` for a full example of `populate_any_indicators()`)
|
||||||
|
|
||||||
|
@ -270,27 +270,22 @@ freqtrade trade --strategy FreqaiExampleStrategy --config config_freqai.example.
|
||||||
By default, Freqai will not find find any existing models and will start by training a new one
|
By default, Freqai will not find find any existing models and will start by training a new one
|
||||||
given the user configuration settings. Following training, it will use that model to predict for the
|
given the user configuration settings. Following training, it will use that model to predict for the
|
||||||
duration of `backtest_period`. After a full `backtest_period` has elapsed, Freqai will auto retrain
|
duration of `backtest_period`. After a full `backtest_period` has elapsed, Freqai will auto retrain
|
||||||
a new model, and begin making predictions with the updated model.
|
a new model, and begin making predictions with the updated model. FreqAI in live mode permits
|
||||||
|
the user to use fractional days (i.e. 0.1) in the `backtest_period`, which enables more frequent
|
||||||
|
retraining.
|
||||||
|
|
||||||
If the user wishes to start dry/live from a saved model, the following configuration
|
If the user wishes to start dry/live from a backtested saved model, the user only needs to reuse
|
||||||
parameters need to be set:
|
the same `identifier` parameter
|
||||||
|
|
||||||
```json
|
```json
|
||||||
"freqai": {
|
"freqai": {
|
||||||
"identifier": "example",
|
"identifier": "example",
|
||||||
"live_trained_timerange": "20220330-20220429",
|
|
||||||
"live_full_backtestrange": "20220302-20220501"
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Where the `identifier` is the same identifier which was set during the backtesting/training. Meanwhile,
|
In this case, although Freqai will initiate with a
|
||||||
the `live_trained_timerange` is the sub-trained timerange (the training window) which was set
|
pre-trained model, it will still check to see how much time has elapsed since the model was trained,
|
||||||
during backtesting/training. These are available to the user inside `user_data/models/*/sub-train-*`.
|
and if a full `backtest_period` has elapsed since the end of the loaded model, FreqAI will self retrain.
|
||||||
`live_full_backtestrange` was the full data range associated with the backtest/training (the full time
|
|
||||||
window that the training window and backtesting windows slide through). These values can be located
|
|
||||||
inside the `user_data/models/` directory. In this case, although Freqai will initiate with a
|
|
||||||
pre-trained model, if a full `backtest_period` has elapsed since the end of the user set
|
|
||||||
`live_trained_timerange`, it will self retrain.
|
|
||||||
|
|
||||||
## Data anylsis techniques
|
## Data anylsis techniques
|
||||||
|
|
||||||
|
|
|
@ -440,15 +440,13 @@ CONF_SCHEMA = {
|
||||||
"train_period": {"type": "integer", "default": 0},
|
"train_period": {"type": "integer", "default": 0},
|
||||||
"backtest_period": {"type": "float", "default": 7},
|
"backtest_period": {"type": "float", "default": 7},
|
||||||
"identifier": {"type": "str", "default": "example"},
|
"identifier": {"type": "str", "default": "example"},
|
||||||
"live_trained_timerange": {"type": "str"},
|
|
||||||
"live_full_backtestrange": {"type": "str"},
|
|
||||||
"corr_pairlist": {"type": "list"},
|
"corr_pairlist": {"type": "list"},
|
||||||
"feature_parameters": {
|
"feature_parameters": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"period": {"type": "integer"},
|
"period": {"type": "integer"},
|
||||||
"shift": {"type": "integer", "default": 0},
|
"shift": {"type": "integer", "default": 0},
|
||||||
"DI_threshold": {"type": "integer", "default": 0},
|
"DI_threshold": {"type": "float", "default": 0},
|
||||||
"weight_factor": {"type": "number", "default": 0},
|
"weight_factor": {"type": "number", "default": 0},
|
||||||
"principal_component_analysis": {"type": "boolean", "default": False},
|
"principal_component_analysis": {"type": "boolean", "default": False},
|
||||||
"use_SVM_to_remove_outliers": {"type": "boolean", "default": False},
|
"use_SVM_to_remove_outliers": {"type": "boolean", "default": False},
|
||||||
|
|
|
@ -74,8 +74,7 @@ class FreqaiDataKitchen:
|
||||||
def set_paths(self, metadata: dict, trained_timestamp: int = None,) -> None:
|
def set_paths(self, metadata: dict, trained_timestamp: int = None,) -> None:
|
||||||
self.full_path = Path(self.config['user_data_dir'] /
|
self.full_path = Path(self.config['user_data_dir'] /
|
||||||
"models" /
|
"models" /
|
||||||
str(self.freqai_config.get('live_full_backtestrange') +
|
str(self.freqai_config.get('identifier')))
|
||||||
self.freqai_config.get('identifier')))
|
|
||||||
|
|
||||||
self.data_path = Path(self.full_path / str("sub-train" + "-" +
|
self.data_path = Path(self.full_path / str("sub-train" + "-" +
|
||||||
metadata['pair'].split("/")[0] +
|
metadata['pair'].split("/")[0] +
|
||||||
|
@ -114,11 +113,11 @@ class FreqaiDataKitchen:
|
||||||
save_path / str(self.model_filename + "_trained_df.pkl")
|
save_path / str(self.model_filename + "_trained_df.pkl")
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.live:
|
# if self.live:
|
||||||
self.data_drawer.model_dictionary[self.model_filename] = model
|
self.data_drawer.model_dictionary[self.model_filename] = model
|
||||||
self.data_drawer.pair_dict[coin]['model_filename'] = self.model_filename
|
self.data_drawer.pair_dict[coin]['model_filename'] = self.model_filename
|
||||||
self.data_drawer.pair_dict[coin]['data_path'] = str(self.data_path)
|
self.data_drawer.pair_dict[coin]['data_path'] = str(self.data_path)
|
||||||
self.data_drawer.save_drawer_to_disk()
|
self.data_drawer.save_drawer_to_disk()
|
||||||
|
|
||||||
# TODO add a helper function to let user save/load any data they are custom adding. We
|
# TODO add a helper function to let user save/load any data they are custom adding. We
|
||||||
# do not want them having to edit the default save/load methods here. Below is an example
|
# do not want them having to edit the default save/load methods here. Below is an example
|
||||||
|
@ -142,9 +141,9 @@ class FreqaiDataKitchen:
|
||||||
:model: User trained model which can be inferenced for new predictions
|
:model: User trained model which can be inferenced for new predictions
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.live:
|
# if self.live:
|
||||||
self.model_filename = self.data_drawer.pair_dict[coin]['model_filename']
|
self.model_filename = self.data_drawer.pair_dict[coin]['model_filename']
|
||||||
self.data_path = Path(self.data_drawer.pair_dict[coin]['data_path'])
|
self.data_path = Path(self.data_drawer.pair_dict[coin]['data_path'])
|
||||||
|
|
||||||
with open(self.data_path / str(self.model_filename + "_metadata.json"), "r") as fp:
|
with open(self.data_path / str(self.model_filename + "_metadata.json"), "r") as fp:
|
||||||
self.data = json.load(fp)
|
self.data = json.load(fp)
|
||||||
|
@ -696,7 +695,7 @@ class FreqaiDataKitchen:
|
||||||
self.full_path = Path(
|
self.full_path = Path(
|
||||||
self.config["user_data_dir"]
|
self.config["user_data_dir"]
|
||||||
/ "models"
|
/ "models"
|
||||||
/ str(full_timerange + self.freqai_config.get("identifier"))
|
/ str(self.freqai_config.get("identifier"))
|
||||||
)
|
)
|
||||||
|
|
||||||
config_path = Path(self.config["config_files"][0])
|
config_path = Path(self.config["config_files"][0])
|
||||||
|
@ -750,10 +749,10 @@ class FreqaiDataKitchen:
|
||||||
str(int(trained_timerange.stopts))))
|
str(int(trained_timerange.stopts))))
|
||||||
|
|
||||||
self.model_filename = "cb_" + coin.lower() + "_" + str(int(trained_timerange.stopts))
|
self.model_filename = "cb_" + coin.lower() + "_" + str(int(trained_timerange.stopts))
|
||||||
# this is not persistent at the moment TODO
|
|
||||||
self.freqai_config['live_trained_timerange'] = str(int(trained_timerange.stopts))
|
# self.freqai_config['live_trained_timerange'] = str(int(trained_timerange.stopts))
|
||||||
# enables persistence, but not fully implemented into save/load data yer
|
# enables persistence, but not fully implemented into save/load data yer
|
||||||
self.data['live_trained_timerange'] = str(int(trained_timerange.stopts))
|
# self.data['live_trained_timerange'] = str(int(trained_timerange.stopts))
|
||||||
|
|
||||||
def download_new_data_for_retraining(self, timerange: TimeRange, metadata: dict) -> None:
|
def download_new_data_for_retraining(self, timerange: TimeRange, metadata: dict) -> None:
|
||||||
|
|
||||||
|
|
|
@ -77,13 +77,13 @@ class IFreqaiModel(ABC):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.live = strategy.dp.runmode in (RunMode.DRY_RUN, RunMode.LIVE)
|
self.live = strategy.dp.runmode in (RunMode.DRY_RUN, RunMode.LIVE)
|
||||||
|
self.data_drawer.set_pair_dict_info(metadata)
|
||||||
|
|
||||||
# For live, we may be training new models on a separate thread while other pairs still need
|
# For live, we may be training new models on a separate thread while other pairs still need
|
||||||
# to inference their historical models. Here we use a training queue system to handle this
|
# to inference their historical models. Here we use a training queue system to handle this
|
||||||
# and we keep the flag self.training_on_separate_threaad in the current object to help
|
# and we keep the flag self.training_on_separate_threaad in the current object to help
|
||||||
# determine what the current pair will do
|
# determine what the current pair will do
|
||||||
if self.live:
|
if self.live:
|
||||||
self.data_drawer.set_pair_dict_info(metadata)
|
|
||||||
if (not self.training_on_separate_thread and
|
if (not self.training_on_separate_thread and
|
||||||
self.data_drawer.training_queue == 1):
|
self.data_drawer.training_queue == 1):
|
||||||
|
|
||||||
|
@ -137,6 +137,7 @@ class IFreqaiModel(ABC):
|
||||||
for tr_train, tr_backtest in zip(
|
for tr_train, tr_backtest in zip(
|
||||||
dh.training_timeranges, dh.backtesting_timeranges
|
dh.training_timeranges, dh.backtesting_timeranges
|
||||||
):
|
):
|
||||||
|
(_, _, _) = self.data_drawer.get_pair_dict_info(metadata)
|
||||||
gc.collect()
|
gc.collect()
|
||||||
dh.data = {} # clean the pair specific data between training window sliding
|
dh.data = {} # clean the pair specific data between training window sliding
|
||||||
self.training_timerange = tr_train
|
self.training_timerange = tr_train
|
||||||
|
@ -150,9 +151,12 @@ class IFreqaiModel(ABC):
|
||||||
if not self.model_exists(metadata["pair"], dh,
|
if not self.model_exists(metadata["pair"], dh,
|
||||||
trained_timestamp=trained_timestamp.stopts):
|
trained_timestamp=trained_timestamp.stopts):
|
||||||
self.model = self.train(dataframe_train, metadata, dh)
|
self.model = self.train(dataframe_train, metadata, dh)
|
||||||
dh.save_data(self.model)
|
self.data_drawer.pair_dict[metadata['pair']][
|
||||||
|
'trained_timestamp'] = trained_timestamp.stopts
|
||||||
|
dh.set_new_model_names(metadata, trained_timestamp)
|
||||||
|
dh.save_data(self.model, metadata['pair'])
|
||||||
else:
|
else:
|
||||||
self.model = dh.load_data()
|
self.model = dh.load_data(metadata['pair'])
|
||||||
|
|
||||||
# strategy_provided_features = self.dh.find_features(dataframe_train)
|
# strategy_provided_features = self.dh.find_features(dataframe_train)
|
||||||
# # FIXME doesnt work with PCA
|
# # FIXME doesnt work with PCA
|
||||||
|
@ -295,8 +299,7 @@ class IFreqaiModel(ABC):
|
||||||
def set_full_path(self) -> None:
|
def set_full_path(self) -> None:
|
||||||
self.full_path = Path(self.config['user_data_dir'] /
|
self.full_path = Path(self.config['user_data_dir'] /
|
||||||
"models" /
|
"models" /
|
||||||
str(self.freqai_info.get('live_full_backtestrange') +
|
str(self.freqai_info.get('identifier')))
|
||||||
self.freqai_info.get('identifier')))
|
|
||||||
|
|
||||||
@threaded
|
@threaded
|
||||||
def retrain_model_on_separate_thread(self, new_trained_timerange: TimeRange, metadata: dict,
|
def retrain_model_on_separate_thread(self, new_trained_timerange: TimeRange, metadata: dict,
|
||||||
|
|
Loading…
Reference in New Issue
Block a user