axdata 0.1.0__py3-none-any.whl
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.
- axdata/__init__.py +16 -0
- axdata/cache.py +19 -0
- axdata/client.py +1223 -0
- axdata-0.1.0.dist-info/METADATA +238 -0
- axdata-0.1.0.dist-info/RECORD +427 -0
- axdata-0.1.0.dist-info/WHEEL +5 -0
- axdata-0.1.0.dist-info/entry_points.txt +8 -0
- axdata-0.1.0.dist-info/licenses/LICENSE +162 -0
- axdata-0.1.0.dist-info/top_level.txt +6 -0
- axdata_core/__init__.py +221 -0
- axdata_core/_tdx_wire/__init__.py +35 -0
- axdata_core/_tdx_wire/_shim.py +140 -0
- axdata_core/_tdx_wire/api/__init__.py +51 -0
- axdata_core/_tdx_wire/api/auction.py +24 -0
- axdata_core/_tdx_wire/api/bars.py +24 -0
- axdata_core/_tdx_wire/api/base.py +26 -0
- axdata_core/_tdx_wire/api/codes.py +25 -0
- axdata_core/_tdx_wire/api/corporate.py +24 -0
- axdata_core/_tdx_wire/api/finance.py +24 -0
- axdata_core/_tdx_wire/api/intraday.py +24 -0
- axdata_core/_tdx_wire/api/quotes.py +26 -0
- axdata_core/_tdx_wire/api/resources.py +25 -0
- axdata_core/_tdx_wire/api/session.py +24 -0
- axdata_core/_tdx_wire/api/trades.py +24 -0
- axdata_core/_tdx_wire/client.py +33 -0
- axdata_core/_tdx_wire/exceptions.py +27 -0
- axdata_core/_tdx_wire/hosts.py +106 -0
- axdata_core/_tdx_wire/models/__init__.py +93 -0
- axdata_core/_tdx_wire/models/auction.py +26 -0
- axdata_core/_tdx_wire/models/corporate.py +27 -0
- axdata_core/_tdx_wire/models/finance.py +26 -0
- axdata_core/_tdx_wire/models/intraday.py +31 -0
- axdata_core/_tdx_wire/models/kline.py +27 -0
- axdata_core/_tdx_wire/models/quote.py +32 -0
- axdata_core/_tdx_wire/models/resource.py +24 -0
- axdata_core/_tdx_wire/models/security.py +24 -0
- axdata_core/_tdx_wire/models/session.py +27 -0
- axdata_core/_tdx_wire/models/subchart.py +25 -0
- axdata_core/_tdx_wire/models/trade.py +28 -0
- axdata_core/_tdx_wire/protocol/__init__.py +49 -0
- axdata_core/_tdx_wire/protocol/commands/__init__.py +43 -0
- axdata_core/_tdx_wire/protocol/commands/auction.py +110 -0
- axdata_core/_tdx_wire/protocol/commands/codec.py +43 -0
- axdata_core/_tdx_wire/protocol/commands/corporate.py +98 -0
- axdata_core/_tdx_wire/protocol/commands/finance.py +96 -0
- axdata_core/_tdx_wire/protocol/commands/intraday.py +122 -0
- axdata_core/_tdx_wire/protocol/commands/klines.py +102 -0
- axdata_core/_tdx_wire/protocol/commands/lookup.py +36 -0
- axdata_core/_tdx_wire/protocol/commands/price_limits.py +89 -0
- axdata_core/_tdx_wire/protocol/commands/quotes.py +102 -0
- axdata_core/_tdx_wire/protocol/commands/registry.py +36 -0
- axdata_core/_tdx_wire/protocol/commands/resources.py +99 -0
- axdata_core/_tdx_wire/protocol/commands/security.py +103 -0
- axdata_core/_tdx_wire/protocol/commands/session.py +82 -0
- axdata_core/_tdx_wire/protocol/commands/subchart.py +97 -0
- axdata_core/_tdx_wire/protocol/commands/trades.py +107 -0
- axdata_core/_tdx_wire/protocol/constants.py +124 -0
- axdata_core/_tdx_wire/protocol/frame.py +68 -0
- axdata_core/_tdx_wire/protocol/unit.py +85 -0
- axdata_core/_tdx_wire/py.typed +1 -0
- axdata_core/_tdx_wire/transport/__init__.py +34 -0
- axdata_core/_tdx_wire/transport/base.py +25 -0
- axdata_core/_tdx_wire/transport/memory.py +24 -0
- axdata_core/_tdx_wire/transport/pool.py +32 -0
- axdata_core/_tdx_wire/transport/socket.py +39 -0
- axdata_core/adapters/__init__.py +0 -0
- axdata_core/adapters/cls/__init__.py +17 -0
- axdata_core/adapters/cls/provider_bridge.py +23 -0
- axdata_core/adapters/cls/request.py +632 -0
- axdata_core/adapters/cls/source_adapter_registry.py +31 -0
- axdata_core/adapters/cninfo/__init__.py +17 -0
- axdata_core/adapters/cninfo/provider_bridge.py +25 -0
- axdata_core/adapters/cninfo/request.py +2425 -0
- axdata_core/adapters/cninfo/source_adapter_registry.py +42 -0
- axdata_core/adapters/eastmoney/__init__.py +17 -0
- axdata_core/adapters/eastmoney/provider_bridge.py +25 -0
- axdata_core/adapters/eastmoney/request.py +1128 -0
- axdata_core/adapters/eastmoney/source_adapter_registry.py +42 -0
- axdata_core/adapters/exchange/__init__.py +17 -0
- axdata_core/adapters/exchange/provider_bridge.py +25 -0
- axdata_core/adapters/exchange/request.py +1214 -0
- axdata_core/adapters/exchange/source_adapter_registry.py +42 -0
- axdata_core/adapters/kph/__init__.py +17 -0
- axdata_core/adapters/kph/provider_bridge.py +23 -0
- axdata_core/adapters/kph/request.py +509 -0
- axdata_core/adapters/kph/source_adapter_registry.py +31 -0
- axdata_core/adapters/sina/__init__.py +17 -0
- axdata_core/adapters/sina/provider_bridge.py +25 -0
- axdata_core/adapters/sina/request.py +5155 -0
- axdata_core/adapters/sina/source_adapter_registry.py +42 -0
- axdata_core/adapters/tdx/__init__.py +32 -0
- axdata_core/adapters/tdx/adjustment.py +50 -0
- axdata_core/adapters/tdx/adjustment_fetch.py +51 -0
- axdata_core/adapters/tdx/auction_fetch.py +51 -0
- axdata_core/adapters/tdx/client_factory.py +110 -0
- axdata_core/adapters/tdx/code_fetch.py +62 -0
- axdata_core/adapters/tdx/codes.py +54 -0
- axdata_core/adapters/tdx/derived_rows.py +69 -0
- axdata_core/adapters/tdx/downloader.py +193 -0
- axdata_core/adapters/tdx/downloader_interface_sets.py +50 -0
- axdata_core/adapters/tdx/downloader_profiles.py +43 -0
- axdata_core/adapters/tdx/downloader_registry.py +115 -0
- axdata_core/adapters/tdx/execution_utils.py +55 -0
- axdata_core/adapters/tdx/f10_executor.py +57 -0
- axdata_core/adapters/tdx/f10_normalize.py +61 -0
- axdata_core/adapters/tdx/f10_params.py +57 -0
- axdata_core/adapters/tdx/f10_postprocess.py +67 -0
- axdata_core/adapters/tdx/f10_render.py +48 -0
- axdata_core/adapters/tdx/f10_request.py +62 -0
- axdata_core/adapters/tdx/finance_fetch.py +61 -0
- axdata_core/adapters/tdx/finance_maps.py +54 -0
- axdata_core/adapters/tdx/finance_normalize.py +57 -0
- axdata_core/adapters/tdx/host_config.py +115 -0
- axdata_core/adapters/tdx/interface_sets.py +50 -0
- axdata_core/adapters/tdx/intraday_fetch.py +68 -0
- axdata_core/adapters/tdx/kline_helpers.py +58 -0
- axdata_core/adapters/tdx/limit_ladder_fetch.py +60 -0
- axdata_core/adapters/tdx/limit_ladder_topics.py +53 -0
- axdata_core/adapters/tdx/normalize_utils.py +75 -0
- axdata_core/adapters/tdx/options.py +61 -0
- axdata_core/adapters/tdx/price_limit_calendar.py +49 -0
- axdata_core/adapters/tdx/price_limit_fetch.py +53 -0
- axdata_core/adapters/tdx/price_limit_history.py +50 -0
- axdata_core/adapters/tdx/price_limits.py +58 -0
- axdata_core/adapters/tdx/provider_bridge.py +68 -0
- axdata_core/adapters/tdx/quote_fetch.py +61 -0
- axdata_core/adapters/tdx/quote_identity.py +49 -0
- axdata_core/adapters/tdx/rank_fetch.py +55 -0
- axdata_core/adapters/tdx/rank_params.py +52 -0
- axdata_core/adapters/tdx/realtime_refresh.py +45 -0
- axdata_core/adapters/tdx/request.py +11 -0
- axdata_core/adapters/tdx/request_adapter_runtime.py +41 -0
- axdata_core/adapters/tdx/request_client.py +51 -0
- axdata_core/adapters/tdx/request_compat.py +89 -0
- axdata_core/adapters/tdx/request_dispatch.py +50 -0
- axdata_core/adapters/tdx/request_filters.py +60 -0
- axdata_core/adapters/tdx/request_host_config.py +110 -0
- axdata_core/adapters/tdx/request_limits.py +59 -0
- axdata_core/adapters/tdx/request_methods.py +41 -0
- axdata_core/adapters/tdx/request_params.py +62 -0
- axdata_core/adapters/tdx/request_seams.py +41 -0
- axdata_core/adapters/tdx/resources/__init__.py +0 -0
- axdata_core/adapters/tdx/resources/finance_maps/__init__.py +0 -0
- axdata_core/adapters/tdx/resources/finance_maps/incon.dat +3702 -0
- axdata_core/adapters/tdx/resources/finance_maps/tdxhy.cfg +5620 -0
- axdata_core/adapters/tdx/resources/finance_maps/tdxzs.cfg +605 -0
- axdata_core/adapters/tdx/security_codes.py +54 -0
- axdata_core/adapters/tdx/series_history.py +57 -0
- axdata_core/adapters/tdx/server_cache.py +58 -0
- axdata_core/adapters/tdx/snapshot_normalize.py +56 -0
- axdata_core/adapters/tdx/source_adapter_registry.py +69 -0
- axdata_core/adapters/tdx/source_execution.py +44 -0
- axdata_core/adapters/tdx/source_execution_registry.py +70 -0
- axdata_core/adapters/tdx/stats_cache.py +184 -0
- axdata_core/adapters/tdx/stats_models.py +159 -0
- axdata_core/adapters/tdx/stats_resource.py +304 -0
- axdata_core/adapters/tdx/status_fetch.py +55 -0
- axdata_core/adapters/tdx/time_series_normalize.py +67 -0
- axdata_core/adapters/tdx/tqlex.py +50 -0
- axdata_core/adapters/tdx/wire_requests.py +60 -0
- axdata_core/adapters/tdx_ext/__init__.py +66 -0
- axdata_core/adapters/tdx_ext/client.py +688 -0
- axdata_core/adapters/tdx_ext/exceptions.py +51 -0
- axdata_core/adapters/tdx_ext/host_config.py +40 -0
- axdata_core/adapters/tdx_ext/interface_sets.py +65 -0
- axdata_core/adapters/tdx_ext/local_cache.py +649 -0
- axdata_core/adapters/tdx_ext/models.py +96 -0
- axdata_core/adapters/tdx_ext/options.py +36 -0
- axdata_core/adapters/tdx_ext/pool.py +153 -0
- axdata_core/adapters/tdx_ext/provider_bridge.py +35 -0
- axdata_core/adapters/tdx_ext/request.py +498 -0
- axdata_core/adapters/tdx_ext/request_execution.py +118 -0
- axdata_core/adapters/tdx_ext/request_instruments.py +221 -0
- axdata_core/adapters/tdx_ext/request_normalize.py +356 -0
- axdata_core/adapters/tdx_ext/request_params.py +157 -0
- axdata_core/adapters/tdx_ext/request_series.py +98 -0
- axdata_core/adapters/tdx_ext/server_cache.py +13 -0
- axdata_core/adapters/tdx_ext/servers.py +35 -0
- axdata_core/adapters/tdx_ext/source_adapter_registry.py +42 -0
- axdata_core/adapters/tdx_ext/source_execution.py +19 -0
- axdata_core/adapters/tdx_ext/source_execution_registry.py +43 -0
- axdata_core/adapters/tencent/__init__.py +17 -0
- axdata_core/adapters/tencent/provider_bridge.py +25 -0
- axdata_core/adapters/tencent/request.py +729 -0
- axdata_core/adapters/tencent/source_adapter_registry.py +42 -0
- axdata_core/axp.py +1843 -0
- axdata_core/builtin_providers.py +586 -0
- axdata_core/builtin_source_declarations.py +156 -0
- axdata_core/cli.py +1818 -0
- axdata_core/collector_registry.py +227 -0
- axdata_core/collector_runner.py +1016 -0
- axdata_core/collector_scheduler.py +3155 -0
- axdata_core/collector_templates.py +339 -0
- axdata_core/data_browser.py +1696 -0
- axdata_core/diagnostics.py +734 -0
- axdata_core/downloader_engine.py +1480 -0
- axdata_core/downloader_registry.py +62 -0
- axdata_core/downloaders.py +1619 -0
- axdata_core/legacy_provider_adapter.py +68 -0
- axdata_core/paths.py +51 -0
- axdata_core/plugin_config.py +272 -0
- axdata_core/plugin_status.py +419 -0
- axdata_core/plugins.py +1363 -0
- axdata_core/provider_catalog.py +462 -0
- axdata_core/provider_registry.py +833 -0
- axdata_core/quality.py +86 -0
- axdata_core/query.py +242 -0
- axdata_core/resources/__init__.py +2 -0
- axdata_core/resources/tdx_extended_servers.json +22 -0
- axdata_core/resources/tdx_quote_servers.json +49 -0
- axdata_core/sample_collectors.py +42 -0
- axdata_core/schema.py +385 -0
- axdata_core/source_adapter_factory.py +95 -0
- axdata_core/source_adapter_options.py +18 -0
- axdata_core/source_errors.py +43 -0
- axdata_core/source_execution_options.py +37 -0
- axdata_core/source_execution_registry.py +27 -0
- axdata_core/source_projection.py +21 -0
- axdata_core/source_request.py +441 -0
- axdata_core/source_session.py +355 -0
- axdata_core/sources/__init__.py +40 -0
- axdata_core/sources/base.py +112 -0
- axdata_core/sources/catalog.py +60 -0
- axdata_core/sources/cls/__init__.py +5 -0
- axdata_core/sources/cls/catalog.py +231 -0
- axdata_core/sources/cninfo/__init__.py +5 -0
- axdata_core/sources/cninfo/catalog.py +1994 -0
- axdata_core/sources/display_docs.py +139 -0
- axdata_core/sources/eastmoney/__init__.py +5 -0
- axdata_core/sources/eastmoney/catalog.py +509 -0
- axdata_core/sources/exchange/__init__.py +5 -0
- axdata_core/sources/exchange/catalog.py +361 -0
- axdata_core/sources/kph/__init__.py +5 -0
- axdata_core/sources/kph/catalog.py +255 -0
- axdata_core/sources/sina/__init__.py +5 -0
- axdata_core/sources/sina/catalog.py +2951 -0
- axdata_core/sources/tdx/__init__.py +5 -0
- axdata_core/sources/tdx/catalog.py +4546 -0
- axdata_core/sources/tdx_ext/__init__.py +5 -0
- axdata_core/sources/tdx_ext/catalog.py +1518 -0
- axdata_core/sources/tencent/__init__.py +5 -0
- axdata_core/sources/tencent/catalog.py +370 -0
- axdata_core/storage.py +186 -0
- axdata_core/tdx_f10_catalog.py +52 -0
- axdata_core/tdx_f10_models.py +50 -0
- axdata_core/tdx_f10_names.py +47 -0
- axdata_core/tdx_f10_specs.py +56 -0
- axdata_core/tdx_plugin_required.py +43 -0
- axdata_core/tdx_server_config.py +65 -0
- axdata_core/trade_calendar_cache.py +397 -0
- axdata_source_cninfo/__init__.py +15 -0
- axdata_source_cninfo/adapter.py +31 -0
- axdata_source_cninfo/axdata-provider.json +384 -0
- axdata_source_cninfo/catalog.py +209 -0
- axdata_source_cninfo/metadata.py +8 -0
- axdata_source_cninfo/provider.py +41 -0
- axdata_source_tdx/__init__.py +15 -0
- axdata_source_tdx/_tdx_wire/__init__.py +26 -0
- axdata_source_tdx/_tdx_wire/_binary.py +218 -0
- axdata_source_tdx/_tdx_wire/_code_utils.py +42 -0
- axdata_source_tdx/_tdx_wire/_command_codec.py +186 -0
- axdata_source_tdx/_tdx_wire/_command_codes.py +74 -0
- axdata_source_tdx/_tdx_wire/_command_defaults.py +15 -0
- axdata_source_tdx/_tdx_wire/_command_dispatch.py +96 -0
- axdata_source_tdx/_tdx_wire/_command_layouts.py +96 -0
- axdata_source_tdx/_tdx_wire/_command_lookup.py +25 -0
- axdata_source_tdx/_tdx_wire/_command_metadata.py +32 -0
- axdata_source_tdx/_tdx_wire/_command_registry.py +61 -0
- axdata_source_tdx/_tdx_wire/_connection_defaults.py +13 -0
- axdata_source_tdx/_tdx_wire/_host_defaults.py +51 -0
- axdata_source_tdx/_tdx_wire/_host_probe.py +81 -0
- axdata_source_tdx/_tdx_wire/_host_resource.py +70 -0
- axdata_source_tdx/_tdx_wire/_host_utils.py +31 -0
- axdata_source_tdx/_tdx_wire/_market.py +66 -0
- axdata_source_tdx/_tdx_wire/_request_defaults.py +11 -0
- axdata_source_tdx/_tdx_wire/_security_classification.py +61 -0
- axdata_source_tdx/_tdx_wire/_time_utils.py +7 -0
- axdata_source_tdx/_tdx_wire/api/__init__.py +45 -0
- axdata_source_tdx/_tdx_wire/api/auction.py +25 -0
- axdata_source_tdx/_tdx_wire/api/bars.py +31 -0
- axdata_source_tdx/_tdx_wire/api/base.py +39 -0
- axdata_source_tdx/_tdx_wire/api/codes.py +25 -0
- axdata_source_tdx/_tdx_wire/api/corporate.py +11 -0
- axdata_source_tdx/_tdx_wire/api/finance.py +10 -0
- axdata_source_tdx/_tdx_wire/api/intraday.py +38 -0
- axdata_source_tdx/_tdx_wire/api/quotes.py +78 -0
- axdata_source_tdx/_tdx_wire/api/resources.py +34 -0
- axdata_source_tdx/_tdx_wire/api/session.py +16 -0
- axdata_source_tdx/_tdx_wire/api/trades.py +34 -0
- axdata_source_tdx/_tdx_wire/client.py +470 -0
- axdata_source_tdx/_tdx_wire/exceptions.py +25 -0
- axdata_source_tdx/_tdx_wire/hosts.py +27 -0
- axdata_source_tdx/_tdx_wire/models/__init__.py +87 -0
- axdata_source_tdx/_tdx_wire/models/auction.py +58 -0
- axdata_source_tdx/_tdx_wire/models/corporate.py +59 -0
- axdata_source_tdx/_tdx_wire/models/finance.py +80 -0
- axdata_source_tdx/_tdx_wire/models/intraday.py +112 -0
- axdata_source_tdx/_tdx_wire/models/kline.py +58 -0
- axdata_source_tdx/_tdx_wire/models/quote.py +262 -0
- axdata_source_tdx/_tdx_wire/models/resource.py +18 -0
- axdata_source_tdx/_tdx_wire/models/security.py +28 -0
- axdata_source_tdx/_tdx_wire/models/session.py +30 -0
- axdata_source_tdx/_tdx_wire/models/subchart.py +39 -0
- axdata_source_tdx/_tdx_wire/models/trade.py +46 -0
- axdata_source_tdx/_tdx_wire/protocol/__init__.py +43 -0
- axdata_source_tdx/_tdx_wire/protocol/_frame_constants.py +5 -0
- axdata_source_tdx/_tdx_wire/protocol/commands/__init__.py +37 -0
- axdata_source_tdx/_tdx_wire/protocol/commands/auction.py +165 -0
- axdata_source_tdx/_tdx_wire/protocol/commands/codec.py +37 -0
- axdata_source_tdx/_tdx_wire/protocol/commands/corporate.py +163 -0
- axdata_source_tdx/_tdx_wire/protocol/commands/finance.py +180 -0
- axdata_source_tdx/_tdx_wire/protocol/commands/intraday.py +382 -0
- axdata_source_tdx/_tdx_wire/protocol/commands/klines.py +396 -0
- axdata_source_tdx/_tdx_wire/protocol/commands/lookup.py +31 -0
- axdata_source_tdx/_tdx_wire/protocol/commands/price_limits.py +110 -0
- axdata_source_tdx/_tdx_wire/protocol/commands/quotes.py +804 -0
- axdata_source_tdx/_tdx_wire/protocol/commands/registry.py +30 -0
- axdata_source_tdx/_tdx_wire/protocol/commands/resources.py +107 -0
- axdata_source_tdx/_tdx_wire/protocol/commands/security.py +163 -0
- axdata_source_tdx/_tdx_wire/protocol/commands/session.py +129 -0
- axdata_source_tdx/_tdx_wire/protocol/commands/subchart.py +182 -0
- axdata_source_tdx/_tdx_wire/protocol/commands/trades.py +275 -0
- axdata_source_tdx/_tdx_wire/protocol/constants.py +70 -0
- axdata_source_tdx/_tdx_wire/protocol/frame.py +141 -0
- axdata_source_tdx/_tdx_wire/protocol/unit.py +98 -0
- axdata_source_tdx/_tdx_wire/py.typed +1 -0
- axdata_source_tdx/_tdx_wire/transport/__init__.py +28 -0
- axdata_source_tdx/_tdx_wire/transport/base.py +21 -0
- axdata_source_tdx/_tdx_wire/transport/memory.py +42 -0
- axdata_source_tdx/_tdx_wire/transport/pool.py +148 -0
- axdata_source_tdx/_tdx_wire/transport/socket.py +373 -0
- axdata_source_tdx/adapter.py +31 -0
- axdata_source_tdx/adjustment.py +163 -0
- axdata_source_tdx/adjustment_fetch.py +148 -0
- axdata_source_tdx/auction_fetch.py +162 -0
- axdata_source_tdx/axdata-provider.json +23680 -0
- axdata_source_tdx/catalog.py +39 -0
- axdata_source_tdx/client_factory.py +61 -0
- axdata_source_tdx/code_fetch.py +579 -0
- axdata_source_tdx/codes.py +69 -0
- axdata_source_tdx/collectors.py +525 -0
- axdata_source_tdx/derived_rows.py +500 -0
- axdata_source_tdx/downloader.py +139 -0
- axdata_source_tdx/downloader_interface_sets.py +29 -0
- axdata_source_tdx/downloader_profiles.py +731 -0
- axdata_source_tdx/downloader_registry.py +85 -0
- axdata_source_tdx/execution_utils.py +141 -0
- axdata_source_tdx/f10_executor.py +414 -0
- axdata_source_tdx/f10_normalize.py +169 -0
- axdata_source_tdx/f10_params.py +221 -0
- axdata_source_tdx/f10_postprocess.py +405 -0
- axdata_source_tdx/f10_render.py +29 -0
- axdata_source_tdx/f10_request.py +365 -0
- axdata_source_tdx/finance_fetch.py +514 -0
- axdata_source_tdx/finance_maps.py +377 -0
- axdata_source_tdx/finance_normalize.py +273 -0
- axdata_source_tdx/host_config.py +51 -0
- axdata_source_tdx/interface_sets.py +180 -0
- axdata_source_tdx/intraday_fetch.py +600 -0
- axdata_source_tdx/kline_helpers.py +333 -0
- axdata_source_tdx/limit_ladder_fetch.py +614 -0
- axdata_source_tdx/limit_ladder_topics.py +280 -0
- axdata_source_tdx/metadata.py +8 -0
- axdata_source_tdx/normalize_utils.py +264 -0
- axdata_source_tdx/options.py +195 -0
- axdata_source_tdx/price_limit_calendar.py +106 -0
- axdata_source_tdx/price_limit_fetch.py +264 -0
- axdata_source_tdx/price_limit_history.py +100 -0
- axdata_source_tdx/price_limits.py +142 -0
- axdata_source_tdx/provider.py +44 -0
- axdata_source_tdx/provider_bridge.py +43 -0
- axdata_source_tdx/quote_fetch.py +485 -0
- axdata_source_tdx/quote_identity.py +50 -0
- axdata_source_tdx/rank_fetch.py +316 -0
- axdata_source_tdx/rank_params.py +181 -0
- axdata_source_tdx/realtime_refresh.py +51 -0
- axdata_source_tdx/request_adapter.py +533 -0
- axdata_source_tdx/request_adapter_runtime.py +302 -0
- axdata_source_tdx/request_client.py +98 -0
- axdata_source_tdx/request_compat.py +432 -0
- axdata_source_tdx/request_dispatch.py +138 -0
- axdata_source_tdx/request_entrypoints.py +146 -0
- axdata_source_tdx/request_filters.py +154 -0
- axdata_source_tdx/request_host_config.py +103 -0
- axdata_source_tdx/request_limits.py +15 -0
- axdata_source_tdx/request_methods.py +1910 -0
- axdata_source_tdx/request_params.py +225 -0
- axdata_source_tdx/request_seams.py +534 -0
- axdata_source_tdx/resources/__init__.py +0 -0
- axdata_source_tdx/resources/finance_maps/__init__.py +1 -0
- axdata_source_tdx/resources/finance_maps/incon.dat +3702 -0
- axdata_source_tdx/resources/finance_maps/tdxhy.cfg +5620 -0
- axdata_source_tdx/resources/finance_maps/tdxzs.cfg +605 -0
- axdata_source_tdx/resources/tdx_extended_servers.json +22 -0
- axdata_source_tdx/resources/tdx_quote_servers.json +49 -0
- axdata_source_tdx/security_codes.py +127 -0
- axdata_source_tdx/series_history.py +348 -0
- axdata_source_tdx/server_cache.py +21 -0
- axdata_source_tdx/snapshot_normalize.py +232 -0
- axdata_source_tdx/source_adapter_registry.py +42 -0
- axdata_source_tdx/source_execution.py +22 -0
- axdata_source_tdx/source_execution_registry.py +43 -0
- axdata_source_tdx/stats_cache.py +150 -0
- axdata_source_tdx/stats_models.py +161 -0
- axdata_source_tdx/stats_resource.py +242 -0
- axdata_source_tdx/status_fetch.py +209 -0
- axdata_source_tdx/tdx_f10_catalog.py +957 -0
- axdata_source_tdx/tdx_f10_models.py +47 -0
- axdata_source_tdx/tdx_f10_names.py +48 -0
- axdata_source_tdx/tdx_f10_specs.py +1142 -0
- axdata_source_tdx/tdx_server_config.py +389 -0
- axdata_source_tdx/time_series_normalize.py +370 -0
- axdata_source_tdx/tqlex.py +138 -0
- axdata_source_tdx/wire.py +32 -0
- axdata_source_tdx/wire_requests.py +190 -0
- axdata_source_tdx_ext/__init__.py +15 -0
- axdata_source_tdx_ext/adapter.py +31 -0
- axdata_source_tdx_ext/axdata-provider.json +9424 -0
- axdata_source_tdx_ext/catalog.py +33 -0
- axdata_source_tdx_ext/metadata.py +8 -0
- axdata_source_tdx_ext/provider.py +44 -0
- axdata_source_tencent/__init__.py +15 -0
- axdata_source_tencent/adapter.py +31 -0
- axdata_source_tencent/axdata-provider.json +291 -0
- axdata_source_tencent/catalog.py +190 -0
- axdata_source_tencent/metadata.py +8 -0
- axdata_source_tencent/provider.py +41 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Compatibility wrapper for TDX F10 interface name constants."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from importlib import import_module
|
|
6
|
+
from types import ModuleType
|
|
7
|
+
from typing import Any as _Any
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
_EXPORTS = (
|
|
11
|
+
'F10_INTERFACE_NAMES',
|
|
12
|
+
)
|
|
13
|
+
__all__ = list(_EXPORTS)
|
|
14
|
+
_IMPLEMENTATION: ModuleType | None = None
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _provider_package_tdx_f10_names() -> ModuleType | None:
|
|
18
|
+
try:
|
|
19
|
+
return import_module("axdata_source_tdx.tdx_f10_names")
|
|
20
|
+
except ModuleNotFoundError as exc:
|
|
21
|
+
if exc.name in {'axdata_source_tdx', 'axdata_source_tdx.tdx_f10_names'}:
|
|
22
|
+
return None
|
|
23
|
+
raise
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _fallback_tdx_f10_names() -> ModuleType:
|
|
27
|
+
from axdata_core.tdx_plugin_required import raise_tdx_plugin_required
|
|
28
|
+
|
|
29
|
+
raise_tdx_plugin_required()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _implementation() -> ModuleType:
|
|
33
|
+
global _IMPLEMENTATION
|
|
34
|
+
if _IMPLEMENTATION is None:
|
|
35
|
+
implementation = _provider_package_tdx_f10_names()
|
|
36
|
+
_IMPLEMENTATION = implementation if implementation is not None else _fallback_tdx_f10_names()
|
|
37
|
+
return _IMPLEMENTATION
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def __getattr__(name: str) -> _Any:
|
|
41
|
+
try:
|
|
42
|
+
value = getattr(_implementation(), name)
|
|
43
|
+
except AttributeError as exc:
|
|
44
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}") from exc
|
|
45
|
+
if name in _EXPORTS:
|
|
46
|
+
globals()[name] = value
|
|
47
|
+
return value
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Compatibility wrapper for TDX 7615 F10 interface specifications."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from importlib import import_module
|
|
6
|
+
from types import ModuleType
|
|
7
|
+
from typing import Any as _Any
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
_EXPORTS = (
|
|
11
|
+
'Any',
|
|
12
|
+
'F10FieldSpec',
|
|
13
|
+
'F10InterfaceSpec',
|
|
14
|
+
'F10ParamSpec',
|
|
15
|
+
'COMMON_STOCK_PARAMS',
|
|
16
|
+
'COUNT_PARAM',
|
|
17
|
+
'CURSOR_PARAM',
|
|
18
|
+
'field',
|
|
19
|
+
'param',
|
|
20
|
+
'F10_INTERFACE_SPECS',
|
|
21
|
+
)
|
|
22
|
+
__all__ = list(_EXPORTS)
|
|
23
|
+
_IMPLEMENTATION: ModuleType | None = None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _provider_package_tdx_f10_specs() -> ModuleType | None:
|
|
27
|
+
try:
|
|
28
|
+
return import_module("axdata_source_tdx.tdx_f10_specs")
|
|
29
|
+
except ModuleNotFoundError as exc:
|
|
30
|
+
if exc.name in {'axdata_source_tdx', 'axdata_source_tdx.tdx_f10_specs'}:
|
|
31
|
+
return None
|
|
32
|
+
raise
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _fallback_tdx_f10_specs() -> ModuleType:
|
|
36
|
+
from axdata_core.tdx_plugin_required import raise_tdx_plugin_required
|
|
37
|
+
|
|
38
|
+
raise_tdx_plugin_required()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _implementation() -> ModuleType:
|
|
42
|
+
global _IMPLEMENTATION
|
|
43
|
+
if _IMPLEMENTATION is None:
|
|
44
|
+
implementation = _provider_package_tdx_f10_specs()
|
|
45
|
+
_IMPLEMENTATION = implementation if implementation is not None else _fallback_tdx_f10_specs()
|
|
46
|
+
return _IMPLEMENTATION
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def __getattr__(name: str) -> _Any:
|
|
50
|
+
try:
|
|
51
|
+
value = getattr(_implementation(), name)
|
|
52
|
+
except AttributeError as exc:
|
|
53
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}") from exc
|
|
54
|
+
if name in _EXPORTS:
|
|
55
|
+
globals()[name] = value
|
|
56
|
+
return value
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Shared TDX plugin availability helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from importlib import import_module
|
|
6
|
+
from types import ModuleType
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from .source_errors import SourceUnavailableError
|
|
10
|
+
|
|
11
|
+
TDX_PLUGIN_REQUIRED_MESSAGE = "TDX 插件未安装或不可用,请安装并启用 TDX 插件。"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def raise_tdx_plugin_required() -> None:
|
|
15
|
+
raise SourceUnavailableError(TDX_PLUGIN_REQUIRED_MESSAGE)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def missing_tdx_provider_module(exc: ModuleNotFoundError, module_name: str) -> bool:
|
|
19
|
+
"""Return whether a ModuleNotFoundError means the TDX plugin is missing."""
|
|
20
|
+
|
|
21
|
+
missing = str(exc.name or "")
|
|
22
|
+
root = module_name.split(".", 1)[0]
|
|
23
|
+
return missing == root or missing == module_name or missing.startswith(f"{root}.")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def load_tdx_provider_module(module_name: str) -> ModuleType:
|
|
27
|
+
"""Load one module from the TDX plugin or raise the shared availability error."""
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
return import_module(module_name)
|
|
31
|
+
except ModuleNotFoundError as exc:
|
|
32
|
+
if missing_tdx_provider_module(exc, module_name):
|
|
33
|
+
raise SourceUnavailableError(TDX_PLUGIN_REQUIRED_MESSAGE) from exc
|
|
34
|
+
raise
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def empty_downloader_profile_declarations(
|
|
38
|
+
concurrency_profile_cls: type[Any],
|
|
39
|
+
downloader_profile_cls: type[Any],
|
|
40
|
+
) -> dict[str, Any]:
|
|
41
|
+
"""Downloader declarations shape used when the TDX plugin is absent."""
|
|
42
|
+
|
|
43
|
+
return {}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""Compatibility wrapper for TDX server list configuration helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from importlib import import_module
|
|
6
|
+
from types import ModuleType
|
|
7
|
+
from typing import Any as _Any
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
_EXPORTS = (
|
|
11
|
+
"LOCAL_TIMEZONE",
|
|
12
|
+
"SERVER_KIND_VALUES",
|
|
13
|
+
"DEFAULT_CACHE_DIR",
|
|
14
|
+
"QUOTE_RESOURCE",
|
|
15
|
+
"EXT_RESOURCE",
|
|
16
|
+
"ServerKind",
|
|
17
|
+
"TdxServerEntry",
|
|
18
|
+
"TdxServerProbeResult",
|
|
19
|
+
"default_server_cache_root",
|
|
20
|
+
"data_root_server_cache_root",
|
|
21
|
+
"built_in_servers",
|
|
22
|
+
"effective_servers",
|
|
23
|
+
"effective_host_strings",
|
|
24
|
+
"read_user_servers",
|
|
25
|
+
"write_user_servers",
|
|
26
|
+
"reset_user_servers",
|
|
27
|
+
"probe_server",
|
|
28
|
+
"probe_servers",
|
|
29
|
+
"server_status",
|
|
30
|
+
)
|
|
31
|
+
__all__ = list(_EXPORTS)
|
|
32
|
+
_IMPLEMENTATION: ModuleType | None = None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _provider_package_tdx_server_config() -> ModuleType | None:
|
|
36
|
+
try:
|
|
37
|
+
return import_module("axdata_source_tdx.tdx_server_config")
|
|
38
|
+
except ModuleNotFoundError as exc:
|
|
39
|
+
if exc.name in {"axdata_source_tdx", "axdata_source_tdx.tdx_server_config"}:
|
|
40
|
+
return None
|
|
41
|
+
raise
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _fallback_tdx_server_config() -> ModuleType:
|
|
45
|
+
from axdata_core.tdx_plugin_required import raise_tdx_plugin_required
|
|
46
|
+
|
|
47
|
+
raise_tdx_plugin_required()
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _implementation() -> ModuleType:
|
|
51
|
+
global _IMPLEMENTATION
|
|
52
|
+
if _IMPLEMENTATION is None:
|
|
53
|
+
implementation = _provider_package_tdx_server_config()
|
|
54
|
+
_IMPLEMENTATION = implementation if implementation is not None else _fallback_tdx_server_config()
|
|
55
|
+
return _IMPLEMENTATION
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def __getattr__(name: str) -> _Any:
|
|
59
|
+
try:
|
|
60
|
+
value = getattr(_implementation(), name)
|
|
61
|
+
except AttributeError as exc:
|
|
62
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}") from exc
|
|
63
|
+
if name in _EXPORTS:
|
|
64
|
+
globals()[name] = value
|
|
65
|
+
return value
|
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
"""Local cache for official exchange trading calendars."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from datetime import date, datetime, timedelta, timezone
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any, Iterable
|
|
9
|
+
|
|
10
|
+
import pandas as pd
|
|
11
|
+
|
|
12
|
+
from .source_request import request_interface
|
|
13
|
+
|
|
14
|
+
DEFAULT_PAST_DAYS = 180
|
|
15
|
+
DEFAULT_FUTURE_DAYS = 180
|
|
16
|
+
DEFAULT_RECHECK_PAST_DAYS = 30
|
|
17
|
+
DEFAULT_EXCHANGE = "SZSE"
|
|
18
|
+
CALENDAR_COLUMNS = ("exchange", "cal_date", "is_open", "pretrade_date", "next_trade_date")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class TradeCalendarCacheError(RuntimeError):
|
|
22
|
+
"""Raised when the trading calendar cache cannot be used."""
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True)
|
|
26
|
+
class CalendarCoverage:
|
|
27
|
+
start_date: str | None
|
|
28
|
+
end_date: str | None
|
|
29
|
+
row_count: int
|
|
30
|
+
open_count: int
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def is_available(self) -> bool:
|
|
34
|
+
return self.row_count > 0 and self.start_date is not None and self.end_date is not None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def trade_calendar_cache_path(data_root: str | Path) -> Path:
|
|
38
|
+
"""Return the default local calendar cache file path."""
|
|
39
|
+
|
|
40
|
+
return Path(data_root).expanduser().resolve() / "cache" / "exchange" / "trade_calendar" / "trade_calendar.parquet"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def get_trade_calendar_cache_status(data_root: str | Path, *, today: date | None = None) -> dict[str, Any]:
|
|
44
|
+
"""Return local cache status without requesting upstream."""
|
|
45
|
+
|
|
46
|
+
path = trade_calendar_cache_path(data_root)
|
|
47
|
+
frame = _read_cache(path)
|
|
48
|
+
coverage = _coverage(frame)
|
|
49
|
+
today_text = _format_date(today or _local_today())
|
|
50
|
+
today_row = _row_for_date(frame, today_text)
|
|
51
|
+
return {
|
|
52
|
+
"path": str(path),
|
|
53
|
+
"exists": path.exists(),
|
|
54
|
+
"row_count": coverage.row_count,
|
|
55
|
+
"open_count": coverage.open_count,
|
|
56
|
+
"start_date": coverage.start_date,
|
|
57
|
+
"end_date": coverage.end_date,
|
|
58
|
+
"today": today_text,
|
|
59
|
+
"today_is_open": _bool_or_none(today_row.get("is_open") if today_row else None),
|
|
60
|
+
"today_pretrade_date": today_row.get("pretrade_date") if today_row else None,
|
|
61
|
+
"today_next_trade_date": today_row.get("next_trade_date") if today_row else None,
|
|
62
|
+
"covers_today": today_row is not None,
|
|
63
|
+
"updated_at": _file_updated_at(path),
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def refresh_trade_calendar_cache(
|
|
68
|
+
data_root: str | Path,
|
|
69
|
+
*,
|
|
70
|
+
start_date: str | date | None = None,
|
|
71
|
+
end_date: str | date | None = None,
|
|
72
|
+
past_days: int = DEFAULT_PAST_DAYS,
|
|
73
|
+
future_days: int = DEFAULT_FUTURE_DAYS,
|
|
74
|
+
recheck_past_days: int = DEFAULT_RECHECK_PAST_DAYS,
|
|
75
|
+
today: date | None = None,
|
|
76
|
+
) -> dict[str, Any]:
|
|
77
|
+
"""Refresh local cache for the requested range and return status metadata."""
|
|
78
|
+
|
|
79
|
+
today_value = today or _local_today()
|
|
80
|
+
requested_start, requested_end = _resolve_refresh_range(
|
|
81
|
+
start_date=start_date,
|
|
82
|
+
end_date=end_date,
|
|
83
|
+
past_days=past_days,
|
|
84
|
+
future_days=future_days,
|
|
85
|
+
today=today_value,
|
|
86
|
+
)
|
|
87
|
+
if requested_start > requested_end:
|
|
88
|
+
raise TradeCalendarCacheError("start_date must be before or equal to end_date")
|
|
89
|
+
|
|
90
|
+
cache_path = trade_calendar_cache_path(data_root)
|
|
91
|
+
existing = _read_cache(cache_path)
|
|
92
|
+
coverage = _coverage(existing)
|
|
93
|
+
fetch_ranges = _missing_ranges(
|
|
94
|
+
requested_start,
|
|
95
|
+
requested_end,
|
|
96
|
+
coverage,
|
|
97
|
+
recheck_start=max(requested_start, today_value - timedelta(days=max(0, recheck_past_days))),
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
fetched_frames: list[pd.DataFrame] = []
|
|
101
|
+
fetched_ranges: list[dict[str, str]] = []
|
|
102
|
+
for fetch_start, fetch_end in fetch_ranges:
|
|
103
|
+
records = request_interface(
|
|
104
|
+
"stock_trade_calendar_exchange",
|
|
105
|
+
params={"start_date": _format_date(fetch_start), "end_date": _format_date(fetch_end)},
|
|
106
|
+
fields=list(CALENDAR_COLUMNS[1:]),
|
|
107
|
+
persist=False,
|
|
108
|
+
).records
|
|
109
|
+
fetched_ranges.append({"start_date": _format_date(fetch_start), "end_date": _format_date(fetch_end)})
|
|
110
|
+
if records:
|
|
111
|
+
fetched_frames.append(_records_to_frame(records, exchange=DEFAULT_EXCHANGE))
|
|
112
|
+
|
|
113
|
+
merged = _merge_frames([existing, *fetched_frames])
|
|
114
|
+
_write_cache(cache_path, merged)
|
|
115
|
+
status = get_trade_calendar_cache_status(data_root, today=today_value)
|
|
116
|
+
status.update(
|
|
117
|
+
{
|
|
118
|
+
"requested_start_date": _format_date(requested_start),
|
|
119
|
+
"requested_end_date": _format_date(requested_end),
|
|
120
|
+
"fetched_ranges": fetched_ranges,
|
|
121
|
+
"fetched_row_count": int(sum(len(frame) for frame in fetched_frames)),
|
|
122
|
+
}
|
|
123
|
+
)
|
|
124
|
+
return status
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def ensure_trade_calendar_cache(
|
|
128
|
+
data_root: str | Path,
|
|
129
|
+
*,
|
|
130
|
+
past_days: int = DEFAULT_PAST_DAYS,
|
|
131
|
+
future_days: int = DEFAULT_FUTURE_DAYS,
|
|
132
|
+
monthly_recheck_past_days: int = DEFAULT_RECHECK_PAST_DAYS,
|
|
133
|
+
today: date | None = None,
|
|
134
|
+
) -> dict[str, Any]:
|
|
135
|
+
"""Ensure the default rolling range exists, with monthly recent rechecks."""
|
|
136
|
+
|
|
137
|
+
today_value = today or _local_today()
|
|
138
|
+
recheck_past_days = monthly_recheck_past_days if today_value.day == 1 else 0
|
|
139
|
+
status = refresh_trade_calendar_cache(
|
|
140
|
+
data_root,
|
|
141
|
+
past_days=past_days,
|
|
142
|
+
future_days=future_days,
|
|
143
|
+
recheck_past_days=recheck_past_days,
|
|
144
|
+
today=today_value,
|
|
145
|
+
)
|
|
146
|
+
status["maintenance_mode"] = "monthly_recheck" if today_value.day == 1 else "startup_gap_check"
|
|
147
|
+
return status
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def check_trade_calendar_cache(
|
|
151
|
+
data_root: str | Path,
|
|
152
|
+
*,
|
|
153
|
+
start_date: str | date,
|
|
154
|
+
end_date: str | date | None = None,
|
|
155
|
+
) -> dict[str, Any]:
|
|
156
|
+
"""Check whether local cache covers a requested date range."""
|
|
157
|
+
|
|
158
|
+
start = _parse_date(start_date, "start_date")
|
|
159
|
+
end = _parse_date(end_date if end_date is not None else start_date, "end_date")
|
|
160
|
+
if start > end:
|
|
161
|
+
raise TradeCalendarCacheError("start_date must be before or equal to end_date")
|
|
162
|
+
|
|
163
|
+
path = trade_calendar_cache_path(data_root)
|
|
164
|
+
frame = _read_cache(path)
|
|
165
|
+
coverage = _coverage(frame)
|
|
166
|
+
start_text = _format_date(start)
|
|
167
|
+
end_text = _format_date(end)
|
|
168
|
+
missing_dates = _missing_dates(frame, start, end)
|
|
169
|
+
return {
|
|
170
|
+
"path": str(path),
|
|
171
|
+
"exists": path.exists(),
|
|
172
|
+
"start_date": start_text,
|
|
173
|
+
"end_date": end_text,
|
|
174
|
+
"is_available": not missing_dates,
|
|
175
|
+
"missing_count": len(missing_dates),
|
|
176
|
+
"missing_dates": missing_dates[:20],
|
|
177
|
+
"cache_start_date": coverage.start_date,
|
|
178
|
+
"cache_end_date": coverage.end_date,
|
|
179
|
+
"row_count": coverage.row_count,
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def update_trade_calendar_cache_from_records(
|
|
184
|
+
data_root: str | Path,
|
|
185
|
+
records: Iterable[dict[str, Any]],
|
|
186
|
+
*,
|
|
187
|
+
exchange: str = DEFAULT_EXCHANGE,
|
|
188
|
+
today: date | None = None,
|
|
189
|
+
) -> dict[str, Any]:
|
|
190
|
+
"""Merge already-fetched trading calendar rows into the local cache."""
|
|
191
|
+
|
|
192
|
+
cache_path = trade_calendar_cache_path(data_root)
|
|
193
|
+
existing = _read_cache(cache_path)
|
|
194
|
+
frame = _records_to_frame([dict(record) for record in records], exchange=exchange)
|
|
195
|
+
merged = _merge_frames([existing, frame])
|
|
196
|
+
_write_cache(cache_path, merged)
|
|
197
|
+
status = get_trade_calendar_cache_status(data_root, today=today)
|
|
198
|
+
status.update({"merged_row_count": int(len(frame))})
|
|
199
|
+
return status
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def _resolve_refresh_range(
|
|
203
|
+
*,
|
|
204
|
+
start_date: str | date | None,
|
|
205
|
+
end_date: str | date | None,
|
|
206
|
+
past_days: int,
|
|
207
|
+
future_days: int,
|
|
208
|
+
today: date,
|
|
209
|
+
) -> tuple[date, date]:
|
|
210
|
+
if start_date is not None or end_date is not None:
|
|
211
|
+
start = _parse_date(start_date if start_date is not None else end_date, "start_date")
|
|
212
|
+
end = _parse_date(end_date if end_date is not None else start_date, "end_date")
|
|
213
|
+
return start, end
|
|
214
|
+
return today - timedelta(days=max(0, past_days)), today + timedelta(days=max(0, future_days))
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def _missing_ranges(
|
|
218
|
+
start: date,
|
|
219
|
+
end: date,
|
|
220
|
+
coverage: CalendarCoverage,
|
|
221
|
+
*,
|
|
222
|
+
recheck_start: date,
|
|
223
|
+
) -> list[tuple[date, date]]:
|
|
224
|
+
if not coverage.is_available:
|
|
225
|
+
return [(start, end)]
|
|
226
|
+
|
|
227
|
+
ranges: list[tuple[date, date]] = []
|
|
228
|
+
cache_start = _parse_date(coverage.start_date, "cache_start_date")
|
|
229
|
+
cache_end = _parse_date(coverage.end_date, "cache_end_date")
|
|
230
|
+
if start < cache_start:
|
|
231
|
+
ranges.append((start, min(end, cache_start - timedelta(days=1))))
|
|
232
|
+
if end > cache_end:
|
|
233
|
+
ranges.append((max(start, cache_end + timedelta(days=1)), end))
|
|
234
|
+
refresh_start = max(start, recheck_start)
|
|
235
|
+
refresh_end = min(end, cache_end)
|
|
236
|
+
if refresh_start <= refresh_end:
|
|
237
|
+
ranges.append((refresh_start, refresh_end))
|
|
238
|
+
return _merge_ranges(ranges)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def _merge_ranges(ranges: Iterable[tuple[date, date]]) -> list[tuple[date, date]]:
|
|
242
|
+
normalized = sorted((start, end) for start, end in ranges if start <= end)
|
|
243
|
+
merged: list[tuple[date, date]] = []
|
|
244
|
+
for start, end in normalized:
|
|
245
|
+
if not merged or start > merged[-1][1] + timedelta(days=1):
|
|
246
|
+
merged.append((start, end))
|
|
247
|
+
else:
|
|
248
|
+
merged[-1] = (merged[-1][0], max(merged[-1][1], end))
|
|
249
|
+
return merged
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def _read_cache(path: Path) -> pd.DataFrame:
|
|
253
|
+
if not path.exists():
|
|
254
|
+
return pd.DataFrame(columns=CALENDAR_COLUMNS)
|
|
255
|
+
frame = pd.read_parquet(path)
|
|
256
|
+
return _normalize_frame(frame)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def _write_cache(path: Path, frame: pd.DataFrame) -> None:
|
|
260
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
261
|
+
_normalize_frame(frame).to_parquet(path, engine="pyarrow", index=False)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def _records_to_frame(records: list[dict[str, Any]], *, exchange: str) -> pd.DataFrame:
|
|
265
|
+
rows = []
|
|
266
|
+
for record in records:
|
|
267
|
+
rows.append(
|
|
268
|
+
{
|
|
269
|
+
"exchange": exchange,
|
|
270
|
+
"cal_date": _normalize_date_text(record.get("cal_date")),
|
|
271
|
+
"is_open": bool(record.get("is_open")),
|
|
272
|
+
"pretrade_date": _normalize_date_text(record.get("pretrade_date")),
|
|
273
|
+
"next_trade_date": _normalize_date_text(record.get("next_trade_date")),
|
|
274
|
+
}
|
|
275
|
+
)
|
|
276
|
+
return _normalize_frame(pd.DataFrame.from_records(rows))
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def _merge_frames(frames: list[pd.DataFrame]) -> pd.DataFrame:
|
|
280
|
+
non_empty = [_normalize_frame(frame) for frame in frames if frame is not None and not frame.empty]
|
|
281
|
+
if not non_empty:
|
|
282
|
+
return pd.DataFrame(columns=CALENDAR_COLUMNS)
|
|
283
|
+
merged = pd.concat(non_empty, ignore_index=True)
|
|
284
|
+
merged = _normalize_frame(merged)
|
|
285
|
+
return merged.drop_duplicates(subset=["exchange", "cal_date"], keep="last").sort_values(["exchange", "cal_date"])
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def _normalize_frame(frame: pd.DataFrame) -> pd.DataFrame:
|
|
289
|
+
if frame.empty:
|
|
290
|
+
return pd.DataFrame(columns=CALENDAR_COLUMNS)
|
|
291
|
+
normalized = frame.copy()
|
|
292
|
+
for column in CALENDAR_COLUMNS:
|
|
293
|
+
if column not in normalized.columns:
|
|
294
|
+
normalized[column] = None
|
|
295
|
+
normalized = normalized.loc[:, list(CALENDAR_COLUMNS)]
|
|
296
|
+
normalized["exchange"] = normalized["exchange"].fillna(DEFAULT_EXCHANGE).astype(str)
|
|
297
|
+
normalized["cal_date"] = normalized["cal_date"].map(_normalize_date_text)
|
|
298
|
+
normalized["pretrade_date"] = normalized["pretrade_date"].map(_normalize_date_text)
|
|
299
|
+
normalized["next_trade_date"] = normalized["next_trade_date"].map(_normalize_date_text)
|
|
300
|
+
normalized["is_open"] = normalized["is_open"].map(_normalize_bool)
|
|
301
|
+
normalized = normalized[normalized["cal_date"].notna()]
|
|
302
|
+
return normalized
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def _coverage(frame: pd.DataFrame) -> CalendarCoverage:
|
|
306
|
+
if frame.empty:
|
|
307
|
+
return CalendarCoverage(start_date=None, end_date=None, row_count=0, open_count=0)
|
|
308
|
+
dates = sorted(str(item) for item in frame["cal_date"].dropna().unique())
|
|
309
|
+
return CalendarCoverage(
|
|
310
|
+
start_date=dates[0] if dates else None,
|
|
311
|
+
end_date=dates[-1] if dates else None,
|
|
312
|
+
row_count=len(frame),
|
|
313
|
+
open_count=int(frame["is_open"].sum()) if "is_open" in frame.columns else 0,
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def _row_for_date(frame: pd.DataFrame, cal_date: str) -> dict[str, Any] | None:
|
|
318
|
+
if frame.empty:
|
|
319
|
+
return None
|
|
320
|
+
matches = frame[frame["cal_date"] == cal_date]
|
|
321
|
+
if matches.empty:
|
|
322
|
+
return None
|
|
323
|
+
return matches.iloc[-1].to_dict()
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def _missing_dates(frame: pd.DataFrame, start: date, end: date) -> list[str]:
|
|
327
|
+
available = set(str(item) for item in frame["cal_date"].dropna()) if not frame.empty else set()
|
|
328
|
+
missing: list[str] = []
|
|
329
|
+
current = start
|
|
330
|
+
while current <= end:
|
|
331
|
+
text = _format_date(current)
|
|
332
|
+
if text not in available:
|
|
333
|
+
missing.append(text)
|
|
334
|
+
current += timedelta(days=1)
|
|
335
|
+
return missing
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def _covers(coverage: CalendarCoverage, start_date: str, end_date: str) -> bool:
|
|
339
|
+
return bool(
|
|
340
|
+
coverage.start_date
|
|
341
|
+
and coverage.end_date
|
|
342
|
+
and coverage.start_date <= start_date
|
|
343
|
+
and coverage.end_date >= end_date
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def _parse_date(value: str | date | None, field_name: str) -> date:
|
|
348
|
+
if isinstance(value, date):
|
|
349
|
+
return value
|
|
350
|
+
text = str(value or "").strip()
|
|
351
|
+
digits = "".join(ch for ch in text if ch.isdigit())
|
|
352
|
+
if len(digits) != 8:
|
|
353
|
+
raise TradeCalendarCacheError(f"{field_name} must be YYYYMMDD or YYYY-MM-DD")
|
|
354
|
+
try:
|
|
355
|
+
return date(int(digits[:4]), int(digits[4:6]), int(digits[6:8]))
|
|
356
|
+
except ValueError as exc:
|
|
357
|
+
raise TradeCalendarCacheError(f"{field_name} is not a valid date") from exc
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def _format_date(value: date) -> str:
|
|
361
|
+
return value.strftime("%Y%m%d")
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
def _normalize_date_text(value: Any) -> str | None:
|
|
365
|
+
if value in (None, ""):
|
|
366
|
+
return None
|
|
367
|
+
digits = "".join(ch for ch in str(value) if ch.isdigit())
|
|
368
|
+
return digits[:8] if len(digits) >= 8 else None
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def _local_today() -> date:
|
|
372
|
+
return datetime.now().astimezone().date()
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def _file_updated_at(path: Path) -> str | None:
|
|
376
|
+
if not path.exists():
|
|
377
|
+
return None
|
|
378
|
+
return datetime.fromtimestamp(path.stat().st_mtime, tz=timezone.utc).isoformat()
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def _bool_or_none(value: Any) -> bool | None:
|
|
382
|
+
if value is None:
|
|
383
|
+
return None
|
|
384
|
+
return bool(value)
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def _normalize_bool(value: Any) -> bool:
|
|
388
|
+
if isinstance(value, bool):
|
|
389
|
+
return value
|
|
390
|
+
if value is None:
|
|
391
|
+
return False
|
|
392
|
+
text = str(value).strip().lower()
|
|
393
|
+
if text in {"1", "true", "yes", "y", "open"}:
|
|
394
|
+
return True
|
|
395
|
+
if text in {"0", "false", "no", "n", "closed", ""}:
|
|
396
|
+
return False
|
|
397
|
+
return bool(value)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Cninfo Provider package for AxData."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
__all__ = ["CninfoProvider", "provider"]
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def __getattr__(name: str) -> Any:
|
|
11
|
+
if name in __all__:
|
|
12
|
+
from .provider import CninfoProvider, provider
|
|
13
|
+
|
|
14
|
+
return {"CninfoProvider": CninfoProvider, "provider": provider}[name]
|
|
15
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Cninfo Provider adapter."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Mapping
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
from axdata_core.legacy_provider_adapter import LegacyProviderAdapter
|
|
9
|
+
|
|
10
|
+
from .metadata import PROVIDER_ID, SOURCE_CODE
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from axdata_core.adapters.cninfo import CninfoRequestAdapter
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class CninfoProviderAdapter(LegacyProviderAdapter):
|
|
17
|
+
"""Adapter that exposes Cninfo announcement interfaces through the Provider API."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, options: Mapping[str, object] | None = None) -> None:
|
|
20
|
+
super().__init__(
|
|
21
|
+
source=SOURCE_CODE,
|
|
22
|
+
provider_id=PROVIDER_ID,
|
|
23
|
+
create_adapter=lambda call_options: _create_legacy_adapter(call_options),
|
|
24
|
+
options=options,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _create_legacy_adapter(options: Mapping[str, object]) -> CninfoRequestAdapter:
|
|
29
|
+
from axdata_core.adapters.cninfo.provider_bridge import create_cninfo_request_adapter
|
|
30
|
+
|
|
31
|
+
return create_cninfo_request_adapter(options)
|