mirror of
https://github.com/aljazceru/bitfinex-api-py.git
synced 2025-12-22 08:14:20 +01:00
Merge branch 'master' of https://github.com/bitfinexcom/bitfinex-api-py
This commit is contained in:
36
.github/workflows/python-app.yml
vendored
Normal file
36
.github/workflows/python-app.yml
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
# This workflow will install Python dependencies, run tests and lint with a single version of Python
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
|
||||
|
||||
name: Python application
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python 3.9
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.9
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install flake8 pytest
|
||||
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
|
||||
- name: Lint with flake8
|
||||
run: |
|
||||
# stop the build if there are Python syntax errors or undefined names
|
||||
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
|
||||
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
|
||||
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
|
||||
- name: Test with pytest
|
||||
run: |
|
||||
pytest
|
||||
68
CHANGELOG
68
CHANGELOG
@@ -1,3 +1,71 @@
|
||||
2.0.3
|
||||
-) Implemented Liquidations endpoint (REST)
|
||||
|
||||
2.0.2
|
||||
-) Use private host for auth-based requests
|
||||
|
||||
2.0.1
|
||||
-) Added User Settings Write/Read/Delete endpoints (REST)
|
||||
-) Added Balance Available for Orders/Offers endpoint (REST)
|
||||
-) Added Alerts endpoints (REST)
|
||||
-) Fixed trades handling error
|
||||
|
||||
2.0.0
|
||||
-) Implemented Movement endpoints (REST)
|
||||
-) Fixed unawaited stop
|
||||
-) Changed account's trade execution (te) and trade update (tu) handling
|
||||
|
||||
1.3.4
|
||||
-) Fixed undefined p_sub issue in subscription_manager.py
|
||||
-) Added submit cancel all funding orders endpoint (REST)
|
||||
-) Added get all exchange pairs endpoint (REST)
|
||||
|
||||
1.3.3
|
||||
-) Fixed socket.send() issue (IndexError: deque index out of range)
|
||||
|
||||
1.3.2
|
||||
-) Implemented Merchants endpoints (REST)
|
||||
|
||||
1.3.1
|
||||
-) Handle exception of asyncio.get_event_loop() | Related to v1.2.8
|
||||
|
||||
1.3.0
|
||||
-) Adjusted get_trades() to allow symbol to be None and get trades for all symbols
|
||||
|
||||
1.2.8
|
||||
-) Bugfix - It is possible to call bfx.ws.run() from an already running event loop
|
||||
|
||||
1.2.7
|
||||
-) Added ws support for Python 3.9 and 3.10
|
||||
|
||||
1.2.6
|
||||
-) Updated websockets to 9.1
|
||||
|
||||
1.2.5
|
||||
-) Adjusted get_order_history() rest endpoint
|
||||
|
||||
1.2.4
|
||||
-) Added example of MARKET order with price=None
|
||||
|
||||
1.2.3
|
||||
-) Tests adjusted
|
||||
|
||||
1.2.2
|
||||
-) WS bugfix (exception InvalidStatusCode not handled)
|
||||
|
||||
1.2.1
|
||||
-) Added orderbook implementation example (ws)
|
||||
|
||||
1.2.0
|
||||
-) Implemented Margin Info (rest)
|
||||
-) Implemented claim position (rest)
|
||||
-) When max_retries == 0 continue forever to retry (websocket)
|
||||
|
||||
1.1.15
|
||||
-) Added 'ids' parameter to get_order_history()
|
||||
-) Added an example to show how it is possible to spawn multiple bfx ws instances to comply with the open subscriptions number constraint (max. 25)
|
||||
-) Implemented Funding Trades (rest)
|
||||
|
||||
1.1.14
|
||||
-) bfx_websockets.py ERRORS dictionary now contains a message for error number 10305
|
||||
|
||||
|
||||
@@ -3,9 +3,10 @@ This module is used to interact with the bitfinex api
|
||||
"""
|
||||
|
||||
from .version import __version__
|
||||
from .client import Client
|
||||
from .client import Client, PUB_REST_HOST, PUB_WS_HOST, REST_HOST, WS_HOST
|
||||
from .models import (Order, Trade, OrderBook, Subscription, Wallet,
|
||||
Position, FundingLoan, FundingOffer, FundingCredit)
|
||||
Position, FundingLoan, FundingOffer, FundingCredit,
|
||||
Movement)
|
||||
from .websockets.generic_websocket import GenericWebsocket, Socket
|
||||
from .websockets.bfx_websocket import BfxWebsocket
|
||||
from .utils.decimal import Decimal
|
||||
|
||||
@@ -5,13 +5,9 @@ a websocket client and a rest interface client
|
||||
|
||||
# pylint: disable-all
|
||||
|
||||
import asyncio
|
||||
|
||||
from .websockets.bfx_websocket import BfxWebsocket
|
||||
from .rest.bfx_rest import BfxRest
|
||||
|
||||
REST_HOST = 'https://api-pub.bitfinex.com/v2'
|
||||
WS_HOST = 'wss://api-pub.bitfinex.com/ws/2'
|
||||
from .constants import *
|
||||
|
||||
class Client:
|
||||
"""
|
||||
|
||||
4
bfxapi/constants.py
Normal file
4
bfxapi/constants.py
Normal file
@@ -0,0 +1,4 @@
|
||||
REST_HOST = 'https://api.bitfinex.com/v2'
|
||||
WS_HOST = 'wss://api.bitfinex.com/ws/2'
|
||||
PUB_REST_HOST = 'https://api-pub.bitfinex.com/v2'
|
||||
PUB_WS_HOST = 'wss://api-pub.bitfinex.com/ws/2'
|
||||
@@ -1,18 +1,21 @@
|
||||
import os
|
||||
import sys
|
||||
import asyncio
|
||||
import time
|
||||
sys.path.append('../../../')
|
||||
|
||||
from bfxapi import Client
|
||||
from bfxapi.constants import WS_HOST, REST_HOST
|
||||
|
||||
API_KEY=os.getenv("BFX_KEY")
|
||||
API_SECRET=os.getenv("BFX_SECRET")
|
||||
|
||||
# Create funding requires private hosts
|
||||
bfx = Client(
|
||||
API_KEY=API_KEY,
|
||||
API_SECRET=API_SECRET,
|
||||
logLevel='DEBUG'
|
||||
logLevel='DEBUG',
|
||||
ws_host=WS_HOST,
|
||||
rest_host=REST_HOST
|
||||
)
|
||||
|
||||
async def create_funding():
|
||||
|
||||
@@ -4,18 +4,23 @@ import asyncio
|
||||
import time
|
||||
sys.path.append('../../../')
|
||||
from bfxapi import Client
|
||||
from bfxapi.constants import WS_HOST, REST_HOST
|
||||
from bfxapi.models import OrderType
|
||||
|
||||
API_KEY=os.getenv("BFX_KEY")
|
||||
API_SECRET=os.getenv("BFX_SECRET")
|
||||
|
||||
# Create order requires private hosts
|
||||
bfx = Client(
|
||||
API_KEY=API_KEY,
|
||||
API_SECRET=API_SECRET,
|
||||
logLevel='DEBUG'
|
||||
logLevel='DEBUG',
|
||||
ws_host=WS_HOST,
|
||||
rest_host=REST_HOST
|
||||
)
|
||||
|
||||
async def create_order():
|
||||
response = await bfx.rest.submit_order("tBTCUSD", 10, 0.1)
|
||||
response = await bfx.rest.submit_order(symbol="tBTCUSD", amount=10, price=None, market_type=OrderType.MARKET)
|
||||
# response is in the form of a Notification object
|
||||
for o in response.notify_info:
|
||||
# each item is in the form of an Order object
|
||||
|
||||
@@ -5,14 +5,18 @@ import time
|
||||
sys.path.append('../../../')
|
||||
|
||||
from bfxapi import Client
|
||||
from bfxapi.constants import WS_HOST, REST_HOST
|
||||
|
||||
API_KEY=os.getenv("BFX_KEY")
|
||||
API_SECRET=os.getenv("BFX_SECRET")
|
||||
|
||||
# Retrieving authenticated data requires private hosts
|
||||
bfx = Client(
|
||||
API_KEY=API_KEY,
|
||||
API_SECRET=API_SECRET,
|
||||
logLevel='DEBUG'
|
||||
logLevel='DEBUG',
|
||||
ws_host=WS_HOST,
|
||||
rest_host=REST_HOST
|
||||
)
|
||||
|
||||
now = int(round(time.time() * 1000))
|
||||
@@ -39,7 +43,7 @@ async def log_active_positions():
|
||||
[ print (p) for p in positions ]
|
||||
|
||||
async def log_trades():
|
||||
trades = await bfx.rest.get_trades('tBTCUSD', 0, then)
|
||||
trades = await bfx.rest.get_trades(symbol='tBTCUSD', start=0, end=then)
|
||||
print ("Trades:")
|
||||
[ print (t) for t in trades]
|
||||
|
||||
@@ -79,6 +83,15 @@ async def log_funding_credits_history():
|
||||
print ("Funding credit history:")
|
||||
[ print (c) for c in credit ]
|
||||
|
||||
async def log_margin_info():
|
||||
margin_info = await bfx.rest.get_margin_info('tBTCUSD')
|
||||
print(margin_info)
|
||||
sym_all = await bfx.rest.get_margin_info('sym_all') # list of Margin Info
|
||||
for margin_info in sym_all:
|
||||
print(margin_info)
|
||||
base = await bfx.rest.get_margin_info('base')
|
||||
print(base)
|
||||
|
||||
async def run():
|
||||
await log_wallets()
|
||||
await log_active_orders()
|
||||
@@ -90,6 +103,7 @@ async def run():
|
||||
await log_funding_offer_history()
|
||||
await log_funding_credits()
|
||||
await log_funding_credits_history()
|
||||
await log_margin_info()
|
||||
|
||||
|
||||
t = asyncio.ensure_future(run())
|
||||
|
||||
21
bfxapi/examples/rest/get_liquidations.py
Normal file
21
bfxapi/examples/rest/get_liquidations.py
Normal file
@@ -0,0 +1,21 @@
|
||||
import os
|
||||
import sys
|
||||
import asyncio
|
||||
import time
|
||||
sys.path.append('../../../')
|
||||
|
||||
from bfxapi import Client, PUB_REST_HOST
|
||||
|
||||
bfx = Client(
|
||||
logLevel='INFO',
|
||||
rest_host=PUB_REST_HOST
|
||||
)
|
||||
|
||||
now = int(round(time.time() * 1000))
|
||||
then = now - (1000 * 60 * 60 * 24 * 10) # 10 days ago
|
||||
|
||||
async def get_liquidations():
|
||||
liquidations = await bfx.rest.get_liquidations(start=then, end=now)
|
||||
print(liquidations)
|
||||
|
||||
asyncio.get_event_loop().run_until_complete(get_liquidations())
|
||||
@@ -5,9 +5,13 @@ import time
|
||||
sys.path.append('../../../')
|
||||
|
||||
from bfxapi import Client
|
||||
from bfxapi.constants import PUB_WS_HOST, PUB_REST_HOST
|
||||
|
||||
# Retrieving public data requires public hosts
|
||||
bfx = Client(
|
||||
logLevel='DEBUG',
|
||||
ws_host=PUB_WS_HOST,
|
||||
rest_host=PUB_REST_HOST
|
||||
)
|
||||
|
||||
now = int(round(time.time() * 1000))
|
||||
|
||||
@@ -4,9 +4,13 @@ import asyncio
|
||||
sys.path.append('../../../')
|
||||
|
||||
from bfxapi import Client
|
||||
from bfxapi.constants import PUB_WS_HOST, PUB_REST_HOST
|
||||
|
||||
# Retrieving seed trades requires public hosts
|
||||
bfx = Client(
|
||||
logLevel='INFO'
|
||||
logLevel='INFO',
|
||||
ws_host=PUB_WS_HOST,
|
||||
rest_host=PUB_REST_HOST
|
||||
)
|
||||
|
||||
async def get_seeds():
|
||||
37
bfxapi/examples/rest/merchant.py
Normal file
37
bfxapi/examples/rest/merchant.py
Normal file
@@ -0,0 +1,37 @@
|
||||
import os
|
||||
import sys
|
||||
import asyncio
|
||||
sys.path.append('../../../')
|
||||
from bfxapi import Client
|
||||
from bfxapi.constants import WS_HOST, REST_HOST
|
||||
|
||||
API_KEY=os.getenv("BFX_KEY")
|
||||
API_SECRET=os.getenv("BFX_SECRET")
|
||||
|
||||
# Submitting invoices requires private hosts
|
||||
bfx = Client(
|
||||
API_KEY=API_KEY,
|
||||
API_SECRET=API_SECRET,
|
||||
logLevel='DEBUG',
|
||||
ws_host=WS_HOST,
|
||||
rest_host=REST_HOST
|
||||
)
|
||||
|
||||
async def run():
|
||||
await bfx.rest.submit_invoice(amount='2.0', currency='USD', pay_currencies=['BTC', 'ETH'], order_id='order123', webhook='https://example.com/api/v3/order/order123',
|
||||
redirect_url='https://example.com/api/v3/order/order123', customer_info_nationality='DE',
|
||||
customer_info_resid_country='GB', customer_info_resid_city='London', customer_info_resid_zip_code='WC2H 7NA',
|
||||
customer_info_resid_street='5-6 Leicester Square', customer_info_resid_building_no='23 A',
|
||||
customer_info_full_name='John Doe', customer_info_email='john@example.com', duration=86339)
|
||||
|
||||
invoices = await bfx.rest.get_invoices()
|
||||
print(invoices)
|
||||
|
||||
# await bfx.rest.complete_invoice(id=invoices[0]['id'], pay_ccy='BTC', deposit_id=1357996)
|
||||
|
||||
unlinked_deposits = await bfx.rest.get_unlinked_deposits(ccy='BTC')
|
||||
print(unlinked_deposits)
|
||||
|
||||
|
||||
t = asyncio.ensure_future(run())
|
||||
asyncio.get_event_loop().run_until_complete(t)
|
||||
@@ -1,18 +1,21 @@
|
||||
import os
|
||||
import sys
|
||||
import asyncio
|
||||
import time
|
||||
sys.path.append('../../../')
|
||||
|
||||
from bfxapi import Client
|
||||
from bfxapi.constants import WS_HOST, REST_HOST
|
||||
|
||||
API_KEY=os.getenv("BFX_KEY")
|
||||
API_SECRET=os.getenv("BFX_SECRET")
|
||||
|
||||
# Transfer wallet requires private hosts
|
||||
bfx = Client(
|
||||
API_KEY=API_KEY,
|
||||
API_SECRET=API_SECRET,
|
||||
logLevel='DEBUG'
|
||||
logLevel='DEBUG',
|
||||
ws_host=WS_HOST,
|
||||
rest_host=REST_HOST
|
||||
)
|
||||
|
||||
async def transfer_wallet():
|
||||
|
||||
@@ -3,14 +3,18 @@ import sys
|
||||
sys.path.append('../../../')
|
||||
|
||||
from bfxapi import Client, Order
|
||||
from bfxapi.constants import WS_HOST, REST_HOST
|
||||
|
||||
API_KEY=os.getenv("BFX_KEY")
|
||||
API_SECRET=os.getenv("BFX_SECRET")
|
||||
|
||||
# Canceling orders requires private hosts
|
||||
bfx = Client(
|
||||
API_KEY=API_KEY,
|
||||
API_SECRET=API_SECRET,
|
||||
logLevel='DEBUG'
|
||||
logLevel='DEBUG',
|
||||
ws_host=WS_HOST,
|
||||
rest_host=REST_HOST
|
||||
)
|
||||
|
||||
@bfx.ws.on('order_closed')
|
||||
|
||||
@@ -3,9 +3,12 @@ import sys
|
||||
sys.path.append('../../../')
|
||||
|
||||
from bfxapi import Client
|
||||
from bfxapi.constants import PUB_WS_HOST, PUB_REST_HOST
|
||||
|
||||
bfx = Client(
|
||||
logLevel='DEBUG'
|
||||
logLevel='DEBUG',
|
||||
ws_host=PUB_WS_HOST,
|
||||
rest_host=PUB_REST_HOST
|
||||
)
|
||||
|
||||
@bfx.ws.on('error')
|
||||
|
||||
@@ -2,7 +2,8 @@ import os
|
||||
import sys
|
||||
sys.path.append('../../../')
|
||||
|
||||
from bfxapi import Client, Order
|
||||
from bfxapi import Client
|
||||
from bfxapi.constants import WS_HOST, REST_HOST
|
||||
|
||||
API_KEY=os.getenv("BFX_KEY")
|
||||
API_SECRET=os.getenv("BFX_SECRET")
|
||||
@@ -11,6 +12,8 @@ bfx = Client(
|
||||
API_KEY=API_KEY,
|
||||
API_SECRET=API_SECRET,
|
||||
logLevel='DEBUG',
|
||||
ws_host=WS_HOST,
|
||||
rest_host=REST_HOST,
|
||||
dead_man_switch=True, # <-- kill all orders if this connection drops
|
||||
channel_filter=['wallet'] # <-- only receive wallet updates
|
||||
)
|
||||
|
||||
83
bfxapi/examples/ws/full_orderbook.py
Normal file
83
bfxapi/examples/ws/full_orderbook.py
Normal file
@@ -0,0 +1,83 @@
|
||||
import sys
|
||||
import time
|
||||
from collections import OrderedDict
|
||||
sys.path.append('../../../')
|
||||
|
||||
from bfxapi import Client
|
||||
from bfxapi.constants import PUB_WS_HOST, PUB_REST_HOST
|
||||
|
||||
# Retrieving orderbook requires public hosts
|
||||
bfx = Client(
|
||||
manageOrderBooks=True,
|
||||
ws_host=PUB_WS_HOST,
|
||||
rest_host=PUB_REST_HOST
|
||||
)
|
||||
|
||||
class OrderBook:
|
||||
def __init__(self, snapshot):
|
||||
self.bids = OrderedDict()
|
||||
self.asks = OrderedDict()
|
||||
self.load(snapshot)
|
||||
|
||||
def load(self, snapshot):
|
||||
for record in snapshot:
|
||||
if record[2] >= 0:
|
||||
self.bids[record[0]] = {
|
||||
'count': record[1],
|
||||
'amount': record[2]
|
||||
}
|
||||
else:
|
||||
self.asks[record[0]] = {
|
||||
'count': record[1],
|
||||
'amount': record[2]
|
||||
}
|
||||
|
||||
def update(self, record):
|
||||
# count is 0
|
||||
if record[1] == 0:
|
||||
if record[2] == 1:
|
||||
# remove from bids
|
||||
del self.bids[record[0]]
|
||||
elif record[2] == -1:
|
||||
# remove from asks
|
||||
del self.asks[record[0]]
|
||||
elif record[1] > 0:
|
||||
if record[2] > 0:
|
||||
# update bids
|
||||
if record[0] not in self.bids:
|
||||
self.bids[record[0]] = {}
|
||||
self.bids[record[0]]['count'] = record[1]
|
||||
self.bids[record[0]]['amount'] = record[2]
|
||||
elif record[2] < 0:
|
||||
# update asks
|
||||
if record[0] not in self.asks:
|
||||
self.asks[record[0]] = {}
|
||||
self.asks[record[0]]['count'] = record[1]
|
||||
self.asks[record[0]]['amount'] = record[2]
|
||||
|
||||
obs = {}
|
||||
|
||||
@bfx.ws.on('error')
|
||||
def log_error(err):
|
||||
print ("Error: {}".format(err))
|
||||
|
||||
@bfx.ws.on('order_book_update')
|
||||
def log_update(data):
|
||||
obs[data['symbol']].update(data['data'])
|
||||
|
||||
@bfx.ws.on('order_book_snapshot')
|
||||
def log_snapshot(data):
|
||||
obs[data['symbol']] = OrderBook(data['data'])
|
||||
|
||||
async def start():
|
||||
await bfx.ws.subscribe('book', 'tBTCUSD')
|
||||
|
||||
bfx.ws.on('connected', start)
|
||||
bfx.ws.run()
|
||||
|
||||
for n in range(0, 10):
|
||||
time.sleep(2)
|
||||
for key in obs:
|
||||
print(f"Printing {key} orderbook...")
|
||||
print(f"{obs[key].bids}\n")
|
||||
print(f"{obs[key].asks}\n")
|
||||
180
bfxapi/examples/ws/multiple_instances.py
Normal file
180
bfxapi/examples/ws/multiple_instances.py
Normal file
@@ -0,0 +1,180 @@
|
||||
"""
|
||||
This is an example of how it is possible to spawn multiple
|
||||
bfx ws instances to comply with the open subscriptions number constraint (max. 25)
|
||||
|
||||
(https://docs.bitfinex.com/docs/requirements-and-limitations)
|
||||
"""
|
||||
|
||||
import sys
|
||||
sys.path.append('../../../')
|
||||
import asyncio
|
||||
from functools import partial
|
||||
import websockets as ws
|
||||
from bfxapi import Client
|
||||
from bfxapi.constants import PUB_WS_HOST, PUB_REST_HOST
|
||||
import math
|
||||
import random
|
||||
|
||||
MAX_CHANNELS = 25
|
||||
|
||||
|
||||
def get_random_list_of_tickers():
|
||||
tickers = ["FILUST", "FTTUSD", "FTTUST", "FUNUSD", "GNOUSD", "GNTUSD", "GOTEUR", "GOTUSD", "GTXUSD", "ZRXUSD"]
|
||||
return random.sample(tickers, 1)
|
||||
|
||||
|
||||
class Instance:
|
||||
def __init__(self, _id):
|
||||
self.id = _id
|
||||
self.bfx = Client(logLevel='INFO', ws_host=PUB_WS_HOST, rest_host=PUB_REST_HOST)
|
||||
self.subscriptions = {'trades': {}, 'ticker': {}}
|
||||
self.is_ready = False
|
||||
|
||||
def run(self):
|
||||
self.bfx.ws.run()
|
||||
self.bfx.ws.on('error', log_error)
|
||||
self.bfx.ws.on('new_trade', log_trade)
|
||||
self.bfx.ws.on('new_ticker', log_ticker)
|
||||
self.bfx.ws.on('subscribed', partial(on_subscribe, self))
|
||||
self.bfx.ws.on('unsubscribed', partial(on_unsubscribed, self))
|
||||
self.bfx.ws.on('connected', partial(on_connected, self))
|
||||
self.bfx.ws.on('stopped', partial(on_stopped, self))
|
||||
|
||||
async def subscribe(self, symbols):
|
||||
for symbol in symbols:
|
||||
print(f'Subscribing to {symbol} channel')
|
||||
await self.bfx.ws.subscribe_ticker(symbol)
|
||||
await self.bfx.ws.subscribe_trades(symbol)
|
||||
self.subscriptions['trades'][symbol] = None
|
||||
self.subscriptions['ticker'][symbol] = None
|
||||
|
||||
async def unsubscribe(self, symbols):
|
||||
for symbol in symbols:
|
||||
if symbol in self.subscriptions['trades']:
|
||||
print(f'Unsubscribing to {symbol} channel')
|
||||
trades_ch_id = self.subscriptions['trades'][symbol]
|
||||
ticker_ch_id = self.subscriptions['ticker'][symbol]
|
||||
if trades_ch_id:
|
||||
await self.bfx.ws.unsubscribe(trades_ch_id)
|
||||
else:
|
||||
del self.subscriptions['trades'][symbol]
|
||||
if ticker_ch_id:
|
||||
await self.bfx.ws.unsubscribe(ticker_ch_id)
|
||||
else:
|
||||
del self.subscriptions['ticker'][symbol]
|
||||
|
||||
|
||||
class Routine:
|
||||
is_stopped = False
|
||||
|
||||
def __new__(cls, _loop, _ws, interval=1, start_delay=10):
|
||||
instance = super().__new__(cls)
|
||||
instance.interval = interval
|
||||
instance.start_delay = start_delay
|
||||
instance.ws = _ws
|
||||
instance.task = _loop.create_task(instance.run())
|
||||
return instance.task
|
||||
|
||||
async def run(self):
|
||||
await asyncio.sleep(self.start_delay)
|
||||
await self.do()
|
||||
while True:
|
||||
await asyncio.sleep(self.interval)
|
||||
await self.do()
|
||||
|
||||
async def do(self):
|
||||
subbed_tickers = get_all_subscriptions_tickers()
|
||||
print(f'Subscribed tickers: {subbed_tickers}')
|
||||
|
||||
# if ticker is not in subbed tickers, then we subscribe to the channel
|
||||
to_sub = [f"t{ticker}" for ticker in get_random_list_of_tickers() if f"t{ticker}" not in subbed_tickers]
|
||||
for ticker in to_sub:
|
||||
print(f'To subscribe: {ticker}')
|
||||
instance = get_available_instance()
|
||||
if instance and instance.is_ready:
|
||||
print(f'Subscribing on instance {instance.id}')
|
||||
await instance.subscribe([ticker])
|
||||
else:
|
||||
instances_to_create = math.ceil(len(to_sub) / MAX_CHANNELS)
|
||||
create_instances(instances_to_create)
|
||||
break
|
||||
|
||||
to_unsub = [f"t{ticker}" for ticker in subbed_tickers if f"t{ticker}" in get_random_list_of_tickers()]
|
||||
if len(to_unsub) > 0:
|
||||
print(f'To unsubscribe: {to_unsub}')
|
||||
for instance in instances:
|
||||
await instance.unsubscribe(to_unsub)
|
||||
|
||||
def stop(self):
|
||||
self.task.cancel()
|
||||
self.is_stopped = True
|
||||
|
||||
|
||||
instances = []
|
||||
|
||||
|
||||
def get_all_subscriptions_tickers():
|
||||
tickers = []
|
||||
for instance in instances:
|
||||
for ticker in instance.subscriptions['trades']:
|
||||
tickers.append(ticker)
|
||||
return tickers
|
||||
|
||||
|
||||
def count_open_channels(instance):
|
||||
return len(instance.subscriptions['trades']) + len(instance.subscriptions['ticker'])
|
||||
|
||||
|
||||
def create_instances(instances_to_create):
|
||||
for _ in range(0, instances_to_create):
|
||||
instance = Instance(len(instances))
|
||||
instance.run()
|
||||
instances.append(instance)
|
||||
|
||||
|
||||
def get_available_instance():
|
||||
for instance in instances:
|
||||
if count_open_channels(instance) + 1 <= MAX_CHANNELS:
|
||||
return instance
|
||||
return None
|
||||
|
||||
|
||||
def log_error(err):
|
||||
print("Error: {}".format(err))
|
||||
|
||||
|
||||
def log_trade(trade):
|
||||
print(trade)
|
||||
|
||||
|
||||
def log_ticker(ticker):
|
||||
print(ticker)
|
||||
|
||||
|
||||
async def on_subscribe(instance, subscription):
|
||||
print(f'Subscribed to {subscription.symbol} channel {subscription.channel_name}')
|
||||
instance.subscriptions[subscription.channel_name][subscription.symbol] = subscription.chan_id
|
||||
|
||||
|
||||
async def on_unsubscribed(instance, subscription):
|
||||
print(f'Unsubscribed to {subscription.symbol} channel {subscription.channel_name}')
|
||||
instance.subscriptions[subscription.channel_name][subscription.symbol] = subscription.chan_id
|
||||
del instance.subscriptions[subscription.channel_name][subscription.symbol]
|
||||
|
||||
|
||||
async def on_connected(instance):
|
||||
print(f"Instance {instance.id} is connected")
|
||||
instance.is_ready = True
|
||||
|
||||
|
||||
async def on_stopped(instance):
|
||||
print(f"Instance {instance.id} is dead, removing it from instances list")
|
||||
instances.pop(instance.id)
|
||||
|
||||
|
||||
def run():
|
||||
loop = asyncio.get_event_loop()
|
||||
task = Routine(loop, ws, interval=5)
|
||||
loop.run_until_complete(task)
|
||||
|
||||
run()
|
||||
@@ -1,11 +1,14 @@
|
||||
import os
|
||||
import sys
|
||||
sys.path.append('../../../')
|
||||
|
||||
from bfxapi import Client
|
||||
from bfxapi.constants import PUB_WS_HOST, PUB_REST_HOST
|
||||
|
||||
# Retrieving orderbook requires public hosts
|
||||
bfx = Client(
|
||||
logLevel='INFO'
|
||||
manageOrderBooks=True,
|
||||
ws_host=PUB_WS_HOST,
|
||||
rest_host=PUB_REST_HOST
|
||||
)
|
||||
|
||||
@bfx.ws.on('error')
|
||||
|
||||
@@ -3,14 +3,18 @@ import sys
|
||||
sys.path.append('../../../')
|
||||
|
||||
from bfxapi import Client, Order
|
||||
from bfxapi.constants import WS_HOST, REST_HOST
|
||||
|
||||
API_KEY=os.getenv("BFX_KEY")
|
||||
API_SECRET=os.getenv("BFX_SECRET")
|
||||
|
||||
# Sending order requires private hosts
|
||||
bfx = Client(
|
||||
API_KEY=API_KEY,
|
||||
API_SECRET=API_SECRET,
|
||||
logLevel='DEBUG'
|
||||
logLevel='DEBUG',
|
||||
ws_host=WS_HOST,
|
||||
rest_host=REST_HOST
|
||||
)
|
||||
|
||||
@bfx.ws.on('order_snapshot')
|
||||
@@ -34,7 +38,7 @@ def log_error(msg):
|
||||
|
||||
@bfx.ws.on('authenticated')
|
||||
async def submit_order(auth_message):
|
||||
await bfx.ws.submit_order('tBTCUSD', 19000, 0.01, Order.Type.EXCHANGE_MARKET)
|
||||
await bfx.ws.submit_order(symbol='tBTCUSD', price=None, amount=0.01, market_type=Order.Type.EXCHANGE_MARKET)
|
||||
|
||||
# If you dont want to use a decorator
|
||||
# ws.on('authenticated', submit_order)
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import os
|
||||
import sys
|
||||
sys.path.append('../../../')
|
||||
|
||||
from bfxapi import Client
|
||||
from bfxapi.constants import PUB_WS_HOST, PUB_REST_HOST
|
||||
|
||||
bfx = Client(
|
||||
logLevel='DEBUG',
|
||||
ws_host=PUB_WS_HOST,
|
||||
rest_host=PUB_REST_HOST
|
||||
)
|
||||
|
||||
@bfx.ws.on('order_book_snapshot')
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import os
|
||||
import sys
|
||||
sys.path.append('../../../')
|
||||
|
||||
from bfxapi import Client
|
||||
from bfxapi.constants import PUB_WS_HOST, PUB_REST_HOST
|
||||
|
||||
# Retrieving derivative status requires public hosts
|
||||
bfx = Client(
|
||||
logLevel='INFO'
|
||||
logLevel='DEBUG',
|
||||
ws_host=PUB_WS_HOST,
|
||||
rest_host=PUB_REST_HOST
|
||||
)
|
||||
|
||||
@bfx.ws.on('error')
|
||||
|
||||
@@ -3,9 +3,13 @@ import sys
|
||||
sys.path.append('../../../')
|
||||
|
||||
from bfxapi import Client
|
||||
from bfxapi.constants import PUB_WS_HOST, PUB_REST_HOST
|
||||
|
||||
# Retrieving trades/candles requires public hosts
|
||||
bfx = Client(
|
||||
logLevel='DEBUG',
|
||||
ws_host=PUB_WS_HOST,
|
||||
rest_host=PUB_REST_HOST,
|
||||
# Verifies that the local orderbook is up to date
|
||||
# with the bitfinex servers
|
||||
manageOrderBooks=True
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import os
|
||||
import sys
|
||||
sys.path.append('../../../')
|
||||
|
||||
from bfxapi import Client
|
||||
from bfxapi.constants import PUB_WS_HOST, PUB_REST_HOST
|
||||
|
||||
# Retrieving tickers requires public hosts
|
||||
bfx = Client(
|
||||
logLevel='DEBUG'
|
||||
logLevel='DEBUG',
|
||||
ws_host=PUB_WS_HOST,
|
||||
rest_host=PUB_REST_HOST
|
||||
)
|
||||
|
||||
@bfx.ws.on('error')
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import os
|
||||
import sys
|
||||
sys.path.append('../../../')
|
||||
|
||||
from bfxapi import Client
|
||||
from bfxapi.constants import PUB_WS_HOST, PUB_REST_HOST
|
||||
|
||||
# Retrieving trades/candles requires public hosts
|
||||
bfx = Client(
|
||||
logLevel='DEBUG'
|
||||
logLevel='DEBUG',
|
||||
ws_host=PUB_WS_HOST,
|
||||
rest_host=PUB_REST_HOST
|
||||
)
|
||||
|
||||
@bfx.ws.on('error')
|
||||
@@ -20,6 +23,10 @@ def log_candle(candle):
|
||||
def log_trade(trade):
|
||||
print ("New trade: {}".format(trade))
|
||||
|
||||
@bfx.ws.on('new_user_trade')
|
||||
def log_user_trade(trade):
|
||||
print ("New user trade: {}".format(trade))
|
||||
|
||||
async def start():
|
||||
await bfx.ws.subscribe('candles', 'tBTCUSD', timeframe='1m')
|
||||
await bfx.ws.subscribe('trades', 'tBTCUSD')
|
||||
|
||||
@@ -3,14 +3,18 @@ import sys
|
||||
sys.path.append('../../../')
|
||||
|
||||
from bfxapi import Client, Order
|
||||
from bfxapi.constants import WS_HOST, REST_HOST
|
||||
|
||||
API_KEY=os.getenv("BFX_KEY")
|
||||
API_SECRET=os.getenv("BFX_SECRET")
|
||||
|
||||
# Update order requires private hosts
|
||||
bfx = Client(
|
||||
API_KEY=API_KEY,
|
||||
API_SECRET=API_SECRET,
|
||||
logLevel='DEBUG'
|
||||
logLevel='INFO',
|
||||
ws_host=WS_HOST,
|
||||
rest_host=REST_HOST
|
||||
)
|
||||
|
||||
@bfx.ws.on('order_update')
|
||||
|
||||
@@ -2,14 +2,18 @@ import os
|
||||
import sys
|
||||
sys.path.append('../../../')
|
||||
from bfxapi import Client
|
||||
from bfxapi.constants import WS_HOST, REST_HOST
|
||||
|
||||
API_KEY=os.getenv("BFX_KEY")
|
||||
API_SECRET=os.getenv("BFX_SECRET")
|
||||
|
||||
# Checking wallet balances requires private hosts
|
||||
bfx = Client(
|
||||
API_KEY=API_KEY,
|
||||
API_SECRET=API_SECRET,
|
||||
logLevel='INFO'
|
||||
logLevel='INFO',
|
||||
ws_host=WS_HOST,
|
||||
rest_host=REST_HOST
|
||||
)
|
||||
|
||||
@bfx.ws.on('wallet_snapshot')
|
||||
|
||||
@@ -19,5 +19,9 @@ from .withdraw import Withdraw
|
||||
from .ticker import Ticker
|
||||
from .funding_ticker import FundingTicker
|
||||
from .ledger import Ledger
|
||||
from .funding_trade import FundingTrade
|
||||
from .margin_info import MarginInfo
|
||||
from .margin_info_base import MarginInfoBase
|
||||
from .movement import Movement
|
||||
|
||||
NAME = 'models'
|
||||
NAME = "models"
|
||||
|
||||
55
bfxapi/models/funding_trade.py
Normal file
55
bfxapi/models/funding_trade.py
Normal file
@@ -0,0 +1,55 @@
|
||||
"""
|
||||
Module used to describe all of the different data types
|
||||
"""
|
||||
|
||||
class FundingTradeModel:
|
||||
"""
|
||||
Enum used to index the different values in a raw funding trade array
|
||||
"""
|
||||
ID = 0
|
||||
SYMBOL = 1
|
||||
MTS_CREATE = 2
|
||||
OFFER_ID = 3
|
||||
AMOUNT = 4
|
||||
RATE = 5
|
||||
PERIOD = 6
|
||||
|
||||
class FundingTrade:
|
||||
"""
|
||||
ID integer Offer ID
|
||||
SYMBOL string The currency of the offer (fUSD, etc)
|
||||
MTS_CREATE int Millisecond Time Stamp when the offer was created
|
||||
OFFER_ID int The ID of the offer
|
||||
AMOUNT float Amount the offer is for
|
||||
RATE float Rate of the offer
|
||||
PERIOD int Period of the offer
|
||||
"""
|
||||
|
||||
def __init__(self, tid, symbol, mts_create, offer_id, amount, rate, period):
|
||||
self.tid = tid
|
||||
self.symbol = symbol
|
||||
self.mts_create = mts_create
|
||||
self.offer_id = offer_id
|
||||
self.amount = amount
|
||||
self.rate = rate
|
||||
self.period = period
|
||||
|
||||
@staticmethod
|
||||
def from_raw_rest_trade(raw_trade):
|
||||
"""
|
||||
Generate a Ticker object from a raw ticker array
|
||||
"""
|
||||
# [[636040,"fUST",1574077528000,41237922,-100,0.0024,2,null]]
|
||||
return FundingTrade(
|
||||
raw_trade[FundingTradeModel.ID],
|
||||
raw_trade[FundingTradeModel.SYMBOL],
|
||||
raw_trade[FundingTradeModel.MTS_CREATE],
|
||||
raw_trade[FundingTradeModel.OFFER_ID],
|
||||
raw_trade[FundingTradeModel.AMOUNT],
|
||||
raw_trade[FundingTradeModel.RATE],
|
||||
raw_trade[FundingTradeModel.PERIOD]
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return "FundingTrade '{}' x {} @ {} for {} days".format(
|
||||
self.symbol, self.amount, self.rate, self.period)
|
||||
47
bfxapi/models/margin_info.py
Normal file
47
bfxapi/models/margin_info.py
Normal file
@@ -0,0 +1,47 @@
|
||||
"""
|
||||
Module used to describe all of the different data types
|
||||
"""
|
||||
|
||||
import datetime
|
||||
|
||||
class MarginInfoModel:
|
||||
"""
|
||||
Enum used to index the different values in a raw margin info array
|
||||
"""
|
||||
TRADABLE_BALANCE = 0
|
||||
GROSS_BALANCE = 1
|
||||
BUY = 2
|
||||
SELL = 3
|
||||
|
||||
class MarginInfo:
|
||||
"""
|
||||
SYMBOL string
|
||||
TRADABLE BALANCE float
|
||||
GROSS_BALANCE float
|
||||
BUY
|
||||
SELL
|
||||
"""
|
||||
|
||||
def __init__(self, symbol, tradable_balance, gross_balance, buy, sell):
|
||||
# pylint: disable=invalid-name
|
||||
self.symbol = symbol
|
||||
self.tradable_balance = tradable_balance
|
||||
self.gross_balance = gross_balance
|
||||
self.buy = buy
|
||||
self.sell = sell
|
||||
|
||||
@staticmethod
|
||||
def from_raw_margin_info(raw_margin_info):
|
||||
"""
|
||||
Generate a MarginInfo object from a raw margin info array
|
||||
"""
|
||||
symbol = raw_margin_info[1]
|
||||
tradable_balance = raw_margin_info[2][MarginInfoModel.TRADABLE_BALANCE]
|
||||
gross_balance = raw_margin_info[2][MarginInfoModel.GROSS_BALANCE]
|
||||
buy = raw_margin_info[2][MarginInfoModel.BUY]
|
||||
sell = raw_margin_info[2][MarginInfoModel.SELL]
|
||||
return MarginInfo(symbol, tradable_balance, gross_balance, buy, sell)
|
||||
|
||||
def __str__(self):
|
||||
return "Margin Info {} buy={} sell={} tradable_balance={} gross_balance={}" \
|
||||
"".format(self.symbol, self.buy, self.sell, self. tradable_balance, self. gross_balance)
|
||||
48
bfxapi/models/margin_info_base.py
Normal file
48
bfxapi/models/margin_info_base.py
Normal file
@@ -0,0 +1,48 @@
|
||||
"""
|
||||
Module used to describe all of the different data types
|
||||
"""
|
||||
|
||||
import datetime
|
||||
|
||||
class MarginInfoBaseModel:
|
||||
"""
|
||||
Enum used to index the different values in a raw margin info array
|
||||
"""
|
||||
USER_PL = 0
|
||||
USER_SWAPS = 1
|
||||
MARGIN_BALANCE = 2
|
||||
MARGIN_NET = 3
|
||||
MARGIN_MIN = 4
|
||||
|
||||
class MarginInfoBase:
|
||||
"""
|
||||
USER_PL float
|
||||
USER_SWAPS float
|
||||
MARGIN_BALANCE float
|
||||
MARGIN_NET float
|
||||
MARGIN_MIN float
|
||||
"""
|
||||
|
||||
def __init__(self, user_pl, user_swaps, margin_balance, margin_net, margin_min):
|
||||
# pylint: disable=invalid-name
|
||||
self.user_pl = user_pl
|
||||
self.user_swaps = user_swaps
|
||||
self.margin_balance = margin_balance
|
||||
self.margin_net = margin_net
|
||||
self.margin_min = margin_min
|
||||
|
||||
@staticmethod
|
||||
def from_raw_margin_info(raw_margin_info):
|
||||
"""
|
||||
Generate a MarginInfoBase object from a raw margin info array
|
||||
"""
|
||||
user_pl = raw_margin_info[1][MarginInfoBaseModel.USER_PL]
|
||||
user_swaps = raw_margin_info[1][MarginInfoBaseModel.USER_SWAPS]
|
||||
margin_balance = raw_margin_info[1][MarginInfoBaseModel.MARGIN_BALANCE]
|
||||
margin_net = raw_margin_info[1][MarginInfoBaseModel.MARGIN_NET]
|
||||
margin_min = raw_margin_info[1][MarginInfoBaseModel.MARGIN_MIN]
|
||||
return MarginInfoBase(user_pl, user_swaps, margin_balance, margin_net, margin_min)
|
||||
|
||||
def __str__(self):
|
||||
return "Margin Info Base user_pl={} user_swaps={} margin_balance={} margin_net={} margin_min={}" \
|
||||
"".format(self.user_pl, self.user_swaps, self.margin_balance, self.margin_net, self.margin_min)
|
||||
76
bfxapi/models/movement.py
Normal file
76
bfxapi/models/movement.py
Normal file
@@ -0,0 +1,76 @@
|
||||
"""
|
||||
Module used to describe movement data types
|
||||
"""
|
||||
|
||||
import time
|
||||
import datetime
|
||||
|
||||
class MovementModel:
|
||||
"""
|
||||
Enum used index the different values in a raw movement array
|
||||
"""
|
||||
|
||||
ID = 0
|
||||
CURRENCY = 1
|
||||
CURRENCY_NAME = 2
|
||||
MTS_STARTED = 5
|
||||
MTS_UPDATED = 6
|
||||
STATUS = 9
|
||||
AMOUNT = 12
|
||||
FEES = 13
|
||||
DESTINATION_ADDRESS = 16
|
||||
TRANSACTION_ID = 20
|
||||
|
||||
class Movement:
|
||||
|
||||
"""
|
||||
ID String Movement identifier
|
||||
CURRENCY String The symbol of the currency (ex. "BTC")
|
||||
CURRENCY_NAME String The extended name of the currency (ex. "BITCOIN")
|
||||
MTS_STARTED Date Movement started at
|
||||
MTS_UPDATED Date Movement last updated at
|
||||
STATUS String Current status
|
||||
AMOUNT String Amount of funds moved
|
||||
FEES String Tx Fees applied
|
||||
DESTINATION_ADDRESS String Destination address
|
||||
TRANSACTION_ID String Transaction identifier
|
||||
"""
|
||||
|
||||
def __init__(self, mid, currency, mts_started, mts_updated, status, amount, fees, dst_address, tx_id):
|
||||
self.id = mid
|
||||
self.currency = currency
|
||||
self.mts_started = mts_started
|
||||
self.mts_updated = mts_updated
|
||||
self.status = status
|
||||
self.amount = amount
|
||||
self.fees = fees
|
||||
self.dst_address = dst_address
|
||||
self.tx_id = tx_id
|
||||
|
||||
self.date = datetime.datetime.fromtimestamp(mts_started/1000.0)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def from_raw_movement(raw_movement):
|
||||
"""
|
||||
Parse a raw movement object into a Movement object
|
||||
@return Movement
|
||||
"""
|
||||
|
||||
mid = raw_movement[MovementModel.ID]
|
||||
currency = raw_movement[MovementModel.CURRENCY]
|
||||
mts_started = raw_movement[MovementModel.MTS_STARTED]
|
||||
mts_updated = raw_movement[MovementModel.MTS_UPDATED]
|
||||
status = raw_movement[MovementModel.STATUS]
|
||||
amount = raw_movement[MovementModel.AMOUNT]
|
||||
fees = raw_movement[MovementModel.FEES]
|
||||
dst_address = raw_movement[MovementModel.DESTINATION_ADDRESS]
|
||||
tx_id = raw_movement[MovementModel.TRANSACTION_ID]
|
||||
|
||||
return Movement(mid, currency, mts_started, mts_updated, status, amount, fees, dst_address, tx_id)
|
||||
|
||||
def __str__(self):
|
||||
''' Allow us to print the Movement object in a pretty format '''
|
||||
text = "Movement <'{}' amount={} fees={} mts_created={} mts_updated={} status='{}' destination_address={} transaction_id={}>"
|
||||
return text.format(self.currency, self.amount, self.fees,
|
||||
self.mts_started, self.mts_updated, self.status, self.dst_address, self.tx_id)
|
||||
@@ -67,7 +67,7 @@ class OrderFlags:
|
||||
as flags
|
||||
"""
|
||||
HIDDEN = 64
|
||||
CLOSE = 12
|
||||
CLOSE = 512
|
||||
REDUCE_ONLY = 1024
|
||||
POST_ONLY = 4096
|
||||
OCO = 16384
|
||||
|
||||
@@ -10,8 +10,8 @@ import datetime
|
||||
|
||||
from ..utils.custom_logger import CustomLogger
|
||||
from ..utils.auth import generate_auth_headers, calculate_order_flags, gen_unique_cid
|
||||
from ..models import Wallet, Order, Position, Trade, FundingLoan, FundingOffer
|
||||
from ..models import FundingCredit, Notification, Ledger
|
||||
from ..models import Wallet, Order, Position, Trade, FundingLoan, FundingOffer, FundingTrade, MarginInfoBase, MarginInfo
|
||||
from ..models import FundingCredit, Notification, Ledger, Movement
|
||||
|
||||
|
||||
class BfxRest:
|
||||
@@ -219,6 +219,24 @@ class BfxRest:
|
||||
status = await self.fetch(endpoint)
|
||||
return status
|
||||
|
||||
async def get_liquidations(self, start, end, limit=100, sort=-1):
|
||||
"""
|
||||
Endpoint to retrieve liquidations. By default it will retrieve the most recent liquidations,
|
||||
but time-specific data can be retrieved using timestamps.
|
||||
|
||||
# Attributes
|
||||
@param start int: millisecond start time
|
||||
@param end int: millisecond end time
|
||||
@param limit int: max number of items in response (max. 500)
|
||||
@param sort int: if = 1 it sorts results returned with old > new
|
||||
@return Array [ POS_ID, MTS, SYMBOL, AMOUNT, BASE_PRICE, IS_MATCH, IS_MARKET_SOLD, PRICE_ACQUIRED ]
|
||||
"""
|
||||
endpoint = "liquidations/hist"
|
||||
params = "?start={}&end={}&limit={}&sort={}".format(
|
||||
start, end, limit, sort)
|
||||
liquidations = await self.fetch(endpoint, params=params)
|
||||
return liquidations
|
||||
|
||||
async def get_public_pulse_hist(self, end=None, limit=25):
|
||||
"""
|
||||
View the latest pulse messages. You can specify an end timestamp to view older messages.
|
||||
@@ -370,6 +388,15 @@ class BfxRest:
|
||||
stats = await self.fetch(endpoint)
|
||||
return stats
|
||||
|
||||
async def get_conf_list_pair_exchange(self):
|
||||
"""
|
||||
Get list of available exchange pairs
|
||||
# Attributes
|
||||
@return Array [ SYMBOL ]
|
||||
"""
|
||||
endpoint = "conf/pub:list:pair:exchange"
|
||||
pairs = await self.fetch(endpoint)
|
||||
return pairs
|
||||
|
||||
##################################################
|
||||
# Authenticated Data #
|
||||
@@ -385,6 +412,22 @@ class BfxRest:
|
||||
raw_wallets = await self.post(endpoint)
|
||||
return [Wallet(*rw[:5]) for rw in raw_wallets]
|
||||
|
||||
async def get_margin_info(self, symbol='base'):
|
||||
"""
|
||||
Get account margin information (like P/L, Swaps, Margin Balance, Tradable Balance and others).
|
||||
Use different keys (base, SYMBOL, sym_all) to retrieve different kinds of data.
|
||||
|
||||
@return Array
|
||||
"""
|
||||
endpoint = f"auth/r/info/margin/{symbol}"
|
||||
raw_margin_info = await self.post(endpoint)
|
||||
if symbol == 'base':
|
||||
return MarginInfoBase.from_raw_margin_info(raw_margin_info)
|
||||
elif symbol == 'sym_all':
|
||||
return [MarginInfo.from_raw_margin_info(record) for record in raw_margin_info]
|
||||
else:
|
||||
return MarginInfo.from_raw_margin_info(raw_margin_info)
|
||||
|
||||
async def get_active_orders(self, symbol):
|
||||
"""
|
||||
Get all of the active orders associated with API_KEY - Requires authentication.
|
||||
@@ -397,7 +440,7 @@ class BfxRest:
|
||||
raw_orders = await self.post(endpoint)
|
||||
return [Order.from_raw_order(ro) for ro in raw_orders]
|
||||
|
||||
async def get_order_history(self, symbol, start, end, limit=25, sort=-1):
|
||||
async def get_order_history(self, symbol, start, end, limit=25, sort=-1, ids=None):
|
||||
"""
|
||||
Get all of the orders between the start and end period associated with API_KEY
|
||||
- Requires authentication.
|
||||
@@ -407,12 +450,22 @@ class BfxRest:
|
||||
@param start int: millisecond start time
|
||||
@param end int: millisecond end time
|
||||
@param limit int: max number of items in response
|
||||
@param ids list of int: allows you to retrieve specific orders by order ID (ids: [ID1, ID2, ID3])
|
||||
@return Array <models.Order>
|
||||
"""
|
||||
endpoint = "auth/r/orders/{}/hist".format(symbol)
|
||||
params = "?start={}&end={}&limit={}&sort={}".format(
|
||||
start, end, limit, sort)
|
||||
raw_orders = await self.post(endpoint, params=params)
|
||||
payload = {}
|
||||
if start:
|
||||
payload['start'] = start
|
||||
if end:
|
||||
payload['end'] = end
|
||||
if limit:
|
||||
payload['limit'] = limit
|
||||
if sort:
|
||||
payload['sort'] = sort
|
||||
if ids:
|
||||
payload['id'] = ids
|
||||
raw_orders = await self.post(endpoint, payload)
|
||||
return [Order.from_raw_order(ro) for ro in raw_orders]
|
||||
|
||||
async def get_active_position(self):
|
||||
@@ -439,7 +492,7 @@ class BfxRest:
|
||||
raw_trades = await self.post(endpoint)
|
||||
return [Trade.from_raw_rest_trade(rt) for rt in raw_trades]
|
||||
|
||||
async def get_trades(self, symbol, start, end, limit=25):
|
||||
async def get_trades(self, start, end, symbol=None, limit=25):
|
||||
"""
|
||||
Get all of the trades between the start and end period associated with API_KEY
|
||||
- Requires authentication.
|
||||
@@ -451,11 +504,27 @@ class BfxRest:
|
||||
@param limit int: max number of items in response
|
||||
@return Array <models.Trade>
|
||||
"""
|
||||
endpoint = "auth/r/trades/{}/hist".format(symbol)
|
||||
endpoint = "auth/r/trades/{}/hist".format(symbol) if symbol else "auth/r/trades/hist"
|
||||
params = "?start={}&end={}&limit={}".format(start, end, limit)
|
||||
raw_trades = await self.post(endpoint, params=params)
|
||||
return [Trade.from_raw_rest_trade(rt) for rt in raw_trades]
|
||||
|
||||
async def get_funding_trades(self, symbol, start, end, limit=25):
|
||||
"""
|
||||
Get all of the funding trades between the start and end period associated with API_KEY
|
||||
- Requires authentication.
|
||||
|
||||
# Attributes
|
||||
@param symbol string: pair symbol i.e fUSD
|
||||
@param start int: millisecond start time
|
||||
@param end int: millisecond end time
|
||||
@param limit int: max number of items in response
|
||||
@return Array <models.FundingTrade>
|
||||
"""
|
||||
endpoint = "auth/r/funding/trades/{}/hist".format(symbol)
|
||||
raw_trades = await self.post(endpoint)
|
||||
return [FundingTrade.from_raw_rest_trade(rt) for rt in raw_trades]
|
||||
|
||||
async def get_funding_offers(self, symbol):
|
||||
"""
|
||||
Get all of the funding offers associated with API_KEY - Requires authentication.
|
||||
@@ -558,6 +627,22 @@ class BfxRest:
|
||||
raw_ledgers = await self.post(endpoint, params=params)
|
||||
return [Ledger.from_raw_ledger(rl) for rl in raw_ledgers]
|
||||
|
||||
async def get_movement_history(self, currency, start="", end="", limit=25):
|
||||
"""
|
||||
Get all of the deposits and withdraws between the start and end period associated with API_KEY
|
||||
- Requires authentication.
|
||||
# Attributes
|
||||
@param currency string: pair symbol i.e BTC
|
||||
@param start int: millisecond start time
|
||||
@param end int: millisecond end time
|
||||
@param limit int: max number of items in response
|
||||
@return Array <models.Movement>
|
||||
"""
|
||||
endpoint = "auth/r/movements/{}/hist".format(currency)
|
||||
params = "?start={}&end={}&limit={}".format(start, end, limit)
|
||||
raw_movements = await self.post(endpoint, params=params)
|
||||
return [Movement.from_raw_movement(rm) for rm in raw_movements]
|
||||
|
||||
async def submit_funding_offer(self, symbol, amount, rate, period,
|
||||
funding_type=FundingOffer.Type.LIMIT, hidden=False):
|
||||
"""
|
||||
@@ -594,6 +679,17 @@ class BfxRest:
|
||||
raw_notification = await self.post(endpoint, {'id': fundingId})
|
||||
return Notification.from_raw_notification(raw_notification)
|
||||
|
||||
async def submit_cancel_all_funding_offer(self, currency):
|
||||
"""
|
||||
Cancel all funding offers at once
|
||||
|
||||
# Attributes
|
||||
@param currency str: currency for which to cancel all offers (USD, BTC, UST ...)
|
||||
"""
|
||||
endpoint = "auth/w/funding/offer/cancel/all"
|
||||
raw_notification = await self.post(endpoint, {'currency': currency})
|
||||
return Notification.from_raw_notification(raw_notification)
|
||||
|
||||
async def keep_funding(self, type, id):
|
||||
"""
|
||||
Toggle to keep funding taken. Specify loan for unused funding and credit for used funding.
|
||||
@@ -834,7 +930,7 @@ class BfxRest:
|
||||
if price_trailing != None:
|
||||
payload['price_trailing'] = str(price_trailing)
|
||||
if time_in_force != None:
|
||||
payload['time_in_force'] = str(time_in_force)
|
||||
payload['tif'] = str(time_in_force)
|
||||
if leverage != None:
|
||||
payload["lev"] = str(leverage)
|
||||
flags = calculate_order_flags(
|
||||
@@ -905,6 +1001,145 @@ class BfxRest:
|
||||
raw_notification = await self.post(endpoint, payload)
|
||||
return Notification.from_raw_notification(raw_notification)
|
||||
|
||||
async def claim_position(self, position_id, amount):
|
||||
"""
|
||||
The claim feature allows the use of funds you have in your Margin Wallet
|
||||
to settle a leveraged position as an exchange buy or sale
|
||||
|
||||
# Attributes
|
||||
@param position_id: id of the position
|
||||
@param amount: amount to claim
|
||||
@return Array [ MTS, TYPE, MESSAGE_ID, null, [SYMBOL, POSITION_STATUS,
|
||||
AMOUNT, BASE_PRICE, MARGIN_FUNDING, MARGIN_FUNDING_TYPE, PLACEHOLDER,
|
||||
PLACEHOLDER, PLACEHOLDER, PLACEHOLDER, PLACEHOLDER, POSITION_ID, MTS_CREATE,
|
||||
MTS_UPDATE, PLACEHOLDER, POS_TYPE, PLACEHOLDER, COLLATERAL, MIN_COLLATERAL,
|
||||
META], CODE, STATUS, TEXT]
|
||||
"""
|
||||
payload = {
|
||||
"id": position_id,
|
||||
"amount": f"{amount * -1}"
|
||||
}
|
||||
endpoint = "auth/w/position/claim"
|
||||
message = await self.post(endpoint, payload)
|
||||
return message
|
||||
|
||||
async def get_alerts(self):
|
||||
"""
|
||||
Retrieve a list of active price alerts
|
||||
"""
|
||||
endpoint = f"auth/r/alerts"
|
||||
|
||||
message = await self.post(endpoint, {})
|
||||
return message
|
||||
|
||||
async def set_alert(self, type, symbol, price):
|
||||
"""
|
||||
Sets up a price alert at the given value
|
||||
|
||||
# Attributes
|
||||
@param type string
|
||||
@param symbol string
|
||||
@param price float
|
||||
"""
|
||||
endpoint = f"auth/w/alert/set"
|
||||
payload = {
|
||||
"type": type,
|
||||
"symbol": symbol,
|
||||
"price": price
|
||||
}
|
||||
|
||||
message = await self.post(endpoint, payload)
|
||||
return message
|
||||
|
||||
async def delete_alert(self, symbol, price):
|
||||
"""
|
||||
Delete an active alert
|
||||
|
||||
# Attributes
|
||||
@param symbol string
|
||||
@param price float
|
||||
"""
|
||||
endpoint = f"auth/w/alert/price:{symbol}:{price}/del"
|
||||
payload = {
|
||||
"symbol": symbol,
|
||||
"price": price
|
||||
}
|
||||
|
||||
message = await self.post(endpoint, payload)
|
||||
return message
|
||||
|
||||
async def calc_order_avail(self, symbol, type, lev, dir=None, rate=None):
|
||||
"""
|
||||
Calculate the balance available for orders/offers
|
||||
|
||||
# Attributes
|
||||
@param symbol str: Symbol (tBTCUSD, tBTCUST, fUSD, .... )
|
||||
@param dir int: Direction of the order (1 for by, -1 for sell) (Mandator for EXCHANGE and MARGIN type, not used for FUNDING)
|
||||
@param rate str: Order price (Mandator for EXCHANGE and MARGIN type, not used for FUNDING)
|
||||
@param type str: Type of the order/offer EXCHANGE, MARGIN, DERIV, or FUNDING
|
||||
@param lev str: Leverage that you want to use in calculating the max order amount (DERIV only)
|
||||
"""
|
||||
endpoint = f"auth/calc/order/avail"
|
||||
payload = {
|
||||
"symbol": symbol,
|
||||
"type": type,
|
||||
"lev": lev
|
||||
}
|
||||
|
||||
if dir:
|
||||
payload["dir"] = dir
|
||||
|
||||
if rate:
|
||||
payload["rate"] = rate
|
||||
|
||||
message = await self.post(endpoint, payload)
|
||||
return message
|
||||
|
||||
async def write_user_settings(self, settings):
|
||||
"""
|
||||
Allows you to create custom settings by creating key: value pairs
|
||||
|
||||
# Attributes
|
||||
@param Settings object: object of keys and values to be set. Must follow regex pattern /^api:[A-Za-z0-9_-]*$/
|
||||
"""
|
||||
endpoint = f"auth/w/settings/set"
|
||||
payload = {
|
||||
"Settings": settings
|
||||
}
|
||||
|
||||
message = await self.post(endpoint, payload)
|
||||
return message
|
||||
|
||||
async def read_user_settings(self, keys):
|
||||
"""
|
||||
Allows you to read custom settings by providing a key
|
||||
|
||||
# Attributes
|
||||
@param Keys array: the keys for which you wish to retrieve the values
|
||||
"""
|
||||
endpoint = f"auth/w/settings"
|
||||
payload = {
|
||||
"Keys": keys
|
||||
}
|
||||
|
||||
message = await self.post(endpoint, payload)
|
||||
return message
|
||||
|
||||
async def delete_user_settings(self, settings):
|
||||
"""
|
||||
Allows you to delete custom settings
|
||||
|
||||
# Attributes
|
||||
@param settings object: object of keys to be deleted followed by value 1. Must follow regex pattern /^api:[A-Za-z0-9_-]*$/
|
||||
"""
|
||||
endpoint = f"auth/w/settings/del"
|
||||
payload = {
|
||||
"Settings": settings
|
||||
}
|
||||
|
||||
message = await self.post(endpoint, payload)
|
||||
return message
|
||||
|
||||
async def get_auth_pulse_hist(self, is_public=None):
|
||||
"""
|
||||
Allows you to retrieve your private pulse history or the public pulse history with an additional UID_LIKED field.
|
||||
@@ -1045,3 +1280,133 @@ class BfxRest:
|
||||
payload['symbol'] = symbol
|
||||
payload['collateral'] = collateral
|
||||
return await self.post(endpoint, data=payload)
|
||||
|
||||
##################################################
|
||||
# Merchants #
|
||||
##################################################
|
||||
|
||||
async def submit_invoice(self, amount, currency, pay_currencies, order_id, webhook, redirect_url, customer_info_nationality,
|
||||
customer_info_resid_country, customer_info_resid_city, customer_info_resid_zip_code,
|
||||
customer_info_resid_street, customer_info_full_name, customer_info_email,
|
||||
customer_info_resid_state=None, customer_info_resid_building_no=None, duration=None):
|
||||
"""
|
||||
Submit an invoice for payment
|
||||
|
||||
# Attributes
|
||||
@param amount str: Invoice amount in currency (From 0.1 USD to 1000 USD)
|
||||
@param currency str: Invoice currency, currently supported: USD
|
||||
@param pay_currencies list of str: Currencies in which invoice accepts the payments, supported values are BTC, ETH, UST-ETH, UST-TRX, UST-LBT, LNX, LBT
|
||||
@param order_id str: Reference order identifier in merchant's platform
|
||||
@param webhook str: The endpoint that will be called once the payment is completed/expired
|
||||
@param redirect_url str: Merchant redirect URL, this one is used in UI to redirect customer to merchant's site once the payment is completed/expired
|
||||
@param customer_info_nationality str: Customer's nationality, alpha2 code or full country name (alpha2 preffered)
|
||||
@param customer_info_resid_country str: Customer's residential country, alpha2 code or full country name (alpha2 preffered)
|
||||
@param customer_info_resid_city str: Customer's residential city/town
|
||||
@param customer_info_resid_zip_code str: Customer's residential zip code/postal code
|
||||
@param customer_info_resid_street str: Customer's residential street address
|
||||
@param customer_info_full_name str: Customer's full name
|
||||
@param customer_info_email str: Customer's email address
|
||||
@param customer_info_resid_state str: Optional, customer's residential state/province
|
||||
@param customer_info_resid_building_no str: Optional, customer's residential building number/name
|
||||
@param duration int: Optional, invoice expire time in seconds, minimal duration is 5 mins (300) and maximal duration is 24 hours (86400). Default value is 15 minutes
|
||||
"""
|
||||
endpoint = 'auth/w/ext/pay/invoice/create'
|
||||
payload = {
|
||||
'amount': amount,
|
||||
'currency': currency,
|
||||
'payCurrencies': pay_currencies,
|
||||
'orderId': order_id,
|
||||
'webhook': webhook,
|
||||
'redirectUrl': redirect_url,
|
||||
'customerInfo': {
|
||||
'nationality': customer_info_nationality,
|
||||
'residCountry': customer_info_resid_country,
|
||||
'residCity': customer_info_resid_city,
|
||||
'residZipCode': customer_info_resid_zip_code,
|
||||
'residStreet': customer_info_resid_street,
|
||||
'fullName': customer_info_full_name,
|
||||
'email': customer_info_email
|
||||
},
|
||||
'duration': duration
|
||||
}
|
||||
|
||||
if customer_info_resid_state:
|
||||
payload['customerInfo']['residState'] = customer_info_resid_state
|
||||
|
||||
if customer_info_resid_building_no:
|
||||
payload['customerInfo']['residBuildingNo'] = customer_info_resid_building_no
|
||||
|
||||
return await self.post(endpoint, data=payload)
|
||||
|
||||
async def get_invoices(self, id=None, start=None, end=None, limit=10):
|
||||
"""
|
||||
List submitted invoices
|
||||
|
||||
# Attributes
|
||||
@param id str: Unique invoice identifier
|
||||
@param start int: Millisecond start time
|
||||
@param end int: Millisecond end time
|
||||
@param limit int: Millisecond start time
|
||||
"""
|
||||
endpoint = 'auth/r/ext/pay/invoices'
|
||||
payload = {}
|
||||
|
||||
if id:
|
||||
payload['id'] = id
|
||||
|
||||
if start:
|
||||
payload['start'] = start
|
||||
|
||||
if end:
|
||||
payload['end'] = end
|
||||
|
||||
if limit:
|
||||
payload['limit'] = limit
|
||||
|
||||
return await self.post(endpoint, data=payload)
|
||||
|
||||
async def complete_invoice(self, id, pay_ccy, deposit_id=None, ledger_id=None):
|
||||
"""
|
||||
Manually complete an invoice
|
||||
|
||||
# Attributes
|
||||
@param id str: Unique invoice identifier
|
||||
@param pay_ccy str: Paid invoice currency, should be one of values under payCurrencies field on invoice
|
||||
@param deposit_id int: Movement/Deposit Id linked to invoice as payment
|
||||
@param ledger_id int: Ledger entry Id linked to invoice as payment, use either depositId or ledgerId
|
||||
"""
|
||||
endpoint = 'auth/w/ext/pay/invoice/complete'
|
||||
payload = {
|
||||
'id': id,
|
||||
'payCcy': pay_ccy
|
||||
}
|
||||
|
||||
if deposit_id:
|
||||
payload['depositId'] = deposit_id
|
||||
|
||||
if ledger_id:
|
||||
payload['ledgerId'] = ledger_id
|
||||
|
||||
return await self.post(endpoint, data=payload)
|
||||
|
||||
async def get_unlinked_deposits(self, ccy, start=None, end=None):
|
||||
"""
|
||||
Retrieve deposits that possibly could be linked to bitfinex pay invoices
|
||||
|
||||
# Attributes
|
||||
@param ccy str: Pay currency to search deposits for, supported values are: BTC, ETH, UST-ETH, UST-TRX, UST-LBT, LNX, LBT
|
||||
@param start int: Millisecond start time
|
||||
@param end int: Millisecond end time
|
||||
"""
|
||||
endpoint = 'auth/r/ext/pay/deposits/unlinked'
|
||||
payload = {
|
||||
'ccy': ccy
|
||||
}
|
||||
|
||||
if start:
|
||||
payload['start'] = start
|
||||
|
||||
if end:
|
||||
payload['end'] = end
|
||||
|
||||
return await self.post(endpoint, data=payload)
|
||||
|
||||
@@ -16,7 +16,7 @@ async def run():
|
||||
print(trades)
|
||||
for trade in trades:
|
||||
orders_ids.append(trade[0])
|
||||
assert orders_ids == [657815316, 657815314, 657815312, 657815311, 657815309]
|
||||
assert orders_ids == [657815316, 657815314, 657815312, 657815308, 657815304]
|
||||
|
||||
# check that strictly decreasing order id condition is always respected
|
||||
# check that not increasing timestamp condition is always respected
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import pytest
|
||||
import json
|
||||
import asyncio
|
||||
from .helpers import (create_stubbed_client, ws_publish_connection_init, EventWatcher)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
12
bfxapi/utils/decorators.py
Normal file
12
bfxapi/utils/decorators.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from ..utils.custom_logger import CustomLogger
|
||||
|
||||
|
||||
def handle_failure(func):
|
||||
async def inner_function(*args, **kwargs):
|
||||
logger = CustomLogger('BfxWebsocket', logLevel="DEBUG")
|
||||
try:
|
||||
await func(*args, **kwargs)
|
||||
except Exception as exception_message:
|
||||
logger.error(exception_message)
|
||||
|
||||
return inner_function
|
||||
@@ -2,4 +2,4 @@
|
||||
This module contains the current version of the bfxapi lib
|
||||
"""
|
||||
|
||||
__version__ = '1.1.14'
|
||||
__version__ = '2.0.3'
|
||||
|
||||
@@ -12,7 +12,9 @@ from .subscription_manager import SubscriptionManager
|
||||
from .wallet_manager import WalletManager
|
||||
from .order_manager import OrderManager
|
||||
from ..utils.auth import generate_auth_payload
|
||||
from ..utils.decorators import handle_failure
|
||||
from ..models import Order, Trade, OrderBook, Ticker, FundingTicker
|
||||
from ..constants import PUB_WS_HOST
|
||||
|
||||
|
||||
class Flags:
|
||||
@@ -65,6 +67,39 @@ def _parse_trade(tData, symbol):
|
||||
'symbol': symbol
|
||||
}
|
||||
|
||||
|
||||
def _parse_user_trade(tData):
|
||||
return {
|
||||
'id': tData[0],
|
||||
'symbol': tData[1],
|
||||
'mts_create': tData[2],
|
||||
'order_id': tData[3],
|
||||
'exec_amount': tData[4],
|
||||
'exec_price': tData[5],
|
||||
'order_type': tData[6],
|
||||
'order_price': tData[7],
|
||||
'maker': tData[8],
|
||||
'cid': tData[11],
|
||||
}
|
||||
|
||||
|
||||
def _parse_user_trade_update(tData):
|
||||
return {
|
||||
'id': tData[0],
|
||||
'symbol': tData[1],
|
||||
'mts_create': tData[2],
|
||||
'order_id': tData[3],
|
||||
'exec_amount': tData[4],
|
||||
'exec_price': tData[5],
|
||||
'order_type': tData[6],
|
||||
'order_price': tData[7],
|
||||
'maker': tData[8],
|
||||
'fee': tData[9],
|
||||
'fee_currency': tData[10],
|
||||
'cid': tData[11],
|
||||
}
|
||||
|
||||
|
||||
def _parse_deriv_status_update(sData, symbol):
|
||||
return {
|
||||
'symbol': symbol,
|
||||
@@ -139,6 +174,7 @@ class BfxWebsocket(GenericWebsocket):
|
||||
- `funding_credit_snapshot` (array): Opening funding credit balances
|
||||
- `balance_update` (array): When the state of a balance is changed
|
||||
- `new_trade` (array): A new trade on the market has been executed
|
||||
- `new_user_trade` (array): A new - your - trade has been executed
|
||||
- `new_ticker` (Ticker|FundingTicker): A new ticker update has been published
|
||||
- `new_funding_ticker` (FundingTicker): A new funding ticker update has been published
|
||||
- `new_trading_ticker` (Ticker): A new trading ticker update has been published
|
||||
@@ -152,7 +188,7 @@ class BfxWebsocket(GenericWebsocket):
|
||||
- `unsubscribed` (Subscription): A channel has been un-subscribed
|
||||
"""
|
||||
|
||||
def __init__(self, API_KEY=None, API_SECRET=None, host='wss://api-pub.bitfinex.com/ws/2',
|
||||
def __init__(self, API_KEY=None, API_SECRET=None, host=PUB_WS_HOST,
|
||||
manageOrderBooks=False, dead_man_switch=False, ws_capacity=25, logLevel='INFO', parse_float=float,
|
||||
channel_filter=[], *args, **kwargs):
|
||||
self.API_KEY = API_KEY
|
||||
@@ -266,7 +302,7 @@ class BfxWebsocket(GenericWebsocket):
|
||||
socketId,
|
||||
ERRORS[data.get('code', 10000)],
|
||||
data.get("msg", ""))
|
||||
self._emit('error', err_string)
|
||||
self._emit(Exception(err_string))
|
||||
|
||||
async def _system_auth_handler(self, socketId, data):
|
||||
if data.get('status') == 'FAILED':
|
||||
@@ -282,6 +318,11 @@ class BfxWebsocket(GenericWebsocket):
|
||||
symbol = self.subscriptionManager.get(data[0]).symbol
|
||||
tradeObj = _parse_trade(tData, symbol)
|
||||
self._emit('trade_update', tradeObj)
|
||||
else:
|
||||
# user trade
|
||||
# [0,"tu",[738045455,"tTESTBTC:TESTUSD",1622169615771,66635385225,0.001,38175,"EXCHANGE LIMIT",39000,-1,-0.000002,"TESTBTC",1622169615685]]
|
||||
tradeObj = _parse_user_trade_update(tData)
|
||||
self._emit('user_trade_update', tradeObj)
|
||||
|
||||
async def _trade_executed_handler(self, data):
|
||||
tData = data[2]
|
||||
@@ -290,6 +331,11 @@ class BfxWebsocket(GenericWebsocket):
|
||||
symbol = self.subscriptionManager.get(data[0]).symbol
|
||||
tradeObj = _parse_trade(tData, symbol)
|
||||
self._emit('new_trade', tradeObj)
|
||||
else:
|
||||
# user trade
|
||||
# [0, 'te', [37558151, 'tBTCUSD', 1643542688513, 1512164914, 0.0001, 30363, 'EXCHANGE MARKET', 100000, -1, None, None, 1643542688390]]
|
||||
tradeObj = _parse_user_trade(tData)
|
||||
self._emit('new_user_trade', tradeObj)
|
||||
|
||||
async def _wallet_update_handler(self, data):
|
||||
# [0,"wu",["exchange","USD",89134.66933283,0]]
|
||||
@@ -481,6 +527,7 @@ class BfxWebsocket(GenericWebsocket):
|
||||
else:
|
||||
self.logger.warn('Unknown (socketId={}) websocket response: {}'.format(socketId, msg))
|
||||
|
||||
@handle_failure
|
||||
async def _ws_authenticate_socket(self, socketId):
|
||||
socket = self.sockets[socketId]
|
||||
socket.set_authenticated()
|
||||
@@ -507,6 +554,7 @@ class BfxWebsocket(GenericWebsocket):
|
||||
# re-subscribe to existing channels
|
||||
await self.subscriptionManager.resubscribe_by_socket(socket_id)
|
||||
|
||||
@handle_failure
|
||||
async def _send_auth_command(self, channel_name, data):
|
||||
payload = [0, channel_name, None, data]
|
||||
socket = self.get_authenticated_socket()
|
||||
@@ -538,6 +586,7 @@ class BfxWebsocket(GenericWebsocket):
|
||||
total += self.get_socket_capacity(socketId)
|
||||
return total
|
||||
|
||||
@handle_failure
|
||||
async def enable_flag(self, flag):
|
||||
"""
|
||||
Enable flag on websocket connection
|
||||
|
||||
@@ -13,7 +13,7 @@ from pyee import AsyncIOEventEmitter
|
||||
from ..utils.custom_logger import CustomLogger
|
||||
|
||||
# websocket exceptions
|
||||
from websockets.exceptions import ConnectionClosed
|
||||
from websockets.exceptions import ConnectionClosed, InvalidStatusCode
|
||||
|
||||
class AuthError(Exception):
|
||||
"""
|
||||
@@ -84,6 +84,10 @@ class GenericWebsocket:
|
||||
thread and connection.
|
||||
"""
|
||||
self._start_new_socket()
|
||||
event_loop = asyncio.get_event_loop()
|
||||
if not event_loop or not event_loop.is_running():
|
||||
while True:
|
||||
time.sleep(1)
|
||||
|
||||
def get_task_executable(self):
|
||||
"""
|
||||
@@ -91,14 +95,14 @@ class GenericWebsocket:
|
||||
"""
|
||||
return self._run_socket()
|
||||
|
||||
def _start_new_async_socket(self):
|
||||
loop = asyncio.new_event_loop()
|
||||
loop.run_until_complete(self._run_socket())
|
||||
|
||||
def _start_new_socket(self, socketId=None):
|
||||
if not socketId:
|
||||
socketId = len(self.sockets)
|
||||
def start_loop(loop):
|
||||
asyncio.set_event_loop(loop)
|
||||
loop.run_until_complete(self._run_socket())
|
||||
worker_loop = asyncio.new_event_loop()
|
||||
worker = Thread(target=start_loop, args=(worker_loop,))
|
||||
worker = Thread(target=self._start_new_async_socket)
|
||||
worker.start()
|
||||
return socketId
|
||||
|
||||
@@ -128,7 +132,7 @@ class GenericWebsocket:
|
||||
s = Socket(sId)
|
||||
self.sockets[sId] = s
|
||||
loop = asyncio.get_event_loop()
|
||||
while retries < self.max_retries and self.attempt_retry:
|
||||
while self.max_retries == 0 or (retries < self.max_retries and self.attempt_retry):
|
||||
try:
|
||||
async with websockets.connect(self.host) as websocket:
|
||||
self.sockets[sId].set_websocket(websocket)
|
||||
@@ -141,7 +145,7 @@ class GenericWebsocket:
|
||||
await asyncio.sleep(0)
|
||||
message = await websocket.recv()
|
||||
await self.on_message(sId, message)
|
||||
except (ConnectionClosed, socket.error) as e:
|
||||
except (ConnectionClosed, socket.error, InvalidStatusCode) as e:
|
||||
self.sockets[sId].set_disconnected()
|
||||
if self.sockets[sId].isAuthenticated:
|
||||
self.sockets[sId].set_unauthenticated()
|
||||
@@ -190,6 +194,8 @@ class GenericWebsocket:
|
||||
self.events.once(event, func)
|
||||
|
||||
def _emit(self, event, *args, **kwargs):
|
||||
if type(event) == Exception:
|
||||
self.logger.error(event)
|
||||
self.events.emit(event, *args, **kwargs)
|
||||
|
||||
async def on_error(self, error):
|
||||
@@ -202,7 +208,7 @@ class GenericWebsocket:
|
||||
"""
|
||||
This is used by the HF data server.
|
||||
"""
|
||||
self.stop()
|
||||
await self.stop()
|
||||
|
||||
async def on_open(self):
|
||||
"""
|
||||
|
||||
@@ -202,7 +202,7 @@ class OrderManager:
|
||||
if price_trailing != None:
|
||||
payload['price_trailing'] = str(price_trailing)
|
||||
if time_in_force != None:
|
||||
payload['time_in_force'] = str(time_in_force)
|
||||
payload['tif'] = str(time_in_force)
|
||||
if leverage != None:
|
||||
payload['lev'] = str(leverage)
|
||||
flags = calculate_order_flags(
|
||||
|
||||
@@ -62,6 +62,7 @@ class SubscriptionManager:
|
||||
channel = raw_ws_data.get("channel")
|
||||
chan_id = raw_ws_data.get("chanId")
|
||||
key = raw_ws_data.get("key", None)
|
||||
p_sub = None
|
||||
get_key = "{}_{}".format(channel, key or symbol)
|
||||
if chan_id in self.subscriptions_chanid:
|
||||
# subscription has already existed in the past
|
||||
|
||||
@@ -58,6 +58,7 @@ https://github.com/Crypto-toolbox/btfxwss
|
||||
- `notification` (Notification): incoming account notification
|
||||
- `error` (array): error from the websocket
|
||||
- `order_closed` (Order, Trade): when an order has been closed
|
||||
- `order_update` (Order, Trade): when an order has been updated
|
||||
- `order_new` (Order, Trade): when an order has been created but not closed. Note: will not be called if order is executed and filled instantly
|
||||
- `order_confirmed` (Order, Trade): When an order has been submitted and received
|
||||
- `wallet_snapshot` (array[Wallet]): Initial wallet balances (Fired once)
|
||||
@@ -75,10 +76,12 @@ https://github.com/Crypto-toolbox/btfxwss
|
||||
- `funding_credit_snapshot` (array): Opening funding credit balances
|
||||
- `balance_update` (array): When the state of a balance is changed
|
||||
- `new_trade` (array): A new trade on the market has been executed
|
||||
- `new_user_trade` (array): A new - your - trade has been executed
|
||||
- `new_ticker` (Ticker|FundingTicker): A new ticker update has been published
|
||||
- `new_funding_ticker` (FundingTicker): A new funding ticker update has been published
|
||||
- `new_trading_ticker` (Ticker): A new trading ticker update has been published
|
||||
- `trade_update` (array): A trade on the market has been updated
|
||||
- `user_trade_update` (array): A - your - trade has been updated
|
||||
- `new_candle` (array): A new candle has been produced
|
||||
- `margin_info_updates` (array): New margin information has been broadcasted
|
||||
- `funding_info_updates` (array): New funding information has been broadcasted
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
asyncio==3.4.3
|
||||
websockets==8.1
|
||||
websockets==9.1
|
||||
pylint==2.3.0
|
||||
pytest-asyncio==0.10.0
|
||||
pytest-asyncio==0.15.1
|
||||
six==1.12.0
|
||||
pyee==8.0.1
|
||||
aiohttp==3.4.4
|
||||
|
||||
4
setup.py
4
setup.py
@@ -11,7 +11,7 @@ from os import path
|
||||
here = path.abspath(path.dirname(__file__))
|
||||
setup(
|
||||
name='bitfinex-api-py',
|
||||
version='1.1.14',
|
||||
version='2.0.3',
|
||||
description='Official Bitfinex Python API',
|
||||
long_description='A Python reference implementation of the Bitfinex API for both REST and websocket interaction',
|
||||
long_description_content_type='text/markdown',
|
||||
@@ -49,7 +49,7 @@ setup(
|
||||
# deps installed by pip
|
||||
install_requires=[
|
||||
'asyncio~=3.0',
|
||||
'websockets~=8.0',
|
||||
'websockets>=8,<10',
|
||||
'aiohttp~=3.0',
|
||||
'pyee~=8.0'
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user