This commit is contained in:
Nicolás Sánchez 2025-02-27 11:49:45 -03:00
parent b5783838ff
commit cb5f6010b3
5 changed files with 52 additions and 73 deletions

View File

@ -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: 2025.02.02:
. new_so_routine now cancels the old take profit order after the new safety order is sent. . new_so_routine now cancels the old take profit order after the new safety order is sent.

View File

@ -34,10 +34,13 @@ class ConfigHandler:
"check_old_long_price": False #switch_to_short should flip this to True unless stated "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" 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() self.config_dictionary = self.default_config_dictionary.copy()
else:
#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.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)}) self.set_config({**self.default_config_dictionary, **json.load(f)})
return 0 return 0
except Exception as e: 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 return 1
def get_config(self): def get_config(self):

View File

@ -43,7 +43,7 @@ class Broker:
self.database_connection.close() self.database_connection.close()
#Load markets #Load markets
self.exchange.load_markets() self.markets = self.exchange.load_markets()
#Populates deals cache #Populates deals cache
self.deals_cache_length = 10 self.deals_cache_length = 10
@ -66,12 +66,20 @@ class Broker:
def get_deals_cache(self): def get_deals_cache(self):
return self.deals_list 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): def all_markets(self,no_retries=False):
retries = self.retries retries = self.retries
while retries>0: while retries>0:
try: try:
return self.exchange.load_markets() self.markets = self.exchange.load_markets()
return self.markets
except Exception as e: except Exception as e:
self.logger.log_this(f"Exception in reload_markets: {e}") self.logger.log_this(f"Exception in reload_markets: {e}")
if no_retries: if no_retries:
@ -83,7 +91,7 @@ class Broker:
def reload_markets(self): def reload_markets(self):
try: try:
self.exchange.load_markets(reload=True) self.markets = self.exchange.load_markets(reload=True)
return 0 return 0
except Exception as e: except Exception as e:
self.logger.log_this(f"Exception in reload_markets: {e}") self.logger.log_this(f"Exception in reload_markets: {e}")

91
main.py
View File

@ -80,24 +80,21 @@ def time_to_unix(year: str, month: str, day: str) -> int:
return 0 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. Imports an previously running trader instance from the status file.
Parameters: 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: Returns:
int: 0 if successful int: 0 if successful
''' '''
broker.logger.log_this(f"Importing {pair}") broker.logger.log_this(f"Importing {base}/{quote}")
#with open(f"status/{pair}.status", "r") as f: instances_to_add.append(trader.trader(broker,f"{base}/{quote}",is_import=True))
# status_file_contents = json.load(f) if f"{base}{quote}" not in tickers:
with open(f"configs/{pair}.json", "r") as g: tickers.append(f"{base}{quote}")
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)
return 0 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) broker.logger.log_this(f"Pair already running, duplicate instances are not allowed",1,pair)
return 1 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 #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: if pair not in tickers:
tickers.append(pair) tickers.append(pair)
return 0 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 Loads the pair config file and initializes the trader object by adding it to the running instances list
Parameters: 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: Returns:
int: 0 if successful int: 0 if successful
''' '''
broker.logger.log_this(f"Initializing {f'{base}/{quote}'}")
with open(f"configs/{pair}.json", "r") as y: running_instances.append(trader.trader(broker,f'{base}/{quote}'))
config_details = json.load(y) if f'{base}{quote}' not in tickers:
broker.logger.log_this(f"Initializing {pair}") tickers.append(f'{base}{quote}')
running_instances.append(trader.trader(broker,config_details))
if pair not in tickers:
tickers.append(pair)
return 0 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): def set_exchange(config: dict):
''' '''
Takes the config dictionary as an input and returns the exchange object Takes the config dictionary as an input and returns the exchange object
@ -1279,7 +1234,7 @@ def reload_safety_order():
def run_API(): 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"]) #base_api.run(host="0.0.0.0", port=broker.get_config()["port"])
@ -1438,7 +1393,7 @@ def unwrapped_import_pair(base,quote):
''' '''
try: try:
import_instance(base+quote) import_instance(base,quote)
broker.add_pair_to_config(f"{base}{quote}") broker.add_pair_to_config(f"{base}{quote}")
broker.rewrite_config_file() broker.rewrite_config_file()
broker.logger.log_this(f"Done",2,f"{base}/{quote}") broker.logger.log_this(f"Done",2,f"{base}/{quote}")
@ -2282,7 +2237,11 @@ if __name__=="__main__":
os._exit(1) os._exit(1)
#broker.logger.log_this(f"Initializing {len(broker.get_pairs())} instances",2) #broker.logger.log_this(f"Initializing {len(broker.get_pairs())} instances",2)
for x in broker.get_pairs(): 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: else:
toggle = input(f"This will import {len(broker.get_pairs())} instances, proceed? (Y/n) ") toggle = input(f"This will import {len(broker.get_pairs())} instances, proceed? (Y/n) ")
if toggle not in ["Y","y",""]: if toggle not in ["Y","y",""]:
@ -2290,7 +2249,11 @@ if __name__=="__main__":
os._exit(1) os._exit(1)
#broker.logger.log_this(f"Importing {len(broker.get_pairs())} instances",2) #broker.logger.log_this(f"Importing {len(broker.get_pairs())} instances",2)
for x in broker.get_pairs(): 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) broker.logger.log_this(f"All instances imported!",2)

View File

@ -6,7 +6,7 @@ from config_handler import ConfigHandler
from status_handler import StatusHandler from status_handler import StatusHandler
class trader: 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 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 #True by default, once the trader is started the start_trader method toggles it
self.quit = False self.quit = False
@ -14,7 +14,7 @@ class trader:
self.broker = broker self.broker = broker
self.tp_order = self.broker.get_empty_order() self.tp_order = self.broker.get_empty_order()
self.so = 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.pair = self.config.get_pair()
self.market = self.broker.fetch_market(self.pair) self.market = self.broker.fetch_market(self.pair)
self.market_load_time = int(time.time()) self.market_load_time = int(time.time())