2

I have the following code to detect stock trades by counting orders:

import json

orders = '[{"side": "SELL", "executedQty": "0.046"}, {"side": "SELL", "executedQty": "0.041"}, {"side": "SELL", "executedQty": "0.056"}, {"side": "SELL", "executedQty": "0.140"}, {"side": "BUY", "executedQty": "0.283"}]'
orders = json.loads(orders)

cur_qty = 0
raw_trades = []
trade = []
trade_side = None
for order in orders:
    if trade_side is None:
        trade_side = order['side']

    numbers_after_decimal = len(order['executedQty'].split('.')[-1])
    order_qty = float(order['executedQty'])
    order_qty = round(order_qty, numbers_after_decimal)

    if trade_side == order['side']:
        cur_qty += order_qty
    else:
        cur_qty -= order_qty

    trade.append(order)
    if cur_qty == 0:
        raw_trades.append(trade)
        trade_side = None
        trade = []

in this example, orders has only 1 trade (we sold 0.046 + 0.041 + 0.056 + 0.140 = 0.283 (entry) and we bought 0.283 (exit))

The problem is, that when running this code, for some reason, when in the for loop and order is {"side": "SELL", "executedQty": "0.140"} and cur_qty = 0.143, when I add them the result is 0.28300000000000003.

This messes up the whole count because I search for cur_qty == 0 to know that the trade is over (I sold everything I bought or vise versa).

Tried to round the float to its string number of decimal places, but it keeps happening.

Any Idea how to solve this, rounding the number didn't help

orr
  • 129
  • 10
  • 1
    See: https://stackoverflow.com/questions/588004/is-floating-point-math-broken Searching the website for an answer before asking as a separate question is a healthy practice. – user79161 May 24 '20 at 11:19
  • @user79161, I did searched the web, didn't find this answer tho. Yet, the answer is talking about why it happens, and rather than saying to round the numbers (which I already tried that), I saw no other solution, which is what this question is about. How do I overcome this and get `0.283` instead of `0.28300000000000003` – orr May 24 '20 at 11:33
  • You didn't get the answer. *The error cannot be overcome in a general way.* By general way, I mean a method that can be applied to all fractions represented in that form. At least that is what I concluded upon reading the answer I referred to and the links presented in it. I believe you should read it once again. – user79161 May 24 '20 at 11:39

1 Answers1

-1

So After reading @user79161 answer I found the following solution- I rounded the number also after the addition base on the higher decimal points in the string representation of both cur_qty and order_qty.

This is how the code looks like now:

import json

orders = '[{"side": "SELL", "executedQty": "0.046"}, {"side": "SELL", "executedQty": "0.041"}, {"side": "SELL", "executedQty": "0.056"}, {"side": "SELL", "executedQty": "0.140"}, {"side": "BUY", "executedQty": "0.283"}]'
orders = json.loads(orders)

cur_qty = 0
raw_trades = []
trade = []
trade_side = None
for order in orders:
    if trade_side is None:
        trade_side = order['side']

    numbers_after_decimal = len(order['executedQty'].split('.')[-1])
    order_qty = float(order['executedQty'])
    order_qty = round(order_qty, numbers_after_decimal)

    numbers_after_decimal_cur_qty = len(str(cur_qty).split('.')[-1])

    decimal_numbers = max([numbers_after_decimal_cur_qty, numbers_after_decimal])

    if trade_side == order['side']:
        cur_qty += order_qty
    else:
        cur_qty -= order_qty

    cur_qty = round(cur_qty, decimal_numbers)

    trade.append(order)
    if cur_qty == 0:
        raw_trades.append(trade)
        trade_side = None
        trade = []
orr
  • 129
  • 10
  • 1
    That is a bad solution. Just convert all stock quantities to an integer multiple of some unit, such as one one-thousandth of a share, and use integer arithmetic. – Eric Postpischil May 24 '20 at 12:32
  • @EricPostpischil Why is it a bad solution? how is what you're suggesting better? – orr May 24 '20 at 12:46
  • 1
    First, you are working in discrete units, so you ought to use a solution designed for that. That is integer arithmetic. Integer arithmetic is designed to do exact integer arithmetic. Floating-point arithmetic is designed to approximate real-number arithmetic. As long as you are only adding and subtracting quantities, you should use the solution designed for that. (If you start getting into further mathematics, such as computing interest rates or returns on investment, then floating-point may be suitable—but then you need to learn more about floating-point arithmetic to use it properly.) – Eric Postpischil May 24 '20 at 13:36
  • Second, rounding is computationally expensive. Even doing it the wrong way (multiplying by a power of 10, rounding to an integer, dividing by a power of ten) adds unnecessary operations to simple addition and subtraction. Doing it the right way (producing the nearest representable result without rounding error from the multiplicaiton) is more expensive. (I do not know which Python’s `round` function uses.) Since it is unnecessary (the integer solution is fine), it is just a waste of computing resources. – Eric Postpischil May 24 '20 at 13:38
  • Third, rounding may suffice for simple additions and subtractions of numbers limited in both magnitude and precision, but, in general, it can make floating-point arithmetic worse, not better, by magnifying small rounding errors into large ones. I did provide a rounding solution in another Stack Overlow question recently, but I did so with explicitly specified constraints on the numbers involved. This sort of manipulation of floating-point ought to be done with clear knowledge of the issues involved. – Eric Postpischil May 24 '20 at 13:40
  • @EricPostpischil Thanks for the detailed explanation. – orr May 24 '20 at 23:17