Skip to content

Commit

Permalink
Merge pull request #26 from btschwertfeger/optimize-ws-clients
Browse files Browse the repository at this point in the history
- added recovering of subscription (works now)
- simplified websocket client instantiation
- added private and protected properties and methods to prevent false calls
  • Loading branch information
btschwertfeger authored Nov 23, 2022
2 parents 11bc369 + 44230bf commit 0cbaa73
Show file tree
Hide file tree
Showing 14 changed files with 583 additions and 846 deletions.
532 changes: 0 additions & 532 deletions README.md

This file was deleted.

51 changes: 34 additions & 17 deletions examples/futures_ws_examples.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
import os, sys, time
import asyncio
import logging
import logging.config
import logging, logging.config
from dotenv import dotenv_values
from datetime import datetime

try:
from kraken.futures.client import WsClient
from kraken.futures.websocket.websocket import KrakenFuturesWSClient
from kraken.futures.client import KrakenFuturesWSClient
except:
print('USING LOCAL MODULE')
sys.path.append('/Users/benjamin/repositories/Trading/python-kraken-sdk')
from kraken.futures.client import WsClient
from kraken.futures.websocket.websocket import KrakenFuturesWSClient
from kraken.futures.client import KrakenFuturesWSClient

logging.basicConfig(
format='%(asctime)s %(module)s,line: %(lineno)d %(levelname)8s | %(message)s',
Expand All @@ -29,7 +26,7 @@ async def main() -> None:
key = dotenv_values('.env')['Futures_API_KEY']
secret = dotenv_values('.env')['Futures_SECRET_KEY']

# ___Custom_Trading_Bot______________
# ___Custom_Trading_Bot__________
class Bot(KrakenFuturesWSClient):

async def on_message(self, event) -> None:
Expand All @@ -38,40 +35,60 @@ async def on_message(self, event) -> None:
# you can also combine this with the Futures REST clients

# _____Public_Websocket_Feeds___________________
bot = Bot(WsClient())
bot = Bot()
# print(bot.get_available_public_subscription_feeds())

products = ['PI_XBTUSD']
products = ['PI_XBTUSD', 'PF_SOLUSD']
# subscribe to a public websocket feed
await bot.subscribe(feed='ticker', products=products)
# await bot.subscribe(feed='book', products=products)
await bot.subscribe(feed='book', products=products)
# await bot.subscribe(feed='trade', products=products)
# await bot.subscribe(feed='ticker_lite', products=products)
# await bot.subscribe(feed='heartbeat')
# time.sleep(2)

# unsubscribe from a websocket feed
time.sleep(2) # in case subscribe is not done yet
await bot.unsubscribe(feed='ticker', products=products)
await bot.unsubscribe(feed='ticker', products=['PI_XBTUSD'])
await bot.unsubscribe(feed='ticker', products=['PF_SOLUSD'])
await bot.unsubscribe(feed='book', products=products)
# ....

# _____Private_Websocket_Feeds_________________
auth_bot = Bot(WsClient(key=key, secret=secret))
auth_bot = Bot(key=key, secret=secret)
# print(auth_bot.get_available_private_subscription_feeds())

# subscribe to a private/authenticated websocket feed
# await auth_bot.subscribe(feed='fills')
await auth_bot.subscribe(feed='fills')
await auth_bot.subscribe(feed='open_positions')
# await auth_bot.subscribe(feed='open_orders')
# await auth_bot.subscribe(feed='open_orders_verbose')
# await auth_bot.subscribe(feed='deposits_withdrawals')
# await auth_bot.subscribe(feed='account_balances_and_margins')
# await auth_bot.subscribe(feed='account_log)
# await auth_bot.subscribe(feed='notifications_auth)
# await auth_bot.subscribe(feed='balances')
# await auth_bot.subscribe(feed='account_log')
# await auth_bot.subscribe(feed='notifications_auth')

# authenticaed clients can also subscribe to public feeds
# await auth_bot.subscribe(feed='ticker', products=['PI_XBTUSD', 'PF_ETHUSD'])

# time.sleep(1)
# unsubscribe from a private/authenticaed websocket feed
# await bot.unsubscribe(feed='fills')
await auth_bot.unsubscribe(feed='fills')
await auth_bot.unsubscribe(feed='open_positions')
# ....

while True: await asyncio.sleep(6)

if __name__ == '__main__':
asyncio.get_event_loop().run_until_complete(main())
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
try:
asyncio.run(main())
except KeyboardInterrupt:
loop.close()
# the websocket client will send {'event': 'ws-cancelled-error'} via on_message
# so you can handle the behavior/next actions individually within you bot

# old way will be deprecated in python 3.11:
# asyncio.get_event_loop().run_until_complete(main())
2 changes: 1 addition & 1 deletion examples/spot_examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def user_examples() -> None:
print(user.delete_export_report(id_=r['id'], type_='delete'))#type=cancel

def market_examples() -> None:
market = Market(key=key, secret=secret)
market = Market()

print(market.get_assets(assets=['XBT']))
print(market.get_tradable_asset_pair(pair=['DOTEUR']))
Expand Down
53 changes: 29 additions & 24 deletions examples/spot_ws_examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,11 @@
import time

try:
from kraken.spot.client import WsClient
from kraken.spot.websocket.websocket import KrakenSpotWSClient
from kraken.spot.client import KrakenSpotWSClient
except:
print('Using local module')
print('USING LOCAL MODULE')
sys.path.append('/Users/benjamin/repositories/Trading/python-kraken-sdk')
from kraken.spot.client import WsClient
from kraken.spot.websocket.websocket import KrakenSpotWSClient
from kraken.spot.client import KrakenSpotWSClient

logging.basicConfig(
format='%(asctime)s %(module)s,line: %(lineno)d %(levelname)8s | %(message)s',
Expand All @@ -25,33 +23,34 @@
logging.getLogger('requests').setLevel(logging.WARNING)
logging.getLogger('urllib3').setLevel(logging.WARNING)

async def main() -> None:
async def main2() -> None:

key = dotenv_values('.env')['API_KEY']
secret = dotenv_values('.env')['SECRET_KEY']

# ___Custom_Trading_Bot______________
class Bot(KrakenSpotWSClient):

async def on_message(self, event) -> None:
if 'event' in event:
topic = event['event']
if topic == 'heartbeat': return
elif topic == 'pong': return

print(event)
# await self._client.create_order(
# ordertype='limit',
# side='buy',
# pair='BTC/EUR',
# price=20000,
# volume=1
# )
# if condition:
# await self.create_order(
# ordertype='limit',
# side='buy',
# pair='BTC/EUR',
# price=20000,
# volume=200
# )
# ... it is also possible to call regular REST endpoints
# but using the websocket messages is more efficient

# ___Public_Websocket_Feed_____
bot = Bot(WsClient()) # only use this one if you dont need private feeds
bot = Bot() # only use this one if you dont need private feeds
# print(bot.public_sub_names) # list public subscription names

await bot.subscribe(subscription={ 'name': 'ticker' }, pair=['XBT/EUR', 'DOT/EUR'])
Expand All @@ -64,12 +63,14 @@ async def on_message(self, event) -> None:
# await bot.subscribe(subscription={ 'name': '*' } , pair=['BTC/EUR'])

time.sleep(2) # wait because unsubscribing is faster than subscribing ...
# print(bot.active_public_subscriptions)
await bot.unsubscribe(subscription={ 'name': 'ticker' }, pair=['XBT/EUR','DOT/EUR'])
await bot.unsubscribe(subscription={ 'name': 'spread' }, pair=['XBT/EUR'])
await bot.unsubscribe(subscription={ 'name': 'spread' }, pair=['DOT/EUR'])
# ....

auth_bot = Bot(WsClient(key=key, secret=secret))
auth_bot = Bot(key=key, secret=secret)
# print(bot.active_private_subscriptions)
# print(auth_bot.private_sub_names) # list private subscription names
# when using the authenticated bot, you can also subscribe to public feeds
await auth_bot.subscribe(subscription={ 'name': 'ownTrades' })
Expand All @@ -80,13 +81,17 @@ async def on_message(self, event) -> None:
await auth_bot.unsubscribe(subscription={ 'name': 'openOrders' })


while True:
await asyncio.sleep(6)
# display the active subscriptions ...
# print(bot.active_public_subscriptions)

# print(auth_bot.active_public_subscriptions)
# print(auth_bot.active_private_subscriptions)
while True: await asyncio.sleep(6)

if __name__ == '__main__':
asyncio.get_event_loop().run_until_complete(main())
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
try:
asyncio.run(main2())
except KeyboardInterrupt:
loop.close()
# the websocket client will send {'event': 'ws-cancelled-error'} via on_message
# so you can handle the behavior/next actions individually within you bot

# deprecated in python 3.11:
# asyncio.get_event_loop().run_until_complete(main())
2 changes: 1 addition & 1 deletion kraken/__version__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
VERSION = (0, 7, 0)
VERSION = (0, 7, 1)
__version__ = '.'.join(map(str, VERSION))
35 changes: 16 additions & 19 deletions kraken/base_api/base_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,12 @@ class KrakenBaseRestAPI(object):

def __init__(self, key: str='', secret: str='', url: str='', sandbox: bool=False, **kwargs):

self._api_v = ''
if url: self.url = url
self.url = 'https://api.kraken.com'
self._api_v = '/0'
if url != '': self.url = url
else: self.url = 'https://api.kraken.com'
self.api_v = '/0'

self.key = key
self.secret = secret
self.__key = key
self.__secret = secret

def _request(self,
method: str,
Expand All @@ -43,16 +42,15 @@ def _request(self,

headers = { 'User-Agent': 'python-kraken-sdk' }
if auth:
if not self.key or self.key == '' or not self.secret or self.secret == '': raise ValueError('Missing credentials')
params['nonce'] = str(int(time.time() * 1000)) # generate nonce
if not self.__key or self.__key == '' or not self.__secret or self.__secret == '': raise ValueError('Missing credentials')
params['nonce'] = str(int(time.time() * 1000))
headers = {
'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
'API-Key': self.key,
'API-Sign': self.get_kraken_signature(f'{self._api_v}{uri}', params)
'API-Key': self.__key,
'API-Sign': self.get_kraken_signature(f'{self.api_v}{uri}', params)
}

url = f'{self.url}{self._api_v}{uri}'
# print(url)
url = f'{self.url}{self.api_v}{uri}'
if method in ['GET', 'DELETE']:
return self.check_response_data(requests.request(method=method, url=url, headers=headers, timeout=timeout), return_raw)
elif do_json:
Expand All @@ -63,7 +61,7 @@ def _request(self,
def get_kraken_signature(self, urlpath: str, data: dict) -> str:
return base64.b64encode(
hmac.new(
base64.b64decode(self.secret),
base64.b64decode(self.__secret),
urlpath.encode() + hashlib.sha256((str(data['nonce']) + urllib.parse.urlencode(data)).encode()).digest(),
hashlib.sha512
).digest()
Expand Down Expand Up @@ -93,7 +91,6 @@ def _to_str_list(self, a) -> str:
elif type(a) == list: return ','.join([i for i in a])
else: raise ValueError('a must be string or list of strings')


class KrakenBaseFuturesAPI(object):
def __init__(self, key: str='', secret: str='', url: str='', sandbox: bool=False, **kwargs):

Expand All @@ -102,8 +99,8 @@ def __init__(self, key: str='', secret: str='', url: str='', sandbox: bool=False
elif self.sandbox: self.url = 'https://demo-futures.kraken.com'
else: self.url = 'https://futures.kraken.com'

self.key = key
self.secret = secret
self.__key = key
self.__secret = secret
self.nonce = 0

def _request(self,
Expand Down Expand Up @@ -131,13 +128,13 @@ def _request(self,

headers = { 'User-Agent': 'python-kraken-sdk' }
if auth:
if not self.key or self.key == '' or not self.secret or self.secret == '': raise ValueError('Missing credentials')
if not self.__key or self.__key == '' or not self.__secret or self.__secret == '': raise ValueError('Missing credentials')
self.nonce = (self.nonce + 1) % 1
nonce = str(int(time.time() * 1000)) + str(self.nonce).zfill(4)
headers = {
'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
'Nonce': nonce,
'APIKey': self.key,
'APIKey': self.__key,
'Authent': self.get_kraken_futures_signature(uri, queryString + postString, nonce)
}

Expand Down Expand Up @@ -180,7 +177,7 @@ def get_kraken_futures_signature(self, endpoint: str, data: str, nonce: str) ->
sha256_hash.update((data + nonce + endpoint).encode('utf8'))
return base64.b64encode(
hmac.new(
base64.b64decode(self.secret),
base64.b64decode(self.__secret),
sha256_hash.digest(),
hashlib.sha512
).digest()
Expand Down
4 changes: 2 additions & 2 deletions kraken/futures/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from kraken.futures.trade.trade import TradeClient
from kraken.futures.user.user import UserClient
from kraken.futures.funding.funding import FundingClient
from kraken.futures.ws_client.ws_client import FuturesWsClientCl
from kraken.futures.websocket.websocket import KrakenFuturesWSClientCl

class User(UserClient):
pass
Expand All @@ -16,5 +16,5 @@ class Market(MarketClient):
class Funding(FundingClient):
pass

class WsClient(FuturesWsClientCl):
class KrakenFuturesWSClient(KrakenFuturesWSClientCl):
pass
Loading

0 comments on commit 0cbaa73

Please sign in to comment.