From e6700bea11f065d876fef2448105e8e7db06a2cb Mon Sep 17 00:00:00 2001 From: Alexandre Brilhante Date: Sat, 8 Jul 2023 09:41:24 -0400 Subject: [PATCH] refactored main classes, add base runner --- .github/workflow/python-publish.yml | 31 +++ .gitignore | 2 +- LICENSE.md | 2 +- README.md | 7 +- quantnet/app.py | 238 ++++++++++++++++++ quantnet/{baseline.py => baselines.py} | 88 +++---- quantnet/global/__init__.py | 0 quantnet/no_transfer/linear.py | 6 +- .../{global => no_transfer}/linear_linear.py | 13 +- .../{global => no_transfer}/linear_lstm.py | 9 +- quantnet/no_transfer/lstm.py | 9 +- .../{global => no_transfer}/lstm_linear.py | 7 +- quantnet/{global => no_transfer}/lstm_lstm.py | 9 +- quantnet/quantnet.py | 9 - quantnet/utils.py | 7 +- requirements.txt | 38 +-- setup.py | 2 +- 17 files changed, 375 insertions(+), 102 deletions(-) create mode 100644 .github/workflow/python-publish.yml create mode 100644 quantnet/app.py rename quantnet/{baseline.py => baselines.py} (70%) delete mode 100644 quantnet/global/__init__.py rename quantnet/{global => no_transfer}/linear_linear.py (94%) rename quantnet/{global => no_transfer}/linear_lstm.py (95%) rename quantnet/{global => no_transfer}/lstm_linear.py (95%) rename quantnet/{global => no_transfer}/lstm_lstm.py (95%) delete mode 100644 quantnet/quantnet.py diff --git a/.github/workflow/python-publish.yml b/.github/workflow/python-publish.yml new file mode 100644 index 0000000..c26dbb9 --- /dev/null +++ b/.github/workflow/python-publish.yml @@ -0,0 +1,31 @@ +name: Upload Python Package + +on: + release: + types: [published] + +permissions: + contents: read + +jobs: + deploy: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v3 + with: + python-version: '3.x' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install build + - name: Build package + run: python -m build + - name: Publish package + uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 + with: + user: __token__ + password: ${{ secrets.PYPI_API_TOKEN }} diff --git a/.gitignore b/.gitignore index 5391d87..b3ac529 100644 --- a/.gitignore +++ b/.gitignore @@ -48,7 +48,7 @@ coverage.xml *.cover *.py,cover .hypothesis/ -.pytest_cache/ +.pY_test_cache/ cover/ # Translations diff --git a/LICENSE.md b/LICENSE.md index a468528..54ff806 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,4 +1,4 @@ -Copyright (c) 2021 Alexandre Brilhante +Copyright (c) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/README.md b/README.md index 9e0e448..6e35a72 100644 --- a/README.md +++ b/README.md @@ -3,4 +3,9 @@ ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/quantnet) ![GitHub](https://img.shields.io/github/license/brilhana/quantnet) -A PyTorch implementation of [QuantNet: Transferring Learning Across Systematic Trading Strategies](https://arxiv.org/abs/2004.03445). \ No newline at end of file +A PyTorch implementation of [QuantNet: Transferring Learning Across Systematic Trading Strategies](https://arxiv.org/abs/2004.03445). + +## Installation +```bash +pip install quantnet +``` diff --git a/quantnet/app.py b/quantnet/app.py new file mode 100644 index 0000000..1253ca6 --- /dev/null +++ b/quantnet/app.py @@ -0,0 +1,238 @@ +import random +import time + +import numpy as np +import pandas as pd + +from quantnet.baselines import ( + BuyAndHold, + CrossSectionalMomentum, + RiskParity, + TimeSeriesMomentum, +) +from quantnet.utils import calc_transaction_costs + + +class QuantNet: + def __init__(self, seed=999999999): + np.random.seed(seed) + random.seed(seed) + + self.data_config = { + "data_path": ".\\Tasks\\", + "region": ["Asia and Pacific", "Europe", "Americas", "MEA"], + "Europe": [ + "Europe_AEX", + "Europe_ASE", + "Europe_ATX", + "Europe_BEL20", + "Europe_BUX", + "Europe_BVLX", + "Europe_CAC", + "Europe_CYSMMAPA", + "Europe_DAX", + "Europe_HEX", + "Europe_IBEX", + "Europe_ISEQ", + "Europe_KFX", + "Europe_OBX", + "Europe_OMX", + "Europe_SMI", + "Europe_UKX", + "Europe_VILSE", + "Europe_WIG20", + "Europe_XU100", + "Europe_SOFIX", + "Europe_SBITOP", + "Europe_PX", + "Europe_CRO", + ], + "Asia and Pacific": [ + "Asia and Pacific_AS51", + "Asia and Pacific_FBMKLCI", + "Asia and Pacific_HSI", + "Asia and Pacific_JCI", + "Asia and Pacific_KOSPI", + "Asia and Pacific_KSE100", + "Asia and Pacific_NIFTY", + "Asia and Pacific_NKY", + "Asia and Pacific_NZSE50FG", + "Asia and Pacific_PCOMP", + "Asia and Pacific_STI", + "Asia and Pacific_SHSZ300", + "Asia and Pacific_TWSE", + ], + "Americas": [ + "Americas_IBOV", + "Americas_MEXBOL", + "Americas_MERVAL", + "Americas_SPTSX", + "Americas_SPX", + "Americas_RTY", + ], + "MEA": [ + "MEA_DFMGI", + "MEA_DSM", + "MEA_EGX30", + "MEA_FTN098", + "MEA_JOSMGNFF", + "MEA_KNSMIDX", + "MEA_KWSEPM", + "MEA_MOSENEW", + "MEA_MSM30", + "MEA_NGSE30", + "MEA_PASISI", + "MEA_SASEIDX", + "MEA_SEMDEX", + "MEA_TA-35", + "MEA_TOP40", + ], + "additional_data_path": "_all_assets_data.pkl.gz", + } + + self.problem_config = { + "export_path": "./Results/", + "val_period": 0, + "holdout_period": 756, + } + + self.model_config = { + "baseline": "risk_parity", + "buy_and_hold": {}, + "risk_parity": {"window": 252}, + "ts_mom": {"window": 252}, + "csec_mom": {"window": 252, "fraction": 0.33}, + } + + self.export_label = ( + "val_period_" + + str(self.problem_config["val_period"]) + + "_testperiod_" + + str(self.problem_config["holdout_period"]) + + "_baseline_" + + self.model_config["baseline"] + ) + + self.data_config["export_label"] = self.export_label + self.problem_config["export_label"] = self.export_label + self.model_config["export_label"] = self.export_label + self.model_config["export_path"] = self.problem_config["export_path"] + + self.X_train_tasks, self.X_val_tasks, self.X_test_tasks = self.get_data( + self.data_config, self.problem_config, self.model_config + ) + + def train(self): + if self.model_config["baseline"] == "buy_and_hold": + trad_strat = BuyAndHold(self.X_train_tasks, self.model_config) + add_label = [""] * len(self.data_config["region"]) + + elif self.model_config["baseline"] == "risk_parity": + trad_strat = RiskParity(self.X_train_tasks, self.model_config) + add_label = [""] * len(self.data_config["region"]) + + elif self.model_config["baseline"] == "ts_mom": + trad_strat = TimeSeriesMomentum(self.X_train_tasks, self.model_config) + add_label = [""] * len(self.data_config["region"]) + + elif self.model_config["baseline"] == "csec_mom": + trad_strat = CrossSectionalMomentum(self.X_train_tasks, self.model_config) + add_label = [""] * len(self.data_config["region"]) + + to_add_label = {} + + for lab, region in zip(add_label, self.data_config["region"]): + to_add_label[region] = lab + + start = time() + trad_strat.train() + + print(time() - start) + + self.X_train_signal = trad_strat.predict(self.X_train_tasks) + self.X_val_signal = trad_strat.predict(self.X_val_tasks) + self.X_test_signal = trad_strat.predict(self.X_test_tasks) + + def predict(self): + results = True + + for region in self.data_config["region"]: + region_task_paths = [ + t + "_all_assets_data.pkl.gz" for t in self.data_config[region] + ] + + metrics = True + + for tk, tk_path in zip(self.data_config[region], region_task_paths): + pred_train = self.X_train_signal[region][tk][:-1, :] + pred_val = self.X_val_signal[region][tk][:-1, :] + pred_test = self.X_test_signal[region][tk][:-1, :] + + Y_train = self.X_train_tasks[region][tk][1:, :] + Y_val = self.X_val_tasks[region][tk][1:, :] + Y_test = self.X_test_tasks[region][tk][1:, :] + + df_train_ret = np.multiply( + pred_train, Y_train + ) - calc_transaction_costs(pred_train) + df_val_ret = np.multiply(pred_val, Y_val) - calc_transaction_costs( + pred_val + ) + df_test_ret = np.multiply(pred_test, Y_test) - calc_transaction_costs( + pred_test + ) + + df = pd.read_pickle(self.data_config["data_path"] + tk_path) + df_train_ret = pd.DataFrame(df_train_ret, columns=df.columns) + df_train_metrics = self.compute_performance_metrics(df_train_ret) + df_train_metrics["exchange"] = tk + + df_val_ret = pd.DataFrame(df_val_ret, columns=df.columns) + df_val_metrics = self.compute_performance_metrics(df_val_ret) + df_val_metrics["exchange"] = tk + + df_test_ret = pd.DataFrame(df_test_ret, columns=df.columns) + df_test_metrics = self.compute_performance_metrics(df_test_ret) + df_test_metrics["exchange"] = tk + + if metrics: + all_df_train_metrics = df_train_metrics.copy() + all_df_val_metrics = df_val_metrics.copy() + all_df_test_metrics = df_test_metrics.copy() + z = False + else: + all_df_train_metrics = pd.concat( + [all_df_train_metrics, df_train_metrics], axis=0 + ) + all_df_val_metrics = pd.concat( + [all_df_val_metrics, df_val_metrics], axis=0 + ) + all_df_test_metrics = pd.concat( + [all_df_test_metrics, df_test_metrics], axis=0 + ) + + all_df_train_metrics["region"] = region + all_df_train_metrics["set"] = "train" + all_df_val_metrics["region"] = region + all_df_val_metrics["set"] = "val" + all_df_test_metrics["region"] = region + all_df_test_metrics["set"] = "test" + + pd.concat( + [all_df_train_metrics, all_df_val_metrics, all_df_test_metrics], axis=0 + ).to_csv( + self.problem_config["export_path"] + + region + + "_" + + self.problem_config["export_label"] + + self.to_add_label[region] + + ".csv" + ) + + if results: + global_df_test_metrics = all_df_test_metrics.copy() + results = False + else: + global_df_test_metrics = pd.concat( + [global_df_test_metrics, all_df_test_metrics.copy()], axis=0 + ) diff --git a/quantnet/baseline.py b/quantnet/baselines.py similarity index 70% rename from quantnet/baseline.py rename to quantnet/baselines.py index ffb8ac0..117a6ee 100644 --- a/quantnet/baseline.py +++ b/quantnet/baselines.py @@ -1,35 +1,21 @@ -import pickle -import random -from time import time - import numpy as np import pandas as pd -from sklearn.gaussian_process import GaussianProcessRegressor -from sklearn.gaussian_process.kernels import ( - RBF, - ExpSineSquared, - RationalQuadratic, - WhiteKernel, -) -from sklearn.linear_model import LinearRegression, MultiTaskElasticNetCV -from sklearn.model_selection import TimeSeriesSplit -from sktime.pipeline import Pipeline -from sktime.regressors import TimeSeriesForestRegressor -from sktime.transformers.compose import ColumnConcatenator +from sklearn.linear_model import LinearRegression class BuyAndHold: def __init__(self, x_tasks, model_config): - self.Xtrain_tasks = x_tasks + self.X_train_tasks = x_tasks self.export_path = model_config["export_path"] self.export_label = model_config["export_label"] - self.mtl_list = self.Xtrain_tasks.keys() + + self.mtl_list = self.X_train_tasks.keys() self.sub_mtl_list = {} self.signal = {} for tk in self.mtl_list: self.signal[tk] = {} - self.sub_mtl_list[tk] = self.Xtrain_tasks[tk].keys() + self.sub_mtl_list[tk] = self.X_train_tasks[tk].keys() for sub_tk in self.sub_mtl_list[tk]: self.signal[tk][sub_tk] = LinearRegression() @@ -37,8 +23,8 @@ def __init__(self, x_tasks, model_config): def train(self): for tk in self.mtl_list: for sub_tk in self.sub_mtl_list[tk]: - X_train = self.Xtrain_tasks[tk][sub_tk][:-1, :] - Y_train = self.Xtrain_tasks[tk][sub_tk][1:, :] + X_train = self.X_train_tasks[tk][sub_tk][:-1, :] + Y_train = self.X_train_tasks[tk][sub_tk][1:, :] self.signal[tk][sub_tk].fit(X_train, Y_train) self.signal[tk][sub_tk].intercept_ = 1.0 @@ -60,18 +46,18 @@ def predict(self, x_test): class RiskParity: def __init__(self, x_tasks, model_config): - self.Xtrain_tasks = x_tasks + self.X_train_tasks = x_tasks self.export_path = model_config["export_path"] self.export_label = model_config["export_label"] self.window = model_config["risk_parity"]["window"] - self.mtl_list = self.Xtrain_tasks.keys() + self.mtl_list = self.X_train_tasks.keys() self.sub_mtl_list = {} self.signal = {} for tk in self.mtl_list: self.signal[tk] = {} - self.sub_mtl_list[tk] = self.Xtrain_tasks[tk].keys() + self.sub_mtl_list[tk] = self.X_train_tasks[tk].keys() for sub_tk in self.sub_mtl_list[tk]: self.signal[tk][sub_tk] = LinearRegression() @@ -79,8 +65,12 @@ def __init__(self, x_tasks, model_config): def train(self): for tk in self.mtl_list: for sub_tk in self.sub_mtl_list[tk]: - X_train = self.Xtrain_tasks[tk][sub_tk][self.window : -1, :] - Y_train = self.Xtrain_tasks[tk][sub_tk][self.window + 1 :, :] + X_train = self.X_train_tasks[tk][sub_tk][self.window : -1, :] + Y_train = self.X_train_tasks[tk][sub_tk][self.window + 1 :, :] + + self.signal[tk][sub_tk].fit(X_train, Y_train) + self.signal[tk][sub_tk].intercept_ = 1.0 + self.signal[tk][sub_tk].coef_ = self.signal[tk][sub_tk].coef_ * 0.0 print(tk, sub_tk) @@ -89,18 +79,19 @@ def predict(self, x_test): for tk in self.mtl_list: y_pred[tk] = {} - for sub_tk in self.sub_mtl_list[tk]: x = pd.DataFrame( np.concatenate( - [self.Xtrain_tasks[tk][sub_tk], x_test[tk][sub_tk]], axis=0 + [self.X_train_tasks[tk][sub_tk], x_test[tk][sub_tk]], axis=0 ) ) + risk = ( x.rolling(window=self.window) .std() .values[-x_test[tk][sub_tk].shape[0] :, :] ) + y_pred[tk][sub_tk] = (1.0 / risk) / np.repeat( np.sum((1.0 / risk), axis=1).reshape(-1, 1), risk.shape[1], axis=1 ) @@ -110,29 +101,31 @@ def predict(self, x_test): class TimeSeriesMomentum: def __init__(self, x_tasks, model_config): - self.Xtrain_tasks = x_tasks + self.X_train_tasks = x_tasks self.export_path = model_config["export_path"] self.export_label = model_config["export_label"] self.window = model_config["ts_mom"]["window"] - self.mtl_list = self.Xtrain_tasks.keys() + self.mtl_list = self.X_train_tasks.keys() self.sub_mtl_list = {} self.signal = {} for tk in self.mtl_list: self.signal[tk] = {} - self.sub_mtl_list[tk] = self.Xtrain_tasks[tk].keys() + self.sub_mtl_list[tk] = self.X_train_tasks[tk].keys() for sub_tk in self.sub_mtl_list[tk]: self.signal[tk][sub_tk] = LinearRegression() - print(tk, sub_tk) - def train(self): for tk in self.mtl_list: for sub_tk in self.sub_mtl_list[tk]: - X_train = pd.DataFrame(self.Xtrain_tasks[tk][sub_tk][:-1, :]) - Y_train = pd.DataFrame(self.Xtrain_tasks[tk][sub_tk][1:, :]) + X_train = pd.DataFrame(self.X_train_tasks[tk][sub_tk][:-1, :]) + Y_train = pd.DataFrame(self.X_train_tasks[tk][sub_tk][1:, :]) + + self.signal[tk][sub_tk].fit(X_train, Y_train) + self.signal[tk][sub_tk].intercept_ = 1.0 + self.signal[tk][sub_tk].coef_ = self.signal[tk][sub_tk].coef_ * 0.0 print(tk, sub_tk) @@ -145,9 +138,10 @@ def predict(self, x_test): for sub_tk in self.sub_mtl_list[tk]: x = pd.DataFrame( np.concatenate( - [self.Xtrain_tasks[tk][sub_tk], x_test[tk][sub_tk]], axis=0 + [self.X_train_tasks[tk][sub_tk], x_test[tk][sub_tk]], axis=0 ) ) + y_pred[tk][sub_tk] = ( -x.rolling(window=self.window) .mean() @@ -159,30 +153,32 @@ def predict(self, x_test): class CrossSectionalMomentum: def __init__(self, x_tasks, model_config): - self.Xtrain_tasks = x_tasks + self.X_train_tasks = x_tasks self.export_path = model_config["export_path"] self.export_label = model_config["export_label"] self.window = model_config["csec_mom"]["window"] self.fraction = model_config["csec_mom"]["fraction"] - self.mtl_list = self.Xtrain_tasks.keys() + self.mtl_list = self.X_train_tasks.keys() self.sub_mtl_list = {} self.signal = {} for tk in self.mtl_list: self.signal[tk] = {} - self.sub_mtl_list[tk] = self.Xtrain_tasks[tk].keys() + self.sub_mtl_list[tk] = self.X_train_tasks[tk].keys() for sub_tk in self.sub_mtl_list[tk]: self.signal[tk][sub_tk] = LinearRegression() - print(tk, sub_tk) - def train(self): for tk in self.mtl_list: for sub_tk in self.sub_mtl_list[tk]: - X_train = pd.DataFrame(self.Xtrain_tasks[tk][sub_tk][:-1, :]) - Y_train = pd.DataFrame(self.Xtrain_tasks[tk][sub_tk][1:, :]) + X_train = pd.DataFrame(self.X_train_tasks[tk][sub_tk][:-1, :]) + Y_train = pd.DataFrame(self.X_train_tasks[tk][sub_tk][1:, :]) + + self.signal[tk][sub_tk].fit(X_train, Y_train) + self.signal[tk][sub_tk].intercept_ = 1.0 + self.signal[tk][sub_tk].coef_ = self.signal[tk][sub_tk].coef_ * 0.0 print(tk, sub_tk) @@ -195,20 +191,24 @@ def predict(self, x_test): for sub_tk in self.sub_mtl_list[tk]: x = pd.DataFrame( np.concatenate( - [self.Xtrain_tasks[tk][sub_tk], x_test[tk][sub_tk]], axis=0 + [self.X_train_tasks[tk][sub_tk], x_test[tk][sub_tk]], axis=0 ) ) + signal = ( x.rolling(window=self.window) .mean() .values[-x_test[tk][sub_tk].shape[0] :, :] ) + bottom = ( pd.DataFrame(signal).rank(axis=1) / signal.shape[1] ).values < self.fraction + top = (pd.DataFrame(signal).rank(axis=1) / signal.shape[1]).values > ( 1 - self.fraction ) + y_pred[tk][sub_tk] = np.multiply(-signal, (bottom + top)) return y_pred diff --git a/quantnet/global/__init__.py b/quantnet/global/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/quantnet/no_transfer/linear.py b/quantnet/no_transfer/linear.py index 8d9146c..bd54fa0 100755 --- a/quantnet/no_transfer/linear.py +++ b/quantnet/no_transfer/linear.py @@ -7,8 +7,8 @@ class NoTransferLinear: def __init__(self, x_tasks, model_config): self.criterion = self.avg_sharpe_ratio self.X_train_tasks = x_tasks - self.tsteps = model_config["tsteps"] - self.tasks_tsteps = model_config["tasks_tsteps"] + self.t_steps = model_config["t_steps"] + self.tasks_t_steps = model_config["tasks_t_steps"] self.batch_size = model_config["batch_size"] self.seq_len = model_config["seq_len"] self.device = model_config["device"] @@ -64,7 +64,7 @@ def __init__(self, x_tasks, model_config): ) def train(self): - for i in range(self.tsteps): + for i in range(self.t_steps): for tk in self.mtl_list: for sub_tk in self.sub_mtl_list[tk]: start_ids = np.random.permutation( diff --git a/quantnet/global/linear_linear.py b/quantnet/no_transfer/linear_linear.py similarity index 94% rename from quantnet/global/linear_linear.py rename to quantnet/no_transfer/linear_linear.py index 8ac8ef6..69fb96e 100755 --- a/quantnet/global/linear_linear.py +++ b/quantnet/no_transfer/linear_linear.py @@ -4,12 +4,11 @@ class GlobalLinearLinear: - def __init__( - self, x_tasks, model_config): + def __init__(self, x_tasks, model_config): self.criterion = self.avg_sharpe_ratio self.X_train_tasks = x_tasks - self.tsteps = model_config["tsteps"] - self.tasks_tsteps = model_config["tasks_tsteps"] + self.t_steps = model_config["t_steps"] + self.tasks_t_steps = model_config["tasks_t_steps"] self.batch_size = model_config["batch_size"] self.seq_len = model_config["seq_len"] self.device = model_config["device"] @@ -46,6 +45,7 @@ def __init__( self.opt_dict[tk], self.losses[tk], ) = ({}, {}, {}, {}, {}) + self.sub_mtl_list[tk] = self.X_train_tasks[tk].keys() for sub_tk in self.sub_mtl_list[tk]: @@ -82,7 +82,7 @@ def __init__( ) def train(self): - for i in range(self.tsteps): + for i in range(self.t_steps): for tk in self.mtl_list: for sub_tk in self.sub_mtl_list[tk]: start_ids = np.random.permutation( @@ -110,6 +110,7 @@ def train(self): in_pred = self.model_in_dict[tk][sub_tk](X_train) global_pred = self.global_transfer_linear(in_pred) + preds = self.signal_layer[tk][sub_tk]( self.model_out_dict[tk][sub_tk](global_pred) ) @@ -136,6 +137,7 @@ def train(self): + self.export_label + "_intransferlinear.pt", ) + torch.save( self.model_out_dict[tk][sub_tk], self.export_path @@ -146,6 +148,7 @@ def train(self): + self.export_label + "_outtransferlinear.pt", ) + torch.save( self.global_transfer_linear, self.export_path diff --git a/quantnet/global/linear_lstm.py b/quantnet/no_transfer/linear_lstm.py similarity index 95% rename from quantnet/global/linear_lstm.py rename to quantnet/no_transfer/linear_lstm.py index 5b33335..7ecffd8 100755 --- a/quantnet/global/linear_lstm.py +++ b/quantnet/no_transfer/linear_lstm.py @@ -1,5 +1,3 @@ -from typing import Dict, Union - import numpy as np import torch from torch import nn @@ -10,8 +8,8 @@ def __init__( self, x_tasks, model_config): self.criterion = self.avg_sharpe_ratio self.X_train_tasks = x_tasks - self.tsteps = model_config["tsteps"] - self.tasks_tsteps = model_config["tasks_tsteps"] + self.t_steps = model_config["t_steps"] + self.tasks_t_steps = model_config["tasks_t_steps"] self.batch_size = model_config["batch_size"] self.seq_len = model_config["seq_len"] self.device = model_config["device"] @@ -54,6 +52,7 @@ def __init__( self.opt_dict[tk], self.losses[tk], ) = ({}, {}, {}, {}, {}, {}) + self.sub_mtl_list[tk] = self.X_train_tasks[tk].keys() for sub_tk in self.sub_mtl_list[tk]: @@ -119,7 +118,7 @@ def __init__( ) def train(self): - for i in range(self.tsteps): + for i in range(self.t_steps): for tk in self.mtl_list: for sub_tk in self.sub_mtl_list[tk]: start_ids = np.random.permutation( diff --git a/quantnet/no_transfer/lstm.py b/quantnet/no_transfer/lstm.py index 9c38330..b76439a 100755 --- a/quantnet/no_transfer/lstm.py +++ b/quantnet/no_transfer/lstm.py @@ -4,12 +4,11 @@ class NoTransferLstm: - def __init__( - self, x_tasks, model_config): + def __init__(self, x_tasks, model_config): self.criterion = self.avg_sharpe_ratio self.X_train_tasks = x_tasks - self.tsteps = model_config["tsteps"] - self.tasks_tsteps = model_config["tasks_tsteps"] + self.t_steps = model_config["t_steps"] + self.tasks_t_steps = model_config["tasks_t_steps"] self.batch_size = model_config["batch_size"] self.seq_len = model_config["seq_len"] self.device = model_config["device"] @@ -70,7 +69,7 @@ def __init__( ) def train(self): - for i in range(self.tsteps): + for i in range(self.t_steps): for tk in self.mtl_list: for sub_tk in self.sub_mtl_list[tk]: start_ids = np.random.permutation( diff --git a/quantnet/global/lstm_linear.py b/quantnet/no_transfer/lstm_linear.py similarity index 95% rename from quantnet/global/lstm_linear.py rename to quantnet/no_transfer/lstm_linear.py index 52cadca..112eabc 100755 --- a/quantnet/global/lstm_linear.py +++ b/quantnet/no_transfer/lstm_linear.py @@ -8,8 +8,8 @@ def __init__( self, x_tasks, model_config): self.criterion = self.avg_sharpe_ratio self.X_train_tasks = x_tasks - self.tsteps = model_config["tsteps"] - self.tasks_tsteps = model_config["tasks_tsteps"] + self.t_steps = model_config["t_steps"] + self.tasks_t_steps = model_config["tasks_t_steps"] self.batch_size = model_config["batch_size"] self.seq_len = model_config["seq_len"] self.device = model_config["device"] @@ -54,6 +54,7 @@ def __init__( self.opt_dict[tk], self.losses[tk], ) = ({}, {}, {}, {}, {}) + self.sub_mtl_list[tk] = self.X_train_tasks[tk].keys() for sub_tk in self.sub_mtl_list[tk]: @@ -89,7 +90,7 @@ def __init__( ) def train(self): - for i in range(self.tsteps): + for i in range(self.t_steps): for tk in self.mtl_list: for sub_tk in self.sub_mtl_list[tk]: start_ids = np.random.permutation( diff --git a/quantnet/global/lstm_lstm.py b/quantnet/no_transfer/lstm_lstm.py similarity index 95% rename from quantnet/global/lstm_lstm.py rename to quantnet/no_transfer/lstm_lstm.py index 5a47adf..b9bc179 100755 --- a/quantnet/global/lstm_lstm.py +++ b/quantnet/no_transfer/lstm_lstm.py @@ -1,5 +1,3 @@ -from typing import Dict, Union - import numpy as np import torch from torch import nn @@ -10,8 +8,8 @@ def __init__( self, x_tasks, model_config): self.criterion = self.avg_sharpe_ratio self.X_train_tasks = x_tasks - self.tsteps = model_config["tsteps"] - self.tasks_tsteps = model_config["tasks_tsteps"] + self.t_steps = model_config["t_steps"] + self.tasks_t_steps = model_config["tasks_t_steps"] self.batch_size = model_config["batch_size"] self.seq_len = model_config["seq_len"] self.device = model_config["device"] @@ -63,6 +61,7 @@ def __init__( self.opt_dict[tk], self.losses[tk], ) = ({}, {}, {}, {}, {}, {}) + self.sub_mtl_list[tk] = self.X_train_tasks[tk].keys() for sub_tk in self.sub_mtl_list[tk]: @@ -126,7 +125,7 @@ def __init__( ) def train(self) -> None: - for i in range(self.tsteps): + for i in range(self.t_steps): for tk in self.mtl_list: for sub_tk in self.sub_mtl_list[tk]: start_ids = np.random.permutation( diff --git a/quantnet/quantnet.py b/quantnet/quantnet.py deleted file mode 100644 index 15915ac..0000000 --- a/quantnet/quantnet.py +++ /dev/null @@ -1,9 +0,0 @@ -class QuantNet: - def __init__(self): - pass - - def train(self): - pass - - def predict(self): - pass diff --git a/quantnet/utils.py b/quantnet/utils.py index 293ffd7..8d2ac62 100755 --- a/quantnet/utils.py +++ b/quantnet/utils.py @@ -46,7 +46,7 @@ def compute_performance_metrics(df): df_metrics = pd.DataFrame(index=range(df.shape[1]), columns=pf_metrics_labels) - for (pf, pf_label) in zip(pf_metrics, pf_metrics_labels): + for pf, pf_label in zip(pf_metrics, pf_metrics_labels): df_metrics[pf_label] = np.array(pf(df)) df_metrics.index = df.columns @@ -61,11 +61,12 @@ def get_data(data_config, problem_config, model_config): region_task_paths = [t + "_all_assets_data.pkl.gz" for t in data_config[region]] X_train_tasks[region], X_val_tasks[region], X_test_tasks[region] = {}, {}, {} - for (tk_path, tk) in zip(region_task_paths, data_config[region]): + for tk_path, tk in zip(region_task_paths, data_config[region]): df = pd.read_pickle(data_config["data_path"] + tk_path) df_train = df.iloc[ : -(problem_config["val_period"] + problem_config["holdout_period"]) ] + if problem_config["val_period"] != 0: df_val = df.iloc[ -( @@ -82,9 +83,11 @@ def get_data(data_config, problem_config, model_config): X_train_tasks[region][tk] = torch.from_numpy(df_train.values).to( model_config["device"] ) + X_val_tasks[region][tk] = torch.from_numpy(df_val.values).to( model_config["device"] ) + X_test_tasks[region][tk] = torch.from_numpy(df_test.values).to( model_config["device"] ) diff --git a/requirements.txt b/requirements.txt index b2c2739..924223c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,19 +1,23 @@ -certifi==2020.12.5 -chardet==4.0.0 +certifi==2023.5.7 +charset-normalizer==3.1.0 empyrical==0.5.5 -idna==2.10 -joblib==1.0.1 -lxml==4.6.3 -numpy==1.20.3 -pandas==1.2.4 -pandas-datareader==0.9.0 -python-dateutil==2.8.1 -pytz==2021.1 -requests==2.25.1 -scikit-learn==0.24.2 -scipy==1.6.3 +filelock==3.12.2 +idna==3.4 +Jinja2==3.1.2 +lxml==4.9.2 +MarkupSafe==2.1.3 +mpmath==1.3.0 +networkx==3.1 +numpy==1.25.0 +pandas==2.0.3 +pandas-datareader==0.10.0 +python-dateutil==2.8.2 +pytz==2023.3 +requests==2.31.0 +scipy==1.11.1 six==1.16.0 -threadpoolctl==2.1.0 -torch==1.8.1 -typing-extensions==3.10.0.0 -urllib3==1.26.4 +sympy==1.12 +torch==2.0.1 +typing_extensions==4.7.1 +tzdata==2023.3 +urllib3==2.0.3 diff --git a/setup.py b/setup.py index b264ee6..800aa0b 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setuptools.setup( name="quantnet", - version="0.0.2", + version="0.0.3", author="Alexandre Brilhante", author_email="alexandre.brilhante@gmail.com", description="A PyTorch implementation of QuantNet: Transferring Learning Across Systematic Trading Strategies.",