tiagosiebler / bybit-api

Node.js SDK for the Bybit APIs and WebSockets, with TypeScript & browser support.
https://www.npmjs.com/package/bybit-api
MIT License
260 stars 85 forks source link

How to keep alive connection on RestClientV5 #368

Closed ne0c0de closed 2 months ago

ne0c0de commented 2 months ago

Hi

I'm currently trading usually on Binance and ByBit over API

On Binance side the library that I'm currently using is able to keep alive connection once I initiated (not websocket, REST API) and I'm able to ping the binance endpoint to keep the connection alive

On ByBit side I couldn't find any option to keep alive the connection neither ping endpoint.

Is there a way to achieve that?

tiagosiebler commented 2 months ago

Which library are you currently using for Binance? Is it a built-in feature for HTTP keep alive? I've seen some do this in the past by injecting custom parameters to the networking library I use under the hood (axios). The REST client exposes a parameter to pass-through configuration for axios, but I haven't tried it myself - if you do try it and get it working, please do share it. I'll try to find what others shared in the past about keep-alive with axios.

ne0c0de commented 2 months ago

I'm currently using https://github.com/ViewBlock/binance-api-node as Binance lib. It has automatically keeping connection allive if you're using nodejs V18+

On axios, there's a parameter that you can use to keep connection as alive:

    httpAgent: new http.Agent({ keepAlive: true }),
    httpsAgent: new https.Agent({ keepAlive: true }),

For instance this type of connection will keep alive until you close:

const axios = require('axios')
const https = require('https')
const http = require('http')

const binanceAPI = axios.create({
    baseURL: 'https://fapi.binance.com/fapi/v1/',
    timeout: 5000, // Adjust timeout as needed
    headers: {
        'Content-Type': 'application/json',
    },
    // Enable keep-alive connections
    httpAgent: new http.Agent({ keepAlive: true }),
    httpsAgent: new https.Agent({ keepAlive: true }),
})

Can you implement it to bybit lib as an option?

tiagosiebler commented 2 months ago

Thanks for this - where does the ViewBlock binance lib handle keep alive? To me it doesn't look to have any logic for it, or is it something enabled for fetch by default in node V18+? Curious what drives this in there.

Regarding the axios approach, that will be very easy to add to any of my SDKs (including my up-to-date binance SDK). The second parameter for any of my REST clients, for any of my SDKs, accepts an axios configuration object that will be merged with the global axios configuration used for every single HTTP request in the SDK. There's actually an example that uses this pass-through configuration to configure a HTTPS proxy via the httpAgent and httpsAgent parameters https://github.com/tiagosiebler/bybit-api/blob/master/examples/rest-v5-proxies2.ts#L30-L44

This way you can already turn this on for any of my SDKs - no code change needed on my end.

Is there more to it than passing keepAlive http/https agents? Do you need to make a HTTP request at least once every x seconds to keep the connection open? Given the latency improvement this should give, I'd be happy to also add a configuration toggle for this as a built-in feature, although I want to make sure I fully understand the proper way to do this.

Are there caveats to using keep-alive requests instead of the default?

ne0c0de commented 2 months ago

I didn't dive into binance lib that I sent but when I started to use node v18+ it's automatically keeping connection open (I'm pinging binance every 10 seconds to keep connection alive)

The caveats of this usage: When you're curious about the miliseconds of the executions of orders, the opened connection is give you an advantage to execute the order in 5-25ms instead of 150-200 ms (because connection open process takes at least 100ms)

Especially it's more important if you're using news or influencer trading ;)

And also thank you for the hint that you gave, I will try..

tiagosiebler commented 2 months ago

Makes sense! If the connection drops for whatever reason (internet shenanigans), the worst-case scenario is just the slower "connection open" process, right? So you'd almost have no reason to consistently have the keep alive disabled?

If you're seeing this performance improvement I'd consider making a convenient setting in the REST client options to turn this on without fussing with the axios configuration directly. Would love to hear how your testing goes either way - please keep me posted!

ne0c0de commented 2 months ago

sure, I'm testing it now

ne0c0de commented 2 months ago

here's my test code:

require('dotenv').config()
const http = require('http')
const https = require('https')
const { ContractClient, RestClientV5, WebsocketClient } = require('bybit-api')

let __bot = {
}

let init = async () => {

    __bot.bybitClient = new RestClientV5({
        key: process.env.BYBIT_API_KEY,
        secret: process.env.BYBIT_API_SECRET,
        parseAPIRateLimits: false
    },
        {
            httpAgent: new http.Agent({ keepAlive: true }),
            httpsAgent: new https.Agent({ keepAlive: true }),
        }
        )

    __bot.bybitBase = new ContractClient({
        key: process.env.BYBIT_API_KEY,
        secret: process.env.BYBIT_API_SECRET,
        parseAPIRateLimits: false
    })

    setInterval(function() {
        let now = new Date()
        console.log(new Date(), 'ping bybit')
        __bot.bybitClient.getServerTime().then(result => {
            console.log(new Date(), 'pong from bybit, it takes ' + (new Date() - now) + 'ms.')
        })
    }, 3000)
}
init()

here's the result:

image

If I disable keepAlive like this:

require('dotenv').config()
const http = require('http')
const https = require('https')
const { ContractClient, RestClientV5, WebsocketClient } = require('bybit-api')

let __bot = {
}

let init = async () => {

    __bot.bybitClient = new RestClientV5({
        key: process.env.BYBIT_API_KEY,
        secret: process.env.BYBIT_API_SECRET,
        parseAPIRateLimits: false
    },
        // {
        //     httpAgent: new http.Agent({ keepAlive: true }),
        //     httpsAgent: new https.Agent({ keepAlive: true }),
        // }
        )

    __bot.bybitBase = new ContractClient({
        key: process.env.BYBIT_API_KEY,
        secret: process.env.BYBIT_API_SECRET,
        parseAPIRateLimits: false
    })

    setInterval(function() {
        let now = new Date()
        console.log(new Date(), 'ping bybit')
        __bot.bybitClient.getServerTime().then(result => {
            console.log(new Date(), 'pong from bybit, it takes ' + (new Date() - now) + 'ms.')
        })
    }, 3000)
}
init()

this is the result:

image

I didn't calculate the average time (I'm lazy atm :D) but as I see from the results it's speedup at least 9ms which is cause hundreds of thousands USDT for me :D

(PS: I tested this on a Tokyo server (which is closest place for Binance API) But from the results I guess ByBit Servers are not located in Tokyo because The resuls usually get as 3ms to 10ms to ping Binance (which has an official endpoint for ping, I used getServerTime function on ByBit))

tiagosiebler commented 2 months ago

Bybit is AWS Singapore, Availability Zone ID apse1-az3, in case you wanted to try being closer. Interesting though - does seem to shave off some time, pretty consistently too!

ne0c0de commented 2 months ago

Yes,

Thanks for location but I may consider move my system from Tokyo to Singapore because the news resource is located in Tokyo and my big operations are still on Binance so I will try to create another solution instead of moving.

Anyway thank you for your support and please consider to enable this keepAlive as defauit ;)

tiagosiebler commented 2 months ago

Not sure about enabling it by default, as I'm not keen in introducing default changes under the hood - part of the promise of my SDKs. No surprises - reliable, robust and regularly updated.

Which does bring to mind you should consider when it might be time to upgrade to a more modern alternative to the binance SDK you are using. Either to the one I maintain & heavily use or the one that binance maintains. Especially if you happen to be relying on the user data stream - these outdated SDKs had serious issues with how they maintained the user-data stream's listen key, which was one of the original motivations for just making my own SDKs (after many ignored PRs and messages to the maintainers of these older SDKs). Food for thought if you're trading any serious capital through these SDKs.

Either way, after more stress testing on my end I will definitely consider adding a simple "option" in the rest client parameters though, so anyone can easily enable it without much thought, as well as some simple docs around it. Wouldn't have it enabled by default, at least not yet, but I'll definitely make it easy and well documented for anyone that would benefit from a faster roundtrip.

tiagosiebler commented 2 months ago

Hey @ne0c0de

Added a simple config flag to enable HTTPS keep alive via axios. Also exposed one setting for the keep alive interval as well as adding an example to demo how to use it, see the PR #371 for more info.

I've only configured HTTPS keep alive, since the SDK does not / should not make any HTTP requests, everything is HTTPS.

It's not enabled by default but at least there's an easy way to turn it on without messing around with the axios configuration object.

ne0c0de commented 2 months ago

thank you so much bro