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,9 @@ 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
+ self .active_trigger : typing .Optional [price_trigger .PriceTrigger ] = None
108
104
109
105
# future trading attributes
110
106
# when True: reduce position quantity only without opening a new position if order.quantity > position.quantity
@@ -304,13 +300,17 @@ def update(
304
300
changed = True
305
301
self .is_active = is_active
306
302
307
- if active_trigger_price is not None and self .active_trigger_price != active_trigger_price :
303
+ if ( active_trigger_price is not None or active_trigger_above is not None ) and self .active_trigger is None :
308
304
changed = True
309
- self .active_trigger_price = active_trigger_price
305
+ self .create_or_update_active_trigger ( active_trigger_price , active_trigger_above )
310
306
311
- if active_trigger_above is not None and self .active_trigger_above != active_trigger_above :
307
+ if active_trigger_price is not None and self .active_trigger . trigger_price != active_trigger_price :
312
308
changed = True
313
- self .active_trigger_above = active_trigger_above
309
+ self .active_trigger .trigger_price = active_trigger_price
310
+
311
+ if active_trigger_above is not None and self .active_trigger .trigger_above != active_trigger_above :
312
+ changed = True
313
+ self .active_trigger .trigger_above = active_trigger_above
314
314
315
315
if should_update_total_cost and not total_cost :
316
316
self ._update_total_cost ()
@@ -435,6 +435,15 @@ def active_or_inactive_transition(self):
435
435
finally :
436
436
self .is_in_active_inactive_transition = previous_value
437
437
438
+ def create_or_update_active_trigger (self , active_trigger_price : decimal .Decimal , active_trigger_above : bool ):
439
+ if self .active_trigger is None :
440
+ self .active_trigger = price_trigger .PriceTrigger (
441
+ self .on_active_trigger , (None , None ), active_trigger_price , active_trigger_above
442
+ )
443
+ else :
444
+ self .active_trigger .trigger_price = active_trigger_price
445
+ self .active_trigger .trigger_above = active_trigger_above
446
+
438
447
async def set_as_inactive (self , active_trigger_price : decimal .Decimal , active_trigger_above : bool ):
439
448
"""
440
449
Marks the instance as inactive and ensures the inactive order watcher is scheduled.
@@ -445,8 +454,7 @@ async def set_as_inactive(self, active_trigger_price: decimal.Decimal, active_tr
445
454
)
446
455
logging .get_logger (self .get_logger_name ()).info ("Order is switching to inactive" )
447
456
self .is_active = False
448
- self .active_trigger_price = active_trigger_price
449
- self .active_trigger_above = active_trigger_above
457
+ self .create_or_update_active_trigger (active_trigger_price , active_trigger_above )
450
458
# enforce attributes in case order has been canceled
451
459
self .status = enums .OrderStatus .OPEN
452
460
self .canceled_time = 0
@@ -456,12 +464,7 @@ def should_become_active(self, price_time: float, current_price: decimal.Decimal
456
464
if self .is_active :
457
465
return False
458
466
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
- )
467
+ return self .active_trigger .triggers (current_price )
465
468
return False
466
469
467
470
async def _ensure_inactive_order_watcher (self ):
@@ -474,40 +477,10 @@ async def _ensure_inactive_order_watcher(self):
474
477
f"Unexpected inactive order (simulated={ self .simulated } self_managed={ self .is_self_managed ()} ): { self } "
475
478
)
476
479
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 )
480
+ if self .active_trigger is None :
481
+ logging .get_logger (self .get_logger_name ()).error ("self.active_trigger is None" )
482
+ return
483
+ await self .active_trigger .create_watcher (self .exchange_manager , self .symbol , self .creation_time )
511
484
512
485
@contextlib .contextmanager
513
486
def order_state_creation (self ):
@@ -520,7 +493,11 @@ async def on_inactive_from_active(self):
520
493
"""
521
494
Update the order to be considered as "confirmed" inactive. Called when the order was active before
522
495
"""
523
- await self .set_as_inactive (self .active_trigger_price , self .active_trigger_above )
496
+ if self .active_trigger is None :
497
+ raise ValueError (
498
+ f"Both active_trigger_price and active_trigger_above must be provided to set an order as inactive"
499
+ )
500
+ await self .set_as_inactive (self .active_trigger .trigger_price , self .active_trigger .trigger_above )
524
501
self .clear_active_order_elements ()
525
502
526
503
async def on_active_from_inactive (self ):
@@ -941,13 +918,13 @@ def update_from_storage_order_details(self, order_details):
941
918
order_dict [enums .ExchangeConstantsOrderColumns .TAKER_OR_MAKER .value ]
942
919
).value if order_dict .get (enums .ExchangeConstantsOrderColumns .TAKER_OR_MAKER .value ) else self .taker_or_maker
943
920
self .is_active = order_dict .get (enums .ExchangeConstantsOrderColumns .IS_ACTIVE .value , self .is_active )
944
- self . active_trigger_price = (
921
+ active_trigger_price = (
945
922
decimal .Decimal (str (order_dict [enums .ExchangeConstantsOrderColumns .ACTIVE_TRIGGER_PRICE .value ]))
946
923
if order_dict .get (enums .ExchangeConstantsOrderColumns .ACTIVE_TRIGGER_PRICE .value ) else None
947
924
)
948
- self . active_trigger_above = order_dict .get (
949
- enums . ExchangeConstantsOrderColumns . ACTIVE_TRIGGER_ABOVE . value , self . active_trigger_above
950
- )
925
+ active_trigger_above = order_dict .get (enums . ExchangeConstantsOrderColumns . ACTIVE_TRIGGER_ABOVE . value )
926
+ if active_trigger_price is not None :
927
+ self . create_or_update_active_trigger ( active_trigger_price , active_trigger_above )
951
928
self .trader_creation_kwargs = order_details .get (enums .StoredOrdersAttr .TRADER_CREATION_KWARGS .value ,
952
929
self .trader_creation_kwargs )
953
930
self .exchange_creation_params = order_details .get (enums .StoredOrdersAttr .EXCHANGE_CREATION_PARAMS .value ,
@@ -1080,7 +1057,8 @@ def clear_active_order_elements(self):
1080
1057
1081
1058
1082
1059
def clear (self ):
1083
- self ._clear_active_trigger_event_and_tasks ()
1060
+ if self .active_trigger :
1061
+ self .active_trigger .clear ()
1084
1062
self .clear_active_order_elements ()
1085
1063
self .trader = None
1086
1064
self .exchange_manager = None
0 commit comments