tigeropen 3.7.0__tar.gz → 3.7.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (279) hide show
  1. {tigeropen-3.7.0/tigeropen.egg-info → tigeropen-3.7.2}/PKG-INFO +47 -2
  2. {tigeropen-3.7.0 → tigeropen-3.7.2}/README.md +45 -0
  3. {tigeropen-3.7.0 → tigeropen-3.7.2}/pyproject.toml +9 -0
  4. tigeropen-3.7.2/scripts/push_latency_monitor.py +202 -0
  5. tigeropen-3.7.2/tests/test_api_registry.py +153 -0
  6. {tigeropen-3.7.0 → tigeropen-3.7.2}/tests/test_client_config.py +10 -1
  7. tigeropen-3.7.2/tests/test_common_utils.py +264 -0
  8. tigeropen-3.7.2/tests/test_low_coverage.py +577 -0
  9. {tigeropen-3.7.0 → tigeropen-3.7.2}/tests/test_option_helper.py +10 -2
  10. {tigeropen-3.7.0 → tigeropen-3.7.2}/tests/test_option_util.py +84 -61
  11. {tigeropen-3.7.0 → tigeropen-3.7.2}/tests/test_push_client.py +9 -1
  12. tigeropen-3.7.2/tests/test_push_network.py +294 -0
  13. {tigeropen-3.7.0 → tigeropen-3.7.2}/tests/test_quote_client.py +18 -299
  14. tigeropen-3.7.2/tests/test_quote_uncovered.py +168 -0
  15. tigeropen-3.7.2/tests/test_request_models.py +477 -0
  16. tigeropen-3.7.2/tests/test_response_parsers.py +287 -0
  17. tigeropen-3.7.2/tests/test_serialization_contract.py +278 -0
  18. {tigeropen-3.7.0 → tigeropen-3.7.2}/tests/test_trade_client.py +17 -191
  19. tigeropen-3.7.2/tests/test_trade_uncovered.py +207 -0
  20. {tigeropen-3.7.0 → tigeropen-3.7.2}/tests/test_utils.py +6 -0
  21. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/__init__.py +1 -1
  22. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/common/consts/tick_constants.py +3 -3
  23. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/examples/option_helpers/helpers.py +17 -4
  24. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/examples/option_helpers/util.py +13 -0
  25. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/examples/quote_client_demo.py +1 -1
  26. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/trade_tick.py +2 -2
  27. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/thread_pool.py +6 -0
  28. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/domain/quote_brief.py +2 -0
  29. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/quote_client.py +7 -3
  30. {tigeropen-3.7.0 → tigeropen-3.7.2/tigeropen.egg-info}/PKG-INFO +47 -2
  31. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen.egg-info/SOURCES.txt +10 -15
  32. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen.egg-info/top_level.txt +0 -2
  33. tigeropen-3.7.0/dist/tigeropen-3.5.8.tar.gz +0 -0
  34. tigeropen-3.7.0/dist/tigeropen-3.5.9-py3-none-any.whl +0 -0
  35. tigeropen-3.7.0/dist/tigeropen-3.5.9.tar.gz +0 -0
  36. tigeropen-3.7.0/dist/tigeropen-3.6.0-py3-none-any.whl +0 -0
  37. tigeropen-3.7.0/dist/tigeropen-3.6.0.tar.gz +0 -0
  38. tigeropen-3.7.0/tests/cli/test_client_factory.py +0 -90
  39. tigeropen-3.7.0/tests/cli/test_config.py +0 -281
  40. tigeropen-3.7.0/tests/cli/test_error_handling.py +0 -120
  41. tigeropen-3.7.0/tests/cli/test_formatting.py +0 -220
  42. tigeropen-3.7.0/tests/cli/test_main.py +0 -63
  43. tigeropen-3.7.0/tests/cli/test_push.py +0 -210
  44. tigeropen-3.7.0/tests/cli/test_quote.py +0 -666
  45. tigeropen-3.7.0/tests/cli/test_trade.py +0 -629
  46. tigeropen-3.7.0/tigeropen/examples/ai/mcp_server/dist/tigermcp-0.1.7.tar.gz +0 -0
  47. tigeropen-3.7.0/tigeropen/fundamental/response/__init__.py +0 -1
  48. {tigeropen-3.7.0 → tigeropen-3.7.2}/MANIFEST.in +0 -0
  49. {tigeropen-3.7.0 → tigeropen-3.7.2}/requirements.txt +0 -0
  50. {tigeropen-3.7.0 → tigeropen-3.7.2}/setup.cfg +0 -0
  51. {tigeropen-3.7.0 → tigeropen-3.7.2}/setup.py +0 -0
  52. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/cli/__init__.py +0 -0
  53. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/cli/account_cmd.py +0 -0
  54. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/cli/client_factory.py +0 -0
  55. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/cli/config_cmd.py +0 -0
  56. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/cli/formatting.py +0 -0
  57. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/cli/main.py +0 -0
  58. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/cli/push_cmd.py +0 -0
  59. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/cli/quote_cmd.py +0 -0
  60. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/cli/trade_cmd.py +0 -0
  61. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/common/__init__.py +0 -0
  62. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/common/consts/__init__.py +0 -0
  63. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/common/consts/filter_fields.py +0 -0
  64. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/common/consts/fundamental_fields.py +0 -0
  65. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/common/consts/params.py +0 -0
  66. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/common/consts/push_destinations.py +0 -0
  67. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/common/consts/push_subscriptions.py +0 -0
  68. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/common/consts/push_types.py +0 -0
  69. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/common/consts/quote_keys.py +0 -0
  70. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/common/consts/service_types.py +0 -0
  71. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/common/exceptions.py +0 -0
  72. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/common/model.py +0 -0
  73. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/common/request.py +0 -0
  74. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/common/response.py +0 -0
  75. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/common/util/__init__.py +0 -0
  76. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/common/util/account_util.py +0 -0
  77. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/common/util/common_utils.py +0 -0
  78. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/common/util/contract_utils.py +0 -0
  79. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/common/util/order_utils.py +0 -0
  80. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/common/util/price_util.py +0 -0
  81. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/common/util/signature_utils.py +0 -0
  82. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/common/util/string_utils.py +0 -0
  83. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/common/util/tick_util.py +0 -0
  84. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/common/util/web_utils.py +0 -0
  85. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/examples/__init__.py +0 -0
  86. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/examples/ai/mcp_server/MANIFEST.in +0 -0
  87. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/examples/ai/mcp_server/README.md +0 -0
  88. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/examples/ai/mcp_server/__init__.py +0 -0
  89. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/examples/ai/mcp_server/setup.py +0 -0
  90. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/examples/ai/mcp_server/tigermcp/__init__.py +0 -0
  91. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/examples/ai/mcp_server/tigermcp/server.py +0 -0
  92. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/examples/ai/mcp_server/tigermcp/version.py +0 -0
  93. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/examples/client_config.py +0 -0
  94. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/examples/financial_demo.py +0 -0
  95. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/examples/nasdaq100.py +0 -0
  96. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/examples/option_helpers/__init__.py +0 -0
  97. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/examples/option_helpers/extra_calculator.py +0 -0
  98. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/examples/option_helpers/probability_calculator.py +0 -0
  99. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/examples/push_client_demo.py +0 -0
  100. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/examples/push_client_stomp_demo.py +0 -0
  101. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/examples/trade_client_demo.py +0 -0
  102. {tigeropen-3.7.0/tests/cli → tigeropen-3.7.2/tigeropen/fundamental}/__init__.py +0 -0
  103. {tigeropen-3.7.0/tigeropen/fundamental → tigeropen-3.7.2/tigeropen/fundamental/domain}/__init__.py +0 -0
  104. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/fundamental/request/__init__.py +0 -0
  105. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/fundamental/request/model.py +0 -0
  106. {tigeropen-3.7.0/tigeropen/fundamental/domain → tigeropen-3.7.2/tigeropen/fundamental/response}/__init__.py +0 -0
  107. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/fundamental/response/corporate_delisting_response.py +0 -0
  108. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/fundamental/response/corporate_dividend_response.py +0 -0
  109. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/fundamental/response/corporate_earnings_calendar_response.py +0 -0
  110. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/fundamental/response/corporate_ipo_response.py +0 -0
  111. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/fundamental/response/corporate_split_response.py +0 -0
  112. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/fundamental/response/corporate_symbol_change_response.py +0 -0
  113. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/fundamental/response/dataframe_response.py +0 -0
  114. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/fundamental/response/financial_daily_response.py +0 -0
  115. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/fundamental/response/financial_exchange_rate_response.py +0 -0
  116. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/fundamental/response/financial_report_response.py +0 -0
  117. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/fundamental/response/industry_response.py +0 -0
  118. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/__init__.py +0 -0
  119. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/network/__init__.py +0 -0
  120. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/network/connect.py +0 -0
  121. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/network/exception.py +0 -0
  122. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/network/listener.py +0 -0
  123. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/network/protocal.py +0 -0
  124. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/network/transport.py +0 -0
  125. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/network/utils.py +0 -0
  126. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/AssetData.proto +0 -0
  127. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/AssetData_pb2.py +0 -0
  128. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/AssetData_pb2.pyi +0 -0
  129. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/KlineData.proto +0 -0
  130. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/KlineData_pb2.py +0 -0
  131. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/KlineData_pb2.pyi +0 -0
  132. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/OptionTopData.proto +0 -0
  133. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/OptionTopData_pb2.py +0 -0
  134. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/OptionTopData_pb2.pyi +0 -0
  135. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/OrderStatusData.proto +0 -0
  136. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/OrderStatusData_pb2.py +0 -0
  137. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/OrderStatusData_pb2.pyi +0 -0
  138. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/OrderTransactionData.proto +0 -0
  139. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/OrderTransactionData_pb2.py +0 -0
  140. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/OrderTransactionData_pb2.pyi +0 -0
  141. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/PositionData.proto +0 -0
  142. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/PositionData_pb2.py +0 -0
  143. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/PositionData_pb2.pyi +0 -0
  144. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/PushData.proto +0 -0
  145. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/PushData_pb2.py +0 -0
  146. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/PushData_pb2.pyi +0 -0
  147. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/QuoteBBOData.proto +0 -0
  148. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/QuoteBBOData_pb2.py +0 -0
  149. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/QuoteBBOData_pb2.pyi +0 -0
  150. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/QuoteBasicData.proto +0 -0
  151. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/QuoteBasicData_pb2.py +0 -0
  152. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/QuoteBasicData_pb2.pyi +0 -0
  153. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/QuoteData.proto +0 -0
  154. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/QuoteData_pb2.py +0 -0
  155. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/QuoteData_pb2.pyi +0 -0
  156. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/QuoteDepthData.proto +0 -0
  157. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/QuoteDepthData_pb2.py +0 -0
  158. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/QuoteDepthData_pb2.pyi +0 -0
  159. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/Request.proto +0 -0
  160. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/Request_pb2.py +0 -0
  161. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/Request_pb2.pyi +0 -0
  162. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/Response.proto +0 -0
  163. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/Response_pb2.py +0 -0
  164. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/Response_pb2.pyi +0 -0
  165. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/SocketCommon.proto +0 -0
  166. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/SocketCommon_pb2.py +0 -0
  167. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/SocketCommon_pb2.pyi +0 -0
  168. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/StockTopData.proto +0 -0
  169. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/StockTopData_pb2.py +0 -0
  170. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/StockTopData_pb2.pyi +0 -0
  171. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/TickData.proto +0 -0
  172. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/TickData_pb2.py +0 -0
  173. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/TickData_pb2.pyi +0 -0
  174. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/TradeTickData.proto +0 -0
  175. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/TradeTickData_pb2.py +0 -0
  176. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/TradeTickData_pb2.pyi +0 -0
  177. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/__init__.py +0 -0
  178. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/readme.md +0 -0
  179. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/util.py +0 -0
  180. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/protobuf_push_client.py +0 -0
  181. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/push_client.py +0 -0
  182. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/stomp_push_client.py +0 -0
  183. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/__init__.py +0 -0
  184. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/domain/__init__.py +0 -0
  185. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/domain/addon_entitlement.py +0 -0
  186. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/domain/bar.py +0 -0
  187. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/domain/capital_distribution.py +0 -0
  188. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/domain/filter.py +0 -0
  189. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/domain/market_status.py +0 -0
  190. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/domain/option_analysis.py +0 -0
  191. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/domain/stock_broker.py +0 -0
  192. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/domain/tick.py +0 -0
  193. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/domain/timeline.py +0 -0
  194. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/request/__init__.py +0 -0
  195. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/request/model.py +0 -0
  196. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/__init__.py +0 -0
  197. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/addon_entitlement_response.py +0 -0
  198. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/broker_hold_response.py +0 -0
  199. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/capital_distribution_response.py +0 -0
  200. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/capital_flow_response.py +0 -0
  201. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/fund_contracts_response.py +0 -0
  202. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/future_briefs_response.py +0 -0
  203. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/future_contract_response.py +0 -0
  204. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/future_depth_response.py +0 -0
  205. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/future_exchange_response.py +0 -0
  206. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/future_history_main_contract_response.py +0 -0
  207. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/future_quote_bar_response.py +0 -0
  208. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/future_quote_ticks_response.py +0 -0
  209. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/future_trading_times_response.py +0 -0
  210. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/kline_quota_response.py +0 -0
  211. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/market_scanner_response.py +0 -0
  212. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/market_status_response.py +0 -0
  213. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/option_analysis_response.py +0 -0
  214. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/option_briefs_response.py +0 -0
  215. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/option_chains_response.py +0 -0
  216. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/option_depth_response.py +0 -0
  217. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/option_expirations_response.py +0 -0
  218. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/option_quote_bar_response.py +0 -0
  219. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/option_quote_ticks_response.py +0 -0
  220. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/option_symbols_response.py +0 -0
  221. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/option_timeline_response.py +0 -0
  222. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/quote_bar_response.py +0 -0
  223. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/quote_brief_response.py +0 -0
  224. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/quote_dataframe_response.py +0 -0
  225. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/quote_delay_briefs_response.py +0 -0
  226. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/quote_depth_response.py +0 -0
  227. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/quote_grab_permission_response.py +0 -0
  228. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/quote_hour_trading_timeline_response.py +0 -0
  229. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/quote_overnight_response.py +0 -0
  230. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/quote_ticks_response.py +0 -0
  231. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/quote_timeline_history_response.py +0 -0
  232. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/quote_timeline_response.py +0 -0
  233. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/stock_briefs_response.py +0 -0
  234. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/stock_broker_response.py +0 -0
  235. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/stock_details_response.py +0 -0
  236. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/stock_short_interest_response.py +0 -0
  237. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/stock_trade_meta_response.py +0 -0
  238. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/symbol_names_response.py +0 -0
  239. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/symbols_response.py +0 -0
  240. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/trade_rank_response.py +0 -0
  241. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/trading_calendar_response.py +0 -0
  242. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/warrant_briefs_response.py +0 -0
  243. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/warrant_filter_response.py +0 -0
  244. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/tiger_open_client.py +0 -0
  245. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/tiger_open_config.py +0 -0
  246. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/__init__.py +0 -0
  247. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/domain/__init__.py +0 -0
  248. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/domain/account.py +0 -0
  249. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/domain/contract.py +0 -0
  250. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/domain/option_exercise.py +0 -0
  251. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/domain/order.py +0 -0
  252. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/domain/position.py +0 -0
  253. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/domain/prime_account.py +0 -0
  254. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/domain/profile.py +0 -0
  255. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/domain/transfer.py +0 -0
  256. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/request/__init__.py +0 -0
  257. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/request/model.py +0 -0
  258. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/response/__init__.py +0 -0
  259. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/response/account_profile_response.py +0 -0
  260. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/response/aggregate_assets_response.py +0 -0
  261. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/response/analytics_asset_response.py +0 -0
  262. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/response/assets_response.py +0 -0
  263. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/response/contracts_response.py +0 -0
  264. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/response/forex_order_response.py +0 -0
  265. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/response/fund_details_response.py +0 -0
  266. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/response/funding_history_response.py +0 -0
  267. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/response/option_exercise_response.py +0 -0
  268. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/response/order_id_response.py +0 -0
  269. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/response/order_preview_response.py +0 -0
  270. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/response/orders_response.py +0 -0
  271. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/response/positions_response.py +0 -0
  272. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/response/prime_assets_response.py +0 -0
  273. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/response/segment_fund_response.py +0 -0
  274. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/response/transactions_response.py +0 -0
  275. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/response/transfer_response.py +0 -0
  276. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/trade_client.py +0 -0
  277. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen.egg-info/dependency_links.txt +0 -0
  278. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen.egg-info/entry_points.txt +0 -0
  279. {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen.egg-info/requires.txt +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: tigeropen
3
- Version: 3.7.0
3
+ Version: 3.7.2
4
4
  Summary: TigerBrokers Open API Python SDK
5
5
  Author-email: TigerBrokers <openapi@itiger.com>
6
6
  License: Apache-2.0
@@ -187,6 +187,51 @@ status = client.get_market_status(Market.US)
187
187
  print(status)
188
188
  ```
189
189
 
190
+ ### 逐笔成交 `cond` 字段说明
191
+
192
+ `trade_tick` 接口和 push 层返回的 `cond` 字段已由 SDK 转换为可读字符串,含义如下:
193
+
194
+ **美股(US)**
195
+
196
+ | 值 | 含义 |
197
+ |----|------|
198
+ | `US_REGULAR_SALE` | 常规交易(Regular Sale) |
199
+ | `US_BUNCHED_TRADE` | 批量交易(Bunched Trade) |
200
+ | `US_CASH_TRADE` | 现金交易(Cash Trade) |
201
+ | `US_INTERMARKET_SWEEP` | 跨市场交易(Intermarket Sweep) |
202
+ | `US_BUNCHED_SOLD_TRADE` | 批量卖出(Bunched Sold Trade) |
203
+ | `US_PRICE_VARIATION_TRADE` | 离价交易(Price Variation Trade) |
204
+ | `US_ODD_LOT_TRADE` | 碎股交易(Odd Lot Trade) |
205
+ | `US_RULE_127_OR_155_TRADE` | 纽交所第 127/155 条交易 |
206
+ | `US_SOLD_LAST` | 延迟交易(Sold Last) |
207
+ | `US_MARKET_CENTER_CLOSE_PRICE` | 中央收市价(Market Center Close Price) |
208
+ | `US_NEXT_DAY_TRADE` | 隔日交易(Next Day Trade) |
209
+ | `US_MARKET_CENTER_OPENING_TRADE` | 中央开盘价交易(Market Center Opening Trade) |
210
+ | `US_PRIOR_REFERENCE_PRICE` | 前参考价(Prior Reference Price) |
211
+ | `US_MARKET_CENTER_OPEN_PRICE` | 中央开盘价(Market Center Open Price) |
212
+ | `US_SELLER` | 卖方(Seller) |
213
+ | `US_FORM_T` | 盘前盘后交易(Form T) |
214
+ | `US_EXTENDED_TRADING_HOURS` | 延长交易时段(Extended Trading Hours) |
215
+ | `US_CONTINGENT_TRADE` | 合单交易(Contingent Trade) |
216
+ | `US_AVERAGE_PRICE_TRADE` | 均价交易(Average Price Trade) |
217
+ | `US_CROSS_TRADE` | 跨市场交易(Cross Trade) |
218
+ | `US_SOLD_OUT_OF_SEQUENCE` | 场外售出(Sold Out of Sequence) |
219
+ | `US_DERIVATIVELY_PRICED` | 衍生工具定价(Derivatively Priced) |
220
+ | `US_QUALIFIED_CONTINGENT_TRADE` | 合单交易(Qualified Contingent Trade) |
221
+
222
+ **港股(HK)**
223
+
224
+ | 值 | 含义 |
225
+ |----|------|
226
+ | `HK_AUTOMATCH_NORMAL` | 自动对盘(Automatch Normal) |
227
+ | `HK_ODD_LOT_TRADE` | 碎股交易(Odd Lot Trade) |
228
+ | `HK_AUCTION_TRADE` | 竞价交易(Auction Trade) |
229
+ | `HK_OVERSEAS_TRADE` | 场外交易(Overseas Trade) |
230
+ | `HK_LATE_TRADE_OFF_EXCHG` | 开市前成交(Late Trade Off Exchange) |
231
+ | `HK_NON_DIRECT_OFF_EXCHG_TRADE` | 非自动对盘(Non-Direct Off Exchange Trade) |
232
+ | `HK_DIRECT_OFF_EXCHG_TRADE` | 同券商自动对盘(Direct Off Exchange Trade) |
233
+ | `HK_AUTOMATIC_INTERNALIZED` | 同券商非自动对盘(Automatic Internalized) |
234
+
190
235
  ### 4. 下单交易
191
236
 
192
237
  ```python
@@ -140,6 +140,51 @@ status = client.get_market_status(Market.US)
140
140
  print(status)
141
141
  ```
142
142
 
143
+ ### 逐笔成交 `cond` 字段说明
144
+
145
+ `trade_tick` 接口和 push 层返回的 `cond` 字段已由 SDK 转换为可读字符串,含义如下:
146
+
147
+ **美股(US)**
148
+
149
+ | 值 | 含义 |
150
+ |----|------|
151
+ | `US_REGULAR_SALE` | 常规交易(Regular Sale) |
152
+ | `US_BUNCHED_TRADE` | 批量交易(Bunched Trade) |
153
+ | `US_CASH_TRADE` | 现金交易(Cash Trade) |
154
+ | `US_INTERMARKET_SWEEP` | 跨市场交易(Intermarket Sweep) |
155
+ | `US_BUNCHED_SOLD_TRADE` | 批量卖出(Bunched Sold Trade) |
156
+ | `US_PRICE_VARIATION_TRADE` | 离价交易(Price Variation Trade) |
157
+ | `US_ODD_LOT_TRADE` | 碎股交易(Odd Lot Trade) |
158
+ | `US_RULE_127_OR_155_TRADE` | 纽交所第 127/155 条交易 |
159
+ | `US_SOLD_LAST` | 延迟交易(Sold Last) |
160
+ | `US_MARKET_CENTER_CLOSE_PRICE` | 中央收市价(Market Center Close Price) |
161
+ | `US_NEXT_DAY_TRADE` | 隔日交易(Next Day Trade) |
162
+ | `US_MARKET_CENTER_OPENING_TRADE` | 中央开盘价交易(Market Center Opening Trade) |
163
+ | `US_PRIOR_REFERENCE_PRICE` | 前参考价(Prior Reference Price) |
164
+ | `US_MARKET_CENTER_OPEN_PRICE` | 中央开盘价(Market Center Open Price) |
165
+ | `US_SELLER` | 卖方(Seller) |
166
+ | `US_FORM_T` | 盘前盘后交易(Form T) |
167
+ | `US_EXTENDED_TRADING_HOURS` | 延长交易时段(Extended Trading Hours) |
168
+ | `US_CONTINGENT_TRADE` | 合单交易(Contingent Trade) |
169
+ | `US_AVERAGE_PRICE_TRADE` | 均价交易(Average Price Trade) |
170
+ | `US_CROSS_TRADE` | 跨市场交易(Cross Trade) |
171
+ | `US_SOLD_OUT_OF_SEQUENCE` | 场外售出(Sold Out of Sequence) |
172
+ | `US_DERIVATIVELY_PRICED` | 衍生工具定价(Derivatively Priced) |
173
+ | `US_QUALIFIED_CONTINGENT_TRADE` | 合单交易(Qualified Contingent Trade) |
174
+
175
+ **港股(HK)**
176
+
177
+ | 值 | 含义 |
178
+ |----|------|
179
+ | `HK_AUTOMATCH_NORMAL` | 自动对盘(Automatch Normal) |
180
+ | `HK_ODD_LOT_TRADE` | 碎股交易(Odd Lot Trade) |
181
+ | `HK_AUCTION_TRADE` | 竞价交易(Auction Trade) |
182
+ | `HK_OVERSEAS_TRADE` | 场外交易(Overseas Trade) |
183
+ | `HK_LATE_TRADE_OFF_EXCHG` | 开市前成交(Late Trade Off Exchange) |
184
+ | `HK_NON_DIRECT_OFF_EXCHG_TRADE` | 非自动对盘(Non-Direct Off Exchange Trade) |
185
+ | `HK_DIRECT_OFF_EXCHG_TRADE` | 同券商自动对盘(Direct Off Exchange Trade) |
186
+ | `HK_AUTOMATIC_INTERNALIZED` | 同券商非自动对盘(Automatic Internalized) |
187
+
143
188
  ### 4. 下单交易
144
189
 
145
190
  ```python
@@ -68,3 +68,12 @@ exclude = ["tests", "tests.*"]
68
68
 
69
69
  [tool.setuptools.package-data]
70
70
  "*" = ["*.*"]
71
+
72
+ [tool.pytest.ini_options]
73
+ testpaths = ["tests"]
74
+ # marker 语义是「这个用例需要什么」,不是「它属于哪一层」。分层由 TIGER_RUN_INTEG 决定,
75
+ # 完整说明见 tests/conftest.py
76
+ markers = [
77
+ "unit: 永远不碰真实接口的用例(纯函数、序列化、配置解析等)",
78
+ "integ: 涉及写操作的真实接口用例,只应手动或定时触发",
79
+ ]
@@ -0,0 +1,202 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ 行情推送延迟监控脚本
4
+
5
+ 参考 tigeropen/examples/push_client_demo.py,订阅指定标的的最优报价(BBO)推送,
6
+ 记录每次收到的 askTimestamp / bidTimestamp、本机接收时的系统时间,以及两者差值
7
+ (即推送延迟),写入日志文件;运行结束后打印延迟统计(min/max/avg/p50/p95/p99)。
8
+
9
+ 配置来源:TIGER_CONFIG_PATH 环境变量指向的目录(含 tiger_openapi_config.properties)。
10
+ 不在代码中硬编码任何账号路径,运行时通过环境变量指定。
11
+
12
+ 用法示例:
13
+ TIGER_CONFIG_PATH=~/.tigeropen/<profile> .venv/bin/python scripts/push_latency_monitor.py
14
+ """
15
+ import logging
16
+ import os
17
+ import statistics
18
+ import threading
19
+ import time
20
+ from datetime import datetime
21
+ from pathlib import Path
22
+
23
+ from tigeropen.push.pb.QuoteBBOData_pb2 import QuoteBBOData
24
+ from tigeropen.push.push_client import PushClient
25
+ from tigeropen.tiger_open_config import TigerOpenClientConfig
26
+
27
+ # ---- 配置 ----
28
+ SYMBOLS = ['01810', '02513']
29
+ RUN_DURATION_SECONDS = 10 * 60 # 持续运行 10 分钟
30
+
31
+ LOG_DIR = Path(__file__).resolve().parent / 'logs'
32
+ LOG_DIR.mkdir(parents=True, exist_ok=True)
33
+ LOG_FILE = LOG_DIR / f'push_latency_{time.strftime("%Y%m%d_%H%M%S")}.log'
34
+
35
+ logger = logging.getLogger('push_latency_monitor')
36
+ logger.setLevel(logging.INFO)
37
+ _formatter = logging.Formatter('%(asctime)s.%(msecs)03d %(levelname)s %(message)s')
38
+ _file_handler = logging.FileHandler(LOG_FILE, encoding='utf-8')
39
+ _file_handler.setFormatter(_formatter)
40
+ _console_handler = logging.StreamHandler()
41
+ _console_handler.setFormatter(_formatter)
42
+ logger.addHandler(_file_handler)
43
+ logger.addHandler(_console_handler)
44
+
45
+
46
+ class LatencyStats:
47
+ """线程安全的延迟采样收集器 (回调可能在线程池的不同线程中并发触发)"""
48
+
49
+ def __init__(self):
50
+ self._lock = threading.Lock()
51
+ self._data = {} # (symbol, 'ask'/'bid') -> [delay_ms, ...]
52
+
53
+ def add(self, symbol, kind, delay_ms):
54
+ with self._lock:
55
+ self._data.setdefault((symbol, kind), []).append(delay_ms)
56
+
57
+ def report(self):
58
+ with self._lock:
59
+ snapshot = {k: list(v) for k, v in self._data.items()}
60
+ if not snapshot:
61
+ return '(未收到任何 BBO 推送数据)'
62
+ lines = []
63
+ for (symbol, kind) in sorted(snapshot.keys()):
64
+ values = sorted(snapshot[(symbol, kind)])
65
+ n = len(values)
66
+ if n == 0:
67
+ continue
68
+
69
+ def pct(p):
70
+ return values[min(int(n * p), n - 1)]
71
+
72
+ over_250 = sum(1 for v in values if v > 250)
73
+ over_250_pct = 100 * over_250 / n
74
+
75
+ lines.append(
76
+ f'{symbol} {kind}: n={n} '
77
+ f'min={values[0]} p50={pct(0.5)} p90={pct(0.9)} p95={pct(0.95)} p99={pct(0.99)} max={values[-1]} '
78
+ f'avg={statistics.mean(values):.1f} >250ms={over_250_pct:.1f}%'
79
+ )
80
+ return '\n'.join(lines)
81
+
82
+
83
+ stats = LatencyStats()
84
+
85
+
86
+ def on_quote_bbo_changed(frame: QuoteBBOData):
87
+ """最优报价推送回调:按用户方式 latency = recv_time - min(askTimestamp, bidTimestamp)"""
88
+ recv_ts_ms = int(time.time() * 1000)
89
+ local_time_str = datetime.now().strftime('%H:%M:%S.%f')[:-3]
90
+ symbol = frame.symbol
91
+
92
+ # frame.timestamp 是推送数据本身的时间戳(服务端打的)
93
+ data_ts = frame.timestamp
94
+ data_delay = recv_ts_ms - data_ts if data_ts else None
95
+ if data_delay is not None:
96
+ stats.add(symbol, 'server_ts', data_delay)
97
+
98
+ # 收集 ask/bid timestamp
99
+ ask_ts = frame.askTimestamp if (frame.HasField('askTimestamp') and frame.askTimestamp) else None
100
+ bid_ts = frame.bidTimestamp if (frame.HasField('bidTimestamp') and frame.bidTimestamp) else None
101
+
102
+ # 分别统计
103
+ if ask_ts:
104
+ stats.add(symbol, 'ask', recv_ts_ms - ask_ts)
105
+ if bid_ts:
106
+ stats.add(symbol, 'bid', recv_ts_ms - bid_ts)
107
+
108
+ # 用户方式: min(askTimestamp, bidTimestamp)
109
+ ts_candidates = [t for t in (ask_ts, bid_ts) if t]
110
+ if ts_candidates:
111
+ min_ts = min(ts_candidates)
112
+ max_ts = max(ts_candidates)
113
+ latency_min = recv_ts_ms - min_ts # 用户方式(取最旧的 → 延迟偏大)
114
+ latency_max = recv_ts_ms - max_ts # 取最新的 → 更接近真实推送延迟
115
+ stats.add(symbol, 'user_method(min)', latency_min)
116
+ stats.add(symbol, 'fresh_method(max)', latency_max)
117
+
118
+ # 日志详情
119
+ parts = [f'symbol={symbol}', f'local_time={local_time_str}', f'recv={recv_ts_ms}']
120
+ if data_ts:
121
+ parts.append(f'svrTs={data_ts} svrDelay={data_delay}')
122
+ if ask_ts:
123
+ ask_time_str = datetime.fromtimestamp(ask_ts / 1000).strftime('%H:%M:%S.%f')[:-3]
124
+ parts.append(f'ask={ask_ts}({ask_time_str}) d={recv_ts_ms - ask_ts}ms p={frame.askPrice}')
125
+ if bid_ts:
126
+ bid_time_str = datetime.fromtimestamp(bid_ts / 1000).strftime('%H:%M:%S.%f')[:-3]
127
+ parts.append(f'bid={bid_ts}({bid_time_str}) d={recv_ts_ms - bid_ts}ms p={frame.bidPrice}')
128
+ if ts_candidates:
129
+ parts.append(f'user_latency(min)={latency_min}ms fresh(max)={latency_max}ms')
130
+
131
+ logger.info(' '.join(parts))
132
+
133
+
134
+ def query_subscribed_callback(data):
135
+ logger.info(f'subscribed data: {data}')
136
+
137
+
138
+ def subscribe_callback(frame):
139
+ logger.info(f'subscribe callback: {frame}')
140
+
141
+
142
+ def error_callback(frame):
143
+ logger.error(f'push error: {frame}')
144
+
145
+
146
+ def connect_callback(frame):
147
+ logger.info('connected')
148
+
149
+
150
+ def disconnect_callback():
151
+ """连接断开回调,参考 demo 的重连逻辑"""
152
+ for t in range(1, 200):
153
+ try:
154
+ logger.warning('disconnected, reconnecting')
155
+ push_client.connect(client_config.tiger_id, client_config.private_key)
156
+ except Exception:
157
+ logger.exception('connect failed, retry')
158
+ time.sleep(t)
159
+ else:
160
+ logger.info('reconnect success')
161
+ return
162
+ logger.error('reconnect failed, please check your network')
163
+
164
+
165
+ if __name__ == '__main__':
166
+ props_path = os.environ.get('TIGER_CONFIG_PATH', os.path.expanduser('~/.tigeropen/'))
167
+ client_config = TigerOpenClientConfig(props_path=props_path)
168
+ protocol, host, port = client_config.socket_host_port
169
+ push_client = PushClient(host, port, use_ssl=(protocol == 'ssl'))
170
+
171
+ push_client.quote_bbo_changed = on_quote_bbo_changed
172
+ push_client.query_subscribed_callback = query_subscribed_callback
173
+ push_client.subscribe_callback = subscribe_callback
174
+ push_client.error_callback = error_callback
175
+
176
+ push_client.connect(client_config.tiger_id, client_config.private_key)
177
+ push_client.disconnect_callback = disconnect_callback
178
+
179
+ logger.info(f'socket={protocol}://{host}:{port} symbols={SYMBOLS} '
180
+ f'duration={RUN_DURATION_SECONDS}s log_file={LOG_FILE}')
181
+ push_client.subscribe_quote(SYMBOLS)
182
+
183
+ try:
184
+ time.sleep(RUN_DURATION_SECONDS)
185
+ except KeyboardInterrupt:
186
+ logger.info('收到中断信号,提前结束')
187
+ finally:
188
+ push_client.disconnect()
189
+ logger.info('已断开连接')
190
+ summary = stats.report()
191
+ # 打印本地系统时间信息
192
+ local_now = datetime.now()
193
+ logger.info(f'本地系统时间: {local_now.strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]}')
194
+ logger.info('==== 延迟统计汇总 ====\n' + summary)
195
+ logger.info('注: dataDelay = recv_time(本地系统时间) - timestamp(服务端时间),'
196
+ '负值说明本地时钟慢于服务端时钟')
197
+ print(f'\n本地系统时间: {local_now.strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]}')
198
+ print('\n==== 延迟统计汇总 ====')
199
+ print(summary)
200
+ print('\n注: dataDelay = recv_time(本地系统时间) - timestamp(服务端时间)')
201
+ print(' 负值说明本地时钟慢于服务端;askDelay/bidDelay = recv_time - ask/bidTimestamp(交易所报价时间)')
202
+ print(f'详细日志: {LOG_FILE}')
@@ -0,0 +1,153 @@
1
+ # -*- coding: utf-8 -*-
2
+ """本仓自查测试:wire method 注册表与契约覆盖完整性。."""
3
+ import ast
4
+ import re
5
+ from pathlib import Path
6
+
7
+ import pytest
8
+
9
+ # 纯静态分析,不碰真实接口
10
+ pytestmark = pytest.mark.unit
11
+
12
+ _REPO_ROOT = Path(__file__).resolve().parent.parent
13
+ _TESTS_DIR = Path(__file__).resolve().parent
14
+
15
+ # 所有会构造 OpenApiRequest 的模块
16
+ _CLIENT_SOURCES = (
17
+ 'tigeropen/quote/quote_client.py',
18
+ 'tigeropen/trade/trade_client.py',
19
+ 'tigeropen/tiger_open_client.py',
20
+ )
21
+ _SERVICE_TYPES = 'tigeropen/common/consts/service_types.py'
22
+
23
+ # 公开但不发起 wire 调用的方法。加新条目前先确认它真的只是本地封装。
24
+ _LOCAL_ONLY_PUBLIC_METHODS = frozenset({
25
+ 'execute', # 泛用发送入口,method 由调用方给
26
+ 'get_bars_by_page', # 分页封装,内部循环调 get_bars
27
+ 'get_future_bars_by_page', # 分页封装,内部循环调 get_future_bars
28
+ 'refresh_token', # 内部调 query_token
29
+ 'run', # 定时器线程的 run,不是 API 方法
30
+ })
31
+
32
+ # 棘轮清单:当前还没有任何用例覆盖的 wire method。
33
+ # 只允许变短。补齐一个用例就从这里删掉一行,否则 test_wire_method_is_exercised_by_test 会红。
34
+ _WIRE_METHODS_WITHOUT_TEST = frozenset({
35
+ })
36
+
37
+ # 测试里持有 client 的变量名
38
+ _CLIENT_ATTR_NAMES = frozenset({'client', 'quote_client', 'trade_client'})
39
+
40
+
41
+ def _openapi_request_constants(func_node):
42
+ """取出一个函数体里 OpenApiRequest(...) 用到的常量名。
43
+
44
+ 两种写法都要认:OpenApiRequest(MARKET_STATE, biz_model=params) 和
45
+ OpenApiRequest(method=USER_LICENSE)。
46
+ """
47
+ constants = set()
48
+ for node in ast.walk(func_node):
49
+ if not (isinstance(node, ast.Call)
50
+ and isinstance(node.func, ast.Name)
51
+ and node.func.id == 'OpenApiRequest'):
52
+ continue
53
+ candidates = list(node.args)
54
+ candidates += [kw.value for kw in node.keywords if kw.arg == 'method']
55
+ for arg in candidates:
56
+ if isinstance(arg, ast.Name):
57
+ constants.add(arg.id)
58
+ return constants
59
+
60
+
61
+ def _scan_clients():
62
+ """返回 (方法名 -> wire 常量名集合, 公开方法名集合)。"""
63
+ method_to_wire = {}
64
+ public_methods = set()
65
+ for rel_path in _CLIENT_SOURCES:
66
+ tree = ast.parse((_REPO_ROOT / rel_path).read_text())
67
+ for class_node in (n for n in ast.walk(tree) if isinstance(n, ast.ClassDef)):
68
+ for func in (n for n in class_node.body
69
+ if isinstance(n, (ast.FunctionDef, ast.AsyncFunctionDef))):
70
+ if not func.name.startswith('_'):
71
+ public_methods.add(func.name)
72
+ constants = _openapi_request_constants(func)
73
+ if constants:
74
+ method_to_wire.setdefault(func.name, set()).update(constants)
75
+ return method_to_wire, public_methods
76
+
77
+
78
+ def _wire_method_constants():
79
+ """service_types.py 里声明的所有常量名。"""
80
+ source = (_REPO_ROOT / _SERVICE_TYPES).read_text()
81
+ pattern = re.compile(r'^([A-Z][A-Z0-9_]*)\s*=\s*[\'"][a-z0-9_]+[\'"]', re.M)
82
+ return {m.group(1) for m in pattern.finditer(source)}
83
+
84
+
85
+ def _client_methods_called_in_tests():
86
+ """测试里通过 client 调用过的方法名。
87
+
88
+ 识别 ``self.client.xxx()`` / ``client.xxx()`` / ``self.quote_client.xxx()`` 这几种写法。
89
+ """
90
+ called = set()
91
+ for path in sorted(_TESTS_DIR.rglob('test_*.py')):
92
+ for node in ast.walk(ast.parse(path.read_text())):
93
+ if not (isinstance(node, ast.Call) and isinstance(node.func, ast.Attribute)):
94
+ continue
95
+ owner = node.func.value
96
+ if isinstance(owner, ast.Attribute) and owner.attr in _CLIENT_ATTR_NAMES:
97
+ called.add(node.func.attr)
98
+ elif isinstance(owner, ast.Name) and owner.id in _CLIENT_ATTR_NAMES:
99
+ called.add(node.func.attr)
100
+ return called
101
+
102
+
103
+ def test_no_dangling_wire_method_constant():
104
+ """No dangling constants in service_types.py."""
105
+ method_to_wire, _ = _scan_clients()
106
+ declared = _wire_method_constants()
107
+ referenced = set().union(*method_to_wire.values())
108
+
109
+ dangling = sorted(declared - referenced)
110
+ assert not dangling, (
111
+ f'service_types.py 里有 {len(dangling)} 个常量没有任何 client 方法引用:{dangling}。'
112
+ f'要么补上对应的 client 方法,要么删掉常量。')
113
+
114
+ unknown = sorted(referenced - declared)
115
+ assert not unknown, f'client 引用了 service_types.py 里不存在的常量:{unknown}'
116
+
117
+
118
+ def test_public_client_method_has_wire_method():
119
+ """Public methods must either make a wire call or be in the local-only list."""
120
+ method_to_wire, public_methods = _scan_clients()
121
+
122
+ without_wire = {m for m in public_methods if m not in method_to_wire}
123
+ unexpected = sorted(without_wire - _LOCAL_ONLY_PUBLIC_METHODS)
124
+ assert not unexpected, (
125
+ f'这些公开方法既不发起 wire 调用,也不在本地封装允许清单里:{unexpected}。'
126
+ f'如果确实只是本地封装,加进 _LOCAL_ONLY_PUBLIC_METHODS 并写明理由。')
127
+
128
+ stale = sorted(_LOCAL_ONLY_PUBLIC_METHODS - without_wire)
129
+ assert not stale, (
130
+ f'_LOCAL_ONLY_PUBLIC_METHODS 里这些条目已经不需要了(方法已发 wire 调用或已删除):'
131
+ f'{stale},请从清单里移除。')
132
+
133
+
134
+ def test_wire_method_is_exercised_by_test():
135
+ """Every wire method must be exercised by at least one test."""
136
+ method_to_wire, _ = _scan_clients()
137
+ referenced = set().union(*method_to_wire.values())
138
+ called_methods = _client_methods_called_in_tests()
139
+
140
+ covered = set()
141
+ for method in called_methods:
142
+ covered |= method_to_wire.get(method, set())
143
+ uncovered = referenced - covered
144
+
145
+ newly_uncovered = sorted(uncovered - _WIRE_METHODS_WITHOUT_TEST)
146
+ assert not newly_uncovered, (
147
+ f'这些 wire method 没有任何用例覆盖:{newly_uncovered}。'
148
+ f'新增接口必须同时补用例,不要往 _WIRE_METHODS_WITHOUT_TEST 里加条目。')
149
+
150
+ now_covered = sorted(_WIRE_METHODS_WITHOUT_TEST - uncovered)
151
+ assert not now_covered, (
152
+ f'这些 wire method 已经有用例覆盖了:{now_covered},'
153
+ f'请从 _WIRE_METHODS_WITHOUT_TEST 里删掉,让棘轮往前走一格。')
@@ -17,6 +17,12 @@ from tigeropen.tiger_open_config import (
17
17
  SANDBOX_TIGER_PUBLIC_KEY, TIGER_PUBLIC_KEY,
18
18
  )
19
19
 
20
+ import pytest
21
+
22
+
23
+ # 纯单测:永远不碰真实接口,contract / integ job 会跳过
24
+ pytestmark = pytest.mark.unit
25
+
20
26
 
21
27
  def make_config(**kwargs) -> TigerOpenClientConfig:
22
28
  """构造一个不触发 query_domains 的 config 实例"""
@@ -292,8 +298,11 @@ class TestRefreshServerInfo(unittest.TestCase):
292
298
  }
293
299
 
294
300
  def test_refresh_server_info(self):
295
- config = TigerOpenClientConfig()
301
+ # 构造时先关掉动态域名,避免 __init__ 里的 query_domains() 发真实请求;
302
+ # 随后再打开并注入 mock 的域名表,验证的是 refresh_server_info 本身的逻辑
303
+ config = TigerOpenClientConfig(enable_dynamic_domain=False)
296
304
  config.query_domains = MagicMock(name='query_domains', return_value=self.domain_map)
305
+ config.enable_dynamic_domain = True
297
306
  config.domain_conf = config.query_domains()
298
307
 
299
308
  self.assertEqual('https://openapi.tigerfintech.com' + GATEWAY_SUFFIX, config.server_url)