28
28
import octobot_trading .personal_data .orders .order_util as order_util
29
29
import octobot_trading .personal_data .orders .trailing_profiles as trailing_profiles
30
30
import octobot_trading .personal_data .orders .decimal_order_adapter as decimal_order_adapter
31
+ import octobot_trading .personal_data .orders .triggers .price_trigger as price_trigger
31
32
import octobot_trading .util as util
32
33
33
34
@@ -97,14 +98,10 @@ def __init__(self, trader, side=None):
97
98
98
99
# order activity
99
100
self .is_active = True # When is_active=False order is not pushed to exchanges
100
- self .active_trigger_price : decimal .Decimal = None # price threshold from which the order becomes active
101
- # when True, order becomes active when current price >= active_trigger_price
102
- self .active_trigger_above : bool = None
103
- self ._active_trigger_event : asyncio .Event = None # will be set when the price is hit
104
- # waiter that will call on_active_trigger() when active_trigger_event is set
105
- self ._active_trigger_task : asyncio .Task = None
106
101
# True when a transition between active and inactive is being made
107
102
self .is_in_active_inactive_transition = False
103
+ # active_trigger is used for active/inactive switch trigger mechanism, it stores relevant data.
104
+ self .active_trigger : typing .Optional [price_trigger .PriceTrigger ] = None
108
105
109
106
# future trading attributes
110
107
# when True: reduce position quantity only without opening a new position if order.quantity > position.quantity
@@ -154,7 +151,7 @@ def update(
154
151
order_type = None , reduce_only = None , close_position = None , position_side = None , fees_currency_side = None ,
155
152
group = None , tag = None , quantity_currency = None , exchange_creation_params = None ,
156
153
associated_entry_id = None , trigger_above = None , trailing_profile : trailing_profiles .TrailingProfile = None ,
157
- is_active = None , active_trigger_price = None , active_trigger_above = None
154
+ is_active = None , active_trigger : price_trigger . PriceTrigger = None ,
158
155
) -> bool :
159
156
changed : bool = False
160
157
should_update_total_cost = False
@@ -304,13 +301,9 @@ def update(
304
301
changed = True
305
302
self .is_active = is_active
306
303
307
- if active_trigger_price is not None and self . active_trigger_price != active_trigger_price :
304
+ if active_trigger is not None and active_trigger != self . active_trigger :
308
305
changed = True
309
- self .active_trigger_price = active_trigger_price
310
-
311
- if active_trigger_above is not None and self .active_trigger_above != active_trigger_above :
312
- changed = True
313
- self .active_trigger_above = active_trigger_above
306
+ self .use_active_trigger (active_trigger )
314
307
315
308
if should_update_total_cost and not total_cost :
316
309
self ._update_total_cost ()
@@ -435,18 +428,29 @@ def active_or_inactive_transition(self):
435
428
finally :
436
429
self .is_in_active_inactive_transition = previous_value
437
430
438
- async def set_as_inactive (self , active_trigger_price : decimal .Decimal , active_trigger_above : bool ):
431
+ def use_active_trigger (self , active_trigger : price_trigger .PriceTrigger ):
432
+ if active_trigger is None :
433
+ raise ValueError ("active_trigger must be provided" )
434
+ if self .active_trigger is None :
435
+ self .active_trigger = active_trigger
436
+ elif self .active_trigger .is_pending ():
437
+ logging .get_logger (self .get_logger_name ()).error (
438
+ f"The current active trigger (at { self .active_trigger .trigger_price } ) is still pending, canceling it "
439
+ f"and replacing it by this new one, this is works but is unexpected."
440
+ )
441
+ self .active_trigger .clear ()
442
+ self .active_trigger = active_trigger
443
+ else :
444
+ self .active_trigger .trigger_price = active_trigger .trigger_price
445
+ self .active_trigger .trigger_above = active_trigger .trigger_above
446
+
447
+ async def set_as_inactive (self , active_trigger : price_trigger .PriceTrigger ):
439
448
"""
440
449
Marks the instance as inactive and ensures the inactive order watcher is scheduled.
441
450
"""
442
- if active_trigger_price is None or active_trigger_above is None :
443
- raise ValueError (
444
- f"Both active_trigger_price and active_trigger_above must be provided to set an order as inactive"
445
- )
446
451
logging .get_logger (self .get_logger_name ()).info ("Order is switching to inactive" )
452
+ self .use_active_trigger (active_trigger )
447
453
self .is_active = False
448
- self .active_trigger_price = active_trigger_price
449
- self .active_trigger_above = active_trigger_above
450
454
# enforce attributes in case order has been canceled
451
455
self .status = enums .OrderStatus .OPEN
452
456
self .canceled_time = 0
@@ -456,12 +460,7 @@ def should_become_active(self, price_time: float, current_price: decimal.Decimal
456
460
if self .is_active :
457
461
return False
458
462
if price_time >= self .creation_time :
459
- return (
460
- (self .active_trigger_above and current_price >= self .active_trigger_price )
461
- or (
462
- not self .active_trigger_above and current_price <= self .active_trigger_price
463
- )
464
- )
463
+ return self .active_trigger .triggers (current_price )
465
464
return False
466
465
467
466
async def _ensure_inactive_order_watcher (self ):
@@ -474,40 +473,10 @@ async def _ensure_inactive_order_watcher(self):
474
473
f"Unexpected inactive order (simulated={ self .simulated } self_managed={ self .is_self_managed ()} ): { self } "
475
474
)
476
475
return
477
- await self ._create_active_trigger_watcher ()
478
-
479
- async def _create_active_trigger_watcher (self ):
480
- # ensure active triggers are ready
481
- if self ._active_trigger_event is None :
482
- self ._create_active_trigger_event (self .creation_time )
483
- else :
484
- self ._active_trigger_event .clear ()
485
- if self ._active_trigger_task is None or self ._active_trigger_task .done ():
486
- if self ._active_trigger_event .is_set ():
487
- await self .on_active_trigger (None , None )
488
- else :
489
- self ._create_active_trigger_task ()
490
-
491
- def _create_active_trigger_event (self , price_time ):
492
- self ._active_trigger_event = self .exchange_manager .exchange_symbols_data .\
493
- get_exchange_symbol_data (self .symbol ).price_events_manager .\
494
- new_event (self .active_trigger_price , price_time , self .active_trigger_above , False )
495
-
496
- async def _wait_for_active_trigger_set (self ):
497
- await asyncio .wait_for (self ._active_trigger_event .wait (), timeout = None )
498
- await self .on_active_trigger (None , None )
499
-
500
- def _create_active_trigger_task (self ):
501
- self ._active_trigger_task = asyncio .create_task (self ._wait_for_active_trigger_set ())
502
-
503
- def _clear_active_trigger_event_and_tasks (self ):
504
- if self ._active_trigger_task is not None :
505
- if not self ._active_trigger_event .is_set ():
506
- self ._active_trigger_task .cancel ()
507
- self ._active_trigger_task = None
508
- if self ._active_trigger_event is not None :
509
- self .exchange_manager .exchange_symbols_data . \
510
- get_exchange_symbol_data (self .symbol ).price_events_manager .remove_event (self ._active_trigger_event )
476
+ if self .active_trigger is None :
477
+ logging .get_logger (self .get_logger_name ()).error ("self.active_trigger is None" )
478
+ return
479
+ await self .active_trigger .create_watcher (self .exchange_manager , self .symbol , self .creation_time )
511
480
512
481
@contextlib .contextmanager
513
482
def order_state_creation (self ):
@@ -520,7 +489,11 @@ async def on_inactive_from_active(self):
520
489
"""
521
490
Update the order to be considered as "confirmed" inactive. Called when the order was active before
522
491
"""
523
- await self .set_as_inactive (self .active_trigger_price , self .active_trigger_above )
492
+ if self .active_trigger is None :
493
+ raise ValueError (
494
+ f"self.active_trigger must be provided to set an order as inactive"
495
+ )
496
+ await self .set_as_inactive (self .active_trigger )
524
497
self .clear_active_order_elements ()
525
498
526
499
async def on_active_from_inactive (self ):
@@ -941,13 +914,15 @@ def update_from_storage_order_details(self, order_details):
941
914
order_dict [enums .ExchangeConstantsOrderColumns .TAKER_OR_MAKER .value ]
942
915
).value if order_dict .get (enums .ExchangeConstantsOrderColumns .TAKER_OR_MAKER .value ) else self .taker_or_maker
943
916
self .is_active = order_dict .get (enums .ExchangeConstantsOrderColumns .IS_ACTIVE .value , self .is_active )
944
- self .active_trigger_price = (
945
- decimal .Decimal (str (order_dict [enums .ExchangeConstantsOrderColumns .ACTIVE_TRIGGER_PRICE .value ]))
946
- if order_dict .get (enums .ExchangeConstantsOrderColumns .ACTIVE_TRIGGER_PRICE .value ) else None
947
- )
948
- self .active_trigger_above = order_dict .get (
949
- enums .ExchangeConstantsOrderColumns .ACTIVE_TRIGGER_ABOVE .value , self .active_trigger_above
950
- )
917
+ if active_trigger := order_details .get (enums .StoredOrdersAttr .ACTIVE_TRIGGER .value ):
918
+ active_trigger_price = (
919
+ decimal .Decimal (str (active_trigger [enums .StoredOrdersAttr .ACTIVE_TRIGGER_PRICE .value ]))
920
+ if active_trigger .get (enums .StoredOrdersAttr .ACTIVE_TRIGGER_PRICE .value ) else None
921
+ )
922
+ active_trigger_above = active_trigger .get (enums .StoredOrdersAttr .ACTIVE_TRIGGER_ABOVE .value )
923
+ self .use_active_trigger (
924
+ order_util .create_order_price_trigger (self , active_trigger_price , active_trigger_above )
925
+ )
951
926
self .trader_creation_kwargs = order_details .get (enums .StoredOrdersAttr .TRADER_CREATION_KWARGS .value ,
952
927
self .trader_creation_kwargs )
953
928
self .exchange_creation_params = order_details .get (enums .StoredOrdersAttr .EXCHANGE_CREATION_PARAMS .value ,
@@ -1080,7 +1055,8 @@ def clear_active_order_elements(self):
1080
1055
1081
1056
1082
1057
def clear (self ):
1083
- self ._clear_active_trigger_event_and_tasks ()
1058
+ if self .active_trigger :
1059
+ self .active_trigger .clear ()
1084
1060
self .clear_active_order_elements ()
1085
1061
self .trader = None
1086
1062
self .exchange_manager = None
0 commit comments