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(),
|
||||
"no_of_safety_orders": 30,
|
||||
"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_scale": 0.0105,
|
||||
"dynamic_so_deviance": True,
|
||||
|
|
@ -69,8 +70,11 @@ class ConfigHandler:
|
|||
def get_max_short_safety_orders(self):
|
||||
return self.config_dictionary["max_short_safety_orders"]
|
||||
|
||||
def get_max_concurrent_safety_orders(self):
|
||||
return self.config_dictionary["max_concurrent_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"]
|
||||
|
|
@ -177,11 +181,18 @@ class ConfigHandler:
|
|||
self.config_dictionary["max_short_safety_orders"] = max_short_safety_orders
|
||||
return 0
|
||||
|
||||
def set_max_concurrent_safety_orders(self, max_concurrent_safety_orders: int):
|
||||
# if not isinstance(max_concurrent_safety_orders, int):
|
||||
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["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
|
||||
|
||||
def set_safety_order_deviance(self, safety_order_deviance: int):
|
||||
|
|
|
|||
|
|
@ -1147,8 +1147,5 @@ class Logger:
|
|||
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
|
||||
|
||||
|
||||
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
|
||||
|
|
@ -340,9 +340,9 @@ 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))
|
||||
|
|
|
|||
23
todo.txt
23
todo.txt
|
|
@ -1,19 +1,16 @@
|
|||
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. Inspect orderbook liquidity prior to changing mode from short to long (big sell market order needs to have liquidity).
|
||||
7. API endpoint to modify the amount of concurrent safety orders
|
||||
8. Maybe when boosted, also increment the amount of concurrent safety orders?
|
||||
9. Use create_orders ccxt method to send the batch of safety orders (Binance does not support it in spot trading)
|
||||
|
||||
|
||||
Would be nice to have:
|
||||
|
|
|
|||
37
trader.py
37
trader.py
|
|
@ -270,7 +270,8 @@ class trader:
|
|||
# Send the initial batch of 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())
|
||||
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)
|
||||
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())
|
||||
|
|
@ -299,12 +300,12 @@ class trader:
|
|||
:return: int
|
||||
'''
|
||||
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()!=[]:
|
||||
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_short(self.config.get_is_short())
|
||||
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 ""
|
||||
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"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")
|
||||
#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
|
||||
:param amount: int - The amount of safety orders to send.
|
||||
: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:
|
||||
return 0
|
||||
|
||||
orders_to_place = min(self.config.get_no_of_safety_orders()-self.status.get_so_amount(),amount)
|
||||
if orders_to_place<1:
|
||||
return 0
|
||||
|
|
@ -888,12 +888,6 @@ class trader:
|
|||
new_order_list.append(order)
|
||||
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
|
||||
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"
|
||||
|
|
@ -919,7 +913,16 @@ class trader:
|
|||
#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_time())
|
||||
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
|
||||
time.sleep(self.broker.get_wait_time())
|
||||
|
||||
#Send new TP order
|
||||
if self.send_new_tp_order()==1:
|
||||
|
|
@ -1268,7 +1271,7 @@ class trader:
|
|||
'''
|
||||
try:
|
||||
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)
|
||||
return 0
|
||||
except Exception as e:
|
||||
|
|
@ -1552,7 +1555,7 @@ class trader:
|
|||
except Exception as 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')}"
|
||||
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():
|
||||
|
|
|
|||
Loading…
Reference in New Issue