tedchou12 / webull

Unofficial APIs for Webull.
MIT License
602 stars 185 forks source link

Option paper trading #316

Closed BirajAd closed 2 years ago

BirajAd commented 2 years ago

Webull now allows options paper trading, if someone could add that as a feature that would be awesome.

ICANTFINDAUSERNAMEATALL commented 2 years ago

Are you sure? How do you do that? I can't seem to be able to place option trades on paper trading

tedchou12 commented 2 years ago

Taking a look at paper trade options, currently web interface is not supported.

截圖 2022-09-11 上午11 53 38

Unfortunately, please help me to monitor, if it is supported on the web interface, we will probably be able to add then.

ICANTFINDAUSERNAMEATALL commented 2 years ago

@tedchou12 It works on the Webull Desktop App now. image I tried updating it myself but I have no clue how networking works and when the order is placed, there are tons of different packages that are sent. I couldn't find out which one was the right package so I just gave up on it.

tedchou12 commented 2 years ago

unfortunately, it needs to work with the web interface version. (Which is the actual endpoints that this API communicates with)

ICANTFINDAUSERNAMEATALL commented 2 years ago

Ahh. I see. Thats probably why I was so confused.

michaelkkehoe commented 1 year ago

I have the options paper API endpoints if you're interested in adding this (I'm super busy at the moment)

ICANTFINDAUSERNAMEATALL commented 1 year ago

Can you send it over? I'll try to add it but I'm not sure if it will work as ted said.

michaelkkehoe commented 1 year ago

Here are the API endpoints:

https://act.webullbroker.com/webull-paper-center/api/paper/v1/order/getOptionAccountDetailAndPosition https://act.webullbroker.com/webull-paper-center/api/paper/v1/order/optionPlace https://act.webullbroker.com/webull-paper-center/api/paper/v1/order/optionReplace

Here is an example function to place option orders:

    def place_order_option(self, optionId=None, lmtPrice=None, stpPrice=None, action=None, orderType='LMT', enforce='DAY', quant=0):
        '''
        create buy / sell order
        stock: string
        lmtPrice: float
        stpPrice: float
        action: string BUY / SELL
        optionId: string
        orderType: MKT / LMT / STP / STP LMT
        enforce: GTC / DAY
        quant: int
        '''
        headers = self.build_req_headers(include_trade_token=True, include_time=True)
        data = {
            'orderType': orderType,
            'serialId': str(uuid.uuid4()),
            'paperId': 1,
            'accountId': self._account_id,
            'tickerId': int(optionId),
            'quantity': int(quant),
            'action': "BUY",
            'timeInForce': enforce,
            'orders': [{'quantity': int(quant), 'action': action, 'tickerId': int(optionId), 'tickerType': 'OPTION'}],
        }

        if orderType == 'LMT' and lmtPrice :
            data['lmtPrice'] = float(lmtPrice)
        elif orderType == 'STP' and stpPrice :
            data['auxPrice'] = float(stpPrice)
        elif orderType == 'STP LMT' and lmtPrice and stpPrice :
            data['lmtPrice'] = float(lmtPrice)
            data['auxPrice'] = float(stpPrice)

        url = 'https://act.webullbroker.com/webull-paper-center/api/paper/v1/order/optionPlace'
        response = requests.post(url, json=data, headers=headers, timeout=self.timeout)
        if response.status_code != 200:
            raise Exception('place_option_order failed', response.status_code, response.reason)
tedchou12 commented 1 year ago

@michaelkkehoe

Thank you so much! Let me try it out and add to the package as well!

dazhonggu commented 1 year ago

Ted, you got a chance to update the package having those endspoints now? many thanks. @tedchou12

M1NL1TE commented 1 year ago

@michaelkkehoe @tedchou12 Have you been successful with a modify option order with the 'https://act.webullbroker.com/webull-paper-center/api/paper/v1/order/optionReplace' endpoint?

I keep getting this response: 2023-05-25 12:42:58,193 INFO b'{"msg":"orderId=null","traceId":"b7ef3d8e73a84dda9b3d8146e2024d20","code":"400","success":false}'

and this is the request: 2023-05-25 12:42:58,192 INFO b'{"oderId": 105353561, "orderType": "LMT", "accountId": 12345, "paperId": 1, "timeInForce": "GTC", "serialId": "XYZ", "orders": [{"quantity": 1, "action": "BUY", "tickerId": 1038356919, "tickerType": "OPTION", "orderId": 105353561}], "lmtPrice": 1.29}'

(intentionally omitted possibly sensitive info)

And here is how I implemented the modify order function in webull.py: def modify_order_option(self, order=None, lmtPrice=None, stpPrice=None, enforce=None, quant=0): ''' order: dict from get_current_orders stpPrice: float lmtPrice: float enforce: GTC / DAY quant: int ''' headers = self.build_req_headers(include_trade_token=True, include_time=True) data = { 'oderId': order[0]['orderId'], 'orderType': order[0]['orderType'], 'accountId': self._account_id, 'paperId': 1, 'timeInForce': enforce or order[0]['timeInForce'], 'serialId': str(uuid.uuid4()), 'orders': [{'quantity': quant or order[0]['totalQuantity'], 'action': order[0]['action'], 'tickerId': order[0]['ticker']['tickerId'], 'tickerType': 'OPTION', 'orderId': order[0]['orderId']}] }

    if order[0]['orderType'] == 'LMT' and (lmtPrice or order.get('lmtPrice')):
        data['lmtPrice'] = lmtPrice or order[0]['lmtPrice']
    elif order[0]['orderType'] == 'STP' and (stpPrice or order.get('auxPrice')):
        data['auxPrice'] = stpPrice or order[0]['auxPrice']
    elif order[0]['orderType'] == 'STP LMT' and (stpPrice or order.get('auxPrice')) and (lmtPrice or order.get('lmtPrice')):
        data['auxPrice'] = stpPrice or order[0]['auxPrice']
        data['lmtPrice'] = lmtPrice or order[0]['lmtPrice']

    url = 'https://act.webullbroker.com/webull-paper-center/api/paper/v1/order/optionReplace'
    response = requests.post(url, json=data, headers=headers, timeout=self.timeout)

    logging.info(response.request.body)
    logging.info(response.content)
    if response.status_code != 200:
        raise Exception('replace_option_order failed', response.status_code, response.reason)
    return True

The main change I made to the regular account modify_order_option was taking out the "'comboId': order['comboId']" line in the data dictionary as I don't see that in the list of "openOrders" for the paper account. It's somehow not accepting the orderId from the request and I'm not sure why.