def get_account_apis(): openapi_client = TradeClient(client_config, logger=logger) openapi_client.get_managed_accounts() # 获取订单 openapi_client.get_orders() # 获取持仓 openapi_client.get_positions() # 获取资产 openapi_client.get_assets()
def get_account_apis(): openapi_client = TradeClient(client_config, logger=logger) openapi_client.get_managed_accounts() # 获取订单 openapi_client.get_orders() # 获取未成交订单 # openapi_client.get_open_orders() # 获取已成交订单 # openapi_client.get_filled_orders(start_time='2019-05-01', end_time='2019-05-21') # 获取持仓 openapi_client.get_positions() # 获取资产 openapi_client.get_assets()
def get_account_apis(): openapi_client = TradeClient(client_config, logger=logger) openapi_client.get_managed_accounts() # 获取订单 openapi_client.get_orders() # 获取未成交订单 # openapi_client.get_open_orders() # 获取已成交订单 # openapi_client.get_filled_orders(start_time='2019-05-01', end_time='2019-05-21') # 获取订单成交记录, 仅适用于综合账户 transactions = openapi_client.get_transactions(symbol='AAPL', sec_type=SecurityType.STK, start_time=1641398400000, end_time=1642398400000) # transactions = openapi_client.get_transactions(symbol='CL2201', sec_type=SecurityType.FUT) # transactions = openapi_client.get_transactions(order_id=24844739769009152) # transactions = openapi_client.get_transactions(symbol='BABA', sec_type='OPT', strike=121, expiry='20220121', # put_call='CALL') # 获取持仓 openapi_client.get_positions() # 获取资产 openapi_client.get_assets() # 综合/模拟账户获取资产 openapi_client.get_prime_assets()
class TigerGateway(BaseGateway): """""" default_setting = { "tiger_id": "", "account": "", "standard_account": "", "private_key": '', } def __init__(self, event_engine): """Constructor""" super(TigerGateway, self).__init__(event_engine, "TIGER") self.tiger_id = "" self.account = "" self.standard_account = "" self.paper_account = "" self.language = "" self.client_config = None self.quote_client = None self.push_client = None self.local_id = 1000000 self.tradeid = 0 self.active = False self.queue = Queue() self.pool = None self.ID_TIGER2VT = {} self.ID_VT2TIGER = {} self.ticks = {} self.trades = set() self.contracts = {} self.symbol_names = {} def run(self): """""" while self.active: try: func, args = self.queue.get(timeout=0.1) func(*args) except Empty: pass def add_task(self, func, *args): """""" self.queue.put((func, [*args])) def connect(self, setting: dict): """""" self.private_key = setting['private_key'] self.tiger_id = setting["tiger_id"] self.account = setting["account"] self.standard_account = setting["standard_account"] self.paper_account = setting["account"] self.languege = Language.zh_CN # Start thread pool for REST call self.active = True self.pool = Pool(5) self.pool.apply_async(self.run) # Put connect task into quque. self.init_client_config() self.add_task(self.connect_quote) self.add_task(self.connect_trade) self.add_task(self.connect_push) def init_client_config(self, sandbox=True): """""" self.client_config = TigerOpenClientConfig(sandbox_debug=sandbox) self.client_config.private_key = self.private_key self.client_config.tiger_id = self.tiger_id self.client_config.account = self.account self.client_config.standard_account = self.standard_account self.client_config.paper_account = self.paper_account self.client_config.language = self.language def connect_quote(self): """ Connect to market data server. """ try: self.quote_client = QuoteClient(self.client_config) self.symbol_names = dict( self.quote_client.get_symbol_names(lang=Language.zh_CN)) self.query_contract() except ApiException: self.write_log("查询合约失败") return self.write_log("行情接口连接成功") self.write_log("合约查询成功") def connect_trade(self): """ Connect to trade server. """ self.trade_client = TradeClient(self.client_config) try: self.add_task(self.query_order) self.add_task(self.query_position) self.add_task(self.query_account) except ApiException: self.write_log("交易接口连接失败") return self.write_log("交易接口连接成功") def connect_push(self): """ Connect to push server. """ protocol, host, port = self.client_config.socket_host_port self.push_client = PushClient(host, port, (protocol == 'ssl')) self.push_client.connect( self.client_config.tiger_id, self.client_config.private_key) self.push_client.quote_changed = self.on_quote_change self.push_client.asset_changed = self.on_asset_change self.push_client.position_changed = self.on_position_change self.push_client.order_changed = self.on_order_change self.write_log("推送接口连接成功") def subscribe(self, req: SubscribeRequest): """""" self.push_client.subscribe_quote([req.symbol]) self.push_client.subscribe_asset() self.push_client.subscribe_position() self.push_client.subscribe_order() def on_quote_change(self, tiger_symbol: str, data: list, trading: bool): """""" data = dict(data) symbol, exchange = convert_symbol_tiger2vt(tiger_symbol) tick = self.ticks.get(symbol, None) if not tick: tick = TickData( symbol=symbol, exchange=exchange, gateway_name=self.gateway_name, datetime=datetime.now(), name=self.symbol_names[symbol], ) self.ticks[symbol] = tick tick.datetime = datetime.fromtimestamp(data["latest_time"] / 1000) tick.pre_close = data.get("prev_close", 0) tick.last_price = data.get("latest_price", 0) tick.volume = data.get("volume", 0) tick.open_price = data.get("open", 0) tick.open_price = data.get("open", 0) tick.high_price = data.get("high", 0) tick.low_price = data.get("low", 0) tick.ask_price_1 = data.get("ask_price", 0) tick.bid_price_1 = data.get("bid_price", 0) tick.ask_volume_1 = data.get("ask_size", 0) tick.bid_volume_1 = data.get("bid_size", 0) self.on_tick(copy(tick)) def on_asset_change(self, tiger_account: str, data: list): """""" data = dict(data) if "net_liquidation" not in data: return account = AccountData( accountid=tiger_account, balance=data["net_liquidation"], frozen=0.0, gateway_name=self.gateway_name, ) self.on_account(account) def on_position_change(self, tiger_account: str, data: list): """""" data = dict(data) symbol, exchange = convert_symbol_tiger2vt(data["origin_symbol"]) pos = PositionData( symbol=symbol, exchange=exchange, direction=Direction.NET, volume=int(data["quantity"]), frozen=0.0, price=data["average_cost"], pnl=data["unrealized_pnl"], gateway_name=self.gateway_name, ) self.on_position(pos) def on_order_change(self, tiger_account: str, data: list): """""" data = dict(data) print("委托推送", data["origin_symbol"], data["order_id"], data["filled"], data["status"]) symbol, exchange = convert_symbol_tiger2vt(data["origin_symbol"]) status = PUSH_STATUS_TIGER2VT[data["status"]] order = OrderData( symbol=symbol, exchange=exchange, orderid=self.ID_TIGER2VT.get( str(data["order_id"]), self.get_new_local_id()), direction=Direction.NET, price=data.get("limit_price", 0), volume=data["quantity"], traded=data["filled"], status=status, time=datetime.fromtimestamp( data["order_time"] / 1000).strftime("%H:%M:%S"), gateway_name=self.gateway_name, ) self.on_order(order) if status == Status.ALLTRADED: self.tradeid += 1 trade = TradeData( symbol=symbol, exchange=exchange, direction=Direction.NET, tradeid=self.tradeid, orderid=self.ID_TIGER2VT[str(data["order_id"])], price=data["avg_fill_price"], volume=data["filled"], time=datetime.fromtimestamp( data["trade_time"] / 1000).strftime("%H:%M:%S"), gateway_name=self.gateway_name, ) self.on_trade(trade) def get_new_local_id(self): self.local_id += 1 return self.local_id def send_order(self, req: OrderRequest): """""" local_id = self.get_new_local_id() order = req.create_order_data(local_id, self.gateway_name) self.on_order(order) self.add_task(self._send_order, req, local_id) return order.vt_orderid def _send_order(self, req: OrderRequest, local_id): """""" currency = config_symbol_currency(req.symbol) try: contract = self.trade_client.get_contracts( symbol=req.symbol, currency=currency)[0] order = self.trade_client.create_order( account=self.account, contract=contract, action=DIRECTION_VT2TIGER[req.direction], order_type=ORDERTYPE_VT2TIGER[req.type], quantity=int(req.volume), limit_price=req.price, ) self.ID_TIGER2VT[str(order.order_id)] = local_id self.ID_VT2TIGER[local_id] = str(order.order_id) self.trade_client.place_order(order) print("发单:", order.contract.symbol, order.order_id, order.quantity, order.status) except: # noqa traceback.print_exc() self.write_log("发单失败") return def cancel_order(self, req: CancelRequest): """""" self.add_task(self._cancel_order, req) def _cancel_order(self, req: CancelRequest): """""" try: order_id = self.ID_VT2TIGER[req.orderid] data = self.trade_client.cancel_order(order_id=order_id) except ApiException: self.write_log(f"撤单失败:{req.orderid}") if not data: self.write_log('撤单成功') def query_contract(self): """""" # HK Stock symbols_names_HK = self.quote_client.get_symbol_names( lang=Language.zh_CN, market=Market.HK) contract_names_HK = DataFrame( symbols_names_HK, columns=['symbol', 'name']) contractList = list(contract_names_HK["symbol"]) i, n = 0, len(contractList) result = pd.DataFrame() while i < n: i += 500 c = contractList[i - 500:i] r = self.quote_client.get_trade_metas(c) result = result.append(r) contract_detail_HK = result.sort_values(by="symbol", ascending=True) contract_HK = pd.merge( contract_names_HK, contract_detail_HK, how='left', on='symbol') for ix, row in contract_HK.iterrows(): contract = ContractData( symbol=row["symbol"], exchange=Exchange.SEHK, name=row["name"], product=Product.EQUITY, size=1, pricetick=row["min_tick"], net_position=True, gateway_name=self.gateway_name, ) self.on_contract(contract) self.contracts[contract.vt_symbol] = contract # US Stock symbols_names_US = self.quote_client.get_symbol_names( lang=Language.zh_CN, market=Market.US) contract_US = DataFrame(symbols_names_US, columns=['symbol', 'name']) for ix, row in contract_US.iterrows(): contract = ContractData( symbol=row["symbol"], exchange=Exchange.SMART, name=row["name"], product=Product.EQUITY, size=1, pricetick=0.001, gateway_name=self.gateway_name, ) self.on_contract(contract) self.contracts[contract.vt_symbol] = contract # CN Stock symbols_names_CN = self.quote_client.get_symbol_names( lang=Language.zh_CN, market=Market.CN) contract_CN = DataFrame(symbols_names_CN, columns=['symbol', 'name']) for ix, row in contract_CN.iterrows(): symbol = row["symbol"] symbol, exchange = convert_symbol_tiger2vt(symbol) contract = ContractData( symbol=symbol, exchange=exchange, name=row["name"], product=Product.EQUITY, size=1, pricetick=0.001, gateway_name=self.gateway_name, ) self.on_contract(contract) self.contracts[contract.vt_symbol] = contract def query_account(self): """""" try: assets = self.trade_client.get_assets() except ApiException: self.write_log("查询资金失败") return for i in assets: account = AccountData( accountid=self.account, balance=i.summary.net_liquidation, frozen=0.0, gateway_name=self.gateway_name, ) self.on_account(account) def query_position(self): """""" try: position = self.trade_client.get_positions() except ApiException: self.write_log("查询持仓失败") return for i in position: symbol, exchange = convert_symbol_tiger2vt(i.contract.symbol) pos = PositionData( symbol=symbol, exchange=exchange, direction=Direction.NET, volume=int(i.quantity), frozen=0.0, price=i.average_cost, pnl=float(i.unrealized_pnl), gateway_name=self.gateway_name, ) self.on_position(pos) def query_order(self): """""" try: data = self.trade_client.get_orders() data = sorted(data, key=lambda x: x.order_time, reverse=False) except: # noqa traceback.print_exc() self.write_log("查询委托失败") return self.process_order(data) self.process_deal(data) def close(self): """""" self.active = False if self.push_client: self.push_client.disconnect() def process_order(self, data): """""" for i in data: symbol, exchange = convert_symbol_tiger2vt(str(i.contract)) local_id = self.get_new_local_id() order = OrderData( symbol=symbol, exchange=exchange, orderid=local_id, direction=Direction.NET, price=i.limit_price if i.limit_price else 0.0, volume=i.quantity, traded=i.filled, status=STATUS_TIGER2VT[i.status], time=datetime.fromtimestamp( i.order_time / 1000).strftime("%H:%M:%S"), gateway_name=self.gateway_name, ) self.ID_TIGER2VT[str(i.order_id)] = local_id self.on_order(order) self.ID_VT2TIGER = {v: k for k, v in self.ID_TIGER2VT.items()} print("原始委托字典", self.ID_TIGER2VT) print("原始反向字典", self.ID_VT2TIGER) def process_deal(self, data): """ Process trade data for both query and update. """ for i in data: if i.status == ORDER_STATUS.PARTIALLY_FILLED or i.status == ORDER_STATUS.FILLED: symbol, exchange = convert_symbol_tiger2vt(str(i.contract)) self.tradeid += 1 trade = TradeData( symbol=symbol, exchange=exchange, direction=Direction.NET, tradeid=self.tradeid, orderid=self.ID_TIGER2VT[str(i.order_id)], price=i.avg_fill_price, volume=i.filled, time=datetime.fromtimestamp( i.trade_time / 1000).strftime("%H:%M:%S"), gateway_name=self.gateway_name, ) self.on_trade(trade)
class TigerGateway(BaseGateway): """""" default_setting = { "tiger_id": "", "account": "", " server ": [" standard ", " global ", " simulation "], "private_key": "", } exchanges = [Exchange.SEHK, Exchange.SMART, Exchange.SSE, Exchange.SZSE] def __init__(self, event_engine): """Constructor""" super(TigerGateway, self).__init__(event_engine, "TIGER") self.tiger_id = "" self.account = "" self.server = "" self.language = "" self.client_config = None self.quote_client = None self.push_client = None self.local_id = 1000000 self.tradeid = 0 self.active = False self.queue = Queue() self.pool = None self.ID_TIGER2VT = {} self.ID_VT2TIGER = {} self.ticks = {} self.trades = set() self.contracts = {} self.symbol_names = {} self.push_connected = False self.subscribed_symbols = set() def run(self): """""" while self.active: try: func, args = self.queue.get(timeout=0.1) func(*args) except Empty: pass def add_task(self, func, *args): """""" self.queue.put((func, [*args])) def connect(self, setting: dict): """""" self.private_key = setting["private_key"] self.tiger_id = setting["tiger_id"] self.server = setting[" server "] self.account = setting["account"] self.languege = Language.zh_CN # Start thread pool for REST call self.active = True self.pool = Pool(5) self.pool.apply_async(self.run) # Put connect task into quque. self.init_client_config() self.add_task(self.connect_quote) self.add_task(self.connect_trade) self.add_task(self.connect_push) def init_client_config(self, sandbox=False): """""" self.client_config = TigerOpenClientConfig(sandbox_debug=sandbox) self.client_config.private_key = self.private_key self.client_config.tiger_id = self.tiger_id self.client_config.account = self.account self.client_config.language = self.language def connect_quote(self): """ Connect to market data server. """ try: self.quote_client = QuoteClient(self.client_config) self.symbol_names = dict( self.quote_client.get_symbol_names(lang=Language.zh_CN)) self.query_contract() except ApiException: self.write_log(" queries contract failure ") return self.write_log(" quotes interfacing success ") self.write_log(" contract query succeeds ") def connect_trade(self): """ Connect to trade server. """ self.trade_client = TradeClient(self.client_config) try: self.add_task(self.query_order) self.add_task(self.query_position) self.add_task(self.query_account) except ApiException: self.write_log(" transaction interface connection failure ") return self.write_log(" successful transaction interface ") def connect_push(self): """ Connect to push server. """ protocol, host, port = self.client_config.socket_host_port self.push_client = PushClient(host, port, (protocol == "ssl")) self.push_client.quote_changed = self.on_quote_change self.push_client.asset_changed = self.on_asset_change self.push_client.position_changed = self.on_position_change self.push_client.order_changed = self.on_order_change self.push_client.connect_callback = self.on_push_connected self.push_client.connect(self.client_config.tiger_id, self.client_config.private_key) def subscribe(self, req: SubscribeRequest): """""" self.subscribed_symbols.add(req.symbol) if self.push_connected: self.push_client.subscribe_quote([req.symbol]) def on_push_connected(self): """""" self.push_connected = True self.write_log(" push interfacing success ") self.push_client.subscribe_asset() self.push_client.subscribe_position() self.push_client.subscribe_order() self.push_client.subscribe_quote(list(self.subscribed_symbols)) def on_quote_change(self, tiger_symbol: str, data: list, trading: bool): """""" data = dict(data) symbol, exchange = convert_symbol_tiger2vt(tiger_symbol) tick = self.ticks.get(symbol, None) if not tick: tick = TickData( symbol=symbol, exchange=exchange, gateway_name=self.gateway_name, datetime=datetime.now(), name=self.symbol_names[symbol], ) self.ticks[symbol] = tick tick.datetime = datetime.fromtimestamp(int(data["timestamp"]) / 1000) tick.pre_close = data.get("prev_close", tick.pre_close) tick.last_price = data.get("latest_price", tick.last_price) tick.volume = data.get("volume", tick.volume) tick.open_price = data.get("open", tick.open_price) tick.high_price = data.get("high", tick.high_price) tick.low_price = data.get("low", tick.low_price) tick.ask_price_1 = data.get("ask_price", tick.ask_price_1) tick.bid_price_1 = data.get("bid_price", tick.bid_price_1) tick.ask_volume_1 = data.get("ask_size", tick.ask_volume_1) tick.bid_volume_1 = data.get("bid_size", tick.bid_volume_1) self.on_tick(copy(tick)) def on_asset_change(self, tiger_account: str, data: list): """""" data = dict(data) if "net_liquidation" not in data: return account = AccountData( accountid=tiger_account, balance=data["net_liquidation"], frozen=0.0, gateway_name=self.gateway_name, ) self.on_account(account) def on_position_change(self, tiger_account: str, data: list): """""" data = dict(data) symbol, exchange = convert_symbol_tiger2vt(data["origin_symbol"]) pos = PositionData( symbol=symbol, exchange=exchange, direction=Direction.NET, volume=int(data["quantity"]), frozen=0.0, price=data["average_cost"], pnl=data["unrealized_pnl"], gateway_name=self.gateway_name, ) self.on_position(pos) def on_order_change(self, tiger_account: str, data: list): """""" data = dict(data) symbol, exchange = convert_symbol_tiger2vt(data["origin_symbol"]) status = STATUS_TIGER2VT[data["status"]] order = OrderData( symbol=symbol, exchange=exchange, orderid=self.ID_TIGER2VT.get(str(data["order_id"]), self.get_new_local_id()), direction=Direction.NET, price=data.get("limit_price", 0), volume=data["quantity"], traded=data["filled"], status=status, time=datetime.fromtimestamp(data["order_time"] / 1000).strftime("%H:%M:%S"), gateway_name=self.gateway_name, ) self.ID_TIGER2VT[str(data["order_id"])] = order.orderid self.on_order(order) if status == Status.ALLTRADED: self.tradeid += 1 trade = TradeData( symbol=symbol, exchange=exchange, direction=Direction.NET, tradeid=self.tradeid, orderid=self.ID_TIGER2VT[str(data["order_id"])], price=data["avg_fill_price"], volume=data["filled"], time=datetime.fromtimestamp(data["trade_time"] / 1000).strftime("%H:%M:%S"), gateway_name=self.gateway_name, ) self.on_trade(trade) def get_new_local_id(self): self.local_id += 1 return self.local_id def send_order(self, req: OrderRequest): """""" local_id = self.get_new_local_id() order = req.create_order_data(local_id, self.gateway_name) self.on_order(order) self.add_task(self._send_order, req, local_id) return order.vt_orderid def _send_order(self, req: OrderRequest, local_id): """""" currency = config_symbol_currency(req.symbol) try: contract = self.trade_client.get_contracts(symbol=req.symbol, currency=currency)[0] order = self.trade_client.create_order( account=self.account, contract=contract, action=DIRECTION_VT2TIGER[req.direction], order_type=ORDERTYPE_VT2TIGER[req.type], quantity=int(req.volume), limit_price=req.price, ) self.ID_TIGER2VT[str(order.order_id)] = local_id self.ID_VT2TIGER[local_id] = str(order.order_id) self.trade_client.place_order(order) except: # noqa traceback.print_exc() self.write_log(" billed failure ") return def cancel_order(self, req: CancelRequest): """""" self.add_task(self._cancel_order, req) def _cancel_order(self, req: CancelRequest): """""" try: order_id = self.ID_VT2TIGER[req.orderid] data = self.trade_client.cancel_order(order_id=order_id) except ApiException: self.write_log(f" withdrawals failure :{req.orderid}") if not data: self.write_log(" withdrawals success ") def query_contract(self): """""" # HK Stock symbols_names_HK = self.quote_client.get_symbol_names( lang=Language.zh_CN, market=Market.HK) contract_names_HK = DataFrame(symbols_names_HK, columns=["symbol", "name"]) contractList = list(contract_names_HK["symbol"]) i, n = 0, len(contractList) result = pd.DataFrame() while i < n: i += 50 c = contractList[i - 50:i] r = self.quote_client.get_trade_metas(c) result = result.append(r) contract_detail_HK = result.sort_values(by="symbol", ascending=True) contract_HK = pd.merge(contract_names_HK, contract_detail_HK, how="left", on="symbol") for ix, row in contract_HK.iterrows(): contract = ContractData( symbol=row["symbol"], exchange=Exchange.SEHK, name=row["name"], product=Product.EQUITY, size=1, min_volume=row["lot_size"], pricetick=row["min_tick"], net_position=True, gateway_name=self.gateway_name, ) self.on_contract(contract) self.contracts[contract.vt_symbol] = contract # US Stock symbols_names_US = self.quote_client.get_symbol_names( lang=Language.zh_CN, market=Market.US) contract_US = DataFrame(symbols_names_US, columns=["symbol", "name"]) for ix, row in contract_US.iterrows(): contract = ContractData( symbol=row["symbol"], exchange=Exchange.SMART, name=row["name"], product=Product.EQUITY, size=1, min_volume=100, pricetick=0.001, gateway_name=self.gateway_name, ) self.on_contract(contract) self.contracts[contract.vt_symbol] = contract # CN Stock symbols_names_CN = self.quote_client.get_symbol_names( lang=Language.zh_CN, market=Market.CN) contract_CN = DataFrame(symbols_names_CN, columns=["symbol", "name"]) for ix, row in contract_CN.iterrows(): symbol = row["symbol"] symbol, exchange = convert_symbol_tiger2vt(symbol) contract = ContractData( symbol=symbol, exchange=exchange, name=row["name"], product=Product.EQUITY, size=1, min_volume=100, pricetick=0.001, gateway_name=self.gateway_name, ) self.on_contract(contract) self.contracts[contract.vt_symbol] = contract def query_account(self): """""" try: assets = self.trade_client.get_assets() except ApiException: self.write_log(" queries funds fail ") return for i in assets: account = AccountData( accountid=self.account, balance=i.summary.net_liquidation, frozen=0.0, gateway_name=self.gateway_name, ) self.on_account(account) def query_position(self): """""" try: position = self.trade_client.get_positions() except ApiException: self.write_log(" queries positions fail ") return for i in position: symbol, exchange = convert_symbol_tiger2vt(i.contract.symbol) pos = PositionData( symbol=symbol, exchange=exchange, direction=Direction.NET, volume=int(i.quantity), frozen=0.0, price=i.average_cost, pnl=float(i.unrealized_pnl), gateway_name=self.gateway_name, ) self.on_position(pos) def query_order(self): """""" try: data = self.trade_client.get_orders() data = sorted(data, key=lambda x: x.order_time, reverse=False) except: # noqa traceback.print_exc() self.write_log(" inquiry commission failed ") return self.process_order(data) self.process_deal(data) def close(self): """""" self.active = False if self.push_client: self.push_client.disconnect() def process_order(self, data): """""" for i in data: symbol, exchange = convert_symbol_tiger2vt(str(i.contract)) local_id = self.get_new_local_id() order = OrderData( symbol=symbol, exchange=exchange, orderid=local_id, direction=Direction.NET, price=i.limit_price if i.limit_price else 0.0, volume=i.quantity, traded=i.filled, status=STATUS_TIGER2VT[i.status], time=datetime.fromtimestamp(i.order_time / 1000).strftime("%H:%M:%S"), gateway_name=self.gateway_name, ) self.ID_TIGER2VT[str(i.order_id)] = local_id self.on_order(order) self.ID_VT2TIGER = {v: k for k, v in self.ID_TIGER2VT.items()} def process_deal(self, data): """ Process trade data for both query and update. """ for i in data: if i.status == OrderStatus.PARTIALLY_FILLED or i.status == OrderStatus.FILLED: symbol, exchange = convert_symbol_tiger2vt(str(i.contract)) self.tradeid += 1 trade = TradeData( symbol=symbol, exchange=exchange, direction=Direction.NET, tradeid=self.tradeid, orderid=self.ID_TIGER2VT[str(i.order_id)], price=i.avg_fill_price, volume=i.filled, time=datetime.fromtimestamp(i.trade_time / 1000).strftime("%H:%M:%S"), gateway_name=self.gateway_name, ) self.on_trade(trade)
class Test: def __init__(self): self.client_config = get_client_config() self.trade_client = TradeClient(self.client_config) self.openapi_client = QuoteClient(self.client_config, logger=logger) # 初始化 pushclient protocol, host, port = self.client_config.socket_host_port self.push_client = PushClient(host, port, use_ssl=(protocol == 'ssl')) self.push_client.connect(self.client_config.tiger_id, self.client_config.private_key) def trade(self): stock = stock_contract(symbol='AAPL', currency='USD') option = option_contract(identifier='AAPL 190927P00200000') emu_account = [] # stock_contract = trade_client.get_contracts(symbol='FB')[0] account = self.trade_client.get_managed_accounts() print(account) emu_account = account[1] account = self.client_config.paper_account assets = self.trade_client.get_assets(account=account) print(assets) posinfo = self.trade_client.get_positions(account=account) print(posinfo) stock_order = market_order(account=account, # 下单账户,可以使用标准、环球、或模拟账户 contract = option, # 第1步中获取的合约对象 action = 'BUY', quantity = 1) print(stock_order) self.trade_client.place_order(stock_order) print(stock_order) # 直接本地构造contract对象。 期货 contract 的构造方法请参考后面的文档 def get_option_quote(self): symbol = 'AAPL' expirations = self.openapi_client.get_option_expirations(symbols=[symbol]) if len(expirations) > 1: print(expirations) expiry = int(expirations[expirations['symbol'] == symbol].at[0, 'timestamp']) chains = self.openapi_client.get_option_chain(symbol, expiry) print(chains) for index,row in chains.iterrows(): print(row["identifier"], row["strike"], row["put_call"]) briefs = self.openapi_client.get_option_briefs(['AAPL 190927P00200000']) print(briefs) bars = self.openapi_client.get_option_bars(['AAPL 190927P00200000']) print(bars) ticks = self.openapi_client.get_option_trade_ticks(['AAPL 190927P00200000']) print(ticks) for index,row in ticks.iterrows(): print(row["identifier"], row["time"], row["price"]) def on_quote_changed(self, symbol, items, hour_trading): print(symbol, items, hour_trading) def test1(self): info = app.openapi_client.get_briefs(symbols) print(info) def run(self): option_trade_ticks = self.openapi_client.get_option_trade_ticks(['AAPL 190927P00200000']) print(option_trade_ticks) def subscribe(self): # self.push_client.connect(self.client_config.tiger_id, self.client_config.private_key) self.push_client.quote_changed = self.on_quote_changed self.push_client.subscribe_quote(symbols=['AAPL 190927P00200000', 'GOOG', 'FB'], quote_key_type=QuoteKeyType.ALL) self.push_client.subscribe_asset() time.sleep(30) # self.push_client.unsubscribe_quote(['AAPL', 'GOOG']) def subscribe2(self): self.push_client.quote_changed = self.on_quote_changed self.push_client.subscribe_quote(symbols=['aapl'], quote_key_type=QuoteKeyType.ALL) self.push_client.subscribe_asset() def on_changed(self, symbol, items, hour_trading): print(symbol, items, hour_trading) data = dict(items) latest_price = data.get('latest_price') volume = data.get('volume') def query_subscribed(self): def on_subscribed_symbols(symbols, focus_keys, limit, used): print(symbols, focus_keys, limit, used) self.push_client.subscribed_symbols = on_subscribed_symbols self.push_client.query_subscribed_quote()
class Tiger: # default logger defualt_logger = logging.getLogger('bc_tiger_logger') # init def __init__(self, account_type, config, sandbox_debug=False, logger_name=None, open_time_adj=0, close_time_adj=0): # get logger self.logger = Tiger.defualt_logger if ( logger_name is None) else logging.getLogger(logger_name) # read user info, position record from local files self.__user_info = io_util.read_config(file_path=config['tiger_path'], file_name='user_info.json') self.__position_record = io_util.read_config( file_path=config['config_path'], file_name='tiger_position_record.json') self.record = self.__position_record[account_type].copy() self.eod_api_key = config['api_key']['eod'] # set account, account type self.account = self.__user_info[account_type] self.account_type = account_type # initialize client_config self.client_config = TigerOpenClientConfig(sandbox_debug=sandbox_debug) self.client_config.private_key = read_private_key( config['tiger_path'] + self.__user_info['private_key_name']) self.client_config.tiger_id = str(self.__user_info['tiger_id']) self.client_config.language = Language.en_US self.client_config.account = self.account # get quote/trade clients, assets, positions self.quote_client = QuoteClient(self.client_config) self.trade_client = TradeClient(self.client_config) self.positions = self.trade_client.get_positions(account=self.account) self.assets = self.trade_client.get_assets(account=self.account) # get market status and trade time self.update_trade_time(open_time_adj=open_time_adj, close_time_adj=close_time_adj) # update position record self.synchronize_position_record(config=config) self.logger.info(f'[tiger]: Tiger instance created: {logger_name}') # get user info def get_user_info(self): return self.__user_info # get position record def get_position_record(self): return self.__position_record # synchronize position record with real position status def synchronize_position_record(self, config): account_type = self.account_type # initialize position record for symbols that not in position record init_cash = config['trade']['init_cash'][account_type] pool = config['selected_sec_list'][config['trade']['pool'] [account_type]] for symbol in pool: if symbol not in self.record.keys(): self.record[symbol] = {'cash': init_cash, 'position': 0} # get real position (dict) position_dict = dict([(x.contract.symbol, x.quantity) for x in self.positions]) # compare position record with real position record_conflicted = False for symbol in self.record.keys(): if symbol not in pool: continue # update position in record record_position = self.record[symbol]['position'] current_position = 0 if ( symbol not in position_dict.keys()) else position_dict[symbol] if current_position != record_position: record_conflicted = True if current_position > 0: self.record[symbol] = { 'cash': 0, 'position': current_position } else: self.record[symbol] = {'cash': init_cash, 'position': 0} self.logger.error( f'[{account_type[:4]}]: {symbol} position({current_position}) rather than ({record_position}), reset record' ) # add record for position that not recorded for symbol in [ x for x in position_dict.keys() if (x in pool and x not in self.record.keys()) ]: record_conflicted = True self.record[symbol] = { 'cash': 0, 'position': position_dict[symbol] } self.logger.error( f'[{account_type[:4]}]: {symbol} position({position_dict[symbol]}) not in record, add record' ) # update __position_record if record_conflicted: self.__position_record[self.account_type] = self.record.copy() io_util.create_config_file(config_dict=self.__position_record, file_path=config['config_path'], file_name='tiger_position_record.json') # update position for an account def update_position_record(self, config, init_cash=None, init_position=None, start_time=None, end_time=None, is_print=True): # set default values init_cash = config['trade']['init_cash'][self.account_type] if ( init_cash is None) else init_cash init_position = 0 if (init_position is None) else init_position start_time = self.trade_time['pre_open_time'].strftime( format="%Y-%m-%d %H:%M:%S") if (start_time is None) else start_time end_time = self.trade_time['post_close_time'].strftime( format="%Y-%m-%d %H:%M:%S") if (end_time is None) else end_time try: # get today filled orders orders = self.trade_client.get_filled_orders(start_time=start_time, end_time=end_time) # update position records for order in orders: symbol = order.contract.symbol action = order.action quantity = order.quantity - order.remaining commission = order.commission avg_fill_price = order.avg_fill_price # init record if not exist if symbol not in self.record.keys(): self.record[symbol] = { 'cash': init_cash, 'position': init_position } record_cash = self.record[symbol]['cash'] record_position = self.record[symbol]['position'] # calculate new cash and position if action == 'BUY': cost = avg_fill_price * quantity + commission new_cash = record_cash - cost new_position = record_position + quantity elif action == 'SELL': acquire = avg_fill_price * quantity - commission new_cash = record_cash + acquire new_position = record_position - quantity else: new_cash = record_cash new_position = record_position # update record if new_cash >= 0 and new_position >= 0: self.record[symbol]['cash'] = new_cash self.record[symbol]['position'] = new_position if is_print: self.logger.info( f'[{self.account_type[:4]}]: updating position record for {symbol} {record_cash, record_position} -> {new_cash, new_position}' ) # update __position_record # self.record['updated'] = datetime.datetime.now().strftime(format="%Y-%m-%d %H:%M:%S") self.__position_record = io_util.read_config( file_path=config['config_path'], file_name='tiger_position_record.json') self.__position_record[self.account_type] = self.record.copy() self.__position_record['updated'][ self.account_type] = datetime.datetime.now().strftime( format="%Y-%m-%d %H:%M:%S") io_util.create_config_file(config_dict=self.__position_record, file_path=config['config_path'], file_name='tiger_position_record.json') except Exception as e: self.logger.exception( f'[erro]: fail updating position records for {self.account_type}, {e}' ) # update portfolio for an account def update_portfolio_record(self, config, position_summary=None, is_print=True): # get position summary if position_summary is None: position_summary = self.get_position_summary(get_briefs=False) position_summary.set_index('symbol', inplace=True) position_summary = position_summary.round(2) # get asset summary net_value = 0 market_value = 0 cash = 0 asset_summary = self.get_asset_summary() if len(asset_summary) > 0: net_value = asset_summary.loc[0, 'net_value'] market_value = asset_summary.loc[0, 'holding_value'] cash = asset_summary.loc[0, 'cash'] # post process if market_value == float('inf'): market_value = position_summary['market_value'].sum().round(2) # load portfolio record portfolio_record = io_util.read_config(file_path=config['config_path'], file_name='portfolio.json') old_net_value = portfolio_record['tiger'][self.account_type].get( 'net_value') support = portfolio_record['tiger'][self.account_type].get( 'portfolio').get('support') resistant = portfolio_record['tiger'][self.account_type].get( 'portfolio').get('resistant') # update portfolio record for current account portfolio_record['tiger'][ self.account_type]['portfolio'] = position_summary.to_dict() portfolio_record['tiger'][ self.account_type]['portfolio']['support'] = {} portfolio_record['tiger'][ self.account_type]['portfolio']['resistant'] = {} quantity = portfolio_record['tiger'][ self.account_type]['portfolio'].get('quantity') if quantity is not None: if support is not None: for symbol in quantity.keys(): portfolio_record['tiger'][self.account_type]['portfolio'][ 'support'][symbol] = support.get(symbol) if resistant is not None: for symbol in quantity.keys(): portfolio_record['tiger'][self.account_type]['portfolio'][ 'resistant'][symbol] = resistant.get(symbol) portfolio_record['tiger'][ self.account_type]['market_value'] = market_value portfolio_record['tiger'][self.account_type]['net_value'] = net_value portfolio_record['tiger'][self.account_type]['cash'] = cash portfolio_record['tiger'][ self.account_type]['updated'] = datetime.datetime.now().strftime( format="%Y-%m-%d %H:%M:%S") io_util.create_config_file(config_dict=portfolio_record, file_path=config['config_path'], file_name='portfolio.json') # print if is_print: self.logger.info( f'[{self.account_type[:4]}]: net value {old_net_value} --> {net_value}' ) # get summary of positions def get_position_summary(self, get_briefs=False): try: # update positions self.positions = self.trade_client.get_positions( account=self.client_config.account) # convert positions(list) to dataframe if len(self.positions) > 0: result = { 'symbol': [], 'quantity': [], 'average_cost': [], 'market_price': [] } for pos in self.positions: result['symbol'].append(pos.contract.symbol) result['quantity'].append(pos.quantity) result['average_cost'].append(pos.average_cost) result['market_price'].append(pos.market_price) result = pd.DataFrame(result) # get briefs for stocks in positions if get_briefs: status = io_util.get_stock_briefs( symbols=[x.contract.symbol for x in self.positions], source='eod', period='1d', interval='1m', api_key=self.eod_api_key) result = pd.merge(result, status, how='left', left_on='symbol', right_on='symbol') result['rate'] = round( (result['latest_price'] - result['average_cost']) / result['average_cost'], 2) result = result[[ 'symbol', 'quantity', 'average_cost', 'latest_price', 'rate', 'latest_time' ]] else: result.rename(columns={'market_price': 'latest_price'}, inplace=True) result['rate'] = round( (result['latest_price'] - result['average_cost']) / result['average_cost'], 2) result['latest_time'] = None # calculate market value result['market_value'] = result['quantity'] * result[ 'latest_price'] else: result = pd.DataFrame({ 'symbol': [], 'quantity': [], 'average_cost': [], 'latest_price': [], 'rate': [], 'market_value': [], 'latest_time': [] }) except Exception as e: result = pd.DataFrame({ 'symbol': [], 'quantity': [], 'average_cost': [], 'latest_price': [], 'rate': [], 'market_value': [], 'latest_time': [] }) self.logger.exception(f'[erro]: can not get position summary: {e}') return result # get summary of assets def get_asset_summary(self, print_summary=False): # update assets self.assets = self.trade_client.get_assets( account=self.client_config.account) asset = self.assets[0] result = { 'account': [asset.account], 'net_value': [asset.summary.net_liquidation], 'holding_value': [asset.summary.gross_position_value], 'cash': [asset.summary.cash], 'available_casg': [asset.summary.available_funds], 'pnl': [asset.summary.realized_pnl], 'holding_pnl': [asset.summary.unrealized_pnl] } if print_summary: summary = f''' 账户: {asset.account}({asset.summary.currency}): 总资产: {asset.summary.net_liquidation} 现金: {asset.summary.cash} (可用 {asset.summary.available_funds}) 持仓市值: {asset.summary.gross_position_value} 日内交易次数: {asset.summary.day_trades_remaining} 已实现盈亏: {asset.summary.realized_pnl} 未实现盈亏: {asset.summary.unrealized_pnl} ''' print(summary) return pd.DataFrame(result) # get available money def get_available_cash(self): # get available cash for real accounts self.assets = self.trade_client.get_assets( account=self.client_config.account) available_cash = self.assets[0].summary.cash return available_cash # get quantity of symbol currently in the position def get_in_position_quantity(self, symbol, get_briefs=False): # initialize affordable quantity quantity = 0 # get position summary position = self.get_position_summary(get_briefs=get_briefs) if len(position) > 0: position = position.set_index('symbol') if symbol in position.index: quantity = position.loc[symbol, 'quantity'] return quantity # check whether it is affordable to buy certain amount of a stock def get_affordable_quantity(self, symbol, cash=None, trading_fee=3): # initialize affordable quantity and available cash quantity = 0 available_cash = self.get_available_cash() if (cash is None) else cash # get latest price of stock stock_brief = io_util.get_stock_briefs( symbols=[symbol], source='eod', period='1d', interval='1m', api_key=self.eod_api_key).set_index('symbol') latest_price = stock_brief.loc[symbol, 'latest_price'] # check if it is affordable quantity = math.floor((available_cash - trading_fee) / latest_price) return quantity # idle for specified time and check position in certain frequency def idle(self, target_time, check_frequency=600): """ Sleep with a fixed frequency, until the target time :param target_time: the target time in datetime.datetime format :param check_frequency: the fixed sleep_time :returns: none :raises: none """ # get current time now = datetime.datetime.now() while now < target_time: # # get position summary # pos = self.get_position_summary() # self.logger.info(f'[rate]:----------------------------------------------\n{pos}\n') # get current time, calculate difference between current time and target time diff_time = round((target_time - now).total_seconds()) sleep_time = (diff_time + 1) if ( diff_time <= check_frequency) else check_frequency # sleep self.logger.info( f'[idle]: {now.strftime(format="%Y-%m-%d %H:%M:%S")}: sleep for {sleep_time} seconds' ) time.sleep(sleep_time) # update current time now = datetime.datetime.now() self.logger.info( f'[wake]: {now.strftime(format="%Y-%m-%d %H:%M:%S")}: exceed target time({target_time})' ) # update trade time def update_trade_time(self, market=Market.US, tz='Asia/Shanghai', open_time_adj=0, close_time_adj=0): # get local timezone tz = pytz.timezone(tz) try: # get open_time status = self.quote_client.get_market_status(market=market)[0] current_status = status.status open_time = status.open_time.astimezone(tz).replace(tzinfo=None) open_time = open_time + datetime.timedelta(hours=open_time_adj) # if program runs after market open, api will return trade time for next trade day, # trade time for current trade day need to be calculated manually if status.status in ['Trading', 'Post-Market Trading']: if open_time.weekday() == 0: open_time = open_time - datetime.timedelta(days=3) else: open_time = open_time - datetime.timedelta(days=1) # calculate close time, pre_open_time, post_close_time close_time = open_time + datetime.timedelta(hours=6.5 + close_time_adj) pre_open_time = open_time - datetime.timedelta(hours=5.5) post_close_time = close_time + datetime.timedelta(hours=4) # open and close time of chinese stock market a_open_time = pre_open_time + datetime.timedelta( hours=9.5 - pre_open_time.hour) a_close_time = pre_open_time + datetime.timedelta( hours=15 - pre_open_time.hour) except Exception as e: self.logger.error(e) current_status = None open_time = None close_time = None pre_open_time = None post_close_time = None self.trade_time = { 'status': current_status, 'tz': tz, 'pre_open_time': pre_open_time, 'open_time': open_time, 'close_time': close_time, 'post_close_time': post_close_time, 'a_open_time': a_open_time, 'a_close_time': a_close_time } # update market status def update_market_status(self, market=Market.US, return_str=False): try: # get market status status = self.quote_client.get_market_status(market=market)[0] self.trade_time['status'] = status.status if return_str: time_format = '%Y-%m-%d %H:%M' pre_open_time = self.trade_time['pre_open_time'].strftime( time_format) post_close_time = self.trade_time['post_close_time'].strftime( time_format) time_format = '%H:%M' open_time = self.trade_time['open_time'].strftime(time_format) close_time = self.trade_time['close_time'].strftime( time_format) time_str = f'<({pre_open_time}){open_time} -- {close_time}({post_close_time})>' return time_str except Exception as e: self.logger.error(e) # buy or sell stocks def trade(self, symbol, action, quantity, price=None, stop_loss=None, stop_profit=None, print_summary=True): trade_summary = '' try: # construct contract contract = stock_contract(symbol=symbol, currency='USD') # construct order if price is None: order_price = 'market' order = market_order(account=self.client_config.account, contract=contract, action=action, quantity=quantity) else: order_price = float(f'{price}') order = limit_order(account=self.client_config.account, contract=contract, action=action, quantity=quantity, limit_price=price) # construct trade summary trade_summary += f'[{action}]: {symbol} X {quantity} ({order_price})\t' # attach order legs order_legs = [] if stop_loss is not None: stop_loss_order_leg = order_leg('LOSS', stop_loss, time_in_force='GTC') # 附加止损单 order_legs.append(stop_loss_order_leg) if stop_profit is not None: stop_profit_order_leg = order_leg('PROFIT', stop_profit, time_in_force='GTC') # 附加止盈单 order_legs.append(stop_profit_order_leg) if len(order_legs) > 0: order.order_legs = order_legs # place buy order if affordable if action == 'BUY': affordable_quantity = self.get_affordable_quantity( symbol=symbol) if quantity <= affordable_quantity: self.trade_client.place_order(order) trade_summary += f'SUCCEED: {order.id}' else: trade_summary += f'FAILED: Not affordable({affordable_quantity}/{quantity})' # place sell order if holding enough stocks elif action == 'SELL': in_position_quantity = self.get_in_position_quantity(symbol) if in_position_quantity >= quantity: self.trade_client.place_order(order) trade_summary += f'SUCCEED: {order.id}' else: trade_summary += f'FAILED: Not enough stock to sell({in_position_quantity}/{quantity})' # other actions else: trade_summary += f'FAILED: Unknown action({action})' except Exception as e: trade_summary += f'FAILED: {e}' # print trade summary if print_summary: self.logger.info(trade_summary) return trade_summary # auto trade according to signals def signal_trade(self, signal, money_per_sec, order_type='market', trading_fee=5, pool=None, according_to_record=True, minimum_position=None): # set symbol to index if len(signal) > 0: # signal = signal.rename(columns={'代码':'symbol', '交易信号':'action'}) # signal = signal.set_index('symbol') # filter sec with pool if pool is not None: filtered_list = [x for x in signal.index if x in pool] signal = signal.loc[filtered_list, signal.columns].copy() # if signal list is not empty if len(signal) > 0: # get latest price for signals # if order_type == 'market': # signal_brief = self.quote_client.get_stock_briefs(symbols=signal.index.tolist()).set_index('symbol') # signal_brief = io_util.get_stock_briefs(symbols=signal.index.tolist(), source='eod', period='1d', interval='1m', api_key=self.eod_api_key).set_index('symbol') # signal = pd.merge(signal, signal_brief[['latest_price']], how='left', left_index=True, right_index=True) # get in-position quantity and latest price for signals position = self.get_position_summary(get_briefs=False) if len(position) == 0: position = pd.DataFrame({'symbol': [], 'quantity': []}) position = position.set_index('symbol') signal = pd.merge(signal, position[['quantity']], how='left', left_index=True, right_index=True).fillna(0) # sell # get sell signals sell_signal = signal.query('action == "s"') if len(sell_signal) > 0: # go through sell signals for symbol in sell_signal.index: # check whether symbol is in positions in_position_quantity = signal.loc[symbol, 'quantity'] if in_position_quantity > 0: if order_type == 'limit': price = signal.loc[symbol, 'latest_price'] else: price = None trade_summary = self.trade( symbol=symbol, action='SELL', quantity=in_position_quantity, price=price, print_summary=False) self.logger.info(trade_summary) else: self.logger.info( f'[SELL]: {symbol} skipped (not in positions)') else: self.logger.info(f'[SELL]: no signal') # buy # get available cash, set minimum position available_cash = self.get_available_cash() if minimum_position is None: minimum_position = money_per_sec # get buy signals which not in posiitons yet default_money_per_sec = money_per_sec buy_signal = signal.query('action == "b"') if len(buy_signal) > 0: # go through buy signals for symbol in buy_signal.index: # break when available cash is below 200 if available_cash <= minimum_position: self.logger.info( f'[BUY]: Available cash is too low({available_cash}/{minimum_position}), stop buying' ) break # check whether symbol is already in positions in_position_quantity = signal.loc[symbol, 'quantity'] if in_position_quantity == 0: # set money used to establish a new position if according_to_record: if (symbol in self.record.keys()) and ( self.record[symbol]['position'] == 0): money_per_sec = self.record[symbol]['cash'] else: money_per_sec = default_money_per_sec # check whether there is enough available money money_per_sec = available_cash if ( money_per_sec > available_cash) else money_per_sec # calculate quantity to buy quantity = math.floor( (money_per_sec - trading_fee) / signal.loc[symbol, 'latest_price']) if quantity > 0: if order_type == 'limit': price = signal.loc[symbol, 'latest_price'] else: price = None trade_summary = self.trade(symbol=symbol, action='BUY', quantity=quantity, price=price, print_summary=False) self.logger.info(trade_summary) # update available cash available_cash -= quantity * signal.loc[ symbol, 'latest_price'] else: self.logger.info(f'[BUY]: not enough money') continue else: self.logger.info( f'[BUY]: {symbol} skipped (already in positions:{in_position_quantity})' ) continue else: self.logger.info(f'[BUY]: no signal') else: self.logger.info(f'[SKIP]: no signal') # stop loss or stop profit or clear all positions def cash_out(self, stop_loss_rate=None, stop_profit_rate=None, clear_all=False, print_summary=True): # get current position with summary position = self.get_position_summary(get_briefs=True) if len(position) > 0: # set symbol as index position = position.set_index('symbol') # if clear all positions if clear_all: cash_out_list = position.index.tolist() else: stop_loss_list = [] if stop_loss_rate is None else position.query( f'rate < {stop_loss_rate}').index.tolist() stop_profit_list = [] if stop_profit_rate is None else position.query( f'rate > {stop_profit_rate}').index.tolist() cash_out_list = list(set(stop_loss_list + stop_profit_list)) # cash out if len(cash_out_list) > 0: cash_out_position = position.loc[cash_out_list, ].copy() self.logger.info( f'[STOP]: LOSS: {stop_loss_list}, PROFIT: {stop_profit_list}' ) for index, row in cash_out_position.iterrows(): self.trade(symbol=index, action='SELL', quantity=row['quantity'], print_summary=print_summary)
class TigerGateway(BaseGateway): """""" default_setting = { "tiger_id": "", "account": "", "服务器": ["标准", "环球", "仿真"], "private_key": "", } # 在 VNTRADER 中展示的交易所列表 exchanges = [ Exchange.SEHK, Exchange.SMART, Exchange.SSE, Exchange.SZSE, Exchange.CFE, Exchange.ECBOT, Exchange.CMECRYPTO, Exchange.CFE, Exchange.GLOBEX, Exchange.NYMEX, Exchange.SGX, Exchange.HKFE ] def __init__(self, event_engine): """Constructor""" super(TigerGateway, self).__init__(event_engine, "TIGER") self.tiger_id = "" self.account = "" self.server = "" self.language = "" self.client_config = None self.quote_client = None self.push_client = None self.local_id = 1000000 self.tradeid = 0 self.active = False self.queue = Queue() self.pool = None self.ID_TIGER2VT = {} self.ID_VT2TIGER = {} self.ticks = {} self.trades = set() self.contracts = {} self.symbol_names = {} # {symbol NQ1909: (exchange, trading_contract)} self.vt_tiger_symbol_map = {} self.push_connected = False self.subscribed_symbols = set() def run(self): """""" while self.active: try: func, args = self.queue.get(timeout=0.1) func(*args) except Empty: pass except Exception: self.write_log('方法%s调用失败,参数为%s' % (func.__name__, args)) def add_task(self, func, *args): """""" self.queue.put((func, [*args])) def connect(self, setting: dict): """""" self.private_key = setting["private_key"] self.tiger_id = setting["tiger_id"] self.server = setting["服务器"] self.account = setting["account"] self.languege = Language.zh_CN # Start thread pool for REST call self.active = True self.pool = Pool(5) self.pool.apply_async(self.run) # Put connect task into quque. self.init_client_config() self.add_task(self.connect_quote) self.add_task(self.connect_trade) self.add_task(self.connect_push) def init_client_config(self, sandbox=SANDBOX): """""" self.client_config = TigerOpenClientConfig(sandbox_debug=sandbox) self.client_config.private_key = self.private_key self.client_config.tiger_id = self.tiger_id self.client_config.account = self.account self.client_config.language = self.language def connect_quote(self): """ Connect to market data server. """ try: self.quote_client = QuoteClient(self.client_config) self.symbol_names = dict( self.quote_client.get_symbol_names(lang=Language.zh_CN)) self.query_contract() except ApiException: self.write_log("查询合约失败") return self.write_log("行情接口连接成功") def connect_trade(self): """ Connect to trade server. """ self.write_log('查询交易接口') self.trade_client = TradeClient(self.client_config) try: self.add_task(self.query_order) self.add_task(self.query_position) self.add_task(self.query_account) except ApiException: self.write_log("交易接口连接失败") return self.write_log("交易接口连接成功") def connect_push(self): """ Connect to push server. """ protocol, host, port = self.client_config.socket_host_port self.push_client = PushClient(host, port, (protocol == "ssl")) self.push_client.quote_changed = self.on_quote_change self.push_client.asset_changed = self.on_asset_change self.push_client.position_changed = self.on_position_change self.push_client.order_changed = self.on_order_change self.push_client.connect_callback = self.on_push_connected self.push_client.disconnect_callback = self.on_disconnected self.push_client.connect(self.client_config.tiger_id, self.client_config.private_key) def subscribe(self, req: SubscribeRequest): """""" self.subscribed_symbols.add(req.symbol) if self.push_connected: self.push_client.subscribe_quote(symbols=[req.symbol], quote_key_type=QuoteKeyType.ALL) def on_push_connected(self): """""" self.push_connected = True self.write_log("推送接口连接成功") self.push_client.subscribe_asset(account=self.account) self.push_client.subscribe_position(account=self.account) self.push_client.subscribe_order(account=self.account) self.push_client.subscribe_quote(list(self.subscribed_symbols)) def on_disconnected(self): self.write_log('推送接口断开链接') self.push_connected = False def on_quote_change(self, tiger_symbol: str, data: list, trading: bool): """""" data = dict(data) symbol, exchange = self.get_vt_symbol_exchange(tiger_symbol) # 如果只推送了时间戳,或只推送了timeline,不向策略中推送新的tick事件 if 'latest_price' not in data and 'bid_price' not in data: return tick = self.ticks.get(symbol, None) if not tick: tick = TickData( symbol=symbol, exchange=exchange, gateway_name=self.gateway_name, datetime=datetime.now(), name=symbol, ) self.ticks[symbol] = tick # 本地止损单的设计依赖于limit up 与limit down(张跌停价格)。目前API中没有提供。 # 所以这里用high low 来代替 limit up 与limit down tick.datetime = datetime.fromtimestamp(int(data["timestamp"]) / 1000) tick.volume = data.get("volume", tick.volume) tick.ask_volume_1 = data.get("ask_size", tick.ask_volume_1) tick.bid_volume_1 = data.get("bid_size", tick.bid_volume_1) tick.pre_close = data.get("prev_close", tick.pre_close) tick.last_price = data.get("latest_price", tick.last_price) tick.open_price = data.get("open", tick.open_price) tick.high_price = data.get("high", tick.high_price) tick.low_price = data.get("low", tick.low_price) tick.ask_price_1 = data.get("ask_price", tick.ask_price_1) tick.bid_price_1 = data.get("bid_price", tick.bid_price_1) tick.limit_down = tick.low_price tick.limit_up = tick.high_price self.on_tick(copy(tick)) def on_asset_change(self, tiger_account: str, data: list): """""" data = dict(data) if "net_liquidation" not in data: return segment = data.get('segment') # 环球账户, 只推送summary的信息,含股票期货 if segment == 'summary' or segment is None: account = tiger_account # 标准账户有『子账户』的概念, 分别推送股票与期货账户的信息 elif segment == 'S': account = 'Security' elif segment == 'C': account = 'Commodity' account = AccountData( accountid=account, balance=round(data["net_liquidation"], 2), frozen=0.0, gateway_name=self.gateway_name, ) self.on_account(account) def on_position_change(self, tiger_account: str, data: list): """""" if tiger_account != self.account: return data = dict(data) # 处理标准与环球账户的差异, 环球的账户的期货信息要从originsymbol中获取,标准的从symbol中获取 origin_symbol = data.get("origin_symbol") if origin_symbol: symbol = origin_symbol2symbol(origin_symbol) else: symbol = data.get('symbol') symbol, exchange = self.get_vt_symbol_exchange(symbol) pos = PositionData( symbol=symbol, exchange=exchange, direction=Direction.NET, volume=int(data["quantity"]), frozen=0.0, price=round(data["average_cost"], 2), pnl=round(data["unrealized_pnl"], 2), gateway_name=self.gateway_name, ) self.on_position(pos) def on_order_change(self, tiger_account: str, data: list): """""" # 处理订阅了多个账户的情况 if tiger_account != self.account: return data = dict(data) origin_symbol = data.get("origin_symbol") if origin_symbol: symbol = origin_symbol2symbol(origin_symbol) else: symbol = data.get('symbol') symbol, exchange = self.get_vt_symbol_exchange(symbol) status = STATUS_TIGER2VT[data["status"]] order_time = data.get('order_time') order = OrderData( symbol=symbol, exchange=exchange, orderid=self.ID_TIGER2VT.get(str(data["id"]), self.get_new_local_id()), direction=DIRECTION_TIGER2VT[data.get('action')], price=data.get("limit_price", 0), volume=data["quantity"], traded=data["filled"], status=status, time=datetime.fromtimestamp(order_time / 1000).strftime("%H:%M:%S") if order_time else datetime.now().strftime("%H:%M:%S"), gateway_name=self.gateway_name, ) self.ID_TIGER2VT[str(data["id"])] = order.orderid self.on_order(order) if status == Status.ALLTRADED: self.tradeid += 1 trade = TradeData( symbol=symbol, exchange=exchange, direction=DIRECTION_TIGER2VT[data.get('action')], tradeid=self.tradeid, orderid=self.ID_TIGER2VT[str(data["id"])], price=data["avg_fill_price"], volume=data["filled"], time=datetime.fromtimestamp(order_time / 1000).strftime("%H:%M:%S") if order_time else datetime.now().strftime("%H:%M:%S"), gateway_name=self.gateway_name, ) self.on_trade(trade) def get_new_local_id(self): self.local_id += 1 return self.local_id def send_order(self, req: OrderRequest): """""" local_id = self.get_new_local_id() order = req.create_order_data(local_id, self.gateway_name) self.on_order(order) self.add_task(self._send_order, req, local_id) return order.vt_orderid def _send_order(self, req: OrderRequest, local_id): """""" try: # 主要处理一些API层面的校验带来的异常,如下单价格错误。 contract = self.get_trading_contract(req.symbol) order = self.trade_client.create_order( account=self.account, contract=contract, action=DIRECTION_VT2TIGER[req.direction], order_type=ORDERTYPE_VT2TIGER[req.type], quantity=int(req.volume), limit_price=round(req.price, 2), ) self.trade_client.place_order(order) self.ID_TIGER2VT[str(order.id)] = local_id self.ID_VT2TIGER[local_id] = str(order.id) except Exception: # 一些订单会在API层面被拒掉,不会推送订单的回报,这里模拟一个订单回报。 # 目前gateway 只实现了mkt 和limit 两种类型的订单,使用其他类型的订单也会出现异常。 # 这时本地的订单已经存在了, 所以返回一个状态为rejected的虚拟订单 symbol = contract2symbol(order.contract) symbol, exchange = self.get_vt_symbol_exchange(symbol) mock_order = OrderData( symbol=symbol, exchange=exchange, orderid=local_id, direction=DIRECTION_TIGER2VT[order.action], price=order.limit_price, volume=order.quantity, traded=0, status=Status.REJECTED, time=datetime.now().strftime("%H:%M:%S"), gateway_name=self.gateway_name, ) self.on_order(mock_order) self.write_log("发单失败") traceback.print_exc() def cancel_order(self, req: CancelRequest): """""" self.add_task(self._cancel_order, req) def _cancel_order(self, req: CancelRequest): """""" try: id = self.ID_VT2TIGER[req.orderid] data = self.trade_client.cancel_order(id=id) if not data: self.write_log("撤单成功") except ApiException: self.write_log(f"撤单请求提交失败:{req.orderid}") traceback.print_exc() except Exception: self.write_log('撤单失败,id:%s' % (req.orderid)) def query_contract(self): """""" self.write_log('开始查询合约信息') # HK Stock # 查询的速度太慢了, 注释掉了 try: symbols_names_HK = self.quote_client.get_symbol_names( lang=Language.zh_CN, market=Market.HK) contract_names_HK = DataFrame(symbols_names_HK, columns=["symbol", "name"]) contractList = list(contract_names_HK["symbol"]) i, n = 0, len(contractList) result = DataFrame() while i < n: i += 50 c = contractList[i - 50:i] r = self.quote_client.get_trade_metas(c) result = result.append(r) sleep(0.1) except: self.write_log('查询港股合约失败') contract_detail_HK = result.sort_values(by="symbol", ascending=True) contract_HK = merge(contract_names_HK, contract_detail_HK, how="left", on="symbol") for ix, row in contract_HK.iterrows(): contract = ContractData( symbol=row["symbol"], exchange=Exchange.SEHK, name=row["name"], product=Product.EQUITY, size=1, min_volume=row["lot_size"], pricetick=row["min_tick"], net_position=True, gateway_name=self.gateway_name, ) self.on_contract(contract) self.contracts[contract.vt_symbol] = contract self.vt_tiger_symbol_map.update({ contract.symbol: (Exchange.SEHK, stock_contract(contract.symbol, currency='HKD')) }) # US Stock symbols_names_US = self.quote_client.get_symbol_names( lang=Language.zh_CN, market=Market.US) contract_US = DataFrame(symbols_names_US, columns=["symbol", "name"]) for ix, row in contract_US.iterrows(): contract = ContractData( symbol=row["symbol"], exchange=Exchange.SMART, name=row["name"], product=Product.EQUITY, size=1, min_volume=1, pricetick=0.001, gateway_name=self.gateway_name, ) self.on_contract(contract) self.vt_tiger_symbol_map.update({ contract.symbol: (Exchange.SMART, stock_contract(contract.symbol, currency='USD')) }) self.contracts[contract.vt_symbol] = contract self.write_log('初始化美股合约完成') # Future contracts exchanges = self.quote_client.get_future_exchanges( sec_type=SecurityType.FUT, lang=Language.zh_CN) exchanges_list = exchanges['code'] contract_futures = DataFrame() for e in exchanges_list: exchange_contract = self.quote_client.get_future_contracts( e, lang=Language.zh_CN) if len(exchange_contract) != 0: contract_futures = contract_futures.append(exchange_contract) for ix, row in contract_futures.iterrows(): contract = ContractData( # symbol 用于查询存储数据 NQ1909 symbol=row.loc['contract_code'], exchange=config_future_exchange(row.loc['exchange']), name=row.loc['name'], product=Product.FUTURES, size=1, min_volume=1, pricetick=0.001, gateway_name=self.gateway_name) self.on_contract(contract) self.vt_tiger_symbol_map.update({ contract.symbol: (contract.exchange, future_contract( symbol=row.type, currency=row.currency, expiry=row.last_trading_date, exchange=row.exchange, multiplier=row.multiplier, )) }) self.contracts[contract.vt_symbol] = contract self.write_log('初始化期货合约完成') def query_account(self): self.write_log('开始查询账户信息') try: assets = self.trade_client.get_assets(segment=True) except ApiException: self.write_log("查询资金失败") return for i in assets: account = AccountData( accountid=self.account, balance=round(i.summary.net_liquidation, 2), frozen=0.0, gateway_name=self.gateway_name, ) # 下面两个账户仅作vntrader 展示使用。 # 环球账户的资产信息可以通过 summary 获取,标准账户需要区分股票和期货Segment, 且没有合并的summary信息。 sec_account = AccountData( accountid='Security', balance=round(i.segments.get('S').net_liquidation, 2), frozen=0.0, gateway_name=self.gateway_name, ) com_account = AccountData( accountid='Commodity', balance=round(i.segments.get('C').net_liquidation, 2), frozen=0.0, gateway_name=self.gateway_name, ) self.on_account(account) self.on_account(sec_account) self.on_account(com_account) self.write_log('账户信息查询完成') def query_position(self): """""" self.write_log('开始查询持仓信息') try: # 分别查询股票和期货的持仓 stock_position = self.trade_client.get_positions( sec_type=SecurityType.STK) future_position = self.trade_client.get_positions( sec_type=SecurityType.FUT) positions = stock_position + future_position except ApiException: self.write_log("查询持仓失败") return for i in positions: try: # 标准账户里面的symbol 是origin symbol symbol, exchange = self.get_vt_symbol_exchange( contract2symbol(i.contract)) pos = PositionData( symbol=symbol, exchange=exchange, direction=Direction.NET, volume=int(i.quantity), frozen=0.0, price=i.average_cost, pnl=float(i.unrealized_pnl), gateway_name=self.gateway_name, ) self.on_position(pos) except: self.write_log('处理持仓失败,symbol: %s' % (i.contract.symbol)) self.write_log('持仓信息查询完成') def query_order(self): self.write_log('开始查询历史订单信息') try: # 需要分别查询股票和期货的订单 stock_data = self.trade_client.get_orders( account=self.account, sec_type=SecurityType.STK) future_data = self.trade_client.get_orders( account=self.account, sec_type=SecurityType.FUT) data = stock_data + future_data data = sorted(data, key=lambda x: x.order_time, reverse=False) except: traceback.print_exc() self.write_log("查询订单失败") return self.process_order(data) self.process_deal(data) self.write_log('历史订单处理完成') def close(self): """""" self.active = False if self.push_client: # 退出前先进行退订操作,避免下次打开时的订阅异常 try: self.push_client.unsubscribe_asset() self.push_client.unsubscribe_position() self.push_client.unsubscribe_order() self.push_client.unsubscribe_quote( symbols=self.subscribed_symbols) self.push_client.disconnect() except: pass def process_order(self, data): """""" for i in data: try: symbol = contract2symbol(i.contract) symbol, exchange = self.get_vt_symbol_exchange(symbol) local_id = self.get_new_local_id() order = OrderData( symbol=symbol, exchange=exchange, orderid=local_id, direction=DIRECTION_TIGER2VT[i.action], price=i.limit_price if i.limit_price else 0.0, volume=i.quantity, traded=i.filled, status=STATUS_TIGER2VT[i.status], time=datetime.fromtimestamp(i.order_time / 1000).strftime("%H:%M:%S"), gateway_name=self.gateway_name, ) self.ID_TIGER2VT[str(i.id)] = local_id self.on_order(order) except: pass self.ID_VT2TIGER = {v: k for k, v in self.ID_TIGER2VT.items()} def process_deal(self, data): """ Process trade data for both query and update. """ for i in data: if i.status == OrderStatus.PARTIALLY_FILLED or i.status == OrderStatus.FILLED: try: symbol = contract2symbol(i.contract) symbol, exchange = self.get_vt_symbol_exchange(symbol) self.tradeid += 1 trade = TradeData( symbol=symbol, exchange=exchange, direction=DIRECTION_TIGER2VT[i.action], tradeid=self.tradeid, orderid=self.ID_TIGER2VT[str(i.id)], price=i.avg_fill_price, volume=i.filled, time=datetime.fromtimestamp(i.trade_time / 1000).strftime("%H:%M:%S"), gateway_name=self.gateway_name, ) self.on_trade(trade) except: pass def get_vt_symbol_exchange(self, symbol): try: exchange = self.vt_tiger_symbol_map.get(symbol)[0] except: self.write_log('can not get symbol %s' % (symbol)) return symbol, exchange def get_trading_contract(self, symbol): """用于下单时获取交易合约 :param symbol: :return: """ try: return self.vt_tiger_symbol_map.get(symbol)[1] except: self.write_log('cannot get traidng contract for symbol %s' % (symbol))