From 5d4d43ec7a6f8e1a03fc7641abe424ecaba4b916 Mon Sep 17 00:00:00 2001 From: Kong Yu Jian Date: Mon, 4 Nov 2019 12:40:39 +0800 Subject: [PATCH 1/3] added order sizing feature --- backtesting/backtesting.py | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/backtesting/backtesting.py b/backtesting/backtesting.py index 39ec1850..f9334d5f 100644 --- a/backtesting/backtesting.py +++ b/backtesting/backtesting.py @@ -50,6 +50,7 @@ class Strategy(metaclass=ABCMeta): `backtesting.backtesting.Strategy.next` to define your own strategy. """ + def __init__(self, broker, data): self._indicators = [] self._broker = broker # type: _Broker @@ -182,7 +183,7 @@ def next(self): super().next() """ - def buy(self, price=None, *, sl=None, tp=None): + def buy(self, price=None, *, sl=None, tp=None, size=None): """ Let the strategy close any current position and use _all available funds_ to @@ -195,9 +196,10 @@ def buy(self, price=None, *, sl=None, tp=None): """ self._broker.buy(price and float(price), sl and float(sl), - tp and float(tp)) + tp and float(tp), + size and float(size)) - def sell(self, price=None, *, sl=None, tp=None): + def sell(self, price=None, *, sl=None, tp=None, size=None): """ Let the strategy close any current position and use _all available funds_ to @@ -210,7 +212,8 @@ def sell(self, price=None, *, sl=None, tp=None): """ self._broker.sell(price and float(price), sl and float(sl), - tp and float(tp)) + tp and float(tp), + size and float(size)) @property def equity(self): @@ -267,16 +270,18 @@ class Orders: `backtesting.backtesting.Orders.set_sl` and `backtesting.backtesting.Orders.set_tp`. """ + def __init__(self, broker): self._broker = broker - self._entry = self._sl = self._tp = self._close = self._is_long = None + self._entry = self._sl = self._tp = self._close = self._is_long = self._size = None - def _update(self, entry, sl, tp, is_long=True): + def _update(self, entry, sl, tp, size, is_long=True): self._entry = entry and float(entry) or _MARKET_PRICE self._sl = sl and float(sl) or None self._tp = tp and float(tp) or None self._close = False self._is_long = is_long + self._size = size and float(size) or None @property def is_long(self): @@ -367,6 +372,7 @@ class Position: if self.position: ... # we have a position, either long or short """ + def __init__(self, broker): self._broker = broker @@ -459,13 +465,13 @@ def __init__(self, *, data, cash, commission, margin, trade_on_close, length): def __repr__(self): return ''.format(self._cash, self.position.pl) - def buy(self, price=None, sl=None, tp=None): + def buy(self, price=None, sl=None, tp=None, size:float=None): assert (sl or -np.inf) <= (price or self.last_close) <= (tp or np.inf), "For long orders should be: SL ({}) < BUY PRICE ({}) < TP ({})".format(sl, price or self.last_close, tp) # noqa: E501 - self.orders._update(price, sl, tp) + self.orders._update(price, sl, tp, size) - def sell(self, price=None, sl=None, tp=None): + def sell(self, price=None, sl=None, tp=None, size:float=None): assert (tp or -np.inf) <= (price or self.last_close) <= (sl or np.inf), "For short orders should be: TP ({}) < BUY PRICE ({}) < SL ({})".format(tp, price or self.last_close, sl) # noqa: E501 - self.orders._update(price, sl, tp, is_long=False) + self.orders._update(price, sl, tp, size, is_long=False) def close(self): self.orders.cancel() @@ -493,7 +499,8 @@ def _open_position(self, price, is_long): i, price = self._get_market_price(price) - position = float(self._cash * self._leverage / (price * (1 + self._commission))) + size = self._cash if self.orders._size == None else self.orders._size + position = float(size * self._leverage / (price * (1 + self._commission))) self._position = position if is_long else -position self._position_open_price = price self._position_open_i = i @@ -580,6 +587,7 @@ class Backtest: instance, or `backtesting.backtesting.Backtest.optimize` to optimize it. """ + def __init__(self, data: pd.DataFrame, strategy: type(Strategy), From 249ea74203494947a91e1dea4350a423ba1d40c1 Mon Sep 17 00:00:00 2001 From: Kong Yu Jian Date: Mon, 4 Nov 2019 13:06:29 +0800 Subject: [PATCH 2/3] wrote a simple test, and documented buy, sell --- backtesting/backtesting.py | 4 ++++ backtesting/test/_test.py | 12 ++++++++++++ 2 files changed, 16 insertions(+) diff --git a/backtesting/backtesting.py b/backtesting/backtesting.py index f9334d5f..d5d805df 100644 --- a/backtesting/backtesting.py +++ b/backtesting/backtesting.py @@ -193,6 +193,8 @@ def buy(self, price=None, *, sl=None, tp=None, size=None): one at take-profit price (`tp`; limit order). If `price` is not set, market price is assumed. + + If `size` is not set, entire holding cash is assumed. """ self._broker.buy(price and float(price), sl and float(sl), @@ -209,6 +211,8 @@ def sell(self, price=None, *, sl=None, tp=None, size=None): one at take-profit price (`tp`; limit order). If `price` is not set, market price is assumed. + + If `size` is not set, entire holding cash is assumed. """ self._broker.sell(price and float(price), sl and float(sl), diff --git a/backtesting/test/_test.py b/backtesting/test/_test.py index 3361a0be..96918e02 100644 --- a/backtesting/test/_test.py +++ b/backtesting/test/_test.py @@ -270,6 +270,17 @@ def next(self): if self.position: self.position.close() + class SingleFixedSizeTrade(Strategy): + def init(self): + self._done = False + + def next(self): + if not self._done: + self.buy(size=1000) + self._done = True + if self.position: + self.position.close() + class SinglePosition(Strategy): def init(self): pass @@ -287,6 +298,7 @@ def next(self): for strategy in (SmaCross, SingleTrade, + SingleFixedSizeTrade, SinglePosition, NoTrade): with self.subTest(strategy=strategy.__name__): From e1a4ffb8a9e333c832944e9defd72181798fe74a Mon Sep 17 00:00:00 2001 From: Kong Yu Jian Date: Mon, 4 Nov 2019 13:10:28 +0800 Subject: [PATCH 3/3] adhered to flake8 linting --- backtesting/backtesting.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/backtesting/backtesting.py b/backtesting/backtesting.py index d5d805df..6663de28 100644 --- a/backtesting/backtesting.py +++ b/backtesting/backtesting.py @@ -193,7 +193,7 @@ def buy(self, price=None, *, sl=None, tp=None, size=None): one at take-profit price (`tp`; limit order). If `price` is not set, market price is assumed. - + If `size` is not set, entire holding cash is assumed. """ self._broker.buy(price and float(price), @@ -211,7 +211,7 @@ def sell(self, price=None, *, sl=None, tp=None, size=None): one at take-profit price (`tp`; limit order). If `price` is not set, market price is assumed. - + If `size` is not set, entire holding cash is assumed. """ self._broker.sell(price and float(price), @@ -469,11 +469,11 @@ def __init__(self, *, data, cash, commission, margin, trade_on_close, length): def __repr__(self): return ''.format(self._cash, self.position.pl) - def buy(self, price=None, sl=None, tp=None, size:float=None): + def buy(self, price=None, sl=None, tp=None, size: float = None): assert (sl or -np.inf) <= (price or self.last_close) <= (tp or np.inf), "For long orders should be: SL ({}) < BUY PRICE ({}) < TP ({})".format(sl, price or self.last_close, tp) # noqa: E501 self.orders._update(price, sl, tp, size) - def sell(self, price=None, sl=None, tp=None, size:float=None): + def sell(self, price=None, sl=None, tp=None, size: float = None): assert (tp or -np.inf) <= (price or self.last_close) <= (sl or np.inf), "For short orders should be: TP ({}) < BUY PRICE ({}) < SL ({})".format(tp, price or self.last_close, sl) # noqa: E501 self.orders._update(price, sl, tp, size, is_long=False) @@ -503,7 +503,7 @@ def _open_position(self, price, is_long): i, price = self._get_market_price(price) - size = self._cash if self.orders._size == None else self.orders._size + size = self._cash if self.orders._size is None else self.orders._size position = float(size * self._leverage / (price * (1 + self._commission))) self._position = position if is_long else -position self._position_open_price = price