Skip to content

Commit d81ae85

Browse files
authored
Merge pull request #2 from zlpatel/kernc#223
Trailing pct instead of ATR kernc#223
2 parents 2cc6724 + d6fa851 commit d81ae85

File tree

2 files changed

+49
-0
lines changed

2 files changed

+49
-0
lines changed

backtesting/lib.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,40 @@ def next(self):
451451
trade.sl = min(trade.sl or np.inf,
452452
self.data.Close[index] + self.__atr[index] * self.__n_atr)
453453

454+
class PercentageTrailingStrategy(Strategy):
455+
"""
456+
A strategy with automatic trailing stop-loss, trailing the current
457+
price at distance of some percentage. Call
458+
`PercentageTrailingStrategy.set_trailing_sl()` to set said percentage
459+
(`5` by default). See [tutorials] for usage examples.
460+
461+
[tutorials]: index.html#tutorials
462+
463+
Remember to call `super().init()` and `super().next()` in your
464+
overridden methods.
465+
"""
466+
_sl_percent = 5.
467+
def init(self):
468+
super().init()
469+
470+
def set_trailing_sl(self, percentage: float = 5):
471+
assert percentage > 0, "percentage must be greater than 0"
472+
"""
473+
Sets the future trailing stop-loss as some (`percentage`)
474+
percentage away from the current price.
475+
"""
476+
self._sl_percent = percentage
477+
478+
def next(self):
479+
super().next()
480+
index = len(self.data)-1
481+
for trade in self.trades:
482+
if trade.is_long:
483+
trade.sl = max(trade.sl or -np.inf,
484+
self.data.Close[index]*(1-(self._sl_percent/100)))
485+
else:
486+
trade.sl = min(trade.sl or np.inf,
487+
self.data.Close[index]*(1+(self._sl_percent/100)))
454488

455489
# Prevent pdoc3 documenting __init__ signature of Strategy subclasses
456490
for cls in list(globals().values()):

backtesting/test/_test.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
quantile,
2525
SignalStrategy,
2626
TrailingStrategy,
27+
PercentageTrailingStrategy,
2728
resample_apply,
2829
plot_heatmaps,
2930
random_ohlc_data,
@@ -862,6 +863,20 @@ def next(self):
862863
stats = Backtest(GOOG, S).run()
863864
self.assertEqual(stats['# Trades'], 57)
864865

866+
def test_PercentageTrailingStrategy(self):
867+
class S(PercentageTrailingStrategy):
868+
def init(self):
869+
super().init()
870+
self.set_trailing_sl(5)
871+
self.sma = self.I(lambda: self.data.Close.s.rolling(10).mean())
872+
873+
def next(self):
874+
super().next()
875+
if not self.position and self.data.Close > self.sma:
876+
self.buy()
877+
878+
stats = Backtest(GOOG, S).run()
879+
self.assertEqual(stats['# Trades'], 91)
865880

866881
class TestUtil(TestCase):
867882
def test_as_str(self):

0 commit comments

Comments
 (0)