Merge pull request #1346 from quantopian/check-exchange-time

ENH: time/calendar business logic improvements
This commit is contained in:
Jean Bredeche
2016-08-04 10:04:55 -04:00
committed by GitHub
35 changed files with 702 additions and 268 deletions
+3 -3
View File
@@ -56,13 +56,13 @@ class CalendarRegistrationTestCase(TestCase):
dummy_cal = self.dummy_cal_type('DMY')
# Try to register and retrieve the calendar
register_calendar(dummy_cal)
register_calendar('DMY', dummy_cal)
retr_cal = get_calendar('DMY')
self.assertEqual(dummy_cal, retr_cal)
# Try to register again, expecting a name collision
with self.assertRaises(CalendarNameCollision):
register_calendar(dummy_cal)
register_calendar('DMY', dummy_cal)
# Deregister the calendar and ensure that it is removed
deregister_calendar('DMY')
@@ -76,7 +76,7 @@ class CalendarRegistrationTestCase(TestCase):
real_nyse = get_calendar('NYSE')
# Force a registration of the dummy NYSE
register_calendar(dummy_nyse, force=True)
register_calendar("NYSE", dummy_nyse, force=True)
# Ensure that the dummy overwrote the real calendar
retr_cal = get_calendar('NYSE')
+3 -3
View File
@@ -17,7 +17,7 @@ from unittest import TestCase
from zipline.finance.cancel_policy import NeverCancel, EODCancel
from zipline.gens.sim_engine import (
BAR,
DAY_END
SESSION_END
)
@@ -25,10 +25,10 @@ class CancelPolicyTestCase(TestCase):
def test_eod_cancel(self):
cancel_policy = EODCancel()
self.assertTrue(cancel_policy.should_cancel(DAY_END))
self.assertTrue(cancel_policy.should_cancel(SESSION_END))
self.assertFalse(cancel_policy.should_cancel(BAR))
def test_never_cancel(self):
cancel_policy = NeverCancel()
self.assertFalse(cancel_policy.should_cancel(DAY_END))
self.assertFalse(cancel_policy.should_cancel(SESSION_END))
self.assertFalse(cancel_policy.should_cancel(BAR))
+1 -1
View File
@@ -165,7 +165,7 @@ class SliceTestCase(WithSeededRandomPipelineEngine, ZiplineTestCase):
Test that indexing into a term with a non-existent asset raises the
proper exception.
"""
my_asset = Asset(0)
my_asset = Asset(0, exchange="TEST")
returns = Returns(window_length=2, inputs=[self.col])
returns_slice = returns[my_asset]
+1 -1
View File
@@ -314,7 +314,7 @@ class StatisticalBuiltInsTestCase(WithTradingEnvironment, ZiplineTestCase):
`RollingLinearRegressionOfReturns` raise the proper exception when
given a nonexistent target asset.
"""
my_asset = Equity(0)
my_asset = Equity(0, exchange="TEST")
start_date = self.pipeline_start_date
end_date = self.pipeline_end_date
run_pipeline = self.run_pipeline
+1 -1
View File
@@ -290,7 +290,7 @@ class ObjectIdentityTestCase(TestCase):
self.assertIs(beta, multiple_outputs.beta)
def test_instance_caching_of_slices(self):
my_asset = Asset(1)
my_asset = Asset(1, exchange="TEST")
f = GenericCustomFactor()
f_slice = f[my_asset]
Binary file not shown.
+62 -34
View File
@@ -12,11 +12,11 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import warnings
from collections import namedtuple
import datetime
from datetime import timedelta
from textwrap import dedent
import warnings
from unittest import skip
from copy import deepcopy
@@ -32,11 +32,10 @@ from testfixtures import TempDirectory
import numpy as np
import pandas as pd
import pytz
from pandas.io.common import PerformanceWarning
from zipline import (
run_algorithm,
TradingAlgorithm,
)
from zipline import run_algorithm
from zipline import TradingAlgorithm
from zipline.api import FixedSlippage
from zipline.assets import Equity, Future
from zipline.assets.synthetic import (
@@ -164,7 +163,7 @@ from zipline.test_algorithms import (
no_handle_data,
)
from zipline.utils.api_support import ZiplineAPI, set_algo_instance
from zipline.utils.calendars import get_calendar
from zipline.utils.calendars import get_calendar, register_calendar
from zipline.utils.context_tricks import CallbackManager
from zipline.utils.control_flow import nullctx
import zipline.utils.events
@@ -211,10 +210,12 @@ class TestMiscellaneousAPI(WithLogger,
pd.DataFrame.from_dict(
{3: {'symbol': 'PLAY',
'start_date': '2002-01-01',
'end_date': '2004-01-01'},
'end_date': '2004-01-01',
'exchange': 'TEST'},
4: {'symbol': 'PLAY',
'start_date': '2005-01-01',
'end_date': '2006-01-01'}},
'end_date': '2006-01-01',
'exchange': 'TEST'}},
orient='index',
),
))
@@ -228,25 +229,33 @@ class TestMiscellaneousAPI(WithLogger,
'root_symbol': 'CL',
'start_date': pd.Timestamp('2005-12-01', tz='UTC'),
'notice_date': pd.Timestamp('2005-12-20', tz='UTC'),
'expiration_date': pd.Timestamp('2006-01-20', tz='UTC')},
'expiration_date': pd.Timestamp('2006-01-20', tz='UTC'),
'exchange': 'TEST'
},
6: {
'root_symbol': 'CL',
'symbol': 'CLK06',
'start_date': pd.Timestamp('2005-12-01', tz='UTC'),
'notice_date': pd.Timestamp('2006-03-20', tz='UTC'),
'expiration_date': pd.Timestamp('2006-04-20', tz='UTC')},
'expiration_date': pd.Timestamp('2006-04-20', tz='UTC'),
'exchange': 'TEST',
},
7: {
'symbol': 'CLQ06',
'root_symbol': 'CL',
'start_date': pd.Timestamp('2005-12-01', tz='UTC'),
'notice_date': pd.Timestamp('2006-06-20', tz='UTC'),
'expiration_date': pd.Timestamp('2006-07-20', tz='UTC')},
'expiration_date': pd.Timestamp('2006-07-20', tz='UTC'),
'exchange': 'TEST',
},
8: {
'symbol': 'CLX06',
'root_symbol': 'CL',
'start_date': pd.Timestamp('2006-02-01', tz='UTC'),
'notice_date': pd.Timestamp('2006-09-20', tz='UTC'),
'expiration_date': pd.Timestamp('2006-10-20', tz='UTC')}
'expiration_date': pd.Timestamp('2006-10-20', tz='UTC'),
'exchange': 'TEST',
}
},
orient='index',
)
@@ -738,6 +747,7 @@ def handle_data(context, data):
'symbol': 'DUP',
'start_date': date.value,
'end_date': (date + timedelta(days=1)).value,
'exchange': 'TEST',
}
for i, date in enumerate(dates)
]
@@ -781,10 +791,13 @@ class TestTransformAlgorithm(WithLogger,
@classmethod
def make_futures_info(cls):
return pd.DataFrame.from_dict(
{3: {'multiplier': 10, 'symbol': 'F'}},
orient='index',
)
return pd.DataFrame.from_dict({
3: {
'multiplier': 10,
'symbol': 'F',
'exchange': 'TEST'
}
}, orient='index')
@classmethod
def make_equity_daily_bar_data(cls):
@@ -990,7 +1003,8 @@ def before_trading_start(context, data):
period_end = pd.Timestamp('2002-1-4', tz='UTC')
equities = pd.DataFrame([{
'start_date': start_session,
'end_date': period_end + timedelta(days=1)
'end_date': period_end + timedelta(days=1),
'exchange': "TEST",
}] * 2)
equities['symbol'] = ['A', 'B']
with TempDirectory() as tempdir, \
@@ -1439,6 +1453,8 @@ class TestAlgoScript(WithLogger,
@classmethod
def make_equity_info(cls):
register_calendar("TEST", get_calendar("NYSE"), force=True)
data = make_simple_equity_info(
cls.sids,
cls.START_DATE,
@@ -1773,6 +1789,7 @@ def handle_data(context, data):
Test that api methods on the data object can be called with positional
arguments.
"""
params = SimulationParameters(
start_session=pd.Timestamp("2006-01-10", tz='UTC'),
end_session=pd.Timestamp("2006-01-11", tz='UTC'),
@@ -1961,6 +1978,8 @@ def handle_data(context, data):
""")
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("ignore", PerformanceWarning)
algo = TradingAlgorithm(
script=algocode,
sim_params=sim_params,
@@ -1968,18 +1987,19 @@ def handle_data(context, data):
)
algo.run(self.data_portal)
self.assertEqual(len(w), 2)
for i, warning in enumerate(w):
self.assertIsInstance(warning.message, UserWarning)
self.assertEqual(
warning.message.args[0],
'Got a time rule for the second positional argument '
'date_rule. You should use keyword argument '
'time_rule= when calling schedule_function without '
'specifying a date_rule'
)
# The warnings come from line 13 and 14 in the algocode
self.assertEqual(warning.lineno, 13 + i)
self.assertEqual(len(w), 2)
for i, warning in enumerate(w):
self.assertIsInstance(warning.message, UserWarning)
self.assertEqual(
warning.message.args[0],
'Got a time rule for the second positional argument '
'date_rule. You should use keyword argument '
'time_rule= when calling schedule_function without '
'specifying a date_rule'
)
# The warnings come from line 13 and 14 in the algocode
self.assertEqual(warning.lineno, 13 + i)
self.assertEqual(
algo.done_at_open,
@@ -2855,7 +2875,8 @@ class TestTradingControls(WithSimParams, WithDataPortal, ZiplineTestCase):
1: {
'symbol': 'SYM',
'start_date': start,
'end_date': start + timedelta(days=6)
'end_date': start + timedelta(days=6),
'exchange': "TEST",
},
},
orient='index',
@@ -2984,6 +3005,8 @@ class TestTradingControls(WithSimParams, WithDataPortal, ZiplineTestCase):
'symbol': 'SYM',
'start_date': self.sim_params.start_session,
'end_date': '2020-01-01',
'exchange': "TEST",
'sid': 999,
}])
with TempDirectory() as tempdir, \
tmp_trading_env(equities=metadata) as env:
@@ -2995,7 +3018,7 @@ class TestTradingControls(WithSimParams, WithDataPortal, ZiplineTestCase):
env.asset_finder,
tempdir,
self.sim_params,
[0],
[999],
self.trading_calendar,
)
algo.run(data_portal)
@@ -3004,6 +3027,8 @@ class TestTradingControls(WithSimParams, WithDataPortal, ZiplineTestCase):
'symbol': 'SYM',
'start_date': '1989-01-01',
'end_date': '1990-01-01',
'exchange': "TEST",
'sid': 999,
}])
with TempDirectory() as tempdir, \
tmp_trading_env(equities=metadata) as env:
@@ -3011,7 +3036,7 @@ class TestTradingControls(WithSimParams, WithDataPortal, ZiplineTestCase):
env.asset_finder,
tempdir,
self.sim_params,
[0],
[999],
self.trading_calendar,
)
algo = SetAssetDateBoundsAlgorithm(
@@ -3025,6 +3050,8 @@ class TestTradingControls(WithSimParams, WithDataPortal, ZiplineTestCase):
'symbol': 'SYM',
'start_date': '2020-01-01',
'end_date': '2021-01-01',
'exchange': "TEST",
'sid': 999,
}])
with TempDirectory() as tempdir, \
tmp_trading_env(equities=metadata) as env:
@@ -3032,7 +3059,7 @@ class TestTradingControls(WithSimParams, WithDataPortal, ZiplineTestCase):
env.asset_finder,
tempdir,
self.sim_params,
[0],
[999],
self.trading_calendar,
)
algo = SetAssetDateBoundsAlgorithm(
@@ -4054,7 +4081,8 @@ class TestOrderAfterDelist(WithTradingEnvironment, ZiplineTestCase):
'start_date': cls.start,
'end_date': cls.day_1,
'auto_close_date': cls.day_4,
'symbol': "ASSET1"
'symbol': "ASSET1",
'exchange': "TEST",
},
},
orient='index',
+7
View File
@@ -3,6 +3,7 @@ import warnings
from mock import patch
import numpy as np
import pandas as pd
from pandas.io.common import PerformanceWarning
from zipline import TradingAlgorithm
from zipline.finance.trading import SimulationParameters
@@ -291,6 +292,7 @@ class TestAPIShim(WithDataPortal, WithSimParams, ZiplineTestCase):
deprecation warning.
"""
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("ignore", PerformanceWarning)
warnings.simplefilter("default", ZiplineDeprecationWarning)
algo = self.create_algo(sid_accessor_algo)
algo.run(self.data_portal)
@@ -319,6 +321,7 @@ class TestAPIShim(WithDataPortal, WithSimParams, ZiplineTestCase):
in `data` is deprecated.
"""
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("ignore", PerformanceWarning)
warnings.simplefilter("default", ZiplineDeprecationWarning)
algo = self.create_algo(data_items_algo)
algo.run(self.data_portal)
@@ -343,6 +346,7 @@ class TestAPIShim(WithDataPortal, WithSimParams, ZiplineTestCase):
def test_iterate_data(self):
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("ignore", PerformanceWarning)
warnings.simplefilter("default", ZiplineDeprecationWarning)
algo = self.create_algo(simple_algo)
@@ -373,6 +377,7 @@ class TestAPIShim(WithDataPortal, WithSimParams, ZiplineTestCase):
def test_history(self):
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("ignore", PerformanceWarning)
warnings.simplefilter("default", ZiplineDeprecationWarning)
sim_params = self.sim_params.create_new(
@@ -414,6 +419,7 @@ class TestAPIShim(WithDataPortal, WithSimParams, ZiplineTestCase):
def test_simple_transforms(self):
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("ignore", PerformanceWarning)
warnings.simplefilter("default", ZiplineDeprecationWarning)
sim_params = SimulationParameters(
@@ -484,6 +490,7 @@ class TestAPIShim(WithDataPortal, WithSimParams, ZiplineTestCase):
def test_manipulation(self):
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("ignore", PerformanceWarning)
warnings.simplefilter("default", ZiplineDeprecationWarning)
algo = self.create_algo(simple_algo)
+71 -47
View File
@@ -110,21 +110,21 @@ def build_lookup_generic_cases(asset_finder_type):
'symbol': 'duplicated',
'start_date': dupe_0_start.value,
'end_date': dupe_0_end.value,
'exchange': '',
'exchange': 'TEST',
},
{
'sid': 1,
'symbol': 'duplicated',
'start_date': dupe_1_start.value,
'end_date': dupe_1_end.value,
'exchange': '',
'exchange': 'TEST',
},
{
'sid': 2,
'symbol': 'unique',
'start_date': unique_start.value,
'end_date': unique_end.value,
'exchange': '',
'exchange': 'TEST',
},
],
index='sid')
@@ -194,15 +194,21 @@ class AssetTestCase(TestCase):
exchange='THE MOON',
)
asset3 = Asset(3, exchange="test")
asset4 = Asset(4, exchange="test")
asset5 = Asset(5, exchange="still testing")
def test_asset_object(self):
self.assertEquals({5061: 'foo'}[Asset(5061)], 'foo')
self.assertEquals(Asset(5061), 5061)
self.assertEquals(5061, Asset(5061))
the_asset = Asset(5061, exchange="bar")
self.assertEquals(Asset(5061), Asset(5061))
self.assertEquals(int(Asset(5061)), 5061)
self.assertEquals({5061: 'foo'}[the_asset], 'foo')
self.assertEquals(the_asset, 5061)
self.assertEquals(5061, the_asset)
self.assertEquals(str(Asset(5061)), 'Asset(5061)')
self.assertEquals(the_asset, the_asset)
self.assertEquals(int(the_asset), 5061)
self.assertEquals(str(the_asset), 'Asset(5061)')
def test_to_and_from_dict(self):
asset_from_dict = Asset.from_dict(self.asset.to_dict())
@@ -220,8 +226,8 @@ class AssetTestCase(TestCase):
def test_asset_comparisons(self):
s_23 = Asset(23)
s_24 = Asset(24)
s_23 = Asset(23, exchange="test")
s_24 = Asset(24, exchange="test")
self.assertEqual(s_23, s_23)
self.assertEqual(s_23, 23)
@@ -250,39 +256,39 @@ class AssetTestCase(TestCase):
self.assertGreater(s_24, s_23)
def test_lt(self):
self.assertTrue(Asset(3) < Asset(4))
self.assertFalse(Asset(4) < Asset(4))
self.assertFalse(Asset(5) < Asset(4))
self.assertTrue(self.asset3 < self.asset4)
self.assertFalse(self.asset4 < self.asset4)
self.assertFalse(self.asset5 < self.asset4)
def test_le(self):
self.assertTrue(Asset(3) <= Asset(4))
self.assertTrue(Asset(4) <= Asset(4))
self.assertFalse(Asset(5) <= Asset(4))
self.assertTrue(self.asset3 <= self.asset4)
self.assertTrue(self.asset4 <= self.asset4)
self.assertFalse(self.asset5 <= self.asset4)
def test_eq(self):
self.assertFalse(Asset(3) == Asset(4))
self.assertTrue(Asset(4) == Asset(4))
self.assertFalse(Asset(5) == Asset(4))
self.assertFalse(self.asset3 == self.asset4)
self.assertTrue(self.asset4 == self.asset4)
self.assertFalse(self.asset5 == self.asset4)
def test_ge(self):
self.assertFalse(Asset(3) >= Asset(4))
self.assertTrue(Asset(4) >= Asset(4))
self.assertTrue(Asset(5) >= Asset(4))
self.assertFalse(self.asset3 >= self.asset4)
self.assertTrue(self.asset4 >= self.asset4)
self.assertTrue(self.asset5 >= self.asset4)
def test_gt(self):
self.assertFalse(Asset(3) > Asset(4))
self.assertFalse(Asset(4) > Asset(4))
self.assertTrue(Asset(5) > Asset(4))
self.assertFalse(self.asset3 > self.asset4)
self.assertFalse(self.asset4 > self.asset4)
self.assertTrue(self.asset5 > self.asset4)
def test_type_mismatch(self):
if sys.version_info.major < 3:
self.assertIsNotNone(Asset(3) < 'a')
self.assertIsNotNone('a' < Asset(3))
self.assertIsNotNone(self.asset3 < 'a')
self.assertIsNotNone('a' < self.asset3)
else:
with self.assertRaises(TypeError):
Asset(3) < 'a'
self.asset3 < 'a'
with self.assertRaises(TypeError):
'a' < Asset(3)
'a' < self.asset3
class TestFuture(WithAssetFinder, ZiplineTestCase):
@@ -298,6 +304,7 @@ class TestFuture(WithAssetFinder, ZiplineTestCase):
'auto_close_date': pd.Timestamp('2014-01-18', tz='UTC'),
'tick_size': .01,
'multiplier': 500.0,
'exchange': "TEST",
},
0: {
'symbol': 'CLG06',
@@ -306,6 +313,7 @@ class TestFuture(WithAssetFinder, ZiplineTestCase):
'notice_date': pd.Timestamp('2005-12-20', tz='UTC'),
'expiration_date': pd.Timestamp('2006-01-20', tz='UTC'),
'multiplier': 1.0,
'exchange': 'TEST',
},
},
orient='index',
@@ -423,6 +431,7 @@ class AssetFinderTestCase(WithTradingCalendar, ZiplineTestCase):
'symbol': 'TEST.%d' % sid,
'start_date': as_of.value,
'end_date': as_of.value,
'exchange': uuid.uuid4().hex
}
for sid in sids
]
@@ -471,9 +480,9 @@ class AssetFinderTestCase(WithTradingCalendar, ZiplineTestCase):
def test_lookup_symbol_fuzzy(self):
metadata = pd.DataFrame.from_records([
{'symbol': 'PRTY_HRD'},
{'symbol': 'BRKA'},
{'symbol': 'BRK_A'},
{'symbol': 'PRTY_HRD', 'exchange': "TEST"},
{'symbol': 'BRKA', 'exchange': "TEST"},
{'symbol': 'BRK_A', 'exchange': "TEST"},
])
self.write_assets(equities=metadata)
finder = self.asset_finder
@@ -516,11 +525,13 @@ class AssetFinderTestCase(WithTradingCalendar, ZiplineTestCase):
'symbol': 'A',
'start_date': T('2014-01-01'),
'end_date': T('2014-01-05'),
'exchange': "TEST",
},
{
'symbol': 'B',
'start_date': T('2014-01-06'),
'end_date': T('2014-01-10'),
'exchange': "TEST",
},
# sid 1
@@ -528,11 +539,13 @@ class AssetFinderTestCase(WithTradingCalendar, ZiplineTestCase):
'symbol': 'C',
'start_date': T('2014-01-01'),
'end_date': T('2014-01-05'),
'exchange': "TEST",
},
{
'symbol': 'A', # claiming the unused symbol 'A'
'start_date': T('2014-01-06'),
'end_date': T('2014-01-10'),
'exchange': "TEST",
},
],
index=[0, 0, 1, 1],
@@ -697,14 +710,14 @@ class AssetFinderTestCase(WithTradingCalendar, ZiplineTestCase):
'symbol': 'real',
'start_date': pd.Timestamp('2013-1-1', tz='UTC'),
'end_date': pd.Timestamp('2014-1-1', tz='UTC'),
'exchange': '',
'exchange': 'TEST',
},
{
'sid': 1,
'symbol': 'also_real',
'start_date': pd.Timestamp('2013-1-1', tz='UTC'),
'end_date': pd.Timestamp('2014-1-1', tz='UTC'),
'exchange': '',
'exchange': 'TEST',
},
# Sid whose end date is before our query date. We should
# still correctly find it.
@@ -713,7 +726,7 @@ class AssetFinderTestCase(WithTradingCalendar, ZiplineTestCase):
'symbol': 'real_but_old',
'start_date': pd.Timestamp('2002-1-1', tz='UTC'),
'end_date': pd.Timestamp('2003-1-1', tz='UTC'),
'exchange': '',
'exchange': 'TEST',
},
# Sid whose start_date is **after** our query date. We should
# **not** find it.
@@ -749,7 +762,8 @@ class AssetFinderTestCase(WithTradingCalendar, ZiplineTestCase):
# Build an asset with an end_date
eq_end = pd.Timestamp('2012-01-01', tz='UTC')
equity_asset = Equity(1, symbol="TESTEQ", end_date=eq_end)
equity_asset = Equity(1, symbol="TESTEQ", end_date=eq_end,
exchange="TEST")
# Catch all warnings
with warnings.catch_warnings(record=True) as w:
@@ -772,14 +786,16 @@ class AssetFinderTestCase(WithTradingCalendar, ZiplineTestCase):
'root_symbol': 'AD',
'notice_date': pd.Timestamp('2015-06-14', tz='UTC'),
'expiration_date': pd.Timestamp('2015-08-14', tz='UTC'),
'start_date': pd.Timestamp('2015-01-01', tz='UTC')
'start_date': pd.Timestamp('2015-01-01', tz='UTC'),
'exchange': "TEST",
},
{
'symbol': 'ADV15',
'root_symbol': 'AD',
'notice_date': pd.Timestamp('2015-05-14', tz='UTC'),
'expiration_date': pd.Timestamp('2015-09-14', tz='UTC'),
'start_date': pd.Timestamp('2015-01-01', tz='UTC')
'start_date': pd.Timestamp('2015-01-01', tz='UTC'),
'exchange': "TEST",
},
# Starts trading today, so should be valid.
{
@@ -787,7 +803,8 @@ class AssetFinderTestCase(WithTradingCalendar, ZiplineTestCase):
'root_symbol': 'AD',
'notice_date': pd.Timestamp('2015-11-16', tz='UTC'),
'expiration_date': pd.Timestamp('2015-12-16', tz='UTC'),
'start_date': pd.Timestamp('2015-05-14', tz='UTC')
'start_date': pd.Timestamp('2015-05-14', tz='UTC'),
'exchange': "TEST",
},
# Starts trading in August, so not valid.
{
@@ -795,7 +812,8 @@ class AssetFinderTestCase(WithTradingCalendar, ZiplineTestCase):
'root_symbol': 'AD',
'notice_date': pd.Timestamp('2015-11-16', tz='UTC'),
'expiration_date': pd.Timestamp('2015-12-16', tz='UTC'),
'start_date': pd.Timestamp('2015-08-01', tz='UTC')
'start_date': pd.Timestamp('2015-08-01', tz='UTC'),
'exchange': "TEST",
},
# Notice date comes after expiration
{
@@ -803,7 +821,8 @@ class AssetFinderTestCase(WithTradingCalendar, ZiplineTestCase):
'root_symbol': 'AD',
'notice_date': pd.Timestamp('2016-11-25', tz='UTC'),
'expiration_date': pd.Timestamp('2016-11-16', tz='UTC'),
'start_date': pd.Timestamp('2015-08-01', tz='UTC')
'start_date': pd.Timestamp('2015-08-01', tz='UTC'),
'exchange': "TEST",
},
# This contract has no start date and also this contract should be
# last in all chains
@@ -811,7 +830,8 @@ class AssetFinderTestCase(WithTradingCalendar, ZiplineTestCase):
'symbol': 'ADZ20',
'root_symbol': 'AD',
'notice_date': pd.Timestamp('2020-11-25', tz='UTC'),
'expiration_date': pd.Timestamp('2020-11-16', tz='UTC')
'expiration_date': pd.Timestamp('2020-11-16', tz='UTC'),
'exchange': "TEST",
},
])
self.write_assets(futures=metadata)
@@ -850,10 +870,10 @@ class AssetFinderTestCase(WithTradingCalendar, ZiplineTestCase):
# Build an empty finder and some Assets
dt = pd.Timestamp('2014-01-01', tz='UTC')
finder = self.asset_finder
asset1 = Equity(1, symbol="AAPL")
asset2 = Equity(2, symbol="GOOG")
asset200 = Future(200, symbol="CLK15")
asset201 = Future(201, symbol="CLM15")
asset1 = Equity(1, symbol="AAPL", exchange="TEST")
asset2 = Equity(2, symbol="GOOG", exchange="TEST")
asset200 = Future(200, symbol="CLK15", exchange="TEST")
asset201 = Future(201, symbol="CLM15", exchange="TEST")
# Check for correct mapping and types
pre_map = [asset1, asset2, asset200, asset201]
@@ -1103,6 +1123,7 @@ class TestFutureChain(WithAssetFinder, ZiplineTestCase):
'start_date': pd.Timestamp('2005-12-01', tz='UTC'),
'notice_date': pd.Timestamp('2005-12-20', tz='UTC'),
'expiration_date': pd.Timestamp('2006-01-20', tz='UTC'),
'exchange': "TEST",
},
{
'root_symbol': 'CL',
@@ -1110,6 +1131,7 @@ class TestFutureChain(WithAssetFinder, ZiplineTestCase):
'start_date': pd.Timestamp('2005-12-01', tz='UTC'),
'notice_date': pd.Timestamp('2006-03-20', tz='UTC'),
'expiration_date': pd.Timestamp('2006-04-20', tz='UTC'),
'exchange': "TEST",
},
{
'symbol': 'CLQ06',
@@ -1117,6 +1139,7 @@ class TestFutureChain(WithAssetFinder, ZiplineTestCase):
'start_date': pd.Timestamp('2005-12-01', tz='UTC'),
'notice_date': pd.Timestamp('2006-06-20', tz='UTC'),
'expiration_date': pd.Timestamp('2006-07-20', tz='UTC'),
'exchange': "TEST",
},
{
'symbol': 'CLX06',
@@ -1124,6 +1147,7 @@ class TestFutureChain(WithAssetFinder, ZiplineTestCase):
'start_date': pd.Timestamp('2006-02-01', tz='UTC'),
'notice_date': pd.Timestamp('2006-09-20', tz='UTC'),
'expiration_date': pd.Timestamp('2006-10-20', tz='UTC'),
'exchange': "TEST",
}
])
+100 -14
View File
@@ -131,6 +131,30 @@ class TestMinuteBarData(WithBarDataChecks,
50,
)
@classmethod
def make_futures_info(cls):
return pd.DataFrame.from_dict(
{
6: {
'symbol': 'CLG06',
'root_symbol': 'CL',
'start_date': pd.Timestamp('2005-12-01', tz='UTC'),
'notice_date': pd.Timestamp('2005-12-20', tz='UTC'),
'expiration_date': pd.Timestamp('2006-01-20', tz='UTC'),
'exchange': 'ICEUS',
},
7: {
'symbol': 'CLK06',
'root_symbol': 'CL',
'start_date': pd.Timestamp('2005-12-01', tz='UTC'),
'notice_date': pd.Timestamp('2006-03-20', tz='UTC'),
'expiration_date': pd.Timestamp('2006-04-20', tz='UTC'),
'exchange': 'ICEUS',
},
},
orient='index',
)
@classmethod
def make_splits_data(cls):
return pd.DataFrame([
@@ -438,7 +462,7 @@ class TestMinuteBarData(WithBarDataChecks,
bd.current(self.HILARIOUSLY_ILLIQUID_ASSET, "volume")
)
def test_can_trade_at_midnight(self):
def test_can_trade_during_non_market_hours(self):
# make sure that if we use `can_trade` at midnight, we don't pretend
# we're in the previous day's last minute
the_day_after = self.trading_calendar.next_session_label(
@@ -453,19 +477,66 @@ class TestMinuteBarData(WithBarDataChecks,
with handle_non_market_minutes(bar_data):
self.assertFalse(bar_data.can_trade(asset))
# but make sure it works when the assets are alive
# NYSE is closed at midnight, so even if the asset is alive, can_trade
# should return False
bar_data2 = BarData(
self.data_portal,
lambda: self.equity_minute_bar_days[1],
"minute",
)
for asset in [self.ASSET1, self.HILARIOUSLY_ILLIQUID_ASSET]:
self.assertTrue(bar_data2.can_trade(asset))
self.assertFalse(bar_data2.can_trade(asset))
with handle_non_market_minutes(bar_data2):
self.assertTrue(bar_data2.can_trade(asset))
self.assertFalse(bar_data2.can_trade(asset))
def test_is_stale_at_midnight(self):
def test_can_trade_exchange_closed(self):
nyse_asset = self.asset_finder.retrieve_asset(1)
ice_asset = self.asset_finder.retrieve_asset(6)
# minutes we're going to check (to verify that that the same bardata
# can check multiple exchange calendars, all times Eastern):
# 2016-01-05:
# 20:00 (minute before ICE opens)
# 20:01 (first minute of ICE session)
# 20:02 (second minute of ICE session)
# 00:00 (Cinderella's ride becomes a pumpkin)
# 2016-01-06:
# 9:30 (minute before NYSE opens)
# 9:31 (first minute of NYSE session)
# 9:32 (second minute of NYSE session)
# 15:59 (second-to-last minute of NYSE session)
# 16:00 (last minute of NYSE session)
# 16:01 (minute after NYSE closed)
# 17:59 (second-to-last minute of ICE session)
# 18:00 (last minute of ICE session)
# 18:01 (minute after ICE closed)
# each row is dt, whether-nyse-is-open, whether-ice-is-open
minutes_to_check = [
(pd.Timestamp("2016-01-05 20:00", tz="US/Eastern"), False, False),
(pd.Timestamp("2016-01-05 20:01", tz="US/Eastern"), False, True),
(pd.Timestamp("2016-01-05 20:02", tz="US/Eastern"), False, True),
(pd.Timestamp("2016-01-06 00:00", tz="US/Eastern"), False, True),
(pd.Timestamp("2016-01-06 9:30", tz="US/Eastern"), False, True),
(pd.Timestamp("2016-01-06 9:31", tz="US/Eastern"), True, True),
(pd.Timestamp("2016-01-06 9:32", tz="US/Eastern"), True, True),
(pd.Timestamp("2016-01-06 15:59", tz="US/Eastern"), True, True),
(pd.Timestamp("2016-01-06 16:00", tz="US/Eastern"), True, True),
(pd.Timestamp("2016-01-06 16:01", tz="US/Eastern"), False, True),
(pd.Timestamp("2016-01-06 17:59", tz="US/Eastern"), False, True),
(pd.Timestamp("2016-01-06 18:00", tz="US/Eastern"), False, True),
(pd.Timestamp("2016-01-06 18:01", tz="US/Eastern"), False, False),
]
for info in minutes_to_check:
bar_data = BarData(self.data_portal, lambda: info[0], "minute")
series = bar_data.can_trade([nyse_asset, ice_asset])
self.assertEqual(info[1], series.loc[nyse_asset])
self.assertEqual(info[2], series.loc[ice_asset])
def test_is_stale_during_non_market_hours(self):
bar_data = BarData(
self.data_portal,
lambda: self.equity_minute_bar_days[1],
@@ -644,13 +715,20 @@ class TestDailyBarData(WithBarDataChecks,
)
cls.ASSETS = [cls.ASSET1, cls.ASSET2]
def get_last_minute_of_session(self, session_label):
return self.trading_calendar.open_and_close_for_session(
session_label
)[1]
def test_day_before_assets_trading(self):
# use the day before self.bcolz_daily_bar_days[0]
day = self.trading_calendar.previous_session_label(
self.equity_daily_bar_days[0]
minute = self.get_last_minute_of_session(
self.trading_calendar.previous_session_label(
self.equity_daily_bar_days[0]
)
)
bar_data = BarData(self.data_portal, lambda: day, "daily")
bar_data = BarData(self.data_portal, lambda: minute, "daily")
self.check_internal_consistency(bar_data)
self.assertFalse(bar_data.can_trade(self.ASSET1))
@@ -674,7 +752,9 @@ class TestDailyBarData(WithBarDataChecks,
# on self.equity_daily_bar_days[0], only asset1 has data
bar_data = BarData(
self.data_portal,
lambda: self.equity_daily_bar_days[0],
lambda: self.get_last_minute_of_session(
self.equity_daily_bar_days[0]
),
"daily",
)
self.check_internal_consistency(bar_data)
@@ -709,7 +789,9 @@ class TestDailyBarData(WithBarDataChecks,
def test_fully_active_day(self):
bar_data = BarData(
self.data_portal,
lambda: self.equity_daily_bar_days[1],
lambda: self.get_last_minute_of_session(
self.equity_daily_bar_days[1]
),
"daily",
)
self.check_internal_consistency(bar_data)
@@ -733,7 +815,9 @@ class TestDailyBarData(WithBarDataChecks,
def test_last_active_day(self):
bar_data = BarData(
self.data_portal,
lambda: self.equity_daily_bar_days[-1],
lambda: self.get_last_minute_of_session(
self.equity_daily_bar_days[-1]
),
"daily",
)
self.check_internal_consistency(bar_data)
@@ -751,11 +835,13 @@ class TestDailyBarData(WithBarDataChecks,
def test_after_assets_dead(self):
# both assets end on self.day[-1], so let's try the next day
next_day = self.trading_calendar.next_session_label(
self.equity_daily_bar_days[-1]
minute = self.get_last_minute_of_session(
self.trading_calendar.next_session_label(
self.equity_daily_bar_days[-1]
)
)
bar_data = BarData(self.data_portal, lambda: next_day, "daily")
bar_data = BarData(self.data_portal, lambda: minute, "daily")
self.check_internal_consistency(bar_data)
for asset in self.ASSETS:
+8 -4
View File
@@ -47,22 +47,26 @@ class TestBenchmark(WithDataPortal, WithSimParams, WithTradingCalendar,
1: {
'symbol': 'A',
'start_date': cls.START_DATE,
'end_date': cls.END_DATE + pd.Timedelta(days=1)
'end_date': cls.END_DATE + pd.Timedelta(days=1),
"exchange": "TEST",
},
2: {
'symbol': 'B',
'start_date': cls.START_DATE,
'end_date': cls.END_DATE + pd.Timedelta(days=1)
'end_date': cls.END_DATE + pd.Timedelta(days=1),
"exchange": "TEST",
},
3: {
'symbol': 'C',
'start_date': pd.Timestamp('2006-05-26', tz='utc'),
'end_date': pd.Timestamp('2006-08-09', tz='utc')
'end_date': pd.Timestamp('2006-08-09', tz='utc'),
"exchange": "TEST",
},
4: {
'symbol': 'D',
'start_date': cls.START_DATE,
'end_date': cls.END_DATE + pd.Timedelta(days=1)
'end_date': cls.END_DATE + pd.Timedelta(days=1),
"exchange": "TEST",
},
},
orient='index',
+3 -3
View File
@@ -25,7 +25,7 @@ from zipline.finance.execution import (
StopOrder,
)
from zipline.gens.sim_engine import DAY_END, BAR
from zipline.gens.sim_engine import SESSION_END, BAR
from zipline.finance.cancel_policy import EODCancel, NeverCancel
from zipline.finance.slippage import (
DEFAULT_VOLUME_SLIPPAGE_BAR_LIMIT,
@@ -143,7 +143,7 @@ class BlotterTestCase(WithLogger,
self.assertEqual(blotter.new_orders[0].status, ORDER_STATUS.OPEN)
self.assertEqual(blotter.new_orders[1].status, ORDER_STATUS.OPEN)
blotter.execute_cancel_policy(DAY_END)
blotter.execute_cancel_policy(SESSION_END)
for order_id in order_ids:
order = blotter.orders[order_id]
self.assertEqual(order.status, ORDER_STATUS.CANCELLED)
@@ -161,7 +161,7 @@ class BlotterTestCase(WithLogger,
blotter.execute_cancel_policy(BAR)
self.assertEqual(blotter.new_orders[0].status, ORDER_STATUS.OPEN)
blotter.execute_cancel_policy(DAY_END)
blotter.execute_cancel_policy(SESSION_END)
self.assertEqual(blotter.new_orders[0].status, ORDER_STATUS.OPEN)
def test_order_rejection(self):
+180
View File
@@ -0,0 +1,180 @@
from datetime import time
from unittest import TestCase
import pandas as pd
from zipline.gens.sim_engine import (
MinuteSimulationClock,
SESSION_START,
BEFORE_TRADING_START_BAR,
BAR,
SESSION_END
)
from zipline.utils.calendars import get_calendar
from zipline.utils.calendars.trading_calendar import days_at_time
class TestClock(TestCase):
@classmethod
def setUpClass(cls):
cls.nyse_calendar = get_calendar("NYSE")
# july 15 is friday, so there are 3 sessions in this range (15, 18, 19)
cls.sessions = cls.nyse_calendar.sessions_in_range(
pd.Timestamp("2016-07-15"),
pd.Timestamp("2016-07-19")
)
trading_o_and_c = cls.nyse_calendar.schedule.ix[cls.sessions]
cls.opens = trading_o_and_c['market_open']
cls.closes = trading_o_and_c['market_close']
def test_bts_before_session(self):
clock = MinuteSimulationClock(
self.sessions,
self.opens,
self.closes,
days_at_time(self.sessions, time(6, 17), "US/Eastern"),
False
)
all_events = list(clock)
def _check_session_bts_first(session_label, events, bts_dt):
minutes = self.nyse_calendar.minutes_for_session(session_label)
self.assertEqual(393, len(events))
self.assertEqual(events[0], (session_label, SESSION_START))
self.assertEqual(events[1], (bts_dt, BEFORE_TRADING_START_BAR))
for i in range(2, 392):
self.assertEqual(events[i], (minutes[i - 2], BAR))
self.assertEqual(events[392], (minutes[-1], SESSION_END))
_check_session_bts_first(
self.sessions[0],
all_events[0:393],
pd.Timestamp("2016-07-15 6:17", tz='US/Eastern')
)
_check_session_bts_first(
self.sessions[1],
all_events[393:786],
pd.Timestamp("2016-07-18 6:17", tz='US/Eastern')
)
_check_session_bts_first(
self.sessions[2],
all_events[786:],
pd.Timestamp("2016-07-19 6:17", tz='US/Eastern')
)
def test_bts_during_session(self):
self.verify_bts_during_session(
time(11, 45), [
pd.Timestamp("2016-07-15 11:45", tz='US/Eastern'),
pd.Timestamp("2016-07-18 11:45", tz='US/Eastern'),
pd.Timestamp("2016-07-19 11:45", tz='US/Eastern')
],
135
)
def test_bts_on_first_minute(self):
self.verify_bts_during_session(
time(9, 30), [
pd.Timestamp("2016-07-15 9:30", tz='US/Eastern'),
pd.Timestamp("2016-07-18 9:30", tz='US/Eastern'),
pd.Timestamp("2016-07-19 9:30", tz='US/Eastern')
],
1
)
def test_bts_on_last_minute(self):
self.verify_bts_during_session(
time(16, 00), [
pd.Timestamp("2016-07-15 16:00", tz='US/Eastern'),
pd.Timestamp("2016-07-18 16:00", tz='US/Eastern'),
pd.Timestamp("2016-07-19 16:00", tz='US/Eastern')
],
390
)
def verify_bts_during_session(self, bts_time, bts_session_times, bts_idx):
def _check_session_bts_during(session_label, events, bts_dt):
minutes = self.nyse_calendar.minutes_for_session(session_label)
self.assertEqual(393, len(events))
self.assertEqual(events[0], (session_label, SESSION_START))
for i in range(1, bts_idx):
self.assertEqual(events[i], (minutes[i - 1], BAR))
self.assertEqual(
events[bts_idx],
(bts_dt, BEFORE_TRADING_START_BAR)
)
for i in range(bts_idx + 1, 391):
self.assertEqual(events[i], (minutes[i - 2], BAR))
self.assertEqual(events[392], (minutes[-1], SESSION_END))
clock = MinuteSimulationClock(
self.sessions,
self.opens,
self.closes,
days_at_time(self.sessions, bts_time, "US/Eastern"),
False
)
all_events = list(clock)
_check_session_bts_during(
self.sessions[0],
all_events[0:393],
bts_session_times[0]
)
_check_session_bts_during(
self.sessions[1],
all_events[393:786],
bts_session_times[1]
)
_check_session_bts_during(
self.sessions[2],
all_events[786:],
bts_session_times[2]
)
def test_bts_after_session(self):
clock = MinuteSimulationClock(
self.sessions,
self.opens,
self.closes,
days_at_time(self.sessions, time(19, 5), "US/Eastern"),
False
)
all_events = list(clock)
# since 19:05 Eastern is after the NYSE is closed, we don't emit
# BEFORE_TRADING_START. therefore, each day has SESSION_START,
# 390 BARs, and then SESSION_END
def _check_session_bts_after(session_label, events):
minutes = self.nyse_calendar.minutes_for_session(session_label)
self.assertEqual(392, len(events))
self.assertEqual(events[0], (session_label, SESSION_START))
for i in range(1, 391):
self.assertEqual(events[i], (minutes[i - 1], BAR))
self.assertEqual(events[-1], (minutes[389], SESSION_END))
for i in range(0, 2):
_check_session_bts_after(
self.sessions[i],
all_events[(i * 392): ((i + 1) * 392)]
)
+1
View File
@@ -120,6 +120,7 @@ class MinuteToDailyAggregationTestCase(WithBcolzEquityMinuteBarReader,
self.equity_daily_aggregator = DailyHistoryAggregator(
self.trading_calendar.schedule.market_open,
self.bcolz_equity_minute_bar_reader,
self.trading_calendar
)
@parameterized.expand([
+16 -8
View File
@@ -121,42 +121,50 @@ class WithHistory(WithDataPortal):
1: {
'start_date': pd.Timestamp('2014-01-03', tz='UTC'),
'end_date': cls.TRADING_END_DT,
'symbol': 'ASSET1'
'symbol': 'ASSET1',
'exchange': "TEST",
},
2: {
'start_date': jan_5_2015,
'end_date': day_after_12312015,
'symbol': 'ASSET2'
'symbol': 'ASSET2',
'exchange': "TEST",
},
3: {
'start_date': jan_5_2015,
'end_date': day_after_12312015,
'symbol': 'ASSET3'
'symbol': 'ASSET3',
'exchange': "TEST",
},
cls.SPLIT_ASSET_SID: {
'start_date': jan_5_2015,
'end_date': day_after_12312015,
'symbol': 'SPLIT_ASSET'
'symbol': 'SPLIT_ASSET',
'exchange': "TEST",
},
cls.DIVIDEND_ASSET_SID: {
'start_date': jan_5_2015,
'end_date': day_after_12312015,
'symbol': 'DIVIDEND_ASSET'
'symbol': 'DIVIDEND_ASSET',
'exchange': "TEST",
},
cls.MERGER_ASSET_SID: {
'start_date': jan_5_2015,
'end_date': day_after_12312015,
'symbol': 'MERGER_ASSET'
'symbol': 'MERGER_ASSET',
'exchange': "TEST",
},
cls.HALF_DAY_TEST_ASSET_SID: {
'start_date': pd.Timestamp('2014-07-02', tz='UTC'),
'end_date': day_after_12312015,
'symbol': 'HALF_DAY_TEST_ASSET'
'symbol': 'HALF_DAY_TEST_ASSET',
'exchange': "TEST",
},
cls.SHORT_ASSET_SID: {
'start_date': pd.Timestamp('2015-01-05', tz='UTC'),
'end_date': pd.Timestamp('2015-01-06', tz='UTC'),
'symbol': 'SHORT_ASSET'
'symbol': 'SHORT_ASSET',
'exchange': "TEST",
}
},
orient='index',
+4 -3
View File
@@ -1046,6 +1046,7 @@ class TestPositionPerformance(WithInstanceTmpDir, WithTradingCalendar,
'start_date': start,
'end_date': end,
'multiplier': 100,
'exchange': "TEST",
}
for sid in futures_sids
},
@@ -2345,9 +2346,9 @@ class TestPositionTracker(WithTradingEnvironment,
def make_futures_info(cls):
return pd.DataFrame.from_dict(
{
3: {'multiplier': 1000},
4: {'multiplier': 1000},
1032201401: {'multiplier': 50},
3: {'multiplier': 1000, 'exchange': 'TEST'},
4: {'multiplier': 1000, 'exchange': 'TEST'},
1032201401: {'multiplier': 50, 'exchange': 'TEST'},
},
orient='index',
)
+5 -2
View File
@@ -85,7 +85,8 @@ class SecurityListTestCase(WithLogger, WithTradingCalendar, ZiplineTestCase):
equities=pd.DataFrame.from_records([{
'start_date': cls.start,
'end_date': end,
'symbol': symbol
'symbol': symbol,
'exchange': "TEST",
} for symbol in symbols]),
))
@@ -103,7 +104,8 @@ class SecurityListTestCase(WithLogger, WithTradingCalendar, ZiplineTestCase):
equities=pd.DataFrame.from_records([{
'start_date': sp2.start_session,
'end_date': sp2.end_session,
'symbol': symbol
'symbol': symbol,
'exchange': "TEST",
} for symbol in symbols]),
))
@@ -267,6 +269,7 @@ class SecurityListTestCase(WithLogger, WithTradingCalendar, ZiplineTestCase):
'symbol': 'BZQ',
'start_date': sim_params.start_session,
'end_date': sim_params.end_session,
'exchange': "TEST",
}])
with TempDirectory() as new_tempdir, \
security_list_copy(), \
+17 -6
View File
@@ -12,6 +12,8 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from datetime import time
import pandas as pd
from mock import patch
@@ -23,6 +25,7 @@ from zipline.sources.benchmark_source import BenchmarkSource
from zipline.test_algorithms import NoopAlgorithm
from zipline.utils import factory
from zipline.testing.core import FakeDataPortal
from zipline.utils.calendars.trading_calendar import days_at_time
class BeforeTradingAlgorithm(TradingAlgorithm):
@@ -75,10 +78,18 @@ class TestTradeSimulation(TestCase):
algo = BeforeTradingAlgorithm(sim_params=params)
algo.run(FakeDataPortal())
self.assertEqual(len(algo.perf_tracker.sim_params.sessions),
num_days)
self.assertEqual(
len(algo.perf_tracker.sim_params.sessions),
num_days
)
self.assertTrue(params.sessions.equals(
pd.DatetimeIndex(algo.before_trading_at)),
"Expected %s but was %s."
% (params.sessions, algo.before_trading_at))
bts_minutes = days_at_time(
params.sessions, time(8, 45), "US/Eastern"
)
self.assertTrue(
bts_minutes.equals(
pd.DatetimeIndex(algo.before_trading_at)
),
"Expected %s but was %s." % (params.sessions,
algo.before_trading_at))
+27 -13
View File
@@ -24,7 +24,7 @@ from six import iteritems, PY2
from cpython cimport bool
from collections import Iterable
from zipline.assets import Asset
from zipline.assets import Asset, Future
from zipline.zipline_warnings import ZiplineDeprecationWarning
@@ -426,9 +426,11 @@ cdef class BarData:
@check_parameters(('assets',), (Asset,))
def can_trade(self, assets):
"""
For the given asset or iterable of assets, returns true if the asset
is alive at the current simulation time and there is a known last
price.
For the given asset or iterable of assets, returns true if all of the
following are true:
- the asset is alive at the current simulation time
- the asset's exchange is open at the current simulation time
- there is a known last price for the asset.
Parameters
----------
@@ -460,15 +462,25 @@ cdef class BarData:
})
cdef bool _can_trade_for_asset(self, asset, dt, adjusted_dt, data_portal):
if asset._is_alive(dt, False):
# is there a last price?
return not np.isnan(
data_portal.get_spot_value(
asset, "price", adjusted_dt, self.data_frequency
)
)
session_label = normalize_date(dt) # FIXME
if not asset.is_alive_for_session(session_label):
# asset isn't alive
return False
return False
if not asset.is_exchange_open(dt):
# exchange isn't open
return False
if isinstance(asset, Future):
# FIXME: this will get removed once we can get prices for futures
return True
# is there a last price?
return not np.isnan(
data_portal.get_spot_value(
asset, "price", adjusted_dt, self.data_frequency
)
)
@check_parameters(('assets',), (Asset,))
def is_stale(self, assets):
@@ -511,7 +523,9 @@ cdef class BarData:
})
cdef bool _is_stale_for_asset(self, asset, dt, adjusted_dt, data_portal):
if not asset._is_alive(dt, False):
session_label = normalize_date(dt) # FIXME
if not asset.is_alive_for_session(session_label):
return False
current_volume = data_portal.get_spot_value(
+22 -18
View File
@@ -15,7 +15,7 @@
from copy import copy
import operator as op
import warnings
from datetime import tzinfo
from datetime import tzinfo, time
import logbook
import pytz
import pandas as pd
@@ -94,9 +94,9 @@ from zipline.utils.api_support import (
require_not_initialized,
ZiplineAPI,
disallowed_in_before_trading_start)
from zipline.utils.input_validation import ensure_upper_case, error_keywords, \
expect_types, optional, coerce_string
from zipline.utils.calendars.trading_calendar import days_at_time
from zipline.utils.cache import CachedObject, Expired
from zipline.utils.calendars import get_calendar
@@ -496,29 +496,32 @@ class TradingAlgorithm(object):
"""
trading_o_and_c = self.trading_calendar.schedule.ix[
self.sim_params.sessions]
market_closes = trading_o_and_c['market_close'].values.astype(np.int64)
market_closes = trading_o_and_c['market_close']
minutely_emission = False
if self.sim_params.data_frequency == 'minute':
market_opens = trading_o_and_c['market_open'].values.astype(
np.int64)
market_opens = trading_o_and_c['market_open']
minutely_emission = self.sim_params.emission_rate == "minute"
return MinuteSimulationClock(
self.sim_params.sessions,
market_opens,
market_closes,
minutely_emission
)
else:
# in daily mode, we want to have one bar per session, timestamped
# as the last minute of the session.
return MinuteSimulationClock(
self.sim_params.sessions,
market_closes,
market_closes,
False
)
market_opens = market_closes
# FIXME generalize these values
before_trading_start_minutes = days_at_time(
self.sim_params.sessions,
time(8, 45),
"US/Eastern"
)
return MinuteSimulationClock(
self.sim_params.sessions,
market_opens,
market_closes,
before_trading_start_minutes,
minute_emission=minutely_emission,
)
def _create_benchmark_source(self):
return BenchmarkSource(
@@ -1545,6 +1548,7 @@ class TradingAlgorithm(object):
self.datetime, self._in_before_trading_start, self.data_portal)
self._account = \
self.perf_tracker.get_account(self.performance_needs_update)
self.account_needs_update = False
self.performance_needs_update = False
return self._account
+28 -24
View File
@@ -34,14 +34,15 @@ from numpy cimport int64_t
import warnings
cimport numpy as np
from zipline.utils.calendars import get_calendar
# IMPORTANT NOTE: You must change this template if you change
# Asset.__reduce__, or else we'll attempt to unpickle an old version of this
# class
from pandas.tslib import normalize_date
CACHE_FILE_TEMPLATE = '/tmp/.%s-%s.v6.cache'
cdef class Asset:
cdef readonly int sid
@@ -71,13 +72,13 @@ cdef class Asset:
def __init__(self,
int sid, # sid is required
object exchange, # exchange is required
object symbol="",
object asset_name="",
object start_date=None,
object end_date=None,
object first_traded=None,
object auto_close_date=None,
object exchange=""):
object auto_close_date=None):
self.sid = sid
self.sid_hash = hash(sid)
@@ -157,13 +158,13 @@ cdef class Asset:
be serialized/deserialized during pickling.
"""
return (self.__class__, (self.sid,
self.exchange,
self.symbol,
self.asset_name,
self.start_date,
self.end_date,
self.first_traded,
self.auto_close_date,
self.exchange,))
self.auto_close_date,))
cpdef to_dict(self):
"""
@@ -187,37 +188,40 @@ cdef class Asset:
"""
return cls(**dict_)
def _is_alive(self, dt, bool normalized):
def is_alive_for_session(self, session_label):
"""
Returns whether the asset is alive at the given dt.
Parameters
----------
dt: pd.Timestamp
The desired timestamp.
normalized: boolean
Whether the date has already been normalized. If not, we need
to first normalize the date before doing the alive check. If the
date is already normalized, this method runs up to 80% faster.
session_label: pd.Timestamp
The desired session label to check. (midnight UTC)
Returns
-------
boolean: whether the asset is alive at the given dt.
"""
cdef int64_t dt_value
cdef int64_t ref_start
cdef int64_t ref_end
if not normalized:
dt_value = normalize_date(dt).value
else:
dt_value = dt.value
ref_start = self.start_date.value
ref_end = self.end_date.value
return ref_start <= dt_value <= ref_end
return ref_start <= session_label.value <= ref_end
def is_exchange_open(self, dt_minute):
"""
Parameters
----------
dt_minute: pd.Timestamp (UTC, tz-aware)
The minute to check.
Returns
-------
boolean: whether the asset's exchange is open at the given minute.
"""
calendar = get_calendar(self.exchange)
return calendar.is_open_on_minute(dt_minute)
cdef class Equity(Asset):
@@ -291,6 +295,7 @@ cdef class Future(Asset):
def __init__(self,
int sid, # sid is required
object exchange, # exchange is required
object symbol="",
object root_symbol="",
object asset_name="",
@@ -300,19 +305,18 @@ cdef class Future(Asset):
object expiration_date=None,
object auto_close_date=None,
object first_traded=None,
object exchange="",
object tick_size="",
float multiplier=1.0):
super().__init__(
sid,
exchange,
symbol=symbol,
asset_name=asset_name,
start_date=start_date,
end_date=end_date,
first_traded=first_traded,
auto_close_date=auto_close_date,
exchange=exchange,
)
self.root_symbol = root_symbol
self.notice_date = notice_date
@@ -347,6 +351,7 @@ cdef class Future(Asset):
be serialized/deserialized during pickling.
"""
return (self.__class__, (self.sid,
self.exchange,
self.symbol,
self.root_symbol,
self.asset_name,
@@ -356,7 +361,6 @@ cdef class Future(Asset):
self.expiration_date,
self.auto_close_date,
self.first_traded,
self.exchange,
self.tick_size,
self.multiplier,))
+1
View File
@@ -217,6 +217,7 @@ def make_future_info(first_sid,
'notice_date': notice_date_func(month_begin),
'expiration_date': notice_date_func(month_begin),
'multiplier': 500,
'exchange': "TEST",
})
return pd.DataFrame.from_records(contracts, index='sid').convert_objects()
+4 -1
View File
@@ -28,7 +28,7 @@ from zipline.utils.compat import mappingproxy
from zipline.utils.input_validation import ensure_timestamp, optionally
import zipline.utils.paths as pth
from zipline.utils.preprocess import preprocess
from zipline.utils.calendars import get_calendar
from zipline.utils.calendars import get_calendar, register_calendar
nyse_cal = get_calendar('NYSE')
trading_days = nyse_cal.all_sessions
@@ -564,3 +564,6 @@ def _make_bundle_core():
return BundleCore(bundles, register, unregister, ingest, load, clean)
bundles, register, unregister, ingest, load, clean = _make_bundle_core()
register_calendar("YAHOO", get_calendar("NYSE"))
register_calendar("QUANDL", get_calendar("NYSE"))
+1 -1
View File
@@ -123,7 +123,7 @@ def fetch_symbol_metadata_frame(api_key,
# cut out all the other stuff in the name column
# we need to escape the paren because it is actually splitting on a regex
data.asset_name = data.asset_name.str.split(r' \(', 1).str.get(0)
data['exchange'] = 'quandl'
data['exchange'] = 'QUANDL'
data['auto_close_date'] = data['end_date'] + pd.Timedelta(days=1)
return data
+5
View File
@@ -123,6 +123,11 @@ def yahoo_equities(symbols, start=None, end=None):
daily_bar_writer.write(_pricing_iter(), show_progress=show_progress)
symbol_map = pd.Series(metadata.symbol.index, metadata.symbol)
# Hardcode the exchange to "YAHOO" for all assets and (elsewhere)
# register "YAHOO" to resolve to the NYSE calendar, because these are
# all equities and thus can use the NYSE calendar.
metadata['exchange'] = "YAHOO"
asset_db_writer.write(equities=metadata)
adjustments = []
+12 -13
View File
@@ -15,8 +15,6 @@
import numpy as np
import pandas as pd
from pandas.tslib import normalize_date
class DailyHistoryAggregator(object):
"""
@@ -32,9 +30,10 @@ class DailyHistoryAggregator(object):
"""
def __init__(self, market_opens, minute_reader):
def __init__(self, market_opens, minute_reader, trading_calendar):
self._market_opens = market_opens
self._minute_reader = minute_reader
self._trading_calendar = trading_calendar
# The caches are structured as (date, market_open, entries), where
# entries is a dict of asset -> (last_visited_dt, value)
@@ -97,10 +96,10 @@ class DailyHistoryAggregator(object):
market_open, prev_dt, dt_value, entries = self._prelude(dt, 'open')
opens = []
normalized_date = normalize_date(dt)
session_label = self._trading_calendar.minute_to_session_label(dt)
for asset in assets:
if not asset._is_alive(normalized_date, True):
if not asset.is_alive_for_session(session_label):
opens.append(np.NaN)
continue
@@ -166,10 +165,10 @@ class DailyHistoryAggregator(object):
market_open, prev_dt, dt_value, entries = self._prelude(dt, 'high')
highs = []
normalized_date = normalize_date(dt)
session_label = self._trading_calendar.minute_to_session_label(dt)
for asset in assets:
if not asset._is_alive(normalized_date, True):
if not asset.is_alive_for_session(session_label):
highs.append(np.NaN)
continue
@@ -235,10 +234,10 @@ class DailyHistoryAggregator(object):
market_open, prev_dt, dt_value, entries = self._prelude(dt, 'low')
lows = []
normalized_date = normalize_date(dt)
session_label = self._trading_calendar.minute_to_session_label(dt)
for asset in assets:
if not asset._is_alive(normalized_date, True):
if not asset.is_alive_for_session(session_label):
lows.append(np.NaN)
continue
@@ -305,10 +304,10 @@ class DailyHistoryAggregator(object):
market_open, prev_dt, dt_value, entries = self._prelude(dt, 'close')
closes = []
normalized_dt = normalize_date(dt)
session_label = self._trading_calendar.minute_to_session_label(dt)
for asset in assets:
if not asset._is_alive(normalized_dt, True):
if not asset.is_alive_for_session(session_label):
closes.append(np.NaN)
continue
@@ -365,10 +364,10 @@ class DailyHistoryAggregator(object):
market_open, prev_dt, dt_value, entries = self._prelude(dt, 'volume')
volumes = []
normalized_date = normalize_date(dt)
session_label = self._trading_calendar.minute_to_session_label(dt)
for asset in assets:
if not asset._is_alive(normalized_date, True):
if not asset.is_alive_for_session(session_label):
volumes.append(0)
continue
+3 -1
View File
@@ -150,7 +150,9 @@ class DataPortal(object):
if self._equity_minute_reader is not None:
self._equity_daily_aggregator = DailyHistoryAggregator(
self.trading_calendar.schedule.market_open,
self._equity_minute_reader)
self._equity_minute_reader,
self.trading_calendar
)
self._equity_minute_history_loader = USEquityMinuteHistoryLoader(
self.trading_calendar,
self._equity_minute_reader,
+5
View File
@@ -7,6 +7,8 @@ from zipline import run_algorithm
# These are used by test_examples.py to discover the examples to run.
from zipline.utils.calendars import register_calendar, get_calendar
EXAMPLE_MODULES = {}
for f in os.listdir(os.path.dirname(__file__)):
if not f.endswith('.py') or f == '__init__.py':
@@ -65,6 +67,9 @@ def run_example(example_name, environ):
Run an example module from zipline.examples.
"""
mod = EXAMPLE_MODULES[example_name]
register_calendar("YAHOO", get_calendar("NYSE"), force=True)
return run_algorithm(
initialize=getattr(mod, 'initialize', None),
handle_data=getattr(mod, 'handle_data', None),
+2 -2
View File
@@ -17,7 +17,7 @@ import abc
from abc import abstractmethod
from six import with_metaclass
from zipline.gens.sim_engine import DAY_END
from zipline.gens.sim_engine import SESSION_END
class CancelPolicy(with_metaclass(abc.ABCMeta)):
@@ -58,7 +58,7 @@ class EODCancel(CancelPolicy):
self.warn_on_cancel = warn_on_cancel
def should_cancel(self, event):
return event == DAY_END
return event == SESSION_END
class NeverCancel(CancelPolicy):
+70 -39
View File
@@ -24,63 +24,94 @@ NANOS_IN_MINUTE = _nanos_in_minute
cpdef enum:
BAR = 0
DAY_START = 1
DAY_END = 2
SESSION_START = 1
SESSION_END = 2
MINUTE_END = 3
BEFORE_TRADING_START_BAR = 4
cdef class MinuteSimulationClock:
cdef object trading_days
cdef bool minute_emission
cdef np.int64_t[:] market_opens, market_closes
cdef public dict minutes_by_day, minutes_to_day
cdef np.int64_t[:] market_opens_nanos, market_closes_nanos, bts_nanos, \
sessions_nanos
cdef dict minutes_by_session
def __init__(self,
trading_days,
sessions,
market_opens,
market_closes,
before_trading_start_minutes,
minute_emission=False):
self.minute_emission = minute_emission
self.market_opens = market_opens
self.market_closes = market_closes
self.trading_days = trading_days
self.minutes_by_day = self.calc_minutes_by_day()
self.market_opens_nanos = market_opens.values.astype(np.int64)
self.market_closes_nanos = market_closes.values.astype(np.int64)
self.sessions_nanos = sessions.values.astype(np.int64)
self.bts_nanos = before_trading_start_minutes.values.astype(np.int64)
self.minutes_by_session = self.calc_minutes_by_session()
@cython.boundscheck(False)
@cython.wraparound(False)
cdef np.ndarray[np.int64_t, ndim=1] market_minutes(self, np.intp_t i):
cdef np.int64_t[:] market_opens, market_closes
cdef dict calc_minutes_by_session(self):
cdef dict minutes_by_session
cdef int session_idx
cdef np.int64_t session_nano
cdef np.ndarray[np.int64_t, ndim=1] minutes_nanos
market_opens = self.market_opens
market_closes = self.market_closes
return np.arange(market_opens[i],
market_closes[i] + _nanos_in_minute,
_nanos_in_minute)
@cython.boundscheck(False)
@cython.wraparound(False)
cdef dict calc_minutes_by_day(self):
cdef dict minutes_by_day
cdef int day_idx
cdef object day
minutes_by_day = {}
for day_idx, day in enumerate(self.trading_days):
minutes_by_day[day] = pd.to_datetime(
self.market_minutes(day_idx), utc=True, box=True)
return minutes_by_day
minutes_by_session = {}
for session_idx, session_nano in enumerate(self.sessions_nanos):
minutes_nanos = np.arange(
self.market_opens_nanos[session_idx],
self.market_closes_nanos[session_idx] + _nanos_in_minute,
_nanos_in_minute
)
minutes_by_session[session_nano] = pd.to_datetime(
minutes_nanos, utc=True, box=True
)
return minutes_by_session
def __iter__(self):
minute_emission = self.minute_emission
for day in self.trading_days:
yield day, DAY_START
for idx, session_nano in enumerate(self.sessions_nanos):
yield pd.Timestamp(session_nano, tz='UTC'), SESSION_START
minutes = self.minutes_by_day[day]
bts_minute = pd.Timestamp(self.bts_nanos[idx], tz='UTC')
regular_minutes = self.minutes_by_session[session_nano]
for minute in minutes:
yield minute, BAR
if minute_emission:
yield minute, MINUTE_END
if bts_minute > regular_minutes[-1]:
# before_trading_start is after the last close,
# so don't emit it
for minute, evt in self._get_minutes_for_list(
regular_minutes,
minute_emission
):
yield minute, evt
else:
# we have to search anew every session, because there is no
# guarantee that any two session start on the same minute
bts_idx = regular_minutes.searchsorted(bts_minute)
yield minutes[-1], DAY_END
# emit all the minutes before bts_minute
for minute, evt in self._get_minutes_for_list(
regular_minutes[0:bts_idx],
minute_emission
):
yield minute, evt
yield bts_minute, BEFORE_TRADING_START_BAR
# emit all the minutes after bts_minute
for minute, evt in self._get_minutes_for_list(
regular_minutes[bts_idx:],
minute_emission
):
yield minute, evt
yield regular_minutes[-1], SESSION_END
def _get_minutes_for_list(self, minutes, minute_emission):
for minute in minutes:
yield minute, BAR
if minute_emission:
yield minute, MINUTE_END
+12 -10
View File
@@ -21,9 +21,10 @@ from six import viewkeys
from zipline.gens.sim_engine import (
BAR,
DAY_START,
DAY_END,
MINUTE_END
SESSION_START,
SESSION_END,
MINUTE_END,
BEFORE_TRADING_START_BAR
)
log = Logger('Trade Simulation')
@@ -181,9 +182,6 @@ class AlgorithmSimulator(object):
algo.blotter.process_splits(splits)
perf_tracker.position_tracker.handle_splits(splits)
# call before trading start
algo.before_trading_start(current_data)
def handle_benchmark(date, benchmark_source=self.benchmark_source):
algo.perf_tracker.all_benchmark_returns[date] = \
benchmark_source.get_value(date)
@@ -202,7 +200,7 @@ class AlgorithmSimulator(object):
if algo.data_frequency == 'minute':
def execute_order_cancellation_policy():
algo.blotter.execute_cancel_policy(DAY_END)
algo.blotter.execute_cancel_policy(SESSION_END)
def calculate_minute_capital_changes(dt):
# process any capital changes that came between the last
@@ -220,16 +218,20 @@ class AlgorithmSimulator(object):
if action == BAR:
for capital_change_packet in every_bar(dt):
yield capital_change_packet
elif action == DAY_START:
elif action == SESSION_START:
for capital_change_packet in once_a_day(dt):
yield capital_change_packet
elif action == DAY_END:
# End of the day.
elif action == SESSION_END:
# End of the session.
if emission_rate == 'daily':
handle_benchmark(normalize_date(dt))
execute_order_cancellation_policy()
yield self._get_daily_message(dt, algo, algo.perf_tracker)
elif action == BEFORE_TRADING_START_BAR:
# call before trading start
algo.on_dt_changed(dt)
algo.before_trading_start(self.current_data)
elif action == MINUTE_END:
handle_benchmark(dt)
minute_msg = \
+2 -2
View File
@@ -525,14 +525,14 @@ class SetLongOnlyAlgorithm(TradingAlgorithm):
class SetAssetDateBoundsAlgorithm(TradingAlgorithm):
"""
Algorithm that tries to order 1 share of sid 0 on every bar and has an
Algorithm that tries to order 1 share of sid 999 on every bar and has an
AssetDateBounds() trading control in place.
"""
def initialize(self):
self.register_trading_control(AssetDateBounds())
def handle_data(algo, data):
algo.order(algo.sid(0), 1)
algo.order(algo.sid(999), 1)
class TestRegisterTransformAlgorithm(TradingAlgorithm):
+3 -1
View File
@@ -37,7 +37,7 @@ from zipline.pipeline import SimplePipelineEngine
from zipline.pipeline.loaders.testing import make_seeded_random_loader
from zipline.utils.calendars import (
get_calendar,
)
register_calendar)
class ZiplineTestCase(with_metaclass(FinalMeta, TestCase)):
@@ -336,6 +336,8 @@ class WithAssetFinder(WithDefaultDateBounds):
@classmethod
def make_equity_info(cls):
register_calendar("TEST", get_calendar("NYSE"), force=True)
return make_simple_equity_info(
cls.ASSET_FINDER_EQUITY_SIDS,
cls.ASSET_FINDER_EQUITY_START_DATE,
+16 -13
View File
@@ -28,24 +28,25 @@ def get_calendar(name):
The desired calendar.
"""
if name not in _static_calendars:
if name == 'NYSE':
if name in ["NYSE", "NASDAQ", "BATS"]:
cal = NYSEExchangeCalendar()
elif name == 'CME':
elif name in ["CME", "CBOT", "COMEX", "NYMEX"]:
cal = CMEExchangeCalendar()
elif name in ["ICEUS", "NYFE"]:
cal = ICEExchangeCalendar()
elif name == "CFE":
cal = CFEExchangeCalendar()
elif name == 'BMF':
cal = BMFExchangeCalendar()
elif name == 'LSE':
cal = LSEExchangeCalendar()
elif name == 'TSX':
cal = TSXExchangeCalendar()
elif name == "ICE":
cal = ICEExchangeCalendar()
elif name == "CFE":
cal = CFEExchangeCalendar()
else:
raise InvalidCalendarName(calendar_name=name)
register_calendar(cal)
register_calendar(name, cal)
return _static_calendars[name]
@@ -72,13 +73,15 @@ def clear_calendars():
_static_calendars.clear()
def register_calendar(calendar, force=False):
def register_calendar(name, calendar, force=False):
"""
Registers a calendar for retrieval by the get_calendar method.
Parameters
----------
calendar : TradingCalendar
name: str
The key with which to register this calendar.
calendar: TradingCalendar
The calendar to be registered for retrieval.
force : bool, optional
If True, old calendars will be overwritten on a name collision.
@@ -92,10 +95,10 @@ def register_calendar(calendar, force=False):
# If we are forcing the registration, remove an existing calendar with the
# same name.
if force:
deregister_calendar(calendar.name)
deregister_calendar(name)
# Check if we are already holding a calendar with the same name
if calendar.name in _static_calendars:
raise CalendarNameCollision(calendar_name=calendar.name)
if name in _static_calendars:
raise CalendarNameCollision(calendar_name=name)
_static_calendars[calendar.name] = calendar
_static_calendars[name] = calendar
@@ -757,12 +757,18 @@ def days_at_time(days, t, tz, day_offset=0):
day_offset : int
The number of days we want to offset @days by
"""
if len(days) == 0:
return days
# Offset days without tz to avoid timezone issues.
days = DatetimeIndex(days).tz_localize(None)
days_offset = days + DateOffset(days=day_offset)
# Shift all days to the target time in the local timezone, then
# convert to UTC.
# FIXME: Once we're off Pandas 16, see if we can replace DateOffset with
# TimeDelta.
return days_offset.shift(
1, freq=DateOffset(hour=t.hour, minute=t.minute, second=t.second)
).tz_localize(tz).tz_convert('UTC')