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:
. 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
}
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):

View File

@ -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}")

91
main.py
View File

@ -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)

View File

@ -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())