alpacahq / alpaca-trade-api-js

Node.js library for Alpaca Trade API.
https://www.npmjs.com/package/@alpacahq/alpaca-trade-api
Apache License 2.0
511 stars 133 forks source link

[Bug]: throw new Error('WebSocket is not open: readyState 0 (CONNECTING)'); #229

Open ghost opened 1 year ago

ghost commented 1 year ago

Is there an existing issue for this?

Current Behavior

when i run node datav2.js i got error C:\Users\User\alpaca-trade-api-js\node_modules\ws\lib\websocket.js:394

      throw new Error('WebSocket is not open: readyState 0 (CONNECTING)');
      ^

Error: WebSocket is not open: readyState 0 (CONNECTING)
s:394:13)
    at AlpacaStocksClient.unsubscribe (C:\Users\User\alpaca-trade-api-js\node_modules\@alpacahq\alpaca-trade-api\dist\resources\datav2\stock_websocket_v2.js:136:19)
modules\@alpacahq\alpaca-trade-api\dist\resources\datav2\stock_websocket_v2.js:93:14)   
    at Timeout._onTimeout (C:\Users\User\alpaca-trade-api-js\datav2.js:63:14)
    at listOnTimeout (node:internal/timers:564:17)
    at process.processTimers (node:internal/timers:507:7)

Expected Behavior

can get data stream

SDK Version I encountered this issue in

2.16.1

Steps To Reproduce

npm install --save @alpacahq/alpaca-trade-api
https://github.com/alpacahq/alpaca-trade-api-js/blob/master/examples/websocket_example_datav2.js
node datav2.js

Filled out the Steps to Reproduce section?

Anything else?

No response

majorsauce commented 1 year ago

Can you add a code snippet on how you connect the WebSocket and also when/how you subscribe to stuff ? It´s not intended to simply run the JS file with Node, this is not actually a binary or standalone application but rather a library which should be used in context of code. Also, the error indicates that you should only send subscriptions once the websocket is actually connected which can be determined by the "onConnect" function, eg:

function initAlpaca() {

    // Setup alpaca
    let alpaca = new Alpaca({
        keyId: 'PK...',
        secretKey: 'EQ...',
        paper: true,
    })

    // Get websocket for stock data
    const stockStream = alpaca.data_stream_v2;

    // Log errors
    stockStream.onError(e => console.log(e))

    // Run this function to execute stuff once connected, for instance subscribe to bars etc.
    stockStream.onConnect(() => {
        console.log("Alpaca connected!")
        stockStream.subscribeForBars(["TSLA"])
    })

    // Process streamed data
    stockStream.onStockBar(bar => console.log(bar))

    // Trigger the websocket to connect
    stockStream.connect()

}
alexk984 commented 1 year ago

Also have the same error which is crash my application every day. Error log:

10:14:07.773 DEBUG \fusion\libs\alpaca\src\lib\AlpacaService.ts:42      Alpaca Status: connecting
Error: WebSocket is not open: readyState 0 (CONNECTING)
    at WebSocket.ping (C:\www\fusion\node_modules\@alpacahq\alpaca-trade-api\node_modules\ws\lib\websocket.js:323:13)
    at AlpacaStocksClient.ping (C:\www\fusion\node_modules\@alpacahq\alpaca-trade-api\dist\resources\datav2\websocket.js:159:19)
    at Timeout._onTimeout (C:\www\fusion\node_modules\@alpacahq\alpaca-trade-api\dist\resources\datav2\websocket.js:128:18)
    at listOnTimeout (node:internal/timers:559:17)
    at processTimers (node:internal/timers:502:7)
10:14:19.608 DEBUG \fusion\libs\alpaca\src\lib\AlpacaService.ts:42      Alpaca Status: authenticating
10:14:19.612 DEBUG \fusion\libs\alpaca\src\lib\AlpacaService.ts:42      Alpaca Status: connected
10:14:19.819 DEBUG \fusion\libs\alpaca\src\lib\AlpacaService.ts:42      Alpaca Status: authenticated
10:14:19.821 INFO \fusion\libs\alpaca\src\lib\AlpacaService.ts:113      Alpaca Subscribe quotes 
Exit...

My code:

   this.alpacaClient = new Alpaca({
      dataStreamUrl: 'wss://stream.data.alpaca.markets',
      keyId: process.env.NX_APCA_API_KEY_ID,
      secretKey: process.env.NX_APCA_API_SECRET_KEY,
      paper: true,
      feed: 'sip',
      apiVersion: 2,
    })

    const websocket = this.alpacaClient.data_stream_v2
    websocket.onStateChange((state) => {
      this._alpacaState = state
      alpacaLogger.debug('Status: ' + state)
      if (state === STATE.AUTHENTICATED) {
        this.subscribeQuotes()
        this.subscribeTrades()
      }
    })
    websocket.onError((err) => {
      alpacaLogger.debug('Error:' + err, 'hidden')
    })
    websocket.onConnect(() => {
      console.log('Alpaca connected!')
    })
    websocket.onStockQuote((quote) => {
      ....
    })
    websocket.onDisconnect(() => {
      alpacaLogger.debug('Disconnected...')
    })
    websocket.connect()
RajRai commented 1 year ago

I'm just messing around with the node_modules right now, it looks like changing this code (from "node_modules\@alpacahq\alpaca-trade-api\dist\resources\datav2\websocket.js" in the following way resolves things. I say looks like because there are some other unchecked calls that I'm seeing, such as conn.ping(), so probably a similar change needs to be made wherever the error comes up.

Old:

    authenticate() {
        const authMsg = {
            action: "auth",
            key: this.session.apiKey,
            secret: this.session.secretKey,
        };
        this.conn.send(this.msgpack.encode(authMsg));
        this.emit(STATE.AUTHENTICATING);
        this.session.currentState = STATE.AUTHENTICATING;
    }

New:

    authenticate() {
        const authMsg = {
            action: "auth",
            key: this.session.apiKey,
            secret: this.session.secretKey,
        };
        if (this.conn.readyState == this.conn.OPEN){ // Check the socket state before calling send in case of a disconnect after the last 'open' emission
            this.conn.send(this.msgpack.encode(authMsg));
            this.emit(STATE.AUTHENTICATING);
            this.session.currentState = STATE.AUTHENTICATING;
        }
    }

It seems like, somehow, the socket has disconnected between emission of the OPEN state and the call to authenticate(). An authenticate() call is registered as a callback for the OPEN state when you call connect().

I think there's a better way of doing this which doesn't involve constantly checking the websocket state, and instead just properly handling the disconnect instead by cancelling any pending authorization or pings or whatever else. However, I know next to nothing about this project's internals, so this is the best I have.