diff --git a/tests/test_execution_styles.py b/tests/test_execution_styles.py index dc1b44c5..5f567886 100644 --- a/tests/test_execution_styles.py +++ b/tests/test_execution_styles.py @@ -31,6 +31,10 @@ from zipline.utils.test_utils import( teardown_logger, ) +from zipline.errors import( + BadOrderParameters +) + class ExecutionStyleTestCase(TestCase): """ @@ -58,7 +62,19 @@ class ExecutionStyleTestCase(TestCase): for delta in range(1, 10) ] - INVALID_PRICES = [(-1,), (-1.0,), (0 - epsilon,)] + class ArbitraryObject(): + def __str__(self): + return """This should yield a bad order error when + passed as a stop or limit price.""" + + INVALID_PRICES = [ + (-1,), + (-1.0,), + (0 - epsilon,), + (float('nan'),), + (float('inf'),), + (ArbitraryObject(),), + ] def setUp(self): setup_logger(self) @@ -72,14 +88,14 @@ class ExecutionStyleTestCase(TestCase): Test that execution styles throw appropriate exceptions upon receipt of an invalid price field. """ - with self.assertRaises(ValueError): + with self.assertRaises(BadOrderParameters): LimitOrder(price) - with self.assertRaises(ValueError): + with self.assertRaises(BadOrderParameters): StopOrder(price) for lmt, stp in [(price, 1), (1, price), (price, price)]: - with self.assertRaises(ValueError): + with self.assertRaises(BadOrderParameters): StopLimitOrder(lmt, stp) def test_market_order_prices(self): diff --git a/zipline/errors.py b/zipline/errors.py index 061dd246..4d92706d 100644 --- a/zipline/errors.py +++ b/zipline/errors.py @@ -137,6 +137,14 @@ class UnsupportedOrderParameters(ZiplineError): msg = "{msg}" +class BadOrderParameters(ZiplineError): + """ + Raised if any impossible parameters (nan, negative limit/stop) + are passed to an order call. + """ + msg = "{msg}" + + class OrderDuringInitialize(ZiplineError): """ Raised if order is called during initialize() diff --git a/zipline/finance/execution.py b/zipline/finance/execution.py index 368d7c25..4423e60b 100644 --- a/zipline/finance/execution.py +++ b/zipline/finance/execution.py @@ -21,6 +21,10 @@ from six import with_metaclass import zipline.utils.math_utils as zp_math +from numpy import isfinite + +from zipline.errors import BadOrderParameters + class ExecutionStyle(with_metaclass(abc.ABCMeta)): """ @@ -77,8 +81,9 @@ class LimitOrder(ExecutionStyle): """ Store the given price. """ - if limit_price < 0: - raise ValueError("Can't place a limit with a negative price.") + + check_stoplimit_prices(limit_price, 'limit') + self.limit_price = limit_price self._exchange = exchange @@ -98,10 +103,9 @@ class StopOrder(ExecutionStyle): """ Store the given price. """ - if stop_price < 0: - raise ValueError( - "Can't place a stop order with a negative price." - ) + + check_stoplimit_prices(stop_price, 'stop') + self.stop_price = stop_price self._exchange = exchange @@ -121,14 +125,10 @@ class StopLimitOrder(ExecutionStyle): """ Store the given prices """ - if limit_price < 0: - raise ValueError( - "Can't place a limit with a negative price." - ) - if stop_price < 0: - raise ValueError( - "Can't place a stop order with a negative price." - ) + + check_stoplimit_prices(limit_price, 'limit') + + check_stoplimit_prices(stop_price, 'stop') self.limit_price = limit_price self.stop_price = stop_price @@ -169,3 +169,28 @@ def asymmetric_round_price_to_penny(price, prefer_round_down, if zp_math.tolerant_equals(rounded, 0.0): return 0.0 return rounded + + +def check_stoplimit_prices(price, label): + """ + Check to make sure the stop/limit prices are reasonable and raise + a BadOrderParameters exception if not. + """ + try: + if not isfinite(price): + raise BadOrderParameters( + msg="""Attempted to place an order with a {} price + of {}.""".format(label, price) + ) + # This catches arbitrary objects + except TypeError: + raise BadOrderParameters( + msg="""Attempted to place an order with a {} price + of {}.""".format(label, type(price)) + ) + + if price < 0: + raise BadOrderParameters( + msg="""Can't place a {} order + with a negative price.""".format(label) + )