Python socketio example to connect to cryptocompare

Viewed 3179

I am trying to connect to Cryptocompare's websocket stream using socketIO from a Python client. This is proving to be challenging. Here is some sample Python code:

from socketIO_client import SocketIO

print "connecting to server"
socketIO = SocketIO('https://streamer.cryptocompare.com/',443, transports=['websocket'])
print "Connected"

However, I am not able to connect, whatever I do. In fact, this is the result of that connect

connecting to server
Traceback (most recent call last):
  File "test.py", line 4, in <module>
socketIO = SocketIO('https://streamer.cryptocompare.com/',443, transports=['websocket'])
  File "/usr/local/lib/python2.7/site-packages/socketIO_client/__init__.py", line 353, in __init__
resource, hurry_interval_in_seconds, **kw)
  File "/usr/local/lib/python2.7/site-packages/socketIO_client/__init__.py", line 54, in __init__
self._transport
  File "/usr/local/lib/python2.7/site-packages/socketIO_client/__init__.py", line 62, in _transport
self._engineIO_session = self._get_engineIO_session()
  File "/usr/local/lib/python2.7/site-packages/socketIO_client/__init__.py", line 76, in _get_engineIO_session
transport.recv_packet())
StopIteration

Wrapping the above code in a try-catch and printing the exception yields no extra information. Any help would be greatly appreciated.

3 Answers

The socketIO_client library does not seem to support the XHR polling protocol that is used by cryptocompare. I got it to work by overriding the method recv_packet in the socketIO_client.transports.XHR_PollingTransport class.

import logging
import socketIO_client
from socketIO_client.transports import get_response
from socketIO_client.parsers import get_byte, _read_packet_text, parse_packet_text

from requests.exceptions import ConnectionError

# extra function to support XHR1 style protocol
def _new_read_packet_length(content, content_index):
    packet_length_string = ''
    while get_byte(content, content_index) != ord(':'):
        byte = get_byte(content, content_index)
        packet_length_string += chr(byte)
        content_index += 1
    content_index += 1
    return content_index, int(packet_length_string)

def new_decode_engineIO_content(content):
    content_index = 0
    content_length = len(content)
    while content_index < content_length:
        try:
            content_index, packet_length = _new_read_packet_length(
                content, content_index)
        except IndexError:
            break
        content_index, packet_text = _read_packet_text(
            content, content_index, packet_length)
        engineIO_packet_type, engineIO_packet_data = parse_packet_text(
            packet_text)
        yield engineIO_packet_type, engineIO_packet_data

def new_recv_packet(self):
    params = dict(self._params)
    params['t'] = self._get_timestamp()
    response = get_response(
        self.http_session.get,
        self._http_url,
        params=params,
        **self._kw_get)
    for engineIO_packet in new_decode_engineIO_content(response.content):
        engineIO_packet_type, engineIO_packet_data = engineIO_packet
        yield engineIO_packet_type, engineIO_packet_data

setattr(socketIO_client.transports.XHR_PollingTransport, 'recv_packet', new_recv_packet)

logging.basicConfig(level=logging.DEBUG)

try:
    socket = socketIO_client.SocketIO('https://streamer.cryptocompare.com')
    socket.emit('SubAdd', { 'subs': ['0~Kraken~BTC~USD'] });
    socket.wait()
except ConnectionError:
    print('The server is down. Try again later.')

The solution is heavily based on this github comment: https://github.com/invisibleroads/socketIO-client/issues/129#issuecomment-330058318

I have looked at this problem from a few angles and concluded that the socketio client for python does not work with this API. If you just want to stream data to python from the CryptoCompare streaming api then I have a working workaround that uses websockets to submit requests to a simple nodejs app that then uses its socketio-client to stream the required data back. I am fairly new to python and only just looked at nodejs for this solution so go easy on me.

Crypto is hot at the moment so I am sure this will be useful to some

The Python part:

import json
import pandas as pd
try:
   import thread
except ImportError:
import _thread as thread

import threading
import time
import websocket


class WebSocketClient(threading.Thread):

 def __init__(self):
    self.url = 'ws://localhost:9030/path'
    # self.daemon = True
    self.clist = list()
    threading.Thread.__init__(self)


 def run(self):

    # Running the run_forever() in a seperate thread.
    #websocket.enableTrace(True)
    self.ws = websocket.WebSocketApp(self.url,
                                     on_message = self.on_message,
                                     on_error = self.on_error,
                                     on_close = self.on_close)
    self.ws.on_open = self.on_open
    self.ws.run_forever()

 def send(self, data):

    data = self._encode_message(data)
    # Wait till websocket is connected.
    while not self.ws.sock.connected:
        time.sleep(0.25)

    print(f'Sending data... {data}')
    self.ws.send(data)

 def stop(self):
    print(f'Stopping the websocket...')
    self.ws.close()

 def on_message(self, ws, message):
    message = self._decode_message(message)
    print(f'Received data...{message}')
    if message['msg']=='crypto':

        self.clist.append(message['data'])

 def on_error(self, ws, error):
    print(f'Received error...')
    print(error)

 def on_close(self, ws):
    print('Closed the connection...')

 def on_open(self, ws):
    print('Opened the connection...')
    data = {"msg":"open" ,"from":"Rob", "data":"Hello from the client"}
    self.send(data)

 def _encode_message(self,message):

    message = json.dumps(message)
    return message

 def _decode_message(self, message):

    message = json.loads(message)
    return message

 def getclist(self):
    return self.clist


wsCli =  WebSocketClient()
wsCli.daemon = True
wsCli.start()
wsCli.send({"msg":"getcrypto" ,"from":"Client", "data":['0~Coinbase~BTC~USD'],"subs":['0~Coinbase~BTC~USD']})
wsCli.stop()

The Nodejs Part:

var socket = require('socket.io-client')('https://streamer.cryptocompare.com/')
var socketon = false

var WebSocketServer = require('ws').Server , wss = new WebSocketServer({port: 9030});
wss.on('connection', function(ws) {
         ws.on('message', function(message) {
              msg = decode_message(message);
                       console.log('received: %s', message);
                       console.log('test: %s', msg['msg']);
          switch(msg['msg']) {
           case 'open':
                                       message_type = 'open';
            
 
          // do something with chat data. i.e.:
          console.log("open from " + msg['from'] +
             ": " + msg['data']);
         
                                     outmessage = 'welcome from server ' + msg['from'];
          ws.send(encode_message(outmessage, message_type = 'open'));
          break
          case 'getcrypto':
          message_type = "crypto"
          // do something with chat data. i.e.:
          console.log("'getcrypto': object" + msg['data']);
                                     socket.emit('SubAdd', { subs: msg.subs });
                                     if (!socketon) {
                                         socket.on("m", function (message) {
                                             var messageType = message.substring(0, message.indexOf("~"));
                                             var res = {};
                                             switch (messageType) {
                                                 case CCC.STATIC.TYPE.TRADE:
                                                     res = CCC.TRADE.unpack(message);
                                                     break;
                                                 case CCC.STATIC.TYPE.CURRENT:
                                                     res = CCC.CURRENT.unpack(message);
                                                     break;
                                                 case CCC.STATIC.TYPE.CURRENTAGG:
                                                     res = CCC.CURRENT.unpack(message);
                                                     // updateQuote(res);
                                                     break;
                                                 case CCC.STATIC.TYPE.ORDERBOOK:
                                                     res = CCC.ORDER.unpack(message);
                                                     break;
                                                 case CCC.STATIC.TYPE.FULLORDERBOOK:
                                                     res = CCC.ORDER.unpack(message);
                                                     break;
                                             }
                                             console.log(message);
                                             console.log(res);
                                             ws.send(encode_message(res, message_type = "crypto"));
                                         })
                                     }
                                     socketon = true;
          break
          case 'canccrypto':
           // do something with chat data. i.e.:
            message_type = "crypto"
           console.log(msg['msg'] +":" + msg['data']);
           socket.emit('SubRemove', { subs: msg.subs });
           break
          case 'other':
          // do something with chat data. i.e.:
            
          console.log("open from " + msg['from'] +
             ": " + msg['data']);
          outmessage = 'other from server ' + msg['from']
          ws.send(encode_message(outmessage, message_type = "other"));
          break
          // ... and so on
          }
           });

     });

function decode_message(message) {
 msg = JSON.parse(message);
 return msg;
}

function encode_message(message, messsage_type) {
 if (message_type == "crypto") {
  var msg_crypto = { 'msg': 'crypto', 'data': message };
  msg = JSON.stringify(msg_crypto);
 }
 else {
  var msg_other = { 'msg': 'other', 'data': message };
  msg = JSON.stringify(msg_other);
 }
 return msg;
}


var CCC = CCC || {};

CCC.STATIC = CCC.STATIC || {};

CCC.STATIC.TYPE = {
 'TRADE': '0'
 , 'FEEDNEWS': '1'
 , 'CURRENT': '2'
 , 'LOADCOMPLATE': '3'
 , 'COINPAIRS': '4'
 , 'CURRENTAGG': '5'
 , 'TOPLIST': '6'
 , 'TOPLISTCHANGE': '7'
 , 'ORDERBOOK': '8'
 , 'FULLORDERBOOK': '9'
 , 'ACTIVATION': '10'

 , 'TRADECATCHUP': '100'
 , 'NEWSCATCHUP': '101'

 , 'TRADECATCHUPCOMPLETE': '300'
 , 'NEWSCATCHUPCOMPLETE': '301'

};

CCC.STATIC.CURRENCY = CCC.STATIC.CURRENCY || {};

CCC.STATIC.CURRENCY.SYMBOL = {
 'BTC': '?'
 , 'LTC': '?'
 , 'DAO': 'Ð'
 , 'USD': '$'
 , 'CNY': '¥'
 , 'EUR': '€'
 , 'GBP': '£'
 , 'JPY': '¥'
 , 'PLN': 'z?'
 , 'RUB': '?'
 , 'ETH': '?'
 , 'GOLD': 'Gold g'
 , 'INR': '?'
 , 'BRL': 'R$'
};

CCC.STATIC.CURRENCY.getSymbol = function (symbol) {
 return CCC.STATIC.CURRENCY.SYMBOL[symbol] || symbol;
};

CCC.STATIC.UTIL = {
 exchangeNameMapping: {
  'CCCAGG': 'CryptoCompare Index',
  'BTCChina': 'BTCC'
 },
 isMobile: function (userAgent) {
  if (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|ipad|iris|kindle|Android|Silk|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(userAgent)
   || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(userAgent.substr(0, 4)))
   return true;
  return false;
 },
 convertToMB: function (bytes) {
  return (parseInt(bytes, 10) / 1024 / 1024).toFixed(2) + ' MB';
 },
 getNameForExchange: function (exchangeName) {
  if (this.exchangeNameMapping.hasOwnProperty(exchangeName)) {
   return this.exchangeNameMapping[exchangeName];
  }
  return exchangeName;
 },
 noExponents: function (value) {
  var data = String(value).split(/[eE]/);
  if (data.length == 1) return data[0];

  var z = '', sign = value < 0 ? '-' : '',
   str = data[0].replace('.', ''),
   mag = Number(data[1]) + 1;

  if (mag < 0) {
   z = sign + '0.';
   while (mag++) z += '0';
   return z + str.replace(/^\-/, '');
  }
  mag -= str.length;
  while (mag--) z += '0';
  return str + z;
 },
 reduceFloatVal: function (value) {
  value = parseFloat(value);
  if (value > 1) {
   value = Math.round(value * 100) / 100;
   return value;
  }
  if (value >= 0.00001000) {
   return parseFloat(value.toPrecision(4));
  }
  if (value >= 0.00000100) {
   return parseFloat(value.toPrecision(3));
  }
  if (value >= 0.00000010) {
   return parseFloat(value.toPrecision(2));
  }
  return parseFloat(value.toPrecision(1));
 },
 reduceReal: function (value) {
  value = parseFloat(value);
  return parseFloat(value.toFixed(8));
 },
 convertCurrentKeyToAll: function (key) {
  var valuesArray = key.split("~");
  valuesArray[0] = CCC.STATIC.TYPE.CURRENTAGG;
  valuesArray[1] = 'CCCAGG';
  return valuesArray.join('~');
 },
 convertCurrentKeyToTrade: function (key) {
  var valuesArray = key.split("~");
  valuesArray[0] = CCC.STATIC.TYPE.TRADE;
  return valuesArray.join('~');
 },
 convertValueToDisplay: function (symbol, value, filterNumberFunctionAngularJS, type, fullNumbers) {
  var prefix = '';
  var valueSign = 1;
  value = parseFloat(value);
  var valueAbs = Math.abs(value);
  var decimalsOnBigNumbers = 2;
  var decimalsOnNormalNumbers = 2;
  var decimalsOnSmallNumbers = 4;
  if (fullNumbers === true) {
   decimalsOnBigNumbers = 2;
   decimalsOnNormalNumbers = 0;
   decimalsOnSmallNumbers = 4;
  }
  if (type == "8decimals") {
   decimalsOnBigNumbers = 4;
   decimalsOnNormalNumbers = 8;
   decimalsOnSmallNumbers = 8;
   if (value < 0.0001 && value >= 0.00001) { decimalsOnSmallNumbers = 4; }
   if (value < 0.001 && value >= 0.0001) { decimalsOnSmallNumbers = 5; }
   if (value < 0.01 && value >= 0.001) { decimalsOnSmallNumbers = 6; }
   if (value < 0.1 && value >= 0.01) { decimalsOnSmallNumbers = 7; }
  }
  if (symbol != '') { prefix = symbol + ' '; }
  if (value < 0) { valueSign = -1; }
  if (value == 0) { return prefix + '0'; }

  if (value < 0.00001000 && value >= 0.00000100 && decimalsOnSmallNumbers > 3) {
   decimalsOnSmallNumbers = 3;
  }
  if (value < 0.00000100 && value >= 0.00000010 && decimalsOnSmallNumbers > 2) {
   decimalsOnSmallNumbers = 2;
  }
  if (value < 0.00000010 && decimalsOnSmallNumbers > 1) {
   decimalsOnSmallNumbers = 1;
  }

  if (type == "short" || type == "8decimals") {
   if (valueAbs > 10000000000) {
    valueAbs = valueAbs / 1000000000;
    return prefix + filterNumberFunctionAngularJS(valueSign * valueAbs, decimalsOnBigNumbers) + ' B';
   }
   if (valueAbs > 10000000) {
    valueAbs = valueAbs / 1000000;
    return prefix + filterNumberFunctionAngularJS(valueSign * valueAbs, decimalsOnBigNumbers) + ' M';
   }
   if (valueAbs > 10000) {
    valueAbs = valueAbs / 1000;
    return prefix + filterNumberFunctionAngularJS(valueSign * valueAbs, decimalsOnBigNumbers) + ' K';
   }
   if (type == "8decimals" && valueAbs >= 100) {
    return prefix + filterNumberFunctionAngularJS(valueSign * valueAbs, decimalsOnBigNumbers);
   }
   if (valueAbs >= 1) {
    return prefix + filterNumberFunctionAngularJS(valueSign * valueAbs, decimalsOnNormalNumbers);
   }
   return prefix + (valueSign * valueAbs).toPrecision(decimalsOnSmallNumbers);
  } else {
   if (valueAbs >= 1) {
    return prefix + filterNumberFunctionAngularJS(valueSign * valueAbs, decimalsOnNormalNumbers);
   }

   return prefix + this.noExponents((valueSign * valueAbs).toPrecision(decimalsOnSmallNumbers));
  }
 }
};


CCC.TRADE = CCC.TRADE || {};
/*
trade fields binary values always in the last ~
*/

CCC.TRADE.FLAGS = {
 'SELL': 0x1 // hex for binary 1
 , 'BUY': 0x2 // hex for binary 10
 , 'UNKNOWN': 0x4 // hex for binary 100
}

CCC.TRADE.FIELDS = {
 'T': 0x0  // hex for binary 0, it is a special case of fields that are always there TYPE
 , 'M': 0x0  // hex for binary 0, it is a special case of fields that are always there MARKET
 , 'FSYM': 0x0  // hex for binary 0, it is a special case of fields that are always there FROM SYMBOL
 , 'TSYM': 0x0  // hex for binary 0, it is a special case of fields that are always there TO SYMBOL
 , 'F': 0x0  // hex for binary 0, it is a special case of fields that are always there FLAGS
 , 'ID': 0x1  // hex for binary 1                                                       ID
 , 'TS': 0x2  // hex for binary 10                                                      TIMESTAMP
 , 'Q': 0x4  // hex for binary 100                                                     QUANTITY
 , 'P': 0x8  // hex for binary 1000                                                    PRICE
 , 'TOTAL': 0x10 // hex for binary 10000                                                   TOTAL

};

CCC.TRADE.DISPLAY = CCC.TRADE.DISPLAY || {};
CCC.TRADE.DISPLAY.FIELDS = {
 'T': { "Show": false }
 , 'M': { "Show": true, 'Filter': 'Market' }
 , 'FSYM': { "Show": true, 'Filter': 'CurrencySymbol' }
 , 'TSYM': { "Show": true, 'Filter': 'CurrencySymbol' }
 , 'F': { "Show": true, 'Filter': 'TradeFlag' }
 , 'ID': { "Show": true, 'Filter': 'Text' }
 , 'TS': { 'Show': true, 'Filter': 'Date', 'Format': 'yyyy MMMM dd HH:mm:ss' }
 , 'Q': { 'Show': true, 'Filter': 'Number', 'Symbol': 'FSYM' }
 , 'P': { 'Show': true, 'Filter': 'Number', 'Symbol': 'TSYM' }
 , 'TOTAL': { 'Show': true, 'Filter': 'Number', 'Symbol': 'TSYM' }

};

CCC.TRADE.pack = function (tradeObject) {
 var mask = 0;
 var packedTrade = '';
 for (var field in tradeObject) {
  packedTrade += '~' + tradeObject[field];
  mask |= this.FIELDS[field];
 }
 return packedTrade.substr(1) + '~' + mask.toString(16);
};

CCC.TRADE.unpack = function (tradeString) {
 var valuesArray = tradeString.split("~");
 var valuesArrayLenght = valuesArray.length;
 var mask = valuesArray[valuesArrayLenght - 1];
 var maskInt = parseInt(mask, 16);
 var unpackedTrade = {};
 var currentField = 0;
 for (var property in this.FIELDS) {
  if (this.FIELDS[property] === 0) {
   unpackedTrade[property] = valuesArray[currentField];
   currentField++;
  }
  else if (maskInt & this.FIELDS[property]) {
   unpackedTrade[property] = valuesArray[currentField];
   currentField++;
  }
 }

 return unpackedTrade;
}

CCC.TRADE.getKey = function (tradeObject) {
 return tradeObject['T'] + '~' + tradeObject['M'] + '~' + tradeObject['FSYM'] + '~' + tradeObject['TSYM'];
};

CCC.CURRENT = CCC.CURRENT || {};
/*
current fields mask values always in the last ~
*/

CCC.CURRENT.FLAGS = {
 'PRICEUP': 0x1    // hex for binary 1
 , 'PRICEDOWN': 0x2    // hex for binary 10
 , 'PRICEUNCHANGED': 0x4    // hex for binary 100
 , 'BIDUP': 0x8    // hex for binary 1000
 , 'BIDDOWN': 0x10   // hex for binary 10000
 , 'BIDUNCHANGED': 0x20   // hex for binary 100000
 , 'OFFERUP': 0x40   // hex for binary 1000000
 , 'OFFERDOWN': 0x80   // hex for binary 10000000
 , 'OFFERUNCHANGED': 0x100  // hex for binary 100000000
 , 'AVGUP': 0x200  // hex for binary 1000000000
 , 'AVGDOWN': 0x400  // hex for binary 10000000000
 , 'AVGUNCHANGED': 0x800  // hex for binary 100000000000
};


CCC.CURRENT.FIELDS = {
 'TYPE': 0x0       // hex for binary 0, it is a special case of fields that are always there
 , 'MARKET': 0x0       // hex for binary 0, it is a special case of fields that are always there
 , 'FROMSYMBOL': 0x0       // hex for binary 0, it is a special case of fields that are always there
 , 'TOSYMBOL': 0x0       // hex for binary 0, it is a special case of fields that are always there
 , 'FLAGS': 0x0       // hex for binary 0, it is a special case of fields that are always there
 , 'PRICE': 0x1       // hex for binary 1
 , 'BID': 0x2       // hex for binary 10
 , 'OFFER': 0x4       // hex for binary 100
 , 'LASTUPDATE': 0x8       // hex for binary 1000
 , 'AVG': 0x10      // hex for binary 10000
 , 'LASTVOLUME': 0x20      // hex for binary 100000
 , 'LASTVOLUMETO': 0x40      // hex for binary 1000000
 , 'LASTTRADEID': 0x80      // hex for binary 10000000
 , 'VOLUMEHOUR': 0x100     // hex for binary 100000000
 , 'VOLUMEHOURTO': 0x200     // hex for binary 1000000000
 , 'VOLUME24HOUR': 0x400     // hex for binary 10000000000
 , 'VOLUME24HOURTO': 0x800     // hex for binary 100000000000
 , 'OPENHOUR': 0x1000    // hex for binary 1000000000000
 , 'HIGHHOUR': 0x2000    // hex for binary 10000000000000
 , 'LOWHOUR': 0x4000    // hex for binary 100000000000000
 , 'OPEN24HOUR': 0x8000    // hex for binary 1000000000000000
 , 'HIGH24HOUR': 0x10000   // hex for binary 10000000000000000
 , 'LOW24HOUR': 0x20000   // hex for binary 100000000000000000
 , 'LASTMARKET': 0x40000   // hex for binary 1000000000000000000, this is a special case and will only appear on CCCAGG messages
};

CCC.CURRENT.DISPLAY = CCC.CURRENT.DISPLAY || {};
CCC.CURRENT.DISPLAY.FIELDS = {
 'TYPE': { 'Show': false }
 , 'MARKET': { 'Show': true, 'Filter': 'Market' }
 , 'FROMSYMBOL': { 'Show': false }
 , 'TOSYMBOL': { 'Show': false }
 , 'FLAGS': { 'Show': false }
 , 'PRICE': { 'Show': true, 'Filter': 'Number', 'Symbol': 'TOSYMBOL' }
 , 'BID': { 'Show': true, 'Filter': 'Number', 'Symbol': 'TOSYMBOL' }
 , 'OFFER': { 'Show': true, 'Filter': 'Number', 'Symbol': 'TOSYMBOL' }
 , 'LASTUPDATE': { 'Show': true, 'Filter': 'Date', 'Format': 'yyyy MMMM dd HH:mm:ss' }
 , 'AVG': { 'Show': true, ' Filter': 'Number', 'Symbol': 'TOSYMBOL' }
 , 'LASTVOLUME': { 'Show': true, 'Filter': 'Number', 'Symbol': 'FROMSYMBOL' }
 , 'LASTVOLUMETO': { 'Show': true, 'Filter': 'Number', 'Symbol': 'TOSYMBOL' }
 , 'LASTTRADEID': { 'Show': true, 'Filter': 'String' }
 , 'VOLUMEHOUR': { 'Show': true, 'Filter': 'Number', 'Symbol': 'FROMSYMBOL' }
 , 'VOLUMEHOURTO': { 'Show': true, 'Filter': 'Number', 'Symbol': 'TOSYMBOL' }
 , 'VOLUME24HOUR': { 'Show': true, 'Filter': 'Number', 'Symbol': 'FROMSYMBOL' }
 , 'VOLUME24HOURTO': { 'Show': true, 'Filter': 'Number', 'Symbol': 'TOSYMBOL' }
 , 'OPENHOUR': { 'Show': true, 'Filter': 'Number', 'Symbol': 'TOSYMBOL' }
 , 'HIGHHOUR': { 'Show': true, 'Filter': 'Number', 'Symbol': 'TOSYMBOL' }
 , 'LOWHOUR': { 'Show': true, 'Filter': 'Number', 'Symbol': 'TOSYMBOL' }
 , 'OPEN24HOUR': { 'Show': true, 'Filter': 'Number', 'Symbol': 'TOSYMBOL' }
 , 'HIGH24HOUR': { 'Show': true, 'Filter': 'Number', 'Symbol': 'TOSYMBOL' }
 , 'LOW24HOUR': { 'Show': true, 'Filter': 'Number', 'Symbol': 'TOSYMBOL' }
 , 'LASTMARKET': { 'Show': true, 'Filter': 'String' }
};

CCC.CURRENT.pack = function (currentObject) {
 var mask = 0;
 var packedCurrent = '';
 for (var property in this.FIELDS) {
  if (currentObject.hasOwnProperty(property)) {
   packedCurrent += '~' + currentObject[property];
   mask |= this.FIELDS[property];
  }
 }
 //removing first character beacsue it is a ~
 return packedCurrent.substr(1) + '~' + mask.toString(16);
};

CCC.CURRENT.unpack = function (value) {
 var valuesArray = value.split("~");
 var valuesArrayLenght = valuesArray.length;
 var mask = valuesArray[valuesArrayLenght - 1];
 var maskInt = parseInt(mask, 16);
 var unpackedCurrent = {};
 var currentField = 0;
 for (var property in this.FIELDS) {
  if (this.FIELDS[property] === 0) {
   unpackedCurrent[property] = valuesArray[currentField];
   currentField++;
  }
  else if (maskInt & this.FIELDS[property]) {
   //i know this is a hack, for cccagg, future code please don't hate me:(, i did this to avoid
   //subscribing to trades as well in order to show the last market
   if (property === 'LASTMARKET') {
    unpackedCurrent[property] = valuesArray[currentField];
   } else {
    unpackedCurrent[property] = parseFloat(valuesArray[currentField]);
   }
   currentField++;
  }
 }

 return unpackedCurrent;
};
CCC.CURRENT.getKey = function (currentObject) {
 return currentObject['TYPE'] + '~' + currentObject['MARKET'] + '~' + currentObject['FROMSYMBOL'] + '~' + currentObject['TOSYMBOL'];
};
CCC.CURRENT.getKeyFromStreamerData = function (streamerData) {
 var valuesArray = streamerData.split("~");
 return valuesArray[0] + '~' + valuesArray[1] + '~' + valuesArray[2] + '~' + valuesArray[3];
};

CCC.noExponents = function (value) {
 var data = String(value).split(/[eE]/);
 if (data.length == 1) return data[0];

 var z = '', sign = value < 0 ? '-' : '',
  str = data[0].replace('.', ''),
  mag = Number(data[1]) + 1;

 if (mag < 0) {
  z = sign + '0.';
  while (mag++) z += '0';
  return z + str.replace(/^\-/, '');
 }
 mag -= str.length;
 while (mag--) z += '0';
 return str + z;
};

CCC.filterNumberFunctionPolyfill = function (value, decimals) {
 var decimalsDenominator = Math.pow(10, decimals);
 var numberWithCorrectDecimals = Math.round(value * decimalsDenominator) / decimalsDenominator;
 var parts = numberWithCorrectDecimals.toString().split(".");
 parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
 return parts.join(".");
}

CCC.convertValueToDisplay = function (symbol, value, type, fullNumbers) {
 var prefix = '';
 var valueSign = 1;
 value = parseFloat(value);
 var valueAbs = Math.abs(value);
 var decimalsOnBigNumbers = 2;
 var decimalsOnNormalNumbers = 2;
 var decimalsOnSmallNumbers = 4;
 if (fullNumbers === true) {
  decimalsOnBigNumbers = 2;
  decimalsOnNormalNumbers = 0;
  decimalsOnSmallNumbers = 4;
 }
 if (symbol != '') {
  prefix = symbol + ' ';
 }
 if (value < 0) {
  valueSign = -1;
 }

 if (value == 0) {
  return prefix + '0';
 }

 if (value < 0.00001000 && value >= 0.00000100 && decimalsOnSmallNumbers > 3) {
  decimalsOnSmallNumbers = 3;
 }
 if (value < 0.00000100 && value >= 0.00000010 && decimalsOnSmallNumbers > 2) {
  decimalsOnSmallNumbers = 2;
 }
 if (value < 0.00000010 && value >= 0.00000001 && decimalsOnSmallNumbers > 1) {
  decimalsOnSmallNumbers = 1;
 }

 if (type == "short") {
  if (valueAbs > 10000000000) {
   valueAbs = valueAbs / 1000000000;
   return prefix + CCC.filterNumberFunctionPolyfill(valueSign * valueAbs, decimalsOnBigNumbers) + ' B';
  }
  if (valueAbs > 10000000) {
   valueAbs = valueAbs / 1000000;
   return prefix + CCC.filterNumberFunctionPolyfill(valueSign * valueAbs, decimalsOnBigNumbers) + ' M';
  }
  if (valueAbs > 10000) {
   valueAbs = valueAbs / 1000;
   return prefix + CCC.filterNumberFunctionPolyfill(valueSign * valueAbs, decimalsOnBigNumbers) + ' K';
  }
  if (valueAbs >= 1) {
   return prefix + CCC.filterNumberFunctionPolyfill(valueSign * valueAbs, decimalsOnNormalNumbers);
  }
  return prefix + (valueSign * valueAbs).toPrecision(decimalsOnSmallNumbers);
 } else {
  if (valueAbs >= 1) {
   return prefix + CCC.filterNumberFunctionPolyfill(valueSign * valueAbs, decimalsOnNormalNumbers);
  }

  return prefix + CCC.noExponents((valueSign * valueAbs).toPrecision(decimalsOnSmallNumbers));
 }
};

Related