From d3d7184ea445c752588b202c1dd800eaf8a9eaca Mon Sep 17 00:00:00 2001 From: Mark Aron Szulyovszky Date: Tue, 21 Dec 2021 15:57:08 +0100 Subject: [PATCH] feat(Models): added StaticAverageModel for average ensembling & StaticNaiveModel (#64) * feat(Models): added StaticAverageModel for average ensembling * feat(Models): made sure we only pipe in predictions to StaticAverageModel, added StaticNaiveModel as potential baseline * chore(Models): removed unnecessary commented out code --- models/average.py | 24 ++++++++++++++++++++++++ models/base.py | 6 +++++- models/momentum.py | 2 +- models/naive.py | 21 +++++++++++++++++++++ run_pipeline.py | 17 +++++++++++------ training/pipeline.py | 3 ++- 6 files changed, 64 insertions(+), 9 deletions(-) create mode 100644 models/average.py create mode 100644 models/naive.py diff --git a/models/average.py b/models/average.py new file mode 100644 index 0000000..8d7ddf1 --- /dev/null +++ b/models/average.py @@ -0,0 +1,24 @@ +from models.base import Model +import numpy as np + +class StaticAverageModel(Model): + ''' + Model that averages . + ''' + + # data_format = 'dataframe' + data_scaling = 'unscaled' + only_column = 'model_' + + def fit(self, X, y): + # This is a static model, it can' learn anything + pass + + def predict(self, X): + # Make sure there's data to average + assert X.shape[1] > 0 + prediction = np.average(X[0]) + return np.array([prediction]) + + def clone(self): + return self \ No newline at end of file diff --git a/models/base.py b/models/base.py index 75a99e1..c44398e 100644 --- a/models/base.py +++ b/models/base.py @@ -1,20 +1,24 @@ from typing import Literal, Optional from sklearn.base import clone +from abc import ABC, abstractmethod, abstractproperty -class Model: +class Model(ABC): # data_format: Literal['dataframe', 'numpy'] data_scaling: Literal["scaled", "unscaled"] # data_format: Literal["wide", "narrow"] only_column: Optional[str] + @abstractmethod def fit(self, X, y): pass + @abstractmethod def predict(self, X): pass + @abstractmethod def clone(self): pass diff --git a/models/momentum.py b/models/momentum.py index 8cf8688..e3a6ee9 100644 --- a/models/momentum.py +++ b/models/momentum.py @@ -6,7 +6,7 @@ class StaticMomentumModel(Model): Model that uses only one feature: momentum. It's positive if momentum is greater than 0, otherwise it's negative. ''' - data_format = 'dataframe' + # data_format = 'dataframe' data_scaling = 'unscaled' only_column = 'mom' diff --git a/models/naive.py b/models/naive.py new file mode 100644 index 0000000..e578c2d --- /dev/null +++ b/models/naive.py @@ -0,0 +1,21 @@ +from models.base import Model +import numpy as np + +class StaticNaiveModel(Model): + ''' + Model that carries the last observation (from returns) to the next one, naively. + ''' + + # data_format = 'dataframe' + data_scaling = 'unscaled' + only_column = None + + def fit(self, X, y): + # This is a static model, it can' learn anything + pass + + def predict(self, X): + return np.array([X[-1][0]]) + + def clone(self): + return self \ No newline at end of file diff --git a/run_pipeline.py b/run_pipeline.py index e355f79..37dca91 100644 --- a/run_pipeline.py +++ b/run_pipeline.py @@ -15,6 +15,8 @@ from sklearn.neural_network import MLPRegressor, MLPClassifier from sklearn.ensemble import AdaBoostRegressor, RandomForestRegressor, ExtraTreesRegressor, AdaBoostClassifier, GradientBoostingClassifier, RandomForestClassifier, ExtraTreesClassifier from models.base import SKLearnModel from models.momentum import StaticMomentumModel +from models.average import StaticAverageModel +from models.naive import StaticNaiveModel import feature_extractors.feature_extractor_presets as feature_extractor_presets from training.pipeline import run_single_asset_trainig_pipeline @@ -53,19 +55,22 @@ def get_config() -> tuple[dict, dict, dict]: # ('RF', SKLearnModel(RandomForestRegressor(n_jobs=-1))), # ('SVR', SKLearnModel(SVR(kernel='rbf', C=1e3, gamma=0.1))) ] - regression_ensemble_model = [('Ensemble - Ridge', SKLearnModel(Ridge(alpha=0.1)))] + regression_ensemble_model = [('Ensemble - Average', StaticAverageModel())] + # regression_ensemble_model = [('Ensemble - Ridge', SKLearnModel(Ridge(alpha=0.1)))] classification_models = [ ('LR', SKLearnModel(LogisticRegression(n_jobs=-1))), - # ('LDA', SKLearnModel(LinearDiscriminantAnalysis())), - # ('KNN', SKLearnModel(KNeighborsClassifier())), - # ('CART', SKLearnModel(DecisionTreeClassifier())), - ('StaticMomentum', StaticMomentumModel(allow_short=True)) + ('LDA', SKLearnModel(LinearDiscriminantAnalysis())), + ('KNN', SKLearnModel(KNeighborsClassifier())), + ('CART', SKLearnModel(DecisionTreeClassifier())), + ('StaticMomentum', StaticMomentumModel(allow_short=True)), + # ('StaticNaive', StaticNaiveModel()), # ('NB', SKLearnModel(GaussianNB())), # ('AB', SKLearnModel(AdaBoostClassifier())), # ('RF', SKLearnModel(RandomForestClassifier(n_jobs=-1))) ] - classification_ensemble_model = [('Ensemble - CART', SKLearnModel(DecisionTreeClassifier()))] + classification_ensemble_model = [('Ensemble - Average', StaticAverageModel())] + # classification_ensemble_model = [('Ensemble - CART', SKLearnModel(DecisionTreeClassifier()))] model_config = dict( level_1_models = regression_models if data_config['method'] == 'regression' else classification_models, diff --git a/training/pipeline.py b/training/pipeline.py index 3c2f428..e18abd3 100644 --- a/training/pipeline.py +++ b/training/pipeline.py @@ -56,7 +56,8 @@ def run_single_asset_trainig_pipeline( ) column_name = ticker_to_predict + "_" + model_name results[column_name] = result - predictions[column_name] = preds + # column names for model outputs should be different, so we can differentiate between original data and model predictions later, where necessary + predictions["model_" + column_name] = preds if wandb_active: run = wandb.init(project="price-forecasting", config={"model_type": model_name, "ticker": ticker_to_predict}, reinit=True)