Files
drift/utils/evaluate.py
T
Mark Aron Szulyovszky cc7061b456 feat(Core): ensemble models, correct forward returns calculation, scaling, only train from when asset returns are available, major bug fixed in walk_forward_train_test (#35)
* fix(Core): correct forward returns calculation, classifiers are now working again, only train from when asset returns are available

* feat(Utils): added get_first_valid_return_index()

* feat(Ensemble): return models from `run_whole_pipeline`

* feat(Ensemble): added ensemble step, fixed walk_forward_train_test predictions index confusion,

* chore(Pipeline): remove unnecessary extra ensemble results dataframe

* refactor(Core): removed unnecessary ensemble_train_predict, moved run_single_asset_trainig_pipeline to a separate file

* feat(Training): added scaling on expanding window (the past) to walk_forward_train_test(), now printing out mean sharpe ratio

* feat(CI): added environment.yml file

* chore(Environment): update env.yml

* feat(CI): added testing workflow

* fix(CI): renamed enviroment.yml

* fix(Tests): added missing new parameter to walk_forward_train_test()
2021-12-17 14:32:17 +01:00

90 lines
3.9 KiB
Python

from typing import Literal
from sklearn.metrics import mean_absolute_error, accuracy_score, r2_score, f1_score, precision_score, recall_score
from quantstats.stats import expected_return, sharpe, skew, sortino
from utils.helpers import get_first_valid_return_index
import pandas as pd
import numpy as np
def backtest(returns: pd.Series, signal: pd.Series, transaction_cost = 0.01) -> pd.Series:
delta_pos = signal.diff(1).abs().fillna(0.)
costs = transaction_cost * delta_pos
return (signal * returns) - costs
def __preprocess(target_returns: pd.Series, y_pred: pd.Series, method: Literal['classification', 'regression']) -> pd.DataFrame:
y_pred.name = 'y_pred'
target_returns.name = 'target_returns'
df = pd.concat([y_pred, target_returns],axis=1).dropna()
df['sign_pred'] = df.y_pred.apply(lambda x: 1 if x>0 else -1)
def sign_true(x):
if x > 0:
return 1
else:
return -1
df['sign_true'] = df.target_returns.apply(sign_true)
df['is_correct'] = 0
df.loc[df.sign_pred * df.sign_true > 0 ,'is_correct'] = 1 # only registers 1 when prediction was made AND it was correct
df['is_incorrect'] = 0
df.loc[df.sign_pred * df.sign_true < 0,'is_incorrect'] = 1 # only registers 1 when prediction was made AND it was wrong
df['is_predicted'] = df.is_correct + df.is_incorrect
df['result'] = backtest(df.target_returns, df.sign_pred)
return df
def evaluate_predictions(
model_name: str,
target_returns: pd.Series,
y_pred: pd.Series,
sliding_window_size: int,
method: Literal['classification', 'regression']
) -> pd.Series:
# ignore the predictions until we see a non-zero returns (and definitely skip the first sliding_window_size)
first_nonzero_return = get_first_valid_return_index(target_returns)
evaluate_from = first_nonzero_return + sliding_window_size + 1
target_returns = pd.Series(target_returns[evaluate_from:])
if method == 'regression':
# if there are lots of zeros in the ground truth returns, probably something is wrong, but we can tolerate a couple of days of missing data.
is_zero = target_returns[target_returns == 0]
assert len(is_zero) < 15
# we can't deal with 0 returns, so we'll just remap the few examples to 0.0001
target_returns = target_returns.apply(lambda x: 0.0001 if x == 0 else x)
y_pred = pd.Series(y_pred[evaluate_from:])
df = __preprocess(target_returns, y_pred, method)
scorecard = pd.Series()
if method == 'regression':
scorecard.loc['RSQ'] = r2_score(df.target_returns, df.y_pred)
scorecard.loc['MAE'] = mean_absolute_error(df.target_returns, df.y_pred)
elif method == 'classification':
scorecard.loc['RSQ'] = 0.
scorecard.loc['MAE Matrix'] = 0.
sign_true = df.sign_true.astype(int)
sign_pred = df.sign_pred.astype(int)
scorecard.loc['sharpe'] = sharpe(df.result)
scorecard.loc['sortino'] = sortino(df.result)
scorecard.loc['skew'] = skew(df.result)
scorecard.loc['accuracy'] = accuracy_score(sign_true, sign_pred) * 100
scorecard.loc['recall'] = recall_score(sign_true, sign_pred, labels = [1, -1])
scorecard.loc['precision'] = precision_score(sign_true, sign_pred, labels = [1, -1])
scorecard.loc['f1_score'] = f1_score(sign_true, sign_pred, labels = [1, -1])
scorecard.loc['edge'] = df.result.mean()
scorecard.loc['noise'] = df.y_pred.diff().abs().mean()
scorecard.loc['edge_to_noise'] = scorecard.loc['edge'] / scorecard.loc['noise']
if method == 'regression':
scorecard.loc['edge_to_mae'] = scorecard.loc['edge'] / scorecard.loc['MAE']
elif method == 'classification':
scorecard.loc['edge_to_mae'] = 0.
scorecard = scorecard.round(3)
print("Model name: ", model_name)
print(scorecard)
return scorecard