From 831571668e6138785fef3a356d2c88715588d20a Mon Sep 17 00:00:00 2001 From: MJ2P Date: Sun, 26 Apr 2015 21:54:20 +0100 Subject: [PATCH] updated CCEDK wrapper. --- python/client.py | 635 ++++++++++---------- python/exchanges.py | 1371 ++++++++++++++++++++++--------------------- python/server.py | 14 +- python/trading.py | 602 ++++++++++--------- 4 files changed, 1378 insertions(+), 1244 deletions(-) diff --git a/python/client.py b/python/client.py index 6448cf6..9a896a6 100755 --- a/python/client.py +++ b/python/client.py @@ -39,322 +39,365 @@ from trading import * from utils import * -_wrappers = { 'bittrex' : Bittrex, 'ccedk' : CCEDK, 'bitcoincoid' : BitcoinCoId, 'bter' : BTER, 'testing' : Peatio } +_wrappers = {'bittrex': Bittrex, 'ccedk': CCEDK, 'bitcoincoid': BitcoinCoId, 'bter': BTER, 'testing': Peatio} _mainlogger = None + + def getlogger(): - global _mainlogger - if not _mainlogger: # initialize logger - if not os.path.isdir('logs'): - os.makedirs('logs') - _mainlogger = logging.getLogger('Client') - _mainlogger.setLevel(logging.DEBUG) - sh = logging.handlers.SocketHandler('', logging.handlers.DEFAULT_TCP_LOGGING_PORT) - sh.setLevel(logging.DEBUG) - fh = logging.FileHandler('logs/%d.log' % time.time()) - fh.setLevel(logging.DEBUG) - ch = logging.StreamHandler() - ch.setLevel(logging.INFO) - formatter = logging.Formatter(fmt = '%(asctime)s %(levelname)s: %(message)s', datefmt="%Y/%m/%d-%H:%M:%S") - sh.setFormatter(formatter) - fh.setFormatter(formatter) - ch.setFormatter(formatter) - _mainlogger.addHandler(sh) - _mainlogger.addHandler(fh) - _mainlogger.addHandler(ch) - return _mainlogger + global _mainlogger + if not _mainlogger: # initialize logger + if not os.path.isdir('logs'): + os.makedirs('logs') + _mainlogger = logging.getLogger('Client') + _mainlogger.setLevel(logging.DEBUG) + sh = logging.handlers.SocketHandler('', logging.handlers.DEFAULT_TCP_LOGGING_PORT) + sh.setLevel(logging.DEBUG) + fh = logging.FileHandler('logs/%d.log' % time.time()) + fh.setLevel(logging.DEBUG) + ch = logging.StreamHandler() + ch.setLevel(logging.INFO) + formatter = logging.Formatter(fmt='%(asctime)s %(levelname)s: %(message)s', datefmt="%Y/%m/%d-%H:%M:%S") + sh.setFormatter(formatter) + fh.setFormatter(formatter) + ch.setFormatter(formatter) + _mainlogger.addHandler(sh) + _mainlogger.addHandler(fh) + _mainlogger.addHandler(ch) + return _mainlogger # one request signer thread for each key and unit class RequestThread(ConnectionThread): - def __init__(self, conn, key, secret, exchange, unit, address, sampling, cost, logger = None): - super(RequestThread, self).__init__(conn, logger) - self.key = key - self.secret = secret - self.exchange = exchange - self.unit = unit - self.initsampling = sampling - self.sampling = sampling - self.address = address - self.errorflag = False - self.trials = 0 - self.exchangeupdate = 0 - self.cost = cost.copy() + def __init__(self, conn, key, secret, exchange, unit, address, sampling, cost, logger=None): + super(RequestThread, self).__init__(conn, logger) + self.key = key + self.secret = secret + self.exchange = exchange + self.unit = unit + self.initsampling = sampling + self.sampling = sampling + self.address = address + self.errorflag = False + self.trials = 0 + self.exchangeupdate = 0 + self.cost = cost.copy() - def register(self): - response = self.conn.post('register', { 'address' : self.address, 'key' : self.key, 'name' : repr(self.exchange) }, trials = 3, timeout = 10) - if response['code'] == 0: # reset sampling in case of server restart - self.sampling = self.initsampling - return response + def register(self): + response = self.conn.post('register', {'address': self.address, 'key': self.key, 'name': repr(self.exchange)}, + trials=3, timeout=10) + if response['code'] == 0: # reset sampling in case of server restart + self.sampling = self.initsampling + return response - def submit(self): - data, sign = self.exchange.create_request(self.unit, self.key, self.secret) - params = { 'unit' : self.unit, 'user' : self.key, 'sign' : sign } - params.update(data) - params.update(self.cost) - curtime = time.time() - ret = self.conn.post('liquidity', params, trials = 1, timeout = 60) - if ret['code'] != 0: - self.trials += time.time() - curtime + 60.0 / self.sampling - self.logger.error("submit: %s" % ret['message']) - if ret['code'] == 11: # user unknown, just register again - self.register() - else: - self.trials = 0 - self.errorflag = self.trials >= 120 # notify that something is wrong after 2 minutes of failures + def submit(self): + data, sign = self.exchange.create_request(self.unit, self.key, self.secret) + params = {'unit': self.unit, 'user': self.key, 'sign': sign} + params.update(data) + params.update(self.cost) + curtime = time.time() + ret = self.conn.post('liquidity', params, trials=1, timeout=60) + if ret['code'] != 0: + self.trials += time.time() - curtime + 60.0 / self.sampling + self.logger.error("submit: %s" % ret['message']) + if ret['code'] == 11: # user unknown, just register again + self.register() + else: + self.trials = 0 + self.errorflag = self.trials >= 120 # notify that something is wrong after 2 minutes of failures - def run(self): - ret = self.register() - if ret['code'] != 0: self.logger.error("register: %s" % ret['message']) - while self.active: - curtime = time.time() - start_new_thread(self.submit, ()) - time.sleep(max(60.0 / self.sampling - time.time() + curtime, 0)) + def run(self): + ret = self.register() + if ret['code'] != 0: self.logger.error("register: %s" % ret['message']) + while self.active: + curtime = time.time() + start_new_thread(self.submit, ()) + time.sleep(max(60.0 / self.sampling - time.time() + curtime, 0)) # actual client class which contains several (key,unit) pairs class Client(ConnectionThread): - def __init__(self, server, logger = None): - self.logger = getlogger() if not logger else logger - self.conn = Connection(server, logger) - super(Client, self).__init__(self.conn, self.logger) - self.basestatus = self.conn.get('status') - self.exchangeinfo = self.conn.get('exchanges') - self.sampling = min(240, 4 * self.basestatus['sampling']) - self.users = {} - self.lock = threading.Lock() + def __init__(self, server, logger=None): + self.logger = getlogger() if not logger else logger + self.conn = Connection(server, logger) + super(Client, self).__init__(self.conn, self.logger) + self.basestatus = self.conn.get('status') + self.exchangeinfo = self.conn.get('exchanges') + self.sampling = min(240, 4 * self.basestatus['sampling']) + self.users = {} + self.lock = threading.Lock() - def set(self, key, secret, address, name, unit, bid = None, ask = None, bot = 'pybot', ordermatch = False): - if not name in self.exchangeinfo or not unit in self.exchangeinfo[name]: - return False - key = str(key) - secret = str(secret) - if isinstance(_wrappers[name], type): - _wrappers[name] = _wrappers[name]() - exchange = _wrappers[name] - cost = { 'bid' : bid if bid else self.exchangeinfo[name][unit]['bid']['rate'], - 'ask' : ask if ask else self.exchangeinfo[name][unit]['ask']['rate'] } - self.lock.acquire() - if not key in self.users: - self.users[key] = {} - if unit in self.users[key]: - self.shutdown(key, unit) - self.users[key][unit] = { 'request' : RequestThread(self.conn, key, secret, exchange, unit, address, self.sampling, cost, self.logger) } - self.users[key][unit]['request'].start() - target = { 'bid': self.exchangeinfo[name][unit]['bid']['target'], 'ask': self.exchangeinfo[name][unit]['ask']['target'] } - if not bot or bot == 'none': - self.users[key][unit]['order'] = None - elif bot == 'nubot': - self.users[key][unit]['order'] = NuBot(self.conn, self.users[key][unit]['request'], key, secret, exchange, unit, target, self.logger, ordermatch) - elif bot == 'pybot': - self.users[key][unit]['order'] = PyBot(self.conn, self.users[key][unit]['request'], key, secret, exchange, unit, target, self.logger, ordermatch) - else: - self.logger.error("unknown order handler: %s", bot) - self.users[key][unit]['order'] = None - if self.users[key][unit]['order']: - if self.users[key][unit]['order']: - self.users[key][unit]['order'].start() - self.lock.release() - return True - - def shutdown(self, key = None, unit = None, join = True): - if key == None: - for key in self.users: - self.shutdown(key, unit, False) - if join: - for key in self.users: - self.shutdown(key, unit, True) - elif unit == None: - for unit in self.users[key]: - self.shutdown(key, unit, False) - if join: - for unit in self.users[key]: - self.shutdown(key, unit, True) - else: - while True: - try: - self.users[key][unit]['request'].stop() - if self.users[key][unit]['order']: - self.users[key][unit]['order'].stop() - if join: - self.users[key][unit]['request'].join() + def set(self, key, secret, address, name, unit, bid=None, ask=None, bot='pybot', ordermatch=False): + if not name in self.exchangeinfo or not unit in self.exchangeinfo[name]: + return False + key = str(key) + secret = str(secret) + if isinstance(_wrappers[name], type): + _wrappers[name] = _wrappers[name]() + exchange = _wrappers[name] + cost = {'bid': bid if bid else self.exchangeinfo[name][unit]['bid']['rate'], + 'ask': ask if ask else self.exchangeinfo[name][unit]['ask']['rate']} + self.lock.acquire() + if not key in self.users: + self.users[key] = {} + if unit in self.users[key]: + self.shutdown(key, unit) + self.users[key][unit] = { + 'request': RequestThread(self.conn, key, secret, exchange, unit, address, self.sampling, cost, self.logger)} + self.users[key][unit]['request'].start() + target = {'bid': self.exchangeinfo[name][unit]['bid']['target'], + 'ask': self.exchangeinfo[name][unit]['ask']['target']} + if not bot or bot == 'none': + self.users[key][unit]['order'] = None + elif bot == 'nubot': + self.users[key][unit]['order'] = NuBot(self.conn, self.users[key][unit]['request'], key, secret, exchange, + unit, target, self.logger, ordermatch) + elif bot == 'pybot': + self.users[key][unit]['order'] = PyBot(self.conn, self.users[key][unit]['request'], key, secret, exchange, + unit, target, self.logger, ordermatch) + else: + self.logger.error("unknown order handler: %s", bot) + self.users[key][unit]['order'] = None + if self.users[key][unit]['order']: if self.users[key][unit]['order']: - self.users[key][unit]['order'].join() - except KeyboardInterrupt: continue - break + self.users[key][unit]['order'].start() + self.lock.release() + return True - def run(self): - starttime = time.time() - curtime = time.time() - efficiencies = [] - while self.active: - sleep = 60 - time.time() + curtime - while sleep > 0: - step = min(sleep, 0.5) - time.sleep(step) - if not self.active: break - sleep -= step - if not self.active: break - self.lock.acquire() - try: - time.sleep(max(60 - time.time() + curtime, 0)) + def shutdown(self, key=None, unit=None, join=True): + if key == None: + for key in self.users: + self.shutdown(key, unit, False) + if join: + for key in self.users: + self.shutdown(key, unit, True) + elif unit == None: + for unit in self.users[key]: + self.shutdown(key, unit, False) + if join: + for unit in self.users[key]: + self.shutdown(key, unit, True) + else: + while True: + try: + self.users[key][unit]['request'].stop() + if self.users[key][unit]['order']: + self.users[key][unit]['order'].stop() + if join: + self.users[key][unit]['request'].join() + if self.users[key][unit]['order']: + self.users[key][unit]['order'].join() + except KeyboardInterrupt: + continue + break + + def run(self): + starttime = time.time() curtime = time.time() - for user in self.users: # post some statistics - response = self.conn.get(user, trials = 1) - if 'error' in response: - logger.error('unable to receive statistics for user %s: %s', user, response['message']) - self.users[user].values()[0]['request'].register() # reassure to be registered - newstatus = self.conn.get('status', trials = 3) - if not 'error' in newstatus: - basestatus = newstatus - sampling = min(240, 4 * self.basestatus['sampling']) - else: - # collect user information - effective_rate = 0.0 - total = 0.0 - for unit in response['units']: - for side in [ 'bid', 'ask' ]: - effective_rate += float(sum([ o['amount'] * o['cost'] for o in response['units'][unit][side] ])) - total += float(sum([ o['amount'] for o in response['units'][unit][side] ])) - if total > 0.0: effective_rate /= total - orderstring = "" - for unit in response['units']: - unitstring = "" - for side in ['bid', 'ask']: - market = response['units'][unit][side] - coststring = "" - for order in response['units'][unit][side]: - if order['amount'] > 0: - coststring += " %.4f x %.2f%%," % (order['amount'], order['cost'] * 100.0) - if len(coststring): - unitstring += " - %s:%s" % (side, coststring[:-1]) - if len(unitstring): - orderstring += " - %s%s" % (unit, unitstring) - # print user information - msg = '' if response['message'] == '' else "ATTENTION: %s " % response['message'] - self.logger.info('%s%s - balance: %.8f rate: %.2f%% ppm: %.8f efficiency: %.2f%% rejects: %d missings: %d%s - %s', msg, repr(self.users[user].values()[0]['request'].exchange), - response['balance'], effective_rate * 100, effective_rate * total / float(60 * 24), response['efficiency'] * 100, response['rejects'], response['missing'], orderstring, user) - if not efficiencies: - efficiencies = [ response['efficiency'] for i in xrange(5) ] - if curtime - starttime > 150: - efficiencies = efficiencies[1:] + [response['efficiency']] - if sorted(efficiencies)[2] < 0.95: - for unit in response['units']: - if response['units'][unit]['rejects'] > 1 and response['units'][unit]['rejects'] / float(self.basestatus['sampling']) >= 0.05: # look for valid error and adjust nonce shift - if response['units'][unit]['last_error'] != "": - if 'deviates too much from current price' in response['units'][unit]['last_error']: - PyBot.pricefeed.price(unit, True) # force a price update - if self.users[user][unit]['order']: self.users[user][unit]['order'].shutdown() - self.logger.warning('price missmatch for %s on %s, forcing price update', unit, repr(self.users[user][unit]['request'].exchange)) - else: - shift = self.users[user][unit]['request'].exchange._shift - self.users[user][unit]['request'].exchange.adjust(response['units'][unit]['last_error']) - if shift != self.users[user][unit]['request'].exchange._shift: - self.logger.warning('too many rejected requests for %s on %s, adjusting nonce shift to %d', - unit, repr(self.users[user][unit]['request'].exchange), self.users[user][unit]['request'].exchange._shift) + efficiencies = [] + while self.active: + sleep = 60 - time.time() + curtime + while sleep > 0: + step = min(sleep, 0.5) + time.sleep(step) + if not self.active: break + sleep -= step + if not self.active: break + self.lock.acquire() + try: + time.sleep(max(60 - time.time() + curtime, 0)) + curtime = time.time() + for user in self.users: # post some statistics + response = self.conn.get(user, trials=1) + if 'error' in response: + logger.error('unable to receive statistics for user %s: %s', user, response['message']) + self.users[user].values()[0]['request'].register() # reassure to be registered + newstatus = self.conn.get('status', trials=3) + if not 'error' in newstatus: + basestatus = newstatus + sampling = min(240, 4 * self.basestatus['sampling']) else: - if self.users[user][unit]['request'].sampling < 3 * self.sampling: # just send more requests - self.users[user][unit]['request'].sampling = self.users[user][unit]['request'].sampling + 1 - self.logger.warning('increasing sampling to %d', - unit, repr(self.users[user][unit]['request'].exchange), self.users[user][unit]['request'].sampling) - if response['units'][unit]['missing'] / float(self.basestatus['sampling']) >= 0.05: # look for missing error and adjust sampling - if self.users[user][unit]['request'].sampling < 3 * self.sampling: # just send more requests - self.users[user][unit]['request'].sampling = self.users[user][unit]['request'].sampling + 1 - self.logger.warning('too many missing requests for %s on %s, increasing sampling to %d', - unit, repr(self.users[user][unit]['request'].exchange), self.users[user][unit]['request'].sampling) - except KeyboardInterrupt: break - except Exception as e: - self.logger.error('exception caught in main loop: %s', sys.exc_info()[1]) - self.lock.release() - self.lock.acquire() - logger.info('stopping trading bots, please allow the client up to 1 minute to terminate') - self.shutdown() - self.lock.release() + # collect user information + effective_rate = 0.0 + total = 0.0 + for unit in response['units']: + for side in ['bid', 'ask']: + effective_rate += float( + sum([o['amount'] * o['cost'] for o in response['units'][unit][side]])) + total += float(sum([o['amount'] for o in response['units'][unit][side]])) + if total > 0.0: effective_rate /= total + orderstring = "" + for unit in response['units']: + unitstring = "" + for side in ['bid', 'ask']: + market = response['units'][unit][side] + coststring = "" + for order in response['units'][unit][side]: + if order['amount'] > 0: + coststring += " %.4f x %.2f%%," % (order['amount'], order['cost'] * 100.0) + if len(coststring): + unitstring += " - %s:%s" % (side, coststring[:-1]) + if len(unitstring): + orderstring += " - %s%s" % (unit, unitstring) + # print user information + msg = '' if response['message'] == '' else "ATTENTION: %s " % response['message'] + self.logger.info( + '%s%s - balance: %.8f rate: %.2f%% ppm: %.8f efficiency: %.2f%% rejects: %d missings: %d%s - %s', + msg, repr(self.users[user].values()[0]['request'].exchange), + response['balance'], effective_rate * 100, effective_rate * total / float(60 * 24), + response['efficiency'] * 100, response['rejects'], response['missing'], orderstring, user) + if not efficiencies: + efficiencies = [response['efficiency'] for i in xrange(5)] + if curtime - starttime > 150: + efficiencies = efficiencies[1:] + [response['efficiency']] + if sorted(efficiencies)[2] < 0.95: + for unit in response['units']: + if response['units'][unit]['rejects'] > 1 and response['units'][unit][ + 'rejects'] / float(self.basestatus[ + 'sampling']) >= 0.05: # look for valid error and adjust nonce shift + if response['units'][unit]['last_error'] != "": + if 'deviates too much from current price' in response['units'][unit][ + 'last_error']: + PyBot.pricefeed.price(unit, True) # force a price update + if self.users[user][unit]['order']: self.users[user][unit][ + 'order'].shutdown() + self.logger.warning( + 'price missmatch for %s on %s, forcing price update', unit, + repr(self.users[user][unit]['request'].exchange)) + else: + shift = self.users[user][unit]['request'].exchange._shift + self.users[user][unit]['request'].exchange.adjust( + response['units'][unit]['last_error']) + if shift != self.users[user][unit]['request'].exchange._shift: + self.logger.warning( + 'too many rejected requests for %s on %s, adjusting nonce shift to %d', + unit, repr(self.users[user][unit]['request'].exchange), + self.users[user][unit]['request'].exchange._shift) + else: + if self.users[user][unit][ + 'request'].sampling < 3 * self.sampling: # just send more requests + self.users[user][unit]['request'].sampling = self.users[user][unit][ + 'request'].sampling + 1 + self.logger.warning('increasing sampling to %d', + unit, + repr(self.users[user][unit]['request'].exchange), + self.users[user][unit]['request'].sampling) + if response['units'][unit]['missing'] / float(self.basestatus[ + 'sampling']) >= 0.05: # look for missing error and adjust sampling + if self.users[user][unit][ + 'request'].sampling < 3 * self.sampling: # just send more requests + self.users[user][unit]['request'].sampling = self.users[user][unit][ + 'request'].sampling + 1 + self.logger.warning( + 'too many missing requests for %s on %s, increasing sampling to %d', + unit, repr(self.users[user][unit]['request'].exchange), + self.users[user][unit]['request'].sampling) + except KeyboardInterrupt: + break + except Exception as e: + self.logger.error('exception caught in main loop: %s', sys.exc_info()[1]) + self.lock.release() + self.lock.acquire() + logger.info('stopping trading bots, please allow the client up to 1 minute to terminate') + self.shutdown() + self.lock.release() if __name__ == "__main__": - logger = getlogger() - userfile = 'pool.conf' if len(sys.argv) == 1 else sys.argv[1] - if userfile == "-": - userdata = [ line.strip().split('#')[0].split() for line in sys.stdin.readlines() if len(line.strip().split('#')[0].split()) >= 5 ] - else: - client = None - try: - userdata = [ line.strip().split('#')[0].split() for line in open(userfile).readlines() if len(line.strip().split('#')[0].split()) >= 5 ] - if len(userdata) != 0: # try to interpret data as list of address unit exchange key secret bid ask bot - if len(sys.argv) == 1: - logger.error('multi-key format in %s requires pool IP to be specified as second parameter to the client', userfile) - sys.exit(1) - client = Client(sys.argv[2]) - for user in userdata: - key = user[3] - secret = user[4] - name = user[2].lower() - if not name in _wrappers: - logger.error("unknown exchange: %s", user[2]) - sys.exit(1) - exchange = _wrappers[name] - for unit in user[1].split(','): - unit = unit.lower() - if len(user) >= 6 and float(user[5]) != 0.0: - bid = float(user[5]) / 100.0 - ask = float(user[5]) / 100.0 - if len(user) >= 7 and float(user[6]) != 0.0: - ask = float(user[6]) / 100.0 - bot = 'pybot' if len(user) < 8 else user[7] - ordermatch = False if len(user) < 9 else (user[8] == 'match') - if not client.set(key, secret, user[0], name, unit, bid, ask, bot): - logger.error("%s on %s not supported by pool", unit, name) - sys.exit(1) - else: - configdata = dict([ ( v.strip() for v in line.strip().split('#')[0].split('=')) for line in open(userfile).readlines() if len(line.strip().split('#')[0].split('=')) == 2 ]) - if len(configdata.keys()) > 0: - if 'interest' in configdata: - bid = float(configdata['interest'].split(',')[0]) / 100.0 - ask = bid - if ',' in configdata['interest']: - ask = float(configdata['interest'].split(',')[1]) / 100.0 - else: - bid = None - ask = None - bot = 'pybot' if not 'trading' in configdata else configdata['trading'] - ordermatch = False if not 'ordermatch' in configdata else (configdata['ordermatch'] in ['True', 'true', '1']) - if 'server' in configdata: - if 'apikey' in configdata: - if 'apisecret' in configdata: - if 'address' in configdata: - if 'unit' in configdata: - if 'exchange' in configdata: - name = configdata['exchange'].lower() - if name in _wrappers: - client = Client(configdata['server'], logger) - client.set(configdata['apikey'], configdata['apisecret'], configdata['address'], name, configdata['unit'].lower(), bid, ask, bot, ordermatch) - else: - logger.error("unknown exchange: %s", name) + logger = getlogger() + userfile = 'pool.conf' if len(sys.argv) == 1 else sys.argv[1] + if userfile == "-": + # get the user data from the command line input + userdata = [line.strip().split('#')[0].split() for line in sys.stdin.readlines() if + len(line.strip().split('#')[0].split()) >= 5] + else: + client = None + try: + userdata = [line.strip().split('#')[0].split() for line in open(userfile).readlines() if + len(line.strip().split('#')[0].split()) >= 5] + if len(userdata) != 0: # try to interpret data as list of address unit exchange key secret bid ask bot + if len(sys.argv) == 1: + logger.error( + 'multi-key format in %s requires pool IP to be specified as second parameter to the client', + userfile) + sys.exit(1) + client = Client(sys.argv[2]) + for user in userdata: + key = user[3] + secret = user[4] + name = user[2].lower() + if not name in _wrappers: + logger.error("unknown exchange: %s", user[2]) + sys.exit(1) + exchange = _wrappers[name] + for unit in user[1].split(','): + unit = unit.lower() + if len(user) >= 6 and float(user[5]) != 0.0: + bid = float(user[5]) / 100.0 + ask = float(user[5]) / 100.0 + if len(user) >= 7 and float(user[6]) != 0.0: + ask = float(user[6]) / 100.0 + bot = 'pybot' if len(user) < 8 else user[7] + ordermatch = False if len(user) < 9 else (user[8] == 'match') + if not client.set(key, secret, user[0], name, unit, bid, ask, bot): + logger.error("%s on %s not supported by pool", unit, name) + sys.exit(1) + else: + configdata = dict( + [(v.strip() for v in line.strip().split('#')[0].split('=')) for line in open(userfile).readlines() + if len(line.strip().split('#')[0].split('=')) == 2]) + if len(configdata.keys()) > 0: + if 'interest' in configdata: + bid = float(configdata['interest'].split(',')[0]) / 100.0 + ask = bid + if ',' in configdata['interest']: + ask = float(configdata['interest'].split(',')[1]) / 100.0 + else: + bid = None + ask = None + bot = 'pybot' if not 'trading' in configdata else configdata['trading'] + ordermatch = False if not 'ordermatch' in configdata else ( + configdata['ordermatch'] in ['True', 'true', '1']) + if 'server' in configdata: + if 'apikey' in configdata: + if 'apisecret' in configdata: + if 'address' in configdata: + if 'unit' in configdata: + if 'exchange' in configdata: + name = configdata['exchange'].lower() + if name in _wrappers: + client = Client(configdata['server'], logger) + client.set(configdata['apikey'], configdata['apisecret'], + configdata['address'], name, configdata['unit'].lower(), bid, + ask, bot, ordermatch) + else: + logger.error("unknown exchange: %s", name) + else: + logger.error('exchange information missing in %s', userfile) + else: + logger.error('unit information missing in %s', userfile) + else: + logger.error('address missing in %s', userfile) + else: + logger.error('apisecret missing in %s', userfile) + else: + logger.error('apikey missing in %s', userfile) else: - logger.error('exchange information missing in %s', userfile) - else: - logger.error('unit information missing in %s', userfile) + logger.error('server missing in %s', userfile) else: - logger.error('address missing in %s', userfile) - else: - logger.error('apisecret missing in %s', userfile) - else: - logger.error('apikey missing in %s', userfile) - else: - logger.error('server missing in %s', userfile) - else: - logger.error('no valid user information could be found') - except: - logger.error("%s could not be read: %s", userfile, sys.exc_info()[1]) - if not client: sys.exit(1) - logger.debug('starting liquidity operation with sampling %d' % client.sampling) - client.start() - stop = False - while True: - try: - if stop: - client.stop() - client.join() - break - time.sleep(60) - except KeyboardInterrupt: stop = True \ No newline at end of file + logger.error('no valid user information could be found') + except: + logger.error("%s could not be read: %s", userfile, sys.exc_info()[1]) + if not client: + sys.exit(1) + logger.debug('starting liquidity operation with sampling %d' % client.sampling) + client.start() + stop = False + while True: + try: + if stop: + client.stop() + client.join() + break + time.sleep(60) + except KeyboardInterrupt: + stop = True \ No newline at end of file diff --git a/python/exchanges.py b/python/exchanges.py index 9bce675..986e9d4 100644 --- a/python/exchanges.py +++ b/python/exchanges.py @@ -34,687 +34,744 @@ import threading import datetime + class Exchange(object): - def __init__(self, fee): - self.fee = fee - self._shift = 1 - self._nonce = 0 + def __init__(self, fee): + self.fee = fee + self._shift = 1 + self._nonce = 0 - def adjust(self, error): - if not 'exception caught:' in error: - self._shift = ((self._shift + 7) % 200) - 100 # -92 15 -78 29 -64 43 -50 57 ... + def adjust(self, error): + if 'exception caught:' not in error: + self._shift = ((self._shift + 7) % 200) - 100 # -92 15 -78 29 -64 43 -50 57 ... - def nonce(self, factor = 1000.0): - n = int((time.time() + self._shift) * float(factor)) - if self._nonce >= n: - n = self._nonce + 10 - self._nonce = n - return n + def nonce(self, factor=1000.0): + n = int((time.time() + self._shift) * float(factor)) + if self._nonce >= n: + n = self._nonce + 10 + self._nonce = n + return n class Bittrex(Exchange): - def __init__(self): - super(Bittrex, self).__init__(0.0025) - self.placed = {} - self.closed = [] - - def __repr__(self): return "bittrex" - - def adjust(self, error): - pass - - def post(self, method, params, key, secret, throttle = 5): - data = 'https://bittrex.com/api/v1.1' + method + '?apikey=%s&nonce=%d&' % (key, self.nonce()) + urllib.urlencode(params) - sign = hmac.new(secret, data, hashlib.sha512).hexdigest() - headers = { 'apisign' : sign } - connection = httplib.HTTPSConnection('bittrex.com', timeout = 10) - connection.request('GET', data, headers = headers) - response = json.loads(connection.getresponse().read()) - if throttle > 0 and not response['success'] and 'THROTTLED' in response['message']: - time.sleep(2) - return self.post(method, params, key, secret, throttle - 1) - return response - - def get(self, method, params): - data = 'https://bittrex.com/api/v1.1' + method + '?' + urllib.urlencode(params) - connection = httplib.HTTPSConnection('bittrex.com', timeout = 10) - connection.request('GET', data, headers = {}) - return json.loads(connection.getresponse().read()) - - def cancel_orders(self, unit, side, key, secret): - response = self.post('/market/getopenorders', { 'market' : "%s-NBT"%unit.upper() }, key, secret) - if not response['success']: - response['error'] = response['message'] - return response - if not response['result']: - response['result'] = [] - response['removed'] = [] - response['amount'] = 0.0 - for order in response['result']: - if side == 'all' or (side == 'bid' and 'BUY' in order['OrderType']) or (side == 'ask' and 'SELL' in order['OrderType']): - ret = self.post('/market/cancel', { 'uuid' : order['OrderUuid'] }, key, secret) - if not ret['success'] and ret['message'] != "ORDER_NOT_OPEN": - if not 'error' in response: response = { 'error': "" } - response['error'] += "," + ret['message'] - else: - response['removed'].append(order['OrderUuid']) - response['amount'] += order['Quantity'] - if not 'error' in response and key in self.placed and unit in self.placed[key]: - if side == 'all': - self.placed[key][unit]['bid'] = False - self.placed[key][unit]['ask'] = False - else: - self.placed[key][unit][side] = False - return response - - def place_order(self, unit, side, key, secret, amount, price): - ret = self.cancel_orders(unit, side, key, secret) - if 'error' in ret: return ret - amount += ret['amount'] - if side == 'bid': - amount *= (1.0 - self.fee) - params = { 'market' : "%s-NBT"%unit.upper(), "rate" : price, "quantity" : amount } - response = self.post('/market/buylimit' if side == 'bid' else '/market/selllimit', params, key, secret) - if response['success']: - response['id'] = response['result']['uuid'] - if not key in self.placed: - self.placed[key] = {} - if not unit in self.placed[key]: - self.placed[key][unit] = { 'bid' : False, 'ask' : False } - self.placed[key][unit][side] = response['id'] - else: - response['error'] = response['message'] - response['residual'] = ret['amount'] - return response - - def get_balance(self, unit, key, secret): - response = self.post('/account/getbalance', {'currency' : unit.upper()}, key, secret) - if response['success']: - try: - response['balance'] = float(response['result']['Available']) - except: - response['balance'] = 0.0 - else: - response['error'] = response['message'] - return response - - def get_price(self, unit): - response = self.get('/public/getticker', {'market' : '%s-NBT' % unit}) - if response['success']: - response.update({'bid': response['result']['Bid'], 'ask': response['result']['Ask']}) - else: - response['error'] = response['message'] - return response - - def create_request(self, unit, key = None, secret = None): - if not secret or not key: - return None, None - uuids = [] - if key in self.placed and unit in self.placed[key]: - if self.placed[key][unit]['bid']: - uuids.append(self.placed[key][unit]['bid']) - if self.placed[key][unit]['ask']: - uuids.append(self.placed[key][unit]['ask']) - requests = [] - signatures = [] - for uuid in uuids: - data = 'https://bittrex.com/api/v1.1/account/getorder?apikey=%s&nonce=%d&uuid=%s' % (key, self.nonce(), uuid) - requests.append(data) - signatures.append(hmac.new(secret, data, hashlib.sha512).hexdigest()) - return { 'requests' : json.dumps(requests), 'signs' : json.dumps(signatures) }, None - - def validate_request(self, key, unit, data, signs): - orders = [] - last_error = "" - requests = json.loads(data['requests']) - signs = json.loads(data['signs']) - if len(requests) != len(signs): - return { 'error' : 'missmatch between requests and signatures (%d vs %d)' % (len(data['requests']), len(signs)) } - if len(requests) > 2: - return { 'error' : 'too many requests received: %d' % len(requests) } - connection = httplib.HTTPSConnection('bittrex.com', timeout = 5) - for data, sign in zip(requests, signs): - uuid = data.split('=')[-1] - if not uuid in self.closed: - headers = { 'apisign' : sign } - connection.request('GET', data, headers = headers) + def __init__(self): + super(Bittrex, self).__init__(0.0025) + self.placed = {} + self.closed = [] + + def __repr__(self): + return "bittrex" + + def adjust(self, error): + pass + + def post(self, method, params, key, secret, throttle=5): + data = 'https://bittrex.com/api/v1.1' + method + '?apikey=%s&nonce=%d&' % ( + key, self.nonce()) + urllib.urlencode(params) + sign = hmac.new(secret, data, hashlib.sha512).hexdigest() + headers = {'apisign': sign} + connection = httplib.HTTPSConnection('bittrex.com', timeout=10) + connection.request('GET', data, headers=headers) response = json.loads(connection.getresponse().read()) + if throttle > 0 and not response['success'] and 'THROTTLED' in response['message']: + time.sleep(2) + return self.post(method, params, key, secret, throttle - 1) + return response + + def get(self, method, params): + data = 'https://bittrex.com/api/v1.1' + method + '?' + urllib.urlencode(params) + connection = httplib.HTTPSConnection('bittrex.com', timeout=10) + connection.request('GET', data, headers={}) + return json.loads(connection.getresponse().read()) + + def cancel_orders(self, unit, side, key, secret): + response = self.post('/market/getopenorders', {'market': "%s-NBT" % unit.upper()}, key, secret) + if not response['success']: + response['error'] = response['message'] + return response + if not response['result']: + response['result'] = [] + response['removed'] = [] + response['amount'] = 0.0 + for order in response['result']: + if side == 'all' or (side == 'bid' and 'BUY' in order['OrderType']) or ( + side == 'ask' and 'SELL' in order['OrderType']): + ret = self.post('/market/cancel', {'uuid': order['OrderUuid']}, key, secret) + if not ret['success'] and ret['message'] != "ORDER_NOT_OPEN": + if not 'error' in response: response = {'error': ""} + response['error'] += "," + ret['message'] + else: + response['removed'].append(order['OrderUuid']) + response['amount'] += order['Quantity'] + if not 'error' in response and key in self.placed and unit in self.placed[key]: + if side == 'all': + self.placed[key][unit]['bid'] = False + self.placed[key][unit]['ask'] = False + else: + self.placed[key][unit][side] = False + return response + + def place_order(self, unit, side, key, secret, amount, price): + ret = self.cancel_orders(unit, side, key, secret) + if 'error' in ret: return ret + amount += ret['amount'] + if side == 'bid': + amount *= (1.0 - self.fee) + params = {'market': "%s-NBT" % unit.upper(), "rate": price, "quantity": amount} + response = self.post('/market/buylimit' if side == 'bid' else '/market/selllimit', params, key, secret) + if response['success']: + response['id'] = response['result']['uuid'] + if not key in self.placed: + self.placed[key] = {} + if not unit in self.placed[key]: + self.placed[key][unit] = {'bid': False, 'ask': False} + self.placed[key][unit][side] = response['id'] + else: + response['error'] = response['message'] + response['residual'] = ret['amount'] + return response + + def get_balance(self, unit, key, secret): + response = self.post('/account/getbalance', {'currency': unit.upper()}, key, secret) + if response['success']: + try: + response['balance'] = float(response['result']['Available']) + except: + response['balance'] = 0.0 + else: + response['error'] = response['message'] + return response + + def get_price(self, unit): + response = self.get('/public/getticker', {'market': '%s-NBT' % unit}) if response['success']: - try: opened = int(datetime.datetime.strptime(response['result']['Opened'], '%Y-%m-%dT%H:%M:%S.%f').strftime("%s")) - except: opened = 0 - try: closed = int(datetime.datetime.strptime(response['result']['Closed'], '%Y-%m-%dT%H:%M:%S.%f').strftime("%s")) - except: closed = sys.maxint - if closed < time.time() - 60: - self.closed.append(uuid) - orders.append({ - 'id' : response['result']['OrderUuid'], - 'price' : response['result']['Limit'], - 'type' : 'ask' if 'SELL' in response['result']['Type'] else 'bid', - 'amount' : response['result']['QuantityRemaining'], # if not closed == sys.maxint else response['result']['Quantity'], - 'opened' : opened, - 'closed' : closed, - }) + response.update({'bid': response['result']['Bid'], 'ask': response['result']['Ask']}) else: - last_error = response['message'] - if not orders and last_error != "": - return { 'error' : last_error } - return orders + response['error'] = response['message'] + return response + + def create_request(self, unit, key=None, secret=None): + if not secret or not key: + return None, None + uuids = [] + if key in self.placed and unit in self.placed[key]: + if self.placed[key][unit]['bid']: + uuids.append(self.placed[key][unit]['bid']) + if self.placed[key][unit]['ask']: + uuids.append(self.placed[key][unit]['ask']) + requests = [] + signatures = [] + for uuid in uuids: + data = 'https://bittrex.com/api/v1.1/account/getorder?apikey=%s&nonce=%d&uuid=%s' % ( + key, self.nonce(), uuid) + requests.append(data) + signatures.append(hmac.new(secret, data, hashlib.sha512).hexdigest()) + return {'requests': json.dumps(requests), 'signs': json.dumps(signatures)}, None + + def validate_request(self, key, unit, data, signs): + orders = [] + last_error = "" + requests = json.loads(data['requests']) + signs = json.loads(data['signs']) + if len(requests) != len(signs): + return { + 'error': 'missmatch between requests and signatures (%d vs %d)' % (len(data['requests']), len(signs))} + if len(requests) > 2: + return {'error': 'too many requests received: %d' % len(requests)} + connection = httplib.HTTPSConnection('bittrex.com', timeout=5) + for data, sign in zip(requests, signs): + uuid = data.split('=')[-1] + if not uuid in self.closed: + headers = {'apisign': sign} + connection.request('GET', data, headers=headers) + response = json.loads(connection.getresponse().read()) + if response['success']: + try: + opened = int( + datetime.datetime.strptime(response['result']['Opened'], '%Y-%m-%dT%H:%M:%S.%f').strftime( + "%s")) + except: + opened = 0 + try: + closed = int( + datetime.datetime.strptime(response['result']['Closed'], '%Y-%m-%dT%H:%M:%S.%f').strftime( + "%s")) + except: + closed = sys.maxint + if closed < time.time() - 60: + self.closed.append(uuid) + orders.append({ + 'id': response['result']['OrderUuid'], + 'price': response['result']['Limit'], + 'type': 'ask' if 'SELL' in response['result']['Type'] else 'bid', + 'amount': response['result']['QuantityRemaining'], + # if not closed == sys.maxint else response['result']['Quantity'], + 'opened': opened, + 'closed': closed, + }) + else: + last_error = response['message'] + if not orders and last_error != "": + return {'error': last_error} + return orders class Poloniex(Exchange): - def __init__(self): - super(Poloniex, self).__init__(0.002) - - def __repr__(self): return "poloniex" - - def adjust(self, error): - if "Nonce must be greater than" in error: # (TODO: regex) - if ':' in error: error = error.split(':')[1].strip() - error = error.replace('.', '').split() - self._shift += 100.0 + (int(error[5]) - int(error[8])) / 1000.0 - else: - self._shift = self._shift + 100.0 - - def post(self, method, params, key, secret): - request = { 'nonce' : self.nonce(), 'command' : method } - request.update(params) - data = urllib.urlencode(request) - sign = hmac.new(secret, data, hashlib.sha512).hexdigest() - headers = { 'Sign' : sign, 'Key' : key } - return json.loads(urllib2.urlopen(urllib2.Request('https://poloniex.com/tradingApi', data, headers)).read()) - - def cancel_orders(self, unit, side, key, secret): - response = self.post('returnOpenOrders', { 'currencyPair' : "%s_NBT"%unit.upper() }, key, secret) - if 'error' in response: return response - for order in response: - if side == 'all' or (side == 'bid' and order['type'] == 'buy') or (side == 'ask' and order['type'] == 'sell'): - ret = self.post('cancelOrder', { 'currencyPair' : "%s_NBT"%unit.upper(), 'orderNumber' : order['orderNumber'] }, key, secret) - if 'error' in ret: - if isinstance(response,list): response = { 'error': "" } - response['error'] += "," + ret['error'] - return response - - def place_order(self, unit, side, key, secret, amount, price): - params = { 'currencyPair' : "%s_NBT"%unit.upper(), "rate" : price, "amount" : amount } - response = self.post('buy' if side == 'bid' else 'sell', params, key, secret) - if not 'error' in response: - response['id'] = int(response['orderNumber']) - return response - - def get_balance(self, unit, key, secret): - response = self.post('returnBalances', {}, key, secret) - if not 'error' in response: - response['balance'] = float(response[unit.upper()]) - return response - - def get_price(self, unit): - response = json.loads(urllib2.urlopen('https://poloniex.com/public?' + - urllib.urlencode({'command' : 'returnOrderBook', 'currencyPair' : "%s_NBT"%unit.upper(), 'depth' : 1}), timeout = 5).read()) - if not 'error' in response: - response.update({'bid': None, 'ask': None}) - if response['bid']: response['bid'] = float(response['bid'][0]) - if response['ask']: response['ask'] = float(response['ask'][0]) - return response - - def create_request(self, unit, key = None, secret = None): - if not secret: return None, None - request = { 'command' : 'returnOpenOrders', 'nonce' : self.nonce(), 'currencyPair' : "%s_NBT"%unit.upper() } - data = urllib.urlencode(request) - sign = hmac.new(secret, data, hashlib.sha512).hexdigest() - return request, sign - - def validate_request(self, key, unit, data, sign): - headers = { 'Sign' : sign, 'Key' : key } - ret = urllib2.urlopen(urllib2.Request('https://poloniex.com/tradingApi', urllib.urlencode(data), headers), timeout = 5) - response = json.loads(ret.read()) - if 'error' in response: return response - return [ { - 'id' : int(order['orderNumber']), - 'price' : float(order['rate']), - 'type' : 'ask' if order['type'] == 'sell' else 'bid', - 'amount' : float(order['amount']), - } for order in response ] + def __init__(self): + super(Poloniex, self).__init__(0.002) + + def __repr__(self): + return "poloniex" + + def adjust(self, error): + if "Nonce must be greater than" in error: # (TODO: regex) + if ':' in error: error = error.split(':')[1].strip() + error = error.replace('.', '').split() + self._shift += 100.0 + (int(error[5]) - int(error[8])) / 1000.0 + else: + self._shift = self._shift + 100.0 + + def post(self, method, params, key, secret): + request = {'nonce': self.nonce(), 'command': method} + request.update(params) + data = urllib.urlencode(request) + sign = hmac.new(secret, data, hashlib.sha512).hexdigest() + headers = {'Sign': sign, 'Key': key} + return json.loads(urllib2.urlopen(urllib2.Request('https://poloniex.com/tradingApi', data, headers)).read()) + + def cancel_orders(self, unit, side, key, secret): + response = self.post('returnOpenOrders', {'currencyPair': "%s_NBT" % unit.upper()}, key, secret) + if 'error' in response: return response + for order in response: + if side == 'all' or (side == 'bid' and order['type'] == 'buy') or ( + side == 'ask' and order['type'] == 'sell'): + ret = self.post('cancelOrder', + {'currencyPair': "%s_NBT" % unit.upper(), 'orderNumber': order['orderNumber']}, key, + secret) + if 'error' in ret: + if isinstance(response, list): response = {'error': ""} + response['error'] += "," + ret['error'] + return response + + def place_order(self, unit, side, key, secret, amount, price): + params = {'currencyPair': "%s_NBT" % unit.upper(), "rate": price, "amount": amount} + response = self.post('buy' if side == 'bid' else 'sell', params, key, secret) + if not 'error' in response: + response['id'] = int(response['orderNumber']) + return response + + def get_balance(self, unit, key, secret): + response = self.post('returnBalances', {}, key, secret) + if not 'error' in response: + response['balance'] = float(response[unit.upper()]) + return response + + def get_price(self, unit): + response = json.loads(urllib2.urlopen('https://poloniex.com/public?' + + urllib.urlencode({'command': 'returnOrderBook', + 'currencyPair': "%s_NBT" % unit.upper(), 'depth': 1}), + timeout=5).read()) + if not 'error' in response: + response.update({'bid': None, 'ask': None}) + if response['bid']: response['bid'] = float(response['bid'][0]) + if response['ask']: response['ask'] = float(response['ask'][0]) + return response + + def create_request(self, unit, key=None, secret=None): + if not secret: return None, None + request = {'command': 'returnOpenOrders', 'nonce': self.nonce(), 'currencyPair': "%s_NBT" % unit.upper()} + data = urllib.urlencode(request) + sign = hmac.new(secret, data, hashlib.sha512).hexdigest() + return request, sign + + def validate_request(self, key, unit, data, sign): + headers = {'Sign': sign, 'Key': key} + ret = urllib2.urlopen(urllib2.Request('https://poloniex.com/tradingApi', urllib.urlencode(data), headers), + timeout=5) + response = json.loads(ret.read()) + if 'error' in response: return response + return [{ + 'id': int(order['orderNumber']), + 'price': float(order['rate']), + 'type': 'ask' if order['type'] == 'sell' else 'bid', + 'amount': float(order['amount']), + } for order in response] class CCEDK(Exchange): - def __init__(self): - super(CCEDK, self).__init__(0.002) - self.pair_id = {} - self.currency_id = {} - failed = False - while not self.pair_id or not self.currency_id: - try: - response = None - if not self.pair_id: - response = json.loads(urllib2.urlopen(urllib2.Request( - 'https://www.ccedk.com/api/v1/stats/marketdepthfull?' + urllib.urlencode({ 'nonce' : self.nonce() }))).read(), timeout = 15) - for unit in response['response']['entities']: - if unit['pair_name'][:4] == 'NBT/': - self.pair_id[unit['pair_name'][4:]] = unit['pair_id'] - if not self.currency_id: - response = json.loads(urllib2.urlopen(urllib2.Request( - 'https://www.ccedk.com/api/v1/currency/list?' + urllib.urlencode({ 'nonce' : self.nonce() }))).read(), timeout = 15) - for unit in response['response']['entities']: - self.currency_id[unit['iso'].lower()] = unit['currency_id'] - except: - if response and not response['response']: - self.adjust(",".join(response['errors'].values())) - if failed: print >> sys.stderr, "could not retrieve ccedk ids, will adjust shift to", self._shift, "reason:", ",".join(response['errors'].values()) + def __init__(self): + super(CCEDK, self).__init__(0.002) + self.pair_id = {} + self.currency_id = {} + failed = False + while not self.pair_id or not self.currency_id: + try: + response = None + if not self.pair_id: + url = 'https://www.ccedk.com/api/v1/stats/marketdepthfull' + response = json.loads(urllib2.urlopen(urllib2.Request(url), timeout=15).read()) + for unit in response['response']['entities']: + if unit['pair_name'][:4] == 'nbt/': + self.pair_id[unit['pair_name'][4:]] = unit['pair_id'] + if not self.currency_id: + url = 'https://www.ccedk.com/api/v1/currency/list' + response = json.loads(urllib2.urlopen(urllib2.Request(url), timeout=15).read()) + for unit in response['response']['entities']: + self.currency_id[unit['iso'].lower()] = unit['currency_id'] + except Exception as e: + if response and not response['response']: + self.adjust(",".join(response['errors'].values())) + if failed: + print >> sys.stderr, "could not retrieve ccedk ids, will adjust shift to", self._shift, \ + "reason:", ",".join(response['errors'].values()) + else: + print >> sys.stderr, "could not retrieve ccedk ids, server is unreachable", e + failed = True + time.sleep(1) + + def __repr__(self): + return "ccedk" + + def nonce(self, factor=1.0): + n = int(time.time() + self._shift) + if n == self._nonce: + n = self._nonce + 1 + self._nonce = n + return n + + def adjust(self, error): + if "incorrect range" in error: # (TODO: regex) + if ':' in error: + error = error.split(':')[1].strip() + try: + minimum = int(error.strip().split()[-3].replace('`', '')) + maximum = int(error.strip().split()[-1].replace('`', '')) + current = int(error.strip().split()[-7].split('`')[3]) + except: + self._shift += random.randrange(-10, 10) + else: + if current < maximum: + new_shift = (minimum + 2 * maximum) / 3 - current + if new_shift < 0: + new_shift = 10 + else: + new_shift = (2 * minimum + maximum) / 3 - current + if new_shift != 0: + self._shift += new_shift + else: + self._shift += random.randrange(-10, 10) else: - print >> sys.stderr, "could not retrieve ccedk ids, server is unreachable" - failed = True - time.sleep(1) - - def __repr__(self): return "ccedk" - - def nonce(self, factor = 1.0): - n = int(time.time() + self._shift) - if n == self._nonce: - n = self._nonce + 1 - self._nonce = n - return n - - def adjust(self, error): - if "incorrect range" in error: #(TODO: regex) - if ':' in error: error = error.split(':')[1].strip() - try: - minimum = int(error.strip().split()[-3].replace('`', '')) - maximum = int(error.strip().split()[-1].replace('`', '')) - current = int(error.strip().split()[-7].split('`')[3]) - except: - self._shift += random.randrange(-10, 10) - else: - if current < maximum: - newshift = (minimum + 2 * maximum) / 3 - current - if newshift < 0: newshift = 10 + self._shift += random.randrange(-10, 10) + + def post(self, method, params, key, secret): + request = {'nonce': self.nonce()} # TODO: check for unique nonce + request.update(params) + data = urllib.urlencode(request) + sign = hmac.new(secret, data, hashlib.sha512).hexdigest() + headers = {"Content-type": "application/x-www-form-urlencoded", "Key": key, "Sign": sign} + url = 'https://www.ccedk.com/api/v1/' + method + response = json.loads(urllib2.urlopen(urllib2.Request(url, data, headers), timeout=15).read()) + if response['errors'] is True: + response['error'] = ",".join(response['errors'].values()) + return response + + def cancel_orders(self, unit, side, key, secret): + response = self.post('order/list', {}, key, secret) + if not response['response'] or not response['response']['entities']: + return response + for order in response['response']['entities']: + if side == 'all' \ + or (side == 'bid' and order['type'] == 'buy') \ + or (side == 'ask' and order['type'] == 'sell'): + if order['pair_id'] == self.pair_id[unit.lower()]: + ret = self.post('order/cancel', {'order_id': order['order_id']}, key, secret) + if ret['errors'] is True: + if 'error' not in response: + response['error'] = "" + response['error'] += ",".join(ret['errors'].values()) + return response + + def place_order(self, unit, side, key, secret, amount, price): + params = {"type": 'buy' if side == 'bid' else 'sell', + "price": price, + "pair_id": int(self.pair_id[unit.lower()]), + "amount": amount} + response = self.post('order/new', params, key, secret) + print response + if response['errors'] is True: + response['error'] = ",".join(response['errors'].values()) else: - newshift = (2 * minimum + maximum) / 3 - current - if newshift != 0: - self._shift += newshift + response['id'] = int(response['response']['entity']['order_id']) + return response + + def get_balance(self, unit, key, secret): + params = {"currency_id": self.currency_id[unit.lower()]} + response = self.post('balance/info', params, key, secret) + if response['errors'] is True: + response['error'] = ",".join(response['errors'].values()) else: - self._shift += random.randrange(-10, 10) - else: - self._shift += random.randrange(-10, 10) - - def post(self, method, params, key, secret): - request = { 'nonce' : self.nonce() } # TODO: check for unique nonce - request.update(params) - data = urllib.urlencode(request) - sign = hmac.new(secret, data, hashlib.sha512).hexdigest() - headers = {"Content-type": "application/x-www-form-urlencoded", "Key": key, "Sign": sign} - response = json.loads(urllib2.urlopen(urllib2.Request('https://www.ccedk.com/api/v1/' + method, data, headers)).read()) - if not response['response']: - response['error'] = ",".join(response['errors'].values()) - return response - - def cancel_orders(self, unit, side, key, secret): - response = self.post('order/list', {}, key, secret) - if not response['response'] or not response['response']['entities']: - return response - for order in response['response']['entities']: - if side == 'all' or (side == 'bid' and order['type'] == 'buy') or (side == 'ask' and order['type'] == 'sell'): - if order['pair_id'] == self.pair_id[unit.upper()]: - ret = self.post('order/cancel', { 'order_id' : order['order_id'] }, key, secret) - if not ret['response']: - if not 'error' in response: response['error'] = "" - response['error'] += ",".join(ret['errors'].values()) - return response - - def place_order(self, unit, side, key, secret, amount, price): - params = { "type" : 'buy' if side == 'bid' else 'sell', - "price" : price, - "pair_id" : int(self.pair_id[unit.upper()]), - "volume" : amount } - response = self.post('order/new', params, key, secret) - if not 'error' in response: - response['id'] = int(response['response']['entity']['order_id']) - return response - - def get_balance(self, unit, key, secret): - params = { "currency_id" : self.currency_id[unit.lower()] } - response = self.post('balance/info', params, key, secret) - if not 'error' in response: - response['balance'] = float(response['response']['entity']['balance']) - return response - - def get_price(self, unit): - response = json.loads(urllib2.urlopen(urllib2.Request( - 'https://www.ccedk.com/api/v1/orderbook/info?' + urllib.urlencode({ 'nonce' : self.nonce(), 'pair_id' : self.pair_id[unit.upper()] })), timeout = 5).read()) - if not response['response']: - response['error'] = ",".join(response['errors'].values()) - return response - response.update({'bid': None, 'ask': None}) - if response['response']['entity']['bids']: response['bid'] = float(response['response']['entity']['bids'][0]['price']) - if response['response']['entity']['asks']: response['ask'] = float(response['response']['entity']['asks'][0]['price']) - return response - - def create_request(self, unit, key = None, secret = None): - if not secret: return None, None - request = { 'nonce' : self.nonce() } - data = urllib.urlencode(request) - sign = hmac.new(secret, data, hashlib.sha512).hexdigest() - return request, sign - - def validate_request(self, key, unit, data, sign): - headers = {"Content-type": "application/x-www-form-urlencoded", "Key": key, "Sign": sign} - response = json.loads(urllib2.urlopen(urllib2.Request('https://www.ccedk.com/api/v1/order/list', urllib.urlencode(data), headers), timeout = 5).read()) - if not response['response']: - response['error'] = ",".join(response['errors'].values()) - return response - if not response['response']['entities']: - response['response']['entities'] = [] - return [ { - 'id' : int(order['order_id']), - 'price' : float(order['price']), - 'type' : 'ask' if order['type'] == 'sell' else 'bid', - 'amount' : float(order['volume']), - } for order in response['response']['entities'] if order['pair_id'] == self.pair_id[unit.upper()]] + response['balance'] = float(response['response']['entity']['balance']) + return response + + def get_price(self, unit): + url = 'https://www.ccedk.com/api/v1/orderbook/info?' + urllib.urlencode({'pair_id': self.pair_id[unit.lower()]}) + response = json.loads(urllib2.urlopen(urllib2.Request(url), timeout=5).read()) + if response['errors'] is True: + response['error'] = ",".join(response['errors'].values()) + return response + response.update({'bid': None, 'ask': None}) + if response['response']['entities']['bids']: + response['bid'] = float(response['response']['entities']['bids'][0]['price']) + if response['response']['entities']['asks']: + response['ask'] = float(response['response']['entities']['asks'][0]['price']) + return response + + def create_request(self, unit, key=None, secret=None): + if not secret: + return None, None + request = {'nonce': self.nonce()} + data = urllib.urlencode(request) + sign = hmac.new(secret, data, hashlib.sha512).hexdigest() + return request, sign + + def validate_request(self, key, unit, data, sign): + headers = {"Content-type": "application/x-www-form-urlencoded", "Key": key, "Sign": sign} + url = 'https://www.ccedk.com/api/v1/order/list' + response = json.loads(urllib2.urlopen(urllib2.Request(url, urllib.urlencode(data), headers), timeout=5).read()) + if response['errors'] is True: + response['error'] = ",".join(response['errors'].values()) + return response + if not response['response']['entities']: + response['response']['entities'] = [] + return [{ + 'id': int(order['order_id']), + 'price': float(order['price']), + 'type': 'ask' if order['type'] == 'sell' else 'bid', + 'amount': float(order['volume']), + } for order in response['response']['entities'] if order['pair_id'] == self.pair_id[unit.lower()]] class BitcoinCoId(Exchange): - def __init__(self): - super(BitcoinCoId, self).__init__(0.0) - try: - ping = time.time() - response = json.loads(urllib2.urlopen(urllib2.Request('https://vip.bitcoin.co.id/api/summaries')).read()) - self._shift = float(response['tickers']['btc_idr']['server_time']) - ping - except: - pass - - def __repr__(self): return "bitcoincoid" - - def adjust(self, error): - if "Nonce must be greater than" in error: # (TODO: regex) - if ':' in error: error = error.split(':')[1].strip() - error = error.replace('.', '').split() - self._shift += 100.0 + (int(error[5]) - int(error[8])) / 1000.0 - else: - self._shift = self._shift + 100.0 - - def nonce(self, factor = 1000.0): - n = int((time.time() + self._shift) * float(factor)) - if n - self._nonce < 300: - n = self._nonce + 300 - self._nonce = n - return n - - def post(self, method, params, key, secret): - request = { 'nonce' : self.nonce(), 'method' : method } - request.update(params) - data = urllib.urlencode(request) - sign = hmac.new(secret, data, hashlib.sha512).hexdigest() - headers = { 'Sign' : sign, 'Key' : key } - response = json.loads(urllib2.urlopen(urllib2.Request('https://vip.bitcoin.co.id/tapi', data, headers)).read()) - return response - - def cancel_orders(self, unit, side, key, secret): - response = self.post('openOrders', {'pair' : 'nbt_' + unit.lower()}, key, secret) - if response['success'] == 0 or not response['return']['orders']: return response - for order in response['return']['orders']: - if side == 'all' or (side == 'bid' and order['type'] == 'buy') or (side == 'ask' and order['type'] == 'sell'): - params = { 'pair' : 'nbt_' + unit.lower(), 'order_id' : order['order_id'], 'type' : order['type'] } - ret = self.post('cancelOrder', params, key, secret) - if 'error' in ret: - if not 'error' in response: response['error'] = "" - response['error'] += "," + ret['error'] - return response - - def place_order(self, unit, side, key, secret, amount, price): - params = { 'pair' : 'nbt_' + unit.lower(), 'type' : 'buy' if side == 'bid' else 'sell', 'price' : price } - if side == 'bid': - params[unit.lower()] = amount * price - else: - params['nbt'] = amount - params[unit] = amount * price - response = self.post('trade', params, key, secret) - if response['success'] == 1: - response['id'] = int(response['return']['order_id']) - return response - - def get_balance(self, unit, key, secret): - response = self.post('getInfo', {}, key, secret) - if response['success'] == 1: - response['balance'] = float(response['return']['balance'][unit.lower()]) - return response - - def get_price(self, unit): - response = json.loads(urllib2.urlopen(urllib2.Request('https://vip.bitcoin.co.id/api/nbt_%s/depth' % unit.lower()), timeout = 5).read()) - if 'error' in response: - return response - response.update({'bid': None, 'ask': None}) - if response['buy']: response['bid'] = float(response['buy'][0][0]) - if response['sell']: response['ask'] = float(response['sell'][0][0]) - return response - - def create_request(self, unit, key = None, secret = None): - if not secret: return None, None - request = { 'nonce' : self.nonce(), 'pair' : 'nbt_' + unit.lower(), 'method' : 'openOrders' } - data = urllib.urlencode(request) - sign = hmac.new(secret, data, hashlib.sha512).hexdigest() - return request, sign - - def validate_request(self, key, unit, data, sign): - headers = {"Key": key, "Sign": sign} - response = json.loads(urllib2.urlopen(urllib2.Request('https://vip.bitcoin.co.id/tapi', urllib.urlencode(data), headers), timeout = 5).read()) - if response['success'] == 0: - return response - if not response['return']['orders']: - response['return']['orders'] = [] - return [ { - 'id' : int(order['order_id']), - 'price' : float(order['price']), - 'type' : 'ask' if order['type'] == 'sell' else 'bid', - 'amount' : float(order['remain_' + (unit.lower() if order['type'] == 'buy' else 'nbt')]) / (float(order['price']) if order['type'] == 'buy' else 1.0), - } for order in response['return']['orders']] + def __init__(self): + super(BitcoinCoId, self).__init__(0.0) + try: + ping = time.time() + response = json.loads(urllib2.urlopen(urllib2.Request('https://vip.bitcoin.co.id/api/summaries')).read()) + self._shift = float(response['tickers']['btc_idr']['server_time']) - ping + except: + pass + + def __repr__(self): + return "bitcoincoid" + + def adjust(self, error): + if "Nonce must be greater than" in error: # (TODO: regex) + if ':' in error: error = error.split(':')[1].strip() + error = error.replace('.', '').split() + self._shift += 100.0 + (int(error[5]) - int(error[8])) / 1000.0 + else: + self._shift = self._shift + 100.0 + + def nonce(self, factor=1000.0): + n = int((time.time() + self._shift) * float(factor)) + if n - self._nonce < 300: + n = self._nonce + 300 + self._nonce = n + return n + + def post(self, method, params, key, secret): + request = {'nonce': self.nonce(), 'method': method} + request.update(params) + data = urllib.urlencode(request) + sign = hmac.new(secret, data, hashlib.sha512).hexdigest() + headers = {'Sign': sign, 'Key': key} + response = json.loads(urllib2.urlopen(urllib2.Request('https://vip.bitcoin.co.id/tapi', data, headers)).read()) + return response + + def cancel_orders(self, unit, side, key, secret): + response = self.post('openOrders', {'pair': 'nbt_' + unit.lower()}, key, secret) + if response['success'] == 0 or not response['return']['orders']: return response + for order in response['return']['orders']: + if side == 'all' or (side == 'bid' and order['type'] == 'buy') or ( + side == 'ask' and order['type'] == 'sell'): + params = {'pair': 'nbt_' + unit.lower(), 'order_id': order['order_id'], 'type': order['type']} + ret = self.post('cancelOrder', params, key, secret) + if 'error' in ret: + if not 'error' in response: response['error'] = "" + response['error'] += "," + ret['error'] + return response + + def place_order(self, unit, side, key, secret, amount, price): + params = {'pair': 'nbt_' + unit.lower(), 'type': 'buy' if side == 'bid' else 'sell', 'price': price} + if side == 'bid': + params[unit.lower()] = amount * price + else: + params['nbt'] = amount + params[unit] = amount * price + response = self.post('trade', params, key, secret) + if response['success'] == 1: + response['id'] = int(response['return']['order_id']) + return response + + def get_balance(self, unit, key, secret): + response = self.post('getInfo', {}, key, secret) + if response['success'] == 1: + response['balance'] = float(response['return']['balance'][unit.lower()]) + return response + + def get_price(self, unit): + response = json.loads( + urllib2.urlopen(urllib2.Request('https://vip.bitcoin.co.id/api/nbt_%s/depth' % unit.lower()), + timeout=5).read()) + if 'error' in response: + return response + response.update({'bid': None, 'ask': None}) + if response['buy']: response['bid'] = float(response['buy'][0][0]) + if response['sell']: response['ask'] = float(response['sell'][0][0]) + return response + + def create_request(self, unit, key=None, secret=None): + if not secret: return None, None + request = {'nonce': self.nonce(), 'pair': 'nbt_' + unit.lower(), 'method': 'openOrders'} + data = urllib.urlencode(request) + sign = hmac.new(secret, data, hashlib.sha512).hexdigest() + return request, sign + + def validate_request(self, key, unit, data, sign): + headers = {"Key": key, "Sign": sign} + response = json.loads( + urllib2.urlopen(urllib2.Request('https://vip.bitcoin.co.id/tapi', urllib.urlencode(data), headers), + timeout=5).read()) + if response['success'] == 0: + return response + if not response['return']['orders']: + response['return']['orders'] = [] + return [{ + 'id': int(order['order_id']), + 'price': float(order['price']), + 'type': 'ask' if order['type'] == 'sell' else 'bid', + 'amount': float(order['remain_' + (unit.lower() if order['type'] == 'buy' else 'nbt')]) / ( + float(order['price']) if order['type'] == 'buy' else 1.0), + } for order in response['return']['orders']] class BTER(Exchange): - def __init__(self): - super(BTER, self).__init__(0.002) - - def __repr__(self): return "bter" - - def adjust(self, error): - pass - - def https_request(self, method, params, headers = None, timeout = None): - if not headers: headers = {} - connection = httplib.HTTPSConnection('data.bter.com', timeout = timeout) - connection.request('POST', '/api/1/private/' + method, params, headers) - response = connection.getresponse().read() - return json.loads(response) - - def post(self, method, params, key, secret): - data = urllib.urlencode(params) - sign = hmac.new(secret, data, hashlib.sha512).hexdigest() - headers = { 'Sign' : sign, 'Key' : key, "Content-type": "application/x-www-form-urlencoded" } - return self.https_request(method, data, headers) - - def cancel_orders(self, unit, side, key, secret): - response = self.post('orderlist', {}, key, secret) - if not response['result']: - response['error'] = response['msg'] - return response - if not response['orders']: response['orders'] = [] - for order in response['orders']: - if side == 'all' or (side == 'ask' and order['sell_type'] != unit) or (side == 'bid' and order['buy_type'] != unit): - if order['pair'] == 'nbt_' + unit.lower(): - params = { 'order_id' : order['oid'] } - ret = self.post('cancelorder', params, key, secret) - if not ret['result']: - if not 'error' in response: response['error'] = "" - response['error'] += "," + ret['msg'] - return response - - def place_order(self, unit, side, key, secret, amount, price): - params = { 'pair' : 'nbt_' + unit.lower(), 'type' : 'buy' if side == 'bid' else 'sell', 'rate' : price, 'amount' : amount } - response = self.post('placeorder', params, key, secret) - if response['result']: - response['id'] = int(response['order_id']) - else: - response['error'] = response['msg'] - return response - - def get_balance(self, unit, key, secret): - response = self.post('getfunds', {}, key, secret) - if response['result']: - if unit.upper() in response['available_funds']: - response['balance'] = float(response['available_funds'][unit.upper()]) - else: - response['balance'] = 0.0 - else: response['error'] = response['msg'] - return response - - def get_price(self, unit): - connection = httplib.HTTPSConnection('data.bter.com', timeout = 5) - connection.request('GET', '/api/1/depth/nbt_' + unit.lower()) - response = json.loads(connection.getresponse().read()) - if not 'result' in response or not response['result']: - response['error'] = response['msg'] if 'msg' in response else 'invalid response: %s' % str(response) - return response - response.update({'bid': None, 'ask': None}) - if response['bids']: response['bid'] = float(response['bids'][0][0]) - if response['asks']: response['ask'] = float(response['asks'][-1][0]) - return response - - def create_request(self, unit, key = None, secret = None): - if not secret: return None, None - request = {} # no nonce required - data = urllib.urlencode(request) - sign = hmac.new(secret, data, hashlib.sha512).hexdigest() - return request, sign - - def validate_request(self, key, unit, data, sign): - headers = { 'Sign' : sign, 'Key' : key, "Content-type": "application/x-www-form-urlencoded" } - response = self.https_request('orderlist', urllib.urlencode(data), headers, timeout = 15) - if not 'result' in response or not response['result']: - response['error'] = response['msg'] if 'msg' in response else 'invalid response: %s' % str(response) - return response - if not response['orders']: - response['orders'] = [] - return [ { - 'id' : int(order['oid']), - 'price' : float(order['rate']), - 'type' : 'ask' if order['buy_type'].lower() == unit.lower() else 'bid', - 'amount' : float(order['amount']) / (1.0 if order['buy_type'].lower() == unit.lower() else float(order['rate'])), - } for order in response['orders'] if order['pair'] == 'nbt_' + unit.lower() ] + def __init__(self): + super(BTER, self).__init__(0.002) + def __repr__(self): + return "bter" -class Peatio(Exchange): - def __init__(self): - super(Peatio, self).__init__(0.002) - - def __repr__(self): return "testing" - - def adjust(self, error): - if "is invalid, current timestamp is" in error: - try: - tonce = int(error.split()[2]) - times = int(error.split()[-1].replace('.', '')) - self._shift = int(float(times - tonce)/1000.0) - except: - print error + def adjust(self, error): pass - else: print error - - def urlencode(self, params): # from https://github.com/JohnnyZhao/peatio-client-python/blob/master/lib/auth.py#L11 - keys = sorted(params.keys()) - query = '' - for key in keys: - value = params[key] - if key != "orders": - query = "%s&%s=%s" % (query, key, value) if len(query) else "%s=%s" % (key, value) - else: - d = {key: params[key]} - for v in value: - ks = v.keys() - ks.sort() - for k in ks: - item = "orders[][%s]=%s" % (k, v[k]) - query = "%s&%s" % (query, item) if len(query) else "%s" % item - return query - - def query(self, qtype, method, params, key, secret): - request = { 'tonce' : self.nonce(), 'access_key' : key } - request.update(params) - data = self.urlencode(request) - msg = "%s|/api/v2/%s|%s" % (qtype, method, data) - data += "&signature=" + hmac.new(secret, msg, hashlib.sha256).hexdigest() - connection = httplib.HTTPSConnection('178.62.140.24', timeout = 5) - connection.request(qtype, '/api/v2/' + method + '?' + data) - return json.loads(connection.getresponse().read()) - - def post(self, method, params, key, secret): - return self.query('POST', method, params, key, secret) - - def get(self, method, params, key, secret): - return self.query('GET', method, params, key, secret) - - def cancel_orders(self, unit, side, key, secret): - response = self.get('orders.json', { 'market' : "nbt%s"%unit.lower() }, key, secret) - if 'error' in response: - response['error'] = response['error']['message'] - return response - for order in response: - if side == 'all' or (side == 'bid' and order['side'] == 'buy') or (side == 'ask' and order['side'] == 'sell'): - ret = self.post('order/delete.json', { 'id' : order['id'] }, key, secret) - if 'error' in ret: - if isinstance(response,list): response = { 'error': "" } - response['error'] += "," + ret['error']['message'] - return response - - def place_order(self, unit, side, key, secret, amount, price): - params = { 'market' : "nbt%s"%unit.lower(), "side" : 'buy' if side == 'bid' else 'sell', "volume" : amount, "price" : price } - response = self.post('orders', params, key, secret) - if 'error' in response: - response['error'] = response['error']['message'] - else: - response['id'] = int(response['id']) - return response - - def get_balance(self, unit, key, secret): - response = self.get('members/me.json', {}, key, secret) - if 'error' in response: - response['error'] = response['error']['message'] - else: - response['balance'] = 0.0 - for pair in response['accounts']: - if pair['currency'] == unit.lower(): - response['balance'] = float(pair['balance']) - return response - - def get_price(self, unit): - connection = httplib.HTTPSConnection('178.62.140.24', timeout = 15) - connection.request('GET', '/api/v2/depth.json?' + self.urlencode({'market' : "nbt%s"%unit.lower(), 'limit' : 1})) - response = json.loads(connection.getresponse().read()) - if 'error' in response: - response['error'] = response['error']['message'] - return response - response.update({'bid': None, 'ask': None}) - if response['bids']: response['bid'] = float(response['bids'][0][0]) - if response['asks']: response['ask'] = float(response['asks'][-1][0]) - return response - - def create_request(self, unit, key = None, secret = None): - if not secret: return None, None - request = { 'tonce' : self.nonce(), 'access_key' : key, 'market' : "nbt%s"%unit.lower() } - data = self.urlencode(request) - msg = "GET|/api/v2/orders.json|%s" % data - request['signature'] = hmac.new(secret, msg, hashlib.sha256).hexdigest() - return request, '' - - def validate_request(self, key, unit, data, sign): - if not 'market' in data or data['market'] != "nbt%s"%unit.lower(): - return { 'error' : 'invalid market' } - connection = httplib.HTTPSConnection('178.62.140.24', timeout = 15) - connection.request('GET', '/api/v2/orders.json?' + self.urlencode(data)) - response = json.loads(connection.getresponse().read()) - if 'error' in response: - response['error'] = response['error']['message'] - return response - return [ { - 'id' : int(order['id']), - 'price' : float(order['price']), - 'type' : 'ask' if order['side'] == 'sell' else 'bid', - 'amount' : float(order['remaining_volume']), - } for order in response ] \ No newline at end of file + + def https_request(self, method, params, headers=None, timeout=None): + if not headers: headers = {} + connection = httplib.HTTPSConnection('data.bter.com', timeout=timeout) + connection.request('POST', '/api/1/private/' + method, params, headers) + response = connection.getresponse().read() + return json.loads(response) + + def post(self, method, params, key, secret): + data = urllib.urlencode(params) + sign = hmac.new(secret, data, hashlib.sha512).hexdigest() + headers = {'Sign': sign, 'Key': key, "Content-type": "application/x-www-form-urlencoded"} + return self.https_request(method, data, headers) + + def cancel_orders(self, unit, side, key, secret): + response = self.post('orderlist', {}, key, secret) + if not response['result']: + response['error'] = response['msg'] + return response + if not response['orders']: response['orders'] = [] + for order in response['orders']: + if side == 'all' or (side == 'ask' and order['sell_type'] != unit) or ( + side == 'bid' and order['buy_type'] != unit): + if order['pair'] == 'nbt_' + unit.lower(): + params = {'order_id': order['oid']} + ret = self.post('cancelorder', params, key, secret) + if not ret['result']: + if not 'error' in response: response['error'] = "" + response['error'] += "," + ret['msg'] + return response + + def place_order(self, unit, side, key, secret, amount, price): + params = {'pair': 'nbt_' + unit.lower(), 'type': 'buy' if side == 'bid' else 'sell', 'rate': price, + 'amount': amount} + response = self.post('placeorder', params, key, secret) + if response['result']: + response['id'] = int(response['order_id']) + else: + response['error'] = response['msg'] + return response + + def get_balance(self, unit, key, secret): + response = self.post('getfunds', {}, key, secret) + if response['result']: + if unit.upper() in response['available_funds']: + response['balance'] = float(response['available_funds'][unit.upper()]) + else: + response['balance'] = 0.0 + else: + response['error'] = response['msg'] + return response + + def get_price(self, unit): + connection = httplib.HTTPSConnection('data.bter.com', timeout=5) + connection.request('GET', '/api/1/depth/nbt_' + unit.lower()) + response = json.loads(connection.getresponse().read()) + if not 'result' in response or not response['result']: + response['error'] = response['msg'] if 'msg' in response else 'invalid response: %s' % str(response) + return response + response.update({'bid': None, 'ask': None}) + if response['bids']: response['bid'] = float(response['bids'][0][0]) + if response['asks']: response['ask'] = float(response['asks'][-1][0]) + return response + + def create_request(self, unit, key=None, secret=None): + if not secret: return None, None + request = {} # no nonce required + data = urllib.urlencode(request) + sign = hmac.new(secret, data, hashlib.sha512).hexdigest() + return request, sign + + def validate_request(self, key, unit, data, sign): + headers = {'Sign': sign, 'Key': key, "Content-type": "application/x-www-form-urlencoded"} + response = self.https_request('orderlist', urllib.urlencode(data), headers, timeout=15) + if not 'result' in response or not response['result']: + response['error'] = response['msg'] if 'msg' in response else 'invalid response: %s' % str(response) + return response + if not response['orders']: + response['orders'] = [] + return [{ + 'id': int(order['oid']), + 'price': float(order['rate']), + 'type': 'ask' if order['buy_type'].lower() == unit.lower() else 'bid', + 'amount': float(order['amount']) / ( + 1.0 if order['buy_type'].lower() == unit.lower() else float(order['rate'])), + } for order in response['orders'] if order['pair'] == 'nbt_' + unit.lower()] + + +class Peatio(Exchange): + def __init__(self): + super(Peatio, self).__init__(0.002) + + def __repr__(self): + return "testing" + + def adjust(self, error): + if "is invalid, current timestamp is" in error: + try: + tonce = int(error.split()[2]) + times = int(error.split()[-1].replace('.', '')) + self._shift = int(float(times - tonce) / 1000.0) + except: + print error + pass + else: + print error + + def urlencode(self, params): # from https://github.com/JohnnyZhao/peatio-client-python/blob/master/lib/auth.py#L11 + keys = sorted(params.keys()) + query = '' + for key in keys: + value = params[key] + if key != "orders": + query = "%s&%s=%s" % (query, key, value) if len(query) else "%s=%s" % (key, value) + else: + d = {key: params[key]} + for v in value: + ks = v.keys() + ks.sort() + for k in ks: + item = "orders[][%s]=%s" % (k, v[k]) + query = "%s&%s" % (query, item) if len(query) else "%s" % item + return query + + def query(self, qtype, method, params, key, secret): + request = {'tonce': self.nonce(), 'access_key': key} + request.update(params) + data = self.urlencode(request) + msg = "%s|/api/v2/%s|%s" % (qtype, method, data) + data += "&signature=" + hmac.new(secret, msg, hashlib.sha256).hexdigest() + connection = httplib.HTTPSConnection('178.62.140.24', timeout=5) + connection.request(qtype, '/api/v2/' + method + '?' + data) + return json.loads(connection.getresponse().read()) + + def post(self, method, params, key, secret): + return self.query('POST', method, params, key, secret) + + def get(self, method, params, key, secret): + return self.query('GET', method, params, key, secret) + + def cancel_orders(self, unit, side, key, secret): + response = self.get('orders.json', {'market': "nbt%s" % unit.lower()}, key, secret) + if 'error' in response: + response['error'] = response['error']['message'] + return response + for order in response: + if side == 'all' or (side == 'bid' and order['side'] == 'buy') or ( + side == 'ask' and order['side'] == 'sell'): + ret = self.post('order/delete.json', {'id': order['id']}, key, secret) + if 'error' in ret: + if isinstance(response, list): response = {'error': ""} + response['error'] += "," + ret['error']['message'] + return response + + def place_order(self, unit, side, key, secret, amount, price): + params = {'market': "nbt%s" % unit.lower(), "side": 'buy' if side == 'bid' else 'sell', "volume": amount, + "price": price} + response = self.post('orders', params, key, secret) + if 'error' in response: + response['error'] = response['error']['message'] + else: + response['id'] = int(response['id']) + return response + + def get_balance(self, unit, key, secret): + response = self.get('members/me.json', {}, key, secret) + if 'error' in response: + response['error'] = response['error']['message'] + else: + response['balance'] = 0.0 + for pair in response['accounts']: + if pair['currency'] == unit.lower(): + response['balance'] = float(pair['balance']) + return response + + def get_price(self, unit): + connection = httplib.HTTPSConnection('178.62.140.24', timeout=15) + connection.request('GET', + '/api/v2/depth.json?' + self.urlencode({'market': "nbt%s" % unit.lower(), 'limit': 1})) + response = json.loads(connection.getresponse().read()) + if 'error' in response: + response['error'] = response['error']['message'] + return response + response.update({'bid': None, 'ask': None}) + if response['bids']: response['bid'] = float(response['bids'][0][0]) + if response['asks']: response['ask'] = float(response['asks'][-1][0]) + return response + + def create_request(self, unit, key=None, secret=None): + if not secret: return None, None + request = {'tonce': self.nonce(), 'access_key': key, 'market': "nbt%s" % unit.lower()} + data = self.urlencode(request) + msg = "GET|/api/v2/orders.json|%s" % data + request['signature'] = hmac.new(secret, msg, hashlib.sha256).hexdigest() + return request, '' + + def validate_request(self, key, unit, data, sign): + if not 'market' in data or data['market'] != "nbt%s" % unit.lower(): + return {'error': 'invalid market'} + connection = httplib.HTTPSConnection('178.62.140.24', timeout=15) + connection.request('GET', '/api/v2/orders.json?' + self.urlencode(data)) + response = json.loads(connection.getresponse().read()) + if 'error' in response: + response['error'] = response['error']['message'] + return response + return [{ + 'id': int(order['id']), + 'price': float(order['price']), + 'type': 'ask' if order['side'] == 'sell' else 'bid', + 'amount': float(order['remaining_volume']), + } for order in response] \ No newline at end of file diff --git a/python/server.py b/python/server.py index 039bfd5..7261659 100755 --- a/python/server.py +++ b/python/server.py @@ -26,18 +26,14 @@ import SocketServer import BaseHTTPServer import cgi -import logging -import urllib -import sys, os -import time -import thread -import threading -import sys -from math import log, exp, ceil +import os +from math import ceil from thread import start_new_thread + from exchanges import * from utils import * -import config +from python import config + _wrappers = { 'bittrex' : Bittrex, 'poloniex' : Poloniex, 'ccedk' : CCEDK, 'bitcoincoid' : BitcoinCoId, 'bter' : BTER, 'testing' : Peatio } for e in config._interest: diff --git a/python/trading.py b/python/trading.py index e65e45d..5ff3bd8 100644 --- a/python/trading.py +++ b/python/trading.py @@ -38,307 +38,345 @@ class NuBot(ConnectionThread): - def __init__(self, conn, requester, key, secret, exchange, unit, target, logger = None, ordermatch = False): - super(NuBot, self).__init__(conn, logger) - self.requester = requester - self.process = None - self.unit = unit - self.running = False - self.exchange = exchange - self.options = { - 'exchangename' : repr(exchange), - 'apikey' : key, - 'apisecret' : secret, - 'txfee' : 0.2, - 'pair' : 'nbt_' + unit, - 'submit-liquidity' : False, - 'dualside' : True, - 'multiple-custodians' : True, - 'executeorders' : ordermatch, - 'mail-notifications' : False, - 'hipchat' : False - } - if unit != 'usd': - if unit == 'btc': - self.options['secondary-peg-options'] = { - 'main-feed' : 'bitfinex', - 'backup-feeds' : { - 'backup1' : { 'name' : 'blockchain' }, - 'backup2' : { 'name' : 'coinbase' }, - 'backup3' : { 'name' : 'bitstamp' } - } } - else: - self.logger.error('no price feed available for %s', unit) - self.options['secondary-peg-options']['wallshift-threshold'] = 0.3 - self.options['secondary-peg-options']['spread'] = 0.0 + def __init__(self, conn, requester, key, secret, exchange, unit, target, logger=None, ordermatch=False): + super(NuBot, self).__init__(conn, logger) + self.requester = requester + self.process = None + self.unit = unit + self.running = False + self.exchange = exchange + self.options = { + 'exchangename': repr(exchange), + 'apikey': key, + 'apisecret': secret, + 'txfee': 0.2, + 'pair': 'nbt_' + unit, + 'submit-liquidity': False, + 'dualside': True, + 'multiple-custodians': True, + 'executeorders': ordermatch, + 'mail-notifications': False, + 'hipchat': False + } + if unit != 'usd': + if unit == 'btc': + self.options['secondary-peg-options'] = { + 'main-feed': 'bitfinex', + 'backup-feeds': { + 'backup1': {'name': 'blockchain'}, + 'backup2': {'name': 'coinbase'}, + 'backup3': {'name': 'bitstamp'} + }} + else: + self.logger.error('no price feed available for %s', unit) + self.options['secondary-peg-options']['wallshift-threshold'] = 0.3 + self.options['secondary-peg-options']['spread'] = 0.0 - def run(self): - out = tempfile.NamedTemporaryFile(delete = False) - out.write(json.dumps({ 'options' : self.options })) - out.close() - while self.active: - if self.requester.errorflag: + def run(self): + out = tempfile.NamedTemporaryFile(delete=False) + out.write(json.dumps({'options': self.options})) + out.close() + while self.active: + if self.requester.errorflag: + self.shutdown() + elif not self.process: + with open(os.devnull, 'w') as fp: + self.logger.info("starting NuBot for %s on %s", self.unit, repr(self.exchange)) + self.process = subprocess.Popen("java -jar NuBot.jar %s" % out.name, + stdout=fp, stderr=fp, shell=True, cwd='nubot') + time.sleep(10) self.shutdown() - elif not self.process: - with open(os.devnull, 'w') as fp: - self.logger.info("starting NuBot for %s on %s", self.unit, repr(self.exchange)) - self.process = subprocess.Popen("java -jar NuBot.jar %s" % out.name, - stdout=fp, stderr=fp, shell=True, cwd = 'nubot') - time.sleep(10) - self.shutdown() - def shutdown(self): - if self.process: - self.logger.info("stopping NuBot for unit %s on %s", self.unit, repr(self.exchange)) - self.process.terminate() - #os.killpg(self.process.pid, signal.SIGTERM) - self.process = None + def shutdown(self): + if self.process: + self.logger.info("stopping NuBot for unit %s on %s", self.unit, repr(self.exchange)) + self.process.terminate() + # os.killpg(self.process.pid, signal.SIGTERM) + self.process = None class PyBot(ConnectionThread): - def __init__(self, conn, requester, key, secret, exchange, unit, target, logger = None, ordermatch = False): - super(PyBot, self).__init__(conn, logger) - self.requester = requester - self.ordermatch = ordermatch - self.key = key - self.secret = secret - self.exchange = exchange - self.unit = unit - self.orders = [] - self.target = target.copy() - self.total = target.copy() - self.limit = target.copy() - self.lastlimit = { 'bid' : 0, 'ask' : 0 } - if not hasattr(PyBot, 'lock'): - PyBot.lock = {} - if not repr(exchange) in PyBot.lock: - PyBot.lock[repr(exchange)] = threading.Lock() - if not hasattr(PyBot, 'pricefeed'): - PyBot.pricefeed = PriceFeed(30, logger) + def __init__(self, conn, requester, key, secret, exchange, unit, target, logger=None, ordermatch=False): + super(PyBot, self).__init__(conn, logger) + self.requester = requester + self.ordermatch = ordermatch + self.key = key + self.secret = secret + self.exchange = exchange + self.unit = unit + self.orders = [] + self.target = target.copy() + self.total = target.copy() + self.limit = target.copy() + self.lastlimit = {'bid': 0, 'ask': 0} + if not hasattr(PyBot, 'lock'): + PyBot.lock = {} + if not repr(exchange) in PyBot.lock: + PyBot.lock[repr(exchange)] = threading.Lock() + if not hasattr(PyBot, 'pricefeed'): + PyBot.pricefeed = PriceFeed(30, logger) - def cancel_orders(self, side = 'all', reset = True): - try: - response = self.exchange.cancel_orders(self.unit, side, self.key, self.secret) - except: - response = {'error' : 'exception caught: %s' % sys.exc_info()[1]} - if 'error' in response: - self.logger.error('unable to delete %s orders for %s on %s: %s', side, self.unit, repr(self.exchange), response['error']) - self.exchange.adjust(response['error']) - self.logger.info('adjusting nonce of %s to %d', repr(self.exchange), self.exchange._shift) - else: - self.logger.info('successfully deleted %s orders for %s on %s', side, self.unit, repr(self.exchange)) - if reset: - if side == 'all': - self.limit['bid'] = max(self.total['bid'], 0.5) - self.limit['ask'] = max(self.total['ask'], 0.5) + def cancel_orders(self, side='all', reset=True): + try: + response = self.exchange.cancel_orders(self.unit, side, self.key, self.secret) + except: + response = {'error': 'exception caught: %s' % sys.exc_info()[1]} + if 'error' in response: + self.logger.error('unable to delete %s orders for %s on %s: %s', side, self.unit, repr(self.exchange), + response['error']) + self.exchange.adjust(response['error']) + self.logger.info('adjusting nonce of %s to %d', repr(self.exchange), self.exchange._shift) else: - self.limit[side] = max(self.total[side], 0.5) - return response + self.logger.info('successfully deleted %s orders for %s on %s', side, self.unit, repr(self.exchange)) + if reset: + if side == 'all': + self.limit['bid'] = max(self.total['bid'], 0.5) + self.limit['ask'] = max(self.total['ask'], 0.5) + else: + self.limit[side] = max(self.total[side], 0.5) + return response + + def shutdown(self): + self.logger.info("stopping PyBot for %s on %s", self.unit, repr(self.exchange)) + trials = 0 + while trials < 10: + response = self.cancel_orders(reset=False) + if not 'error' in response: break + trials = trials + 1 - def shutdown(self): - self.logger.info("stopping PyBot for %s on %s", self.unit, repr(self.exchange)) - trials = 0 - while trials < 10: - response = self.cancel_orders(reset = False) - if not 'error' in response: break - trials = trials + 1 + def acquire_lock(self): + PyBot.lock[repr(self.exchange)].acquire() - def acquire_lock(self): - PyBot.lock[repr(self.exchange)].acquire() + def release_lock(self): + PyBot.lock[repr(self.exchange)].release() - def release_lock(self): - PyBot.lock[repr(self.exchange)].release() + def balance(self, exunit, price): + try: + response = self.exchange.get_balance(exunit, self.key, self.secret) + if not 'error' in response: + response['balance'] = response['balance'] if exunit == 'nbt' else response['balance'] / price + response['balance'] = int(response['balance'] * 10 ** 3) / float(10 ** 3) + except KeyboardInterrupt: + raise + except: + response = {'error': 'exception caught: %s' % sys.exc_info()[1]} + return response - def balance(self, exunit, price): - try: - response = self.exchange.get_balance(exunit, self.key, self.secret) - if not 'error' in response: - response['balance'] = response['balance'] if exunit == 'nbt' else response['balance'] / price - response['balance'] = int(response['balance'] * 10**3) / float(10**3) - except KeyboardInterrupt: raise - except: response = { 'error' : 'exception caught: %s' % sys.exc_info()[1] } - return response + def place(self, side, price): + exunit = 'nbt' if side == 'ask' else self.unit + response = self.balance(exunit, price) + if 'error' in response: + self.logger.error('unable to receive balance for %s on %s: %s', exunit, repr(self.exchange), + response['error']) + self.exchange.adjust(response['error']) + self.logger.info('adjusting nonce of %s to %d', repr(self.exchange), self.exchange._shift) + elif response['balance'] > 0.1: + amount = min(self.limit[side], response['balance']) + if amount >= 0.5: + try: + response = self.exchange.place_order(self.unit, side, self.key, self.secret, amount, price) + except KeyboardInterrupt: + raise + except: + response = {'error': 'exception caught: %s' % sys.exc_info()[1]} + if 'error' in response: + if 'residual' in response and response['residual'] > 0: + self.limit[side] += response['residual'] + else: + self.logger.error('unable to place %s %s order of %.4f nbt at %.8f on %s: %s', + side, self.unit, amount, price, repr(self.exchange), response['error']) + self.exchange.adjust(response['error']) + else: + self.logger.info('successfully placed %s %s order of %.4f nbt at %.8f on %s', + side, self.unit, amount, price, repr(self.exchange)) + self.orders.append(response['id']) + self.limit[side] -= amount + return response - def place(self, side, price): - exunit = 'nbt' if side == 'ask' else self.unit - response = self.balance(exunit, price) - if 'error' in response: - self.logger.error('unable to receive balance for %s on %s: %s', exunit, repr(self.exchange), response['error']) - self.exchange.adjust(response['error']) - self.logger.info('adjusting nonce of %s to %d', repr(self.exchange), self.exchange._shift) - elif response['balance'] > 0.1: - amount = min(self.limit[side], response['balance']) - if amount >= 0.5: + def place_orders(self): try: - response = self.exchange.place_order(self.unit, side, self.key, self.secret, amount, price) - except KeyboardInterrupt: raise - except: response = { 'error' : 'exception caught: %s' % sys.exc_info()[1] } + response = self.exchange.get_price(self.unit) + except: + response = {'error': 'exception caught: %s' % sys.exc_info()[1]} if 'error' in response: - if 'residual' in response and response['residual'] > 0: - self.limit[side] += response['residual'] - else: - self.logger.error('unable to place %s %s order of %.4f nbt at %.8f on %s: %s', - side, self.unit, amount, price, repr(self.exchange), response['error']) - self.exchange.adjust(response['error']) + self.logger.error('unable to retrieve order book for %s on %s: %s', self.unit, repr(self.exchange), + response['error']) else: - self.logger.info('successfully placed %s %s order of %.4f nbt at %.8f on %s', - side, self.unit, amount, price, repr(self.exchange)) - self.orders.append(response['id']) - self.limit[side] -= amount - return response - - def place_orders(self): - try: - response = self.exchange.get_price(self.unit) - except: - response = { 'error': 'exception caught: %s' % sys.exc_info()[1] } - if 'error' in response: - self.logger.error('unable to retrieve order book for %s on %s: %s', self.unit, repr(self.exchange), response['error']) - else: - spread = max(self.exchange.fee, 0.002) - bidprice = ceil(self.price * (1.0 - spread) * 10**8) / float(10**8) # truncate floating point precision after 8th position - askprice = ceil(self.price * (1.0 + spread) * 10**8) / float(10**8) - if response['ask'] == None or response['ask'] > bidprice: - self.place('bid', bidprice) - else: - if 1.0 - response['ask'] / bidprice < 0.00425 - spread: - devprice = ceil(bidprice * (1.0 - 0.0045 + spread) * 10**8) / float(10**8) - self.logger.debug('decreasing bid %s order at %.8f on %s to %.8f to avoid order match', self.unit, bidprice, repr(self.exchange), devprice) - self.place('bid', devprice) - elif self.lastlimit['bid'] != self.limit['bid']: - self.logger.error('unable to place bid %s order at %.8f on %s: matching order at %.8f detected', self.unit, bidprice, repr(self.exchange), response['ask']) - elif self.ordermatch: - self.logger.warning('matching ask %s order at %.8f on %s', self.unit, response['ask'], repr(self.exchange)) - self.place('bid', bidprice) - if response['bid'] == None or response['bid'] < askprice: - self.place('ask', askprice) - else: - if 1.0 - askprice / response['bid'] < 0.00425 - spread: - devprice = ceil(askprice * (1.0045 - spread) * 10**8) / float(10**8) - self.logger.debug('increasing ask %s order at %.8f on %s to %.8f to avoid order match', self.unit, askprice, repr(self.exchange), devprice) - self.place('ask', devprice) - elif self.lastlimit['ask'] != self.limit['ask']: - self.logger.error('unable to place ask %s order at %.8f on %s: matching order at %.8f detected', self.unit, askprice, repr(self.exchange), response['bid']) - elif self.ordermatch: - self.logger.warning('matching bid %s order at %.8f on %s', self.unit, response['bid'], repr(self.exchange)) - self.place('ask', askprice) - self.lastlimit = self.limit.copy() - self.requester.submit() - - def sync(self, trials = 3): - ts = int(time.time() * 1000.0) - response = self.conn.get('sync', trials = 1, timeout = 15) - if not 'error' in response: - delay = (response['sync'] - (response['time'] % response['sync'])) - (int(time.time() * 1000.0) - ts) / 2 - if delay <= 0: - self.logger.error("unable to synchronize time with server for %s on %s: time difference to small", self.unit, repr(self.exchange)) - if trials > 0: - return self.sync(trials - 1) - self.logger.info("waiting %.2f seconds to synchronize with other trading bots for %s on %s", delay / 1000.0, self.unit, repr(self.exchange)) - time.sleep(delay / 1000.0) - elif trials > 0: - self.logger.error("unable to synchronize time with server for %s on %s: %s", self.unit, repr(self.exchange), response['message']) - return self.sync(trials - 1) - else: - return False - return True + spread = max(self.exchange.fee, 0.002) + bidprice = ceil(self.price * (1.0 - spread) * 10 ** 8) / float( + 10 ** 8) # truncate floating point precision after 8th position + askprice = ceil(self.price * (1.0 + spread) * 10 ** 8) / float(10 ** 8) + if response['ask'] == None or response['ask'] > bidprice: + self.place('bid', bidprice) + else: + if 1.0 - response['ask'] / bidprice < 0.00425 - spread: + devprice = ceil(bidprice * (1.0 - 0.0045 + spread) * 10 ** 8) / float(10 ** 8) + self.logger.debug('decreasing bid %s order at %.8f on %s to %.8f to avoid order match', self.unit, + bidprice, repr(self.exchange), devprice) + self.place('bid', devprice) + elif self.lastlimit['bid'] != self.limit['bid']: + self.logger.error('unable to place bid %s order at %.8f on %s: matching order at %.8f detected', + self.unit, bidprice, repr(self.exchange), response['ask']) + elif self.ordermatch: + self.logger.warning('matching ask %s order at %.8f on %s', self.unit, response['ask'], + repr(self.exchange)) + self.place('bid', bidprice) + if response['bid'] == None or response['bid'] < askprice: + self.place('ask', askprice) + else: + if 1.0 - askprice / response['bid'] < 0.00425 - spread: + devprice = ceil(askprice * (1.0045 - spread) * 10 ** 8) / float(10 ** 8) + self.logger.debug('increasing ask %s order at %.8f on %s to %.8f to avoid order match', self.unit, + askprice, repr(self.exchange), devprice) + self.place('ask', devprice) + elif self.lastlimit['ask'] != self.limit['ask']: + self.logger.error('unable to place ask %s order at %.8f on %s: matching order at %.8f detected', + self.unit, askprice, repr(self.exchange), response['bid']) + elif self.ordermatch: + self.logger.warning('matching bid %s order at %.8f on %s', self.unit, response['bid'], + repr(self.exchange)) + self.place('ask', askprice) + self.lastlimit = self.limit.copy() + self.requester.submit() - def run(self): - self.logger.info("starting PyBot for %s on %s", self.unit, repr(self.exchange)) - self.serverprice = self.conn.get('price/' + self.unit, trials = 3, timeout = 15)['price'] - self.price = self.serverprice - trials = 0 - while trials < 10: - response = self.cancel_orders(reset = False) - if not 'error' in response: break - trials = trials + 1 - self.sync() - self.place_orders() - curtime = time.time() - efftime = curtime - lasttime = curtime - lastdev = { 'bid': 1.0, 'ask': 1.0 } - delay = 0.0 - while self.active: - try: - sleep = 30 - time.time() + curtime - if sleep < 0: - delay += abs(sleep) - if delay > 5.0: - self.logger.warning('need to resynchronize trading bot for %s on %s because the deviation reached %.2f', self.unit, repr(self.exchange), delay) - if self.sync(): - delay = 0.0 - if not self.requester.errorflag: - self.place_orders() + def sync(self, trials=3): + ts = int(time.time() * 1000.0) + response = self.conn.get('sync', trials=1, timeout=15) + if not 'error' in response: + delay = (response['sync'] - (response['time'] % response['sync'])) - (int(time.time() * 1000.0) - ts) / 2 + if delay <= 0: + self.logger.error("unable to synchronize time with server for %s on %s: time difference to small", + self.unit, repr(self.exchange)) + if trials > 0: + return self.sync(trials - 1) + self.logger.info("waiting %.2f seconds to synchronize with other trading bots for %s on %s", delay / 1000.0, + self.unit, repr(self.exchange)) + time.sleep(delay / 1000.0) + elif trials > 0: + self.logger.error("unable to synchronize time with server for %s on %s: %s", self.unit, repr(self.exchange), + response['message']) + return self.sync(trials - 1) else: - while sleep > 0: - step = min(sleep, 0.5) - time.sleep(step) - if not self.active: break - sleep -= step - if not self.active: break + return False + return True + + def run(self): + self.logger.info("starting PyBot for %s on %s", self.unit, repr(self.exchange)) + self.serverprice = self.conn.get('price/' + self.unit, trials=3, timeout=15)['price'] + self.price = self.serverprice + trials = 0 + while trials < 10: + response = self.cancel_orders(reset=False) + if not 'error' in response: break + trials = trials + 1 + self.sync() + self.place_orders() curtime = time.time() + efftime = curtime lasttime = curtime - if self.requester.errorflag: - self.logger.error('server unresponsive for %s on %s', self.unit, repr(self.exchange)) - self.shutdown() - self.limit = self.target.copy() - efftime = curtime - else: - response = self.conn.get('price/' + self.unit, trials = 3, timeout = 10) - if not 'error' in response: - self.serverprice = response['price'] - userprice = PyBot.pricefeed.price(self.unit) - if 1.0 - min(self.serverprice, userprice) / max(self.serverprice, userprice) > 0.005: # validate server price - self.logger.error('server price %.8f for %s deviates too much from price %.8f received from ticker, will try to delete orders on %s', - self.serverprice, self.unit, userprice, repr(self.exchange)) - self.price = self.serverprice - self.cancel_orders() - efftime = curtime - else: - deviation = 1.0 - min(self.price, self.serverprice) / max(self.price, self.serverprice) - if deviation > 0.0025: - self.logger.info('price of %s moved from %.8f to %.8f, will try to delete orders on %s', self.unit, self.price, self.serverprice, repr(self.exchange)) - self.price = self.serverprice - self.cancel_orders() - self.place_orders() - efftime = curtime - continue - elif curtime - efftime > 60: - efftime = curtime - response = self.conn.get(self.key, trials = 1) - if 'error' in response: - self.logger.error('unable to receive statistics for user %s: %s', self.key, response['message']) + lastdev = {'bid': 1.0, 'ask': 1.0} + delay = 0.0 + while self.active: + try: + sleep = 30 - time.time() + curtime + if sleep < 0: + delay += abs(sleep) + if delay > 5.0: + self.logger.warning( + 'need to resynchronize trading bot for %s on %s because the deviation reached %.2f', + self.unit, repr(self.exchange), delay) + if self.sync(): + delay = 0.0 + if not self.requester.errorflag: + self.place_orders() + else: + while sleep > 0: + step = min(sleep, 0.5) + time.sleep(step) + if not self.active: break + sleep -= step + if not self.active: break + curtime = time.time() + lasttime = curtime + if self.requester.errorflag: + self.logger.error('server unresponsive for %s on %s', self.unit, repr(self.exchange)) + self.shutdown() + self.limit = self.target.copy() + efftime = curtime else: - for side in [ 'bid', 'ask' ]: - effective_rate = 0.0 - effective_rate = float(sum([ o['amount'] * o['cost'] for o in response['units'][self.unit][side] ])) - self.total[side] = float(sum([ o['amount'] for o in response['units'][self.unit][side] ])) - contrib = float(sum([ o['amount'] for o in response['units'][self.unit][side] if o['cost'] > 0.0 ])) - if self.total[side] < 0.5: - self.limit[side] = min(self.limit[side] + 0.5, self.target[side]) + response = self.conn.get('price/' + self.unit, trials=3, timeout=10) + if not 'error' in response: + self.serverprice = response['price'] + userprice = PyBot.pricefeed.price(self.unit) + if 1.0 - min(self.serverprice, userprice) / max(self.serverprice, + userprice) > 0.005: # validate server price + self.logger.error( + 'server price %.8f for %s deviates too much from price %.8f received from ticker, will try to delete orders on %s', + self.serverprice, self.unit, userprice, repr(self.exchange)) + self.price = self.serverprice + self.cancel_orders() + efftime = curtime + else: + deviation = 1.0 - min(self.price, self.serverprice) / max(self.price, self.serverprice) + if deviation > 0.0025: + self.logger.info('price of %s moved from %.8f to %.8f, will try to delete orders on %s', + self.unit, self.price, self.serverprice, repr(self.exchange)) + self.price = self.serverprice + self.cancel_orders() + self.place_orders() + efftime = curtime + continue + elif curtime - efftime > 60: + efftime = curtime + response = self.conn.get(self.key, trials=1) + if 'error' in response: + self.logger.error('unable to receive statistics for user %s: %s', self.key, + response['message']) + else: + for side in ['bid', 'ask']: + effective_rate = 0.0 + effective_rate = float( + sum([o['amount'] * o['cost'] for o in response['units'][self.unit][side]])) + self.total[side] = float( + sum([o['amount'] for o in response['units'][self.unit][side]])) + contrib = float(sum([o['amount'] for o in response['units'][self.unit][side] if + o['cost'] > 0.0])) + if self.total[side] < 0.5: + self.limit[side] = min(self.limit[side] + 0.5, self.target[side]) + else: + effective_rate /= self.total[side] + deviation = 1.0 - min(effective_rate, self.requester.cost[side]) / max( + effective_rate, self.requester.cost[side]) + if deviation > 0.02 and lastdev[side] > 0.02: + if self.total[side] > 0.5 and effective_rate < self.requester.cost[ + side]: + funds = max(0.5, self.total[side] * (1.0 - max(deviation, 0.1))) + self.logger.info( + "decreasing tier 1 %s limit of %s on %s from %.8f to %.8f", + side, self.unit, repr(self.exchange), self.total[side], funds) + self.cancel_orders(side) + self.limit[side] = funds + elif self.limit[side] < self.total[ + side] * deviation and effective_rate > self.requester.cost[ + side] and contrib < self.target[side]: + self.logger.info( + "increasing tier 1 %s limit of %s on %s from %.8f to %.8f", + side, self.unit, repr(self.exchange), self.total[side], + self.total[side] + max(1.0, max(contrib * deviation, 0.5))) + self.limit[side] = max(1.0, max(contrib * deviation, 0.5)) + elif deviation < 0.01 and lastdev[side] < 0.01 and self.limit[side] < max( + 1.0, max(contrib * deviation, 0.5)) and contrib < self.target[ + side] and effective_rate >= self.requester.cost[side]: + self.logger.info( + "increasing tier 1 %s limit of %s on %s from %.8f to %.8f", + side, self.unit, repr(self.exchange), self.total[side], + self.total[side] + max(1.0, max(contrib * deviation, 0.5))) + self.limit[side] = max(1.0, max(contrib * deviation, 0.5)) + lastdev[side] = deviation + self.place_orders() else: - effective_rate /= self.total[side] - deviation = 1.0 - min(effective_rate, self.requester.cost[side]) / max(effective_rate, self.requester.cost[side]) - if deviation > 0.02 and lastdev[side] > 0.02: - if self.total[side] > 0.5 and effective_rate < self.requester.cost[side]: - funds = max(0.5, self.total[side] * (1.0 - max(deviation, 0.1))) - self.logger.info("decreasing tier 1 %s limit of %s on %s from %.8f to %.8f", side, self.unit, repr(self.exchange), self.total[side], funds) - self.cancel_orders(side) - self.limit[side] = funds - elif self.limit[side] < self.total[side] * deviation and effective_rate > self.requester.cost[side] and contrib < self.target[side]: - self.logger.info("increasing tier 1 %s limit of %s on %s from %.8f to %.8f", - side, self.unit, repr(self.exchange), self.total[side], self.total[side] + max(1.0, max(contrib * deviation, 0.5))) - self.limit[side] = max(1.0, max(contrib * deviation, 0.5)) - elif deviation < 0.01 and lastdev[side] < 0.01 and self.limit[side] < max(1.0, max(contrib * deviation, 0.5)) and contrib < self.target[side] and effective_rate >= self.requester.cost[side]: - self.logger.info("increasing tier 1 %s limit of %s on %s from %.8f to %.8f", - side, self.unit, repr(self.exchange), self.total[side], self.total[side] + max(1.0, max(contrib * deviation, 0.5))) - self.limit[side] = max(1.0, max(contrib * deviation, 0.5)) - lastdev[side] = deviation - self.place_orders() - else: - self.logger.error('unable to retrieve server price: %s', response['message']) - except Exception as e: - self.logger.debug('exception caught in trading bot: %s', sys.exc_info()[1]) - time.sleep(1) # this is to ensure that the order book is updated - self.shutdown() \ No newline at end of file + self.logger.error('unable to retrieve server price: %s', response['message']) + except Exception as e: + self.logger.debug('exception caught in trading bot: %s', sys.exc_info()[1]) + time.sleep(1) # this is to ensure that the order book is updated + self.shutdown() \ No newline at end of file