jmfernandes / robin_stocks

This is a library to use with Robinhood Financial App. It currently supports trading crypto-currencies, options, and stocks. In addition, it can be used to get real time ticker information, assess the performance of your portfolio, and can also get tax documents, total dividends paid, and more. More info at
http://www.robin-stocks.com
MIT License
1.68k stars 457 forks source link

[Bug] Dollar-Based Buy Orders Incorrectly Set to Limit Order During Regular Market Hours #485

Open hassan12-code opened 1 month ago

hassan12-code commented 1 month ago

When attempting to place a dollar-based buy order during regular market hours using the order_buy_fractional_by_price function, the order is incorrectly set as a limit order due to the preset_percent_limit parameter being added along with the type being set as limit. This behavior contradicts the expected functionality where dollar-based buy orders should be executed as market orders.

Steps to Reproduce:

Use the order_buy_fractional_by_price function to place a dollar-based buy order during regular market hours. Observe that the order is submitted as a limit order instead of a market order.

Expected Behavior:

Dollar-based buy orders should be executed as market orders when placed during regular market hours.

Actual Behavior:

Dollar-based buy orders are converted to limit orders during regular market hours due to the preset_percent_limit parameter and type being set as limit.

Relevant Code:

The issue lies in the order function:

if market_hours == 'regular_hours':
    if side == "buy":
        payload['preset_percent_limit'] = "0.05"
        payload['type'] = 'limit'

Suggested Fix:

Remove or conditionally set the preset_percent_limit and type to ensure dollar-based buy orders are processed as market orders:

if market_hours == 'regular_hours':
    if side == "buy":
        payload['type'] = 'market'
BigFav commented 1 month ago

Have you tried removing that code locally in your package manager?

The Robinhood API just broke when I tried, so I ended up adding it back.

hassan12-code commented 1 month ago

Have you tried removing that code locally in your package manager?

The Robinhood API just broke when I tried, so I ended up adding it back.

Yes, I tried modifying or even removing that code, but similar to your experience, I encountered an error, and the API broke for me as well.

khulaid22 commented 1 month ago

I thought I was doing something wrong but turns out that this is a bug. It seems an important matter that requires quick fixing.

BigFav commented 1 month ago

Have you tried removing that code locally in your package manager? The Robinhood API just broke when I tried, so I ended up adding it back.

Yes, I tried modifying or even removing that code, but similar to your experience, I encountered an error, and the API broke for me as well.

Yeah, then your suggested fix seems to not work. There might be something going on with Robinhood here.

hassan12-code commented 1 month ago

Have you tried removing that code locally in your package manager? The Robinhood API just broke when I tried, so I ended up adding it back.

Yes, I tried modifying or even removing that code, but similar to your experience, I encountered an error, and the API broke for me as well.

Yeah, then your suggested fix seems to not work. There might be something going on with Robinhood here.

I totally agree, and apologies that I didn't mention that the proposed solution was giving errors. I just thought this might be a potential fix along with some other adjustments to address the error response from Robinhood.

hassan12-code commented 3 weeks ago

UPDATE (FIX):

I made the following changes to ensure that dollar-based buy orders are correctly processed as market orders during regular market hours:

1. New Parameters:

2. Modified Payload for Buy Orders:

3. Cleanup for Market Orders:

4. Set json=True in POST call:

data = request_post(url, payload, json=True)

- Completed Order Function:

@login_required
def order(symbol, quantity, side, limitPrice=None, stopPrice=None, account_number=None, timeInForce='gtc', extendedHours=False, jsonify=True, market_hours='regular_hours', isFractional=False, priceInDollars = None):
    try:
        symbol = symbol.upper().strip()
    except AttributeError as message:
        print(message, file=get_output())
        return None

    orderType = "market"
    trigger = "immediate"

    if side == "buy":
        priceType = "ask_price"
    else:
        priceType = "bid_price"

    if limitPrice and stopPrice:
        price = round_price(limitPrice)
        stopPrice = round_price(stopPrice)
        orderType = "limit"
        trigger = "stop"
    elif limitPrice:
        price = round_price(limitPrice)
        orderType = "limit"
    elif stopPrice:
        stopPrice = round_price(stopPrice)
        if side == "buy":
            price = stopPrice
        else:
            price = None
        trigger = "stop"
    else:
        price = round_price(next(iter(get_latest_price(symbol, priceType, extendedHours)), 0.00))

    from datetime import datetime
    payload = {
        'account': load_account_profile(account_number=account_number, info='url'),
        'instrument': get_instruments_by_symbols(symbol, info='url')[0],
        'symbol': symbol,
        'price': price,
        'ask_price': str(round_price(next(iter(get_latest_price(symbol, "ask_price", extendedHours)), 0.00))),
        'bid_ask_timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f'),
        'bid_price': str(round_price(next(iter(get_latest_price(symbol, "bid_price", extendedHours)), 0.00))),
        'quantity': quantity,
        'ref_id': str(uuid4()),
        'type': orderType,
        'stop_price': stopPrice,
        'time_in_force': timeInForce,
        'trigger': trigger,
        'side': side,
        'market_hours': market_hours, # choices are ['regular_hours', 'all_day_hours']
        'extended_hours': extendedHours,
        'order_form_version': 4
    }

    # adjust market orders
    if orderType == 'market':
        del payload['stop_price']

    if market_hours == 'regular_hours':
        if side == "buy":
            payload['dollar_based_amount'] = {
                'amount': str(priceInDollars),
                'currency_code': 'USD'
            }
            del payload['price']
            del payload['quantity']
            del payload['extended_hours']
        # regular market sell
        elif orderType == 'market' and side == 'sell':
            del payload['price']
    elif market_hours == 'all_day_hours':
        payload['type'] = 'limit'
        payload['quantity']=int(payload['quantity']) # round to integer instead of fractional

    url = orders_url()
    data = request_post(url, payload, json=True)

    return(data)

- Order Buy Fractional By Price Function:

@login_required
def order_buy_fractional_by_price(symbol, amountInDollars, account_number=None, timeInForce='gfd', extendedHours=False, jsonify=True, market_hours='regular_hours'):
    if amountInDollars < 1:
        print("ERROR: Fractional share price should meet minimum 1.00.", file=get_output())
        return None

    # turn the money amount into decimal number of shares
    price = next(iter(get_latest_price(symbol, 'ask_price', extendedHours)), 0.00)
    fractional_shares = 0 if (price == 0.00) else round_price(amountInDollars/float(price))

    return order(symbol, fractional_shares, "buy", None, None, account_number, timeInForce, extendedHours, jsonify, market_hours, priceInDollars=amountInDollars)

Please note, these changes were a quick fix given my scenario; the code might be optimized further.