Skip to content

Commit

Permalink
Merge pull request #35 from quantopian/add-india
Browse files Browse the repository at this point in the history
ENH: Add Bombay, Shanghai, Singapore
  • Loading branch information
Jean Bredeche authored Nov 1, 2018
2 parents 4f015f8 + 8ba22ac commit 3915b3d
Show file tree
Hide file tree
Showing 17 changed files with 22,860 additions and 38 deletions.
5,956 changes: 5,956 additions & 0 deletions tests/resources/xbom.csv

Large diffs are not rendered by default.

8,796 changes: 8,796 additions & 0 deletions tests/resources/xses.csv

Large diffs are not rendered by default.

6,587 changes: 6,587 additions & 0 deletions tests/resources/xshg.csv

Large diffs are not rendered by default.

7 changes: 0 additions & 7 deletions tests/test_always_open.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,6 @@ class AlwaysOpenTestCase(ExchangeCalendarTestBase, TestCase):

DAYLIGHT_SAVINGS_DATES = ['2016-04-05', '2016-11-01']

@classmethod
def setUpClass(cls):
cls.cal = AlwaysOpenCalendar(
start=pd.Timestamp('2016', tz='UTC'),
end=pd.Timestamp('2016-12-31', tz='UTC'),
)

def test_open_every_day(self):
cal = self.calendar

Expand Down
7 changes: 7 additions & 0 deletions tests/test_calendar_dispatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ def setUp(self):
**{k: v.copy() for k, v in self.dispatcher_kwargs.items()}
)

def tearDown(self):
self.dispatcher = None

@classmethod
def tearDownClass(cls):
cls.dispatcher_kwargs = None

def test_follow_alias_chain(self):
self.assertIs(
self.dispatcher.get_calendar('IEPA_ALIAS'),
Expand Down
64 changes: 39 additions & 25 deletions tests/test_trading_calendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,12 @@
)

from trading_calendars import (
deregister_calendar,
get_calendar,
register_calendar,
)
from trading_calendars.calendar_utils import (
_default_calendar_aliases,
_default_calendar_factories,
register_calendar_type,

TradingCalendarDispatcher,
)
from trading_calendars.trading_calendar import (
days_at_time,
Expand All @@ -65,67 +62,79 @@ class FakeCalendar(TradingCalendar):
class CalendarRegistrationTestCase(TestCase):
def setUp(self):
self.dummy_cal_type = FakeCalendar
self.dispatcher = TradingCalendarDispatcher({}, {}, {})

def tearDown(self):
deregister_calendar('DMY')
self.dispatcher.clear_calendars()

def test_register_calendar(self):
# Build a fake calendar
dummy_cal = self.dummy_cal_type()

# Try to register and retrieve the calendar
register_calendar('DMY', dummy_cal)
retr_cal = get_calendar('DMY')
self.dispatcher.register_calendar('DMY', dummy_cal)
retr_cal = self.dispatcher.get_calendar('DMY')
self.assertEqual(dummy_cal, retr_cal)

# Try to register again, expecting a name collision
with self.assertRaises(CalendarNameCollision):
register_calendar('DMY', dummy_cal)
self.dispatcher.register_calendar('DMY', dummy_cal)

# Deregister the calendar and ensure that it is removed
deregister_calendar('DMY')
self.dispatcher.deregister_calendar('DMY')
with self.assertRaises(InvalidCalendarName):
get_calendar('DMY')
self.dispatcher.get_calendar('DMY')

def test_register_calendar_type(self):
register_calendar_type("DMY", self.dummy_cal_type)
retr_cal = get_calendar("DMY")
self.dispatcher.register_calendar_type("DMY", self.dummy_cal_type)
retr_cal = self.dispatcher.get_calendar("DMY")
self.assertEqual(self.dummy_cal_type, type(retr_cal))

def test_both_places_are_checked(self):
dummy_cal = self.dummy_cal_type()

# if instance is registered, can't register type with same name
register_calendar('DMY', dummy_cal)
self.dispatcher.register_calendar('DMY', dummy_cal)
with self.assertRaises(CalendarNameCollision):
register_calendar_type('DMY', type(dummy_cal))
self.dispatcher.register_calendar_type('DMY', type(dummy_cal))

deregister_calendar('DMY')
self.dispatcher.deregister_calendar('DMY')

# if type is registered, can't register instance with same name
register_calendar_type('DMY', type(dummy_cal))
self.dispatcher.register_calendar_type('DMY', type(dummy_cal))

with self.assertRaises(CalendarNameCollision):
register_calendar('DMY', dummy_cal)
self.dispatcher.register_calendar('DMY', dummy_cal)

def test_force_registration(self):
register_calendar("DMY", self.dummy_cal_type())
first_dummy = get_calendar("DMY")
self.dispatcher.register_calendar("DMY", self.dummy_cal_type())
first_dummy = self.dispatcher.get_calendar("DMY")

# force-register a new instance
register_calendar("DMY", self.dummy_cal_type(), force=True)
self.dispatcher.register_calendar("DMY", self.dummy_cal_type(),
force=True)

second_dummy = get_calendar("DMY")
second_dummy = self.dispatcher.get_calendar("DMY")

self.assertNotEqual(first_dummy, second_dummy)


class DefaultsTestCase(TestCase):
def test_default_calendars(self):
for name in concat([_default_calendar_factories,
_default_calendar_aliases]):
self.assertIsNotNone(get_calendar(name),
dispatcher = TradingCalendarDispatcher(
calendars={},
calendar_factories=_default_calendar_factories,
aliases=_default_calendar_aliases,
)

# These are ordered aliases first, so that we can deregister the
# canonical factories when we're done with them, and we'll be done with
# them after they've been used by all aliases and by canonical name.
for name in concat([_default_calendar_aliases,
_default_calendar_factories]):
self.assertIsNotNone(dispatcher.get_calendar(name),
"get_calendar(%r) returned None" % name)
dispatcher.deregister_calendar(name)


class DaysAtTimeTestCase(TestCase):
Expand Down Expand Up @@ -227,12 +236,17 @@ def setupClass(cls):
cls.one_minute = pd.Timedelta(minutes=1)
cls.one_hour = pd.Timedelta(hours=1)

@classmethod
def teardownClass(cls):
cls.calendar = None
cls.answers = None

def test_sanity_check_session_lengths(self):
# make sure that no session is longer than self.MAX_SESSION_HOURS hours
for session in self.calendar.all_sessions:
o, c = self.calendar.open_and_close_for_session(session)
delta = c - o
self.assertTrue((delta.seconds / 3600) <= self.MAX_SESSION_HOURS)
self.assertLessEqual(delta.seconds / 3600, self.MAX_SESSION_HOURS)

def test_calculated_against_csv(self):
assert_index_equal(self.calendar.schedule.index, self.answers.index)
Expand Down
5 changes: 5 additions & 0 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import pandas as pd


def T(x):
return pd.Timestamp(x, tz='UTC')
59 changes: 59 additions & 0 deletions tests/test_xbom_calendar.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
from unittest import TestCase

from .test_trading_calendar import ExchangeCalendarTestBase
from .test_utils import T
from trading_calendars.exchange_calendar_xbom import XBOMExchangeCalendar


class XBOMCalendarTestCase(ExchangeCalendarTestBase, TestCase):

answer_key_filename = 'xbom'
calendar_class = XBOMExchangeCalendar

# BSE is open from 9:15 am to 3:30 pm
MAX_SESSION_HOURS = 6.25

HAVE_EARLY_CLOSES = False

def test_normal_year(self):
expected_holidays_2017 = [
T('2017-01-26'),
T('2017-02-24'),
T('2017-03-13'),
T('2017-04-04'),
T('2017-04-14'),
T('2017-05-01'),
T('2017-06-26'),
T('2017-08-15'),
T('2017-08-25'),
T('2017-10-02'),
T('2017-10-20'),
T('2017-12-25'),
]

for session_label in expected_holidays_2017:
self.assertNotIn(session_label, self.calendar.all_sessions)

def test_constrain_construction_dates(self):
# the XBOM calendar currently goes from 1997 to 2020, inclusive.
with self.assertRaises(ValueError) as e:
self.calendar_class(T('1996-12-31'), T('1998-01-01'))

self.assertEqual(
str(e.exception),
(
'The XBOM holidays are only recorded back to 1997,'
' cannot instantiate the XBOM calendar back to 1996.'
)
)

with self.assertRaises(ValueError) as e:
self.calendar_class(T('1998-01-01'), T('2021-01-01'))

self.assertEqual(
str(e.exception),
(
'The XBOM holidays are only recorded to 2020,'
' cannot instantiate the XBOM calendar for 2021.'
)
)
5 changes: 1 addition & 4 deletions tests/test_xhkg_calendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,10 @@
import pandas as pd

from .test_trading_calendar import ExchangeCalendarTestBase
from .test_utils import T
from trading_calendars.exchange_calendar_xhkg import XHKGExchangeCalendar


def T(x):
return pd.Timestamp(x, tz='UTC')


class XHKGCalendarTestCase(ExchangeCalendarTestBase, TestCase):

answer_key_filename = 'xhkg'
Expand Down
58 changes: 58 additions & 0 deletions tests/test_xses_calendar.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
from unittest import TestCase

from .test_trading_calendar import ExchangeCalendarTestBase
from .test_utils import T
from trading_calendars.exchange_calendar_xses import XSESExchangeCalendar


class XSESCalendarTestCase(ExchangeCalendarTestBase, TestCase):

answer_key_filename = 'xses'
calendar_class = XSESExchangeCalendar

# Singapore stock exchange is open from 9am to 5pm
# (for now, ignoring lunch break)
MAX_SESSION_HOURS = 8

HAVE_EARLY_CLOSES = False

def test_normal_year(self):
expected_holidays_2017 = [
T("2017-01-02"),
T("2017-01-30"),
T("2017-04-14"),
T("2017-05-01"),
T("2017-05-10"),
T("2017-06-26"),
T("2017-08-09"),
T("2017-09-01"),
T("2017-10-18"),
T("2017-12-25"),
]

for session_label in expected_holidays_2017:
self.assertNotIn(session_label, self.calendar.all_sessions)

def test_constrain_construction_dates(self):
# the XSES calendar currently goes from 1999 to 2025, inclusive.
with self.assertRaises(ValueError) as e:
self.calendar_class(T('1985-12-31'), T('2005-01-01'))

self.assertEqual(
str(e.exception),
(
'The XSES holidays are only recorded back to 1986,'
' cannot instantiate the XSES calendar back to 1985.'
)
)

with self.assertRaises(ValueError) as e:
self.calendar_class(T('2005-01-01'), T('2021-01-01'))

self.assertEqual(
str(e.exception),
(
'The XSES holidays are only recorded to 2020,'
' cannot instantiate the XSES calendar for 2021.'
)
)
67 changes: 67 additions & 0 deletions tests/test_xshg_calendar.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
from unittest import TestCase
import pandas as pd

from .test_trading_calendar import ExchangeCalendarTestBase
from .test_utils import T
from trading_calendars.exchange_calendar_xshg import XSHGExchangeCalendar


class XSHGCalendarTestCase(ExchangeCalendarTestBase, TestCase):

answer_key_filename = 'xshg'
calendar_class = XSHGExchangeCalendar

# Shanghai stock exchange is open from 9:30 am to 3pm
# (for now, ignoring lunch break)
MAX_SESSION_HOURS = 5.5

HAVE_EARLY_CLOSES = False

MINUTE_INDEX_TO_SESSION_LABELS_END = pd.Timestamp('2011-04-07', tz='UTC')

def test_normal_year(self):
expected_holidays_2017 = [
T("2017-01-02"),
T("2017-01-27"),
T("2017-01-30"),
T("2017-01-31"),
T("2017-02-01"),
T("2017-02-02"),
T("2017-04-03"),
T("2017-04-04"),
T("2017-05-01"),
T("2017-05-29"),
T("2017-05-30"),
T("2017-10-02"),
T("2017-10-03"),
T("2017-10-04"),
T("2017-10-05"),
T("2017-10-06"),
]

for session_label in expected_holidays_2017:
self.assertNotIn(session_label, self.calendar.all_sessions)

def test_constrain_construction_dates(self):
# the XSHG calendar currently goes from 1999 to 2025, inclusive.
with self.assertRaises(ValueError) as e:
self.calendar_class(T('1998-12-31'), T('2005-01-01'))

self.assertEqual(
str(e.exception),
(
'The XSHG holidays are only recorded back to 1999,'
' cannot instantiate the XSHG calendar back to 1998.'
)
)

with self.assertRaises(ValueError) as e:
self.calendar_class(T('2005-01-01'), T('2026-01-01'))

self.assertEqual(
str(e.exception),
(
'The XSHG holidays are only recorded to 2025,'
' cannot instantiate the XSHG calendar for 2026.'
)
)
Loading

0 comments on commit 3915b3d

Please sign in to comment.