fix + improve optimize's debugging mode
This commit is contained in:
@@ -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):
|
||||
|
||||
@@ -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}')
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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}")
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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])
|
||||
|
||||
|
||||
Reference in New Issue
Block a user