Skip to content
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

Certain price values are not respected due to floating point rounding errors and truncation #320

Open
JordanMandel opened this issue Aug 11, 2022 · 2 comments

Comments

@JordanMandel
Copy link

JordanMandel commented Aug 11, 2022

Description of Bug
Certain price values (such as 5.06) are not respected when creating a limit order

Code to Reproduce

from tda.orders.equities import equity_buy_limit
order = equity_buy_limit('AAPL', quantity=100, price=5.06)
print(order.build())

Expected Behavior
The resulting order should have a limit price of 5.06

Actual Behavior
Instead, the limit order has a price of 5.05

{'session': 'NORMAL', 'duration': 'DAY', 'orderType': 'LIMIT', 'price': '5.05', 'orderLegCollection': [{'instruction': 'BUY', 'instrument': {'assetType': 'EQUITY', 'symbol': 'AAPL'}, 'quantity': 100}], 'orderStrategyType': 'SINGLE'}

The error is in truncate_float due to floating point rounding errors.

flt = 5.06
values = {
    'flt':                                            flt,
    'flt * 100':                                      flt * 100,
    'int(flt * 100)':                                 int(flt * 100),
    'float(int(flt * 100))':                          float(int(flt * 100)),
    'float(int(flt * 100)) / 100.0':                  float(int(flt * 100)) / 100.0,
    "'{:.2f}'.format(float(int(flt * 100)) / 100.0)": '{:.2f}'.format(float(int(flt * 100)) / 100.0),
}

for key, value in values.items():
    print(f'{key:<50}: {value}')
flt                                               : 5.06
flt * 100                                         : 505.99999999999994
int(flt * 100)                                    : 505
float(int(flt * 100))                             : 505.0
float(int(flt * 100)) / 100.0                     : 5.05
'{:.2f}'.format(float(int(flt * 100)) / 100.0)    : 5.05

Notice how flt * 100 is 505.99999999999994. A possible fix might be to add a small epsilon value (haven't tested negative values though):

def truncate_float_fixed(flt):
    epsilon = 1e-7
    if abs(flt) < 1 and flt != 0.0:
        return '{:.4f}'.format(float(int((flt + epsilon) * 10000)) / 10000.0)
    else:
        return '{:.2f}'.format(float(int((flt + epsilon) * 100)) / 100.0)


print('BEFORE: ', truncate_float(5.06))
print('AFTER : ', truncate_float_fixed(5.06))
BEFORE:  5.05
AFTER :  5.06
@john157157
Copy link

I've been looking at the truncation issue too. JordanMandel - while your approach looks good to me, why not bypass the whole issue and pass the price as a string? Oh wait, the docs say

You can sidestep this entire process by passing your price as a string, although be forewarned that TDAmeritrade may reject your order or even interpret it in unexpected ways.

Does anyone know the circumstances where the TDAmeritrade weirdness happens? Fetched quotes routinely arrive with 4 decimals places of precision - eg 77.1234. Would placing a buy_limit order using the string "77.1234" ever give unexpected results?

@tirthb
Copy link

tirthb commented Feb 16, 2023

truncate_float(price) is not working 4.1 is giving "4.09"

https://github.com/alexgolec/tda-api/blob/master/tda/orders/generic.py#L35

I am passing the price as string for now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants