Skip to content

Commit

Permalink
[Optimizer] properly handle multiple and single options
Browse files Browse the repository at this point in the history
  • Loading branch information
techfreaque committed Nov 11, 2024
1 parent 3e4581a commit 96ceb59
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 16 deletions.
35 changes: 33 additions & 2 deletions octobot/strategy_optimizer/strategy_design_optimizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ class ConfigTypes(enum.Enum):
NUMBER = "number"
BOOLEAN = "boolean"
OPTIONS = "options"
MULTIPLE_OPTIONS = "multiple-options"
UNKNOWN = "unknown"


Expand All @@ -75,6 +76,7 @@ class StrategyDesignOptimizer:
CONFIG_USER_INPUTS = "user_inputs"
CONFIG_FILTER_SETTINGS = "filters_settings"
CONFIG_VALUE = "value"
CONFIG_TYPE = "type"
CONFIG_TENTACLE = "tentacle"
CONFIG_NESTED_TENTACLE_SEPARATOR = "_------_"
CONFIG_TYPE = "type"
Expand Down Expand Up @@ -791,8 +793,13 @@ def _generate_possible_values(self, config_element):
values = []
try:
if config_element[self.CONFIG_ENABLED]:
if config_element[self.CONFIG_TYPE] is ConfigTypes.MULTIPLE_OPTIONS:
self._generate_possible_multiple_options_values(
possible_values=values,
config_element_values=config_element[self.CONFIG_VALUE][self.CONFIG_VALUE],
)
if config_element[self.CONFIG_TYPE] is ConfigTypes.OPTIONS:
values = [[value] for value in config_element[self.CONFIG_VALUE][self.CONFIG_VALUE]]
values = [value for value in config_element[self.CONFIG_VALUE][self.CONFIG_VALUE]]
if config_element[self.CONFIG_TYPE] is ConfigTypes.BOOLEAN:
values = config_element[self.CONFIG_VALUE][self.CONFIG_VALUE]
if config_element[self.CONFIG_TYPE] is ConfigTypes.NUMBER:
Expand Down Expand Up @@ -825,7 +832,28 @@ def _get_all_possible_values(self, start, stop, step):
while current <= d_stop:
yield return_type(current)
current += d_step


def _generate_possible_multiple_options_values(
self,
possible_values: list,
config_element_values,
parent_values: list = None,
):
if parent_values is None:
parent_values = []
for combinable_value in config_element_values:
if combinable_value not in parent_values:
new_parent_values = parent_values + [combinable_value]
# sort to check uniqueness
new_parent_values.sort()
if new_parent_values not in possible_values:
possible_values.append(new_parent_values)
self._generate_possible_multiple_options_values(
possible_values,
config_element_values,
new_parent_values,
)

@staticmethod
def get_accurate_number_type(*values):
return int if all(isinstance(e, int) for e in values) else float
Expand All @@ -841,6 +869,9 @@ def _get_config_elements(self):
}

def _get_config_type(self, value):
config_type = value.get(self.CONFIG_TYPE)
if config_type:
return ConfigTypes(config_type)
config_values = value[self.CONFIG_VALUE]
if isinstance(config_values, list):
if not config_values:
Expand Down
48 changes: 37 additions & 11 deletions tests/unit_tests/strategy_optimizer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import pytest_asyncio

import octobot.strategy_optimizer as strategy_optimizer
import octobot.strategy_optimizer.strategy_design_optimizer as strategy_design_optimizer
import octobot_trading.api as trading_api
import octobot_tentacles_manager.api as tentacles_manager_api
import octobot_commons.enums as commons_enums
Expand All @@ -24,6 +25,7 @@

import tentacles.Evaluator.Strategies as Strategies
import tentacles.Evaluator.TA as Evaluator
import tentacles.Evaluator.RealTime as RealTimeEvaluator
import tentacles.Trading.Mode as Mode


Expand All @@ -39,13 +41,14 @@ async def optimizer_inputs():
return tentacles_setup_config, trading_mode


def _get_optimizer_user_input_config(tentacle, user_input_name, enabled, value):
def _get_optimizer_user_input_config(tentacle, user_input_name, enabled, value, value_type=None):
return {
get_ui_identifier(tentacle, user_input_name): {
"enabled": enabled,
"tentacle": tentacle.get_name(),
"user_input": user_input_name,
"value": value
"value": value,
"type": value_type
}
}

Expand Down Expand Up @@ -84,6 +87,15 @@ def _get_mocked_ui_config():
),
_get_optimizer_user_input_config(
Strategies.SimpleStrategyEvaluator, evaluators_constants.STRATEGIES_REQUIRED_TIME_FRAME, True,
[
commons_enums.TimeFrames.FIVE_MINUTES.value,
commons_enums.TimeFrames.ONE_HOUR.value,
commons_enums.TimeFrames.ONE_DAY.value,
],
value_type=strategy_design_optimizer.ConfigTypes.MULTIPLE_OPTIONS.value
),
_get_optimizer_user_input_config(
RealTimeEvaluator.InstantFluctuationsEvaluator, commons_constants.CONFIG_TIME_FRAME, True,
[
commons_enums.TimeFrames.FIVE_MINUTES.value,
commons_enums.TimeFrames.ONE_HOUR.value,
Expand Down Expand Up @@ -145,37 +157,51 @@ def _get_mocked_filters_settings():
}


def _get_mocked_run_ui(ui_name, tentacle, value):
def _get_mocked_run_ui(
ui_name, tentacle, value):
return {
strategy_optimizer.StrategyDesignOptimizer.CONFIG_USER_INPUT: ui_name,
strategy_optimizer.StrategyDesignOptimizer.CONFIG_TENTACLE: [tentacle.get_name()],
strategy_optimizer.StrategyDesignOptimizer.CONFIG_VALUE: value
strategy_optimizer.StrategyDesignOptimizer.CONFIG_VALUE: value,
}


def _get_mocked_run(period_length, constrained_risk, time_frame):
def _get_mocked_run(period_length, constrained_risk, time_frame, time_frames):
return (
_get_mocked_run_ui("period_length", Evaluator.RSIMomentumEvaluator, period_length),
_get_mocked_run_ui("constrained_risk", Evaluator.RSIMomentumEvaluator, constrained_risk),
_get_mocked_run_ui(evaluators_constants.STRATEGIES_REQUIRED_TIME_FRAME,
Strategies.SimpleStrategyEvaluator,
[time_frame.value]),
time_frames),
_get_mocked_run_ui(commons_constants.CONFIG_TIME_FRAME,
RealTimeEvaluator.InstantFluctuationsEvaluator,
time_frame.value),
_get_mocked_run_ui(Strategies.SimpleStrategyEvaluator.RE_EVAL_TA_ON_RT_OR_SOCIAL,
Strategies.SimpleStrategyEvaluator, False),
)


MOCKED_RUNS = [
_get_mocked_run(period_length_val, constrained_risk_val, time_frame_val)
_get_mocked_run(period_length_val, constrained_risk_val,
time_frame_val, time_frames_vals)
for period_length_val in (5, 7, 9, 11, 13)
for constrained_risk_val in (1, 2)
for time_frame_val in (commons_enums.TimeFrames.FIVE_MINUTES,
commons_enums.TimeFrames.ONE_HOUR,
commons_enums.TimeFrames.ONE_DAY)
for time_frames_vals in (
[commons_enums.TimeFrames.FIVE_MINUTES.value],
[commons_enums.TimeFrames.ONE_HOUR.value],
[commons_enums.TimeFrames.ONE_DAY.value],
[commons_enums.TimeFrames.ONE_HOUR.value, commons_enums.TimeFrames.FIVE_MINUTES.value],
[commons_enums.TimeFrames.ONE_DAY.value, commons_enums.TimeFrames.ONE_HOUR.value],
[commons_enums.TimeFrames.ONE_DAY.value, commons_enums.TimeFrames.FIVE_MINUTES.value],
[commons_enums.TimeFrames.ONE_DAY.value,
commons_enums.TimeFrames.ONE_HOUR.value,
commons_enums.TimeFrames.FIVE_MINUTES.value])
]


EXPECTED_RUNS_FROM_MOCK = {
index + 6: val # index + 6 as the first 5 runs have been filtered
for index, val in enumerate(MOCKED_RUNS)
}
EXPECTED_RUNS_FROM_MOCK = {}
for index, val in enumerate(MOCKED_RUNS):
EXPECTED_RUNS_FROM_MOCK[index] = val
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,18 @@ async def test_generate_and_save_strategy_optimizer_runs(optimizer_inputs):
mock.patch.object(strategy_optimizer.StrategyDesignOptimizer, "shuffle_and_select_runs", mock.Mock(
side_effect=lambda *args, **_: args[0]
)) as shuffle_and_select_runs_mock:
assert await bot_module_api.generate_and_save_strategy_optimizer_runs(
generated_runs = await bot_module_api.generate_and_save_strategy_optimizer_runs(
trading_mode,
tentacles_setup_config,
optimizer_settings
) == EXPECTED_RUNS_FROM_MOCK
)
expected_run_list = list(EXPECTED_RUNS_FROM_MOCK.values())
assert len(expected_run_list) == len(generated_runs)
for run in generated_runs.values():
assert run in expected_run_list
create_identifier_mock.assert_called_once()
shuffle_and_select_runs_mock.assert_called_once()
_save_run_schedule_mock.assert_awaited_once_with(EXPECTED_RUNS_FROM_MOCK)
_save_run_schedule_mock.assert_awaited_once_with(generated_runs)


async def test_resume_unknown_mode(optimizer_inputs):
Expand Down

0 comments on commit 96ceb59

Please sign in to comment.