test_lookup_symbol_from_multiple_valid()   A
last analyzed

Complexity

Conditions 3

Size

Total Lines 67

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 3
dl 0
loc 67
rs 9.2817

1 Method

Rating   Name   Duplication   Size   Complexity  
A tests.AssetFinderTestCase.check() 0 6 1

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
#
2
# Copyright 2015 Quantopian, Inc.
3
#
4
# Licensed under the Apache License, Version 2.0 (the "License");
5
# you may not use this file except in compliance with the License.
6
# You may obtain a copy of the License at
7
#
8
#     http://www.apache.org/licenses/LICENSE-2.0
9
#
10
# Unless required by applicable law or agreed to in writing, software
11
# distributed under the License is distributed on an "AS IS" BASIS,
12
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
# See the License for the specific language governing permissions and
14
# limitations under the License.
15
16
"""
17
Tests for the zipline.assets package
18
"""
19
from contextlib import contextmanager
20
from datetime import datetime, timedelta
21
import pickle
22
import sys
23
from unittest import TestCase
24
import uuid
25
import warnings
26
27
import pandas as pd
28
from pandas.tseries.tools import normalize_date
29
from pandas.util.testing import assert_frame_equal
30
31
from nose_parameterized import parameterized
32
from numpy import full
33
import sqlalchemy as sa
34
35
from zipline.assets import (
36
    Asset,
37
    Equity,
38
    Future,
39
    AssetFinder,
40
    AssetFinderCachedEquities,
41
)
42
from six import itervalues
43
from toolz import valmap
44
45
from zipline.assets.futures import (
46
    cme_code_to_month,
47
    FutureChain,
48
    month_to_cme_code
49
)
50
from zipline.assets.asset_writer import (
51
    check_version_info,
52
    write_version_info,
53
)
54
from zipline.assets.asset_db_schema import (
55
    ASSET_DB_VERSION,
56
    _version_table_schema,
57
)
58
from zipline.errors import (
59
    EquitiesNotFound,
60
    FutureContractsNotFound,
61
    MultipleSymbolsFound,
62
    RootSymbolNotFound,
63
    AssetDBVersionError,
64
    SidAssignmentError,
65
    SidsNotFound,
66
    SymbolNotFound,
67
)
68
from zipline.finance.trading import TradingEnvironment, noop_load
69
from zipline.utils.test_utils import (
70
    all_subindices,
71
    make_commodity_future_info,
72
    make_rotating_equity_info,
73
    make_simple_equity_info,
74
    tmp_assets_db,
75
    tmp_asset_finder,
76
)
77
78
79
@contextmanager
80
def build_lookup_generic_cases(asset_finder_type):
81
    """
82
    Generate test cases for the type of asset finder specific by
83
    asset_finder_type for test_lookup_generic.
84
    """
85
86
    unique_start = pd.Timestamp('2013-01-01', tz='UTC')
87
    unique_end = pd.Timestamp('2014-01-01', tz='UTC')
88
89
    dupe_0_start = pd.Timestamp('2013-01-01', tz='UTC')
90
    dupe_0_end = dupe_0_start + timedelta(days=1)
91
92
    dupe_1_start = pd.Timestamp('2013-01-03', tz='UTC')
93
    dupe_1_end = dupe_1_start + timedelta(days=1)
94
95
    frame = pd.DataFrame.from_records(
96
        [
97
            {
98
                'sid': 0,
99
                'symbol': 'duplicated',
100
                'start_date': dupe_0_start.value,
101
                'end_date': dupe_0_end.value,
102
                'exchange': '',
103
            },
104
            {
105
                'sid': 1,
106
                'symbol': 'duplicated',
107
                'start_date': dupe_1_start.value,
108
                'end_date': dupe_1_end.value,
109
                'exchange': '',
110
            },
111
            {
112
                'sid': 2,
113
                'symbol': 'unique',
114
                'start_date': unique_start.value,
115
                'end_date': unique_end.value,
116
                'exchange': '',
117
            },
118
        ],
119
        index='sid')
120
    with tmp_assets_db(equities=frame) as assets_db:
121
        finder = asset_finder_type(assets_db)
122
        dupe_0, dupe_1, unique = assets = [
123
            finder.retrieve_asset(i)
124
            for i in range(3)
125
        ]
126
127
        dupe_0_start = dupe_0.start_date
128
        dupe_1_start = dupe_1.start_date
129
        yield (
130
            ##
131
            # Scalars
132
133
            # Asset object
134
            (finder, assets[0], None, assets[0]),
135
            (finder, assets[1], None, assets[1]),
136
            (finder, assets[2], None, assets[2]),
137
            # int
138
            (finder, 0, None, assets[0]),
139
            (finder, 1, None, assets[1]),
140
            (finder, 2, None, assets[2]),
141
            # Duplicated symbol with resolution date
142
            (finder, 'DUPLICATED', dupe_0_start, dupe_0),
143
            (finder, 'DUPLICATED', dupe_1_start, dupe_1),
144
            # Unique symbol, with or without resolution date.
145
            (finder, 'UNIQUE', unique_start, unique),
146
            (finder, 'UNIQUE', None, unique),
147
148
            ##
149
            # Iterables
150
151
            # Iterables of Asset objects.
152
            (finder, assets, None, assets),
153
            (finder, iter(assets), None, assets),
154
            # Iterables of ints
155
            (finder, (0, 1), None, assets[:-1]),
156
            (finder, iter((0, 1)), None, assets[:-1]),
157
            # Iterables of symbols.
158
            (finder, ('DUPLICATED', 'UNIQUE'), dupe_0_start, [dupe_0, unique]),
159
            (finder, ('DUPLICATED', 'UNIQUE'), dupe_1_start, [dupe_1, unique]),
160
            # Mixed types
161
            (finder,
162
             ('DUPLICATED', 2, 'UNIQUE', 1, dupe_1),
163
             dupe_0_start,
164
             [dupe_0, assets[2], unique, assets[1], dupe_1]),
165
        )
166
167
168
class AssetTestCase(TestCase):
169
170
    def test_asset_object(self):
171
        self.assertEquals({5061: 'foo'}[Asset(5061)], 'foo')
172
        self.assertEquals(Asset(5061), 5061)
173
        self.assertEquals(5061, Asset(5061))
174
175
        self.assertEquals(Asset(5061), Asset(5061))
176
        self.assertEquals(int(Asset(5061)), 5061)
177
178
        self.assertEquals(str(Asset(5061)), 'Asset(5061)')
179
180
    def test_asset_is_pickleable(self):
181
182
        # Very wow
183
        s = Asset(
184
            1337,
185
            symbol="DOGE",
186
            asset_name="DOGECOIN",
187
            start_date=pd.Timestamp('2013-12-08 9:31AM', tz='UTC'),
188
            end_date=pd.Timestamp('2014-06-25 11:21AM', tz='UTC'),
189
            first_traded=pd.Timestamp('2013-12-08 9:31AM', tz='UTC'),
190
            exchange='THE MOON',
191
        )
192
        s_unpickled = pickle.loads(pickle.dumps(s))
193
194
        attrs_to_check = ['end_date',
195
                          'exchange',
196
                          'first_traded',
197
                          'end_date',
198
                          'asset_name',
199
                          'start_date',
200
                          'sid',
201
                          'start_date',
202
                          'symbol']
203
204
        for attr in attrs_to_check:
205
            self.assertEqual(getattr(s, attr), getattr(s_unpickled, attr))
206
207
    def test_asset_comparisons(self):
208
209
        s_23 = Asset(23)
210
        s_24 = Asset(24)
211
212
        self.assertEqual(s_23, s_23)
213
        self.assertEqual(s_23, 23)
214
        self.assertEqual(23, s_23)
215
216
        self.assertNotEqual(s_23, s_24)
217
        self.assertNotEqual(s_23, 24)
218
        self.assertNotEqual(s_23, "23")
219
        self.assertNotEqual(s_23, 23.5)
220
        self.assertNotEqual(s_23, [])
221
        self.assertNotEqual(s_23, None)
222
223
        self.assertLess(s_23, s_24)
224
        self.assertLess(s_23, 24)
225
        self.assertGreater(24, s_23)
226
        self.assertGreater(s_24, s_23)
227
228
    def test_lt(self):
229
        self.assertTrue(Asset(3) < Asset(4))
230
        self.assertFalse(Asset(4) < Asset(4))
231
        self.assertFalse(Asset(5) < Asset(4))
232
233
    def test_le(self):
234
        self.assertTrue(Asset(3) <= Asset(4))
235
        self.assertTrue(Asset(4) <= Asset(4))
236
        self.assertFalse(Asset(5) <= Asset(4))
237
238
    def test_eq(self):
239
        self.assertFalse(Asset(3) == Asset(4))
240
        self.assertTrue(Asset(4) == Asset(4))
241
        self.assertFalse(Asset(5) == Asset(4))
242
243
    def test_ge(self):
244
        self.assertFalse(Asset(3) >= Asset(4))
245
        self.assertTrue(Asset(4) >= Asset(4))
246
        self.assertTrue(Asset(5) >= Asset(4))
247
248
    def test_gt(self):
249
        self.assertFalse(Asset(3) > Asset(4))
250
        self.assertFalse(Asset(4) > Asset(4))
251
        self.assertTrue(Asset(5) > Asset(4))
252
253
    def test_type_mismatch(self):
254
        if sys.version_info.major < 3:
255
            self.assertIsNotNone(Asset(3) < 'a')
256
            self.assertIsNotNone('a' < Asset(3))
257
        else:
258
            with self.assertRaises(TypeError):
259
                Asset(3) < 'a'
260
            with self.assertRaises(TypeError):
261
                'a' < Asset(3)
262
263
264
class TestFuture(TestCase):
265
266
    @classmethod
267
    def setUpClass(cls):
268
        cls.future = Future(
269
            2468,
270
            symbol='OMH15',
271
            root_symbol='OM',
272
            notice_date=pd.Timestamp('2014-01-20', tz='UTC'),
273
            expiration_date=pd.Timestamp('2014-02-20', tz='UTC'),
274
            auto_close_date=pd.Timestamp('2014-01-18', tz='UTC'),
275
            contract_multiplier=500
276
        )
277
        cls.future2 = Future(
278
            0,
279
            symbol='CLG06',
280
            root_symbol='CL',
281
            start_date=pd.Timestamp('2005-12-01', tz='UTC'),
282
            notice_date=pd.Timestamp('2005-12-20', tz='UTC'),
283
            expiration_date=pd.Timestamp('2006-01-20', tz='UTC')
284
        )
285
        env = TradingEnvironment(load=noop_load)
286
        env.write_data(futures_identifiers=[TestFuture.future,
287
                                            TestFuture.future2])
288
        cls.asset_finder = env.asset_finder
289
290
    def test_str(self):
291
        strd = self.future.__str__()
292
        self.assertEqual("Future(2468 [OMH15])", strd)
293
294
    def test_repr(self):
295
        reprd = self.future.__repr__()
296
        self.assertTrue("Future" in reprd)
297
        self.assertTrue("2468" in reprd)
298
        self.assertTrue("OMH15" in reprd)
299
        self.assertTrue("root_symbol='OM'" in reprd)
300
        self.assertTrue(("notice_date=Timestamp('2014-01-20 00:00:00+0000', "
301
                        "tz='UTC')") in reprd)
302
        self.assertTrue("expiration_date=Timestamp('2014-02-20 00:00:00+0000'"
303
                        in reprd)
304
        self.assertTrue("auto_close_date=Timestamp('2014-01-18 00:00:00+0000'"
305
                        in reprd)
306
        self.assertTrue("contract_multiplier=500" in reprd)
307
308
    def test_reduce(self):
309
        reduced = self.future.__reduce__()
310
        self.assertEqual(Future, reduced[0])
311
312
    def test_to_and_from_dict(self):
313
        dictd = self.future.to_dict()
314
        self.assertTrue('root_symbol' in dictd)
315
        self.assertTrue('notice_date' in dictd)
316
        self.assertTrue('expiration_date' in dictd)
317
        self.assertTrue('auto_close_date' in dictd)
318
        self.assertTrue('contract_multiplier' in dictd)
319
320
        from_dict = Future.from_dict(dictd)
321
        self.assertTrue(isinstance(from_dict, Future))
322
        self.assertEqual(self.future, from_dict)
323
324
    def test_root_symbol(self):
325
        self.assertEqual('OM', self.future.root_symbol)
326
327
    def test_lookup_future_symbol(self):
328
        """
329
        Test the lookup_future_symbol method.
330
        """
331
        om = TestFuture.asset_finder.lookup_future_symbol('OMH15')
332
        self.assertEqual(om.sid, 2468)
333
        self.assertEqual(om.symbol, 'OMH15')
334
        self.assertEqual(om.root_symbol, 'OM')
335
        self.assertEqual(om.notice_date, pd.Timestamp('2014-01-20', tz='UTC'))
336
        self.assertEqual(om.expiration_date,
337
                         pd.Timestamp('2014-02-20', tz='UTC'))
338
        self.assertEqual(om.auto_close_date,
339
                         pd.Timestamp('2014-01-18', tz='UTC'))
340
341
        cl = TestFuture.asset_finder.lookup_future_symbol('CLG06')
342
        self.assertEqual(cl.sid, 0)
343
        self.assertEqual(cl.symbol, 'CLG06')
344
        self.assertEqual(cl.root_symbol, 'CL')
345
        self.assertEqual(cl.start_date, pd.Timestamp('2005-12-01', tz='UTC'))
346
        self.assertEqual(cl.notice_date, pd.Timestamp('2005-12-20', tz='UTC'))
347
        self.assertEqual(cl.expiration_date,
348
                         pd.Timestamp('2006-01-20', tz='UTC'))
349
350
        with self.assertRaises(SymbolNotFound):
351
            TestFuture.asset_finder.lookup_future_symbol('')
352
353
        with self.assertRaises(SymbolNotFound):
354
            TestFuture.asset_finder.lookup_future_symbol('#&?!')
355
356
        with self.assertRaises(SymbolNotFound):
357
            TestFuture.asset_finder.lookup_future_symbol('FOOBAR')
358
359
        with self.assertRaises(SymbolNotFound):
360
            TestFuture.asset_finder.lookup_future_symbol('XXX99')
361
362
363
class AssetFinderTestCase(TestCase):
364
365
    def setUp(self):
366
        self.env = TradingEnvironment(load=noop_load)
367
        self.asset_finder_type = AssetFinder
368
369
    def test_lookup_symbol_delimited(self):
370
        as_of = pd.Timestamp('2013-01-01', tz='UTC')
371
        frame = pd.DataFrame.from_records(
372
            [
373
                {
374
                    'sid': i,
375
                    'symbol':  'TEST.%d' % i,
376
                    'company_name': "company%d" % i,
377
                    'start_date': as_of.value,
378
                    'end_date': as_of.value,
379
                    'exchange': uuid.uuid4().hex
380
                }
381
                for i in range(3)
382
            ]
383
        )
384
        self.env.write_data(equities_df=frame)
385
        finder = self.asset_finder_type(self.env.engine)
386
        asset_0, asset_1, asset_2 = (
387
            finder.retrieve_asset(i) for i in range(3)
388
        )
389
390
        # we do it twice to catch caching bugs
391
        for i in range(2):
392
            with self.assertRaises(SymbolNotFound):
393
                finder.lookup_symbol('TEST', as_of)
394
            with self.assertRaises(SymbolNotFound):
395
                finder.lookup_symbol('TEST1', as_of)
396
            # '@' is not a supported delimiter
397
            with self.assertRaises(SymbolNotFound):
398
                finder.lookup_symbol('TEST@1', as_of)
399
400
            # Adding an unnecessary fuzzy shouldn't matter.
401
            for fuzzy_char in ['-', '/', '_', '.']:
402
                self.assertEqual(
403
                    asset_1,
404
                    finder.lookup_symbol('TEST%s1' % fuzzy_char, as_of)
405
                )
406
407
    def test_lookup_symbol_fuzzy(self):
408
        metadata = {
409
            0: {'symbol': 'PRTY_HRD'},
410
            1: {'symbol': 'BRKA'},
411
            2: {'symbol': 'BRK_A'},
412
        }
413
        self.env.write_data(equities_data=metadata)
414
        finder = self.env.asset_finder
415
        dt = pd.Timestamp('2013-01-01', tz='UTC')
416
417
        # Try combos of looking up PRTYHRD with and without a time or fuzzy
418
        # Both non-fuzzys get no result
419
        with self.assertRaises(SymbolNotFound):
420
            finder.lookup_symbol('PRTYHRD', None)
421
        with self.assertRaises(SymbolNotFound):
422
            finder.lookup_symbol('PRTYHRD', dt)
423
        # Both fuzzys work
424
        self.assertEqual(0, finder.lookup_symbol('PRTYHRD', None, fuzzy=True))
425
        self.assertEqual(0, finder.lookup_symbol('PRTYHRD', dt, fuzzy=True))
426
427
        # Try combos of looking up PRTY_HRD, all returning sid 0
428
        self.assertEqual(0, finder.lookup_symbol('PRTY_HRD', None))
429
        self.assertEqual(0, finder.lookup_symbol('PRTY_HRD', dt))
430
        self.assertEqual(0, finder.lookup_symbol('PRTY_HRD', None, fuzzy=True))
431
        self.assertEqual(0, finder.lookup_symbol('PRTY_HRD', dt, fuzzy=True))
432
433
        # Try combos of looking up BRKA, all returning sid 1
434
        self.assertEqual(1, finder.lookup_symbol('BRKA', None))
435
        self.assertEqual(1, finder.lookup_symbol('BRKA', dt))
436
        self.assertEqual(1, finder.lookup_symbol('BRKA', None, fuzzy=True))
437
        self.assertEqual(1, finder.lookup_symbol('BRKA', dt, fuzzy=True))
438
439
        # Try combos of looking up BRK_A, all returning sid 2
440
        self.assertEqual(2, finder.lookup_symbol('BRK_A', None))
441
        self.assertEqual(2, finder.lookup_symbol('BRK_A', dt))
442
        self.assertEqual(2, finder.lookup_symbol('BRK_A', None, fuzzy=True))
443
        self.assertEqual(2, finder.lookup_symbol('BRK_A', dt, fuzzy=True))
444
445
    def test_lookup_symbol(self):
446
447
        # Incrementing by two so that start and end dates for each
448
        # generated Asset don't overlap (each Asset's end_date is the
449
        # day after its start date.)
450
        dates = pd.date_range('2013-01-01', freq='2D', periods=5, tz='UTC')
451
        df = pd.DataFrame.from_records(
452
            [
453
                {
454
                    'sid': i,
455
                    'symbol':  'existing',
456
                    'start_date': date.value,
457
                    'end_date': (date + timedelta(days=1)).value,
458
                    'exchange': 'NYSE',
459
                }
460
                for i, date in enumerate(dates)
461
            ]
462
        )
463
        self.env.write_data(equities_df=df)
464
        finder = self.asset_finder_type(self.env.engine)
465
        for _ in range(2):  # Run checks twice to test for caching bugs.
466
            with self.assertRaises(SymbolNotFound):
467
                finder.lookup_symbol('NON_EXISTING', dates[0])
468
469
            with self.assertRaises(MultipleSymbolsFound):
470
                finder.lookup_symbol('EXISTING', None)
471
472
            for i, date in enumerate(dates):
473
                # Verify that we correctly resolve multiple symbols using
474
                # the supplied date
475
                result = finder.lookup_symbol('EXISTING', date)
476
                self.assertEqual(result.symbol, 'EXISTING')
477
                self.assertEqual(result.sid, i)
478
479
    def test_lookup_symbol_from_multiple_valid(self):
480
        # This test asserts that we resolve conflicts in accordance with the
481
        # following rules when we have multiple assets holding the same symbol
482
        # at the same time:
483
484
        # If multiple SIDs exist for symbol S at time T, return the candidate
485
        # SID whose start_date is highest. (200 cases)
486
487
        # If multiple SIDs exist for symbol S at time T, the best candidate
488
        # SIDs share the highest start_date, return the SID with the highest
489
        # end_date. (34 cases)
490
491
        # It is the opinion of the author (ssanderson) that we should consider
492
        # this malformed input and fail here.  But this is the current indended
493
        # behavior of the code, and I accidentally broke it while refactoring.
494
        # These will serve as regression tests until the time comes that we
495
        # decide to enforce this as an error.
496
497
        # See https://github.com/quantopian/zipline/issues/837 for more
498
        # details.
499
500
        df = pd.DataFrame.from_records(
501
            [
502
                {
503
                    'sid': 1,
504
                    'symbol': 'multiple',
505
                    'start_date': pd.Timestamp('2010-01-01'),
506
                    'end_date': pd.Timestamp('2012-01-01'),
507
                    'exchange': 'NYSE'
508
                },
509
                # Same as asset 1, but with a later end date.
510
                {
511
                    'sid': 2,
512
                    'symbol': 'multiple',
513
                    'start_date': pd.Timestamp('2010-01-01'),
514
                    'end_date': pd.Timestamp('2013-01-01'),
515
                    'exchange': 'NYSE'
516
                },
517
                # Same as asset 1, but with a later start_date
518
                {
519
                    'sid': 3,
520
                    'symbol': 'multiple',
521
                    'start_date': pd.Timestamp('2011-01-01'),
522
                    'end_date': pd.Timestamp('2012-01-01'),
523
                    'exchange': 'NYSE'
524
                },
525
            ]
526
        )
527
528
        def check(expected_sid, date):
529
            result = finder.lookup_symbol(
530
                'MULTIPLE', date,
531
            )
532
            self.assertEqual(result.symbol, 'MULTIPLE')
533
            self.assertEqual(result.sid, expected_sid)
534
535
        with tmp_asset_finder(finder_cls=self.asset_finder_type,
536
                              equities=df) as finder:
537
            self.assertIsInstance(finder, self.asset_finder_type)
538
539
            # Sids 1 and 2 are eligible here.  We should get asset 2 because it
540
            # has the later end_date.
541
            check(2, pd.Timestamp('2010-12-31'))
542
543
            # Sids 1, 2, and 3 are eligible here.  We should get sid 3 because
544
            # it has a later start_date
545
            check(3, pd.Timestamp('2011-01-01'))
546
547
    def test_lookup_generic(self):
548
        """
549
        Ensure that lookup_generic works with various permutations of inputs.
550
        """
551
        with build_lookup_generic_cases(self.asset_finder_type) as cases:
552
            for finder, symbols, reference_date, expected in cases:
553
                results, missing = finder.lookup_generic(symbols,
554
                                                         reference_date)
555
                self.assertEqual(results, expected)
556
                self.assertEqual(missing, [])
557
558
    def test_lookup_generic_handle_missing(self):
559
        data = pd.DataFrame.from_records(
560
            [
561
                {
562
                    'sid': 0,
563
                    'symbol': 'real',
564
                    'start_date': pd.Timestamp('2013-1-1', tz='UTC'),
565
                    'end_date': pd.Timestamp('2014-1-1', tz='UTC'),
566
                    'exchange': '',
567
                },
568
                {
569
                    'sid': 1,
570
                    'symbol': 'also_real',
571
                    'start_date': pd.Timestamp('2013-1-1', tz='UTC'),
572
                    'end_date': pd.Timestamp('2014-1-1', tz='UTC'),
573
                    'exchange': '',
574
                },
575
                # Sid whose end date is before our query date.  We should
576
                # still correctly find it.
577
                {
578
                    'sid': 2,
579
                    'symbol': 'real_but_old',
580
                    'start_date': pd.Timestamp('2002-1-1', tz='UTC'),
581
                    'end_date': pd.Timestamp('2003-1-1', tz='UTC'),
582
                    'exchange': '',
583
                },
584
                # Sid whose start_date is **after** our query date.  We should
585
                # **not** find it.
586
                {
587
                    'sid': 3,
588
                    'symbol': 'real_but_in_the_future',
589
                    'start_date': pd.Timestamp('2014-1-1', tz='UTC'),
590
                    'end_date': pd.Timestamp('2020-1-1', tz='UTC'),
591
                    'exchange': 'THE FUTURE',
592
                },
593
            ]
594
        )
595
        self.env.write_data(equities_df=data)
596
        finder = self.asset_finder_type(self.env.engine)
597
        results, missing = finder.lookup_generic(
598
            ['REAL', 1, 'FAKE', 'REAL_BUT_OLD', 'REAL_BUT_IN_THE_FUTURE'],
599
            pd.Timestamp('2013-02-01', tz='UTC'),
600
        )
601
602
        self.assertEqual(len(results), 3)
603
        self.assertEqual(results[0].symbol, 'REAL')
604
        self.assertEqual(results[0].sid, 0)
605
        self.assertEqual(results[1].symbol, 'ALSO_REAL')
606
        self.assertEqual(results[1].sid, 1)
607
        self.assertEqual(results[2].symbol, 'REAL_BUT_OLD')
608
        self.assertEqual(results[2].sid, 2)
609
610
        self.assertEqual(len(missing), 2)
611
        self.assertEqual(missing[0], 'FAKE')
612
        self.assertEqual(missing[1], 'REAL_BUT_IN_THE_FUTURE')
613
614
    def test_insert_metadata(self):
615
        data = {0: {'start_date': '2014-01-01',
616
                    'end_date': '2015-01-01',
617
                    'symbol': "PLAY",
618
                    'foo_data': "FOO"}}
619
        self.env.write_data(equities_data=data)
620
        finder = self.asset_finder_type(self.env.engine)
621
        # Test proper insertion
622
        equity = finder.retrieve_asset(0)
623
        self.assertIsInstance(equity, Equity)
624
        self.assertEqual('PLAY', equity.symbol)
625
        self.assertEqual(pd.Timestamp('2015-01-01', tz='UTC'),
626
                         equity.end_date)
627
628
        # Test invalid field
629
        with self.assertRaises(AttributeError):
630
            equity.foo_data
631
632
    def test_consume_metadata(self):
633
634
        # Test dict consumption
635
        dict_to_consume = {0: {'symbol': 'PLAY'},
636
                           1: {'symbol': 'MSFT'}}
637
        self.env.write_data(equities_data=dict_to_consume)
638
        finder = self.asset_finder_type(self.env.engine)
639
640
        equity = finder.retrieve_asset(0)
641
        self.assertIsInstance(equity, Equity)
642
        self.assertEqual('PLAY', equity.symbol)
643
644
        # Test dataframe consumption
645
        df = pd.DataFrame(columns=['asset_name', 'exchange'], index=[0, 1])
646
        df['asset_name'][0] = "Dave'N'Busters"
647
        df['exchange'][0] = "NASDAQ"
648
        df['asset_name'][1] = "Microsoft"
649
        df['exchange'][1] = "NYSE"
650
        self.env = TradingEnvironment(load=noop_load)
651
        self.env.write_data(equities_df=df)
652
        finder = self.asset_finder_type(self.env.engine)
653
        self.assertEqual('NASDAQ', finder.retrieve_asset(0).exchange)
654
        self.assertEqual('Microsoft', finder.retrieve_asset(1).asset_name)
655
656
    def test_consume_asset_as_identifier(self):
657
        # Build some end dates
658
        eq_end = pd.Timestamp('2012-01-01', tz='UTC')
659
        fut_end = pd.Timestamp('2008-01-01', tz='UTC')
660
661
        # Build some simple Assets
662
        equity_asset = Equity(1, symbol="TESTEQ", end_date=eq_end)
663
        future_asset = Future(200, symbol="TESTFUT", end_date=fut_end)
664
665
        # Consume the Assets
666
        self.env.write_data(equities_identifiers=[equity_asset],
667
                            futures_identifiers=[future_asset])
668
        finder = self.asset_finder_type(self.env.engine)
669
670
        # Test equality with newly built Assets
671
        self.assertEqual(equity_asset, finder.retrieve_asset(1))
672
        self.assertEqual(future_asset, finder.retrieve_asset(200))
673
        self.assertEqual(eq_end, finder.retrieve_asset(1).end_date)
674
        self.assertEqual(fut_end, finder.retrieve_asset(200).end_date)
675
676
    def test_sid_assignment(self):
677
678
        # This metadata does not contain SIDs
679
        metadata = ['PLAY', 'MSFT']
680
681
        today = normalize_date(pd.Timestamp('2015-07-09', tz='UTC'))
682
683
        # Write data with sid assignment
684
        self.env.write_data(equities_identifiers=metadata,
685
                            allow_sid_assignment=True)
686
687
        # Verify that Assets were built and different sids were assigned
688
        finder = self.asset_finder_type(self.env.engine)
689
        play = finder.lookup_symbol('PLAY', today)
690
        msft = finder.lookup_symbol('MSFT', today)
691
        self.assertEqual('PLAY', play.symbol)
692
        self.assertIsNotNone(play.sid)
693
        self.assertNotEqual(play.sid, msft.sid)
694
695
    def test_sid_assignment_failure(self):
696
697
        # This metadata does not contain SIDs
698
        metadata = ['PLAY', 'MSFT']
699
700
        # Write data without sid assignment, asserting failure
701
        with self.assertRaises(SidAssignmentError):
702
            self.env.write_data(equities_identifiers=metadata,
703
                                allow_sid_assignment=False)
704
705
    def test_security_dates_warning(self):
706
707
        # Build an asset with an end_date
708
        eq_end = pd.Timestamp('2012-01-01', tz='UTC')
709
        equity_asset = Equity(1, symbol="TESTEQ", end_date=eq_end)
710
711
        # Catch all warnings
712
        with warnings.catch_warnings(record=True) as w:
713
            # Cause all warnings to always be triggered
714
            warnings.simplefilter("always")
715
            equity_asset.security_start_date
716
            equity_asset.security_end_date
717
            equity_asset.security_name
718
            # Verify the warning
719
            self.assertEqual(3, len(w))
720
            for warning in w:
721
                self.assertTrue(issubclass(warning.category,
722
                                           DeprecationWarning))
723
724
    def test_lookup_future_chain(self):
725
        metadata = {
726
            # Notice day is today, so should be valid.
727
            0: {
728
                'symbol': 'ADN15',
729
                'root_symbol': 'AD',
730
                'notice_date': pd.Timestamp('2015-05-14', tz='UTC'),
731
                'expiration_date': pd.Timestamp('2015-06-14', tz='UTC'),
732
                'start_date': pd.Timestamp('2015-01-01', tz='UTC')
733
            },
734
            1: {
735
                'symbol': 'ADV15',
736
                'root_symbol': 'AD',
737
                'notice_date': pd.Timestamp('2015-08-14', tz='UTC'),
738
                'expiration_date': pd.Timestamp('2015-09-14', tz='UTC'),
739
                'start_date': pd.Timestamp('2015-01-01', tz='UTC')
740
            },
741
            # Starts trading today, so should be valid.
742
            2: {
743
                'symbol': 'ADF16',
744
                'root_symbol': 'AD',
745
                'notice_date': pd.Timestamp('2015-11-16', tz='UTC'),
746
                'expiration_date': pd.Timestamp('2015-12-16', tz='UTC'),
747
                'start_date': pd.Timestamp('2015-05-14', tz='UTC')
748
            },
749
            # Starts trading in August, so not valid.
750
            3: {
751
                'symbol': 'ADX16',
752
                'root_symbol': 'AD',
753
                'notice_date': pd.Timestamp('2015-11-16', tz='UTC'),
754
                'expiration_date': pd.Timestamp('2015-12-16', tz='UTC'),
755
                'start_date': pd.Timestamp('2015-08-01', tz='UTC')
756
            },
757
            # Notice date comes after expiration
758
            4: {
759
                'symbol': 'ADZ16',
760
                'root_symbol': 'AD',
761
                'notice_date': pd.Timestamp('2016-11-25', tz='UTC'),
762
                'expiration_date': pd.Timestamp('2016-11-16', tz='UTC'),
763
                'start_date': pd.Timestamp('2015-08-01', tz='UTC')
764
            },
765
            # This contract has no start date and also this contract should be
766
            # last in all chains
767
            5: {
768
                'symbol': 'ADZ20',
769
                'root_symbol': 'AD',
770
                'notice_date': pd.Timestamp('2020-11-25', tz='UTC'),
771
                'expiration_date': pd.Timestamp('2020-11-16', tz='UTC')
772
            },
773
        }
774
        self.env.write_data(futures_data=metadata)
775
        finder = self.asset_finder_type(self.env.engine)
776
        dt = pd.Timestamp('2015-05-14', tz='UTC')
777
        dt_2 = pd.Timestamp('2015-10-14', tz='UTC')
778
        dt_3 = pd.Timestamp('2016-11-17', tz='UTC')
779
780
        # Check that we get the expected number of contracts, in the
781
        # right order
782
        ad_contracts = finder.lookup_future_chain('AD', dt)
783
        self.assertEqual(len(ad_contracts), 6)
784
        self.assertEqual(ad_contracts[0].sid, 0)
785
        self.assertEqual(ad_contracts[1].sid, 1)
786
        self.assertEqual(ad_contracts[5].sid, 5)
787
788
        # Check that, when some contracts have expired, the chain has advanced
789
        # properly to the next contracts
790
        ad_contracts = finder.lookup_future_chain('AD', dt_2)
791
        self.assertEqual(len(ad_contracts), 4)
792
        self.assertEqual(ad_contracts[0].sid, 2)
793
        self.assertEqual(ad_contracts[3].sid, 5)
794
795
        # Check that when the expiration_date has passed but the
796
        # notice_date hasn't, contract is still considered invalid.
797
        ad_contracts = finder.lookup_future_chain('AD', dt_3)
798
        self.assertEqual(len(ad_contracts), 1)
799
        self.assertEqual(ad_contracts[0].sid, 5)
800
801
        # Check that pd.NaT for as_of_date gives the whole chain
802
        ad_contracts = finder.lookup_future_chain('AD', pd.NaT)
803
        self.assertEqual(len(ad_contracts), 6)
804
        self.assertEqual(ad_contracts[5].sid, 5)
805
806
    def test_map_identifier_index_to_sids(self):
807
        # Build an empty finder and some Assets
808
        dt = pd.Timestamp('2014-01-01', tz='UTC')
809
        finder = self.asset_finder_type(self.env.engine)
810
        asset1 = Equity(1, symbol="AAPL")
811
        asset2 = Equity(2, symbol="GOOG")
812
        asset200 = Future(200, symbol="CLK15")
813
        asset201 = Future(201, symbol="CLM15")
814
815
        # Check for correct mapping and types
816
        pre_map = [asset1, asset2, asset200, asset201]
817
        post_map = finder.map_identifier_index_to_sids(pre_map, dt)
818
        self.assertListEqual([1, 2, 200, 201], post_map)
819
        for sid in post_map:
820
            self.assertIsInstance(sid, int)
821
822
        # Change order and check mapping again
823
        pre_map = [asset201, asset2, asset200, asset1]
824
        post_map = finder.map_identifier_index_to_sids(pre_map, dt)
825
        self.assertListEqual([201, 2, 200, 1], post_map)
826
827
    def test_compute_lifetimes(self):
828
        num_assets = 4
829
        trading_day = self.env.trading_day
830
        first_start = pd.Timestamp('2015-04-01', tz='UTC')
831
832
        frame = make_rotating_equity_info(
833
            num_assets=num_assets,
834
            first_start=first_start,
835
            frequency=self.env.trading_day,
836
            periods_between_starts=3,
837
            asset_lifetime=5
838
        )
839
840
        self.env.write_data(equities_df=frame)
841
        finder = self.env.asset_finder
842
843
        all_dates = pd.date_range(
844
            start=first_start,
845
            end=frame.end_date.max(),
846
            freq=trading_day,
847
        )
848
849
        for dates in all_subindices(all_dates):
850
            expected_with_start_raw = full(
851
                shape=(len(dates), num_assets),
852
                fill_value=False,
853
                dtype=bool,
854
            )
855
            expected_no_start_raw = full(
856
                shape=(len(dates), num_assets),
857
                fill_value=False,
858
                dtype=bool,
859
            )
860
861
            for i, date in enumerate(dates):
862
                it = frame[['start_date', 'end_date']].itertuples()
863
                for j, start, end in it:
864
                    # This way of doing the checks is redundant, but very
865
                    # clear.
866
                    if start <= date <= end:
867
                        expected_with_start_raw[i, j] = True
868
                        if start < date:
869
                            expected_no_start_raw[i, j] = True
870
871
            expected_with_start = pd.DataFrame(
872
                data=expected_with_start_raw,
873
                index=dates,
874
                columns=frame.index.values,
875
            )
876
            result = finder.lifetimes(dates, include_start_date=True)
877
            assert_frame_equal(result, expected_with_start)
878
879
            expected_no_start = pd.DataFrame(
880
                data=expected_no_start_raw,
881
                index=dates,
882
                columns=frame.index.values,
883
            )
884
            result = finder.lifetimes(dates, include_start_date=False)
885
            assert_frame_equal(result, expected_no_start)
886
887
    def test_sids(self):
888
        # Ensure that the sids property of the AssetFinder is functioning
889
        self.env.write_data(equities_identifiers=[1, 2, 3])
890
        sids = self.env.asset_finder.sids
891
        self.assertEqual(3, len(sids))
892
        self.assertTrue(1 in sids)
893
        self.assertTrue(2 in sids)
894
        self.assertTrue(3 in sids)
895
896
    def test_group_by_type(self):
897
        equities = make_simple_equity_info(
898
            range(5),
899
            start_date=pd.Timestamp('2014-01-01'),
900
            end_date=pd.Timestamp('2015-01-01'),
901
        )
902
        futures = make_commodity_future_info(
903
            first_sid=6,
904
            root_symbols=['CL'],
905
            years=[2014],
906
        )
907
        # Intersecting sid queries, to exercise loading of partially-cached
908
        # results.
909
        queries = [
910
            ([0, 1, 3], [6, 7]),
911
            ([0, 2, 3], [7, 10]),
912
            (list(equities.index), list(futures.index)),
913
        ]
914
        with tmp_asset_finder(equities=equities, futures=futures) as finder:
915
            for equity_sids, future_sids in queries:
916
                results = finder.group_by_type(equity_sids + future_sids)
917
                self.assertEqual(
918
                    results,
919
                    {'equity': set(equity_sids), 'future': set(future_sids)},
920
                )
921
922
    @parameterized.expand([
923
        (Equity, 'retrieve_equities', EquitiesNotFound),
924
        (Future, 'retrieve_futures_contracts', FutureContractsNotFound),
925
    ])
926
    def test_retrieve_specific_type(self, type_, lookup_name, failure_type):
927
        equities = make_simple_equity_info(
928
            range(5),
929
            start_date=pd.Timestamp('2014-01-01'),
930
            end_date=pd.Timestamp('2015-01-01'),
931
        )
932
        max_equity = equities.index.max()
933
        futures = make_commodity_future_info(
934
            first_sid=max_equity + 1,
935
            root_symbols=['CL'],
936
            years=[2014],
937
        )
938
        equity_sids = [0, 1]
939
        future_sids = [max_equity + 1, max_equity + 2, max_equity + 3]
940
        if type_ == Equity:
941
            success_sids = equity_sids
942
            fail_sids = future_sids
943
        else:
944
            fail_sids = equity_sids
945
            success_sids = future_sids
946
947
        with tmp_asset_finder(equities=equities, futures=futures) as finder:
948
            # Run twice to exercise caching.
949
            lookup = getattr(finder, lookup_name)
950
            for _ in range(2):
951
                results = lookup(success_sids)
952
                self.assertIsInstance(results, dict)
953
                self.assertEqual(set(results.keys()), set(success_sids))
954
                self.assertEqual(
955
                    valmap(int, results),
956
                    dict(zip(success_sids, success_sids)),
957
                )
958
                self.assertEqual(
959
                    {type_},
960
                    {type(asset) for asset in itervalues(results)},
961
                )
962
                with self.assertRaises(failure_type):
963
                    lookup(fail_sids)
964
                with self.assertRaises(failure_type):
965
                    # Should fail if **any** of the assets are bad.
966
                    lookup([success_sids[0], fail_sids[0]])
967
968
    def test_retrieve_all(self):
969
        equities = make_simple_equity_info(
970
            range(5),
971
            start_date=pd.Timestamp('2014-01-01'),
972
            end_date=pd.Timestamp('2015-01-01'),
973
        )
974
        max_equity = equities.index.max()
975
        futures = make_commodity_future_info(
976
            first_sid=max_equity + 1,
977
            root_symbols=['CL'],
978
            years=[2014],
979
        )
980
981
        with tmp_asset_finder(equities=equities, futures=futures) as finder:
982
            all_sids = finder.sids
983
            self.assertEqual(len(all_sids), len(equities) + len(futures))
984
            queries = [
985
                # Empty Query.
986
                (),
987
                # Only Equities.
988
                tuple(equities.index[:2]),
989
                # Only Futures.
990
                tuple(futures.index[:3]),
991
                # Mixed, all cache misses.
992
                tuple(equities.index[2:]) + tuple(futures.index[3:]),
993
                # Mixed, all cache hits.
994
                tuple(equities.index[2:]) + tuple(futures.index[3:]),
995
                # Everything.
996
                all_sids,
997
                all_sids,
998
            ]
999
            for sids in queries:
1000
                equity_sids = [i for i in sids if i <= max_equity]
1001
                future_sids = [i for i in sids if i > max_equity]
1002
                results = finder.retrieve_all(sids)
1003
                self.assertEqual(sids, tuple(map(int, results)))
1004
1005
                self.assertEqual(
1006
                    [Equity for _ in equity_sids] +
1007
                    [Future for _ in future_sids],
1008
                    list(map(type, results)),
1009
                )
1010
                self.assertEqual(
1011
                    (
1012
                        list(equities.symbol.loc[equity_sids]) +
1013
                        list(futures.symbol.loc[future_sids])
1014
                    ),
1015
                    list(asset.symbol for asset in results),
1016
                )
1017
1018
    @parameterized.expand([
1019
        (EquitiesNotFound, 'equity', 'equities'),
1020
        (FutureContractsNotFound, 'future contract', 'future contracts'),
1021
        (SidsNotFound, 'asset', 'assets'),
1022
    ])
1023
    def test_error_message_plurality(self,
1024
                                     error_type,
1025
                                     singular,
1026
                                     plural):
1027
        try:
1028
            raise error_type(sids=[1])
1029
        except error_type as e:
1030
            self.assertEqual(
1031
                str(e),
1032
                "No {singular} found for sid: 1.".format(singular=singular)
1033
            )
1034
        try:
1035
            raise error_type(sids=[1, 2])
1036
        except error_type as e:
1037
            self.assertEqual(
1038
                str(e),
1039
                "No {plural} found for sids: [1, 2].".format(plural=plural)
1040
            )
1041
1042
1043
class AssetFinderCachedEquitiesTestCase(AssetFinderTestCase):
1044
1045
    def setUp(self):
1046
        self.env = TradingEnvironment(load=noop_load)
1047
        self.asset_finder_type = AssetFinderCachedEquities
1048
1049
1050
class TestFutureChain(TestCase):
1051
1052
    @classmethod
1053
    def setUpClass(cls):
1054
        metadata = {
1055
            0: {
1056
                'symbol': 'CLG06',
1057
                'root_symbol': 'CL',
1058
                'start_date': pd.Timestamp('2005-12-01', tz='UTC'),
1059
                'notice_date': pd.Timestamp('2005-12-20', tz='UTC'),
1060
                'expiration_date': pd.Timestamp('2006-01-20', tz='UTC')},
1061
            1: {
1062
                'root_symbol': 'CL',
1063
                'symbol': 'CLK06',
1064
                'start_date': pd.Timestamp('2005-12-01', tz='UTC'),
1065
                'notice_date': pd.Timestamp('2006-03-20', tz='UTC'),
1066
                'expiration_date': pd.Timestamp('2006-04-20', tz='UTC')},
1067
            2: {
1068
                'symbol': 'CLQ06',
1069
                'root_symbol': 'CL',
1070
                'start_date': pd.Timestamp('2005-12-01', tz='UTC'),
1071
                'notice_date': pd.Timestamp('2006-06-20', tz='UTC'),
1072
                'expiration_date': pd.Timestamp('2006-07-20', tz='UTC')},
1073
            3: {
1074
                'symbol': 'CLX06',
1075
                'root_symbol': 'CL',
1076
                'start_date': pd.Timestamp('2006-02-01', tz='UTC'),
1077
                'notice_date': pd.Timestamp('2006-09-20', tz='UTC'),
1078
                'expiration_date': pd.Timestamp('2006-10-20', tz='UTC')}
1079
        }
1080
1081
        env = TradingEnvironment(load=noop_load)
1082
        env.write_data(futures_data=metadata)
1083
        cls.asset_finder = env.asset_finder
1084
1085
    @classmethod
1086
    def tearDownClass(cls):
1087
        del cls.asset_finder
1088
1089
    def test_len(self):
1090
        """ Test the __len__ method of FutureChain.
1091
        """
1092
        # Sids 0, 1, & 2 have started, 3 has not yet started, but all are in
1093
        # the chain
1094
        cl = FutureChain(self.asset_finder, lambda: '2005-12-01', 'CL')
1095
        self.assertEqual(len(cl), 4)
1096
1097
        # Sid 0 is still valid on its notice date.
1098
        cl = FutureChain(self.asset_finder, lambda: '2005-12-20', 'CL')
1099
        self.assertEqual(len(cl), 4)
1100
1101
        # Sid 0 is now invalid, leaving Sids 1 & 2 valid (and 3 not started).
1102
        cl = FutureChain(self.asset_finder, lambda: '2005-12-21', 'CL')
1103
        self.assertEqual(len(cl), 3)
1104
1105
        # Sid 3 has started, so 1, 2, & 3 are now valid.
1106
        cl = FutureChain(self.asset_finder, lambda: '2006-02-01', 'CL')
1107
        self.assertEqual(len(cl), 3)
1108
1109
        # All contracts are no longer valid.
1110
        cl = FutureChain(self.asset_finder, lambda: '2006-09-21', 'CL')
1111
        self.assertEqual(len(cl), 0)
1112
1113
    def test_getitem(self):
1114
        """ Test the __getitem__ method of FutureChain.
1115
        """
1116
        cl = FutureChain(self.asset_finder, lambda: '2005-12-01', 'CL')
1117
        self.assertEqual(cl[0], 0)
1118
        self.assertEqual(cl[1], 1)
1119
        self.assertEqual(cl[2], 2)
1120
1121
        cl = FutureChain(self.asset_finder, lambda: '2005-12-20', 'CL')
1122
        self.assertEqual(cl[0], 0)
1123
1124
        cl = FutureChain(self.asset_finder, lambda: '2005-12-21', 'CL')
1125
        self.assertEqual(cl[0], 1)
1126
1127
        cl = FutureChain(self.asset_finder, lambda: '2006-02-01', 'CL')
1128
        self.assertEqual(cl[-1], 3)
1129
1130
    def test_iter(self):
1131
        """ Test the __iter__ method of FutureChain.
1132
        """
1133
        cl = FutureChain(self.asset_finder, lambda: '2005-12-01', 'CL')
1134
        for i, contract in enumerate(cl):
1135
            self.assertEqual(contract, i)
1136
1137
        # First contract is now invalid, so sids will be offset by one
1138
        cl = FutureChain(self.asset_finder, lambda: '2005-12-21', 'CL')
1139
        for i, contract in enumerate(cl):
1140
            self.assertEqual(contract, i + 1)
1141
1142
    def test_root_symbols(self):
1143
        """ Test that different variations on root symbols are handled
1144
        as expected.
1145
        """
1146
        # Make sure this successfully gets the chain for CL.
1147
        cl = FutureChain(self.asset_finder, lambda: '2005-12-01', 'CL')
1148
        self.assertEqual(cl.root_symbol, 'CL')
1149
1150
        # These root symbols don't exist, so RootSymbolNotFound should
1151
        # be raised immediately.
1152
        with self.assertRaises(RootSymbolNotFound):
1153
            FutureChain(self.asset_finder, lambda: '2005-12-01', 'CLZ')
1154
1155
        with self.assertRaises(RootSymbolNotFound):
1156
            FutureChain(self.asset_finder, lambda: '2005-12-01', '')
1157
1158
    def test_repr(self):
1159
        """ Test the __repr__ method of FutureChain.
1160
        """
1161
        cl = FutureChain(self.asset_finder, lambda: '2005-12-01', 'CL')
1162
        cl_feb = FutureChain(self.asset_finder, lambda: '2005-12-01', 'CL',
1163
                             as_of_date=pd.Timestamp('2006-02-01', tz='UTC'))
1164
1165
        # The default chain should not include the as of date.
1166
        self.assertEqual(repr(cl), "FutureChain(root_symbol='CL')")
1167
1168
        # An explicit as of date should show up in the repr.
1169
        self.assertEqual(
1170
            repr(cl_feb),
1171
            ("FutureChain(root_symbol='CL', "
1172
             "as_of_date='2006-02-01 00:00:00+00:00')")
1173
        )
1174
1175
    def test_as_of(self):
1176
        """ Test the as_of method of FutureChain.
1177
        """
1178
        cl = FutureChain(self.asset_finder, lambda: '2005-12-01', 'CL')
1179
1180
        # Test that the as_of_date is set correctly to the future
1181
        feb = pd.Timestamp('2006-02-01', tz='UTC')
1182
        cl_feb = cl.as_of(feb)
1183
        self.assertEqual(
1184
            cl_feb.as_of_date,
1185
            pd.Timestamp(feb, tz='UTC')
1186
        )
1187
1188
        # Test that the as_of_date is set correctly to the past, with
1189
        # args of str, datetime.datetime, and pd.Timestamp.
1190
        feb_prev = pd.Timestamp('2005-02-01', tz='UTC')
1191
        cl_feb_prev = cl.as_of(feb_prev)
1192
        self.assertEqual(
1193
            cl_feb_prev.as_of_date,
1194
            pd.Timestamp(feb_prev, tz='UTC')
1195
        )
1196
1197
        feb_prev = pd.Timestamp(datetime(year=2005, month=2, day=1), tz='UTC')
1198
        cl_feb_prev = cl.as_of(feb_prev)
1199
        self.assertEqual(
1200
            cl_feb_prev.as_of_date,
1201
            pd.Timestamp(feb_prev, tz='UTC')
1202
        )
1203
1204
        feb_prev = pd.Timestamp('2005-02-01', tz='UTC')
1205
        cl_feb_prev = cl.as_of(feb_prev)
1206
        self.assertEqual(
1207
            cl_feb_prev.as_of_date,
1208
            pd.Timestamp(feb_prev, tz='UTC')
1209
        )
1210
1211
        # Test that the as_of() method works with str args
1212
        feb_str = '2006-02-01'
1213
        cl_feb = cl.as_of(feb_str)
1214
        self.assertEqual(
1215
            cl_feb.as_of_date,
1216
            pd.Timestamp(feb, tz='UTC')
1217
        )
1218
1219
        # The chain as of the current dt should always be the same as
1220
        # the defualt chain.
1221
        self.assertEqual(cl[0], cl.as_of(pd.Timestamp('2005-12-01'))[0])
1222
1223
    def test_offset(self):
1224
        """ Test the offset method of FutureChain.
1225
        """
1226
        cl = FutureChain(self.asset_finder, lambda: '2005-12-01', 'CL')
1227
1228
        # Test that an offset forward sets as_of_date as expected
1229
        self.assertEqual(
1230
            cl.offset('3 days').as_of_date,
1231
            cl.as_of_date + pd.Timedelta(days=3)
1232
        )
1233
1234
        # Test that an offset backward sets as_of_date as expected, with
1235
        # time delta given as str, datetime.timedelta, and pd.Timedelta.
1236
        self.assertEqual(
1237
            cl.offset('-1000 days').as_of_date,
1238
            cl.as_of_date + pd.Timedelta(days=-1000)
1239
        )
1240
        self.assertEqual(
1241
            cl.offset(timedelta(days=-1000)).as_of_date,
1242
            cl.as_of_date + pd.Timedelta(days=-1000)
1243
        )
1244
        self.assertEqual(
1245
            cl.offset(pd.Timedelta('-1000 days')).as_of_date,
1246
            cl.as_of_date + pd.Timedelta(days=-1000)
1247
        )
1248
1249
        # An offset of zero should give the original chain.
1250
        self.assertEqual(cl[0], cl.offset(0)[0])
1251
        self.assertEqual(cl[0], cl.offset("0 days")[0])
1252
1253
        # A string that doesn't represent a time delta should raise a
1254
        # ValueError.
1255
        with self.assertRaises(ValueError):
1256
            cl.offset("blah")
1257
1258
    def test_cme_code_to_month(self):
1259
        codes = {
1260
            'F': 1,   # January
1261
            'G': 2,   # February
1262
            'H': 3,   # March
1263
            'J': 4,   # April
1264
            'K': 5,   # May
1265
            'M': 6,   # June
1266
            'N': 7,   # July
1267
            'Q': 8,   # August
1268
            'U': 9,   # September
1269
            'V': 10,  # October
1270
            'X': 11,  # November
1271
            'Z': 12   # December
1272
        }
1273
        for key in codes:
1274
            self.assertEqual(codes[key], cme_code_to_month(key))
1275
1276
    def test_month_to_cme_code(self):
1277
        codes = {
1278
            1: 'F',   # January
1279
            2: 'G',   # February
1280
            3: 'H',   # March
1281
            4: 'J',   # April
1282
            5: 'K',   # May
1283
            6: 'M',   # June
1284
            7: 'N',   # July
1285
            8: 'Q',   # August
1286
            9: 'U',   # September
1287
            10: 'V',  # October
1288
            11: 'X',  # November
1289
            12: 'Z',  # December
1290
        }
1291
        for key in codes:
1292
            self.assertEqual(codes[key], month_to_cme_code(key))
1293
1294
1295
class TestAssetDBVersioning(TestCase):
1296
1297
    def test_check_version(self):
1298
        env = TradingEnvironment(load=noop_load)
1299
        version_table = env.asset_finder.version_info
1300
1301
        # This should not raise an error
1302
        check_version_info(version_table, ASSET_DB_VERSION)
1303
1304
        # This should fail because the version is too low
1305
        with self.assertRaises(AssetDBVersionError):
1306
            check_version_info(version_table, ASSET_DB_VERSION - 1)
1307
1308
        # This should fail because the version is too high
1309
        with self.assertRaises(AssetDBVersionError):
1310
            check_version_info(version_table, ASSET_DB_VERSION + 1)
1311
1312
    def test_write_version(self):
1313
        env = TradingEnvironment(load=noop_load)
1314
        metadata = sa.MetaData(bind=env.engine)
1315
        version_table = _version_table_schema(metadata)
1316
        version_table.delete().execute()
1317
1318
        # Assert that the version is not present in the table
1319
        self.assertIsNone(sa.select((version_table.c.version,)).scalar())
1320
1321
        # This should fail because the table has no version info and is,
1322
        # therefore, consdered v0
1323
        with self.assertRaises(AssetDBVersionError):
1324
            check_version_info(version_table, -2)
1325
1326
        # This should not raise an error because the version has been written
1327
        write_version_info(version_table, -2)
1328
        check_version_info(version_table, -2)
1329
1330
        # Assert that the version is in the table and correct
1331
        self.assertEqual(sa.select((version_table.c.version,)).scalar(), -2)
1332
1333
        # Assert that trying to overwrite the version fails
1334
        with self.assertRaises(sa.exc.IntegrityError):
1335
            write_version_info(version_table, -3)
1336
1337
    def test_finder_checks_version(self):
1338
        # Create an env and give it a bogus version number
1339
        env = TradingEnvironment(load=noop_load)
1340
        metadata = sa.MetaData(bind=env.engine)
1341
        version_table = _version_table_schema(metadata)
1342
        version_table.delete().execute()
1343
        write_version_info(version_table, -2)
1344
        check_version_info(version_table, -2)
1345
1346
        # Assert that trying to build a finder with a bad db raises an error
1347
        with self.assertRaises(AssetDBVersionError):
1348
            AssetFinder(engine=env.engine)
1349
1350
        # Change the version number of the db to the correct version
1351
        version_table.delete().execute()
1352
        write_version_info(version_table, ASSET_DB_VERSION)
1353
        check_version_info(version_table, ASSET_DB_VERSION)
1354
1355
        # Now that the versions match, this Finder should succeed
1356
        AssetFinder(engine=env.engine)
1357