mirror of
https://github.com/wassname/catalyst.git
synced 2026-07-18 12:20:12 +08:00
TEST: Clean AssetFinder initialization and tests.
- AssetFinder no longer accepts an unused trading_calendar. - AssetFinder correctly accepts a DataFrame as input. - Tests for AssetFinder no longer rely on a global trading environment.
This commit is contained in:
+155
-195
@@ -21,12 +21,10 @@ import sys
|
||||
from unittest import TestCase
|
||||
|
||||
from datetime import (
|
||||
datetime,
|
||||
timedelta,
|
||||
)
|
||||
import pickle
|
||||
import pprint
|
||||
import pytz
|
||||
import uuid
|
||||
import warnings
|
||||
import pandas as pd
|
||||
@@ -66,40 +64,7 @@ class FakeTable(object):
|
||||
return self.df.to_records()
|
||||
|
||||
|
||||
class FakeTableIdenticalSymbols(object):
|
||||
def __init__(self, name, as_of_dates):
|
||||
self.name = name
|
||||
self.as_of_dates = as_of_dates
|
||||
self.df = pd.DataFrame.from_records(
|
||||
[
|
||||
{
|
||||
'sid': i,
|
||||
'file_name': self.name,
|
||||
'company_name': self.name,
|
||||
'start_date_nano': date.value,
|
||||
'end_date_nano': (date + timedelta(days=1)).value,
|
||||
'exchange': self.name,
|
||||
}
|
||||
for i, date in enumerate(self.as_of_dates)
|
||||
]
|
||||
)
|
||||
|
||||
def read(self, *args, **kwargs):
|
||||
return self.df.to_records()
|
||||
|
||||
|
||||
class FakeTableFromRecords(object):
|
||||
|
||||
def __init__(self, records):
|
||||
self.records = records
|
||||
self.df = pd.DataFrame.from_records(self.records)
|
||||
|
||||
def read(self, *args, **kwargs):
|
||||
return self.df.to_records()
|
||||
|
||||
|
||||
@with_environment()
|
||||
def build_lookup_generic_cases(env=None):
|
||||
def build_lookup_generic_cases():
|
||||
"""
|
||||
Generate test cases for AssetFinder test_lookup_generic.
|
||||
"""
|
||||
@@ -113,7 +78,7 @@ def build_lookup_generic_cases(env=None):
|
||||
dupe_1_start = pd.Timestamp('2013-01-03', tz='UTC')
|
||||
dupe_1_end = dupe_1_start + timedelta(days=1)
|
||||
|
||||
table = FakeTableFromRecords(
|
||||
frame = pd.DataFrame.from_records(
|
||||
[
|
||||
{
|
||||
'sid': 0,
|
||||
@@ -141,17 +106,12 @@ def build_lookup_generic_cases(env=None):
|
||||
},
|
||||
],
|
||||
)
|
||||
env.update_asset_finder(asset_metadata=table.df)
|
||||
finder = AssetFinder(metadata=frame)
|
||||
dupe_0, dupe_1, unique = assets = [
|
||||
env.asset_finder.retrieve_asset(i)
|
||||
finder.retrieve_asset(i)
|
||||
for i in range(3)
|
||||
]
|
||||
|
||||
# This expansion code is run at module import time, which means we have to
|
||||
# clear the AssetFinder here or else it will interfere with the cache
|
||||
# for other tests.
|
||||
env.update_asset_finder(clear_metadata=True)
|
||||
|
||||
dupe_0_start = dupe_0.start_date
|
||||
dupe_1_start = dupe_1.start_date
|
||||
cases = [
|
||||
@@ -159,34 +119,34 @@ def build_lookup_generic_cases(env=None):
|
||||
# Scalars
|
||||
|
||||
# Asset object
|
||||
(table, assets[0], None, assets[0]),
|
||||
(table, assets[1], None, assets[1]),
|
||||
(table, assets[2], None, assets[2]),
|
||||
(finder, assets[0], None, assets[0]),
|
||||
(finder, assets[1], None, assets[1]),
|
||||
(finder, assets[2], None, assets[2]),
|
||||
# int
|
||||
(table, 0, None, assets[0]),
|
||||
(table, 1, None, assets[1]),
|
||||
(table, 2, None, assets[2]),
|
||||
(finder, 0, None, assets[0]),
|
||||
(finder, 1, None, assets[1]),
|
||||
(finder, 2, None, assets[2]),
|
||||
# Duplicated symbol with resolution date
|
||||
(table, 'duplicated', dupe_0_start, dupe_0),
|
||||
(table, 'duplicated', dupe_1_start, dupe_1),
|
||||
(finder, 'duplicated', dupe_0_start, dupe_0),
|
||||
(finder, 'duplicated', dupe_1_start, dupe_1),
|
||||
# Unique symbol, with or without resolution date.
|
||||
(table, 'unique', unique_start, unique),
|
||||
(table, 'unique', None, unique),
|
||||
(finder, 'unique', unique_start, unique),
|
||||
(finder, 'unique', None, unique),
|
||||
|
||||
##
|
||||
# Iterables
|
||||
|
||||
# Iterables of Asset objects.
|
||||
(table, assets, None, assets),
|
||||
(table, iter(assets), None, assets),
|
||||
(finder, assets, None, assets),
|
||||
(finder, iter(assets), None, assets),
|
||||
# Iterables of ints
|
||||
(table, (0, 1), None, assets[:-1]),
|
||||
(table, iter((0, 1)), None, assets[:-1]),
|
||||
(finder, (0, 1), None, assets[:-1]),
|
||||
(finder, iter((0, 1)), None, assets[:-1]),
|
||||
# Iterables of symbols.
|
||||
(table, ('duplicated', 'unique'), dupe_0_start, [dupe_0, unique]),
|
||||
(table, ('duplicated', 'unique'), dupe_1_start, [dupe_1, unique]),
|
||||
(finder, ('duplicated', 'unique'), dupe_0_start, [dupe_0, unique]),
|
||||
(finder, ('duplicated', 'unique'), dupe_1_start, [dupe_1, unique]),
|
||||
# Mixed types
|
||||
(table,
|
||||
(finder,
|
||||
('duplicated', 2, 'unique', 1, dupe_1),
|
||||
dupe_0_start,
|
||||
[dupe_0, assets[2], unique, assets[1], dupe_1]),
|
||||
@@ -328,70 +288,86 @@ class TestFuture(TestCase):
|
||||
|
||||
class AssetFinderTestCase(TestCase):
|
||||
|
||||
@with_environment()
|
||||
def test_lookup_symbol_fuzzy(self, env=None):
|
||||
fuzzy_str = '@'
|
||||
as_of_date = datetime(2013, 1, 1, tzinfo=pytz.utc)
|
||||
table = FakeTable(uuid.uuid4().hex, 2, as_of_date,
|
||||
fuzzy_str)
|
||||
env.update_asset_finder(asset_metadata=table.df)
|
||||
sf = env.asset_finder
|
||||
|
||||
try:
|
||||
for i in range(2): # we do it twice to test for caching bugs
|
||||
self.assertIsNone(sf.lookup_symbol('test', as_of_date))
|
||||
self.assertIsNotNone(sf.lookup_symbol(
|
||||
'test%s%s' % (fuzzy_str, 1), as_of_date))
|
||||
self.assertIsNone(sf.lookup_symbol('test%s' % 1, as_of_date))
|
||||
|
||||
self.assertIsNone(sf.lookup_symbol(table.name, as_of_date,
|
||||
fuzzy=fuzzy_str))
|
||||
self.assertIsNotNone(sf.lookup_symbol(
|
||||
'test%s%s' % (fuzzy_str, 1), as_of_date, fuzzy=fuzzy_str))
|
||||
self.assertIsNotNone(sf.lookup_symbol(
|
||||
'test%s' % 1, as_of_date, fuzzy=fuzzy_str))
|
||||
finally:
|
||||
env.update_asset_finder(clear_metadata=True)
|
||||
|
||||
@with_environment()
|
||||
def test_lookup_symbol_resolve_multiple(self, env=None):
|
||||
|
||||
as_of_dates = [
|
||||
pd.Timestamp('2013-01-01', tz='UTC') + timedelta(days=i)
|
||||
# Incrementing by two so that start and end dates for each
|
||||
# generated Asset don't overlap (each Asset's end_date is the
|
||||
# day after its start date.)
|
||||
for i in range(0, 10, 2)
|
||||
]
|
||||
|
||||
table = FakeTableIdenticalSymbols(
|
||||
name='existing',
|
||||
as_of_dates=as_of_dates,
|
||||
def test_lookup_symbol_fuzzy(self):
|
||||
as_of = pd.Timestamp('2013-01-01', tz='UTC')
|
||||
frame = pd.DataFrame.from_records(
|
||||
[
|
||||
{
|
||||
'sid': i,
|
||||
'file_name': 'TEST@%d' % i,
|
||||
'company_name': "company%d" % i,
|
||||
'start_date_nano': as_of.value,
|
||||
'end_date_nano': as_of.value,
|
||||
'exchange': uuid.uuid4().hex,
|
||||
}
|
||||
for i in range(3)
|
||||
]
|
||||
)
|
||||
finder = AssetFinder(frame)
|
||||
asset_0, asset_1, asset_2 = (
|
||||
finder.retrieve_asset(i) for i in range(3)
|
||||
)
|
||||
env.update_asset_finder(asset_metadata=table.df)
|
||||
sf = env.asset_finder
|
||||
|
||||
try:
|
||||
for _ in range(2): # we do it twice to test for caching bugs
|
||||
with self.assertRaises(SymbolNotFound):
|
||||
sf.lookup_symbol_resolve_multiple('non_existing',
|
||||
as_of_dates[0])
|
||||
with self.assertRaises(MultipleSymbolsFound):
|
||||
sf.lookup_symbol_resolve_multiple('existing',
|
||||
None)
|
||||
for i in range(2): # we do it twice to test for caching bugs
|
||||
self.assertIsNone(finder.lookup_symbol('test', as_of))
|
||||
self.assertEqual(
|
||||
asset_1,
|
||||
finder.lookup_symbol('test@1', as_of)
|
||||
)
|
||||
|
||||
for i, date in enumerate(as_of_dates):
|
||||
# Verify that we correctly resolve multiple symbols using
|
||||
# the supplied date
|
||||
result = sf.lookup_symbol_resolve_multiple(
|
||||
'existing',
|
||||
date,
|
||||
)
|
||||
self.assertEqual(result.symbol, 'existing')
|
||||
self.assertEqual(result.sid, i)
|
||||
# Adding an unnecessary fuzzy shouldn't matter.
|
||||
self.assertEqual(
|
||||
asset_1,
|
||||
finder.lookup_symbol('test@1', as_of, fuzzy='@')
|
||||
)
|
||||
|
||||
finally:
|
||||
env.update_asset_finder(clear_metadata=True)
|
||||
# Shouldn't find this with no fuzzy_str passed.
|
||||
self.assertIsNone(finder.lookup_symbol('test1', as_of))
|
||||
# Shouldn't find this with an incorrect fuzzy_str.
|
||||
self.assertIsNone(finder.lookup_symbol('test1', as_of, fuzzy='*'))
|
||||
# Should find it with the correct fuzzy_str.
|
||||
self.assertEqual(
|
||||
asset_1,
|
||||
finder.lookup_symbol('test1', as_of, fuzzy='@'),
|
||||
)
|
||||
|
||||
def test_lookup_symbol_resolve_multiple(self):
|
||||
|
||||
# Incrementing by two so that start and end dates for each
|
||||
# generated Asset don't overlap (each Asset's end_date is the
|
||||
# day after its start date.)
|
||||
dates = pd.date_range('2013-01-01', freq='2D', periods=5, tz='UTC')
|
||||
df = pd.DataFrame.from_records(
|
||||
[
|
||||
{
|
||||
'sid': i,
|
||||
'file_name': 'existing',
|
||||
'company_name': 'existing',
|
||||
'start_date_nano': date.value,
|
||||
'end_date_nano': (date + timedelta(days=1)).value,
|
||||
'exchange': 'NYSE',
|
||||
}
|
||||
for i, date in enumerate(dates)
|
||||
]
|
||||
)
|
||||
|
||||
finder = AssetFinder(df)
|
||||
for _ in range(2): # Run checks twice to test for caching bugs.
|
||||
with self.assertRaises(SymbolNotFound):
|
||||
finder.lookup_symbol_resolve_multiple('non_existing', dates[0])
|
||||
|
||||
with self.assertRaises(MultipleSymbolsFound):
|
||||
finder.lookup_symbol_resolve_multiple('existing', None)
|
||||
|
||||
for i, date in enumerate(dates):
|
||||
# Verify that we correctly resolve multiple symbols using
|
||||
# the supplied date
|
||||
result = finder.lookup_symbol_resolve_multiple(
|
||||
'existing',
|
||||
date,
|
||||
)
|
||||
self.assertEqual(result.symbol, 'existing')
|
||||
self.assertEqual(result.sid, i)
|
||||
|
||||
@with_environment()
|
||||
def test_lookup_symbol_nasdaq_underscore_collisions(self, env=None):
|
||||
@@ -433,87 +409,71 @@ class AssetFinderTestCase(TestCase):
|
||||
@parameterized.expand(
|
||||
build_lookup_generic_cases()
|
||||
)
|
||||
@with_environment()
|
||||
def test_lookup_generic(self, table, symbols, reference_date, expected,
|
||||
env=None):
|
||||
def test_lookup_generic(self, finder, symbols, reference_date, expected):
|
||||
"""
|
||||
Ensure that lookup_generic works with various permutations of inputs.
|
||||
"""
|
||||
try:
|
||||
env.update_asset_finder(asset_metadata=table.df)
|
||||
finder = env.asset_finder
|
||||
results, missing = finder.lookup_generic(symbols, reference_date)
|
||||
self.assertEqual(results, expected)
|
||||
self.assertEqual(missing, [])
|
||||
finally:
|
||||
env.update_asset_finder(clear_metadata=True)
|
||||
results, missing = finder.lookup_generic(symbols, reference_date)
|
||||
self.assertEqual(results, expected)
|
||||
self.assertEqual(missing, [])
|
||||
|
||||
@with_environment()
|
||||
def test_lookup_generic_handle_missing(self, env=None):
|
||||
try:
|
||||
table = FakeTableFromRecords(
|
||||
[
|
||||
# Sids that will be found when we do lookups.
|
||||
{
|
||||
'sid': 0,
|
||||
'file_name': 'real',
|
||||
'company_name': 'real',
|
||||
'start_date_nano': pd.Timestamp('2013-1-1', tz='UTC'),
|
||||
'end_date_nano': pd.Timestamp('2014-1-1', tz='UTC'),
|
||||
'exchange': '',
|
||||
},
|
||||
{
|
||||
'sid': 1,
|
||||
'file_name': 'also_real',
|
||||
'company_name': 'also_real',
|
||||
'start_date_nano': pd.Timestamp('2013-1-1', tz='UTC'),
|
||||
'end_date_nano': pd.Timestamp('2014-1-1', tz='UTC'),
|
||||
'exchange': '',
|
||||
},
|
||||
# Sid whose end date is before our query date. We should
|
||||
# still correctly find it.
|
||||
{
|
||||
'sid': 2,
|
||||
'file_name': 'real_but_old',
|
||||
'company_name': 'real_but_old',
|
||||
'start_date_nano': pd.Timestamp('2002-1-1', tz='UTC'),
|
||||
'end_date_nano': pd.Timestamp('2003-1-1', tz='UTC'),
|
||||
'exchange': '',
|
||||
},
|
||||
# Sid whose end date is before our query date. We should
|
||||
# still correctly find it.
|
||||
{
|
||||
'sid': 3,
|
||||
'file_name': 'real_but_in_the_future',
|
||||
'company_name': 'real_but_in_the_future',
|
||||
'start_date_nano': pd.Timestamp('2014-1-1', tz='UTC'),
|
||||
'end_date_nano': pd.Timestamp('2020-1-1', tz='UTC'),
|
||||
'exchange': 'THE FUTURE',
|
||||
},
|
||||
]
|
||||
)
|
||||
env.update_asset_finder(asset_metadata=table.df)
|
||||
symbols = [
|
||||
'real', 1, 'fake', 'real_but_old', 'real_but_in_the_future',
|
||||
def test_lookup_generic_handle_missing(self):
|
||||
data = pd.DataFrame.from_records(
|
||||
[
|
||||
# Sids that will be found when we do lookups.
|
||||
{
|
||||
'sid': 0,
|
||||
'file_name': 'real',
|
||||
'company_name': 'real',
|
||||
'start_date_nano': pd.Timestamp('2013-1-1', tz='UTC'),
|
||||
'end_date_nano': pd.Timestamp('2014-1-1', tz='UTC'),
|
||||
'exchange': '',
|
||||
},
|
||||
{
|
||||
'sid': 1,
|
||||
'file_name': 'also_real',
|
||||
'company_name': 'also_real',
|
||||
'start_date_nano': pd.Timestamp('2013-1-1', tz='UTC'),
|
||||
'end_date_nano': pd.Timestamp('2014-1-1', tz='UTC'),
|
||||
'exchange': '',
|
||||
},
|
||||
# Sid whose end date is before our query date. We should
|
||||
# still correctly find it.
|
||||
{
|
||||
'sid': 2,
|
||||
'file_name': 'real_but_old',
|
||||
'company_name': 'real_but_old',
|
||||
'start_date_nano': pd.Timestamp('2002-1-1', tz='UTC'),
|
||||
'end_date_nano': pd.Timestamp('2003-1-1', tz='UTC'),
|
||||
'exchange': '',
|
||||
},
|
||||
# Sid whose end date is before our query date. We should
|
||||
# still correctly find it.
|
||||
{
|
||||
'sid': 3,
|
||||
'file_name': 'real_but_in_the_future',
|
||||
'company_name': 'real_but_in_the_future',
|
||||
'start_date_nano': pd.Timestamp('2014-1-1', tz='UTC'),
|
||||
'end_date_nano': pd.Timestamp('2020-1-1', tz='UTC'),
|
||||
'exchange': 'THE FUTURE',
|
||||
},
|
||||
]
|
||||
)
|
||||
finder = AssetFinder(data)
|
||||
results, missing = finder.lookup_generic(
|
||||
['real', 1, 'fake', 'real_but_old', 'real_but_in_the_future'],
|
||||
pd.Timestamp('2013-02-01', tz='UTC'),
|
||||
)
|
||||
|
||||
results, missing = env.asset_finder.lookup_generic(
|
||||
symbols,
|
||||
pd.Timestamp('2013-02-01', tz='UTC'),
|
||||
)
|
||||
self.assertEqual(len(results), 3)
|
||||
self.assertEqual(results[0].symbol, 'real')
|
||||
self.assertEqual(results[0].sid, 0)
|
||||
self.assertEqual(results[1].symbol, 'also_real')
|
||||
self.assertEqual(results[1].sid, 1)
|
||||
|
||||
self.assertEqual(len(results), 3)
|
||||
self.assertEqual(results[0].symbol, 'real')
|
||||
self.assertEqual(results[0].sid, 0)
|
||||
self.assertEqual(results[1].symbol, 'also_real')
|
||||
self.assertEqual(results[1].sid, 1)
|
||||
|
||||
self.assertEqual(len(missing), 2)
|
||||
self.assertEqual(missing[0], 'fake')
|
||||
self.assertEqual(missing[1], 'real_but_in_the_future')
|
||||
|
||||
finally:
|
||||
env.update_asset_finder(clear_metadata=True)
|
||||
self.assertEqual(len(missing), 2)
|
||||
self.assertEqual(missing[0], 'fake')
|
||||
self.assertEqual(missing[1], 'real_but_in_the_future')
|
||||
|
||||
def test_insert_metadata(self):
|
||||
finder = AssetFinder()
|
||||
|
||||
@@ -33,7 +33,6 @@ from zipline.errors import (
|
||||
SidNotFound,
|
||||
SymbolNotFound,
|
||||
)
|
||||
from zipline.utils import tradingcalendar
|
||||
from zipline.assets._assets import (
|
||||
Asset, Equity, Future
|
||||
)
|
||||
@@ -63,10 +62,7 @@ ASSET_FIELDS = [
|
||||
|
||||
class AssetFinder(object):
|
||||
|
||||
def __init__(self,
|
||||
metadata=None,
|
||||
trading_calendar=tradingcalendar,
|
||||
allow_sid_assignment=True):
|
||||
def __init__(self, metadata=None, allow_sid_assignment=True):
|
||||
|
||||
# Any particular instance of AssetFinder should be
|
||||
# consistent throughout its lifetime, so we grab a reference
|
||||
@@ -85,10 +81,9 @@ class AssetFinder(object):
|
||||
# The AssetFinder also holds a nested-dict of all metadata for
|
||||
# reference when building Assets
|
||||
self.metadata_cache = {}
|
||||
if metadata:
|
||||
if metadata is not None:
|
||||
self.consume_metadata(metadata)
|
||||
|
||||
self.trading_calendar = trading_calendar
|
||||
self.populate_cache()
|
||||
|
||||
def _next_free_sid(self):
|
||||
|
||||
@@ -136,7 +136,7 @@ class TradingEnvironment(object):
|
||||
|
||||
self.exchange_tz = exchange_tz
|
||||
|
||||
self.asset_finder = AssetFinder(trading_calendar=env_trading_calendar)
|
||||
self.asset_finder = AssetFinder()
|
||||
|
||||
def __enter__(self, *args, **kwargs):
|
||||
global environment
|
||||
|
||||
Reference in New Issue
Block a user