def __init__(self, api, symbol, price="ACTIVE", init_pos=None, offset_priority="今昨,开", trade_chan=None): """ 创建目标持仓task实例,负责调整归属于该task的持仓(默认为整个账户的该合约净持仓) Args: api (TqApi): TqApi实例,该task依托于指定api下单/撤单 symbol (str): 负责调整的合约代码 price (str): [可选]下单方式, ACTIVE=对价下单, PASSIVE=挂价下单 init_pos (int): [可选]初始持仓,默认整个账户的该合净持仓 offset_priority (str): [可选]开平仓顺序,昨=平昨仓,今=平今仓,开=开仓,逗号=等待之前操作完成 对于下单指令区分平今/昨的交易所(如上期所),按照今/昨仓的数量计算是否能平今/昨仓 对于下单指令不区分平今/昨的交易所(如中金所),按照“先平当日新开仓,再平历史仓”的规则计算是否能平今/昨仓 * "今昨,开" 表示先平今仓,再平昨仓,等待平仓完成后开仓,对于没有单向大边的品种避免了开仓保证金不足 * "今昨开" 表示先平今仓,再平昨仓,并开仓,所有指令同时发出,适合有单向大边的品种 * "昨开" 表示先平昨仓,再开仓,禁止平今仓,适合股指这样平今手续费较高的品种 trade_chan (TqChan): [可选]成交通知channel, 当有成交发生时会将成交手数(多头为正数,空头为负数)发到该channel上 """ super(TargetPosTask, self).__init__() self.api = api self.symbol = symbol self.exchange = symbol.split(".")[0] self.price = price self.pos = self.api.get_position(self.symbol) self.current_pos = init_pos self.offset_priority = offset_priority self.pos_chan = TqChan(self.api, last_only=True) self.trade_chan = trade_chan if trade_chan is not None else TqChan(self.api) self.task = self.api.create_task(self._target_pos_task())
def __init__(self, api, symbol, direction, offset, volume, limit_price=None, order_chan = None, trade_chan = None): """ 创建下单task实例 Args: api (TqApi): TqApi实例,该task依托于指定api下单/撤单 symbol (str): 拟下单的合约symbol, 格式为 交易所代码.合约代码, 例如 "SHFE.cu1801" direction (str): "BUY" 或 "SELL" offset (str): "OPEN", "CLOSE" 或 "CLOSETODAY" volume (int): 需要下单的手数 limit_price (float): [可选]下单价格, 默认市价单 order_chan (TqChan): [可选]委托单通知channel, 当委托单状态发生时会将委托单信息发到该channel上 trade_chan (TqChan): [可选]成交通知channel, 当有成交发生时会将成交手数(多头为正数,空头为负数)发到该channel上 """ self.api = api self.symbol = symbol self.direction = direction self.offset = offset self.volume = volume self.limit_price = limit_price self.order_chan = order_chan if order_chan is not None else TqChan(self.api) self.trade_chan = trade_chan if trade_chan is not None else TqChan(self.api) self.task = self.api.create_task(self._run())
def __init__(self, api: TqApi, symbol: str, price: str = "ACTIVE", offset_priority: str = "今昨,开", trade_chan: Optional[TqChan] = None) -> None: """ 创建目标持仓task实例,负责调整归属于该task的持仓 **(默认为整个账户的该合约净持仓)**. **注意:** 1. TargetPosTask 在 set_target_volume 时并不下单或撤单, 它的下单和撤单动作, 是在之后的每次 wait_update 时执行的. 因此, **需保证 set_target_volume 后还会继续调用wait_update()** 。 2. 请勿在使用 TargetPosTask 的同时使用 insert_order() 函数, 否则将导致 TargetPosTask 报错或错误下单。 Args: api (TqApi): TqApi实例,该task依托于指定api下单/撤单 symbol (str): 负责调整的合约代码 price (str): [可选]下单方式, ACTIVE=对价下单, PASSIVE=挂价下单. * 在持仓调整过程中,若下单方向为买: 对价为卖一价, 挂价为买一价 * 在持仓调整过程中,若下单方向为卖: 对价为买一价, 挂价为卖一价 offset_priority (str): [可选]开平仓顺序,昨=平昨仓,今=平今仓,开=开仓,逗号=等待之前操作完成 对于下单指令区分平今/昨的交易所(如上期所),按照今/昨仓的数量计算是否能平今/昨仓 对于下单指令不区分平今/昨的交易所(如中金所),按照“先平当日新开仓,再平历史仓”的规则计算是否能平今/昨仓 * "今昨,开" 表示先平今仓,再平昨仓,等待平仓完成后开仓,对于没有单向大边的品种避免了开仓保证金不足 * "今昨开" 表示先平今仓,再平昨仓,并开仓,所有指令同时发出,适合有单向大边的品种 * "昨开" 表示先平昨仓,再开仓,禁止平今仓,适合股指这样平今手续费较高的品种 trade_chan (TqChan): [可选]成交通知channel, 当有成交发生时会将成交手数(多头为正数,空头为负数)发到该channel上 """ super(TargetPosTask, self).__init__() self._api = api if symbol not in api._data.get("quotes", {}): raise Exception("代码 %s 不存在, 请检查合约代码是否填写正确" % (symbol)) self._symbol = symbol self._exchange = symbol.split(".")[0] if price not in ("ACTIVE", "PASSIVE"): raise Exception("下单方式(price) %s 错误, 请检查 price 参数是否填写正确" % (price)) self._price = price if len( offset_priority.replace(",", "").replace("今", "", 1).replace( "昨", "", 1).replace("开", "", 1)) > 0: raise Exception( "开平仓顺序(offset_priority) %s 错误, 请检查 offset_priority 参数是否填写正确" % (offset_priority)) self._offset_priority = offset_priority self._pos = self._api.get_position(self._symbol) self._pos_chan = TqChan(self._api, last_only=True) self._trade_chan = trade_chan if trade_chan is not None else TqChan( self._api) self._task = self._api.create_task(self._target_pos_task())
def __init__(self, api, symbol, direction, offset, volume, limit_price=None, order_chan=None, trade_chan=None): """ 创建下单task实例 Args: api (TqApi): TqApi实例,该task依托于指定api下单/撤单 symbol (str): 拟下单的合约symbol, 格式为 交易所代码.合约代码, 例如 "SHFE.cu1801" direction (str): "BUY" 或 "SELL" offset (str): "OPEN", "CLOSE" 或 "CLOSETODAY" volume (int): 需要下单的手数 limit_price (float): [可选]下单价格, 默认市价单 order_chan (TqChan): [可选]委托单通知channel, 当委托单状态发生时会将委托单信息发到该channel上 trade_chan (TqChan): [可选]成交通知channel, 当有成交发生时会将成交手数(多头为正数,空头为负数)发到该channel上 """ self._api = api if symbol not in api._data.get("quotes", {}): raise Exception("代码 %s 不存在, 请检查合约代码是否填写正确" % (symbol)) self._symbol = symbol if direction not in ("BUY", "SELL"): raise Exception("下单方向(direction) %s 错误, 请检查 direction 参数是否填写正确" % (direction)) self._direction = direction if offset not in ("OPEN", "CLOSE", "CLOSETODAY"): raise Exception("开平标志(offset) %s 错误, 请检查 offset 是否填写正确" % (offset)) self._offset = offset self._volume = int(volume) self._limit_price = float( limit_price) if limit_price is not None else None self._order_chan = order_chan if order_chan is not None else TqChan( self._api) self._trade_chan = trade_chan if trade_chan is not None else TqChan( self._api) self._task = self._api.create_task(self._run())
async def _run(self): """负责追价下单的task""" async with self.api.register_update_notify() as update_chan: # 确保获得初始行情 while self.quote.datetime == "": await update_chan.recv() while self.volume != 0: limit_price = self._get_price() insert_order_task = InsertOrderTask(self.api, self.symbol, self.direction, self.offset, self.volume, limit_price=limit_price, trade_chan=self.trade_chan) order = await insert_order_task.order_chan.recv() check_chan = TqChan(self.api, last_only=True) check_task = self.api.create_task( self._check_price(check_chan, limit_price, order)) try: await insert_order_task.task order = insert_order_task.order_chan.recv_latest(order) self.volume = order.volume_left if self.volume != 0 and not check_task.done(): raise Exception( "遇到错单: %s %s %s %d手 %f %s" % (self.symbol, self.direction, self.offset, self.volume, limit_price, order.last_msg)) finally: await check_chan.close() await check_task
def __init__(self, api, symbol, direction, offset, volume, price = "ACTIVE", trade_chan = None): """ 创建追价下单task实例 Args: api (TqApi): TqApi实例,该task依托于指定api下单/撤单 symbol (str): 拟下单的合约symbol, 格式为 交易所代码.合约代码, 例如 "SHFE.cu1801" direction (str): "BUY" 或 "SELL" offset (str): "OPEN", "CLOSE" 或 "CLOSETODAY" volume (int): 需要下单的手数 price (str): [可选]下单方式, ACTIVE=对价下单, PASSIVE=挂价下单 trade_chan (TqChan): [可选]成交通知channel, 当有成交发生时会将成交手数(多头为正数,空头为负数)发到该channel上 """ self.api = api self.symbol = symbol self.direction = direction self.offset = offset self.volume = volume self.price = price self.trade_chan = trade_chan if trade_chan is not None else TqChan(self.api) self.quote = self.api.get_quote(self.symbol) self.task = self.api.create_task(self._run())
def __init__(self, api, symbol, price="ACTIVE", offset_priority="今昨,开", trade_chan=None): """ 创建目标持仓task实例,负责调整归属于该task的持仓(默认为整个账户的该合约净持仓) Args: api (TqApi): TqApi实例,该task依托于指定api下单/撤单 symbol (str): 负责调整的合约代码 price (str): [可选]下单方式, ACTIVE=对价下单, PASSIVE=挂价下单 offset_priority (str): [可选]开平仓顺序,昨=平昨仓,今=平今仓,开=开仓,逗号=等待之前操作完成 对于下单指令区分平今/昨的交易所(如上期所),按照今/昨仓的数量计算是否能平今/昨仓 对于下单指令不区分平今/昨的交易所(如中金所),按照“先平当日新开仓,再平历史仓”的规则计算是否能平今/昨仓 * "今昨,开" 表示先平今仓,再平昨仓,等待平仓完成后开仓,对于没有单向大边的品种避免了开仓保证金不足 * "今昨开" 表示先平今仓,再平昨仓,并开仓,所有指令同时发出,适合有单向大边的品种 * "昨开" 表示先平昨仓,再开仓,禁止平今仓,适合股指这样平今手续费较高的品种 trade_chan (TqChan): [可选]成交通知channel, 当有成交发生时会将成交手数(多头为正数,空头为负数)发到该channel上 """ super(TargetPosTask, self).__init__() self.api = api if symbol not in api.data.get("quotes", {}): raise Exception("代码 %s 不存在, 请检查合约代码是否填写正确" % (symbol)) self.symbol = symbol self.exchange = symbol.split(".")[0] if price not in ("ACTIVE", "PASSIVE"): raise Exception("下单方式(price) %s 错误, 请检查 price 参数是否填写正确" % (price)) self.price = price if len( offset_priority.replace(",", "").replace("今", "", 1).replace( "昨", "", 1).replace("开", "", 1)) > 0: raise Exception( "开平仓顺序(offset_priority) %s 错误, 请检查 offset_priority 参数是否填写正确" % (offset_priority)) self.offset_priority = offset_priority self.pos = self.api.get_position(self.symbol) self.pos_chan = TqChan(self.api, last_only=True) self.trade_chan = trade_chan if trade_chan is not None else TqChan( self.api) self.task = self.api.create_task(self._target_pos_task())
def __init__(self, api, symbol, price="ACTIVE", init_pos=0, trade_chan=None): """ 创建目标持仓task实例 Args: api (TqApi): TqApi实例,该task依托于指定api下单/撤单 symbol (str): 负责调整的合约代码 price (str): [可选]下单方式, ACTIVE=对价下单, PASSIVE=挂价下单 init_pos (int): [可选]初始持仓,默认为0 trade_chan (TqChan): [可选]成交通知channel, 当有成交发生时会将成交手数(多头为正数,空头为负数)发到该channel上 """ self.api = api self.symbol = symbol self.price = price self.init_pos = init_pos self.pos_chan = TqChan(last_only=True) self.trade_chan = trade_chan if trade_chan is not None else TqChan() self.task = self.api.create_task(self._target_pos_task())
async def _send_snapshot(self): async with TqChan(self.api, last_only=True) as update_chan: self.data["_listener"].add(update_chan) while self.data.get("mdhis_more_data", True): await update_chan.recv() # 发送合约信息截面 quotes = {} for ins, quote in self.data["quotes"].items(): if not ins.startswith("_"): quotes[ins] = { "datetime": "", "ask_price1": float("nan"), "ask_volume1": 0, "bid_price1": float("nan"), "bid_volume1": 0, "last_price": float("nan"), "highest": float("nan"), "lowest": float("nan"), "open": None, "close": None, "average": float("nan"), "volume": 0, "amount": float("nan"), "open_interest": 0, "settlement": None, "lower_limit": None, "upper_limit": None, "pre_open_interest": None, "pre_settlement": None, "pre_close": None, "price_tick": quote["price_tick"], "price_decs": quote["price_decs"], "volume_multiple": quote["volume_multiple"], "max_limit_order_volume": quote["max_limit_order_volume"], "max_market_order_volume": quote["max_market_order_volume"], "min_limit_order_volume": quote["min_limit_order_volume"], "min_market_order_volume": quote["min_market_order_volume"], "underlying_symbol": quote["underlying_symbol"], "strike_price": quote["strike_price"], "change": None, "change_percent": None, "expired": None, } self.diffs.append({ "quotes": quotes, "ins_list": "", "mdhis_more_data": False, })
async def _send_snapshot(self): """发送初始合约信息""" async with TqChan(self.api, last_only=True) as update_chan: # 等待与行情服务器连接成功 self.data["_listener"].add(update_chan) while self.data.get("mdhis_more_data", True): await update_chan.recv() # 发送合约信息截面 quotes = {} for ins, quote in self.data["quotes"].items(): if not ins.startswith("_"): quotes[ins] = { "open": None, # 填写None: 删除api中的这个字段 "close": None, "settlement": None, "lower_limit": None, "upper_limit": None, "pre_open_interest": None, "pre_settlement": None, "pre_close": None, "ins_class": quote.get("ins_class", ""), "margin": quote.get( "margin"), # 用于内部实现模拟交易, 不作为api对外可用数据(即 Quote 类中无此字段) "commission": quote.get("commission" ), # 用于内部实现模拟交易, 不作为api对外可用数据(即 Quote 类中无此字段) "price_tick": quote["price_tick"], "price_decs": quote["price_decs"], "volume_multiple": quote["volume_multiple"], "max_limit_order_volume": quote["max_limit_order_volume"], "max_market_order_volume": quote["max_market_order_volume"], "min_limit_order_volume": quote["min_limit_order_volume"], "min_market_order_volume": quote["min_market_order_volume"], "underlying_symbol": quote["underlying_symbol"], "strike_price": quote["strike_price"], "expired": None, "trading_time": quote.get("trading_time"), "expire_datetime": quote.get("expire_datetime"), "delivery_month": quote.get("delivery_month"), "delivery_year": quote.get("delivery_year"), } self.diffs.append({ "quotes": quotes, "ins_list": "", "mdhis_more_data": False, })
def __init__(self, api, symbol, direction, offset, volume, price="ACTIVE", trade_chan=None): """ 创建追价下单task实例 Args: api (TqApi): TqApi实例,该task依托于指定api下单/撤单 symbol (str): 拟下单的合约symbol, 格式为 交易所代码.合约代码, 例如 "SHFE.cu1801" direction (str): "BUY" 或 "SELL" offset (str): "OPEN", "CLOSE" 或 "CLOSETODAY" volume (int): 需要下单的手数 price (str): [可选]下单方式, ACTIVE=对价下单, PASSIVE=挂价下单 trade_chan (TqChan): [可选]成交通知channel, 当有成交发生时会将成交手数(多头为正数,空头为负数)发到该channel上 """ self._api = api if symbol not in api._data.get("quotes", {}): raise Exception("代码 %s 不存在, 请检查合约代码是否填写正确" % (symbol)) self._symbol = symbol if direction not in ("BUY", "SELL"): raise Exception("下单方向(direction) %s 错误, 请检查 direction 参数是否填写正确" % (direction)) self._direction = direction if offset not in ("OPEN", "CLOSE", "CLOSETODAY"): raise Exception("开平标志(offset) %s 错误, 请检查 offset 是否填写正确" % (offset)) self._offset = offset self._volume = int(volume) if self._volume <= 0: raise Exception("下单手数(volume) %s 错误, 请检查 volume 是否填写正确" % (volume)) if price not in ("ACTIVE", "PASSIVE"): raise Exception("下单方式(price) %s 错误, 请检查 price 参数是否填写正确" % (price)) self._price = price self._trade_chan = trade_chan if trade_chan is not None else TqChan( self._api) self._quote = self._api.get_quote(self._symbol) self._task = self._api.create_task(self._run())
async def _run(self): """负责追价下单的task""" async with self.api.register_update_notify() as update_chan: # 确保获得初始行情 while self.quote["datetime"] == "": await update_chan.recv() while self.volume != 0: limit_price = self._get_price() insert_order_task = InsertOrderTask(self.api, self.symbol, self.direction, self.offset, self.volume, limit_price = limit_price, trade_chan = self.trade_chan) order = await insert_order_task.order_chan.recv() check_chan = TqChan(last_only=True) check_task = self.api.create_task(self._check_price(check_chan, limit_price, order)) await insert_order_task.task await check_chan.close() await check_task order = insert_order_task.order_chan.recv_latest(order) self.volume = order["volume_left"]
def link_tq(api): """ 处理py进程到天勤的连接 根据天勤提供的命令行参数, 决定 TqApi 工作方式 * 直接调整api的参数 TqApi运行过程中的一批信息主动发送到天勤显示 * 进程启动和停止 * set_chart_data 指令全部发往天勤绘图 * 所有 log / print 信息传递一份 * exception 发送一份 * 所有报单/成交记录抄送一份 :return: (account, backtest, md_url) """ from tqsdk.api import TqChan, TqAccount from tqsdk.sim import TqSim # 解析命令行参数 parser = argparse.ArgumentParser() # 天勤连接基本参数 parser.add_argument('--_action', type=str, required=False) parser.add_argument('--_tq_pid', type=int, required=False) parser.add_argument('--_tq_url', type=str, required=False) # action==run时需要这几个 parser.add_argument('--_broker_id', type=str, required=False) parser.add_argument('--_account_id', type=str, required=False) parser.add_argument('--_password', type=str, required=False) # action==backtest时需要这几个 parser.add_argument('--_start_dt', type=str, required=False) parser.add_argument('--_end_dt', type=str, required=False) # action==mdreplay时需要这几个 parser.add_argument('--_ins_url', type=str, required=False) parser.add_argument('--_md_url', type=str, required=False) args, unknown = parser.parse_known_args() # 非天勤启动时直接返回 if args._action is None: return None, None if args._tq_pid is None: raise Exception("_tq_pid 参数缺失") if args._tq_url is None: raise Exception("_tq_url 参数缺失") if args._action == "run" and (not args._broker_id or not args._account_id or not args._password): raise Exception("run 必要参数缺失") if args._action == "backtest" and (not args._start_dt or not args._end_dt): raise Exception("backtest 必要参数缺失") if args._action == "mdreplay" and (not args._ins_url or not args._md_url): raise Exception("mdreplay 必要参数缺失") # 监控天勤进程存活情况 TqMonitorThread(args._tq_pid).start() # 建立到天勤进程的连接 tq_send_chan, tq_recv_chan = TqChan(api), TqChan(api) # 连接到天勤的channel api.create_task(api._connect(args._tq_url, tq_send_chan, tq_recv_chan)) # 启动到天勤客户端的连接 # 根据运行模式分别执行不同的初始化任务 if args._action == "run": instance = SingleInstance(args._account_id) api._account = TqAccount(args._broker_id, args._account_id, args._password) api._backtest = None dt_func = lambda: int(datetime.datetime.now().timestamp() * 1e9) tq_send_chan.send_nowait({ "aid": "register_instance", "instance_id": instance.instance_id, "full_path": get_self_full_name(), "instance_pid": os.getpid(), "instance_type": "RUN", "broker_id": args._broker_id, "account_id": args._account_id, "password": args._password, }) elif args._action == "backtest": instance = SingleInstance("%s-%s" % (args._start_dt, args._end_dt)) if not isinstance(api._account, TqSim): api._account = TqSim() from tqsdk.backtest import TqBacktest start_date = datetime.datetime.strptime(args._start_dt, '%Y%m%d') end_date = datetime.datetime.strptime(args._end_dt, '%Y%m%d') api._backtest = TqBacktest(start_dt=start_date, end_dt=end_date) dt_func = lambda: api._account._get_current_timestamp() tq_send_chan.send_nowait({ "aid": "register_instance", "instance_id": instance.instance_id, "full_path": get_self_full_name(), "instance_pid": os.getpid(), "instance_type": "BACKTEST", "start_dt": args._start_dt, "end_dt": args._end_dt, }) elif args._action == "mdreplay": instance = SingleInstance(args._account_id) api._account = TqSim(account_id=args._account_id) api._backtest = None api._md_url = args._md_url api._ins_url = args._ins_url dt_func = lambda: api._account._get_current_timestamp() tq_send_chan.send_nowait({ "aid": "register_instance", "instance_id": instance.instance_id, "full_path": get_self_full_name(), "instance_pid": os.getpid(), "instance_type": "RUN", "account_id": "SIM", }) else: raise Exception("_action 参数异常") # print输出, exception信息转发到天勤 logger = logging.getLogger("TQ") logger.setLevel(logging.INFO) logger.addHandler(LogHandlerChan(tq_send_chan, dt_func=dt_func)) # log输出到天勤接口 sys.stdout = PrintWriterToLog(logger) # print信息转向log输出 sys.excepthook = partial(exception_handler, api, sys.excepthook) # exception信息转向log输出 # 向api注入监控任务, 将账户交易信息主动推送到天勤 api.create_task(account_watcher(api, dt_func, tq_send_chan)) return tq_send_chan, tq_recv_chan
class TargetPosTask(object): """目标持仓 task, 该 task 可以将指定合约调整到目标头寸""" def __init__(self, api, symbol, price="ACTIVE", offset_priority="今昨,开", trade_chan=None): """ 创建目标持仓task实例,负责调整归属于该task的持仓(默认为整个账户的该合约净持仓) Args: api (TqApi): TqApi实例,该task依托于指定api下单/撤单 symbol (str): 负责调整的合约代码 price (str): [可选]下单方式, ACTIVE=对价下单, PASSIVE=挂价下单 init_pos (int): [可选]初始持仓,默认整个账户的该合净持仓 offset_priority (str): [可选]开平仓顺序,昨=平昨仓,今=平今仓,开=开仓,逗号=等待之前操作完成 对于下单指令区分平今/昨的交易所(如上期所),按照今/昨仓的数量计算是否能平今/昨仓 对于下单指令不区分平今/昨的交易所(如中金所),按照“先平当日新开仓,再平历史仓”的规则计算是否能平今/昨仓 * "今昨,开" 表示先平今仓,再平昨仓,等待平仓完成后开仓,对于没有单向大边的品种避免了开仓保证金不足 * "今昨开" 表示先平今仓,再平昨仓,并开仓,所有指令同时发出,适合有单向大边的品种 * "昨开" 表示先平昨仓,再开仓,禁止平今仓,适合股指这样平今手续费较高的品种 trade_chan (TqChan): [可选]成交通知channel, 当有成交发生时会将成交手数(多头为正数,空头为负数)发到该channel上 """ super(TargetPosTask, self).__init__() self.api = api self.symbol = symbol self.exchange = symbol.split(".")[0] self.price = price self.pos = self.api.get_position(self.symbol) self.offset_priority = offset_priority self.pos_chan = TqChan(self.api, last_only=True) self.trade_chan = trade_chan if trade_chan is not None else TqChan( self.api) self.task = self.api.create_task(self._target_pos_task()) def set_target_volume(self, volume): """ 设置目标持仓手数 Args: volume (int): 目标持仓手数,正数表示多头,负数表示空头,0表示空仓 Example:: # 设置 rb1810 持仓为多头5手 from tqsdk import TqApi, TqSim, TargetPosTask api = TqApi(TqSim()) target_pos = TargetPosTask(api, "SHFE.rb1810") target_pos.set_target_volume(5) while True: api.wait_update() """ self.pos_chan.send_nowait(volume) def _get_order(self, offset, vol): """ 根据指定的offset和预期下单手数vol, 返回符合要求的委托单最大报单手数 :param offset: "昨" / "今" / "开" :param vol: int, <0表示SELL, >0表示BUY :return: order_offset: "CLOSE"/"CLOSETODAY"/"OPEN"; order_dir: "BUY"/"SELL"; "order_volume": >=0, 报单手数 """ if vol > 0: # 买单(增加净持仓) order_dir = "BUY" pos_all = self.pos.pos_short else: # 卖单 order_dir = "SELL" pos_all = self.pos.pos_long if offset == "昨": order_offset = "CLOSE" if self.exchange == "SHFE" or self.exchange == "INE": if vol > 0: pos_all = self.pos.pos_short_his else: pos_all = self.pos.pos_long_his frozen_volume = sum([ order.volume_left for order in self.pos.orders.values() if not order.is_dead() and order.offset == order_offset and order.direction == order_dir ]) else: frozen_volume = sum([ order.volume_left for order in self.pos.orders.values() if not order.is_dead() and order.offset != "OPEN" and order.direction == order_dir ]) order_volume = max(0, pos_all - frozen_volume) elif offset == "今": if self.exchange == "SHFE" or self.exchange == "INE": order_offset = "CLOSETODAY" if vol > 0: pos_all = self.pos.pos_short_today else: pos_all = self.pos.pos_long_today frozen_volume = sum([ order.volume_left for order in self.pos.orders.values() if not order.is_dead() and order.offset == order_offset and order.direction == order_dir ]) else: order_offset = "CLOSE" frozen_volume = sum([ order.volume_left for order in self.pos.orders.values() if not order.is_dead() and order.offset != "OPEN" and order.direction == order_dir ]) order_volume = max(0, pos_all - frozen_volume) elif offset == "开": order_offset = "OPEN" order_volume = abs(vol) else: order_offset = "" order_volume = 0 return order_offset, order_dir, order_volume async def _target_pos_task(self): """负责调整目标持仓的task""" async for target_pos in self.pos_chan: # 确定调仓增减方向 delta_volume = target_pos - self.pos.pos all_tasks = [] for each_priority in self.offset_priority + ",": # 按不同模式的优先级顺序报出不同的offset单,股指(“昨开”)平昨优先从不平今就先报平昨,原油平今优先("今昨开")就报平今 if each_priority == ",": await gather(*[each.task for each in all_tasks]) all_tasks = [] continue order_offset, order_dir, order_volume = self._get_order( each_priority, delta_volume) if order_volume == 0: # 如果没有则直接到下一种offset continue order_task = InsertOrderUntilAllTradedTask( self.api, self.symbol, order_dir, offset=order_offset, volume=order_volume, price=self.price, trade_chan=self.trade_chan) all_tasks.append(order_task) delta_volume -= order_volume if order_dir == "BUY" else -order_volume self.current_pos = target_pos
class TargetPosTask: """目标持仓task, 该task可以将指定合约调整到目标头寸""" def __init__(self, api, symbol, price="ACTIVE", init_pos=0, trade_chan=None): """ 创建目标持仓task实例 Args: api (TqApi): TqApi实例,该task依托于指定api下单/撤单 symbol (str): 负责调整的合约代码 price (str): [可选]下单方式, ACTIVE=对价下单, PASSIVE=挂价下单 init_pos (int): [可选]初始持仓,默认为0 trade_chan (TqChan): [可选]成交通知channel, 当有成交发生时会将成交手数(多头为正数,空头为负数)发到该channel上 """ self.api = api self.symbol = symbol self.price = price self.init_pos = init_pos self.pos_chan = TqChan(last_only=True) self.trade_chan = trade_chan if trade_chan is not None else TqChan() self.task = self.api.create_task(self._target_pos_task()) def set_target_volume(self, volume): """ 设置目标持仓手数 Args: volume (int): 目标持仓手数,正数表示多头,负数表示空头,0表示空仓 Example:: # 设置 rb1810 持仓为多头5手 from tqsdk.api import TqApi from tqsdk.lib import TargetPosTask api = TqApi("SIM") target_pos = TargetPosTask(api, "SHFE.rb1810") while True: api.wait_update() target_pos.set_target_volume(5) """ self.pos_chan.put_nowait(volume) async def _target_pos_task(self): """负责调整目标持仓的task""" current_pos = self.init_pos async for target_pos in self.pos_chan: if (current_pos < 0 and target_pos > current_pos) or (current_pos > 0 and target_pos < current_pos): # 平仓 vol = min(abs(target_pos-current_pos), abs(current_pos)) insert_order_until_all_traded_task = InsertOrderUntilAllTradedTask(self.api, self.symbol, "BUY" if current_pos < 0 else "SELL", "CLOSE", vol, price = self.price, trade_chan=self.trade_chan) await insert_order_until_all_traded_task.task current_pos += vol if current_pos < 0 else -vol if target_pos != current_pos: # 开仓 vol = target_pos-current_pos insert_order_until_all_traded_task = InsertOrderUntilAllTradedTask(self.api, self.symbol, "BUY" if vol > 0 else "SELL", "OPEN", abs(vol), price = self.price, trade_chan=self.trade_chan) await insert_order_until_all_traded_task.task current_pos += vol
async def _gen_serial(self, ins, dur): """k线/tick 序列的 async generator, yield 出来的行情数据带有时间戳, 因此 _send_diff 可以据此归并""" # 先定位左端点, focus_datetime 是 lower_bound ,这里需要的是 upper_bound # 因此将 view_width 和 focus_position 设置成一样,这样 focus_datetime 所对应的 k线刚好位于屏幕外 chart_info = { "aid": "set_chart", "chart_id": TqApi._generate_chart_id("backtest", ins, dur // 1000000000), "ins_list": ins, "duration": dur, "view_width": 8964, "focus_datetime": int(self.current_dt), "focus_position": 8964, } chart = TqApi._get_obj(self.data, ["charts", chart_info["chart_id"]]) current_id = None # 当前数据指针 serial = TqApi._get_obj( self.data, ["klines", ins, str(dur)] if dur != 0 else ["ticks", ins]) async with TqChan(self.api, last_only=True) as update_chan: serial["_listener"].add(update_chan) chart["_listener"].add(update_chan) await self.md_send_chan.send(chart_info.copy()) try: async for _ in update_chan: if not (chart_info.items() <= TqApi._get_obj( chart, ["state"]).items()): # 当前请求还没收齐回应, 不应继续处理 continue left_id = chart.get("left_id", -1) right_id = chart.get("right_id", -1) last_id = serial.get("last_id", -1) if (left_id == -1 and right_id == -1) or last_id == -1: # 定位信息还没收到, 或数据序列还没收到 continue if self.data.get("mdhis_more_data", True): self.data["_listener"].add(update_chan) continue else: self.data["_listener"].discard(update_chan) if current_id is None: current_id = max(left_id, 0) while True: if current_id > last_id: # 当前 id 已超过 last_id return if current_id - chart_info.get("left_kline_id", left_id) > 5000: # 当前 id 已超出订阅范围, 需重新订阅后续数据 chart_info["left_kline_id"] = current_id chart_info.pop("focus_datetime", None) chart_info.pop("focus_position", None) await self.md_send_chan.send(chart_info.copy()) if current_id > right_id: break item = serial["data"].get(str(current_id), {}).copy() del item["_path"] del item["_listener"] if dur == 0: diff = { "ticks": { ins: { "last_id": current_id, "data": { str(current_id): item, str(current_id - 8964): None, } } } } if item["datetime"] > self.end_dt: # 超过结束时间 return yield item[ "datetime"], diff, self._get_quotes_from_tick( item) else: diff = { "klines": { ins: { str(dur): { "last_id": current_id, "data": { str(current_id): { "datetime": item["datetime"], "open": item["open"], "high": item["open"], "low": item["open"], "close": item["open"], "volume": 0, "open_oi": item["open_oi"], "close_oi": item["open_oi"], }, str(current_id - 8964): None, } } } } } timestamp = item[ "datetime"] if dur < 86400000000000 else TqApi._get_trading_day_start_time( item["datetime"]) if timestamp > self.end_dt: # 超过结束时间 return yield timestamp, diff, None diff = { "klines": { ins: { str(dur): { "data": { str(current_id): item, } } } } } timestamp = item[ "datetime"] + dur - 1000 if dur < 86400000000000 else TqApi._get_trading_day_end_time( item["datetime"]) if timestamp > self.end_dt: # 超过结束时间 return yield timestamp, diff, self._get_quotes_from_kline( self.data["quotes"][ins], timestamp, item) current_id += 1 finally: # 释放chart资源 chart_info["ins_list"] = "" await self.md_send_chan.send(chart_info.copy())
class TargetPosTask(object, metaclass=TargetPosTaskSingleton): """目标持仓 task, 该 task 可以将指定合约调整到目标头寸""" def __init__(self, api: TqApi, symbol: str, price: str = "ACTIVE", offset_priority: str = "今昨,开", trade_chan: Optional[TqChan] = None) -> None: """ 创建目标持仓task实例,负责调整归属于该task的持仓 **(默认为整个账户的该合约净持仓)**. **注意:** TargetPosTask 在 set_target_volume 时并不下单或撤单, 它的下单和撤单动作, 是在之后的每次 wait_update 时执行的. 因此, **需保证 set_target_volume 后还会继续调用wait_update()** Args: api (TqApi): TqApi实例,该task依托于指定api下单/撤单 symbol (str): 负责调整的合约代码 price (str): [可选]下单方式, ACTIVE=对价下单, PASSIVE=挂价下单. * 在持仓调整过程中,若下单方向为买: 对价为卖一价, 挂价为买一价 * 在持仓调整过程中,若下单方向为卖: 对价为买一价, 挂价为卖一价 offset_priority (str): [可选]开平仓顺序,昨=平昨仓,今=平今仓,开=开仓,逗号=等待之前操作完成 对于下单指令区分平今/昨的交易所(如上期所),按照今/昨仓的数量计算是否能平今/昨仓 对于下单指令不区分平今/昨的交易所(如中金所),按照“先平当日新开仓,再平历史仓”的规则计算是否能平今/昨仓 * "今昨,开" 表示先平今仓,再平昨仓,等待平仓完成后开仓,对于没有单向大边的品种避免了开仓保证金不足 * "今昨开" 表示先平今仓,再平昨仓,并开仓,所有指令同时发出,适合有单向大边的品种 * "昨开" 表示先平昨仓,再开仓,禁止平今仓,适合股指这样平今手续费较高的品种 trade_chan (TqChan): [可选]成交通知channel, 当有成交发生时会将成交手数(多头为正数,空头为负数)发到该channel上 """ super(TargetPosTask, self).__init__() self._api = api if symbol not in api._data.get("quotes", {}): raise Exception("代码 %s 不存在, 请检查合约代码是否填写正确" % (symbol)) self._symbol = symbol self._exchange = symbol.split(".")[0] if price not in ("ACTIVE", "PASSIVE"): raise Exception("下单方式(price) %s 错误, 请检查 price 参数是否填写正确" % (price)) self._price = price if len( offset_priority.replace(",", "").replace("今", "", 1).replace( "昨", "", 1).replace("开", "", 1)) > 0: raise Exception( "开平仓顺序(offset_priority) %s 错误, 请检查 offset_priority 参数是否填写正确" % (offset_priority)) self._offset_priority = offset_priority self._pos = self._api.get_position(self._symbol) self._pos_chan = TqChan(self._api, last_only=True) self._trade_chan = trade_chan if trade_chan is not None else TqChan( self._api) self._task = self._api.create_task(self._target_pos_task()) def set_target_volume(self, volume: int) -> None: """ 设置目标持仓手数 Args: volume (int): 目标持仓手数,正数表示多头,负数表示空头,0表示空仓 Example:: # 设置 rb1810 持仓为多头5手 from tqsdk import TqApi, TargetPosTask api = TqApi() target_pos = TargetPosTask(api, "SHFE.rb1810") target_pos.set_target_volume(5) while True: # 需在 set_target_volume 后调用wait_update()以发出指令 api.wait_update() """ self._pos_chan.send_nowait(int(volume)) def _get_order(self, offset, vol, pending_frozen): """ 根据指定的offset和预期下单手数vol, 返回符合要求的委托单最大报单手数 :param offset: "昨" / "今" / "开" :param vol: int, <0表示SELL, >0表示BUY :return: order_offset: "CLOSE"/"CLOSETODAY"/"OPEN"; order_dir: "BUY"/"SELL"; "order_volume": >=0, 报单手数 """ if vol > 0: # 买单(增加净持仓) order_dir = "BUY" pos_all = self._pos.pos_short else: # 卖单 order_dir = "SELL" pos_all = self._pos.pos_long if offset == "昨": order_offset = "CLOSE" if self._exchange == "SHFE" or self._exchange == "INE": if vol > 0: pos_all = self._pos.pos_short_his else: pos_all = self._pos.pos_long_his frozen_volume = sum([ order.volume_left for order in self._pos.orders.values() if not order.is_dead and order.offset == order_offset and order.direction == order_dir ]) else: frozen_volume = pending_frozen + sum([ order.volume_left for order in self._pos.orders.values() if not order.is_dead and order.offset != "OPEN" and order.direction == order_dir ]) # 判断是否有未冻结的今仓手数: 若有则不平昨仓 if (self._pos.pos_short_today if vol > 0 else self._pos.pos_long_today) - frozen_volume > 0: pos_all = frozen_volume order_volume = min(abs(vol), max(0, pos_all - frozen_volume)) elif offset == "今": if self._exchange == "SHFE" or self._exchange == "INE": order_offset = "CLOSETODAY" if vol > 0: pos_all = self._pos.pos_short_today else: pos_all = self._pos.pos_long_today frozen_volume = sum([ order.volume_left for order in self._pos.orders.values() if not order.is_dead and order.offset == order_offset and order.direction == order_dir ]) else: order_offset = "CLOSE" frozen_volume = pending_frozen + sum([ order.volume_left for order in self._pos.orders.values() if not order.is_dead and order.offset != "OPEN" and order.direction == order_dir ]) pos_all = self._pos.pos_short_today if vol > 0 else self._pos.pos_long_today order_volume = min(abs(vol), max(0, pos_all - frozen_volume)) elif offset == "开": order_offset = "OPEN" order_volume = abs(vol) else: order_offset = "" order_volume = 0 return order_offset, order_dir, order_volume async def _target_pos_task(self): """负责调整目标持仓的task""" async for target_pos in self._pos_chan: # 确定调仓增减方向 delta_volume = target_pos - self._pos.pos pending_forzen = 0 all_tasks = [] for each_priority in self._offset_priority + ",": # 按不同模式的优先级顺序报出不同的offset单,股指(“昨开”)平昨优先从不平今就先报平昨,原油平今优先("今昨开")就报平今 if each_priority == ",": await gather(*[each._task for each in all_tasks]) pending_forzen = 0 all_tasks = [] continue order_offset, order_dir, order_volume = self._get_order( each_priority, delta_volume, pending_forzen) if order_volume == 0: # 如果没有则直接到下一种offset continue elif order_offset != "OPEN": pending_forzen += order_volume order_task = InsertOrderUntilAllTradedTask( self._api, self._symbol, order_dir, offset=order_offset, volume=order_volume, price=self._price, trade_chan=self._trade_chan) all_tasks.append(order_task) delta_volume -= order_volume if order_dir == "BUY" else -order_volume
class TargetPosTask(object): """目标持仓 task, 该 task 可以将指定合约调整到目标头寸""" def __init__(self, api, symbol, price="ACTIVE", init_pos=None, offset_priority="今昨,开", trade_chan=None): """ 创建目标持仓task实例,负责调整归属于该task的持仓(默认为整个账户的该合约净持仓) Args: api (TqApi): TqApi实例,该task依托于指定api下单/撤单 symbol (str): 负责调整的合约代码 price (str): [可选]下单方式, ACTIVE=对价下单, PASSIVE=挂价下单 init_pos (int): [可选]初始持仓,默认整个账户的该合净持仓 offset_priority (str): [可选]开平仓顺序,昨=平昨仓,今=平今仓,开=开仓,逗号=等待之前操作完成 对于下单指令区分平今/昨的交易所(如上期所),按照今/昨仓的数量计算是否能平今/昨仓 对于下单指令不区分平今/昨的交易所(如中金所),按照“先平当日新开仓,再平历史仓”的规则计算是否能平今/昨仓 * "今昨,开" 表示先平今仓,再平昨仓,等待平仓完成后开仓,对于没有单向大边的品种避免了开仓保证金不足 * "今昨开" 表示先平今仓,再平昨仓,并开仓,所有指令同时发出,适合有单向大边的品种 * "昨开" 表示先平昨仓,再开仓,禁止平今仓,适合股指这样平今手续费较高的品种 trade_chan (TqChan): [可选]成交通知channel, 当有成交发生时会将成交手数(多头为正数,空头为负数)发到该channel上 """ super(TargetPosTask, self).__init__() self.api = api self.symbol = symbol self.exchange = symbol.split(".")[0] self.price = price self.pos = self.api.get_position(self.symbol) self.current_pos = init_pos self.offset_priority = offset_priority self.pos_chan = TqChan(self.api, last_only=True) self.trade_chan = trade_chan if trade_chan is not None else TqChan( self.api) self.task = self.api.create_task(self._target_pos_task()) def set_target_volume(self, volume): """ 设置目标持仓手数 Args: volume (int): 目标持仓手数,正数表示多头,负数表示空头,0表示空仓 Example:: # 设置 rb1810 持仓为多头5手 from tqsdk import TqApi, TqSim, TargetPosTask api = TqApi(TqSim()) target_pos = TargetPosTask(api, "SHFE.rb1810") while True: api.wait_update() target_pos.set_target_volume(5) """ self.pos_chan.send_nowait(volume) def _init_position(self): """初始化当前持仓""" if self.current_pos is None: self.current_pos = self.pos["volume_long_today"] + self.pos[ "volume_long_his"] - self.pos["volume_short_today"] - self.pos[ "volume_short_his"] def _get_order(self, offset, vol, pos): """获得可平手数""" if vol > 0: # 买单(增加净持仓) order_dir = "BUY" ydAvailable = pos["volume_short_his"] - ( pos["volume_short_frozen"] - pos["volume_short_frozen_today"] ) # 昨空可用 tdAvailable = pos["volume_short_today"] - pos[ "volume_short_frozen_today"] # 今空可用 else: # 卖单 order_dir = "SELL" ydAvailable = pos["volume_long_his"] - ( pos["volume_long_frozen"] - pos["volume_long_frozen_today"] ) # 昨多可用 tdAvailable = pos["volume_long_today"] - pos[ "volume_long_frozen_today"] # 今多可用 if offset == "昨": order_offset = "CLOSE" order_volume = min( abs(vol), ydAvailable if self.exchange == "SHFE" or self.exchange == "INE" or tdAvailable == 0 else 0) if vol > 0: pos["volume_short_frozen"] += order_volume pos["volume_short_frozen_his"] += order_volume else: pos["volume_long_frozen"] += order_volume pos["volume_long_frozen_his"] += order_volume elif offset == "今": order_offset = "CLOSETODAY" if self.exchange == "SHFE" or self.exchange == "INE" else "CLOSE" order_volume = min( abs(vol), tdAvailable if self.exchange == "SHFE" or self.exchange == "INE" else tdAvailable + ydAvailable) if vol > 0: pos["volume_short_frozen"] += order_volume pos["volume_short_frozen_today"] += order_volume pos["volume_short_frozen_his"] += max( 0, pos["volume_short_frozen_today"] - pos["volume_short_today"]) pos["volume_short_frozen_today"] = min( pos["volume_short_frozen_today"], pos["volume_short_today"]) else: pos["volume_long_frozen"] += order_volume pos["volume_long_frozen_today"] += order_volume pos["volume_long_frozen_his"] += max( 0, pos["volume_long_frozen_today"] - pos["volume_long_today"]) pos["volume_long_frozen_today"] = min( pos["volume_long_frozen_today"], pos["volume_long_today"]) elif offset == "开": order_offset = "OPEN" order_volume = abs(vol) else: order_offset = "" order_volume = 0 return order_offset, order_dir, order_volume async def _target_pos_task(self): """负责调整目标持仓的task""" self._init_position() async for target_pos in self.pos_chan: # 确定调仓增减方向 delta_volume = target_pos - self.current_pos all_tasks = [] pos = self.pos.copy() for each_priority in self.offset_priority + ",": # 按不同模式的优先级顺序报出不同的offset单,股指(“昨开”)平昨优先从不平今就先报平昨,原油平今优先("今昨开")就报平今 if each_priority == ",": await gather(*[each.task for each in all_tasks]) all_tasks = [] pos = self.pos.copy() continue order_offset, order_dir, order_volume = self._get_order( each_priority, delta_volume, pos) if order_volume == 0: # 如果没有则直接到下一种offset continue order_task = InsertOrderUntilAllTradedTask( self.api, self.symbol, order_dir, offset=order_offset, volume=order_volume, price=self.price, trade_chan=self.trade_chan) all_tasks.append(order_task) delta_volume -= order_volume if order_dir == "BUY" else -order_volume self.current_pos = target_pos