From b4836b976e22ef54fc4a924eac4f48f11be9300e Mon Sep 17 00:00:00 2001 From: Richard Frank Date: Fri, 20 Sep 2013 15:55:24 -0400 Subject: [PATCH] ENH: Restrict limit prices to a penny precision to account for minimum price variation. 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. --- tests/test_blotter.py | 29 +++++++++++++++++++++++++++++ zipline/finance/blotter.py | 11 +++++++++++ 2 files changed, 40 insertions(+) create mode 100644 tests/test_blotter.py diff --git a/tests/test_blotter.py b/tests/test_blotter.py new file mode 100644 index 00000000..c722f5cc --- /dev/null +++ b/tests/test_blotter.py @@ -0,0 +1,29 @@ +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) + + @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) diff --git a/zipline/finance/blotter.py b/zipline/finance/blotter.py index 9bd1f588..e592c60b 100644 --- a/zipline/finance/blotter.py +++ b/zipline/finance/blotter.py @@ -42,6 +42,14 @@ 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 + return round(x - (diff if is_buy else -diff), 2) + + class Blotter(object): def __init__(self): @@ -107,6 +115,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,