From 72c8773263149e090bfb6f9adaee884a97c77ff5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20S=C3=A1nchez?= Date: Tue, 25 Feb 2025 20:24:50 -0300 Subject: [PATCH] ConfigHandler almost ready (without parameter validation) --- config_handler.py | 47 +++++++++++-- exchange_wrapper.py | 2 + status_handler.py | 35 +++++++++- trader.py | 164 +++++++++++++++++++++----------------------- 4 files changed, 155 insertions(+), 93 deletions(-) diff --git a/config_handler.py b/config_handler.py index c8411b6..7a33c9f 100644 --- a/config_handler.py +++ b/config_handler.py @@ -1,9 +1,12 @@ +import json + class ConfigHandler: ''' Handles the configuration of the trader and the validation of the parameters ''' - def __init__(self, pair, config_dict = None): + def __init__(self, pair, broker, config_dict = None): + self.broker = broker self.default_config_dictionary = { "pair": pair, "is_short": False, @@ -19,6 +22,7 @@ class ConfigHandler: "autoswitch": False, "attempt_restart": True, "tp_mode": 3, + "tp_level": 1.02, "tp_table": [], "check_slippage": True, "programmed_stop": False, @@ -34,13 +38,10 @@ class ConfigHandler: self.config_dictionary = self.default_config_dictionary.copy() else: self.config_dictionary = {**self.default_config_dictionary, **config_dict} + - - def get_config(self): - return self.config_dictionary - def get_pair(self): return self.config_dictionary["pair"] @@ -83,6 +84,9 @@ class ConfigHandler: def get_tp_mode(self): return self.config_dictionary["tp_mode"] + def get_tp_level(self): + return self.config_dictionary["tp_level"] + def get_tp_table(self): return self.config_dictionary["tp_table"] @@ -166,6 +170,10 @@ class ConfigHandler: self.config_dictionary["tp_mode"] = tp_mode return 0 + def set_tp_level(self, tp_level: float): + self.config_dictionary["tp_level"] = tp_level + return 0 + def set_tp_table(self, tp_table: list): self.config_dictionary["tp_table"] = tp_table return 0 @@ -202,4 +210,33 @@ class ConfigHandler: def set_check_old_long_price(self, check_old_long_price: bool): self.config_dictionary["check_old_long_price"] = check_old_long_price + return 0 + + + def save_to_file(self, file_path: str): + try: + with open(file_path, "w") as f: + json.dump(self.config_dictionary, f) + return 0 + except Exception as e: + self.broker.logger.log_this(f"Error saving config to file: {file_path}: {e}",1,self.get_pair()) + return 1 + + def load_from_file(self, file_path: str): + try: + with open(file_path, "r") as f: + self.set_config(json.load(f)) + return 0 + except Exception as e: + self.broker.logger.log_this(f"Error loading config to file: {file_path}: {e}",1,self.get_pair()) + return 1 + + def get_config(self): + return self.config_dictionary + + def set_config(self, config_dictionary: dict): + ''' + Validates every key in the config dictionary and sets the config dictionary + ''' + self.config_dictionary = config_dictionary return 0 \ No newline at end of file diff --git a/exchange_wrapper.py b/exchange_wrapper.py index a193d5a..e09d581 100755 --- a/exchange_wrapper.py +++ b/exchange_wrapper.py @@ -25,6 +25,7 @@ class Broker: self.logger = Logger(self.read_config) self.write_order_history = True #This should be a toggle in config_file + #Initialize database self.profits_database_filename = "profits/profits_database.db" self.database_connection = sqlite3.connect(self.profits_database_filename) self.database_cursor = self.database_connection.cursor() @@ -41,6 +42,7 @@ class Broker: self.database_connection.commit() self.database_connection.close() + #Load markets self.exchange.load_markets() #Populates deals cache diff --git a/status_handler.py b/status_handler.py index 560ca50..b17ef5c 100644 --- a/status_handler.py +++ b/status_handler.py @@ -1,9 +1,12 @@ +import json + class StatusHandler: ''' Handles the status of the trader and the validation of the parameters ''' - def __init__(self, status_dict = None): + def __init__(self, broker, status_dict = None): + self.broker = broker self.default_status_dictionary = { "tp_order_id": "", "take_profit_order": {}, @@ -274,10 +277,38 @@ class StatusHandler: self.status_dictionary["deal_order_history"] = deal_history return 0 + def update_deal_order_history(self, new_deal: dict): self.status_dictionary["deal_order_history"].append(new_deal) return 0 + def save_to_file(self, file_path: str): + try: + with open(file_path, "w") as f: + json.dump(self.status_dictionary, f) + return 0 + except Exception as e: + self.broker.logger.log_this(f"Error saving status to file: {file_path}: {e}",1) + return 1 + + def load_from_file(self, file_path: str): + try: + with open(file_path, "r") as f: + self.set_status(json.load(f)) + return 0 + except Exception as e: + self.broker.logger.log_this(f"Error loading status from file: {file_path}: {e}",1) + return 1 + + def get_status(self): - return self.status_dictionary \ No newline at end of file + return self.status_dictionary + + def set_status(self, dictionary: dict): + ''' + Validates every key in the dictionary and then sets the status dictionary + ''' + + self.status_dictionary = dictionary + return 0 \ No newline at end of file diff --git a/trader.py b/trader.py index ac70e9f..8331fc1 100755 --- a/trader.py +++ b/trader.py @@ -2,6 +2,8 @@ import csv import json import time import os +from config_handler import ConfigHandler +from status_handler import StatusHandler class trader: def __init__(self, broker, config_dict: dict, is_import: bool = False): @@ -12,20 +14,16 @@ class trader: self.broker = broker self.tp_order = self.broker.get_empty_order() self.so = self.broker.get_empty_order() - self.config_dict = config_dict - self.pair = self.config_dict["pair"] + self.config = ConfigHandler(config_dict["pair"],broker,config_dict) + self.pair = self.config.get_pair() self.market = self.broker.fetch_market(self.pair) self.market_load_time = int(time.time()) self.market_reload_period = 86400 #Market reload period in seconds self.base,self.quote = self.pair.split("/") - self.is_short = self.config_dict["is_short"] - self.profit_table = self.config_dict["tp_table"] - self.max_short_safety_orders = 45 - if "max_short_safety_orders" in config_dict: - self.max_short_safety_orders = config_dict["max_short_safety_orders"] - self.check_slippage = True - if "check_slippage" in self.config_dict: - self.check_slippage = self.config_dict["check_slippage"] + self.is_short = self.config.get_is_short() + self.profit_table = self.config.get_tp_table() + self.max_short_safety_orders = self.config.get_max_short_safety_orders() + self.check_slippage = self.config.get_check_slippage() self.is_boosted = False self.start_time = int(time.time()) self.total_amount_of_quote=0 @@ -42,10 +40,8 @@ class trader: "quote_spent": 0, "base_bought": 0, "so_amount": 0, - #"max_so_amount": config_dict["no_of_safety_orders"], "take_profit_price": 1, "next_so_price": 1, - #"acc_profit": 0, "tp_order_id": "", "take_profit_order": {}, "so_order_id": "", @@ -61,10 +57,12 @@ class trader: "short_price_exceeds_old_long": False, "speol_notified": False } - if "stop_time" in self.config_dict and int(self.config_dict["stop_time"])self.config_dict["order_size"]: + if minimum_order_size_allowed is not None and minimum_order_size_allowed>self.config.get_order_size(): self.broker.logger.log_this(f"Order size too small. Minimum order size is {minimum_order_size_allowed} {self.quote}",1,self.pair) - if minimum_order_size_allowedminimum_amount_of_safety_orders: - optimal_order_size = self.return_optimal_order_size(free_base,min_base_size,amount_of_so,self.config_dict["safety_order_scale"]) #safety_order_scale: safety order growth factor + optimal_order_size = self.return_optimal_order_size(free_base,min_base_size,amount_of_so,self.config.get_safety_order_scale()) #safety_order_scale: safety order growth factor if optimal_order_size!=0: self.broker.logger.log_this(f"Optimal order size is {optimal_order_size}",2,self.pair) break @@ -770,7 +762,7 @@ class trader: self.safety_order_index = 0 #Disabling autoswitch - #self.config_dict["autoswitch"] = False + #self.config.set_autoswitch(False) #Done. Ready for start_trader return 0 @@ -894,7 +886,7 @@ class trader: self.status_dict["pause_reason"] = "take_profit_routine - check time limit" #Checks if there is a time limit for the trader - if "stop_time" in self.config_dict and time.time()>int(self.config_dict["stop_time"]): + if self.config.get_programmed_stop() and time.time()>int(self.config.get_programmed_stop_time()): self.stop_when_profit = True self.status_dict["pause_reason"] = "take_profit_routine - if stop_when_profit" @@ -918,7 +910,7 @@ class trader: self.pause = False self.restart = True return 1 - elif self.check_orderbook_depth(self.broker.get_slippage_default_threshold(),self.config_dict["order_size"],filled_order["price"]): + elif self.check_orderbook_depth(self.broker.get_slippage_default_threshold(),self.config.get_order_size(),filled_order["price"]): self.broker.logger.log_this(f"Orderbook depth not sufficient, waiting for cooldown and restarting trader",1,self.pair) time.sleep(self.broker.get_wait_time()*self.broker.get_cooldown_multiplier()) self.pause = False @@ -1046,7 +1038,7 @@ class trader: if not self.warnings["speol_notified"] and price_exceeds: #Only notify one time AND if autoswitch is off self.warnings["speol_notified"] = True - if not self.config_dict["autoswitch"]: + if not self.config.get_autoswitch(): message = f"{self.base}@{self.status_dict['price']} ({str(self.broker.exchange)}), exceeds old long price of {self.status_dict['old_long']['tp_price']}" self.broker.logger.log_this(message,0,self.pair) return 0 @@ -1120,7 +1112,7 @@ class trader: if self.tp_order["id"]=="": self.broker.logger.log_this(f"Take profit order missing. Stopping bot. Order ID: {self.tp_order['id']}",1,self.pair) self.broker.cancel_order(self.so["id"],self.pair) - if self.config_dict["attempt_restart"]: + if self.config.get_attempt_restart(): self.write_status_file(is_backup=True) self.restart = True self.broker.logger.log_this("Raising restart flag: take profit order missing, trader will be restarted",0,self.pair) @@ -1137,7 +1129,7 @@ class trader: self.broker.logger.log_this(f"Take profit order closed but not filled, 0 filled. Stopping bot. Order ID: {self.tp_order['id']}",1,self.pair) #Cancelling safety order and stopping bot self.broker.cancel_order(self.so["id"],self.pair) - if self.config_dict["attempt_restart"]: + if self.config.get_attempt_restart(): self.write_status_file(is_backup=True) self.restart = True self.broker.logger.log_this("Take profit order closed but not filled, trader will be restarted.",0,self.pair) @@ -1146,7 +1138,7 @@ class trader: return 1 elif tp_status["status"]=="canceled": #TODO: Here, if the safety order is still open, we could resend the tp order. - if self.config_dict["attempt_restart"]: + if self.config.get_attempt_restart(): self.broker.logger.log_this("Take profit order canceled. Restarting the bot.",1,self.pair) self.write_status_file(is_backup=True) self.restart = True @@ -1158,7 +1150,7 @@ class trader: return 1 # Check if safety order is filled - if self.so["id"] not in open_orders_ids and self.safety_order_index<=self.config_dict["no_of_safety_orders"]: + if self.so["id"] not in open_orders_ids and self.safety_order_index<=self.config.get_no_of_safety_orders(): #so_status = self.so #if self.so["id"]!="": so_status = self.broker.get_order(self.so["id"],self.pair) @@ -1170,42 +1162,42 @@ class trader: if so_status["status"] in ["closed", "canceled", ""] and tp_order_status["status"]=="open": #Switch to short if all safety orders are sent and autoswitch is enabled. #May get into trouble if the trader is short of funds - if not self.is_short and self.safety_order_index==self.config_dict["no_of_safety_orders"] and self.config_dict["autoswitch"]: + if not self.is_short and self.safety_order_index==self.config.get_no_of_safety_orders() and self.config.get_autoswitch(): self.switch_to_short() self.write_status_file(is_backup=True) self.restart = True return 0 - a = self.new_so_routine(so_status,self.safety_order_index float: ''' - Returns the correct take profit percentage, according to the strategy (config_dict["tp_mode"]): + Returns the correct take profit percentage, according to the strategy (config.get_tp_mode()): 0. Fixed percentage 1. Variable percentage (+0.5% to -0.5% of the fixed percentage) 2. Custom percentage table @@ -1257,26 +1249,26 @@ class trader: self.is_boosted = True boost_percentage = self.boosted_amount - if self.is_short or self.config_dict["tp_mode"]==0: #Fixed take profit percentage - tp_level = self.config_dict["tp_level"] - elif self.config_dict["tp_mode"]==1: #Variable percentage - limit = self.config_dict["no_of_safety_orders"]/3 + if self.is_short or self.config.get_tp_mode()==0: #Fixed take profit percentage + tp_level = self.config.get_tp_level() + elif self.config.get_tp_mode()==1: #Variable percentage + limit = self.config.get_no_of_safety_orders()/3 if order_index<=1: - tp_level = self.config_dict["tp_level"]+0.005 + tp_level = self.config.get_tp_level()+0.005 elif order_index<=limit: - tp_level = self.config_dict["tp_level"] + tp_level = self.config.get_tp_level() elif limit<=order_index<=limit*2: - tp_level = self.config_dict["tp_level"]-0.0025 + tp_level = self.config.get_tp_level()-0.0025 else: - tp_level = self.config_dict["tp_level"]-0.005 - elif self.config_dict["tp_mode"]==2: + tp_level = self.config.get_tp_level()-0.005 + elif self.config.get_tp_mode()==2: if ["tp_table"] in self.config_dict: - if len(self.config_dict["tp_table"])>=order_index: - tp_level = self.config_dict["tp_table"][order_index] #Custom percentage table - tp_level = self.config_dict["tp_table"][-1] - tp_level = self.config_dict["tp_level"] - elif self.config_dict["tp_mode"]==3: #Linear percentage table - profit_table = self.linear_space(self.config_dict["tp_level"]+0.005,self.config_dict["tp_level"]-0.005,self.config_dict["no_of_safety_orders"]) + if len(self.config.get_tp_table())>=order_index: + tp_level = self.config.get_tp_table()[order_index] #Custom percentage table + tp_level = self.config.get_tp_table()[-1] + tp_level = self.config.get_tp_level() + elif self.config.get_tp_mode()==3: #Linear percentage table + profit_table = self.linear_space(self.config.get_tp_level()+0.005,self.config.get_tp_level()-0.005,self.config.get_no_of_safety_orders()) tp_level = profit_table[-1] if order_index=30: - #if self.config_dict["dynamic_so_deviance"] and not self.is_short: + if self.config.get_dynamic_so_deviance():# and no_of_safety_orders>=30: + #if self.config.get_dynamic_so_deviance() and not self.is_short: #bias should be a real number between -1 and 1 (1>n>-1, NOT 1=>n>=-1) #If bias -> 1, more space between the first orders, if -> -1, more space between the last orders, if 0, no change.. if "bias" in self.config_dict: - deviance_factor = safety_order_deviance*self.clip_value(self.config_dict["bias"],-.99,.99) + deviance_factor = safety_order_deviance*self.clip_value(self.config.get_bias(),-.99,.99) so_deviance_table = self.linear_space(safety_order_deviance+deviance_factor,safety_order_deviance-deviance_factor,no_of_safety_orders) else: #Old way of calculating deviance - so_deviance_table = self.linear_space(safety_order_deviance-self.config_dict["dsd_range"],safety_order_deviance+self.config_dict["dsd_range"],no_of_safety_orders) + so_deviance_table = self.linear_space(safety_order_deviance-self.config.get_dsd_range(),safety_order_deviance+self.config.get_dsd_range(),no_of_safety_orders) so_deviance_table.extend([so_deviance_table[-1]]*2) #This extra entries are needed in the next for loop else: so_deviance_table = [safety_order_deviance]*(no_of_safety_orders+2) @@ -1540,10 +1532,10 @@ class trader: #Change pair-related variables old_quote = self.quote self.quote = new_quote - self.config_dict["pair"] = f"{self.base}/{self.quote}" + self.config.set_pair(f"{self.base}/{self.quote}") self.profit_filename = f"profits/{self.base}{self.quote}.profits" self.log_filename = f"logs/{self.base}{self.quote}.log" - self.pair = self.config_dict["pair"] + self.pair = self.config.get_pair() #If there is an old_long file, also copy it if self.is_short and "old_long" in self.status_dict: @@ -1576,10 +1568,10 @@ class trader: self.broker.remove_pair_from_config(f"{self.base}{new_quote}") self.broker.add_pair_to_config(f"{self.base}{self.quote}") - self.config_dict["pair"] = f"{self.base}/{self.quote}" + self.config.set_pair(f"{self.base}/{self.quote}") self.profit_filename = f"profits/{self.base}{self.quote}.profits" self.log_filename = f"logs/{self.base}{self.quote}.log" - self.pair = self.config_dict["pair"] + self.pair = self.config.get_pair() #Writing config file if write_broker_file and self.broker.rewrite_config_file()==1: @@ -1626,7 +1618,7 @@ class trader: mid_percentage = 10 high_percentage = 20 - safety_order_string = f"{self.status_dict['so_amount']-1}/{self.config_dict['no_of_safety_orders']}".rjust(5) + safety_order_string = f"{self.status_dict['so_amount']-1}/{self.config.get_no_of_safety_orders()}".rjust(5) #Check if necessary low_price = 0 @@ -1701,12 +1693,12 @@ class trader: line1 = f"{p}{pair_color}{self.pair.center(13)}{white}| {safety_order_string} |{prices}| Uptime: {self.seconds_to_time(self.status_dict['deal_uptime'])}" if self.is_boosted: line1 = f"{line1} | BOOSTED" - if self.config_dict["autoswitch"]: + if self.config.get_autoswitch(): line1 = f"{line1} | AUTO" if multiplier>1: #Only displays the multiplier if autoswitch is enabled. line1 = f"{line1}x{multiplier}" - if "stop_time" in self.config_dict and time.time()<=int(self.config_dict["stop_time"]): + if self.config.get_programmed_stop() and time.time()<=self.config.get_programmed_stop_time(): line1 = f"{line1} | PROGRAMMED LAST DEAL" if self.stop_when_profit==True: line1 = f"{line1} | LAST DEAL" @@ -1733,7 +1725,7 @@ class trader: self.total_amount_of_quote = self.status_dict["quote_spent"] self.total_amount_of_base = self.status_dict["base_bought"] self.safety_order_index = self.status_dict["so_amount"] - self.config_dict["no_of_safety_orders"] = self.status_dict["no_of_safety_orders"] #If this is not loaded from status_dict, it will ignore if safety orders were added at runtime + self.config.set_no_of_safety_orders(self.status_dict["no_of_safety_orders"]) #If this is not loaded from status_dict, it will ignore if safety orders were added at runtime self.take_profit_price = self.status_dict["take_profit_price"] self.safety_price_table = self.status_dict["safety_price_table"] self.fees_paid_in_base = self.status_dict["fees_paid_in_base"] @@ -1756,9 +1748,9 @@ class trader: #Load safety order self.so = self.broker.get_order(self.status_dict["so_order_id"],self.pair) - if self.so==self.broker.get_empty_order() and self.safety_order_index