Merge pull request #385 from quantopian/exception-on-nan-order-value

BUG: StopLimit better bad value exception handling
This commit is contained in:
Delaney Granizo-Mackenzie
2014-08-26 14:43:22 -04:00
3 changed files with 67 additions and 18 deletions
+20 -4
View File
@@ -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):
+8
View File
@@ -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()
+39 -14
View File
@@ -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)
)