diff --git a/tests/test_blotter.py b/tests/test_blotter.py new file mode 100644 index 00000000..b47a282a --- /dev/null +++ b/tests/test_blotter.py @@ -0,0 +1,35 @@ +import math + +from nose_parameterized import parameterized +from unittest import TestCase + +from zipline.finance.blotter import round_for_minimum_price_variation + + +class BlotterTestCase(TestCase): + + @parameterized.expand([(0.00, 0.00), + (0.01, 0.01), + (0.0005, 0.00), + (1.006, 1.00), + (1.0095, 1.01), + (1.00949, 1.00), + (1.0005, 1.00)]) + def test_round_for_minimum_price_variation_buy(self, price, expected): + result = round_for_minimum_price_variation(price, is_buy=True) + self.assertEqual(result, expected) + self.assertEqual(math.copysign(1.0, result), + math.copysign(1.0, expected)) + + @parameterized.expand([(0.00, 0.00), + (0.01, 0.01), + (0.0005, 0.00), + (1.006, 1.01), + (1.0005, 1.00), + (1.00051, 1.01), + (1.0095, 1.01)]) + def test_round_for_minimum_price_variation_sell(self, price, expected): + result = round_for_minimum_price_variation(price, is_buy=False) + self.assertEqual(result, expected) + self.assertEqual(math.copysign(1.0, result), + math.copysign(1.0, expected)) diff --git a/zipline/finance/blotter.py b/zipline/finance/blotter.py index 9bd1f588..3af95fd5 100644 --- a/zipline/finance/blotter.py +++ b/zipline/finance/blotter.py @@ -42,6 +42,17 @@ ORDER_STATUS = Enum( ) +# On an order to buy, between .05 below to .95 above a penny, use that penny. +# On an order to sell, between .05 above to .95 below a penny, use that penny. +# buy: [.0095, .0195) -> round to .01, sell: (.0005, .0105] -> round to .01 +def round_for_minimum_price_variation(x, is_buy, diff=(0.0095 - .005)): + # relies on rounding half away from zero, unlike numpy's bankers' rounding + rounded = round(x - (diff if is_buy else -diff), 2) + if zp_math.tolerant_equals(rounded, 0.0): + return 0.0 + return rounded + + class Blotter(object): def __init__(self): @@ -107,6 +118,9 @@ class Blotter(object): raise OverflowError("Can't order more than %d shares" % self.max_shares) + if limit_price: + limit_price = round_for_minimum_price_variation(limit_price, + amount > 0) order = Order( dt=self.current_dt, sid=sid,