Skip to content

Commit

Permalink
ENH: Use dividend adjusted history
Browse files Browse the repository at this point in the history
  • Loading branch information
Brian Fink committed May 6, 2015
1 parent 4210ebb commit 9fd3c4d
Show file tree
Hide file tree
Showing 9 changed files with 59 additions and 30 deletions.
28 changes: 14 additions & 14 deletions tests/history_cases.py
Expand Up @@ -45,28 +45,28 @@ def mixed_frequency_expected_data(count, frequency):
to_utc('2013-07-03 9:31AM'), 600,
)
ONE_MINUTE_PRICE_ONLY_SPECS = [
HistorySpec(1, '1m', 'price', True, data_frequency='minute'),
HistorySpec(1, '1m', 'price', True, False, data_frequency='minute'),
]
DAILY_OPEN_CLOSE_SPECS = [
HistorySpec(3, '1d', 'open_price', False, data_frequency='minute'),
HistorySpec(3, '1d', 'close_price', False, data_frequency='minute'),
HistorySpec(3, '1d', 'open_price', False, False, data_frequency='minute'),
HistorySpec(3, '1d', 'close_price', False, False, data_frequency='minute'),
]
ILLIQUID_PRICES_SPECS = [
HistorySpec(3, '1m', 'price', False, data_frequency='minute'),
HistorySpec(5, '1m', 'price', True, data_frequency='minute'),
HistorySpec(3, '1m', 'price', False, False, data_frequency='minute'),
HistorySpec(5, '1m', 'price', True, False, data_frequency='minute'),
]
MIXED_FREQUENCY_SPECS = [
HistorySpec(1, '1m', 'price', False, data_frequency='minute'),
HistorySpec(2, '1m', 'price', False, data_frequency='minute'),
HistorySpec(2, '1d', 'price', False, data_frequency='minute'),
HistorySpec(1, '1m', 'price', False, False, data_frequency='minute'),
HistorySpec(2, '1m', 'price', False, False, data_frequency='minute'),
HistorySpec(2, '1d', 'price', False, False, data_frequency='minute'),
]
MIXED_FIELDS_SPECS = [
HistorySpec(3, '1m', 'price', True, data_frequency='minute'),
HistorySpec(3, '1m', 'open_price', True, data_frequency='minute'),
HistorySpec(3, '1m', 'close_price', True, data_frequency='minute'),
HistorySpec(3, '1m', 'high', True, data_frequency='minute'),
HistorySpec(3, '1m', 'low', True, data_frequency='minute'),
HistorySpec(3, '1m', 'volume', True, data_frequency='minute'),
HistorySpec(3, '1m', 'price', True, False, data_frequency='minute'),
HistorySpec(3, '1m', 'open_price', True, False, data_frequency='minute'),
HistorySpec(3, '1m', 'close_price', True, False, data_frequency='minute'),
HistorySpec(3, '1m', 'high', True, False, data_frequency='minute'),
HistorySpec(3, '1m', 'low', True, False, data_frequency='minute'),
HistorySpec(3, '1m', 'volume', True, False, data_frequency='minute'),
]


Expand Down
11 changes: 11 additions & 0 deletions tests/test_history.py
Expand Up @@ -137,6 +137,7 @@ def get_index_at_dt(case_input):
case_input['frequency'],
None,
False,
False,
data_frequency='minute',
)
return history.index_at_dt(history_spec, case_input['algo_dt'])
Expand Down Expand Up @@ -230,13 +231,15 @@ def test_multiple_specs_on_same_bar(self):
frequency='1m',
field='price',
ffill=True,
dividend_adjusted=False,
data_frequency='minute'
)
no_fill_spec = history.HistorySpec(
bar_count=3,
frequency='1m',
field='price',
ffill=False,
dividend_adjusted=False,
data_frequency='minute'
)

Expand Down Expand Up @@ -280,6 +283,7 @@ def test_container_nans_and_daily_roll(self):
frequency='1d',
field='price',
ffill=True,
dividend_adjusted=False,
data_frequency='minute'
)
specs = {spec.key_str: spec}
Expand Down Expand Up @@ -1102,6 +1106,7 @@ def test_history_grow_length(self,
frequency=freq,
field=field,
ffill=True,
dividend_adjusted=False,
data_frequency=data_frequency,
)
specs = {spec.key_str: spec}
Expand Down Expand Up @@ -1131,13 +1136,15 @@ def test_history_grow_length(self,
frequency=freq,
field=field,
ffill=True,
dividend_adjusted=False,
data_frequency=data_frequency,
),
history.HistorySpec(
bar_count=bar_count + 2,
frequency=freq,
field=field,
ffill=True,
dividend_adjusted=False,
data_frequency=data_frequency,
),
)
Expand Down Expand Up @@ -1167,6 +1174,7 @@ def test_history_add_field(self, bar_count, freq, pair, data_frequency):
frequency=freq,
field=first,
ffill=True,
dividend_adjusted=False,
data_frequency=data_frequency,
)
specs = {spec.key_str: spec}
Expand Down Expand Up @@ -1195,6 +1203,7 @@ def test_history_add_field(self, bar_count, freq, pair, data_frequency):
frequency=freq,
field=second,
ffill=True,
dividend_adjusted=False,
data_frequency=data_frequency,
)

Expand Down Expand Up @@ -1224,6 +1233,7 @@ def test_history_add_freq(self, bar_count, pair, field, data_frequency):
frequency=first,
field=field,
ffill=True,
dividend_adjusted=False,
data_frequency=data_frequency,
)
specs = {spec.key_str: spec}
Expand Down Expand Up @@ -1252,6 +1262,7 @@ def test_history_add_freq(self, bar_count, pair, field, data_frequency):
frequency=second,
field=field,
ffill=True,
dividend_adjusted=False,
data_frequency=data_frequency,
)

Expand Down
11 changes: 11 additions & 0 deletions tests/test_perf_tracking.py
Expand Up @@ -174,12 +174,23 @@ def calculate_results(host,
perf_tracker = perf.PerformanceTracker(host.sim_params)

if dividend_events is not None:

dividend_frame = pd.DataFrame(
[
event.to_series(index=zp.DIVIDEND_FIELDS)
for event in dividend_events
],
)

# The 'id' field is set in the trading algorithm by update dividend
# For testing purposes, we are building the dividend frame and
# injecting it into the perf_tracker directly. Thus, we need to add
# the 'id' field here manually.
dividend_frame['id'] = np.arange(
len(dividend_frame)
)
dividend_frame.sort(['pay_date', 'ex_date']).set_index('id',
drop=False)
perf_tracker.update_dividends(dividend_frame)

# Raw trades
Expand Down
2 changes: 2 additions & 0 deletions zipline/algorithm.py
Expand Up @@ -1021,6 +1021,8 @@ def get_history_spec(self, bar_count, frequency, field, ffill,
self.sim_params.data_frequency,
bar_data=self._most_recent_data,
)

self.history_container.update_dividends(self.dividend_frame)
self.history_container.ensure_spec(
spec, self.datetime, self._most_recent_data,
)
Expand Down
3 changes: 1 addition & 2 deletions zipline/finance/performance/tracker.py
Expand Up @@ -62,7 +62,6 @@
import pickle
from six import iteritems

import numpy as np
import pandas as pd
from pandas.tseries.tools import normalize_date

Expand Down Expand Up @@ -211,7 +210,7 @@ def set_date(self, date):

def update_dividends(self, dividend_frame):
"""
Set the dividend_frame to the updated dividend frame.
Set the dividend_frame to the updated dividend frame.
DataFrame columns should contain at least the entries in
zp.DIVIDEND_FIELDS.
"""
Expand Down
1 change: 0 additions & 1 deletion zipline/gens/tradesimulation.py
Expand Up @@ -197,7 +197,6 @@ def _process_snapshot(self, dt, snapshot, instant_fill):
occurring in the next snapshot. This is the more conservative model,
and as such it is the default behavior in TradingAlgorithm.
"""

# Flags indicating whether we saw any events of type TRADE and type
# BENCHMARK. Respectively, these control whether or not handle_data is
# called for this snapshot and whether we emit a perf message for this
Expand Down
15 changes: 9 additions & 6 deletions zipline/history/history_container.py
Expand Up @@ -20,7 +20,6 @@
import numpy as np
import pandas as pd
from six import itervalues, iteritems, iterkeys
from datetime import date, timedelta

from . history import HistorySpec

Expand Down Expand Up @@ -487,7 +486,6 @@ def _create_panel(self, dt, spec, env=None):
window=window,
items=self.fields,
sids=self.sids,
dividend_adjusted=spec.dividend_adjusted,
initial_dates=date_buf,
)

Expand Down Expand Up @@ -717,7 +715,7 @@ def frame_from_bardata(self, data, algo_dt):
"""
data = data._data
frame_data = np.empty((len(self.fields), len(self.sids))) * np.nan

for j, sid in enumerate(self.sids):
sid_data = data.get(sid)

Expand Down Expand Up @@ -747,7 +745,7 @@ def update(self, data, algo_dt):

def update_dividends(self, dividend_frame):
"""
Set the dividend_frame to the updated dividend frame.
Set the dividend_frame to the updated dividend frame.
DataFrame columns should contain at least the entries in
zp.DIVIDEND_FIELDS.
"""
Expand Down Expand Up @@ -944,6 +942,10 @@ def get_history(self, history_spec, algo_dt):
columns=self.sids)

if do_dividend_adjust:
# return the non adjusted price if there are no dividends
if self.dividend_frame.empty:
return history_output

for security_object in history_output.columns.values:
dividends = get_dividends_for_security(self.dividend_frame,
security_object,
Expand All @@ -970,9 +972,9 @@ def get_history(self, history_spec, algo_dt):

history_output[security_object] = adjusted_pricing


return history_output


def get_dividends_for_security(dividend_frame, security_object, algo_dt):
# get the dividends for this security
dividends = dividend_frame[dividend_frame['sid'] == security_object]
Expand All @@ -984,11 +986,12 @@ def get_dividends_for_security(dividend_frame, security_object, algo_dt):

return dividends


def get_dividend_multiplier(close_price_day_before_ex_date,
dividend_amt):

return 1 - (dividend_amt/close_price_day_before_ex_date)


def fast_build_history_output(buffer_frame,
last_period,
algo_dt,
Expand Down
16 changes: 11 additions & 5 deletions zipline/protocol.py
Expand Up @@ -334,7 +334,8 @@ def __contains__(self, name):
def __repr__(self):
return "SIDData({0})".format(self.__dict__)

def _get_buffer(self, bars, field='price', raw=False):
def _get_buffer(self, bars, field='price', raw=False,
dividend_adjusted=False):
"""
Gets the result of history for the given number of bars and field.
Expand All @@ -357,6 +358,7 @@ def _get_buffer(self, bars, field='price', raw=False):
# then we need to get more history.
hst = algo.history(
bars, self._freqstr, field, ffill=True,
dividend_adjusted=dividend_adjusted,
)
# Assert that the column holds ints, not security objects.
if not isinstance(self._sid, str):
Expand Down Expand Up @@ -441,19 +443,22 @@ def minute_get_bars(days, env=None):
def mavg(self, days):
bars = self._get_bars(days)
max_bars = self._get_max_bars(days)
prices = self._get_buffer(max_bars, raw=True)[-bars:]
prices = self._get_buffer(max_bars, raw=True,
dividend_adjusted=True)[-bars:]
return nanmean(prices)

def stddev(self, days):
bars = self._get_bars(days)
max_bars = self._get_max_bars(days)
prices = self._get_buffer(max_bars, raw=True)[-bars:]
prices = self._get_buffer(max_bars, raw=True,
dividend_adjusted=True)[-bars:]
return nanstd(prices, ddof=1)

def vwap(self, days):
bars = self._get_bars(days)
max_bars = self._get_max_bars(days)
prices = self._get_buffer(max_bars, raw=True)[-bars:]
prices = self._get_buffer(max_bars, raw=True,
dividend_adjusted=True)[-bars:]
vols = self._get_buffer(max_bars, field='volume', raw=True)[-bars:]

vol_sum = nansum(vols)
Expand All @@ -470,7 +475,8 @@ def returns(self):
now = algo.datetime
if now != self._returns_cache_dt:
self._returns_cache_dt = now
self._returns_cache = algo.history(2, '1d', 'price', ffill=True)
self._returns_cache = algo.history(2, '1d', 'price', ffill=True,
dividend_adjusted=True)

hst = self._returns_cache[self._sid]
return (hst.iloc[-1] - hst.iloc[0]) / hst.iloc[0]
Expand Down
2 changes: 0 additions & 2 deletions zipline/utils/data.py
Expand Up @@ -38,7 +38,6 @@ def __init__(self,
window,
items,
sids,
dividend_adjusted,
cap_multiple=2,
dtype=np.float64,
initial_dates=None):
Expand All @@ -50,7 +49,6 @@ def __init__(self,
self.minor_axis = _ensure_index(sids)

self.cap_multiple = cap_multiple
self.dividend_adjusted = dividend_adjusted

self.dtype = dtype
if initial_dates is None:
Expand Down

0 comments on commit 9fd3c4d

Please sign in to comment.