Skip to content

Commit c8294bb

Browse files
authored
Merge pull request #204 from s4w3d0ff/websocket-dev
Cleanup the websocket class
2 parents 2dd8a37 + 6938443 commit c8294bb

File tree

4 files changed

+100
-138
lines changed

4 files changed

+100
-138
lines changed

README.md

Lines changed: 26 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -77,60 +77,31 @@ print(polo.returnTradeHistory('BTC_ETH'))
7777
You can also not use the 'helper' methods at all and use `poloniex.PoloniexBase` which only has `returnMarketHist` and `__call__` to make rest api calls.
7878

7979
#### Websocket Usage:
80-
To connect to the websocket api just create a child class of `PoloniexSocketed` like so:
80+
To connect to the websocket api use the `PoloniexSocketed` class like so:
8181
```python
8282
import poloniex
8383
import logging
84+
from time import sleep
8485

85-
logging.basicConfig()
86-
87-
class MySocket(poloniex.PoloniexSocketed):
88-
89-
def on_heartbeat(self, msg):
90-
"""
91-
Triggers whenever we get a heartbeat message
92-
"""
93-
print(msg)
94-
95-
def on_volume(self, msg):
96-
"""
97-
Triggers whenever we get a 24hvolume message
98-
"""
99-
print(msg)
100-
101-
def on_ticker(self, msg):
102-
"""
103-
Triggers whenever we get a ticker message
104-
"""
105-
print(msg)
106-
107-
def on_market(self, msg):
108-
"""
109-
Triggers whenever we get a market ('currencyPair') message
110-
"""
111-
print(msg)
112-
113-
def on_account(self, msg):
114-
"""
115-
Triggers whenever we get an account message
116-
"""
117-
print(msg)
118-
119-
sock = MySocket()
12086
# helps show what is going on
121-
sock.logger.setLevel(logging.DEBUG)
122-
# start the websocket thread and subscribe to '24hvolume'
123-
sock.startws(subscribe=['24hvolume'])
87+
logging.basicConfig()
88+
poloniex.logger.setLevel(logging.DEBUG)
89+
90+
def on_volume(data):
91+
print(data)
92+
# make instance
93+
sock = poloniex.PoloniexSocketed()
94+
# start the websocket thread and subscribe to '24hvolume' setting the callback to 'on_volume'
95+
sock.startws(subscribe={'24hvolume': on_volume})
12496
# give the socket some time to init
125-
poloniex.sleep(5)
126-
# this won't work:
127-
#sock.subscribe('ticker')
128-
# use channel id to un/sub
129-
sock.subscribe('1002')
130-
poloniex.sleep(1)
131-
# unsub from ticker
132-
sock.unsubscribe('1002')
133-
poloniex.sleep(4)
97+
sleep(5)
98+
# use the channel name str or id int to subscribe/unsubscribe
99+
sock.subscribe(chan='ticker', callback=print)
100+
sleep(1)
101+
# unsub from ticker using id (str name can be use as well)
102+
sock.unsubscribe(1002)
103+
sleep(4)
104+
# stop websocket
134105
sock.stopws()
135106

136107
```
@@ -152,5 +123,12 @@ DEBUG:poloniex:Unsubscribed to ticker
152123
DEBUG:poloniex:Websocket Closed
153124
INFO:poloniex:Websocket thread stopped/joined
154125
```
126+
You can also subscribe and start the websocket thread when creating an instance of `PoloniexSocketed` by using the `subscribe` and `start` args:
127+
```python
128+
129+
sock = poloniex.PoloniexSocketed(subscribe={'24hvolume': print}, start=True)
130+
131+
```
132+
155133

156134
**More examples of how to use websocket push API can be found [here](https://github.com/s4w3d0ff/python-poloniex/tree/master/examples).**

examples/websocket/dictTicker.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def on_ticker(self, data):
4545
polo = TickPolo()
4646
poloniex.logging.basicConfig()
4747
polo.logger.setLevel(poloniex.logging.DEBUG)
48-
polo.startws(['ticker'])
48+
polo.startws({'ticker': polo.on_ticker})
4949
for i in range(3):
5050
pprint(polo.ticker('BTC_LTC'))
5151
poloniex.sleep(10)

examples/websocket/stopLimit.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ def __init__(self, *args, **kwargs):
99
def on_ticker(self, msg):
1010
data = [float(dat) for dat in msg]
1111
# check stop orders
12-
mkt = self.channels[str(int(data[0]))]['name']
12+
mkt = self._getChannelName(str(int(data[0])))
1313
la = data[2]
1414
hb = data[3]
1515
for id in self.stopOrders:
@@ -98,6 +98,6 @@ def callbk(id):
9898
callback=callbk,
9999
# remove or set 'test' to false to place real orders
100100
test=True)
101-
test.startws(['ticker'])
101+
test.startws({'ticker': test.on_ticker})
102102
poloniex.sleep(120)
103103
test.stopws(3)

poloniex/__init__.py

Lines changed: 71 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -683,14 +683,8 @@ def toggleAutoRenew(self, orderNumber):
683683

684684
class PoloniexSocketed(Poloniex):
685685
""" Child class of Poloniex with support for the websocket api """
686-
def __init__(self, *args, **kwargs):
687-
subscribe = False
688-
start = False
689-
if 'subscribe' in kwargs:
690-
subscribe = kwargs.pop('subscribe')
691-
if 'startws' in kwargs:
692-
start = kwargs.pop('startws')
693-
super(PoloniexSocketed, self).__init__(*args, **kwargs)
686+
def __init__(self, key=None, secret=None, subscribe={}, start=False, *args, **kwargs):
687+
super(PoloniexSocketed, self).__init__(key, secret, *args, **kwargs)
694688
self.socket = WebSocketApp(url="wss://api2.poloniex.com/",
695689
on_open=self.on_open,
696690
on_message=self.on_message,
@@ -699,61 +693,30 @@ def __init__(self, *args, **kwargs):
699693
self._t = None
700694
self._running = False
701695
self.channels = {
702-
'1000': {'name': 'account',
703-
'sub': False,
704-
'callback': self.on_account},
705-
'1002': {'name': 'ticker',
706-
'sub': False,
707-
'callback': self.on_ticker},
708-
'1003': {'name': '24hvolume',
709-
'sub': False,
710-
'callback': self.on_volume},
711-
'1010': {'name': 'heartbeat',
712-
'sub': False,
713-
'callback': self.on_heartbeat},
696+
'account': {'id': '1000'},
697+
'ticker': {'id': '1002'},
698+
'24hvolume': {'id': '1003'},
699+
'heartbeat': {'id': '1010',
700+
'callback': self.on_heartbeat},
714701
}
715702
# add each market to channels list by id
716-
# (wish there was a cleaner way of doing this...)
717703
tick = self.returnTicker()
718704
for market in tick:
719-
self.channels[str(tick[market]['id'])] = {
720-
'name': market,
721-
'sub': False,
722-
'callback': self.on_market
723-
}
705+
self.channels[market] = {'id': str(tick[market]['id'])}
706+
# handle init subscribes
724707
if subscribe:
725-
for chan in self.channels:
726-
if self.channels[chan]['name'] in subscribe or chan in subscribe:
727-
self.channels[chan]['sub'] = True
708+
self.setSubscribes(**subscribe)
728709
if start:
729710
self.startws()
730711

731-
732-
def _handle_sub(self, message):
733-
""" Handles websocket un/subscribe messages """
734-
chan = str(message[0])
735-
# skip heartbeats
736-
if not chan == '1010':
737-
# Subscribed
738-
if message[1] == 1:
739-
# update self.channels[chan]['sub'] flag
740-
self.channels[chan]['sub'] = True
741-
self.logger.debug('Subscribed to %s', self.channels[chan]['name'])
742-
# return False so no callback trigger
743-
return False
744-
# Unsubscribed
745-
if message[1] == 0:
746-
# update self.channels[chan]['sub'] flag
747-
self.channels[chan]['sub'] = False
748-
self.logger.debug('Unsubscribed to %s', self.channels[chan]['name'])
749-
# return False so no callback trigger
750-
return False
751-
# return chan name
752-
return chan
712+
def _getChannelName(self, id):
713+
return next(
714+
(chan for chan in self.channels if self.channels[chan]['id'] == id),
715+
False)
753716

754717
def on_open(self, *ws):
755718
for chan in self.channels:
756-
if self.channels[chan]['sub']:
719+
if 'sub' in self.channels[chan] and self.channels[chan]['sub']:
757720
self.subscribe(chan)
758721

759722
def on_message(self, data):
@@ -766,45 +729,63 @@ def on_message(self, data):
766729
# catch errors
767730
if 'error' in message:
768731
return self.logger.error(message['error'])
732+
chan = self._getChannelName(str(message[0]))
769733
# handle sub/unsub
770-
chan = self._handle_sub(message)
771-
if chan:
734+
# skip heartbeats
735+
if not chan == 'heartbeat':
736+
# Subscribed
737+
if message[1] == 1:
738+
self.logger.debug('Subscribed to %s', chan)
739+
# return False so no callback trigger
740+
return False
741+
# Unsubscribed
742+
if message[1] == 0:
743+
self.logger.debug('Unsubscribed to %s', chan)
744+
# return False so no callback trigger
745+
return False
746+
if 'callback' in self.channels[chan]:
772747
# activate chan callback
773-
# heartbeats are funky
774-
if not chan == '1010':
748+
if not chan in ['account', 'heartbeat']:
775749
message = message[2]
776750
self.socket._callback(self.channels[chan]['callback'], message)
777751

778752
def on_error(self, error):
779753
self.logger.error(error)
780754

781755
def on_close(self, *args):
782-
self.logger.debug('Websocket Closed')
756+
self.logger.info('Websocket Closed')
783757

784-
def on_ticker(self, args):
785-
self.logger.debug(args)
786-
787-
def on_account(self, args):
788-
self.logger.debug(args)
789-
790-
def on_market(self, args):
758+
def on_heartbeat(self, args):
791759
self.logger.debug(args)
792760

793-
def on_volume(self, args):
794-
self.logger.debug(args)
761+
def setCallback(self, chan, callback):
762+
""" Sets the callback function for <chan> """
763+
if isinstance(chan, int):
764+
chan = self._getChannelName(str(chan))
765+
self.channels[chan]['callback'] = callback
795766

796-
def on_heartbeat(self, args):
797-
self.logger.debug(args)
767+
def setSubscribes(self, **subs):
768+
for sub in subs:
769+
if not sub in self.channels:
770+
self.logger.warning('Invalid channel: %s', sub)
771+
else:
772+
self.channels[sub]['sub'] = True
773+
self.channels[sub]['callback'] = subs[sub]
798774

799-
def subscribe(self, chan):
775+
def subscribe(self, chan, callback=None):
800776
""" Sends the 'subscribe' command for <chan> """
777+
if isinstance(chan, int):
778+
chan = self._getChannelName(str(chan))
801779
# account chan?
802-
if chan in ['1000', 1000]:
780+
if chan == 'account':
803781
# sending commands to 'account' requires a key, secret and nonce
804782
if not self.key or not self.secret:
805783
raise PoloniexError(
806784
"self.key and self.secret needed for 'account' channel"
807785
)
786+
self.channels[chan]['sub'] = True
787+
if callback:
788+
self.channels[chan]['callback'] = callback
808789
payload = {'nonce': self.nonce}
809790
payload_encoded = _urlencode(payload)
810791
sign = _new(
@@ -814,22 +795,29 @@ def subscribe(self, chan):
814795

815796
self.socket.send(_dumps({
816797
'command': 'subscribe',
817-
'channel': chan,
798+
'channel': self.channels[chan]['id'],
818799
'sign': sign.hexdigest(),
819800
'key': self.key,
820801
'payload': payload_encoded}))
821802
else:
822-
self.socket.send(_dumps({'command': 'subscribe', 'channel': chan}))
803+
self.channels[chan]['sub'] = True
804+
if callback:
805+
self.channels[chan]['callback'] = callback
806+
self.socket.send(_dumps({'command': 'subscribe',
807+
'channel': self.channels[chan]['id']}))
823808

824809
def unsubscribe(self, chan):
825810
""" Sends the 'unsubscribe' command for <chan> """
811+
if isinstance(chan, int):
812+
chan = self._getChannelName(str(chan))
826813
# account chan?
827-
if chan in ['1000', 1000]:
814+
if chan == 'account':
828815
# sending commands to 'account' requires a key, secret and nonce
829816
if not self.key or not self.secret:
830817
raise PoloniexError(
831818
"self.key and self.secret needed for 'account' channel"
832819
)
820+
self.channels[chan]['sub'] = False
833821
payload = {'nonce': self.nonce}
834822
payload_encoded = _urlencode(payload)
835823
sign = _new(
@@ -839,39 +827,35 @@ def unsubscribe(self, chan):
839827

840828
self.socket.send(_dumps({
841829
'command': 'unsubscribe',
842-
'channel': chan,
830+
'channel': self.channels[chan]['id'],
843831
'sign': sign.hexdigest(),
844832
'key': self.key,
845833
'payload': payload_encoded}))
846834
else:
847-
self.socket.send(_dumps({'command': 'unsubscribe', 'channel': chan}))
835+
self.channels[chan]['sub'] = False
836+
self.socket.send(_dumps({'command': 'unsubscribe',
837+
'channel': self.channels[chan]['id']}))
848838

849-
def setCallback(self, chan, callback):
850-
""" Sets the callback function for <chan> """
851-
self.channels[chan]['callback'] = callback
852-
853-
def startws(self, subscribe=[]):
839+
def startws(self, subscribe=False):
854840
"""
855841
Run the websocket in a thread, use 'subscribe' arg to subscribe
856-
to a channel on start
842+
to channels on start
857843
"""
858844
self._t = Thread(target=self.socket.run_forever)
859845
self._t.daemon = True
860846
self._running = True
861847
# set subscribes
862-
for chan in self.channels:
863-
if self.channels[chan]['name'] in subscribe or chan in subscribe:
864-
self.channels[chan]['sub'] = True
848+
if subscribe:
849+
self.setSubscribes(**subscribe)
865850
self._t.start()
866851
self.logger.info('Websocket thread started')
867852

868-
869-
def stopws(self, wait=0):
853+
def stopws(self, wait=1):
870854
""" Stop/join the websocket thread """
871855
self._running = False
872856
# unsubscribe from subs
873857
for chan in self.channels:
874-
if self.channels[chan]['sub'] == True:
858+
if 'sub' in self.channels[chan] and self.channels[chan]['sub']:
875859
self.unsubscribe(chan)
876860
sleep(wait)
877861
try:

0 commit comments

Comments
 (0)