From 15f33d3e9dfdabcbd5b61226fcecef26bd0dc9b9 Mon Sep 17 00:00:00 2001 From: Delaney Granizo-Mackenzie Date: Fri, 22 Aug 2014 00:55:09 -0400 Subject: [PATCH 1/2] BUG: Exception will be raised when nan value sent to order() Previously order was not checking for nan values sent as limit or stop prices. It will now raise a runtime exception in the event that an attempt to order with a nan price is made. --- tests/test_execution_styles.py | 12 ++++++--- zipline/errors.py | 8 ++++++ zipline/finance/execution.py | 46 ++++++++++++++++++++++++++++------ 3 files changed, 55 insertions(+), 11 deletions(-) diff --git a/tests/test_execution_styles.py b/tests/test_execution_styles.py index dc1b44c5..3ae60de5 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,7 @@ class ExecutionStyleTestCase(TestCase): for delta in range(1, 10) ] - INVALID_PRICES = [(-1,), (-1.0,), (0 - epsilon,)] + INVALID_PRICES = [(-1,), (-1.0,), (0 - epsilon,), (float('nan'),)] def setUp(self): setup_logger(self) @@ -72,14 +76,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..23ce52bb 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 math import isnan + +from zipline.errors import BadOrderParameters + class ExecutionStyle(with_metaclass(abc.ABCMeta)): """ @@ -77,8 +81,17 @@ class LimitOrder(ExecutionStyle): """ Store the given price. """ + + if isnan(limit_price): + raise BadOrderParameters( + msg="""Attempted to place an order with a limit price + of NaN.""" + ) + if limit_price < 0: - raise ValueError("Can't place a limit with a negative price.") + raise BadOrderParameters( + msg="Can't place a limit with a negative price." + ) self.limit_price = limit_price self._exchange = exchange @@ -98,9 +111,16 @@ class StopOrder(ExecutionStyle): """ Store the given price. """ + + if isnan(stop_price): + raise BadOrderParameters( + msg="""Attempted to place an order with a stop price + of NaN.""" + ) + if stop_price < 0: - raise ValueError( - "Can't place a stop order with a negative price." + raise BadOrderParameters( + msg="Can't place a stop order with a negative price." ) self.stop_price = stop_price self._exchange = exchange @@ -121,13 +141,25 @@ class StopLimitOrder(ExecutionStyle): """ Store the given prices """ + + if isnan(limit_price): + raise BadOrderParameters( + msg="""Attempted to place an order with a limit price + of NaN.""" + ) + if isnan(stop_price): + raise BadOrderParameters( + msg="""Attempted to place an order with a stop price + of NaN.""" + ) + if limit_price < 0: - raise ValueError( - "Can't place a limit with a negative price." + raise BadOrderParameters( + msg="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." + raise BadOrderParameters( + msg="Can't place a stop order with a negative price." ) self.limit_price = limit_price From 5488da0dc29a92230c849b7143efa2d02d8aeeaa Mon Sep 17 00:00:00 2001 From: Delaney Granizo-Mackenzie Date: Mon, 25 Aug 2014 15:47:31 -0400 Subject: [PATCH 2/2] ENH: Well formed exception for any value passed to OrderStyle This commit adds support for arbitrary objects in addition to NaN and infinity values. The object well be returned in string format as part of the error message. --- tests/test_execution_styles.py | 14 ++++++- zipline/finance/execution.py | 67 +++++++++++++++------------------- 2 files changed, 43 insertions(+), 38 deletions(-) diff --git a/tests/test_execution_styles.py b/tests/test_execution_styles.py index 3ae60de5..5f567886 100644 --- a/tests/test_execution_styles.py +++ b/tests/test_execution_styles.py @@ -62,7 +62,19 @@ class ExecutionStyleTestCase(TestCase): for delta in range(1, 10) ] - INVALID_PRICES = [(-1,), (-1.0,), (0 - epsilon,), (float('nan'),)] + 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) diff --git a/zipline/finance/execution.py b/zipline/finance/execution.py index 23ce52bb..4423e60b 100644 --- a/zipline/finance/execution.py +++ b/zipline/finance/execution.py @@ -21,7 +21,7 @@ from six import with_metaclass import zipline.utils.math_utils as zp_math -from math import isnan +from numpy import isfinite from zipline.errors import BadOrderParameters @@ -82,16 +82,8 @@ class LimitOrder(ExecutionStyle): Store the given price. """ - if isnan(limit_price): - raise BadOrderParameters( - msg="""Attempted to place an order with a limit price - of NaN.""" - ) + check_stoplimit_prices(limit_price, 'limit') - if limit_price < 0: - raise BadOrderParameters( - msg="Can't place a limit with a negative price." - ) self.limit_price = limit_price self._exchange = exchange @@ -112,16 +104,8 @@ class StopOrder(ExecutionStyle): Store the given price. """ - if isnan(stop_price): - raise BadOrderParameters( - msg="""Attempted to place an order with a stop price - of NaN.""" - ) + check_stoplimit_prices(stop_price, 'stop') - if stop_price < 0: - raise BadOrderParameters( - msg="Can't place a stop order with a negative price." - ) self.stop_price = stop_price self._exchange = exchange @@ -142,25 +126,9 @@ class StopLimitOrder(ExecutionStyle): Store the given prices """ - if isnan(limit_price): - raise BadOrderParameters( - msg="""Attempted to place an order with a limit price - of NaN.""" - ) - if isnan(stop_price): - raise BadOrderParameters( - msg="""Attempted to place an order with a stop price - of NaN.""" - ) + check_stoplimit_prices(limit_price, 'limit') - if limit_price < 0: - raise BadOrderParameters( - msg="Can't place a limit with a negative price." - ) - if stop_price < 0: - raise BadOrderParameters( - msg="Can't place a stop order with a negative price." - ) + check_stoplimit_prices(stop_price, 'stop') self.limit_price = limit_price self.stop_price = stop_price @@ -201,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) + )