def context_setting(self): """ API trading and quote context setting :returns: trade context, quote context """ if self.unlock_password == "": raise Exception("请先配置交易解锁密码! password: {}".format(self.unlock_password)) quote_ctx = OpenQuoteContext(host=self.api_svr_ip, port=self.api_svr_port) if 'HK.' in self.stock: trade_ctx = OpenHKTradeContext(host=self.api_svr_ip, port=self.api_svr_port) if self.trade_env == 0: ret_code, ret_data = trade_ctx.unlock_trade(self.unlock_password) if ret_code == 0: print('解锁交易成功!') else: print("请求交易解锁失败, 请确认解锁密码! password: {}".format(self.unlock_password)) elif 'US.' in self.stock: if self.trade_env != 0: raise Exception("美股交易接口不支持仿真环境 trade_env: {}".format(self.trade_env)) trade_ctx = OpenUSTradeContext(host=self.api_svr_ip, port=self.api_svr_port) else: raise Exception("stock输入错误 stock: {}".format(self.stock)) return quote_ctx, trade_ctx
def __init__(self, host, port, trd_env, market, unlock_path=ct.FUTU_PATH): if market != ct.CN_MARKET_SYMBOL and market != ct.US_MARKET_SYMBOL and market != ct.HK_MARKET_SYMBOL: raise Exception("not supported market:%s" % market) if ct.CN_MARKET_SYMBOL == market: self.trd_ctx = OpenCNTradeContext(host, port) elif ct.US_MARKET_SYMBOL == market: self.trd_ctx = OpenUSTradeContext(host, port) else: self.trd_ctx = OpenHKTradeContext(host, port) self.trd_env = trd_env self.acc_id = self.get_acc_id() self.unlock_pwd = self.get_unlock_pwd(fpath=unlock_path) self._status = True self._lock = threading.Lock()
def connectTrade(self): """连接交易功能""" # 连接交易接口 if self.market == 'US': self.tradeCtx = OpenUSTradeContext(self.host, self.port) else: self.tradeCtx = OpenHKTradeContext(self.host, self.port) # 继承实现处理器类 class OrderHandler(TradeOrderHandlerBase): """委托处理器""" gateway = self # 缓存Gateway对象 def on_recv_rsp(self, rsp_str): ret_code, content = super(OrderHandler, self).on_recv_rsp(rsp_str) if ret_code != RET_OK: return RET_ERROR, content self.gateway.processOrder(content) return RET_OK, content class DealHandler(TradeDealHandlerBase): """订单簿处理器""" gateway = self def on_recv_rsp(self, rsp_str): ret_code, content = super(DealHandler, self).on_recv_rsp(rsp_str) if ret_code != RET_OK: return RET_ERROR, content self.gateway.processDeal(content) return RET_OK, content # 只有港股实盘交易才需要解锁 code, data = self.tradeCtx.unlock_trade(self.password) if code == RET_OK: self.writeLog(u'交易接口解锁成功') else: self.writeLog(u'交易接口解锁失败,原因:%s' % data) # 设置回调处理对象 self.tradeCtx.set_handler(OrderHandler()) self.tradeCtx.set_handler(DealHandler()) # 启动交易接口 self.tradeCtx.start() self.writeLog(u'交易接口连接成功')
def connectTrade(self): """连接交易功能""" # 连接交易接口 if self.market == 'US': self.tradeCtx = OpenUSTradeContext(self.host, self.port) else: self.tradeCtx = OpenHKTradeContext(self.host, self.port) # 继承实现处理器类 class OrderHandler(TradeOrderHandlerBase): """委托处理器""" gateway = self # 缓存Gateway对象 def on_recv_rsp(self, rsp_str): ret_code, content = super(OrderHandler, self).on_recv_rsp(rsp_str) if ret_code != RET_OK: return RET_ERROR, content self.gateway.processOrder(content) return RET_OK, content class DealHandler(TradeDealHandlerBase): """订单簿处理器""" gateway = self def on_recv_rsp(self, rsp_str): ret_code, content = super(DealHandler, self).on_recv_rsp(rsp_str) if ret_code != RET_OK: return RET_ERROR, content self.gateway.processDeal(content) return RET_OK, content # 只有港股实盘交易才需要解锁 code, data = self.tradeCtx.unlock_trade(self.password) if code == RET_OK: self.writeLog(u'交易接口解锁成功') else: self.writeLog(u'交易接口解锁失败,原因:%s' %data) # 设置回调处理对象 self.tradeCtx.set_handler(OrderHandler()) self.tradeCtx.set_handler(DealHandler()) # 启动交易接口 self.tradeCtx.start() self.writeLog(u'交易接口连接成功')
def __init__(self, env, mod_config): self._env = env self._mod_config = mod_config self._portfolio = None self._open_order = [] self._env.event_bus.add_listener(EVENT.PRE_BEFORE_TRADING, self._pre_before_trading) self._env.event_bus.add_listener(EVENT.PRE_AFTER_TRADING, self._pre_after_trading) # futu api创建及参数 self._trade_context = OpenHKTradeContext(self._mod_config.api_svr.ip, self._mod_config.api_svr.port) self._trade_envtype = 1 # futu交易 envtype : 0 = 实盘 1 = 仿真 if IsRuntype_RealTrade(): self._trade_envtype = 0 thread_order_check = Thread(target=self._thread_order_check) thread_order_check.setDaemon(True) thread_order_check.start()
class FutuTrader: def __init__(self, host, port, trd_env, market, unlock_path=ct.FUTU_PATH): if market != ct.CN_MARKET_SYMBOL and market != ct.US_MARKET_SYMBOL and market != ct.HK_MARKET_SYMBOL: raise Exception("not supported market:%s" % market) if ct.CN_MARKET_SYMBOL == market: self.trd_ctx = OpenCNTradeContext(host, port) elif ct.US_MARKET_SYMBOL == market: self.trd_ctx = OpenUSTradeContext(host, port) else: self.trd_ctx = OpenHKTradeContext(host, port) self.trd_env = trd_env self.acc_id = self.get_acc_id() self.unlock_pwd = self.get_unlock_pwd(fpath=unlock_path) self._status = True self._lock = threading.Lock() def __del__(self): if self.trd_ctx is not None: self.trd_ctx.close() def start(self): if self.trd_ctx is not None: self.trd_ctx.start() def get_acc_id(self): ret, data = self.trd_ctx.get_acc_list() if ret != 0: raise Exception("get accid failed") return data.loc[data.trd_env == self.trd_env, 'acc_id'].values[0] def get_unlock_pwd(self, fpath=ct.FUTU_PATH): with open(fpath) as f: infos = json.load(f) return infos['unlock_pwd'] def get_cash(self): ret, data = self.trd_ctx.accinfo_query(trd_env=self.trd_env) if ret != 0: raise Exception("get cash failed") return data['cash'].values[0] def set_handler(self, mclass): with self._lock: self.trd_ctx.set_handler(mclass) def get_shares(self): mshares = dict() ret, data = self.trd_ctx.position_list_query(trd_env=self.trd_env) if ret != 0: raise Exception("get shares failed") for index, code in data.code.iteritems(): mshares[code] = data.loc[index, 'qty'] return mshares def get_order(self, id_="", filter_list=list()): orders = list() ret, data = self.trd_ctx.order_list_query( trd_env=TrdEnv.SIMULATE, order_id=id_, status_filter_list=filter_list) if ret != 0: raise Exception( "get opend order failed. order_id:%s, filter_list:%s" % (id_, filter_list)) if data.empty: return orders data = data[[ 'order_id', 'order_type', 'order_status', 'code', 'trd_side', 'qty', 'price', 'create_time', 'dealt_qty', 'dealt_avg_price', 'updated_time' ]] for mdict in data.to_dict("records"): orders.append(MOrder(mdict)) return orders if id_ != "" else orders[0] def trade(self, order): code = order.getInstrument() price = order.getLimitPrice() quantity = order.getQuantity() type_ = OrderType.NORMAL direction = TrdSide.BUY if order.isBuy() else TrdSide.SELL ret, data = self.trd_ctx.place_order(price, quantity, code, trd_side=direction, order_type=type_, adjust_limit=0, trd_env=self.trd_env, acc_id=self.acc_id) if ret != 0: logger.error("trade failed, ret:%s, data:%s" % (ret, data)) #logger.info("trade %s success, ret:%s, data:%s" % (code, ret, data)) return ret, data def buy(self, code, price, quantity): if self.trd_env == TrdEnv.REAL: self.trd_ctx.unlock_trade(password_md5=self.unlock_pwd) ret, data = self.trd_ctx.place_order(code=code, price=price, qty=quantity, trd_side=TrdSide.BUY, order_type=OrderType.NORMAL, trd_env=self.trd_env) return ret, data def modify(self, order, operation): id_ = order.getId() price = order.getLimitPrice() quantity = order.getQuantity() type_ = order.getOrderType() side = order.getOrderDirection() ret, data = self.trd_ctx.modify_order(operation, id_, quantity, price, adjust_limit=0, trd_env=self.trd_env, acc_id=self.acc_id) return ret, data def close(self): with self._lock: self.trd_ctx.close() self._status = False def status(self): with self._lock: return self._status
class FUTUBrokerHK(AbstractBroker): """ FUTUBrokerHK 对象用于对接futu港股的仿真和真实交易 设计思路: 1. 帐户的初始资金需要在rqalpha框架下的config中设置 config.base.stock_starting_cash 不与futu的帐户信息同步, 一方面是不影响长期自动运行时计算的收益率等指标,另一方面也为了控制策略脚本对futu实际帐户资金的占用. 2. 初始化时会同步一次futu帐户的持仓数据, 后续状态完全由rqalpha框架内部维护状态, 故策略中记录的持仓有可能与用户实际futu帐户不一致 3. 下单 ,撤单调后,脚本中会定时检查该订单在futu环境中的状态, 产生对应的event事件,可能存在延时。 """ def __init__(self, env, mod_config): self._env = env self._mod_config = mod_config self._portfolio = None self._open_order = [] self._env.event_bus.add_listener(EVENT.PRE_BEFORE_TRADING, self._pre_before_trading) self._env.event_bus.add_listener(EVENT.PRE_AFTER_TRADING, self._pre_after_trading) # futu api创建及参数 self._trade_context = OpenHKTradeContext(self._mod_config.api_svr.ip, self._mod_config.api_svr.port) self._trade_envtype = 1 # futu交易 envtype : 0 = 实盘 1 = 仿真 if IsRuntype_RealTrade(): self._trade_envtype = 0 thread_order_check = Thread(target=self._thread_order_check) thread_order_check.setDaemon(True) thread_order_check.start() def get_portfolio(self): """ 获取投资组合。系统初始化时,会调用此接口,获取包含账户信息、净值、份额等内容的投资组合 :return: Portfolio """ if self._portfolio is not None: return self._portfolio self._portfolio = self._init_portfolio() if not self._portfolio._accounts: raise RuntimeError("accout config error") return self._portfolio def submit_order(self, order): """ 提交订单。在当前版本,RQAlpha 会生成 :class:`~Order` 对象,再通过此接口提交到 Broker。 TBD: 由 Broker 对象生成 Order 并返回? """ print("FUTUBrokerHK.submit_order:{}".format(order)) if order.type == ORDER_TYPE.MARKET: raise RuntimeError("submit_order not support ORDER_TYPE.MARKET") account = self._get_account(order.order_book_id) self._env.event_bus.publish_event( Event(EVENT.ORDER_PENDING_NEW, account=account, order=order)) order.active() # 发起futu api接口请求 futu_order_side = 0 if order.side == SIDE.BUY else 1 futu_order_type = 0 # 港股增强限价单 ret_code, ret_data = self._trade_context.place_order( order.price, order.quantity, order.order_book_id, futu_order_side, futu_order_type, self._trade_envtype) # 事件通知 if ret_code != 0: order.mark_rejected("futu api req err:{} ".format(ret_code)) self._env.event_bus.publish_event( Event(EVENT.ORDER_CREATION_REJECT, account=account, order=order)) else: futu_order_id = ret_data.loc[0, 'orderid'] self._open_order.append((futu_order_id, order)) self._env.event_bus.publish_event( Event(EVENT.ORDER_CREATION_PASS, account=account, order=order)) sleep(0.1) self._check_open_orders(futu_order_id) def cancel_order(self, order): """ 撤单。 :param order: 订单 :type order: :class:`~Order` """ account = self._get_account(order.order_book_id) futu_order_id = self._get_futu_order_id(order) if futu_order_id is None: return # 立即检查一次订单状态 self._check_open_orders(futu_order_id) if order.is_final(): return self._env.event_bus.publish_event( Event(EVENT.ORDER_PENDING_CANCEL, account=account, order=order)) ret_code, ret_data = self._trade_context.set_order_status( 0, futu_order_id, self._env) # 0 = 撤单 if ret_code != 0: self._env.event_bus.publish_event( Event(EVENT.ORDER_CANCELLATION_REJECT, account=account, order=order)) else: sleep(0.1) self._check_open_orders(futu_order_id) # 提交请求后,立即再检查一次状态 def get_open_orders(self, order_book_id=None): """ [Required] 获得当前未完成的订单。 :return: list[:class:`~Order`] """ if order_book_id is None: return [order for __, order in self._open_orders] else: return [ order for __, order in self._open_orders if order.order_book_id == order_book_id ] def _pre_before_trading(self, event): print("broker before_trading") def _pre_after_trading(self, event): # 收盘时清掉未完成的订单 for __, order in self._open_order: order.mark_rejected( _(u"Order Rejected: {order_book_id} can not match. Market close." ).format(order_book_id=order.order_book_id)) account = self._env.get_account(order.order_book_id) self._env.event_bus.publish_event( Event(EVENT.ORDER_UNSOLICITED_UPDATE, account=account, order=order)) self._open_orders = [] print("broker after_trading") def _check_open_orders(self, futu_order_id=None): if len(self._open_order) == 0: return ret_code, pd_data = self._trade_context.order_list_query( '', self._trade_envtype) if ret_code != 0: return ft_orders = [] if futu_order_id is not None: ft_orders.append(futu_order_id) else: for (fid, __) in self._open_order: ft_orders.append(fid) for fid in ft_orders: pd_find = pd_data[pd_data.orderid == fid] if len(pd_find) != 1: continue order = self._get_order_by_futu_id(fid) account = self._get_account(order.order_book_id) if order is None: continue ct_amount = 0 # 期货用的,期货分平当天的仓位和以前的仓位 price = order.avg_price # 分多笔成交下的平均值 trade = Trade.__from_create__( order_id=order.order_id, price=price, amount=0, side=order.side, position_effect=order.position_effect, order_book_id=order.order_book_id, frozen_price=order.frozen_price, close_today_amount=ct_amount, commission=0., tax=0., trade_id=None) trade._commission = 0 trade._tax = 0 row = pd_find.iloc[0] ft_status = int(row['status']) if ft_status == 2 or ft_status == 3: # 部分成交 | 全部成交 qty_deal_last = order.quantity - order.unfilled_quantity qty_deal_new = int(row['dealt_qty']) if qty_deal_last == qty_deal_new: # 记录的成交数量与上次相同 continue trade._amount = qty_deal_new - qty_deal_last order.fill(trade) self._env.event_bus.publish_event( Event(EVENT.TRADE, account=account, trade=trade, order=order)) if ft_status == 3: self._remove_open_order_by_futu_id(fid) elif ft_status == 5: # 下单失败 self._env.event_bus.publish_event( Event(EVENT.ORDER_CREATION_REJECT, account=account, order=order)) self._remove_open_order_by_futu_id(fid) elif ft_status == 6: # 6=已撤单 order.mark_cancelled( _(u"{order_id} order has been cancelled by user.").format( order_id=order.order_id)) self._env.event_bus.publish_event( Event(EVENT.ORDER_CANCELLATION_PASS, account=account, order=order)) self._remove_open_order_by_futu_id(fid) elif ft_status == 4 or ft_status == 7: # 4=已失效 7=已删除 reason = _( u"Order Cancelled: code = {order_book_id} ft_status = {ft_status} " ).format(order_book_id=order.order_book_id, ft_status=ft_status) order.mark_rejected(reason) self._env.event_bus.publish_event( Event(EVENT.ORDER_CREATION_REJECT, account=account, order=order)) self._remove_open_order_by_futu_id(fid) else: pass # 8 = 等待开盘 21= 本地已发送 22=本地已发送,服务器返回下单失败、没产生订单 23=本地已发送,等待服务器返回超时 def _get_futu_positions(self, env): StockPosition = env.get_position_model( FUTU_ACCOUNT_TYPE.FUTU_STOCK.name) positions = Positions(StockPosition) ret, pd_data = self._trade_context.position_list_query( self._trade_envtype) if ret != 0: return None for i in range(len(pd_data)): row = pd_data.iloc[i] code_str = str(row['code']) pos_state = {} pos_state['order_book_id'] = code_str pos_state['quantity'] = int(row['qty']) pos_state['avg_price'] = float(row['cost_price']) pos_state['non_closable'] = 0 pos_state['frozen'] = int(row['qty']) - int(row['can_sell_qty']) pos_state['transaction_cost'] = 0 item = positions.get_or_create(code_str) item.set_state(pos_state) return positions def _init_portfolio(self): accounts = {} config = self._env.config start_date = config.base.start_date total_cash = 0 for account_type, stock_starting_cash in six.iteritems( config.base.accounts): if account_type == FUTU_ACCOUNT_TYPE.FUTU_STOCK.name: # stock_starting_cash = config.base.accounts if stock_starting_cash == 0: raise RuntimeError( _(u"stock starting cash can not be 0, using `--stock-starting-cash 1000`" )) all_positons = self._get_futu_positions(self._env) if all_positons is None: raise RuntimeError("_init_portfolio fail") StockAccount = self._env.get_account_model( FUTU_ACCOUNT_TYPE.FUTU_STOCK.name) accounts[FUTU_ACCOUNT_TYPE.FUTU_STOCK.name] = StockAccount( stock_starting_cash, all_positons) total_cash += stock_starting_cash else: raise NotImplementedError return Portfolio(start_date, 1, total_cash, accounts) def _get_account(self, order_book_id): # account = self._env.get_account(order_book_id) # for debug account = self._env.portfolio.accounts[ FUTU_ACCOUNT_TYPE.FUTU_STOCK.name] return account def _get_futu_order_id(self, order): for fid, order_item in self._open_order: if order_item is order: return fid return None def _get_order_by_futu_id(self, futu_order_id): for fid, order_item in self._open_order: if futu_order_id == fid: return order_item return None def _remove_open_order_by_futu_id(self, futu_order_id): order = self._get_order_by_futu_id(futu_order_id) if order is not None: self._open_order.remove((futu_order_id, order)) def _thread_order_check(self): while True: if len(self._open_order) == 0: print("broker:_thread_order_check None") sleep(5) else: self._check_open_orders() sleep(1)
class FutuGateway(VtGateway): """富途接口""" #---------------------------------------------------------------------- def __init__(self, eventEngine, gatewayName='FUTU'): """Constructor""" super(FutuGateway, self).__init__(eventEngine, gatewayName) self.quoteCtx = None self.tradeCtx = None self.host = '' self.ip = 0 self.market = '' self.password = '' self.env = TrdEnv.SIMULATE # 默认仿真交易 self.fileName = self.gatewayName + '_connect.json' self.filePath = getJsonPath(self.fileName, __file__) self.tickDict = {} self.tradeSet = set() # 保存成交编号的集合,防止重复推送 self.qryEnabled = True self.qryThread = Thread(target=self.qryData) #---------------------------------------------------------------------- def writeLog(self, content): """输出日志""" log = VtLogData() log.gatewayName = self.gatewayName log.logContent = content self.onLog(log) #---------------------------------------------------------------------- def writeError(self, code, msg): """输出错误""" error = VtErrorData() error.gatewayName = self.gatewayName error.errorID = code error.errorMsg = msg self.onError(error) #---------------------------------------------------------------------- def connect(self): """连接""" # 载入配置 try: f = open(self.filePath) setting = json.load(f) self.host = setting['host'] self.port = setting['port'] self.market = setting['market'] self.password = setting['password'] self.env = setting['env'] except: self.writeLog(u'载入配置文件出错') return self.connectQuote() self.connectTrade() self.qryThread.start() #---------------------------------------------------------------------- def qryData(self): """初始化时查询数据""" # 等待2秒保证行情和交易接口启动完成 sleep(2.0) # 查询合约、成交、委托、持仓、账户 self.qryContract() self.qryTrade() self.qryOrder() self.qryPosition() self.qryAccount() # 启动循环查询 self.initQuery() #---------------------------------------------------------------------- def connectQuote(self): """连接行情功能""" self.quoteCtx = OpenQuoteContext(self.host, self.port) # 继承实现处理器类 class QuoteHandler(StockQuoteHandlerBase): """报价处理器""" gateway = self # 缓存Gateway对象 def on_recv_rsp(self, rsp_str): ret_code, content = super(QuoteHandler, self).on_recv_rsp(rsp_str) if ret_code != RET_OK: return RET_ERROR, content self.gateway.processQuote(content) return RET_OK, content class OrderBookHandler(OrderBookHandlerBase): """订单簿处理器""" gateway = self def on_recv_rsp(self, rsp_str): ret_code, content = super(OrderBookHandler, self).on_recv_rsp(rsp_str) if ret_code != RET_OK: return RET_ERROR, content self.gateway.processOrderBook(content) return RET_OK, content # 设置回调处理对象 self.quoteCtx.set_handler(QuoteHandler()) self.quoteCtx.set_handler(OrderBookHandler()) # 启动行情 self.quoteCtx.start() self.writeLog(u'行情接口连接成功') #---------------------------------------------------------------------- def connectTrade(self): """连接交易功能""" # 连接交易接口 if self.market == 'US': self.tradeCtx = OpenUSTradeContext(self.host, self.port) else: self.tradeCtx = OpenHKTradeContext(self.host, self.port) # 继承实现处理器类 class OrderHandler(TradeOrderHandlerBase): """委托处理器""" gateway = self # 缓存Gateway对象 def on_recv_rsp(self, rsp_str): ret_code, content = super(OrderHandler, self).on_recv_rsp(rsp_str) if ret_code != RET_OK: return RET_ERROR, content self.gateway.processOrder(content) return RET_OK, content class DealHandler(TradeDealHandlerBase): """订单簿处理器""" gateway = self def on_recv_rsp(self, rsp_str): ret_code, content = super(DealHandler, self).on_recv_rsp(rsp_str) if ret_code != RET_OK: return RET_ERROR, content self.gateway.processDeal(content) return RET_OK, content # 只有港股实盘交易才需要解锁 code, data = self.tradeCtx.unlock_trade(self.password) if code == RET_OK: self.writeLog(u'交易接口解锁成功') else: self.writeLog(u'交易接口解锁失败,原因:%s' % data) # 设置回调处理对象 self.tradeCtx.set_handler(OrderHandler()) self.tradeCtx.set_handler(DealHandler()) # 启动交易接口 self.tradeCtx.start() self.writeLog(u'交易接口连接成功') #---------------------------------------------------------------------- def subscribe(self, subscribeReq): """订阅行情""" for data_type in ['QUOTE', 'ORDER_BOOK']: code, data = self.quoteCtx.subscribe(subscribeReq.symbol, data_type, True) if code: self.writeError(code, u'订阅行情失败:%s' % data) #---------------------------------------------------------------------- def sendOrder(self, orderReq): """发单""" side = directionMap[orderReq.direction] priceType = OrderType.NORMAL # 只支持限价单 # 设置价格调整模式为向内调整(即买入调整后价格比原始价格低) if orderReq.direction == DIRECTION_LONG: adjustLimit = 0.05 else: adjustLimit = -0.05 code, data = self.tradeCtx.place_order(orderReq.price, orderReq.volume, orderReq.symbol, side, priceType, trd_env=self.env, adjust_limit=adjustLimit) if code: self.writeError(code, u'委托失败:%s' % data) return '' for ix, row in data.iterrows(): orderID = str(row['order_id']) vtOrderID = '.'.join([self.gatewayName, orderID]) return vtOrderID #---------------------------------------------------------------------- def cancelOrder(self, cancelOrderReq): """撤单""" code, data = self.tradeCtx.modify_order(ModifyOrderOp.CANCEL, cancelOrderReq.orderID, 0, 0, trd_env=self.env) if code: self.writeError(code, u'撤单失败:%s' % data) return #---------------------------------------------------------------------- def qryContract(self): """查询合约""" for vtProductClass, product in productMap.items(): code, data = self.quoteCtx.get_stock_basicinfo( self.market, product) if code: self.writeError(code, u'查询合约信息失败:%s' % data) return for ix, row in data.iterrows(): contract = VtContractData() contract.gatewayName = self.gatewayName contract.symbol = row['code'] contract.vtSymbol = contract.symbol contract.name = row['name'] contract.productClass = vtProductClass contract.size = int(row['lot_size']) contract.priceTick = 0.001 self.onContract(contract) self.writeLog(u'合约信息查询成功') #---------------------------------------------------------------------- def qryAccount(self): """查询账户资金""" code, data = self.tradeCtx.accinfo_query(trd_env=self.env, acc_id=0) if code: self.writeError(code, u'查询账户资金失败:%s' % data) return for ix, row in data.iterrows(): account = VtAccountData() account.gatewayName = self.gatewayName account.accountID = '%s_%s' % (self.gatewayName, self.market) account.vtAccountID = '.'.join( [self.gatewayName, account.accountID]) account.balance = float(row['total_assets']) account.available = float(row['avl_withdrawal_cash']) self.onAccount(account) #---------------------------------------------------------------------- def qryPosition(self): """查询持仓""" code, data = self.tradeCtx.position_list_query(trd_env=self.env, acc_id=0) if code: self.writeError(code, u'查询持仓失败:%s' % data) return for ix, row in data.iterrows(): pos = VtPositionData() pos.gatewayName = self.gatewayName pos.symbol = row['code'] pos.vtSymbol = pos.symbol pos.direction = DIRECTION_LONG pos.vtPositionName = '.'.join([pos.vtSymbol, pos.direction]) pos.position = float(row['qty']) pos.price = float(row['cost_price']) pos.positionProfit = float(row['pl_val']) pos.frozen = float(row['qty']) - float(row['can_sell_qty']) if pos.price < 0: pos.price = 0 if pos.positionProfit > 100000000: pos.positionProfit = 0 self.onPosition(pos) #---------------------------------------------------------------------- def qryOrder(self): """查询委托""" code, data = self.tradeCtx.order_list_query("", trd_env=self.env) if code: self.writeError(code, u'查询委托失败:%s' % data) return self.processOrder(data) self.writeLog(u'委托查询成功') #---------------------------------------------------------------------- def qryTrade(self): """查询成交""" code, data = self.tradeCtx.deal_list_query(self.env) if code: self.writeError(code, u'查询成交失败:%s' % data) return self.processDeal(data) self.writeLog(u'成交查询成功') #---------------------------------------------------------------------- def close(self): """关闭""" if self.quoteCtx: self.quoteCtx.close() if self.tradeCtx: self.tradeCtx.close() #---------------------------------------------------------------------- def initQuery(self): """初始化连续查询""" if self.qryEnabled: # 需要循环的查询函数列表 self.qryFunctionList = [self.qryAccount, self.qryPosition] self.qryCount = 0 # 查询触发倒计时 self.qryTrigger = 2 # 查询触发点 self.qryNextFunction = 0 # 上次运行的查询函数索引 self.startQuery() #---------------------------------------------------------------------- def query(self, event): """注册到事件处理引擎上的查询函数""" self.qryCount += 1 if self.qryCount > self.qryTrigger: # 清空倒计时 self.qryCount = 0 # 执行查询函数 function = self.qryFunctionList[self.qryNextFunction] function() # 计算下次查询函数的索引,如果超过了列表长度,则重新设为0 self.qryNextFunction += 1 if self.qryNextFunction == len(self.qryFunctionList): self.qryNextFunction = 0 #---------------------------------------------------------------------- def startQuery(self): """启动连续查询""" self.eventEngine.register(EVENT_TIMER, self.query) #---------------------------------------------------------------------- def setQryEnabled(self, qryEnabled): """设置是否要启动循环查询""" self.qryEnabled = qryEnabled #---------------------------------------------------------------------- def processQuote(self, data): """报价推送""" for ix, row in data.iterrows(): symbol = row['code'] tick = self.tickDict.get(symbol, None) if not tick: tick = VtTickData() tick.symbol = symbol tick.vtSymbol = tick.symbol tick.gatewayName = self.gatewayName self.tickDict[symbol] = tick tick.date = row['data_date'].replace('-', '') tick.time = row['data_time'] tick.datetime = datetime.strptime(' '.join([tick.date, tick.time]), '%Y%m%d %H:%M:%S') tick.openPrice = row['open_price'] tick.highPrice = row['high_price'] tick.lowPrice = row['low_price'] tick.preClosePrice = row['prev_close_price'] tick.lastPrice = row['last_price'] tick.volume = row['volume'] if 'price_spread' in row: spread = row['price_spread'] tick.upperLimit = tick.lastPrice + spread * 10 tick.lowerLimit = tick.lastPrice - spread * 10 newTick = copy(tick) self.onTick(newTick) #---------------------------------------------------------------------- def processOrderBook(self, data): """订单簿推送""" symbol = data['code'] tick = self.tickDict.get(symbol, None) if not tick: tick = VtTickData() tick.symbol = symbol tick.vtSymbol = tick.symbol tick.gatewayName = self.gatewayName self.tickDict[symbol] = tick d = tick.__dict__ for i in range(5): bidData = data['Bid'][i] askData = data['Ask'][i] n = i + 1 d['bidPrice%s' % n] = bidData[0] d['bidVolume%s' % n] = bidData[1] d['askPrice%s' % n] = askData[0] d['askVolume%s' % n] = askData[1] if tick.datetime: newTick = copy(tick) self.onTick(newTick) #---------------------------------------------------------------------- def processOrder(self, data): """处理委托推送""" for ix, row in data.iterrows(): # 如果状态是已经删除,则直接忽略 if row['order_status'] == OrderStatus.DELETED: continue print(row['order_status']) order = VtOrderData() order.gatewayName = self.gatewayName order.symbol = row['code'] order.vtSymbol = order.symbol order.orderID = str(row['order_id']) order.vtOrderID = '.'.join([self.gatewayName, order.orderID]) order.price = float(row['price']) order.totalVolume = float(row['qty']) order.tradedVolume = float(row['dealt_qty']) order.orderTime = row['create_time'].split(' ')[-1] order.status = statusMapReverse.get(row['order_status'], STATUS_UNKNOWN) order.direction = directionMapReverse[row['trd_side']] self.onOrder(order) #---------------------------------------------------------------------- def processDeal(self, data): """处理成交推送""" for ix, row in data.iterrows(): tradeID = str(row['deal_id']) if tradeID in self.tradeSet: continue self.tradeSet.add(tradeID) trade = VtTradeData() trade.gatewayName = self.gatewayName trade.symbol = row['code'] trade.vtSymbol = trade.symbol trade.tradeID = tradeID trade.vtTradeID = '.'.join([self.gatewayName, trade.tradeID]) trade.orderID = row['order_id'] trade.vtOrderID = '.'.join([self.gatewayName, trade.orderID]) trade.price = float(row['price']) trade.volume = float(row['qty']) trade.direction = directionMapReverse[row['trd_side']] trade.tradeTime = row['create_time'].split(' ')[-1] self.onTrade(trade)
class FutuGateway(VtGateway): """富途接口""" #---------------------------------------------------------------------- def __init__(self, eventEngine, gatewayName='FUTU'): """Constructor""" super(FutuGateway, self).__init__(eventEngine, gatewayName) self.quoteCtx = None self.tradeCtx = None self.host = '' self.ip = 0 self.market = '' self.password = '' self.env = TrdEnv.SIMULATE # 默认仿真交易 self.fileName = self.gatewayName + '_connect.json' self.filePath = getJsonPath(self.fileName, __file__) self.tickDict = {} self.tradeSet = set() # 保存成交编号的集合,防止重复推送 self.qryEnabled = True self.qryThread = Thread(target=self.qryData) #---------------------------------------------------------------------- def writeLog(self, content): """输出日志""" log = VtLogData() log.gatewayName = self.gatewayName log.logContent = content self.onLog(log) #---------------------------------------------------------------------- def writeError(self, code, msg): """输出错误""" error = VtErrorData() error.gatewayName = self.gatewayName error.errorID = code error.errorMsg = msg self.onError(error) #---------------------------------------------------------------------- def connect(self): """连接""" # 载入配置 try: f = open(self.filePath) setting = json.load(f) self.host = setting['host'] self.port = setting['port'] self.market = setting['market'] self.password = setting['password'] self.env = setting['env'] except: self.writeLog(u'载入配置文件出错') return self.connectQuote() self.connectTrade() self.qryThread.start() #---------------------------------------------------------------------- def qryData(self): """初始化时查询数据""" # 等待2秒保证行情和交易接口启动完成 sleep(2.0) # 查询合约、成交、委托、持仓、账户 self.qryContract() self.qryTrade() self.qryOrder() self.qryPosition() self.qryAccount() # 启动循环查询 self.initQuery() #---------------------------------------------------------------------- def connectQuote(self): """连接行情功能""" self.quoteCtx = OpenQuoteContext(self.host, self.port) # 继承实现处理器类 class QuoteHandler(StockQuoteHandlerBase): """报价处理器""" gateway = self # 缓存Gateway对象 def on_recv_rsp(self, rsp_str): ret_code, content = super(QuoteHandler, self).on_recv_rsp(rsp_str) if ret_code != RET_OK: return RET_ERROR, content self.gateway.processQuote(content) return RET_OK, content class OrderBookHandler(OrderBookHandlerBase): """订单簿处理器""" gateway = self def on_recv_rsp(self, rsp_str): ret_code, content = super(OrderBookHandler, self).on_recv_rsp(rsp_str) if ret_code != RET_OK: return RET_ERROR, content self.gateway.processOrderBook(content) return RET_OK, content # 设置回调处理对象 self.quoteCtx.set_handler(QuoteHandler()) self.quoteCtx.set_handler(OrderBookHandler()) # 启动行情 self.quoteCtx.start() self.writeLog(u'行情接口连接成功') #---------------------------------------------------------------------- def connectTrade(self): """连接交易功能""" # 连接交易接口 if self.market == 'US': self.tradeCtx = OpenUSTradeContext(self.host, self.port) else: self.tradeCtx = OpenHKTradeContext(self.host, self.port) # 继承实现处理器类 class OrderHandler(TradeOrderHandlerBase): """委托处理器""" gateway = self # 缓存Gateway对象 def on_recv_rsp(self, rsp_str): ret_code, content = super(OrderHandler, self).on_recv_rsp(rsp_str) if ret_code != RET_OK: return RET_ERROR, content self.gateway.processOrder(content) return RET_OK, content class DealHandler(TradeDealHandlerBase): """订单簿处理器""" gateway = self def on_recv_rsp(self, rsp_str): ret_code, content = super(DealHandler, self).on_recv_rsp(rsp_str) if ret_code != RET_OK: return RET_ERROR, content self.gateway.processDeal(content) return RET_OK, content # 只有港股实盘交易才需要解锁 code, data = self.tradeCtx.unlock_trade(self.password) if code == RET_OK: self.writeLog(u'交易接口解锁成功') else: self.writeLog(u'交易接口解锁失败,原因:%s' %data) # 设置回调处理对象 self.tradeCtx.set_handler(OrderHandler()) self.tradeCtx.set_handler(DealHandler()) # 启动交易接口 self.tradeCtx.start() self.writeLog(u'交易接口连接成功') #---------------------------------------------------------------------- def subscribe(self, subscribeReq): """订阅行情""" for data_type in ['QUOTE', 'ORDER_BOOK']: code, data = self.quoteCtx.subscribe(subscribeReq.symbol, data_type, True) if code: self.writeError(code, u'订阅行情失败:%s' %data) #---------------------------------------------------------------------- def sendOrder(self, orderReq): """发单""" side = directionMap[orderReq.direction] priceType = OrderType.NORMAL # 只支持限价单 # 设置价格调整模式为向内调整(即买入调整后价格比原始价格低) if orderReq.direction == DIRECTION_LONG: adjustLimit = 0.05 else: adjustLimit = -0.05 code, data = self.tradeCtx.place_order(orderReq.price, orderReq.volume, orderReq.symbol, side, priceType, trd_env=self.env, adjust_limit=adjustLimit) if code: self.writeError(code, u'委托失败:%s' %data) return '' for ix, row in data.iterrows(): orderID = str(row['order_id']) vtOrderID = '.'.join([self.gatewayName, orderID]) return vtOrderID #---------------------------------------------------------------------- def cancelOrder(self, cancelOrderReq): """撤单""" code, data = self.tradeCtx.modify_order(ModifyOrderOp.CANCEL, cancelOrderReq.orderID, 0, 0, trd_env=self.env) if code: self.writeError(code, u'撤单失败:%s' %data) return #---------------------------------------------------------------------- def qryContract(self): """查询合约""" for vtProductClass, product in productMap.items(): code, data = self.quoteCtx.get_stock_basicinfo(self.market, product) if code: self.writeError(code, u'查询合约信息失败:%s' %data) return for ix, row in data.iterrows(): contract = VtContractData() contract.gatewayName = self.gatewayName contract.symbol = row['code'] contract.vtSymbol = contract.symbol contract.name = row['name'] contract.productClass = vtProductClass contract.size = int(row['lot_size']) contract.priceTick = 0.001 self.onContract(contract) self.writeLog(u'合约信息查询成功') #---------------------------------------------------------------------- def qryAccount(self): """查询账户资金""" code, data = self.tradeCtx.accinfo_query(trd_env=self.env, acc_id=0) if code: self.writeError(code, u'查询账户资金失败:%s' %data) return for ix, row in data.iterrows(): account = VtAccountData() account.gatewayName = self.gatewayName account.accountID = '%s_%s' %(self.gatewayName, self.market) account.vtAccountID = '.'.join([self.gatewayName, account.accountID]) account.balance = float(row['total_assets']) account.available = float(row['avl_withdrawal_cash']) self.onAccount(account) #---------------------------------------------------------------------- def qryPosition(self): """查询持仓""" code, data = self.tradeCtx.position_list_query(trd_env=self.env, acc_id=0) if code: self.writeError(code, u'查询持仓失败:%s' %data) return for ix, row in data.iterrows(): pos = VtPositionData() pos.gatewayName = self.gatewayName pos.symbol = row['code'] pos.vtSymbol = pos.symbol pos.direction = DIRECTION_LONG pos.vtPositionName = '.'.join([pos.vtSymbol, pos.direction]) pos.position = float(row['qty']) pos.price = float(row['cost_price']) pos.positionProfit = float(row['pl_val']) pos.frozen = float(row['qty']) - float(row['can_sell_qty']) if pos.price < 0: pos.price = 0 if pos.positionProfit > 100000000: pos.positionProfit = 0 self.onPosition(pos) #---------------------------------------------------------------------- def qryOrder(self): """查询委托""" code, data = self.tradeCtx.order_list_query("", trd_env=self.env) if code: self.writeError(code, u'查询委托失败:%s' %data) return self.processOrder(data) self.writeLog(u'委托查询成功') #---------------------------------------------------------------------- def qryTrade(self): """查询成交""" code, data = self.tradeCtx.deal_list_query(self.env) if code: self.writeError(code, u'查询成交失败:%s' %data) return self.processDeal(data) self.writeLog(u'成交查询成功') #---------------------------------------------------------------------- def close(self): """关闭""" if self.quoteCtx: self.quoteCtx.close() if self.tradeCtx: self.tradeCtx.close() #---------------------------------------------------------------------- def initQuery(self): """初始化连续查询""" if self.qryEnabled: # 需要循环的查询函数列表 self.qryFunctionList = [self.qryAccount, self.qryPosition] self.qryCount = 0 # 查询触发倒计时 self.qryTrigger = 2 # 查询触发点 self.qryNextFunction = 0 # 上次运行的查询函数索引 self.startQuery() #---------------------------------------------------------------------- def query(self, event): """注册到事件处理引擎上的查询函数""" self.qryCount += 1 if self.qryCount > self.qryTrigger: # 清空倒计时 self.qryCount = 0 # 执行查询函数 function = self.qryFunctionList[self.qryNextFunction] function() # 计算下次查询函数的索引,如果超过了列表长度,则重新设为0 self.qryNextFunction += 1 if self.qryNextFunction == len(self.qryFunctionList): self.qryNextFunction = 0 #---------------------------------------------------------------------- def startQuery(self): """启动连续查询""" self.eventEngine.register(EVENT_TIMER, self.query) #---------------------------------------------------------------------- def setQryEnabled(self, qryEnabled): """设置是否要启动循环查询""" self.qryEnabled = qryEnabled #---------------------------------------------------------------------- def processQuote(self, data): """报价推送""" for ix, row in data.iterrows(): symbol = row['code'] tick = self.tickDict.get(symbol, None) if not tick: tick = VtTickData() tick.symbol = symbol tick.vtSymbol = tick.symbol tick.gatewayName = self.gatewayName self.tickDict[symbol] = tick tick.date = row['data_date'].replace('-', '') tick.time = row['data_time'] tick.datetime = datetime.strptime(' '.join([tick.date, tick.time]), '%Y%m%d %H:%M:%S') tick.openPrice = row['open_price'] tick.highPrice = row['high_price'] tick.lowPrice = row['low_price'] tick.preClosePrice = row['prev_close_price'] tick.lastPrice = row['last_price'] tick.volume = row['volume'] if 'price_spread' in row: spread = row['price_spread'] tick.upperLimit = tick.lastPrice + spread * 10 tick.lowerLimit = tick.lastPrice - spread * 10 newTick = copy(tick) self.onTick(newTick) #---------------------------------------------------------------------- def processOrderBook(self, data): """订单簿推送""" symbol = data['code'] tick = self.tickDict.get(symbol, None) if not tick: tick = VtTickData() tick.symbol = symbol tick.vtSymbol = tick.symbol tick.gatewayName = self.gatewayName self.tickDict[symbol] = tick d = tick.__dict__ for i in range(5): bidData = data['Bid'][i] askData = data['Ask'][i] n = i + 1 d['bidPrice%s' %n] = bidData[0] d['bidVolume%s' %n] = bidData[1] d['askPrice%s' %n] = askData[0] d['askVolume%s' %n] = askData[1] if tick.datetime: newTick = copy(tick) self.onTick(newTick) #---------------------------------------------------------------------- def processOrder(self, data): """处理委托推送""" for ix, row in data.iterrows(): # 如果状态是已经删除,则直接忽略 if row['order_status'] == OrderStatus.DELETED: continue print(row['order_status']) order = VtOrderData() order.gatewayName = self.gatewayName order.symbol = row['code'] order.vtSymbol = order.symbol order.orderID = str(row['order_id']) order.vtOrderID = '.'.join([self.gatewayName, order.orderID]) order.price = float(row['price']) order.totalVolume = float(row['qty']) order.tradedVolume = float(row['dealt_qty']) order.orderTime = row['create_time'].split(' ')[-1] order.status = statusMapReverse.get(row['order_status'], STATUS_UNKNOWN) order.direction = directionMapReverse[row['trd_side']] self.onOrder(order) #---------------------------------------------------------------------- def processDeal(self, data): """处理成交推送""" for ix, row in data.iterrows(): tradeID = str(row['deal_id']) if tradeID in self.tradeSet: continue self.tradeSet.add(tradeID) trade = VtTradeData() trade.gatewayName = self.gatewayName trade.symbol = row['code'] trade.vtSymbol = trade.symbol trade.tradeID = tradeID trade.vtTradeID = '.'.join([self.gatewayName, trade.tradeID]) trade.orderID = row['order_id'] trade.vtOrderID = '.'.join([self.gatewayName, trade.orderID]) trade.price = float(row['price']) trade.volume = float(row['qty']) trade.direction = directionMapReverse[row['trd_side']] trade.tradeTime = row['create_time'].split(' ')[-1] self.onTrade(trade)