Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CorporateActions: new algo to model dividends and splits #382

Open
wants to merge 5 commits into
base: master
Choose a base branch
from

Conversation

danilogalisteu
Copy link
Contributor

@danilogalisteu danilogalisteu commented Aug 23, 2022

This is an initial proposal for an algo to model dividends and splits. The intention is to be able to use unadjusted price data and to get historically correct transactions (position sizes and commissions). It should be tested more extensively before adoption.

The only way I could find to change positions due to splits on the fly was by changing the _position member on each security (otherwise security and portfolio values wouldn't be updated correctly). This requires the algo to be run on every row and so, it needs to be positioned before any algos that skip bars, such as RunMonthly. This and other constraints are described in the last paragraph of the docstring. This algorithm could be improved by someone more familiar with the library internals.

The input data is similar to what can be obtained from free sources such as Yahoo Finance. An example is provided below, showing backtests with adjusted prices and unadjusted prices with splits and dividends. The results will not be identical, since adjusted data considers that the dividend inflows are reinvested on the same security at the 'ex' date. The simulation using CorporateActions will get the dividend inflows as cash at the ex date, but the cash will be reinvested on the whole portfolio only at the next rebalancing event.

Finally, this simulation doesn't completely correspond to reality since dividends are paid at a later time and not on the 'ex' date. But since data about payment dates is not easy to obtain, this improvement was left for the future.

Sample code:

import pandas as pd
import yahooquery as yq
import bt


data = yq.Ticker(['GLD', 'TLT', 'VTI']).history(period='max', interval='1d').unstack('symbol')
data.index = pd.to_datetime(data.index)

divs = data['dividends']

# Yahoo data comes with 0.0 instead of 1.0 when there is no split
splits = data['splits'].replace(0.0, 1.0)

close = data['adjclose']

# Yahoo unadjusted 'close' is actually adjusted for splits but not for dividends. Here we undo it.
splits_multiplier = splits.sort_index(ascending=False).cumprod().shift(1).ffill().fillna(1.0).sort_index()
unadjclose = data['close'] * splits_multiplier

s_adj = bt.Strategy('adj', [
                        bt.algos.RunMonthly(run_on_end_of_period=True),
                        bt.algos.SelectAll(),
                        bt.algos.WeighEqually(),
                        bt.algos.Rebalance()])

s_div = bt.Strategy('div', [
                        bt.algos.CorporateActions(divs, splits),
                        bt.algos.RunMonthly(run_on_end_of_period=True),
                        bt.algos.SelectAll(),
                        bt.algos.WeighEqually(),
                        bt.algos.Rebalance()])

b_adj = bt.Backtest(s_adj, close, initial_capital=100000, commissions=lambda quantity, price: 0, integer_positions=False)
b_div = bt.Backtest(s_div, unadjclose, initial_capital=100000, commissions=lambda quantity, price: 0, integer_positions=True)

r = bt.run(b_adj, b_div)

r.display()
r.plot()

@codecov-commenter
Copy link

codecov-commenter commented Aug 23, 2022

Codecov Report

Attention: Patch coverage is 15.78947% with 16 lines in your changes missing coverage. Please review.

Project coverage is 45.24%. Comparing base (dadbc4d) to head (764a27e).
Report is 15 commits behind head on master.

Files Patch % Lines
bt/algos.py 15.78% 16 Missing ⚠️

❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #382      +/-   ##
==========================================
- Coverage   45.50%   45.24%   -0.27%     
==========================================
  Files           4        4              
  Lines        1936     1954      +18     
  Branches      449      455       +6     
==========================================
+ Hits          881      884       +3     
- Misses        998     1013      +15     
  Partials       57       57              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@timkpaine
Copy link
Collaborator

this needs a test but otherwise lgtm

This is an initial proposal for an algo to model dividends and splits. The intention is to be able to use unadjusted price data and to get historically correct transactions (position sizes and commissions). It should be tested more extensively before adoption.

The only way I could find to change positions due to splits on the fly was by changing the `_position` member on each security (otherwise security and portfolio values wouldn't be updated correctly). This requires the algo to be run on every row and so, it needs to be positioned before any algos that skip bars, such as RunMonthly. This and other constraints are described in the last paragraph of the docstring. This algorithm could be improved by someone more familiar with the library internals.

The input data is similar to what can be obtained from free sources such as Yahoo Finance. An example is provided below, showing backtests with adjusted prices and unadjusted prices with splits and dividends. The results will not be identical, since adjusted data considers that the dividend inflows are reinvested on the same security at the `ex` date. The simulation using CorporateActions will get the dividend inflows as cash at the `ex` date, but the cash will be reinvested on the whole portfolio only at the next rebalancing event.

Finally, this simulation doesn't completely correspond to reality since dividends are paid at a later time and not on the `ex` date. But since data about payment dates is not easy to obtain, this improvement was left for the future.

Sample code:
```
import pandas as pd
import yahooquery as yq
import bt

data = yq.Ticker(['GLD', 'TLT', 'VTI']).history(period='max', interval='1d').unstack('symbol')
data.index = pd.to_datetime(data.index)

divs = data['dividends']

splits = data['splits'].replace(0.0, 1.0)

close = data['adjclose']

splits_multiplier = splits.sort_index(ascending=False).cumprod().shift(1).ffill().fillna(1.0).sort_index()
unadjclose = data['close'] * splits_multiplier

s_adj = bt.Strategy('adj', [
                        bt.algos.RunMonthly(run_on_end_of_period=True),
                        bt.algos.SelectAll(),
                        bt.algos.WeighEqually(),
                        bt.algos.Rebalance()])

s_div = bt.Strategy('div', [
                        bt.algos.CorporateActions(divs, splits),
                        bt.algos.RunMonthly(run_on_end_of_period=True),
                        bt.algos.SelectAll(),
                        bt.algos.WeighEqually(),
                        bt.algos.Rebalance()])

b_adj = bt.Backtest(s_adj, close, initial_capital=100000, commissions=lambda quantity, price: 0, integer_positions=False)
b_div = bt.Backtest(s_div, unadjclose, initial_capital=100000, commissions=lambda quantity, price: 0, integer_positions=True)

r = bt.run(b_adj, b_div)

r.display()
r.plot()
```

Update algos.py

Removed white space on empty lines.
@danilogalisteu
Copy link
Contributor Author

Added test, please move it if appropriate.

…olumns); added extra price column on test data to check
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants