mirror of
https://github.com/wassname/catalyst.git
synced 2026-07-03 17:47:24 +08:00
MAINT: Consolidates minute_window methods in schedule classes
This commit is contained in:
@@ -112,7 +112,7 @@ class ExchangeCalendarTestBase(object):
|
||||
def test_minute_window(self):
|
||||
for open in self.answers.market_open:
|
||||
open_tz = open.tz_localize('UTC')
|
||||
window = self.calendar.minute_window(open_tz, 390, 1)
|
||||
window = self.calendar.market_minute_window(open_tz, 390, step=1)
|
||||
self.assertEqual(len(window), 390)
|
||||
|
||||
|
||||
|
||||
+11
-11
@@ -203,7 +203,7 @@ class FinanceTestCase(WithLogger,
|
||||
data_frequency="minute"
|
||||
)
|
||||
|
||||
minutes = default_nyse_schedule.minute_window(
|
||||
minutes = default_nyse_schedule.execution_minute_window(
|
||||
sim_params.first_open,
|
||||
int((trade_interval.total_seconds() / 60) * trade_count)
|
||||
+ 100)
|
||||
@@ -497,7 +497,7 @@ class TradingEnvironmentTestCase(WithLogger,
|
||||
utc_start = pd.Timestamp(start.astimezone(utc))
|
||||
|
||||
# Get the next 10 minutes
|
||||
minutes = self.cal.minute_window(
|
||||
minutes = self.cal.market_minute_window(
|
||||
utc_start, 10,
|
||||
)
|
||||
self.assertEqual(len(minutes), 10)
|
||||
@@ -505,7 +505,7 @@ class TradingEnvironmentTestCase(WithLogger,
|
||||
self.assertEqual(minutes[i], utc_start + timedelta(minutes=i))
|
||||
|
||||
# Get the previous 10 minutes.
|
||||
minutes = self.cal.minute_window(
|
||||
minutes = self.cal.market_minute_window(
|
||||
utc_start, 10, step=-1,
|
||||
)
|
||||
self.assertEqual(len(minutes), 10)
|
||||
@@ -518,14 +518,14 @@ class TradingEnvironmentTestCase(WithLogger,
|
||||
# Today: 10:01 AM -> 4:00 PM (360 minutes)
|
||||
# Tomorrow: 9:31 AM -> 4:00 PM (390 minutes, 750 total)
|
||||
# Last Day: 9:31 AM -> 12:00 PM (150 minutes, 900 total)
|
||||
minutes = self.cal.minute_window(
|
||||
minutes = self.cal.market_minute_window(
|
||||
start, 900,
|
||||
)
|
||||
today = self.cal.minutes_for_date(utc_start)[30:]
|
||||
tomorrow = self.cal.minutes_for_date(
|
||||
today = self.cal.trading_minutes_for_day(utc_start)[30:]
|
||||
tomorrow = self.cal.trading_minutes_for_day(
|
||||
start + timedelta(days=1)
|
||||
)
|
||||
last_day = self.cal.minutes_for_date(
|
||||
last_day = self.cal.trading_minutes_for_day(
|
||||
start + timedelta(days=2))[:150]
|
||||
|
||||
self.assertEqual(len(minutes), 900)
|
||||
@@ -540,17 +540,17 @@ class TradingEnvironmentTestCase(WithLogger,
|
||||
# Today: 10:01 AM -> 9:31 AM (31 minutes)
|
||||
# Friday: 4:00 PM -> 9:31 AM (390 minutes, 421 total)
|
||||
# Thursday: 4:00 PM -> 9:41 AM (380 minutes, 801 total)
|
||||
minutes = self.cal.minute_window(
|
||||
minutes = self.cal.market_minute_window(
|
||||
start, 801, step=-1,
|
||||
)
|
||||
|
||||
today = self.cal.minutes_for_date(utc_start)[30::-1]
|
||||
today = self.cal.trading_minutes_for_day(utc_start)[30::-1]
|
||||
# minus an extra two days from each of these to account for the two
|
||||
# weekend days we skipped
|
||||
friday = self.cal.minutes_for_date(
|
||||
friday = self.cal.trading_minutes_for_day(
|
||||
start + timedelta(days=-3),
|
||||
)[::-1]
|
||||
thursday = self.cal.minutes_for_date(
|
||||
thursday = self.cal.trading_minutes_for_day(
|
||||
start + timedelta(days=-4),
|
||||
)[:9:-1]
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
import bisect
|
||||
from pytz import timezone
|
||||
|
||||
from zipline.errors import NoFurtherDataError
|
||||
|
||||
@@ -110,11 +111,13 @@ def days_in_range(start, end, all_days):
|
||||
Get all execution days between start and end,
|
||||
inclusive.
|
||||
"""
|
||||
|
||||
start_date = normalize_date(start)
|
||||
end_date = normalize_date(end)
|
||||
return all_days[all_days.slice_indexer(start_date, end_date)]
|
||||
|
||||
mask = ((all_days >= start_date) & (all_days <= end_date))
|
||||
return all_days[mask]
|
||||
#mask = ((all_days >= start_date) & (all_days <= end_date))
|
||||
#return all_days[mask]
|
||||
|
||||
|
||||
def minutes_for_days_in_range(start, end, days_in_range_hook,
|
||||
@@ -211,3 +214,60 @@ def previous_scheduled_minute(start, is_scheduled_day_hook,
|
||||
# If start is not a trading day, or is before the market open
|
||||
# then return the close of the *previous* trading day.
|
||||
return previous_open_and_close_hook(start)[1]
|
||||
|
||||
|
||||
def minute_window(start, count, schedule, is_scheduled_minute_hook,
|
||||
session_date_hook, minutes_for_date_hook, step=1):
|
||||
"""
|
||||
Returns a DatetimeIndex containing `count` market minutes, starting
|
||||
with `start` and continuing `step` minutes at a time.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
start : Timestamp
|
||||
The start of the window.
|
||||
count : int
|
||||
The number of minutes needed.
|
||||
step : int
|
||||
The step size by which to increment.
|
||||
|
||||
Returns
|
||||
-------
|
||||
DatetimeIndex
|
||||
A window with @count minutes, start with @start.
|
||||
"""
|
||||
if not is_scheduled_minute_hook(start):
|
||||
raise ValueError("minute_window starting at non-market time "
|
||||
"{minute}".format(minute=start))
|
||||
|
||||
start_utc = start.astimezone(timezone('UTC'))
|
||||
|
||||
session = session_date_hook(start)
|
||||
session_idx = schedule.index.get_loc(session)
|
||||
|
||||
mins_in_session = minutes_for_date_hook(session)
|
||||
start_idx = mins_in_session.searchsorted(start_utc)
|
||||
|
||||
# Use a list instead of a pandas DatetimeIndex, as using .append()
|
||||
# with DatetimeIndex can become expensive if used several times, since
|
||||
# it makes a full copy of the data. list.extend() will not typically
|
||||
# copy the data unless there is not enough memory to extend into, which
|
||||
# is usually not problem.
|
||||
all_minutes = list(mins_in_session[start_idx::np.sign(step)])
|
||||
|
||||
while True:
|
||||
|
||||
step_minutes = all_minutes[0::np.absolute(step)]
|
||||
|
||||
if len(step_minutes) >= count:
|
||||
step_minutes = step_minutes[:count]
|
||||
return pd.DatetimeIndex(step_minutes, copy=False)
|
||||
|
||||
# Iterate session forward or backward
|
||||
session_idx += np.sign(step)
|
||||
# Get the minutes in the next exchange session
|
||||
session = schedule.index[session_idx]
|
||||
session_minutes = minutes_for_date_hook(session)[::np.sign(step)]
|
||||
|
||||
# A these new session_minutes to the `all_minutes` candidate list
|
||||
all_minutes.extend(list(session_minutes))
|
||||
|
||||
-78
@@ -374,81 +374,3 @@ class CMEExchangeCalendar(ExchangeCalendar):
|
||||
"""
|
||||
dt_utc = dt.tz_convert('UTC').tz_convert(None)
|
||||
return dt_utc.replace(hour=0, minute=0, second=0)
|
||||
|
||||
def minutes_for_date(self, date):
|
||||
"""
|
||||
Given a UTC-canonicalized date, returns a DatetimeIndex of all trading
|
||||
minutes in the exchange session for that date.
|
||||
|
||||
SD: Sounds like @date can be an arbitrary datetime, and that we should
|
||||
first map to an exchange session by calling self.session_date. Need to
|
||||
check what the consumers expect.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
date : Timestamp
|
||||
The UTC-canonicalized date whose minutes are needed.
|
||||
|
||||
Returns
|
||||
-------
|
||||
DatetimeIndex
|
||||
A DatetimeIndex populated with all of the minutes in the
|
||||
given date.
|
||||
"""
|
||||
open, close = self.open_and_close(date)
|
||||
return date_range(open, close, freq='min', tz='UTC')
|
||||
|
||||
def minute_window(self, start, count, step=1):
|
||||
"""
|
||||
Return a DatetimeIndex containing `count` market minutes, starting with
|
||||
`start` and continuing `step` minutes at a time.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
start : Timestamp
|
||||
The start of the window.
|
||||
count : int
|
||||
The number of minutes needed.
|
||||
step : int
|
||||
The step size by which to increment.
|
||||
|
||||
Returns
|
||||
-------
|
||||
DatetimeIndex
|
||||
A window with @count minutes, start with @start.
|
||||
"""
|
||||
if not self.is_open(start):
|
||||
raise ValueError("minute_window starting at non-market time "
|
||||
"{minute}".format(minute=start))
|
||||
|
||||
start_utc = start.tz_convert('UTC')
|
||||
|
||||
session = self.session_date(start)
|
||||
session_idx = self.schedule.index.get_loc(session)
|
||||
|
||||
mins_in_session = self.minutes_for_date(session)
|
||||
start_idx = mins_in_session.searchsorted(start_utc)
|
||||
|
||||
# Use a list instead of a pandas DatetimeIndex, as using .append()
|
||||
# with DatetimeIndex can become expensive if used several times, since
|
||||
# it makes a full copy of the data. list.extend() will not typically
|
||||
# copy the data unless there is not enough memory to extend into, which
|
||||
# is usually not problem.
|
||||
all_minutes = list(mins_in_session[start_idx::np.sign(step)])
|
||||
|
||||
while True:
|
||||
|
||||
step_minutes = all_minutes[0::step]
|
||||
|
||||
if len(step_minutes) >= count:
|
||||
step_minutes = step_minutes[:count]
|
||||
return pd.DatetimeIndex(step_minutes, copy=False)
|
||||
|
||||
# Iterate session forward or backward
|
||||
session_idx += np.sign(step)
|
||||
# Get the minutes in the next exchange session
|
||||
session = self.schedule.index[session_idx]
|
||||
session_minutes = self.minutes_for_date(session)
|
||||
|
||||
# A these new session_minutes to the `all_minutes` candidate list
|
||||
all_minutes.extend(list(session_minutes))
|
||||
@@ -48,6 +48,7 @@ from .calendar_helpers import (
|
||||
all_scheduled_minutes,
|
||||
next_scheduled_minute,
|
||||
previous_scheduled_minute,
|
||||
minute_window,
|
||||
)
|
||||
|
||||
start_default = pd.Timestamp('1990-01-01', tz='UTC')
|
||||
@@ -242,6 +243,13 @@ class ExchangeCalendar(with_metaclass(ABCMeta)):
|
||||
open_and_close_hook=self.open_and_close,
|
||||
previous_open_and_close_hook=self.previous_open_and_close,
|
||||
)
|
||||
self.market_minute_window = partial(
|
||||
minute_window,
|
||||
schedule=self.schedule,
|
||||
is_scheduled_minute_hook=self.is_open_on_minute,
|
||||
session_date_hook=self.session_date,
|
||||
minutes_for_date_hook=self.trading_minutes_for_day,
|
||||
)
|
||||
|
||||
def _special_dates(self, calendars, ad_hoc_dates, start_date, end_date):
|
||||
"""
|
||||
@@ -413,56 +421,10 @@ class ExchangeCalendar(with_metaclass(ABCMeta)):
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@abstractmethod
|
||||
def minutes_for_date(self, date):
|
||||
"""
|
||||
Given a UTC-canonicalized date, returns a DatetimeIndex of all trading
|
||||
minutes in the exchange session for that date.
|
||||
|
||||
SD: Sounds like @date can be an arbitrary datetime, and that we should
|
||||
first map to an exchange session by calling self.session_date. Need to
|
||||
check what the consumers expect.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
date : Timestamp
|
||||
The UTC-canonicalized date whose minutes are needed.
|
||||
|
||||
Returns
|
||||
-------
|
||||
DatetimeIndex
|
||||
A DatetimeIndex populated with all of the minutes in the
|
||||
given date.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@abstractmethod
|
||||
def minute_window(self, start, count, step=1):
|
||||
"""
|
||||
Return a DatetimeIndex containing `count` market minutes, starting with
|
||||
`start` and continuing `step` minutes at a time.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
start : Timestamp
|
||||
The start of the window.
|
||||
count : int
|
||||
The number of minutes needed.
|
||||
step : int
|
||||
The step size by which to increment.
|
||||
|
||||
Returns
|
||||
-------
|
||||
DatetimeIndex
|
||||
A window with @count minutes, starting with @start a returning
|
||||
every @step minute.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
_static_calendars = {}
|
||||
|
||||
_lazy_calendar_names = ['NYSE']
|
||||
_lazy_calendar_names = ['NYSE', 'CME']
|
||||
|
||||
|
||||
def get_calendar(name):
|
||||
@@ -481,12 +443,18 @@ def get_calendar(name):
|
||||
# It's not a lazy calendar, so raise an exception
|
||||
raise InvalidCalendarName(calendar_name=name)
|
||||
|
||||
if name is 'NYSE':
|
||||
if name == 'NYSE':
|
||||
from zipline.utils.calendars.nyse_exchange_calendar \
|
||||
import NYSEExchangeCalendar
|
||||
nyse_cal = NYSEExchangeCalendar()
|
||||
register_calendar(nyse_cal)
|
||||
|
||||
if name == 'CME':
|
||||
from zipline.utils.calendars.cme_exchange_calendar \
|
||||
import CMEExchangeCalendar
|
||||
cme_cal = CMEExchangeCalendar()
|
||||
register_calendar(cme_cal)
|
||||
|
||||
return _static_calendars[name]
|
||||
|
||||
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
from datetime import time
|
||||
from itertools import chain
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from dateutil.relativedelta import (
|
||||
MO,
|
||||
TH,
|
||||
@@ -440,83 +438,3 @@ class NYSEExchangeCalendar(ExchangeCalendar):
|
||||
while not self.is_open_on_day(dt):
|
||||
dt += Timedelta(days=1)
|
||||
return normalize_date(dt)
|
||||
|
||||
def minutes_for_date(self, dt):
|
||||
"""
|
||||
Given a datetime, returns a DatetimeIndex of all trading
|
||||
minutes in the exchange session for that datetime.
|
||||
|
||||
SD: Should @dt be an arbitrary datetime, so that we should
|
||||
first map to an exchange session by calling self.session_date. Need to
|
||||
check what the consumers expect. Here, I assume we need to map it to a
|
||||
session.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
dt : Timestamp
|
||||
The datetime whose exchange session minutes are needed.
|
||||
|
||||
Returns
|
||||
-------
|
||||
DatetimeIndex
|
||||
A DatetimeIndex populated with all of the minutes in the
|
||||
given dt.
|
||||
"""
|
||||
session = self.session_date(dt)
|
||||
open, close = self.open_and_close(session)
|
||||
return date_range(open, close, freq='min', tz='UTC')
|
||||
|
||||
def minute_window(self, start, count, step=1):
|
||||
"""
|
||||
Returns a DatetimeIndex containing `count` market minutes, starting
|
||||
with `start` and continuing `step` minutes at a time.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
start : Timestamp
|
||||
The start of the window.
|
||||
count : int
|
||||
The number of minutes needed.
|
||||
step : int
|
||||
The step size by which to increment.
|
||||
|
||||
Returns
|
||||
-------
|
||||
DatetimeIndex
|
||||
A window with @count minutes, start with @start.
|
||||
"""
|
||||
if not self.is_open_on_minute(start):
|
||||
raise ValueError("minute_window starting at non-market time "
|
||||
"{minute}".format(minute=start))
|
||||
|
||||
start_utc = start.astimezone(timezone('UTC'))
|
||||
|
||||
session = self.session_date(start)
|
||||
session_idx = self.schedule.index.get_loc(session)
|
||||
|
||||
mins_in_session = self.minutes_for_date(session)
|
||||
start_idx = mins_in_session.searchsorted(start_utc)
|
||||
|
||||
# Use a list instead of a pandas DatetimeIndex, as using .append()
|
||||
# with DatetimeIndex can become expensive if used several times, since
|
||||
# it makes a full copy of the data. list.extend() will not typically
|
||||
# copy the data unless there is not enough memory to extend into, which
|
||||
# is usually not problem.
|
||||
all_minutes = list(mins_in_session[start_idx::np.sign(step)])
|
||||
|
||||
while True:
|
||||
|
||||
step_minutes = all_minutes[0::np.absolute(step)]
|
||||
|
||||
if len(step_minutes) >= count:
|
||||
step_minutes = step_minutes[:count]
|
||||
return pd.DatetimeIndex(step_minutes, copy=False)
|
||||
|
||||
# Iterate session forward or backward
|
||||
session_idx += np.sign(step)
|
||||
# Get the minutes in the next exchange session
|
||||
session = self.schedule.index[session_idx]
|
||||
session_minutes = self.minutes_for_date(session)[::np.sign(step)]
|
||||
|
||||
# A these new session_minutes to the `all_minutes` candidate list
|
||||
all_minutes.extend(list(session_minutes))
|
||||
|
||||
@@ -19,6 +19,7 @@ from abc import (
|
||||
abstractproperty,
|
||||
)
|
||||
from functools import partial
|
||||
from six import with_metaclass
|
||||
|
||||
from zipline.utils.memoize import remember_last
|
||||
|
||||
@@ -36,14 +37,14 @@ from .calendar_helpers import (
|
||||
all_scheduled_minutes,
|
||||
next_scheduled_minute,
|
||||
previous_scheduled_minute,
|
||||
minute_window,
|
||||
)
|
||||
|
||||
|
||||
class TradingSchedule(object):
|
||||
class TradingSchedule(with_metaclass(ABCMeta)):
|
||||
"""
|
||||
A TradingSchedule defines the execution timing of a TradingAlgorithm.
|
||||
"""
|
||||
__metaclass__ = ABCMeta
|
||||
|
||||
def __init__(self):
|
||||
# Assign the partial calendar helpers
|
||||
@@ -102,12 +103,19 @@ class TradingSchedule(object):
|
||||
open_and_close_hook=self.start_and_end,
|
||||
previous_open_and_close_hook=self.previous_start_and_end,
|
||||
)
|
||||
self.execution_minute_window = partial(
|
||||
minute_window,
|
||||
schedule=self.schedule,
|
||||
is_scheduled_minute_hook=self.is_executing_on_minute,
|
||||
session_date_hook=self.session_date,
|
||||
minutes_for_date_hook=self.execution_minutes_for_day,
|
||||
)
|
||||
|
||||
@abstractproperty
|
||||
def day(self):
|
||||
"""
|
||||
A CustomBusinessDay defining those days on which the algorithm is
|
||||
usually trading.
|
||||
trading.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@@ -257,26 +265,23 @@ class TradingSchedule(object):
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
@abstractmethod
|
||||
def minute_window(self, start, count, step=1):
|
||||
def session_date(self, dt):
|
||||
"""
|
||||
Return a DatetimeIndex containing `count` market minutes, starting with
|
||||
`start` and continuing `step` minutes at a time.
|
||||
Given a time, returns the UTC-canonicalized date of the trading
|
||||
session in which the time belongs. If the time is not in a trading
|
||||
session (while algorithm isn't trading), returns the date of the next
|
||||
exchange session after the time.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
start : Timestamp
|
||||
The start of the window.
|
||||
count : int
|
||||
The number of minutes needed.
|
||||
step : int
|
||||
The step size by which to increment.
|
||||
dt : Timestamp
|
||||
|
||||
Returns
|
||||
-------
|
||||
DatetimeIndex
|
||||
A window with @count minutes, starting with @start a returning
|
||||
every @step minute.
|
||||
Timestamp
|
||||
The date of the exchange session in which dt belongs.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@@ -358,10 +363,11 @@ class ExchangeTradingSchedule(TradingSchedule):
|
||||
"""
|
||||
return self._exchange_calendar.is_open_on_day(dt)
|
||||
|
||||
def minute_window(self, start, count, step=1):
|
||||
return self._exchange_calendar.minute_window(start=start,
|
||||
count=count,
|
||||
step=step)
|
||||
def session_date(self, dt):
|
||||
"""
|
||||
See TradingSchedule definition.
|
||||
"""
|
||||
return self._exchange_calendar.session_date(dt)
|
||||
|
||||
@property
|
||||
def early_ends(self):
|
||||
|
||||
@@ -38,21 +38,9 @@ def create_test_zipline(**config):
|
||||
"argument 'sid_list' or 'sid'")
|
||||
|
||||
concurrent_trades = config.get('concurrent_trades', False)
|
||||
|
||||
if 'order_count' in config:
|
||||
order_count = config['order_count']
|
||||
else:
|
||||
order_count = 100
|
||||
|
||||
if 'order_amount' in config:
|
||||
order_amount = config['order_amount']
|
||||
else:
|
||||
order_amount = 100
|
||||
|
||||
if 'trading_schedule' in config:
|
||||
trading_schedule = config['trading_schedule']
|
||||
else:
|
||||
trading_schedule = default_nyse_schedule
|
||||
order_count = config.get('order_count', 100)
|
||||
order_amount = config.get('order_amount', 100)
|
||||
trading_schedule = config.get('trading_schedule', default_nyse_schedule)
|
||||
|
||||
# -------------------
|
||||
# Create the Algo
|
||||
|
||||
Reference in New Issue
Block a user