Last week I posted a proposal for how to get dividend-ajdusted prices into Quantopian. The good news is that one of our developers, James, found a flaw in the proposal. I love it when we find the mistakes before we've done the hard work. . . . The flaw is that we were unintentionally creating the possibility of look-ahead bias. In the original proposal, at any given moment the backtest had access to both the dividend-adjusted price and the unadjusted price. That meant that the backtest could always know if there was a dividend scheduled in the future. That's not good.
Proposed Change (revised Apr-21-2015)
In the new proposal, the use cases and the actual calculation are the same as the April 13 proposal. The implementation and API access method are different than the original proposal. The API change is around the history call, not the data object. Here is some sample code:
open_history = history(bar_count=252, frequency='1d', field='open', dividend_adjusted=True)
close_history = history(bar_count=252, frequency='1d', field='close_price', dividend_adjusted=True)
low_history = history(bar_count=252, frequency='1d', field='low', dividend_adjusted=True)
high_history = history(bar_count=252, frequency='1d', field='high', dividend_adjusted=True)
The key concept in the new proposal is: throughout the course of a backtest, the dividend adjusted price and the current price are always the same ("current" in this context is the datetime within the backtest). This is perhaps best explained through example.
Company ABC puts out a $1 dividend quarterly with ex-dates of Jan 17, Apr 16, Jul 15, and Oct 15 in 2012. Company ABC is really boring and has the flattest stock price you've ever seen. It trades at 100 on the first day of the year and declines by a dollar on each ex-date as you'd expect, closing out the year at 96. In your algorithm you create a close_history dataframe as described in the example above. You want to run a backtest over 2012. During the course of your backtest, the prices look like this:
This can be a bit non-intuitive because the calculated dividend-adjusted price for a given day (i.e. Jan 3 2012) is different depending on what day the backtest is currently simulating. But when you think about it, it makes sense. At the end of 2012, the value of owning ABC's stock is exactly the same as it was on the first day of the year. The dividend-adjusted price for ABC is flat at all times. You can reconstruct this example with a more volatile stock and the principle is the same.
Most importantly, this implementation solves the algorithm writing use cases that we were targetting in the beginning.
Notes
- The way that the Quantopian backtester handles dividends is unaffected. You still get dividends as cash payments. It's probably worth a minute of your time to review the current implementation:
- With the change, your portfolio value is unaffected - still the same split-adjusted price times the same volume.
- Even with the change, your portfolio overall returns will still have a "jag" during dividends. That's because your portfolio loses value on the ex-date when the price of the stock drops, and the portfolio recovers a few days later when the actual cash is delivered.
How it's calculated
Quantopian already provides split-adjusted prices. Our documentation describes it this way: "Our data uses adjusted close prices. That means that the effect of all stock splits and merger activity are applied to price and volume data. As an example: A stock is trading at $100, and has a 2:1 split. The new price is $50, and all past price data is retroactively updated 2:1 (volume data is correspondingly updated 1:2)."
In the same way, we will apply a dividend multiplier. When there is a dividend, all past price data will be updated by the dividend multiplier. (Yahoo's adjusted price is computed by the same process.
dividend_multiplier = 1 - (dividend_amount/close_price_pre_ex_date) = 0.nnnnn
Use cases
- You want to know the long-term economic return for a given security
close_history = history(bar_count=252, frequency='1d', field='close_price', dividend_adjusted=True)
returns = close_history.iloc[[0, -1]].pct_change()
- You want to track the moving average, or perform any other calculations on a look-back window
close_history = history(bar_count=252, frequency='1d', field='close_price', dividend_adjusted=True)
returns = close_history.mean
- You want to open or close a position (long or short) based on the upcoming dividend, or study price behavior around dividend surprises
- Unfortunately, this is not supported still.