Skip to content

Commit 783ab41

Browse files
Qs38 buy and hold rebalance business day (#393)
* Updated rebalance buy and hold to check for whether backtest start date is a business day * added tests for full buy and hold rebalance e2e back test and updated test_buy_and_hold_rebalance
1 parent 93eb8ae commit 783ab41

File tree

3 files changed

+48
-8
lines changed

3 files changed

+48
-8
lines changed
Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
import pandas as pd
2+
from pandas.tseries.offsets import BusinessDay
13
from qstrader.system.rebalance.rebalance import Rebalance
24

35

46
class BuyAndHoldRebalance(Rebalance):
57
"""
6-
Generates a single rebalance timestamp at the start date in
7-
order to create a single set of orders at the beginning of
8+
Generates a single rebalance timestamp at the first business day
9+
after the start date. Creates a single set of orders at the beginning of
810
a backtest, with no further rebalances carried out.
911
1012
Parameters
@@ -15,4 +17,32 @@ class BuyAndHoldRebalance(Rebalance):
1517

1618
def __init__(self, start_dt):
1719
self.start_dt = start_dt
18-
self.rebalances = [start_dt]
20+
self.rebalances = self._generate_rebalances()
21+
22+
def _is_business_day(self):
23+
"""
24+
Checks if the start_dt is a business day.
25+
26+
Returns
27+
-------
28+
`boolean`
29+
"""
30+
return bool(len(pd.bdate_range(self.start_dt, self.start_dt)))
31+
32+
def _generate_rebalances(self):
33+
"""
34+
Outputs the rebalance timestamp offset to the next
35+
business day.
36+
37+
Does not include holidays.
38+
39+
Returns
40+
-------
41+
`list[pd.Timestamp]`
42+
The rebalance timestamp list.
43+
"""
44+
if not self._is_business_day():
45+
rebalance_date = self.start_dt + BusinessDay()
46+
else:
47+
rebalance_date = self.start_dt
48+
return [rebalance_date]

tests/integration/trading/test_backtest_e2e.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,14 +133,17 @@ def test_backtest_long_short_leveraged(etf_filepath):
133133

134134
def test_backtest_buy_and_hold(etf_filepath, capsys):
135135
"""
136+
Ensures a backtest with a buy and hold rebalance calculates
137+
the correct dates for execution orders when the start date is not
138+
a business day.
136139
"""
137140
settings.print_events=True
138141
os.environ['QSTRADER_CSV_DATA_DIR'] = etf_filepath
139142
assets = ['EQ:GHI']
140143
universe = StaticUniverse(assets)
141144
alpha_model = FixedSignalsAlphaModel({'EQ:GHI':1.0})
142145

143-
start_dt = pd.Timestamp('2015-11-09 14:30:00', tz=pytz.UTC)
146+
start_dt = pd.Timestamp('2015-11-07 14:30:00', tz=pytz.UTC)
144147
end_dt = pd.Timestamp('2015-11-10 14:30:00', tz=pytz.UTC)
145148

146149
backtest = BacktestTradingSession(

tests/unit/system/rebalance/test_buy_and_hold_rebalance.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,22 @@
66

77

88
@pytest.mark.parametrize(
9-
"start_dt", [('2020-01-01'), ('2020-02-02')]
9+
"start_dt, reb_dt", [
10+
('2020-01-01', '2020-01-01'),
11+
('2020-02-02', '2020-02-03')
12+
],
1013
)
11-
def test_buy_and_hold_rebalance(start_dt):
14+
def test_buy_and_hold_rebalance(start_dt, reb_dt):
1215
"""
1316
Checks that the buy and hold rebalance sets the
14-
appropriate internal attributes.
17+
appropriate rebalance dates, both for a business and
18+
a non-business day.
19+
20+
Does not include holidays.
1521
"""
1622
sd = pd.Timestamp(start_dt, tz=pytz.UTC)
23+
rd = pd.Timestamp(reb_dt, tz=pytz.UTC)
1724
reb = BuyAndHoldRebalance(start_dt=sd)
1825

1926
assert reb.start_dt == sd
20-
assert reb.rebalances == [sd]
27+
assert reb.rebalances == [rd]

0 commit comments

Comments
 (0)