class TermArbitrage: # 正表示期货价格高于现货 # TODO # 增加动态调整 open_positive_thd = 25 close_positive_thd = -5 open_negative_thd = -25 close_negetive_thd = 5 # 风险控制 margin_thd = 150 risk_thd = 0.5 margin_coefficient = 0.5 min_contract_amount = 1 symbol = 'btc_usdt' contract_type = 'quarter' # 每次最大合约交易量 max_contract_exchange_amount = 5 # 1 开多 2 开空 3 平多 4 平空 debug_type = 0 # trend_10_thd = 5 trend_30_thd = 3 def __init__(self): key_dict = {} # 读取配置文件 with open('config', 'r') as f: for line in f.readlines(): splited = line.split('=') if len(splited) == 2: key_dict[splited[0].strip()] = splited[1].strip() self.huobi_client = HuobiSpot(key_dict['HUOBI_ACCESS_KEY3'], key_dict['HUOBI_SECRET_KEY3']) self.future_client = OkexFutureClient(key_dict['OKEX_ACCESS_KEY'], key_dict['OKEX_SECRET_KEY']) # 空头合约数量 self.bear_amount = 0 # 多头合约数量 self.bull_amount = 0 # self.buy_available = 0 self.sell_available = 0 self.buy_profit_lossratio = 0 self.sell_profit_lossratio = 0 self.sell_price_avg = 0 self.buy_price_avg = 0 # 保证金 self.keep_deposit = 0 self.risk_rate = 0 self.future_rights = 0 self.profit_real = 0 self.profit_unreal = 0 # 指数 self.future_index = 0 self.spot_free_btc = 0 self.spot_free_usdt = 0 self.spot_freezed_btc = 0 self.spot_freezed_usdt = 0 # 统计收益 # config logging self.logger = logging.getLogger("Future") # 指定logger输出格式 formatter = logging.Formatter( '%(asctime)s %(levelname)-8s: %(message)s') # 文件日志 file_handler = logging.FileHandler("term.log") file_handler.setFormatter(formatter) # 控制台日志 console_handler = logging.StreamHandler(sys.stdout) console_handler.formatter = formatter # 为logger添加的日志处理器 self.logger.addHandler(file_handler) self.logger.addHandler(console_handler) # 指定日志的最低输出级别,默认为WARN级别 self.logger.setLevel(logging.INFO) # # 5s self.avg_line_1m = [] self.avg_line_3m = [] self.avg_line_5m = [] # 10s self.avg_line_10m = [] # 30s self.avg_line_30m = [] self.avg_1m = 0 self.avg_5m = 0 self.avg_10m_prev = 0 self.avg_10m_post = 0 self.avg_30m_prev = 0 self.avg_30m_post = 0 def trend_test(self): while True: try: future_depth = self.future_client.depth( self.symbol, self.contract_type, 10) except Exception as e: self.logger.error('获取期货市场深度错误: %s' % e) time.sleep(3) continue future_bids = future_depth['bids'] future_asks = future_depth['asks'][::-1] spot_depth = self.huobi_client.get_depth('btcusdt', 'step5') if spot_depth['status'] == 'ok': spot_bids = spot_depth['tick']['bids'] spot_asks = spot_depth['tick']['asks'] else: time.sleep(3) continue d1 = future_bids[0][0] - spot_asks[0][0] d1 = float('%.2f' % d1) timestamp = int(time.time()) # 10m last_item_10m = self.avg_line_10m[-1] if len( self.avg_line_10m) > 0 else (0, 0) if timestamp - last_item_10m[0] >= 10: self.avg_line_10m.append((timestamp, d1)) if len(self.avg_line_10m) > 90: self.avg_line_10m.pop(0) self.avg_10m_prev = np.mean(self.avg_line_10m[:60], axis=0)[1] self.avg_10m_post = np.mean(self.avg_line_10m[60:], axis=0)[1] print d1, self.avg_10m_prev, self.avg_10m_post last_item_30m = self.avg_line_30m[-1] if len( self.avg_line_30m) > 0 else (0, 0) if timestamp - last_item_30m >= 30: self.avg_line_30m.append((timestamp, d1)) if len(self.avg_line_30m) > 90: self.avg_line_30m.pop(0) self.avg_30m_prev = np.mean(self.avg_line_30m[:60], axis=0)[1] self.avg_30m_post = np.mean(self.avg_line_30m[60:], axis=0)[1] def update_future_position(self): self.logger.info('全仓用户持仓查询') try: info = self.future_client.position(self.symbol, self.contract_type) except Exception as e: self.logger.error('全仓用户持仓查询异常: %s' % e) else: print info if info['result']: holding = info['holding'] if len(holding) > 0: # hold是数组 self.bull_amount = holding[0]['buy_amount'] self.bear_amount = holding[0]['sell_amount'] self.buy_available = holding[0]['buy_available'] self.sell_available = holding[0]['sell_available'] self.buy_price_avg = holding[0]['buy_price_avg'] self.sell_price_avg = holding[0]['sell_price_avg'] if self.bull_amount == 0 and self.bear_amount == 0: self.logger.info('用户未持仓') # self.asset_balance() else: self.logger.info('多仓: %s\t空仓: %s' % (self.bull_amount, self.bear_amount)) # 暂定认为len(holding)一定大于0 # else: # self.logger.info('用户未持仓') # self.asset_balance() else: self.logger.info('postion_4fix result error') def update_account_info(self): self.logger.info('获取Future全仓账户信息') try: future_info = self.future_client.userinfo() except Exception as e: self.logger.error('获取Future全仓账户信息异常: %s' % e) else: if future_info['result']: btc_info = future_info['info']['btc'] self.keep_deposit = btc_info['keep_deposit'] self.risk_rate = btc_info['risk_rate'] self.future_rights = btc_info['account_rights'] self.profit_real = btc_info['profit_real'] self.profit_unreal = btc_info['profit_unreal'] self.logger.info('bond:%s\trights:%s' % (self.keep_deposit, self.future_rights)) self.logger.info('获取SPOT账户信息') # update huobi r = self.huobi_client.get_balance() if r['status'] == 'ok': for item in r['data']['list']: if item['currency'] == 'btc' and item['type'] == 'trade': self.spot_free_btc = float(item['balance']) elif item['currency'] == 'btc' and item['type'] == 'frozen': self.spot_freezed_btc = float(item['balance']) elif item['currency'] == 'usdt' and item['type'] == 'trade': self.spot_free_usdt = float(item['balance']) elif item['currency'] == 'usdt' and item['type'] == 'frozen': self.spot_freezed_usdt = float(item['balance']) self.logger.info('spot_btc:%s\tspot_usdt:%s' % (self.spot_free_btc, self.spot_free_usdt)) else: print r if 'fail' == r['status']: self.logger.error('Huobi get_balance error: %s' % r['msg']) else: self.logger.error('Huobi get_balance error: %s' % r['err-msg']) def calc_available_contract(self, price): available_btc = self.future_rights * self.margin_coefficient - self.keep_deposit return int(available_btc * price / 10) if available_btc > 0 else 0 def go(self): while True: self.logger.info('获取深度') try: future_depth = self.future_client.depth( self.symbol, self.contract_type, 10) except Exception as e: self.logger.error('获取期货市场深度错误: %s' % e) time.sleep(3) continue future_bids = future_depth['bids'] future_asks = future_depth['asks'][::-1] spot_depth = self.huobi_client.get_depth('btcusdt', 'step5') # print spot_depth if spot_depth['status'] == 'ok': spot_bids = spot_depth['tick']['bids'] spot_asks = spot_depth['tick']['asks'] else: time.sleep(3) continue d1 = future_bids[0][0] - spot_asks[0][0] d1 = float('%.2f' % d1) timestamp = int(time.time()) last_item_10m = self.avg_line_10m[-1] if len( self.avg_line_10m) > 0 else (0, 0) if timestamp - last_item_10m[0] >= 10: self.avg_line_10m.append((timestamp, d1)) if len(self.avg_line_10m) > 90: self.avg_line_10m.pop(0) self.avg_10m_prev = np.mean(self.avg_line_10m[:60], axis=0)[1] self.avg_10m_post = np.mean(self.avg_line_10m[60:], axis=0)[1] print d1, self.avg_10m_prev, self.avg_10m_post last_item_30m = self.avg_line_30m[-1] if len( self.avg_line_30m) > 0 else (0, 0) if timestamp - last_item_30m >= 30: self.avg_line_30m.append((timestamp, d1)) if len(self.avg_line_30m) > 90: self.avg_line_30m.pop(0) self.avg_30m_prev = np.mean(self.avg_line_30m[:60], axis=0)[1] self.avg_30m_post = np.mean(self.avg_line_30m[60:], axis=0)[1] # 开空 if self.debug_type == 2 or future_bids[0][0] - spot_asks[0][ 0] > self.open_positive_thd: self.logger.info('期货开空,现货买入') self.logger.info('期货价格: %s,现货价格: %s' % (future_bids[0][0], spot_asks[0][0])) available_bear_amount = self.calc_available_contract( future_bids[0][0]) if available_bear_amount == 0: self.logger.info('可开合约数量不足') continue self.logger.info('可开合约数量为: %s' % available_bear_amount) spot_sum = np.sum(spot_asks[:3], axis=0) spot_std = np.std(spot_asks[:3], axis=0) if spot_sum[1] < 0.5 or spot_std[0] > 5: self.logger.info('现货btc不足或标准差过大') time.sleep(1) continue future_contract_amount = min( future_bids[0][1], self.max_contract_exchange_amount, int(spot_sum[1] * spot_asks[0][0] / 200), available_bear_amount) spot_usdt_amount = 100 * future_contract_amount if spot_usdt_amount > self.spot_free_usdt: self.logger.info('现货USDT数量:%s, 不足:%s,此单取消' % (self.spot_free_usdt, spot_usdt_amount)) continue # 限价购买期货合约 self.logger.info('期货开空:%s' % future_contract_amount) price = future_bids[0][0] try: future_order = self.future_client.place_order( self.symbol, self.contract_type, price, future_contract_amount, '2', '0') except Exception as e: self.logger.error('Future订单异常: %s' % e) continue print future_order orderid = future_order['order_id'] try: order_info = self.future_client.order_info( self.symbol, self.contract_type, 0, orderid, 1, 5) except Exception as e: self.logger.error('获取订单信息异常: %s,程序终止' % e) break print order_info order_info = order_info['orders'][0] self.logger.info('order status: %s' % order_info['status']) # 等待成交或未成交 if order_info['status'] == 0 or order_info['status'] == 1: self.logger.info('撤销未完成委托') try: self.future_client.cancel(self.symbol, self.contract_type, orderid) except Exception as e: self.logger.error('撤销异常: %s' % e) self.logger.info('更新订单状态') times = 0 while times < 10: try: order_info = self.future_client.order_info( self.symbol, self.contract_type, 0, orderid, 1, 5) order_info = order_info['orders'][0] self.logger.info('订单状态: %s' % order_info['status']) # 若撤销失败,状态必定为完全成交 if order_info['status'] == 2: break except Exception as e: self.logger.error('查询订单信息异常: %s' % e) times += 1 continue times += 1 if times == 10: self.logger.error('未知错误,程序终止') break else: self.logger.info('撤销成功') self.logger.info('更新订单状态') try: order_info = self.future_client.order_info( self.symbol, self.contract_type, 0, orderid, 1, 5) order_info = order_info['orders'][0] self.logger.info('订单状态: %s' % order_info['status']) except Exception as e: self.logger.error('查询订单状态异常: %s,程序终止' % e) break print order_info future_deal_contract_amount = order_info['deal_amount'] # 未完成任何委托 if future_deal_contract_amount == 0: continue future_deal_price = order_info['price_avg'] # fee精度是8位,fee默认是负数,改成习惯的正数 future_deal_fee = -1 * float('%.8f' % order_info['fee']) # btc精度是8 future_deal_btc_amount = float( '%.8f' % (future_deal_contract_amount * 10 / future_deal_price)) self.bear_amount += future_deal_contract_amount self.keep_deposit -= future_deal_btc_amount self.keep_deposit -= future_deal_fee self.logger.info( 'future_deal:contract:%d\tbtc:%s\tfee:%s\tprice:%s' % (future_deal_contract_amount, future_deal_btc_amount, future_deal_fee, future_deal_price)) # 市价买入现货 self.logger.info('现货买入%s USDT' % spot_usdt_amount) spot_order = self.huobi_client.send_order( spot_usdt_amount, 'api', 'btcusdt', 'buy-market') if spot_order['status'] != 'ok': if spot_order['status'] == 'fail': self.logger.error('spot buy failed : %s' % spot_order['msg']) else: self.logger.error('spot buy failed : %s' % spot_order['err-msg']) # TODO self.logger.info('开始回滚') self.logger.info('终止程序') break orderid = spot_order['data'] times = 0 while times < 20: self.logger.info('第%s次确认订单信息' % (times + 1)) order_info = self.huobi_client.order_info(orderid) print order_info if order_info['status'] == 'ok' and order_info['data'][ 'state'] == 'filled': self.logger.info('spot buy filled, orderId: %s' % orderid) field_cash_amount = order_info['data'][ 'field-cash-amount'] field_amount = order_info['data']['field-amount'] field_fees = order_info['data']['field-fees'] break times += 1 if times == 19: time.sleep(15) if times == 20: self.logger.info('现货买入错误, 终止程序') break self.logger.info( 'spot_field_amount:%.8f\tspot_field_cash_amount:%.8f' % (float(field_amount), float(field_cash_amount))) # self.update_account_info() # 开多 if self.debug_type == 1 or future_asks[0][0] - spot_bids[0][ 0] < self.open_negative_thd: self.logger.info('期货开多,现货卖出') self.logger.info('期货价格: %s,现货价格 %s' % (future_asks[0][0], spot_bids[0][0])) available_bull_amount = self.calc_available_contract( future_asks[0][0]) if available_bull_amount == 0: self.logger.info('可开合约数量不足') continue self.logger.info('可开合约数量: %s' % available_bull_amount) # print spot_bids spot_sum = np.sum(spot_bids[:3], axis=0) spot_std = np.std(spot_bids[:3], axis=0) self.logger.info('BIDS:\tsum:%10.4f\tstd:%10.4f' % (spot_sum[1], spot_std[0])) if spot_sum[1] < 0.5 or spot_std[0] > 5: self.logger.info('标准差过大') time.sleep(1) continue future_contract_amount = min( self.max_contract_exchange_amount, available_bull_amount, future_asks[0][1], int(spot_sum[1] * spot_bids[0][0] / 200)) spot_limited_price = spot_bids[0][0] - 20 spot_btc_amount = float( '%.4f' % (100 * future_contract_amount / spot_limited_price)) if spot_btc_amount > self.spot_free_btc: self.logger.info('现货BTC数量%s, 不足%s,交易取消' % (self.spot_free_btc, spot_btc_amount)) continue self.logger.info('期货开多:%s' % future_contract_amount) # 限价购买期货合约 price = future_asks[0][0] + 1 try: future_order = self.future_client.place_order( self.symbol, self.contract_type, price, future_contract_amount, '1', '0') except Exception as e: self.logger.error('Future订单异常: %s' % e) continue print future_order orderid = future_order['order_id'] try: order_info = self.future_client.order_info( self.symbol, self.contract_type, 0, orderid, 1, 5) except Exception as e: self.logger.error('获取订单信息异常: %s, 程序终止' % e) break print order_info order_info = order_info['orders'][0] self.logger.info('order status: %s' % order_info['status']) # 等待成交或未成交 if order_info['status'] == 0 or order_info['status'] == 1: self.logger.info('撤销未完成委托') try: self.future_client.cancel(self.symbol, self.contract_type, orderid) except Exception as e: self.logger.error('撤销异常: %s' % e) self.logger.info('更新订单状态') times = 0 while times < 10: try: order_info = self.future_client.order_info( self.symbol, self.contract_type, 0, orderid, 1, 5) order_info = order_info['orders'][0] self.logger.info('订单状态: %s' % order_info['status']) # 若撤销失败,状态必定为完全成交 if order_info['status'] == 2: break except Exception as e: self.logger.error('查询订单信息异常: %s' % e) times += 1 continue times += 1 if times == 10: self.logger.error('未知错误,程序终止') break else: self.logger.info('撤销成功') self.logger.info('更新订单状态') try: order_info = self.future_client.order_info( self.symbol, self.contract_type, 0, orderid, 1, 5) order_info = order_info['orders'][0] self.logger.info('订单状态: %s' % order_info['status']) except Exception as e: self.logger.error('查询订单状态异常: %s,程序终止' % e) break print order_info future_deal_contract_amount = order_info['deal_amount'] # 未完成任何委托 if future_deal_contract_amount == 0: continue future_deal_price = order_info['price_avg'] # fee精度是8位,fee默认是负数,改成习惯的正数 future_deal_fee = -1 * float('%.8f' % order_info['fee']) # btc精度是8 future_deal_btc_amount = float( '%.8f' % (future_deal_contract_amount * 10 / future_deal_price)) self.bull_amount += future_deal_contract_amount self.keep_deposit -= future_deal_btc_amount self.keep_deposit -= future_deal_fee self.logger.info( 'future_deal_contract:%d\tbtc:%s\tfee:%s\tprice:%s' % (future_deal_contract_amount, future_deal_btc_amount, future_deal_fee, future_deal_price)) # 市价卖出现货 self.logger.info('现货卖出') spot_limited_price = spot_bids[0][0] - 20 spot_btc_amount = float( '%.4f' % (100 * future_deal_contract_amount / spot_limited_price)) spot_order = self.huobi_client.send_order( spot_btc_amount, 'api', 'btcusdt', 'sell-limit', spot_limited_price) print spot_order if spot_order['status'] != 'ok': if spot_order['status'] == 'fail': self.logger.error('spot sell failed : %s' % spot_order['msg']) else: self.logger.error('spot sell failed : %s' % spot_order['err-msg']) self.logger.info('开始回滚') self.logger.info('终止程序') break orderid = spot_order['data'] times = 0 while times < 20: self.logger.info('第%s次确认订单信息' % (times + 1)) order_info = self.huobi_client.order_info(orderid) print order_info if order_info['status'] == 'ok' and order_info['data'][ 'state'] == 'filled': self.logger.info('spot sell filled, orderId: %s' % orderid) field_amount = float( '%.8f' % float(order_info['data']['field-amount'])) field_cash_amount = float( '%.8f' % float(order_info['data']['field-cash-amount'])) field_fees = float('%.8f' % (field_cash_amount * 0.002)) break times += 1 if times == 19: time.sleep(15) if times == 20: self.logger.info('现货卖出错误, 终止程序') self.logger.info( 'spot_field_amount:%.8f\tspot_field_cash_amount:%.8f' % (field_amount, field_cash_amount)) # self.update_account_info() # 平空 if self.debug_type == 4 or future_asks[0][0] - spot_bids[0][ 0] < self.close_positive_thd: if self.bear_amount == 0: continue self.logger.info('期货平空,现货卖出') self.logger.info('期货价格: %s,现货价格 %s' % (future_asks[0][0], spot_bids[0][0])) self.logger.info('当前持空仓: %s' % self.bear_amount) spot_sum = np.sum(spot_bids[:3], axis=0) spot_std = np.std(spot_bids[:3], axis=0) if spot_sum[1] < 0.5 or spot_std[0] > 5: self.logger.info('现货btc不足或标准差过大') time.sleep(1) continue future_contract_amount = min( self.bear_amount, future_asks[0][1], spot_sum[1] * spot_bids[0][0] / 200) spot_limited_price = float(spot_asks[0][0]) - 20 spot_btc_amount = float( '%.4f' % (100 * future_contract_amount / spot_limited_price)) if spot_btc_amount > self.spot_free_btc: self.logger.info('现货BTC数量:%s, 不足:%s,本单取消' % (self.spot_free_btc, spot_btc_amount)) continue self.logger.info('期货平空%s' % future_contract_amount) price = future_asks[0][0] try: future_order = self.future_client.place_order( self.symbol, self.contract_type, price, future_contract_amount, '4', '0') except Exception as e: self.logger.error('Future订单异常: %s' % e) continue print future_order orderid = future_order['order_id'] try: order_info = self.future_client.order_info( self.symbol, self.contract_type, 0, orderid, 1, 5) except Exception as e: self.logger.error('查询订单信息异常:%s, 程序终止' % e) break order_info = order_info['orders'][0] self.logger.info('order status: %s' % order_info['status']) # 等待成交或未成交 if order_info['status'] == 0 or order_info['status'] == 1: self.logger.info('撤销未完成委托') try: self.future_client.cancel(self.symbol, self.contract_type, orderid) except Exception as e: self.logger.error('撤销异常: %s' % e) self.logger.info('更新订单状态') times = 0 while times < 10: try: order_info = self.future_client.order_info( self.symbol, self.contract_type, 0, orderid, 1, 5) order_info = order_info['orders'][0] self.logger.info('订单状态: %s' % order_info['status']) # 若撤销失败,状态必定为完全成交 if order_info['status'] == 2: break except Exception as e: self.logger.error('查询订单信息异常: %s' % e) times += 1 continue times += 1 if times == 10: self.logger.error('未知错误,程序终止') break else: self.logger.info('撤销成功') self.logger.info('更新订单状态') try: order_info = self.future_client.order_info( self.symbol, self.contract_type, 0, orderid, 1, 5) order_info = order_info['orders'][0] self.logger.info('订单状态: %s' % order_info['status']) except Exception as e: self.logger.error('查询订单信息异常: %s' % e) break print order_info future_deal_contract_amount = order_info['deal_amount'] # 未完成任何委托 if future_deal_contract_amount == 0: continue future_deal_price = order_info['price_avg'] # fee精度是8位,fee默认是负数,改成习惯的正数 future_deal_fee = -1 * float('%.8f' % order_info['fee']) # btc精度是8 future_deal_btc_amount = float( '%.8f' % (future_deal_contract_amount * 10 / future_deal_price)) self.bear_amount -= future_deal_contract_amount self.keep_deposit += future_deal_btc_amount self.keep_deposit -= future_deal_fee self.logger.info( 'future_deal:contract:%d\tbtc:%s\tfee:%s\tprice:%s' % (future_deal_contract_amount, future_deal_btc_amount, future_deal_fee, future_deal_price)) spot_limited_price = spot_bids[0][0] - 20 spot_btc_amount = float( '%.4f' % (100 * future_deal_contract_amount / spot_limited_price)) self.logger.info('现货卖出: %s' % spot_btc_amount) spot_order = self.huobi_client.send_order( spot_btc_amount, 'api', 'btcusdt', 'sell-limit', spot_limited_price) print spot_order if spot_order['status'] != 'ok': if spot_order['status'] == 'fail': self.logger.error('spot sell failed : %s' % spot_order['msg']) else: self.logger.error('spot sell failed : %s' % spot_order['err-msg']) self.logger.info('开始回滚') self.logger.info('终止程序') break orderid = spot_order['data'] times = 0 while times < 20: self.logger.info('第%s次确认订单信息' % (times + 1)) order_info = self.huobi_client.order_info(orderid) print order_info if order_info['status'] == 'ok' and order_info['data'][ 'state'] == 'filled': self.logger.info('spot sell filled, orderId: %s' % orderid) field_amount = float( '%.8f' % float(order_info['data']['field-amount'])) field_cash_amount = float( '%.8f' % float(order_info['data']['field-cash-amount'])) field_fees = float('%.8f' % (field_cash_amount * 0.002)) break times += 1 if times == 19: time.sleep(15) if times == 20: self.logger.info('现货卖出错误, 终止程序') break self.logger.info('field_amount:%.8f\tfield_cash_amount:%.8f' % (field_amount, field_cash_amount)) # self.update_account_info() # 计算当前收益,并短信通知 if self.bear_amount == 0: pass # 平多 if self.debug_type == 3 or future_bids[0][0] - spot_asks[0][ 0] > self.close_negetive_thd: if self.bull_amount == 0: continue self.logger.info('期货平多,现货买入') self.logger.info('期货价格: %s,现货价格 %s' % (future_bids[0][0], spot_asks[0][0])) self.logger.info('当前持多仓: %s' % self.bull_amount) spot_sum = np.sum(spot_asks[:3], axis=0) spot_std = np.std(spot_asks[:3], axis=0) if spot_sum[1] < 0.10 or spot_std[0] > 5: self.logger.info('现货btc不足或标准差过大') time.sleep(1) continue future_contract_amount = min( self.bull_amount, future_bids[0][1], spot_sum[1] * spot_asks[0][0] / 200) spot_usdt_amount = 100 * future_contract_amount if spot_usdt_amount > self.spot_free_usdt: self.logger.info('现货USDT数量:%s, 不足:%s,本单取消' % (self.spot_free_usdt, spot_usdt_amount)) continue self.logger.info('期货平多%s' % future_contract_amount) # 限价购买期货合约 price = future_bids[0][0] try: future_order = self.future_client.place_order( self.symbol, self.contract_type, price, future_contract_amount, '3', 0) except Exception as e: self.logger.error('Future订单异常: %s' % e) continue print future_order orderid = future_order['order_id'] try: order_info = self.future_client.order_info( self.symbol, self.contract_type, 0, orderid, 1, 5) except Exception as e: self.logger.error('查询订单信息异常:%s,程序终止' % e) break order_info = order_info['orders'][0] self.logger.info('order status: %s' % order_info['status']) # 等待成交或未成交 if order_info['status'] == 0 or order_info['status'] == 1: self.logger.info('撤销未完成委托') try: self.future_client.cancel(self.symbol, self.contract_type, orderid) except Exception as e: self.logger.error('撤销异常: %s' % e) self.logger.info('更新订单状态') times = 0 while times < 10: try: order_info = self.future_client.order_info( self.symbol, self.contract_type, 0, orderid, 1, 5) order_info = order_info['orders'][0] self.logger.info('订单状态: %s' % order_info['status']) # 若撤销失败,状态必定为完全成交 if order_info['status'] == 2: break except Exception as e: self.logger.error('查询订单信息异常: %s' % e) times += 1 continue times += 1 if times == 10: self.logger.error('未知错误,程序终止') break else: self.logger.info('撤销成功') self.logger.info('更新订单状态') try: order_info = self.future_client.order_info( self.symbol, self.contract_type, 0, orderid, 1, 5) order_info = order_info['orders'][0] self.logger.info('订单状态: %s' % order_info['status']) except Exception as e: self.logger.error('查询订单信息异常: %s' % e) break print order_info future_deal_contract_amount = order_info['deal_amount'] # 未完成任何委托 if future_deal_contract_amount == 0: continue future_deal_price = order_info['price_avg'] # fee精度是8位,fee默认是负数,改成习惯的正数 future_deal_fee = -1 * float('%.8f' % order_info['fee']) # btc精度是8 future_deal_btc_amount = float( '%.8f' % (future_deal_contract_amount * 10 / future_deal_price)) self.bull_amount -= future_deal_contract_amount self.keep_deposit += future_deal_btc_amount self.keep_deposit -= future_deal_fee self.logger.info( 'future_deal:contract:%d\tbtc:%s\tfee:%s\tprice:%s' % (future_deal_contract_amount, future_deal_btc_amount, future_deal_fee, future_deal_price)) self.logger.info('现货买入%s USDT' % spot_usdt_amount) spot_order = self.huobi_client.send_order( spot_usdt_amount, 'api', 'btcusdt', 'buy-market') if spot_order['status'] != 'ok': if spot_order['status'] == 'fail': self.logger.error('spot buy failed : %s' % spot_order['msg']) else: self.logger.error('spot buy failed : %s' % spot_order['err-msg']) self.logger.info('开始回滚') self.logger.info('终止程序') break orderid = spot_order['data'] times = 0 while times < 20: self.logger.info('第%s次确认订单信息' % (times + 1)) order_info = self.huobi_client.order_info(orderid) print order_info if order_info['status'] == 'ok' and order_info['data'][ 'state'] == 'filled': self.logger.info('huobi buy filled, orderId: %s' % orderid) field_cash_amount = order_info['data'][ 'field-cash-amount'] field_amount = order_info['data']['field-amount'] field_fees = order_info['data']['field-fees'] break times += 1 if times == 19: time.sleep(15) if times == 20: self.logger.info('现货买入错误, 终止程序') break self.logger.info( 'spot_field_amount:%.8f\tspot_field_cash_amount:%.8f' % (float(field_amount), float(field_cash_amount))) # self.update_account_info() # 计算当前收益,并短信通知 if self.bull_amount == 0: pass time.sleep(1)
class TermArbitrage: # 正表示期货价格高于现货 profit_rate = 0.05 slippage = 0.0002 # open_positive_rate = 40 close__thd = 0 # open_negative_thd = -40 # close_thd = 0 # 风险控制 margin_thd = 200 risk_thd = 0.5 margin_coefficient = 0.6 symbol = 'btc_usdt' contract_type = 'quarter' # 每次最大合约交易量 max_contract_exchange_amount = 5 # 1 开多 2 开空 3 平多 4 平空 debug_type = 0 status_dict = [''] def __init__(self): key_dict = {} # 读取配置文件 with open('config', 'r') as f: for line in f.readlines(): splited = line.split('=') if len(splited) == 2: key_dict[splited[0].strip()] = splited[1].strip() self.huobi_client = HuobiSpot(key_dict['HUOBI_ACCESS_KEY3'], key_dict['HUOBI_SECRET_KEY3']) self.future_client = OkexFutureClient(key_dict['OKEX_ACCESS_KEY'], key_dict['OKEX_SECRET_KEY']) # 空头合约数量 self.bear_amount = 0 # 多头合约数量 self.bull_amount = 0 # 保证金 self.keep_deposit = 0 self.risk_rate = 0 self.future_rights = 0 self.profit_real = 0 self.profit_unreal = 0 self.bond = 0 # 指数 self.future_index = 0 self.spot_free_btc = 0 self.spot_free_usdt = 0 self.spot_freezed_btc = 0 self.spot_freezed_usdt = 0 self.total_btc = 0 self.total_usdt = 0 # 统计收益 # config logging self.logger = logging.getLogger("Future") # 指定logger输出格式 formatter = logging.Formatter( '%(asctime)s %(levelname)-8s: %(message)s') # 文件日志 file_handler = logging.FileHandler("term.log") file_handler.setFormatter(formatter) # 控制台日志 console_handler = logging.StreamHandler(sys.stdout) console_handler.formatter = formatter # 为logger添加的日志处理器 self.logger.addHandler(file_handler) self.logger.addHandler(console_handler) # 指定日志的最低输出级别,默认为WARN级别 self.logger.setLevel(logging.INFO) # 平均成交差价 self.avg_price_diff = 0 # 2018/4/18 self.spot_kline = [] self.future_kline = [] def update_future_position(self): self.logger.info('全仓用户持仓查询') try: info = self.future_client.position(self.symbol, self.contract_type) except Exception as e: self.logger.error('全仓用户持仓查询异常: %s' % e) else: # print info if info['result']: holding = info['holding'] if len(holding) > 0: # hold是数组 self.bull_amount = holding[0]['buy_amount'] self.bear_amount = holding[0]['sell_amount'] if self.bull_amount == 0 and self.bear_amount == 0: self.logger.info('用户未持仓') self.total_btc = self.future_rights + self.spot_free_btc self.total_usdt = self.spot_free_usdt self.logger.info('total:') self.logger.info('BTC: %s\tUSDT: %s' % (self.total_btc, self.total_usdt)) else: self.logger.info('多仓: %s\t空仓: %s' % (self.bull_amount, self.bear_amount)) else: self.logger.info('postion_4fix result error') def update_spot_account(self): self.logger.info('获取SPOT账户信息') r = self.huobi_client.margin_balance('btcusdt') # print r if r['status'] == 'ok': for item in r['data'][0]['list']: if item['currency'] == 'btc' and item['type'] == 'trade': self.spot_free_btc = TermArbitrage.cut2_float( item['balance'], 8) elif item['currency'] == 'btc' and item['type'] == 'frozen': freezed_btc = float(item['balance']) elif item['currency'] == 'usdt' and item['type'] == 'trade': self.spot_free_usdt = TermArbitrage.cut2_float( item['balance'], 8) elif item['currency'] == 'usdt' and item['type'] == 'frozen': freezed_usdt = float(item['balance']) elif item['currency'] == 'btc' and item['type'] == 'loan': spot_loan_btc = float(item['balance']) elif item['currency'] == 'usdt' and item['type'] == 'loan': spot_loan_usdt = float(item['balance']) elif item['currency'] == 'btc' and item['type'] == 'interest': spot_interest_btc = float(item['balance']) elif item['currency'] == 'usdt' and item['type'] == 'interest': spot_interest_usdt = float(item['balance']) self.logger.info('spot_btc:%s\tspot_usdt:%s' % (self.spot_free_btc, self.spot_free_usdt)) else: if 'fail' == r['status']: self.logger.error('Huobi get_balance error: %s' % r['msg']) else: self.logger.error('Huobi get_balance error: %s' % r['err-msg']) def update_future_account(self, vertify=False): times = 0 while times < 10: old_bond = self.bond self.logger.info('第%s次获取全仓账户信息' % (times + 1)) try: future_info = self.future_client.userinfo() except Exception as e: self.logger.error('获取Future全仓账户信息异常: %s' % e) else: # print future_info if future_info['result']: btc_info = future_info['info']['btc'] print btc_info self.keep_deposit = btc_info['keep_deposit'] self.risk_rate = btc_info['risk_rate'] self.future_rights = btc_info['account_rights'] self.profit_real = btc_info['profit_real'] self.profit_unreal = btc_info['profit_unreal'] self.bond = self.keep_deposit + self.profit_unreal / 10 self.logger.info('bond:%s\trights:%s' % (self.bond, self.future_rights)) if not vertify or abs(self.bond - old_bond) > 1e-9: break times += 1 if times == 9: time.sleep(3) if times == 10: self.logger.info('账户信息未更新') @staticmethod def cut2_float(s, n): if isinstance(s, float): s = '{:.8f}'.format(s) pattern = re.compile(r'(\d+\.\d{1,%d})\d*' % n) return float(pattern.match(s).group(1)) def calc_available_contract(self, price): available_btc = self.future_rights * self.margin_coefficient - self.bond return int(available_btc * price / 10) if available_btc > 0 else 0 def judge_trend2(self): nowtime = time.strftime('%H:%M:%S', time.localtime(time.time())) second = int(nowtime.split(':')[2]) if 30 < second < 40: self.future_kline = [] self.spot_kline = [] # .logger.info('get spot kline') spot_kline = self.huobi_client.get_kline('btcusdt', '1min', 120) if spot_kline['status'] == 'ok': # print spot_kline for item in spot_kline['data']: self.spot_kline.append({ 'ts': item['id'], 'open': item['open'], 'close': item['close'], 'high': item['high'], 'low': item['low'], 'avg': float('%.2f' % (item['vol'] / item['amount'])) if item['amount'] > 0 else (item['high'] + item['low']) / 2 }) else: self.logger.info('get spot kline error') # self.logger.info('get future kline') try: future_kline = self.future_client.kline( self.symbol, '1min', self.contract_type) except Exception as e: self.logger.error('get future kline error: %s' % e) else: for item in future_kline[::-1][:120]: self.future_kline.append({ 'ts': item[0] / 1000, 'open': item[1], 'close': item[4], 'high': item[2], 'low': item[3], 'avg': float('%.2f' % (100 * item[5] / item[6])) if item[6] > 0 else (item[2] + item[3]) / 2 }) max_diff1 = -1 << 31 max_diff2 = -1 << 31 min_diff1 = (1 << 31) - 1 min_diff2 = (1 << 31) - 1 # print self.spot_kline # print self.future_kline if len(self.spot_kline) == 120 and len(self.future_kline) == 120 and self.future_kline[0]['ts'] == \ self.spot_kline[0]['ts']: cur_diff = self.future_kline[0]['avg'] - self.spot_kline[0][ 'avg'] for i in xrange(1, 60): diff = self.future_kline[i]['avg'] - self.spot_kline[i][ 'avg'] if diff > max_diff1: max_diff1 = diff elif diff < min_diff1: min_diff1 = diff max_diff2 = max_diff1 min_diff2 = min_diff1 for i in xrange(60, 120): diff = self.future_kline[i]['avg'] - self.spot_kline[i][ 'avg'] if diff > max_diff2: max_diff2 = diff elif diff < min_diff2: min_diff2 = diff print cur_diff, max_diff1, max_diff2 print cur_diff, min_diff1, min_diff2 if cur_diff < 0: if cur_diff - min_diff1 > 10 or cur_diff - min_diff2 > 15: # 下降 return 1 elif cur_diff - max_diff1 < -10 or cur_diff - max_diff2 < -15: # 上升 return 2 else: if cur_diff - max_diff1 < -10 or cur_diff - max_diff2 < -15: # 下降 return 3 elif cur_diff - min_diff1 > 10 or cur_diff - min_diff2 > 15: # 上升 return 4 return 0 def calc_exchange_contract_amount(self, spot_depth, future_depth): spot_amount, future_amount = 0, 0 for d in spot_depth: if abs(d[0] - spot_depth[0][0]) / spot_depth[0][0] <= self.slippage / 2: spot_amount += d[1] for d in future_depth: if abs(d[0] - future_depth[0][0]) / future_depth[0][0] <= self.slippage: future_amount += d[1] return min(int(spot_amount * spot_depth[0][0] / 200), int(future_amount / 2)) def go(self): while True: # print('获取深度') try: future_depth = self.future_client.depth( self.symbol, self.contract_type, 10) except Exception as e: self.logger.error('获取期货市场深度错误: %s' % e) time.sleep(3) continue future_bids = future_depth['bids'] future_asks = future_depth['asks'][::-1] spot_depth = self.huobi_client.get_depth('btcusdt', 'step5') # print spot_depth if spot_depth['status'] == 'ok': spot_bids = spot_depth['tick']['bids'] spot_asks = spot_depth['tick']['asks'] else: time.sleep(3) self.logger.error('获取现货市场深度错误') continue price_index = (future_bids[0][0] + spot_asks[0][0]) / 2 diff_thd = self.profit_rate * price_index d1 = future_bids[0][0] - spot_asks[0][0] d1 = float('%.2f' % d1) trend = self.judge_trend2() print d1, trend if abs(d1) > self.margin_thd: self.logger.info('期现差价超过阈值, 程序终止') break # 期货价格低于现货,且差价开始降低, 期货开多 if trend == 1 and spot_bids[0][0] - future_asks[0][0] > diff_thd: available_bull_amount = self.calc_available_contract( future_asks[0][0]) if available_bull_amount == 0: # self.logger.info('可开合约数量不足') time.sleep(3) continue self.logger.info('可开合约数量: %s' % available_bull_amount) self.logger.info('期货开多,现货卖出') self.logger.info('期货价格: %s,现货价格: %s,差价为: %s' % (future_asks[0][0], spot_bids[0][0], future_asks[0][0] - spot_bids[0][0])) self.logger.info( 'future_bond:%s\tspot_btc:%s\tspot_usdt:%s' % (self.bond, self.spot_free_btc, self.spot_free_usdt)) future_contract_amount = min( self.max_contract_exchange_amount, available_bull_amount, self.calc_exchange_contract_amount(spot_bids, future_asks), int(self.spot_free_btc * (spot_bids[0][0] - 20) / 100)) if future_contract_amount == 0: continue self.logger.info('期货开多:%s 张' % future_contract_amount) # 限价购买期货合约 price = float('%.2f' % (future_asks[0][0] * (1 + self.slippage))) try: future_order = self.future_client.place_order( self.symbol, self.contract_type, price, future_contract_amount, '1', '0') except Exception as e: self.logger.error('Future订单异常: %s' % e) continue print future_order orderid = future_order['order_id'] try: order_info = self.future_client.order_info( self.symbol, self.contract_type, 0, orderid, 1, 5) except Exception as e: self.logger.error('获取订单信息异常: %s, 程序终止' % e) break print order_info order_info = order_info['orders'][0] self.logger.info('order status: %s' % order_info['status']) # 等待成交或未成交 if order_info['status'] == 0 or order_info['status'] == 1: self.logger.info('撤销未完成委托') try: self.future_client.cancel(self.symbol, self.contract_type, orderid) except Exception as e: self.logger.error('撤销异常: %s' % e) else: self.logger.info('撤销成功') self.logger.info('更新订单状态') # status:-1:已撤销 0:未成交 1:部分成交 2:完全成交 4:撤单处理中 times = 0 while times < 20: self.logger.info('第%s次查询订单状态' % (times + 1)) try: order_info = self.future_client.order_info( self.symbol, self.contract_type, 0, orderid, 1, 5) print order_info order_info = order_info['orders'][0] self.logger.info('订单状态: %s' % order_info['status']) # 订单状态还有待确认 if order_info['status'] == 2 or order_info[ 'status'] == -1: break except Exception as e: self.logger.error('查询订单信息异常: %s' % e) times += 1 if times == 9: time.sleep(3) if times == 10: time.sleep(10) if times == 20: self.logger.error('未知错误,程序终止') break print order_info future_deal_contract_amount = order_info['deal_amount'] if future_deal_contract_amount == 0: self.logger.info('未完成任何委托') continue future_deal_price = order_info['price_avg'] # btc精度是8 future_deal_btc_amount = float( '%.8f' % (future_deal_contract_amount * 10.0 / future_deal_price)) self.bull_amount += future_deal_contract_amount self.logger.info('future_deal_contract:%d\tbtc:%s\tprice:%s' % (future_deal_contract_amount, future_deal_btc_amount, future_deal_price)) # 市价卖出现货 self.logger.info('现货卖出') spot_limited_price = spot_bids[0][0] - 20 spot_btc_amount = float( '%.4f' % (100 * future_deal_contract_amount / spot_limited_price)) spot_order = self.huobi_client.send_order( spot_btc_amount, 'margin-api', 'btcusdt', 'sell-limit', spot_limited_price) print spot_order if spot_order['status'] != 'ok': if spot_order['status'] == 'fail': self.logger.error('spot sell failed : %s' % spot_order['msg']) else: self.logger.error('spot sell failed : %s' % spot_order['err-msg']) self.logger.info('开始回滚') self.logger.info('终止程序') break orderid = spot_order['data'] field_cash_amount = 0 field_amount = 0 times = 0 while times < 20: self.logger.info('第%s次确认订单信息' % (times + 1)) order_info = self.huobi_client.order_info(orderid) print order_info if order_info['status'] == 'ok' and order_info['data'][ 'state'] == 'filled': self.logger.info('spot sell filled, orderId: %s' % orderid) field_amount = float( '%.8f' % float(order_info['data']['field-amount'])) field_cash_amount = float( '%.8f' % float(order_info['data']['field-cash-amount'])) field_price = float('%.2f' % (field_cash_amount / field_amount)) price_diff = future_deal_price - field_price self.avg_price_diff = ( (self.bull_amount - future_deal_contract_amount) * self.avg_price_diff + future_deal_contract_amount * price_diff) / self.bull_amount self.spot_free_btc -= field_amount self.spot_free_usdt += field_cash_amount break times += 1 if times == 19: time.sleep(15) if times == 20: self.logger.info('现货卖出错误, 终止程序') break self.logger.info( 'spot_field_amount:%.8f\tspot_field_cash_amount:%.8f' % (field_amount, field_cash_amount)) self.logger.info('avg_price_diff: %.2f' % self.avg_price_diff) self.update_future_account(True) # self.update_account_info() # 期货高于现货,且差价开始降低, 期货开空 elif trend == 3 and future_bids[0][0] - spot_asks[0][0] > diff_thd: available_bear_amount = self.calc_available_contract( future_bids[0][0]) if available_bear_amount == 0: # self.logger.info('可开合约数量不足') time.sleep(3) continue self.logger.info('可开合约数量为: %s' % available_bear_amount) self.logger.info('期货开空,现货买入') self.logger.info('期货价格: %s,现货价格: %s, 差价: %s' % (future_bids[0][0], spot_asks[0][0], future_bids[0][0] - spot_asks[0][0])) self.logger.info( 'future_bond:%s\tspot_btc:%s\tspot_usdt:%s' % (self.bond, self.spot_free_btc, self.spot_free_usdt)) future_contract_amount = min( self.calc_exchange_contract_amount(spot_asks, future_bids), self.max_contract_exchange_amount, available_bear_amount, int(self.spot_free_usdt / 100)) if future_contract_amount == 0: continue # 限价购买期货合约 self.logger.info('期货开空:%s' % future_contract_amount) price = float('%.2f' % (future_bids[0][0] * (1 - self.slippage))) try: future_order = self.future_client.place_order( self.symbol, self.contract_type, price, future_contract_amount, '2', '0') except Exception as e: self.logger.error('Future订单异常: %s' % e) continue print future_order orderid = future_order['order_id'] try: order_info = self.future_client.order_info( self.symbol, self.contract_type, 0, orderid, 1, 5) except Exception as e: self.logger.error('获取订单信息异常: %s,程序终止' % e) break print order_info order_info = order_info['orders'][0] self.logger.info('order status: %s' % order_info['status']) # 等待成交或未成交 if order_info['status'] == 0 or order_info['status'] == 1: self.logger.info('撤销未完成委托') try: self.future_client.cancel(self.symbol, self.contract_type, orderid) except Exception as e: self.logger.error('撤销异常: %s' % e) else: self.logger.info('撤销成功') self.logger.info('更新订单状态') # status:-1:已撤销 0:未成交 1:部分成交 2:完全成交 4:撤单处理中 times = 0 while times < 20: self.logger.info('第%s次查询订单状态' % (times + 1)) try: order_info = self.future_client.order_info( self.symbol, self.contract_type, 0, orderid, 1, 5) print order_info order_info = order_info['orders'][0] self.logger.info('订单状态: %s' % order_info['status']) # 订单状态还有待确认 if order_info['status'] == 2 or order_info[ 'status'] == -1: break except Exception as e: self.logger.error('查询订单信息异常: %s' % e) times += 1 if times == 9: time.sleep(3) if times == 19: time.sleep(10) if times == 20: self.logger.error('未知错误,程序终止') break print order_info future_deal_contract_amount = order_info['deal_amount'] if future_deal_contract_amount == 0: self.logger.info('未完成任何委托') continue future_deal_price = order_info['price_avg'] # btc精度是8 future_deal_btc_amount = float( '%.8f' % (future_deal_contract_amount * 10.0 / future_deal_price)) self.bear_amount += future_deal_contract_amount self.logger.info('future_deal:contract:%d\tbtc:%s\tprice:%s' % (future_deal_contract_amount, future_deal_btc_amount, future_deal_price)) # 市价买入现货 spot_usdt_amount = 100 * future_deal_contract_amount self.logger.info('现货买入 %sUSDT' % spot_usdt_amount) spot_order = self.huobi_client.send_order( spot_usdt_amount, 'margin-api', 'btcusdt', 'buy-market') if spot_order['status'] != 'ok': if spot_order['status'] == 'fail': self.logger.error('spot buy failed : %s' % spot_order['msg']) else: self.logger.error('spot buy failed : %s' % spot_order['err-msg']) # TODO self.logger.info('开始回滚') self.logger.info('终止程序') break orderid = spot_order['data'] field_cash_amount = 0 field_amount = 0 times = 0 while times < 20: self.logger.info('第%s次确认订单信息' % (times + 1)) order_info = self.huobi_client.order_info(orderid) print order_info if order_info['status'] == 'ok' and order_info['data'][ 'state'] == 'filled': self.logger.info('spot buy filled, orderId: %s' % orderid) field_amount = float( '%.8f' % float(order_info['data']['field-amount'])) field_cash_amount = float( '%.8f' % float(order_info['data']['field-cash-amount'])) field_price = float('%.2f' % (field_cash_amount / field_amount)) price_diff = future_deal_price - field_price self.avg_price_diff = ( (self.bear_amount - future_deal_contract_amount) * self.avg_price_diff + future_deal_contract_amount * price_diff) / self.bear_amount self.spot_free_btc += field_amount self.spot_free_usdt -= field_cash_amount break times += 1 if times == 19: time.sleep(15) if times == 20: self.logger.info('现货买入错误, 终止程序') break self.logger.info( 'spot_field_amount:%.8f\tspot_field_cash_amount:%.8f' % (float(field_amount), float(field_cash_amount))) self.logger.info('avg_price_diff: %.2f' % self.avg_price_diff) self.update_future_account(True) # 平空 if self.bear_amount > 0 and \ (self.debug_type == 4 or self.risk_rate < self.risk_thd or future_asks[0][0] - spot_bids[0][0] < 0 or (trend == 4 and self.avg_price_diff - future_asks[0][0] + spot_bids[0][0] > diff_thd)): self.logger.info('期货平空,现货卖出') self.logger.info('期货价格: %s,现货价格: %s,差价: %s' % (future_asks[0][0], spot_bids[0][0], future_asks[0][0] - spot_bids[0][0])) self.logger.info('当前持空仓: %s' % self.bear_amount) self.logger.info( 'future_bond:%s\tspot_btc:%s\tspot_usdt:%s' % (self.bond, self.spot_free_btc, self.spot_free_usdt)) future_contract_amount = min( self.max_contract_exchange_amount, self.bear_amount, self.calc_exchange_contract_amount(spot_bids, future_asks), int(self.spot_free_btc * (float(spot_asks[0][0]) - 20) / 100)) if future_contract_amount == 0: continue self.logger.info('期货平空: %s' % future_contract_amount) price = float('%.2f' % (future_asks[0][0] * (1 + self.slippage))) try: future_order = self.future_client.place_order( self.symbol, self.contract_type, price, future_contract_amount, '4', '0') except Exception as e: self.logger.error('Future订单异常: %s' % e) continue print future_order orderid = future_order['order_id'] try: order_info = self.future_client.order_info( self.symbol, self.contract_type, 0, orderid, 1, 5) except Exception as e: self.logger.error('查询订单信息异常:%s, 程序终止' % e) break order_info = order_info['orders'][0] self.logger.info('order status: %s' % order_info['status']) # 等待成交或未成交 if order_info['status'] == 0 or order_info['status'] == 1: self.logger.info('撤销未完成委托') try: self.future_client.cancel(self.symbol, self.contract_type, orderid) except Exception as e: self.logger.error('撤销异常: %s' % e) else: self.logger.info('撤销成功') self.logger.info('更新订单状态') # status:-1:已撤销 0:未成交 1:部分成交 2:完全成交 4:撤单处理中 times = 0 while times < 20: self.logger.info('第%s次查询订单状态' % (times + 1)) try: order_info = self.future_client.order_info( self.symbol, self.contract_type, 0, orderid, 1, 5) print order_info order_info = order_info['orders'][0] self.logger.info('订单状态: %s' % order_info['status']) # 订单状态还有待确认 if order_info['status'] == 2 or order_info[ 'status'] == -1: break except Exception as e: self.logger.error('查询订单信息异常: %s' % e) times += 1 if times == 9: time.sleep(3) if times == 19: time.sleep(10) if times == 20: self.logger.error('未知错误,程序终止') break print order_info future_deal_contract_amount = order_info['deal_amount'] # 未完成任何委托 if future_deal_contract_amount == 0: self.logger.info('未完成任何委托') continue future_deal_price = order_info['price_avg'] # btc精度是8 future_deal_btc_amount = float( '%.8f' % (future_deal_contract_amount * 10.0 / future_deal_price)) self.bear_amount -= future_deal_contract_amount self.logger.info('future_deal:contract:%d\tbtc:%s\tprice:%s' % (future_deal_contract_amount, future_deal_btc_amount, future_deal_price)) spot_limited_price = spot_bids[0][0] - 20 spot_btc_amount = float( '%.4f' % (100 * future_deal_contract_amount / spot_limited_price)) self.logger.info('现货卖出: %sUSDT' % (100 * future_deal_contract_amount)) spot_order = self.huobi_client.send_order( spot_btc_amount, 'margin-api', 'btcusdt', 'sell-limit', spot_limited_price) print spot_order if spot_order['status'] != 'ok': if spot_order['status'] == 'fail': self.logger.error('spot sell failed : %s' % spot_order['msg']) else: self.logger.error('spot sell failed : %s' % spot_order['err-msg']) self.logger.info('开始回滚') self.logger.info('终止程序') break orderid = spot_order['data'] field_amount = 0 field_cash_amount = 0 times = 0 while times < 20: self.logger.info('第%s次确认订单信息' % (times + 1)) order_info = self.huobi_client.order_info(orderid) print order_info if order_info['status'] == 'ok' and order_info['data'][ 'state'] == 'filled': self.logger.info('spot sell filled, orderId: %s' % orderid) field_amount = float( '%.8f' % float(order_info['data']['field-amount'])) field_cash_amount = float( '%.8f' % float(order_info['data']['field-cash-amount'])) self.spot_free_btc -= field_amount self.spot_free_usdt += field_cash_amount break times += 1 if times == 19: time.sleep(15) if times == 20: self.logger.info('现货卖出错误, 终止程序') break self.logger.info('field_amount:%.8f\tfield_cash_amount:%.8f' % (field_amount, field_cash_amount)) # 计算当前收益 if self.bear_amount == 0: time.sleep(15) self.update_spot_account() self.update_future_account() self.logger.info( 'total_btc: %s\tfuture_rights: %s\tspot_btc: %s' % (self.future_rights + self.spot_free_btc, self.future_rights, self.spot_free_btc)) # 平多 if self.bull_amount > 0 and \ (self.debug_type == 3 or self.risk_rate < self.risk_thd or future_bids[0][0] - spot_asks[0][0] > 0 or (trend == 3 and self.avg_price_diff - future_bids[0][0] + spot_asks[0][0] < -1 * diff_thd)): self.logger.info('期货平多,现货买入') self.logger.info('期货价格: %s,现货价格 %s, 差价: %s' % (future_bids[0][0], spot_asks[0][0], future_bids[0][0] - spot_asks[0][0])) self.logger.info('当前持多仓: %s' % self.bull_amount) self.logger.info( 'future_bond:%s\tspot_btc:%s\tspot_usdt:%s' % (self.bond, self.spot_free_btc, self.spot_free_usdt)) future_contract_amount = min( self.max_contract_exchange_amount, self.bull_amount, self.calc_exchange_contract_amount(spot_asks, future_bids), int(self.spot_free_usdt / 100)) if future_contract_amount == 0: continue self.logger.info('期货平多: %s' % future_contract_amount) # 限价购买期货合约 price = float('%.2f' % (future_bids[0][0] * (1 - self.slippage))) try: future_order = self.future_client.place_order( self.symbol, self.contract_type, price, future_contract_amount, '3', 0) except Exception as e: self.logger.error('Future订单异常: %s' % e) continue print future_order orderid = future_order['order_id'] try: order_info = self.future_client.order_info( self.symbol, self.contract_type, 0, orderid, 1, 5) except Exception as e: self.logger.error('查询订单信息异常: %s,程序终止' % e) break order_info = order_info['orders'][0] self.logger.info('order status: %s' % order_info['status']) # 等待成交或未成交 if order_info['status'] == 0 or order_info['status'] == 1: self.logger.info('撤销未完成委托') try: self.future_client.cancel(self.symbol, self.contract_type, orderid) except Exception as e: self.logger.error('撤销异常: %s' % e) else: self.logger.info('撤销成功') self.logger.info('更新订单状态') # status:-1:已撤销 0:未成交 1:部分成交 2:完全成交 4:撤单处理中 times = 0 while times < 20: self.logger.info('第%s次查询订单状态' % (times + 1)) try: order_info = self.future_client.order_info( self.symbol, self.contract_type, 0, orderid, 1, 5) print order_info order_info = order_info['orders'][0] self.logger.info('订单状态: %s' % order_info['status']) # 订单状态还有待确认 if order_info['status'] == 2 or order_info[ 'status'] == -1: break except Exception as e: self.logger.error('查询订单信息异常: %s' % e) times += 1 if times == 9: time.sleep(3) if times == 19: time.sleep(10) if times == 20: self.logger.error('未知错误,程序终止') break print order_info future_deal_contract_amount = order_info['deal_amount'] # 未完成任何委托 if future_deal_contract_amount == 0: self.logger.info('未完成任何委托') continue future_deal_price = order_info['price_avg'] # btc精度是8 future_deal_btc_amount = float( '%.8f' % (future_deal_contract_amount * 10.0 / future_deal_price)) self.bull_amount -= future_deal_contract_amount self.logger.info('future_deal:contract:%d\tbtc:%s\tprice:%s' % (future_deal_contract_amount, future_deal_btc_amount, future_deal_price)) # 现货买入 spot_usdt_amount = 100 * future_deal_contract_amount self.logger.info('现货买入: %sUSDT' % spot_usdt_amount) spot_order = self.huobi_client.send_order( spot_usdt_amount, 'margin-api', 'btcusdt', 'buy-market') if spot_order['status'] != 'ok': if spot_order['status'] == 'fail': self.logger.error('spot buy failed : %s' % spot_order['msg']) else: self.logger.error('spot buy failed : %s' % spot_order['err-msg']) self.logger.info('开始回滚') self.logger.info('终止程序') break orderid = spot_order['data'] field_cash_amount = 0 field_amount = 0 times = 0 while times < 20: self.logger.info('第%s次确认订单信息' % (times + 1)) order_info = self.huobi_client.order_info(orderid) print order_info if order_info['status'] == 'ok' and order_info['data'][ 'state'] == 'filled': self.logger.info('huobi buy filled, orderId: %s' % orderid) field_amount = float( '%.8f' % float(order_info['data']['field-amount'])) field_cash_amount = float( '%.8f' % float(order_info['data']['field-cash-amount'])) self.spot_free_btc += field_amount self.spot_free_usdt -= field_cash_amount break times += 1 if times == 19: time.sleep(15) if times == 20: self.logger.info('现货买入错误, 终止程序') break self.logger.info( 'spot_field_amount:%.8f\tspot_field_cash_amount:%.8f' % (float(field_amount), float(field_cash_amount))) # self.update_future_account() # 计算当前收益 if self.bull_amount == 0: time.sleep(15) self.update_spot_account() self.update_future_account() self.logger.info( 'total_btc: %s\tfuture_rights: %s\tspot_btc: %s' % (self.future_rights + self.spot_free_btc, self.future_rights, self.spot_free_btc)) time.sleep(1)
class ArbitrageStratety: # 手续费率 huobi_fee_rate = 0.0012 binance_fee_rate = 0.0005 bnb_price = 10.8159 # 盈利率 huobi_profit_rate = 0.0003 binance_profit_rate = 0.0003 # btc每次最大交易量 btc_exchange_min = 0.001 usdt_exchange_min = 10 # 程序里有3处需要同时更改 btc_exchange_max = 0.065 # HUOBI API最大连续超时次数 huobi_max_timeout = 3 # 深度参数阈值 STD_THD = 5 SUM_THD = 0.5 def __init__(self): key_dict = {} # 读取配置文件 with open('config', 'r') as f: for line in f.readlines(): splited = line.split('=') if len(splited) == 2: key_dict[splited[0].strip()] = splited[1].strip() self.output = open('history', 'a+') self.huobiSpot = HuobiSpot(key_dict['HUOBI_ACCESS_KEY2'], key_dict['HUOBI_SECRET_KEY2']) self.binanceClient = BinanceSpot(key_dict['BINANCE_ACCESS_KEY'], key_dict['BINANCE_SECRET_KEY']) self.btc_mat = "BTC :\tfree:{:<20.8f}locked:{:<20.8f}" self.usdt_mat = "USDT:\tfree:{:<20.8f}locked:{:<20.8f}" self.total_format = "BTC:{:<20.8f}USDT:{:<20.8f}" # config logging self.logger = logging.getLogger("Robot") # 指定logger输出格式 formatter = logging.Formatter('%(asctime)s %(levelname)-8s: %(message)s') # 文件日志 file_handler = logging.handlers.TimedRotatingFileHandler('log', when='midnight') # 设置日志文件后缀,以当前时间作为日志文件后缀名。 file_handler.suffix = "%Y-%m-%d" # 可以通过setFormatter指定输出格式 file_handler.setFormatter(formatter) # 控制台日志 console_handler = logging.StreamHandler(sys.stdout) console_handler.formatter = formatter # 也可以直接给formatter赋值 # 为logger添加的日志处理器 self.logger.addHandler(file_handler) self.logger.addHandler(console_handler) # 指定日志的最低输出级别,默认为WARN级别 self.logger.setLevel(logging.INFO) # 用于记录huobi连续响应过长时间的次数,超过两次,就退出 self.huobi_timeout = 0 # 收益统计 self.huobi_usdt_inc = 0 self.huobi_usdt_dec = 0 self.binance_usdt_inc = 0 self.binance_usdt_dec = 0 self.huobi_usdt_total_change = 0 self.binance_usdt_total_change = 0 # 账户余额 self.huobi_trade_btc = 0 self.huobi_trade_usdt = 0 self.binance_trade_btc = 0 self.binance_trade_usdt = 0 # 成交量统计 self.usdt_exchange_amount = 0 self.btc_exchange_amount = 0 self.last_deal_time = 0 # 由于数量小于0.001而未能成交 # >0 表示需要卖出的 self.untreated_btc = 0 @staticmethod def sms_notify(msg): url = 'http://221.228.17.88:8080/sendmsg/send' params = { 'phonenum': '18118999630', 'msg': msg } requests.get(url, params=params) def update_profit_rate(self): self.logger.info('更新盈利率') huobi_btc_percent = float('%.2f' % (self.huobi_trade_btc / (self.huobi_trade_btc + self.binance_trade_btc))) binance_btc_percent = 1 - huobi_btc_percent self.logger.info('Huobi: %s\tBinance:%s' % (huobi_btc_percent, binance_btc_percent)) if huobi_btc_percent < 0.1: self.huobi_profit_rate = 0.001 elif huobi_btc_percent < 0.2: self.huobi_profit_rate = 0.0007 elif huobi_btc_percent > 0.9: self.binance_profit_rate = 0.001 elif huobi_btc_percent > 0.8: self.binance_profit_rate = 0.0007 else: self.huobi_profit_rate = 0.0003 self.binance_profit_rate = 0.0003 self.logger.info('huobi_profit_rate: %s\t, binance_profit_rate: %s' % ( self.huobi_profit_rate, self.binance_profit_rate)) def update_account_info(self): # update binance self.logger.info('|--------------------------------------------------') self.logger.info('|' + '更新账户信息') try: account_info = self.binanceClient.get_account() except Exception as e: self.logger.error('Binance get_account error: %s' % e) else: freezed_btc = 0 freezed_usdt = 0 for info in account_info['balances']: if info['asset'] == 'BTC': self.binance_trade_btc = float(info['free']) freezed_btc = float(info['locked']) elif info['asset'] == 'USDT': self.binance_trade_usdt = float(info['free']) freezed_usdt = float(info['locked']) self.logger.info('|' + 'Binance:') self.logger.info('|' + self.btc_mat.format(self.binance_trade_btc, freezed_btc)) self.logger.info('|' + self.usdt_mat.format(self.binance_trade_usdt, freezed_usdt)) # update huobi # 修复了float进位的问题 json_r = self.huobiSpot.get_balance() if json_r['status'] == 'ok': for item in json_r['data']['list']: if item['currency'] == 'btc' and item['type'] == 'trade': self.huobi_trade_btc = ArbitrageStratety.cut2_float(item['balance'], 8) # self.huobi_trade_btc = float(item['balance']) elif item['currency'] == 'btc' and item['type'] == 'frozen': freezed_btc = float(item['balance']) elif item['currency'] == 'usdt' and item['type'] == 'trade': self.huobi_trade_usdt = ArbitrageStratety.cut2_float(item['balance'], 8) elif item['currency'] == 'usdt' and item['type'] == 'frozen': freezed_usdt = float(item['balance']) self.logger.info('|' + 'Huobi:') self.logger.info('|' + self.btc_mat.format(self.huobi_trade_btc, freezed_btc)) self.logger.info('|' + self.usdt_mat.format(self.huobi_trade_usdt, freezed_usdt)) self.logger.info('|' + 'Total:') self.logger.info('|' + self.total_format.format(self.binance_trade_btc + self.huobi_trade_btc, self.binance_trade_usdt + self.huobi_trade_usdt)) self.logger.info('|' + 'Untreated: %s' % self.untreated_btc) self.update_profit_rate() else: print json_r if 'fail' == json_r['status']: self.logger.error('Huobi get_balance error: %s' % json_r['msg']) else: self.logger.error('Huobi get_balance error: %s' % json_r['err-msg']) self.logger.info('|--------------------------------------------------') @staticmethod def merge_depth(depth): new_depth = [] for d in depth: price = ArbitrageStratety.cut2_float(d[0], 1) # price = float(re.match('(\d+\.\d)\d*', '{:.8f}'.format(float(d[0]))).group(1)) amount = float(d[1]) if len(new_depth) == 0: new_depth.append([price, amount]) else: if new_depth[-1][0] == price: new_depth[-1] = [price, new_depth[-1][1] + amount] else: new_depth.append([price, amount]) return new_depth[:5] @staticmethod def cut2_float(s, n): if isinstance(s, float): s = '{:.8f}'.format(s) pattern = re.compile(r'(\d+\.\d{1,%d})\d*' % n) return float(pattern.match(s).group(1)) def go(self): while True: # get depth info print '获取深度信息' h_depth = self.huobiSpot.get_depth('btcusdt', 'step5') if h_depth['status'] == 'ok': h_bids = h_depth['tick']['bids'] h_asks = h_depth['tick']['asks'] else: time.sleep(3) continue try: b_depth = self.binanceClient.get_order_book(symbol='BTCUSDT') except Exception as e: self.logger.error(u'获取Binance市场深度错误: %s' % e) time.sleep(3) continue # print b_depth # 需要合并深度 b_bids = ArbitrageStratety.merge_depth(b_depth['bids']) b_asks = ArbitrageStratety.merge_depth(b_depth['asks']) print b_asks # huobi sell if h_bids[0][0] * 1.0 / float( b_asks[0][0]) > 1 + self.huobi_fee_rate + self.binance_fee_rate + self.huobi_profit_rate: self.logger.info('binance买入,huobi卖出,') # print h_bids h_sum = np.sum(h_bids[:3], axis=0) h_std = np.std(h_bids[:3], axis=0) h_avg = np.mean(h_bids[:3], axis=0) a = [(float(i[0]), float(i[1])) for i in b_asks] b_sum = np.sum(a[:3], axis=0) b_std = np.std(a[:3], axis=0) b_avg = np.mean(a[:3], axis=0) # print a self.logger.info('ASKS:\tsum:%10.4f\tstd:%10.4f\tavg:%10.4f' % (h_sum[1], h_std[0], h_avg[0])) self.logger.info('BIDS:\tsum:%10.4f\tstd:%10.4f\tavg:%10.4f' % (b_sum[1], b_std[0], b_avg[0])) self.logger.info( '卖出价:%s, 买入价:%s, 比例:%s' % (h_bids[0][0], float(b_asks[0][0]), h_bids[0][0] * 1.0 / float( b_asks[0][0]))) if h_std[0] > self.STD_THD or h_sum[1] < self.SUM_THD: self.logger.info('标准差过大,本单取消') time.sleep(0.1) continue btc_amount = float( '%.4f' % min(h_bids[0][1], float(b_asks[0][1]), self.btc_exchange_max)) # Binance btc-amount 精度是6位,需要截取前4位,不能产生进位 if btc_amount > float(b_asks[0][1]): # btc_amount = float(re.match('(\d+\.\d{4})\d*', '{:.8f}'.format(float(b_asks[0][1]))).group(1)) btc_amount = ArbitrageStratety.cut2_float(b_asks[0][1], 4) if btc_amount > self.huobi_trade_btc: # btc_amount = float( # re.match('(\d+\.\d{4})\d*', '{:.8f}'.format(self.huobi_trade_btc)).group(1)) btc_amount = ArbitrageStratety.cut2_float(self.huobi_trade_btc, 4) order_price = float(b_asks[0][0]) + 1.1 usdt_amount = float('%.4f' % (btc_amount * order_price)) self.logger.info('本次交易量:%s BTC, %s USDT' % (btc_amount, usdt_amount)) if btc_amount < self.btc_exchange_min: self.logger.info('BTC交易数量不足: %s, 本单取消' % self.btc_exchange_min) time.sleep(1) continue if usdt_amount < self.usdt_exchange_min: self.logger.info('USDT交易数量不足: %s, 本单取消' % self.usdt_exchange_min) time.sleep(1) continue if usdt_amount > self.binance_trade_usdt - 5: self.logger.info('Binance USDT 数量: %s, 不足:%s, 本单取消' % (self.binance_trade_usdt, usdt_amount)) time.sleep(1) continue # 限价买 self.logger.info('开始限价买入') try: buy_order = self.binanceClient.order_limit_buy(symbol='BTCUSDT', quantity=btc_amount, price=order_price, newOrderRespType='FULL') except Exception as e: self.logger.error(u'Binance买入错误: %s' % e) time.sleep(3) continue print buy_order buy_order_id = buy_order['orderId'] self.output.write('\n' + str(buy_order_id)) self.output.flush() self.logger.info('binance buy orderId: %s, state: %s' % (buy_order_id, buy_order['status'])) field_cash_amount = 0 field_amount = 0 if buy_order['status'] == 'NEW' or buy_order['status'] == 'PARTIALLY_FILLED': self.logger.info('撤消未完成委托') try: cancel_r = self.binanceClient.cancel_order(symbol='BTCUSDT', orderId=buy_order_id) print cancel_r # get_my_trades有超时问题,暂时不用此函数 except Exception as e: self.logger.error(u'撤销错误: %s' % e) else: self.logger.info('撤销成功') # 有可能会出现撤销成功,但是撤销完成的过程中,又完成了部分委托,需要更新实际成交量 self.logger.info('更新成交量') times = 0 while times < 10: self.logger.info(u'第%s次查询Binance订单状态' % (times + 1)) try: order = self.binanceClient.get_order(symbol='BTCUSDT', orderId=buy_order_id) self.logger.info(u'当前订单状态为: %s', order['status']) # 撤销成功,状态必定为CANCELED, 撤销成功则为FILLED if order['status'] == 'CANCELED' or order['status'] == 'FILLED': field_amount = float(order['executedQty']) price = float(order['price']) field_cash_amount = float('%.8f' % (field_amount * price)) break except Exception as e: self.logger.error(u'Binance get order error: %s' % e) times += 1 if times == 10: self.logger.info('未知错误,程序终止') break # filled elif buy_order['status'] == 'FILLED': fills = buy_order['fills'] for f in fills: price = float('%.2f' % float(f['price'])) qty = float('%.8f' % float(f['qty'])) field_amount += qty field_cash_amount += price * qty else: self.logger.info('订单状态异常: %s' % buy_order['status']) if field_amount == 0: self.logger.info('未完成任何委托') continue self.logger.info('field_amount:%.8f\tfield_cash_amount:%.8f' % ( float(field_amount), float(field_cash_amount))) self.binance_trade_btc += field_amount self.binance_trade_usdt -= field_cash_amount self.binance_usdt_dec = field_cash_amount self.binance_usdt_total_change -= self.binance_usdt_dec # 市价卖 self.logger.info('开始市价卖出') btc_amount = float('%.4f' % field_amount) # 记录总共损失的BTC数目,达到0.001时候,进行补全 if btc_amount < self.btc_exchange_min: self.logger.info('BTC卖出数量低于 %s', self.btc_exchange_min) self.logger.info('本次交易终止') self.untreated_btc += field_amount # self.rollback_binance_order(buy_order_id) time.sleep(3) continue # 买入卖出由于精度不同,会存在一定的偏差,这里进行统计调整 else: self.untreated_btc += field_amount - btc_amount sell_order = self.huobiSpot.send_order(btc_amount, 'api', 'btcusdt', 'sell-market') if sell_order['status'] != 'ok': if sell_order['status'] == 'fail': self.logger.error('sell failed : %s' % sell_order['msg']) else: self.logger.error('sell failed : %s' % sell_order['err-msg']) self.logger.info('开始回滚') self.rollback_binance_order(buy_order_id) self.logger.info('终止程序') break sell_order_id = sell_order['data'] self.output.write(':' + str(sell_order_id)) self.output.flush() times = 0 while times < 20: self.logger.info('第%s次确认订单信息' % (times + 1)) sell_order_info = self.huobiSpot.order_info(sell_order_id) print sell_order_info if sell_order_info['status'] == 'ok' and sell_order_info['data']['state'] == 'filled': self.logger.info('huobi sell filled, orderId: %s' % sell_order_id) field_cash_amount = sell_order_info['data']['field-cash-amount'] field_amount = sell_order_info['data']['field-amount'] break times += 1 if times == 19: time.sleep(15) if times == 20: self.huobi_timeout += 1 if self.huobi_timeout == self.huobi_max_timeout: self.logger.info('连续%s次超时,终止程序' % self.huobi_timeout) break else: self.logger.info('连续%s次超时,继续程序' % self.huobi_timeout) if self.huobi_timeout == 1: time.sleep(60) else: time.sleep(600) self.btc_exchange_max /= 2 continue else: self.huobi_timeout = 0 self.btc_exchange_max = 0.065 self.logger.info('field_amount:%.8f\tfield_cash_amount:%.8f' % ( float(field_amount), float(field_cash_amount))) self.huobi_trade_btc -= float(field_amount) self.huobi_trade_usdt += float(field_cash_amount) # 更新交易量统计 self.usdt_exchange_amount += float(field_cash_amount) self.btc_exchange_amount += float(field_amount) self.huobi_usdt_inc = float(field_cash_amount) self.huobi_usdt_total_change += self.huobi_usdt_inc usdt_inc = self.huobi_usdt_inc - self.binance_usdt_dec thistime_earnings = usdt_inc thistime_earnings_rate = thistime_earnings * 1.0 / float(field_cash_amount) total_usdt_earnings = self.huobi_usdt_total_change + self.binance_usdt_total_change self.logger.info('本次交易量: %s BTC, 盈利: %s USDT, 盈利率: %s' % ( float(field_amount), thistime_earnings, thistime_earnings_rate)) self.logger.info( '总BTC成交量: %s, 盈利: %s USDT, 盈利率: %s' % ( self.btc_exchange_amount, total_usdt_earnings, total_usdt_earnings * 1.0 / self.usdt_exchange_amount)) self.logger.info('|--------------------------------------------------') self.logger.info('|' + ' BTC:\tTOTAL:{:<20.8f}HUOBI:{:<20.8f}BINANCE:{:<20.8f}'.format( (self.huobi_trade_btc + self.binance_trade_btc), self.huobi_trade_btc, self.binance_trade_btc)) self.logger.info( '|' + 'USDT:\tTOTAL:{:<20.8f}HUOBI:{:<20.8f}BINANCE:{:<20.8f}'.format( (self.huobi_trade_usdt + self.binance_trade_usdt), self.huobi_trade_usdt, self.binance_trade_usdt)) self.logger.info('|--------------------------------------------------') self.update_profit_rate() self.last_deal_time = int(time.time()) # binance sell elif float(b_bids[0][0]) / h_asks[0][ 0] > 1 + self.huobi_fee_rate + self.binance_fee_rate + self.binance_profit_rate: self.logger.info('binance 卖出, huobi买入') b = [(float(i[0]), float(i[1])) for i in b_bids] print b b_sum = np.sum(b[:3], axis=0) b_std = np.std(b[:3], axis=0) b_avg = np.mean(b[:3], axis=0) print h_asks h_sum = np.sum(h_asks[:3], axis=0) h_std = np.std(h_asks[:3], axis=0) h_avg = np.mean(h_asks[:3], axis=0) self.logger.info('ASKS:\tsum:%10.4f\tstd:%10.4f\tavg:%10.4f' % (b_sum[1], b_std[0], b_avg[0])) self.logger.info('BIDS:\tsum:%10.4f\tstd:%10.4f\tavg:%10.4f' % (h_sum[1], h_std[0], h_avg[0])) self.logger.info( '卖出价:%s, 买入价:%s, 比例:%s' % (float(b_bids[0][0]), h_asks[0][0], float(b_bids[0][0]) / h_asks[0][0])) if h_std[0] > self.STD_THD or h_sum[1] < self.SUM_THD: self.logger.info('标准差过大,本单取消') time.sleep(0.1) continue order_price = b_bids[0][0] - 1.1 btc_amount = float('%.4f' % min(float(b_bids[0][1]), h_asks[0][1], self.btc_exchange_max)) if btc_amount > float(b_bids[0][1]): # = float(re.match('(\d+\.\d{4})\d*', '{:.8f}'.format(float(b_bids[0][1]))).group(1)) btc_amount = ArbitrageStratety.cut2_float(b_bids[0][1], 4) if btc_amount > self.binance_trade_btc: # btc_amount = float( # re.match('(\d+\.\d{4})\d*', '{:.8f}'.format(self.binance_trade_btc)).group(1)) btc_amount = ArbitrageStratety.cut2_float(self.binance_trade_btc, 4) usdt_amount = float('%.4f' % (btc_amount * h_asks[0][0])) self.logger.info('本次交易量:%s BTC, %s USDT' % (btc_amount, usdt_amount)) if btc_amount < self.btc_exchange_min: self.logger.info('BTC交易数量不足: %s, 本单取消' % self.btc_exchange_min) time.sleep(1) continue if float('%.4f' % (btc_amount * order_price)) < self.usdt_exchange_min: self.logger.info('USDT交易数量不足: %s, 本单取消' % self.usdt_exchange_min) time.sleep(1) continue if usdt_amount > self.huobi_trade_usdt - 5: self.logger.info('Huobi USDT 数量: %s, 不足:%s, 本单取消' % (self.huobi_trade_usdt, usdt_amount)) time.sleep(1) continue # 限价卖 self.logger.info('开始限价卖出') try: sell_r = self.binanceClient.order_limit_sell(symbol='BTCUSDT', quantity=btc_amount, price=order_price, newOrderRespType='FULL') except Exception as e: self.logger.error(u'Binance卖出错误: %s' % e) time.sleep(3) continue print sell_r sell_order_id = sell_r['orderId'] self.output.write('\n' + str(sell_order_id)) self.output.flush() self.logger.info('binance sell orderId: %s, state: %s' % (sell_order_id, sell_r['status'])) field_cash_amount = 0 field_amount = 0 if sell_r['status'] == 'NEW' or sell_r['status'] == 'PARTIALLY_FILLED': # 撤销未完成订单 self.logger.info('撤消未完成委托') try: cancel_r = self.binanceClient.cancel_order(symbol='BTCUSDT', orderId=sell_order_id) print cancel_r except Exception as e: self.logger.error(u'撤销错误: %s' % e) else: self.logger.info('撤销成功') self.logger.info('更新成交量') times = 0 while times < 10: self.logger.info(u'第%s次查询Binance订单状态' % (times + 1)) try: order = self.binanceClient.get_order(symbol='BTCUSDT', orderId=sell_order_id) print order self.logger.info(u'当前订单状态为: %s', order['status']) # 撤销成功,状态必定为CANCELED, 撤销成功则为FILLED if order['status'] == 'CANCELED' or order['status'] == 'FILLED': field_amount = float(order['executedQty']) price = float(order['price']) field_cash_amount = float('%.8f' % (field_amount * price)) break except Exception as e: self.logger.error(u'Binance get order error: %s' % e) times += 1 if times == 10: self.logger.info('未知错误,程序终止') break # filled elif sell_r['status'] == 'FILLED': fills = sell_r['fills'] for f in fills: price = float('%.2f' % float(f['price'])) qty = float('%.8f' % float(f['qty'])) field_amount += qty field_cash_amount += price * qty else: self.logger.info('订单状态异常: %s' % sell_r['status']) if field_amount == 0: self.logger.info('未完成任何委托') continue # 更新统计数据 self.btc_exchange_amount += float(field_amount) self.usdt_exchange_amount += float(field_cash_amount) # update income self.binance_usdt_inc = field_cash_amount self.binance_usdt_total_change += self.binance_usdt_inc self.logger.info('field_amount:%.8f\tfield_cash_amount:%.8f' % ( field_amount, field_cash_amount)) self.binance_trade_btc -= field_amount self.binance_trade_usdt += field_cash_amount # 限价买,使买价高于市价,最后会已市价成交 self.logger.info('开始伪市价(限价)买入') buy_price = h_asks[0][0] + 20 btc_amount = float('%.4f' % field_amount) if btc_amount < self.btc_exchange_min: self.logger.error('BTC交易数量低于 %s' % self.btc_exchange_min) self.logger.info('本次交易终止,开始回滚') self.untreated_btc -= field_amount time.sleep(3) continue else: self.untreated_btc -= field_amount - btc_amount buy_r = self.huobiSpot.send_order(btc_amount, 'api', 'btcusdt', 'buy-limit', buy_price) print buy_r if buy_r['status'] != 'ok': if buy_r['status'] == 'fail': self.logger.error('buy failed : %s' % buy_r['msg']) else: self.logger.error('buy failed : %s' % buy_r['err-msg']) self.logger.info('开始回滚') self.rollback_binance_order(sell_order_id) self.logger.info('终止程序') break buy_order_id = buy_r['data'] self.output.write(':' + str(buy_order_id)) self.output.flush() times = 0 while times < 20: self.logger.info('第%s次确认订单信息' % (times + 1)) buy_order_result = self.huobiSpot.order_info(buy_order_id) print buy_order_result if buy_order_result['status'] == 'ok' and buy_order_result['data']['state'] == 'filled': self.logger.info('huobi buy filled, orderId: %s' % buy_order_id) field_amount = float('%.8f' % float(buy_order_result['data']['field-amount'])) field_cash_amount = float('%.8f' % float(buy_order_result['data']['field-cash-amount'])) break times += 1 if times == 19: time.sleep(15) if times == 20: self.huobi_timeout += 1 if self.huobi_timeout == self.huobi_max_timeout: self.logger.info('连续%s次超时,终止程序' % self.huobi_timeout) break else: self.logger.info('连续%s次超时,继续程序' % self.huobi_timeout) if self.huobi_timeout == 1: time.sleep(60) else: time.sleep(600) # self.btc_exchange_max /= 2 continue else: self.huobi_timeout = 0 self.btc_exchange_max = 0.065 self.logger.info('field_amount:%.8f\tfield_cash_amount:%.8f' % ( field_amount, field_cash_amount)) # update income self.huobi_usdt_dec = field_cash_amount self.huobi_usdt_total_change -= self.huobi_usdt_dec self.huobi_trade_btc += field_amount self.huobi_trade_usdt -= field_cash_amount # total usdt_inc = self.binance_usdt_inc - self.huobi_usdt_dec thistime_earnings = usdt_inc thistime_earnings_rate = thistime_earnings * 1.0 / field_cash_amount # total_btc_earnings = self.huobi_btc_total_change + self.binance_btc_total_change total_usdt_earnings = self.huobi_usdt_total_change + self.binance_usdt_total_change self.logger.info('本次交易量: %s BTC, 盈利: %s USDT, 盈利率: %s' % ( float(field_amount), thistime_earnings, thistime_earnings_rate)) self.logger.info( '总BTC成交量: %s, 盈利: %s USDT, 盈利率: %s' % ( self.btc_exchange_amount, total_usdt_earnings, total_usdt_earnings * 1.0 / self.usdt_exchange_amount)) self.logger.info('|--------------------------------------------------') self.logger.info('|' + ' BTC:\tTOTAL:{:<20.8f}HUOBI:{:<20.8f}BINANCE:{:<20.8f}'.format( (self.huobi_trade_btc + self.binance_trade_btc), self.huobi_trade_btc, self.binance_trade_btc)) self.logger.info( '|' + 'USDT:\tTOTAL:{:<20.8f}HUOBI:{:<20.8f}BINANCE:{:<20.8f}'.format( (self.huobi_trade_usdt + self.binance_trade_usdt), self.huobi_trade_usdt, self.binance_trade_usdt)) self.logger.info('|--------------------------------------------------') self.update_profit_rate() self.last_deal_time = int(time.time()) time.sleep(0.1) nowtime = time.strftime('%H:%M:%S', time.localtime(time.time())) if nowtime.startswith('08:30'): self.order_statistics() self.usdt_exchange_amount = 0 self.btc_exchange_amount = 0 self.huobi_usdt_total_change = 0 self.binance_usdt_total_change = 0 time.sleep(60) # 每5分钟没有交易就更新账户信息 if self.last_deal_time > 0 and int(time.time()) - self.last_deal_time > 300: orderid = 0 if self.untreated_btc > 0.001: sell_amount = float('%.4f' % self.untreated_btc) self.logger.info('平衡账户资产,Huobi卖出: %s BTC' % sell_amount) sell_r = self.huobiSpot.send_order(sell_amount, 'api', 'btcusdt', 'sell-market') if sell_r['status'] != 'ok': if sell_r['status'] == 'fail': self.logger.error('sell failed : %s' % sell_r['msg']) else: self.logger.error('sell failed : %s' % sell_r['err-msg']) break else: orderid = sell_r['data'] self.untreated_btc -= sell_amount elif self.untreated_btc < -0.001: buy_price = h_asks[0][0] + 20 buy_amount = float('%.4f' % self.untreated_btc) self.logger.info('平衡账户资产,Huobi买入: %s BTC' % buy_amount) buy_r = self.huobiSpot.send_order(-1 * buy_amount, 'api', 'btcusdt', 'buy-limit', buy_price) if buy_r['status'] != 'ok': if buy_r['status'] == 'fail': self.logger.error('sell failed : %s' % buy_r['msg']) else: self.logger.error('sell failed : %s' % buy_r['err-msg']) break else: orderid = buy_r['data'] self.untreated_btc -= buy_amount print orderid if orderid: times = 0 while times < 20: self.logger.info('第%s次确认订单信息' % (times + 1)) order_result = self.huobiSpot.order_info(orderid) print order_result if order_result['status'] == 'ok' and order_result['data']['state'] == 'filled': self.logger.info('order filled, orderId: %s' % orderid) field_amount = float('%.8f' % float(order_result['data']['field-amount'])) field_cash_amount = float('%.8f' % float(order_result['data']['field-cash-amount'])) if 'buy' in order_result['data']['type']: self.huobi_trade_btc += field_amount self.huobi_trade_usdt -= field_cash_amount else: self.huobi_trade_btc -= field_amount self.huobi_trade_usdt += field_cash_amount break times += 1 if times == 9: time.sleep(10) if times == 19: time.sleep(300) if times == 20: self.logger.error('未知错误,程序终止') break total_btc_amount_before = self.binance_trade_btc + self.huobi_trade_btc self.logger.info('before: binance:%s\thuobi: %s' % (self.binance_trade_btc, self.huobi_trade_btc)) self.update_account_info() self.logger.info('after : binance:%s\thuobi: %s' % (self.binance_trade_btc, self.huobi_trade_btc)) total_btc_amount_after = self.binance_trade_btc + self.huobi_trade_btc if abs(total_btc_amount_after - total_btc_amount_before) > 0.001: self.logger.info('账户BTC总量发生异常,程序终止') break self.last_deal_time = 0 def rollback_binance_order(self, orderid): order_info = self.binanceClient.get_order(symbol='BTCUSDT', orderId=orderid) side = order_info['side'].upper() field_amount = float('%.6f' % float(order_info['executedQty'])) price = float('%.2f' % float(order_info['price'])) if side == 'BUY': try: order = self.binanceClient.order_limit_sell(symbol='BTCUSDT', quantity=field_amount, price=price - 5, newOrderRespType='FULL') print order except Exception as e: self.logger.error(u'Binance卖出错误: %s, 回滚失败' % e) else: try: order = self.binanceClient.order_limit_buy(symbol='BTCUSDT', quantity=field_amount, price=price + 5, newOrderRespType='FULL') print order except Exception as e: self.logger.error(u'Binance买入错误: %s, 回滚失败' % e) # HUOBI API 提供的只能获取100条,此函数为扩展,提取500条 def get_huobi_orders(self): result = [] last_order_id = None for i in range(5): orders_list = self.huobiSpot.orders_list('btcusdt', '', _from=last_order_id, size=100) if orders_list['status'] != 'ok': print ('获取火币历史委托错误') print orders_list return [] else: if i == 0: for item in orders_list['data']: result.append(item) else: for item in orders_list['data'][1:]: result.append(item) last_order_id = orders_list['data'][-1]['id'] return result def order_statistics(self, start=None, end=None): huobi_order_list = [] binance_order_list = [] # Huobi for order in self.get_huobi_orders(): dict = { 'id': order['id'], 'state': order['state'].upper(), 'amount': float('%.8f' % float(order['amount'])), 'field-cash-amount': float('%.4f' % float(order['field-cash-amount'])), 'field-amount': float('%.8f' % float(order['field-amount'])), 'created-at': order['created-at'], 'finished-at': order['finished-at'], 'canceled-at': order['canceled-at'], 'type': u'LIMIT' if 'limit' in order['type'] else u'MARKET', 'side': u'BUY' if 'buy' in order['type'] else u'SELL', } # print dict if dict['finished-at'] == 0: continue if dict['field-amount'] > 0: dict['price'] = float('%.2f' % (dict['field-cash-amount'] / dict['field-amount'])) huobi_order_list.append(dict) print('获取币安历史委托数据') binance_orders = self.binanceClient.get_all_orders(symbol='BTCUSDT') binance_trades = self.binanceClient.get_my_trades(symbol='BTCUSDT', recvWindow=130000) # print binance_trades for order in binance_orders: dict = { 'id': order['orderId'], 'state': order['status'].upper(), 'amount': float('%.8f' % float(order['origQty'])), 'created-at': order['time'], 'finished-at': '', 'canceled-at': '', 'type': order['type'].upper(), 'side': order['side'].upper(), 'commission': 0, 'field-amount': 0, 'field-cash-amount': 0 } binance_order_list.append(dict) for item in binance_order_list: id = item['id'] for trade in binance_trades: if trade['orderId'] == id: item['commission'] += float('%.8f' % float(trade['commission'])) item['field-amount'] += float('%.8f' % float(trade['qty'])) item['price'] = float('%.2f' % float(trade['price'])) item['field-cash-amount'] += float('%.8f' % (float(trade['qty']) * item['price'])) huobi_order_list = sorted(huobi_order_list, key=lambda x: x['created-at'], reverse=True) binance_order_list = sorted(binance_order_list, key=lambda x: x['created-at'], reverse=True) # print huobi_order_list # print binance_order_list yestoday = datetime.date.today() + datetime.timedelta(days=-1) timearray = time.strptime(str(yestoday) + ' 8:30:00', "%Y-%m-%d %H:%M:%S") timestamp = int(round(time.mktime(timearray)) * 1000) print timestamp workbook = xlsxwriter.Workbook('output.xlsx') worksheet = workbook.add_worksheet(u'成功') worksheet2 = workbook.add_worksheet(u'失败') worksheet3 = workbook.add_worksheet(u'总计') date_format_str = 'yy/mm/dd/ hh:mm:ss' binance_common_format = workbook.add_format({'align': 'left', 'font_name': 'Consolas'}) binance_date_format = workbook.add_format({'num_format': date_format_str, 'align': 'left', 'font_name': 'Consolas'}) huobi_common_format = workbook.add_format({'align': 'left', 'font_name': 'Consolas', 'bg_color': 'yellow'}) huobi_date_format = workbook.add_format({'num_format': date_format_str, 'align': 'left', 'font_name': 'Consolas', 'bg_color': 'yellow'}) merged_format = workbook.add_format({'align': 'center', 'valign': 'vcenter', 'font_name': 'Consolas'}) row_1 = 0 row_2 = 0 row_3 = 0 col = 0 header = [u'委托时间', u'方向', u'类型', u'价格', u'委托数量', u'成交数量', u'成交金额', u'状态', u'盈利(USDT)', u'盈利率'] total_usdt_earnings = 0 total_huobi_usdt_trade = 0 total_binance_commission = 0 total_btc_exchange = 0 i = 0 for h in header: worksheet.write(row_1, col + i, h) worksheet2.write(row_2, col + i, h) i += 1 row_1 += 1 row_2 += 1 with open('history', 'r') as f: for line in f.readlines(): if len(line) < 5: continue splited = line.strip().split(':') huobi_id = 0 if len(splited) == 2: binance_id = int(splited[0]) huobi_id = int(splited[1]) elif len(splited) == 1: binance_id = int(splited[0]) else: continue # print binance_id, huobi_id if binance_id > 0 and huobi_id > 0: binance_order = None huobi_order = None for order in binance_order_list: if order['created-at'] < timestamp: continue if order['id'] == binance_id: binance_order = order break for order in huobi_order_list: if order['created-at'] < timestamp: continue if order['id'] == huobi_id: huobi_order = order break if not binance_order or not huobi_order: continue # print binance_id, huobi_id total_huobi_usdt_trade += float('%.8f' % float(huobi_order['field-cash-amount'])) total_binance_commission += float('%.8f' % float(binance_order['commission'])) total_btc_exchange += float('%.8f' % float(binance_order['field-amount'])) order = huobi_order worksheet.write(row_1, col, datetime.datetime.fromtimestamp( float(str(order['created-at'])[0:-3] + '.' + str(order['created-at'])[-3:0])), huobi_date_format) worksheet.write(row_1, col + 1, order['side'], huobi_common_format) worksheet.write(row_1, col + 2, order['type'], huobi_common_format) worksheet.write(row_1, col + 3, '%.2f' % float(order['price']), huobi_common_format) worksheet.write(row_1, col + 4, '%.4f' % float(order['amount']), huobi_common_format) worksheet.write(row_1, col + 5, '%.8f' % float(order['field-amount']), huobi_common_format) worksheet.write(row_1, col + 6, '%.8f' % float(order['field-cash-amount']), huobi_common_format) worksheet.write(row_1, col + 7, order['state'], huobi_common_format) row_1 += 1 order = binance_order worksheet.write_datetime(row_1, col, datetime.datetime.fromtimestamp( float(str(order['created-at'])[0:-3] + '.' + str(order['created-at'])[-3:0])), binance_date_format) worksheet.write(row_1, col + 1, order['side'], binance_common_format) worksheet.write(row_1, col + 2, order['type'], binance_common_format) worksheet.write(row_1, col + 3, '%.2f' % float(order['price']), binance_common_format) worksheet.write(row_1, col + 4, '%.4f' % float(order['amount']), binance_common_format) worksheet.write(row_1, col + 5, '%.8f' % float(order['field-amount']), binance_common_format) worksheet.write(row_1, col + 6, '%.8f' % float(order['field-cash-amount']), binance_common_format) worksheet.write(row_1, col + 7, order['state'], binance_common_format) earnings = abs(float(huobi_order['field-cash-amount']) - float(binance_order['field-cash-amount'])) earning_rate = earnings / binance_order['field-cash-amount'] total_usdt_earnings += earnings worksheet.merge_range(row_1 - 1, 8, row_1, 8, '%.8f' % earnings, merged_format) worksheet.merge_range(row_1 - 1, 9, row_1, 9, '%.8f' % earning_rate, merged_format) row_1 += 1 else: for order in binance_order_list: # print order['created-at'] if order['created-at'] < timestamp: continue if order['id'] == binance_id and order['field-amount'] > 0: print order if order['id'] == binance_id and 'CANCELED' in order['state']: worksheet2.write_datetime(row_2, col, datetime.datetime.fromtimestamp( float(str(order['created-at'])[0:-3] + '.' + str(order['created-at'])[-3:0])), binance_date_format) worksheet2.write(row_2, col + 1, order['side'], binance_common_format) worksheet2.write(row_2, col + 2, order['type'], binance_common_format) worksheet2.write(row_2, col + 3, 0, binance_common_format) worksheet2.write(row_2, col + 4, '%.4f' % float(order['amount']), binance_common_format) worksheet2.write(row_2, col + 5, 0, binance_common_format) worksheet2.write(row_2, col + 6, 0, binance_common_format) worksheet2.write(row_2, col + 7, order['state'], binance_common_format) row_2 += 1 break total_huobi_commission = total_huobi_usdt_trade * 0.002 total_binance_commission = total_binance_commission * 10.8159 # print total_huobi_commission, total_binance_commission header = [u'BTC总量', u'BTC成交量', u'USDT总量' u'USDT盈亏', u'HUOBI手续费', u'BINANCE手续费'] i = 0 for h in header: worksheet3.write(row_3, i, h) i += 1 row_3 += 1 worksheet3.write(row_3, 0, total_btc_exchange) worksheet3.write(row_3, 1, total_usdt_earnings) worksheet3.write(row_3, 2, total_huobi_commission) worksheet3.write(row_3, 3, total_binance_commission) workbook.close() # 发送邮件 self.logger.info('邮件通知') ArbitrageStratety.send_mail_with_attachment() @staticmethod def send_mail_with_attachment(): from email import encoders from email.header import Header from email.mime.base import MIMEBase from email.mime.multipart import MIMEMultipart from email.utils import parseaddr, formataddr from email.mime.text import MIMEText import smtplib def _format_addr(s): name, addr = parseaddr(s) return formataddr(( \ Header(name, 'utf-8').encode(), \ addr.encode('utf-8') if isinstance(addr, unicode) else addr)) from_addr = '*****@*****.**' username = '******' password = '******' to_addr = '*****@*****.**' smtp_server = 'smtp.163.com' print 'sending mail to [email protected]' msg = MIMEMultipart() msg['From'] = _format_addr(from_addr) msg['To'] = _format_addr(to_addr) msg['Subject'] = u'收益情况(%s)' % (time.strftime('%Y-%m-%d', time.localtime(time.time()))) msg.attach(MIMEText('send with file...', 'plain', 'utf-8')) # add file: with open('output.xlsx', 'rb') as f: mime = MIMEBase('text', 'txt', filename='output.xlsx') mime.add_header('Content-Disposition', 'attachment', filename='output.xlsx') mime.add_header('Content-ID', '<0>') mime.add_header('X-Attachment-Id', '0') mime.set_payload(f.read()) encoders.encode_base64(mime) msg.attach(mime) server = smtplib.SMTP() server.connect(smtp_server) server.login(username, password) server.sendmail(from_addr, [to_addr], msg.as_string()) server.quit()