Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 50 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,55 @@
# Simple package for Deribit API v2 websocket

## Description
The package use [aiohttp](https://aiohttp.readthedocs.io)

API description look at [Deribit API v2 websocket](https://docs.deribit.com/v2/?python#json-rpc)
# ssc2ce
A Set of Simple Connectors for access To Cryptocurrency Exchanges via websocket based on
[aiohttp](https://aiohttp.readthedocs.io) .

## Installation
Install ons-deribit with:
Install ssc2ce with:
```bash
$ pip install ons-deribit
$ pip install ssc2ce
```
## Basic example

## Bitfinex
### Description
API description look at [Websocket API v2](https://docs.bitfinex.com/v2/docs/ws-general)
### Basic example
```python
import asyncio

from ssc2ce import Bitfinex

conn = Bitfinex()


async def handle_subscription(data):
print(data)

async def subscribe():
await conn.subscribe({
"channel": "ticker",
"symbol": "tBTCUSD"
}, handler=handle_subscription)


conn.on_connect_ws = subscribe

loop = asyncio.get_event_loop()

try:
loop.run_until_complete(conn.run_receiver())
except KeyboardInterrupt:
print("Application closed by KeyboardInterrupt.")

```

## Deribit
### Description

API description look at [Deribit API v2 websocket](https://docs.deribit.com/v2/?python#json-rpc)

### Basic example
```python
#!/usr/bin/env python
import asyncio
from deribit import Deribit
from ssc2ce import Deribit

conn = Deribit()

Expand Down Expand Up @@ -56,15 +91,15 @@ If you clone repository you can run examples from the root directory.
$ PYTHONPATH=.:$PYTHONPATH python examples/basic_example.py
```

The private.py example uses [python-dotenv](https://github.com/theskumar/python-dotenv), you must either install it if you want the example to work right out of the box,
The deribit_private.py example uses [python-dotenv](https://github.com/theskumar/python-dotenv), you must either install it if you want the example to work right out of the box,
```bash
$ pip install python-dotenv
```
or make the corresponding changes, removed followed code.
```python
from dotenv import load_dotenv
dotenv_path = os.path.join(os.path.dirname(__file__), '.env')
load_dotenv(dotenv_path)
from dotenv import load_dotenv
dotenv_path = os.path.join(os.path.dirname(__file__), '.env')
load_dotenv(dotenv_path)
```
To run the private.py example, you must either fill in the .env file or set the environment variables DERIBIT_CLIENT_ID and DERIBIT_CLIENT_SECRET. Look at .env_default.
```bash
Expand Down
1 change: 0 additions & 1 deletion deribit/VERSION.py

This file was deleted.

1 change: 0 additions & 1 deletion deribit/__init__.py

This file was deleted.

31 changes: 31 additions & 0 deletions examples/bitfinex_basic_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/usr/bin/env python
import asyncio
import logging

from ssc2ce import Bitfinex

logging.basicConfig(format='%(asctime)s %(name)s %(funcName)s %(levelname)s %(message)s', level=logging.INFO)
logger = logging.getLogger("ons-derobit-ws-python-sample")

conn = Bitfinex()


async def handle_subscription(data):
print(data)


async def subscribe():
await conn.subscribe({
"channel": "ticker",
"symbol": "tBTCUSD"
}, handler=handle_subscription)


conn.on_connect_ws = subscribe

loop = asyncio.get_event_loop()

try:
loop.run_until_complete(conn.run_receiver())
except KeyboardInterrupt:
print("Application closed by KeyboardInterrupt.")
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env python
import asyncio
from deribit import Deribit
from ssc2ce import Deribit

conn = Deribit()

Expand Down
4 changes: 2 additions & 2 deletions examples/private.py → examples/deribit_private.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@
from uuid import uuid4

from dotenv import load_dotenv
from deribit.deribit import Deribit, AuthType
from ssc2ce.deribit import Deribit, AuthType

dotenv_path = os.path.join(os.path.dirname(__file__), '.env')
load_dotenv(dotenv_path)

logging.basicConfig(format='%(asctime)s %(name)s %(funcName)s %(levelname)s %(message)s', level=logging.WARNING)
logger = logging.getLogger("ons-derobit-ws-python-sample")
logger = logging.getLogger("deribit-private")

client_id = os.environ.get('DERIBIT_CLIENT_ID')
client_secret = os.environ.get('DERIBIT_CLIENT_SECRET')
Expand Down
8 changes: 4 additions & 4 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,20 @@

import setuptools

from deribit.VERSION import __version__
from ssc2ce.VERSION import __version__

with open("README.md", "r") as fh:
long_description = fh.read()

setuptools.setup(
name='ons-deribit',
name='ssc2ce',
version=__version__,
author='Oleg Nedbaylo',
author_email='olned64@gmail.com',
description='Simple Deribit API v2 on Websocket',
description='A Set of Simple Connectors for access To Cryptocurrency Exchanges',
long_description=long_description,
long_description_content_type="text/markdown",
url='https://github.com/olned/ons-deribit-ws-python',
url='https://github.com/olned/ssc2ce-python',
packages=setuptools.find_packages(),
install_requires=['aiohttp'],
classifiers=[
Expand Down
1 change: 1 addition & 0 deletions ssc2ce/VERSION.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__version__ = "0.5.0"
2 changes: 2 additions & 0 deletions ssc2ce/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from .deribit import Deribit
from .bitfinex import Bitfinex
173 changes: 173 additions & 0 deletions ssc2ce/bitfinex.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
import logging

import aiohttp

from .session import SessionWrapper
from .utils import resolve_route

from enum import IntEnum


class Bitfinex(SessionWrapper):
class ConfigFlag(IntEnum):
TIMESTAMP = 32768
SEQ_ALL = 65536
CHECKSUM = 131072

class StatusFlag(IntEnum):
MAINTENANCE = 0
OPERATIVE = 1

ws: aiohttp.ClientWebSocketResponse = None
on_connect_ws = None
on_close_ws = None
on_maintenance = None
on_conf = None

ws_api = 'wss://api-pub.bitfinex.com/ws/2'

last_message = None
is_connected = False
subscriptions = []
channel_handlers = {}

def __init__(self,
flags: ConfigFlag = ConfigFlag.TIMESTAMP | ConfigFlag.SEQ_ALL):
super().__init__()

self.flags = flags
self.logger = logging.getLogger(__name__)

self._timeout: aiohttp.ClientTimeout = aiohttp.ClientTimeout(total=20)

self.on_message = self.handle_message
# self.on_connect = self.configure
self.routes = [
("subscribed", self.handle_subscribed),
("info", self.handle_info),
("conf", self.handle_conf),
# ("", self.warn_handler),
]

# self.channel_route = []

async def configure(self, flags: ConfigFlag = None):
if flags is not None:
self.flags = flags

if self.flags is not None:
request = dict(event="conf", flags=self.flags)
await self.ws.send_json(request)

async def subscribe(self, request, handler):
self.subscriptions.append((request, handler))
await self.ws.send_json({
"event": "subscribe",
**request
})

async def run_receiver(self):
self.ws = await self._session.ws_connect(self.ws_api)

while self.ws and not self.ws.closed:
message = await self.ws.receive()
self.last_message = message

if message.type in (aiohttp.WSMsgType.CLOSED, aiohttp.WSMsgType.CLOSE, aiohttp.WSMsgType.ERROR):
self.logger.warning(f"Connection close {repr(message)}")
if self.on_close_ws:
await self.on_close_ws()

continue
if message.type == aiohttp.WSMsgType.CLOSING:
self.logger.debug(f"Connection closing {repr(message)}")
continue

if self.on_message:
await self.on_message(message)

async def handle_message(self, message: aiohttp.WSMessage):
if message.type == aiohttp.WSMsgType.TEXT:
data = message.json()

if isinstance(data, list):
channel_id = data[0]
handler = self.channel_handlers.get(channel_id)
if handler:
await handler(data)
else:
self.logger.warning(f"Can't find handler for channel_id{channel_id}, {data}")
elif isinstance(data, dict):
if "event" in data:
await self.handle_event(data)
else:
self.logger.warning(f"Unknown message {message.data}")
else:
self.logger.warning(f"Unknown message {message.data}")
else:
self.logger.warning(f"Unknown type of message {repr(message)}")

async def empty_handler(self, data):
pass

async def handle_subscribed(self, message):
self.logger.info(f"receive subscribed: {message}")
idx = None
for i, s in enumerate(self.subscriptions):
if s[0].items() <= message.items():
idx = i
self.channel_handlers[message["chanId"]] = s[1]

if idx:
del self.subscriptions[idx]

async def handle_conf(self, message):
"""{'event': 'conf', 'status': 'OK', 'flags': 98304}"""
self.logger.info(f"{message}")
if self.on_conf:
await self.on_conf()

async def handle_info(self, message):
"""
Handle an info message that contains the actual version of the websocket stream, along with a platform status
flag (1 for operative, 0 for maintenance
:param message: Info messages {'event': 'info', 'version': 2, 'serverId': ..., 'platform': {'status': 1}}
version: the actual version of the websocket stream must be 2
status: a platform status flag (1 for operative, 0 for maintenance).
:return:
"""
self.logger.info(f"{message}")

if message["version"] != 2:
raise NotImplemented(f"Bitfinex connector support only version 2 but receive {message}")

if message["platform"]["status"] == 1:
if not self.is_connected:
await self.configure()
if self.on_connect_ws:
await self.on_connect_ws()
else:
if self.on_maintenance:
await self.on_maintenance(message)
else:
if self.on_maintenance:
await self.on_maintenance(message)

async def warn_handler(self, message):
self.logger.warning(f"Unsupported message {message}")

async def handle_event(self, message):
event = message["event"]
handler = resolve_route(event, self.routes)

if handler:
return await handler(message)

self.logger.warning(f"Unhandled event:{event}, message:{repr(message)} .")
return

def close(self):
super()._close()

async def stop(self):
await self.ws.close()
8 changes: 8 additions & 0 deletions ssc2ce/common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from enum import IntEnum


class AuthType(IntEnum):
NONE = 0
PASSWORD = 1
CREDENTIALS = 2
SIGNATURE = 3
Loading