From 46cffe3ce75863f6eb934b8f020e4184ce5249c0 Mon Sep 17 00:00:00 2001 From: Zeel Patel Date: Sat, 12 Jun 2021 15:11:58 -0700 Subject: [PATCH 1/3] Trailing pct instead of ATR #223 --- backtesting/lib.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/backtesting/lib.py b/backtesting/lib.py index f7f61e74..73d5a935 100644 --- a/backtesting/lib.py +++ b/backtesting/lib.py @@ -451,6 +451,24 @@ def next(self): trade.sl = min(trade.sl or np.inf, self.data.Close[index] + self.__atr[index] * self.__n_atr) +class PercentageTrailingStrategy(Strategy): + _sl_percent = 5 + def init(self): + super().init() + + def set_trailing_sl(self, percentage: float = 5): + self._sl_percent = percentage + + def next(self): + super().next() + index = len(self.data)-1 + for trade in self.trades: + if trade.is_long: + trade.sl = max(trade.sl or -np.inf, + self.data.Close[index]*(1-(self._sl_percent/100))) + else: + trade.sl = min(trade.sl or np.inf, + self.data.Close[index]*(1+(self._sl_percent/100))) # Prevent pdoc3 documenting __init__ signature of Strategy subclasses for cls in list(globals().values()): From 8eab87c15b226bc4c9360fe4826204ed46fb3934 Mon Sep 17 00:00:00 2001 From: Zeel Patel Date: Sat, 12 Jun 2021 20:56:32 -0700 Subject: [PATCH 2/3] Trailing pct instead of ATR kernc#223 Add code description and use float type for default percentage value. --- backtesting/lib.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/backtesting/lib.py b/backtesting/lib.py index 73d5a935..e1c5b3a4 100644 --- a/backtesting/lib.py +++ b/backtesting/lib.py @@ -452,11 +452,27 @@ def next(self): self.data.Close[index] + self.__atr[index] * self.__n_atr) class PercentageTrailingStrategy(Strategy): - _sl_percent = 5 + """ + A strategy with automatic trailing stop-loss, trailing the current + price at distance of some percentage. Call + `PercentageTrailingStrategy.set_trailing_sl()` to set said percentage + (`5` by default). See [tutorials] for usage examples. + + [tutorials]: index.html#tutorials + + Remember to call `super().init()` and `super().next()` in your + overridden methods. + """ + _sl_percent = 5. def init(self): super().init() def set_trailing_sl(self, percentage: float = 5): + assert percentage > 0, "percentage must be greater than 0" + """ + Sets the future trailing stop-loss as some (`percentage`) + percentage away from the current price. + """ self._sl_percent = percentage def next(self): From d6fa85127f5cdfa34922697e7119ab5e04ee2e4e Mon Sep 17 00:00:00 2001 From: Zeel Patel Date: Sat, 12 Jun 2021 22:56:35 -0700 Subject: [PATCH 3/3] Test case for PercentageTrailingStrategy kernc#223 --- backtesting/test/_test.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/backtesting/test/_test.py b/backtesting/test/_test.py index 85ecea6a..3cbcc372 100644 --- a/backtesting/test/_test.py +++ b/backtesting/test/_test.py @@ -24,6 +24,7 @@ quantile, SignalStrategy, TrailingStrategy, + PercentageTrailingStrategy, resample_apply, plot_heatmaps, random_ohlc_data, @@ -862,6 +863,20 @@ def next(self): stats = Backtest(GOOG, S).run() self.assertEqual(stats['# Trades'], 57) + def test_PercentageTrailingStrategy(self): + class S(PercentageTrailingStrategy): + def init(self): + super().init() + self.set_trailing_sl(5) + self.sma = self.I(lambda: self.data.Close.s.rolling(10).mean()) + + def next(self): + super().next() + if not self.position and self.data.Close > self.sma: + self.buy() + + stats = Backtest(GOOG, S).run() + self.assertEqual(stats['# Trades'], 91) class TestUtil(TestCase): def test_as_str(self):