From 27420946cd7efe8e9cda56ccb97c3b15b0d977ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20S=C3=A1nchez?= Date: Thu, 4 Sep 2025 13:56:25 -0300 Subject: [PATCH] I hate Kucoin --- exchange_wrapper.py | 2 +- main.py | 13 +++--- trader.py | 107 ++++++++++++++++++++++++++++++-------------- 3 files changed, 82 insertions(+), 40 deletions(-) diff --git a/exchange_wrapper.py b/exchange_wrapper.py index 28a6ab1..15453e6 100755 --- a/exchange_wrapper.py +++ b/exchange_wrapper.py @@ -538,7 +538,7 @@ class Broker: if pairs is None: pairs = [] try: - if self.get_exchange_name()=="binance": + if self.get_exchange_name()in ["binance","kucoin"]: return self.get_opened_orders_binance(pairs) return self.get_opened_orders() except Exception as e: diff --git a/main.py b/main.py index 7e282a7..7f8065b 100644 --- a/main.py +++ b/main.py @@ -18,7 +18,7 @@ import exchange_wrapper import trader -version = "2025.09.03" +version = "2025.09.04" ''' Color definitions. If you want to change them, check the reference at https://en.wikipedia.org/wiki/ANSI_escape_code#Colors @@ -311,12 +311,16 @@ def main_routine(): futures = [] pairs_to_fetch = [] online_pairs = [] - open_orders = broker.fetch_open_orders(tickers) + + for instance in running_traders: + pairs_to_fetch.append(instance.status.get_pair()) + + open_orders = broker.fetch_open_orders(pairs_to_fetch) + for instance in running_traders: future = executor.submit(instance.check_status, open_orders) futures.append(future) online_pairs.append(f"{instance.base}{instance.quote}") - pairs_to_fetch.append(instance.status.get_pair()) #Fetch prices price_list = broker.get_prices(pairs_to_fetch) @@ -328,9 +332,6 @@ def main_routine(): except Exception as e: broker.logger.log_this(f"Error in thread - {e}") - #Delete no longer used data - del open_orders - curr = 0 top = 0 long_traders_status_strings = [] diff --git a/trader.py b/trader.py index 5cfae81..2e1c9b2 100755 --- a/trader.py +++ b/trader.py @@ -765,12 +765,14 @@ class trader: self.broker.cancel_order(order["id"],self.status.get_pair()) #Check if some safety orders were filled + partial_filled_amount = 0 for order in self.status.get_safety_orders(): closed_order = self.broker.get_order(order["id"],self.status.get_pair()) if closed_order["filled"]==0: #If this order wasn't filled, it is safe to assume that no order coming after this one was. break #Sum the filled amounts + partial_filled_amount+=closed_order["filled"] #Better than this, the total filled and total cost can be used to send a sell market order of the partial filled amount, and add that to the profit. self.broker.logger.log_this(f"Old safety order is partially filled, ID: {closed_order['id']}, {closed_order['filled']}/{closed_order['amount']} {self.base} filled",1,self.status.get_pair()) self.status.set_base_bought(self.status.get_base_bought() + closed_order["filled"] - self.parse_fees(closed_order)[0]) @@ -784,6 +786,24 @@ class trader: #Now we can clear the safety order list self.status.set_safety_orders([]) + # if partial_filled_amount!=0 and partial_filled_amount>self.broker.get_min_base_size(self.status.get_pair()): + # #send a sell market order and sum the profits + # market_order = self.broker.new_market_order(self.status.get_pair(),partial_filled_amount,"sell",amount_in_base=True) + # #Wait for it to be filled + # tries = self.broker.get_retries() + # while True: + # time.sleep(self.broker.get_wait_time()) + # partial_fill_order = self.broker.get_order(market_order["id"],self.status.get_pair()) + # if partial_fill_order["status"]=="closed": + # break + # tries-=1 + # if tries==0: + # self.broker.logger.log_this("Partial fill sell order not filling.",1,self.status.get_pair()) + # break + # #Sum the profit + + + if not self.broker.check_for_duplicate_profit_in_db(filled_order): self.status.set_pause_reason("calculating profit") # Calculate the profit @@ -1113,8 +1133,11 @@ class trader: return 1 #Extract ids from order list - open_orders_ids = [order["id"] for order in open_orders if order["symbol"]==self.status.get_pair()] + self.status.set_pause_reason("filtering open orders") + open_orders_list = [order for order in open_orders if order["symbol"]==self.status.get_pair()] + open_orders_ids = [order["id"] for order in open_orders_list] + self.status.set_pause_reason("check for tp_order is valid") #Checks if the take profit order is valid if self.status.get_take_profit_order() is None: self.broker.logger.log_this("Take profit order is None",1,self.status.get_pair()) @@ -1132,8 +1155,19 @@ class trader: self.broker.logger.log_this("Take profit order missing. Trader restart disabled.",2,self.status.get_pair()) return 1 + self.status.set_pause_reason("check if tp_order is filled") #Checks if the take profit order is filled if self.status.get_take_profit_order()["id"] not in open_orders_ids: + # I hate Kucoin: + # Check if the order has a wrong id. If so, update the order. + for order in open_orders_list: + if order["amount"]==self.status.get_take_profit_order()["amount"] and order["price"]==self.status.get_take_profit_order()["price"] and order["side"]==self.status.get_take_profit_order()["side"]: + #Right order, wrong id. Update order + self.broker.logger.log_this(f"Updating take profit order for {self.status.get_pair()}") + self.status.set_take_profit_order(order) + self.update_status(True) + return 0 + tp_status = self.broker.get_order(self.status.get_take_profit_order()["id"],self.status.get_pair()) if tp_status["status"]=="closed": if tp_status["filled"]>0: @@ -1162,6 +1196,7 @@ class trader: self.broker.logger.log_this(f"Take profit order search returned empty order. Order ID: {tp_status['id']}",1,self.status.get_pair()) return 1 + self.status.set_pause_reason("check for any so is filled") # Check if any safety order is filled filled_ids = [] for order in self.status.get_safety_orders(): @@ -1169,38 +1204,44 @@ class trader: filled_ids.append(order["id"]) if filled_ids!=[]: - closed_orders = self.broker.get_closed_orders(self.status.get_pair()) - filled_orders = [item for item in closed_orders if item["id"] in filled_ids and item["status"]=="closed"] #maybe item["status"] in ["closed", "canceled", ""]? - self.status.set_safety_orders_filled(self.status.get_safety_orders_filled()+len(filled_orders)) - renew_outcome = self.renew_tp_and_so_routine(filled_orders) - #0 OK, 1 take profit order is None, 2 not enough funds, 3 can't cancel TP (filled?), 4 can't send new TP - if renew_outcome==1: - self.broker.logger.log_this(f"Error in trader: TP order is None. Restart will be attempted. renew_tp_and_so_routine returned 1",0,self.status.get_pair()) - if self.config.get_attempt_restart(): - self.status.save_to_file(is_backup=True) - self.restart = True - return 1 - elif renew_outcome==2: - #Not enough funds? - self.broker.logger.log_this(f"Can't send new safety order. Not enough funds? renew_tp_and_so_routine returned 2",1,self.status.get_pair()) - #Set no_of_safety_orders to the same amount of orders open so the script does not try to send new safety orders - #This can be improved - self.status.set_no_of_safety_orders(self.status.get_so_amount()) - 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("") - if self.config.get_attempt_restart(): - self.status.save_to_file(is_backup=True) - self.restart = True - return 1 - elif renew_outcome==4: - self.broker.logger.log_this(f"Error in trader: Can't send new take profit order. Restart will be attempted. renew_tp_and_so_routine returned 4",0,self.status.get_pair()) - if self.config.get_attempt_restart(): - self.status.save_to_file(is_backup=True) - self.restart = True - return 1 + #closed_orders = self.broker.get_closed_orders(self.status.get_pair()) + #filled_orders = [item for item in closed_orders if item["id"] in filled_ids and item["status"]=="closed"] + filled_orders = [] + for id in filled_ids: + order = self.broker.get_order(id, self.status.get_pair()) + if order["status"]=="closed": + filled_orders.append(order) + if len(filled_orders)>0: #To make sure that the safety orders are actually filled (Kucoin sometimes sends incomplete order lists) + self.status.set_safety_orders_filled(self.status.get_safety_orders_filled()+len(filled_orders)) + renew_outcome = self.renew_tp_and_so_routine(filled_orders) + #0 OK, 1 take profit order is None, 2 not enough funds, 3 can't cancel TP (filled?), 4 can't send new TP + if renew_outcome==1: + self.broker.logger.log_this(f"Error in trader: TP order is None. Restart will be attempted. renew_tp_and_so_routine returned 1",0,self.status.get_pair()) + if self.config.get_attempt_restart(): + self.status.save_to_file(is_backup=True) + self.restart = True + return 1 + elif renew_outcome==2: + #Not enough funds? + self.broker.logger.log_this(f"Can't send new safety order. Not enough funds? renew_tp_and_so_routine returned 2",1,self.status.get_pair()) + #Set no_of_safety_orders to the same amount of orders open so the script does not try to send new safety orders + #This can be improved + self.status.set_no_of_safety_orders(self.status.get_so_amount()) + 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("") + if self.config.get_attempt_restart(): + self.status.save_to_file(is_backup=True) + self.restart = True + return 1 + elif renew_outcome==4: + self.broker.logger.log_this(f"Error in trader: Can't send new take profit order. Restart will be attempted. renew_tp_and_so_routine returned 4",0,self.status.get_pair()) + if self.config.get_attempt_restart(): + self.status.save_to_file(is_backup=True) + self.restart = True + return 1 #Should we send more safety orders without touching the TP order? #Necessary check if we add to no_of_safety_orders or modify concurrent_safety_orders at runtime