def init_bts_price(self): config_bts = self.config_bts self.client = BTS(config_bts["user"], config_bts["password"], config_bts["host"], config_bts["port"]) self.bts_market = BTSMarket(self.client) self.bts_price = BTSPriceAfterMatch(self.bts_market) # don't use cover order, because it's feed price related, # if there is a big order, feed price will down without stop self.bts_price.set_need_cover(False)
def __init__(self): config_file = os.getenv("HOME") + "/.python-bts/bts_client.json" fd_config = open(config_file) self.config_bts = json.load(fd_config)["client_default"] fd_config.close() config_bts = self.config_bts self.client = BTS(config_bts["user"], config_bts["password"], config_bts["host"], config_bts["port"]) self.market = BTSMarket(self.client) self.height = -1
def init_market(self): config_file = os.getenv("HOME") + "/.python-bts/bts_client.json" fd_config = open(config_file) config_bts = json.load(fd_config)[self.config["bts_client"]] fd_config.close() self.bts_client = BTS(config_bts["user"], config_bts["password"], config_bts["host"], config_bts["port"]) self.market = BTSMarket(self.bts_client) client_info = self.bts_client.get_info() self.height = int(client_info["blockchain_head_block_num"])
class TestMain(object): logfile = open("/tmp/test-bts-api.log", 'a') config_file = os.getenv("HOME") + "/.python-bts/bts_client.json" fd_config = open(config_file) config_bts = json.load(fd_config)["client_default"] fd_config.close() client = BTS(config_bts["user"], config_bts["password"], config_bts["host"], config_bts["port"]) bts_market = BTSMarket(client) def test_float_precision(self): assert trim_float_precision(0.1323234234234234, 10000) == '0.1323' def test_fixed_point(self): assert to_fixed_point("hello 3.1415926E-3") == 'hello 0.0031415926' def test_get_median(self): assert get_median([1, 2, 3, 4]) == 2.5 assert get_median([1, 2, 3]) == 2 assert get_median([1]) == 1 assert get_median([]) is None def test_info(self): result = self.client.get_info() pprint("======= test_info =========", self.logfile) pprint(result, self.logfile) assert result["blockchain_head_block_num"] > 1 def test_asset_info(self): result = self.client.get_asset_info("BTS") assert result["symbol"] == "BTS" result = self.client.get_asset_info(0) assert result["id"] == 0 pprint("======= test_asset_info =========", self.logfile) pprint(result, self.logfile) def test_is_asset_peg(self): assert self.client.is_peg_asset("CNY") assert not self.client.is_peg_asset("BTS") assert not self.client.is_peg_asset("BTSBOTS") def test_asset_precision(self): assert self.client.get_asset_precision("CNY") == 10**4 assert self.client.get_asset_precision("BTS") == 10**5 assert self.client.get_asset_precision("BTSBOTS") == 10**2 def test_feed_price(self): feed_price_cny = self.client.get_feed_price("CNY") feed_price_usd = self.client.get_feed_price("USD") feed_price_usd_per_cny = self.client.get_feed_price("USD", base="CNY") assert feed_price_usd_per_cny == feed_price_usd / feed_price_cny assert not self.client.get_feed_price("BTSBOTS") def test_list_accounts(self): accounts = self.client._list_accounts() pprint("======= test__list_accounts =========", self.logfile) pprint(accounts, self.logfile) accounts = self.client.list_accounts() pprint("======= test_list_accounts =========", self.logfile) pprint(accounts, self.logfile) def test_get_balance(self): balance = self.client.get_balance() pprint("======= test_get_balance =========", self.logfile) pprint(balance, self.logfile) def test_transaction_history(self): trx_history = self.client.get_transaction_history(limit=-100) pprint("======= test_transaction_history =========", self.logfile) pprint(trx_history, self.logfile) def test_order_book(self): order_book = self.bts_market.get_order_book("CNY", "BTS") pprint("======= test_order_book =========", self.logfile) pprint(order_book, self.logfile) assert len(order_book) > 0 #def test_publish_feeds(self): #def test_transfer(self): #def test_market_batch_update(self): def test_exchanges_yahoo(self): exchanges = Exchanges() rate_cny = exchanges.fetch_from_yahoo() pprint("======= test_exchanges_yahoo =========", self.logfile) pprint(rate_cny, self.logfile) assert rate_cny["USD"] > 0 def test_exchanges_yunbi(self): exchanges = Exchanges() order_book = exchanges.fetch_from_yunbi() pprint("======= test_exchanges_yunbi =========", self.logfile) pprint(order_book, self.logfile) assert len(order_book["bids"]) > 0 def test_exchanges_poloniex(self): exchanges = Exchanges() order_book = exchanges.fetch_from_poloniex() pprint("======= test_exchanges_poloniex =========", self.logfile) pprint(order_book, self.logfile) assert len(order_book["bids"]) > 0 def test_exchanges_btc38(self): exchanges = Exchanges() order_book = exchanges.fetch_from_btc38() pprint("======= test_exchanges_btc38 =========", self.logfile) pprint(order_book, self.logfile) assert len(order_book["bids"]) > 0 def test_exchanges_bter(self): exchanges = Exchanges() order_book = exchanges.fetch_from_bter() pprint("======= test_exchanges_bter =========", self.logfile) pprint(order_book, self.logfile) assert len(order_book["bids"]) > 0 def test_bts_price_after_match(self): bts_price = BTSPriceAfterMatch(self.bts_market) bts_price.set_need_cover(False) bts_price.get_rate_from_yahoo() # get all order book bts_price.get_order_book_all() # can add weight here for order_type in bts_price.order_types: for _order in bts_price.order_book["wallet_usd"][order_type]: _order[1] *= 0.1 # calculate real price volume, volume2, real_price = bts_price.get_real_price(spread=0.01) valid_depth = bts_price.get_valid_depth(price=real_price, spread=0.01) pprint("======= test_bts_price_after_match =========", self.logfile) pprint([volume, real_price, real_price / 1.01, real_price / 0.99], self.logfile) pprint(valid_depth, self.logfile) #pprint(bts_price.order_book, self.logfile) assert volume > 0 def test_account_info(self): account_info = self.client.get_account_info("baozi") assert account_info is not None pprint("======= test_account_info =========", self.logfile) pprint(account_info, self.logfile) account_info = self.client.get_account_info("none.baozi") assert account_info is None def test_list_blocks(self): list_blocks = self.client.list_blocks(100000, 10) assert len(list_blocks) == 10 pprint("======= test_list_blocks =========", self.logfile) pprint(list_blocks, self.logfile) def test_list_active_delegates(self): active_delegates = self.client.list_active_delegates(0, 2) assert len(active_delegates) == 2 pprint("======= test_list_active_delegates =========", self.logfile) pprint(active_delegates, self.logfile) def test_get_block_transactions(self): trxs = self.client.get_block_transactions(2549745) assert len(trxs) == 3 pprint("======= test_get_block_transactions =========", self.logfile) pprint(trxs, self.logfile) trxs = self.client.get_block_transactions(2549737) assert len(trxs) == 0 def test_my_order_book(self): order_book = self.bts_market.get_my_order_book("CNY", "BTS") assert order_book is not None pprint("======= test_my_order_book =========", self.logfile) pprint(order_book, self.logfile) def test_get_address_balances(self): balances = self.client.get_address_balances( "BTSJT2yrAEcPSYjX3yLmRgFaxA5zDsUHFBDe") pprint("======= test_address_bbalances =========", self.logfile) pprint(balances, self.logfile)
class GetMarketTrx(object): def __init__(self): config_file = os.getenv("HOME") + "/.python-bts/bts_client.json" fd_config = open(config_file) self.config_bts = json.load(fd_config)["client_default"] fd_config.close() config_bts = self.config_bts self.client = BTS(config_bts["user"], config_bts["password"], config_bts["host"], config_bts["port"]) self.market = BTSMarket(self.client) self.height = -1 def display_deal_trx(self, deal_trx): for trx in deal_trx: if trx["type"] == "bid": deal_type = "buy" else: deal_type = "sell" sys.stdout.write("\n%s %s %s %s %s at price %.4g %s/%s" % ( trx["timestamp"], trx["block"], deal_type, trx["volume"], trx["base"], trx["price"], trx["quote"], trx["base"])) def display_place_trx(self, place_trx): trx_id = "" for trx in place_trx: if trx_id == trx["trx_id"]: sys.stdout.write(".") continue trx_id = trx["trx_id"] if trx["type"] == "cover": sys.stdout.write("\n%s %s %s %s %s %s" % ( trx["timestamp"], trx["block"], trx["trx_id"], trx["type"], trx["amount"], trx["quote"])) elif trx["type"] == "add_collateral": sys.stdout.write("\n%s %s %s %s %s %s" % ( trx["timestamp"], trx["block"], trx["trx_id"], trx["type"], trx["amount"], trx["base"])) else: if trx["type"] == "bid": volume = trx["amount"] / trx["price"] else: volume = trx["amount"] order_type = trx["type"] if trx["cancel"]: order_type = "cancel " + trx["type"] if trx["price"]: price_str = "%.4g" % trx["price"] else: price_str = "unlimited" sys.stdout.write("\n%s %s %s %s %s %s at price %s %s/%s" % ( trx["timestamp"], trx["block"], trx["trx_id"], order_type, volume, trx["base"], price_str, trx["quote"], trx["base"])) def excute(self): client_info = self.client.get_info() height_now = int(client_info["blockchain_head_block_num"]) if self.height == -1: self.height = height_now - 1000 while self.height < height_now: self.height += 1 trxs = self.client.get_block_transactions(self.height) recs = self.market.get_order_deal_rec(self.height) self.display_deal_trx(recs) recs = self.market.get_order_place_rec(trxs) self.display_place_trx(recs) self.market.update_order_owner(recs)
class DelegateTask(object): def __init__(self): self.load_config() self.init_bts_price() self.setup_log() self.init_task_feed_price() def load_config(self): config_file = os.getenv("HOME") + "/.python-bts/delegate_task.json" fd_config = open(config_file) config = json.load(fd_config) self.notify = config["notify"] self.config = config["delegate_task"] self.config_price_feed = self.config["price_feed"] self.config_withdraw_pay = self.config["withdraw_pay"] fd_config.close() config_file = os.getenv("HOME") + "/.python-bts/bts_client.json" fd_config = open(config_file) self.config_bts = json.load(fd_config)[self.config["bts_client"]] fd_config.close() def init_bts_price(self): config_bts = self.config_bts self.client = BTS(config_bts["user"], config_bts["password"], config_bts["host"], config_bts["port"]) self.bts_market = BTSMarket(self.client) self.bts_price = BTSPriceAfterMatch(self.bts_market) # don't use cover order, because it's feed price related, # if there is a big order, feed price will down without stop self.bts_price.set_need_cover(False) def setup_log(self): # Setting up Logger self.logger = logging.getLogger('bts') self.logger.setLevel(logging.INFO) formatter = logging.Formatter( '%(asctime)s[%(levelname)s]: %(message)s') fh = logging.handlers.RotatingFileHandler("/tmp/bts_delegate_task.log") fh.setFormatter(formatter) self.logger.addHandler(fh) def init_task_feed_price(self): peg_asset_list = [ "KRW", "BTC", "SILVER", "GOLD", "TRY", "SGD", "HKD", "RUB", "SEK", "NZD", "CNY", "MXN", "CAD", "CHF", "AUD", "GBP", "JPY", "EUR", "USD", "SHENZHEN", "NASDAQC", "NIKKEI", "HANGSENG", "SHANGHAI" ] self.price_queue = {} for asset in peg_asset_list: self.price_queue[asset] = [] self.time_publish_feed = 0 self.last_publish_price = {} # Actually I think it may be beneficial to discount all feeds by 0.995 # to give the market makers some breathing room and provide a buffer # against down trends. self.discount = 0.995 def fetch_bts_price(self): # updat rate cny self.bts_price.get_rate_from_yahoo() # get all order book self.bts_price.get_order_book_all() # can add weight here for order_type in self.bts_price.order_types: for market in self.bts_price.order_book: if market not in self.config_price_feed["market_weight"]: _weight = 0.0 else: _weight = self.config_price_feed["market_weight"][market] for _order in self.bts_price.order_book[market][order_type]: _order[1] *= _weight # calculate real price volume, volume_sum, real_price = self.bts_price.get_real_price( spread=self.config_price_feed["price_limit"]["spread"]) valid_depth = self.bts_price.get_valid_depth( price=real_price, spread=self.config_price_feed["price_limit"]["spread"]) price_cny = real_price / self.bts_price.rate_btc["CNY"] self.logger.info("fetch price is %.5f CNY/BTS, volume is %.3f", price_cny, volume) self.logger.info("efficent depth : %s" % valid_depth) return real_price # these MPA's precision is 100, it's too small, # have to change the price # but we should fixed these at BTS2.0 def patch_nasdaqc(self, median_price): if "SHENZHEN" in median_price: median_price["SHENZHEN"] /= median_price["CNY"] if "SHANGHAI" in median_price: median_price["SHANGHAI"] /= median_price["CNY"] if "NASDAQC" in median_price: median_price["NASDAQC"] /= median_price["USD"] if "NIKKEI" in median_price: median_price["NIKKEI"] /= median_price["JPY"] if "HANGSENG" in median_price: median_price["HANGSENG"] /= median_price["HKD"] def get_median_price(self, bts_price_in_btc): median_price = {} for asset in self.price_queue: if asset not in self.bts_price.rate_btc or \ self.bts_price.rate_btc[asset] is None: continue self.price_queue[asset].append(bts_price_in_btc / self.bts_price.rate_btc[asset]) if len(self.price_queue[asset]) > \ self.config_price_feed["price_limit"]["median_length"]: self.price_queue[asset].pop(0) median_price[asset] = sorted(self.price_queue[asset])[int( len(self.price_queue[asset]) / 2)] self.patch_nasdaqc(median_price) return median_price def get_feed_price(self): current_feed_price = {} for asset in self.price_queue: current_feed_price[asset] = self.client.get_feed_price(asset) return current_feed_price def publish_rule_check2(self, median_price): if time.time() - self.time_publish_feed > \ self.config_price_feed["max_update_hours"] * 60 * 60: #self.logger.info("publish 1") return True change_min = self.config_price_feed["price_limit"]["change_min"] for asset in median_price: if asset not in self.last_publish_price: continue price_change = 100.0 * ( median_price[asset] - self.last_publish_price[asset]) \ / self.last_publish_price[asset] if fabs(price_change) > change_min: return True return False def publish_rule_check(self, median_price, current_feed_price): # When attempting to write a market maker the slow movement of the feed # can be difficult. # I would recommend the following: # if REAL_PRICE < MEDIAN and YOUR_PRICE > MEDIAN publish price # if you haven't published a price in the past 20 minutes # if REAL_PRICE > MEDIAN and YOUR_PRICE < MEDIAN and # abs( YOUR_PRICE - REAL_PRICE ) / REAL_PRICE > 0.005 publish price # The goal is to force the price down rapidly and allow it to creep up # slowly. # By publishing prices more often it helps market makers maintain the # peg and minimizes opportunity for shorts to sell USD below the peg # that the market makers then have to absorb. # If we can get updates flowing smoothly then we can gradually reduce # the spread in the market maker bots. # note: all prices in USD per BTS if time.time() - self.time_publish_feed > \ self.config_price_feed["max_update_hours"] * 60 * 60: return True for asset in median_price: price_change = 100.0 * ( median_price[asset] - self.last_publish_price[asset]) \ / self.last_publish_price[asset] # ignore if change less than 0.2% change_ignore = \ self.config_price_feed["price_limit"]["change_ignore"] if fabs(price_change) < change_ignore: continue if current_feed_price[asset] and \ median_price[asset] < current_feed_price[asset] / \ self.discount and self.last_publish_price[asset] > \ current_feed_price[asset] / self.discount: # self.logger.info( # "need update: %s %s %s" % ( # median_price[asset], self.last_publish_price[asset], # current_feed_price[asset] / self.discount)) return True # if you haven't published a price in the past 20 minutes, # and the price change more than 0.5% change_min = self.config_price_feed["price_limit"]["change_min"] if fabs(price_change) > change_min and \ time.time() - self.time_publish_feed > 20 * 60: return True return False def check_median_price(self, median_price, current_feed_price): for asset in median_price.keys(): if current_feed_price[asset] is None: continue price_change = 100.0 * ( median_price[asset] - current_feed_price[asset]) \ / current_feed_price[asset] # don't publish price which change more than "change_max" if fabs(price_change) > \ self.config_price_feed["price_limit"]["change_max"]: median_price.pop(asset) def publish_feed_price(self, median_price): self.time_publish_feed = time.time() self.last_publish_price = median_price publish_feeds = [] for asset in median_price: publish_feeds.append([asset, median_price[asset] * self.discount]) self.logger.info("publish price %s", publish_feeds) active_delegates = self.client.list_active_delegates() for delegate in self.config["delegate_list"]: if "allow_stand_by" in self.config["price_feed"] and \ self.config["price_feed"]["allow_stand_by"] == 1: self.client.publish_feeds(delegate, publish_feeds) elif any(d['name'] == delegate for d in active_delegates): self.client.publish_feeds(delegate, publish_feeds) def display_price(self, median_price, current_feed_price): os.system("clear") print("================ %s ===================" % time.strftime("%Y%m%dT%H%M%S", time.localtime(time.time()))) print(" ASSET RATE(/BTC) CURRENT_FEED LAST_PUBLISH") print("-----------------------------------------------------") for asset in sorted(median_price): if asset not in self.last_publish_price: continue _rate_btc = "%.3f" % 1 / self.bts_price.rate_btc[asset] if current_feed_price[asset] is None: _current_feed_price = None else: _current_feed_price = "%.4g" % current_feed_price[asset] _last_publish_price = "%.4g" % self.last_publish_price[asset] print('{: >8}'.format("%s" % asset), '{: >10}'.format('%s' % _rate_btc), '{: >15}'.format("%s" % _current_feed_price), '{: >15}'.format('%s' % _last_publish_price)) print("====================================================") def task_feed_price(self): bts_price_in_btc = self.fetch_bts_price() median_price = self.get_median_price(bts_price_in_btc) current_feed_price = self.get_feed_price() # if self.publish_rule_check(median_price, current_feed_price): if self.publish_rule_check2(median_price): self.check_median_price(median_price, current_feed_price) self.publish_feed_price(median_price) self.display_price(median_price, current_feed_price) def pay_notify(self, delegate_account, pay_account, pay_balance, percent): if self.notify["enable"] == 0: return mail_list = self.config_withdraw_pay["mail_list"] if pay_account not in mail_list: return sender = self.notify["sender"] msg_from = "From: %s <%s>\n" % (self.notify["name"], sender) msg_to = "" for receiver in mail_list[pay_account]: msg_to = msg_to + "To: <%s>\n" % receiver msg_subject = "Subject: pay day from %s\n" % delegate_account msg_content = "you have got payment %d*%.3f BTS\n" % (pay_balance, percent) message = msg_from + msg_to + msg_subject + msg_content smtpObj = smtplib.SMTP(self.notify["smtp_server"]) smtpObj.sendmail(sender, mail_list[pay_account], message) def withdraw_pay(self): for pay_list in self.config_withdraw_pay["pay_list"]: for delegate_account in pay_list["delegate_account"]: pay_balance = pay_list["pay_balance"] account_info = self.client.get_account_info(delegate_account) balance = float( account_info['delegate_info']['pay_balance']) / ( self.client.get_asset_precision("BTS")) if balance > pay_balance + 10: for pay_account, percent in pay_list["pay_account"]: self.logger.info("withdraw pay %s %s %s" % (delegate_account, pay_account, pay_balance * percent)) self.client.delegate_withdraw_pay( delegate_account, pay_account, pay_balance * percent) self.pay_notify(delegate_account, pay_account, pay_balance, percent) def task_withdraw_pay(self): self.withdraw_pay() def excute(self): run_timer = 0 while True: try: if self.config_price_feed["run_timer"]and \ run_timer % self.config_price_feed["run_timer"] == 0: self.task_feed_price() if self.config_withdraw_pay["run_timer"]and \ run_timer % self.config_withdraw_pay["run_timer"] == 0: self.task_withdraw_pay() except Exception as e: self.logger.exception(e) run_timer += 1 time.sleep(int(self.config["base_timer"]))
class PublishMarket(object): def __init__(self): self.pusher = None self.order_book = {} self.order_book_brief = {} self.load_config() self.init_market() def load_config(self): config_file = os.getenv("HOME")+"/.python-bts/publish_market.json" fd_config = open(config_file) self.config = json.load(fd_config) fd_config.close() def init_market(self): config_file = os.getenv("HOME") + "/.python-bts/bts_client.json" fd_config = open(config_file) config_bts = json.load(fd_config)[self.config["bts_client"]] fd_config.close() self.bts_client = BTS(config_bts["user"], config_bts["password"], config_bts["host"], config_bts["port"]) self.market = BTSMarket(self.bts_client) client_info = self.bts_client.get_info() self.height = int(client_info["blockchain_head_block_num"]) def myPublish(self, topic, event): if self.pusher: self.pusher.publish(topic, event, c=topic) def publish_deal_trx(self, deal_trx): for trx in deal_trx: if trx["type"] == "bid": deal_type = "buy" else: deal_type = "sell" prefix = get_prefix(trx["quote"], trx["base"]) format_trx = [prefix, trx["block"], trx["timestamp"], deal_type, trx["price"], trx["volume"]] self.myPublish( u'bts.orderbook.%s.trx' % (format_trx[0]), format_trx[1:]) self.myPublish(u'bts.orderbook.trx', format_trx) print(format_trx) def publish_place_trx(self, place_trx): trx_id = "" for trx in place_trx: if trx_id == trx["trx_id"]: continue prefix = get_prefix(trx["quote"], trx["base"]) trx_id = trx["trx_id"] if trx["cancel"]: trx["type"] = "cancel " + trx["type"] format_trx = [prefix, trx["block"], trx["timestamp"], trx["type"], trx["price"], trx["amount"]] self.myPublish( u'bts.orderbook.%s.order' % (format_trx[0]), format_trx[1:]) self.myPublish(u'bts.orderbook.order', format_trx) print(format_trx) def publish_order_book(self): market_list = self.config["market_list"] for quote, base in market_list: prefix = get_prefix(quote, base) order_book = self.market.get_order_book( quote, base, cover=True) order_book["bids"] = order_book["bids"][:10] order_book["asks"] = order_book["asks"][:10] if (prefix not in self.order_book or self.order_book[prefix] != order_book): self.order_book[prefix] = order_book self.myPublish( u'bts.orderbook.%s' % prefix, order_book) self.publish_order_book_brief(quote, base, order_book) def publish_order_book_brief(self, quote, base, order_book): prefix = get_prefix(quote, base) order_book_brief = {"quote": quote, "base": base, "ask1": None, "bid1": None} if order_book["bids"]: order_book_brief["bid1"] = order_book["bids"][0][0] if order_book["asks"]: order_book_brief["ask1"] = order_book["asks"][0][0] if (prefix not in self.order_book_brief or self.order_book_brief[prefix] != order_book_brief): self.order_book_brief[prefix] = order_book_brief self.myPublish( u'bts.orderbook.%s.brief' % prefix, order_book_brief) def execute(self): client_info = self.bts_client.get_info() height_now = int(client_info["blockchain_head_block_num"]) if(self.height < height_now): time_stamp = client_info["blockchain_head_block_timestamp"] self.myPublish(u'bts.blockchain.info', {"height": height_now, "time_stamp": time_stamp}) self.publish_order_book() while self.height < height_now: self.height += 1 trxs = self.bts_client.get_block_transactions( self.height) recs = self.market.get_order_deal_rec(self.height) self.publish_deal_trx(recs) recs = self.market.get_order_place_rec(trxs) self.publish_place_trx(recs) self.market.update_order_owner(recs)
class DelegateTask(object): def __init__(self): self.load_config() self.init_bts_price() self.setup_log() self.init_task_feed_price() def load_config(self): config_file = os.getenv("HOME")+"/.python-bts/delegate_task.json" fd_config = open(config_file) config = json.load(fd_config) self.notify = config["notify"] self.config = config["delegate_task"] self.config_price_feed = self.config["price_feed"] self.config_withdraw_pay = self.config["withdraw_pay"] fd_config.close() config_file = os.getenv("HOME")+"/.python-bts/bts_client.json" fd_config = open(config_file) self.config_bts = json.load(fd_config)[self.config["bts_client"]] fd_config.close() def init_bts_price(self): config_bts = self.config_bts self.client = BTS(config_bts["user"], config_bts["password"], config_bts["host"], config_bts["port"]) self.bts_market = BTSMarket(self.client) self.bts_price = BTSPriceAfterMatch(self.bts_market) # don't use cover order, because it's feed price related, # if there is a big order, feed price will down without stop self.bts_price.set_need_cover(False) def setup_log(self): # Setting up Logger self.logger = logging.getLogger('bts') self.logger.setLevel(logging.INFO) formatter = logging.Formatter( '%(asctime)s[%(levelname)s]: %(message)s') fh = logging.handlers.RotatingFileHandler("/tmp/bts_delegate_task.log") fh.setFormatter(formatter) self.logger.addHandler(fh) def init_task_feed_price(self): peg_asset_list = ["KRW", "BTC", "SILVER", "GOLD", "TRY", "SGD", "HKD", "RUB", "SEK", "NZD", "CNY", "MXN", "CAD", "CHF", "AUD", "GBP", "JPY", "EUR", "USD", "SHENZHEN", "NASDAQC", "NIKKEI", "HANGSENG", "SHANGHAI"] self.price_queue = {} for asset in peg_asset_list: self.price_queue[asset] = [] self.time_publish_feed = 0 self.last_publish_price = {} # Actually I think it may be beneficial to discount all feeds by 0.995 # to give the market makers some breathing room and provide a buffer # against down trends. self.discount = 0.995 def fetch_bts_price(self): # updat rate cny self.bts_price.get_rate_from_yahoo() # get all order book self.bts_price.get_order_book_all() # can add weight here for order_type in self.bts_price.order_types: for market in self.bts_price.order_book: if market not in self.config_price_feed["market_weight"]: _weight = 0.0 else: _weight = self.config_price_feed["market_weight"][market] for _order in self.bts_price.order_book[market][order_type]: _order[1] *= _weight # calculate real price volume, volume_sum, real_price = self.bts_price.get_real_price( spread=self.config_price_feed["price_limit"]["spread"]) valid_depth = self.bts_price.get_valid_depth( price=real_price, spread=self.config_price_feed["price_limit"]["spread"]) price_cny = real_price / self.bts_price.rate_btc["CNY"] self.logger.info("fetch price is %.5f CNY/BTS, volume is %.3f", price_cny, volume) self.logger.info("efficent depth : %s" % valid_depth) return real_price # these MPA's precision is 100, it's too small, # have to change the price # but we should fixed these at BTS2.0 def patch_nasdaqc(self, median_price): if "SHENZHEN" in median_price: median_price["SHENZHEN"] /= median_price["CNY"] if "SHANGHAI" in median_price: median_price["SHANGHAI"] /= median_price["CNY"] if "NASDAQC" in median_price: median_price["NASDAQC"] /= median_price["USD"] if "NIKKEI" in median_price: median_price["NIKKEI"] /= median_price["JPY"] if "HANGSENG" in median_price: median_price["HANGSENG"] /= median_price["HKD"] def get_median_price(self, bts_price_in_btc): median_price = {} for asset in self.price_queue: if asset not in self.bts_price.rate_btc or \ self.bts_price.rate_btc[asset] is None: continue self.price_queue[asset].append(bts_price_in_btc / self.bts_price.rate_btc[asset]) if len(self.price_queue[asset]) > \ self.config_price_feed["price_limit"]["median_length"]: self.price_queue[asset].pop(0) median_price[asset] = sorted( self.price_queue[asset])[int(len(self.price_queue[asset]) / 2)] self.patch_nasdaqc(median_price) return median_price def get_feed_price(self): current_feed_price = {} for asset in self.price_queue: current_feed_price[asset] = self.client.get_feed_price(asset) return current_feed_price def publish_rule_check2(self, median_price): if time.time() - self.time_publish_feed > \ self.config_price_feed["max_update_hours"] * 60 * 60: #self.logger.info("publish 1") return True change_min = self.config_price_feed["price_limit"]["change_min"] for asset in median_price: if asset not in self.last_publish_price: continue price_change = 100.0 * ( median_price[asset] - self.last_publish_price[asset]) \ / self.last_publish_price[asset] if fabs(price_change) > change_min: return True return False def publish_rule_check(self, median_price, current_feed_price): # When attempting to write a market maker the slow movement of the feed # can be difficult. # I would recommend the following: # if REAL_PRICE < MEDIAN and YOUR_PRICE > MEDIAN publish price # if you haven't published a price in the past 20 minutes # if REAL_PRICE > MEDIAN and YOUR_PRICE < MEDIAN and # abs( YOUR_PRICE - REAL_PRICE ) / REAL_PRICE > 0.005 publish price # The goal is to force the price down rapidly and allow it to creep up # slowly. # By publishing prices more often it helps market makers maintain the # peg and minimizes opportunity for shorts to sell USD below the peg # that the market makers then have to absorb. # If we can get updates flowing smoothly then we can gradually reduce # the spread in the market maker bots. # note: all prices in USD per BTS if time.time() - self.time_publish_feed > \ self.config_price_feed["max_update_hours"] * 60 * 60: return True for asset in median_price: price_change = 100.0 * ( median_price[asset] - self.last_publish_price[asset]) \ / self.last_publish_price[asset] # ignore if change less than 0.2% change_ignore = \ self.config_price_feed["price_limit"]["change_ignore"] if fabs(price_change) < change_ignore: continue if current_feed_price[asset] and \ median_price[asset] < current_feed_price[asset] / \ self.discount and self.last_publish_price[asset] > \ current_feed_price[asset] / self.discount: # self.logger.info( # "need update: %s %s %s" % ( # median_price[asset], self.last_publish_price[asset], # current_feed_price[asset] / self.discount)) return True # if you haven't published a price in the past 20 minutes, # and the price change more than 0.5% change_min = self.config_price_feed["price_limit"]["change_min"] if fabs(price_change) > change_min and \ time.time() - self.time_publish_feed > 20 * 60: return True return False def check_median_price(self, median_price, current_feed_price): for asset in median_price.keys(): if current_feed_price[asset] is None: continue price_change = 100.0 * ( median_price[asset] - current_feed_price[asset]) \ / current_feed_price[asset] # don't publish price which change more than "change_max" if fabs(price_change) > \ self.config_price_feed["price_limit"]["change_max"]: median_price.pop(asset) def publish_feed_price(self, median_price): self.time_publish_feed = time.time() self.last_publish_price = median_price publish_feeds = [] for asset in median_price: publish_feeds.append( [asset, median_price[asset] * self.discount]) self.logger.info("publish price %s", publish_feeds) active_delegates = self.client.list_active_delegates() for delegate in self.config["delegate_list"]: if "allow_stand_by" in self.config["price_feed"] and \ self.config["price_feed"]["allow_stand_by"] == 1: self.client.publish_feeds(delegate, publish_feeds) elif any(d['name'] == delegate for d in active_delegates): self.client.publish_feeds(delegate, publish_feeds) def display_price(self, median_price, current_feed_price): os.system("clear") print("================ %s ===================" % time.strftime("%Y%m%dT%H%M%S", time.localtime(time.time()))) print(" ASSET RATE(/BTC) CURRENT_FEED LAST_PUBLISH") print("-----------------------------------------------------") for asset in sorted(median_price): if asset not in self.last_publish_price: continue _rate_btc = "%.3f" % 1/self.bts_price.rate_btc[asset] if current_feed_price[asset] is None: _current_feed_price = None else: _current_feed_price = "%.4g" % current_feed_price[asset] _last_publish_price = "%.4g" % self.last_publish_price[asset] print( '{: >8}'.format("%s" % asset), '{: >10}'. format('%s' % _rate_btc), '{: >15}'. format("%s" % _current_feed_price), '{: >15}'. format('%s' % _last_publish_price)) print("====================================================") def task_feed_price(self): bts_price_in_btc = self.fetch_bts_price() median_price = self.get_median_price(bts_price_in_btc) current_feed_price = self.get_feed_price() # if self.publish_rule_check(median_price, current_feed_price): if self.publish_rule_check2(median_price): self.check_median_price(median_price, current_feed_price) self.publish_feed_price(median_price) self.display_price(median_price, current_feed_price) def pay_notify(self, delegate_account, pay_account, pay_balance, percent): if self.notify["enable"] == 0: return mail_list = self.config_withdraw_pay["mail_list"] if pay_account not in mail_list: return sender = self.notify["sender"] msg_from = "From: %s <%s>\n" % (self.notify["name"], sender) msg_to = "" for receiver in mail_list[pay_account]: msg_to = msg_to+"To: <%s>\n" % receiver msg_subject = "Subject: pay day from %s\n" % delegate_account msg_content = "you have got payment %d*%.3f BTS\n" % ( pay_balance, percent) message = msg_from+msg_to+msg_subject+msg_content smtpObj = smtplib.SMTP(self.notify["smtp_server"]) smtpObj.sendmail(sender, mail_list[pay_account], message) def withdraw_pay(self): for pay_list in self.config_withdraw_pay["pay_list"]: for delegate_account in pay_list["delegate_account"]: pay_balance = pay_list["pay_balance"] account_info = self.client.get_account_info(delegate_account) balance = float(account_info['delegate_info']['pay_balance'])/( self.client.get_asset_precision("BTS")) if balance > pay_balance + 10: for pay_account, percent in pay_list["pay_account"]: self.logger.info("withdraw pay %s %s %s" % (delegate_account, pay_account, pay_balance*percent)) self.client.delegate_withdraw_pay( delegate_account, pay_account, pay_balance*percent) self.pay_notify(delegate_account, pay_account, pay_balance, percent) def task_withdraw_pay(self): self.withdraw_pay() def excute(self): run_timer = 0 while True: try: if self.config_price_feed["run_timer"]and \ run_timer % self.config_price_feed["run_timer"] == 0: self.task_feed_price() if self.config_withdraw_pay["run_timer"]and \ run_timer % self.config_withdraw_pay["run_timer"] == 0: self.task_withdraw_pay() except Exception as e: self.logger.exception(e) run_timer += 1 time.sleep(int(self.config["base_timer"]))
class BTSOrderBook(object): def __init__(self): self.market_list = [ ["BOTSCNY", "BTS"], ["CNY", "BTS"], ["USD", "BTS"], ["GOLD", "BTS"], ["BTC", "BTS"], ["BDR.AAPL", "CNY"], ["NOTE", "BTS"]] self.init_done = False self.pusher = None self.order_book = {} self.deal_trx = {} self.place_trx = {} for quote, base in self.market_list: prefix = get_prefix(quote, base) self.order_book[prefix] = {} self.deal_trx[prefix] = [] self.place_trx[prefix] = [] self.init_market() self.execute() self.init_done = True def init_market(self): config_file = os.getenv("HOME") + "/.python-bts/bts_client.json" fd_config = open(config_file) config_bts = json.load(fd_config)["client_default"] fd_config.close() self.bts_client = BTS(config_bts["user"], config_bts["password"], config_bts["host"], config_bts["port"]) self.market = BTSMarket(self.bts_client) client_info = self.bts_client.get_info() self.height = int(client_info["blockchain_head_block_num"]) - 180 def myPublish(self, topic, event): if self.pusher and self.init_done: self.pusher.emit(topic, event, namespace="") def publish_deal_trx(self, deal_trx): for trx in deal_trx: if trx["type"] == "bid": deal_type = "buy" else: deal_type = "sell" prefix = get_prefix(trx["quote"], trx["base"]) format_trx = [prefix, trx["block"], trx["timestamp"], deal_type, trx["price"], trx["volume"]] self.myPublish( u'bts.orderbook.%s.trx' % (format_trx[0]), format_trx[1:]) self.myPublish(u'bts.orderbook.trx', format_trx) market = format_trx[0] if market not in self.deal_trx: self.deal_trx[market] = [] self.deal_trx[market].append(format_trx[1:]) print(format_trx) def publish_place_trx(self, place_trx): trx_id = "" for trx in place_trx: if trx_id == trx["trx_id"]: continue prefix = get_prefix(trx["quote"], trx["base"]) trx_id = trx["trx_id"] if trx["cancel"]: trx["type"] = "cancel " + trx["type"] format_trx = [prefix, trx["block"], trx["timestamp"], trx["type"], trx["price"], trx["amount"]] self.myPublish( u'bts.orderbook.%s.order' % (format_trx[0]), format_trx[1:]) self.myPublish(u'bts.orderbook.order', format_trx) market = format_trx[0] if market not in self.place_trx: self.place_trx[market] = [] self.place_trx[market].append(format_trx[1:]) print(format_trx) def publish_order_book(self): for quote, base in self.market_list: prefix = get_prefix(quote, base) order_book = self.market.get_order_book( quote, base) order_book["bids"] = order_book["bids"][:10] order_book["asks"] = order_book["asks"][:10] if (prefix not in self.order_book or self.order_book[prefix] != order_book): self.order_book[prefix] = order_book self.myPublish( u'bts.orderbook.%s' % prefix, order_book) def execute(self): client_info = self.bts_client.get_info() height_now = int(client_info["blockchain_head_block_num"]) if(self.height < height_now): time_stamp = client_info["blockchain_head_block_timestamp"] self.myPublish(u'bts.blockchain.info', {"height": height_now, "time_stamp": time_stamp}) self.publish_order_book() while self.height < height_now: self.height += 1 trxs = self.bts_client.get_block_transactions( self.height) recs = self.market.get_order_deal_rec(self.height) self.publish_deal_trx(recs) recs = self.market.get_order_place_rec(trxs) self.publish_place_trx(recs) self.market.update_order_owner(recs)
def init_bts(self): config_bts = self.config_bts self.bts_client = BTS(config_bts["user"], config_bts["password"], config_bts["host"], config_bts["port"]) self.delegate_num = int(self.bts_client.chain_info["delegate_num"]) self.period = float(self.bts_client.chain_info["block_interval"])
class DelegateWatch(object): def __init__(self): self.confirm = 2 self.load_config() self.init_bts() self.setup_log() self.init_watch() self.init_contact() def load_config(self): config_file = os.getenv("HOME")+"/.python-bts/delegate_watch.json" fd_config = open(config_file) self.config = json.load(fd_config)["delegate_watch"] fd_config.close() config_file = os.getenv("HOME")+"/.python-bts/bts_client.json" fd_config = open(config_file) self.config_bts = json.load(fd_config)[self.config["bts_client"]] fd_config.close() def init_bts(self): config_bts = self.config_bts self.bts_client = BTS(config_bts["user"], config_bts["password"], config_bts["host"], config_bts["port"]) self.delegate_num = int(self.bts_client.chain_info["delegate_num"]) self.period = float(self.bts_client.chain_info["block_interval"]) def setup_log(self): # Setting up Logger self.logger = logging.getLogger('bts') self.logger.setLevel(logging.INFO) formatter = logging.Formatter( '%(asctime)s[%(levelname)s]: %(message)s') fh = logging.handlers.RotatingFileHandler( "/tmp/bts_delegate_watch.log") fh.setFormatter(formatter) self.logger.addHandler(fh) def init_watch(self): client_info = self.bts_client.get_info() self.height = int(client_info["blockchain_head_block_num"]) self.round_left = int(client_info["blockchain_blocks_left_in_round"]) self.active_delegates = self.bts_client.list_active_delegates() self.active_offset = self.height for delegate in self.active_delegates: last_block_num = int( delegate["delegate_info"]["last_block_num_produced"]) if last_block_num == self.height: break self.active_offset -= 1 def init_contact(self): self.contact = {} mail_list = self.config["contact"] for mail in mail_list: if mail_list[mail]["enable"] == 0: continue for delegate in mail_list[mail]["delegates"]: if delegate not in self.contact: self.contact[delegate] = [mail] else: self.contact[delegate].append(mail) def process_missed_block(self, height): index = (height-self.active_offset) % self.delegate_num account = self.active_delegates[index]["name"] print("missed", height, account) self.logger.info("missed %s", account) self.active_offset -= 1 self.notify(account, height) return account def get_block_delegate(self, height): index = (height-self.active_offset) % self.delegate_num account = self.active_delegates[index]["name"] print("......", height, account) self.logger.info("%d %s", height, account) return account def check_missed_block(self, height): limit = height - self.height + 1 list_blocks = self.bts_client.list_blocks(height, limit) last_timestamp = -1 for block in reversed(list_blocks): timestamp = int(block["timestamp"][-2:]) block_num = int(block["block_num"]) if last_timestamp != -1: period = (timestamp - last_timestamp + 60) % 60 while period != self.period: period -= self.period self.process_missed_block(block_num) self.get_block_delegate(block_num) last_timestamp = timestamp def notify(self, account, height): if account not in self.contact: print("no contact") return print ("sent notify mail") sender = self.config["sender"] msg_from = "From: %s <%s>\n" % (self.config["name"], sender) msg_to = "" for receiver in self.contact[account]: msg_to = msg_to+"To: <%s>\n" % receiver msg_subject = "Subject: missed block attention for %s\n" % account msg_content = "you have missed block %d\n" % height message = msg_from+msg_to+msg_subject+msg_content print(message) smtpObj = smtplib.SMTP('localhost') smtpObj.sendmail(sender, self.contact[account], message) def execute(self): while True: try: client_info = self.bts_client.get_info() height = int( client_info["blockchain_head_block_num"]) - self.confirm if height > self.height: round_left = (int( client_info["blockchain_blocks_left_in_round"] ) + self.confirm - 1) % self.delegate_num + 1 if round_left > self.round_left: if self.round_left != 1: round_left = 1 height = self.height+self.round_left-1 else: self.active_delegates = \ self.bts_client.list_active_delegates() self.check_missed_block(height) self.height = height self.round_left = round_left except Exception as e: self.logger.exception(e) now = time.time() nexttime = int(now/self.period+1)*self.period - now time.sleep(nexttime+1)
class BTSOrderBook(object): def __init__(self): self.market_list = [["BOTSCNY", "BTS"], ["CNY", "BTS"], ["USD", "BTS"], ["GOLD", "BTS"], ["BTC", "BTS"], ["BDR.AAPL", "CNY"], ["NOTE", "BTS"]] self.init_done = False self.pusher = None self.order_book = {} self.deal_trx = {} self.place_trx = {} for quote, base in self.market_list: prefix = get_prefix(quote, base) self.order_book[prefix] = {} self.deal_trx[prefix] = [] self.place_trx[prefix] = [] self.init_market() self.execute() self.init_done = True def init_market(self): config_file = os.getenv("HOME") + "/.python-bts/bts_client.json" fd_config = open(config_file) config_bts = json.load(fd_config)["client_default"] fd_config.close() self.bts_client = BTS(config_bts["user"], config_bts["password"], config_bts["host"], config_bts["port"]) self.market = BTSMarket(self.bts_client) client_info = self.bts_client.get_info() self.height = int(client_info["blockchain_head_block_num"]) - 180 def myPublish(self, topic, event): if self.pusher and self.init_done: self.pusher.emit(topic, event, namespace="") def publish_deal_trx(self, deal_trx): for trx in deal_trx: if trx["type"] == "bid": deal_type = "buy" else: deal_type = "sell" prefix = get_prefix(trx["quote"], trx["base"]) format_trx = [ prefix, trx["block"], trx["timestamp"], deal_type, trx["price"], trx["volume"] ] self.myPublish(u'bts.orderbook.%s.trx' % (format_trx[0]), format_trx[1:]) self.myPublish(u'bts.orderbook.trx', format_trx) market = format_trx[0] if market not in self.deal_trx: self.deal_trx[market] = [] self.deal_trx[market].append(format_trx[1:]) print(format_trx) def publish_place_trx(self, place_trx): trx_id = "" for trx in place_trx: if trx_id == trx["trx_id"]: continue prefix = get_prefix(trx["quote"], trx["base"]) trx_id = trx["trx_id"] if trx["cancel"]: trx["type"] = "cancel " + trx["type"] format_trx = [ prefix, trx["block"], trx["timestamp"], trx["type"], trx["price"], trx["amount"] ] self.myPublish(u'bts.orderbook.%s.order' % (format_trx[0]), format_trx[1:]) self.myPublish(u'bts.orderbook.order', format_trx) market = format_trx[0] if market not in self.place_trx: self.place_trx[market] = [] self.place_trx[market].append(format_trx[1:]) print(format_trx) def publish_order_book(self): for quote, base in self.market_list: prefix = get_prefix(quote, base) order_book = self.market.get_order_book(quote, base) order_book["bids"] = order_book["bids"][:10] order_book["asks"] = order_book["asks"][:10] if (prefix not in self.order_book or self.order_book[prefix] != order_book): self.order_book[prefix] = order_book self.myPublish(u'bts.orderbook.%s' % prefix, order_book) def execute(self): client_info = self.bts_client.get_info() height_now = int(client_info["blockchain_head_block_num"]) if (self.height < height_now): time_stamp = client_info["blockchain_head_block_timestamp"] self.myPublish(u'bts.blockchain.info', { "height": height_now, "time_stamp": time_stamp }) self.publish_order_book() while self.height < height_now: self.height += 1 trxs = self.bts_client.get_block_transactions(self.height) recs = self.market.get_order_deal_rec(self.height) self.publish_deal_trx(recs) recs = self.market.get_order_place_rec(trxs) self.publish_place_trx(recs) self.market.update_order_owner(recs)