mirror of
https://github.com/webclinic017/drift.git
synced 2026-08-18 05:18:09 +00:00
refactor(Evaluate): print out accuracy, f1, etc. for the final & meta predictions, separated out evaluation step (#228)
* refactor(Evaluate): print out accuracy, f1, etc. for the final & meta predictions, separated out evaluation step * fix(Linter): ran * fix(Tests): syntax change * fix(Inference): runs now again * fix(Linter): ran
This commit is contained in:
@@ -2,6 +2,7 @@ from data_loader.types import ReturnSeries
|
|||||||
from ..types import EventLabeller, EventsDataFrame
|
from ..types import EventLabeller, EventsDataFrame
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
from .utils import create_forward_returns
|
from .utils import create_forward_returns
|
||||||
|
from typing import Callable
|
||||||
|
|
||||||
|
|
||||||
class FixedTimeHorionThreeClassBalancedEventLabeller(EventLabeller):
|
class FixedTimeHorionThreeClassBalancedEventLabeller(EventLabeller):
|
||||||
@@ -52,3 +53,9 @@ class FixedTimeHorionThreeClassBalancedEventLabeller(EventLabeller):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
return events
|
return events
|
||||||
|
|
||||||
|
def get_labels(self) -> list[int]:
|
||||||
|
return [-1, 0, 1]
|
||||||
|
|
||||||
|
def get_discretize_function(self) -> Callable:
|
||||||
|
raise NotImplementedError
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from ..types import EventLabeller, EventsDataFrame, ReturnSeries
|
from ..types import EventLabeller, EventsDataFrame, ReturnSeries
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
from .utils import create_forward_returns
|
from .utils import create_forward_returns
|
||||||
|
from typing import Callable
|
||||||
|
|
||||||
|
|
||||||
class FixedTimeHorionThreeClassImbalancedEventLabeller(EventLabeller):
|
class FixedTimeHorionThreeClassImbalancedEventLabeller(EventLabeller):
|
||||||
@@ -51,3 +52,9 @@ class FixedTimeHorionThreeClassImbalancedEventLabeller(EventLabeller):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
return events
|
return events
|
||||||
|
|
||||||
|
def get_labels(self) -> list[int]:
|
||||||
|
return [-1, 0, 1]
|
||||||
|
|
||||||
|
def get_discretize_function(self) -> Callable:
|
||||||
|
raise NotImplementedError
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
from ..types import EventLabeller, EventsDataFrame, ReturnSeries
|
from ..types import EventLabeller, EventsDataFrame, ReturnSeries
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
from .utils import create_forward_returns
|
from .utils import create_forward_returns
|
||||||
|
from typing import Callable
|
||||||
|
from .utils import discretize_binary
|
||||||
|
|
||||||
|
|
||||||
class FixedTimeHorionTwoClassEventLabeller(EventLabeller):
|
class FixedTimeHorionTwoClassEventLabeller(EventLabeller):
|
||||||
@@ -32,3 +34,9 @@ class FixedTimeHorionTwoClassEventLabeller(EventLabeller):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
return events
|
return events
|
||||||
|
|
||||||
|
def get_labels(self) -> list[int]:
|
||||||
|
return [-1, 1]
|
||||||
|
|
||||||
|
def get_discretize_function(self) -> Callable:
|
||||||
|
return discretize_binary
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import pandas as pd
|
import pandas as pd
|
||||||
from data_loader.types import ForwardReturnSeries
|
from data_loader.types import ForwardReturnSeries
|
||||||
from labeling.types import EventsDataFrame
|
from labeling.types import EventsDataFrame
|
||||||
|
from typing import Callable
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
|
||||||
def create_forward_returns(series: pd.Series, period: int) -> ForwardReturnSeries:
|
def create_forward_returns(series: pd.Series, period: int) -> ForwardReturnSeries:
|
||||||
@@ -22,3 +24,31 @@ def purge_overlapping_events(events: EventsDataFrame) -> EventsDataFrame:
|
|||||||
last_event_end = row["end"]
|
last_event_end = row["end"]
|
||||||
events.drop(indicies_to_remove, inplace=True)
|
events.drop(indicies_to_remove, inplace=True)
|
||||||
return events
|
return events
|
||||||
|
|
||||||
|
|
||||||
|
def discretize_binary(x):
|
||||||
|
return 1 if x > 0 else -1
|
||||||
|
|
||||||
|
|
||||||
|
def discretize_binary_zero_one(x):
|
||||||
|
return 1 if x > 0 else 0
|
||||||
|
|
||||||
|
|
||||||
|
def discretize_threeway(x):
|
||||||
|
return 0 if x == 0 else 1 if x > 0 else -1
|
||||||
|
|
||||||
|
|
||||||
|
def discretize_threeway_threshold(threshold: float) -> Callable:
|
||||||
|
def discretize(current_value):
|
||||||
|
lower_threshold = -threshold
|
||||||
|
upper_threshold = threshold
|
||||||
|
if np.isnan(current_value):
|
||||||
|
return np.nan
|
||||||
|
elif current_value <= lower_threshold:
|
||||||
|
return -1
|
||||||
|
elif current_value > lower_threshold and current_value < upper_threshold:
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
return discretize
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from abc import ABC, abstractmethod
|
|||||||
import pandas as pd
|
import pandas as pd
|
||||||
import pandera as pa
|
import pandera as pa
|
||||||
from pandera.typing import DataFrame, Series
|
from pandera.typing import DataFrame, Series
|
||||||
|
from typing import Callable
|
||||||
|
|
||||||
|
|
||||||
class EventFilter(ABC):
|
class EventFilter(ABC):
|
||||||
@@ -27,3 +28,9 @@ class EventLabeller(ABC):
|
|||||||
self, event_start_times: pd.DatetimeIndex, returns: ReturnSeries
|
self, event_start_times: pd.DatetimeIndex, returns: ReturnSeries
|
||||||
) -> EventsDataFrame:
|
) -> EventsDataFrame:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def get_labels(self) -> list[int]:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def get_discretize_function(self) -> Callable:
|
||||||
|
raise NotImplementedError
|
||||||
|
|||||||
+2
-3
@@ -75,10 +75,9 @@ def __inference(config: Config, pipeline_outcome: PipelineOutcome):
|
|||||||
model=config.meta_model,
|
model=config.meta_model,
|
||||||
transformations=config.transformations,
|
transformations=config.transformations,
|
||||||
config=config,
|
config=config,
|
||||||
model_suffix="meta",
|
|
||||||
from_index=inference_from,
|
from_index=inference_from,
|
||||||
transformations_over_time=pipeline_outcome.bet_sizing.meta_training.transformations,
|
transformations_over_time=pipeline_outcome.bet_sizing.transformations,
|
||||||
preloaded_models=pipeline_outcome.bet_sizing.meta_training.model_over_time,
|
preloaded_models=pipeline_outcome.bet_sizing.model_over_time,
|
||||||
)
|
)
|
||||||
|
|
||||||
return PipelineOutcome(directional_training_outcome, bet_sizing_outcome)
|
return PipelineOutcome(directional_training_outcome, bet_sizing_outcome)
|
||||||
|
|||||||
@@ -100,7 +100,6 @@ def run_training(config: Config) -> PipelineOutcome:
|
|||||||
config.meta_model,
|
config.meta_model,
|
||||||
config.transformations,
|
config.transformations,
|
||||||
config,
|
config,
|
||||||
"meta",
|
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ from training.walk_forward import walk_forward_train, walk_forward_inference
|
|||||||
from models.base import Model
|
from models.base import Model
|
||||||
from utils.evaluate import evaluate_predictions
|
from utils.evaluate import evaluate_predictions
|
||||||
from sklearn.base import BaseEstimator, ClassifierMixin
|
from sklearn.base import BaseEstimator, ClassifierMixin
|
||||||
|
from labeling.labellers.utils import discretize_binary
|
||||||
|
|
||||||
no_of_rows = 100
|
no_of_rows = 100
|
||||||
|
|
||||||
@@ -97,8 +98,9 @@ def test_evaluation():
|
|||||||
forward_returns=fake_forward_returns,
|
forward_returns=fake_forward_returns,
|
||||||
y_pred=processed_predictions_to_match_returns,
|
y_pred=processed_predictions_to_match_returns,
|
||||||
y_true=y,
|
y_true=y,
|
||||||
no_of_classes="two",
|
discretize_func=discretize_binary,
|
||||||
discretize=True,
|
labels=[1, -1],
|
||||||
|
transaction_costs=0.002,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert result["accuracy"] == 100.0
|
assert result["accuracy"] == 100.0
|
||||||
|
|||||||
+27
-17
@@ -1,20 +1,23 @@
|
|||||||
from data_loader.types import ForwardReturnSeries, XDataFrame, ySeries
|
from data_loader.types import ForwardReturnSeries, XDataFrame, ySeries
|
||||||
from utils.evaluate import discretize_threeway_threshold, evaluate_predictions
|
from utils.evaluate import evaluate_predictions
|
||||||
from utils.helpers import equal_except_nan
|
from utils.helpers import equal_except_nan
|
||||||
from .train_model import train_model
|
from .train_model import train_model
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
from models.base import Model
|
from models.base import Model
|
||||||
from models.model_map import default_feature_selector_classification
|
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from config.types import Config
|
from config.types import Config
|
||||||
from .types import (
|
from .types import (
|
||||||
BetSizingWithMetaOutcome,
|
BetSizingWithMetaOutcome,
|
||||||
ModelOverTime,
|
ModelOverTime,
|
||||||
TrainingOutcome,
|
|
||||||
TransformationsOverTime,
|
TransformationsOverTime,
|
||||||
)
|
)
|
||||||
from training.walk_forward import walk_forward_process_transformations
|
from training.walk_forward import walk_forward_process_transformations
|
||||||
from transformations.base import Transformation
|
from transformations.base import Transformation
|
||||||
|
from labeling.labellers.utils import (
|
||||||
|
discretize_binary_zero_one,
|
||||||
|
discretize_threeway_threshold,
|
||||||
|
)
|
||||||
|
import pprint
|
||||||
|
|
||||||
|
|
||||||
def bet_sizing_with_meta_model(
|
def bet_sizing_with_meta_model(
|
||||||
@@ -25,7 +28,6 @@ def bet_sizing_with_meta_model(
|
|||||||
model: Model,
|
model: Model,
|
||||||
transformations: list[Transformation],
|
transformations: list[Transformation],
|
||||||
config: Config,
|
config: Config,
|
||||||
model_suffix: str,
|
|
||||||
from_index: Optional[pd.Timestamp],
|
from_index: Optional[pd.Timestamp],
|
||||||
transformations_over_time: Optional[TransformationsOverTime] = None,
|
transformations_over_time: Optional[TransformationsOverTime] = None,
|
||||||
preloaded_models: Optional[ModelOverTime] = None,
|
preloaded_models: Optional[ModelOverTime] = None,
|
||||||
@@ -63,36 +65,44 @@ def bet_sizing_with_meta_model(
|
|||||||
sliding_window_size=config.sliding_window_size,
|
sliding_window_size=config.sliding_window_size,
|
||||||
retrain_every=config.retrain_every,
|
retrain_every=config.retrain_every,
|
||||||
from_index=from_index,
|
from_index=from_index,
|
||||||
no_of_classes="two",
|
|
||||||
level="meta",
|
level="meta",
|
||||||
output_stats=config.mode == "training",
|
|
||||||
transformations_over_time=transformations_over_time,
|
transformations_over_time=transformations_over_time,
|
||||||
model_over_time=preloaded_models,
|
model_over_time=preloaded_models,
|
||||||
)
|
)
|
||||||
meta_outcome = TrainingOutcome(
|
|
||||||
**vars(meta_outcome), transformations=transformations_over_time
|
|
||||||
)
|
|
||||||
|
|
||||||
meta_predictions = meta_outcome.predictions
|
meta_predictions = meta_outcome.predictions
|
||||||
bet_size = meta_outcome.probabilities.iloc[:, 1]
|
bet_size = meta_outcome.probabilities.iloc[:, 1]
|
||||||
avg_predictions_with_sizing = input_predictions * meta_predictions * bet_size
|
avg_predictions_with_sizing = input_predictions * meta_predictions * bet_size
|
||||||
|
|
||||||
if config.mode == "training":
|
if config.mode == "training":
|
||||||
|
pp = pprint.PrettyPrinter(depth=2)
|
||||||
|
meta_stats = evaluate_predictions(
|
||||||
|
forward_returns=forward_returns,
|
||||||
|
y_pred=meta_outcome.predictions,
|
||||||
|
y_true=meta_y,
|
||||||
|
discretize_func=discretize_binary_zero_one,
|
||||||
|
labels=[0, 1],
|
||||||
|
transaction_costs=config.transaction_costs,
|
||||||
|
)
|
||||||
|
pp.pprint(meta_stats)
|
||||||
stats = evaluate_predictions(
|
stats = evaluate_predictions(
|
||||||
forward_returns=forward_returns,
|
forward_returns=forward_returns,
|
||||||
y_pred=avg_predictions_with_sizing,
|
y_pred=avg_predictions_with_sizing,
|
||||||
y_true=y,
|
y_true=y,
|
||||||
no_of_classes="three-balanced",
|
discretize_func=config.labeling.get_discretize_function(),
|
||||||
discretize=False,
|
labels=config.labeling.get_labels(),
|
||||||
|
transaction_costs=config.transaction_costs,
|
||||||
)
|
)
|
||||||
print(stats)
|
pp.pprint(stats)
|
||||||
else:
|
else:
|
||||||
stats = None
|
stats = None
|
||||||
model_id = "model_" + config.target_asset[1] + "_" + model_suffix
|
model_id = "model_" + config.target_asset[1] + "_meta"
|
||||||
|
|
||||||
|
outcome_dict = vars(meta_outcome)
|
||||||
|
outcome_dict["model_id"] = model_id
|
||||||
return BetSizingWithMetaOutcome(
|
return BetSizingWithMetaOutcome(
|
||||||
model_id,
|
**outcome_dict,
|
||||||
meta_outcome,
|
transformations=transformations_over_time,
|
||||||
avg_predictions_with_sizing,
|
weights=avg_predictions_with_sizing,
|
||||||
stats,
|
stats=stats,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
|
||||||
from transformations.base import Transformation
|
from transformations.base import Transformation
|
||||||
|
from utils.evaluate import evaluate_predictions
|
||||||
|
|
||||||
from .types import TrainingOutcome
|
from .types import TrainingOutcome
|
||||||
from training.train_model import train_model
|
from training.train_model import train_model
|
||||||
@@ -9,6 +10,7 @@ from training.walk_forward import walk_forward_process_transformations
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
from config.types import Config
|
from config.types import Config
|
||||||
from models.base import Model
|
from models.base import Model
|
||||||
|
import pprint
|
||||||
|
|
||||||
|
|
||||||
def train_directional_model(
|
def train_directional_model(
|
||||||
@@ -45,17 +47,30 @@ def train_directional_model(
|
|||||||
sliding_window_size=config.sliding_window_size,
|
sliding_window_size=config.sliding_window_size,
|
||||||
retrain_every=config.retrain_every,
|
retrain_every=config.retrain_every,
|
||||||
from_index=from_index,
|
from_index=from_index,
|
||||||
no_of_classes=config.no_of_classes,
|
|
||||||
level="primary",
|
level="primary",
|
||||||
output_stats=config.mode == "training",
|
|
||||||
transformations_over_time=transformations_over_time,
|
transformations_over_time=transformations_over_time,
|
||||||
model_over_time=preloaded_training_step.model_over_time
|
model_over_time=preloaded_training_step.model_over_time
|
||||||
if preloaded_training_step
|
if preloaded_training_step
|
||||||
else None,
|
else None,
|
||||||
)
|
)
|
||||||
if config.mode == "training":
|
|
||||||
print(training_outcome.stats)
|
stats = (
|
||||||
|
evaluate_predictions(
|
||||||
|
forward_returns=forward_returns,
|
||||||
|
y_pred=training_outcome.predictions,
|
||||||
|
y_true=y,
|
||||||
|
discretize_func=config.labeling.get_discretize_function(),
|
||||||
|
labels=config.labeling.get_labels(),
|
||||||
|
transaction_costs=config.transaction_costs,
|
||||||
|
)
|
||||||
|
if config.mode == "training"
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
|
||||||
|
if stats is not None:
|
||||||
|
pp = pprint.PrettyPrinter(depth=2)
|
||||||
|
pp.pprint(stats)
|
||||||
|
|
||||||
return TrainingOutcome(
|
return TrainingOutcome(
|
||||||
**vars(training_outcome), transformations=transformations_over_time
|
**vars(training_outcome), transformations=transformations_over_time, stats=stats
|
||||||
)
|
)
|
||||||
|
|||||||
+10
-24
@@ -5,12 +5,11 @@ from training.walk_forward import (
|
|||||||
walk_forward_inference,
|
walk_forward_inference,
|
||||||
walk_forward_inference_batched,
|
walk_forward_inference_batched,
|
||||||
)
|
)
|
||||||
from utils.evaluate import evaluate_predictions
|
|
||||||
from models.base import Model
|
from models.base import Model
|
||||||
from .types import (
|
from .types import (
|
||||||
ModelOverTime,
|
ModelOverTime,
|
||||||
TransformationsOverTime,
|
TransformationsOverTime,
|
||||||
TrainingOutcomeWithoutTransformations,
|
BaseTrainingOutcome,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -23,12 +22,17 @@ def train_model(
|
|||||||
sliding_window_size: int,
|
sliding_window_size: int,
|
||||||
retrain_every: int,
|
retrain_every: int,
|
||||||
from_index: Optional[pd.Timestamp],
|
from_index: Optional[pd.Timestamp],
|
||||||
no_of_classes: Literal["two", "three-balanced", "three-imbalanced"],
|
|
||||||
level: str,
|
level: str,
|
||||||
output_stats: bool,
|
|
||||||
transformations_over_time: TransformationsOverTime,
|
transformations_over_time: TransformationsOverTime,
|
||||||
model_over_time: Optional[ModelOverTime],
|
model_over_time: Optional[ModelOverTime],
|
||||||
) -> TrainingOutcomeWithoutTransformations:
|
) -> BaseTrainingOutcome:
|
||||||
|
|
||||||
|
levelname = ("_" + level) if level == "meta" else ""
|
||||||
|
model_id = (
|
||||||
|
"model_" + model.name + "_" + ticker_to_predict + levelname
|
||||||
|
if model_over_time is None
|
||||||
|
else model_over_time.name
|
||||||
|
)
|
||||||
|
|
||||||
if model_over_time is None:
|
if model_over_time is None:
|
||||||
print("Train model")
|
print("Train model")
|
||||||
@@ -44,12 +48,6 @@ def train_model(
|
|||||||
transformations_over_time=transformations_over_time,
|
transformations_over_time=transformations_over_time,
|
||||||
)
|
)
|
||||||
|
|
||||||
levelname = ("_" + level) if level == "meta" else ""
|
|
||||||
if model_over_time is None:
|
|
||||||
model_id = "model_" + model.name + "_" + ticker_to_predict + levelname
|
|
||||||
else:
|
|
||||||
model_id = model_over_time.name
|
|
||||||
|
|
||||||
inference_function = (
|
inference_function = (
|
||||||
walk_forward_inference
|
walk_forward_inference
|
||||||
if from_index is not None
|
if from_index is not None
|
||||||
@@ -67,17 +65,5 @@ def train_model(
|
|||||||
)
|
)
|
||||||
|
|
||||||
assert len(predictions) == len(y)
|
assert len(predictions) == len(y)
|
||||||
if output_stats:
|
|
||||||
stats = evaluate_predictions(
|
|
||||||
forward_returns=forward_returns,
|
|
||||||
y_pred=predictions,
|
|
||||||
y_true=y,
|
|
||||||
no_of_classes=no_of_classes,
|
|
||||||
discretize=True,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
stats = None
|
|
||||||
|
|
||||||
return TrainingOutcomeWithoutTransformations(
|
return BaseTrainingOutcome(model_id, predictions, probabilities, model_over_time)
|
||||||
model_id, predictions, probabilities, stats, model_over_time
|
|
||||||
)
|
|
||||||
|
|||||||
+4
-7
@@ -12,25 +12,22 @@ TransformationsOverTime = list[pd.Series]
|
|||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class TrainingOutcomeWithoutTransformations:
|
class BaseTrainingOutcome:
|
||||||
model_id: str
|
model_id: str
|
||||||
predictions: PredictionsSeries
|
predictions: PredictionsSeries
|
||||||
probabilities: ProbabilitiesDataFrame
|
probabilities: ProbabilitiesDataFrame
|
||||||
stats: Optional[Stats]
|
|
||||||
model_over_time: ModelOverTime
|
model_over_time: ModelOverTime
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class TrainingOutcome(TrainingOutcomeWithoutTransformations):
|
class TrainingOutcome(BaseTrainingOutcome):
|
||||||
transformations: TransformationsOverTime
|
transformations: TransformationsOverTime
|
||||||
|
stats: Optional[Stats]
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class BetSizingWithMetaOutcome:
|
class BetSizingWithMetaOutcome(TrainingOutcome):
|
||||||
model_id: str
|
|
||||||
meta_training: TrainingOutcome
|
|
||||||
weights: WeightsSeries
|
weights: WeightsSeries
|
||||||
stats: Optional[Stats]
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
|||||||
+27
-59
@@ -1,16 +1,15 @@
|
|||||||
from typing import Literal, Callable
|
from typing import Callable
|
||||||
from sklearn.metrics import accuracy_score, f1_score, precision_score, recall_score
|
from sklearn.metrics import accuracy_score, f1_score, precision_score, recall_score
|
||||||
from quantstats.stats import skew, sortino
|
from quantstats.stats import skew, sortino
|
||||||
from utils.metrics import probabilistic_sharpe_ratio, sharpe_ratio
|
from utils.metrics import probabilistic_sharpe_ratio, sharpe_ratio
|
||||||
from utils.helpers import get_first_valid_return_index
|
from utils.helpers import get_first_valid_return_index
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
import numpy as np
|
|
||||||
from data_loader.types import ForwardReturnSeries, ySeries
|
from data_loader.types import ForwardReturnSeries, ySeries
|
||||||
from training.types import Stats, WeightsSeries
|
from training.types import Stats, WeightsSeries
|
||||||
|
|
||||||
|
|
||||||
def backtest(
|
def backtest(
|
||||||
returns: pd.Series, signal: pd.Series, transaction_cost=0.002
|
returns: pd.Series, signal: pd.Series, transaction_cost: float
|
||||||
) -> pd.Series:
|
) -> pd.Series:
|
||||||
delta_pos = signal.diff(1).abs().fillna(0.0)
|
delta_pos = signal.diff(1).abs().fillna(0.0)
|
||||||
costs = transaction_cost * delta_pos
|
costs = transaction_cost * delta_pos
|
||||||
@@ -21,19 +20,18 @@ def __preprocess(
|
|||||||
forward_returns: ForwardReturnSeries,
|
forward_returns: ForwardReturnSeries,
|
||||||
y_pred: pd.Series,
|
y_pred: pd.Series,
|
||||||
y_true: pd.Series,
|
y_true: pd.Series,
|
||||||
no_of_classes: Literal["two", "three-balanced", "three-imbalanced"],
|
discretize_func: Callable,
|
||||||
discretize: bool,
|
transaction_costs: float,
|
||||||
) -> pd.DataFrame:
|
) -> pd.DataFrame:
|
||||||
y_pred.name = "y_pred"
|
y_pred.name = "y_pred"
|
||||||
forward_returns.name = "forward_returns"
|
forward_returns.name = "forward_returns"
|
||||||
df = pd.concat([y_pred, forward_returns], axis=1).dropna()
|
df = pd.concat([y_pred, forward_returns], axis=1).dropna()
|
||||||
|
|
||||||
discretize_func = get_discretize_function(no_of_classes)
|
|
||||||
# make sure that we evaluate binary/three-way predictions even if the model is a regression
|
# make sure that we evaluate binary/three-way predictions even if the model is a regression
|
||||||
df["sign_pred"] = df.y_pred.apply(discretize_func) if discretize else df.y_pred
|
df["sign_pred"] = df.y_pred.apply(discretize_func)
|
||||||
df["sign_true"] = y_true
|
df["sign_true"] = y_true
|
||||||
|
|
||||||
df["result"] = backtest(df.forward_returns, df.sign_pred)
|
df["result"] = backtest(df.forward_returns, df.y_pred, transaction_costs)
|
||||||
|
|
||||||
return df
|
return df
|
||||||
|
|
||||||
@@ -42,8 +40,9 @@ def evaluate_predictions(
|
|||||||
forward_returns: ForwardReturnSeries,
|
forward_returns: ForwardReturnSeries,
|
||||||
y_pred: WeightsSeries,
|
y_pred: WeightsSeries,
|
||||||
y_true: ySeries,
|
y_true: ySeries,
|
||||||
no_of_classes: Literal["two", "three-balanced", "three-imbalanced"],
|
discretize_func: Callable,
|
||||||
discretize: bool = False,
|
labels: list[int],
|
||||||
|
transaction_costs: float,
|
||||||
) -> Stats:
|
) -> Stats:
|
||||||
# ignore the predictions until we see a non-zero returns (and definitely skip the first sliding_window_size)
|
# ignore the predictions until we see a non-zero returns (and definitely skip the first sliding_window_size)
|
||||||
evaluate_from = max(
|
evaluate_from = max(
|
||||||
@@ -54,7 +53,9 @@ def evaluate_predictions(
|
|||||||
forward_returns = pd.Series(forward_returns[evaluate_from:])
|
forward_returns = pd.Series(forward_returns[evaluate_from:])
|
||||||
y_pred = pd.Series(y_pred[evaluate_from:])
|
y_pred = pd.Series(y_pred[evaluate_from:])
|
||||||
|
|
||||||
df = __preprocess(forward_returns, y_pred, y_true, no_of_classes, discretize)
|
df = __preprocess(
|
||||||
|
forward_returns, y_pred, y_true, discretize_func, transaction_costs
|
||||||
|
)
|
||||||
|
|
||||||
scorecard = dict()
|
scorecard = dict()
|
||||||
|
|
||||||
@@ -73,60 +74,27 @@ def evaluate_predictions(
|
|||||||
scorecard["sortino"] = sortino(df.result)
|
scorecard["sortino"] = sortino(df.result)
|
||||||
scorecard["skew"] = skew(df.result)
|
scorecard["skew"] = skew(df.result)
|
||||||
|
|
||||||
labels = [1, -1] if no_of_classes == "two" else [1, -1, 0]
|
avg_type = "weighted" if len(labels) == 2 else "macro"
|
||||||
avg_type = "weighted" if no_of_classes == "two" else "macro"
|
|
||||||
|
|
||||||
if discretize == True:
|
scorecard["accuracy"] = accuracy_score(df.sign_true, df.sign_pred) * 100
|
||||||
scorecard["accuracy"] = accuracy_score(df.sign_true, df.sign_pred) * 100
|
scorecard["recall"] = recall_score(
|
||||||
scorecard["recall"] = recall_score(
|
df.sign_true, df.sign_pred, labels=labels, average=avg_type
|
||||||
df.sign_true, df.sign_pred, labels=labels, average=avg_type
|
)
|
||||||
)
|
scorecard["precision"] = precision_score(
|
||||||
scorecard["precision"] = precision_score(
|
df.sign_true, df.sign_pred, labels=labels, average=avg_type
|
||||||
df.sign_true, df.sign_pred, labels=labels, average=avg_type
|
)
|
||||||
)
|
scorecard["f1_score"] = f1_score(
|
||||||
scorecard["f1_score"] = f1_score(
|
df.sign_true, df.sign_pred, labels=labels, average=avg_type
|
||||||
df.sign_true, df.sign_pred, labels=labels, average=avg_type
|
)
|
||||||
)
|
|
||||||
scorecard["edge"] = df.result.mean()
|
scorecard["edge"] = df.result.mean()
|
||||||
scorecard["noise"] = df.y_pred.diff().abs().mean()
|
scorecard["noise"] = df.y_pred.diff().abs().mean()
|
||||||
scorecard["edge_to_noise"] = scorecard["edge"] / (scorecard["noise"] + 0.00001)
|
scorecard["edge_to_noise"] = scorecard["edge"] / (scorecard["noise"] + 0.00001)
|
||||||
|
|
||||||
if discretize == True:
|
for index, row in df.sign_true.value_counts().iteritems():
|
||||||
for index, row in df.sign_true.value_counts().iteritems():
|
scorecard["sign_true_ratio_" + str(index)] = row / len(df.sign_true)
|
||||||
scorecard["sign_true_ratio_" + str(index)] = row / len(df.sign_true)
|
|
||||||
|
|
||||||
for index, row in df.sign_pred.value_counts().iteritems():
|
for index, row in df.sign_pred.value_counts().iteritems():
|
||||||
scorecard["sign_pred_ratio_" + str(index)] = row / len(df.sign_pred)
|
scorecard["sign_pred_ratio_" + str(index)] = row / len(df.sign_pred)
|
||||||
|
|
||||||
scorecard = {k: round(float(v), 3) for k, v in scorecard.items()}
|
scorecard = {k: round(float(v), 3) for k, v in scorecard.items()}
|
||||||
return scorecard
|
return scorecard
|
||||||
|
|
||||||
|
|
||||||
def __discretize_binary(x):
|
|
||||||
return 1 if x > 0 else -1
|
|
||||||
|
|
||||||
|
|
||||||
def __discretize_threeway(x):
|
|
||||||
return 0 if x == 0 else 1 if x > 0 else -1
|
|
||||||
|
|
||||||
|
|
||||||
def discretize_threeway_threshold(threshold: float) -> Callable:
|
|
||||||
def discretize(current_value):
|
|
||||||
lower_threshold = -threshold
|
|
||||||
upper_threshold = threshold
|
|
||||||
if np.isnan(current_value):
|
|
||||||
return np.nan
|
|
||||||
elif current_value <= lower_threshold:
|
|
||||||
return -1
|
|
||||||
elif current_value > lower_threshold and current_value < upper_threshold:
|
|
||||||
return 0
|
|
||||||
else:
|
|
||||||
return 1
|
|
||||||
|
|
||||||
return discretize
|
|
||||||
|
|
||||||
|
|
||||||
def get_discretize_function(
|
|
||||||
no_of_classes: Literal["two", "three-balanced", "three-imbalanced"]
|
|
||||||
) -> Callable:
|
|
||||||
return __discretize_binary if no_of_classes == "two" else __discretize_threeway
|
|
||||||
|
|||||||
Reference in New Issue
Block a user