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.
- {tigeropen-3.7.0/tigeropen.egg-info → tigeropen-3.7.2}/PKG-INFO +47 -2
- {tigeropen-3.7.0 → tigeropen-3.7.2}/README.md +45 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/pyproject.toml +9 -0
- tigeropen-3.7.2/scripts/push_latency_monitor.py +202 -0
- tigeropen-3.7.2/tests/test_api_registry.py +153 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tests/test_client_config.py +10 -1
- tigeropen-3.7.2/tests/test_common_utils.py +264 -0
- tigeropen-3.7.2/tests/test_low_coverage.py +577 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tests/test_option_helper.py +10 -2
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tests/test_option_util.py +84 -61
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tests/test_push_client.py +9 -1
- tigeropen-3.7.2/tests/test_push_network.py +294 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tests/test_quote_client.py +18 -299
- tigeropen-3.7.2/tests/test_quote_uncovered.py +168 -0
- tigeropen-3.7.2/tests/test_request_models.py +477 -0
- tigeropen-3.7.2/tests/test_response_parsers.py +287 -0
- tigeropen-3.7.2/tests/test_serialization_contract.py +278 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tests/test_trade_client.py +17 -191
- tigeropen-3.7.2/tests/test_trade_uncovered.py +207 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tests/test_utils.py +6 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/__init__.py +1 -1
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/common/consts/tick_constants.py +3 -3
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/examples/option_helpers/helpers.py +17 -4
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/examples/option_helpers/util.py +13 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/examples/quote_client_demo.py +1 -1
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/trade_tick.py +2 -2
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/thread_pool.py +6 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/domain/quote_brief.py +2 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/quote_client.py +7 -3
- {tigeropen-3.7.0 → tigeropen-3.7.2/tigeropen.egg-info}/PKG-INFO +47 -2
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen.egg-info/SOURCES.txt +10 -15
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen.egg-info/top_level.txt +0 -2
- tigeropen-3.7.0/dist/tigeropen-3.5.8.tar.gz +0 -0
- tigeropen-3.7.0/dist/tigeropen-3.5.9-py3-none-any.whl +0 -0
- tigeropen-3.7.0/dist/tigeropen-3.5.9.tar.gz +0 -0
- tigeropen-3.7.0/dist/tigeropen-3.6.0-py3-none-any.whl +0 -0
- tigeropen-3.7.0/dist/tigeropen-3.6.0.tar.gz +0 -0
- tigeropen-3.7.0/tests/cli/test_client_factory.py +0 -90
- tigeropen-3.7.0/tests/cli/test_config.py +0 -281
- tigeropen-3.7.0/tests/cli/test_error_handling.py +0 -120
- tigeropen-3.7.0/tests/cli/test_formatting.py +0 -220
- tigeropen-3.7.0/tests/cli/test_main.py +0 -63
- tigeropen-3.7.0/tests/cli/test_push.py +0 -210
- tigeropen-3.7.0/tests/cli/test_quote.py +0 -666
- tigeropen-3.7.0/tests/cli/test_trade.py +0 -629
- tigeropen-3.7.0/tigeropen/examples/ai/mcp_server/dist/tigermcp-0.1.7.tar.gz +0 -0
- tigeropen-3.7.0/tigeropen/fundamental/response/__init__.py +0 -1
- {tigeropen-3.7.0 → tigeropen-3.7.2}/MANIFEST.in +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/requirements.txt +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/setup.cfg +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/setup.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/cli/__init__.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/cli/account_cmd.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/cli/client_factory.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/cli/config_cmd.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/cli/formatting.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/cli/main.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/cli/push_cmd.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/cli/quote_cmd.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/cli/trade_cmd.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/common/__init__.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/common/consts/__init__.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/common/consts/filter_fields.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/common/consts/fundamental_fields.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/common/consts/params.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/common/consts/push_destinations.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/common/consts/push_subscriptions.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/common/consts/push_types.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/common/consts/quote_keys.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/common/consts/service_types.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/common/exceptions.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/common/model.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/common/request.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/common/response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/common/util/__init__.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/common/util/account_util.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/common/util/common_utils.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/common/util/contract_utils.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/common/util/order_utils.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/common/util/price_util.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/common/util/signature_utils.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/common/util/string_utils.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/common/util/tick_util.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/common/util/web_utils.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/examples/__init__.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/examples/ai/mcp_server/MANIFEST.in +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/examples/ai/mcp_server/README.md +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/examples/ai/mcp_server/__init__.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/examples/ai/mcp_server/setup.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/examples/ai/mcp_server/tigermcp/__init__.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/examples/ai/mcp_server/tigermcp/server.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/examples/ai/mcp_server/tigermcp/version.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/examples/client_config.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/examples/financial_demo.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/examples/nasdaq100.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/examples/option_helpers/__init__.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/examples/option_helpers/extra_calculator.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/examples/option_helpers/probability_calculator.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/examples/push_client_demo.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/examples/push_client_stomp_demo.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/examples/trade_client_demo.py +0 -0
- {tigeropen-3.7.0/tests/cli → tigeropen-3.7.2/tigeropen/fundamental}/__init__.py +0 -0
- {tigeropen-3.7.0/tigeropen/fundamental → tigeropen-3.7.2/tigeropen/fundamental/domain}/__init__.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/fundamental/request/__init__.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/fundamental/request/model.py +0 -0
- {tigeropen-3.7.0/tigeropen/fundamental/domain → tigeropen-3.7.2/tigeropen/fundamental/response}/__init__.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/fundamental/response/corporate_delisting_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/fundamental/response/corporate_dividend_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/fundamental/response/corporate_earnings_calendar_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/fundamental/response/corporate_ipo_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/fundamental/response/corporate_split_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/fundamental/response/corporate_symbol_change_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/fundamental/response/dataframe_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/fundamental/response/financial_daily_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/fundamental/response/financial_exchange_rate_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/fundamental/response/financial_report_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/fundamental/response/industry_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/__init__.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/network/__init__.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/network/connect.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/network/exception.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/network/listener.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/network/protocal.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/network/transport.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/network/utils.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/AssetData.proto +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/AssetData_pb2.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/AssetData_pb2.pyi +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/KlineData.proto +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/KlineData_pb2.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/KlineData_pb2.pyi +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/OptionTopData.proto +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/OptionTopData_pb2.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/OptionTopData_pb2.pyi +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/OrderStatusData.proto +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/OrderStatusData_pb2.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/OrderStatusData_pb2.pyi +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/OrderTransactionData.proto +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/OrderTransactionData_pb2.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/OrderTransactionData_pb2.pyi +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/PositionData.proto +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/PositionData_pb2.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/PositionData_pb2.pyi +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/PushData.proto +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/PushData_pb2.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/PushData_pb2.pyi +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/QuoteBBOData.proto +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/QuoteBBOData_pb2.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/QuoteBBOData_pb2.pyi +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/QuoteBasicData.proto +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/QuoteBasicData_pb2.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/QuoteBasicData_pb2.pyi +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/QuoteData.proto +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/QuoteData_pb2.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/QuoteData_pb2.pyi +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/QuoteDepthData.proto +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/QuoteDepthData_pb2.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/QuoteDepthData_pb2.pyi +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/Request.proto +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/Request_pb2.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/Request_pb2.pyi +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/Response.proto +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/Response_pb2.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/Response_pb2.pyi +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/SocketCommon.proto +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/SocketCommon_pb2.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/SocketCommon_pb2.pyi +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/StockTopData.proto +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/StockTopData_pb2.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/StockTopData_pb2.pyi +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/TickData.proto +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/TickData_pb2.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/TickData_pb2.pyi +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/TradeTickData.proto +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/TradeTickData_pb2.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/TradeTickData_pb2.pyi +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/__init__.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/readme.md +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/pb/util.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/protobuf_push_client.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/push_client.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/push/stomp_push_client.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/__init__.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/domain/__init__.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/domain/addon_entitlement.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/domain/bar.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/domain/capital_distribution.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/domain/filter.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/domain/market_status.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/domain/option_analysis.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/domain/stock_broker.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/domain/tick.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/domain/timeline.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/request/__init__.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/request/model.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/__init__.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/addon_entitlement_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/broker_hold_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/capital_distribution_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/capital_flow_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/fund_contracts_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/future_briefs_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/future_contract_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/future_depth_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/future_exchange_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/future_history_main_contract_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/future_quote_bar_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/future_quote_ticks_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/future_trading_times_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/kline_quota_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/market_scanner_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/market_status_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/option_analysis_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/option_briefs_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/option_chains_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/option_depth_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/option_expirations_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/option_quote_bar_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/option_quote_ticks_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/option_symbols_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/option_timeline_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/quote_bar_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/quote_brief_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/quote_dataframe_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/quote_delay_briefs_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/quote_depth_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/quote_grab_permission_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/quote_hour_trading_timeline_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/quote_overnight_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/quote_ticks_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/quote_timeline_history_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/quote_timeline_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/stock_briefs_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/stock_broker_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/stock_details_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/stock_short_interest_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/stock_trade_meta_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/symbol_names_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/symbols_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/trade_rank_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/trading_calendar_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/warrant_briefs_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/quote/response/warrant_filter_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/tiger_open_client.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/tiger_open_config.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/__init__.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/domain/__init__.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/domain/account.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/domain/contract.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/domain/option_exercise.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/domain/order.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/domain/position.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/domain/prime_account.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/domain/profile.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/domain/transfer.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/request/__init__.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/request/model.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/response/__init__.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/response/account_profile_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/response/aggregate_assets_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/response/analytics_asset_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/response/assets_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/response/contracts_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/response/forex_order_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/response/fund_details_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/response/funding_history_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/response/option_exercise_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/response/order_id_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/response/order_preview_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/response/orders_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/response/positions_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/response/prime_assets_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/response/segment_fund_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/response/transactions_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/response/transfer_response.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen/trade/trade_client.py +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen.egg-info/dependency_links.txt +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen.egg-info/entry_points.txt +0 -0
- {tigeropen-3.7.0 → tigeropen-3.7.2}/tigeropen.egg-info/requires.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: tigeropen
|
|
3
|
-
Version: 3.7.
|
|
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
|
-
|
|
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)
|