From cb5f6010b31a418b7e6aa9b04069b63e7fbc8664 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20S=C3=A1nchez?= Date: Thu, 27 Feb 2025 11:49:45 -0300 Subject: [PATCH] . --- changelog.txt | 5 +++ config_handler.py | 11 ++++-- exchange_wrapper.py | 14 +++++-- main.py | 91 ++++++++++++++------------------------------- trader.py | 4 +- 5 files changed, 52 insertions(+), 73 deletions(-) diff --git a/changelog.txt b/changelog.txt index 42d0334..871f2d2 100755 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,8 @@ +2025.02.27 +. config_handler: centralized configuration handling in an object, for easier future development (parameter validation, for example) +. Bugfixes everywhere. +. Exchange_wrapper now validates every symbol against the market information from the exchange, both when adding and importing a trader. + 2025.02.02: . new_so_routine now cancels the old take profit order after the new safety order is sent. diff --git a/config_handler.py b/config_handler.py index 5137bdd..ab26ba3 100644 --- a/config_handler.py +++ b/config_handler.py @@ -34,10 +34,13 @@ class ConfigHandler: "check_old_long_price": False #switch_to_short should flip this to True unless stated } self.config_file_path = f"configs/{pair.split('/')[0]}{pair.split('/')[1]}.json" - if config_dict is None: - self.config_dictionary = self.default_config_dictionary.copy() - else: + self.config_dictionary = self.default_config_dictionary.copy() + + #Loads from disk the config file (if it exists) + self.load_from_file() + if config_dict is not None: self.config_dictionary = {**self.default_config_dictionary, **config_dict} + self.save_to_file() @@ -232,7 +235,7 @@ class ConfigHandler: self.set_config({**self.default_config_dictionary, **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()) + self.broker.logger.log_this(f"Config file does not exist or is not readable: {e}",1,self.get_pair()) return 1 def get_config(self): diff --git a/exchange_wrapper.py b/exchange_wrapper.py index e09d581..58f45d3 100755 --- a/exchange_wrapper.py +++ b/exchange_wrapper.py @@ -43,7 +43,7 @@ class Broker: self.database_connection.close() #Load markets - self.exchange.load_markets() + self.markets = self.exchange.load_markets() #Populates deals cache self.deals_cache_length = 10 @@ -66,12 +66,20 @@ class Broker: def get_deals_cache(self): return self.deals_list + def get_symbol(self,pair): + if "/" in pair: + return pair + for item in self.markets: + if f"{self.markets[item]['base']}{self.markets[item]['quote']}"==pair: + return self.markets[item]["symbol"] + return "Error" def all_markets(self,no_retries=False): retries = self.retries while retries>0: try: - return self.exchange.load_markets() + self.markets = self.exchange.load_markets() + return self.markets except Exception as e: self.logger.log_this(f"Exception in reload_markets: {e}") if no_retries: @@ -83,7 +91,7 @@ class Broker: def reload_markets(self): try: - self.exchange.load_markets(reload=True) + self.markets = self.exchange.load_markets(reload=True) return 0 except Exception as e: self.logger.log_this(f"Exception in reload_markets: {e}") diff --git a/main.py b/main.py index f2a92c3..8c26a82 100644 --- a/main.py +++ b/main.py @@ -80,24 +80,21 @@ def time_to_unix(year: str, month: str, day: str) -> int: return 0 -def import_instance(pair: str) -> int: +def import_instance(base: str, quote: str) -> int: ''' Imports an previously running trader instance from the status file. Parameters: - pair (str): The trading pair to import with the format BASEQUOTE (no slash). + base (str): The base currency of the pair. + quote (str): The quote currency of the pair. Returns: int: 0 if successful ''' - broker.logger.log_this(f"Importing {pair}") - #with open(f"status/{pair}.status", "r") as f: - # status_file_contents = json.load(f) - with open(f"configs/{pair}.json", "r") as g: - config_file_contents = json.load(g) - instances_to_add.append(trader.trader(broker,config_file_contents,is_import=True)) - if pair not in tickers: - tickers.append(pair) + broker.logger.log_this(f"Importing {base}/{quote}") + instances_to_add.append(trader.trader(broker,f"{base}/{quote}",is_import=True)) + if f"{base}{quote}" not in tickers: + tickers.append(f"{base}{quote}") return 0 @@ -120,73 +117,31 @@ def add_instance(base: str, quote: str) -> int: broker.logger.log_this(f"Pair already running, duplicate instances are not allowed",1,pair) return 1 - #Check if config file already exists; if not, generate a new one - if not os.path.isfile(f"configs/{pair}.json"): - broker.logger.log_this(f"Config file does not exist. Generating...",1,pair) - details = generate_config_file(base,quote) - with open(f"configs/{pair}.json","w") as cf: - cf.write(json.dumps(details, indent=4)) - else: - with open(f"configs/{pair}.json", "r") as cf: - details = json.load(cf) - #Initialize the trader object and add the pair to the tickers list - instances_to_add.append(trader.trader(broker,details)) + instances_to_add.append(trader.trader(broker,f"{base}/{quote}")) if pair not in tickers: tickers.append(pair) return 0 -def initialize_instance(pair: str) -> int: +def initialize_instance(base: str, quote: str) -> int: ''' Loads the pair config file and initializes the trader object by adding it to the running instances list Parameters: - pair (str): The pair to initialize with the format BASEQUOTE (no slash) + base (str): The base currency of the pair. + quote (str): The quote currency of the pair. Returns: int: 0 if successful ''' - - with open(f"configs/{pair}.json", "r") as y: - config_details = json.load(y) - broker.logger.log_this(f"Initializing {pair}") - running_instances.append(trader.trader(broker,config_details)) - if pair not in tickers: - tickers.append(pair) + broker.logger.log_this(f"Initializing {f'{base}/{quote}'}") + running_instances.append(trader.trader(broker,f'{base}/{quote}')) + if f'{base}{quote}' not in tickers: + tickers.append(f'{base}{quote}') return 0 -def generate_config_file(base: str, quote: str) -> dict: - ''' - Generates a config file with default values for a given pair and returns that content in dictionary form. - TODO: Add a pair check against exchange's tickers data to properly support BASEQUOTE input format (without a slash) - 1. load tickers - 2. search for pair in dictionary - 3. assign proper base and quote values from the dictionary - ''' - return {"pair": f"{base}/{quote}", - "order_size": broker.get_default_order_size(), - "tp_level": 1.02, - "no_of_safety_orders": 30, - "safety_order_deviance": 2, - "safety_order_scale": 0.0105, - "write_logs": True, - "cleanup": True, - "telegram": True, - "tp_mode": 3, - "tp_table": [], - "is_short": False, - "autoswitch": False, - "check_old_long_price": True, - "attempt_restart": True, - "dynamic_so_deviance": True, - "dsd_range": 1, - "slippage_default_threshold": .02, - "generated_at": int(time.time()) - } - - def set_exchange(config: dict): ''' Takes the config dictionary as an input and returns the exchange object @@ -1279,7 +1234,7 @@ def reload_safety_order(): def run_API(): - serve(base_api, host="0.0.0.0", port=broker.get_config()["port"], threads=4) + serve(base_api, host="0.0.0.0", port=broker.get_config()["port"], threads=16) #base_api.run(host="0.0.0.0", port=broker.get_config()["port"]) @@ -1438,7 +1393,7 @@ def unwrapped_import_pair(base,quote): ''' try: - import_instance(base+quote) + import_instance(base,quote) broker.add_pair_to_config(f"{base}{quote}") broker.rewrite_config_file() broker.logger.log_this(f"Done",2,f"{base}/{quote}") @@ -2282,7 +2237,11 @@ if __name__=="__main__": os._exit(1) #broker.logger.log_this(f"Initializing {len(broker.get_pairs())} instances",2) for x in broker.get_pairs(): - initialize_instance(x) + symbol = broker.get_symbol(x) + if symbol!="Error": + initialize_instance(symbol.split("/")[0],symbol.split("/")[1]) + else: + broker.logger.log_this(f"Can't initialize {x}, symbol query returned Error",1) else: toggle = input(f"This will import {len(broker.get_pairs())} instances, proceed? (Y/n) ") if toggle not in ["Y","y",""]: @@ -2290,7 +2249,11 @@ if __name__=="__main__": os._exit(1) #broker.logger.log_this(f"Importing {len(broker.get_pairs())} instances",2) for x in broker.get_pairs(): - import_instance(x) + symbol = broker.get_symbol(x) + if symbol!="Error": + import_instance(symbol.split("/")[0],symbol.split("/")[1]) + else: + broker.logger.log_this(f"Can't import {x}, symbol query returned Error",1) broker.logger.log_this(f"All instances imported!",2) diff --git a/trader.py b/trader.py index c4e34c3..b7bb026 100755 --- a/trader.py +++ b/trader.py @@ -6,7 +6,7 @@ from config_handler import ConfigHandler from status_handler import StatusHandler class trader: - def __init__(self, broker, config_dict: dict, is_import: bool = False): + def __init__(self, broker, pair: str, is_import: bool = False): self.pause = True #Signals the trader to not process order info when an API call manhandles the trader #True by default, once the trader is started the start_trader method toggles it self.quit = False @@ -14,7 +14,7 @@ class trader: self.broker = broker self.tp_order = self.broker.get_empty_order() self.so = self.broker.get_empty_order() - self.config = ConfigHandler(config_dict["pair"],broker,config_dict) + self.config = ConfigHandler(pair,broker) self.pair = self.config.get_pair() self.market = self.broker.fetch_market(self.pair) self.market_load_time = int(time.time())