Giter VIP home page Giter VIP logo

pybit's Introduction

pybit

Build Status Build Status Build Status contributions welcome

Python3 API connector for Bybit's HTTP and Websockets APIs.

Hi all—

pybit has now become the official Python wrapper for Bybit's API and is available at https://github.com/bybit-exchange/pybit. This repository has been archived and is now read-only. If you wish to create a pull request or issue, please head over to Bybit's fork.

Appreciate all the help and support, but my time has been taken up by other projects. Two Bybit developers (Dexter and MJ) have been helping me upkeep the project, and have been kind enough to take over maintanence.

~ ❤️ Verata

pybit's People

Contributors

allcontributors[bot] avatar apf20 avatar blacksabbather avatar cameronhh avatar dextertd avatar joesham avatar kuegi avatar leftcoastgeek avatar tomcru avatar verata-veritatis avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

pybit's Issues

my_position() uses symbol to determine endpoint path, but it's not a required param

As symbol is not a required parameter for all Futures position/list endpoints, this means that /v2/private/position/list is called by default; but this is not necessarily intended by the user.

Also, this means that it's not possible to call other endpoints without specifying a symbol, so it's impossible to receive a response detailing multiple symbols.

This is unfortunately as it means this method will need a special way to know which endpoint path to use.

https://bybit-exchange.github.io/docs/inverse/#t-myposition

get_active_order() fails to recognise BTCUSD, calling incorrect endpoint

Executing

session.get_active_order(symbol="BTCUSD")

pybit raises

pybit.exceptions.InvalidRequestError: Params error! (ErrCode: 10001) (ErrTime: 19:14:19).
Request → GET https://api-testnet.bybit.com/futures/private/order/list: {'api_key': 'api_key', 'recv_window': 5000, 'symbol': 'BTCUSD', 'timestamp': 1623179659029, 'sign': 'sign'}.

(sensitive info replaced)

pybit called the incorrect endpoint, /futures/private/order/list, which does not support the BTCUSD symbol. The problem lies here: https://github.com/verata-veritatis/pybit/blob/master/pybit/__init__.py#L410

Orderbook subscribing not working for UNIUSDT

Nice project !

When running this minimal example, there is no output, according to the api docs I would expect to see output:

from pybit import WebSocket
from time import sleep

ws = WebSocket(
    endpoint='wss://stream.bybit.com/realtime',
    subscriptions=['orderBookL2_25.UNIUSDT'],
)


while True:
    for j in ws.fetch('orderBookL2_25.UNIUSDT'):
        print(j)
    sleep(0.1)

Could you clarify on what I might be doing wrong ? Otherwise I think there is a bug with getting the orderbook for UNIUSDT,

Api docs : https://bybit-exchange.github.io/docs/inverse/#t-websocketorderbook25

Issues with Websocket and Query Parameter "from"

When using the from parameter for the query_mark_kline method it is saying that it is invalid syntax.
I think it is because from is a keyword.

Also, i noticed the Websocket.md page is no longer maintained.
Is anyone maintaining that?
I am getting a Websocket object does not have a send method error.

Can someone please assist?
Is this codebase being actively maintained?

Websockets is under documented, i'm having trouble using it

I'm having trouble using the Websockets feature support.

If there were enhanced docs, or a strong example, or test case that would help alot.

  • I cannot figure out how to easily override the various callback methods from their toy versions in the Websocket wrapper object. This is required if we want to do stuff like store message output into databases or queues.
  • managing the thread has been difficult in my configuration (Flask and in GCP Cloud Run remotely) and I'm having trouble with workarounds.

The best progress I made was running the pybit/test.py file which I found browsing the code. This seems like a leftover prototype for the Websockets class that exists, but it gives me some output that works.

Part of the problem could be that I'm running it as part of a Flask application, and managing the background thread is problematic. I'm narrowing in on a solution of running it in Cloud Run as its own application but that requires using some background persistence like Redis: https://cloud.google.com/run/docs/triggering/websockets

Alternatively, I've played with https://kendhia.medium.com/run-python-webserver-flask-as-a-websocket-client-also-175c130f7ca4 to run multiple processes together but it hasn't come together yet.

Any enhanced docs, or a strong example, or test case that would help alot. As I explore this on my own side I will try to contribute back.

Websocket auth request expired

Hi,
I am trying to subscribe to position or order and I don't get data because the auth failed. The problem is that the websocket not raise any exception. I debug the websocket and I can see the error:

msg_json:  {'success': False, 'ret_msg': 'error:request expired', 'conn_id': '31950833-ab7d-4fd2-8b1c-1dc1b7205ae2', 'request': {'op': 'auth', 'args': ['bqZTxSi30b', '1622325037946', '2dab68953beb4d27bc4c1257f1f594dabc9abb631']}}
msg_json:  {'success': False, 'ret_msg': 'error:topic:order needs auth first', 'conn_id': '31950833-ab7d-4f1c-1dc1b7205ae2', 'request': {'op': 'subscribe', 'args': ['order', 'position']}}

If I change the "def _auth(self)" and increase the expires variable the websocket return data correctly.
I thing that is a good idea implement the auto increase recv window like HTTP that work fine, because is very difficult to sync the timestamp. What do you think?

if s_json['ret_code'] == 10002:   
   error_msg += '. Added 2.5 seconds to recv_window'     
   recv_window += 2500

ModuleNotFoundError: No module named 'jsonschema.compat'

I use pybit for accessing bybit.
Versions before 1.3 I never saw problems like this.

(env) PS E:\programming\Project\TradeBot> python .\TradingBot.py
Traceback (most recent call last):
File "E:\programming\Project\TradeBot\TradingBot.py", line 12, in
from bybit import HTTP
File "E:\programming\Project\TradeBot\env\lib\site-packages\bybit_init_.py", line 3, in
from bravado.client import SwaggerClient
File "E:\programming\Project\TradeBot\env\lib\site-packages\bravado\client.py", line 51, in
from bravado_core.param import marshal_param
File "E:\programming\Project\TradeBot\env\lib\site-packages\bravado_core\param.py", line 13, in
from bravado_core.marshal import marshal_schema_object
File "E:\programming\Project\TradeBot\env\lib\site-packages\bravado_core\marshal.py", line 11, in
from bravado_core.model import Model
File "E:\programming\Project\TradeBot\env\lib\site-packages\bravado_core\model.py", line 13, in
from swagger_spec_validator.ref_validators import attach_scope
File "E:\programming\Project\TradeBot\env\lib\site-packages\swagger_spec_validator_init_.py", line 8, in
from swagger_spec_validator.util import validate_spec_url
File "E:\programming\Project\TradeBot\env\lib\site-packages\swagger_spec_validator\util.py", line 9, in
from swagger_spec_validator import validator12
File "E:\programming\Project\TradeBot\env\lib\site-packages\swagger_spec_validator\validator12.py", line 29, in
from swagger_spec_validator.ref_validators import default_handlers
File "E:\programming\Project\TradeBot\env\lib\site-packages\swagger_spec_validator\ref_validators.py", line 14, in
from jsonschema.compat import iteritems
ModuleNotFoundError: No module named 'jsonschema.compat'

I am sure there was something missing, Any help or ideas?
Regards,

Website: Get risk limit endpoint does not require authentication but Pybit doesn't seem to agree

There seems to be a mismatch between document @ https://bybit-exchange.github.io/docs/linear/?python--pybit#t-getrisklimit vs actual pybit v1.3.2

Document mentioned Get risk limit. This endpoint does not require authentication but when I run the example Python code below,

from pybit import HTTP
session = HTTP("https://api-testnet.bybit.com")
print(session.get_risk_limit(
    symbol="BTCUSDT"
))

authentication is expected:
Traceback (most recent call last):
File "", line 3, in
File "D:\xxx\venv\lib\site-packages\pybit_init_.py", line 1259, in get_risk_limit
return self.submit_request(
File "D:\xxx\venv\lib\site-packages\pybit_init
.py", line 1734, in _submit_request
signature = self.auth(
File "D:\xxx\venv\lib\site-packages\pybit_init
.py", line 1655, in _auth
raise PermissionError('Authenticated endpoints require keys.')
PermissionError: Authenticated endpoints require keys.

I trust website is correct whereas Pybit requires fix. Rationale is that Get risk limit is mapped to /public/linear/risk-limit logically should not require authentication.

USDT perpetual order creation fails with "Error sign!" in place_active_order

System: macOS
pybit version: 1.3.0

My code is working for for coin based perpetual contracts. Tried adapting for the ETCUSDT perpetual contract.
Authentication and active order queries work fine. However, creating an order fails with an "Error sign!" exception

Traceback (most recent call last):
  File "/Users/user/Dropbox/Projects/PTrader/PTrader.py", line 237, in <module>
    close_on_trigger="False")
  File "/Users/user/Library/Python/3.7/lib/python/site-packages/pybit/__init__.py", line 492, in place_active_order
    auth=True
  File "/Users/user/Library/Python/3.7/lib/python/site-packages/pybit/__init__.py", line 1870, in _submit_request
    time=dt.utcnow().strftime("%H:%M:%S")
pybit.exceptions.InvalidRequestError: Error sign! origin_string[api_key=valid_api_key&close_on_trigger=false&order_type=limit&price=52.11&qty=0.1&recv_window=5000&reduce_only=true&side=sell&symbol=etcusdt&time_in_force=goodtillcancel&timestamp=1633520617145] (ErrCode: 10004) (ErrTime: 11:43:37).
Request → POST https://api.bybit.com/private/linear/order/create: {'api_key': 'valid_api_key', 'close_on_trigger': 'False', 'order_type': 'Limit', 'price': 52.11, 'qty': 0.1, 'recv_window': 5000, 'reduce_only': 'True', 'side': 'Sell', 'symbol': 'ETCUSDT', 'time_in_force': 'GoodTillCancel', 'timestamp': 1633520617145, 'sign': '1c52731c9926946bc22332592dd31abeba4bedc017caed64528c79facb2428f2'}.

Issues with Websocket connection from windows server 2016

below is the error I get when I try to connect to websocket on widows server 2016

the code works fine on my personal PC

ERROR:
2021-03-14 11:28:44 - websocket - ERROR - error from callback <bound method WebSocket._on_message of <pybit.WebSocket object at 0x000002064BE7FE50>>: _on_message() takes 2 positional arguments but 3 were given

Need help regarding websocket data

Hey i have setup my websocket and its working great.

But i cant seem to get the data unpackked in the correct manner.

It seems that the data is a JSON dictonary, and when i try to unpack it, i get to this point.
[{'start': 1641475140, 'end': 1641475200, 'period': '1', 'open': 464.3, 'close': 464.8, 'high': 464.8, 'low': 464.3, 'volume': '11.62', 'turnover': '5397.945', 'confirm': False, 'cross_seq': 9713056403, 'timestamp': 1641475159431119}]

i use JSON.loads to unload the data, and then move in to the data by using: msg['data'] and get the above mentioned result.

Does anybody know how to reach into this block of data, makeing columns and stuff, or can anybody point me in the right direction.
I havent worked much with dictonaries before.

Thanks.
Btw i appreciate you making this library this is very good to work with.

Mathias.

session.get_wallet_balance

Hi! Thank you for alle the work in this project.

I am not quite sure how to use the endpoints right, i assume that any endpoint, such as get_wallet_balance() requires the "session." as a prefix? If so, I do not understand how the use the parameters "self" and "kwargs". For instance, session.get_wallet_balance() returns the same bunch of data as session.get_wallet_balance (symbol='BTCUSD', kwargs='equity'). Is it possible to restrict the output to relevant information and how is the "self" and "kwargs" parameter meant to be used. Do you have an example?

Kind regards

Paul

Parsing Returned Data

Could you provide an example of how to parse data for pybit in Python? To get just the order_id from session.get_active_order(symbol="XRPUSDT") what would I need to do?

ENHANCEMENT: New common API round_tick and round_step before placing order

Hi @verata-veritatis,

Problem Statement
Often for take profit (TP) and stop loss (SL) calculation, if they are certain percentage away from the order price, the result will not be matching with the tick size (step size for quantity).

Observation
Chances are users would have created their own version of round_tick and round_step function call to change TP, SL and quantity in order to cater for need to comply with exchange's rick size and step size requirements.

Opportunity
I've came across one of the exchanges providing roundStep API to their users, I would like to extend the request for PyBit to consider providing both round_tick and round_step to lead the crypto market instead.

Example Use Case

take_profit = round_tick(ask * (1 + 10/100))
stop_loss = round_tick(bid * (1 + 5/100))
single_quantity = round_step(total_quantity / 5))

Things to Watch Out

  1. Assuming user doesn't have to deal with symbol's decimal point precision by default.
  2. Assuming API is intelligent enough to decide it is round up or round down.

Benefits for PyBit Users
Prevent non-obvious error such as the following that I've spend an hour and eventually figured out it is caused by TP and SL are not complying with tick size:

Traceback (most recent call last):
  File "D:\venv\lib\site-packages\pybit\__init__.py", line 1812, in _submit_request
    s_json = s.json()
  File "D:\venv\lib\site-packages\requests\models.py", line 900, in json
    return complexjson.loads(self.text, **kwargs)
  File "C:\Users\xxx\AppData\Local\Programs\Python\Python39\lib\json\__init__.py", line 346, in loads
    return _default_decoder.decode(s)
  File "C:\Users\xxx\AppData\Local\Programs\Python\Python39\lib\json\decoder.py", line 340, in decode
    raise JSONDecodeError("Extra data", s, end)
json.decoder.JSONDecodeError: Extra data: line 1 column 194 (char 193)

Empty data entry on WebSocket raises KeyError

>>> ws.fetch(['position'])
Traceback (most recent call last):
    return [self.data[i].pop() for i in topics]
IndexError: pop from empty list

Need to handle this Exception by returning an empty list instead.

connection refused

I'm running your example, and got time out on ssl or refused. And I found that even if I use:
import requests
h = {
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36",
}
r = requests.get('https://api-testnet.bybit.com/spot/quote/v1/kline?symbol=ETHUSDT&interval=5m', headers=h)

I still got refused.

while if I use:
import urllib.request

req = urllib.request.Request(web, headers=headers_)
data = urllib.request.urlopen(req).read().decode("utf-8",'ignore')
print(data)

I could get the result.
We know that the kline is unauthorized url. So I wonder if requests library is treated as ROBOT ?

Websocket.fetch returns reference to underlying data

Hey there, thanks a lot for building a solid Bybit connector!

I found it very easy to get set up and running, but it seems there is a key usage issue that was counter-intuitive to detect that might be useful to address:

After subscribing to data that gets updated via deltas (such as the orderbook), the returned object is a reference to the underlying Websocket.data structure, which means that if you have code akin to this:

ws = WebSocket(
    endpoint='wss://stream.bybit.com/realtime', 
    subscriptions=['orderBookL2_25.BTCUSD']
)
snapshots = []
for i in range(10):
    snapshot = ws.fetch('orderBookL2_25.BTCUSD')
    snapshots.append(snapshot)
    time.sleep(1)

Then snapshots[0] will actually resolve to the same values as snapshots[1] even if the data has updated between the snapshots, since they resolve to the same object in pybit unless there has been a delete or insert.

I suspect this might be desired behavior if one is only ever interested in the most recent data, but that is not always the case. There would of course be some performance penalty to returning copies each time. Nonetheless, it might be worth highlighting this behaviour in the README if leaving it unchanged.

Websocket - logger error for 'stop_order_id'

Greets,

I'm getting the following error from the logging module when a stoploss order is placed or modified on the testnet. I can only presume that the bybit websocket is passing back 'stop_order_id' as a parameter which is not expected?

ERROR - error from callback <bound method WebSocket._on_message of <pybit.WebSocket object at 0x1a1b442ad0>>: 'stop_order_id'

Conditional order placement and modify orders are working fine for me BTW, the error just appears when orders are sent / modified.

Any idea why this is happening?

Thanks

False positive test of websocket hides failure

Running tests on the master branch like python setup.py test yields error output like this, but no tests fail.

.... 2021-12-27 16:11:35 - pybit - ERROR - You aren't subscribed to the ['instrument_info.100ms.BTCUSD'] topic. ERROR

This is a failure situation that should yield an error. Currently this situation is only logged to error with no return value. This clearly should instead throw an exception like elsewhere in the code.

Websocket Orderbook not Properly Ordered

When using websockets to get the content of the orderbook (orderBookL2_25.Symbol) the content of the orderbook obtained when calling fetch() is out of order. When Bybit sends the orderbook the first time on the websocket it is ordered properly and then on subsequent delta pushes, pybit updates the local version of the orderbook, but does not re-sort it after delta updates. So when we call fetch() to get the orderbook we might assume the orderbook is sorted, but that is not the case and reading only the top rows for buyers/sellers sometime gives inaccurate results. Here is an example of what fetch() returns after a few delta updates.
For example:

Orderbook Top 25
       price   symbol         id  side    size
0   41426.00  BTCUSDT  414260000   Buy  11.807
1   41426.50  BTCUSDT  414265000   Buy   0.049
2   41428.00  BTCUSDT  414280000   Buy   0.003
3   41429.50  BTCUSDT  414295000   Buy   0.027
4   41430.00  BTCUSDT  414300000   Buy   0.505
5   41430.50  BTCUSDT  414305000   Buy   0.404
6   41432.00  BTCUSDT  414320000   Buy   0.012
7   41432.50  BTCUSDT  414325000   Buy   0.540
8   41433.50  BTCUSDT  414335000   Buy   1.052
9   41434.50  BTCUSDT  414345000   Buy   6.034
10  41435.00  BTCUSDT  414350000   Buy   1.110
11  41435.50  BTCUSDT  414355000   Buy   2.517
12  41436.00  BTCUSDT  414360000   Buy   0.774
13  41436.50  BTCUSDT  414365000   Buy   1.093
14  41437.00  BTCUSDT  414370000   Buy   1.283
15  41437.50  BTCUSDT  414375000   Buy   1.052
16  41438.50  BTCUSDT  414385000   Buy   0.616
17  41439.00  BTCUSDT  414390000   Buy   0.034
18  41439.50  BTCUSDT  414395000   Buy   2.354
19  41440.00  BTCUSDT  414400000   Buy   0.854
20  41441.00  BTCUSDT  414410000   Buy   3.300
21  41441.50  BTCUSDT  414415000   Buy  78.825
22  41442.00  BTCUSDT  414420000  Sell   7.434
23  41444.50  BTCUSDT  414445000  Sell   0.081
24  41447.50  BTCUSDT  414475000  Sell  16.000
25  41449.00  BTCUSDT  414490000  Sell   0.744
26  41450.00  BTCUSDT  414500000  Sell   1.097
27  41450.50  BTCUSDT  414505000  Sell   0.035
28  41451.00  BTCUSDT  414510000  Sell   0.677
29  41451.50  BTCUSDT  414515000  Sell   0.331
30  41452.00  BTCUSDT  414520000  Sell   1.350
31  41453.00  BTCUSDT  414530000  Sell   5.000
32  41455.50  BTCUSDT  414555000  Sell   0.679
33  41456.50  BTCUSDT  414565000  Sell   0.020
34  41458.00  BTCUSDT  414580000  Sell   0.837
35  41458.50  BTCUSDT  414585000  Sell   0.334
36  41459.00  BTCUSDT  414590000  Sell   0.003
37  41459.50  BTCUSDT  414595000  Sell   1.687
38  41460.00  BTCUSDT  414600000  Sell   0.641
39  41460.50  BTCUSDT  414605000  Sell   1.337
40  41462.00  BTCUSDT  414620000  Sell   4.463
41  41462.50  BTCUSDT  414625000  Sell   1.948
42  41438.00  BTCUSDT  414380000   Buy   0.719  <--
43  41452.50  BTCUSDT  414525000  Sell   0.570
44  41425.50  BTCUSDT  414255000   Buy   0.050 <--
45  41449.50  BTCUSDT  414495000  Sell   0.777
46  41446.00  BTCUSDT  414460000  Sell   0.010
47  41454.50  BTCUSDT  414545000  Sell   0.007
48  41425.00  BTCUSDT  414250000   Buy   0.362 <--
49  41463.00  BTCUSDT  414630000  Sell   0.010

Maybe pybit should re-sort the local orderbook in _on_message() after each delta updates, or prior to returning the orderbook to the user in the fetch() method and if not, at least tell the user he needs to re-sort the orderbook himself prior to using it.

Issue: Pybit/Bybit API failed to configure a series of leverage value when risk ID is highest

Hi Pybit/Bybit API Admin,

We've noticed a strange behavior in Pybit/Bybit API where certain range of leverage value is constantly rejected by Bybit API despite the new leverage value is different from the existing leverage value. We believe it is an issue Pybit/Bybit API team is interested to look into.

Below is the code to reproduce the issue:

import pybit
from pybit import HTTP
import sys
import time

if __name__ == "__main__":
    mainnet_url = "https://api.bybit.com"
    testnet_url = "https://api-testnet.bybit.com"

    symbol_name = "UNI/USDT"
    symbol_id = symbol_name.replace("/", "")

    # Unauthenticated
    session_unauth = HTTP(endpoint=testnet_url)

    # Authenticated
    session_auth = HTTP(
        endpoint=testnet_url,
        api_key=api_key,
        api_secret=api_secret,
    )

    '''
    Reference: https://help.bybit.com/hc/en-us/articles/360039749753-Risk-Limit-Inverse-Contract-
    Reference: https://help.bybit.com/hc/en-us/articles/900000170023-Risk-Limit-USDT-Contract-
    Reference: https://bybit-exchange.github.io/docs/inverse/?python#t-querysymbol
    Reference: https://bybit-exchange.github.io/docs/linear/#t-querysymbol
    '''
    response = session_auth.query_symbol(
        symbol=symbol_id
    )

    min_leverage = None
    leverage_step = None
    for result in response['result']:
        if result['name'] != symbol_id:
            continue
        min_leverage = float(result['leverage_filter']['min_leverage'])
        leverage_step = float(result['leverage_filter']['leverage_step'])
    assert min_leverage is not None
    assert leverage_step is not None

    print("symbol: {}, min_leverage: {}, leverage_step: {}".format(
        symbol_id, min_leverage, leverage_step,
    ))

    response = session_auth.get_risk_limit(
        symbol=symbol_id
    )

    lowest_risk_id = response['result'][0]['id']
    highest_risk_id = response['result'][-1]['id']
    lowest_risk_max_leverage = response['result'][0]['max_leverage']
    highest_risk_max_leverage = response['result'][-1]['max_leverage']
    print("symbol: {}, lowest_risk_id: {}, lowest_risk_max_leverage: {}, highest_risk_id: {}, highest_risk_max_leverage: {}".format(
        symbol_id, lowest_risk_id, lowest_risk_max_leverage, highest_risk_id, highest_risk_max_leverage,
    ))

    '''
    Reference: https://bybit-exchange.github.io/docs/inverse/#t-myposition
    '''
    position_response = session_auth.my_position(
        symbol=symbol_id
    )
    for result in position_response['result']:
        side = result['side']
        leverage = result['leverage']

        print("symbol: {}, side: {}, leverage: {}, get risk_id: {}".format(
            result['symbol'], side, leverage, result['risk_id']
        ))
        current_risk_id = int(result['risk_id'])
        if current_risk_id != highest_risk_id:
            '''
            Reference: https://bybit-exchange.github.io/docs/inverse/#t-setrisklimit
            '''
            risk_limit_response = session_auth.set_risk_limit(symbol=symbol_id, side=side, risk_id=highest_risk_id)
            print("symbol: {}, side: {}, set risk_id: {}".format(
                symbol_id, side, risk_limit_response['result']['risk_id']
            ))

    # Convert to integer as Python range only accept integer value
    int_min_leverage = int(min_leverage * 100)
    int_highest_risk_max_leverage = int(highest_risk_max_leverage * 100)
    int_leverage_step = int(leverage_step * 100)

    for int_new_leverage in range(int_min_leverage, int_highest_risk_max_leverage, int_leverage_step):
        # Convert from integer back to float
        float_new_leverage = float(int_new_leverage / 100)
        try:
            '''
            Reference: https://bybit-exchange.github.io/docs/linear/#t-setleverage
            '''
            response = session_auth.set_leverage(
                    symbol=symbol_id,
                    buy_leverage=float_new_leverage,
                    sell_leverage=float_new_leverage,
            )
        except pybit.exceptions.InvalidRequestError as e:
            print("symbol: {}, configuring leverage: {} failed with: {}".format(
                symbol_id, float_new_leverage, e.message,
            ))

        time.sleep(1)

Sample output screen:

symbol: UNIUSDT, min_leverage: 1.0, leverage_step: 0.01
symbol: UNIUSDT, lowest_risk_id: 146, lowest_risk_max_leverage: 25, highest_risk_id: 160, highest_risk_max_leverage: 5.56
symbol: UNIUSDT, side: Buy, leverage: 4.01, get risk_id: 160
symbol: UNIUSDT, side: Sell, leverage: 4.01, get risk_id: 160
symbol: UNIUSDT, configuring leverage: 1.13 failed with: leverage not modified
symbol: UNIUSDT, configuring leverage: 2.01 failed with: leverage not modified
symbol: UNIUSDT, configuring leverage: 2.03 failed with: leverage not modified
symbol: UNIUSDT, configuring leverage: 2.05 failed with: leverage not modified
symbol: UNIUSDT, configuring leverage: 2.07 failed with: leverage not modified
symbol: UNIUSDT, configuring leverage: 2.26 failed with: leverage not modified
symbol: UNIUSDT, configuring leverage: 2.28 failed with: leverage not modified
symbol: UNIUSDT, configuring leverage: 2.3 failed with: leverage not modified
symbol: UNIUSDT, configuring leverage: 2.32 failed with: leverage not modified
symbol: UNIUSDT, configuring leverage: 2.51 failed with: leverage not modified
symbol: UNIUSDT, configuring leverage: 2.53 failed with: leverage not modified
symbol: UNIUSDT, configuring leverage: 2.55 failed with: leverage not modified
symbol: UNIUSDT, configuring leverage: 4.02 failed with: leverage not modified
symbol: UNIUSDT, configuring leverage: 4.06 failed with: leverage not modified
symbol: UNIUSDT, configuring leverage: 4.1 failed with: leverage not modified
symbol: UNIUSDT, configuring leverage: 4.14 failed with: leverage not modified
symbol: UNIUSDT, configuring leverage: 4.27 failed with: leverage not modified
symbol: UNIUSDT, configuring leverage: 4.31 failed with: leverage not modified
symbol: UNIUSDT, configuring leverage: 4.35 failed with: leverage not modified
symbol: UNIUSDT, configuring leverage: 4.39 failed with: leverage not modified
symbol: UNIUSDT, configuring leverage: 4.52 failed with: leverage not modified
symbol: UNIUSDT, configuring leverage: 4.56 failed with: leverage not modified
symbol: UNIUSDT, configuring leverage: 4.6 failed with: leverage not modified
symbol: UNIUSDT, configuring leverage: 4.64 failed with: leverage not modified
symbol: UNIUSDT, configuring leverage: 4.77 failed with: leverage not modified
symbol: UNIUSDT, configuring leverage: 4.81 failed with: leverage not modified
symbol: UNIUSDT, configuring leverage: 4.85 failed with: leverage not modified
symbol: UNIUSDT, configuring leverage: 4.89 failed with: leverage not modified
symbol: UNIUSDT, configuring leverage: 5.02 failed with: leverage not modified
symbol: UNIUSDT, configuring leverage: 5.06 failed with: leverage not modified
symbol: UNIUSDT, configuring leverage: 5.1 failed with: leverage not modified

It seems like quite a long list of failure here. Hence, thought process have been considered to first post the issue here prior to Bybit API.

Please allow us to summarize the code logic here:

  1. The risk limit is configured to highest number.
  2. Swept the range of leverage from min_leverage to max_leverage using leverage_step. All of these values are data driven from Pybit API. There is no hardcoded value here.
  3. Noticed the leverage failed to be configured at long list of values above.

Hypothesis
There seems to be some kind of previous leverage versus new leverage checking failed behind Pybit/Bybit API. When the max_leverage is 5.56, the precision on the leverage_step is crucial when comparing previous leverage versus new leverage value. We suspect it is related to precision of the checking performed. It is not clear to us how these values are related to each other but chances are they are related to precision that do not correlate to leverage_step value. Naturally, Bybit API team should be in better position to confirm and comment than us.

Websocket logger error from callback

Greets,

I'm getting the following error from the logging module when a stoploss order is placed or modified on the testnet. I can only presume that the bybit websocket is passing back 'stop_order_id' as a parameter which is not expected?

ERROR - error from callback <bound method WebSocket._on_message of <pybit.WebSocket object at 0x1a1b442ad0>>: 'stop_order_id'

From how I'm reading the docs - it sends back the order id as 'order_id' rather than 'stop_order_id'

Conditional order placement and modify orders are working fine for me BTW, the error just appears when orders are sent / modified.

Any idea why this is happening?

Thanks

malloc: can't allocate region

Hi there,

I have pybit v1.1.8 installed in my virtual environment. When I try to run my app I get the following error:

python data.py 
python(2411,0x112488e00) malloc: can't allocate region
:*** mach_vm_map(size=18446744071964418048, flags: 100) failed (error code=3)
python(2411,0x112488e00) malloc: *** set a breakpoint in malloc_error_break to debug
init_dgelsd failed init
Traceback (most recent call last):
  File "/Users/rr/Developer/Python/bybit/data.py", line 1, in <module>
    from classes.Client import Client
  File "/Users/rr/Developer/Python/bybit/classes/Client.py", line 2, in <module>
    from pybit import WebSocket
  File "/Users/rr/Developer/Python/bybit/env/lib/python3.9/site-packages/pybit/__init__.py", line 24, in <module>
    import websocket
  File "/Users/rr/Developer/Python/bybit/env/lib/python3.9/site-packages/websocket/__init__.py", line 22, in <module>
    from ._abnf import *
  File "/Users/rr/Developer/Python/bybit/env/lib/python3.9/site-packages/websocket/_abnf.py", line 34, in <module>
    import numpy
  File "/Users/rr/Developer/Python/bybit/env/lib/python3.9/site-packages/numpy/__init__.py", line 286, in <module>
    raise RuntimeError(msg)
RuntimeError: Polyfit sanity test emitted a warning, most likely due to using a buggy Accelerate backend. If you compiled yourself, see site.cfg.example for information. Otherwise report this to the vendor that provided NumPy.
RankWarning: Polyfit may be poorly conditioned

My data.py has the following code. When I commented line 2 the erorr disappears:

import bybit
from pybit import WebSocket
from config import API_KEY, API_SECRET

Do you have any idea what the issue can be?
Running python 3.9.0
Thanks for th help.

New APIs released by bybit can you please modify it in the code.

  1. The following WebSocket topics will be removed from service:

a) orderBook25 (please replace with topic orderBookL2_25 or

orderBook_200.100ms)

b) kline (please replace with topic klineV2)

c) instrument (please replace with topic instrument_info)

  1. The following REST API will be upgraded. The older version of API will be deprecated on December 10, 2020. Effective immediately, the rate limit will be reduced by half before being completely removed from service on December 17, 2020. Hence, please use the following new REST API endpoints:

a) /open-api/order/list (please replace with /v2/private/order/list)

b) /open-api/stop-order/create (please replace with /v2/private/stop-order/create)

c) /open-api/stop-order/list (please replace with /v2/private/stop-order/list)

d) /open-api/stop-order/cancel (please replace with /v2/private/stop-order/cancel)

e) /open-api/order/replace (please replace with /v2/private/order/replace)

f) /open-api/stop-order/replace (please replace with /v2/private/stop-order/replace)

g) /open-api/order/create (please replace with /v2/private/order/create)

h) /open-api/order/cancel (please replace with /v2/private/order/cancel)

i) /position/list (please replace with /v2/private/position/list)

j) /user/leverage (please replace with /v2/private/position/list)

Unable to get ws kline data

Hi! I was trying to get kline data via the perpetual websocket, it connects without error but the responses thereafter are just empty dicts. Don't know if your supporting perpetual klines?
This is the code I'm using:

from pybit import WebSocket
endpoint = 'wss://stream.bybit.com/realtime_public'
sub =  'candle.1.BTCUSDT'
ws_unauth = WebSocket(endpoint, subscriptions=sub)

while True:
    try:
        snap=ws_unauth.fetch('candle.1.BTCUSDT')
        pprint(snap)
        sleep(1)
    except: print('-')

and this is the response:

2020-10-20 18:45:58 - pybit - INFO - Initializing WebSocket.
{}
2020-10-20 18:46:00 - pybit - INFO - Subscription to ['candle.1.BTCUSDT'] successful.
{}
{}
{}
{}

It continues without change.

Also the orderbookL2 endpoint is spitting out this:

2020-10-20 18:48:57 - websocket - ERROR - error from callback <bound method WebSocket._on_message of <pybit.WebSocket object at 0x7fe3d9c760d0>>: string indices must be integers

Thanks in advance and for the awesome library my man!

Place conditional order: encoding without a string argument

Just came over from the Binance API and I'm just trying to place an order.
Exeption reads as : TypeError: "encoding without a string argument"
def placeTrade(self, params) -> dict: """place trade""" try: res = self._client.place_conditional_order( symbol=params["symbol"], order_type=params["order_type"], side=params["side"], qty=params["qty"], price=params["price"], base_price = params["base_price"], stop_px=params["price"], time_in_force='GoodTillCancel', close_on_trigger=True, reduce_only = False, take_profit=params["take_profit"], stop_loss=params["stop_loss"], trigger_by='last_price') except Exception as e: print(e) return {} else: return res

Issue: max_leverage of Query Symbol does not change even after risk_id change using set_risk_limit

Hi Pybit Admin,

It has been discovered that the max_leverage value from Query Symbol always exhibit the highest maximum leverage value despite the set_risk_limit has been switched from lowest Risk ID to highest Risk ID.

import inspect
from pprint import pprint

from pybit import HTTP

bybit_testnet_authentication = {
    'key'     : api_key,
    'secret'  : api_secret
}

def print_symbol_leverage(session_auth):
    '''
    Reference: https://help.bybit.com/hc/en-us/articles/360039749753-Risk-Limit-Inverse-Contract-
    Reference: https://help.bybit.com/hc/en-us/articles/900000170023-Risk-Limit-USDT-Contract-
    Reference: https://bybit-exchange.github.io/docs/inverse/?python#t-querysymbol
    Reference: https://bybit-exchange.github.io/docs/linear/#t-querysymbol
    '''
    response = session_auth.query_symbol(
        symbol=symbol_id
    )

    for result in response['result']:
        if result['name'] != symbol_id:
            continue

        print("symbol_id: {}, leverage_filter:".format(symbol_id))
        pprint(result['leverage_filter'])


if __name__ == "__main__":
    api_key = bybit_testnet_authentication['key']
    api_secret = bybit_testnet_authentication['secret']

    mainnet_url = "https://api.bybit.com"
    testnet_url = "https://api-testnet.bybit.com"

    symbol_name = "UNI/USDT"
    symbol_id = symbol_name.replace("/", "")

    # Unauthenticated
    session_unauth = HTTP(endpoint=testnet_url)

    # Authenticated
    session_auth = HTTP(
        endpoint=testnet_url,
        api_key=api_key,
        api_secret=api_secret,
    )

    response = session_auth.get_risk_limit(
        symbol=symbol_id
    )
    # print("Risk Limit:")
    # pprint(response['result'])

    # for result in response['result']:
    #     print("symbol: {}, risk_id: {}, max_leverage: {}".format(
    #         result['symbol'], result['id'], result['max_leverage']
    #     ))

    min_risk_id = response['result'][0]['id']
    max_risk_id = response['result'][-1]['id']
    print("symbol: {}, min_risk_id: {}, max_risk_id: {}".format(
        symbol_id, min_risk_id, max_risk_id
    ))

    print_symbol_leverage(session_auth)

    '''
    Reference: https://bybit-exchange.github.io/docs/inverse/#t-myposition
    '''
    position_response = session_auth.my_position(
        symbol=symbol_id
    )
    # pprint(response['result'])
    for result in position_response['result']:
        side = result['side']

        print("symbol: {}, side: {}, get risk_id: {}".format(
            result['symbol'], side, result['risk_id']
        ))
        current_risk_id = int(result['risk_id'])

        risk_id = min_risk_id
        if current_risk_id == min_risk_id:
            risk_id = max_risk_id

        '''
        Reference: https://bybit-exchange.github.io/docs/inverse/#t-setrisklimit
        '''
        risk_limit_response = session_auth.set_risk_limit(symbol=symbol_id, side=side, risk_id=risk_id)
        print("symbol: {}, side: {}, set risk_id: {}".format(
            symbol_id, side, risk_limit_response['result']['risk_id']
        ))

    print_symbol_leverage(session_auth)

Sample output:

symbol: UNIUSDT, min_risk_id: 146, max_risk_id: 160
symbol_id: UNIUSDT, leverage_filter:
{'leverage_step': '0.01', 'max_leverage': 25, 'min_leverage': 1}
symbol: UNIUSDT, side: Buy, get risk_id: 146
symbol: UNIUSDT, side: Buy, set risk_id: 160
symbol: UNIUSDT, side: Sell, get risk_id: 146
symbol: UNIUSDT, side: Sell, set risk_id: 160
symbol_id: UNIUSDT, leverage_filter:
{'leverage_step': '0.01', 'max_leverage': 25, 'min_leverage': 1}

"max_leverage" seems to be stuck at value of 25 forever, which when looking back at Query Symbol, it is designated as public instead of private API which makes sense for the misalignment.

Then the applicability of this "max_leverage" value in "leverage_filter" is in question as user cannot rely on this "max_leverage" value as reliable information to trade as it could be in conflict with "max_leverage" in risk limit provided IF the risk ID is set to different value other that lowest risk ID. We know assuming symbol to exercise lowest risk ID is NOT always guaranteed as user could set risk limit to other risk ID.

Since "max_leverage" value in "leverage_filter" of Query Symbol does not serve any valid use case other than capturing the maximum leverage value of lowest risk ID, do you think it make more sense to remove "max_leverage" entry from "leverage_filter"?

Please feel free to comment if you have other thoughts.

Standard post-only Limit orders on linear USDT being rejected ('reduce_only' test fails!)

Greets,
A puzzling problem here - I am hoping it's something small I have missed... if so please let me know!

Trying: send 3 post only limit orders to open a position, using place_active_order_bulk()

As reduce_only is not necessary, or even logical to have in a limit order that would increse a position of 0 to 0.008 BTC if filled - this error makes no sense to me!

The orders being passed to place_active_order_bulk() are:
[{'symbol': 'BTCUSDT', 'order_type': 'Limit', 'side': 'Buy', 'qty': 0.008, 'price': 45555.0, 'time_in_force': 'PostOnly'}, {'symbol': 'BTCUSDT', 'order_type': 'Limit', 'side': 'Buy', 'qty': 0.008, 'price': 44321.0, 'time_in_force': 'PostOnly'}, {'symbol': 'BTCUSDT', 'order_type': 'Limit', 'side': 'Buy', 'qty': 0.008, 'price': 43434.0, 'time_in_force': 'PostOnly'}]

error is:

Param validation for 'reduce_only' failed on the 'exists' tag (ErrCode: 10001) (ErrTime: 12:46:31).

Request → POST https://api-testnet.bybit.com/private/linear/order/create: {'api_key': 'xxxxxxx', 'order_type': 'Limit', 'price': 45555, 'qty': 0.008, 'recv_window': 5000, 'side': 'Buy', 'symbol': 'BTCUSDT', 'time_in_force': 'PostOnly', 'timestamp': 1619786791191, 'sign': 'xxxxxxx'}.

Any help welcome!

Orderbook Snapshot Error

Line 1819 should be changed

self.data[topic] = msg_json['data'] -> self.data[topic] = msg_json['data']['order_book']

Bug: Param validation for 'from' failed on the 'required' tag

Just stumbled upon this repo toady, really appreciate everyone's work!

When using query_kline, query_mark_price_kline on the HTTP class, trying to use from in **kwargs will throw a SyntaxError since from is a reserved keyword in Python.

Steps to reproduce:

import time
from pybit import HTTP

if __name__ == "__main__":
    session = HTTP("https://api.bybit.com")
    session.query_kline(symbol="BTCUSD", interval="1", from=int(time.time()) - 60 * 60)

I noticed you've used from_time in the tests, but it appears the tests have gone stale. Will make a PR today.

get_wallet_balance(spot=True) gives empty results when sub account has no balances

This bug is related to spot sub accounts only.

Pybit version: 1.3.0

How to reproduce:

Make sure the spot section of a sub account has no balances, then try to query its balance with get_wallet_balance(spot=True) using the sub account's API keys. Instead of getting a well formed output showing zero balances you will get the following empty output instead:

{'ret_code': 0, 'ret_msg': '', 'ext_code': None, 'ext_info': None, 'result': {'balances': []}}

Query KLine parameter "from"

I don't know how I am supposed to pass the parameter "from" on the query_kline() method, as "from" is a python keyword.

I have tried passing it anyway, and it obviously gives a syntax error:

File "path/to/file.py", line 19
session.query_kline(symbol="BTCUSD", interval=interval, from=timestamp)
^
SyntaxError: invalid syntax

I am probably missing something, since no one pointed out this issue, so I am hoping someone will help me.

ws.fetch("position") returns buy and sell side data when short

Hello,

Firstly thanks for pybit, I'm building code around it nicely, but I have one area of confusion when getting position data via the websocket.

During tests, I had the bot enter 20 contracts long and then 40 contracts short to have the position be short/sell side.
However, ws.fetch("position") returned data for both the Buy and Sell sides of 20 contracts each. Is there something I'm missing? This doesn't reflect the actual position of the account at that time, which should only have data for the Sell side?
Here is the result of a fetch on the (BTCUSD) position so you can see what I mean.

`

'Buy': 
    {'user_id': 11111, 'symbol': 'BTCUSD', 'size': 20, 'side': 'Buy', 'position_value': '0.00059908', 
    'entry_price': '33384.52293517', 'liq_price': '16734.5', 'bust_price': '16692.5', 'leverage': '1', 'order_margin': '0', 'position_margin': '0.00059908', 
    'available_balance': '1.59463741', 'take_profit': '0', 'stop_loss': '0', 'realised_pnl': '0.000002', 'trailing_stop': '0', 'trailing_active': '0', 
    'wallet_balance': '1.59523739', 'risk_id': 1, 'occ_closing_fee': '0.0000009', 'occ_funding_fee': '0', 'auto_add_margin': 0, 
    'cum_realised_pnl': '0.00013621', 'position_status': 'Normal', 'position_seq': 0, 'Isolated': True, 'mode': 0, 'position_idx': 0
    }, 

'Sell': 
    {'user_id': 11111, 'symbol': 'BTCUSD', 'size': 20, 'side': 'Sell', 'position_value': '0.00059725', 'entry_price': '33486.81456676', 
    'liq_price': '999999', 'bust_price': '999999', 'leverage': '1', 'order_margin': '0', 'position_margin': '0.00059723', 'available_balance': '1.59464108', 
    'take_profit': '0', 'stop_loss': '0', 'realised_pnl': '0.00000294', 'trailing_stop': '0', 'trailing_active': '0', 'wallet_balance': '1.59523833', 
    'risk_id': 1, 'occ_closing_fee': '0.00000002', 'occ_funding_fee': '0', 'auto_add_margin': 0, 'cum_realised_pnl': '0.00013715', 'position_status': 'Normal', 
    'position_seq': 0, 'Isolated': True, 'mode': 0, 'position_idx': 0
    }

`

Any help / pointers as to why the data returned does not only have the "Sell" side information gladly received!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.