Advertisement
Guest User

Untitled

a guest
Sep 15th, 2015
136
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 21.98 KB | None | 0 0
  1. #! /usr/bin/env python
  2. """
  3. The MIT License (MIT)
  4. Copyright (c) 2015 creon (creon.nu@gmail.com)
  5.  
  6. Permission is hereby granted, free of charge, to any person obtaining a copy
  7. of this software and associated documentation files (the "Software"), to deal
  8. in the Software without restriction, including without limitation the rights
  9. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10. copies of the Software, and to permit persons to whom the Software is
  11. furnished to do so, subject to the following conditions:
  12.  
  13. The above copyright notice and this permission notice shall be included in all
  14. copies or substantial portions of the Software.
  15.  
  16. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  17. EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  18. MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
  19. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
  20. DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  21. OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
  22. OR OTHER DEALINGS IN THE SOFTWARE.
  23. """
  24.  
  25. import os
  26. import sys
  27. import time
  28. import json
  29. import urllib
  30. import urllib2
  31. import threading
  32. import subprocess
  33. import logging
  34. import tempfile
  35. import signal
  36. from math import ceil
  37. from utils import *
  38.  
  39.  
  40. class NuBot(ConnectionThread):
  41. def __init__(self, conn, requester, key, secret, exchange, unit, target, logger=None, ordermatch=False, deviation=0.0025):
  42. super(NuBot, self).__init__(conn, logger)
  43. self.requester = requester
  44. self.process = None
  45. self.unit = unit
  46. self.running = False
  47. self.exchange = exchange
  48. self.deviation = deviation
  49. self.options = {
  50. 'exchangename': repr(exchange),
  51. 'apikey': key,
  52. 'apisecret': secret,
  53. 'txfee': 0.2,
  54. 'pair': 'nbt_' + unit,
  55. 'submit-liquidity': False,
  56. 'dualside': True,
  57. 'multiple-custodians': True,
  58. 'executeorders': ordermatch,
  59. 'mail-notifications': False,
  60. 'hipchat': False
  61. }
  62. if unit != 'usd':
  63. if unit == 'btc':
  64. self.options['secondary-peg-options'] = {
  65. 'main-feed': 'bitfinex',
  66. 'backup-feeds': {
  67. 'backup1': {'name': 'blockchain'},
  68. 'backup2': {'name': 'coinbase'},
  69. 'backup3': {'name': 'bitstamp'}
  70. }}
  71. else:
  72. self.logger.error('no price feed available for %s', unit)
  73. self.options['secondary-peg-options']['wallshift-threshold'] = 0.3
  74. self.options['secondary-peg-options']['spread'] = 0.0
  75.  
  76. def run(self):
  77. out = tempfile.NamedTemporaryFile(delete=False)
  78. out.write(json.dumps({'options': self.options}))
  79. out.close()
  80. while self.active:
  81. if self.requester.errorflag:
  82. self.shutdown()
  83. elif not self.process:
  84. with open(os.devnull, 'w') as fp:
  85. self.logger.info("starting NuBot for %s on %s", self.unit, repr(self.exchange))
  86. self.process = subprocess.Popen("java -jar NuBot.jar %s" % out.name,
  87. stdout=fp, stderr=fp, shell=True, cwd='nubot')
  88. time.sleep(10)
  89. self.shutdown()
  90.  
  91. def shutdown(self):
  92. if self.process:
  93. self.logger.info("stopping NuBot for unit %s on %s", self.unit, repr(self.exchange))
  94. self.process.terminate()
  95. # os.killpg(self.process.pid, signal.SIGTERM)
  96. self.process = None
  97.  
  98.  
  99. class PyBot(ConnectionThread):
  100. def __init__(self, conn, requester, key, secret, exchange, unit, target, logger=None, ordermatch=False, deviation=0.0025, offset=0.002, restime=24):
  101. super(PyBot, self).__init__(conn, logger)
  102. self.requester = requester
  103. self.ordermatch = ordermatch
  104. self.key = key
  105. self.secret = secret
  106. self.exchange = exchange
  107. self.unit = unit
  108. self.orders = []
  109. self.target = target.copy()
  110. self.total = target.copy()
  111. self.limit = target.copy()
  112. self.lastlimit = {'bid': 0, 'ask': 0}
  113. self.deviation = float(deviation)
  114. self.offset = float(offset)
  115. self.restime = float(restime)
  116. self.startime = time.time()
  117. self.timer = 0
  118. if not hasattr(PyBot, 'lock'):
  119. PyBot.lock = {}
  120. if not repr(exchange) in PyBot.lock:
  121. PyBot.lock[repr(exchange)] = threading.Lock()
  122. if not hasattr(PyBot, 'pricefeed'):
  123. PyBot.pricefeed = PriceFeed(30, logger)
  124.  
  125. def cancel_orders(self, side='all', reset=True):
  126. try:
  127. response = self.exchange.cancel_orders(self.unit, side, self.key, self.secret)
  128. except:
  129. response = {'error': 'exception caught: %s' % sys.exc_info()[1]}
  130. if 'error' in response:
  131. self.logger.error('unable to delete %s orders for %s on %s: %s', side, self.unit, repr(self.exchange),
  132. response['error'])
  133. self.exchange.adjust(response['error'])
  134. self.logger.info('adjusting nonce of %s to %d', repr(self.exchange), self.exchange._shift)
  135. else:
  136. self.logger.info('successfully deleted %s orders for %s on %s', side, self.unit, repr(self.exchange))
  137. if reset:
  138. if side == 'all':
  139. self.limit['bid'] = max(self.total['bid'], 0.5)
  140. self.limit['ask'] = max(self.total['ask'], 0.5)
  141. else:
  142. self.limit[side] = max(self.total[side], 0.5)
  143. return response
  144.  
  145. def shutdown(self):
  146. self.logger.info("stopping PyBot for %s on %s", self.unit, repr(self.exchange))
  147. trials = 0
  148. while trials < 10:
  149. response = self.cancel_orders(reset=False)
  150. if not 'error' in response: break
  151. trials = trials + 1
  152.  
  153. def acquire_lock(self):
  154. PyBot.lock[repr(self.exchange)].acquire()
  155.  
  156. def release_lock(self):
  157. PyBot.lock[repr(self.exchange)].release()
  158.  
  159. def balance(self, exunit, price):
  160. try:
  161. response = self.exchange.get_balance(exunit, self.key, self.secret)
  162. if not 'error' in response:
  163. response['balance'] = response['balance'] if exunit == 'nbt' else response['balance'] / price
  164. response['balance'] = int(response['balance'] * 10 ** 3) / float(10 ** 3)
  165. except KeyboardInterrupt:
  166. raise
  167. except:
  168. response = {'error': 'exception caught: %s' % sys.exc_info()[1]}
  169. return response
  170.  
  171. def place(self, side, price):
  172. exunit = 'nbt' if side == 'ask' else self.unit
  173. response = self.balance(exunit, price)
  174. order_response = self.conn.get(self.key, trials=1)
  175. if self.unit not in order_response['units']:
  176. filler = 0
  177. else:
  178. filler = 0
  179. for orders in order_response['units'][self.unit]['ask']:
  180. filler += orders['amount']
  181. fillfactor = 5
  182. empty = fillfactor - filler
  183. if time.time() - self.timer < 105:
  184. empty = 0
  185. if 'error' in response:
  186. self.logger.error('unable to receive balance for %s on %s: %s', exunit, repr(self.exchange),
  187. response['error'])
  188. self.exchange.adjust(response['error'])
  189. self.logger.info('adjusting nonce of %s to %d', repr(self.exchange), self.exchange._shift)
  190. elif response['balance'] > 0.1:
  191. amount = min(self.limit[side], response['balance'], empty)
  192. if amount >= 0.5:
  193. try:
  194. response = self.exchange.place_order(self.unit, side, self.key, self.secret, amount, price)
  195. except KeyboardInterrupt:
  196. raise
  197. except:
  198. response = {'error': 'exception caught: %s' % sys.exc_info()[1]}
  199. if 'error' in response:
  200. if 'residual' in response and response['residual'] > 0:
  201. self.limit[side] += response['residual']
  202. else:
  203. self.logger.error('unable to place %s %s order of %.4f nbt at %.8f on %s: %s',
  204. side, self.unit, amount, price, repr(self.exchange), response['error'])
  205. self.exchange.adjust(response['error'])
  206. else:
  207. self.logger.info('successfully placed %s %s order of %.4f nbt at %.8f on %s',
  208. side, self.unit, amount, price, repr(self.exchange))
  209. self.orders.append(response['id'])
  210. self.limit[side] -= amount
  211. self.timer=time.time()
  212. return response
  213.  
  214. def place_orders(self):
  215. try:
  216. response = self.exchange.get_price(self.unit)
  217. except:
  218. response = {'error': 'exception caught: %s' % sys.exc_info()[1]}
  219. if 'error' in response:
  220. self.logger.error('unable to retrieve order book for %s on %s: %s', self.unit, repr(self.exchange),
  221. response['error'])
  222. else:
  223. spread = max(self.exchange.fee, self.offset)
  224. bidprice = ceil(self.price * (1.0 - spread) * 10 ** 8) / float(
  225. 10 ** 8) # truncate floating point precision after 8th position
  226. askprice = ceil(self.price * (1.0 + spread) * 10 ** 8) / float(10 ** 8)
  227. if response['ask'] == None or response['ask'] > bidprice:
  228. self.place('bid', bidprice)
  229. else:
  230. if 1.0 - response['ask'] / bidprice < 0.00425 - spread:
  231. devprice = ceil(bidprice * (1.0 - 0.0045 + spread) * 10 ** 8) / float(10 ** 8)
  232. self.logger.debug('decreasing bid %s order at %.8f on %s to %.8f to avoid order match', self.unit,
  233. bidprice, repr(self.exchange), devprice)
  234. self.place('bid', devprice)
  235. elif self.lastlimit['bid'] != self.limit['bid']:
  236. self.logger.error('unable to place bid %s order at %.8f on %s: matching order at %.8f detected',
  237. self.unit, bidprice, repr(self.exchange), response['ask'])
  238. elif self.ordermatch:
  239. self.logger.warning('matching ask %s order at %.8f on %s', self.unit, response['ask'],
  240. repr(self.exchange))
  241. self.place('bid', bidprice)
  242. if response['bid'] == None or response['bid'] < askprice:
  243. if response['bid'] == None:
  244. print 'response["bid"]', response['bid']
  245. self.place('ask', askprice)
  246. else:
  247. if 1.0 - askprice / response['bid'] < 0.00425 - spread:
  248. devprice = ceil(askprice * (1.0045 - spread) * 10 ** 8) / float(10 ** 8)
  249. self.logger.debug('increasing ask %s order at %.8f on %s to %.8f to avoid order match', self.unit,
  250. askprice, repr(self.exchange), devprice)
  251. self.place('ask', devprice)
  252. elif self.lastlimit['ask'] != self.limit['ask']:
  253. self.logger.error('unable to place ask %s order at %.8f on %s: matching order at %.8f detected',
  254. self.unit, askprice, repr(self.exchange), response['bid'])
  255. elif self.ordermatch:
  256. self.logger.warning('matching bid %s order at %.8f on %s', self.unit, response['bid'],
  257. repr(self.exchange))
  258. self.place('ask', askprice)
  259. self.lastlimit = self.limit.copy()
  260. self.requester.submit()
  261.  
  262. def sync(self, trials=3):
  263. ts = int(time.time() * 1000.0)
  264. response = self.conn.get('sync', trials=1, timeout=15)
  265. if not 'error' in response:
  266. delay = (response['sync'] - (response['time'] % response['sync'])) - (int(time.time() * 1000.0) - ts) / 2
  267. if delay <= 0:
  268. self.logger.error("unable to synchronize time with server for %s on %s: time difference to small",
  269. self.unit, repr(self.exchange))
  270. if trials > 0:
  271. return self.sync(trials - 1)
  272. self.logger.info("waiting %.2f seconds to synchronize with other trading bots for %s on %s", delay / 1000.0,
  273. self.unit, repr(self.exchange))
  274. time.sleep(delay / 1000.0)
  275. elif trials > 0:
  276. self.logger.error("unable to synchronize time with server for %s on %s: %s", self.unit, repr(self.exchange),
  277. response['message'])
  278. return self.sync(trials - 1)
  279. else:
  280. return False
  281. return True
  282.  
  283. def run(self):
  284. self.logger.info("starting PyBot for %s on %s", self.unit, repr(self.exchange))
  285. self.serverprice = self.conn.get('price/' + self.unit, trials=3, timeout=15)['price']
  286. self.price = self.serverprice
  287. trials = 0
  288. while trials < 10:
  289. response = self.cancel_orders(reset=False)
  290. if 'error' not in response:
  291. break
  292. trials += 1
  293. self.sync()
  294. self.place_orders()
  295. curtime = time.time()
  296. efftime = curtime
  297. lasttime = curtime
  298. lastdev = {'bid': 1.0, 'ask': 1.0}
  299. delay = 0.0
  300. while self.active:
  301. try:
  302. totime = time.time() - self.startime
  303. if totime > (self.restime*3600):
  304. python = sys.executable
  305. os.execl(python, python, * sys.argv)
  306. except:
  307. print "issues restarting bot"
  308. try:
  309. sleep = 30 - time.time() + curtime
  310. if sleep < 0:
  311. delay += abs(sleep)
  312. if delay > 5.0:
  313. self.logger.warning(
  314. 'need to resynchronize trading bot for %s on %s because the deviation reached %.2f',
  315. self.unit, repr(self.exchange), delay)
  316. if self.sync():
  317. delay = 0.0
  318. if not self.requester.errorflag:
  319. self.place_orders()
  320. else:
  321. while sleep > 0:
  322. step = min(sleep, 0.5)
  323. time.sleep(step)
  324. if not self.active:
  325. break
  326. sleep -= step
  327. if not self.active:
  328. break
  329. curtime = time.time()
  330. lasttime = curtime
  331. if self.requester.errorflag:
  332. self.logger.error('server unresponsive for %s on %s', self.unit, repr(self.exchange))
  333. self.shutdown()
  334. self.limit = self.target.copy()
  335. efftime = curtime
  336. else:
  337. response = self.conn.get('price/' + self.unit, trials=3, timeout=10)
  338. if not 'error' in response:
  339. self.serverprice = response['price']
  340. userprice = PyBot.pricefeed.price(self.unit)
  341. if 1.0 - min(self.serverprice, userprice) / max(self.serverprice,
  342. userprice) > 0.005: # validate server price
  343. self.logger.error(
  344. 'server price %.8f for %s deviates too much from price %.8f received from ticker, will try to delete orders on %s',
  345. self.serverprice, self.unit, userprice, repr(self.exchange))
  346. self.price = self.serverprice
  347. self.cancel_orders()
  348. efftime = curtime
  349. else:
  350. deviation = 1.0 - min(self.price, self.serverprice) / max(self.price, self.serverprice)
  351. if deviation > self.deviation:
  352. self.logger.info('price of %s moved from %.8f to %.8f, will try to delete orders on %s',
  353. self.unit, self.price, self.serverprice, repr(self.exchange))
  354. self.price = self.serverprice
  355. self.cancel_orders()
  356. self.place_orders()
  357. efftime = curtime
  358. continue
  359. elif curtime - efftime > 60:
  360. efftime = curtime
  361. response = self.conn.get(self.key, trials=1)
  362. if 'error' in response:
  363. self.logger.error('unable to receive statistics for user %s: %s', self.key,
  364. response['message'])
  365. else:
  366. if self.unit not in response['units']:
  367. response['units'][self.unit] = { 'bid': [], 'ask': [] }
  368. for side in ['bid', 'ask']:
  369. effective_rate = 0.0
  370. effective_rate = float(
  371. sum([o['amount'] * o['cost'] for o in response['units'][self.unit][side]]))
  372. self.total[side] = float(
  373. sum([o['amount'] for o in response['units'][self.unit][side]]))
  374. contrib = float(sum([o['amount'] for o in response['units'][self.unit][side] if
  375. o['cost'] > 0.0]))
  376. if self.total[side] < 0.5:
  377. self.limit[side] = min(self.limit[side] + 0.5, self.target[side])
  378. else:
  379. effective_rate /= self.total[side]
  380. deviation = 1.0 - min(effective_rate, self.requester.cost[side]) / max(
  381. effective_rate, self.requester.cost[side])
  382. if deviation > 0.02 and lastdev[side] > 0.02:
  383. if self.total[side] > 0.5 \
  384. and effective_rate < self.requester.cost[side] \
  385. and time.time() - self.timer > 250:
  386. funds = max(0.5, self.total[side] * (1.0 - max(deviation, 0.1)))
  387. self.logger.info(
  388. "decreasing tier 1 %s limit of %s on %s from %.8f to %.8f",
  389. side, self.unit, repr(self.exchange), self.total[side], funds)
  390. self.cancel_orders(side)
  391. self.limit[side] = funds
  392. elif self.limit[side] < self.total[side] * deviation \
  393. and effective_rate > self.requester.cost[side] \
  394. and contrib < self.target[side]:
  395. self.logger.info(
  396. "increasing tier 1 %s limit of %s on %s from %.8f to %.8f",
  397. side, self.unit, repr(self.exchange), self.total[side],
  398. self.total[side] + max(1.0, max(contrib * deviation, 0.5)))
  399. self.limit[side] = max(1.0, max(contrib * deviation, 0.5))
  400. self.cancel_orders(side)
  401. elif 0 < deviation < 0.01 \
  402. and lastdev[side] < 0.01 \
  403. and self.limit[side] < max(1.0, max(contrib * deviation, 0.5)) \
  404. and contrib < self.target[side] \
  405. and effective_rate >= self.requester.cost[side]:
  406. self.limit[side] = max(1.0, max(contrib * deviation, 0.5))
  407. self.logger.info(
  408. "increasing tier 1 %s limit of %s on %s from %.8f to %.8f",
  409. side, self.unit, repr(self.exchange), self.total[side],
  410. self.total[side] + self.limit[side])
  411. self.cancel_orders(side)
  412. lastdev[side] = deviation
  413. self.place_orders()
  414. else:
  415. self.logger.error('unable to retrieve server price: %s', response['message'])
  416. except Exception as e:
  417. self.logger.debug('exception caught in trading bot: %s', sys.exc_info()[1])
  418. time.sleep(1) # this is to ensure that the order book is updated
  419. self.shutdown()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement