diff --git a/infertrade/api.py b/infertrade/api.py index 679a2217..539904ac 100644 --- a/infertrade/api.py +++ b/infertrade/api.py @@ -25,7 +25,7 @@ # InferTrade packages from infertrade.algos import algorithm_functions, ta_adaptor -from infertrade.utilities.operations import ReturnsFromPositions +from infertrade.utilities.operations import ReturnsFromPositions, restrict_allocation, limit_allocation from infertrade.PandasEnum import PandasEnum @@ -159,13 +159,14 @@ def _get_raw_callable(name_of_strategy_or_signal: str) -> callable: @staticmethod def calculate_allocations( - df: pd.DataFrame, name_of_strategy: str, name_of_price_series: str = PandasEnum.MID.value + df: pd.DataFrame, name_of_strategy: str, name_of_price_series: str = PandasEnum.MID.value, + allocation_lower_limit: float = -1.0, allocation_upper_limit: float = 1.0 ) -> pd.DataFrame: """Calculates the allocations using the supplied strategy.""" if name_of_price_series is not PandasEnum.MID.value: df[PandasEnum.MID.value] = df[name_of_price_series] rule_function = Api._get_raw_callable(name_of_strategy) - df_with_positions = rule_function(df) + df_with_positions = limit_allocation(rule_function(df), allocation_lower_limit, allocation_upper_limit) return df_with_positions @staticmethod @@ -176,10 +177,10 @@ def calculate_returns(df: pd.DataFrame) -> pd.DataFrame: @staticmethod def calculate_allocations_and_returns( - df: pd.DataFrame, name_of_strategy: str, name_of_price_series: str = PandasEnum.MID.value + df: pd.DataFrame, name_of_strategy: str, name_of_price_series: str = PandasEnum.MID.value, *args, **kwargs ) -> pd.DataFrame: """Calculates the returns using the supplied strategy.""" - df_with_positions = Api.calculate_allocations(df, name_of_strategy, name_of_price_series) + df_with_positions = Api.calculate_allocations(df, name_of_strategy, name_of_price_series, *args, **kwargs) df_with_returns = ReturnsFromPositions().transform(df_with_positions) return df_with_returns diff --git a/tests/test_allocations.py b/tests/test_allocations.py index 22a81ed4..e922f565 100644 --- a/tests/test_allocations.py +++ b/tests/test_allocations.py @@ -37,6 +37,7 @@ from infertrade.algos import algorithm_functions from infertrade.algos.community import allocations from infertrade.algos.community.allocations import create_infertrade_export_allocations +from infertrade.api import Api def test_under_minimum_length_to_calculate(): @@ -115,3 +116,10 @@ def test_create_infertrade_export_allocations(): """Checks that a valid dictionary can be created.""" dictionary_algorithms = create_infertrade_export_allocations() assert isinstance(dictionary_algorithms, dict) # could add checks for contents too + + +def test_all_allocations_list_required_series(): + """Checks that all allocation rules list required series.""" + for ii_rule in Api.available_algorithms(filter_by_category="allocation"): + assert isinstance(Api.required_inputs_for_algorithm(ii_rule), list) + diff --git a/tests/test_api.py b/tests/test_api.py index c4421951..945c4683 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -19,6 +19,8 @@ """ # External imports +import copy + import pandas as pd import pytest @@ -54,7 +56,7 @@ def test_get_available_algorithms(algorithm): assert Api.determine_package_of_algorithm(algorithm) in Api.available_packages() try: Api.determine_package_of_algorithm("not_available_algo") - except (NameError): + except NameError: pass inputs = Api.required_inputs_for_algorithm(algorithm) @@ -172,8 +174,8 @@ def test_return_representations(algorithm): ) for representation in dict_of_properties[algorithm]["available_representation_types"]: assert ( - returned_representations[representation] - == dict_of_properties[algorithm]["available_representation_types"][representation] + returned_representations[representation] + == dict_of_properties[algorithm]["available_representation_types"][representation] ) # Check if the if the function returns the correct representation when given a string @@ -185,8 +187,8 @@ def test_return_representations(algorithm): type(returned_representations), ) assert ( - returned_representations[representation] - == dict_of_properties[algorithm]["available_representation_types"][representation] + returned_representations[representation] + == dict_of_properties[algorithm]["available_representation_types"][representation] ) # Check if the function returns the correct representations when given a list @@ -198,8 +200,8 @@ def test_return_representations(algorithm): ) for representation in algorithm_representations: assert ( - returned_representations[representation] - == dict_of_properties[algorithm]["available_representation_types"][representation] + returned_representations[representation] + == dict_of_properties[algorithm]["available_representation_types"][representation] ) @@ -357,3 +359,23 @@ def test_export_cross_prediction(): ) assert isinstance(sorted_dict, dict) + + +@pytest.mark.parametrize("test_df", test_dfs) +def test_allocation_limit(test_df): + """Test used to see if calculated allocation values are inside of specified limit""" + + test_df_copy = copy.deepcopy(test_df) + df_with_allocations = Api.calculate_allocations( + df=test_df_copy, name_of_strategy=available_allocation_algorithms[0], name_of_price_series="close", + allocation_lower_limit=0, allocation_upper_limit=0 + ) + if not all(df_with_allocations["allocation"] == 0.0): + raise ValueError("Allocation limits breached") + + df_with_allocations = Api.calculate_allocations( + df=test_df_copy, name_of_strategy=available_allocation_algorithms[0], name_of_price_series="close", + allocation_lower_limit=-0.1, allocation_upper_limit=0.1 + ) + if any(-0.1 > df_with_allocations["allocation"]) or any(df_with_allocations["allocation"] > 0.1): + raise ValueError("Allocation limits breached")