Compare commits
No commits in common. "ab77840bea4c932fe460a773ff3583c180daf494" and "56cbf511296d93275fd9b590f2a5a321ebc8b893" have entirely different histories.
ab77840bea
...
56cbf51129
|
|
@ -1,9 +1,5 @@
|
||||||
2025.09.04:
|
2025.09.01:
|
||||||
. Fixed bug in unwrapped_last_call().
|
. Fixed bug in unwrapped_last_call().
|
||||||
. Now the trader supports multiple safety orders at the same time.
|
|
||||||
. Removed forcing orders when importing a trader. Maybe it will be reinstated at a later date.
|
|
||||||
. Removed endpoint /reload_safety_orders.
|
|
||||||
. New endpoints: /mod_concurrent_safety orders, /mod_boosted_concurrent_safety_orders and /force_trader_close.
|
|
||||||
|
|
||||||
2025.08.19:
|
2025.08.19:
|
||||||
. Improved log trimming.
|
. Improved log trimming.
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,6 @@ class ConfigHandler:
|
||||||
"order_size": self.broker.get_default_order_size(),
|
"order_size": self.broker.get_default_order_size(),
|
||||||
"no_of_safety_orders": 30,
|
"no_of_safety_orders": 30,
|
||||||
"max_short_safety_orders": 45,
|
"max_short_safety_orders": 45,
|
||||||
"concurrent_safety_orders": 3,
|
|
||||||
"boosted_concurrent_safety_orders": 5,
|
|
||||||
"safety_order_deviance": 2,
|
"safety_order_deviance": 2,
|
||||||
"safety_order_scale": 0.0105,
|
"safety_order_scale": 0.0105,
|
||||||
"dynamic_so_deviance": True,
|
"dynamic_so_deviance": True,
|
||||||
|
|
@ -37,9 +35,6 @@ class ConfigHandler:
|
||||||
"force_restart_if_retries_exhausted": False,
|
"force_restart_if_retries_exhausted": False,
|
||||||
"check_old_long_price": False #switch_to_short should flip this to True unless stated
|
"check_old_long_price": False #switch_to_short should flip this to True unless stated
|
||||||
}
|
}
|
||||||
# if self.broker.get_exchange_name()=="kucoin":
|
|
||||||
# self.default_config_dictionary["concurrent_safety_orders"]=1
|
|
||||||
# self.default_config_dictionary["boosted_concurrent_safety_orders"]=1
|
|
||||||
self.config_file_path = f"configs/{pair.split('/')[0]}{pair.split('/')[1]}.json"
|
self.config_file_path = f"configs/{pair.split('/')[0]}{pair.split('/')[1]}.json"
|
||||||
self.config_dictionary = self.default_config_dictionary.copy()
|
self.config_dictionary = self.default_config_dictionary.copy()
|
||||||
|
|
||||||
|
|
@ -73,12 +68,6 @@ class ConfigHandler:
|
||||||
def get_max_short_safety_orders(self):
|
def get_max_short_safety_orders(self):
|
||||||
return self.config_dictionary["max_short_safety_orders"]
|
return self.config_dictionary["max_short_safety_orders"]
|
||||||
|
|
||||||
def get_concurrent_safety_orders(self):
|
|
||||||
return self.config_dictionary["concurrent_safety_orders"]
|
|
||||||
|
|
||||||
def get_boosted_concurrent_safety_orders(self):
|
|
||||||
return self.config_dictionary["boosted_concurrent_safety_orders"]
|
|
||||||
|
|
||||||
def get_safety_order_deviance(self):
|
def get_safety_order_deviance(self):
|
||||||
return self.config_dictionary["safety_order_deviance"]
|
return self.config_dictionary["safety_order_deviance"]
|
||||||
|
|
||||||
|
|
@ -184,20 +173,6 @@ class ConfigHandler:
|
||||||
self.config_dictionary["max_short_safety_orders"] = max_short_safety_orders
|
self.config_dictionary["max_short_safety_orders"] = max_short_safety_orders
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def set_concurrent_safety_orders(self, concurrent_safety_orders: int):
|
|
||||||
# if not isinstance(concurrent_safety_orders, int):
|
|
||||||
# self.broker.logger.log_this(f"Max concurrent safety orders provided is not an integer",1,self.get_pair())
|
|
||||||
# return 1
|
|
||||||
self.config_dictionary["concurrent_safety_orders"] = concurrent_safety_orders
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def set_boosted_concurrent_safety_orders(self, boosted_concurrent_safety_orders: int):
|
|
||||||
# if not isinstance(concurrent_safety_orders, int):
|
|
||||||
# self.broker.logger.log_this(f"Max concurrent safety orders provided is not an integer",1,self.get_pair())
|
|
||||||
# return 1
|
|
||||||
self.config_dictionary["boosted_concurrent_safety_orders"] = boosted_concurrent_safety_orders
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def set_safety_order_deviance(self, safety_order_deviance: int):
|
def set_safety_order_deviance(self, safety_order_deviance: int):
|
||||||
# if not isinstance(safety_order_deviance, int):
|
# if not isinstance(safety_order_deviance, int):
|
||||||
# self.broker.logger.log_this(f"Safety order deviance provided is not an integer",1,self.get_pair())
|
# self.broker.logger.log_this(f"Safety order deviance provided is not an integer",1,self.get_pair())
|
||||||
|
|
|
||||||
|
|
@ -379,11 +379,6 @@ class Broker:
|
||||||
if self.get_exchange_name()=="binance":
|
if self.get_exchange_name()=="binance":
|
||||||
a = self.exchange.fetch_last_prices(pair_list)
|
a = self.exchange.fetch_last_prices(pair_list)
|
||||||
return {x: a[x]["price"] for x in a.keys()}
|
return {x: a[x]["price"] for x in a.keys()}
|
||||||
elif self.get_exchange_name()=="kucoin":
|
|
||||||
a = self.exchange.fetch_tickers(pair_list)
|
|
||||||
if pair_list is None:
|
|
||||||
return {x: a[x]["close"] for x in a.keys()}
|
|
||||||
return {x: a[x]["close"] for x in a.keys() if x in pair_list}
|
|
||||||
else:
|
else:
|
||||||
a = self.exchange.fetch_tickers()
|
a = self.exchange.fetch_tickers()
|
||||||
if pair_list is None:
|
if pair_list is None:
|
||||||
|
|
@ -538,7 +533,7 @@ class Broker:
|
||||||
if pairs is None:
|
if pairs is None:
|
||||||
pairs = []
|
pairs = []
|
||||||
try:
|
try:
|
||||||
if self.get_exchange_name()in ["binance","kucoin"]:
|
if self.get_exchange_name()=="binance":
|
||||||
return self.get_opened_orders_binance(pairs)
|
return self.get_opened_orders_binance(pairs)
|
||||||
return self.get_opened_orders()
|
return self.get_opened_orders()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
@ -586,7 +581,7 @@ class Broker:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
def get_closed_orders(self,pair=None,no_retries=False): #It should return a list of all opened orders
|
def get_closed_orders(self,no_retries=False): #It should return a list of all opened orders
|
||||||
'''
|
'''
|
||||||
Returns a list of all the open orders on the exchange
|
Returns a list of all the open orders on the exchange
|
||||||
|
|
||||||
|
|
@ -597,7 +592,7 @@ class Broker:
|
||||||
retries = self.retries
|
retries = self.retries
|
||||||
while retries>0:
|
while retries>0:
|
||||||
try:
|
try:
|
||||||
return self.exchange.fetch_closed_orders(pair)
|
return self.exchange.fetch_closed_orders()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.log_this(f"Exception in get_closed_orders: {e}",1)
|
self.logger.log_this(f"Exception in get_closed_orders: {e}",1)
|
||||||
if no_retries:
|
if no_retries:
|
||||||
|
|
@ -659,15 +654,16 @@ class Broker:
|
||||||
:return: 0 if order was succesfully canceled, 1 if not
|
:return: 0 if order was succesfully canceled, 1 if not
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
pair = symbol
|
||||||
tries = self.retries//2
|
tries = self.retries//2
|
||||||
while tries>0:
|
while tries>0:
|
||||||
try:
|
try:
|
||||||
while self.get_order(id,symbol)["status"]=="open":
|
while self.get_order(id,pair)["status"]=="open":
|
||||||
self.exchange.cancel_order(id,symbol)
|
self.exchange.cancel_order(id,symbol=pair)
|
||||||
time.sleep(self.wait_time)
|
time.sleep(self.wait_time)
|
||||||
return 0
|
return 0
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if self.get_order(id,symbol)["status"]=="canceled":
|
if self.get_order(id,pair)["status"]=="canceled":
|
||||||
return 0
|
return 0
|
||||||
self.logger.log_this(f"Exception in cancel_order: id {id} - exception: {e}",1)
|
self.logger.log_this(f"Exception in cancel_order: id {id} - exception: {e}",1)
|
||||||
if no_retries:
|
if no_retries:
|
||||||
|
|
@ -728,25 +724,26 @@ class Broker:
|
||||||
'''
|
'''
|
||||||
|
|
||||||
retries = self.retries//2
|
retries = self.retries//2
|
||||||
|
pair = symbol
|
||||||
while retries>0:
|
while retries>0:
|
||||||
try:
|
try:
|
||||||
if self.get_exchange_name()=="gateio" and side=="buy" and not amount_in_base:
|
if self.get_exchange_name()=="gateio" and side=="buy" and not amount_in_base:
|
||||||
new_order = self.exchange.create_market_buy_order_with_cost(symbol, size)
|
new_order = self.exchange.create_market_buy_order_with_cost(pair, size)
|
||||||
else:
|
else:
|
||||||
order_book = self.get_order_book(symbol)
|
order_book = self.get_order_book(symbol)
|
||||||
if order_book=={}:
|
if order_book=={}:
|
||||||
self.logger.log_this(f"new_simulated_market_order. Order book returned an empty dictionary",1,symbol)
|
self.logger.log_this(f"new_simulated_market_order. Order book returned an empty dictionary",1,symbol)
|
||||||
return self.empty_order
|
return self.empty_order
|
||||||
if amount_in_base or side!="buy":
|
if amount_in_base or side!="buy":
|
||||||
base_amount = self.amount_to_precision(symbol,size)
|
base_amount = self.amount_to_precision(pair,size)
|
||||||
else:
|
else:
|
||||||
avg_price = self.average_price_depth(order_book,size,"sell")
|
avg_price = self.average_price_depth(order_book,size,"sell")
|
||||||
base_amount = size/avg_price if avg_price is not None else size/self.get_ticker_price(symbol)
|
base_amount = size/avg_price if avg_price is not None else size/self.get_ticker_price(symbol)
|
||||||
price = self.find_minimum_viable_price(order_book,base_amount,side)
|
price = self.find_minimum_viable_price(order_book,base_amount,side)
|
||||||
#Maybe check for slippage here instead of within the trader itself? idk
|
#Maybe check for slippage here instead of within the trader itself? idk
|
||||||
new_order = self.exchange.create_order(symbol,"limit",side,base_amount,price)
|
new_order = self.exchange.create_order(pair,"limit",side,base_amount,price)
|
||||||
time.sleep(self.wait_time)
|
time.sleep(self.wait_time)
|
||||||
return self.get_order(new_order["id"],symbol)
|
return self.get_order(new_order["id"],pair)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.log_this(f"new_simulated_market_order exception: {e}",1,symbol)
|
self.logger.log_this(f"new_simulated_market_order exception: {e}",1,symbol)
|
||||||
if no_retries:
|
if no_retries:
|
||||||
|
|
@ -805,23 +802,24 @@ class Broker:
|
||||||
if self.broker_config["simulate_market_orders"]:
|
if self.broker_config["simulate_market_orders"]:
|
||||||
return self.new_simulated_market_order(symbol,size,side,amount_in_base=amount_in_base)
|
return self.new_simulated_market_order(symbol,size,side,amount_in_base=amount_in_base)
|
||||||
retries = self.retries
|
retries = self.retries
|
||||||
|
pair = symbol
|
||||||
while retries>0:
|
while retries>0:
|
||||||
try:
|
try:
|
||||||
if side=="buy":
|
if side=="buy":
|
||||||
to_buy = float(size)
|
to_buy = float(size)
|
||||||
if not amount_in_base:
|
if not amount_in_base:
|
||||||
to_buy = float(size)/self.get_top_ask_price(symbol)
|
to_buy = float(size)/self.get_top_ask_price(pair)
|
||||||
amount = self.amount_to_precision(symbol,to_buy)
|
amount = self.amount_to_precision(pair,to_buy)
|
||||||
else:
|
else:
|
||||||
amount = self.amount_to_precision(symbol,size) #Market sell orders are always nominated in base currency
|
amount = self.amount_to_precision(pair,size) #Market sell orders are always nominated in base currency
|
||||||
|
|
||||||
order_to_send = self.exchange.create_order(symbol,"market",side,amount)
|
order_to_send = self.exchange.create_order(pair,"market",side,amount)
|
||||||
time.sleep(self.wait_time)
|
time.sleep(self.wait_time)
|
||||||
# Wait a bit more when dealing with Kucoin
|
# Wait a bit more when dealing with Kucoin
|
||||||
|
|
||||||
return self.get_order(order_to_send["id"],symbol)
|
return self.get_order(order_to_send["id"],pair)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.log_this(f"Exception in new_market_order: {e}",1,symbol)
|
self.logger.log_this(f"Exception in new_market_order: {e}",1,pair)
|
||||||
if no_retries:
|
if no_retries:
|
||||||
break
|
break
|
||||||
time.sleep(self.wait_time)
|
time.sleep(self.wait_time)
|
||||||
|
|
@ -869,40 +867,6 @@ class Broker:
|
||||||
return "the lowest price limit for sell orders is" in str(error_object).lower()
|
return "the lowest price limit for sell orders is" in str(error_object).lower()
|
||||||
|
|
||||||
|
|
||||||
def new_limit_orders(self, orders: list) -> list:
|
|
||||||
sent_orders = []
|
|
||||||
#Send the orders
|
|
||||||
tries = self.retries
|
|
||||||
while tries>=0:
|
|
||||||
try:
|
|
||||||
sent_orders = self.exchange.create_orders(orders)
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.log_this(f"Exception while sending safety orders: {e}",1)
|
|
||||||
tries-=1
|
|
||||||
time.sleep(self.wait_time)
|
|
||||||
if tries==0:
|
|
||||||
return []
|
|
||||||
|
|
||||||
#Retrieve the orders from the exchange by id to confirm that they were sent
|
|
||||||
#Specially for OKX, since the orders that create_orders return are empty (only id is present)
|
|
||||||
returned_orders = []
|
|
||||||
for order in sent_orders:
|
|
||||||
tries = self.retries
|
|
||||||
while tries>=0:
|
|
||||||
try:
|
|
||||||
returned_orders.append(self.get_order(order["id"],order["symbol"]))
|
|
||||||
time.sleep(self.wait_time)
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.log_this(f"Exception while retrieving safety orders: {e}",1)
|
|
||||||
tries-=1
|
|
||||||
if tries==0:
|
|
||||||
if self.get_exchange_name()=="okex":
|
|
||||||
return returned_orders
|
|
||||||
returned_orders.append(order) #In the case of the other exchanges, we just assume that the order was sent and append it.
|
|
||||||
time.sleep(self.wait_time)
|
|
||||||
return returned_orders
|
|
||||||
|
|
||||||
|
|
||||||
def new_limit_order(self,symbol,size,side,price,no_retries=False):
|
def new_limit_order(self,symbol,size,side,price,no_retries=False):
|
||||||
'''
|
'''
|
||||||
Sends a new limit order.
|
Sends a new limit order.
|
||||||
|
|
@ -915,13 +879,20 @@ class Broker:
|
||||||
'''
|
'''
|
||||||
|
|
||||||
tries = self.retries
|
tries = self.retries
|
||||||
|
pair = symbol
|
||||||
while tries>=0:
|
while tries>=0:
|
||||||
try:
|
try:
|
||||||
order_to_send = self.exchange.create_order(symbol,"limit",side,self.amount_to_precision(symbol,size),price)
|
order_to_send = self.exchange.create_order(pair,"limit",side,self.amount_to_precision(pair,size),price)
|
||||||
time.sleep(self.wait_time)
|
time.sleep(self.wait_time)
|
||||||
return self.get_order(order_to_send["id"],symbol)
|
return self.get_order(order_to_send["id"],pair)
|
||||||
|
#if order_to_send["amount"] is not None: # Because Kucoin etc etc
|
||||||
|
# return self.get_order(order_to_send["id"],pair) #
|
||||||
|
#self.logger.log_this(f"Error sending order: Null order returned",2,pair) #
|
||||||
|
#self.cancel_order(order_to_send["id"],symbol,no_retries=True) #
|
||||||
|
#retries-=1
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.log_this(f"Exception in new_limit_order - Side: {side} - Size: {size} - {self.amount_to_precision(symbol,size)} - Exception: {e}",1,symbol)
|
self.logger.log_this(f"Exception in new_limit_order - Side: {side} - Size: {size} - {self.amount_to_precision(pair,size)} - Exception: {e}",1,symbol)
|
||||||
if self.not_enough_balance_error(e):
|
if self.not_enough_balance_error(e):
|
||||||
if tries<=self.retries//2: #Halves the amount of retries if there is a balance error.
|
if tries<=self.retries//2: #Halves the amount of retries if there is a balance error.
|
||||||
return 1
|
return 1
|
||||||
|
|
@ -952,9 +923,10 @@ class Broker:
|
||||||
if id=="":
|
if id=="":
|
||||||
return self.empty_order
|
return self.empty_order
|
||||||
tries = self.retries
|
tries = self.retries
|
||||||
|
pair = symbol
|
||||||
while tries>0:
|
while tries>0:
|
||||||
try:
|
try:
|
||||||
return self.exchange.fetch_order(id,symbol)
|
return self.exchange.fetch_order(id,symbol=pair)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.log_this(f"Exception in get_order: {e}",1,symbol)
|
self.logger.log_this(f"Exception in get_order: {e}",1,symbol)
|
||||||
if no_retries:
|
if no_retries:
|
||||||
|
|
@ -972,9 +944,10 @@ class Broker:
|
||||||
:return: The market information.
|
:return: The market information.
|
||||||
'''
|
'''
|
||||||
tries = self.retries
|
tries = self.retries
|
||||||
|
pair = symbol
|
||||||
while tries>0:
|
while tries>0:
|
||||||
try:
|
try:
|
||||||
return self.exchange.market(symbol)
|
return self.exchange.market(pair)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.log_this(f"Exception in fetch_market: {e}",1,symbol)
|
self.logger.log_this(f"Exception in fetch_market: {e}",1,symbol)
|
||||||
if no_retries:
|
if no_retries:
|
||||||
|
|
@ -992,9 +965,10 @@ class Broker:
|
||||||
:return: The ticker information.
|
:return: The ticker information.
|
||||||
'''
|
'''
|
||||||
tries = self.retries
|
tries = self.retries
|
||||||
|
pair = symbol
|
||||||
while tries>0:
|
while tries>0:
|
||||||
try:
|
try:
|
||||||
return self.exchange.fetch_ticker(symbol)
|
return self.exchange.fetch_ticker(pair)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.log_this(f"Exception in get_ticker: {e}")
|
self.logger.log_this(f"Exception in get_ticker: {e}")
|
||||||
if no_retries:
|
if no_retries:
|
||||||
|
|
@ -1058,8 +1032,8 @@ class Broker:
|
||||||
|
|
||||||
:param pair: pair
|
:param pair: pair
|
||||||
:return: step size
|
:return: step size
|
||||||
'''
|
|
||||||
|
|
||||||
|
'''
|
||||||
market = self.fetch_market(pair)
|
market = self.fetch_market(pair)
|
||||||
if market is None:
|
if market is None:
|
||||||
return None
|
return None
|
||||||
|
|
@ -1159,14 +1133,22 @@ class Logger:
|
||||||
|
|
||||||
#Append to log list
|
#Append to log list
|
||||||
self.log_list.append(text)
|
self.log_list.append(text)
|
||||||
|
|
||||||
|
#Trim log list
|
||||||
|
#self.log_list = self.log_list[-self.log_list_max_length:]
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Can't write log file")
|
print("Can't write log file")
|
||||||
print(e)
|
print(e)
|
||||||
|
|
||||||
if level<1:
|
if level<1:
|
||||||
self.send_tg_message(f"{self.broker_config['exchange'].capitalize()} | {pair_data}{message}",ignore_config=level==-1)
|
self.send_tg_message(f"{self.broker_config['exchange'].capitalize()} | {pair_data}{message}",ignore_config=level==-1)
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
class Order:
|
||||||
|
def __init__(self, order: dict = {}):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
350
main.py
350
main.py
|
|
@ -18,7 +18,7 @@ import exchange_wrapper
|
||||||
import trader
|
import trader
|
||||||
|
|
||||||
|
|
||||||
version = "2025.09.04"
|
version = "2025.09.01"
|
||||||
|
|
||||||
'''
|
'''
|
||||||
Color definitions. If you want to change them, check the reference at https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
|
Color definitions. If you want to change them, check the reference at https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
|
||||||
|
|
@ -39,7 +39,6 @@ worker_threads_overprovisioning = 3 #Number of worker threads to create over
|
||||||
#Only use 0 if you are sure that you won't be adding any.
|
#Only use 0 if you are sure that you won't be adding any.
|
||||||
executor = None
|
executor = None
|
||||||
|
|
||||||
#Shutdown handler
|
|
||||||
def shutdown_handler(signum, _):
|
def shutdown_handler(signum, _):
|
||||||
broker.logger.log_this(f"Received signal {signum}, shutting down as gracefully as possible...", 2)
|
broker.logger.log_this(f"Received signal {signum}, shutting down as gracefully as possible...", 2)
|
||||||
if executor:
|
if executor:
|
||||||
|
|
@ -87,7 +86,7 @@ def time_to_unix(year: str, month: str, day: str) -> int:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def import_instance(base: str, quote: str) -> int:
|
def import_instance(base: str, quote: str, forced_tp_id = None, forced_so_id = None) -> int:
|
||||||
'''
|
'''
|
||||||
Imports an previously running trader instance from the status file.
|
Imports an previously running trader instance from the status file.
|
||||||
|
|
||||||
|
|
@ -99,7 +98,7 @@ def import_instance(base: str, quote: str) -> int:
|
||||||
int: 0 if successful
|
int: 0 if successful
|
||||||
'''
|
'''
|
||||||
broker.logger.log_this(f"Importing {base}/{quote}")
|
broker.logger.log_this(f"Importing {base}/{quote}")
|
||||||
instances_to_add.append(trader.trader(broker,f"{base}/{quote}",is_import=True))
|
instances_to_add.append(trader.trader(broker,f"{base}/{quote}",is_import=True,forced_tp_id=forced_tp_id,forced_so_id=forced_so_id))
|
||||||
if f"{base}{quote}" not in tickers:
|
if f"{base}{quote}" not in tickers:
|
||||||
tickers.append(f"{base}{quote}")
|
tickers.append(f"{base}{quote}")
|
||||||
return 0
|
return 0
|
||||||
|
|
@ -248,7 +247,7 @@ def restart_pair_no_json(base: str, quote: str) -> int:
|
||||||
symbol = f"{base}/{quote}"
|
symbol = f"{base}/{quote}"
|
||||||
order_list = broker.fetch_full_orders(tickers)
|
order_list = broker.fetch_full_orders(tickers)
|
||||||
for instance in running_traders:
|
for instance in running_traders:
|
||||||
if symbol==instance.status.get_pair():
|
if symbol==instance.config.get_pair():
|
||||||
instance.pause = True
|
instance.pause = True
|
||||||
#Backing up old status file
|
#Backing up old status file
|
||||||
instance.status.save_to_file(is_backup=True)
|
instance.status.save_to_file(is_backup=True)
|
||||||
|
|
@ -263,7 +262,7 @@ def restart_pair_no_json(base: str, quote: str) -> int:
|
||||||
try:
|
try:
|
||||||
running_traders.remove(instance)
|
running_traders.remove(instance)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
broker.logger.log_this(f"Instance {instance.status.get_pair()} not found in running_traders.",1,instance.status.get_pair())
|
broker.logger.log_this(f"Instance {instance.config.get_pair()} not found in running_traders.",1,instance.config.get_pair())
|
||||||
add_instance(base,quote)
|
add_instance(base,quote)
|
||||||
return 0
|
return 0
|
||||||
return 1
|
return 1
|
||||||
|
|
@ -277,7 +276,7 @@ def main_routine():
|
||||||
global reload_interval
|
global reload_interval
|
||||||
global screen_buffer
|
global screen_buffer
|
||||||
|
|
||||||
executor = ThreadPoolExecutor(max_workers=len(broker.get_config()["pairs"])+worker_threads_overprovisioning)
|
executor = ThreadPoolExecutor(max_workers=len(running_traders)+worker_threads_overprovisioning)
|
||||||
is_testnet = "TESTNET " if broker.get_config()["is_sandbox"] else ""
|
is_testnet = "TESTNET " if broker.get_config()["is_sandbox"] else ""
|
||||||
exchange_version_label = f"{bright_white}{broker.get_config()['exchange'].upper()} {is_testnet}{white}| DCAv2 {version} | CCXT v{ccxt.__version__}"
|
exchange_version_label = f"{bright_white}{broker.get_config()['exchange'].upper()} {is_testnet}{white}| DCAv2 {version} | CCXT v{ccxt.__version__}"
|
||||||
separator_line = blue + "="*80 + white
|
separator_line = blue + "="*80 + white
|
||||||
|
|
@ -286,12 +285,12 @@ def main_routine():
|
||||||
#Restart traders that have the restart flag raised and remove traders that have the quit flag raised
|
#Restart traders that have the restart flag raised and remove traders that have the quit flag raised
|
||||||
for instance in running_traders:
|
for instance in running_traders:
|
||||||
if instance.restart and instance.config.get_attempt_restart():
|
if instance.restart and instance.config.get_attempt_restart():
|
||||||
broker.logger.log_this(f"Restarting trader",1,instance.status.get_pair())
|
broker.logger.log_this(f"Restarting trader",1,instance.config.get_pair())
|
||||||
restart_pair_no_json(instance.base,instance.quote)
|
restart_pair_no_json(instance.base,instance.quote)
|
||||||
if instance.quit:
|
if instance.quit:
|
||||||
#Here, check if a duster is needed
|
#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.",0,instance.config.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
|
broker.logger.log_this(f"{broker.get_exchange_name()} | Quit flag raised, removing trader: {instance.config.get_pair()}",-1) #Forced message to TG
|
||||||
if f"{instance.base}{instance.quote}" in tickers:
|
if f"{instance.base}{instance.quote}" in tickers:
|
||||||
tickers.remove(f"{instance.base}{instance.quote}")
|
tickers.remove(f"{instance.base}{instance.quote}")
|
||||||
broker.remove_pair_from_config(f"{instance.base}{instance.quote}")
|
broker.remove_pair_from_config(f"{instance.base}{instance.quote}")
|
||||||
|
|
@ -299,7 +298,7 @@ def main_routine():
|
||||||
try:
|
try:
|
||||||
running_traders.remove(instance)
|
running_traders.remove(instance)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
broker.logger.log_this(f"Instance {instance.status.get_pair()} not found in running_traders.",1,instance.status.get_pair())
|
broker.logger.log_this(f"Instance {instance.config.get_pair()} not found in running_traders.",1,instance.config.get_pair())
|
||||||
|
|
||||||
#Adds pending traders
|
#Adds pending traders
|
||||||
if bool(instances_to_add):
|
if bool(instances_to_add):
|
||||||
|
|
@ -311,21 +310,21 @@ def main_routine():
|
||||||
futures = []
|
futures = []
|
||||||
pairs_to_fetch = []
|
pairs_to_fetch = []
|
||||||
online_pairs = []
|
online_pairs = []
|
||||||
|
open_orders = broker.fetch_open_orders(tickers)
|
||||||
for instance in running_traders:
|
|
||||||
pairs_to_fetch.append(instance.status.get_pair())
|
|
||||||
|
|
||||||
open_orders = broker.fetch_open_orders(pairs_to_fetch)
|
|
||||||
|
|
||||||
for instance in running_traders:
|
for instance in running_traders:
|
||||||
future = executor.submit(instance.check_status, open_orders)
|
future = executor.submit(instance.check_status, open_orders)
|
||||||
futures.append(future)
|
futures.append(future)
|
||||||
online_pairs.append(f"{instance.base}{instance.quote}")
|
online_pairs.append(f"{instance.base}{instance.quote}")
|
||||||
|
pairs_to_fetch.append(instance.config.get_pair())
|
||||||
|
|
||||||
|
#Delete no longer used data
|
||||||
|
del open_orders
|
||||||
|
|
||||||
#Fetch prices
|
#Fetch prices
|
||||||
price_list = broker.get_prices(pairs_to_fetch)
|
price_list = broker.get_prices(pairs_to_fetch)
|
||||||
#Here, assign the prices to the dusters (if any)
|
#Here, assign the prices to the dusters (if any)
|
||||||
|
|
||||||
|
|
||||||
for future in as_completed(futures):
|
for future in as_completed(futures):
|
||||||
try:
|
try:
|
||||||
future.result()
|
future.result()
|
||||||
|
|
@ -340,21 +339,21 @@ def main_routine():
|
||||||
global_status["paused_traders"].clear()
|
global_status["paused_traders"].clear()
|
||||||
for instance in running_traders:
|
for instance in running_traders:
|
||||||
if not instance.config.get_is_short():
|
if not instance.config.get_is_short():
|
||||||
curr += int(instance.status.get_so_amount()) # For the safety order occupancy percentage calculation
|
curr += int(instance.get_status_dict()["so_amount"]) # For the safety order occupancy percentage calculation
|
||||||
top += int(instance.config.get_no_of_safety_orders())
|
top += int(instance.config.get_no_of_safety_orders()) # It shows the percentage of safety orders not filled
|
||||||
if "status_string" in instance.get_status_dict():
|
if "status_string" in instance.get_status_dict(): # status_strings
|
||||||
long_traders_status_strings.append(str(instance))
|
long_traders_status_strings.append(str(instance))
|
||||||
elif "status_string" in instance.get_status_dict():
|
elif "status_string" in instance.get_status_dict():
|
||||||
short_traders_status_strings.append(str(instance))
|
short_traders_status_strings.append(str(instance))
|
||||||
try:
|
try:
|
||||||
if instance.status.get_pair() in price_list and price_list[instance.status.get_pair()] is not None:
|
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.status.get_pair()]
|
instance.get_status_dict()["price"] = price_list[instance.config.get_pair()]
|
||||||
except Exception as e:
|
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())
|
broker.logger.log_this(f"Exception while querying for pair price, key not present on price_list dictionary: {e}",1,instance.config.get_pair())
|
||||||
|
|
||||||
#Add paused traders to the paused trader list
|
#Add paused traders to the paused trader list
|
||||||
if instance.pause:
|
if instance.pause:
|
||||||
global_status["paused_traders"].append(instance.status.get_pair())
|
global_status["paused_traders"].append(instance.config.get_pair())
|
||||||
paused_traders_status_strings.append(f"{cyan}Paused pairs: {list(global_status['paused_traders'])}{white}")
|
paused_traders_status_strings.append(f"{cyan}Paused pairs: {list(global_status['paused_traders'])}{white}")
|
||||||
|
|
||||||
#Delete no longer used data
|
#Delete no longer used data
|
||||||
|
|
@ -369,7 +368,7 @@ def main_routine():
|
||||||
|
|
||||||
#Updates some global status variables prior to deletion of those
|
#Updates some global status variables prior to deletion of those
|
||||||
if len(running_traders)!=len(global_status["online_workers"]):
|
if len(running_traders)!=len(global_status["online_workers"]):
|
||||||
global_status["online_workers"] = [instance.status.get_pair() for instance in running_traders]
|
global_status["online_workers"] = [instance.config.get_pair() for instance in running_traders]
|
||||||
|
|
||||||
#Prints general info
|
#Prints general info
|
||||||
instance_uptime = int(time.time()) - instance_start_time
|
instance_uptime = int(time.time()) - instance_start_time
|
||||||
|
|
@ -405,7 +404,7 @@ def main_routine():
|
||||||
#Toggle pauses
|
#Toggle pauses
|
||||||
if toggle_pauses:
|
if toggle_pauses:
|
||||||
for instance in running_traders:
|
for instance in running_traders:
|
||||||
if instance.status.get_pair() in toggle_pauses:
|
if instance.config.get_pair() in toggle_pauses:
|
||||||
instance.pause = not instance.pause
|
instance.pause = not instance.pause
|
||||||
toggle_pauses.clear()
|
toggle_pauses.clear()
|
||||||
|
|
||||||
|
|
@ -691,7 +690,9 @@ def import_pair():
|
||||||
data = request.json
|
data = request.json
|
||||||
base = data["base"]
|
base = data["base"]
|
||||||
quote = data["quote"]
|
quote = data["quote"]
|
||||||
return unwrapped_import_pair(base,quote)
|
forced_tp_id = data["forced_tp_id"] if "forced_tp_id" in data else None
|
||||||
|
forced_so_id = data["forced_so_id"] if "forced_so_id" in data else None
|
||||||
|
return unwrapped_import_pair(base,quote,forced_tp_id,forced_so_id)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(e)
|
print(e)
|
||||||
return jsonify({'Error': 'Halp'})
|
return jsonify({'Error': 'Halp'})
|
||||||
|
|
@ -849,58 +850,6 @@ def mod_order_size():
|
||||||
return jsonify({'Error': 'Halp'})
|
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_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'])
|
@base_api.route("/mod_default_order_size", methods=['POST'])
|
||||||
def mod_default_order_size():
|
def mod_default_order_size():
|
||||||
'''
|
'''
|
||||||
|
|
@ -1111,7 +1060,7 @@ def toggle_cleanup():
|
||||||
return jsonify({'Error': 'Halp'})
|
return jsonify({'Error': 'Halp'})
|
||||||
|
|
||||||
|
|
||||||
@base_api.route("/toggle_autoswitch", methods=['POST'])
|
@base_api.route("/toggle_autoswitch", methods=['POST']) #type:ignore
|
||||||
def toggle_autoswitch():
|
def toggle_autoswitch():
|
||||||
'''
|
'''
|
||||||
POST request
|
POST request
|
||||||
|
|
@ -1135,32 +1084,8 @@ def toggle_autoswitch():
|
||||||
return jsonify({'Error': 'Halp'})
|
return jsonify({'Error': 'Halp'})
|
||||||
|
|
||||||
|
|
||||||
@base_api.route("/force_trader_close", methods=['POST'])
|
@base_api.route("/toggle_liquidate_after_switch", methods=['POST']) #type:ignore
|
||||||
def force_trader_close():
|
def toggle_liquidate_after_switch(): #type:ignore
|
||||||
'''
|
|
||||||
POST request
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
base: str
|
|
||||||
quote: str
|
|
||||||
'''
|
|
||||||
|
|
||||||
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"]
|
|
||||||
return unwrapped_force_trader_close(base,quote)
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
return jsonify({'Error': 'Halp'})
|
|
||||||
|
|
||||||
|
|
||||||
@base_api.route("/toggle_liquidate_after_switch", methods=['POST'])
|
|
||||||
def toggle_liquidate_after_switch():
|
|
||||||
'''
|
'''
|
||||||
POST request
|
POST request
|
||||||
|
|
||||||
|
|
@ -1183,7 +1108,7 @@ def toggle_liquidate_after_switch():
|
||||||
return jsonify({'Error': 'Halp'})
|
return jsonify({'Error': 'Halp'})
|
||||||
|
|
||||||
|
|
||||||
@base_api.route("/toggle_check_old_long_price", methods=['POST'])
|
@base_api.route("/toggle_check_old_long_price", methods=['POST'])#type:ignore
|
||||||
def toggle_check_old_long_price():
|
def toggle_check_old_long_price():
|
||||||
'''
|
'''
|
||||||
POST request
|
POST request
|
||||||
|
|
@ -1413,7 +1338,30 @@ def reload_markets():
|
||||||
return unwrapped_reload_markets()
|
return unwrapped_reload_markets()
|
||||||
|
|
||||||
|
|
||||||
@base_api.route("/reload_trader_config", methods=['POST'])
|
@base_api.route("/reload_safety_order", methods=['POST'])
|
||||||
|
def reload_safety_order():
|
||||||
|
'''
|
||||||
|
POST request
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
None
|
||||||
|
'''
|
||||||
|
|
||||||
|
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"]
|
||||||
|
return unwrapped_reload_safety_order(base,quote)
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
return jsonify({'Error': 'Halp'})
|
||||||
|
|
||||||
|
|
||||||
|
@base_api.route("/reload_trader_config", methods=['POST'])#type:ignore
|
||||||
def reload_trader_config():
|
def reload_trader_config():
|
||||||
'''
|
'''
|
||||||
POST request
|
POST request
|
||||||
|
|
@ -1505,7 +1453,7 @@ def unwrapped_add_pair(base,quote):
|
||||||
|
|
||||||
#Check if the trader is already running
|
#Check if the trader is already running
|
||||||
for instance in running_traders:
|
for instance in running_traders:
|
||||||
if symbol==instance.status.get_pair():
|
if symbol==instance.config.get_pair():
|
||||||
broker.logger.log_this(f"Pair already running",1,symbol)
|
broker.logger.log_this(f"Pair already running",1,symbol)
|
||||||
return jsonify({"Error": "Pair already running"})
|
return jsonify({"Error": "Pair already running"})
|
||||||
|
|
||||||
|
|
@ -1539,7 +1487,7 @@ def unwrapped_remove_pair(base,quote):
|
||||||
try:
|
try:
|
||||||
symbol = f"{base}/{quote}"
|
symbol = f"{base}/{quote}"
|
||||||
for instance in running_traders:
|
for instance in running_traders:
|
||||||
if symbol==instance.status.get_pair():
|
if symbol==instance.config.get_pair():
|
||||||
instance.quit = True
|
instance.quit = True
|
||||||
return jsonify({"Success": "Pair to be removed"})
|
return jsonify({"Success": "Pair to be removed"})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
@ -1564,13 +1512,15 @@ def unwrapped_restart_pair(base,quote):
|
||||||
return jsonify({"Error": "Halp"})
|
return jsonify({"Error": "Halp"})
|
||||||
|
|
||||||
|
|
||||||
def unwrapped_import_pair(base,quote):
|
def unwrapped_import_pair(base,quote,forced_tp_id = None, forced_so_id = None):
|
||||||
'''
|
'''
|
||||||
Imports a previously running pair
|
Imports a previously running pair
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
base (str): The base currency of the pair
|
base (str): The base currency of the pair
|
||||||
quote (str): The quote currency of the pair
|
quote (str): The quote currency of the pair
|
||||||
|
forced_tp_id (str): The ID of the take profit order to use
|
||||||
|
forced_so_id (str): The ID of the stop order to use
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
jsonified dictionary detailing the outcome of the operation.
|
jsonified dictionary detailing the outcome of the operation.
|
||||||
|
|
@ -1578,7 +1528,7 @@ def unwrapped_import_pair(base,quote):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
symbol = f"{base}/{quote}"
|
symbol = f"{base}/{quote}"
|
||||||
import_instance(base,quote)
|
import_instance(base,quote,forced_tp_id,forced_so_id)
|
||||||
broker.add_pair_to_config(f"{base}{quote}")
|
broker.add_pair_to_config(f"{base}{quote}")
|
||||||
broker.rewrite_config_file()
|
broker.rewrite_config_file()
|
||||||
broker.logger.log_this(f"Done",2,symbol)
|
broker.logger.log_this(f"Done",2,symbol)
|
||||||
|
|
@ -1606,7 +1556,7 @@ def unwrapped_switch_to_long(base,quote,calculate_profits):
|
||||||
if f"{base}{quote}" not in broker.get_pairs():
|
if f"{base}{quote}" not in broker.get_pairs():
|
||||||
return jsonify({"Error": "Pair not running"})
|
return jsonify({"Error": "Pair not running"})
|
||||||
for instance in running_traders:
|
for instance in running_traders:
|
||||||
if f"{base}/{quote}"==instance.status.get_pair():
|
if f"{base}/{quote}"==instance.config.get_pair():
|
||||||
instance.pause = True
|
instance.pause = True
|
||||||
if instance.switch_to_long(ignore_old_long=ignore_old_long)==1:
|
if instance.switch_to_long(ignore_old_long=ignore_old_long)==1:
|
||||||
return jsonify({"Error": "Error in switch_to_long()"})
|
return jsonify({"Error": "Error in switch_to_long()"})
|
||||||
|
|
@ -1634,14 +1584,14 @@ def unwrapped_switch_to_short(base,quote):
|
||||||
if f"{base}{quote}" not in broker.get_pairs():
|
if f"{base}{quote}" not in broker.get_pairs():
|
||||||
return jsonify({"Error": "Pair not running"})
|
return jsonify({"Error": "Pair not running"})
|
||||||
for instance in running_traders:
|
for instance in running_traders:
|
||||||
if symbol==instance.status.get_pair() and instance.switch_to_short()==1:
|
if symbol==instance.config.get_pair() and instance.switch_to_short()==1:
|
||||||
return jsonify({"Error": "Error in switch_to_short()"})
|
return jsonify({"Error": "Error in switch_to_short()"})
|
||||||
|
|
||||||
#Restart instance
|
#Restart instance
|
||||||
try:
|
try:
|
||||||
broker.logger.log_this(f"Reinitializing trader",2,symbol)
|
broker.logger.log_this(f"Reinitializing trader",2,symbol)
|
||||||
for instance in running_traders:
|
for instance in running_traders:
|
||||||
if symbol==instance.status.get_pair():
|
if symbol==instance.config.get_pair():
|
||||||
instance.status.set_take_profit_order(instance.broker.empty_order)
|
instance.status.set_take_profit_order(instance.broker.empty_order)
|
||||||
instance.so = instance.broker.empty_order
|
instance.so = instance.broker.empty_order
|
||||||
|
|
||||||
|
|
@ -1690,7 +1640,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.
|
#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:
|
for instance in running_traders:
|
||||||
if instance.status.get_pair()==symbol:
|
if instance.config.get_pair()==symbol:
|
||||||
instance.get_status_dict()["old_long"]=old_long
|
instance.get_status_dict()["old_long"]=old_long
|
||||||
instance.update_status(True)
|
instance.update_status(True)
|
||||||
return jsonify({"Success": "old_long file loaded to status_dict"})
|
return jsonify({"Success": "old_long file loaded to status_dict"})
|
||||||
|
|
@ -1717,7 +1667,7 @@ def unwrapped_view_old_long(base,quote,from_file):
|
||||||
old_long = load(ol)
|
old_long = load(ol)
|
||||||
return jsonify(old_long)
|
return jsonify(old_long)
|
||||||
for instance in running_traders:
|
for instance in running_traders:
|
||||||
if symbol==instance.status.get_pair():
|
if symbol==instance.config.get_pair():
|
||||||
if "old_long" in instance.get_status_dict():
|
if "old_long" in instance.get_status_dict():
|
||||||
return jsonify(instance.get_status_dict()["old_long"])
|
return jsonify(instance.get_status_dict()["old_long"])
|
||||||
return jsonify({"Error": "No old_long info found"})
|
return jsonify({"Error": "No old_long info found"})
|
||||||
|
|
@ -1743,7 +1693,7 @@ def unwrapped_switch_to_long_price(base,quote):
|
||||||
try:
|
try:
|
||||||
symbol = f"{base}/{quote}"
|
symbol = f"{base}/{quote}"
|
||||||
for instance in running_traders:
|
for instance in running_traders:
|
||||||
if symbol==instance.status.get_pair():
|
if symbol==instance.config.get_pair():
|
||||||
if "old_long" in instance.get_status_dict():
|
if "old_long" in instance.get_status_dict():
|
||||||
#minimum_switch_price = (old_target - quote_already_in)/base_left
|
#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"]
|
old_target = instance.get_status_dict()["old_long"]["tp_price"]*instance.get_status_dict()["old_long"]["tp_amount"]
|
||||||
|
|
@ -1774,7 +1724,7 @@ def unwrapped_add_safety_orders(base,quote,amount):
|
||||||
try:
|
try:
|
||||||
symbol = f"{base}/{quote}"
|
symbol = f"{base}/{quote}"
|
||||||
for instance in running_traders:
|
for instance in running_traders:
|
||||||
if symbol==instance.status.get_pair():
|
if symbol==instance.config.get_pair():
|
||||||
instance.pause = True
|
instance.pause = True
|
||||||
#x.no_of_safety_orders += int(amount)
|
#x.no_of_safety_orders += int(amount)
|
||||||
instance.config.set_no_of_safety_orders(instance.config.get_no_of_safety_orders()+int(amount))
|
instance.config.set_no_of_safety_orders(instance.config.get_no_of_safety_orders()+int(amount))
|
||||||
|
|
@ -1805,7 +1755,7 @@ def unwrapped_base_add_so_calculation(base,quote):
|
||||||
try:
|
try:
|
||||||
symbol = f"{base}/{quote}"
|
symbol = f"{base}/{quote}"
|
||||||
for instance in running_traders:
|
for instance in running_traders:
|
||||||
if symbol==instance.status.get_pair():
|
if symbol==instance.config.get_pair():
|
||||||
free_base = instance.fetch_free_base()
|
free_base = instance.fetch_free_base()
|
||||||
if free_base is None:
|
if free_base is None:
|
||||||
return jsonify({"Error": "Can't fetch amount of free base on the exchange"})
|
return jsonify({"Error": "Can't fetch amount of free base on the exchange"})
|
||||||
|
|
@ -1833,7 +1783,7 @@ def unwrapped_mod_tp_level(base,quote,amount):
|
||||||
try:
|
try:
|
||||||
symbol = f"{base}/{quote}"
|
symbol = f"{base}/{quote}"
|
||||||
for instance in running_traders:
|
for instance in running_traders:
|
||||||
if symbol==instance.status.get_pair():
|
if symbol==instance.config.get_pair():
|
||||||
instance.config.set_tp_level(float(amount))
|
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)
|
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"})
|
return jsonify({"Success": "Success. The change will take effect when the next TP order is placed"})
|
||||||
|
|
@ -1858,9 +1808,8 @@ def unwrapped_mod_order_size(base,quote,amount):
|
||||||
try:
|
try:
|
||||||
symbol = f"{base}/{quote}"
|
symbol = f"{base}/{quote}"
|
||||||
for instance in running_traders:
|
for instance in running_traders:
|
||||||
if symbol==instance.status.get_pair():
|
if symbol==instance.config.get_pair():
|
||||||
instance.config.set_order_size(float(amount))
|
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)
|
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"})
|
return jsonify({"Success": "Success. The change will take effect when the next deal is started"})
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|
@ -1868,58 +1817,6 @@ def unwrapped_mod_order_size(base,quote,amount):
|
||||||
return jsonify({"Error": "Error changing order size"})
|
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.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:
|
|
||||||
broker.logger.log_this("Error changing safety orders amount. Ignoring...",2,symbol)
|
|
||||||
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):
|
def unwrapped_mod_default_order_size(amount):
|
||||||
'''
|
'''
|
||||||
Modifies the default order size of a broker.
|
Modifies the default order size of a broker.
|
||||||
|
|
@ -1975,7 +1872,7 @@ def unwrapped_last_call(base,quote):
|
||||||
try:
|
try:
|
||||||
symbol = f"{base}/{quote}"
|
symbol = f"{base}/{quote}"
|
||||||
for instance in running_traders:
|
for instance in running_traders:
|
||||||
if symbol==instance.status.get_pair():
|
if symbol==instance.config.get_pair():
|
||||||
instance.status.set_stop_when_profit(not instance.status.get_stop_when_profit())
|
instance.status.set_stop_when_profit(not instance.status.get_stop_when_profit())
|
||||||
instance.update_status(True)
|
instance.update_status(True)
|
||||||
if instance.status.get_stop_when_profit():
|
if instance.status.get_stop_when_profit():
|
||||||
|
|
@ -2009,7 +1906,7 @@ def unwrapped_deferred_last_call(base,quote,yyyymmdd):
|
||||||
if limit==0:
|
if limit==0:
|
||||||
return jsonify({"Error": "Can't convert date to unix"})
|
return jsonify({"Error": "Can't convert date to unix"})
|
||||||
for instance in running_traders:
|
for instance in running_traders:
|
||||||
if f"{base}{quote}"==instance.status.get_pair():
|
if f"{base}{quote}"==instance.config.get_pair():
|
||||||
instance.config.set_programmed_stop_time(limit)
|
instance.config.set_programmed_stop_time(limit)
|
||||||
instance.config.set_programmed_stop(True)
|
instance.config.set_programmed_stop(True)
|
||||||
#save config file to disk
|
#save config file to disk
|
||||||
|
|
@ -2036,7 +1933,7 @@ def unwrapped_toggle_pause(base,quote):
|
||||||
symbol = f"{base}/{quote}"
|
symbol = f"{base}/{quote}"
|
||||||
toggle_pauses.append(symbol)
|
toggle_pauses.append(symbol)
|
||||||
for instance in running_traders:
|
for instance in running_traders:
|
||||||
if instance.status.get_pair()==symbol:
|
if instance.config.get_pair()==symbol:
|
||||||
if instance.pause:
|
if instance.pause:
|
||||||
instance.status.set_pause_reason("")
|
instance.status.set_pause_reason("")
|
||||||
return jsonify({"Success": "Trader will be resumed"})
|
return jsonify({"Success": "Trader will be resumed"})
|
||||||
|
|
@ -2095,55 +1992,55 @@ def unwrapped_add_quote(base,quote,amount):
|
||||||
'''
|
'''
|
||||||
|
|
||||||
for instance in running_traders:
|
for instance in running_traders:
|
||||||
if f"{base}/{quote}"==instance.status.get_pair():
|
if f"{base}/{quote}"==instance.config.get_pair():
|
||||||
if instance.config.get_is_short():
|
if instance.config.get_is_short():
|
||||||
return jsonify({"Error": "Quote can't be added to short traders"})
|
return jsonify({"Error": "Quote can't be added to short traders"})
|
||||||
instance.pause = True
|
instance.pause = True
|
||||||
new_average_price = (instance.status.get_quote_spent()+float(amount))/(instance.status.get_base_bought()+(float(amount)/instance.status.get_price()))
|
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 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.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.config.get_pair())
|
||||||
new_order = broker.new_market_order(instance.status.get_pair(),float(amount),"buy")
|
new_order = broker.new_market_order(instance.config.get_pair(),float(amount),"buy")
|
||||||
if new_order is None:
|
if new_order is None:
|
||||||
broker.logger.log_this("Error: Market order returned None",2,instance.status.get_pair())
|
broker.logger.log_this("Error: Market order returned None",2,instance.config.get_pair())
|
||||||
instance.pause = False
|
instance.pause = False
|
||||||
return jsonify({"Error": "Market order returned None"})
|
return jsonify({"Error": "Market order returned None"})
|
||||||
while True:
|
while True:
|
||||||
time.sleep(broker.get_wait_time())
|
time.sleep(broker.get_wait_time())
|
||||||
returned_order = broker.get_order(new_order["id"],instance.status.get_pair())
|
returned_order = broker.get_order(new_order["id"],instance.config.get_pair())
|
||||||
if returned_order==broker.empty_order:
|
if returned_order==broker.empty_order:
|
||||||
broker.logger.log_this("Problems sending the order",2,instance.status.get_pair())
|
broker.logger.log_this("Problems sending the order",2,instance.config.get_pair())
|
||||||
instance.pause = False
|
instance.pause = False
|
||||||
return jsonify({"Error": "Problems sending the order"})
|
return jsonify({"Error": "Problems sending the order"})
|
||||||
elif returned_order["status"]=="expired":
|
elif returned_order["status"]=="expired":
|
||||||
instance.pause = False
|
instance.pause = False
|
||||||
return jsonify({"Error": "New order expired"})
|
return jsonify({"Error": "New order expired"})
|
||||||
elif returned_order["status"]=="closed":
|
elif returned_order["status"]=="closed":
|
||||||
broker.logger.log_this("Order sent",2,instance.status.get_pair())
|
broker.logger.log_this("Order sent",2,instance.config.get_pair())
|
||||||
new_fees_in_base, new_fees_in_quote = instance.parse_fees(returned_order)
|
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_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_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_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"])
|
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())
|
broker.logger.log_this("Cancelling old take profit order and sending a new one",2,instance.config.get_pair())
|
||||||
attempts = 5
|
attempts = 5
|
||||||
while broker.cancel_order(instance.status.get_take_profit_order()["id"],instance.status.get_pair())==1:
|
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.status.get_pair())
|
broker.logger.log_this("Can't cancel old take profit order, retrying...",2,instance.config.get_pair())
|
||||||
time.sleep(broker.get_wait_time())
|
time.sleep(broker.get_wait_time())
|
||||||
attempts-=1
|
attempts-=1
|
||||||
if attempts==0:
|
if attempts==0:
|
||||||
broker.logger.log_this("Can't cancel old take profit order, cancelling...",2,instance.status.get_pair())
|
broker.logger.log_this("Can't cancel old take profit order, cancelling...",2,instance.config.get_pair())
|
||||||
instance.pause = False
|
instance.pause = False
|
||||||
return jsonify({"Error": "Can't cancel old take profit order."})
|
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_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.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.update_status(True)
|
instance.update_status(True)
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
broker.logger.log_this("Waiting for initial order to get filled",2,instance.status.get_pair())
|
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.status.get_pair())
|
broker.logger.log_this(f"{returned_order}",2,instance.config.get_pair())
|
||||||
time.sleep(broker.get_wait_time())
|
time.sleep(broker.get_wait_time())
|
||||||
instance.pause = False
|
instance.pause = False
|
||||||
broker.logger.log_this("Done",2,instance.status.get_pair())
|
broker.logger.log_this("Done",2,instance.config.get_pair())
|
||||||
return jsonify({"Success": "Quote added successfully"})
|
return jsonify({"Success": "Quote added successfully"})
|
||||||
return jsonify({"Error": "Something horrible happened :S"})
|
return jsonify({"Error": "Something horrible happened :S"})
|
||||||
|
|
||||||
|
|
@ -2181,7 +2078,7 @@ def unwrapped_toggle_cleanup(base,quote):
|
||||||
try:
|
try:
|
||||||
symbol = f"{base}/{quote}"
|
symbol = f"{base}/{quote}"
|
||||||
for instance in running_traders:
|
for instance in running_traders:
|
||||||
if symbol==instance.status.get_pair():
|
if symbol==instance.config.get_pair():
|
||||||
instance.config.set_cleanup(not instance.config.get_cleanup())
|
instance.config.set_cleanup(not instance.config.get_cleanup())
|
||||||
if instance.config.get_cleanup():
|
if instance.config.get_cleanup():
|
||||||
return jsonify({"Success": "Cleanup turned ON"})
|
return jsonify({"Success": "Cleanup turned ON"})
|
||||||
|
|
@ -2192,32 +2089,6 @@ def unwrapped_toggle_cleanup(base,quote):
|
||||||
return jsonify({"Error": "Task failed successfully"})
|
return jsonify({"Error": "Task failed successfully"})
|
||||||
|
|
||||||
|
|
||||||
def unwrapped_force_trader_close(base,quote):
|
|
||||||
'''
|
|
||||||
Forces a trader to close the position.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
base (str): The base currency of the pair to close
|
|
||||||
quote (str): The quote currency of the pair to close
|
|
||||||
|
|
||||||
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():
|
|
||||||
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"})
|
|
||||||
|
|
||||||
|
|
||||||
def unwrapped_toggle_autoswitch(base,quote):
|
def unwrapped_toggle_autoswitch(base,quote):
|
||||||
'''
|
'''
|
||||||
Signals a trader to enable or disable autoswitch.
|
Signals a trader to enable or disable autoswitch.
|
||||||
|
|
@ -2233,7 +2104,7 @@ def unwrapped_toggle_autoswitch(base,quote):
|
||||||
try:
|
try:
|
||||||
symbol = f"{base}/{quote}"
|
symbol = f"{base}/{quote}"
|
||||||
for instance in running_traders:
|
for instance in running_traders:
|
||||||
if symbol==instance.status.get_pair():
|
if symbol==instance.config.get_pair():
|
||||||
if instance.config.get_autoswitch():
|
if instance.config.get_autoswitch():
|
||||||
broker.logger.log_this("Autoswitch turned OFF",1,symbol)
|
broker.logger.log_this("Autoswitch turned OFF",1,symbol)
|
||||||
instance.config.set_autoswitch(False)
|
instance.config.set_autoswitch(False)
|
||||||
|
|
@ -2242,7 +2113,6 @@ def unwrapped_toggle_autoswitch(base,quote):
|
||||||
broker.logger.log_this("Autoswitch turned ON",1,symbol)
|
broker.logger.log_this("Autoswitch turned ON",1,symbol)
|
||||||
instance.config.set_autoswitch(True)
|
instance.config.set_autoswitch(True)
|
||||||
return jsonify({"Success": "Autoswitch is now ON"})
|
return jsonify({"Success": "Autoswitch is now ON"})
|
||||||
return jsonify({"Error": "Trader not running"})
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
broker.logger.log_this(f"Exception while toggling autoswitch: {e}",1,symbol)
|
broker.logger.log_this(f"Exception while toggling autoswitch: {e}",1,symbol)
|
||||||
return jsonify({"Error": "Halp"})
|
return jsonify({"Error": "Halp"})
|
||||||
|
|
@ -2262,7 +2132,7 @@ def unwrapped_toggle_liquidate_after_switch(base,quote):
|
||||||
try:
|
try:
|
||||||
symbol = f"{base}/{quote}"
|
symbol = f"{base}/{quote}"
|
||||||
for instance in running_traders:
|
for instance in running_traders:
|
||||||
if symbol==instance.status.get_pair():
|
if symbol==instance.config.get_pair():
|
||||||
if instance.config.get_liquidate_after_switch():
|
if instance.config.get_liquidate_after_switch():
|
||||||
broker.logger.log_this("Liquidate after switch turned OFF",1,symbol)
|
broker.logger.log_this("Liquidate after switch turned OFF",1,symbol)
|
||||||
instance.config.set_liquidate_after_switch(False)
|
instance.config.set_liquidate_after_switch(False)
|
||||||
|
|
@ -2271,12 +2141,10 @@ def unwrapped_toggle_liquidate_after_switch(base,quote):
|
||||||
broker.logger.log_this("Liquidate after switch turned ON",1,symbol)
|
broker.logger.log_this("Liquidate after switch turned ON",1,symbol)
|
||||||
instance.config.set_liquidate_after_switch(True)
|
instance.config.set_liquidate_after_switch(True)
|
||||||
return jsonify({"Success": "Liquidate after switch is now ON"})
|
return jsonify({"Success": "Liquidate after switch is now ON"})
|
||||||
return jsonify({"Error": "Trader not running"})
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
broker.logger.log_this(f"Exception while toggling liquidate after switch: {e}",1,symbol)
|
broker.logger.log_this(f"Exception while toggling liquidate after switch: {e}",1,symbol)
|
||||||
return jsonify({"Error": "Halp"})
|
return jsonify({"Error": "Halp"})
|
||||||
|
|
||||||
|
|
||||||
def unwrapped_toggle_check_old_long_price(base,quote):
|
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.
|
Signals to the trader if it should compare the current price to the old_long price stored in the old_long dictionary.
|
||||||
|
|
@ -2292,7 +2160,7 @@ def unwrapped_toggle_check_old_long_price(base,quote):
|
||||||
try:
|
try:
|
||||||
symbol = f"{base}/{quote}"
|
symbol = f"{base}/{quote}"
|
||||||
for instance in running_traders:
|
for instance in running_traders:
|
||||||
if symbol==instance.status.get_pair():
|
if symbol==instance.config.get_pair():
|
||||||
if instance.config.get_check_old_long_price():
|
if instance.config.get_check_old_long_price():
|
||||||
broker.logger.log_this("Check OFF",1,symbol)
|
broker.logger.log_this("Check OFF",1,symbol)
|
||||||
instance.config.set_check_old_long_price(False)
|
instance.config.set_check_old_long_price(False)
|
||||||
|
|
@ -2301,7 +2169,6 @@ def unwrapped_toggle_check_old_long_price(base,quote):
|
||||||
broker.logger.log_this("Check ON",1,symbol)
|
broker.logger.log_this("Check ON",1,symbol)
|
||||||
instance.config.set_check_old_long_price(True)
|
instance.config.set_check_old_long_price(True)
|
||||||
return jsonify({"Success": "Old long price check turned ON"})
|
return jsonify({"Success": "Old long price check turned ON"})
|
||||||
return jsonify({"Error": "Trader not running"})
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
broker.logger.log_this(f"Exception while toggling check_old_long_price: {e}",1,symbol)
|
broker.logger.log_this(f"Exception while toggling check_old_long_price: {e}",1,symbol)
|
||||||
return jsonify({"Error": "Halp"})
|
return jsonify({"Error": "Halp"})
|
||||||
|
|
@ -2323,7 +2190,7 @@ def unwrapped_switch_quote_currency(base,quote,new_quote):
|
||||||
try:
|
try:
|
||||||
symbol = f"{base}/{quote}"
|
symbol = f"{base}/{quote}"
|
||||||
for trader in running_traders:
|
for trader in running_traders:
|
||||||
if symbol==trader.status.get_pair():
|
if symbol==trader.config.get_pair():
|
||||||
#Pause the trader
|
#Pause the trader
|
||||||
trader.pause = True
|
trader.pause = True
|
||||||
|
|
||||||
|
|
@ -2489,6 +2356,29 @@ def unwrapped_reload_markets():
|
||||||
return jsonify({"Error": "Markets couldn't be reloaded"})
|
return jsonify({"Error": "Markets couldn't be reloaded"})
|
||||||
|
|
||||||
|
|
||||||
|
def unwrapped_reload_safety_order(base,quote):
|
||||||
|
'''
|
||||||
|
Reloads the safety order of a trader.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
base (str): The base currency of the trader.
|
||||||
|
quote (str): The quote currency of the trader.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
jsonify: A jsonified dictionary detailing the outcome of the operation.
|
||||||
|
'''
|
||||||
|
try:
|
||||||
|
symbol = f"{base}/{quote}"
|
||||||
|
for trader in running_traders:
|
||||||
|
if trader.config.get_pair()==symbol:
|
||||||
|
trader.config.load_from_file()
|
||||||
|
return jsonify({"Success": "Safety order reloaded successfully"})
|
||||||
|
return jsonify({"Error": "Trader not found"})
|
||||||
|
except Exception as e:
|
||||||
|
broker.logger.log_this(f"Exception while reloading safety order: {e}",1,symbol)
|
||||||
|
return jsonify({"Error": "Safety order couldn't be reloaded"})
|
||||||
|
|
||||||
|
|
||||||
def unwrapped_get_balance(coin):
|
def unwrapped_get_balance(coin):
|
||||||
'''
|
'''
|
||||||
Returns the balance of a given coin.
|
Returns the balance of a given coin.
|
||||||
|
|
@ -2523,7 +2413,7 @@ def unwrapped_reload_trader_config(base,quote):
|
||||||
'''
|
'''
|
||||||
symbol = f"{base}/{quote}"
|
symbol = f"{base}/{quote}"
|
||||||
for trader in running_traders:
|
for trader in running_traders:
|
||||||
if trader.status.get_pair() == symbol:
|
if trader.config.get_pair() == symbol:
|
||||||
if trader.config.load_from_file()==0:
|
if trader.config.load_from_file()==0:
|
||||||
return jsonify({"Success": "Config file reloaded"})
|
return jsonify({"Success": "Config file reloaded"})
|
||||||
return jsonify({"Error": "Error reloading config file"})
|
return jsonify({"Error": "Error reloading config file"})
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,7 @@ class StatusHandler:
|
||||||
"pair": f"{base}/{quote}",
|
"pair": f"{base}/{quote}",
|
||||||
"take_profit_order": broker.get_empty_order(),
|
"take_profit_order": broker.get_empty_order(),
|
||||||
"take_profit_price": 0.0,
|
"take_profit_price": 0.0,
|
||||||
"safety_orders": [],
|
"safety_order": broker.get_empty_order(),
|
||||||
"safety_orders_filled": 0,
|
|
||||||
"next_so_price": 0.0,
|
"next_so_price": 0.0,
|
||||||
"order_size": 0.0,
|
"order_size": 0.0,
|
||||||
"partial_profit": 0.0,
|
"partial_profit": 0.0,
|
||||||
|
|
@ -24,7 +23,7 @@ class StatusHandler:
|
||||||
"quote_spent": 0.0,
|
"quote_spent": 0.0,
|
||||||
"base_bought": 0.0,
|
"base_bought": 0.0,
|
||||||
"so_amount": 0,
|
"so_amount": 0,
|
||||||
"no_of_safety_orders": 0,
|
"no_of_safety_orders": "",
|
||||||
"safety_price_table": [],
|
"safety_price_table": [],
|
||||||
"deal_uptime": 0.0,
|
"deal_uptime": 0.0,
|
||||||
"total_uptime": 0.0,
|
"total_uptime": 0.0,
|
||||||
|
|
@ -59,14 +58,8 @@ class StatusHandler:
|
||||||
def get_take_profit_price(self):
|
def get_take_profit_price(self):
|
||||||
return self.status_dictionary["take_profit_price"]
|
return self.status_dictionary["take_profit_price"]
|
||||||
|
|
||||||
def get_safety_orders(self):
|
def get_safety_order(self):
|
||||||
"""
|
return self.status_dictionary["safety_order"]
|
||||||
Returns the list of open safety orders
|
|
||||||
"""
|
|
||||||
return self.status_dictionary["safety_orders"]
|
|
||||||
|
|
||||||
def get_safety_orders_filled(self):
|
|
||||||
return self.status_dictionary["safety_orders_filled"]
|
|
||||||
|
|
||||||
def get_next_so_price(self):
|
def get_next_so_price(self):
|
||||||
return self.status_dictionary["next_so_price"]
|
return self.status_dictionary["next_so_price"]
|
||||||
|
|
@ -155,10 +148,6 @@ class StatusHandler:
|
||||||
def get_status_file_path(self):
|
def get_status_file_path(self):
|
||||||
return self.status_file_path
|
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):
|
def set_status_file_path(self, new_file_path: str):
|
||||||
# if not isinstance(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())
|
# self.broker.logger.log_this(f"File path provided is not a string",1,self.get_pair())
|
||||||
|
|
@ -192,15 +181,8 @@ class StatusHandler:
|
||||||
self.status_dictionary["so_order_id"] = order_id
|
self.status_dictionary["so_order_id"] = order_id
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def set_safety_orders(self, orders: list):
|
def set_safety_order(self, order):
|
||||||
"""
|
self.status_dictionary["safety_order"] = order
|
||||||
Replaces the whole safety orders list
|
|
||||||
"""
|
|
||||||
self.status_dictionary["safety_orders"] = orders
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def set_safety_orders_filled(self, amount: int):
|
|
||||||
self.status_dictionary["safety_orders_filled"] = amount
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def set_next_so_price(self, price: float):
|
def set_next_so_price(self, price: float):
|
||||||
|
|
@ -399,21 +381,6 @@ class StatusHandler:
|
||||||
self.status_dictionary["deal_order_history"] = deal_history
|
self.status_dictionary["deal_order_history"] = deal_history
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def add_safety_order(self, order):
|
|
||||||
"""
|
|
||||||
Appends a newly-created safety order to the internal list
|
|
||||||
"""
|
|
||||||
self.status_dictionary["safety_orders"].append(order)
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def remove_safety_order_by_id(self, order_id: str):
|
|
||||||
"""
|
|
||||||
Removes an order from the list (mostly used when that order is filled or canceled)
|
|
||||||
"""
|
|
||||||
orders = self.get_safety_orders()
|
|
||||||
self.status_dictionary["safety_orders"] = [order for order in orders if order["id"] != order_id]
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def clear_deal_order_history(self):
|
def clear_deal_order_history(self):
|
||||||
self.status_dictionary["deal_order_history"] = []
|
self.status_dictionary["deal_order_history"] = []
|
||||||
return 0
|
return 0
|
||||||
|
|
|
||||||
27
todo.txt
27
todo.txt
|
|
@ -1,25 +1,32 @@
|
||||||
Mandatory:
|
Mandatory:
|
||||||
=========
|
=========
|
||||||
0. Stats webpage.
|
1. Stats webpage.
|
||||||
1. Maintain local orderbooks for each trading pair, which enables:
|
2. Maintain local orderbooks for each trading pair, which enables:
|
||||||
2a. Smart order pricing: Prioritization of fill speed over instant profit or vice versa
|
2a. Smart order pricing: Prioritization of fill speed over instant profit or vice versa
|
||||||
2. Proper handling of order price too high/low in OKX (rare, it happens when under heavy volatility).
|
3. Proper handling of order price too high/low in OKX (rare, it happens when under heavy volatility).
|
||||||
3. API documentation.
|
4. Multiple safety orders open at the same time (to catch big volatility spikes more effectively)
|
||||||
4. Implement api key hashing.
|
5. Things that should be objects (it's not 1994):
|
||||||
5. Dockerize.
|
* Orders.
|
||||||
6. Earn should be integrated into the instance, in order to be able to invest the idle funds from the short traders.
|
* Config (Mostly done).
|
||||||
|
* Status (Mostly done).
|
||||||
|
6. API documentation.
|
||||||
|
7. Implement api key hashing.
|
||||||
|
8. Dockerize.
|
||||||
|
9. Cache generated status strings, only recalculate when prices change.
|
||||||
|
10. Inspect orderbook liquidity prior to changing mode from short to long (big sell market order needs to have liquidity).
|
||||||
|
|
||||||
|
|
||||||
Would be nice to have:
|
Would be nice to have:
|
||||||
=====================
|
=====================
|
||||||
0. Trader order: alphabetical; by uptime; by safety orders, by percentage_to_completion. (Although this may be more suitable for the web and mobile apps)
|
0. Trader order: alphabetical; by uptime; by safety orders, by percentage_to_completion. (Although this may be more suitable for the web and mobile apps)
|
||||||
1. Local implementation of amount_to_precision, cost_to_precision and price_to_precision. (Unless the plan is to continue to use CCXT forever)
|
1. Local implementation of amount_to_precision, cost_to_precision and price_to_precision. (Unless the plan is to continue to use CCXT forever)
|
||||||
2. Instead of cancelling and resending the take profit order, edit it (Kucoin only supports editing on high frequency orders)
|
2. Instead of cancelling and resending the take profit order, you could just edit it (Kucoin only supports editing on high frequency orders)
|
||||||
3. When autoswitching to long, instead of using a big market order, the last safety order should be a sell order of all the available funds.
|
3. Round-robin trading pairs: Instead of a fixed list of trading pairs, after n closed deals the trader is terminated and a new one spawns, picking the trading pair
|
||||||
4. Round-robin trading pairs: Instead of a fixed list of trading pairs, after n closed deals the trader is terminated and a new one spawns, picking the trading pair
|
|
||||||
from a pre-populated list (the trading pairs can be selected by using Yang-Zhang, Parkinson or another volatility indicator)
|
from a pre-populated list (the trading pairs can be selected by using Yang-Zhang, Parkinson or another volatility indicator)
|
||||||
This could be very benefitial, since it limits the long time commitment to a small list of trading pairs, enabling the instance to react to market trends very
|
This could be very benefitial, since it limits the long time commitment to a small list of trading pairs, enabling the instance to react to market trends very
|
||||||
rapidly.
|
rapidly.
|
||||||
|
4. Earn should also use funds from short traders.
|
||||||
|
4b. Should Earn be integrated to the instance?
|
||||||
|
|
||||||
|
|
||||||
Maybe it's a good idea?:
|
Maybe it's a good idea?:
|
||||||
|
|
|
||||||
|
|
@ -11,12 +11,6 @@ try:
|
||||||
api_key = credentials.get_credentials("testnet_api_key")["key"]
|
api_key = credentials.get_credentials("testnet_api_key")["key"]
|
||||||
base_url = credentials.get_url("testnet") #type: ignore
|
base_url = credentials.get_url("testnet") #type: ignore
|
||||||
exchanges = {"Binance":"/binance"}
|
exchanges = {"Binance":"/binance"}
|
||||||
elif sys.argv[1]=="--local_testnet":
|
|
||||||
is_testnet = True
|
|
||||||
string_to_add = "LOCAL TESTNET "
|
|
||||||
api_key = credentials.get_credentials("local_testnet_api_key")["key"]
|
|
||||||
base_url = credentials.get_url("local_testnet") #type: ignore
|
|
||||||
exchanges = {"Binance":":5001"}
|
|
||||||
elif sys.argv[1]=="--mainnet":
|
elif sys.argv[1]=="--mainnet":
|
||||||
is_testnet = False
|
is_testnet = False
|
||||||
string_to_add = "MAINNET "
|
string_to_add = "MAINNET "
|
||||||
|
|
@ -62,9 +56,9 @@ TRADERS
|
||||||
62) mod_tp_level 63) last_call 64) deferred_last_call
|
62) mod_tp_level 63) last_call 64) deferred_last_call
|
||||||
65) toggle_pause 66) toggle_cleanup 67) toggle_autoswitch
|
65) toggle_pause 66) toggle_cleanup 67) toggle_autoswitch
|
||||||
68) toggle_check_old_long_price 69) switch_quote_currency
|
68) toggle_check_old_long_price 69) switch_quote_currency
|
||||||
70) view_old_long 71) switch_price 72) reload_trader_config
|
70) reload_safety_order 71) view_old_long 72) switch_price
|
||||||
73) toggle_liquidate_after_switch 74) base_add_calculation
|
73) reload_trader_config 74) toggle_liquidate_after_switch
|
||||||
75) mod_concurrent_safety_orders 76) force_trader_close
|
75) base_add_calculation
|
||||||
|
|
||||||
98) Change broker 99) Exit
|
98) Change broker 99) Exit
|
||||||
'''
|
'''
|
||||||
|
|
@ -567,6 +561,10 @@ if __name__=="__main__":
|
||||||
print("In order for the importing to be successful, a status file must exist in the status directory ")
|
print("In order for the importing to be successful, a status file must exist in the status directory ")
|
||||||
print("and the take profit order must be open.")
|
print("and the take profit order must be open.")
|
||||||
trading_pair = input("Input trader in the format BASE/QUOTE: ").upper()
|
trading_pair = input("Input trader in the format BASE/QUOTE: ").upper()
|
||||||
|
tp_id_input = input("Input take profit order id to use (if any)")
|
||||||
|
forced_tp_id = None if tp_id_input=="" else tp_id_input
|
||||||
|
so_id_input = input("Input safety order id to use (if any)")
|
||||||
|
forced_so_id = None if so_id_input=="" else so_id_input
|
||||||
|
|
||||||
if not validate_pair(trading_pair):
|
if not validate_pair(trading_pair):
|
||||||
print("The input is invalid")
|
print("The input is invalid")
|
||||||
|
|
@ -575,7 +573,9 @@ if __name__=="__main__":
|
||||||
url = f"{base_url}{port}/import_pair"
|
url = f"{base_url}{port}/import_pair"
|
||||||
base,quote = trading_pair.split("/")
|
base,quote = trading_pair.split("/")
|
||||||
parameters = {"base": base,
|
parameters = {"base": base,
|
||||||
"quote": quote}
|
"quote": quote,
|
||||||
|
"forced_tp_id": forced_tp_id,
|
||||||
|
"forced_so_id": forced_so_id}
|
||||||
print(json.loads(requests.post(url, headers=headers, json=parameters).content))
|
print(json.loads(requests.post(url, headers=headers, json=parameters).content))
|
||||||
input("Press ENTER to continue ")
|
input("Press ENTER to continue ")
|
||||||
|
|
||||||
|
|
@ -798,6 +798,20 @@ if __name__=="__main__":
|
||||||
input("Press ENTER to continue ")
|
input("Press ENTER to continue ")
|
||||||
|
|
||||||
elif command==70:
|
elif command==70:
|
||||||
|
print("reload_safety_order reloads the safety order to the reader using the order id present in the status dictionary")
|
||||||
|
trading_pair = input("Input trader in the format BASE/QUOTE: ").upper()
|
||||||
|
if not validate_pair(trading_pair):
|
||||||
|
print("The input is invalid")
|
||||||
|
break
|
||||||
|
if input("Proceed? (Y/n) ") in ["Y","y",""]:
|
||||||
|
url = f"{base_url}{port}/reload_safety_order"
|
||||||
|
base,quote = trading_pair.split("/")
|
||||||
|
parameters = {"base": base,
|
||||||
|
"quote": quote}
|
||||||
|
print(json.loads(requests.post(url, headers=headers, json=parameters).content))
|
||||||
|
input("Press ENTER to continue ")
|
||||||
|
|
||||||
|
elif command==71:
|
||||||
print("Views the old_long information")
|
print("Views the old_long information")
|
||||||
trading_pair = input("Input trader in the format BASE/QUOTE: ").upper()
|
trading_pair = input("Input trader in the format BASE/QUOTE: ").upper()
|
||||||
if not validate_pair(trading_pair):
|
if not validate_pair(trading_pair):
|
||||||
|
|
@ -810,7 +824,7 @@ if __name__=="__main__":
|
||||||
print(json.loads(requests.get(url,headers=headers).content))
|
print(json.loads(requests.get(url,headers=headers).content))
|
||||||
input("Press ENTER to continue ")
|
input("Press ENTER to continue ")
|
||||||
|
|
||||||
elif command==71:
|
elif command==72:
|
||||||
print("Returns the price target to reach to switch to long mode")
|
print("Returns the price target to reach to switch to long mode")
|
||||||
trading_pair = input("Input trader in the format BASE/QUOTE: ").upper()
|
trading_pair = input("Input trader in the format BASE/QUOTE: ").upper()
|
||||||
if not validate_pair(trading_pair):
|
if not validate_pair(trading_pair):
|
||||||
|
|
@ -822,7 +836,7 @@ if __name__=="__main__":
|
||||||
print(json.loads(requests.get(url,headers=headers).content))
|
print(json.loads(requests.get(url,headers=headers).content))
|
||||||
input("Press ENTER to continue ")
|
input("Press ENTER to continue ")
|
||||||
|
|
||||||
elif command==72:
|
elif command==73:
|
||||||
print("Reloads from disk the configuration file of a trader")
|
print("Reloads from disk the configuration file of a trader")
|
||||||
trading_pair = input("Input trader in the format BASE/QUOTE: ").upper()
|
trading_pair = input("Input trader in the format BASE/QUOTE: ").upper()
|
||||||
if not validate_pair(trading_pair):
|
if not validate_pair(trading_pair):
|
||||||
|
|
@ -836,7 +850,7 @@ if __name__=="__main__":
|
||||||
print(json.loads(requests.post(url, headers=headers, json=parameters).content))
|
print(json.loads(requests.post(url, headers=headers, json=parameters).content))
|
||||||
input("Press ENTER to continue ")
|
input("Press ENTER to continue ")
|
||||||
|
|
||||||
elif command==73:
|
elif command==74:
|
||||||
print("toggle_liquidate_after_switch enables or disables the liquidation after an automatic switch to long of a short trader")
|
print("toggle_liquidate_after_switch enables or disables the liquidation after an automatic switch to long of a short trader")
|
||||||
print("This is only valid in a short trader, of course.")
|
print("This is only valid in a short trader, of course.")
|
||||||
trading_pair = input("Input trader in the format BASE/QUOTE: ").upper()
|
trading_pair = input("Input trader in the format BASE/QUOTE: ").upper()
|
||||||
|
|
@ -851,7 +865,7 @@ if __name__=="__main__":
|
||||||
print(json.loads(requests.post(url, headers=headers, json=parameters).content))
|
print(json.loads(requests.post(url, headers=headers, json=parameters).content))
|
||||||
input("Press ENTER to continue ")
|
input("Press ENTER to continue ")
|
||||||
|
|
||||||
elif command==74:
|
elif command==75:
|
||||||
print("Returns the amount of safety orders that can be added to a short trader with the available funds")
|
print("Returns the amount of safety orders that can be added to a short trader with the available funds")
|
||||||
trading_pair = input("Input trader in the format BASE/QUOTE: ").upper()
|
trading_pair = input("Input trader in the format BASE/QUOTE: ").upper()
|
||||||
if not validate_pair(trading_pair):
|
if not validate_pair(trading_pair):
|
||||||
|
|
@ -862,36 +876,3 @@ if __name__=="__main__":
|
||||||
url = f"{base_url}{port}/base_add_so_calculation?base={base}"e={quote}"
|
url = f"{base_url}{port}/base_add_so_calculation?base={base}"e={quote}"
|
||||||
print(json.loads(requests.get(url,headers=headers).content))
|
print(json.loads(requests.get(url,headers=headers).content))
|
||||||
input("Press ENTER to continue ")
|
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 ")
|
|
||||||
|
|
||||||
elif command==76:
|
|
||||||
print("force_trader_close forces a trader to close the current position")
|
|
||||||
trading_pair = input("Input trader in the format BASE/QUOTE: ").upper()
|
|
||||||
if not validate_pair(trading_pair):
|
|
||||||
print("The input is invalid")
|
|
||||||
break
|
|
||||||
if input("Proceed? (Y/n) ") in ["Y","y",""]:
|
|
||||||
url = f"{base_url}{port}/force_trader_close"
|
|
||||||
base,quote = trading_pair.split("/")
|
|
||||||
parameters = {"base": base,
|
|
||||||
"quote": quote}
|
|
||||||
print(json.loads(requests.post(url, headers=headers, json=parameters).content))
|
|
||||||
input("Press ENTER to continue ")
|
|
||||||
Loading…
Reference in New Issue