- Fixed tp mode 2 non-functional

- Fixed duster binance fee estimation
- Fixed executor variable shadowing breaking graceful shutdown
- Fixed infinite loop in cancel_order
- Fixed modifying running_traders during iteration
- Fixed missing "status/" prefix in old_long file paths
- Removed double TP order cancellation while switching to short.
- Added locks to prevent race conditions on running_traders.
This commit is contained in:
Nicolás Sánchez 2026-06-03 18:43:40 -03:00
parent 12999a2189
commit ffe58e2c0d
5 changed files with 389 additions and 317 deletions

View File

@ -1,3 +1,13 @@
2026.06.03:
. Fixed tp mode 2 non-functional
. Fixed duster binance fee estimation
. Fixed executor variable shadowing breaking graceful shutdown
. Fixed infinite loop in cancel_order
. Fixed modifying running_traders during iteration
. Fixed missing "status/" prefix in old_long file paths
. Removed double TP order cancellation while switching to short.
. Added locks to prevent race conditions on running_traders.
2025.12.01:
. Modified log output of new_market_order.
. Modified Kucoin's case in min_amount_of_base.

View File

@ -189,7 +189,8 @@ class duster:
if self.broker.get_exchange_name()=="binance": #CCXT still to this day does not take Binance fees into account.
try:
fee_rate = self.broker.fetch_market["maker"] if order["type"]=="limit" else self.broker.fetch_market["taker"]
market = self.broker.fetch_maker(self.duster_status["pair"])
fee_rate = market["maker"] if order["type"]=="limit" else market["taker"]
except Exception as e:
self.broker.logger.log_this(f"Exception fetching market information: {e}. Using default fee rate of 0.1%",1,f"{base}{quote}")
fee_rate = 0.001

View File

@ -622,22 +622,40 @@ class Broker:
:return: 0 if order was succesfully canceled, 1 if not
'''
tries = self.retries//2
while tries>0:
cancel_attempts = self.retries//2
while cancel_attempts > 0:
try:
while self.get_order(id,symbol)["status"]=="open":
self.exchange.cancel_order(id,symbol)
time.sleep(self.wait_time)
return 0
self.exchange.cancel_order(id, symbol)
time.sleep(self.wait_time)
if self.get_order(id, symbol)["status"] != "open":
return 0
except Exception as e:
if self.get_order(id,symbol)["status"]=="canceled":
if self.get_order(id, symbol)["status"] == "canceled":
return 0
self.logger.log_this(f"Exception in cancel_order: id {id} - exception: {e}",1)
if no_retries:
break
time.sleep(self.wait_time)
tries-=1
cancel_attempts -= 1
time.sleep(self.wait_time)
return 1
# tries = self.retries//2
# while tries>0:
# try:
# while self.get_order(id,symbol)["status"]=="open":
# self.exchange.cancel_order(id,symbol)
# time.sleep(self.wait_time)
# return 0
# except Exception as e:
# if self.get_order(id,symbol)["status"]=="canceled":
# return 0
# self.logger.log_this(f"Exception in cancel_order: id {id} - exception: {e}",1)
# if no_retries:
# break
# time.sleep(self.wait_time)
# tries-=1
# return 1
def amount_to_precision(self,pair,amount):

652
main.py
View File

@ -5,7 +5,7 @@ from sys import argv
from os import _exit as os_exit
from json import load
from datetime import date
from threading import Thread
from threading import Thread, Lock
from waitress import serve
from concurrent.futures import ThreadPoolExecutor, as_completed
@ -18,7 +18,7 @@ import exchange_wrapper
import trader
version = "2025.12.01"
version = "2026.06.03"
'''
Color definitions. If you want to change them, check the reference at https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
@ -43,7 +43,7 @@ executor = None
def shutdown_handler(signum, _):
broker.logger.log_this(f"Received signal {signum}, shutting down.", 2)
if executor:
executor.shutdown(wait=True, timeout=5)
executor.shutdown(wait=True)
os_exit(0)
# Register signals for shutdown handler
@ -119,10 +119,11 @@ def add_instance(base: str, quote: str) -> int:
#Check if the pair is already running
pair = f"{base}{quote}"
for instance in running_traders:
if f"{instance.base}{instance.quote}"==pair:
broker.logger.log_this(f"Pair already running, duplicate traders are not allowed",1,pair)
return 1
with traders_lock:
for instance in running_traders:
if f"{instance.base}{instance.quote}"==pair:
broker.logger.log_this(f"Pair already running, duplicate traders are not allowed",1,pair)
return 1
#Initialize the trader object and add the pair to the tickers list
instances_to_add.append(trader.trader(broker,f"{base}/{quote}"))
@ -143,7 +144,8 @@ def initialize_instance(base: str, quote: str) -> int:
int: 0 if successful
'''
broker.logger.log_this(f"Initializing {f'{base}/{quote}'}")
running_traders.append(trader.trader(broker,f'{base}/{quote}'))
with traders_lock:
running_traders.append(trader.trader(broker,f'{base}/{quote}'))
if f'{base}{quote}' not in tickers:
tickers.append(f'{base}{quote}')
return 0
@ -246,33 +248,34 @@ def restart_pair_no_json(base: str, quote: str) -> int:
try:
symbol = f"{base}/{quote}"
for instance in running_traders:
if symbol==instance.status.get_pair():
instance.set_pause(True, "Restarting trader")
#Backing up old status file
instance.status.save_to_file(is_backup=True)
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():
with traders_lock:
for instance in running_traders:
if symbol==instance.status.get_pair():
instance.set_pause(True, "Restarting trader")
#Backing up old status file
instance.status.save_to_file(is_backup=True)
broker.logger.log_this(f"Cancelling old take profit order",2,symbol)
try:
broker.cancel_order(item["id"],item["symbol"])
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 safety order: {e}",2,symbol)
try:
running_traders.remove(instance)
except ValueError:
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
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:
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
except Exception as e:
broker.logger.log_this(f"Exception in restart_pair_no_json: {e}",1,symbol)
return 1
@ -282,6 +285,7 @@ def main_routine():
global last_market_reload
global reload_interval
global screen_buffer
global executor
executor = ThreadPoolExecutor(max_workers=len(broker.get_config()["pairs"])+worker_threads_overprovisioning)
is_testnet = "TESTNET " if broker.get_config()["is_sandbox"] else ""
@ -290,42 +294,51 @@ def main_routine():
while True:
#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.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.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}")
broker.rewrite_config_file()
try:
running_traders.remove(instance)
except ValueError:
broker.logger.log_this(f"Instance {instance.status.get_pair()} not found in running_traders.",1,instance.status.get_pair())
to_restart = []
to_remove = []
with traders_lock:
for instance in running_traders:
if instance.restart and instance.config.get_attempt_restart():
to_restart.append(instance)
if instance.quit:
to_remove.append(instance)
for instance in to_restart:
broker.logger.log_this(f"Restarting trader",1,instance.status.get_pair())
restart_pair_no_json(instance.base,instance.quote)
for instance in to_remove:
#Here, check if a duster is needed
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}")
broker.rewrite_config_file()
try:
running_traders.remove(instance)
except ValueError:
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):
for instance in instances_to_add:
running_traders.append(instance)
instances_to_add.clear()
with traders_lock:
for instance in instances_to_add:
running_traders.append(instance)
instances_to_add.clear()
#Prepares the trader threads
futures = []
pairs_to_fetch = []
online_pairs = []
for instance in running_traders:
pairs_to_fetch.append(instance.status.get_pair())
with traders_lock:
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}")
with traders_lock:
for instance in running_traders:
future = executor.submit(instance.check_status, open_orders)
futures.append(future)
online_pairs.append(f"{instance.base}{instance.quote}")
#Fetch prices
price_list = broker.get_prices(pairs_to_fetch)
@ -343,24 +356,26 @@ def main_routine():
short_traders_status_strings = []
paused_traders_status_strings = []
global_status["paused_traders"].clear()
for instance in running_traders:
if not instance.config.get_is_short():
curr += int(instance.status.get_so_amount()) # For the safety order occupancy percentage calculation
top += int(instance.config.get_no_of_safety_orders())
if "status_string" in instance.get_status_dict():
long_traders_status_strings.append(str(instance))
elif "status_string" in instance.get_status_dict():
short_traders_status_strings.append(str(instance))
try:
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.status.get_pair())
#Add paused traders to the paused trader list
if instance.pause:
global_status["paused_traders"].append(instance.status.get_pair())
paused_traders_status_strings.append(f"{cyan}Paused pairs: {list(global_status['paused_traders'])}{white}")
with traders_lock:
for instance in running_traders:
if not instance.config.get_is_short():
curr += int(instance.status.get_so_amount()) # For the safety order occupancy percentage calculation
top += int(instance.config.get_no_of_safety_orders())
if "status_string" in instance.get_status_dict():
long_traders_status_strings.append(str(instance))
elif "status_string" in instance.get_status_dict():
short_traders_status_strings.append(str(instance))
try:
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.status.get_pair())
#Add paused traders to the paused trader list
if instance.pause:
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
del price_list
@ -374,7 +389,8 @@ 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.status.get_pair() for instance in running_traders]
with traders_lock:
global_status["online_workers"] = [instance.status.get_pair() for instance in running_traders]
#Prints general info
instance_uptime = int(time.time()) - instance_start_time
@ -409,9 +425,10 @@ def main_routine():
#Toggle pauses
if toggle_pauses:
for instance in running_traders:
if instance.status.get_pair() in toggle_pauses:
instance.pause = not instance.pause
with traders_lock:
for instance in running_traders:
if instance.status.get_pair() in toggle_pauses:
instance.pause = not instance.pause
toggle_pauses.clear()
#Checks if market reload is due
@ -1505,9 +1522,10 @@ def unwrapped_return_worker_status(base,quote):
dict: The status dictionary of the trader.
'''
symbol = f"{base}/{quote}"
for instance in running_traders:
if instance.status.get_pair() == symbol:
return jsonify(instance.status.get_status())
with traders_lock:
for instance in running_traders:
if instance.status.get_pair() == symbol:
return jsonify(instance.status.get_status())
return jsonify({"Error": "Worker does not exist"})
@ -1518,8 +1536,8 @@ def unwrapped_return_all_worker_status():
Returns:
dict: The status dictionary of all traders.
'''
return {instance.status.get_pair(): instance.status.get_status() for instance in running_traders}
with traders_lock:
return {instance.status.get_pair(): instance.status.get_status() for instance in running_traders}
def unwrapped_add_pair(base,quote):
@ -1538,10 +1556,11 @@ def unwrapped_add_pair(base,quote):
symbol = f"{base}/{quote}"
#Check if the trader is already running
for instance in running_traders:
if symbol==instance.status.get_pair():
broker.logger.log_this(f"Pair already running",1,symbol)
return jsonify({"Error": "Pair already running"})
with traders_lock:
for instance in running_traders:
if symbol==instance.status.get_pair():
broker.logger.log_this(f"Pair already running",1,symbol)
return jsonify({"Error": "Pair already running"})
#Check if the market exists and it's open
if not broker.validate_market(symbol):
@ -1571,10 +1590,11 @@ def unwrapped_remove_pair(base,quote):
'''
try:
symbol = f"{base}/{quote}"
for instance in running_traders:
if symbol==instance.status.get_pair():
instance.quit = True
symbol = f"{base}/{quote}"
with traders_lock:
for instance in running_traders:
if symbol==instance.status.get_pair():
instance.quit = True
return jsonify({"Success": "Pair to be removed"})
except Exception as e:
broker.logger.log_this(f"Exception while removing instance: {e}",1,symbol)
@ -1639,15 +1659,16 @@ def unwrapped_switch_to_long(base,quote,calculate_profits):
#Close trader and orders and pull info our of the orders
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.status.get_pair():
instance.set_pause(True, "Switching to long mode")
if instance.switch_to_long(ignore_old_long=ignore_old_long,double_check_price=False)==1:
return jsonify({"Error": "Error in switch_to_long()"})
if instance.start_trader()==1:
instance.quit = True
return jsonify({"Error": "Error switching to long mode (wAPI)"})
return jsonify({"Success": "Pair switched to long mode"})
with traders_lock:
for instance in running_traders:
if f"{base}/{quote}"==instance.status.get_pair():
instance.set_pause(True, "Switching to long mode")
if instance.switch_to_long(ignore_old_long=ignore_old_long,double_check_price=False)==1:
return jsonify({"Error": "Error in switch_to_long()"})
if instance.start_trader()==1:
instance.quit = True
return jsonify({"Error": "Error switching to long mode (wAPI)"})
return jsonify({"Success": "Pair switched to long mode"})
return jsonify({"Error": "Pair not found"})
@ -1667,27 +1688,29 @@ def unwrapped_switch_to_short(base,quote):
symbol = f"{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.status.get_pair() and instance.switch_to_short()==1:
return jsonify({"Error": "Error in switch_to_short()"})
with traders_lock:
for instance in running_traders:
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.status.get_pair():
instance.status.set_take_profit_order(instance.broker.empty_order)
instance.so = instance.broker.empty_order
with traders_lock:
for instance in running_traders:
if symbol==instance.status.get_pair():
instance.status.set_take_profit_order(instance.broker.empty_order)
instance.so = instance.broker.empty_order
#Reloading config file
instance.config.load_from_file()
#Reloading config file
instance.config.load_from_file()
#Enabling autoswitch
instance.config.set_autoswitch(True)
if instance.start_trader()==1:
instance.quit = True
return jsonify({"Error": "Error switching to short mode (wAPI)"})
return jsonify({"Success": "Pair switched to short mode"})
#Enabling autoswitch
instance.config.set_autoswitch(True)
if instance.start_trader()==1:
instance.quit = True
return jsonify({"Error": "Error switching to short mode (wAPI)"})
return jsonify({"Success": "Pair switched to short mode"})
except Exception as e:
broker.logger.log_this(f"Exception while reinitializing instance: {e}",1,symbol)
return jsonify({"Error": "Can't initialize trader"})
@ -1709,7 +1732,7 @@ def unwrapped_load_old_long(base,quote):
#Load the file
try:
symbol = f"{base}/{quote}"
with open(f"{base}{quote}.oldlong") as ol:
with open(f"status/{base}{quote}.oldlong") as ol:
old_long = load(ol)
except Exception as e:
broker.logger.log_this(f"Exception while loading old_long file: {e}",1,symbol)
@ -1723,11 +1746,12 @@ def unwrapped_load_old_long(base,quote):
#Maybe here we could also check that the keys have the proper values
#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.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"})
with traders_lock:
for instance in running_traders:
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"})
return jsonify({"Error": "Pair not found"})
@ -1747,14 +1771,15 @@ def unwrapped_view_old_long(base,quote,from_file):
try:
symbol = f"{base}/{quote}"
if int(from_file)==1:
with open(f"{base}{quote}.oldlong") as ol:
with open(f"status/{base}{quote}.oldlong") as ol:
old_long = load(ol)
return jsonify(old_long)
for instance in running_traders:
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"})
with traders_lock:
for instance in running_traders:
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"})
return jsonify({"Error": "Pair not found"})
except Exception as e:
broker.logger.log_this(f"Exception while viewing old_long info: {e}",1,symbol)
@ -1776,16 +1801,17 @@ def unwrapped_switch_to_long_price(base,quote):
try:
symbol = f"{base}/{quote}"
for instance in running_traders:
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"]
base_left = instance.get_status_dict()["old_long"]["tp_amount"]-instance.get_status_dict()["base_bought"]
minimum_switch_price = (old_target - instance.get_status_dict()["quote_spent"])/base_left
return jsonify({"switch_price": minimum_switch_price})
return jsonify({"Error": "No old_long info found"})
return jsonify({"Error": "Pair not found"})
with traders_lock:
for instance in running_traders:
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"]
base_left = instance.get_status_dict()["old_long"]["tp_amount"]-instance.get_status_dict()["base_bought"]
minimum_switch_price = (old_target - instance.get_status_dict()["quote_spent"])/base_left
return jsonify({"switch_price": minimum_switch_price})
return jsonify({"Error": "No old_long info found"})
return jsonify({"Error": "Pair not found"})
except Exception as e:
broker.logger.log_this(f"Exception while viewing old_long info: {e}",1,symbol)
return jsonify({"Error": f"{e}"})
@ -1807,17 +1833,18 @@ def unwrapped_add_safety_orders(base,quote,amount):
try:
symbol = f"{base}/{quote}"
for instance in running_traders:
if symbol==instance.status.get_pair():
instance.set_pause(True, "Adding safety orders")
instance.status.set_no_of_safety_orders(instance.status.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.set_pause(False)
return jsonify({"Success": f"Done. Added {amount} safety orders"})
return jsonify({"Error": "Pair not found"})
with traders_lock:
for instance in running_traders:
if symbol==instance.status.get_pair():
instance.set_pause(True, "Adding safety orders")
instance.status.set_no_of_safety_orders(instance.status.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.set_pause(False)
return jsonify({"Success": f"Done. Added {amount} safety orders"})
return jsonify({"Error": "Pair not found"})
except Exception as e:
broker.logger.log_this(f"{e}",2,symbol)
return jsonify({"Error": "Error adding safety orders"})
@ -1837,14 +1864,15 @@ def unwrapped_base_add_so_calculation(base,quote):
try:
symbol = f"{base}/{quote}"
for instance in running_traders:
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"})
amount_of_orders = instance.base_add_calculation(free_base)
return jsonify({"Amount": amount_of_orders, "Free base on exchange": free_base})
return jsonify({"Error": "Can't find the pair in the running instances"})
with traders_lock:
for instance in running_traders:
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"})
amount_of_orders = instance.base_add_calculation(free_base)
return jsonify({"Amount": amount_of_orders, "Free base on exchange": free_base})
return jsonify({"Error": "Can't find the pair in the running instances"})
except Exception as e:
broker.logger.log_this(f"{e}",2,symbol)
return jsonify({"Error": "Error in unwrapped_base_add_so_calculation"})
@ -1865,9 +1893,10 @@ def unwrapped_mod_tp_level(base,quote,amount):
try:
symbol = f"{base}/{quote}"
for instance in running_traders:
if symbol==instance.status.get_pair():
instance.config.set_tp_level(float(amount))
with traders_lock:
for instance in running_traders:
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"})
except Exception:
@ -1890,10 +1919,11 @@ def unwrapped_mod_order_size(base,quote,amount):
try:
symbol = f"{base}/{quote}"
for instance in running_traders:
if symbol==instance.status.get_pair():
instance.config.set_order_size(float(amount))
instance.config.save_to_file()
with traders_lock:
for instance in running_traders:
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)
return jsonify({"Success": "Success. The change will take effect when the next deal is started"})
except Exception:
@ -1916,10 +1946,11 @@ def unwrapped_mod_concurrent_safety_orders(base,quote,amount):
try:
symbol = f"{base}/{quote}"
for instance in running_traders:
if symbol==instance.status.get_pair():
instance.config.set_concurrent_safety_orders(int(amount))
instance.config.save_to_file()
with traders_lock:
for instance in running_traders:
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)
return jsonify({"Success": "Success. The change will take effect as new safety orders are sent or filled"})
except Exception:
@ -1942,10 +1973,11 @@ def unwrapped_mod_boosted_concurrent_safety_orders(base,quote,amount):
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()
with traders_lock:
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:
@ -1983,13 +2015,13 @@ def unwrapped_mod_global_tp_level(amount):
Returns:
jsonify: A jsonified dictionary detailing the outcome of the operation
'''
for instance in running_traders:
try:
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)
except Exception:
broker.logger.log_this("Error changing percentage. Ignoring.",2)
with traders_lock:
for instance in running_traders:
try:
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)
except Exception:
broker.logger.log_this("Error changing percentage. Ignoring.",2)
return jsonify({"Success": "Success. The change will take effect when the next TP order is placed"})
@ -2007,16 +2039,17 @@ def unwrapped_last_call(base,quote):
try:
symbol = f"{base}/{quote}"
for instance in running_traders:
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():
instance.config.set_autoswitch(False)
with traders_lock:
for instance in running_traders:
if symbol==instance.status.get_pair():
instance.status.set_stop_when_profit(not instance.status.get_stop_when_profit())
instance.update_status(True)
return jsonify({"Success": "Trader scheduled to go offline when profit is reached"})
return jsonify({"Success": "Last call cancelled"})
return jsonify({"Error": "Trader does not exist"})
if instance.status.get_stop_when_profit():
instance.config.set_autoswitch(False)
instance.update_status(True)
return jsonify({"Success": "Trader scheduled to go offline when profit is reached"})
return jsonify({"Success": "Last call cancelled"})
return jsonify({"Error": "Trader does not exist"})
except Exception:
return jsonify({"Error": "Halp"})
@ -2041,12 +2074,13 @@ def unwrapped_deferred_last_call(base,quote,yyyymmdd):
limit = time_to_unix(year,month,day)
if limit==0:
return jsonify({"Error": "Can't convert date to unix"})
for instance in running_traders:
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
instance.broker.rewrite_config_file()
with traders_lock:
for instance in running_traders:
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
instance.broker.rewrite_config_file()
return jsonify({"Success": f"Trader scheduled to go offline when profit is reached after {limit}"})
except Exception:
return jsonify({"Error": "Halp"})
@ -2068,13 +2102,14 @@ def unwrapped_toggle_pause(base,quote):
try:
symbol = f"{base}/{quote}"
toggle_pauses.append(symbol)
for instance in running_traders:
if instance.status.get_pair()==symbol:
if instance.pause:
instance.status.set_pause_reason("")
return jsonify({"Success": "Trader will be resumed"})
instance.status.set_pause_reason("User requested pause")
return jsonify({"Success": "Trader will be paused"})
with traders_lock:
for instance in running_traders:
if instance.status.get_pair()==symbol:
if instance.pause:
instance.status.set_pause_reason("")
return jsonify({"Success": "Trader will be resumed"})
instance.status.set_pause_reason("User requested pause")
return jsonify({"Success": "Trader will be paused"})
return jsonify({"Error": "Trader does not exist"})
except Exception:
return jsonify({"Error": "Halp"})
@ -2088,10 +2123,11 @@ def unwrapped_global_last_call():
jsonify: A jsonified dictionary detailing the outcome of the operation.
'''
try:
for instance in running_traders:
instance.status.set_stop_when_profit(True)
instance.config.set_autoswitch(False)
broker.logger.log_this("Modified flag",2,f"{instance.base}/{instance.quote}")
with traders_lock:
for instance in running_traders:
instance.status.set_stop_when_profit(True)
instance.config.set_autoswitch(False)
broker.logger.log_this("Modified flag",2,f"{instance.base}/{instance.quote}")
return jsonify({"Success": "All traders scheduled to go offline when profit is reached"})
except Exception:
return jsonify({"Error": "Halp"})
@ -2105,9 +2141,10 @@ def unwrapped_cancel_global_last_call():
jsonify: A jsonified dictionary detailing the outcome of the operation.
'''
try:
for instance in running_traders:
instance.status.set_stop_when_profit(False)
broker.logger.log_this("Modified flag",2,f"{instance.base}/{instance.quote}")
with traders_lock:
for instance in running_traders:
instance.status.set_stop_when_profit(False)
broker.logger.log_this("Modified flag",2,f"{instance.base}/{instance.quote}")
return jsonify({"Success": "Last call canceled"})
except Exception:
return jsonify({"Error": "Halp"})
@ -2126,58 +2163,58 @@ def unwrapped_add_quote(base,quote,amount):
Returns:
json: A jsonified dictionary detailing the outcome of the operation.
'''
for instance in running_traders:
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.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.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())
with traders_lock:
for instance in running_traders:
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.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.set_pause(False)
return jsonify({"Error": "Problems sending the order"})
elif returned_order["status"]=="expired":
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())
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.status.get_pair())
attempts = 5
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.status.get_pair())
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()))
instance.update_status(True)
break
else:
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())
return jsonify({"Error": "Market order returned None"})
while True:
time.sleep(broker.get_wait_time())
instance.set_pause(False)
broker.logger.log_this("Done",2,instance.status.get_pair())
return jsonify({"Success": "Quote added successfully"})
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.set_pause(False)
return jsonify({"Error": "Problems sending the order"})
elif returned_order["status"]=="expired":
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())
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.status.get_pair())
attempts = 5
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.status.get_pair())
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()))
instance.update_status(True)
break
else:
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.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"})
@ -2213,12 +2250,13 @@ def unwrapped_toggle_cleanup(base,quote):
try:
symbol = f"{base}/{quote}"
for instance in running_traders:
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"})
return jsonify({"Success": "Cleanup turned OFF"})
with traders_lock:
for instance in running_traders:
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"})
return jsonify({"Success": "Cleanup turned OFF"})
except Exception as e:
broker.logger.log_this(f"Exception while toggling cleanup: {e}",1,symbol)
return jsonify({"Error": "Halp"})
@ -2239,13 +2277,14 @@ def unwrapped_force_trader_close(base,quote):
try:
symbol = f"{base}/{quote}"
for instance in running_traders:
if symbol==instance.status.get_pair():
outcome = instance.force_close()
if outcome==0:
return jsonify({"Success": "Trader closed position successfully"})
return jsonify({"Error": "Error while forcing trader to close position"})
return jsonify({"Error": "Trader not found"})
with traders_lock:
for instance in running_traders:
if symbol==instance.status.get_pair():
outcome = instance.force_close()
if outcome==0:
return jsonify({"Success": "Trader closed position successfully"})
return jsonify({"Error": "Error while forcing trader to close position"})
return jsonify({"Error": "Trader not found"})
except Exception as e:
broker.logger.log_this(f"Exception while forcing trader to close position: {e}",1,symbol)
return jsonify({"Error": "Halp"})
@ -2265,16 +2304,17 @@ def unwrapped_toggle_autoswitch(base,quote):
try:
symbol = f"{base}/{quote}"
for instance in running_traders:
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)
return jsonify({"Success": "Autoswitch is now OFF"})
else:
broker.logger.log_this("Autoswitch turned ON",1,symbol)
instance.config.set_autoswitch(True)
return jsonify({"Success": "Autoswitch is now ON"})
with traders_lock:
for instance in running_traders:
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)
return jsonify({"Success": "Autoswitch is now OFF"})
else:
broker.logger.log_this("Autoswitch turned ON",1,symbol)
instance.config.set_autoswitch(True)
return jsonify({"Success": "Autoswitch is now ON"})
return jsonify({"Error": "Trader not running"})
except Exception as e:
broker.logger.log_this(f"Exception while toggling autoswitch: {e}",1,symbol)
@ -2295,16 +2335,17 @@ def unwrapped_toggle_liquidate_after_switch(base,quote):
try:
symbol = f"{base}/{quote}"
for instance in running_traders:
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)
return jsonify({"Success": "Liquidate after switch is now OFF"})
else:
broker.logger.log_this("Liquidate after switch turned ON",1,symbol)
instance.config.set_liquidate_after_switch(True)
return jsonify({"Success": "Liquidate after switch is now ON"})
with traders_lock:
for instance in running_traders:
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)
return jsonify({"Success": "Liquidate after switch is now OFF"})
else:
broker.logger.log_this("Liquidate after switch turned ON",1,symbol)
instance.config.set_liquidate_after_switch(True)
return jsonify({"Success": "Liquidate after switch is now ON"})
return jsonify({"Error": "Trader not running"})
except Exception as e:
broker.logger.log_this(f"Exception while toggling liquidate after switch: {e}",1,symbol)
@ -2325,16 +2366,17 @@ def unwrapped_toggle_check_old_long_price(base,quote):
try:
symbol = f"{base}/{quote}"
for instance in running_traders:
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)
return jsonify({"Success": "Old long price check turned OFF"})
else:
broker.logger.log_this("Check ON",1,symbol)
instance.config.set_check_old_long_price(True)
return jsonify({"Success": "Old long price check turned ON"})
with traders_lock:
for instance in running_traders:
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)
return jsonify({"Success": "Old long price check turned OFF"})
else:
broker.logger.log_this("Check ON",1,symbol)
instance.config.set_check_old_long_price(True)
return jsonify({"Success": "Old long price check turned ON"})
return jsonify({"Error": "Trader not running"})
except Exception as e:
broker.logger.log_this(f"Exception while toggling check_old_long_price: {e}",1,symbol)
@ -2439,7 +2481,8 @@ def unwrapped_trader_time():
'''
try:
return jsonify({"Time": max(instance.last_time_seen for instance in running_traders)})
with traders_lock:
return jsonify({"Time": max(instance.last_time_seen for instance in running_traders)})
except Exception as e:
broker.logger.log_this(f"Exception while retrieving trader_time: {e}",1)
return jsonify({"Error": str(e)})
@ -2634,6 +2677,7 @@ if __name__=="__main__":
broker = exchange_wrapper.Broker(exchange,read_config,argv[1]) #Also passes the config filename
#Declaring some variables
traders_lock = Lock()
running_traders = []
instances_to_add = []
online_pairs = []

View File

@ -560,8 +560,6 @@ class trader:
self.broker.logger.log_this("Can't cancel the take profit order. Can't switch mode",1,self.status.get_pair())
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())
#Save the old take profit order info for later use
self.broker.logger.log_this("Saving state in status_dict",2,self.status.get_pair())
@ -1307,7 +1305,8 @@ class trader:
if len(self.config.get_tp_table())>=order_index:
tp_level = self.config.get_tp_table()[order_index] #Custom percentage table
tp_level = self.config.get_tp_table()[-1]
tp_level = self.config.get_tp_level()
else:
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.status.get_no_of_safety_orders())
tp_level = profit_table[-1]