From c2560c607901cb536e8f0413a38ee912e2a5819b Mon Sep 17 00:00:00 2001 From: Andrew Daniels Date: Thu, 9 Feb 2017 08:56:15 -0500 Subject: [PATCH] TST: Adds TestingSlippage slippage model (#1679) Allows specifying a constant number of shares filled per tick. Also adds the WithConstantEquityMinuteBarData fixture, relocated from internal repo. --- tests/test_testing.py | 64 +++++++++++++++++++++++++++++++++++++ zipline/testing/fixtures.py | 31 ++++++++++++++++++ zipline/testing/slippage.py | 32 +++++++++++++++++++ 3 files changed, 127 insertions(+) create mode 100644 zipline/testing/slippage.py diff --git a/tests/test_testing.py b/tests/test_testing.py index bde9501a..dacba150 100644 --- a/tests/test_testing.py +++ b/tests/test_testing.py @@ -6,12 +6,22 @@ from unittest import TestCase from numpy import array, empty +from zipline._protocol import BarData +from zipline.finance.asset_restrictions import NoRestrictions +from zipline.finance.order import Order + from zipline.testing import ( check_arrays, make_alternating_boolean_array, make_cascading_boolean_array, parameter_space, ) +from zipline.testing.fixtures import ( + WithConstantEquityMinuteBarData, + WithDataPortal, + ZiplineTestCase, +) +from zipline.testing.slippage import TestingSlippage from zipline.utils.numpy_utils import bool_dtype @@ -109,3 +119,57 @@ class TestMakeBooleanArray(TestCase): make_cascading_boolean_array((3, 0)), empty((3, 0), dtype=bool_dtype), ) + + +class TestTestingSlippage(WithConstantEquityMinuteBarData, + WithDataPortal, + ZiplineTestCase): + ASSET_FINDER_EQUITY_SYMBOLS = ('A',) + ASSET_FINDER_EQUITY_SIDS = (1,) + + @classmethod + def init_class_fixtures(cls): + super(TestTestingSlippage, cls).init_class_fixtures() + cls.asset = cls.asset_finder.retrieve_asset(1) + cls.minute, _ = ( + cls.trading_calendar.open_and_close_for_session(cls.START_DATE) + ) + + def init_instance_fixtures(self): + super(TestTestingSlippage, self).init_instance_fixtures() + self.bar_data = BarData( + self.data_portal, + lambda: self.minute, + "minute", + self.trading_calendar, + NoRestrictions() + ) + + def make_order(self, amount): + return Order( + self.minute, + self.asset, + amount, + ) + + def test_constant_filled_per_tick(self): + filled_per_tick = 1 + model = TestingSlippage(filled_per_tick) + order = self.make_order(100) + + price, volume = model.process_order(self.bar_data, order) + + self.assertEqual(price, self.EQUITY_MINUTE_CONSTANT_CLOSE) + self.assertEqual(volume, filled_per_tick) + + def test_fill_all(self): + filled_per_tick = TestingSlippage.ALL + order_amount = 100 + + model = TestingSlippage(filled_per_tick) + order = self.make_order(order_amount) + + price, volume = model.process_order(self.bar_data, order) + + self.assertEqual(price, self.EQUITY_MINUTE_CONSTANT_CLOSE) + self.assertEqual(volume, order_amount) diff --git a/zipline/testing/fixtures.py b/zipline/testing/fixtures.py index 8050162a..01cecdbc 100644 --- a/zipline/testing/fixtures.py +++ b/zipline/testing/fixtures.py @@ -1099,6 +1099,37 @@ class WithBcolzFutureMinuteBarReader(WithFutureMinuteBarData, WithTmpDir): BcolzMinuteBarReader(p) +class WithConstantEquityMinuteBarData(WithEquityMinuteBarData): + + EQUITY_MINUTE_CONSTANT_LOW = 3.0 + EQUITY_MINUTE_CONSTANT_OPEN = 4.0 + EQUITY_MINUTE_CONSTANT_CLOSE = 5.0 + EQUITY_MINUTE_CONSTANT_HIGH = 6.0 + EQUITY_MINUTE_CONSTANT_VOLUME = 100.0 + + @classmethod + def make_equity_minute_bar_data(cls): + trading_calendar = cls.trading_calendars[Equity] + + sids = cls.asset_finder.equities_sids + minutes = trading_calendar.minutes_for_sessions_in_range( + cls.equity_minute_bar_days[0], + cls.equity_minute_bar_days[-1], + ) + frame = pd.DataFrame( + { + 'open': cls.EQUITY_MINUTE_CONSTANT_OPEN, + 'high': cls.EQUITY_MINUTE_CONSTANT_HIGH, + 'low': cls.EQUITY_MINUTE_CONSTANT_LOW, + 'close': cls.EQUITY_MINUTE_CONSTANT_CLOSE, + 'volume': cls.EQUITY_MINUTE_CONSTANT_VOLUME, + }, + index=minutes, + ) + + return ((sid, frame) for sid in sids) + + class WithAdjustmentReader(WithBcolzEquityDailyBarReader): """ ZiplineTestCase mixin providing cls.adjustment_reader as a class level diff --git a/zipline/testing/slippage.py b/zipline/testing/slippage.py new file mode 100644 index 00000000..5b2cbd31 --- /dev/null +++ b/zipline/testing/slippage.py @@ -0,0 +1,32 @@ +from zipline.finance.slippage import SlippageModel +from zipline.utils.sentinel import sentinel + + +class TestingSlippage(SlippageModel): + """ + Slippage model that fills a constant number of shares per tick, for + testing purposes. + + Parameters + ---------- + filled_per_tick : int or TestingSlippage.ALL + The number of shares to fill on each call to process_order. If + TestingSlippage.ALL is passed, the entire order is filled. + + See also + -------- + zipline.finance.slippage.SlippageModel + """ + ALL = sentinel('ALL') + + def __init__(self, filled_per_tick): + self.filled_per_tick = filled_per_tick + + def process_order(self, data, order): + price = data.current(order.asset, "close") + if self.filled_per_tick is self.ALL: + volume = order.amount + else: + volume = self.filled_per_tick + + return (price, volume)