std/boosted concurrent safety orders
This commit is contained in:
parent
6bf3df0418
commit
069cff2402
|
|
@ -14,7 +14,8 @@ 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,
|
||||||
"max_concurrent_safety_orders": 5,
|
"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,
|
||||||
|
|
@ -69,8 +70,11 @@ 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_max_concurrent_safety_orders(self):
|
def get_concurrent_safety_orders(self):
|
||||||
return self.config_dictionary["max_concurrent_safety_orders"]
|
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"]
|
||||||
|
|
@ -177,11 +181,18 @@ 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_max_concurrent_safety_orders(self, max_concurrent_safety_orders: int):
|
def set_concurrent_safety_orders(self, concurrent_safety_orders: int):
|
||||||
# if not isinstance(max_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())
|
# self.broker.logger.log_this(f"Max concurrent safety orders provided is not an integer",1,self.get_pair())
|
||||||
# return 1
|
# return 1
|
||||||
self.config_dictionary["max_concurrent_safety_orders"] = max_concurrent_safety_orders
|
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
|
return 0
|
||||||
|
|
||||||
def set_safety_order_deviance(self, safety_order_deviance: int):
|
def set_safety_order_deviance(self, safety_order_deviance: int):
|
||||||
|
|
|
||||||
|
|
@ -1147,8 +1147,5 @@ class Logger:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
class Order:
|
|
||||||
def __init__(self, order: dict = {}):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
8
main.py
8
main.py
|
|
@ -18,7 +18,7 @@ import exchange_wrapper
|
||||||
import trader
|
import trader
|
||||||
|
|
||||||
|
|
||||||
version = "2025.08.23"
|
version = "2025.08.24"
|
||||||
|
|
||||||
'''
|
'''
|
||||||
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
|
||||||
|
|
@ -340,9 +340,9 @@ 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.get_status_dict()["so_amount"]) # For the safety order occupancy percentage calculation
|
curr += int(instance.status.get_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
|
top += int(instance.config.get_no_of_safety_orders())
|
||||||
if "status_string" in instance.get_status_dict(): # status_strings
|
if "status_string" in instance.get_status_dict():
|
||||||
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))
|
||||||
|
|
|
||||||
23
todo.txt
23
todo.txt
|
|
@ -1,19 +1,16 @@
|
||||||
Mandatory:
|
Mandatory:
|
||||||
=========
|
=========
|
||||||
1. Stats webpage.
|
0. Stats webpage.
|
||||||
2. Maintain local orderbooks for each trading pair, which enables:
|
1. 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
|
||||||
3. Proper handling of order price too high/low in OKX (rare, it happens when under heavy volatility).
|
2. 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)
|
3. API documentation.
|
||||||
5. Things that should be objects (it's not 1994):
|
4. Implement api key hashing.
|
||||||
* Orders.
|
5. Dockerize.
|
||||||
* Config (Mostly done).
|
6. Inspect orderbook liquidity prior to changing mode from short to long (big sell market order needs to have liquidity).
|
||||||
* Status (Mostly done).
|
7. API endpoint to modify the amount of concurrent safety orders
|
||||||
6. API documentation.
|
8. Maybe when boosted, also increment the amount of concurrent safety orders?
|
||||||
7. Implement api key hashing.
|
9. Use create_orders ccxt method to send the batch of safety orders (Binance does not support it in spot trading)
|
||||||
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:
|
||||||
|
|
|
||||||
35
trader.py
35
trader.py
|
|
@ -270,7 +270,8 @@ class trader:
|
||||||
# Send the initial batch of safety orders
|
# Send the initial batch of safety orders
|
||||||
self.status.set_pause_reason("start_trader - sending safety orders")
|
self.status.set_pause_reason("start_trader - sending safety orders")
|
||||||
self.broker.logger.log_this("Sending safety orders...",2,self.config.get_pair())
|
self.broker.logger.log_this("Sending safety orders...",2,self.config.get_pair())
|
||||||
max_initial_safety_orders = min(self.config.get_max_concurrent_safety_orders(),self.config.get_no_of_safety_orders()) #To never send more than the max amount of safety orders
|
amount_of_so = self.config.get_concurrent_safety_orders() if not self.status.get_is_boosted() else self.config.get_boosted_concurrent_safety_orders()
|
||||||
|
max_initial_safety_orders = min(amount_of_so,self.config.get_no_of_safety_orders()) #To never send more than the max amount of safety orders
|
||||||
orders_placed = self.send_new_safety_order_batch(max_initial_safety_orders)
|
orders_placed = self.send_new_safety_order_batch(max_initial_safety_orders)
|
||||||
if orders_placed is not None:
|
if orders_placed is not None:
|
||||||
self.broker.logger.log_this(f"{orders_placed}/{max_initial_safety_orders} safety orders placed",2,self.config.get_pair())
|
self.broker.logger.log_this(f"{orders_placed}/{max_initial_safety_orders} safety orders placed",2,self.config.get_pair())
|
||||||
|
|
@ -299,12 +300,12 @@ class trader:
|
||||||
:return: int
|
:return: int
|
||||||
'''
|
'''
|
||||||
try:
|
try:
|
||||||
try:
|
|
||||||
self.status.set_next_so_price(self.status.get_safety_price_table()[self.status.get_so_amount()])
|
|
||||||
except Exception as e:
|
|
||||||
self.broker.logger.log_this(f"Is safety_price_table populated? Exception: {e} | Safety price table: {self.status.get_safety_price_table()} | Safety order index: {self.status.get_so_amount()}",1,self.config.get_pair())
|
|
||||||
if self.status.get_safety_orders()!=[]:
|
if self.status.get_safety_orders()!=[]:
|
||||||
self.status.set_next_so_price(self.status.get_safety_orders()[0]["price"])
|
self.status.set_next_so_price(self.status.get_safety_orders()[0]["price"])
|
||||||
|
elif len(self.status.get_safety_price_table())>self.status.get_safety_orders_filled():
|
||||||
|
self.status.set_next_so_price(self.status.get_safety_price_table()[self.status.get_safety_orders_filled()])
|
||||||
|
else:
|
||||||
|
self.status.set_next_so_price(0)
|
||||||
self.status.set_is_paused(self.pause)
|
self.status.set_is_paused(self.pause)
|
||||||
self.status.set_is_short(self.config.get_is_short())
|
self.status.set_is_short(self.config.get_is_short())
|
||||||
self.status.set_no_of_safety_orders(self.config.get_no_of_safety_orders())
|
self.status.set_no_of_safety_orders(self.config.get_no_of_safety_orders())
|
||||||
|
|
@ -768,7 +769,7 @@ class trader:
|
||||||
extra = ' and {:.4f}'.format(base_profit) + f" {self.base}" if base_profit>0 else ""
|
extra = ' and {:.4f}'.format(base_profit) + f" {self.base}" if base_profit>0 else ""
|
||||||
self.broker.logger.log_this(f"Trader closed a deal. Profit: {'{:.4f}'.format(profit)} {self.quote}{extra}",2,self.config.get_pair())
|
self.broker.logger.log_this(f"Trader closed a deal. Profit: {'{:.4f}'.format(profit)} {self.quote}{extra}",2,self.config.get_pair())
|
||||||
self.broker.logger.log_this(f"Fill price: {filled_order['price']} {self.quote}",2,self.config.get_pair())
|
self.broker.logger.log_this(f"Fill price: {filled_order['price']} {self.quote}",2,self.config.get_pair())
|
||||||
self.broker.logger.log_this(f"Safety orders triggered: {self.status.get_so_amount()-1}",2,self.config.get_pair())
|
self.broker.logger.log_this(f"Safety orders triggered: {self.status.get_safety_orders_filled()}",2,self.config.get_pair())
|
||||||
|
|
||||||
self.status.set_pause_reason("take_profit_routine - check time limit")
|
self.status.set_pause_reason("take_profit_routine - check time limit")
|
||||||
#Checks if there is a time limit for the trader
|
#Checks if there is a time limit for the trader
|
||||||
|
|
@ -821,12 +822,11 @@ class trader:
|
||||||
Sends a new safety order batch to the broker
|
Sends a new safety order batch to the broker
|
||||||
:param amount: int - The amount of safety orders to send.
|
:param amount: int - The amount of safety orders to send.
|
||||||
:return: The amount of orders succesfully sent. None if an error occurs.
|
:return: The amount of orders succesfully sent. None if an error occurs.
|
||||||
|
|
||||||
If the amount of orders returned is less than the amount expected, we should not try to send more safety orders.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if amount<1:
|
if amount<1:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
orders_to_place = min(self.config.get_no_of_safety_orders()-self.status.get_so_amount(),amount)
|
orders_to_place = min(self.config.get_no_of_safety_orders()-self.status.get_so_amount(),amount)
|
||||||
if orders_to_place<1:
|
if orders_to_place<1:
|
||||||
return 0
|
return 0
|
||||||
|
|
@ -888,12 +888,6 @@ class trader:
|
||||||
new_order_list.append(order)
|
new_order_list.append(order)
|
||||||
self.status.set_safety_orders(new_order_list)
|
self.status.set_safety_orders(new_order_list)
|
||||||
|
|
||||||
#Cooldown
|
|
||||||
time.sleep(self.broker.get_wait_before_new_safety_order())
|
|
||||||
|
|
||||||
#Send new SO(s)
|
|
||||||
orders_sent = self.send_new_safety_order_batch(len(filled_safety_orders))
|
|
||||||
|
|
||||||
#Cancel old TP order
|
#Cancel old TP order
|
||||||
if self.broker.cancel_order(self.status.get_take_profit_order()["id"],self.config.get_pair())==1:
|
if self.broker.cancel_order(self.status.get_take_profit_order()["id"],self.config.get_pair())==1:
|
||||||
error_string = f"{self.config.get_pair()} | {self.status.get_take_profit_order()['id']} | Old TP order probably filled. Can't cancel. This trader should be restarted"
|
error_string = f"{self.config.get_pair()} | {self.status.get_take_profit_order()['id']} | Old TP order probably filled. Can't cancel. This trader should be restarted"
|
||||||
|
|
@ -918,6 +912,15 @@ class trader:
|
||||||
self.status.set_fees_paid_in_quote(self.status.get_fees_paid_in_quote() + self.parse_fees(old_tp_order)[1])
|
self.status.set_fees_paid_in_quote(self.status.get_fees_paid_in_quote() + self.parse_fees(old_tp_order)[1])
|
||||||
#self.status.set_fees_paid_in_base(self.status.get_fees_paid_in_base() + self.parse_fees(old_tp_order)[0])
|
#self.status.set_fees_paid_in_base(self.status.get_fees_paid_in_base() + self.parse_fees(old_tp_order)[0])
|
||||||
|
|
||||||
|
#Cooldown
|
||||||
|
time.sleep(self.broker.get_wait_before_new_safety_order())
|
||||||
|
|
||||||
|
#Send new SO(s)
|
||||||
|
#Do not send new orders if the max amount is reached or surpassed.
|
||||||
|
#It can happen if the max amount of concurrent orders is modified through an API call.
|
||||||
|
max_orders = self.config.get_concurrent_safety_orders() if not self.status.get_is_boosted() else self.config.get_boosted_concurrent_safety_orders()
|
||||||
|
if len(self.status.get_safety_orders())<max_orders:
|
||||||
|
self.send_new_safety_order_batch(len(filled_safety_orders))
|
||||||
#Cooldown
|
#Cooldown
|
||||||
time.sleep(self.broker.get_wait_time())
|
time.sleep(self.broker.get_wait_time())
|
||||||
|
|
||||||
|
|
@ -1268,7 +1271,7 @@ class trader:
|
||||||
'''
|
'''
|
||||||
try:
|
try:
|
||||||
extra = f" and {round(base_profit,6)} {self.base}" if base_profit>0 else ""
|
extra = f" and {round(base_profit,6)} {self.base}" if base_profit>0 else ""
|
||||||
message = f"{self.config.get_pair()} closed a {'short' if self.config.get_is_short() else 'long'} trade.\nProfit: {round(profit,6)} {self.quote}{extra}\nSafety orders triggered: {self.status.get_so_amount()-1}\nTake profit price: {order['price']} {self.quote}\nTrade size: {round(order['cost'],2)} {self.quote}\nDeal uptime: {self.seconds_to_time(self.status.get_deal_uptime())}\nOrder ID: {order['id']}\nExchange: {self.broker.get_exchange_name().capitalize()}\n"
|
message = f"{self.config.get_pair()} closed a {'short' if self.config.get_is_short() else 'long'} trade.\nProfit: {round(profit,6)} {self.quote}{extra}\nSafety orders triggered: {self.status.get_safety_orders_filled()}\nTake profit price: {order['price']} {self.quote}\nTrade size: {round(order['cost'],2)} {self.quote}\nDeal uptime: {self.seconds_to_time(self.status.get_deal_uptime())}\nOrder ID: {order['id']}\nExchange: {self.broker.get_exchange_name().capitalize()}\n"
|
||||||
self.broker.logger.send_tg_message(message)
|
self.broker.logger.send_tg_message(message)
|
||||||
return 0
|
return 0
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
@ -1552,7 +1555,7 @@ class trader:
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(e)
|
print(e)
|
||||||
|
|
||||||
safety_order_string = f"{self.status.get_safety_orders_filled()}/{self.config.get_no_of_safety_orders()}{self.get_color('cyan')} {len(self.status.get_safety_orders())}{self.get_color('white')}".rjust(6)
|
safety_order_string = f"{self.status.get_safety_orders_filled()}/{self.get_color('cyan')}{len(self.status.get_safety_orders())}{self.get_color('white')}/{self.config.get_no_of_safety_orders()}".rjust(27)
|
||||||
prices = f"{low_boundary_color}{low_boundary}{self.get_color('white')}|{price_color}{mid_boundary}{self.get_color('white')}|{target_price_color}{high_boundary}{self.get_color('white')}|{pct_color}{pct_to_profit_str}%{self.get_color('white')}"
|
prices = f"{low_boundary_color}{low_boundary}{self.get_color('white')}|{price_color}{mid_boundary}{self.get_color('white')}|{target_price_color}{high_boundary}{self.get_color('white')}|{pct_color}{pct_to_profit_str}%{self.get_color('white')}"
|
||||||
line1 = f"{p}{pair_color}{self.config.get_pair().center(13)}{self.get_color('white')}| {safety_order_string} |{prices}| Uptime: {self.seconds_to_time(self.status.get_deal_uptime())}"
|
line1 = f"{p}{pair_color}{self.config.get_pair().center(13)}{self.get_color('white')}| {safety_order_string} |{prices}| Uptime: {self.seconds_to_time(self.status.get_deal_uptime())}"
|
||||||
if self.status.get_is_boosted():
|
if self.status.get_is_boosted():
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue