From 3daca5336e1f9a689d1c0ccedbd83d64cfc365d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20S=C3=A1nchez?= Date: Mon, 25 Aug 2025 10:39:03 -0300 Subject: [PATCH] /mod_concurrent_safety_orders endpoint added --- main.py | 55 ++++++- status_handler.py | 4 + todo.txt | 3 +- trader.py | 382 +++++++++++++++++++++++---------------------- utils/commander.py | 24 ++- 5 files changed, 273 insertions(+), 195 deletions(-) diff --git a/main.py b/main.py index 1b35840..f55f062 100644 --- a/main.py +++ b/main.py @@ -18,7 +18,7 @@ import exchange_wrapper import trader -version = "2025.08.24" +version = "2025.08.25" ''' Color definitions. If you want to change them, check the reference at https://en.wikipedia.org/wiki/ANSI_escape_code#Colors @@ -849,6 +849,32 @@ def mod_order_size(): return jsonify({'Error': 'Halp'}) +@base_api.route("/mod_concurrent_safety_orders", methods=['POST']) +def mod_concurrent_safety_orders(): + ''' + POST request + + Parameters: + base: str + quote: str + amount: int + ''' + + if not "X-API-KEY" in request.headers or not request.headers.get("X-API-KEY") in valid_keys: + return jsonify({'Error': 'API key invalid'}), 401 + try: + if request.json is None: + return jsonify({'Error': 'request.json is None'}) + data = request.json + base = data["base"] + quote = data["quote"] + amount = data["amount"] + return unwrapped_mod_concurrent_safety_orders(base,quote,amount) + except Exception as e: + print(e) + return jsonify({'Error': 'Halp'}) + + @base_api.route("/mod_default_order_size", methods=['POST']) def mod_default_order_size(): ''' @@ -1784,6 +1810,7 @@ def unwrapped_mod_order_size(base,quote,amount): for instance in running_traders: if symbol==instance.config.get_pair(): instance.config.set_order_size(float(amount)) + instance.config.save_to_file() broker.logger.log_this("Done. The change will take effect when the next deal is started",2,symbol) return jsonify({"Success": "Success. The change will take effect when the next deal is started"}) except Exception: @@ -1791,6 +1818,32 @@ def unwrapped_mod_order_size(base,quote,amount): return jsonify({"Error": "Error changing order size"}) +def unwrapped_mod_concurrent_safety_orders(base,quote,amount): + ''' + Modifies the amount of safety orders that a trader keeps opened at the same time. + + Parameters: + base (str): The base currency of the pair. + quote (str): The quote currency of the pair. + amount (str): The new amount. + + Returns: + jsonify: A jsonified dictionary detailing the outcome of the operation + ''' + + try: + symbol = f"{base}/{quote}" + for instance in running_traders: + if symbol==instance.config.get_pair(): + instance.config.set_concurrent_safety_orders(int(amount)) + instance.config.save_to_file() + broker.logger.log_this("Done. The change will take effect as new safety orders are sent or filled",2,symbol) + return jsonify({"Success": "Success. The change will take effect as new safety orders are sent or filled"}) + except Exception: + broker.logger.log_this("Error changing safety orders amount. Ignoring...",2,symbol) + return jsonify({"Error": "Error changing safety orders amount"}) + + def unwrapped_mod_default_order_size(amount): ''' Modifies the default order size of a broker. diff --git a/status_handler.py b/status_handler.py index afb4a49..d81b0d7 100644 --- a/status_handler.py +++ b/status_handler.py @@ -155,6 +155,10 @@ class StatusHandler: def get_status_file_path(self): return self.status_file_path + def set_pair(self, trading_pair): + self.pair = trading_pair + return 0 + def set_status_file_path(self, new_file_path: str): # if not isinstance(new_file_path, str): # self.broker.logger.log_this(f"File path provided is not a string",1,self.get_pair()) diff --git a/todo.txt b/todo.txt index d73f08a..2ec36ed 100755 --- a/todo.txt +++ b/todo.txt @@ -9,8 +9,7 @@ Mandatory: 5. Dockerize. 6. Inspect orderbook liquidity prior to changing mode from short to long (big sell market order needs to have liquidity). 7. API endpoint to modify the amount of concurrent safety orders -8. Maybe when boosted, also increment the amount of concurrent safety orders? -9. Use create_orders ccxt method to send the batch of safety orders (Binance does not support it in spot trading) +8. Use create_orders ccxt method to send the batch of safety orders (Binance does not support it in spot trading) Would be nice to have: diff --git a/trader.py b/trader.py index a887278..06e4668 100755 --- a/trader.py +++ b/trader.py @@ -118,77 +118,77 @@ class trader: self.status.set_safety_orders_filled(0) #Reloads the market - new_market_data = self.broker.fetch_market(self.config.get_pair()) + new_market_data = self.broker.fetch_market(self.status.get_pair()) if new_market_data is not None: self.market = new_market_data self.pause = True self.status.set_pause_reason("start_trader") - if self.config.get_is_short(): - self.broker.logger.log_this("Calculating optimal order size...",2,self.config.get_pair()) + if self.status.get_is_short(): + self.broker.logger.log_this("Calculating optimal order size...",2,self.status.get_pair()) #Get minimum order size from exchange - self.broker.logger.log_this("Fetching minimum order size...",2,self.config.get_pair()) - min_base_size = self.broker.get_min_base_size(self.config.get_pair()) + self.broker.logger.log_this("Fetching minimum order size...",2,self.status.get_pair()) + min_base_size = self.broker.get_min_base_size(self.status.get_pair()) if min_base_size is None: - self.broker.logger.log_this("Can't fetch the minimum order size",1,self.config.get_pair()) + self.broker.logger.log_this("Can't fetch the minimum order size",1,self.status.get_pair()) return 1 #Fetch the amount of free base available on the exchange - self.broker.logger.log_this("Fetching free base currency on the exchange...",2,self.config.get_pair()) + self.broker.logger.log_this("Fetching free base currency on the exchange...",2,self.status.get_pair()) free_base = self.fetch_free_base() if free_base is None: - self.broker.logger.log_this("Can't fetch the amount of base at the exchange",1,self.config.get_pair()) + self.broker.logger.log_this("Can't fetch the amount of base at the exchange",1,self.status.get_pair()) return 1 #Buy missing base sold because of rounding errors (rare) if self.status.get_old_long()!={}: - diff = self.broker.amount_to_precision(self.config.get_pair(), self.status.get_old_long()["tp_amount"] - free_base) + diff = self.broker.amount_to_precision(self.status.get_pair(), self.status.get_old_long()["tp_amount"] - free_base) if diff>min_base_size: - self.broker.logger.log_this(f"Buying missing {diff} {self.base}",1,self.config.get_pair()) - self.broker.new_market_order(self.config.get_pair(),diff,"buy",amount_in_base=True) + self.broker.logger.log_this(f"Buying missing {diff} {self.base}",1,self.status.get_pair()) + self.broker.new_market_order(self.status.get_pair(),diff,"buy",amount_in_base=True) time.sleep(self.broker.get_wait_time()*2) #Re-quering for the amount of base currency on the exchange free_base = self.fetch_free_base() if free_base is None: - self.broker.logger.log_this("Can't fetch the amount of base at the exchange",1,self.config.get_pair()) + self.broker.logger.log_this("Can't fetch the amount of base at the exchange",1,self.status.get_pair()) return 1 #Calculate order size and amount of safety orders - self.broker.logger.log_this("Calculating the order size...",2,self.config.get_pair()) + self.broker.logger.log_this("Calculating the order size...",2,self.status.get_pair()) order_size,no_of_safety_orders = self.calculate_order_size(free_base,min_base_size,self.config.get_max_short_safety_orders()) if order_size is None or no_of_safety_orders is None: - self.broker.logger.log_this("Can't calculate optimal size",1,self.config.get_pair()) + self.broker.logger.log_this("Can't calculate optimal size",1,self.status.get_pair()) return 1 - self.config.set_order_size(order_size) - self.config.set_no_of_safety_orders(no_of_safety_orders) - self.broker.logger.log_this(f"Order size: {self.broker.amount_to_precision(self.config.get_pair(),order_size)}. Amount of safety orders: {no_of_safety_orders}",2,self.config.get_pair()) + self.status.set_order_size(order_size) + self.status.set_no_of_safety_orders(no_of_safety_orders) + self.broker.logger.log_this(f"Order size: {self.broker.amount_to_precision(self.status.get_pair(),order_size)}. Amount of safety orders: {no_of_safety_orders}",2,self.status.get_pair()) #Write the changes to the config file self.config.save_to_file() else: #Check order size self.status.set_pause_reason("start_trader - checking order size") - self.broker.logger.log_this("Checking for order size",2,self.config.get_pair()) - minimum_order_size_allowed = self.broker.get_min_quote_size(self.config.get_pair()) + self.broker.logger.log_this("Checking for order size",2,self.status.get_pair()) + minimum_order_size_allowed = self.broker.get_min_quote_size(self.status.get_pair()) if minimum_order_size_allowed is not None and minimum_order_size_allowed>self.config.get_order_size(): - self.broker.logger.log_this(f"Order size too small. Minimum order size is {minimum_order_size_allowed} {self.quote}",1,self.config.get_pair()) + self.broker.logger.log_this(f"Order size too small. Minimum order size is {minimum_order_size_allowed} {self.quote}",1,self.status.get_pair()) if minimum_order_size_allowed= min_base_size: - self.broker.logger.log_this(f"Balance to clean: {balance_to_clean-minimum_cleanup_size} {self.base}",2,self.config.get_pair()) - self.broker.logger.log_this("Sending cleanup order...",2,self.config.get_pair()) - cleanup_order = self.broker.new_limit_order(self.config.get_pair(),balance_to_clean-minimum_cleanup_size,"sell",self.status.get_take_profit_price()) + self.broker.logger.log_this(f"Balance to clean: {balance_to_clean-minimum_cleanup_size} {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-minimum_cleanup_size,"sell",self.status.get_take_profit_price()) if cleanup_order not in [None,self.broker.get_empty_order()]: - self.broker.logger.log_this("Cleanup successful",2,self.config.get_pair()) + self.broker.logger.log_this("Cleanup successful",2,self.status.get_pair()) return 0 - self.broker.logger.log_this("Problems with the cleanup order",1,self.config.get_pair()) + self.broker.logger.log_this("Problems with the cleanup order",1,self.status.get_pair()) return 1 - self.broker.logger.log_this("No cleanup needed",2,self.config.get_pair()) + self.broker.logger.log_this("No cleanup needed",2,self.status.get_pair()) return 0 @@ -463,16 +463,16 @@ class trader: while amount_of_so>minimum_amount_of_safety_orders: optimal_order_size = self.return_optimal_order_size(free_base,min_base_size,amount_of_so,self.config.get_safety_order_scale()) #safety_order_scale: safety order growth factor if optimal_order_size!=0: - self.broker.logger.log_this(f"Optimal order size is {optimal_order_size}",2,self.config.get_pair()) + self.broker.logger.log_this(f"Optimal order size is {optimal_order_size}",2,self.status.get_pair()) break 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.config.get_pair()) + 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("") return None,None if optimal_order_size0: #Negative profits are not saved because the cleanup takes care of the unsold base currency (the notorious small change issue that plagues some exchanges) self.profit_to_db(profit,filled_order["id"],self.broker.get_write_order_history()) else: #For logging purposes - self.broker.logger.log_this(f"NEGATIVE PROFIT - Total amount of base: {self.status.get_base_bought()}, base in the order: {filled_order['amount']}, base filled: {filled_order['filled']}, base 'profit': {base_profit}",1,self.config.get_pair()) + self.broker.logger.log_this(f"NEGATIVE PROFIT - Total amount of base: {self.status.get_base_bought()}, base in the order: {filled_order['amount']}, base filled: {filled_order['filled']}, base 'profit': {base_profit}",1,self.status.get_pair()) self.telegram_bot_sendprofit(profit,filled_order,base_profit=base_profit) # Print profit message on screen extra = ' and {:.4f}'.format(base_profit) + f" {self.base}" if base_profit>0 else "" - self.broker.logger.log_this(f"Trader closed a deal. Profit: {'{:.4f}'.format(profit)} {self.quote}{extra}",2,self.config.get_pair()) - self.broker.logger.log_this(f"Fill price: {filled_order['price']} {self.quote}",2,self.config.get_pair()) - self.broker.logger.log_this(f"Safety orders triggered: {self.status.get_safety_orders_filled()}",2,self.config.get_pair()) + self.broker.logger.log_this(f"Trader closed a deal. Profit: {'{:.4f}'.format(profit)} {self.quote}{extra}",2,self.status.get_pair()) + self.broker.logger.log_this(f"Fill price: {filled_order['price']} {self.quote}",2,self.status.get_pair()) + self.broker.logger.log_this(f"Safety orders triggered: {self.status.get_safety_orders_filled()}",2,self.status.get_pair()) self.status.set_pause_reason("take_profit_routine - check time limit") #Checks if there is a time limit for the trader @@ -778,7 +778,7 @@ class trader: self.status.set_pause_reason("take_profit_routine - if stop_when_profit") if self.status.get_stop_when_profit(): #Signal to stop when trade is closed - self.broker.logger.log_this("Pair shutting down. So long and thanks for all the fish",0,self.config.get_pair()) + self.broker.logger.log_this("Pair shutting down. So long and thanks for all the fish",0,self.status.get_pair()) self.quit = True return 1 @@ -787,18 +787,18 @@ class trader: self.status.set_pause_reason("Checking slippage") if self.config.get_check_slippage(): - self.broker.logger.log_this("Checking slippage...",2,self.config.get_pair()) - price_to_compare = self.broker.get_top_bid_price(self.config.get_pair()) if self.config.get_is_short() else self.broker.get_top_ask_price(self.config.get_pair()) + self.broker.logger.log_this("Checking slippage...",2,self.status.get_pair()) + price_to_compare = self.broker.get_top_bid_price(self.status.get_pair()) if self.config.get_is_short() else self.broker.get_top_ask_price(self.status.get_pair()) 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.config.get_pair()) + 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()) #The trader is restarted by the instance instead of by itself to allow a couple of more seconds for the price to return to normal. #This could also be the default behavior. self.pause = False self.restart = True return 1 - elif self.check_orderbook_depth(self.broker.get_slippage_default_threshold(),self.config.get_order_size(),filled_order["price"]): - self.broker.logger.log_this(f"Orderbook depth not sufficient, waiting for cooldown and restarting trader",1,self.config.get_pair()) + 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.restart = True @@ -813,7 +813,7 @@ class trader: self.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.config.get_pair()) + self.broker.logger.log_this(self.trader_restart_errors[restart_trader],1,self.status.get_pair()) return restart_trader @@ -827,22 +827,22 @@ class trader: if amount<1: return 0 - orders_to_place = min(self.config.get_no_of_safety_orders()-self.status.get_so_amount(),amount) + orders_to_place = min(self.status.get_no_of_safety_orders()-self.status.get_so_amount(),amount) if orders_to_place<1: return 0 orders_placed = 0 for i in range(orders_to_place): - self.broker.logger.log_this(f"Sending a new safety order ({i+1}/{orders_to_place})",2,self.config.get_pair()) + self.broker.logger.log_this(f"Sending a new safety order ({i+1}/{orders_to_place})",2,self.status.get_pair()) so_size = self.gib_so_size(self.status.get_order_size(),self.status.get_so_amount()+1,self.config.get_safety_order_scale()) if self.config.get_is_short(): - new_order = self.broker.new_limit_order(self.config.get_pair(),so_size,"sell",self.status.get_safety_price_table()[self.status.get_so_amount()+1]) + new_order = self.broker.new_limit_order(self.status.get_pair(),so_size,"sell",self.status.get_safety_price_table()[self.status.get_so_amount()+1]) else: - new_order = self.broker.new_limit_order(self.config.get_pair(),so_size/self.status.get_safety_price_table()[self.status.get_so_amount()+1],"buy",self.status.get_safety_price_table()[self.status.get_so_amount()+1]) + new_order = self.broker.new_limit_order(self.status.get_pair(),so_size/self.status.get_safety_price_table()[self.status.get_so_amount()+1],"buy",self.status.get_safety_price_table()[self.status.get_so_amount()+1]) if new_order==1: - self.broker.logger.log_this("Not enough balance to send a new safety order",1,self.config.get_pair()) + self.broker.logger.log_this("Not enough balance to send a new safety order",1,self.status.get_pair()) return orders_placed elif new_order is None: - self.broker.logger.log_this("new_limit_order returned None",1,self.config.get_pair()) + self.broker.logger.log_this("new_limit_order returned None",1,self.status.get_pair()) return orders_placed orders_placed+=1 self.status.add_safety_order(new_order) @@ -859,7 +859,7 @@ class trader: #Check if current TP order is valid if self.status.get_take_profit_order() is None: - self.broker.logger.log_this("Take profit order is None, can't send a new safety order",1,self.config.get_pair()) + self.broker.logger.log_this("Take profit order is None, can't send a new safety order",1,self.status.get_pair()) return 1 #Pause the trader @@ -889,16 +889,16 @@ class trader: self.status.set_safety_orders(new_order_list) #Cancel old TP order - if self.broker.cancel_order(self.status.get_take_profit_order()["id"],self.config.get_pair())==1: - error_string = f"{self.config.get_pair()} | {self.status.get_take_profit_order()['id']} | Old TP order probably filled. Can't cancel. This trader should be restarted" - self.broker.logger.log_this(f"Old take profit order is probably filled, can't cancel. This trader should be restarted. Order ID: {self.status.get_take_profit_order()['id']}",1,self.config.get_pair()) + if self.broker.cancel_order(self.status.get_take_profit_order()["id"],self.status.get_pair())==1: + error_string = f"{self.status.get_pair()} | {self.status.get_take_profit_order()['id']} | Old TP order probably filled. Can't cancel. This trader should be restarted" + self.broker.logger.log_this(f"Old take profit order is probably filled, can't cancel. This trader should be restarted. Order ID: {self.status.get_take_profit_order()['id']}",1,self.status.get_pair()) self.status.set_pause_reason(error_string) return 2 #Check if old TP order was partially filled - old_tp_order = self.broker.get_order(self.status.get_take_profit_order()["id"],self.config.get_pair()) + old_tp_order = self.broker.get_order(self.status.get_take_profit_order()["id"],self.status.get_pair()) if old_tp_order["filled"]>0: - self.broker.logger.log_this(f"Old take profit order is partially filled, id {old_tp_order['id']}",1,self.config.get_pair()) + self.broker.logger.log_this(f"Old take profit order is partially filled, id {old_tp_order['id']}",1,self.status.get_pair()) if self.broker.get_follow_order_history(): self.status.update_deal_order_history(old_tp_order) #self.status.set_base_bought(old_tp_order["remaining"]) @@ -922,12 +922,12 @@ class trader: if len(self.status.get_safety_orders())0: return self.take_profit_routine(tp_status) - self.broker.logger.log_this(f"Take profit order closed but not filled, 0 filled. Stopping trader. Order ID: {self.status.get_take_profit_order()['id']}",1,self.config.get_pair()) + self.broker.logger.log_this(f"Take profit order closed but not filled, 0 filled. Stopping trader. Order ID: {self.status.get_take_profit_order()['id']}",1,self.status.get_pair()) #Cancelling safety orders for item in self.status.get_safety_orders(): - self.broker.cancel_order(item["id"],self.config.get_pair()) + self.broker.cancel_order(item["id"],self.status.get_pair()) if self.config.get_attempt_restart(): self.status.save_to_file(is_backup=True) self.restart = True - self.broker.logger.log_this("Take profit order closed but not filled, trader will be restarted.",0,self.config.get_pair()) + self.broker.logger.log_this("Take profit order closed but not filled, trader will be restarted.",0,self.status.get_pair()) else: - self.broker.logger.log_this("Take profit order closed but not filled, trader restart disabled.",1,self.config.get_pair()) + self.broker.logger.log_this("Take profit order closed but not filled, trader restart disabled.",1,self.status.get_pair()) return 1 elif tp_status["status"]=="canceled": #TODO: Here, if the safety order is still open, we could resend the tp order. if self.config.get_attempt_restart(): - self.broker.logger.log_this("Take profit order canceled. Restarting the trader.",1,self.config.get_pair()) + self.broker.logger.log_this("Take profit order canceled. Restarting the trader.",1,self.status.get_pair()) self.status.save_to_file(is_backup=True) self.restart = True else: - self.broker.logger.log_this("Take profit order canceled. Trader restart disabled.",1,self.config.get_pair()) + self.broker.logger.log_this("Take profit order canceled. Trader restart disabled.",1,self.status.get_pair()) return 1 elif tp_status["status"]=="": - self.broker.logger.log_this(f"Take profit order search returned empty order. Order ID: {tp_status['id']}",1,self.config.get_pair()) + 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 # Check if any safety order is filled @@ -1093,26 +1093,26 @@ class trader: filled_ids.append(order["id"]) if filled_ids!=[]: - closed_orders = self.broker.get_closed_orders(self.config.get_pair()) + 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.config.get_pair()) + 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.config.get_pair()) + 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.config.set_no_of_safety_orders(self.status.get_so_amount()) + 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.config.get_pair()) + 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(): @@ -1120,7 +1120,7 @@ class trader: 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.config.get_pair()) + 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 @@ -1128,7 +1128,7 @@ class trader: #Check for autoswitch (long->short) #Commented out because i'm not sure where this should go - #if not self.config.get_is_short() and self.status.get_so_amount()==self.config.get_no_of_safety_orders() and self.config.get_autoswitch(): + #if not self.config.get_is_short() and self.status.get_so_amount()==self.status.get_no_of_safety_orders() and self.config.get_autoswitch(): # self.switch_to_short() # self.status.save_to_file(is_backup=True) # self.restart = True @@ -1169,7 +1169,7 @@ class trader: if self.config.get_is_short() or self.config.get_tp_mode()==0: #Fixed take profit percentage tp_level = self.config.get_tp_level() elif self.config.get_tp_mode()==1: #Variable percentage - limit = self.config.get_no_of_safety_orders()/3 + limit = self.status.get_no_of_safety_orders()/3 if order_index<=1: tp_level = self.config.get_tp_level()+0.005 elif order_index<=limit: @@ -1185,7 +1185,7 @@ class trader: tp_level = self.config.get_tp_table()[-1] tp_level = self.config.get_tp_level() elif self.config.get_tp_mode()==3: #Linear percentage table - profit_table = self.linear_space(self.config.get_tp_level()+0.005,self.config.get_tp_level()-0.005,self.config.get_no_of_safety_orders()) + profit_table = self.linear_space(self.config.get_tp_level()+0.005,self.config.get_tp_level()-0.005,self.status.get_no_of_safety_orders()) tp_level = profit_table[-1] if order_index0: if self.status.get_base_bought()==0: - self.broker.logger.log_this("Amount of base equals 0, can't send take profit order",1,self.config.get_pair()) + self.broker.logger.log_this("Amount of base equals 0, can't send take profit order",1,self.status.get_pair()) return 1 if self.config.get_is_short(): self.status.set_take_profit_price(self.status.get_quote_spent()/self.status.get_base_bought()*(1-(self.get_tp_level(self.status.get_so_amount())-1))) - self.status.set_take_profit_order(self.broker.new_limit_order(self.config.get_pair(),self.status.get_base_bought(),"buy",self.status.get_take_profit_price())) + self.status.set_take_profit_order(self.broker.new_limit_order(self.status.get_pair(),self.status.get_base_bought(),"buy",self.status.get_take_profit_price())) else: self.status.set_take_profit_price(self.status.get_quote_spent()/self.status.get_base_bought()*self.get_tp_level(self.status.get_so_amount())) - self.status.set_take_profit_order(self.broker.new_limit_order(self.config.get_pair(),self.status.get_base_bought(),"sell",self.status.get_take_profit_price())) + self.status.set_take_profit_order(self.broker.new_limit_order(self.status.get_pair(),self.status.get_base_bought(),"sell",self.status.get_take_profit_price())) if self.status.get_take_profit_order()==1: #This means that there was a miscalculation of base currency amount, let's correct it. if self.config.get_is_short(): #If in short mode, we don't recalculate anything. return 1 @@ -1242,7 +1242,7 @@ class trader: return 0 tries-=1 time.sleep(self.broker.get_wait_time()) - self.broker.logger.log_this("Problems sending take profit order",1,self.config.get_pair()) + self.broker.logger.log_this("Problems sending take profit order",1,self.status.get_pair()) return 1 @@ -1254,12 +1254,12 @@ class trader: while retries>0: try: order_history = dumps(self.status.get_deal_order_history()) if write_deal_order_history else "" - dataset = (time.time(),self.config.get_pair(),amount,self.broker.get_exchange_name(),str(orderid),order_history) + dataset = (time.time(),self.status.get_pair(),amount,self.broker.get_exchange_name(),str(orderid),order_history) #Write profit to cache self.broker.write_profit_to_cache(dataset) return self.broker.write_profit_to_db(dataset) except Exception as e: - self.broker.logger.log_this(f"Exception while writing profit: {e}",1,self.config.get_pair()) + self.broker.logger.log_this(f"Exception while writing profit: {e}",1,self.status.get_pair()) retries-=1 time.sleep(.1) #Shorter wait time since it's not an API call return 1 @@ -1271,11 +1271,11 @@ class trader: ''' try: extra = f" and {round(base_profit,6)} {self.base}" if base_profit>0 else "" - message = f"{self.config.get_pair()} closed a {'short' if self.config.get_is_short() else 'long'} trade.\nProfit: {round(profit,6)} {self.quote}{extra}\nSafety orders triggered: {self.status.get_safety_orders_filled()}\nTake profit price: {order['price']} {self.quote}\nTrade size: {round(order['cost'],2)} {self.quote}\nDeal uptime: {self.seconds_to_time(self.status.get_deal_uptime())}\nOrder ID: {order['id']}\nExchange: {self.broker.get_exchange_name().capitalize()}\n" + message = f"{self.status.get_pair()} closed a {'short' if self.config.get_is_short() else 'long'} trade.\nProfit: {round(profit,6)} {self.quote}{extra}\nSafety orders triggered: {self.status.get_safety_orders_filled()}\nTake profit price: {order['price']} {self.quote}\nTrade size: {round(order['cost'],2)} {self.quote}\nDeal uptime: {self.seconds_to_time(self.status.get_deal_uptime())}\nOrder ID: {order['id']}\nExchange: {self.broker.get_exchange_name().capitalize()}\n" self.broker.logger.send_tg_message(message) return 0 except Exception as e: - self.broker.logger.log_this(f"Exception in telegram_bot_sendprofit: {e}",1,self.config.get_pair()) + self.broker.logger.log_this(f"Exception in telegram_bot_sendprofit: {e}",1,self.status.get_pair()) return 1 @@ -1341,17 +1341,17 @@ class trader: #First let's check if the market exists market = self.broker.fetch_market(f"{self.base}/{new_quote}") if market is None: - self.broker.logger.log_this("Market might not exist",1,self.config.get_pair()) + self.broker.logger.log_this("Market might not exist",1,self.status.get_pair()) return 1 if "active" in market and not market["active"]: - self.broker.logger.log_this("Market is closed",1,self.config.get_pair()) + self.broker.logger.log_this("Market is closed",1,self.status.get_pair()) return 1 if self.status.get_take_profit_order() is None: - self.broker.logger.log_this("Take profit order is None",1,self.config.get_pair()) + self.broker.logger.log_this("Take profit order is None",1,self.status.get_pair()) return 1 #Replace the current take profit order with a new one with new quote currency - self.broker.logger.log_this("Replacing take profit order",2,self.config.get_pair()) + self.broker.logger.log_this("Replacing take profit order",2,self.status.get_pair()) self.status.set_take_profit_order(self.quote_currency_replace_order(self.status.get_take_profit_order(),new_quote)) if self.status.get_take_profit_order()==self.broker.get_empty_order(): return 1 @@ -1360,21 +1360,21 @@ class trader: #This is WRONG: We need to build a list of the newly sent orders and assign them with self.status.set_safety_orders() new_order_list = [] for order in self.status.get_safety_orders(): - self.broker.logger.log_this("Replacing safety order",2,self.config.get_pair()) + self.broker.logger.log_this("Replacing safety order",2,self.status.get_pair()) new_order_list.append(self.quote_currency_replace_order(order,new_quote)) self.status.set_safety_orders(new_order_list) #Calls switch_quote_currency_config - self.broker.logger.log_this("Modifying config file",2,self.config.get_pair()) + self.broker.logger.log_this("Modifying config file",2,self.status.get_pair()) self.quote_currency_switch_configs(new_quote) #Updates status_dict - self.broker.logger.log_this("Updating status file",2,self.config.get_pair()) + self.broker.logger.log_this("Updating status file",2,self.status.get_pair()) self.status.set_status_file_path(f"status/{self.base}{self.quote}.status") self.update_status(True) #Done - self.broker.logger.log_this("Quote swap successful",2,self.config.get_pair()) + self.broker.logger.log_this("Quote swap successful",2,self.status.get_pair()) return 0 @@ -1383,8 +1383,8 @@ class trader: Cancels the order and returns the new updated order ''' #Cancels the old order - if self.broker.cancel_order(old_order["id"],self.config.get_pair())==1: - self.broker.logger.log_this(f"Can't cancel old order {old_order['id']}",1,self.config.get_pair()) + if self.broker.cancel_order(old_order["id"],self.status.get_pair())==1: + self.broker.logger.log_this(f"Can't cancel old order {old_order['id']}",1,self.status.get_pair()) return self.broker.get_empty_order() #Sends the new order @@ -1400,7 +1400,7 @@ class trader: self.broker.add_pair_to_config(f"{self.base}{new_quote}") if self.broker.rewrite_config_file()==1: #Error writing broker config file, undoing changes - self.broker.logger.log_this("Error writing new broker config file",1,self.config.get_pair()) + self.broker.logger.log_this("Error writing new broker config file",1,self.status.get_pair()) self.quote_currency_undo_changes(new_quote,self.quote,False) return 1 @@ -1408,6 +1408,7 @@ class trader: old_quote = self.quote self.quote = new_quote self.config.set_pair(f"{self.base}/{self.quote}") + self.status.set_pair(f"{self.base}/{self.quote}") self.profit_filename = f"profits/{self.base}{self.quote}.profits" self.log_filename = f"logs/{self.base}{self.quote}.log" @@ -1417,12 +1418,12 @@ class trader: with open(f"status/{self.base}{self.quote}.oldlong","w") as c: c.write(dumps(self.status.get_old_long(), indent=4)) except Exception as e: - self.broker.logger.log_this(f"Exception while writing new old_long file: {e}",1,self.config.get_pair()) + self.broker.logger.log_this(f"Exception while writing new old_long file: {e}",1,self.status.get_pair()) #Write the new config file self.config.set_config_file_path(f"configs/{self.base}{self.quote}.json") if self.config.save_to_file()==1: - self.broker.logger.log_this(f"Error while writing the new trader config file",1,self.config.get_pair()) + self.broker.logger.log_this(f"Error while writing the new trader config file",1,self.status.get_pair()) #Undoing changes self.quote_currency_undo_changes(new_quote,old_quote,True) return 1 @@ -1441,12 +1442,13 @@ class trader: self.broker.add_pair_to_config(f"{self.base}{self.quote}") self.config.set_pair(f"{self.base}/{self.quote}") + self.status.set_pair(f"{self.base}/{self.quote}") self.profit_filename = f"profits/{self.base}{self.quote}.profits" self.log_filename = f"logs/{self.base}{self.quote}.log" #Writing config file if write_broker_file and self.broker.rewrite_config_file()==1: - self.broker.logger.log_this("Error in quote_currency_undo_changed: error writing new broker config file",1,self.config.get_pair()) + self.broker.logger.log_this("Error in quote_currency_undo_changed: error writing new broker config file",1,self.status.get_pair()) #Done return 0 @@ -1555,9 +1557,9 @@ class trader: except Exception as e: print(e) - safety_order_string = f"{self.status.get_safety_orders_filled()}/{self.get_color('cyan')}{len(self.status.get_safety_orders())}{self.get_color('white')}/{self.config.get_no_of_safety_orders()}".rjust(27) + safety_order_string = f"{self.status.get_safety_orders_filled()}/{self.get_color('cyan')}{len(self.status.get_safety_orders())}{self.get_color('white')}/{self.status.get_no_of_safety_orders()}".rjust(27) prices = f"{low_boundary_color}{low_boundary}{self.get_color('white')}|{price_color}{mid_boundary}{self.get_color('white')}|{target_price_color}{high_boundary}{self.get_color('white')}|{pct_color}{pct_to_profit_str}%{self.get_color('white')}" - line1 = f"{p}{pair_color}{self.config.get_pair().center(13)}{self.get_color('white')}| {safety_order_string} |{prices}| Uptime: {self.seconds_to_time(self.status.get_deal_uptime())}" + line1 = f"{p}{pair_color}{self.status.get_pair().center(13)}{self.get_color('white')}| {safety_order_string} |{prices}| Uptime: {self.seconds_to_time(self.status.get_deal_uptime())}" if self.status.get_is_boosted(): line1 = f"{line1} | BOOSTED" if self.config.get_autoswitch(): @@ -1584,18 +1586,18 @@ class trader: ''' #Load status dict if self.status.load_from_file()==1: - self.broker.logger.log_this(f"Error: Couldn't load status dict. Aborting",1,self.config.get_pair()) + self.broker.logger.log_this(f"Error: Couldn't load status dict. Aborting",1,self.status.get_pair()) self.quit = True return 1 self.status.set_pause_reason("Importing trader") - self.config.set_no_of_safety_orders(self.status.get_no_of_safety_orders()) #If this is not loaded from status_dict, it will ignore if safety orders were added at runtime + #self.config.set_no_of_safety_orders(self.status.get_no_of_safety_orders()) #If this is not loaded from status_dict, it will ignore if safety orders were added at runtime #Refresh take profit order order_id = self.status.get_take_profit_order()["id"] - self.status.set_take_profit_order(self.broker.get_order(order_id,self.config.get_pair())) + self.status.set_take_profit_order(self.broker.get_order(order_id,self.status.get_pair())) if self.status.get_take_profit_order()==self.broker.get_empty_order(): - self.broker.logger.log_this("Couldn't load take profit order (broker returned empty order). Aborting.",1,self.config.get_pair()) + self.broker.logger.log_this("Couldn't load take profit order (broker returned empty order). Aborting.",1,self.status.get_pair()) self.quit = True return 1 @@ -1604,7 +1606,7 @@ class trader: #Refresh safety orders #new_order_list = [] #for order in self.status.get_safety_orders(): - # new_order_list.append(self.broker.get_order(order["id"],self.config.get_pair())) + # new_order_list.append(self.broker.get_order(order["id"],self.status.get_pair())) # time.sleep(self.broker.get_wait_time()) #self.status.set_safety_orders(new_order_list) diff --git a/utils/commander.py b/utils/commander.py index 09d0400..30c7c89 100644 --- a/utils/commander.py +++ b/utils/commander.py @@ -11,7 +11,7 @@ try: api_key = credentials.get_credentials("testnet_api_key")["key"] base_url = credentials.get_url("testnet") #type: ignore exchanges = {"Binance":"/binance"} - if sys.argv[1]=="--local_testnet": + elif sys.argv[1]=="--local_testnet": is_testnet = True string_to_add = "LOCAL TESTNET " api_key = credentials.get_credentials("local_testnet_api_key")["key"] @@ -64,6 +64,7 @@ TRADERS 68) toggle_check_old_long_price 69) switch_quote_currency 70) view_old_long 71) switch_price 72) reload_trader_config 73) toggle_liquidate_after_switch 74) base_add_calculation +75) mod_concurrent_safety_orders 98) Change broker 99) Exit ''' @@ -860,4 +861,23 @@ if __name__=="__main__": base,quote = trading_pair.split("/") url = f"{base_url}{port}/base_add_so_calculation?base={base}"e={quote}" print(json.loads(requests.get(url,headers=headers).content)) - input("Press ENTER to continue ") \ No newline at end of file + input("Press ENTER to continue ") + + elif command==75: + print("mod_concurrent_safety_orders modifies the amount of safety orders opened at the same time") + trading_pair = input("Input trader in the format BASE/QUOTE: ").upper() + new_amount = input("Desired amount of orders: ") + if not validate_pair(trading_pair): + print("The input is invalid") + break + if not validate_int(new_amount): + print("The amount entered is invalid") + break + if input("Proceed? (Y/n) ") in ["Y","y",""]: + url = f"{base_url}{port}/mod_concurrent_safety_orders" + base,quote = trading_pair.split("/") + parameters = {"base": base, + "quote": quote, + "amount": new_amount} + print(json.loads(requests.post(url, headers=headers, json=parameters).content)) + input("Press ENTER to continue ") \ No newline at end of file