From 43ccfa25ae0744fe5e575bb9c3da1f9eba421e89 Mon Sep 17 00:00:00 2001 From: shinny-pack Date: Fri, 22 Apr 2022 02:34:05 +0000 Subject: [PATCH] Update Version 3.2.7 --- PKG-INFO | 2 +- doc/advanced/tqsdk2ctptest.rst | 12 ++++----- doc/conf.py | 4 +-- doc/demo/option_base.rst | 6 +++++ doc/version.rst | 7 +++++ setup.py | 2 +- tqsdk/__version__.py | 2 +- tqsdk/api.py | 31 +++++++++++++--------- tqsdk/demo/option_tutorial/o20.py | 2 +- tqsdk/demo/option_tutorial/o74.py | 39 +++++++++++++++++++++++++++ tqsdk/lib/target_pos_task.py | 30 +++++++++++---------- tqsdk/log.py | 44 +++++++++++++++++++++++++------ tqsdk/objs.py | 2 +- tqsdk/tradeable/otg/tqkq.py | 2 +- 14 files changed, 136 insertions(+), 49 deletions(-) create mode 100644 tqsdk/demo/option_tutorial/o74.py diff --git a/PKG-INFO b/PKG-INFO index 8218e5f4..9df23b3e 100644 --- a/PKG-INFO +++ b/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: tqsdk -Version: 3.2.6 +Version: 3.2.7 Summary: TianQin SDK Home-page: https://www.shinnytech.com/tqsdk Author: TianQin diff --git a/doc/advanced/tqsdk2ctptest.rst b/doc/advanced/tqsdk2ctptest.rst index 6ee90a58..cf8ffc90 100644 --- a/doc/advanced/tqsdk2ctptest.rst +++ b/doc/advanced/tqsdk2ctptest.rst @@ -1,10 +1,10 @@ .. _tqsdk2ctptest: -在 tqsdk 中调用 tqsdk2 查询保证金 +在 TqSdk 中调用 TqSdk2 查询保证金 ================================================= -tqSdk 没有直接提供查询保证金的接口,但是你可以通过使用tqsdk2 的直连功能来做到这个效果。tqsdk和tqsdk2可以在一个py文件中同时运行。 +TqSdk 没有直接提供查询保证金的接口,但是你可以通过使用 TqSdk2 的直连功能来做到这个效果。tqsdk和tqsdk2可以在一个py文件中同时运行。 -该方法仅支持直连 CTP 柜台时使用。受限制于 CTP 柜台的流控机制(每秒 1 笔), 短时间发送大量查询指令后, 后续查询指令将会排队等待。 +该方法仅支持 TqSdk2 中直连CTP 柜台时使用。受限制于 CTP 柜台的流控机制(每秒 1 笔), 短时间发送大量查询指令后, 后续查询指令将会排队等待。 为了避免盘中的查询等待时间, 建议盘前启动程序, 对标的合约提前进行查询:: from tqsdk import TqApi, TqAuth, TqAccount @@ -18,9 +18,9 @@ tqSdk 没有直接提供查询保证金的接口,但是你可以通过使用tq quote = api.get_quote("SHFE.cu2201") while True: api.wait_update() - quote.datetime + print(quote.datetime) # 正常和tqsdk一样执行策略 -tqsdk2的直连功能需要企业版权限,有关企业版的具体费用和功能,请参考 `天勤官方网站 `_ -如果想了解更多关于tqsdk2的直连功能TqCtp,请参考 `tqsdk2官方文档 `_ +TqSdk2 的直连功能需要企业版权限,有关企业版的具体费用和功能,请参考 `天勤官方网站 `_ +如果想了解更多关于 TqSdk2 的直连功能TqCtp,请参考 `tqsdk2官方文档 `_ diff --git a/doc/conf.py b/doc/conf.py index 4fd8e588..9c8fd3ab 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -48,9 +48,9 @@ # built documents. # # The short X.Y version. -version = u'3.2.6' +version = u'3.2.7' # The full version, including alpha/beta/rc tags. -release = u'3.2.6' +release = u'3.2.7' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/doc/demo/option_base.rst b/doc/demo/option_base.rst index 45bfc509..0f7400ba 100644 --- a/doc/demo/option_base.rst +++ b/doc/demo/option_base.rst @@ -83,4 +83,10 @@ o73 - 查询标的对应期权按虚值平值实值分类方法二 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. literalinclude:: ../../tqsdk/demo/option_tutorial/o73.py + :language: python + +o74 - 本地计算ETF期权卖方开仓保证金 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. literalinclude:: ../../tqsdk/demo/option_tutorial/o74.py :language: python \ No newline at end of file diff --git a/doc/version.rst b/doc/version.rst index 4f34ea2c..cd1c411f 100644 --- a/doc/version.rst +++ b/doc/version.rst @@ -2,6 +2,13 @@ 版本变更 ============================= +3.2.7 (2022/04/22) + +* 优化:对多线程用例,增加可能的错误提示 +* 优化:TqApi 的 debug 默认值修改为 None,且 debug 为 None 情况下在磁盘剩余空间大于 3G 时才可能开启日志 +* docs:增加 ETF 期权本地计算卖方保证金示例 o74,完善 targetpostask 的示例文档,完善 Position 下 orders 定义,统一修正文档大小写、变量命名等 + + 3.2.6 (2022/03/09) * 修复:修正深交所 ETF 期权的昨结算(pre_settlement)字段未正确显示的问题 diff --git a/setup.py b/setup.py index 8d71677d..f46a8935 100644 --- a/setup.py +++ b/setup.py @@ -36,7 +36,7 @@ def get_tag(self): setuptools.setup( name='tqsdk', - version="3.2.6", + version="3.2.7", description='TianQin SDK', author='TianQin', author_email='tianqincn@gmail.com', diff --git a/tqsdk/__version__.py b/tqsdk/__version__.py index 4a3d7343..b135b9f9 100644 --- a/tqsdk/__version__.py +++ b/tqsdk/__version__.py @@ -1 +1 @@ -__version__ = '3.2.6' +__version__ = '3.2.7' diff --git a/tqsdk/api.py b/tqsdk/api.py index 1c341db2..90b91567 100644 --- a/tqsdk/api.py +++ b/tqsdk/api.py @@ -58,7 +58,7 @@ from tqsdk.diff import _merge_diff, _get_obj, _is_key_exist, _register_update_chan from tqsdk.entity import Entity from tqsdk.exceptions import TqTimeoutError -from tqsdk.log import _get_log_name, _clear_logs +from tqsdk.log import _clear_logs, _get_log_name, _get_disk_free from tqsdk.objs import Quote, TradingStatus, Kline, Tick, Account, Position, Order, Trade, QuotesEntity, RiskManagementRule, RiskManagementData from tqsdk.objs import SecurityAccount, SecurityOrder, SecurityTrade, SecurityPosition from tqsdk.objs_not_entity import QuoteList, TqDataFrame, TqSymbolDataFrame, SymbolList, SymbolLevelList, \ @@ -88,7 +88,7 @@ class TqApi(TqBaseApi): def __init__(self, account: Optional[Union[TqMultiAccount, UnionTradeable]] = None, auth: Union[TqAuth, str, None] = None, url: Optional[str] = None, backtest: Union[TqBacktest, TqReplay, None] = None, - web_gui: Union[bool, str] = False, debug: Union[bool, str, None] = False, + web_gui: Union[bool, str] = False, debug: Union[bool, str, None] = None, loop: Optional[asyncio.AbstractEventLoop] = None, disable_print: bool = False, _stock: bool = True, _ins_url=None, _md_url=None, _td_url=None) -> None: """ @@ -128,10 +128,11 @@ def __init__(self, account: Optional[Union[TqMultiAccount, UnionTradeable]] = No 在回测模式下, TqBacktest 连接 wss://backtest.shinnytech.com/t/md/front/mobile 接收行情数据, \ 由 TqBacktest 内部完成回测时间段内的行情推进和 K 线、Tick 更新. - debug(bool/str): [可选] 是否将调试信息输出到指定文件,默认值为 False。 + debug(bool/str): [可选] 是否将调试信息输出到指定文件,默认值为 None。 * None [默认]: 根据账户情况不同,默认值的行为不同。 - + 当有以下账户之一时,:py:class:`~tqsdk.TqAccount`、:py:class:`~tqsdk.TqKq`、:py:class:`~tqsdk.TqKqStock` 账户时,调试信息输出到指定文件夹 `~/.tqsdk/logs`。 + + 当有以下账户之一时,:py:class:`~tqsdk.TqAccount`、:py:class:`~tqsdk.TqKq`、:py:class:`~tqsdk.TqKqStock` 账户时,\ + 调试信息输出到指定文件夹 `~/.tqsdk/logs`(如果磁盘剩余空间不足 3G 则不会输出调试信息)。 + 其他情况,即仅有本地模拟账户 :py:class:`~tqsdk.TqSim`、:py:class:`~tqsdk.TqSimStock` 时,调试信息不输出。 @@ -271,7 +272,9 @@ def __init__(self, account: Optional[Union[TqMultiAccount, UnionTradeable]] = No raise Exception("不可以为slave再创建slave") self._master._slaves.append(self) self._account = self._master._account - self._web_gui = False # 如果是slave, _web_gui 一定是 False + if isinstance(self._account, TqMultiAccount): + warnings.warn("TqSdk 多账户功能暂不支持在多线程下使用") + self._web_gui = False # 如果是slave, _web_gui 一定是 False return # 注: 如果是slave,则初始化到这里结束并返回,以下代码不执行 self._web_gui = web_gui @@ -349,7 +352,6 @@ def close(self) -> None: super(TqApi, self)._close() mem = psutil.virtual_memory() self._logger.debug("process end", mem_total=mem.total, mem_free=mem.free) - _clear_logs() # 清除过期日志文件 def __enter__(self): return self @@ -591,7 +593,7 @@ def get_kline_serial(self, symbol: Union[str, List[str]], duration_seconds: int, 注意: 周期在日线以内时此参数可以任意填写, 在日线以上时只能是日线(86400)的整数倍, 最大为28天 (86400*28)。 data_length (int): 需要获取的序列长度。默认200根, 返回的K线序列数据是从当前最新一根K线开始往回取data_length根。\ - 每个序列最大支持请求 8964 个数据 + 每个序列最大支持请求 8000 个数据 chart_id (str): [可选]指定序列id, 默认由 api 自动生成 @@ -607,7 +609,7 @@ def get_kline_serial(self, symbol: Union[str, List[str]], duration_seconds: int, 3. 若设置了较大的序列长度参数,而所有可对齐的数据并没有这么多,则序列前面部分数据为NaN(这与获取单合约K线且数据不足序列长度时情况相似)。 - 4. 若主合约与副合约的交易时间在所有合约数据中最晚一根K线时间开始往回的 8964*周期 时间段内完全不重合,则无法生成多合约K线,程序会报出获取数据超时异常。 + 4. 若主合约与副合约的交易时间在所有合约数据中最晚一根K线时间开始往回的 8000*周期 时间段内完全不重合,则无法生成多合约K线,程序会报出获取数据超时异常。 5. datetime、duration是所有合约公用的字段,则未单独为每个副合约增加一份副本,这两个字段使用原始字段名(即没有数字后缀)。 @@ -737,7 +739,7 @@ def get_tick_serial(self, symbol: str, data_length: int = 200, chart_id: Optiona Args: symbol (str): 指定合约代码. - data_length (int): 需要获取的序列长度。每个序列最大支持请求 8964 个数据 + data_length (int): 需要获取的序列长度。每个序列最大支持请求 8000 个数据 chart_id (str): [可选]指定序列id, 默认由 api 自动生成 @@ -3094,11 +3096,14 @@ def _setup_connection(self): # TqWebHelper 初始化可能会修改 self._account、self._backtest,所以在这里才初始化 logger # 在此之前使用 self._logger 不会打印日志 if not self._logger.handlers and (self._debug or (self._account._has_tq_account and self._debug is not False)): + _clear_logs() # 先清空日志 log_name = self._debug if isinstance(self._debug, str) else _get_log_name() - fh = logging.FileHandler(filename=log_name) - fh.setFormatter(JSONFormatter()) - fh.setLevel(logging.DEBUG) - self._logger.addHandler(fh) + if self._debug is not None or _get_disk_free() >= 3: + # self._debug is None 并且磁盘剩余空间小于 3G 则不写入日志 + fh = logging.FileHandler(filename=log_name) + fh.setFormatter(JSONFormatter()) + fh.setLevel(logging.DEBUG) + self._logger.addHandler(fh) mem = psutil.virtual_memory() self._logger.debug("process start", product="tqsdk-python", version=__version__, os=platform.platform(), py_version=platform.python_version(), py_arch=platform.architecture()[0], diff --git a/tqsdk/demo/option_tutorial/o20.py b/tqsdk/demo/option_tutorial/o20.py index 65edc233..91b22b9b 100644 --- a/tqsdk/demo/option_tutorial/o20.py +++ b/tqsdk/demo/option_tutorial/o20.py @@ -18,7 +18,7 @@ ls = api.query_options("SHFE.au2012", strike_price=340) print(ls) # 标的为 "SHFE.au2012" 、行权价为 340 的期权 -ls = api.query_options("SSE.510300", exchange_id="CFFEX") +ls = api.query_options("SSE.000300", exchange_id="CFFEX") print(ls) # 中金所沪深300股指期权 ls = api.query_options("SSE.510300", exchange_id="SSE") diff --git a/tqsdk/demo/option_tutorial/o74.py b/tqsdk/demo/option_tutorial/o74.py new file mode 100644 index 00000000..f127643a --- /dev/null +++ b/tqsdk/demo/option_tutorial/o74.py @@ -0,0 +1,39 @@ +from tqsdk import TqApi, TqAuth + +''' +根据输入的ETF期权来查询该期权的交易所规则下的理论卖方保证金,实际情况请以期货公司收取的一手保证金为准 +''' + +def etf_margin_cal(symbol): + quote_etf = api.get_quote(symbol) + # 判断期权标的是不是ETF + if quote_etf.underlying_symbol in ["SSE.510050", "SSE.510300", "SZSE.159919"]: + if quote_etf.option_class == "CALL": + # 认购期权虚值=Max(行权价-合约标的前收盘价,0) + call_out_value = max(quote_etf.strike_price - quote_etf.underlying_quote.pre_close, 0) + # 认购期权义务仓开仓保证金=[合约前结算价+Max(12%×合约标的前收盘价-认购期权虚值,7%×合约标的前收盘价)]×合约单位 + call_margin = (quote_etf.pre_settlement + max(0.12 * quote_etf.underlying_quote.pre_close - call_out_value, + 0.07 * quote_etf.underlying_quote.pre_close)) * quote_etf.volume_multiple + return round(call_margin, 2) + elif quote_etf.option_class == "PUT": + # 认沽期权虚值=Max(合约标的前收盘价-行权价,0) + put_out_value = max(quote_etf.underlying_quote.pre_close - quote_etf.strike_price, 0) + # 认沽期权义务仓开仓保证金=Min[合约前结算价+Max(12%×合约标的前收盘价-认沽期权虚值,7%×行权价),行权价]×合约单位。 + put_margin = min(quote_etf.pre_settlement + max(0.12 * quote_etf.underlying_quote.pre_close - put_out_value, + 0.07 * quote_etf.strike_price), + quote_etf.strike_price) * quote_etf.volume_multiple + return round(put_margin, 2) + else: + print("输入的不是ETF期权合约") + return None + + +# 创建api +api = TqApi(auth=TqAuth("信易账户", "账户密码")) + +# 深交所300etf期权 +symbol = "SZSE.90000833" + +print(etf_margin_cal(symbol)) + +api.close() diff --git a/tqsdk/lib/target_pos_task.py b/tqsdk/lib/target_pos_task.py index b0c37695..7df82676 100644 --- a/tqsdk/lib/target_pos_task.py +++ b/tqsdk/lib/target_pos_task.py @@ -147,22 +147,24 @@ def get_price(direction): Example2:: # ... 用户代码 ... - quote1 = api.get_quote("SHFE.cu2012") - quote2 = api.get_quote("SHFE.au2012") + quote_list = api.get_quote_list(["SHFE.cu2012","SHFE.au2012"]) - def get_price(direction, quote): - # 在 BUY 时使用买一价加一档价格,SELL 时使用卖一价减一档价格 - if direction == "BUY": - price = quote.bid_price1 + quote.price_tick - else: - price = quote.ask_price1 - quote.price_tick - # 如果 price 价格是 nan,使用最新价报单 - if price != price: - price = quote.last_price - return price + def get_price_by_quote(quote): + def get_price(direction): + # 在 BUY 时使用买一价加一档价格,SELL 时使用卖一价减一档价格 + if direction == "BUY": + price = quote["upper_limit"] + else: + price = quote.lower_limit + # 如果 price 价格是 nan,使用最新价报单 + if price != price: + price = quote.last_price + return price + return get_price + + for quote in quote_list: + target_pos_active_dict[quote.instrument_id] = TargetPosTask(api, quote.instrument_id, price=get_price_by_quote(quote)) - target_pos1 = TargetPosTask(api, "SHFE.cu2012", price=lambda direction: get_price(direction, quote1)) - target_pos2 = TargetPosTask(api, "SHFE.au2012", price=lambda direction: get_price(direction, quote2)) # ... 用户代码 ... Example3:: diff --git a/tqsdk/log.py b/tqsdk/log.py index b727dfdc..514f1038 100644 --- a/tqsdk/log.py +++ b/tqsdk/log.py @@ -5,6 +5,7 @@ import datetime import os +import psutil DEBUG_DIR = os.path.join(os.path.expanduser('~'), ".tqsdk/logs") @@ -16,16 +17,43 @@ def _get_log_name(): return os.path.join(DEBUG_DIR, f"{datetime.datetime.now().strftime('%Y%m%d%H%M%S%f')}-{os.getpid()}.log") +def _get_disk_free(): + free = psutil.disk_usage(DEBUG_DIR).free + return free / 1e9 + + +def _log_path_list(): + # 获取所有日志文件路径,并按照修改时间递减排序 + path_list = [os.path.join(DEBUG_DIR, log) for log in os.listdir(DEBUG_DIR)] + path_list.sort(key=lambda x: _stat_dt(x)) + return path_list + + +def _stat_dt(path): + try: + return datetime.datetime.fromtimestamp(os.stat(path).st_mtime) + except: + return datetime.datetime.now() + + +def _remove_log(path): + try: + os.remove(path) + except: + pass # 忽略抛错 + + def _clear_logs(): """清除最后修改时间是 n 天前的日志""" if not os.path.exists(DEBUG_DIR): return n = os.getenv("TQ_SAVE_LOG_DAYS", 30) - dt = datetime.datetime.now() - datetime.timedelta(days=int(n)) - for log in os.listdir(DEBUG_DIR): - path = os.path.join(DEBUG_DIR, log) - try: - if datetime.datetime.fromtimestamp(os.stat(path).st_mtime) < dt: - os.remove(path) - except: - pass # 忽略抛错 + # 清除最后修改时间是 n 天前的日志 + # 清空日志保证剩余空间大于 3G,但是最近 3 个自然日的一定不会清除,保证最近的一个交易日不会被清除日志 + dt30 = datetime.datetime.now() - datetime.timedelta(days=int(n)) + dt3 = datetime.datetime.now() - datetime.timedelta(days=int(3)) + for path in _log_path_list(): + if _stat_dt(path) < dt30 or (_get_disk_free() < 3 and _stat_dt(path) < dt3): + _remove_log(path) + else: + break diff --git a/tqsdk/objs.py b/tqsdk/objs.py index ae7081e0..2c28ff0a 100644 --- a/tqsdk/objs.py +++ b/tqsdk/objs.py @@ -451,7 +451,7 @@ def __init__(self, api): @property def orders(self): """ - 与此持仓相关的开仓/平仓挂单 + 与此持仓相关的且目前委托单状态为ALIVE的开仓/平仓挂单 :return: dict, 其中每个元素的key为委托单ID, value为 :py:class:`~tqsdk.objs.Order` """ diff --git a/tqsdk/tradeable/otg/tqkq.py b/tqsdk/tradeable/otg/tqkq.py index 8edd4637..845e0866 100644 --- a/tqsdk/tradeable/otg/tqkq.py +++ b/tqsdk/tradeable/otg/tqkq.py @@ -22,7 +22,7 @@ def __init__(self, td_url: Optional[str] = None): from tqsdk import TqApi, TqAuth, TqKq - tq_kq_stock = TqKq() + tq_kq = TqKq() api = TqApi(account=tq_kq, auth=TqAuth("信易账户", "账户密码")) quote = api.get_quote("SHFE.cu2206") print(quote)