@@ -39,7 +39,7 @@ def initialize(self):
39
39
self .__open_stop_market_orders = []
40
40
self .__open_stop_limit_orders = []
41
41
self .__open_trailing_stop_orders = []
42
-
42
+ self . __open_trailing_stop_limit_orders = []
43
43
44
44
def on_data (self , data ):
45
45
'''OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here.'''
@@ -64,6 +64,8 @@ def on_data(self, data):
64
64
# MARKET ON CLOSE ORDERS
65
65
self .market_on_close_orders ()
66
66
67
+ # TRAILING STOP LIMIT ORDERS
68
+ self .trailing_stop_limit_orders ()
67
69
68
70
def market_orders (self ):
69
71
''' MarketOrders are the only orders that are processed synchronously by default, so
@@ -320,6 +322,75 @@ def trailing_stop_orders(self):
320
322
update_order_fields .tag = "Update #{0}" .format (len (short_order .update_requests ) + 1 )
321
323
short_order .update (update_order_fields )
322
324
325
+ def trailing_stop_limit_orders (self ):
326
+ '''TrailingStopLimitOrders work the same way as StopLimitOrders, except
327
+ their stop price is adjusted to a certain amount, keeping it a certain
328
+ fixed distance from/to the market price, depending on the order direction.
329
+ The limit price adjusts based on a limit offset compared to the stop price.
330
+ You can submit requests to update or cancel the StopLimitOrder at any time.
331
+ The stop price, trailing amount, limit price and limit offset for an order
332
+ can be retrieved from the ticket using the OrderTicket.Get(OrderField) method, for example:
333
+ Code:
334
+ current_stop_price = order_ticket.get(OrderField.STOP_PRICE);
335
+ trailing_amount = order_ticket.get(OrderField.TRAILING_AMOUNT);
336
+ current_limit_price = order_ticket.get(OrderField.LIMIT_PRICE);
337
+ limit_offset = order_ticket.get(OrderField.LIMIT_OFFSET)'''
338
+ if self .time_is (7 , 12 , 0 ):
339
+ self .log ("Submitting TrailingStopLimitOrder" )
340
+
341
+ # a long stop is triggered when the price rises above the value
342
+ # so we'll set a long stop .25% above the current bar's close
343
+
344
+ close = self .securities [self .spy .value ].close
345
+ stop_price = close * 1.0025
346
+ limit_price = stop_price + 0.1
347
+ new_ticket = self .trailing_stop_limit_order (self .spy .value , 10 , stop_price , limit_price ,
348
+ trailing_amount = 0.0025 , trailing_as_percentage = True , limit_offset = 0.1 )
349
+ self .__open_trailing_stop_limit_orders .append (new_ticket )
350
+
351
+
352
+ # a short stop is triggered when the price falls below the value
353
+ # so we'll set a short stop .25% below the current bar's close
354
+
355
+ stop_price = close * 0.9975 ;
356
+ limit_price = stop_price - 0.1 ;
357
+ new_ticket = self .trailing_stop_limit_order (self .spy .value , - 10 , stop_price , limit_price ,
358
+ trailing_amount = 0.0025 , trailing_as_percentage = True , limit_offset = 0.1 );
359
+ self .__open_trailing_stop_limit_orders .append (new_ticket )
360
+
361
+ # when we submitted new trailing stop limit orders we placed them into this list,
362
+ # so while there's two entries they're still open and need processing
363
+ elif len (self .__open_trailing_stop_limit_orders ) == 2 :
364
+
365
+ # check if either is filled and cancel the other
366
+ long_order = self .__open_trailing_stop_limit_orders [0 ]
367
+ short_order = self .__open_trailing_stop_limit_orders [1 ]
368
+ if self .check_pair_orders_for_fills (long_order , short_order ):
369
+ self .__open_trailing_stop_limit_orders = []
370
+ return
371
+
372
+ # if neither order has filled in the last 5 minutes, bring in the trailing percentage by 0.01%
373
+ if ((self .utc_time - long_order .time ).total_seconds () / 60 ) % 5 != 0 :
374
+ return
375
+
376
+ long_trailing_percentage = long_order .get (OrderField .TRAILING_AMOUNT )
377
+ new_long_trailing_percentage = max (long_trailing_percentage - 0.0001 , 0.0001 )
378
+ short_trailing_percentage = short_order .get (OrderField .TRAILING_AMOUNT )
379
+ new_short_trailing_percentage = max (short_trailing_percentage - 0.0001 , 0.0001 )
380
+ self .log (self .log ("Updating trailing percentages - Long: {0:.3f} Short: {1:.3f}" .format (new_long_trailing_percentage , new_short_trailing_percentage )))
381
+
382
+ update_order_fields = UpdateOrderFields ()
383
+ # we could change the quantity, but need to specify it
384
+ #Quantity =
385
+ update_order_fields .trailing_amount = new_long_trailing_percentage
386
+ update_order_fields .tag = "Update #{0}" .format (len (long_order .update_requests ) + 1 )
387
+ long_order .update (update_order_fields )
388
+
389
+ update_order_fields = UpdateOrderFields ()
390
+ update_order_fields .trailing_amount = new_short_trailing_percentage
391
+ update_order_fields .tag = "Update #{0}" .format (len (short_order .update_requests ) + 1 )
392
+ short_order .update (update_order_fields )
393
+
323
394
324
395
def market_on_close_orders (self ):
325
396
'''MarketOnCloseOrders are always executed at the next market's closing price.
@@ -439,7 +510,7 @@ def on_end_of_algorithm(self):
439
510
order_tickets_size = sum (1 for ticket in order_tickets )
440
511
open_order_tickets_size = sum (1 for ticket in open_order_tickets )
441
512
442
- assert (filled_orders_size == 9 and order_tickets_size == 12 ), "There were expected 9 filled orders and 12 order tickets"
513
+ assert (filled_orders_size == 10 and order_tickets_size == 14 ), "There were expected 10 filled orders and 14 order tickets"
443
514
assert (not (len (open_orders ) or open_order_tickets_size )), "No open orders or tickets were expected"
444
515
assert (not remaining_open_orders ), "No remaining quantity to be filled from open orders was expected"
445
516
@@ -461,6 +532,6 @@ def on_end_of_algorithm(self):
461
532
default_order_tickets_size = sum (1 for ticket in default_order_tickets )
462
533
default_open_order_tickets_size = sum (1 for ticket in default_open_order_tickets )
463
534
464
- assert (default_orders_size == 12 and default_order_tickets_size == 12 ), "There were expected 12 orders and 12 order tickets"
535
+ assert (default_orders_size == 14 and default_order_tickets_size == 14 ), "There were expected 14 orders and 14 order tickets"
465
536
assert (not (len (default_open_orders ) or default_open_order_tickets_size )), "No open orders or tickets were expected"
466
537
assert (not default_open_orders_remaining ), "No remaining quantity to be filled from open orders was expected"
0 commit comments