class FutuGateway(BaseGateway): """""" default_setting = { "密码": "", "地址": "127.0.0.1", "端口": 11111, "市场": ["HK", "US"], "环境": [TrdEnv.REAL, TrdEnv.SIMULATE], } exchanges = list(EXCHANGE_FUTU2VT.values()) def __init__(self, event_engine): """Constructor""" super(FutuGateway, self).__init__(event_engine, "FUTU") self.quote_ctx = None self.trade_ctx = None self.host = "" self.port = 0 self.market = "" self.password = "" self.env = TrdEnv.SIMULATE self.ticks = {} self.trades = set() self.contracts = {} self.thread = Thread(target=self.query_data) # For query function. self.count = 0 self.interval = 1 self.query_funcs = [self.query_account, self.query_position] def connect(self, setting: dict): """""" self.host = setting["地址"] self.port = setting["端口"] self.market = setting["市场"] self.password = setting["密码"] self.env = setting["环境"] self.connect_quote() self.connect_trade() self.thread.start() def query_data(self): """ Query all data necessary. """ sleep(2.0) # Wait 2 seconds till connection completed. self.query_contract() self.query_trade() self.query_order() self.query_position() self.query_account() # Start fixed interval query. self.event_engine.register(EVENT_TIMER, self.process_timer_event) def process_timer_event(self, event): """""" self.count += 1 if self.count < self.interval: return self.count = 0 func = self.query_funcs.pop(0) func() self.query_funcs.append(func) def connect_quote(self): """ Connect to market data server. """ self.quote_ctx = OpenQuoteContext(self.host, self.port) class QuoteHandler(StockQuoteHandlerBase): gateway = self 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.process_quote(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.process_orderbook(content) return RET_OK, content self.quote_ctx.set_handler(QuoteHandler()) self.quote_ctx.set_handler(OrderBookHandler()) self.quote_ctx.start() self.write_log("行情接口连接成功") def connect_trade(self): """ Connect to trade server. """ # Initialize context according to market. if self.market == "US": self.trade_ctx = OpenUSTradeContext(self.host, self.port) else: self.trade_ctx = OpenHKTradeContext(self.host, self.port) # Implement handlers. class OrderHandler(TradeOrderHandlerBase): gateway = self 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.process_order(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.process_deal(content) return RET_OK, content # Unlock to allow trading. code, data = self.trade_ctx.unlock_trade(self.password) if code == RET_OK: self.write_log("交易接口解锁成功") else: self.write_log(f"交易接口解锁失败,原因:{data}") # Start context. self.trade_ctx.set_handler(OrderHandler()) self.trade_ctx.set_handler(DealHandler()) self.trade_ctx.start() self.write_log("交易接口连接成功") def subscribe(self, req: SubscribeRequest): """""" for data_type in ["QUOTE", "ORDER_BOOK"]: futu_symbol = convert_symbol_vt2futu(req.symbol, req.exchange) code, data = self.quote_ctx.subscribe(futu_symbol, data_type, True) if code: self.write_log(f"订阅行情失败:{data}") def send_order(self, req: OrderRequest): """""" side = DIRECTION_VT2FUTU[req.direction] futu_order_type = OrderType.NORMAL # Only limit order is supported. # Set price adjustment mode to inside adjustment. if req.direction is Direction.LONG: adjust_limit = 0.05 else: adjust_limit = -0.05 futu_symbol = convert_symbol_vt2futu(req.symbol, req.exchange) code, data = self.trade_ctx.place_order( req.price, req.volume, futu_symbol, side, futu_order_type, trd_env=self.env, adjust_limit=adjust_limit, ) if code: self.write_log(f"委托失败:{data}") return "" for ix, row in data.iterrows(): orderid = str(row["order_id"]) order = req.create_order_data(orderid, self.gateway_name) self.on_order(order) return order.vt_orderid def cancel_order(self, req: CancelRequest): """""" code, data = self.trade_ctx.modify_order(ModifyOrderOp.CANCEL, req.orderid, 0, 0, trd_env=self.env) if code: self.write_log(f"撤单失败:{data}") def query_contract(self): """""" for product, futu_product in PRODUCT_VT2FUTU.items(): code, data = self.quote_ctx.get_stock_basicinfo( self.market, futu_product) if code: self.write_log(f"查询合约信息失败:{data}") return for ix, row in data.iterrows(): symbol, exchange = convert_symbol_futu2vt(row["code"]) contract = ContractData( symbol=symbol, exchange=exchange, name=row["name"], product=product, size=1, pricetick=0.001, net_position=True, gateway_name=self.gateway_name, ) self.on_contract(contract) self.contracts[contract.vt_symbol] = contract self.write_log("合约信息查询成功") def query_account(self): """""" code, data = self.trade_ctx.accinfo_query(trd_env=self.env, acc_id=0) if code: self.write_log(f"查询账户资金失败:{data}") return for ix, row in data.iterrows(): account = AccountData( accountid=f"{self.gateway_name}_{self.market}", balance=float(row["total_assets"]), frozen=(float(row["total_assets"]) - float(row["avl_withdrawal_cash"])), gateway_name=self.gateway_name, ) self.on_account(account) def query_position(self): """""" code, data = self.trade_ctx.position_list_query(trd_env=self.env, acc_id=0) if code: self.write_log(f"查询持仓失败:{data}") return for ix, row in data.iterrows(): symbol, exchange = convert_symbol_futu2vt(row["code"]) pos = PositionData( symbol=symbol, exchange=exchange, direction=Direction.NET, volume=row["qty"], frozen=(float(row["qty"]) - float(row["can_sell_qty"])), price=float(row["cost_price"]), pnl=float(row["pl_val"]), gateway_name=self.gateway_name, ) self.on_position(pos) def query_order(self): """""" code, data = self.trade_ctx.order_list_query("", trd_env=self.env) if code: self.write_log(f"查询委托失败:{data}") return self.process_order(data) self.write_log("委托查询成功") def query_trade(self): """""" code, data = self.trade_ctx.deal_list_query("", trd_env=self.env) if code: self.write_log(f"查询成交失败:{data}") return self.process_deal(data) self.write_log("成交查询成功") def close(self): """""" if self.quote_ctx: self.quote_ctx.close() if self.trade_ctx: self.trade_ctx.close() def get_tick(self, code): """ Get tick buffer. """ tick = self.ticks.get(code, None) symbol, exchange = convert_symbol_futu2vt(code) if not tick: tick = TickData( symbol=symbol, exchange=exchange, datetime=datetime.now(CHINA_TZ), gateway_name=self.gateway_name, ) self.ticks[code] = tick contract = self.contracts.get(tick.vt_symbol, None) if contract: tick.name = contract.name return tick def process_quote(self, data): """报价推送""" for ix, row in data.iterrows(): symbol = row["code"] date = row["data_date"].replace("-", "") time = row["data_time"] dt = datetime.strptime(f"{date} {time}", "%Y%m%d %H:%M:%S") dt = CHINA_TZ.localize(dt) tick = self.get_tick(symbol) tick.datetime = dt tick.open_price = row["open_price"] tick.high_price = row["high_price"] tick.low_price = row["low_price"] tick.pre_close = row["prev_close_price"] tick.last_price = row["last_price"] tick.volume = row["volume"] if "price_spread" in row: spread = row["price_spread"] tick.limit_up = tick.last_price + spread * 10 tick.limit_down = tick.last_price - spread * 10 self.on_tick(copy(tick)) def process_orderbook(self, data): """""" symbol = data["code"] tick = self.get_tick(symbol) d = tick.__dict__ for i in range(5): bid_data = data["Bid"][i] ask_data = data["Ask"][i] n = i + 1 d["bid_price_%s" % n] = bid_data[0] d["bid_volume_%s" % n] = bid_data[1] d["ask_price_%s" % n] = ask_data[0] d["ask_volume_%s" % n] = ask_data[1] if tick.datetime: self.on_tick(copy(tick)) def process_order(self, data): """ Process order data for both query and update. """ for ix, row in data.iterrows(): # Ignore order with status DELETED if row["order_status"] == OrderStatus.DELETED: continue symbol, exchange = convert_symbol_futu2vt(row["code"]) order = OrderData( symbol=symbol, exchange=exchange, orderid=str(row["order_id"]), direction=DIRECTION_FUTU2VT[row["trd_side"]], price=float(row["price"]), volume=row["qty"], traded=row["dealt_qty"], status=STATUS_FUTU2VT[row["order_status"]], datetime=generate_datetime(row["create_time"]), gateway_name=self.gateway_name, ) self.on_order(order) def process_deal(self, data): """ Process trade data for both query and update. """ for ix, row in data.iterrows(): tradeid = str(row["deal_id"]) if tradeid in self.trades: continue self.trades.add(tradeid) symbol, exchange = convert_symbol_futu2vt(row["code"]) trade = TradeData( symbol=symbol, exchange=exchange, direction=DIRECTION_FUTU2VT[row["trd_side"]], tradeid=tradeid, orderid=row["order_id"], price=float(row["price"]), volume=row["qty"], datetime=generate_datetime(row["create_time"]), gateway_name=self.gateway_name, ) self.on_trade(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("", trd_env=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(BaseGateway): """""" default_setting = { "password": "", "host": "127.0.0.1", "port": 11111, "market": ["HK", "US"], "env": [TrdEnv.REAL, TrdEnv.SIMULATE], } def __init__(self, event_engine): """Constructor""" super(FutuGateway, self).__init__(event_engine, "FUTU") self.quote_ctx = None self.trade_ctx = None self.host = "" self.port = 0 self.market = "" self.password = "" self.env = TrdEnv.SIMULATE self.ticks = {} self.trades = set() self.contracts = {} self.thread = Thread(target=self.query_data) # For query function. self.count = 0 self.interval = 1 self.query_funcs = [self.query_account, self.query_position] def connect(self, setting: dict): """""" self.host = setting["host"] self.port = setting["port"] self.market = setting["market"] self.password = setting["password"] self.env = setting["env"] self.connect_quote() self.connect_trade() self.thread.start() def query_data(self): """ Query all data necessary. """ sleep(2.0) # Wait 2 seconds till connection completed. self.query_contract() self.query_trade() self.query_order() self.query_position() self.query_account() # Start fixed interval query. self.event_engine.register(EVENT_TIMER, self.process_timer_event) def process_timer_event(self, event): """""" self.count += 1 if self.count < self.interval: return self.count = 0 func = self.query_funcs.pop(0) func() self.query_funcs.append(func) def connect_quote(self): """ Connect to market data server. """ self.quote_ctx = OpenQuoteContext(self.host, self.port) class QuoteHandler(StockQuoteHandlerBase): gateway = self 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.process_quote(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.process_orderbook(content) return RET_OK, content self.quote_ctx.set_handler(QuoteHandler()) self.quote_ctx.set_handler(OrderBookHandler()) self.quote_ctx.start() self.write_log("行情接口连接成功") def connect_trade(self): """ Connect to trade server. """ # Initialize context according to market. if self.market == "US": self.trade_ctx = OpenUSTradeContext(self.host, self.port) else: self.trade_ctx = OpenHKTradeContext(self.host, self.port) # Implement handlers. class OrderHandler(TradeOrderHandlerBase): gateway = self 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.process_order(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.process_deal(content) return RET_OK, content # Unlock to allow trading. code, data = self.trade_ctx.unlock_trade(self.password) if code == RET_OK: self.write_log("交易接口解锁成功") else: self.write_log(f"交易接口解锁失败,原因:{data}") # Start context. self.trade_ctx.set_handler(OrderHandler()) self.trade_ctx.set_handler(DealHandler()) self.trade_ctx.start() self.write_log("交易接口连接成功") def subscribe(self, req: SubscribeRequest): """""" for data_type in ["QUOTE", "ORDER_BOOK"]: futu_symbol = convert_symbol_vt2futu(req.symbol, req.exchange) code, data = self.quote_ctx.subscribe(futu_symbol, data_type, True) if code: self.write_log(f"订阅行情失败:{data}") def send_order(self, req: OrderRequest): """""" side = DIRECTION_VT2FUTU[req.direction] price_type = OrderType.NORMAL # Only limit order is supported. # Set price adjustment mode to inside adjustment. if req.direction is Direction.LONG: adjust_limit = 0.05 else: adjust_limit = -0.05 futu_symbol = convert_symbol_vt2futu(req.symbol, req.exchange) code, data = self.trade_ctx.place_order( req.price, req.volume, futu_symbol, side, price_type, trd_env=self.env, adjust_limit=adjust_limit, ) if code: self.write_log(f"委托失败:{data}") return "" for ix, row in data.iterrows(): orderid = str(row["order_id"]) order = req.create_order_data(orderid, self.gateway_name) self.on_order(order) return order.vt_orderid def cancel_order(self, req: CancelRequest): """""" code, data = self.trade_ctx.modify_order( ModifyOrderOp.CANCEL, req.orderid, 0, 0, trd_env=self.env ) if code: self.write_log(f"撤单失败:{data}") def query_contract(self): """""" for product, futu_product in PRODUCT_VT2FUTU.items(): code, data = self.quote_ctx.get_stock_basicinfo( self.market, futu_product ) if code: self.write_log(f"查询合约信息失败:{data}") return for ix, row in data.iterrows(): symbol, exchange = convert_symbol_futu2vt(row["code"]) contract = ContractData( symbol=symbol, exchange=exchange, name=row["name"], product=product, size=1, pricetick=0.001, gateway_name=self.gateway_name, ) self.on_contract(contract) self.contracts[contract.vt_symbol] = contract self.write_log("合约信息查询成功") def query_account(self): """""" code, data = self.trade_ctx.accinfo_query(trd_env=self.env, acc_id=0) if code: self.write_log(f"查询账户资金失败:{data}") return for ix, row in data.iterrows(): account = AccountData( accountid=f"{self.gateway_name}_{self.market}", balance=float(row["total_assets"]), frozen=(float(row["total_assets"]) - float(row["avl_withdrawal_cash"])), gateway_name=self.gateway_name, ) self.on_account(account) def query_position(self): """""" code, data = self.trade_ctx.position_list_query( trd_env=self.env, acc_id=0 ) if code: self.write_log(f"查询持仓失败:{data}") return for ix, row in data.iterrows(): symbol, exchange = convert_symbol_futu2vt(row["code"]) pos = PositionData( symbol=symbol, exchange=exchange, direction=Direction.LONG, volume=float(row["qty"]), frozen=(float(row["qty"]) - float(row["can_sell_qty"])), price=float(row["pl_val"]), pnl=float(row["cost_price"]), gateway_name=self.gateway_name, ) self.on_position(pos) def query_order(self): """""" code, data = self.trade_ctx.order_list_query("", trd_env=self.env) if code: self.write_log(f"查询委托失败:{data}") return self.process_order(data) self.write_log("委托查询成功") def query_trade(self): """""" code, data = self.trade_ctx.deal_list_query("", trd_env=self.env) if code: self.write_log(f"查询成交失败:{data}") return self.process_deal(data) self.write_log("成交查询成功") def close(self): """""" if self.quote_ctx: self.quote_ctx.close() if self.trade_ctx: self.trade_ctx.close() def get_tick(self, code): """ Get tick buffer. """ tick = self.ticks.get(code, None) symbol, exchange = convert_symbol_futu2vt(code) if not tick: tick = TickData( symbol=symbol, exchange=exchange, datetime=datetime.now(), gateway_name=self.gateway_name, ) self.ticks[code] = tick contract = self.contracts.get(tick.vt_symbol, None) if contract: tick.name = contract.name return tick def process_quote(self, data): """报价推送""" for ix, row in data.iterrows(): symbol = row["code"] tick = self.get_tick(symbol) date = row["data_date"].replace("-", "") time = row["data_time"] tick.datetime = datetime.strptime( f"{date} {time}", "%Y%m%d %H:%M:%S") tick.open_price = row["open_price"] tick.high_price = row["high_price"] tick.low_price = row["low_price"] tick.pre_close = row["prev_close_price"] tick.last_price = row["last_price"] tick.volume = row["volume"] if "price_spread" in row: spread = row["price_spread"] tick.limit_up = tick.last_price + spread * 10 tick.limit_down = tick.last_price - spread * 10 self.on_tick(copy(tick)) def process_orderbook(self, data): """""" symbol = data["code"] tick = self.get_tick(symbol) d = tick.__dict__ for i in range(5): bid_data = data["Bid"][i] ask_data = data["Ask"][i] n = i + 1 d["bid_price_%s" % n] = bid_data[0] d["bid_volume_%s" % n] = bid_data[1] d["ask_price_%s" % n] = ask_data[0] d["ask_volume_%s" % n] = ask_data[1] if tick.datetime: self.on_tick(copy(tick)) def process_order(self, data): """ Process order data for both query and update. """ for ix, row in data.iterrows(): # Ignore order with status DELETED if row["order_status"] == OrderStatus.DELETED: continue symbol, exchange = convert_symbol_futu2vt(row["code"]) order = OrderData( symbol=symbol, exchange=exchange, orderid=str(row["order_id"]), direction=DIRECTION_FUTU2VT[row["trd_side"]], price=float(row["price"]), volume=float(row["qty"]), traded=float(row["dealt_qty"]), status=STATUS_FUTU2VT[row["order_status"]], time=row["create_time"].split(" ")[-1], gateway_name=self.gateway_name, ) self.on_order(order) def process_deal(self, data): """ Process trade data for both query and update. """ for ix, row in data.iterrows(): tradeid = str(row["deal_id"]) if tradeid in self.trades: continue self.trades.add(tradeid) symbol, exchange = convert_symbol_futu2vt(row["code"]) trade = TradeData( symbol=symbol, exchange=exchange, direction=DIRECTION_FUTU2VT[row["trd_side"]], tradeid=tradeid, orderid=row["order_id"], price=float(row["price"]), volume=float(row["qty"]), time=row["create_time"].split(" ")[-1], gateway_name=self.gateway_name, ) self.on_trade(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("", trd_env=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 FutuTrader: ORDER_SCHEMA = [ 'order_id', 'order_type', 'order_status', 'code', 'stock_name', 'trd_side', 'qty', 'price', 'create_time', 'dealt_qty', 'dealt_avg_price', 'updated_time', 'last_err_msg' ] DEAL_SCHEMA = [ 'trd_side', 'deal_id', 'order_id', 'code', 'stock_name', 'qty', 'price', 'create_time', 'dealt_qty', 'counter_broker_id', 'counter_broker_name' ] 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) if self.trd_env == TrdEnv.REAL: self.trd_ctx.unlock_trade(password_md5=self.unlock_pwd) 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[self.ORDER_SCHEMA] 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 get_history_orders(self, code="", start="", end="", status_filter_list=["FILLED_ALL"]): orders = list() ret, data = self.trd_ctx.history_order_list_query(status_filter_list, code, start, end, trd_env=self.trd_env, acc_id=self.acc_id) if ret != 0: raise Exception( "get history orders failed.code:%s, start:%s, end:%s, ret:%s, msg:%s" % (code, start, end, ret, data)) if data.empty: return orders return data #data = data[self.ORDER_SCHEMA] #for mdict in data.to_dict("records"): # orders.append(MOrder(mdict)) #return orders def get_history_deals(self, code="", start="", end=""): deals = list() ret, data = self.trd_ctx.history_deal_list_query(code, start, end, trd_env=self.trd_env, acc_id=self.acc_id) if ret != 0: raise Exception( "get history deals failed.code:%s, start:%s, end:%s" % (code, start, end)) if data.empty: return deals return data #data = data[self.DEAL_SCHEMA] #for mdict in data.to_dict("records"): # deals.append(MDeal(mdict)) #return deals def buy(self, code, price, quantity): 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