From 5cf2979f3855ec0b58defbd61efedbea6075f4d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20S=C3=A1nchez?= Date: Sat, 6 Sep 2025 11:29:36 -0300 Subject: [PATCH] partial profit support --- main.py | 10 ++-------- trader.py | 50 ++++++++++++++++++++++++++++---------------------- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/main.py b/main.py index 6b4ef8e..5b284e7 100644 --- a/main.py +++ b/main.py @@ -311,19 +311,13 @@ def main_routine(): futures = [] pairs_to_fetch = [] online_pairs = [] - open_orders_dict = {} for instance in running_traders: pairs_to_fetch.append(instance.status.get_pair()) - for item in broker.fetch_open_orders(pairs_to_fetch): - if item["symbol"] not in open_orders_dict: - open_orders_dict[item["symbol"]] = [item] - else: - open_orders_dict[item["symbol"]].append(item) - + open_orders = broker.fetch_open_orders(pairs_to_fetch) for instance in running_traders: - future = executor.submit(instance.check_status, open_orders_dict[instance.status.get_pair()]) + future = executor.submit(instance.check_status, open_orders) futures.append(future) online_pairs.append(f"{instance.base}{instance.quote}") diff --git a/trader.py b/trader.py index 0af70db..0dcbba7 100755 --- a/trader.py +++ b/trader.py @@ -770,6 +770,7 @@ class trader: #Check if some safety orders were filled partial_filled_amount = 0 + partial_filled_price = [] 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: @@ -777,10 +778,11 @@ class trader: break #Sum the filled amounts partial_filled_amount+=closed_order["filled"] + partial_filled_price.append(closed_order["average"]) #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]) - self.status.set_quote_spent(self.status.get_quote_spent() + closed_order["cost"]) + #self.status.set_base_bought(self.status.get_base_bought() + closed_order["filled"] - self.parse_fees(closed_order)[0]) + #self.status.set_quote_spent(self.status.get_quote_spent() + closed_order["cost"]) #Save the order if self.broker.get_follow_order_history(): self.status.update_deal_order_history(closed_order) @@ -790,23 +792,26 @@ 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 - - + #Handle the partial fills + if not self.status.get_is_short(): + #With short traders is just an accounting issue, since when the trader restarts it will be buying cheaper what it sold more expensive in the partially filled safety order(s) + if partial_filled_amount!=0 and len(partial_filled_price)>0 and partial_filled_amount>self.broker.get_min_base_size(self.status.get_pair()): + #send a 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": + avg_buy_price = sum(partial_filled_price)/len(partial_filled_price) + partial_profit = market_order["cost"]-(avg_buy_price*partial_filled_amount)-self.parse_fees(market_order)[1] + self.status.set_partial_profit(self.status.get_partial_profit()+partial_profit) + break + tries-=1 + if tries==0: + self.broker.logger.log_this("Partial fill sell order not filling.",1,self.status.get_pair()) + break if not self.broker.check_for_duplicate_profit_in_db(filled_order): self.status.set_pause_reason("calculating profit") @@ -1138,9 +1143,10 @@ class trader: #Extract ids from order list self.status.set_pause_reason("filtering open orders") - open_orders_ids = [order["id"] for order in 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") + self.status.set_pause_reason("check if 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()) @@ -1164,7 +1170,7 @@ class trader: # Check if the order has a wrong id. If so, update the order. # To cover a very rare case that happens if the trader sends a new take profit order but is interrupted before saving the status. # Not sure if it is worth to keep this code. - for order in open_orders: + 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()}",1,self.status.get_pair())