alpacahq / alpaca-trade-api-python

Python client for Alpaca's trade API
https://pypi.org/project/alpaca-trade-api/
Apache License 2.0
1.73k stars 534 forks source link

Trailing stop order triggers error #396

Closed brycelund closed 2 years ago

brycelund commented 3 years ago

Following the Submit Trailing Stop Orders section from here I get the error alpaca_trade_api.rest.APIError: cannot open a short sell while a long buy order is open.



api = tradeapi.REST()

# Submit a market order to buy 1 share of Apple at market price
api.submit_order(
    symbol='AAPL',
    qty=1,
    side='buy',
    type='market',
    time_in_force='gtc'
)

# Submit a trailing stop order to sell 1 share of Apple at a
# trailing stop of
api.submit_order(
    symbol='AAPL',
    qty=1,
    side='sell',
    type='trailing_stop',
    trail_price=1.00,  # stop price will be hwm - 1.00$
    time_in_force='gtc',
)
brycelund commented 3 years ago

Adding a sleep(1) to wait for order to fill between orders works. Is there a way to specify a trailing stop on fill to remove the need to send an order, wait for fill, then send another order?

FergusClare commented 2 years ago

I added time.sleep(1.0) between my initial order and the submit order for the trailing stop as recommended by @brycelund and I'm still getting the following error message. Does the forbidden URL have anything to do with this? My long positions are going through on the paper trade api endpoint without issue and I'm able to submit trailing stop orders through Postman. I'm using Python 3.9.4.

Traceback (most recent call last):
  File "/usr/local/lib/python3.9/dist-packages/alpaca_trade_api/rest.py", line 208, in _one_request
    resp.raise_for_status()
  File "/usr/local/lib/python3.9/dist-packages/requests/models.py", line 953, in raise_for_status
    raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 403 Client Error: Forbidden for url: https://paper-api.alpaca.markets/v2/orders

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/var/www/wedge_screener/wedge_screener.py", line 145, in <module>
    main()
  File "/var/www/wedge_screener/wedge_screener.py", line 137, in main
    wedge_trader(final_selections, 10)
  File "/var/www/wedge_screener/wedge_trader.py", line 31, in wedge_trader
    api.submit_order(
  File "/usr/local/lib/python3.9/dist-packages/alpaca_trade_api/rest.py", line 395, in submit_order
    resp = self.post('/orders', params)
  File "/usr/local/lib/python3.9/dist-packages/alpaca_trade_api/rest.py", line 227, in post
    return self._request('POST', path, data)
  File "/usr/local/lib/python3.9/dist-packages/alpaca_trade_api/rest.py", line 187, in _request
    return self._one_request(method, url, opts, retry)
  File "/usr/local/lib/python3.9/dist-packages/alpaca_trade_api/rest.py", line 216, in _one_request
    raise APIError(error, http_error)
alpaca_trade_api.rest.APIError: cannot open a short sell while a long buy order is open

Here's the successful Postman POST in python:

import requests

url = "https://paper-api.alpaca.markets/v2/orders"

payload = "{\n    \"symbol\": \"BNTX\",\n    \"qty\": 10,\n    \"side\": \"sell\",\n    \"type\": \"trailing_stop\",\n    \"trail_price\": 3.00,\n    \"time_in_force\": \"gtc\"\n}"
headers = {
  'APCA-API-KEY-ID': 'my_key_id',
  'APCA-API-SECRET-KEY': 'my_secret_key',
  'Content-Type': 'text/plain'
}

response = requests.request("POST", url, headers=headers, data=payload)

print(response.text)

And here's what I have in my code using the alpaca api:

import time

from alpaca_trade_api.rest import REST
import alpaca_trade_api as tradeapi

def wedge_trader(dataframe, quantity):
    key_id = 'my_key'
    secret_key = 'my_secret'
    base_url = 'https://paper-api.alpaca.markets'
    api = tradeapi.REST(key_id=key_id, secret_key=secret_key, base_url=base_url)
    account = api.get_account()
    symbols = [s for s in dataframe['ticker']] #get the symbols from the dataframe
    atrs = [a for a in dataframe['atr']] #get the average true ranges from the dataframe

        if account.status == 'ACTIVE':
        for s, a in zip(symbols,atrs):
            api.submit_order(
                symbol=s,
                side='buy',
                type='market',
                qty=quantity,
                time_in_force='gtc'
                )
            time.sleep(1)

            api.submit_order(
                symbol=s,
                qty=quantity,
                side='sell',
                type='trailing_stop',
                trail_price=a,  # stop price will be hwm - average true range$
                time_in_force='gtc'
                )
FergusClare commented 2 years ago

I found the error above. My code was attempting to place orders at market close and in doing so, was placing the long position, which wasn't being filled, at the same time as the short order for the trailing stop. I updated my code to call 1/2 hour before market close and now the trailing stops are going through without problems. In short, the long position has to be filled before the trailing stop can be entered. This is why adding the time.sleep(1.0) in @brycelund code worked.

I don't think this is an issue that needs to be addressed withe the Alpaca API code and instead is something we need to handle for when writing the logic for our applications.

I think this issue can be closed. Here's my response when running the trailing stop with the market still open:

>>> api.submit_order(symbol='MRNA', qty=10, side='sell', type='trailing_stop',trail_price=7.00,time_in_force='gtc')
Order({   'asset_class': 'us_equity',
    'asset_id': 'b02df0cc-0a0a-4ecb-8e92-201b1044ea21',
    'canceled_at': None,
    'client_order_id': '7a735f43-c586-4eee-a11f-2f9711df6fe3',
    'created_at': '2021-11-18T14:30:37.444586527Z',
    'expired_at': None,
    'extended_hours': False,
    'failed_at': None,
    'filled_at': None,
    'filled_avg_price': None,
    'filled_qty': '0',
    'hwm': '244.67',
    'id': 'bcaddbc2-329b-4c5d-ab6b-ee40749aa7b5',
    'legs': None,
    'limit_price': None,
    'notional': None,
    'order_class': '',
    'order_type': 'trailing_stop',
    'qty': '10',
    'replaced_at': None,
    'replaced_by': None,
    'replaces': None,
    'side': 'sell',
    'status': 'new',
    'stop_price': '237.67',
    'submitted_at': '2021-11-18T14:30:37.443143037Z',
    'symbol': 'MRNA',
    'time_in_force': 'gtc',
    'trail_percent': None,
    'trail_price': '7',
    'type': 'trailing_stop',
    'updated_at': '2021-11-18T14:30:37.444586527Z'})
>>>