@@ -47,6 +47,7 @@ class Strategy(metaclass=ABCMeta):
47
47
`backtesting.backtesting.Strategy.next` to define
48
48
your own strategy.
49
49
"""
50
+
50
51
def __init__ (self , broker , data , params ):
51
52
self ._indicators = []
52
53
self ._broker : _Broker = broker
@@ -193,30 +194,32 @@ def buy(self, *,
193
194
limit : float = None ,
194
195
stop : float = None ,
195
196
sl : float = None ,
196
- tp : float = None ):
197
+ tp : float = None ,
198
+ tag : object = None ):
197
199
"""
198
200
Place a new long order. For explanation of parameters, see `Order` and its properties.
199
201
200
202
See also `Strategy.sell()`.
201
203
"""
202
204
assert 0 < size < 1 or round (size ) == size , \
203
205
"size must be a positive fraction of equity, or a positive whole number of units"
204
- return self ._broker .new_order (size , limit , stop , sl , tp )
206
+ return self ._broker .new_order (size , limit , stop , sl , tp , tag )
205
207
206
208
def sell (self , * ,
207
209
size : float = 1 - sys .float_info .epsilon ,
208
210
limit : float = None ,
209
211
stop : float = None ,
210
212
sl : float = None ,
211
- tp : float = None ):
213
+ tp : float = None ,
214
+ tag : object = None ):
212
215
"""
213
216
Place a new short order. For explanation of parameters, see `Order` and its properties.
214
217
215
218
See also `Strategy.buy()`.
216
219
"""
217
220
assert 0 < size < 1 or round (size ) == size , \
218
221
"size must be a positive fraction of equity, or a positive whole number of units"
219
- return self ._broker .new_order (- size , limit , stop , sl , tp )
222
+ return self ._broker .new_order (- size , limit , stop , sl , tp , tag )
220
223
221
224
@property
222
225
def equity (self ) -> float :
@@ -277,6 +280,7 @@ class _Orders(tuple):
277
280
"""
278
281
TODO: remove this class. Only for deprecation.
279
282
"""
283
+
280
284
def cancel (self ):
281
285
"""Cancel all non-contingent (i.e. SL/TP) orders."""
282
286
for order in self :
@@ -304,6 +308,7 @@ class Position:
304
308
if self.position:
305
309
... # we have a position, either long or short
306
310
"""
311
+
307
312
def __init__ (self , broker : '_Broker' ):
308
313
self .__broker = broker
309
314
@@ -368,13 +373,15 @@ class Order:
368
373
[filled]: https://www.investopedia.com/terms/f/fill.asp
369
374
[Good 'Til Canceled]: https://www.investopedia.com/terms/g/gtc.asp
370
375
"""
376
+
371
377
def __init__ (self , broker : '_Broker' ,
372
378
size : float ,
373
379
limit_price : float = None ,
374
380
stop_price : float = None ,
375
381
sl_price : float = None ,
376
382
tp_price : float = None ,
377
- parent_trade : 'Trade' = None ):
383
+ parent_trade : 'Trade' = None ,
384
+ tag : object = None ):
378
385
self .__broker = broker
379
386
assert size != 0
380
387
self .__size = size
@@ -383,6 +390,7 @@ def __init__(self, broker: '_Broker',
383
390
self .__sl_price = sl_price
384
391
self .__tp_price = tp_price
385
392
self .__parent_trade = parent_trade
393
+ self .__tag = tag
386
394
387
395
def _replace (self , ** kwargs ):
388
396
for k , v in kwargs .items ():
@@ -398,6 +406,7 @@ def __repr__(self):
398
406
('sl' , self .__sl_price ),
399
407
('tp' , self .__tp_price ),
400
408
('contingent' , self .is_contingent ),
409
+ ('tag' , self .__tag ),
401
410
) if value is not None ))
402
411
403
412
def cancel (self ):
@@ -468,6 +477,15 @@ def tp(self) -> Optional[float]:
468
477
def parent_trade (self ):
469
478
return self .__parent_trade
470
479
480
+ @property
481
+ def tag (self ) -> Optional [object ]:
482
+ """
483
+ An attribute which, if set, persists to enable tracking of this order
484
+ by an external identifier if it becomes a trade in `Strategy.trades`
485
+ and when closed in `Strategy.closed_trades`.
486
+ """
487
+ return self .__tag
488
+
471
489
__pdoc__ ['Order.parent_trade' ] = False
472
490
473
491
# Extra properties
@@ -502,7 +520,8 @@ class Trade:
502
520
When an `Order` is filled, it results in an active `Trade`.
503
521
Find active trades in `Strategy.trades` and closed, settled trades in `Strategy.closed_trades`.
504
522
"""
505
- def __init__ (self , broker : '_Broker' , size : int , entry_price : float , entry_bar ):
523
+
524
+ def __init__ (self , broker : '_Broker' , size : int , entry_price : float , entry_bar , tag : object ):
506
525
self .__broker = broker
507
526
self .__size = size
508
527
self .__entry_price = entry_price
@@ -511,10 +530,12 @@ def __init__(self, broker: '_Broker', size: int, entry_price: float, entry_bar):
511
530
self .__exit_bar : Optional [int ] = None
512
531
self .__sl_order : Optional [Order ] = None
513
532
self .__tp_order : Optional [Order ] = None
533
+ self .__tag = tag
514
534
515
535
def __repr__ (self ):
516
536
return f'<Trade size={ self .__size } time={ self .__entry_bar } -{ self .__exit_bar or "" } ' \
517
- f'price={ self .__entry_price } -{ self .__exit_price or "" } pl={ self .pl :.0f} >'
537
+ f'price={ self .__entry_price } -{ self .__exit_price or "" } pl={ self .pl :.0f} ' \
538
+ f'{ " tag=" + str (self .__tag ) if self .__tag is not None else "" } >'
518
539
519
540
def _replace (self , ** kwargs ):
520
541
for k , v in kwargs .items ():
@@ -528,7 +549,7 @@ def close(self, portion: float = 1.):
528
549
"""Place new `Order` to close `portion` of the trade at next market price."""
529
550
assert 0 < portion <= 1 , "portion must be a fraction between 0 and 1"
530
551
size = copysign (max (1 , round (abs (self .__size ) * portion )), - self .__size )
531
- order = Order (self .__broker , size , parent_trade = self )
552
+ order = Order (self .__broker , size , parent_trade = self , tag = self . __tag )
532
553
self .__broker .orders .insert (0 , order )
533
554
534
555
# Fields getters
@@ -561,6 +582,15 @@ def exit_bar(self) -> Optional[int]:
561
582
"""
562
583
return self .__exit_bar
563
584
585
+ @property
586
+ def tag (self ) -> Optional [object ]:
587
+ """
588
+ A tag attribute optionally set when placing an order with
589
+ `Strategy.buy()` or `Strategy.sell()`.
590
+ See `Order.tag`.
591
+ """
592
+ return self .__tag
593
+
564
594
@property
565
595
def _sl_order (self ):
566
596
return self .__sl_order
@@ -652,7 +682,7 @@ def __set_contingent(self, type, price):
652
682
order .cancel ()
653
683
if price :
654
684
kwargs = dict (stop = price ) if type == 'sl' else dict (limit = price )
655
- order = self .__broker .new_order (- self .size , trade = self , ** kwargs )
685
+ order = self .__broker .new_order (- self .size , trade = self , tag = self . tag , ** kwargs )
656
686
setattr (self , attr , order )
657
687
658
688
@@ -685,6 +715,7 @@ def new_order(self,
685
715
stop : float = None ,
686
716
sl : float = None ,
687
717
tp : float = None ,
718
+ tag : object = None ,
688
719
* ,
689
720
trade : Trade = None ):
690
721
"""
@@ -710,7 +741,7 @@ def new_order(self,
710
741
"Short orders require: "
711
742
f"TP ({ tp } ) < LIMIT ({ limit or stop or adjusted_price } ) < SL ({ sl } )" )
712
743
713
- order = Order (self , size , limit , stop , sl , tp , trade )
744
+ order = Order (self , size , limit , stop , sl , tp , trade , tag )
714
745
# Put the new order in the order queue,
715
746
# inserting SL/TP/trade-closing orders in-front
716
747
if trade :
@@ -890,7 +921,12 @@ def _process_orders(self):
890
921
891
922
# Open a new trade
892
923
if need_size :
893
- self ._open_trade (adjusted_price , need_size , order .sl , order .tp , time_index )
924
+ self ._open_trade (adjusted_price ,
925
+ need_size ,
926
+ order .sl ,
927
+ order .tp ,
928
+ time_index ,
929
+ order .tag )
894
930
895
931
# We need to reprocess the SL/TP orders newly added to the queue.
896
932
# This allows e.g. SL hitting in the same bar the order was open.
@@ -948,8 +984,8 @@ def _close_trade(self, trade: Trade, price: float, time_index: int):
948
984
self .closed_trades .append (trade ._replace (exit_price = price , exit_bar = time_index ))
949
985
self ._cash += trade .pl
950
986
951
- def _open_trade (self , price : float , size : int , sl : float , tp : float , time_index : int ):
952
- trade = Trade (self , size , price , time_index )
987
+ def _open_trade (self , price : float , size : int , sl : float , tp : float , time_index : int , tag : object ):
988
+ trade = Trade (self , size , price , time_index , tag )
953
989
self .trades .append (trade )
954
990
# Create SL/TP (bracket) orders.
955
991
# Make sure SL order is created first so it gets adversarially processed before TP order
@@ -971,6 +1007,7 @@ class Backtest:
971
1007
instance, or `backtesting.backtesting.Backtest.optimize` to
972
1008
optimize it.
973
1009
"""
1010
+
974
1011
def __init__ (self ,
975
1012
data : pd .DataFrame ,
976
1013
strategy : Type [Strategy ],
0 commit comments