StatusHandler initial implementation and variable cleanup

This commit is contained in:
Nicolás Sánchez 2025-03-01 10:55:00 -03:00
parent 168c5c823f
commit bb796f6933
4 changed files with 333 additions and 438 deletions

View File

@ -1,5 +1,9 @@
2025.03.01:
. StatusHandler initial implementation.
. Variable cleanup
2025.02.27
. config_handler: centralized configuration handling in an object, for easier future development (parameter validation, for example)
. ConfigHandler: 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.

45
main.py
View File

@ -24,7 +24,7 @@ In case the permissions of the certificate changes, reset them this way:
# ll /etc/letsencrypt/
'''
version = "2025.02.27"
version = "2025.03.01"
'''
Color definitions. If you want to change them, check the reference at https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
@ -246,10 +246,10 @@ def restart_pair_no_json(base: str, quote: str) -> int:
x.write_status_file(True)
#Here, we could open a duster (if needed)
for order in order_list:
if order["symbol"]==f"{base}/{quote}" and x.is_short and order["side"]=="sell":
if order["symbol"]==f"{base}/{quote}" and x.config.get_is_short() and order["side"]=="sell":
broker.logger.log_this(f"Cancelling old sell orders",2,f"{base}/{quote}")
broker.cancel_order(order["id"],order["symbol"])
elif order["symbol"]==f"{base}/{quote}" and not x.is_short and order["side"]=="buy":
elif order["symbol"]==f"{base}/{quote}" and not x.config.get_is_short() and order["side"]=="buy":
broker.logger.log_this(f"Cancelling old buy orders",2,f"{base}/{quote}")
broker.cancel_order(order["id"],order["symbol"])
running_instances.remove(x)
@ -328,7 +328,7 @@ def main_loop():
curr = 0
top = 0
for x in running_instances:
if not x.is_short:
if not x.config.get_is_short():
curr += int(x.get_status_dict()["so_amount"]) # For the safety order occupancy percentage calculation
top += int(x.config.get_no_of_safety_orders()) # It shows the percentage of safety orders not filled
if not x.quit: #Why? Maybe to protect return_status() from weird errors if the trader errored out?
@ -337,8 +337,6 @@ def main_loop():
x.get_status_dict()["price"] = price_list[x.pair]
except Exception as e:
broker.logger.log_this(f"Exception while querying for pair price, key not present on price_list dictionary: {e}",1,x.pair)
#if "status_string" in x.status_dict:
# screen_buffer.append(x.status_dict["status_string"])
worker_status[x.pair] = x.get_status_dict()
#Clear the screen buffer
@ -346,12 +344,10 @@ def main_loop():
#Append worker data to screen buffer, shorts first.
for x in running_instances:
if x.is_short and "status_string" in x.get_status_dict():
#screen_buffer.append(x.status_dict["status_string"])
if x.config.get_is_short() and "status_string" in x.get_status_dict():
screen_buffer.append(str(x))
for x in running_instances:
if not x.is_short and "status_string" in x.get_status_dict():
#screen_buffer.append(x.status_dict["status_string"])
if not x.config.get_is_short() and "status_string" in x.get_status_dict():
screen_buffer.append(str(x))
#Updates some global status variables prior to deletion of those
@ -369,8 +365,6 @@ def main_loop():
#Prints general info
instance_uptime = int(time.time()) - instance_start_time
#long_traders = len([None for x in running_instances if not x.is_short])
#short_traders = len([None for x in running_instances if x.is_short])
screen_buffer.append(time.strftime(f"[%Y/%m/%d %H:%M:%S] | {len(running_instances)} traders online | Instance uptime: {seconds_to_time(instance_uptime)}"))
if top==0: #To protect from division by zero when there are no traders active
@ -1455,13 +1449,8 @@ def unwrapped_switch_to_short(base,quote):
broker.logger.log_this(f"Reinitializing trader",2,f"{base}/{quote}")
for x in running_instances:
if f"{base}/{quote}"==x.pair:
#Clearing some variables
#x.fees_paid_in_quote = 0
#x.fees_paid_in_base = 0
x.tp_order = x.broker.empty_order
x.status.set_take_profit_order(x.broker.empty_order)
x.so = x.broker.empty_order
#x.safety_price_table.clear() #This clearing (probably) is the origin of the update_status error after switching to short.
#x.safety_order_index = 0
#Reloading config file
x.config.load_from_file()
@ -1593,7 +1582,7 @@ def unwrapped_add_safety_orders(base,quote,amount):
#x.no_of_safety_orders += int(amount)
x.config.set_no_of_safety_orders(x.config.get_no_of_safety_orders()+int(amount))
broker.logger.log_this("Recalculating safety price table...",1,f"{base}/{quote}")
x.safety_price_table = x.calculate_safety_prices(x.start_price,x.config.get_no_of_safety_orders(),x.config.get_safety_order_deviance())
x.status.set_safety_price_table(x.calculate_safety_prices(x.status.get_start_price(),x.config.get_no_of_safety_orders(),x.config.get_safety_order_deviance()))
broker.logger.log_this(f"Done. Added {amount} safety orders",1,f"{base}/{quote}")
x.update_status(True)
x.pause = False
@ -1783,10 +1772,10 @@ def unwrapped_add_quote(base,quote,amount):
for x in running_instances:
if f"{base}/{quote}"==x.pair:
if x.is_short:
if x.config.get_is_short():
return jsonify({"Error": "Quote can't be added to short bots"})
x.pause = True
new_average_price = (x.total_amount_of_quote+float(amount))/(x.total_amount_of_base+(float(amount)/x.get_status_dict()["price"]))
new_average_price = (x.status.get_quote_spent()+float(amount))/(x.status.get_base_bought()+(float(amount)/x.get_status_dict()["price"]))
broker.logger.log_this(f"Your new average buy price will be {new_average_price} {x.quote}",2,f"{base}/{quote}")
broker.logger.log_this(f"Your new take profit price price will be {new_average_price*x.get_tp_level()} {x.quote}",2,f"{base}/{quote}")
new_order = broker.new_market_order(x.pair,float(amount),"buy")
@ -1807,13 +1796,13 @@ def unwrapped_add_quote(base,quote,amount):
elif returned_order["status"]=="closed":
broker.logger.log_this("Order sent",2,f"{base}/{quote}")
new_fees_in_base, new_fees_in_quote = x.parse_fees(returned_order)
x.fees_paid_in_base += new_fees_in_base
x.fees_paid_in_quote += new_fees_in_quote
x.total_amount_of_base = x.total_amount_of_base + returned_order["filled"] - new_fees_in_base
x.total_amount_of_quote += returned_order["cost"]
x.status.set_fees_paid_in_base(x.status.get_fees_paid_in_base() + new_fees_in_base)
x.status.set_fees_paid_in_quote(x.status.get_fees_paid_in_quote() + new_fees_in_quote)
x.status.set_base_bought(x.status.get_base_bought() + returned_order["filled"] - new_fees_in_base)
x.status.set_quote_spent(x.status.get_quote_spent()+returned_order["cost"])
broker.logger.log_this("Cancelling old take profit order and sending a new one",2,f"{base}/{quote}")
attempts = 5
while broker.cancel_order(x.tp_order["id"],x.pair)==1:
while broker.cancel_order(x.status.get_tp_order_id(),x.pair)==1:
broker.logger.log_this("Can't cancel old take profit order, retrying...",2,f"{base}/{quote}")
time.sleep(broker.get_wait_time())
attempts-=1
@ -1821,8 +1810,8 @@ def unwrapped_add_quote(base,quote,amount):
broker.logger.log_this("Can't cancel old take profit order, cancelling...",2,f"{base}/{quote}")
x.pause = False
return jsonify({"Error": "Can't cancel old take profit order."})
x.take_profit_price = x.total_amount_of_quote/x.total_amount_of_base*x.get_tp_level()
x.tp_order = broker.new_limit_order(x.pair,x.total_amount_of_base,"sell",x.take_profit_price)
x.status.set_take_profit_price(x.status.get_quote_spent()/x.status.get_base_bought()*x.get_tp_level())
x.status.set_take_profit_order(broker.new_limit_order(x.pair,x.status.get_base_bought(),"sell",x.status.get_take_profit_price()))
x.update_status(True)
break
else:

View File

@ -1,19 +1,18 @@
import json
import time
class StatusHandler:
'''
Handles the status of the trader and the validation of the parameters
'''
def __init__(self, broker, pair, status_dict = None):
def __init__(self, broker, base, quote, status_dict = None):
self.broker = broker
self.default_status_dictionary = {
"pair": pair,
"tp_order_id": "",
"take_profit_order": {},
"pair": f"{base}/{quote}",
"take_profit_order": broker.get_empty_order(),
"take_profit_price": 0.0,
"so_order_id": "",
"safety_order": {},
"safety_order": broker.get_empty_order(),
"next_so_price": 0.0,
"order_size": 0.0,
"partial_profit": 0.0,
@ -43,7 +42,7 @@ class StatusHandler:
"status_string": "",
"deal_order_history": []
}
self.status_file_path = f"configs/{pair.split('/')[0]}{pair.split('/')[1]}.json"
self.status_file_path = f"status/{base}{quote}.status"
self.status_dictionary = self.default_status_dictionary.copy()
if status_dict is not None:
@ -51,25 +50,21 @@ class StatusHandler:
self.save_to_file()
def get_tp_order_id(self):
return self.status_dictionary["tp_order_id"]
def get_take_profit_order(self):
return self.status_dictionary["take_profit_order"]
def get_take_profit_price(self):
return self.status_dictionary["take_profit_price"]
def get_so_order_id(self):
return self.status_dictionary["so_order_id"]
def get_safety_order(self):
return self.status_dictionary["safety_order"]
def get_next_so_price(self):
return self.status_dictionary["next_so_price"]
def get_order_size(self):
return self.status_dictionary["order_size"]
def get_partial_profit(self):
return self.status_dictionary["partial_profit"]
@ -149,7 +144,7 @@ class StatusHandler:
self.status_dictionary["tp_order_id"] = order_id
return 0
def set_take_profit_order(self, order: dict):
def set_take_profit_order(self, order):
self.status_dictionary["take_profit_order"] = order
return 0
@ -161,7 +156,7 @@ class StatusHandler:
self.status_dictionary["so_order_id"] = order_id
return 0
def set_safety_order(self, order: dict):
def set_safety_order(self, order):
self.status_dictionary["safety_order"] = order
return 0
@ -281,18 +276,27 @@ class StatusHandler:
self.status_dictionary["deal_order_history"] = deal_history
return 0
def clear_deal_order_history(self):
self.status_dictionary["deal_order_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 = None):
def save_to_file(self, file_path = None, is_backup = False):
if file_path is None:
file_path = self.status_file_path
if is_backup:
try:
with open(time.strftime(f"{file_path}_%Y-%m-%d_%H:%M:%S.json"), "w") as f:
f.write(json.dumps(self.status_dictionary, indent=4))
except Exception as e:
self.broker.logger.log_this(f"Error creating status backup file: {e}",1)
try:
with open(file_path, "w") as f:
json.dump(self.status_dictionary, f)
f.write(json.dumps(self.status_dictionary, indent=4))
return 0
except Exception as e:
self.broker.logger.log_this(f"Error saving status to file: {file_path}: {e}",1)
@ -303,13 +307,12 @@ class StatusHandler:
file_path = self.status_file_path
try:
with open(file_path, "r") as f:
self.set_status(json.load(f))
self.status_dictionary = {**self.default_status_dictionary, **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

669
trader.py

File diff suppressed because it is too large Load Diff