feat(Model): added own Model class, SkLearnModel wrapper and StaticMomentumModel (#61)

* feat(Model): added own `Model` class, SkLearnModel wrapper and StaticMomentumModel

* fix(Tests): added missing Model variable

* fix(Tests): added missing clone method()
This commit is contained in:
Mark Aron Szulyovszky
2021-12-21 10:30:09 +01:00
committed by GitHub
parent 85ad937078
commit 79d84cf0a3
8 changed files with 122 additions and 45 deletions
+38
View File
@@ -0,0 +1,38 @@
from typing import Literal, Optional
from sklearn.base import clone
class Model:
# data_format: Literal['dataframe', 'numpy']
data_scaling: Literal["scaled", "unscaled"]
# data_format: Literal["wide", "narrow"]
only_column: Optional[str]
def fit(self, X, y):
pass
def predict(self, X):
pass
def clone(self):
pass
class SKLearnModel(Model):
# data_format = 'numpy'
data_scaling = 'scaled'
only_column = None
def __init__(self, model):
self.model = model
def fit(self, X, y):
self.model.fit(X, y)
def predict(self, X):
return self.model.predict(X)
def clone(self):
return SKLearnModel(clone(self.model))
+27
View File
@@ -0,0 +1,27 @@
from models.base import Model
import numpy as np
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_scaling = 'unscaled'
only_column = 'mom'
def __init__(self, allow_short: bool) -> None:
super().__init__()
self.allow_short = allow_short
def fit(self, X, y):
# This is a static model, it can' learn anything
pass
def predict(self, X):
negative_class = -1.0 if self.allow_short == True else 0.0
prediction = 1.0 if X[-1][0] > 0 else negative_class
return np.array([prediction])
def clone(self):
return self
+25 -24
View File
@@ -13,21 +13,22 @@ from sklearn.svm import SVR
from sklearn.naive_bayes import GaussianNB from sklearn.naive_bayes import GaussianNB
from sklearn.neural_network import MLPRegressor, MLPClassifier from sklearn.neural_network import MLPRegressor, MLPClassifier
from sklearn.ensemble import AdaBoostRegressor, RandomForestRegressor, ExtraTreesRegressor, AdaBoostClassifier, GradientBoostingClassifier, RandomForestClassifier, ExtraTreesClassifier from sklearn.ensemble import AdaBoostRegressor, RandomForestRegressor, ExtraTreesRegressor, AdaBoostClassifier, GradientBoostingClassifier, RandomForestClassifier, ExtraTreesClassifier
from models.base import SKLearnModel
from models.momentum import StaticMomentumModel
import feature_extractors.feature_extractor_presets as feature_extractor_presets import feature_extractors.feature_extractor_presets as feature_extractor_presets
from training.pipeline import run_single_asset_trainig_pipeline from training.pipeline import run_single_asset_trainig_pipeline
from typing import Tuple
def get_config()->Tuple[dict, dict, dict]: def get_config() -> tuple[dict, dict, dict]:
training_config = dict( training_config = dict(
sliding_window_size = 150, sliding_window_size = 150,
retrain_every = 20, retrain_every = 20,
scaler = 'minmax', # 'normalize' 'minmax' 'standardize' 'none' scaler = 'minmax', # 'normalize' 'minmax' 'standardize' 'none'
include_original_data_in_ensemble = True, include_original_data_in_ensemble = True,
) )
data_config = dict( data_config = dict(
path='data/', path='data/',
@@ -41,30 +42,30 @@ def get_config()->Tuple[dict, dict, dict]:
method= 'classification', method= 'classification',
) )
classification_models = [
('LR', LogisticRegression(n_jobs=-1)),
# ('LDA', LinearDiscriminantAnalysis()),
('KNN', KNeighborsClassifier()),
# ('CART', DecisionTreeClassifier()),
# ('NB', GaussianNB()),
# ('AB', AdaBoostClassifier()),
# ('RF', RandomForestClassifier(n_jobs=-1))
]
regression_models = [ regression_models = [
# ('Lasso', Lasso(alpha=0.1, max_iter=1000)), # ('Lasso', SKLearnModel(Lasso(alpha=0.1, max_iter=1000))),
('Ridge', Ridge(alpha=0.1)), ('Ridge', SKLearnModel(Ridge(alpha=0.1))),
('BayesianRidge', BayesianRidge()), ('BayesianRidge', SKLearnModel(BayesianRidge())),
# ('KNN', KNeighborsRegressor(n_neighbors=25)), # ('KNN', SKLearnModel(KNeighborsRegressor(n_neighbors=25))),
# ('AB', AdaBoostRegressor(random_state=1)), # ('AB', SKLearnModel(AdaBoostRegressor(random_state=1))),
# ('LR', LinearRegression(n_jobs=-1)), # ('LR', SKLearnModel(LinearRegression(n_jobs=-1))),
# ('MLP', MLPRegressor(hidden_layer_sizes=(100,20), max_iter=1000)), # ('MLP', SKLearnModel(MLPRegressor(hidden_layer_sizes=(100,20), max_iter=1000))),
# ('RF', RandomForestRegressor(n_jobs=-1)), # ('RF', SKLearnModel(RandomForestRegressor(n_jobs=-1))),
# ('SVR', SVR(kernel='rbf', C=1e3, gamma=0.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 - Ridge', Ridge(alpha=0.1))] classification_models = [
classification_ensemble_model = [('Ensemble - CART', DecisionTreeClassifier())] ('LR', SKLearnModel(LogisticRegression(n_jobs=-1))),
# ('LDA', SKLearnModel(LinearDiscriminantAnalysis())),
# ('KNN', SKLearnModel(KNeighborsClassifier())),
# ('CART', SKLearnModel(DecisionTreeClassifier())),
('StaticMomentum', StaticMomentumModel(allow_short=True))
# ('NB', SKLearnModel(GaussianNB())),
# ('AB', SKLearnModel(AdaBoostClassifier())),
# ('RF', SKLearnModel(RandomForestClassifier(n_jobs=-1)))
]
classification_ensemble_model = [('Ensemble - CART', SKLearnModel(DecisionTreeClassifier()))]
model_config = dict( model_config = dict(
level_1_models = regression_models if data_config['method'] == 'regression' else classification_models, level_1_models = regression_models if data_config['method'] == 'regression' else classification_models,
+8 -2
View File
@@ -2,7 +2,7 @@
import numpy as np import numpy as np
import pandas as pd import pandas as pd
from training.walk_forward import walk_forward_train_test from training.walk_forward import walk_forward_train_test
from sklearn.base import BaseEstimator from models.base import Model
from utils.evaluate import evaluate_predictions from utils.evaluate import evaluate_predictions
no_of_rows = 100 no_of_rows = 100
@@ -29,12 +29,15 @@ def __generate_even_odd_test_data(no_of_rows) -> tuple[pd.DataFrame, pd.Series]:
return X, y return X, y
class EvenOddStubModel(BaseEstimator): class EvenOddStubModel(Model):
''' '''
A deteministic model that can predict the future with 100% accuracy A deteministic model that can predict the future with 100% accuracy
It verifies that the X[n][any_column] == 1 if n is even, It verifies that the X[n][any_column] == 1 if n is even,
''' '''
data_scaling = "unscaled"
only_column = None
def __init__(self, window_length) -> None: def __init__(self, window_length) -> None:
super().__init__() super().__init__()
self.window_length = window_length self.window_length = window_length
@@ -47,6 +50,9 @@ class EvenOddStubModel(BaseEstimator):
def predict(self, X): def predict(self, X):
return np.array([-1 if X[0][0] == 1 else 1]) return np.array([-1 if X[0][0] == 1 else 1])
def clone(self):
return self
def test_evaluation(): def test_evaluation():
X, y = __generate_even_odd_test_data(no_of_rows) X, y = __generate_even_odd_test_data(no_of_rows)
+7 -2
View File
@@ -1,7 +1,7 @@
import numpy as np import numpy as np
import pandas as pd import pandas as pd
from training.walk_forward import walk_forward_train_test from training.walk_forward import walk_forward_train_test
from sklearn.base import BaseEstimator from models.base import Model
no_of_rows = 100 no_of_rows = 100
@@ -27,12 +27,15 @@ def __generate_incremental_test_data(no_of_rows) -> tuple[pd.DataFrame, pd.Serie
class IncrementingStubModel(BaseEstimator): class IncrementingStubModel(Model):
''' '''
A deteministic model that can predict the future with 100% accuracy A deteministic model that can predict the future with 100% accuracy
It verifies that the X[n][any_column]+1 == y[n] It verifies that the X[n][any_column]+1 == y[n]
''' '''
data_scaling = "unscaled"
only_column = None
def __init__(self, window_length) -> None: def __init__(self, window_length) -> None:
super().__init__() super().__init__()
self.window_length = window_length self.window_length = window_length
@@ -45,6 +48,8 @@ class IncrementingStubModel(BaseEstimator):
def predict(self, X): def predict(self, X):
return np.array([X[0][0] + 1]) return np.array([X[0][0] + 1])
def clone(self):
return self
def test_walk_forward_train_test(): def test_walk_forward_train_test():
X, y = __generate_incremental_test_data(no_of_rows) X, y = __generate_incremental_test_data(no_of_rows)
+2 -2
View File
@@ -3,7 +3,7 @@ from typing import Literal
from training.walk_forward import walk_forward_train_test from training.walk_forward import walk_forward_train_test
from sklearn.preprocessing import MinMaxScaler, Normalizer, StandardScaler from sklearn.preprocessing import MinMaxScaler, Normalizer, StandardScaler
from utils.evaluate import evaluate_predictions from utils.evaluate import evaluate_predictions
from utils.typing import SKLearnModel from models.base import Model
def __get_scaler(type: Literal['normalize', 'minmax', 'standardize', 'none']): def __get_scaler(type: Literal['normalize', 'minmax', 'standardize', 'none']):
if type == 'normalize': if type == 'normalize':
@@ -20,7 +20,7 @@ def run_single_asset_trainig_pipeline(
X: pd.DataFrame, X: pd.DataFrame,
y: pd.Series, y: pd.Series,
target_returns: pd.Series, target_returns: pd.Series,
models: list[tuple[str, SKLearnModel]], models: list[tuple[str, Model]],
method: Literal['regression', 'classification'], method: Literal['regression', 'classification'],
sliding_window_size: int, sliding_window_size: int,
retrain_every: int, retrain_every: int,
+14 -8
View File
@@ -1,12 +1,13 @@
import pandas as pd import pandas as pd
from sklearn.base import clone from models.base import Model
from utils.typing import SKLearnModel
import numpy as np import numpy as np
from utils.helpers import get_first_valid_return_index from utils.helpers import get_first_valid_return_index
from sklearn.base import clone
def walk_forward_train_test( def walk_forward_train_test(
model_name: str, model_name: str,
model: SKLearnModel, model: Model,
X: pd.DataFrame, X: pd.DataFrame,
y: pd.Series, y: pd.Series,
target_returns: pd.Series, target_returns: pd.Series,
@@ -23,7 +24,12 @@ def walk_forward_train_test(
train_till = len(y) train_till = len(y)
iterations_before_retrain = 0 iterations_before_retrain = 0
if scaler is not None: if model.only_column is not None:
X = X[[column for column in X.columns if model.only_column in column]]
is_scaling_on = scaler is not None and model.data_scaling == 'scaled'
if is_scaling_on:
scaler = clone(scaler) scaler = clone(scaler)
for index in range(train_from, train_till): for index in range(train_from, train_till):
@@ -32,7 +38,7 @@ def walk_forward_train_test(
train_window_start = index - window_size - 1 train_window_start = index - window_size - 1
train_window_end = index - 1 train_window_end = index - 1
if scaler is not None: if is_scaling_on:
# First we need to fit on the expanding window data slice # First we need to fit on the expanding window data slice
# This is our only way to avoid lookahead bia # This is our only way to avoid lookahead bia
X_expanding_window = X[first_nonzero_return:train_window_end] X_expanding_window = X[first_nonzero_return:train_window_end]
@@ -41,12 +47,12 @@ def walk_forward_train_test(
X_slice = X[train_window_start:train_window_end] X_slice = X[train_window_start:train_window_end]
y_slice = y[train_window_start:train_window_end] y_slice = y[train_window_start:train_window_end]
if scaler is not None: if is_scaling_on:
X_slice = scaler.transform(X_slice.values) X_slice = scaler.transform(X_slice.values)
else: else:
X_slice = X_slice.to_numpy() X_slice = X_slice.to_numpy()
current_model = clone(model) current_model = model.clone()
current_model.fit(X_slice, y_slice.to_numpy()) current_model.fit(X_slice, y_slice.to_numpy())
iterations_before_retrain = retrain_every iterations_before_retrain = retrain_every
else: else:
@@ -55,7 +61,7 @@ def walk_forward_train_test(
models[index] = current_model models[index] = current_model
next_timestep = X.iloc[index].to_numpy().reshape(1, -1) next_timestep = X.iloc[index].to_numpy().reshape(1, -1)
if scaler is not None: if is_scaling_on:
next_timestep = scaler.transform(next_timestep) next_timestep = scaler.transform(next_timestep)
prediction = current_model.predict(next_timestep).item() prediction = current_model.predict(next_timestep).item()
+1 -7
View File
@@ -1,12 +1,6 @@
from typing import Protocol, Callable, Union from typing import Callable, Union
import pandas as pd import pandas as pd
class SKLearnModel(Protocol):
def fit(self, X, y, sample_weight=None): ...
def predict(self, X): ...
def score(self, X, y, sample_weight=None): ...
def set_params(self, **params): ...
Period = int Period = int
IsLogReturn = bool IsLogReturn = bool