Skip to content

Commit

Permalink
Qs38 buy and hold rebalance business day (#393)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
juliettejames authored Apr 26, 2024
1 parent 93eb8ae commit 783ab41
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 8 deletions.
36 changes: 33 additions & 3 deletions qstrader/system/rebalance/buy_and_hold.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import pandas as pd
from pandas.tseries.offsets import BusinessDay
from qstrader.system.rebalance.rebalance import Rebalance


class BuyAndHoldRebalance(Rebalance):
"""
Generates a single rebalance timestamp at the start date in
order to create a single set of orders at the beginning of
Generates a single rebalance timestamp at the first business day
after the start date. Creates a single set of orders at the beginning of
a backtest, with no further rebalances carried out.
Parameters
Expand All @@ -15,4 +17,32 @@ class BuyAndHoldRebalance(Rebalance):

def __init__(self, start_dt):
self.start_dt = start_dt
self.rebalances = [start_dt]
self.rebalances = self._generate_rebalances()

def _is_business_day(self):
"""
Checks if the start_dt is a business day.
Returns
-------
`boolean`
"""
return bool(len(pd.bdate_range(self.start_dt, self.start_dt)))

def _generate_rebalances(self):
"""
Outputs the rebalance timestamp offset to the next
business day.
Does not include holidays.
Returns
-------
`list[pd.Timestamp]`
The rebalance timestamp list.
"""
if not self._is_business_day():
rebalance_date = self.start_dt + BusinessDay()
else:
rebalance_date = self.start_dt
return [rebalance_date]
5 changes: 4 additions & 1 deletion tests/integration/trading/test_backtest_e2e.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,14 +133,17 @@ def test_backtest_long_short_leveraged(etf_filepath):

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

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

backtest = BacktestTradingSession(
Expand Down
15 changes: 11 additions & 4 deletions tests/unit/system/rebalance/test_buy_and_hold_rebalance.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,22 @@


@pytest.mark.parametrize(
"start_dt", [('2020-01-01'), ('2020-02-02')]
"start_dt, reb_dt", [
('2020-01-01', '2020-01-01'),
('2020-02-02', '2020-02-03')
],
)
def test_buy_and_hold_rebalance(start_dt):
def test_buy_and_hold_rebalance(start_dt, reb_dt):
"""
Checks that the buy and hold rebalance sets the
appropriate internal attributes.
appropriate rebalance dates, both for a business and
a non-business day.
Does not include holidays.
"""
sd = pd.Timestamp(start_dt, tz=pytz.UTC)
rd = pd.Timestamp(reb_dt, tz=pytz.UTC)
reb = BuyAndHoldRebalance(start_dt=sd)

assert reb.start_dt == sd
assert reb.rebalances == [sd]
assert reb.rebalances == [rd]

0 comments on commit 783ab41

Please sign in to comment.