fix + improve optimize's debugging mode

This commit is contained in:
Saleh Mir
2022-02-09 17:28:12 +01:00
parent b75f1e87b5
commit cd7c41a2e0
7 changed files with 84 additions and 20 deletions

View File

@@ -347,6 +347,9 @@ def cancel_optimization(request_json: CancelRequestJson, authorization: Optional
@fastapi_app.get("/download/{mode}/{file_type}/{session_id}")
def download(mode: str, file_type: str, session_id: str, token: str = Query(...)):
"""
Log files require session_id because there is one log per each session. Except for the optimize mode
"""
if not authenticator.is_valid_token(token):
return authenticator.unauthorized_response()
@@ -355,6 +358,19 @@ def download(mode: str, file_type: str, session_id: str, token: str = Query(...)
return data_provider.download_file(mode, file_type, session_id)
@fastapi_app.get("/download/optimize/log")
def download_optimization_log(token: str = Query(...)):
"""
Optimization logs don't have have session ID
"""
if not authenticator.is_valid_token(token):
return authenticator.unauthorized_response()
from jesse.modes import data_provider
return data_provider.download_file('optimize', 'log')
@fastapi_app.delete("/backtest")
def cancel_backtest(request_json: CancelRequestJson, authorization: Optional[str] = Header(None)):
if not authenticator.is_valid_token(authorization):

View File

@@ -169,7 +169,7 @@ def update_config(client_config: dict):
database.close_connection()
def download_file(mode: str, file_type: str, session_id: str):
def download_file(mode: str, file_type: str, session_id: str = None):
if mode == 'backtest' and file_type == 'log':
path = f'storage/logs/backtest-mode/{session_id}.txt'
filename = f'backtest-{session_id}.txt'
@@ -188,6 +188,10 @@ def download_file(mode: str, file_type: str, session_id: str):
elif mode == 'backtest' and file_type == 'tradingview':
path = f'storage/trading-view-pine-editor/{session_id}.txt'
filename = f'backtest-{session_id}.txt'
elif mode == 'optimize' and file_type == 'log':
path = f'storage/logs/optimize-mode.txt'
# filename should be "optimize-" + current timestamp
filename = f'optimize-{jh.timestamp_to_date(jh.now(True))}.txt'
else:
raise Exception(f'Unknown file type: {file_type} or mode: {mode}')

View File

@@ -181,26 +181,40 @@ class Optimizer(ABC):
# len(self.population) instead of self.population_size because some DNAs might not have been created due to errors
# to fix an issue with being less than 100 population_len (which means there's only on hyperparameter in the strategy)
population_len = len(self.population)
random_index = np.random.choice(population_len, int(population_len / 100), replace=False)
if population_len == 0:
raise IndexError('population is empty')
count = int(population_len / 100)
if count == 0:
count = 1
random_index = np.random.choice(population_len, count, replace=False)
chosen_ones = [self.population[r] for r in random_index]
return pydash.max_by(chosen_ones, 'fitness')
def evolve(self) -> list:
"""
the main method, that runs the evolutionary algorithm
"""
# clear the logs to start from a clean slate
jh.clear_file('storage/logs/optimize-mode.txt')
logger.log_optimize_mode('Optimization session started')
if self.started_index == 0:
logger.log_optimize_mode(
f"Generating {self.population_size} population size (random DNAs) using {self.cpu_cores} CPU cores"
)
self.generate_initial_population()
if len(self.population) < 0.5 * self.population_size:
raise ValueError(f'Too many errors! less than half of the expected population size could be generated. Only {len(self.population)} indviduals from planned {self.population_size} are usable.')
msg = f'Too many errors! less than half of the expected population size could be generated. Only {len(self.population)} indviduals from planned {self.population_size} are usable.'
logger.log_optimize_mode(msg)
raise ValueError(msg)
# if even our best individual is too weak, then we better not continue
if self.population[0]['fitness'] == 0.0001:
raise exceptions.InvalidStrategy(
'Cannot continue because no individual with the minimum fitness-score was found. '
'Your strategy seems to be flawed or maybe it requires modifications. ')
msg = 'Cannot continue because no individual with the minimum fitness-score was found. Your strategy seems to be flawed or maybe it requires modifications. '
logger.log_optimize_mode(msg)
raise exceptions.InvalidStrategy(msg)
loop_length = int(self.iterations / self.cpu_cores)

View File

@@ -35,7 +35,6 @@ def get_fitness(
hp = jh.dna_to_hp(strategy_hp, dna)
# run backtest simulation
# TODO: log errors if any
try:
training_data_metrics = isolated_backtest(
_formatted_inputs_for_isolated_backtest(optimization_config, routes),
@@ -43,9 +42,12 @@ def get_fitness(
extra_routes,
training_candles,
hyperparameters=hp
)
)['metrics']
except Exception as e:
jh.dump("".join(traceback.TracebackException.from_exception(e).format()))
# get the main title of the exception
log_text = e
log_text = f"Exception in strategy execution:\n {log_text}"
logger.log_optimize_mode(log_text)
raise e
training_log = {'win-rate': None, 'total': None, 'PNL': None}
@@ -83,6 +85,7 @@ def get_fitness(
if ratio < 0:
score = 0.0001
logger.log_optimize_mode(f"NEGATIVE RATIO: DNA is not usable => {ratio_config}: {ratio}, total: {training_data_metrics['total']}")
return score, training_log, testing_log
# log for debugging/monitoring
@@ -95,7 +98,11 @@ def get_fitness(
score = total_effect_rate * ratio_normalized
# if score is numpy nan, replace it with 0.0001
if np.isnan(score):
logger.log_optimize_mode(f'Score is nan. DNA is invalid')
score = 0.0001
# elif jh.is_debugging():
else:
logger.log_optimize_mode(f"DNA is usable => {ratio_config}: {round(ratio, 2)}, total: {training_data_metrics['total']}, PNL%: {round(training_data_metrics['net_profit_percentage'], 2)}%, win-rate: {round(training_data_metrics['win_rate']*100, 2)}%")
# run backtest simulation
testing_data_metrics = isolated_backtest(
@@ -104,7 +111,7 @@ def get_fitness(
extra_routes,
testing_candles,
hyperparameters=hp
)
)['metrics']
# log for debugging/monitoring
if testing_data_metrics['total'] > 0:
@@ -112,8 +119,8 @@ def get_fitness(
'win-rate': int(testing_data_metrics['win_rate'] * 100), 'total': testing_data_metrics['total'],
'PNL': round(testing_data_metrics['net_profit_percentage'], 2)
}
else:
logger.log_optimize_mode(f'Less than 5 trades in the training data. DNA is invalid')
score = 0.0001
return score, training_log, testing_log
@@ -137,9 +144,8 @@ def get_and_add_fitness_to_the_bucket(
else:
raise ValueError(f"Initial Population: Double DNA: {dna}")
except Exception as e:
proc = os.getpid()
logger.error(f'process failed - ID: {proc}')
logger.error("".join(traceback.TracebackException.from_exception(e).format()))
pid = os.getpid()
logger.log_optimize_mode(f"process failed (ID: {pid}):\n{e}")
def make_love(
@@ -201,6 +207,5 @@ def create_baby(
)
people_bucket.append(baby)
except Exception as e:
proc = os.getpid()
logger.error(f'process failed - ID: {proc}')
logger.error("".join(traceback.TracebackException.from_exception(e).format()))
pid = os.getpid()
logger.log_optimize_mode(f"process failed - ID: {pid}\n{e}")

View File

@@ -99,7 +99,7 @@ def _isolated_backtest(
simulator(trading_candles, run_silently, hyperparameters)
result = {
'metrics': None,
'metrics': {'total': 0, 'win_rate': 0, 'net_profit_percentage': 0},
'charts': None,
'logs': None,
}

View File

@@ -41,6 +41,16 @@ def create_disposable_logger(name):
LOGGERS[name] = new_logger
def create_logger_file(name):
log_file = f"storage/logs/{name}.txt"
os.makedirs('storage/logs', exist_ok=True)
new_logger = logging.getLogger(name)
new_logger.setLevel(logging.INFO)
# should add to the end of file
new_logger.addHandler(logging.FileHandler(log_file, mode='a'))
LOGGERS[name] = new_logger
def info(msg: str, send_notification=False) -> None:
if jh.app_mode() not in LOGGERS:
_init_main_logger()
@@ -123,6 +133,21 @@ def log_exchange_message(exchange, message):
LOGGERS['exchange-streams'].info(message)
def log_optimize_mode(message):
# if the type of message is not str, convert it to str
if not isinstance(message, str):
message = str(message)
formatted_time = jh.timestamp_to_time(jh.now())[:19]
message = f'[{formatted_time}]: ' + message
file_name = 'optimize-mode'
if file_name not in LOGGERS:
create_logger_file(file_name)
LOGGERS[file_name].info(message)
def broadcast_error_without_logging(msg: str):
msg = str(msg)

View File

@@ -47,7 +47,7 @@ def trades(trades_list: List[CompletedTrade], daily_balance: list, final: bool =
current_balance += store.exchanges.storage[e].assets[jh.app_currency()]
if not trades_list:
return {'total': 0}
return {'total': 0, 'win_rate': 0, 'net_profit_percentage': 0}
df = pd.DataFrame.from_records([t.to_dict() for t in trades_list])