Pages:
Author

Topic: [BETA] MTGox websocket API, testers wanted - page 8. (Read 77681 times)

hero member
Activity: 938
Merit: 500
https://youengine.io/
Im actually using php server side, and there is very little to be found on the web. Only code to create websockets, not to connect to them.

Anyway, I dont mind coding it if I know what to send, but python is mostly like chinese to me, so can you do me one more favor and modify websocket.py
change line 43 to:
_debug = True

Then run your app.
It will spit out the handshakes. If you copy/paste the output  I can try to implement it in php.

You seem to be looking at a different version of websocket.py, there is no _debug variable at line 43 (I'm using version 0.9.0 (this is the latest version, implementing websocket version 13). I have enabled tracing, this is the log until after the channel_subscribe()

Note the messages starting with 0x81, this is the framing to delimit the messages, this is why it is absolutely no fun implementing this by hand if you can get a working implementation from somewhere. Now after looking at this dump myself I remember, my first attempt writing the webocket client myself the first problem I stumbled upon were the Sec-Websocket-Key and the Sec-Websocket-Version headers, they look slightly different for every of the dozen ws versions that exist and MtGox does not like all variants of them. The funny thing is my first implementation worked just fine with their plain websocket server and refused to connect to the socketio. Then i threw it away and used websocket.py.

Code:
2013-03-19 13:48:54,752:DEBUG:SocketIOClient:trying Websocket: wss://socketio.mtgox.com/socket.io/1/websocket/10083243691392078721?Currency=USD ...
2013-03-19 13:48:59,312:DEBUG:--- request header ---
2013-03-19 13:48:59,313:DEBUG:GET /socket.io/1/websocket/10083243691392078721?Currency=USD HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Host: socketio.mtgox.com:443
Origin: socketio.mtgox.com:443
Sec-WebSocket-Key: UWaphFPiSqq3f2gOmaD5Sg==
Sec-WebSocket-Version: 13


2013-03-19 13:48:59,313:DEBUG:-----------------------
2013-03-19 13:48:59,313:DEBUG:--- response header ---
2013-03-19 13:48:59,762:DEBUG:HTTP/1.1 101 Switching Protocols
2013-03-19 13:48:59,763:DEBUG:Upgrade: websocket
2013-03-19 13:48:59,763:DEBUG:Connection: Upgrade
2013-03-19 13:48:59,764:DEBUG:Sec-WebSocket-Accept: Bzz8qKJPEMNNOgC4hOZd3iZNb5o=
2013-03-19 13:48:59,764:DEBUG:-----------------------
2013-03-19 13:48:59,764:DEBUG:SocketIOClient:connected
2013-03-19 13:48:59,767:DEBUG:send: '\x81\x89\x9f\x99\x10\xa1\xae\xa3*\x8e\xf2\xedw\xce\xe7'
2013-03-19 13:48:59,767:DEBUG:recv: '\x81\x031::'
2013-03-19 13:49:00,303:DEBUG:recv: '\x81\t1::/mtgox'
2013-03-19 13:49:00,304:DEBUG:SocketIOClient:subscribing to channels
2013-03-19 13:49:00,306:DEBUG:send: '\x81\xb4\xe7\xc2\xc8\xda\xd3\xf8\xf2\xf5\x8a\xb6\xaf\xb5\x9f\xf8\xb3\xf8\x93\xbb\xb8\xbf\xc5\xf8\xe8\xf8\x83\xa7\xb8\xae\x8f\xe0\xe4\xfa\xc5\xad\xb8\xf8\xdd\xe2\xea\xb7\x93\xa5\xa7\xa2\xc9\xb1\xbd\xb8\x94\xa1\xba\xb3\x85\xa7\xea\xa7'
2013-03-19 13:49:00,307:DEBUG:send: '\x81\xb5\x8c\xda\xf7\x7f\xb8\xe0\xcdP\xe1\xae\x90\x10\xf4\xe0\x8c]\xf8\xa3\x87\x1a\xae\xe0\xd7]\xf8\xb3\x94\x14\xe9\xa8\xd5S\xac\xf8\x98\x0f\xae\xe0\xd7]\xe1\xae\x90\x10\xf4\xf4\x84\n\xee\xa9\x94\r\xe5\xb8\x92]\xf1'
2013-03-19 13:49:00,307:DEBUG:send: '\x81\xb5w\xf2\x8b\xbeC\xc8\xb1\x91\x1a\x86\xec\xd1\x0f\xc8\xf0\x9c\x03\x8b\xfb\xdbU\xc8\xab\x9c\x03\x80\xea\xda\x12\x81\xa9\x92W\xd0\xe4\xceU\xc8\xab\x9c\x1a\x86\xec\xd1\x0f\xdc\xf8\xcb\x15\x81\xe8\xcc\x1e\x90\xee\x9c\n'

...and so on. Now its subscribed to all channels and should start sending data,

legendary
Activity: 980
Merit: 1040
Im actually using php server side, and there is very little to be found on the web. Only code to create websockets, not to connect to them.

Anyway, I dont mind coding it if I know what to send, but python is mostly like chinese to me, so can you do me one more favor and modify websocket.py
change line 43 to:
_debug = True

Then run your app.
It will spit out the handshakes. If you copy/paste the output  I can try to implement it in php.
hero member
Activity: 938
Merit: 500
https://youengine.io/
I debugged the python app linked above, and that made me use this string:
Code:
GET /socket.io/1/websocket/5360180921750645752 HTTP/1.1
Sec-WebSocket-Draft: 2
Host: socketio.mtgox.com
Upgrade: WebSocket
Connection: Upgrade
Origin: socketio.mtgox.com

Still no response, but now I wonder, do i need to set the authentication thing?

Looks good but remember there exist multiple different versions of websocket and their server might refuse to connect if you use the wrong version. The python example uses the websocket.py module https://pypi.python.org/pypi/websocket-client/ to do the actual websocket connect and so does my program to.

I remember before that I experimented with different implementations of websocket and not all of them worked, one of them was disconnected when sending the headers, i guess because it tried to use the wrong websocket version or something else was wrong with its headers (iirc by investigating the reasons it seemed to have to do with the sec-websocket-* headers and the random generated key, but then I just threw the code away and started from scratch with websocket.py). What I now use is urllib2 to do the initial http request, then construct the URL like above (the url you posted looks good) and then I let the websocket.py do the websocket connect and don't care about low level details like request headers

I don't know how this is in javascript/node.js (assuming this is what you are using?) but I guess there should exist some library too that will let you establish a webocket connection that takes care about all the low-level websocket request header stuff.

this is my working python code if this is of any help for you (its basically doing exactly the same as the other code snippets in this thread): (I guess to solve your problem you need to find the equivalent of "websocket.py" for your programming language)
Code:

        use_ssl = self.config.get_bool("gox", "use_ssl")
        wsp = {True: "wss://", False: "ws://"}[use_ssl]
        htp = {True: "https://", False: "http://"}[use_ssl]

        while not self._terminating: #loop 0 (connect, reconnect)
            try:
                url = urlopen(htp + self.SOCKETIO_HOST + "/socket.io/1?Currency=" + self.currency, timeout=20)
                params = url.read()
                url.close()

                ws_id = params.split(":")[0]
                ws_url = wsp + self.SOCKETIO_HOST + "/socket.io/1/websocket/" + ws_id + "?Currency=" + self.currency

                self.socket = websocket.WebSocket()
                self.socket.connect(ws_url)

                self.debug("connected")

                self.socket.send("1::/mtgox")
                self.socket.recv() # '1::'
                self.socket.recv() # '1::/mtgox'

                self.debug("subscribing to channels...")
                self.channel_subscribe()

                self.debug("waiting for data...")
                while not self._terminating: #loop1 (read messages)
                    msg = self.socket.recv()
                    if msg == "2::":
                        self.debug("### ping -> pong")
                        self.socket.send("2::")
                        continue
                    prefix = msg[:10]
                    if prefix == "4::/mtgox:":
                        str_json = msg[10:]
                        if str_json[0] == "{":
                            self.signal_recv(self, (str_json))

            except Exception as exc:
                if not self._terminating:
                    self.debug(exc, "reconnecting in 5 seconds...")
                    if self.socket:
                        self.socket.close()
                    time.sleep(5)

legendary
Activity: 980
Merit: 1040
Ah, you make it sound easy! And it probably is when you know what you are doing, but I dont Smiley
I found the url to get the session thingy. Im not sure what to do next; this is the closest I got so far:

edit: I guess the handshake should be this instead?
GET /socket.io/1/websocket/454534534534 HTTP/1.1
Upgrade: WebSocket
Connection: Upgrade
Host: socketio.mtgox.com
Origin: null

Nothing is happening, no response.

edit2.
I debugged the python app linked above, and that made me use this string:
Code:
GET /socket.io/1/websocket/5360180921750645752 HTTP/1.1
Sec-WebSocket-Draft: 2
Host: socketio.mtgox.com
Upgrade: WebSocket
Connection: Upgrade
Origin: socketio.mtgox.com

Still no response, but now I wonder, do i need to set the authentication thing?
hero member
Activity: 938
Merit: 500
https://youengine.io/
or  redoing most of the work trying to reverse engineering the socketio server to use websockets. Bah!

If you have a working version for plain websocket already then it should be trivial to add the little socketio overhead on top of it without needing a complete socketio library.

basically instead of just connecting to websocket.mtgox.com like you do it now you do the following:

send a http request to socketio.mtgox.com (look for the example somewhere in this thread for the exact URL), parse the result to get the session ID which is part of your personalized websocket url and then you use *that* to do the websocket connect. The rest is adding (or removing) the "1::" and "4::/mtgox:" headers before/after send/receive and the rest stays the same, its effectively only a handful of code you have to add, you don't have to implement everything of socketio, just these few things, its actually simpler than it might seem.

There was a python example near the beginning (or somewhere in the middle) of this thread that helped me a lot, and of course also the official socketio specification.
hero member
Activity: 674
Merit: 500
This sucks. I spent a day learning about websockets and making goxgame work pretty decently, now I can start over again and it seems got a choice between redoing most of the work while learning (a lot more) javascript and node.js to implement some existing nodejs socket.io implementation and make it talk with my db, or  redoing most of the work trying to reverse engineering the socketio server to use websockets. Bah!

Thanks for the long explanation though.

Btw, if you're doing node.js+socket.io you can use my example: https://github.com/icbit/trader.nodejs
It's not very well tested and debugged yet, but it supports both Mt.Gox and ICBIT.
legendary
Activity: 980
Merit: 1040
This sucks. I spent a day learning about websockets and making goxgame work pretty decently, now I can start over again and it seems got a choice between redoing most of the work while learning (a lot more) javascript and node.js to implement some existing nodejs socket.io implementation and make it talk with my db, or  redoing most of the work trying to reverse engineering the socketio server to use websockets. Bah!

Thanks for the long explanation though.
hero member
Activity: 938
Merit: 500
https://youengine.io/
Quote
...I'm not totally sure ...
There is a specification describing socket.io protocol.

With "I'm not totally sure" I was referring to websocket framing (0xff and friends), not to socketio. For websocket there exists an RFC. Websocket is actually not the problem, client implementations exist for many languages.

I actually just wanted to explain that these two MtGox servers are not the same and after writing down the bytes I realized that I might have forgotten some tiny details and therefore wanted to make clear that this is NOT a protocol specification, I just wrote it down as I remember it and so I wrote "I'm not totally sure" as a warning that it might be not 100% correct and nobody tries to implement something based on this posting.
hero member
Activity: 674
Merit: 500
its a separate server. And on the socketio websocket all messages look like this:

0x00 "4::/mtgox:{a json message}" 0xff
0x00 "4::/mtgox:{another json msg}" 0xff

and occasionally:

0x00 "2::" 0xff     <-- this is a ping, must be answered with "2::"

...I'm not totally sure ...

There is a specification describing socket.io protocol.
hero member
Activity: 938
Merit: 500
https://youengine.io/
Thanks for confirming its not just me. I thought socketio was implemented on top of websockets though, so its weird that still works?

its a separate server. And on the socketio websocket all messages look like this:

0x00 "4::/mtgox:{a json message}" 0xff
0x00 "4::/mtgox:{another json msg}" 0xff

and occasionally:

0x00 "2::" 0xff     <-- this is a ping, must be answered with "2::"

(I'm not totally sure about the 0xff 0x00 thing, there exist 13 different (no joke!) versions of websocket and there exist also different ways to delimit the messages, crazy stuff like: if bit7=1 then bit 0..6 = message length and when the next byte also has bit7=1 then its even more bits for the message length until a byte comes along that has bit7=0 but if all bits = 0 then its variable length ascii, 0xff terminated.

Fortunately the binary stuff is abstracted away by libraries like websocket.py in Python and other client implementations for other languages, so usually you just have a send() and a recv() method and don't need to care about 0x00 and 0xff etc., only the socketio "1::", "2::" and "4::" must be done manually.

The "/mtgox" part is some kind of "channel" name on the socketio layer but MtGox actually does not make any use of this feature, there is only one channel, the channels you subscribe *through* the websocket actually are an MtGox-internal thing, not part of the socketio layer. So there is some socketio protocol related stuff going on and wrapped around it that is not actually used for anything but its needed to satisfy the protocol rules.


On the plain websocket server you just connect, send the request header and it immediately begins talking to you with no additional handshake or other overhead:

0x00 "{a json message}" 0xff
0x00 "{another json msg}" 0xff

So its not the *same* websocket server (also it has a different hostname and IP address). Socketio is using websocket transport but a totally different handshake and protocol and framing.


Either way, Ill make a HTTP fallback option. Do you know how often you can poll it? The documentation says no more than once per 10 seconds, but Im not sure if that is per request or for all requests (I need trades, depth, and ticker) ?

depth (fulldepth) definitely has a limit and I ran into this once while testing (and it tried to ban me from calling this API for 24 hours [getting a new IP address on my DSL line fixed it]). I don't know about the other APIs.
legendary
Activity: 980
Merit: 1040
Thanks for confirming its not just me. I thought socketio was implemented on top of websockets though, so its weird that still works?
Either way, Ill make a HTTP fallback option. Do you know how often you can poll it? The documentation says no more than once per 10 seconds, but Im not sure if that is per request or for all requests (I need trades, depth, and ticker) ?
hero member
Activity: 938
Merit: 500
https://youengine.io/
is websocket service down? I can connected but I cant get any response (nor via telnet).
 Clarkmoody and the other sites still seem to work though?

Yes, its down again (for almost 3 hours now [this is nothing, sometimes its down for a few days]). And in the 24/7 IRC support channel #mtgox on freenode (btw. why does a commercial company use freenode?) there is currently nobody answering any questions.

Socketio works (clarkmoody is using socketio) but trading via the socketio server is no fun at all because of the 10 seconds lag (on top of the usual goxlag) even during quiet times and becomes totally unusable when there is some more action.
legendary
Activity: 980
Merit: 1040
is websocket service down? I can connected but I cant get any response (nor via telnet).
 Clarkmoody and the other sites still seem to work though?
legendary
Activity: 980
Merit: 1040
On trade messages you need to update the orderbook yourself, there will be no depth message.

If the trade is type:"bid" then it has filled an ask order, update your asks, if trade is type:"ask" then it has filled a bid order, update your bids.

On *own* trades there will be two trade messages: One public that everybody receives (like above), use that to update your orderbook like above and one private only to notify you about your trade that happened. Also on every trade that affects your *own* orders there will be user_order messages, at least one that sets the volume to the remaining volume (or zero and another one to finally remove it).

thanks a lot. That explains the weirdness I was getting.
hero member
Activity: 938
Merit: 500
https://youengine.io/
One thing isnt clear to me. when a trade occurs. does the API also send a depth message that reflects this trade? Or do I need to subscribe to the trade channel as well and then  match the trade with the orderbook to keep the orderbook up to date?

On trade messages you need to update the orderbook yourself, there will be no depth message. [Edit: this is wrong, there is a depth message following every trade message but it can't do any damage if you always only use the total_volume and never use the volume difference]

If the trade is type:"bid" then it has filled an ask order, update your asks, if trade is type:"ask" then it has filled a bid order, update your bids.

On *own* trades there will be two trade messages: One public that everybody receives (like above), use that to update your orderbook like above and one private only to notify you about your trade that happened. Also on every trade that affects your *own* orders there will be user_order messages, at least one that sets the volume to the remaining volume (or zero and another one to finally remove it).

[Edit: the code below is flawed (maybe even outright wrong: You should not use the volume difference in the depth message, you should use the total volume]

WRONG WRONG WRONG
Code:
def on_trade(typ, price, volume, own):
  typ_filled = {"ask":"bid", "bid":"ask"}[typ]
  if own:
    # do nothing as far as bookkeeping the order lists is concerned
    # maybe notify your bot about this event, or notify the user.
    pass
  else:
    update_orderbook(typ_filled, price, -volume)

def on_depth(typ, price, volume):
  update_orderbook(typ, price, volume)

def on_user_order(oid, price, new_volume, new_status):
  update_own_orders(oid, price, new_volume, new_status)
/WRONG /WRONG /WRONG


Edit: Here is a corrected version:
you would have two methods updating the orderbook, one that accepts a delta and one that accepts the new total volume.

Code:
def on_trade(typ, price, trade_volume, own):
  typ_filled = {"ask":"bid", "bid":"ask"}[typ]
  if own:
    # do nothing as far as bookkeeping the order lists is concerned
    # maybe notify your bot about this event, or notify the user.
    pass
  else:
    update_orderbook_with_delta(typ_filled, price, -trade_volume)

def on_depth(typ, price, total_volumel):
  update_orderbook_with_absolute(typ, price, total_volume)

def on_user_order(oid, price, new_volume, new_status):
  update_own_orders(oid, price, new_volume, new_status)

def update_orderbook_with_delta(typ, price, delta_volume):
  # do whatever is needed to update a price level,
  # remove it if it is <= 0 after the update
  [...]

def update_orderbook_with_absolute(typ, price, total_volume);
  # do whatever is needed to update a price level to the new total_volume
  # remove the level if total_volume == 0
  [...]

And here is a simplified version that just waits for the inevitable depth message and completely ignores the trade message:
Code:
def on_trade(typ, price, trade_volume, own):
  if own:
    # an own order has been filled (this is only received in the private channel)
    # do nothing as far as bookkeeping the order lists is concerned
    # maybe notify your bot about this event, or notify the user.
    # the same message will be fired again with vol=False in the public
    # channel like all other trade message too.
    pass
  else:
    # a trade has happened on mtgox (this is the public message)
    # do nothing as far as bookkeeping the order lists is concerned
    # maybe log the message or notify your bot if it needs it
    pass

def on_depth(typ, price, total_volumel):
  update_orderbook_with_absolute(typ, price, total_volume)

def on_user_order(oid, price, new_volume, new_status):
  update_own_orders(oid, price, new_volume, new_status)

def update_orderbook_with_absolute(typ, price, total_volume);
  # do whatever is needed to update a price level to the new total_volume
  # remove the level if total_volume == 0
  [...]
legendary
Activity: 980
Merit: 1040
One thing isnt clear to me. when a trade occurs. does the API also send a depth message that reflects this trade? Or do I need to subscribe to the trade channel as well and then  match the trade with the orderbook to keep the orderbook up to date?
hero member
Activity: 938
Merit: 500
https://youengine.io/
Yeah, I'm seeing this to. Also seeing "invalid call".

I have had invalid call before i changed the way my nonce was created. Now I'm just using 8 bytes from urandom to make a 64 bit integer and no invalid calls anymore, before i used something like time.Time()*1E6 and saaw a lot of "invalid call"

Thanks, I'll try this out.

The documentation and PHP example code led me to believe that the nonce needs to increment for each message.

STOP

forget everything I just wrote about the nonce, I had a bug in my program (I used struct.unpack() which creates a tuple and put that tuple with the random number into my json instead of only the nonce). And the mtgox-server also has a bug so that it *accepts* this without complaining!

It will *not* accept it if the nonce is a plain random number and not incrementing. And it will never forget, you need a new API key after the change.

I have just spent another hour with my code and now my code look like this, I also requested a brand new virgin API key and now it works:

Code:
   def send_signed_call(self, api_endpoint, params, reqid):
        """send a signed (authenticated) API call over the socket.io.
        This method will only succeed if the secret key is available,
        otherwise it will just log a warning and do nothing."""
        if (not self.secret) or (not self.secret.know_secret()):
            self.debug("### don't know secret, cannot call %s" % api_endpoint)
            return

        key = self.secret.key
        sec = self.secret.secret

        nonce = str(int(time.time() * 1E6))

        call = json.dumps({
            "id"       : reqid,
            "call"     : api_endpoint,
            "nonce"    : nonce,
            "params"   : params,
            "currency" : self.currency,
            "item"     : "BTC"
        })

        # pylint: disable=E1101
        sign = hmac.new(base64.b64decode(sec), call, hashlib.sha512).digest()
        signedcall = key.replace("-", "").decode("hex") + sign + call

        self.debug("### calling %s" % api_endpoint)
        self.send(json.dumps({
            "op"      : "call",
            "call"    : base64.b64encode(signedcall),
            "id"      : reqid,
            "context" : "mtgox.com"
        }))


you find it at github: prof7bit/goxtool
newbie
Activity: 17
Merit: 0
Yeah, I'm seeing this to. Also seeing "invalid call".

I have had invalid call before i changed the way my nonce was created. Now I'm just using 8 bytes from urandom to make a 64 bit integer and no invalid calls anymore, before i used something like time.Time()*1E6 and saaw a lot of "invalid call"

Thanks, I'll try this out.

The documentation and PHP example code led me to believe that the nonce needs to increment for each message. Currently I'm setting the nonce of the first request to the Unix time in microseconds and then incrementing by 1 in each additional request.

It's weird that this would cause problems if a random value works fine. I'll see if I can get some clarification from MtGox.

The socketio server only accepts the socketio protocol (http request, then get a session ID, then use that ID to have a new URL to connect to a websocket service but this is not the same websocket as the plain old websocket, all messages are wrapped into some socketio protocol ("1::/mtgox" headers and the like). This is a whole new complicated layer on top of websocket.

Sounds heinous.
hero member
Activity: 938
Merit: 500
https://youengine.io/

Yeah, I'm seeing this to. Also seeing "invalid call".

I have had invalid call before i changed the way my nonce was created. Now I'm just using 8 bytes from urandom to make an unsigned 64 bit integer and not one invalid calls anymore since the day I made that change, before i used something like time.time()*1E6 and saw a lot of "invalid call". One day I'm going to investigate what exactly it was that was wrong with my microseconds and why it works now.

The socketio server only accepts the socketio protocol (http request, then get a session ID, then use that ID to have a new URL to connect to a websocket service but this is not the same websocket as the plain old websocket, all messages are wrapped into some socketio protocol ("1::/mtgox" headers and the like). This is a whole new complicated layer on top of websocket.

According to the person I just talked to the plain websocket server is NOT (I repeat: NOT) deprecated, we can use it! Its also 10 seconds faster (no annoying lag, no sluggishness), reacts instantly to all commands.

hero member
Activity: 938
Merit: 500
https://youengine.io/
fixed within 5 minutes after quick chat in #mtgox on freenode ;-)
Now I know where to go when there are problems
Pages:
Jump to: