mirror of
https://github.com/wassname/catalyst.git
synced 2026-08-08 11:16:58 +08:00
Merge pull request #45 from quantopian/handle_no_treasury_data
Forward-fill missing treasury data
This commit is contained in:
+1
-1
@@ -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"
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user