Skip to content

Commit

Permalink
[Optimizer] allow multi exchange backtests and handle settings fallbacks
Browse files Browse the repository at this point in the history
  • Loading branch information
techfreaque committed Mar 3, 2023
1 parent 559158b commit cd524a0
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 60 deletions.
1 change: 1 addition & 0 deletions octobot/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@
OPTIMIZER_DEFAULT_MIN_MUTATION_PROBABILITY_PERCENT = decimal.Decimal(10)
OPTIMIZER_DEFAULT_MAX_MUTATION_NUMBER_MULTIPLIER = 3
OPTIMIZER_DEFAULT_DB_UPDATE_PERIOD = 15
OPTIMIZER_DEFAULT_TARGET_FITNESS_SCORE = None

# Databases
DEFAULT_MAX_TOTAL_RUN_DATABASES_SIZE = 1000000000 # 1GB
Expand Down
2 changes: 2 additions & 0 deletions octobot/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ class OptimizerConfig(enum.Enum):
DATA_FILES = "data_files"
OPTIMIZER_CONFIG = "optimizer_config"
EXCHANGE_TYPE = "exchange_type"
EXCHANGE_IDS = "exchange_ids"
EXCHANGE_ID = "exchange_id"
QUEUE_SIZE = "queue_size"
EMPTY_THE_QUEUE = "empty_the_queue"
START_TIMESTAMP = "start_timestamp"
Expand Down
185 changes: 125 additions & 60 deletions octobot/strategy_optimizer/optimizer_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,72 +21,142 @@
import octobot.strategy_optimizer.fitness_parameter as fitness_parameter
import octobot.strategy_optimizer.optimizer_filter as optimizer_filter
import octobot.strategy_optimizer.optimizer_constraint as optimizer_constraint
import tentacles.Services.Interfaces.web_interface.models.backtesting as backtesting_models


class OptimizerSettings:
def __init__(self, settings_dict=None):
if settings_dict is None:
settings_dict = {}
# generic
self.optimizer_config = settings_dict.get(enums.OptimizerConfig.OPTIMIZER_CONFIG.value, None)
self.randomly_chose_runs = settings_dict.get(enums.OptimizerConfig.RANDOMLY_CHOSE_RUNS.value,
constants.OPTIMIZER_DEFAULT_RANDOMLY_CHOSE_RUNS)
self.data_files = settings_dict.get(enums.OptimizerConfig.DATA_FILES.value)
self.start_timestamp = settings_dict.get(enums.OptimizerConfig.START_TIMESTAMP.value, None)
self.end_timestamp = settings_dict.get(enums.OptimizerConfig.END_TIMESTAMP.value, None)
self.required_idle_cores = int(settings_dict.get(enums.OptimizerConfig.IDLE_CORES.value,
constants.OPTIMIZER_DEFAULT_REQUIRED_IDLE_CORES))
self.notify_when_complete = settings_dict.get(enums.OptimizerConfig.NOTIFY_WHEN_COMPLETE.value,
constants.OPTIMIZER_DEFAULT_NOTIFY_WHEN_COMPLETE)
self.optimizer_mode = settings_dict.get(enums.OptimizerConfig.MODE.value,
enums.OptimizerModes.NORMAL.value)
self.exchange_ids = settings_dict.get(
enums.OptimizerConfig.EXCHANGE_IDS.value,
)
if not self.exchange_ids:
self.exchange_ids = [
settings_dict.get(enums.OptimizerConfig.EXCHANGE_ID.value)
]

self.exchange_type = settings_dict.get(
enums.OptimizerConfig.EXCHANGE_TYPE.value
)
self.optimizer_config = (
settings_dict.get(enums.OptimizerConfig.OPTIMIZER_CONFIG.value) or None
)
self.randomly_chose_runs = (
settings_dict.get(enums.OptimizerConfig.RANDOMLY_CHOSE_RUNS.value)
or constants.OPTIMIZER_DEFAULT_RANDOMLY_CHOSE_RUNS
)

self.data_files = settings_dict.get(
enums.OptimizerConfig.DATA_FILES.value,
[backtesting_models.CURRENT_BOT_DATA],
)
if not isinstance(self.data_files, list):
self.data_files = [self.data_files]
if backtesting_models.CURRENT_BOT_DATA in self.data_files:
self.data_files = [backtesting_models.CURRENT_BOT_DATA]
self.start_timestamp = settings_dict.get(
enums.OptimizerConfig.START_TIMESTAMP.value, None
)
self.end_timestamp = settings_dict.get(
enums.OptimizerConfig.END_TIMESTAMP.value, None
)
self.required_idle_cores = int(
settings_dict.get(enums.OptimizerConfig.IDLE_CORES.value)
or constants.OPTIMIZER_DEFAULT_REQUIRED_IDLE_CORES
)
self.notify_when_complete = (
settings_dict.get(enums.OptimizerConfig.NOTIFY_WHEN_COMPLETE.value)
or constants.OPTIMIZER_DEFAULT_NOTIFY_WHEN_COMPLETE
)
self.optimizer_mode = settings_dict.get(
enums.OptimizerConfig.MODE.value, enums.OptimizerModes.NORMAL.value
)
optimizer_id = settings_dict.get(enums.OptimizerConfig.OPTIMIZER_ID.value, 1)
self.optimizer_id = optimizer_id if optimizer_id is None else int(optimizer_id)
self.optimizer_ids = settings_dict.get(enums.OptimizerConfig.OPTIMIZER_IDS.value)
self.optimizer_mode = settings_dict.get(enums.OptimizerConfig.MODE.value,
enums.OptimizerModes.NORMAL.value)
self.queue_size = int(settings_dict.get(enums.OptimizerConfig.QUEUE_SIZE.value,
constants.OPTIMIZER_DEFAULT_QUEUE_SIZE))
self.empty_the_queue = settings_dict.get(enums.OptimizerConfig.EMPTY_THE_QUEUE.value, False)
self.optimizer_ids = settings_dict.get(
enums.OptimizerConfig.OPTIMIZER_IDS.value
)
self.optimizer_mode = settings_dict.get(
enums.OptimizerConfig.MODE.value, enums.OptimizerModes.NORMAL.value
)
self.queue_size = int(
settings_dict.get(
enums.OptimizerConfig.QUEUE_SIZE.value,
constants.OPTIMIZER_DEFAULT_QUEUE_SIZE,
)
)
self.empty_the_queue = settings_dict.get(
enums.OptimizerConfig.EMPTY_THE_QUEUE.value, False
)
# update run database at the end of each period
self.db_update_period = int(settings_dict.get(enums.OptimizerConfig.DB_UPDATE_PERIOD.value,
constants.OPTIMIZER_DEFAULT_DB_UPDATE_PERIOD))
self.db_update_period = int(
settings_dict.get(enums.OptimizerConfig.DB_UPDATE_PERIOD.value)
or constants.OPTIMIZER_DEFAULT_DB_UPDATE_PERIOD
)
# AI / genetic
self.max_optimizer_runs = settings_dict.get(enums.OptimizerConfig.MAX_OPTIMIZER_RUNS.value,
constants.OPTIMIZER_DEFAULT_MAX_OPTIMIZER_RUNS)
self.generations_count = settings_dict.get(enums.OptimizerConfig.DEFAULT_GENERATIONS_COUNT.value,
constants.OPTIMIZER_DEFAULT_GENERATIONS_COUNT)
self.initial_generation_count = settings_dict.get(enums.OptimizerConfig.INITIAL_GENERATION_COUNT.value,
constants.OPTIMIZER_DEFAULT_INITIAL_GENERATION_COUNT)
self.run_per_generation = settings_dict.get(enums.OptimizerConfig.DEFAULT_RUN_PER_GENERATION.value,
constants.OPTIMIZER_DEFAULT_RUN_PER_GENERATION)
self.max_optimizer_runs = (
settings_dict.get(enums.OptimizerConfig.MAX_OPTIMIZER_RUNS.value)
or constants.OPTIMIZER_DEFAULT_MAX_OPTIMIZER_RUNS
)
self.generations_count = (
settings_dict.get(enums.OptimizerConfig.DEFAULT_GENERATIONS_COUNT.value)
or constants.OPTIMIZER_DEFAULT_GENERATIONS_COUNT
)
self.initial_generation_count = (
settings_dict.get(enums.OptimizerConfig.INITIAL_GENERATION_COUNT.value)
or constants.OPTIMIZER_DEFAULT_INITIAL_GENERATION_COUNT
)
self.run_per_generation = (
settings_dict.get(enums.OptimizerConfig.DEFAULT_RUN_PER_GENERATION.value)
or constants.OPTIMIZER_DEFAULT_RUN_PER_GENERATION
)
self.fitness_parameters = self.parse_fitness_parameters(
settings_dict.get(enums.OptimizerConfig.DEFAULT_SCORING_PARAMETERS.value,
self.get_default_fitness_parameters())
settings_dict.get(enums.OptimizerConfig.DEFAULT_SCORING_PARAMETERS.value)
or self.get_default_fitness_parameters()
)

self.exclude_filters = self.parse_optimizer_filter(
settings_dict.get(enums.OptimizerConfig.DEFAULT_OPTIMIZER_FILTERS.value,
self.get_default_optimizer_filters())
settings_dict.get(enums.OptimizerConfig.DEFAULT_OPTIMIZER_FILTERS.value)
or self.get_default_optimizer_filters()
)

self.constraints_by_key = self.parse_optimizer_constraint(
settings_dict.get(enums.OptimizerConfig.DEFAULT_OPTIMIZER_CONSTRAINTS.value,
self.get_default_optimizer_constraints())
)
self.mutation_percent = float(settings_dict.get(
enums.OptimizerConfig.DEFAULT_MUTATION_PERCENT.value,
constants.OPTIMIZER_DEFAULT_MUTATION_PERCENT))
self.max_mutation_probability_percent = decimal.Decimal(settings_dict.get(
enums.OptimizerConfig.MAX_MUTATION_PROBABILITY_PERCENT.value,
constants.OPTIMIZER_DEFAULT_MAX_MUTATION_PROBABILITY_PERCENT))
self.min_mutation_probability_percent = decimal.Decimal(settings_dict.get(
enums.OptimizerConfig.MIN_MUTATION_PROBABILITY_PERCENT.value,
constants.OPTIMIZER_DEFAULT_MIN_MUTATION_PROBABILITY_PERCENT))
self.max_mutation_number_multiplier = decimal.Decimal(settings_dict.get(
enums.OptimizerConfig.DEFAULT_MAX_MUTATION_NUMBER_MULTIPLIER.value,
constants.OPTIMIZER_DEFAULT_MAX_MUTATION_NUMBER_MULTIPLIER))
self.target_fitness_score = settings_dict.get(enums.OptimizerConfig.TARGET_FITNESS_SCORE.value)
self.stay_within_boundaries = settings_dict.get(enums.OptimizerConfig.STAY_WITHIN_BOUNDARIES.value,
False)
settings_dict.get(enums.OptimizerConfig.DEFAULT_OPTIMIZER_CONSTRAINTS.value)
or self.get_default_optimizer_constraints()
)

self.mutation_percent = float(
settings_dict.get(enums.OptimizerConfig.DEFAULT_MUTATION_PERCENT.value)
or constants.OPTIMIZER_DEFAULT_MUTATION_PERCENT
)
self.max_mutation_probability_percent = decimal.Decimal(
settings_dict.get(
enums.OptimizerConfig.MAX_MUTATION_PROBABILITY_PERCENT.value
)
or constants.OPTIMIZER_DEFAULT_MAX_MUTATION_PROBABILITY_PERCENT
)
self.min_mutation_probability_percent = decimal.Decimal(
settings_dict.get(
enums.OptimizerConfig.MIN_MUTATION_PROBABILITY_PERCENT.value
)
or constants.OPTIMIZER_DEFAULT_MIN_MUTATION_PROBABILITY_PERCENT
)
self.max_mutation_number_multiplier = decimal.Decimal(
settings_dict.get(
enums.OptimizerConfig.DEFAULT_MAX_MUTATION_NUMBER_MULTIPLIER.value
)
or constants.OPTIMIZER_DEFAULT_MAX_MUTATION_NUMBER_MULTIPLIER
)
self.target_fitness_score = (
settings_dict.get(enums.OptimizerConfig.TARGET_FITNESS_SCORE.value)
or constants.OPTIMIZER_DEFAULT_TARGET_FITNESS_SCORE
)
self.stay_within_boundaries = (
settings_dict.get(enums.OptimizerConfig.STAY_WITHIN_BOUNDARIES.value)
or False
)

def get_constraint(self, constraint_key):
if constraint_key in self.constraints_by_key:
Expand All @@ -95,8 +165,7 @@ def get_constraint(self, constraint_key):

def parse_fitness_parameters(self, parameters):
return [
fitness_parameter.FitnessParameter.from_dict(param)
for param in parameters
fitness_parameter.FitnessParameter.from_dict(param) for param in parameters
]

def get_default_fitness_parameters(self):
Expand All @@ -107,17 +176,15 @@ def get_default_fitness_parameters(self):
fitness_parameter.FitnessParameter.IS_RATIO_FROM_MAX_KEY: True,
},
{
fitness_parameter.FitnessParameter.NAME_KEY:
commons_enums.BacktestingMetadata.COEFFICIENT_OF_DETERMINATION_MAX_BALANCE.value,
fitness_parameter.FitnessParameter.NAME_KEY: commons_enums.BacktestingMetadata.COEFFICIENT_OF_DETERMINATION_MAX_BALANCE.value,
fitness_parameter.FitnessParameter.WEIGHT_KEY: 0,
fitness_parameter.FitnessParameter.IS_RATIO_FROM_MAX_KEY: False,
},
]

def parse_optimizer_filter(self, filters):
return [
optimizer_filter.OptimizerFilter.from_dict(element)
for element in filters
optimizer_filter.OptimizerFilter.from_dict(element) for element in filters
]

def get_default_optimizer_filters(self):
Expand All @@ -130,16 +197,14 @@ def get_default_optimizer_filters(self):
optimizer_filter.OptimizerFilter.OPERATOR_KEY: commons_enums.LogicalOperators.LOWER_THAN.value,
},
{
optimizer_filter.OptimizerFilter.LEFT_OPERAND_KEY_KEY:
commons_enums.BacktestingMetadata.COEFFICIENT_OF_DETERMINATION_MAX_BALANCE.value,
optimizer_filter.OptimizerFilter.LEFT_OPERAND_KEY_KEY: commons_enums.BacktestingMetadata.COEFFICIENT_OF_DETERMINATION_MAX_BALANCE.value,
optimizer_filter.OptimizerFilter.RIGHT_OPERAND_KEY_KEY: None,
optimizer_filter.OptimizerFilter.LEFT_OPERAND_VALUE_KEY: None,
optimizer_filter.OptimizerFilter.RIGHT_OPERAND_VALUE_KEY: 0,
optimizer_filter.OptimizerFilter.OPERATOR_KEY: commons_enums.LogicalOperators.LOWER_THAN.value,
},
{
optimizer_filter.OptimizerFilter.LEFT_OPERAND_KEY_KEY:
commons_enums.BacktestingMetadata.PERCENT_GAINS.value,
optimizer_filter.OptimizerFilter.LEFT_OPERAND_KEY_KEY: commons_enums.BacktestingMetadata.PERCENT_GAINS.value,
optimizer_filter.OptimizerFilter.RIGHT_OPERAND_KEY_KEY: None,
optimizer_filter.OptimizerFilter.LEFT_OPERAND_VALUE_KEY: None,
optimizer_filter.OptimizerFilter.RIGHT_OPERAND_VALUE_KEY: 0,
Expand Down

0 comments on commit cd524a0

Please sign in to comment.