add model expiration feature, fix bug in DI return values
This commit is contained in:
parent
0b0688a91e
commit
f631ae911b
@ -452,6 +452,24 @@ config:
|
||||
|
||||
which will automatically purge all models older than the two most recently trained ones.
|
||||
|
||||
## Defining model expirations
|
||||
|
||||
During dry/live, FreqAI trains each pair sequentially (on separate threads/GPU from the main
|
||||
Freqtrade bot). This means there is always an age discrepancy between models. If a user is training
|
||||
on 50 pairs, and each pair requires 5 minutes to train, the oldest model will be over 4 hours old.
|
||||
This may be undesirable if the characteristic time scale (read trade duration target) for a strategy
|
||||
is much less than 4 hours. The user can decide to only make trade entries if the model is less than
|
||||
a certain number of hours in age by setting the `expiration_hours` in the config file:
|
||||
|
||||
```json
|
||||
"freqai": {
|
||||
"expiration_hours": 0.5,
|
||||
}
|
||||
```
|
||||
|
||||
In the present example, the user will only allow predictions on models that are less than 1/2 hours
|
||||
old.
|
||||
|
||||
<!-- ## Dynamic target expectation
|
||||
|
||||
The labels used for model training have a unique statistical distribution for each separate model training.
|
||||
|
@ -30,6 +30,7 @@ class FreqaiDataDrawer:
|
||||
def __init__(self, full_path: Path, config: dict, follow_mode: bool = False):
|
||||
|
||||
self.config = config
|
||||
self.freqai_info = config.get('freqai', {})
|
||||
# dictionary holding all pair metadata necessary to load in from disk
|
||||
self.pair_dict: Dict[str, Any] = {}
|
||||
# dictionary holding all actively inferenced models in memory given a model filename
|
||||
@ -168,6 +169,7 @@ class FreqaiDataDrawer:
|
||||
self.model_return_values[pair]['do_preds'] = dh.full_do_predict
|
||||
self.model_return_values[pair]['target_mean'] = dh.full_target_mean
|
||||
self.model_return_values[pair]['target_std'] = dh.full_target_std
|
||||
if self.freqai_info.get('feature_parameters', {}).get('DI_threshold', 0) > 0:
|
||||
self.model_return_values[pair]['DI_values'] = dh.full_DI_values
|
||||
|
||||
# if not self.follow_mode:
|
||||
@ -189,6 +191,7 @@ class FreqaiDataDrawer:
|
||||
|
||||
self.model_return_values[pair]['predictions'] = np.append(
|
||||
self.model_return_values[pair]['predictions'][i:], predictions[-1])
|
||||
if self.freqai_info.get('feature_parameters', {}).get('DI_threshold', 0) > 0:
|
||||
self.model_return_values[pair]['DI_values'] = np.append(
|
||||
self.model_return_values[pair]['DI_values'][i:], dh.DI_values[-1])
|
||||
self.model_return_values[pair]['do_preds'] = np.append(
|
||||
@ -202,6 +205,7 @@ class FreqaiDataDrawer:
|
||||
prepend = np.zeros(abs(length_difference) - 1)
|
||||
self.model_return_values[pair]['predictions'] = np.insert(
|
||||
self.model_return_values[pair]['predictions'], 0, prepend)
|
||||
if self.freqai_info.get('feature_parameters', {}).get('DI_threshold', 0) > 0:
|
||||
self.model_return_values[pair]['DI_values'] = np.insert(
|
||||
self.model_return_values[pair]['DI_values'], 0, prepend)
|
||||
self.model_return_values[pair]['do_preds'] = np.insert(
|
||||
@ -215,6 +219,7 @@ class FreqaiDataDrawer:
|
||||
dh.full_do_predict = copy.deepcopy(self.model_return_values[pair]['do_preds'])
|
||||
dh.full_target_mean = copy.deepcopy(self.model_return_values[pair]['target_mean'])
|
||||
dh.full_target_std = copy.deepcopy(self.model_return_values[pair]['target_std'])
|
||||
if self.freqai_info.get('feature_parameters', {}).get('DI_threshold', 0) > 0:
|
||||
dh.full_DI_values = copy.deepcopy(self.model_return_values[pair]['DI_values'])
|
||||
|
||||
# if not self.follow_mode:
|
||||
@ -227,6 +232,7 @@ class FreqaiDataDrawer:
|
||||
dh.full_do_predict = np.zeros(len_df)
|
||||
dh.full_target_mean = np.zeros(len_df)
|
||||
dh.full_target_std = np.zeros(len_df)
|
||||
if self.freqai_info.get('feature_parameters', {}).get('DI_threshold', 0) > 0:
|
||||
dh.full_DI_values = np.zeros(len_df)
|
||||
|
||||
def purge_old_models(self) -> None:
|
||||
|
@ -673,7 +673,7 @@ class FreqaiDataKitchen:
|
||||
|
||||
self.full_predictions = np.append(self.full_predictions, predictions)
|
||||
self.full_do_predict = np.append(self.full_do_predict, do_predict)
|
||||
if self.freqai_config.get('feature_parameters', {}).get('DI-threshold', 0) > 0:
|
||||
if self.freqai_config.get('feature_parameters', {}).get('DI_threshold', 0) > 0:
|
||||
self.full_DI_values = np.append(self.full_DI_values, self.DI_values)
|
||||
self.full_target_mean = np.append(self.full_target_mean, target_mean)
|
||||
self.full_target_std = np.append(self.full_target_std, target_std)
|
||||
@ -689,7 +689,7 @@ class FreqaiDataKitchen:
|
||||
filler = np.zeros(len_dataframe - len(self.full_predictions)) # startup_candle_count
|
||||
self.full_predictions = np.append(filler, self.full_predictions)
|
||||
self.full_do_predict = np.append(filler, self.full_do_predict)
|
||||
if self.freqai_config.get('feature_parameters', {}).get('DI-threshold', 0) > 0:
|
||||
if self.freqai_config.get('feature_parameters', {}).get('DI_threshold', 0) > 0:
|
||||
self.full_DI_values = np.append(filler, self.full_DI_values)
|
||||
self.full_target_mean = np.append(filler, self.full_target_mean)
|
||||
self.full_target_std = np.append(filler, self.full_target_std)
|
||||
@ -725,6 +725,12 @@ class FreqaiDataKitchen:
|
||||
|
||||
return full_timerange
|
||||
|
||||
def check_if_model_expired(self, trained_timestamp: int) -> bool:
|
||||
time = datetime.datetime.now(tz=datetime.timezone.utc).timestamp()
|
||||
elapsed_time = (time - trained_timestamp) / 3600 # hours
|
||||
max_time = self.freqai_config.get('expiration_hours', 0)
|
||||
return elapsed_time > max_time
|
||||
|
||||
def check_if_new_training_required(self, trained_timestamp: int) -> Tuple[bool,
|
||||
TimeRange, TimeRange]:
|
||||
|
||||
@ -873,6 +879,8 @@ class FreqaiDataKitchen:
|
||||
|
||||
# check if newest candle is already appended
|
||||
df_dp = strategy.dp.get_pair_dataframe(pair, tf)
|
||||
if len(df_dp.index) == 0:
|
||||
continue
|
||||
if (
|
||||
str(history_data[pair][tf].iloc[-1]['date']) ==
|
||||
str(df_dp.iloc[-1:]['date'].iloc[-1])
|
||||
|
@ -8,6 +8,7 @@ from abc import ABC, abstractmethod
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, Tuple
|
||||
|
||||
import numpy as np
|
||||
import numpy.typing as npt
|
||||
import pandas as pd
|
||||
from pandas import DataFrame
|
||||
@ -65,6 +66,7 @@ class IFreqaiModel(ABC):
|
||||
self.identifier = self.freqai_info.get('identifier', 'no_id_provided')
|
||||
self.scanning = False
|
||||
self.ready_to_scan = False
|
||||
self.first = True
|
||||
|
||||
def assert_config(self, config: Dict[str, Any]) -> None:
|
||||
|
||||
@ -252,7 +254,7 @@ class IFreqaiModel(ABC):
|
||||
# # trained_timestamp=trained_timestamp,
|
||||
# # model_filename=model_filename)
|
||||
|
||||
(self.retrain,
|
||||
(_,
|
||||
new_trained_timerange,
|
||||
data_load_timerange) = dh.check_if_new_training_required(trained_timestamp)
|
||||
dh.set_paths(metadata['pair'], new_trained_timerange.stopts)
|
||||
@ -288,6 +290,7 @@ class IFreqaiModel(ABC):
|
||||
|
||||
# load the model and associated data into the data kitchen
|
||||
self.model = dh.load_data(coin=metadata['pair'])
|
||||
|
||||
if not self.model:
|
||||
logger.warning('No model ready, returning null values to strategy.')
|
||||
self.data_drawer.return_null_values_to_strategy(dataframe, dh)
|
||||
@ -296,22 +299,38 @@ class IFreqaiModel(ABC):
|
||||
# ensure user is feeding the correct indicators to the model
|
||||
self.check_if_feature_list_matches_strategy(dataframe, dh)
|
||||
|
||||
self.build_strategy_return_arrays(dataframe, dh, metadata['pair'], trained_timestamp)
|
||||
|
||||
return dh
|
||||
|
||||
def build_strategy_return_arrays(self, dataframe: DataFrame,
|
||||
dh: FreqaiDataKitchen, pair: str,
|
||||
trained_timestamp: int) -> None:
|
||||
|
||||
# hold the historical predictions in memory so we are sending back
|
||||
# correct array to strategy FIXME currently broken, but only affecting
|
||||
# Frequi reporting. Signals remain unaffeted.
|
||||
if metadata['pair'] not in self.data_drawer.model_return_values:
|
||||
|
||||
if pair not in self.data_drawer.model_return_values:
|
||||
preds, do_preds = self.predict(dataframe, dh)
|
||||
dh.append_predictions(preds, do_preds, len(dataframe))
|
||||
dh.fill_predictions(len(dataframe))
|
||||
self.data_drawer.set_initial_return_values(metadata['pair'], dh)
|
||||
self.data_drawer.set_initial_return_values(pair, dh)
|
||||
return
|
||||
elif self.dh.check_if_model_expired(trained_timestamp):
|
||||
preds, do_preds, dh.DI_values = np.zeros(2), np.ones(2) * 2, np.zeros(2)
|
||||
logger.warning('Model expired, returning null values to strategy. Strategy '
|
||||
'construction should take care to consider this event with '
|
||||
'prediction == 0 and do_predict == 2')
|
||||
else:
|
||||
preds, do_preds = self.predict(dataframe.iloc[-2:], dh)
|
||||
self.data_drawer.append_model_predictions(metadata['pair'], preds, do_preds,
|
||||
dh.data["target_mean"],
|
||||
dh.data["target_std"], dh,
|
||||
len(dataframe))
|
||||
|
||||
return dh
|
||||
self.data_drawer.append_model_predictions(pair, preds, do_preds,
|
||||
dh.data["target_mean"],
|
||||
dh.data["target_std"],
|
||||
dh,
|
||||
len(dataframe))
|
||||
return
|
||||
|
||||
def check_if_feature_list_matches_strategy(self, dataframe: DataFrame,
|
||||
dh: FreqaiDataKitchen) -> None:
|
||||
|
@ -24,7 +24,7 @@ class CatboostPredictionModel(IFreqaiModel):
|
||||
dataframe["do_predict"] = dh.full_do_predict
|
||||
dataframe["target_mean"] = dh.full_target_mean
|
||||
dataframe["target_std"] = dh.full_target_std
|
||||
if self.freqai_info.get('feature_parameters', {}).get('DI-threshold', 0) > 0:
|
||||
if self.freqai_info.get('feature_parameters', {}).get('DI_threshold', 0) > 0:
|
||||
dataframe["DI"] = dh.full_DI_values
|
||||
|
||||
return dataframe
|
||||
|
Loading…
Reference in New Issue
Block a user