Skip to content

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

@JordanMandel

Description

@JordanMandel

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions