Merge pull request #45 from quantopian/handle_no_treasury_data

Forward-fill missing treasury data
This commit is contained in:
Richard Frank
2012-12-14 14:42:53 -08:00
5 changed files with 117 additions and 89 deletions
+1 -1
View File
@@ -4,7 +4,7 @@ python:
before_install:
- sudo apt-get install gfortran
install:
- cat etc/requirements_dev.txt | grep -v "^#" | grep -v "^$" | grep -v ipython | grep -v nose | xargs pip install
- cat etc/requirements_dev.txt | grep -v "^#" | grep -v "^$" | grep -v ipython | grep -v nose== | xargs pip install
- etc/ordered_pip.sh etc/requirements.txt
before_script:
- "flake8 zipline tests"
+1
View File
@@ -4,6 +4,7 @@ pyzmq==2.2.0.1
# Testing
nose==1.2.1
nose-parameterized==0.1
nosexcover==1.0.7
coverage==3.5.3
+68 -47
View File
@@ -14,6 +14,7 @@
# limitations under the License.
import unittest
from nose_parameterized import parameterized
import copy
import random
import datetime
@@ -33,43 +34,48 @@ from zipline.finance.trading import TradingEnvironment
class PerformanceTestCase(unittest.TestCase):
def setUp(self):
self.benchmark_returns, self.treasury_curves = \
factory.load_market_data()
for n in range(100):
random_index = random.randint(
0,
len(self.treasury_curves)
)
self.dt = self.treasury_curves.keys()[random_index]
self.end_dt = self.dt + datetime.timedelta(days=365)
now = datetime.datetime.utcnow().replace(tzinfo=pytz.utc)
if self.end_dt <= now:
break
assert self.end_dt <= now, """
failed to find a date suitable daterange after 100 attempts. please double
check treasury and benchmark data in findb, and re-run the test."""
self.trading_environment = TradingEnvironment(
self.benchmark_returns,
self.treasury_curves,
period_start=self.dt,
period_end=self.end_dt
)
self.onesec = datetime.timedelta(seconds=1)
self.oneday = datetime.timedelta(days=1)
self.tradingday = datetime.timedelta(hours=6, minutes=30)
self.dt = self.trading_environment.trading_day_map.keys()[random_index]
self.trading_environment, self.dt, self.end_dt = self.create_env()
def tearDown(self):
pass
def create_env(self, start_dt=None):
benchmark_returns, treasury_curves = \
factory.load_market_data()
if not start_dt:
for n in range(100):
random_index = random.randint(
0,
len(treasury_curves)
)
start_dt = treasury_curves.keys()[random_index]
end_dt = start_dt + datetime.timedelta(days=365)
now = datetime.datetime.utcnow().replace(tzinfo=pytz.utc)
if end_dt <= now:
break
else:
end_dt = start_dt + datetime.timedelta(days=365)
now = datetime.datetime.utcnow().replace(tzinfo=pytz.utc)
assert end_dt <= now, """
failed to find a date suitable daterange after 100 attempts. please double
check treasury and benchmark data in findb, and re-run the test."""
assert start_dt < end_dt, "start_dt must be less than end_dt"
trading_environment = TradingEnvironment(
benchmark_returns,
treasury_curves,
period_start=start_dt,
period_end=end_dt
)
return trading_environment, start_dt, end_dt
def test_long_position(self):
"""
@@ -530,7 +536,18 @@ shares in position"
"should be -400 for all trades and transactions in period"
)
def test_tracker(self):
@parameterized.expand([
(datetime.datetime(year=2008,
month=10,
day=9,
tzinfo=pytz.utc),),
(datetime.datetime(year=2010,
month=10,
day=9,
tzinfo=pytz.utc),),
(None,), # random start_dt
])
def test_tracker(self, start_dt):
trade_count = 100
sid = 133
@@ -538,12 +555,15 @@ shares in position"
price_list = [price] * trade_count
volume = [100] * trade_count
trade_time_increment = datetime.timedelta(days=1)
trading_environment, start_dt, end_dt = self.create_env(start_dt)
trade_history = factory.create_trade_history(
sid,
price_list,
volume,
trade_time_increment,
self.trading_environment,
trading_environment,
source_id="factory1"
)
@@ -555,27 +575,27 @@ shares in position"
price2_list,
volume,
trade_time_increment,
self.trading_environment,
trading_environment,
source_id="factory2"
)
trade_history.extend(trade_history2)
self.trading_environment.period_start = trade_history[0].dt
self.trading_environment.period_end = trade_history[-1].dt
self.trading_environment.first_open = \
self.trading_environment.calculate_first_open()
self.trading_environment.last_close = \
self.trading_environment.calculate_last_close()
self.trading_environment.capital_base = 1000.0
self.trading_environment.frame_index = [
trading_environment.period_start = trade_history[0].dt
trading_environment.period_end = trade_history[-1].dt
trading_environment.first_open = \
trading_environment.calculate_first_open()
trading_environment.last_close = \
trading_environment.calculate_last_close()
trading_environment.capital_base = 1000.0
trading_environment.frame_index = [
'sid',
'volume',
'dt',
'price',
'changed']
perf_tracker = perf.PerformanceTracker(
self.trading_environment
trading_environment
)
# date_sort requires 'DONE' messages from each source
@@ -594,7 +614,8 @@ shares in position"
events = itertools.chain(events,
[ndict({'dt': 'DONE'})])
events = [self.event_with_txn(event) for event in events]
events = [self.event_with_txn(event, trading_environment)
for event in events]
list(perf_tracker.transform(
itertools.groupby(events, attrgetter('dt'))))
@@ -610,10 +631,10 @@ shares in position"
self.assertEqual(perf_tracker.last_close,
perf_tracker.cumulative_risk_metrics.end_date)
def event_with_txn(self, event):
def event_with_txn(self, event, env):
#create a transaction for all but
#first trade in each sid, to simulate None transaction
if event.dt != self.trading_environment.period_start \
if event.dt != env.period_start \
and event.dt != 'DONE':
txn = ndict({
'sid': event.sid,
+4 -4
View File
@@ -627,12 +627,12 @@ class Risk(unittest.TestCase):
0.0038,
0.0044,
0.0043,
0.0041])
0.004])
self.assertEqual([round(x.treasury_period_return, 4)
for x in metrics.three_month_periods],
[0.0114,
0.0118,
0.0116,
0.0122,
0.0125,
0.0129,
@@ -640,7 +640,7 @@ class Risk(unittest.TestCase):
0.0123,
0.0128,
0.0125,
0.0128])
0.0127])
self.assertEqual([round(x.treasury_period_return, 4)
for x in metrics.six_month_periods],
[0.0260,
@@ -649,7 +649,7 @@ class Risk(unittest.TestCase):
0.0252,
0.0259,
0.0256,
0.0258])
0.0257])
self.assertEqual([round(x.treasury_period_return, 4)
for x in metrics.year_periods],
+43 -37
View File
@@ -57,7 +57,6 @@ import datetime
import math
from collections import OrderedDict
import bisect
from operator import itemgetter
import numpy as np
import numpy.linalg as la
from zipline.utils.date_utils import epoch_now
@@ -304,6 +303,12 @@ class RiskMetricsBase(object):
return 1.0 - math.exp(max_drawdown)
@property
def treasury_durations(self):
return ['1month', '3month', '6month',
'1year', '2year', '3year', '5year',
'7year', '10year', '30year']
def choose_treasury(self):
td = self.end_date - self.start_date
if td.days <= 31:
@@ -327,37 +332,31 @@ class RiskMetricsBase(object):
else:
self.treasury_duration = '30year'
# in case end date is not a trading day, search for the next or
# previous market day for an interest rate. choose next in a tie.
end_day = self.end_date.replace(hour=0, minute=0, second=0)
search_day = None
if self.end_date in self.treasury_curves:
search_day = self.end_date
else:
if end_day in self.treasury_curves:
rate = self.get_treasury_rate(end_day)
if rate is not None:
search_day = end_day
if not search_day:
# in case end date is not a trading day or there is no treasury
# data, search for the previous day with an interest rate.
search_days = self.treasury_curves.keys()
next_day = prev_day = None
# Find leftmost item greater than or equal to end_date
i = bisect.bisect_left(search_days, self.end_date)
if i != len(search_days):
next_day = search_days[i]
if i:
prev_day = search_days[i - 1]
search_dist = None
if next_day and prev_day:
search_day, search_dist = \
min(((dt, self.search_day_distance(dt))
for dt in (next_day, prev_day)), key=itemgetter(1))
else:
search_day = next_day or prev_day
# Find rightmost value less than or equal to end_day
i = bisect.bisect_right(search_days, end_day)
for prev_day in search_days[i - 1::-1]:
rate = self.get_treasury_rate(prev_day)
if rate is not None:
search_day = prev_day
search_dist = self.search_day_distance(prev_day)
break
if search_day:
search_dist = search_dist or \
self.search_day_distance(search_day)
if (search_dist is None or search_dist > 1) and \
search_days[0] <= self.end_date <= search_days[-1]:
search_days[0] <= end_day <= search_days[-1]:
message = "No rate within 1 trading day of end date = \
{dt} and term = {term}. Using {search_day}. Check that date doesn't exceed \
treasury history range."
@@ -367,16 +366,8 @@ treasury history range."
log.warn(message)
if search_day:
curve = self.treasury_curves[search_day]
self.treasury_curve = curve
rate = self.treasury_curve[self.treasury_duration]
# 1month note data begins in 8/2001,
# so we can use 3month instead.
if rate is None and self.treasury_duration == '1month':
rate = self.treasury_curve['3month']
if rate is not None:
return rate * (td.days + 1) / 365
self.treasury_curve = self.treasury_curves[search_day]
return rate * (td.days + 1) / 365
message = "No rate for end date = {dt} and term = {term}. Check \
that date doesn't exceed treasury history range."
@@ -387,10 +378,25 @@ that date doesn't exceed treasury history range."
raise Exception(message)
def search_day_distance(self, dt):
tdd = self.trading_environment.trading_day_distance(self.end_date, dt)
tdd = self.trading_environment.trading_day_distance(dt, self.end_date)
if tdd is None:
return None
return tdd if tdd >= 0 else -1 * tdd + .5 # prev is 'farther'
assert tdd >= 0
return tdd
def get_treasury_rate(self, day):
rate = None
curve = self.treasury_curves[day]
# 1month note data begins in 8/2001,
# so we can use 3month instead.
idx = self.treasury_durations.index(self.treasury_duration)
for duration in self.treasury_durations[idx:]:
rate = curve[duration]
if rate is not None:
break
return rate
class RiskMetricsIterative(RiskMetricsBase):