2025.09.15

This commit is contained in:
Nicolás Sánchez 2025-09-15 18:55:59 -03:00
parent 73eff21dbb
commit 0d753aa3cd
4 changed files with 141 additions and 124 deletions

View File

@ -1,3 +1,14 @@
2025.09.14:
. Refactored full order list fetching.
. Minor refactor of restart_pair_no_json.
. Pausing the trader is now done via set_pause() method.
. Reverted modification of wait time after initial market order.
. wait_time now present in broker config file.
. Minor refactorings.
2025.09.13:
. Increased wait time after initial market order.
2025.09.12:
. No retries when sending a cleanup order.
. Removed redundant try...except blocks in switch_to_long.

View File

@ -14,16 +14,16 @@ class Broker:
self.broker_config = broker_config
self.exchange = exchange
self.last_price = 0
self.wait_time = .5 #Default wait time for API breathing room
self.empty_order = {"id": "", "status": "", "filled": 0, "remaining": 0, "price": 0, "cost": 0, "fees": [], "symbol": ""}
#Default values
self.cooldown_multiplier = self.broker_config["cooldown_multiplier"] if "cooldown_multiplier" in self.broker_config else 2
self.wait_before_new_safety_order = self.broker_config["wait_before_new_safety_order"] if "wait_before_new_safety_order" in self.broker_config else 1
self.retries = self.broker_config["retries"] if "retries" in self.broker_config else 5
self.slippage_default_threshold = self.broker_config["slippage_default_threshold"] if "slippage_default_threshold" in self.broker_config else .03
self.follow_order_history = self.broker_config["follow_order_history"] if "follow_order_history" in self.broker_config else False
self.write_order_history = self.broker_config["write_order_history"] if "write_order_history" in self.broker_config else False
self.wait_time = self.broker_config.get("wait_time",.5)
self.cooldown_multiplier = self.broker_config.get("cooldown_multiplier",2)
self.wait_before_new_safety_order = self.broker_config.get("wait_before_new_safety_order",1)
self.retries = self.broker_config.get("retries",5)
self.slippage_default_threshold = self.broker_config.get("slippage_default_threshold",.03)
self.follow_order_history = self.broker_config.get("follow_order_history",False)
self.write_order_history = self.broker_config.get("write_order_history", False)
self.logger = Logger(self.broker_config)
#Initialize database
@ -503,26 +503,26 @@ class Broker:
return []
def fetch_full_orders(self,pairs=None) -> list:
'''
Returns a list of all orders on the exchange
# def fetch_full_orders(self,pairs=None) -> list:
# '''
# Returns a list of all orders on the exchange
:param pairs: list of pairs to get orders for
:return: list of orders
'''
# :param pairs: list of pairs to get orders for
# :return: list of orders
# '''
if pairs is None:
pairs = []
try:
orders = []
if self.get_exchange_name()=="binance":
orders = self.get_opened_orders_binance(pairs)
else:
orders = self.get_opened_orders()
return [] if orders is None else orders
except Exception as e:
self.logger.log_this(f"Exception in fetch_full_orders: {e}",2)
return []
# if pairs is None:
# pairs = []
# try:
# orders = []
# if self.get_exchange_name()=="binance":
# orders = self.get_opened_orders_binance(pairs)
# else:
# orders = self.get_opened_orders()
# return [] if orders is None else orders
# except Exception as e:
# self.logger.log_this(f"Exception in fetch_full_orders: {e}",2)
# return []
def fetch_open_orders(self,pairs=None) -> list:
@ -536,9 +536,16 @@ class Broker:
if pairs is None:
pairs = []
try:
if self.get_exchange_name()in ["binance","kucoin"]:
return self.get_opened_orders_binance(pairs)
return self.get_opened_orders()
if self.get_exchange_name() in ["binance","kucoin"]:
if "unified_order_query" in self.broker_config and self.broker_config["unified_order_query"] is True:
return self.exchange.fetch_open_orders()
result = []
for pair in pairs:
a = self.exchange.fetch_open_orders(pair)
result.extend(iter(a))
return result
else:
return self.exchange.fetch_open_orders()
except Exception as e:
self.logger.log_this(f"Exception in fetch_open_orders: {e}",2)
return []
@ -563,25 +570,25 @@ class Broker:
return []
def get_opened_orders(self,no_retries=False): #It should return a list of all opened orders
'''
Returns a list of all the open orders on the exchange
# def get_opened_orders(self,no_retries=False): #It should return a list of all opened orders
# '''
# Returns a list of all the open orders on the exchange
:param pairs: list of pairs
:return: list of all the open orders on the exchange
'''
# :param pairs: list of pairs
# :return: list of all the open orders on the exchange
# '''
retries = self.retries
while retries>0:
try:
return self.exchange.fetch_open_orders()
except Exception as e:
self.logger.log_this(f"Exception in get_opened_orders: {e}",1)
if no_retries:
break
time.sleep(self.wait_time)
retries-=1
return []
# retries = self.retries
# while retries>0:
# try:
# return self.exchange.fetch_open_orders()
# except Exception as e:
# self.logger.log_this(f"Exception in get_opened_orders: {e}",1)
# if no_retries:
# break
# time.sleep(self.wait_time)
# retries-=1
# return []
def get_closed_orders(self,pair=None,no_retries=False): #It should return a list of all opened orders
@ -605,25 +612,25 @@ class Broker:
return []
def get_opened_orders_binance(self,pairs):
'''
Returns a list of all the open orders on the exchange
# def get_opened_orders_binance(self,pairs):
# '''
# Returns a list of all the open orders on the exchange
:param pairs: list of pairs
:return: list of all the open orders on the exchange
'''
# :param pairs: list of pairs
# :return: list of all the open orders on the exchange
# '''
try:
if "unified_order_query" in self.broker_config and self.broker_config["unified_order_query"] is True:
return self.exchange.fetch_open_orders()
result = []
for pair in pairs:
a = self.exchange.fetch_open_orders(pair)
result.extend(iter(a))
return result
except Exception as e:
self.logger.log_this(f"Exception in get_opened_orders_binance: {e}",1)
return []
# try:
# if "unified_order_query" in self.broker_config and self.broker_config["unified_order_query"] is True:
# return self.exchange.fetch_open_orders()
# result = []
# for pair in pairs:
# a = self.exchange.fetch_open_orders(pair)
# result.extend(iter(a))
# return result
# except Exception as e:
# self.logger.log_this(f"Exception in get_opened_orders_binance: {e}",1)
# return []
def get_closed_orders_binance(self,pairs):

50
main.py
View File

@ -18,7 +18,7 @@ import exchange_wrapper
import trader
version = "2025.09.12"
version = "2025.09.14"
'''
Color definitions. If you want to change them, check the reference at https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
@ -246,20 +246,26 @@ def restart_pair_no_json(base: str, quote: str) -> int:
try:
symbol = f"{base}/{quote}"
order_list = broker.fetch_full_orders(tickers)
for instance in running_traders:
if symbol==instance.status.get_pair():
instance.pause = True
instance.set_pause(True, "Restarting trader")
#Backing up old status file
instance.status.save_to_file(is_backup=True)
#Here, we could open a duster (if needed)
for order in order_list:
if order["symbol"]==symbol and instance.config.get_is_short() and order["side"]=="sell":
broker.logger.log_this(f"Cancelling old sell orders",2,symbol)
broker.cancel_order(order["id"],order["symbol"])
elif order["symbol"]==symbol and not instance.config.get_is_short() and order["side"]=="buy":
broker.logger.log_this(f"Cancelling old buy orders",2,symbol)
broker.cancel_order(order["id"],order["symbol"])
broker.logger.log_this(f"Cancelling old take profit order",2,symbol)
try:
old_tp_order = instance.status.get_take_profit_order()
broker.cancel_order(old_tp_order["id"],old_tp_order["symbol"])
except Exception as e:
broker.logger.log_this(f"Error canceling old take profit order: {e}",2,symbol)
broker.logger.log_this(f"Cancelling old take safety orders",2,symbol)
for item in instance.status.get_safety_orders():
try:
broker.cancel_order(item["id"],item["symbol"])
except Exception as e:
broker.logger.log_this(f"Error canceling old safety order: {e}",2,symbol)
try:
running_traders.remove(instance)
except ValueError:
@ -1606,7 +1612,7 @@ def unwrapped_switch_to_long(base,quote,calculate_profits):
return jsonify({"Error": "Pair not running"})
for instance in running_traders:
if f"{base}/{quote}"==instance.status.get_pair():
instance.pause = True
instance.set_pause(True, "Switching to long mode")
if instance.switch_to_long(ignore_old_long=ignore_old_long)==1:
return jsonify({"Error": "Error in switch_to_long()"})
if instance.start_trader()==1:
@ -1774,14 +1780,14 @@ def unwrapped_add_safety_orders(base,quote,amount):
symbol = f"{base}/{quote}"
for instance in running_traders:
if symbol==instance.status.get_pair():
instance.pause = True
instance.set_pause(True, "Adding safety orders")
#x.no_of_safety_orders += int(amount)
instance.config.set_no_of_safety_orders(instance.config.get_no_of_safety_orders()+int(amount))
broker.logger.log_this("Recalculating safety price table...",1,symbol)
instance.status.set_safety_price_table(instance.calculate_safety_prices(instance.status.get_start_price(),instance.config.get_no_of_safety_orders(),instance.config.get_safety_order_deviance()))
broker.logger.log_this(f"Done. Added {amount} safety orders",1,symbol)
instance.update_status(True)
instance.pause = False
instance.set_pause(False)
return jsonify({"Success": f"Done. Added {amount} safety orders"})
return jsonify({"Error": "Pair not found"})
except Exception as e:
@ -2097,24 +2103,24 @@ def unwrapped_add_quote(base,quote,amount):
if f"{base}/{quote}"==instance.status.get_pair():
if instance.config.get_is_short():
return jsonify({"Error": "Quote can't be added to short traders"})
instance.pause = True
instance.set_pause(True, "Adding quote")
new_average_price = (instance.status.get_quote_spent()+float(amount))/(instance.status.get_base_bought()+(float(amount)/instance.status.get_price()))
broker.logger.log_this(f"Your new average buy price will be {new_average_price} {quote}",2,instance.status.get_pair())
broker.logger.log_this(f"Your new take profit price price will be {new_average_price*instance.get_tp_level()} {quote}",2,instance.status.get_pair())
new_order = broker.new_market_order(instance.status.get_pair(),float(amount),"buy")
if new_order is None:
broker.logger.log_this("Error: Market order returned None",2,instance.status.get_pair())
instance.pause = False
instance.set_pause(False)
return jsonify({"Error": "Market order returned None"})
while True:
time.sleep(broker.get_wait_time())
returned_order = broker.get_order(new_order["id"],instance.status.get_pair())
if returned_order==broker.empty_order:
broker.logger.log_this("Problems sending the order",2,instance.status.get_pair())
instance.pause = False
instance.set_pause(False)
return jsonify({"Error": "Problems sending the order"})
elif returned_order["status"]=="expired":
instance.pause = False
instance.set_pause(False)
return jsonify({"Error": "New order expired"})
elif returned_order["status"]=="closed":
broker.logger.log_this("Order sent",2,instance.status.get_pair())
@ -2131,7 +2137,7 @@ def unwrapped_add_quote(base,quote,amount):
attempts-=1
if attempts==0:
broker.logger.log_this("Can't cancel old take profit order, cancelling...",2,instance.status.get_pair())
instance.pause = False
instance.set_pause(False)
return jsonify({"Error": "Can't cancel old take profit order."})
instance.status.set_take_profit_price(instance.status.get_quote_spent()/instance.status.get_base_bought()*instance.get_tp_level())
instance.status.set_take_profit_order(broker.new_limit_order(instance.status.get_pair(),instance.status.get_base_bought(),"sell",instance.status.get_take_profit_price()))
@ -2141,7 +2147,7 @@ def unwrapped_add_quote(base,quote,amount):
broker.logger.log_this("Waiting for initial order to get filled",2,instance.status.get_pair())
broker.logger.log_this(f"{returned_order}",2,instance.status.get_pair())
time.sleep(broker.get_wait_time())
instance.pause = False
instance.set_pause(False)
broker.logger.log_this("Done",2,instance.status.get_pair())
return jsonify({"Success": "Quote added successfully"})
return jsonify({"Error": "Something horrible happened :S"})
@ -2324,14 +2330,14 @@ def unwrapped_switch_quote_currency(base,quote,new_quote):
for trader in running_traders:
if symbol==trader.status.get_pair():
#Pause the trader
trader.pause = True
trader.set_pause(True, "Switching quote currency")
#Call x.switch_quote_currency
if trader.switch_quote_currency(new_quote)==1:
return jsonify({"Error": "Swap failed. Check log files for details."})
#Resume the trader
trader.pause = False
trader.set_pause(False)
return jsonify({"Success": "Mission successful"})
return jsonify({"Error": "Trader not found"})
except Exception as e:

View File

@ -67,12 +67,12 @@ class trader:
self.quit = True
elif start_result==2: #Retries exceeded
if self.config.get_force_restart_if_retries_exhausted():
self.pause = False
self.set_pause(False)
self.restart = True
else:
self.quit = True
elif start_result==3: #Not enough liquidity
self.pause = False
self.set_pause(False)
self.restart = True
@ -127,8 +127,7 @@ class trader:
if new_market_data is not None:
self.market = new_market_data
self.pause = True
self.status.set_pause_reason("start_trader")
self.set_pause(True, "start_trader")
if self.status.get_is_short():
self.broker.logger.log_this("Calculating optimal order size...",2,self.status.get_pair())
@ -293,8 +292,7 @@ class trader:
self.status.set_deal_start_time(int(time.time()))
self.update_status(True)
self.pause = False
self.status.set_pause_reason("")
self.set_pause(False)
return 0
@ -443,10 +441,8 @@ class trader:
self.broker.logger.log_this("Can't fetch free base",1,self.status.get_pair())
return 1
balance_to_clean /= 2 #Maybe it's a good idea, sort of DCAing the dust.
min_base_size = self.broker.get_min_base_size(self.status.get_pair())
#minimum_cleanup_size = self.status.get_safety_orders()[0]["amount"]*2
if balance_to_clean >= min_base_size:
if balance_to_clean >= self.broker.get_min_base_size(self.status.get_pair()):
self.broker.logger.log_this(f"Balance to clean: {balance_to_clean} {self.base}",2,self.status.get_pair())
self.broker.logger.log_this("Sending cleanup order...",2,self.status.get_pair())
cleanup_order = self.broker.new_limit_order(self.status.get_pair(),balance_to_clean,"sell",self.status.get_take_profit_price(),no_retries=True)
@ -478,13 +474,11 @@ class trader:
amount_of_so-=1
if optimal_order_size==0:
self.broker.logger.log_this("Not enough base to switch. Order size would be too small",1,self.status.get_pair())
self.pause = False
self.status.set_pause_reason("")
self.set_pause(False)
return None,None
if optimal_order_size<min_base_size: #Sometimes amount_to_precision rounds to a value less than the minimum
self.broker.logger.log_this("Optimal order size is smaller than the minimum order size",1,self.status.get_pair())
self.pause = False
self.status.set_pause_reason("")
self.set_pause(False)
return None,None
return optimal_order_size,amount_of_so
@ -522,8 +516,7 @@ class trader:
return 1
#Pauses trader
self.pause = True
self.status.set_pause_reason("switch_to_short")
self.set_pause(True,"switch_to_short")
#Fetch the real amount of available base
self.broker.logger.log_this(f"Fetching available {self.base}",2,self.status.get_pair())
@ -535,15 +528,13 @@ class trader:
min_base_size = self.broker.get_min_base_size(self.status.get_pair())
if min_base_size is None:
self.broker.logger.log_this("Error. Can't fetch market info from the exchange",1,self.status.get_pair())
self.pause = False
self.status.set_pause_reason("")
self.set_pause(False)
return 1
#Check if there is enough base
if self.broker.amount_to_precision(self.status.get_pair(),free_base+self.status.get_take_profit_order()["amount"])<=min_base_size:
self.broker.logger.log_this("Error. Not enough base currency",1,self.status.get_pair())
self.pause = False
self.status.set_pause_reason("")
self.set_pause(False)
return 1
#Calculate order size
@ -559,8 +550,7 @@ class trader:
self.broker.logger.log_this("Closing orders...",2,self.status.get_pair())
if self.broker.cancel_order(self.status.get_take_profit_order()["id"],self.status.get_pair())==1:
self.broker.logger.log_this("Can't cancel the take profit order. Can't switch mode",1,self.status.get_pair())
self.pause = False
self.status.set_pause_reason("")
self.set_pause(False)
return 1
if self.status.get_take_profit_order()["id"]!="":
self.broker.cancel_order(self.status.get_take_profit_order()["id"],self.status.get_pair())
@ -588,7 +578,6 @@ class trader:
self.broker.logger.log_this("Config file updated",2,self.status.get_pair())
except Exception as e:
self.broker.logger.log_this(f"Error. Can't write the config file. Exception: {e}",1,self.status.get_pair())
#self.pause = False
return 1
self.status.set_stop_when_profit(False)
#self.config.set_is_short(True)
@ -709,8 +698,7 @@ class trader:
but KuCoin only supports order editing on high frequency orders.
'''
self.pause = True
self.status.set_pause_reason("force_close - order handling")
self.set_pause(True,"force_close - order handling")
#Close the take profit order
self.broker.cancel_order(self.status.get_take_profit_order()["id"],self.status.get_pair())
@ -743,8 +731,7 @@ class trader:
the reporting and the restart of the trader.
'''
self.pause = True #To stop the main thread to iterate through this trader's orders (just in case)
self.status.set_pause_reason("take_profit_routine - order handling") #start_trader will set this flag to False again once it starts
self.set_pause(True,"take_profit_routine - order handling") #To stop the main thread to iterate through this trader's orders (just in case)
#Add the timestamp to the deals cache and trims it
self.deals_timestamps.append(time.time())
@ -853,13 +840,13 @@ class trader:
if abs(filled_order["price"]-price_to_compare)/filled_order["price"]>self.broker.get_slippage_default_threshold():
self.broker.logger.log_this(f"Slippage threshold exceeded, waiting for cooldown and restarting trader",1,self.status.get_pair())
time.sleep(self.broker.get_wait_time()*self.broker.get_cooldown_multiplier())
self.pause = False
self.set_pause(False)
self.restart = True
return 1
elif self.check_orderbook_depth(self.broker.get_slippage_default_threshold(),self.status.get_order_size(),filled_order["price"]):
self.broker.logger.log_this(f"Orderbook depth not sufficient, waiting for cooldown and restarting trader",1,self.status.get_pair())
time.sleep(self.broker.get_wait_time()*self.broker.get_cooldown_multiplier())
self.pause = False
self.set_pause(False)
self.restart = True
return 1
@ -868,7 +855,7 @@ class trader:
restart_trader = self.start_trader()
self.status.set_pause_reason("take_profit_routine - restart_trader call - start_trader() called")
if restart_trader in self.trader_restart_errors.keys():
self.pause = False
self.set_pause(False)
self.restart = True
self.status.save_to_file(is_backup=True)
self.broker.logger.log_this(self.trader_restart_errors[restart_trader],1,self.status.get_pair())
@ -955,8 +942,7 @@ class trader:
return 1
#Pause the trader
self.pause = True
self.status.set_pause_reason("renew_tp_and_so_routine")
self.set_pause(True, "renew_tp_and_so_routine")
#Save the order
if self.broker.get_follow_order_history():
@ -1027,8 +1013,7 @@ class trader:
self.update_status(True)
#Toggle the pause flag
self.pause = False
self.status.set_pause_reason("")
self.set_pause(False)
#Done
return 0
@ -1234,8 +1219,7 @@ class trader:
return 1
elif renew_outcome==3:
self.broker.logger.log_this(f"Can't cancel old take profit order. renew_tp_and_so_routine returned 3",1,self.status.get_pair())
self.pause = False
self.status.set_pause_reason("")
self.set_pause(False)
if self.config.get_attempt_restart():
self.status.save_to_file(is_backup=True)
self.restart = True
@ -1255,9 +1239,9 @@ class trader:
if condition_a and condition_b:
amount_to_send = max_concurrent_safety_orders-len(self.status.get_safety_orders())
self.pause = True
self.set_pause(True, "sending safety order batch")
self.send_new_safety_order_batch(amount_to_send)
self.pause = False
self.set_pause(False)
self.update_status(True)
#Render status line(s)
@ -1708,6 +1692,16 @@ class trader:
return status_string
def set_pause(self, pause: bool, msg: str = "") -> None:
'''
Sets the pause state and reason
'''
self.pause = pause
self.status.set_pause_reason(msg)
return
def load_imported_trader(self) -> int:
'''
Loads status dictionary, orders and sets up variables
@ -1729,8 +1723,7 @@ class trader:
return 1
#Done
self.pause = False
self.status.set_pause_reason("")
self.set_pause(False)
self.update_status(True)
return 0