Compare commits
26 Commits
56cbf51129
...
ab77840bea
| Author | SHA1 | Date |
|---|---|---|
|
|
ab77840bea | |
|
|
27420946cd | |
|
|
5dde1a1def | |
|
|
b36d73306d | |
|
|
f7365c0340 | |
|
|
84bada9967 | |
|
|
bb3fb692df | |
|
|
694e5a95d1 | |
|
|
23d85de155 | |
|
|
433813115f | |
|
|
559b95819a | |
|
|
e88ed99d6b | |
|
|
5544df9bd7 | |
|
|
7dab4d4890 | |
|
|
406067497e | |
|
|
0dd3077eb5 | |
|
|
16e1994ed1 | |
|
|
c4cfa40577 | |
|
|
3a4ce2311e | |
|
|
2e35ea9c13 | |
|
|
b594bd2007 | |
|
|
58fcff8618 | |
|
|
3daca5336e | |
|
|
069cff2402 | |
|
|
6bf3df0418 | |
|
|
ca85e454f9 |
|
|
@ -1,5 +1,9 @@
|
|||
2025.09.01:
|
||||
2025.09.04:
|
||||
. 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:
|
||||
. Improved log trimming.
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ class ConfigHandler:
|
|||
"order_size": self.broker.get_default_order_size(),
|
||||
"no_of_safety_orders": 30,
|
||||
"max_short_safety_orders": 45,
|
||||
"concurrent_safety_orders": 3,
|
||||
"boosted_concurrent_safety_orders": 5,
|
||||
"safety_order_deviance": 2,
|
||||
"safety_order_scale": 0.0105,
|
||||
"dynamic_so_deviance": True,
|
||||
|
|
@ -35,6 +37,9 @@ class ConfigHandler:
|
|||
"force_restart_if_retries_exhausted": False,
|
||||
"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_dictionary = self.default_config_dictionary.copy()
|
||||
|
||||
|
|
@ -68,6 +73,12 @@ class ConfigHandler:
|
|||
def get_max_short_safety_orders(self):
|
||||
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):
|
||||
return self.config_dictionary["safety_order_deviance"]
|
||||
|
||||
|
|
@ -173,6 +184,20 @@ class ConfigHandler:
|
|||
self.config_dictionary["max_short_safety_orders"] = max_short_safety_orders
|
||||
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):
|
||||
# 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())
|
||||
|
|
|
|||
|
|
@ -379,6 +379,11 @@ class Broker:
|
|||
if self.get_exchange_name()=="binance":
|
||||
a = self.exchange.fetch_last_prices(pair_list)
|
||||
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:
|
||||
a = self.exchange.fetch_tickers()
|
||||
if pair_list is None:
|
||||
|
|
@ -533,7 +538,7 @@ class Broker:
|
|||
if pairs is None:
|
||||
pairs = []
|
||||
try:
|
||||
if self.get_exchange_name()=="binance":
|
||||
if self.get_exchange_name()in ["binance","kucoin"]:
|
||||
return self.get_opened_orders_binance(pairs)
|
||||
return self.get_opened_orders()
|
||||
except Exception as e:
|
||||
|
|
@ -581,7 +586,7 @@ class Broker:
|
|||
return []
|
||||
|
||||
|
||||
def get_closed_orders(self,no_retries=False): #It should return a list of all opened orders
|
||||
def get_closed_orders(self,pair=None,no_retries=False): #It should return a list of all opened orders
|
||||
'''
|
||||
Returns a list of all the open orders on the exchange
|
||||
|
||||
|
|
@ -592,7 +597,7 @@ class Broker:
|
|||
retries = self.retries
|
||||
while retries>0:
|
||||
try:
|
||||
return self.exchange.fetch_closed_orders()
|
||||
return self.exchange.fetch_closed_orders(pair)
|
||||
except Exception as e:
|
||||
self.logger.log_this(f"Exception in get_closed_orders: {e}",1)
|
||||
if no_retries:
|
||||
|
|
@ -654,16 +659,15 @@ class Broker:
|
|||
:return: 0 if order was succesfully canceled, 1 if not
|
||||
'''
|
||||
|
||||
pair = symbol
|
||||
tries = self.retries//2
|
||||
while tries>0:
|
||||
try:
|
||||
while self.get_order(id,pair)["status"]=="open":
|
||||
self.exchange.cancel_order(id,symbol=pair)
|
||||
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,pair)["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:
|
||||
|
|
@ -724,26 +728,25 @@ class Broker:
|
|||
'''
|
||||
|
||||
retries = self.retries//2
|
||||
pair = symbol
|
||||
while retries>0:
|
||||
try:
|
||||
if self.get_exchange_name()=="gateio" and side=="buy" and not amount_in_base:
|
||||
new_order = self.exchange.create_market_buy_order_with_cost(pair, size)
|
||||
new_order = self.exchange.create_market_buy_order_with_cost(symbol, size)
|
||||
else:
|
||||
order_book = self.get_order_book(symbol)
|
||||
if order_book=={}:
|
||||
self.logger.log_this(f"new_simulated_market_order. Order book returned an empty dictionary",1,symbol)
|
||||
return self.empty_order
|
||||
if amount_in_base or side!="buy":
|
||||
base_amount = self.amount_to_precision(pair,size)
|
||||
base_amount = self.amount_to_precision(symbol,size)
|
||||
else:
|
||||
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)
|
||||
price = self.find_minimum_viable_price(order_book,base_amount,side)
|
||||
#Maybe check for slippage here instead of within the trader itself? idk
|
||||
new_order = self.exchange.create_order(pair,"limit",side,base_amount,price)
|
||||
new_order = self.exchange.create_order(symbol,"limit",side,base_amount,price)
|
||||
time.sleep(self.wait_time)
|
||||
return self.get_order(new_order["id"],pair)
|
||||
return self.get_order(new_order["id"],symbol)
|
||||
except Exception as e:
|
||||
self.logger.log_this(f"new_simulated_market_order exception: {e}",1,symbol)
|
||||
if no_retries:
|
||||
|
|
@ -802,24 +805,23 @@ class Broker:
|
|||
if self.broker_config["simulate_market_orders"]:
|
||||
return self.new_simulated_market_order(symbol,size,side,amount_in_base=amount_in_base)
|
||||
retries = self.retries
|
||||
pair = symbol
|
||||
while retries>0:
|
||||
try:
|
||||
if side=="buy":
|
||||
to_buy = float(size)
|
||||
if not amount_in_base:
|
||||
to_buy = float(size)/self.get_top_ask_price(pair)
|
||||
amount = self.amount_to_precision(pair,to_buy)
|
||||
to_buy = float(size)/self.get_top_ask_price(symbol)
|
||||
amount = self.amount_to_precision(symbol,to_buy)
|
||||
else:
|
||||
amount = self.amount_to_precision(pair,size) #Market sell orders are always nominated in base currency
|
||||
amount = self.amount_to_precision(symbol,size) #Market sell orders are always nominated in base currency
|
||||
|
||||
order_to_send = self.exchange.create_order(pair,"market",side,amount)
|
||||
order_to_send = self.exchange.create_order(symbol,"market",side,amount)
|
||||
time.sleep(self.wait_time)
|
||||
# Wait a bit more when dealing with Kucoin
|
||||
|
||||
return self.get_order(order_to_send["id"],pair)
|
||||
return self.get_order(order_to_send["id"],symbol)
|
||||
except Exception as e:
|
||||
self.logger.log_this(f"Exception in new_market_order: {e}",1,pair)
|
||||
self.logger.log_this(f"Exception in new_market_order: {e}",1,symbol)
|
||||
if no_retries:
|
||||
break
|
||||
time.sleep(self.wait_time)
|
||||
|
|
@ -867,6 +869,40 @@ class Broker:
|
|||
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):
|
||||
'''
|
||||
Sends a new limit order.
|
||||
|
|
@ -879,20 +915,13 @@ class Broker:
|
|||
'''
|
||||
|
||||
tries = self.retries
|
||||
pair = symbol
|
||||
while tries>=0:
|
||||
try:
|
||||
order_to_send = self.exchange.create_order(pair,"limit",side,self.amount_to_precision(pair,size),price)
|
||||
order_to_send = self.exchange.create_order(symbol,"limit",side,self.amount_to_precision(symbol,size),price)
|
||||
time.sleep(self.wait_time)
|
||||
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
|
||||
|
||||
return self.get_order(order_to_send["id"],symbol)
|
||||
except Exception as e:
|
||||
self.logger.log_this(f"Exception in new_limit_order - Side: {side} - Size: {size} - {self.amount_to_precision(pair,size)} - Exception: {e}",1,symbol)
|
||||
self.logger.log_this(f"Exception in new_limit_order - Side: {side} - Size: {size} - {self.amount_to_precision(symbol,size)} - Exception: {e}",1,symbol)
|
||||
if self.not_enough_balance_error(e):
|
||||
if tries<=self.retries//2: #Halves the amount of retries if there is a balance error.
|
||||
return 1
|
||||
|
|
@ -923,10 +952,9 @@ class Broker:
|
|||
if id=="":
|
||||
return self.empty_order
|
||||
tries = self.retries
|
||||
pair = symbol
|
||||
while tries>0:
|
||||
try:
|
||||
return self.exchange.fetch_order(id,symbol=pair)
|
||||
return self.exchange.fetch_order(id,symbol)
|
||||
except Exception as e:
|
||||
self.logger.log_this(f"Exception in get_order: {e}",1,symbol)
|
||||
if no_retries:
|
||||
|
|
@ -944,10 +972,9 @@ class Broker:
|
|||
:return: The market information.
|
||||
'''
|
||||
tries = self.retries
|
||||
pair = symbol
|
||||
while tries>0:
|
||||
try:
|
||||
return self.exchange.market(pair)
|
||||
return self.exchange.market(symbol)
|
||||
except Exception as e:
|
||||
self.logger.log_this(f"Exception in fetch_market: {e}",1,symbol)
|
||||
if no_retries:
|
||||
|
|
@ -965,10 +992,9 @@ class Broker:
|
|||
:return: The ticker information.
|
||||
'''
|
||||
tries = self.retries
|
||||
pair = symbol
|
||||
while tries>0:
|
||||
try:
|
||||
return self.exchange.fetch_ticker(pair)
|
||||
return self.exchange.fetch_ticker(symbol)
|
||||
except Exception as e:
|
||||
self.logger.log_this(f"Exception in get_ticker: {e}")
|
||||
if no_retries:
|
||||
|
|
@ -1032,8 +1058,8 @@ class Broker:
|
|||
|
||||
:param pair: pair
|
||||
:return: step size
|
||||
|
||||
'''
|
||||
|
||||
market = self.fetch_market(pair)
|
||||
if market is None:
|
||||
return None
|
||||
|
|
@ -1133,22 +1159,14 @@ class Logger:
|
|||
|
||||
#Append to log list
|
||||
self.log_list.append(text)
|
||||
|
||||
#Trim log list
|
||||
#self.log_list = self.log_list[-self.log_list_max_length:]
|
||||
|
||||
except Exception as e:
|
||||
print("Can't write log file")
|
||||
print(e)
|
||||
|
||||
if level<1:
|
||||
self.send_tg_message(f"{self.broker_config['exchange'].capitalize()} | {pair_data}{message}",ignore_config=level==-1)
|
||||
|
||||
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
|
||||
|
||||
|
||||
version = "2025.09.01"
|
||||
version = "2025.09.04"
|
||||
|
||||
'''
|
||||
Color definitions. If you want to change them, check the reference at https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
|
||||
|
|
@ -39,6 +39,7 @@ 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.
|
||||
executor = None
|
||||
|
||||
#Shutdown handler
|
||||
def shutdown_handler(signum, _):
|
||||
broker.logger.log_this(f"Received signal {signum}, shutting down as gracefully as possible...", 2)
|
||||
if executor:
|
||||
|
|
@ -86,7 +87,7 @@ def time_to_unix(year: str, month: str, day: str) -> int:
|
|||
return 0
|
||||
|
||||
|
||||
def import_instance(base: str, quote: str, forced_tp_id = None, forced_so_id = None) -> int:
|
||||
def import_instance(base: str, quote: str) -> int:
|
||||
'''
|
||||
Imports an previously running trader instance from the status file.
|
||||
|
||||
|
|
@ -98,7 +99,7 @@ def import_instance(base: str, quote: str, forced_tp_id = None, forced_so_id = N
|
|||
int: 0 if successful
|
||||
'''
|
||||
broker.logger.log_this(f"Importing {base}/{quote}")
|
||||
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))
|
||||
instances_to_add.append(trader.trader(broker,f"{base}/{quote}",is_import=True))
|
||||
if f"{base}{quote}" not in tickers:
|
||||
tickers.append(f"{base}{quote}")
|
||||
return 0
|
||||
|
|
@ -247,7 +248,7 @@ def restart_pair_no_json(base: str, quote: str) -> int:
|
|||
symbol = f"{base}/{quote}"
|
||||
order_list = broker.fetch_full_orders(tickers)
|
||||
for instance in running_traders:
|
||||
if symbol==instance.config.get_pair():
|
||||
if symbol==instance.status.get_pair():
|
||||
instance.pause = True
|
||||
#Backing up old status file
|
||||
instance.status.save_to_file(is_backup=True)
|
||||
|
|
@ -262,7 +263,7 @@ def restart_pair_no_json(base: str, quote: str) -> int:
|
|||
try:
|
||||
running_traders.remove(instance)
|
||||
except ValueError:
|
||||
broker.logger.log_this(f"Instance {instance.config.get_pair()} not found in running_traders.",1,instance.config.get_pair())
|
||||
broker.logger.log_this(f"Instance {instance.status.get_pair()} not found in running_traders.",1,instance.status.get_pair())
|
||||
add_instance(base,quote)
|
||||
return 0
|
||||
return 1
|
||||
|
|
@ -276,7 +277,7 @@ def main_routine():
|
|||
global reload_interval
|
||||
global screen_buffer
|
||||
|
||||
executor = ThreadPoolExecutor(max_workers=len(running_traders)+worker_threads_overprovisioning)
|
||||
executor = ThreadPoolExecutor(max_workers=len(broker.get_config()["pairs"])+worker_threads_overprovisioning)
|
||||
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__}"
|
||||
separator_line = blue + "="*80 + white
|
||||
|
|
@ -285,12 +286,12 @@ def main_routine():
|
|||
#Restart traders that have the restart flag raised and remove traders that have the quit flag raised
|
||||
for instance in running_traders:
|
||||
if instance.restart and instance.config.get_attempt_restart():
|
||||
broker.logger.log_this(f"Restarting trader",1,instance.config.get_pair())
|
||||
broker.logger.log_this(f"Restarting trader",1,instance.status.get_pair())
|
||||
restart_pair_no_json(instance.base,instance.quote)
|
||||
if instance.quit:
|
||||
#Here, check if a duster is needed
|
||||
broker.logger.log_this(f"{broker.get_exchange_name()} | Quit flag raised, removing trader.",0,instance.config.get_pair())
|
||||
broker.logger.log_this(f"{broker.get_exchange_name()} | Quit flag raised, removing trader: {instance.config.get_pair()}",-1) #Forced message to TG
|
||||
broker.logger.log_this(f"{broker.get_exchange_name()} | Quit flag raised, removing trader.",0,instance.status.get_pair())
|
||||
broker.logger.log_this(f"{broker.get_exchange_name()} | Quit flag raised, removing trader: {instance.status.get_pair()}",-1) #Forced message to TG
|
||||
if f"{instance.base}{instance.quote}" in tickers:
|
||||
tickers.remove(f"{instance.base}{instance.quote}")
|
||||
broker.remove_pair_from_config(f"{instance.base}{instance.quote}")
|
||||
|
|
@ -298,7 +299,7 @@ def main_routine():
|
|||
try:
|
||||
running_traders.remove(instance)
|
||||
except ValueError:
|
||||
broker.logger.log_this(f"Instance {instance.config.get_pair()} not found in running_traders.",1,instance.config.get_pair())
|
||||
broker.logger.log_this(f"Instance {instance.status.get_pair()} not found in running_traders.",1,instance.status.get_pair())
|
||||
|
||||
#Adds pending traders
|
||||
if bool(instances_to_add):
|
||||
|
|
@ -310,21 +311,21 @@ def main_routine():
|
|||
futures = []
|
||||
pairs_to_fetch = []
|
||||
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:
|
||||
future = executor.submit(instance.check_status, open_orders)
|
||||
futures.append(future)
|
||||
online_pairs.append(f"{instance.base}{instance.quote}")
|
||||
pairs_to_fetch.append(instance.config.get_pair())
|
||||
|
||||
#Delete no longer used data
|
||||
del open_orders
|
||||
|
||||
#Fetch prices
|
||||
price_list = broker.get_prices(pairs_to_fetch)
|
||||
#Here, assign the prices to the dusters (if any)
|
||||
|
||||
|
||||
for future in as_completed(futures):
|
||||
try:
|
||||
future.result()
|
||||
|
|
@ -339,21 +340,21 @@ def main_routine():
|
|||
global_status["paused_traders"].clear()
|
||||
for instance in running_traders:
|
||||
if not instance.config.get_is_short():
|
||||
curr += int(instance.get_status_dict()["so_amount"]) # For the safety order occupancy percentage calculation
|
||||
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(): # status_strings
|
||||
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.config.get_pair() in price_list and price_list[instance.config.get_pair()] is not None:
|
||||
instance.get_status_dict()["price"] = price_list[instance.config.get_pair()]
|
||||
if instance.status.get_pair() in price_list and price_list[instance.status.get_pair()] is not None:
|
||||
instance.get_status_dict()["price"] = price_list[instance.status.get_pair()]
|
||||
except Exception as e:
|
||||
broker.logger.log_this(f"Exception while querying for pair price, key not present on price_list dictionary: {e}",1,instance.config.get_pair())
|
||||
broker.logger.log_this(f"Exception while querying for pair price, key not present on price_list dictionary: {e}",1,instance.status.get_pair())
|
||||
|
||||
#Add paused traders to the paused trader list
|
||||
if instance.pause:
|
||||
global_status["paused_traders"].append(instance.config.get_pair())
|
||||
global_status["paused_traders"].append(instance.status.get_pair())
|
||||
paused_traders_status_strings.append(f"{cyan}Paused pairs: {list(global_status['paused_traders'])}{white}")
|
||||
|
||||
#Delete no longer used data
|
||||
|
|
@ -368,7 +369,7 @@ def main_routine():
|
|||
|
||||
#Updates some global status variables prior to deletion of those
|
||||
if len(running_traders)!=len(global_status["online_workers"]):
|
||||
global_status["online_workers"] = [instance.config.get_pair() for instance in running_traders]
|
||||
global_status["online_workers"] = [instance.status.get_pair() for instance in running_traders]
|
||||
|
||||
#Prints general info
|
||||
instance_uptime = int(time.time()) - instance_start_time
|
||||
|
|
@ -404,7 +405,7 @@ def main_routine():
|
|||
#Toggle pauses
|
||||
if toggle_pauses:
|
||||
for instance in running_traders:
|
||||
if instance.config.get_pair() in toggle_pauses:
|
||||
if instance.status.get_pair() in toggle_pauses:
|
||||
instance.pause = not instance.pause
|
||||
toggle_pauses.clear()
|
||||
|
||||
|
|
@ -690,9 +691,7 @@ def import_pair():
|
|||
data = request.json
|
||||
base = data["base"]
|
||||
quote = data["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)
|
||||
return unwrapped_import_pair(base,quote)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return jsonify({'Error': 'Halp'})
|
||||
|
|
@ -850,6 +849,58 @@ def mod_order_size():
|
|||
return jsonify({'Error': 'Halp'})
|
||||
|
||||
|
||||
@base_api.route("/mod_concurrent_safety_orders", methods=['POST'])
|
||||
def mod_concurrent_safety_orders():
|
||||
'''
|
||||
POST request
|
||||
|
||||
Parameters:
|
||||
base: str
|
||||
quote: str
|
||||
amount: int
|
||||
'''
|
||||
|
||||
if not "X-API-KEY" in request.headers or not request.headers.get("X-API-KEY") in valid_keys:
|
||||
return jsonify({'Error': 'API key invalid'}), 401
|
||||
try:
|
||||
if request.json is None:
|
||||
return jsonify({'Error': 'request.json is None'})
|
||||
data = request.json
|
||||
base = data["base"]
|
||||
quote = data["quote"]
|
||||
amount = data["amount"]
|
||||
return unwrapped_mod_concurrent_safety_orders(base,quote,amount)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return jsonify({'Error': 'Halp'})
|
||||
|
||||
|
||||
@base_api.route("/mod_boosted_concurrent_safety_orders", methods=['POST'])
|
||||
def mod_boosted_concurrent_safety_orders():
|
||||
'''
|
||||
POST request
|
||||
|
||||
Parameters:
|
||||
base: str
|
||||
quote: str
|
||||
amount: int
|
||||
'''
|
||||
|
||||
if not "X-API-KEY" in request.headers or not request.headers.get("X-API-KEY") in valid_keys:
|
||||
return jsonify({'Error': 'API key invalid'}), 401
|
||||
try:
|
||||
if request.json is None:
|
||||
return jsonify({'Error': 'request.json is None'})
|
||||
data = request.json
|
||||
base = data["base"]
|
||||
quote = data["quote"]
|
||||
amount = data["amount"]
|
||||
return unwrapped_mod_boosted_concurrent_safety_orders(base,quote,amount)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return jsonify({'Error': 'Halp'})
|
||||
|
||||
|
||||
@base_api.route("/mod_default_order_size", methods=['POST'])
|
||||
def mod_default_order_size():
|
||||
'''
|
||||
|
|
@ -1060,7 +1111,7 @@ def toggle_cleanup():
|
|||
return jsonify({'Error': 'Halp'})
|
||||
|
||||
|
||||
@base_api.route("/toggle_autoswitch", methods=['POST']) #type:ignore
|
||||
@base_api.route("/toggle_autoswitch", methods=['POST'])
|
||||
def toggle_autoswitch():
|
||||
'''
|
||||
POST request
|
||||
|
|
@ -1084,8 +1135,32 @@ def toggle_autoswitch():
|
|||
return jsonify({'Error': 'Halp'})
|
||||
|
||||
|
||||
@base_api.route("/toggle_liquidate_after_switch", methods=['POST']) #type:ignore
|
||||
def toggle_liquidate_after_switch(): #type:ignore
|
||||
@base_api.route("/force_trader_close", methods=['POST'])
|
||||
def force_trader_close():
|
||||
'''
|
||||
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
|
||||
|
||||
|
|
@ -1108,7 +1183,7 @@ def toggle_liquidate_after_switch(): #type:ignore
|
|||
return jsonify({'Error': 'Halp'})
|
||||
|
||||
|
||||
@base_api.route("/toggle_check_old_long_price", methods=['POST'])#type:ignore
|
||||
@base_api.route("/toggle_check_old_long_price", methods=['POST'])
|
||||
def toggle_check_old_long_price():
|
||||
'''
|
||||
POST request
|
||||
|
|
@ -1338,30 +1413,7 @@ def reload_markets():
|
|||
return unwrapped_reload_markets()
|
||||
|
||||
|
||||
@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
|
||||
@base_api.route("/reload_trader_config", methods=['POST'])
|
||||
def reload_trader_config():
|
||||
'''
|
||||
POST request
|
||||
|
|
@ -1453,7 +1505,7 @@ def unwrapped_add_pair(base,quote):
|
|||
|
||||
#Check if the trader is already running
|
||||
for instance in running_traders:
|
||||
if symbol==instance.config.get_pair():
|
||||
if symbol==instance.status.get_pair():
|
||||
broker.logger.log_this(f"Pair already running",1,symbol)
|
||||
return jsonify({"Error": "Pair already running"})
|
||||
|
||||
|
|
@ -1487,7 +1539,7 @@ def unwrapped_remove_pair(base,quote):
|
|||
try:
|
||||
symbol = f"{base}/{quote}"
|
||||
for instance in running_traders:
|
||||
if symbol==instance.config.get_pair():
|
||||
if symbol==instance.status.get_pair():
|
||||
instance.quit = True
|
||||
return jsonify({"Success": "Pair to be removed"})
|
||||
except Exception as e:
|
||||
|
|
@ -1512,15 +1564,13 @@ def unwrapped_restart_pair(base,quote):
|
|||
return jsonify({"Error": "Halp"})
|
||||
|
||||
|
||||
def unwrapped_import_pair(base,quote,forced_tp_id = None, forced_so_id = None):
|
||||
def unwrapped_import_pair(base,quote):
|
||||
'''
|
||||
Imports a previously running pair
|
||||
|
||||
Parameters:
|
||||
base (str): The base 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:
|
||||
jsonified dictionary detailing the outcome of the operation.
|
||||
|
|
@ -1528,7 +1578,7 @@ def unwrapped_import_pair(base,quote,forced_tp_id = None, forced_so_id = None):
|
|||
|
||||
try:
|
||||
symbol = f"{base}/{quote}"
|
||||
import_instance(base,quote,forced_tp_id,forced_so_id)
|
||||
import_instance(base,quote)
|
||||
broker.add_pair_to_config(f"{base}{quote}")
|
||||
broker.rewrite_config_file()
|
||||
broker.logger.log_this(f"Done",2,symbol)
|
||||
|
|
@ -1556,7 +1606,7 @@ def unwrapped_switch_to_long(base,quote,calculate_profits):
|
|||
if f"{base}{quote}" not in broker.get_pairs():
|
||||
return jsonify({"Error": "Pair not running"})
|
||||
for instance in running_traders:
|
||||
if f"{base}/{quote}"==instance.config.get_pair():
|
||||
if f"{base}/{quote}"==instance.status.get_pair():
|
||||
instance.pause = True
|
||||
if instance.switch_to_long(ignore_old_long=ignore_old_long)==1:
|
||||
return jsonify({"Error": "Error in switch_to_long()"})
|
||||
|
|
@ -1584,14 +1634,14 @@ def unwrapped_switch_to_short(base,quote):
|
|||
if f"{base}{quote}" not in broker.get_pairs():
|
||||
return jsonify({"Error": "Pair not running"})
|
||||
for instance in running_traders:
|
||||
if symbol==instance.config.get_pair() and instance.switch_to_short()==1:
|
||||
if symbol==instance.status.get_pair() and instance.switch_to_short()==1:
|
||||
return jsonify({"Error": "Error in switch_to_short()"})
|
||||
|
||||
#Restart instance
|
||||
try:
|
||||
broker.logger.log_this(f"Reinitializing trader",2,symbol)
|
||||
for instance in running_traders:
|
||||
if symbol==instance.config.get_pair():
|
||||
if symbol==instance.status.get_pair():
|
||||
instance.status.set_take_profit_order(instance.broker.empty_order)
|
||||
instance.so = instance.broker.empty_order
|
||||
|
||||
|
|
@ -1640,7 +1690,7 @@ def unwrapped_load_old_long(base,quote):
|
|||
|
||||
#Creates (or modifies) a key in the status dictionary and assigns the contents of the file to that same key.
|
||||
for instance in running_traders:
|
||||
if instance.config.get_pair()==symbol:
|
||||
if instance.status.get_pair()==symbol:
|
||||
instance.get_status_dict()["old_long"]=old_long
|
||||
instance.update_status(True)
|
||||
return jsonify({"Success": "old_long file loaded to status_dict"})
|
||||
|
|
@ -1667,7 +1717,7 @@ def unwrapped_view_old_long(base,quote,from_file):
|
|||
old_long = load(ol)
|
||||
return jsonify(old_long)
|
||||
for instance in running_traders:
|
||||
if symbol==instance.config.get_pair():
|
||||
if symbol==instance.status.get_pair():
|
||||
if "old_long" in instance.get_status_dict():
|
||||
return jsonify(instance.get_status_dict()["old_long"])
|
||||
return jsonify({"Error": "No old_long info found"})
|
||||
|
|
@ -1693,7 +1743,7 @@ def unwrapped_switch_to_long_price(base,quote):
|
|||
try:
|
||||
symbol = f"{base}/{quote}"
|
||||
for instance in running_traders:
|
||||
if symbol==instance.config.get_pair():
|
||||
if symbol==instance.status.get_pair():
|
||||
if "old_long" in instance.get_status_dict():
|
||||
#minimum_switch_price = (old_target - quote_already_in)/base_left
|
||||
old_target = instance.get_status_dict()["old_long"]["tp_price"]*instance.get_status_dict()["old_long"]["tp_amount"]
|
||||
|
|
@ -1724,7 +1774,7 @@ def unwrapped_add_safety_orders(base,quote,amount):
|
|||
try:
|
||||
symbol = f"{base}/{quote}"
|
||||
for instance in running_traders:
|
||||
if symbol==instance.config.get_pair():
|
||||
if symbol==instance.status.get_pair():
|
||||
instance.pause = True
|
||||
#x.no_of_safety_orders += int(amount)
|
||||
instance.config.set_no_of_safety_orders(instance.config.get_no_of_safety_orders()+int(amount))
|
||||
|
|
@ -1755,7 +1805,7 @@ def unwrapped_base_add_so_calculation(base,quote):
|
|||
try:
|
||||
symbol = f"{base}/{quote}"
|
||||
for instance in running_traders:
|
||||
if symbol==instance.config.get_pair():
|
||||
if symbol==instance.status.get_pair():
|
||||
free_base = instance.fetch_free_base()
|
||||
if free_base is None:
|
||||
return jsonify({"Error": "Can't fetch amount of free base on the exchange"})
|
||||
|
|
@ -1783,7 +1833,7 @@ def unwrapped_mod_tp_level(base,quote,amount):
|
|||
try:
|
||||
symbol = f"{base}/{quote}"
|
||||
for instance in running_traders:
|
||||
if symbol==instance.config.get_pair():
|
||||
if symbol==instance.status.get_pair():
|
||||
instance.config.set_tp_level(float(amount))
|
||||
broker.logger.log_this("Done. The change will take effect when the next take profit order is placed",2,symbol)
|
||||
return jsonify({"Success": "Success. The change will take effect when the next TP order is placed"})
|
||||
|
|
@ -1808,8 +1858,9 @@ def unwrapped_mod_order_size(base,quote,amount):
|
|||
try:
|
||||
symbol = f"{base}/{quote}"
|
||||
for instance in running_traders:
|
||||
if symbol==instance.config.get_pair():
|
||||
if symbol==instance.status.get_pair():
|
||||
instance.config.set_order_size(float(amount))
|
||||
instance.config.save_to_file()
|
||||
broker.logger.log_this("Done. The change will take effect when the next deal is started",2,symbol)
|
||||
return jsonify({"Success": "Success. The change will take effect when the next deal is started"})
|
||||
except Exception:
|
||||
|
|
@ -1817,6 +1868,58 @@ def unwrapped_mod_order_size(base,quote,amount):
|
|||
return jsonify({"Error": "Error changing order size"})
|
||||
|
||||
|
||||
def unwrapped_mod_concurrent_safety_orders(base,quote,amount):
|
||||
'''
|
||||
Modifies the amount of safety orders that a trader keeps opened at the same time.
|
||||
|
||||
Parameters:
|
||||
base (str): The base currency of the pair.
|
||||
quote (str): The quote currency of the pair.
|
||||
amount (str): The new amount.
|
||||
|
||||
Returns:
|
||||
jsonify: A jsonified dictionary detailing the outcome of the operation
|
||||
'''
|
||||
|
||||
try:
|
||||
symbol = f"{base}/{quote}"
|
||||
for instance in running_traders:
|
||||
if symbol==instance.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):
|
||||
'''
|
||||
Modifies the default order size of a broker.
|
||||
|
|
@ -1872,7 +1975,7 @@ def unwrapped_last_call(base,quote):
|
|||
try:
|
||||
symbol = f"{base}/{quote}"
|
||||
for instance in running_traders:
|
||||
if symbol==instance.config.get_pair():
|
||||
if symbol==instance.status.get_pair():
|
||||
instance.status.set_stop_when_profit(not instance.status.get_stop_when_profit())
|
||||
instance.update_status(True)
|
||||
if instance.status.get_stop_when_profit():
|
||||
|
|
@ -1906,7 +2009,7 @@ def unwrapped_deferred_last_call(base,quote,yyyymmdd):
|
|||
if limit==0:
|
||||
return jsonify({"Error": "Can't convert date to unix"})
|
||||
for instance in running_traders:
|
||||
if f"{base}{quote}"==instance.config.get_pair():
|
||||
if f"{base}{quote}"==instance.status.get_pair():
|
||||
instance.config.set_programmed_stop_time(limit)
|
||||
instance.config.set_programmed_stop(True)
|
||||
#save config file to disk
|
||||
|
|
@ -1933,7 +2036,7 @@ def unwrapped_toggle_pause(base,quote):
|
|||
symbol = f"{base}/{quote}"
|
||||
toggle_pauses.append(symbol)
|
||||
for instance in running_traders:
|
||||
if instance.config.get_pair()==symbol:
|
||||
if instance.status.get_pair()==symbol:
|
||||
if instance.pause:
|
||||
instance.status.set_pause_reason("")
|
||||
return jsonify({"Success": "Trader will be resumed"})
|
||||
|
|
@ -1992,55 +2095,55 @@ def unwrapped_add_quote(base,quote,amount):
|
|||
'''
|
||||
|
||||
for instance in running_traders:
|
||||
if f"{base}/{quote}"==instance.config.get_pair():
|
||||
if f"{base}/{quote}"==instance.status.get_pair():
|
||||
if instance.config.get_is_short():
|
||||
return jsonify({"Error": "Quote can't be added to short traders"})
|
||||
instance.pause = True
|
||||
new_average_price = (instance.status.get_quote_spent()+float(amount))/(instance.status.get_base_bought()+(float(amount)/instance.status.get_price()))
|
||||
broker.logger.log_this(f"Your new average buy price will be {new_average_price} {quote}",2,instance.config.get_pair())
|
||||
broker.logger.log_this(f"Your new take profit price price will be {new_average_price*instance.get_tp_level()} {quote}",2,instance.config.get_pair())
|
||||
new_order = broker.new_market_order(instance.config.get_pair(),float(amount),"buy")
|
||||
broker.logger.log_this(f"Your new average buy price will be {new_average_price} {quote}",2,instance.status.get_pair())
|
||||
broker.logger.log_this(f"Your new take profit price price will be {new_average_price*instance.get_tp_level()} {quote}",2,instance.status.get_pair())
|
||||
new_order = broker.new_market_order(instance.status.get_pair(),float(amount),"buy")
|
||||
if new_order is None:
|
||||
broker.logger.log_this("Error: Market order returned None",2,instance.config.get_pair())
|
||||
broker.logger.log_this("Error: Market order returned None",2,instance.status.get_pair())
|
||||
instance.pause = False
|
||||
return jsonify({"Error": "Market order returned None"})
|
||||
while True:
|
||||
time.sleep(broker.get_wait_time())
|
||||
returned_order = broker.get_order(new_order["id"],instance.config.get_pair())
|
||||
returned_order = broker.get_order(new_order["id"],instance.status.get_pair())
|
||||
if returned_order==broker.empty_order:
|
||||
broker.logger.log_this("Problems sending the order",2,instance.config.get_pair())
|
||||
broker.logger.log_this("Problems sending the order",2,instance.status.get_pair())
|
||||
instance.pause = False
|
||||
return jsonify({"Error": "Problems sending the order"})
|
||||
elif returned_order["status"]=="expired":
|
||||
instance.pause = False
|
||||
return jsonify({"Error": "New order expired"})
|
||||
elif returned_order["status"]=="closed":
|
||||
broker.logger.log_this("Order sent",2,instance.config.get_pair())
|
||||
broker.logger.log_this("Order sent",2,instance.status.get_pair())
|
||||
new_fees_in_base, new_fees_in_quote = instance.parse_fees(returned_order)
|
||||
instance.status.set_fees_paid_in_base(instance.status.get_fees_paid_in_base() + new_fees_in_base)
|
||||
instance.status.set_fees_paid_in_quote(instance.status.get_fees_paid_in_quote() + new_fees_in_quote)
|
||||
instance.status.set_base_bought(instance.status.get_base_bought() + returned_order["filled"] - new_fees_in_base)
|
||||
instance.status.set_quote_spent(instance.status.get_quote_spent()+returned_order["cost"])
|
||||
broker.logger.log_this("Cancelling old take profit order and sending a new one",2,instance.config.get_pair())
|
||||
broker.logger.log_this("Cancelling old take profit order and sending a new one",2,instance.status.get_pair())
|
||||
attempts = 5
|
||||
while broker.cancel_order(instance.status.get_take_profit_order()["id"],instance.config.get_pair())==1:
|
||||
broker.logger.log_this("Can't cancel old take profit order, retrying...",2,instance.config.get_pair())
|
||||
while broker.cancel_order(instance.status.get_take_profit_order()["id"],instance.status.get_pair())==1:
|
||||
broker.logger.log_this("Can't cancel old take profit order, retrying...",2,instance.status.get_pair())
|
||||
time.sleep(broker.get_wait_time())
|
||||
attempts-=1
|
||||
if attempts==0:
|
||||
broker.logger.log_this("Can't cancel old take profit order, cancelling...",2,instance.config.get_pair())
|
||||
broker.logger.log_this("Can't cancel old take profit order, cancelling...",2,instance.status.get_pair())
|
||||
instance.pause = False
|
||||
return jsonify({"Error": "Can't cancel old take profit order."})
|
||||
instance.status.set_take_profit_price(instance.status.get_quote_spent()/instance.status.get_base_bought()*instance.get_tp_level())
|
||||
instance.status.set_take_profit_order(broker.new_limit_order(instance.config.get_pair(),instance.status.get_base_bought(),"sell",instance.status.get_take_profit_price()))
|
||||
instance.status.set_take_profit_order(broker.new_limit_order(instance.status.get_pair(),instance.status.get_base_bought(),"sell",instance.status.get_take_profit_price()))
|
||||
instance.update_status(True)
|
||||
break
|
||||
else:
|
||||
broker.logger.log_this("Waiting for initial order to get filled",2,instance.config.get_pair())
|
||||
broker.logger.log_this(f"{returned_order}",2,instance.config.get_pair())
|
||||
broker.logger.log_this("Waiting for initial order to get filled",2,instance.status.get_pair())
|
||||
broker.logger.log_this(f"{returned_order}",2,instance.status.get_pair())
|
||||
time.sleep(broker.get_wait_time())
|
||||
instance.pause = False
|
||||
broker.logger.log_this("Done",2,instance.config.get_pair())
|
||||
broker.logger.log_this("Done",2,instance.status.get_pair())
|
||||
return jsonify({"Success": "Quote added successfully"})
|
||||
return jsonify({"Error": "Something horrible happened :S"})
|
||||
|
||||
|
|
@ -2078,7 +2181,7 @@ def unwrapped_toggle_cleanup(base,quote):
|
|||
try:
|
||||
symbol = f"{base}/{quote}"
|
||||
for instance in running_traders:
|
||||
if symbol==instance.config.get_pair():
|
||||
if symbol==instance.status.get_pair():
|
||||
instance.config.set_cleanup(not instance.config.get_cleanup())
|
||||
if instance.config.get_cleanup():
|
||||
return jsonify({"Success": "Cleanup turned ON"})
|
||||
|
|
@ -2089,6 +2192,32 @@ def unwrapped_toggle_cleanup(base,quote):
|
|||
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):
|
||||
'''
|
||||
Signals a trader to enable or disable autoswitch.
|
||||
|
|
@ -2104,7 +2233,7 @@ def unwrapped_toggle_autoswitch(base,quote):
|
|||
try:
|
||||
symbol = f"{base}/{quote}"
|
||||
for instance in running_traders:
|
||||
if symbol==instance.config.get_pair():
|
||||
if symbol==instance.status.get_pair():
|
||||
if instance.config.get_autoswitch():
|
||||
broker.logger.log_this("Autoswitch turned OFF",1,symbol)
|
||||
instance.config.set_autoswitch(False)
|
||||
|
|
@ -2113,6 +2242,7 @@ def unwrapped_toggle_autoswitch(base,quote):
|
|||
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)
|
||||
return jsonify({"Error": "Halp"})
|
||||
|
|
@ -2132,7 +2262,7 @@ def unwrapped_toggle_liquidate_after_switch(base,quote):
|
|||
try:
|
||||
symbol = f"{base}/{quote}"
|
||||
for instance in running_traders:
|
||||
if symbol==instance.config.get_pair():
|
||||
if symbol==instance.status.get_pair():
|
||||
if instance.config.get_liquidate_after_switch():
|
||||
broker.logger.log_this("Liquidate after switch turned OFF",1,symbol)
|
||||
instance.config.set_liquidate_after_switch(False)
|
||||
|
|
@ -2141,10 +2271,12 @@ def unwrapped_toggle_liquidate_after_switch(base,quote):
|
|||
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)
|
||||
return jsonify({"Error": "Halp"})
|
||||
|
||||
|
||||
def unwrapped_toggle_check_old_long_price(base,quote):
|
||||
'''
|
||||
Signals to the trader if it should compare the current price to the old_long price stored in the old_long dictionary.
|
||||
|
|
@ -2160,7 +2292,7 @@ def unwrapped_toggle_check_old_long_price(base,quote):
|
|||
try:
|
||||
symbol = f"{base}/{quote}"
|
||||
for instance in running_traders:
|
||||
if symbol==instance.config.get_pair():
|
||||
if symbol==instance.status.get_pair():
|
||||
if instance.config.get_check_old_long_price():
|
||||
broker.logger.log_this("Check OFF",1,symbol)
|
||||
instance.config.set_check_old_long_price(False)
|
||||
|
|
@ -2169,6 +2301,7 @@ def unwrapped_toggle_check_old_long_price(base,quote):
|
|||
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)
|
||||
return jsonify({"Error": "Halp"})
|
||||
|
|
@ -2190,7 +2323,7 @@ def unwrapped_switch_quote_currency(base,quote,new_quote):
|
|||
try:
|
||||
symbol = f"{base}/{quote}"
|
||||
for trader in running_traders:
|
||||
if symbol==trader.config.get_pair():
|
||||
if symbol==trader.status.get_pair():
|
||||
#Pause the trader
|
||||
trader.pause = True
|
||||
|
||||
|
|
@ -2356,29 +2489,6 @@ def unwrapped_reload_markets():
|
|||
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):
|
||||
'''
|
||||
Returns the balance of a given coin.
|
||||
|
|
@ -2413,7 +2523,7 @@ def unwrapped_reload_trader_config(base,quote):
|
|||
'''
|
||||
symbol = f"{base}/{quote}"
|
||||
for trader in running_traders:
|
||||
if trader.config.get_pair() == symbol:
|
||||
if trader.status.get_pair() == symbol:
|
||||
if trader.config.load_from_file()==0:
|
||||
return jsonify({"Success": "Config file reloaded"})
|
||||
return jsonify({"Error": "Error reloading config file"})
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@ class StatusHandler:
|
|||
"pair": f"{base}/{quote}",
|
||||
"take_profit_order": broker.get_empty_order(),
|
||||
"take_profit_price": 0.0,
|
||||
"safety_order": broker.get_empty_order(),
|
||||
"safety_orders": [],
|
||||
"safety_orders_filled": 0,
|
||||
"next_so_price": 0.0,
|
||||
"order_size": 0.0,
|
||||
"partial_profit": 0.0,
|
||||
|
|
@ -23,7 +24,7 @@ class StatusHandler:
|
|||
"quote_spent": 0.0,
|
||||
"base_bought": 0.0,
|
||||
"so_amount": 0,
|
||||
"no_of_safety_orders": "",
|
||||
"no_of_safety_orders": 0,
|
||||
"safety_price_table": [],
|
||||
"deal_uptime": 0.0,
|
||||
"total_uptime": 0.0,
|
||||
|
|
@ -58,8 +59,14 @@ class StatusHandler:
|
|||
def get_take_profit_price(self):
|
||||
return self.status_dictionary["take_profit_price"]
|
||||
|
||||
def get_safety_order(self):
|
||||
return self.status_dictionary["safety_order"]
|
||||
def get_safety_orders(self):
|
||||
"""
|
||||
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):
|
||||
return self.status_dictionary["next_so_price"]
|
||||
|
|
@ -148,6 +155,10 @@ class StatusHandler:
|
|||
def get_status_file_path(self):
|
||||
return self.status_file_path
|
||||
|
||||
def set_pair(self, trading_pair):
|
||||
self.pair = trading_pair
|
||||
return 0
|
||||
|
||||
def set_status_file_path(self, new_file_path: str):
|
||||
# if not isinstance(new_file_path, str):
|
||||
# self.broker.logger.log_this(f"File path provided is not a string",1,self.get_pair())
|
||||
|
|
@ -181,8 +192,15 @@ class StatusHandler:
|
|||
self.status_dictionary["so_order_id"] = order_id
|
||||
return 0
|
||||
|
||||
def set_safety_order(self, order):
|
||||
self.status_dictionary["safety_order"] = order
|
||||
def set_safety_orders(self, orders: list):
|
||||
"""
|
||||
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
|
||||
|
||||
def set_next_so_price(self, price: float):
|
||||
|
|
@ -381,6 +399,21 @@ class StatusHandler:
|
|||
self.status_dictionary["deal_order_history"] = deal_history
|
||||
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):
|
||||
self.status_dictionary["deal_order_history"] = []
|
||||
return 0
|
||||
|
|
|
|||
27
todo.txt
27
todo.txt
|
|
@ -1,32 +1,25 @@
|
|||
Mandatory:
|
||||
=========
|
||||
1. Stats webpage.
|
||||
2. Maintain local orderbooks for each trading pair, which enables:
|
||||
0. Stats webpage.
|
||||
1. Maintain local orderbooks for each trading pair, which enables:
|
||||
2a. Smart order pricing: Prioritization of fill speed over instant profit or vice versa
|
||||
3. Proper handling of order price too high/low in OKX (rare, it happens when under heavy volatility).
|
||||
4. Multiple safety orders open at the same time (to catch big volatility spikes more effectively)
|
||||
5. Things that should be objects (it's not 1994):
|
||||
* Orders.
|
||||
* 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).
|
||||
2. Proper handling of order price too high/low in OKX (rare, it happens when under heavy volatility).
|
||||
3. API documentation.
|
||||
4. Implement api key hashing.
|
||||
5. Dockerize.
|
||||
6. Earn should be integrated into the instance, in order to be able to invest the idle funds from the short traders.
|
||||
|
||||
|
||||
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)
|
||||
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, you could just edit it (Kucoin only supports editing on high frequency orders)
|
||||
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
|
||||
2. Instead of cancelling and resending the take profit order, 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.
|
||||
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)
|
||||
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.
|
||||
4. Earn should also use funds from short traders.
|
||||
4b. Should Earn be integrated to the instance?
|
||||
|
||||
|
||||
Maybe it's a good idea?:
|
||||
|
|
|
|||
|
|
@ -11,6 +11,12 @@ try:
|
|||
api_key = credentials.get_credentials("testnet_api_key")["key"]
|
||||
base_url = credentials.get_url("testnet") #type: ignore
|
||||
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":
|
||||
is_testnet = False
|
||||
string_to_add = "MAINNET "
|
||||
|
|
@ -56,9 +62,9 @@ TRADERS
|
|||
62) mod_tp_level 63) last_call 64) deferred_last_call
|
||||
65) toggle_pause 66) toggle_cleanup 67) toggle_autoswitch
|
||||
68) toggle_check_old_long_price 69) switch_quote_currency
|
||||
70) reload_safety_order 71) view_old_long 72) switch_price
|
||||
73) reload_trader_config 74) toggle_liquidate_after_switch
|
||||
75) base_add_calculation
|
||||
70) view_old_long 71) switch_price 72) reload_trader_config
|
||||
73) toggle_liquidate_after_switch 74) base_add_calculation
|
||||
75) mod_concurrent_safety_orders 76) force_trader_close
|
||||
|
||||
98) Change broker 99) Exit
|
||||
'''
|
||||
|
|
@ -561,10 +567,6 @@ if __name__=="__main__":
|
|||
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.")
|
||||
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):
|
||||
print("The input is invalid")
|
||||
|
|
@ -573,9 +575,7 @@ if __name__=="__main__":
|
|||
url = f"{base_url}{port}/import_pair"
|
||||
base,quote = trading_pair.split("/")
|
||||
parameters = {"base": base,
|
||||
"quote": quote,
|
||||
"forced_tp_id": forced_tp_id,
|
||||
"forced_so_id": forced_so_id}
|
||||
"quote": quote}
|
||||
print(json.loads(requests.post(url, headers=headers, json=parameters).content))
|
||||
input("Press ENTER to continue ")
|
||||
|
||||
|
|
@ -798,20 +798,6 @@ if __name__=="__main__":
|
|||
input("Press ENTER to continue ")
|
||||
|
||||
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")
|
||||
trading_pair = input("Input trader in the format BASE/QUOTE: ").upper()
|
||||
if not validate_pair(trading_pair):
|
||||
|
|
@ -824,7 +810,7 @@ if __name__=="__main__":
|
|||
print(json.loads(requests.get(url,headers=headers).content))
|
||||
input("Press ENTER to continue ")
|
||||
|
||||
elif command==72:
|
||||
elif command==71:
|
||||
print("Returns the price target to reach to switch to long mode")
|
||||
trading_pair = input("Input trader in the format BASE/QUOTE: ").upper()
|
||||
if not validate_pair(trading_pair):
|
||||
|
|
@ -836,7 +822,7 @@ if __name__=="__main__":
|
|||
print(json.loads(requests.get(url,headers=headers).content))
|
||||
input("Press ENTER to continue ")
|
||||
|
||||
elif command==73:
|
||||
elif command==72:
|
||||
print("Reloads from disk the configuration file of a trader")
|
||||
trading_pair = input("Input trader in the format BASE/QUOTE: ").upper()
|
||||
if not validate_pair(trading_pair):
|
||||
|
|
@ -850,7 +836,7 @@ if __name__=="__main__":
|
|||
print(json.loads(requests.post(url, headers=headers, json=parameters).content))
|
||||
input("Press ENTER to continue ")
|
||||
|
||||
elif command==74:
|
||||
elif command==73:
|
||||
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.")
|
||||
trading_pair = input("Input trader in the format BASE/QUOTE: ").upper()
|
||||
|
|
@ -865,7 +851,7 @@ if __name__=="__main__":
|
|||
print(json.loads(requests.post(url, headers=headers, json=parameters).content))
|
||||
input("Press ENTER to continue ")
|
||||
|
||||
elif command==75:
|
||||
elif command==74:
|
||||
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()
|
||||
if not validate_pair(trading_pair):
|
||||
|
|
@ -876,3 +862,36 @@ if __name__=="__main__":
|
|||
url = f"{base_url}{port}/base_add_so_calculation?base={base}"e={quote}"
|
||||
print(json.loads(requests.get(url,headers=headers).content))
|
||||
input("Press ENTER to continue ")
|
||||
|
||||
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