From 58fcff8618ff3fa0b96202a1989082356fed0cd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20S=C3=A1nchez?= Date: Mon, 25 Aug 2025 13:34:30 -0300 Subject: [PATCH] /mod_boosted_concurrent_safety_orders endpoint --- changelog.txt | 1 + main.py | 155 +++++++++++++++++++++++++++++++++----------------- 2 files changed, 105 insertions(+), 51 deletions(-) diff --git a/changelog.txt b/changelog.txt index 3cf9052..8011471 100755 --- a/changelog.txt +++ b/changelog.txt @@ -3,6 +3,7 @@ next: . Now the trader supports multiple safety orders at the same time. . Removed endpoint /reload_safety_orders. . Removed forcing orders when importing a trader. Maybe it will be reinstated at a later date. +. New endpoints: /mod_concurrent_safety orders and /mod_boosted_concurrent_safety_orders. 2025.08.19: . Improved log trimming. diff --git a/main.py b/main.py index f55f062..ba6fbd3 100644 --- a/main.py +++ b/main.py @@ -248,7 +248,7 @@ def restart_pair_no_json(base: str, quote: str) -> int: symbol = f"{base}/{quote}" order_list = broker.fetch_full_orders(tickers) for instance in running_traders: - if symbol==instance.config.get_pair(): + if symbol==instance.status.get_pair(): instance.pause = True #Backing up old status file instance.status.save_to_file(is_backup=True) @@ -263,7 +263,7 @@ def restart_pair_no_json(base: str, quote: str) -> int: try: running_traders.remove(instance) except ValueError: - broker.logger.log_this(f"Instance {instance.config.get_pair()} not found in running_traders.",1,instance.config.get_pair()) + broker.logger.log_this(f"Instance {instance.status.get_pair()} not found in running_traders.",1,instance.status.get_pair()) add_instance(base,quote) return 0 return 1 @@ -286,12 +286,12 @@ def main_routine(): #Restart traders that have the restart flag raised and remove traders that have the quit flag raised for instance in running_traders: if instance.restart and instance.config.get_attempt_restart(): - broker.logger.log_this(f"Restarting trader",1,instance.config.get_pair()) + broker.logger.log_this(f"Restarting trader",1,instance.status.get_pair()) restart_pair_no_json(instance.base,instance.quote) if instance.quit: #Here, check if a duster is needed - broker.logger.log_this(f"{broker.get_exchange_name()} | Quit flag raised, removing trader.",0,instance.config.get_pair()) - broker.logger.log_this(f"{broker.get_exchange_name()} | Quit flag raised, removing trader: {instance.config.get_pair()}",-1) #Forced message to TG + broker.logger.log_this(f"{broker.get_exchange_name()} | Quit flag raised, removing trader.",0,instance.status.get_pair()) + broker.logger.log_this(f"{broker.get_exchange_name()} | Quit flag raised, removing trader: {instance.status.get_pair()}",-1) #Forced message to TG if f"{instance.base}{instance.quote}" in tickers: tickers.remove(f"{instance.base}{instance.quote}") broker.remove_pair_from_config(f"{instance.base}{instance.quote}") @@ -299,7 +299,7 @@ def main_routine(): try: running_traders.remove(instance) except ValueError: - broker.logger.log_this(f"Instance {instance.config.get_pair()} not found in running_traders.",1,instance.config.get_pair()) + broker.logger.log_this(f"Instance {instance.status.get_pair()} not found in running_traders.",1,instance.status.get_pair()) #Adds pending traders if bool(instances_to_add): @@ -316,7 +316,7 @@ def main_routine(): future = executor.submit(instance.check_status, open_orders) futures.append(future) online_pairs.append(f"{instance.base}{instance.quote}") - pairs_to_fetch.append(instance.config.get_pair()) + pairs_to_fetch.append(instance.status.get_pair()) #Delete no longer used data del open_orders @@ -347,14 +347,14 @@ def main_routine(): elif "status_string" in instance.get_status_dict(): short_traders_status_strings.append(str(instance)) try: - if instance.config.get_pair() in price_list and price_list[instance.config.get_pair()] is not None: - instance.get_status_dict()["price"] = price_list[instance.config.get_pair()] + if instance.status.get_pair() in price_list and price_list[instance.status.get_pair()] is not None: + instance.get_status_dict()["price"] = price_list[instance.status.get_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,instance.config.get_pair()) + broker.logger.log_this(f"Exception while querying for pair price, key not present on price_list dictionary: {e}",1,instance.status.get_pair()) #Add paused traders to the paused trader list if instance.pause: - global_status["paused_traders"].append(instance.config.get_pair()) + global_status["paused_traders"].append(instance.status.get_pair()) paused_traders_status_strings.append(f"{cyan}Paused pairs: {list(global_status['paused_traders'])}{white}") #Delete no longer used data @@ -369,7 +369,7 @@ def main_routine(): #Updates some global status variables prior to deletion of those if len(running_traders)!=len(global_status["online_workers"]): - global_status["online_workers"] = [instance.config.get_pair() for instance in running_traders] + global_status["online_workers"] = [instance.status.get_pair() for instance in running_traders] #Prints general info instance_uptime = int(time.time()) - instance_start_time @@ -405,7 +405,7 @@ def main_routine(): #Toggle pauses if toggle_pauses: for instance in running_traders: - if instance.config.get_pair() in toggle_pauses: + if instance.status.get_pair() in toggle_pauses: instance.pause = not instance.pause toggle_pauses.clear() @@ -875,6 +875,32 @@ def mod_concurrent_safety_orders(): return jsonify({'Error': 'Halp'}) +@base_api.route("/mod_boosted_concurrent_safety_orders", methods=['POST']) +def mod_boosted_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_boosted_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(): ''' @@ -1455,7 +1481,7 @@ def unwrapped_add_pair(base,quote): #Check if the trader is already running for instance in running_traders: - if symbol==instance.config.get_pair(): + if symbol==instance.status.get_pair(): broker.logger.log_this(f"Pair already running",1,symbol) return jsonify({"Error": "Pair already running"}) @@ -1489,7 +1515,7 @@ def unwrapped_remove_pair(base,quote): try: symbol = f"{base}/{quote}" for instance in running_traders: - if symbol==instance.config.get_pair(): + if symbol==instance.status.get_pair(): instance.quit = True return jsonify({"Success": "Pair to be removed"}) except Exception as e: @@ -1556,7 +1582,7 @@ def unwrapped_switch_to_long(base,quote,calculate_profits): if f"{base}{quote}" not in broker.get_pairs(): return jsonify({"Error": "Pair not running"}) for instance in running_traders: - if f"{base}/{quote}"==instance.config.get_pair(): + if f"{base}/{quote}"==instance.status.get_pair(): instance.pause = True if instance.switch_to_long(ignore_old_long=ignore_old_long)==1: return jsonify({"Error": "Error in switch_to_long()"}) @@ -1584,14 +1610,14 @@ def unwrapped_switch_to_short(base,quote): if f"{base}{quote}" not in broker.get_pairs(): return jsonify({"Error": "Pair not running"}) for instance in running_traders: - if symbol==instance.config.get_pair() and instance.switch_to_short()==1: + if symbol==instance.status.get_pair() and instance.switch_to_short()==1: return jsonify({"Error": "Error in switch_to_short()"}) #Restart instance try: broker.logger.log_this(f"Reinitializing trader",2,symbol) for instance in running_traders: - if symbol==instance.config.get_pair(): + if symbol==instance.status.get_pair(): instance.status.set_take_profit_order(instance.broker.empty_order) instance.so = instance.broker.empty_order @@ -1640,7 +1666,7 @@ def unwrapped_load_old_long(base,quote): #Creates (or modifies) a key in the status dictionary and assigns the contents of the file to that same key. for instance in running_traders: - if instance.config.get_pair()==symbol: + if instance.status.get_pair()==symbol: instance.get_status_dict()["old_long"]=old_long instance.update_status(True) return jsonify({"Success": "old_long file loaded to status_dict"}) @@ -1667,7 +1693,7 @@ def unwrapped_view_old_long(base,quote,from_file): old_long = load(ol) return jsonify(old_long) for instance in running_traders: - if symbol==instance.config.get_pair(): + if symbol==instance.status.get_pair(): if "old_long" in instance.get_status_dict(): return jsonify(instance.get_status_dict()["old_long"]) return jsonify({"Error": "No old_long info found"}) @@ -1693,7 +1719,7 @@ def unwrapped_switch_to_long_price(base,quote): try: symbol = f"{base}/{quote}" for instance in running_traders: - if symbol==instance.config.get_pair(): + if symbol==instance.status.get_pair(): if "old_long" in instance.get_status_dict(): #minimum_switch_price = (old_target - quote_already_in)/base_left old_target = instance.get_status_dict()["old_long"]["tp_price"]*instance.get_status_dict()["old_long"]["tp_amount"] @@ -1724,7 +1750,7 @@ def unwrapped_add_safety_orders(base,quote,amount): try: symbol = f"{base}/{quote}" for instance in running_traders: - if symbol==instance.config.get_pair(): + if symbol==instance.status.get_pair(): instance.pause = True #x.no_of_safety_orders += int(amount) instance.config.set_no_of_safety_orders(instance.config.get_no_of_safety_orders()+int(amount)) @@ -1755,7 +1781,7 @@ def unwrapped_base_add_so_calculation(base,quote): try: symbol = f"{base}/{quote}" for instance in running_traders: - if symbol==instance.config.get_pair(): + if symbol==instance.status.get_pair(): free_base = instance.fetch_free_base() if free_base is None: return jsonify({"Error": "Can't fetch amount of free base on the exchange"}) @@ -1783,7 +1809,7 @@ def unwrapped_mod_tp_level(base,quote,amount): try: symbol = f"{base}/{quote}" for instance in running_traders: - if symbol==instance.config.get_pair(): + if symbol==instance.status.get_pair(): instance.config.set_tp_level(float(amount)) broker.logger.log_this("Done. The change will take effect when the next take profit order is placed",2,symbol) return jsonify({"Success": "Success. The change will take effect when the next TP order is placed"}) @@ -1808,7 +1834,7 @@ def unwrapped_mod_order_size(base,quote,amount): try: symbol = f"{base}/{quote}" for instance in running_traders: - if symbol==instance.config.get_pair(): + if symbol==instance.status.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) @@ -1834,7 +1860,7 @@ def unwrapped_mod_concurrent_safety_orders(base,quote,amount): try: symbol = f"{base}/{quote}" for instance in running_traders: - if symbol==instance.config.get_pair(): + if symbol==instance.status.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) @@ -1844,6 +1870,32 @@ def unwrapped_mod_concurrent_safety_orders(base,quote,amount): return jsonify({"Error": "Error changing safety orders amount"}) +def unwrapped_mod_boosted_concurrent_safety_orders(base,quote,amount): + ''' + Modifies the amount of safety orders that a trader keeps opened at the same time while boosted. + + 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.status.get_pair(): + instance.config.set_boosted_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. @@ -1900,7 +1952,7 @@ def unwrapped_last_call(base,quote): symbol = f"{base}/{quote}" if symbol in broker.get_pairs(): #Why was this? for instance in running_traders: - if symbol==instance.config.get_pair(): + if symbol==instance.status.get_pair(): instance.status.set_stop_when_profit(not instance.status.get_stop_when_profit()) instance.update_status(True) if instance.status.get_stop_when_profit(): @@ -1934,7 +1986,7 @@ def unwrapped_deferred_last_call(base,quote,yyyymmdd): if limit==0: return jsonify({"Error": "Can't convert date to unix"}) for instance in running_traders: - if f"{base}{quote}"==instance.config.get_pair(): + if f"{base}{quote}"==instance.status.get_pair(): instance.config.set_programmed_stop_time(limit) instance.config.set_programmed_stop(True) #save config file to disk @@ -1961,7 +2013,7 @@ def unwrapped_toggle_pause(base,quote): symbol = f"{base}/{quote}" toggle_pauses.append(symbol) for instance in running_traders: - if instance.config.get_pair()==symbol: + if instance.status.get_pair()==symbol: if instance.pause: instance.status.set_pause_reason("") return jsonify({"Success": "Trader will be resumed"}) @@ -2026,55 +2078,55 @@ def unwrapped_add_quote(base,quote,amount): ''' for instance in running_traders: - if f"{base}/{quote}"==instance.config.get_pair(): + 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 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.config.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.config.get_pair()) - new_order = broker.new_market_order(instance.config.get_pair(),float(amount),"buy") + 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.config.get_pair()) + broker.logger.log_this("Error: Market order returned None",2,instance.status.get_pair()) instance.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.config.get_pair()) + 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.config.get_pair()) + broker.logger.log_this("Problems sending the order",2,instance.status.get_pair()) instance.pause = False return jsonify({"Error": "Problems sending the order"}) elif returned_order["status"]=="expired": instance.pause = False return jsonify({"Error": "New order expired"}) elif returned_order["status"]=="closed": - broker.logger.log_this("Order sent",2,instance.config.get_pair()) + broker.logger.log_this("Order sent",2,instance.status.get_pair()) new_fees_in_base, new_fees_in_quote = instance.parse_fees(returned_order) instance.status.set_fees_paid_in_base(instance.status.get_fees_paid_in_base() + new_fees_in_base) instance.status.set_fees_paid_in_quote(instance.status.get_fees_paid_in_quote() + new_fees_in_quote) instance.status.set_base_bought(instance.status.get_base_bought() + returned_order["filled"] - new_fees_in_base) instance.status.set_quote_spent(instance.status.get_quote_spent()+returned_order["cost"]) - broker.logger.log_this("Cancelling old take profit order and sending a new one",2,instance.config.get_pair()) + broker.logger.log_this("Cancelling old take profit order and sending a new one",2,instance.status.get_pair()) attempts = 5 - while broker.cancel_order(instance.status.get_take_profit_order()["id"],instance.config.get_pair())==1: - broker.logger.log_this("Can't cancel old take profit order, retrying...",2,instance.config.get_pair()) + while broker.cancel_order(instance.status.get_take_profit_order()["id"],instance.status.get_pair())==1: + broker.logger.log_this("Can't cancel old take profit order, retrying...",2,instance.status.get_pair()) time.sleep(broker.get_wait_time()) attempts-=1 if attempts==0: - broker.logger.log_this("Can't cancel old take profit order, cancelling...",2,instance.config.get_pair()) + broker.logger.log_this("Can't cancel old take profit order, cancelling...",2,instance.status.get_pair()) instance.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.config.get_pair(),instance.status.get_base_bought(),"sell",instance.status.get_take_profit_price())) + 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())) instance.update_status(True) break else: - broker.logger.log_this("Waiting for initial order to get filled",2,instance.config.get_pair()) - broker.logger.log_this(f"{returned_order}",2,instance.config.get_pair()) + 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 - broker.logger.log_this("Done",2,instance.config.get_pair()) + broker.logger.log_this("Done",2,instance.status.get_pair()) return jsonify({"Success": "Quote added successfully"}) return jsonify({"Error": "Something horrible happened :S"}) @@ -2112,7 +2164,7 @@ def unwrapped_toggle_cleanup(base,quote): try: symbol = f"{base}/{quote}" for instance in running_traders: - if symbol==instance.config.get_pair(): + if symbol==instance.status.get_pair(): instance.config.set_cleanup(not instance.config.get_cleanup()) if instance.config.get_cleanup(): return jsonify({"Success": "Cleanup turned ON"}) @@ -2138,7 +2190,7 @@ def unwrapped_toggle_autoswitch(base,quote): try: symbol = f"{base}/{quote}" for instance in running_traders: - if symbol==instance.config.get_pair(): + if symbol==instance.status.get_pair(): if instance.config.get_autoswitch(): broker.logger.log_this("Autoswitch turned OFF",1,symbol) instance.config.set_autoswitch(False) @@ -2167,7 +2219,7 @@ def unwrapped_toggle_liquidate_after_switch(base,quote): try: symbol = f"{base}/{quote}" for instance in running_traders: - if symbol==instance.config.get_pair(): + if symbol==instance.status.get_pair(): if instance.config.get_liquidate_after_switch(): broker.logger.log_this("Liquidate after switch turned OFF",1,symbol) instance.config.set_liquidate_after_switch(False) @@ -2181,6 +2233,7 @@ def unwrapped_toggle_liquidate_after_switch(base,quote): broker.logger.log_this(f"Exception while toggling liquidate after switch: {e}",1,symbol) return jsonify({"Error": "Halp"}) + def unwrapped_toggle_check_old_long_price(base,quote): ''' Signals to the trader if it should compare the current price to the old_long price stored in the old_long dictionary. @@ -2196,7 +2249,7 @@ def unwrapped_toggle_check_old_long_price(base,quote): try: symbol = f"{base}/{quote}" for instance in running_traders: - if symbol==instance.config.get_pair(): + if symbol==instance.status.get_pair(): if instance.config.get_check_old_long_price(): broker.logger.log_this("Check OFF",1,symbol) instance.config.set_check_old_long_price(False) @@ -2227,7 +2280,7 @@ def unwrapped_switch_quote_currency(base,quote,new_quote): try: symbol = f"{base}/{quote}" for trader in running_traders: - if symbol==trader.config.get_pair(): + if symbol==trader.status.get_pair(): #Pause the trader trader.pause = True @@ -2427,7 +2480,7 @@ def unwrapped_reload_trader_config(base,quote): ''' symbol = f"{base}/{quote}" for trader in running_traders: - if trader.config.get_pair() == symbol: + if trader.status.get_pair() == symbol: if trader.config.load_from_file()==0: return jsonify({"Success": "Config file reloaded"}) return jsonify({"Error": "Error reloading config file"})