Compare commits
13 Commits
develop
...
feat/convo
Author | SHA1 | Date | |
---|---|---|---|
|
6c96a2464f | ||
|
2c3a310ce2 | ||
|
71c6ff18c4 | ||
|
b438cd4b3f | ||
|
6343fbf9e3 | ||
|
389ab7e44b | ||
|
665eed3906 | ||
|
9ce8255f24 | ||
|
72b1d1c9ae | ||
|
5826fae8ee | ||
|
43c0d305a3 | ||
|
ad7729e5d8 | ||
|
57aaa390d0 |
@ -239,3 +239,20 @@ If you want to predict multiple targets you must specify all labels in the same
|
|||||||
df['&s-up_or_down'] = np.where( df["close"].shift(-100) > df["close"], 'up', 'down')
|
df['&s-up_or_down'] = np.where( df["close"].shift(-100) > df["close"], 'up', 'down')
|
||||||
df['&s-up_or_down'] = np.where( df["close"].shift(-100) == df["close"], 'same', df['&s-up_or_down'])
|
df['&s-up_or_down'] = np.where( df["close"].shift(-100) == df["close"], 'same', df['&s-up_or_down'])
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Convolutional Neural Network model
|
||||||
|
|
||||||
|
The `CNNPredictionModel` is a non-linear regression based on `Tensorflow` which follows very similar configuration to the other regressors. Feature engineering and label creation remains the same as highlighted [here](#building-a-freqai-strategy) and [here](#setting-model-targets). Control of the model is focused in the `model_training_parameters` configuration dictionary, which accepts any hyperparameter available to the CNN `fit()` function of Tensorflow [more here](https://www.tensorflow.org/api_docs/python/tf/keras/Model#fit). For example, this is where the `epochs` and `batch_size` are controlled:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"model_training_parameters" : {
|
||||||
|
"batch_size": 64,
|
||||||
|
"epochs": 10,
|
||||||
|
"verbose": "auto",
|
||||||
|
"shuffle": false,
|
||||||
|
"workers": 1,
|
||||||
|
"use_multiprocessing": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Running the `CNNPredictionModel` is the same as other regressors: `--freqaimodel CNNPredictionModel`.
|
||||||
|
@ -89,6 +89,6 @@ Mandatory parameters are marked as **Required** and have to be set in one of the
|
|||||||
| Parameter | Description |
|
| Parameter | Description |
|
||||||
|------------|-------------|
|
|------------|-------------|
|
||||||
| | **Extraneous parameters**
|
| | **Extraneous parameters**
|
||||||
| `freqai.keras` | If the selected model makes use of Keras (typical for Tensorflow-based prediction models), this flag needs to be activated so that the model save/loading follows Keras standards. <br> **Datatype:** Boolean. <br> Default: `False`.
|
| `freqai.keras` | If the selected model makes use of Keras (typical for Tensorflow-based prediction models), this flag should be activated so that the model save/loading follows Keras standards. If the the provided `CNNPredictionModel` is used, then this is handled automatically. <br> **Datatype:** Boolean. <br> Default: `False`.
|
||||||
| `freqai.conv_width` | The width of a convolutional neural network input tensor. This replaces the need for shifting candles (`include_shifted_candles`) by feeding in historical data points as the second dimension of the tensor. Technically, this parameter can also be used for regressors, but it only adds computational overhead and does not change the model training/prediction. <br> **Datatype:** Integer. <br> Default: `2`.
|
| `freqai.conv_width` | The width of a convolutional neural network input tensor. This replaces the need for shifting candles (`include_shifted_candles`) by feeding in historical data points as the second dimension of the tensor. Technically, this parameter can also be used for regressors, but it only adds computational overhead and does not change the model training/prediction. <br> **Datatype:** Integer. <br> Default: `2`.
|
||||||
| `freqai.reduce_df_footprint` | Recast all numeric columns to float32/int32, with the objective of reducing ram/disk usage and decreasing train/inference timing. This parameter is set in the main level of the Freqtrade configuration file (not inside FreqAI). <br> **Datatype:** Boolean. <br> Default: `False`.
|
| `freqai.reduce_df_footprint` | Recast all numeric columns to float32/int32, with the objective of reducing ram/disk usage and decreasing train/inference timing. This parameter is set in the main level of the Freqtrade configuration file (not inside FreqAI). <br> **Datatype:** Boolean. <br> Default: `False`.
|
||||||
|
@ -2,6 +2,8 @@ import logging
|
|||||||
from time import time
|
from time import time
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import tensorflow as tf
|
||||||
from pandas import DataFrame
|
from pandas import DataFrame
|
||||||
|
|
||||||
from freqtrade.freqai.data_kitchen import FreqaiDataKitchen
|
from freqtrade.freqai.data_kitchen import FreqaiDataKitchen
|
||||||
@ -17,6 +19,14 @@ class BaseTensorFlowModel(IFreqaiModel):
|
|||||||
User *must* inherit from this class and set fit() and predict().
|
User *must* inherit from this class and set fit() and predict().
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super().__init__(config=kwargs['config'])
|
||||||
|
self.keras = True
|
||||||
|
# if self.ft_params.get("DI_threshold", 0):
|
||||||
|
# self.ft_params["DI_threshold"] = 0
|
||||||
|
# logger.warning("DI threshold is not configured for Keras models yet. Deactivating.")
|
||||||
|
self.dd.model_type = 'keras'
|
||||||
|
|
||||||
def train(
|
def train(
|
||||||
self, unfiltered_df: DataFrame, pair: str, dk: FreqaiDataKitchen, **kwargs
|
self, unfiltered_df: DataFrame, pair: str, dk: FreqaiDataKitchen, **kwargs
|
||||||
) -> Any:
|
) -> Any:
|
||||||
@ -33,7 +43,6 @@ class BaseTensorFlowModel(IFreqaiModel):
|
|||||||
|
|
||||||
start_time = time()
|
start_time = time()
|
||||||
|
|
||||||
# filter the features requested by user in the configuration file and elegantly handle NaNs
|
|
||||||
features_filtered, labels_filtered = dk.filter_features(
|
features_filtered, labels_filtered = dk.filter_features(
|
||||||
unfiltered_df,
|
unfiltered_df,
|
||||||
dk.training_features_list,
|
dk.training_features_list,
|
||||||
@ -41,13 +50,9 @@ class BaseTensorFlowModel(IFreqaiModel):
|
|||||||
training_filter=True,
|
training_filter=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
start_date = unfiltered_df["date"].iloc[0].strftime("%Y-%m-%d")
|
|
||||||
end_date = unfiltered_df["date"].iloc[-1].strftime("%Y-%m-%d")
|
|
||||||
logger.info(f"-------------------- Training on data from {start_date} to "
|
|
||||||
f"{end_date} --------------------")
|
|
||||||
# split data into train/test data.
|
# split data into train/test data.
|
||||||
data_dictionary = dk.make_train_test_datasets(features_filtered, labels_filtered)
|
data_dictionary = dk.make_train_test_datasets(features_filtered, labels_filtered)
|
||||||
if not self.freqai_info.get("fit_live_predictions_candles", 0) or not self.live:
|
if not self.freqai_info.get("fit_live_predictions", 0) or not self.live:
|
||||||
dk.fit_labels()
|
dk.fit_labels()
|
||||||
# normalize all data based on train_dataset only
|
# normalize all data based on train_dataset only
|
||||||
data_dictionary = dk.normalize_data(data_dictionary)
|
data_dictionary = dk.normalize_data(data_dictionary)
|
||||||
@ -68,3 +73,76 @@ class BaseTensorFlowModel(IFreqaiModel):
|
|||||||
f"({end_time - start_time:.2f} secs) --------------------")
|
f"({end_time - start_time:.2f} secs) --------------------")
|
||||||
|
|
||||||
return model
|
return model
|
||||||
|
|
||||||
|
|
||||||
|
class WindowGenerator:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
input_width,
|
||||||
|
label_width,
|
||||||
|
shift,
|
||||||
|
train_df=None,
|
||||||
|
val_df=None,
|
||||||
|
test_df=None,
|
||||||
|
train_labels=None,
|
||||||
|
val_labels=None,
|
||||||
|
test_labels=None,
|
||||||
|
batch_size=None,
|
||||||
|
):
|
||||||
|
# Store the raw data.
|
||||||
|
self.train_df = train_df
|
||||||
|
self.val_df = val_df
|
||||||
|
self.test_df = test_df
|
||||||
|
self.train_labels = train_labels
|
||||||
|
self.val_labels = val_labels
|
||||||
|
self.test_labels = test_labels
|
||||||
|
self.batch_size = batch_size
|
||||||
|
self.input_width = input_width
|
||||||
|
self.label_width = label_width
|
||||||
|
self.shift = shift
|
||||||
|
self.total_window_size = input_width + shift
|
||||||
|
self.input_slice = slice(0, input_width)
|
||||||
|
self.input_indices = np.arange(self.total_window_size)[self.input_slice]
|
||||||
|
|
||||||
|
def make_dataset(self, data, labels=None):
|
||||||
|
data = np.array(data, dtype=np.float32)
|
||||||
|
if labels is not None:
|
||||||
|
labels = np.array(labels, dtype=np.float32)
|
||||||
|
ds = tf.keras.preprocessing.timeseries_dataset_from_array(
|
||||||
|
data=data,
|
||||||
|
targets=labels,
|
||||||
|
sequence_length=self.total_window_size,
|
||||||
|
sequence_stride=1,
|
||||||
|
sampling_rate=1,
|
||||||
|
shuffle=False,
|
||||||
|
batch_size=self.batch_size,
|
||||||
|
)
|
||||||
|
|
||||||
|
return ds
|
||||||
|
|
||||||
|
@property
|
||||||
|
def train(self):
|
||||||
|
return self.make_dataset(self.train_df, self.train_labels)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def val(self):
|
||||||
|
return self.make_dataset(self.val_df, self.val_labels)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def test(self):
|
||||||
|
return self.make_dataset(self.test_df, self.test_labels)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def inference(self):
|
||||||
|
return self.make_dataset(self.test_df)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def example(self):
|
||||||
|
"""Get and cache an example batch of `inputs, labels` for plotting."""
|
||||||
|
result = getattr(self, "_example", None)
|
||||||
|
if result is None:
|
||||||
|
# No example batch was found, so get one from the `.train` dataset
|
||||||
|
result = next(iter(self.train))
|
||||||
|
# And cache it for next time
|
||||||
|
self._example = result
|
||||||
|
return result
|
||||||
|
152
freqtrade/freqai/prediction_models/CNNPredictionModel.py
Normal file
152
freqtrade/freqai/prediction_models/CNNPredictionModel.py
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
import logging
|
||||||
|
from typing import Any, Dict, Tuple
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import tensorflow as tf
|
||||||
|
from pandas import DataFrame
|
||||||
|
from tensorflow.keras.layers import Conv1D, Dense, Input
|
||||||
|
from tensorflow.keras.models import Model
|
||||||
|
|
||||||
|
from freqtrade.exceptions import OperationalException
|
||||||
|
from freqtrade.freqai.base_models.BaseTensorFlowModel import BaseTensorFlowModel, WindowGenerator
|
||||||
|
from freqtrade.freqai.data_kitchen import FreqaiDataKitchen
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class CNNPredictionModel(BaseTensorFlowModel):
|
||||||
|
"""
|
||||||
|
User created prediction model. The class needs to override three necessary
|
||||||
|
functions, predict(), fit().
|
||||||
|
"""
|
||||||
|
|
||||||
|
def fit(self, data_dictionary: Dict[str, Any], dk: FreqaiDataKitchen) -> Any:
|
||||||
|
"""
|
||||||
|
User sets up the training and test data to fit their desired model here
|
||||||
|
:params:
|
||||||
|
:data_dictionary: the dictionary constructed by DataHandler to hold
|
||||||
|
all the training and test data/labels.
|
||||||
|
"""
|
||||||
|
train_df = data_dictionary["train_features"]
|
||||||
|
train_labels = data_dictionary["train_labels"]
|
||||||
|
test_df = data_dictionary["test_features"]
|
||||||
|
test_labels = data_dictionary["test_labels"]
|
||||||
|
n_labels = len(train_labels.columns)
|
||||||
|
|
||||||
|
if n_labels > 1:
|
||||||
|
raise OperationalException(
|
||||||
|
"Neural Net not yet configured for multi-targets. Please "
|
||||||
|
" reduce number of targets to 1 in strategy."
|
||||||
|
)
|
||||||
|
|
||||||
|
n_features = len(data_dictionary["train_features"].columns)
|
||||||
|
BATCH_SIZE = self.model_training_parameters.get("batch_size", 64)
|
||||||
|
|
||||||
|
# we need to remove batch_size from the model_training_params because
|
||||||
|
# we dont want fit() to get the incorrect assignment (we use the WindowGenerator)
|
||||||
|
# to handle our batches.
|
||||||
|
if 'batch_size' in self.model_training_parameters:
|
||||||
|
self.model_training_parameters.pop('batch_size')
|
||||||
|
input_dims = [BATCH_SIZE, self.CONV_WIDTH, n_features]
|
||||||
|
|
||||||
|
w1 = WindowGenerator(
|
||||||
|
input_width=self.CONV_WIDTH,
|
||||||
|
label_width=1,
|
||||||
|
shift=1,
|
||||||
|
train_df=train_df,
|
||||||
|
val_df=test_df,
|
||||||
|
train_labels=train_labels,
|
||||||
|
val_labels=test_labels,
|
||||||
|
batch_size=BATCH_SIZE,
|
||||||
|
)
|
||||||
|
|
||||||
|
model = self.create_model(input_dims, n_labels)
|
||||||
|
|
||||||
|
steps_per_epoch = np.ceil(len(test_df) / BATCH_SIZE)
|
||||||
|
lr_schedule = tf.keras.optimizers.schedules.InverseTimeDecay(
|
||||||
|
0.001, decay_steps=steps_per_epoch * 1000, decay_rate=1, staircase=False
|
||||||
|
)
|
||||||
|
|
||||||
|
early_stopping = tf.keras.callbacks.EarlyStopping(
|
||||||
|
monitor="loss", patience=3, mode="min", min_delta=0.0001
|
||||||
|
)
|
||||||
|
|
||||||
|
model.compile(
|
||||||
|
loss=tf.losses.MeanSquaredError(),
|
||||||
|
optimizer=tf.optimizers.Adam(lr_schedule),
|
||||||
|
metrics=[tf.metrics.MeanAbsoluteError()],
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.freqai_info.get('data_split_parameters', {}).get('test_size', 0.1) == 0:
|
||||||
|
val_data = None
|
||||||
|
else:
|
||||||
|
val_data = w1.val
|
||||||
|
|
||||||
|
model.fit(
|
||||||
|
w1.train,
|
||||||
|
validation_data=val_data,
|
||||||
|
callbacks=[early_stopping],
|
||||||
|
**self.model_training_parameters,
|
||||||
|
)
|
||||||
|
|
||||||
|
return model
|
||||||
|
|
||||||
|
def predict(
|
||||||
|
self, unfiltered_dataframe: DataFrame, dk: FreqaiDataKitchen, first=True
|
||||||
|
) -> Tuple[DataFrame, DataFrame]:
|
||||||
|
"""
|
||||||
|
Filter the prediction features data and predict with it.
|
||||||
|
:param: unfiltered_dataframe: Full dataframe for the current backtest period.
|
||||||
|
:return:
|
||||||
|
:predictions: np.array of predictions
|
||||||
|
:do_predict: np.array of 1s and 0s to indicate places where freqai needed to remove
|
||||||
|
data (NaNs) or felt uncertain about data (PCA and DI index)
|
||||||
|
"""
|
||||||
|
|
||||||
|
dk.find_features(unfiltered_dataframe)
|
||||||
|
filtered_dataframe, _ = dk.filter_features(
|
||||||
|
unfiltered_dataframe, dk.training_features_list, training_filter=False
|
||||||
|
)
|
||||||
|
filtered_dataframe = dk.normalize_data_from_metadata(filtered_dataframe)
|
||||||
|
dk.data_dictionary["prediction_features"] = filtered_dataframe
|
||||||
|
|
||||||
|
# optional additional data cleaning/analysis
|
||||||
|
self.data_cleaning_predict(dk)
|
||||||
|
|
||||||
|
if first:
|
||||||
|
full_df = dk.data_dictionary["prediction_features"]
|
||||||
|
|
||||||
|
w1 = WindowGenerator(
|
||||||
|
input_width=self.CONV_WIDTH,
|
||||||
|
label_width=1,
|
||||||
|
shift=1,
|
||||||
|
test_df=full_df,
|
||||||
|
batch_size=len(full_df),
|
||||||
|
)
|
||||||
|
|
||||||
|
predictions = self.model.predict(w1.inference)
|
||||||
|
len_diff = len(dk.do_predict) - len(predictions)
|
||||||
|
if len_diff > 0:
|
||||||
|
dk.do_predict = dk.do_predict[len_diff:]
|
||||||
|
|
||||||
|
else:
|
||||||
|
data = dk.data_dictionary["prediction_features"]
|
||||||
|
data = tf.expand_dims(data, axis=0)
|
||||||
|
data = tf.convert_to_tensor(data)
|
||||||
|
predictions = self.model(data, training=False)
|
||||||
|
|
||||||
|
predictions = predictions[:, 0, 0]
|
||||||
|
pred_df = DataFrame(predictions, columns=dk.label_list)
|
||||||
|
|
||||||
|
pred_df = dk.denormalize_labels_from_metadata(pred_df)
|
||||||
|
|
||||||
|
return (pred_df, np.ones(len(pred_df)))
|
||||||
|
|
||||||
|
def create_model(self, input_dims, n_labels) -> Any:
|
||||||
|
|
||||||
|
input_layer = Input(shape=(input_dims[1], input_dims[2]))
|
||||||
|
Layer_1 = Conv1D(filters=32, kernel_size=(self.CONV_WIDTH,), activation="relu")(input_layer)
|
||||||
|
Layer_3 = Dense(units=32, activation="relu")(Layer_1)
|
||||||
|
output_layer = Dense(units=n_labels)(Layer_3)
|
||||||
|
return Model(inputs=input_layer, outputs=output_layer)
|
@ -9,3 +9,4 @@ catboost==1.1.1; platform_machine != 'aarch64'
|
|||||||
lightgbm==3.3.3
|
lightgbm==3.3.3
|
||||||
xgboost==1.7.2
|
xgboost==1.7.2
|
||||||
tensorboard==2.11.0
|
tensorboard==2.11.0
|
||||||
|
tensorflow==2.11.0
|
||||||
|
@ -34,7 +34,8 @@ def is_mac() -> bool:
|
|||||||
('CatboostRegressor', False, False, False),
|
('CatboostRegressor', False, False, False),
|
||||||
('ReinforcementLearner', False, True, False),
|
('ReinforcementLearner', False, True, False),
|
||||||
('ReinforcementLearner_multiproc', False, False, False),
|
('ReinforcementLearner_multiproc', False, False, False),
|
||||||
('ReinforcementLearner_test_4ac', False, False, False)
|
('ReinforcementLearner_test_4ac', False, False, False),
|
||||||
|
('CNNPredictionModel', False, False, False)
|
||||||
])
|
])
|
||||||
def test_extract_data_and_train_model_Standard(mocker, freqai_conf, model, pca, dbscan, float32):
|
def test_extract_data_and_train_model_Standard(mocker, freqai_conf, model, pca, dbscan, float32):
|
||||||
if is_arm() and model == 'CatboostRegressor':
|
if is_arm() and model == 'CatboostRegressor':
|
||||||
@ -71,6 +72,10 @@ def test_extract_data_and_train_model_Standard(mocker, freqai_conf, model, pca,
|
|||||||
if 'test_4ac' in model:
|
if 'test_4ac' in model:
|
||||||
freqai_conf["freqaimodel_path"] = str(Path(__file__).parents[1] / "freqai" / "test_models")
|
freqai_conf["freqaimodel_path"] = str(Path(__file__).parents[1] / "freqai" / "test_models")
|
||||||
|
|
||||||
|
if 'CNNPredictionModel' in model:
|
||||||
|
freqai_conf['freqai']['model_training_parameters'].pop('n_estimators')
|
||||||
|
model_save_ext = 'h5'
|
||||||
|
|
||||||
strategy = get_patched_freqai_strategy(mocker, freqai_conf)
|
strategy = get_patched_freqai_strategy(mocker, freqai_conf)
|
||||||
exchange = get_patched_exchange(mocker, freqai_conf)
|
exchange = get_patched_exchange(mocker, freqai_conf)
|
||||||
strategy.dp = DataProvider(freqai_conf, exchange)
|
strategy.dp = DataProvider(freqai_conf, exchange)
|
||||||
|
Loading…
Reference in New Issue
Block a user