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 .base_trigger as base_trigger_import
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 [base_trigger_import .BaseTrigger ] = 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 : base_trigger_import . BaseTrigger = 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,28 @@ 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 : base_trigger_import .BaseTrigger ):
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 ({ str (self .active_trigger )} ) 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 .update_from_other_trigger (active_trigger )
445
+
446
+ async def set_as_inactive (self , active_trigger : base_trigger_import .BaseTrigger ):
439
447
"""
440
448
Marks the instance as inactive and ensures the inactive order watcher is scheduled.
441
449
"""
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
450
logging .get_logger (self .get_logger_name ()).info ("Order is switching to inactive" )
451
+ self .use_active_trigger (active_trigger )
447
452
self .is_active = False
448
- self .active_trigger_price = active_trigger_price
449
- self .active_trigger_above = active_trigger_above
450
453
# enforce attributes in case order has been canceled
451
454
self .status = enums .OrderStatus .OPEN
452
455
self .canceled_time = 0
@@ -456,12 +459,7 @@ def should_become_active(self, price_time: float, current_price: decimal.Decimal
456
459
if self .is_active :
457
460
return False
458
461
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
- )
462
+ return self .active_trigger .triggers (current_price )
465
463
return False
466
464
467
465
async def _ensure_inactive_order_watcher (self ):
@@ -474,40 +472,10 @@ async def _ensure_inactive_order_watcher(self):
474
472
f"Unexpected inactive order (simulated={ self .simulated } self_managed={ self .is_self_managed ()} ): { self } "
475
473
)
476
474
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 )
475
+ if self .active_trigger is None :
476
+ logging .get_logger (self .get_logger_name ()).error ("self.active_trigger is None" )
477
+ return
478
+ await self .active_trigger .create_watcher (self .exchange_manager , self .symbol , self .creation_time )
511
479
512
480
@contextlib .contextmanager
513
481
def order_state_creation (self ):
@@ -520,7 +488,11 @@ async def on_inactive_from_active(self):
520
488
"""
521
489
Update the order to be considered as "confirmed" inactive. Called when the order was active before
522
490
"""
523
- await self .set_as_inactive (self .active_trigger_price , self .active_trigger_above )
491
+ if self .active_trigger is None :
492
+ raise ValueError (
493
+ f"self.active_trigger must be provided to set an order as inactive"
494
+ )
495
+ await self .set_as_inactive (self .active_trigger )
524
496
self .clear_active_order_elements ()
525
497
526
498
async def on_active_from_inactive (self ):
@@ -941,13 +913,20 @@ def update_from_storage_order_details(self, order_details):
941
913
order_dict [enums .ExchangeConstantsOrderColumns .TAKER_OR_MAKER .value ]
942
914
).value if order_dict .get (enums .ExchangeConstantsOrderColumns .TAKER_OR_MAKER .value ) else self .taker_or_maker
943
915
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
- )
916
+ if active_trigger := order_details .get (enums .StoredOrdersAttr .ACTIVE_TRIGGER .value ):
917
+ active_trigger_price = (
918
+ decimal .Decimal (str (active_trigger [enums .StoredOrdersAttr .ACTIVE_TRIGGER_PRICE .value ]))
919
+ if active_trigger .get (enums .StoredOrdersAttr .ACTIVE_TRIGGER_PRICE .value ) else None
920
+ )
921
+ active_trigger_above = active_trigger .get (enums .StoredOrdersAttr .ACTIVE_TRIGGER_ABOVE .value )
922
+ if active_trigger_price is not None and active_trigger_above is not None :
923
+ self .use_active_trigger (
924
+ order_util .create_order_price_trigger (self , active_trigger_price , active_trigger_above )
925
+ )
926
+ else :
927
+ logging .get_logger (self .__class__ .__name__ ).error (
928
+ f"Ignored unknown trigger configuration: { active_trigger } "
929
+ )
951
930
self .trader_creation_kwargs = order_details .get (enums .StoredOrdersAttr .TRADER_CREATION_KWARGS .value ,
952
931
self .trader_creation_kwargs )
953
932
self .exchange_creation_params = order_details .get (enums .StoredOrdersAttr .EXCHANGE_CREATION_PARAMS .value ,
@@ -1080,7 +1059,8 @@ def clear_active_order_elements(self):
1080
1059
1081
1060
1082
1061
def clear (self ):
1083
- self ._clear_active_trigger_event_and_tasks ()
1062
+ if self .active_trigger :
1063
+ self .active_trigger .clear ()
1084
1064
self .clear_active_order_elements ()
1085
1065
self .trader = None
1086
1066
self .exchange_manager = None
0 commit comments