New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BUG: Fix rounding error in StopOrder/LimitOrder #2211

Merged
merged 19 commits into from Jul 12, 2018

Conversation

Projects
None yet
5 participants
@jnazaren
Contributor

jnazaren commented Jun 13, 2018

Solves rounding bug which caused rounding of all asset stop/limit prices to the nearest penny regardless of their tick size.

@jnazaren jnazaren added the Bug label Jun 13, 2018

@jnazaren jnazaren requested a review from prsutherland Jun 13, 2018

@coveralls

This comment has been minimized.

coveralls commented Jun 13, 2018

Coverage Status

Coverage increased (+0.06%) to 86.391% when pulling 9707d26 on rounding_bugfix into c20a588 on master.

@coveralls

This comment has been minimized.

coveralls commented Jun 13, 2018

Coverage Status

Coverage decreased (-0.6%) to 85.443% when pulling d433521 on rounding_bugfix into f61d3df on master.

@@ -158,19 +189,31 @@ def asymmetric_round_price_to_penny(price, prefer_round_down,
If prefer_round_down: [<X-1>.0095, X.0195) -> round to X.01.
If not prefer_round_down: (<X-1>.0005, X.0105] -> round to X.01.
"""
precision = get_precision(tick_size)
diff *= (10 ** -precision)
print(price, precision, diff)

This comment has been minimized.

@prsutherland

prsutherland Jun 14, 2018

Member

While useful for debugging, we probably don't want to merge with the print.

This comment has been minimized.

@jnazaren

jnazaren Jun 14, 2018

Contributor

Oh yep! Forgot to take that out, sorry

def asymmetric_round_price_to_penny(price, prefer_round_down,
diff=(0.0095 - .005)):
def asymmetric_round_price(price, prefer_round_down,

This comment has been minimized.

@prsutherland

prsutherland Jun 14, 2018

Member

Are there tests for this method? How does it handle tick sizes like .0005?

This comment has been minimized.

@jnazaren

jnazaren Jun 14, 2018

Contributor

@prsutherland I actually noticed that all of the tests that used this functionality were fine with the normal precision of this function (at least 0.01), which is why I left 0.01 as a default parameter, but I can now add some tests for smaller precision

return 0.0
rounded = round(price - (diff if prefer_round_down else -diff), precision)
# if zp_math.tolerant_equals(rounded, 0.0):
# return 0.0

This comment has been minimized.

@prsutherland

prsutherland Jun 14, 2018

Member

Is this dead code?

This comment has been minimized.

@jnazaren

jnazaren Jun 14, 2018

Contributor

@prsutherland It could be, but I'm not sure if we may need it - it seems unnecessary now that precision could be as small as desired

This comment has been minimized.

@prsutherland

prsutherland Jun 14, 2018

Member

I believe it is needed to avoid near zero floating point errors. For example, I believe without it, the code will return

>>> asymmetric_round_price(.004, True, tick_size=0.005)
-0.0

This comment has been minimized.

@jnazaren

jnazaren Jun 14, 2018

Contributor

Ok, I'll leave it in that case

def get_limit_price(self, is_buy):
return asymmetric_round_price_to_penny(self.limit_price, is_buy)
if hasattr(self.asset, 'tick_size'):

This comment has been minimized.

@prsutherland

prsutherland Jun 14, 2018

Member

It looks like roughly the same block is repeated ~4 times. Can the pattern be abstracted?

This comment has been minimized.

@richafrank

richafrank Jun 14, 2018

Member

Would make sense to put tick_size on all asset types, with a default value? Any performance or domain modeling concerns?

@@ -65,7 +67,9 @@ cdef class Asset:
object end_date=None,
object first_traded=None,
object auto_close_date=None,
object exchange_full=None):
object exchange_full=None,
object tick_size="0.001",

This comment has been minimized.

@prsutherland

prsutherland Jun 26, 2018

Member

Why is tick_size a string?

This comment has been minimized.

@prsutherland

prsutherland Jun 26, 2018

Member

Oh, I see that it was previously defaulting to an empty string in Future below. Still not sure I understand why though.

This comment has been minimized.

@jnazaren

jnazaren Jun 26, 2018

Contributor

Yeah, I left it as a string primarily because I noticed its default value was an empty string. Similarly, it was defined as an object rather than a float in the cython definitions.

def _get_stop_price(asset, price, is_buy):
if hasattr(asset, 'tick_size'):

This comment has been minimized.

@prsutherland

prsutherland Jun 26, 2018

Member

I think this if statement can be removed now that all assets have a tick_size.

@@ -160,8 +160,8 @@ class Order(Event):
)
def asset_multiplier(asset):
return asset.multiplier if isinstance(asset, Future) else 1
def asset_price_multiplier(asset):

This comment has been minimized.

@prsutherland

prsutherland Jun 26, 2018

Member

We can probably replace invocations of this method with asset.price_multiplier.

return asymmetric_round_price(
price,
not is_buy,
tick_size=float(asset.tick_size)

This comment has been minimized.

@prsutherland

prsutherland Jun 26, 2018

Member

For this PR, we just cast tick_size to a float. Are there other uses that we want it to be a string?

style = LimitOrder(price, asset=self.FUTURE)
assert_equal(expected_limit_buy_or_stop_sell,
style.get_limit_price(True))

This comment has been minimized.

@prsutherland

prsutherland Jun 26, 2018

Member

Can you make the calls to style.get_limit_price and style.get_stop_price in these tests pass in constants/flags as kwargs? style.get_limit_price(is_buy=True) is easier to understand what the flag means.

return pd.DataFrame.from_dict({
1: {
'multiplier': 100,
'tick_size': '0.0001',

This comment has been minimized.

@prsutherland

prsutherland Jun 26, 2018

Member

Can we add tests for tick_sizes that aren't powers of 10? I think we always want asymmetric_round_price to return a multiple of the tick_size, but for non-powers of 10, one gets:

>>> asymmetric_round_price(.0049, prefer_round_down=True, tick_size=0.005)
0.004

jnazaren added some commits Jun 28, 2018

@prsutherland

Overall, looks good, just two small questions before merging.

@@ -271,7 +281,7 @@ cdef class Future(Asset):
object expiration_date=None,
object auto_close_date=None,
object first_traded=None,
object tick_size="",
object tick_size=0.001,

This comment has been minimized.

@prsutherland

prsutherland Jul 5, 2018

Member

Thank's for cleaning up the stringly-typed tick_size field! Can this be float tick_size now?

This comment has been minimized.

@jnazaren

jnazaren Jul 5, 2018

Contributor

Sure, I can change that

@@ -65,7 +67,9 @@ cdef class Asset:
object end_date=None,
object first_traded=None,
object auto_close_date=None,
object exchange_full=None):
object exchange_full=None,
object tick_size=0.001,

This comment has been minimized.

@prsutherland

prsutherland Jul 5, 2018

Member

For Equities, do we want a different default size? The get_stop_price and get_limit_price default to 0.01 if there is no asset.

This comment has been minimized.

@jnazaren

jnazaren Jul 5, 2018

Contributor

Sure, I'll change it to 0.01

This comment has been minimized.

@prsutherland

prsutherland Jul 10, 2018

Member

I see you updated Equity to the 0.01 tick size. Why the default value of 0.001 for futures?

This comment has been minimized.

@jnazaren

jnazaren Jul 10, 2018

Contributor

A while back I had asked @ehebert for a default tick size and that's the value he gave, but I believe the default was 0.01 previously

@prsutherland

This LGTM once we figure out what the defaults should be for futures and equities.

@jnazaren jnazaren merged commit 7e642c8 into master Jul 12, 2018

2 checks passed

continuous-integration/appveyor/pr AppVeyor build succeeded
Details
continuous-integration/travis-ci/pr The Travis CI build passed
Details

@jnazaren jnazaren deleted the rounding_bugfix branch Jul 12, 2018

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment