cfquant 0.2.66__tar.gz → 0.2.68__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.
- {cfquant-0.2.66 → cfquant-0.2.68}/MANIFEST.in +5 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/PKG-INFO +3 -1
- {cfquant-0.2.66 → cfquant-0.2.68}/README.md +1 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/__init__.py +2 -1
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/batch_orders.py +4 -1
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/client.py +2 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/monitor_notifications.py +3 -30
- cfquant-0.2.68/cfquant/notify.py +52 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/order_identity.py +27 -1
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/order_meta.py +128 -3
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/qmt_bridge.py +105 -30
- cfquant-0.2.68/cfquant/qmt_mac_agent.js +87 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/qmt_python_runtime.py +76 -29
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/qmt_update.py +17 -4
- cfquant-0.2.68/cfquant/qmt_vm_agent.js +109 -0
- cfquant-0.2.68/cfquant/qmt_vm_discovery.py +178 -0
- cfquant-0.2.68/cfquant/qmt_vm_runtime.py +105 -0
- cfquant-0.2.68/cfquant/qmt_vm_settings.py +69 -0
- cfquant-0.2.68/cfquant/qmt_vm_worker.py +164 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/quote_service.py +11 -5
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/12_/350/256/242/345/215/225/345/244/207/346/263/250/346/265/213/350/257/225.py +6 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/20_/350/201/224/350/260/203/351/227/256/351/242/230/344/277/256/345/244/215/346/265/213/350/257/225.py +33 -5
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/26_/345/205/250/346/216/250/350/256/242/351/230/205/346/265/213/350/257/225.py +8 -4
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/27_/345/205/250/346/216/250/350/256/242/351/230/205/351/241/265/351/235/242/346/265/213/350/257/225.py +105 -1
- cfquant-0.2.68/cfquant/tests/qmt_vm_native_fixture.js +118 -0
- cfquant-0.2.68/cfquant/tests/test_auto_login_app.py +363 -0
- cfquant-0.2.68/cfquant/tests/test_binding_python_runtime_browser.py +131 -0
- cfquant-0.2.68/cfquant/tests/test_binding_vm_browser.py +90 -0
- cfquant-0.2.68/cfquant/tests/test_binding_webhook_browser.py +106 -0
- cfquant-0.2.68/cfquant/tests/test_history_download_memory.py +68 -0
- cfquant-0.2.68/cfquant/tests/test_notify.py +214 -0
- cfquant-0.2.68/cfquant/tests/test_qmt_python_runtime.py +309 -0
- cfquant-0.2.68/cfquant/tests/test_qmt_vm_discovery.py +161 -0
- cfquant-0.2.68/cfquant/tests/test_qmt_vm_native.py +41 -0
- cfquant-0.2.68/cfquant/tests/test_qmt_vm_runtime.py +167 -0
- cfquant-0.2.68/cfquant/tests/test_qmt_vm_settings.py +86 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_quote_service.py +153 -0
- cfquant-0.2.68/cfquant/tests/test_split_order_identity.py +263 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_trading_monitor.py +57 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_trading_monitor_browser.py +204 -0
- cfquant-0.2.68/cfquant/tests/test_version_check.py +75 -0
- cfquant-0.2.68/cfquant/tests/test_version_check_browser.py +128 -0
- cfquant-0.2.68/cfquant/tools/THIRD_PARTY.md +16 -0
- cfquant-0.2.68/cfquant/tools/upx/COPYING +339 -0
- cfquant-0.2.68/cfquant/tools/upx/LICENSE +138 -0
- cfquant-0.2.68/cfquant/tools/upx/NEWS +603 -0
- cfquant-0.2.68/cfquant/tools/upx/README +117 -0
- cfquant-0.2.68/cfquant/tools/upx/THANKS.txt +74 -0
- cfquant-0.2.68/cfquant/tools/upx/upx-doc.html +845 -0
- cfquant-0.2.68/cfquant/tools/upx/upx-doc.txt +893 -0
- cfquant-0.2.68/cfquant/tools/upx/upx.1 +1015 -0
- cfquant-0.2.68/cfquant/tools/upx/upx.exe +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/trading_monitor.py +16 -4
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tx_trade_bridge.py +122 -76
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/version.py +2 -2
- cfquant-0.2.68/cfquant/webhook.py +151 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/xtdata.py +2 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant.egg-info/PKG-INFO +3 -1
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant.egg-info/SOURCES.txt +32 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant.egg-info/requires.txt +1 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant_web_server.py +288 -24
- {cfquant-0.2.66 → cfquant-0.2.68}/pyproject.toml +4 -3
- {cfquant-0.2.66 → cfquant-0.2.68}/qmt_scripts/CFQUANT_LITE.py +148 -36
- {cfquant-0.2.66 → cfquant-0.2.68}/qmt_scripts//345/220/214/350/264/246/345/217/267/347/213/254/347/253/213/345/270/202/345/234/272/CFQUANT_LITE_SH.py +148 -36
- {cfquant-0.2.66 → cfquant-0.2.68}/qmt_scripts//345/220/214/350/264/246/345/217/267/347/213/254/347/253/213/345/270/202/345/234/272/CFQUANT_LITE_SZ.py +148 -36
- {cfquant-0.2.66 → cfquant-0.2.68}/requirements.txt +1 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/api-tester.js +1 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/app.js +557 -83
- {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/index.html +76 -5
- cfquant-0.2.68/web_dashboard/notify-api-data.js +38 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/python-api.js +4 -4
- {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/styles.css +26 -1
- {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/trading-monitor.js +191 -31
- cfquant-0.2.66/cfquant/tests/test_auto_login_app.py +0 -166
- cfquant-0.2.66/cfquant/tests/test_qmt_python_runtime.py +0 -133
- {cfquant-0.2.66 → cfquant-0.2.68}/LICENSE +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/LTtx/__init__.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/LTtx/tx/Config.txt +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/LTtx/tx/LTtx_server.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/LTtx/tx/__init__.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/LTtx/tx/tx.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/_editable_install.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/account_routing.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/algo.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/algo_broker.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/build_info.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/callback_dispatcher.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/cftrader.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/channels.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/cli.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/conditional_orders.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/config.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/diagnostic_context.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/diagnostics.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/flow_control.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/hub.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/level2.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/log_management.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/logging_i18n.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/management.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/monitor_performance.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/normal_bridge.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/pipe_bridge.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/pipe_client.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/pipe_hub.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/pipe_transport.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/planning_broker.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/position_sizing.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/protocol.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/qmt_binding_defaults.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/qmt_binding_settings.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/qmt_component_agent.js +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/qmt_component_login.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/qmt_credentials.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/qmt_dependencies.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/qmt_inner.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/qmt_inner_catalog.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/qmt_inner_runtime.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/qmt_native_files.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/qmt_native_worker.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/qmt_pipe_runtime.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/qmt_strategy_agent.js +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/qmt_strategy_deploy.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/qmt_strategy_package.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/qmt_strategy_recovery.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/qmt_strategy_runtime.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/quote_history.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/quote_leases.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/remote_adapter.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/remote_management.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/remote_supervisor.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/rpc_timing.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/runtime_report.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/shared_quotes.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/smart_orders.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/stock_connect.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/subscription_bridge.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/subscription_client.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/subscription_manager.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/subscription_store.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/10_/345/221/275/344/273/244/350/241/214/346/216/245/345/217/243/346/265/213/350/257/225.py" +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/11_/346/231/256/351/200/232/346/241/245/346/216/245/346/264/276/345/217/221/346/265/213/350/257/225.py" +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/13_/347/256/241/351/201/223/344/270/255/345/277/203/350/277/236/346/216/245/346/265/213/350/257/225.py" +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/14_/350/277/220/350/241/214/346/227/266/346/212/245/345/221/212/346/265/213/350/257/225.py" +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/15_/347/275/221/351/241/265/347/212/266/346/200/201/347/274/223/345/255/230/346/265/213/350/257/225.py" +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/16_/345/220/257/345/212/250/345/256/211/350/243/205/346/265/213/350/257/225.py" +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/17_/345/244/247QMT/346/216/245/345/217/243/351/200/202/351/205/215/346/265/213/350/257/225.py" +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/18_/351/253/230/347/272/247/346/250/241/345/274/217/345/256/236/346/234/272/350/201/224/350/260/203.py" +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/19_/345/256/236/346/234/272/350/201/224/350/260/203/345/210/244/345/256/232/346/265/213/350/257/225.py" +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/1_/350/241/214/346/203/205/346/216/245/346/224/266/346/265/213/350/257/225.py" +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/21_/345/215/225/350/202/241/350/241/214/346/203/205/350/256/242/351/230/205/346/265/213/350/257/225.py" +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/22_/350/277/236/346/216/245/350/257/212/346/226/255/346/265/213/350/257/225.py" +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/23_/347/244/272/344/276/213/351/205/215/347/275/256/346/265/213/350/257/225.py" +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/24_/350/264/246/346/210/267/347/274/223/345/255/230/346/214/211/351/234/200/345/210/267/346/226/260/346/265/213/350/257/225.py" +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/25_/350/264/246/346/210/267/347/274/223/345/255/230/351/241/265/351/235/242/346/265/213/350/257/225.py" +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/28_Level2/346/216/245/345/217/243/351/200/202/351/205/215/346/265/213/350/257/225.py" +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/29_Level2/350/241/214/346/203/205/346/265/213/350/257/225.py" +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/2_/346/225/260/346/215/256/350/216/267/345/217/226/346/265/213/350/257/225.py" +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/30_cftrader/346/211/271/351/207/217/344/270/213/345/215/225/346/200/247/350/203/275/345/237/272/345/207/206.py" +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/31_cftrader/346/250/241/346/213/237/350/264/246/345/217/267/346/211/271/351/207/217/344/270/213/345/215/225/346/265/213/350/257/225.py" +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/32_/345/274/202/346/255/245/344/270/213/345/215/225/346/265/213/350/257/225.py" +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/33_/345/220/214/346/255/245/344/270/213/345/215/225/345/233/236/350/260/203/346/265/213/350/257/225.py" +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/34_/345/205/250/346/216/250/345/206/205/351/203/250/345/273/266/350/277/237/346/265/213/350/257/225.py" +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/3_/346/225/260/346/215/256/344/270/213/350/275/275/346/265/213/350/257/225.py" +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/4_/344/272/244/346/230/223/345/247/224/346/211/230/346/237/245/350/257/242/346/265/213/350/257/225.py" +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/5_/347/234/237/345/256/236/344/270/213/345/215/225/346/265/213/350/257/225.py" +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/6_/345/233/236/350/260/203/346/265/213/350/257/225.py" +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/7_/345/220/214/346/255/245/345/274/202/346/255/245/344/270/213/345/215/225/346/265/213/350/257/225.py" +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/8_/346/210/220/344/272/244/350/256/242/345/215/225/345/222/214/345/247/224/346/211/230/350/256/242/345/215/225/346/237/245/350/257/242/346/265/213/350/257/225.py" +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/9_/345/217/257/346/222/244/350/256/242/345/215/225/346/265/213/350/257/225.py" +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/_helpers.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/readme.md +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_account_query_timing.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_account_subscription_probe.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_activity_log_browser.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_advanced_parity.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_algo.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_algo_broker.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_api_tester_browser.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_api_tester_web.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_binding_delete_confirmation.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_binding_live_refresh_browser.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_binding_runtime_browser.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_callback_recovery.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_callback_requests.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_cftrader.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_cftrader_tutorial.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_cftrader_web.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_credit_compat.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_dataframe_encoding.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_diagnostic_context.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_json_serialization.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_latency_flow_control.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_lite_account_routing.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_lite_deployment_paths.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_lite_parity.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_lite_qmt_dispatch.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_local_trading_calendar.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_log_management.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_management.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_management_batch.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_market_data_fallback.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_market_timing.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_monitor_notifications.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_monitor_performance.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_navigation_browser.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_order_identity.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_order_time_callback.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_pipe_shutdown.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_project_updater.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_python_api_reference.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_qmt_auto_login_browser.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_qmt_auto_login_integration.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_qmt_binding_defaults.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_qmt_binding_settings.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_qmt_component_login.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_qmt_dependencies.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_qmt_fileless.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_qmt_inner.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_qmt_inner_reference.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_qmt_inner_web.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_qmt_native_files.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_qmt_native_reuse.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_qmt_native_worker.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_qmt_package_import.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_qmt_strategy_browser.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_qmt_strategy_capabilities.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_qmt_strategy_deploy.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_qmt_strategy_recovery.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_qmt_strategy_web.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_qmt_update.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_quote_latency.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_quote_latency_encoding.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_remote_management.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_remote_management_browser.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_remote_version_check.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_setup_qmt_prompt.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_shared_quotes.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_shared_quotes_generated.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_smart_orders.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_smart_orders_api.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_smart_orders_docs.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_startup_recovery.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_stock_connect.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_stock_connect_browser.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_strategy_recovery_api.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_subscription_congestion.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_subscription_persistence.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_table_conversion.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_trade_strategy_callback.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_trader_subscription_recovery.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_trading_dates.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_tutorial_reader.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_tx_auto_import.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_tx_shutdown.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/trading_calendar.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tx.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/xtconstant.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/xttrader.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/xttype.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant.egg-info/dependency_links.txt +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant.egg-info/entry_points.txt +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant.egg-info/top_level.txt +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/cfquant_pipe_hub.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/qmt_scripts/CFQUANT.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/qmt_scripts/CFQUANT_CTYPE_ALL_LOWLAT.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/qmt_scripts/CFQUANT_TRADE_LOWLAT.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/qmt_scripts/__init__.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/qmt_scripts//345/220/214/350/264/246/345/217/267/347/213/254/347/253/213/345/270/202/345/234/272/CFQUANT_CTYPE_ALL_LOWLAT_SH.py" +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/qmt_scripts//345/220/214/350/264/246/345/217/267/347/213/254/347/253/213/345/270/202/345/234/272/CFQUANT_CTYPE_ALL_LOWLAT_SZ.py" +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/qmt_scripts//345/220/214/350/264/246/345/217/267/347/213/254/347/253/213/345/270/202/345/234/272/CFQUANT_TRADE_LOWLAT_SH.py" +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/qmt_scripts//345/220/214/350/264/246/345/217/267/347/213/254/347/253/213/345/270/202/345/234/272/CFQUANT_TRADE_LOWLAT_SZ.py" +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/qmt_scripts//345/220/214/350/264/246/345/217/267/347/213/254/347/253/213/345/270/202/345/234/272/readme.md" +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/setup.cfg +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/__init__.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/activity-log.js +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/avatars/amber-pulse.svg +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/avatars/copper-grid.svg +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/avatars/market-blue.svg +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/avatars/rose-circuit.svg +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/avatars/signal-green.svg +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/avatars/slate-wave.svg +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/avatars/teal-orbit.svg +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/avatars/violet-node.svg +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/cfquant-architecture.html +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/cftrader-api-data.js +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/docs.js +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/favicon.svg +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/image/qmt_deploy/image-10.png +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/image/qmt_deploy/image-11.png +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/image/qmt_deploy/image-12.png +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/image/qmt_deploy/image-13.png +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/image/qmt_deploy/image-14.png +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/image/qmt_deploy/image-15.png +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/image/qmt_deploy/image-16.png +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/image/qmt_deploy/image-17.png +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/image/qmt_deploy/image-18.png +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/image/qmt_deploy/image-19.png +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/image/qmt_deploy/image-20.png +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/image/qmt_deploy/image-21.png +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/image/qmt_deploy/image-4.png +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/image/qmt_deploy/image-5.png +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/image/qmt_deploy/image-6.png +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/image/qmt_deploy/image-7.png +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/image/qmt_deploy/image-8.png +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/image/qmt_deploy/image-9.png +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/navigation.js +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/python-api-data.js +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/qmt-inner-api-data.js +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/remote-management.js +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/smart-order-api-data.js +0 -0
- {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/vendor/mermaid.min.js +0 -0
|
@@ -6,7 +6,12 @@ include cfquant_pipe_hub.py
|
|
|
6
6
|
recursive-include cfquant *.py
|
|
7
7
|
include cfquant/qmt_component_agent.js
|
|
8
8
|
include cfquant/qmt_strategy_agent.js
|
|
9
|
+
include cfquant/qmt_vm_agent.js
|
|
10
|
+
include cfquant/qmt_mac_agent.js
|
|
11
|
+
include cfquant/tools/THIRD_PARTY.md
|
|
12
|
+
recursive-include cfquant/tools/upx *
|
|
9
13
|
recursive-include cfquant/tests *.py *.md
|
|
14
|
+
include cfquant/tests/qmt_vm_native_fixture.js
|
|
10
15
|
recursive-include LTtx *.py
|
|
11
16
|
include LTtx/tx/Config.txt
|
|
12
17
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: cfquant
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.68
|
|
4
4
|
Summary: QMT local bridge and xtquant-compatible Python client.
|
|
5
5
|
License: MIT
|
|
6
6
|
Requires-Python: >=3.8
|
|
@@ -17,6 +17,7 @@ Requires-Dist: pandas-market-calendars
|
|
|
17
17
|
Requires-Dist: importlib-resources>=5
|
|
18
18
|
Requires-Dist: frida==17.16.4; sys_platform == "win32"
|
|
19
19
|
Requires-Dist: pywin32>=306; sys_platform == "win32"
|
|
20
|
+
Requires-Dist: pefile>=2023.2.7; sys_platform == "win32"
|
|
20
21
|
Provides-Extra: dev
|
|
21
22
|
Requires-Dist: build>=1.0; extra == "dev"
|
|
22
23
|
Requires-Dist: twine>=5.0; extra == "dev"
|
|
@@ -403,6 +404,7 @@ restart_cfquant.bat 重启
|
|
|
403
404
|
| 极致模式部署 | [极致模式部署指南](docs/极致模式部署指南.md) |
|
|
404
405
|
| 高级模式部署 | [高级模式部署指南](docs/高级模式部署指南.md) |
|
|
405
406
|
| 账号和 QMT 目录配置 | [Web 账号运行配置说明](docs/Web账号运行配置说明.md) |
|
|
407
|
+
| 虚拟机检测适配与 MAC 注入 | [QMT 启动设置](docs/qmt_launch_settings.md),按绑定配置,默认关闭 |
|
|
406
408
|
| 从 miniQMT 迁移 | [miniQMT 迁移到大 QMT 指南](docs/miniQMT迁移到大QMT指南.md) |
|
|
407
409
|
| 官网 nativeApi 接口功能及适配状态 | [xtquant 原版接口适配清单](docs/xtquant原版接口适配清单.md) |
|
|
408
410
|
| Level2 六类行情、订阅回调与千档边界 | [Level2 行情适配说明](docs/Level2行情适配说明.md) |
|
|
@@ -378,6 +378,7 @@ restart_cfquant.bat 重启
|
|
|
378
378
|
| 极致模式部署 | [极致模式部署指南](docs/极致模式部署指南.md) |
|
|
379
379
|
| 高级模式部署 | [高级模式部署指南](docs/高级模式部署指南.md) |
|
|
380
380
|
| 账号和 QMT 目录配置 | [Web 账号运行配置说明](docs/Web账号运行配置说明.md) |
|
|
381
|
+
| 虚拟机检测适配与 MAC 注入 | [QMT 启动设置](docs/qmt_launch_settings.md),按绑定配置,默认关闭 |
|
|
381
382
|
| 从 miniQMT 迁移 | [miniQMT 迁移到大 QMT 指南](docs/miniQMT迁移到大QMT指南.md) |
|
|
382
383
|
| 官网 nativeApi 接口功能及适配状态 | [xtquant 原版接口适配清单](docs/xtquant原版接口适配清单.md) |
|
|
383
384
|
| Level2 六类行情、订阅回调与千档边界 | [Level2 行情适配说明](docs/Level2行情适配说明.md) |
|
|
@@ -12,7 +12,7 @@ cfquant: 面向 QMT 的 xtquant 兼容层。
|
|
|
12
12
|
import sys as _sys
|
|
13
13
|
|
|
14
14
|
from . import xtconstant, xtdata, xttrader, xttype
|
|
15
|
-
from . import cftrader
|
|
15
|
+
from . import cftrader, notify
|
|
16
16
|
from .qmt_inner import QmtInnerApi
|
|
17
17
|
from .client import CfquantError, CfquantTimeout, configure, get_client
|
|
18
18
|
from .version import __version__
|
|
@@ -28,6 +28,7 @@ __all__ = [
|
|
|
28
28
|
"xtdata",
|
|
29
29
|
"xttrader",
|
|
30
30
|
"cftrader",
|
|
31
|
+
"notify",
|
|
31
32
|
"QmtInnerApi",
|
|
32
33
|
"xttype",
|
|
33
34
|
"configure",
|
|
@@ -7,7 +7,7 @@ import time
|
|
|
7
7
|
from collections.abc import Mapping
|
|
8
8
|
from numbers import Integral, Real
|
|
9
9
|
from .stock_connect import normalize_connect_code, validate_connect_market
|
|
10
|
-
from .order_identity import prepare_order_remark, order_remark_key
|
|
10
|
+
from .order_identity import prepare_order_remark, order_remark_key, restore_order_remark
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
CFTRADER_BATCH_ORDER_ACTIONS = frozenset(("cftrader.order_stock_batch", "cftrader.order_stock_batch_async"))
|
|
@@ -203,6 +203,9 @@ def batch_cancel_result_rows(cancels, seqs=None):
|
|
|
203
203
|
|
|
204
204
|
|
|
205
205
|
def batch_result(account, batch_id, asynchronous, results, operation="order"):
|
|
206
|
+
if operation == "order":
|
|
207
|
+
for row in results:
|
|
208
|
+
restore_order_remark(row)
|
|
206
209
|
counts = {name: sum(row["status"] == name for row in results)
|
|
207
210
|
for name in ("submitted", "failed", "unknown", "skipped")}
|
|
208
211
|
counts["rejected"] = sum(row.get("dispatched") is False for row in results)
|
|
@@ -6,11 +6,9 @@ never perform SMTP or HTTP work on a QMT callback/status thread.
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import copy
|
|
9
|
-
import base64
|
|
10
9
|
from datetime import datetime, timedelta, timezone
|
|
11
10
|
from email.message import EmailMessage
|
|
12
11
|
import hashlib
|
|
13
|
-
import hmac
|
|
14
12
|
import json
|
|
15
13
|
import os
|
|
16
14
|
from pathlib import Path
|
|
@@ -20,9 +18,7 @@ import ssl
|
|
|
20
18
|
import tempfile
|
|
21
19
|
import threading
|
|
22
20
|
import time
|
|
23
|
-
import
|
|
24
|
-
import urllib.parse
|
|
25
|
-
import urllib.request
|
|
21
|
+
from .webhook import send_webhook
|
|
26
22
|
|
|
27
23
|
|
|
28
24
|
SHANGHAI = timezone(timedelta(hours=8))
|
|
@@ -566,28 +562,5 @@ class MonitorNotificationService:
|
|
|
566
562
|
server.send_message(msg)
|
|
567
563
|
|
|
568
564
|
def _send_webhook(self, config, event):
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
raise ValueError("webhook url is required")
|
|
572
|
-
kind = str(config.get("type") or "generic").lower()
|
|
573
|
-
if kind == "dingtalk" and config.get("secret"):
|
|
574
|
-
timestamp = str(int(time.time() * 1000))
|
|
575
|
-
secret = str(config.get("secret"))
|
|
576
|
-
raw = "%s\n%s" % (timestamp, secret)
|
|
577
|
-
sign = base64.b64encode(hmac.new(secret.encode("utf-8"), raw.encode("utf-8"), hashlib.sha256).digest()).decode("ascii")
|
|
578
|
-
separator = "&" if "?" in url else "?"
|
|
579
|
-
url = "%s%stimestamp=%s&sign=%s" % (url, separator, urllib.parse.quote(timestamp), urllib.parse.quote(sign))
|
|
580
|
-
text = self._format_text(event)
|
|
581
|
-
if kind == "feishu":
|
|
582
|
-
body = {"msg_type": "text", "content": {"text": text}}
|
|
583
|
-
elif kind in ("dingtalk", "wecom"):
|
|
584
|
-
body = {"msgtype": "text", "text": {"content": text}}
|
|
585
|
-
else:
|
|
586
|
-
body = {"event_type": event.get("event_type"), "title": event.get("title"),
|
|
587
|
-
"message": event.get("message"), "payload": event.get("payload"),
|
|
588
|
-
"occurred_at": event.get("occurred_at")}
|
|
589
|
-
request = urllib.request.Request(url, data=json.dumps(body, ensure_ascii=False, default=str).encode("utf-8"),
|
|
590
|
-
headers={"Content-Type": "application/json; charset=utf-8", "User-Agent": "cfquant-monitor"}, method="POST")
|
|
591
|
-
with urllib.request.urlopen(request, timeout=8) as response:
|
|
592
|
-
if int(getattr(response, "status", 200)) >= 400:
|
|
593
|
-
raise urllib.error.HTTPError(url, response.status, "webhook returned %s" % response.status, response.headers, None)
|
|
565
|
+
payload = {key: event.get(key) for key in ("event_type", "title", "message", "payload", "occurred_at")}
|
|
566
|
+
return send_webhook(config, self._format_text(event), payload=payload)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""User-authored messages: explicit Webhook first, otherwise account binding."""
|
|
2
|
+
|
|
3
|
+
import atexit
|
|
4
|
+
import threading
|
|
5
|
+
|
|
6
|
+
from .webhook import WebhookError, normalize_webhook, send_webhook, validate_message
|
|
7
|
+
|
|
8
|
+
_client = None
|
|
9
|
+
_client_lock = threading.Lock()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def close():
|
|
13
|
+
"""Release the notification RPC connection; the next call reconnects."""
|
|
14
|
+
global _client
|
|
15
|
+
with _client_lock:
|
|
16
|
+
client, _client = _client, None
|
|
17
|
+
if client is not None:
|
|
18
|
+
client.close()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
atexit.register(close)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def send(message, account_id=None, *, title="", webhook_url=None, provider=None,
|
|
25
|
+
secret=None, account_type=None, account_key=None, bridge_id=None, timeout=8):
|
|
26
|
+
"""Send text synchronously; returns sent/provider/source, or raises on failure.
|
|
27
|
+
|
|
28
|
+
webhook_url and provider must be supplied together and override the binding
|
|
29
|
+
completely (including its secret). Account-based sends require cfquant Web
|
|
30
|
+
and LTtx, but do not require QMT to be online. No automatic retry is performed.
|
|
31
|
+
"""
|
|
32
|
+
timeout = validate_message(message, title, timeout)
|
|
33
|
+
if webhook_url is not None or provider is not None or secret is not None:
|
|
34
|
+
if not webhook_url or not provider:
|
|
35
|
+
raise ValueError("调用时覆盖推送配置必须同时提供 webhook_url 和 provider")
|
|
36
|
+
config = normalize_webhook({"enabled": True, "url": webhook_url, "provider": provider, "secret": secret})
|
|
37
|
+
return dict(send_webhook(config, message, title=title, timeout=timeout), source="explicit")
|
|
38
|
+
if not str(account_id or "").strip() and not str(account_key or "").strip():
|
|
39
|
+
raise ValueError("请指定资金账号 account_id 或 account_key,或提供 webhook_url 和 provider")
|
|
40
|
+
params = {"message": message, "title": title, "timeout": timeout}
|
|
41
|
+
params.update({key: value for key, value in dict(account_id=account_id, account_type=account_type,
|
|
42
|
+
account_key=account_key, bridge_id=bridge_id).items() if value is not None})
|
|
43
|
+
global _client
|
|
44
|
+
with _client_lock:
|
|
45
|
+
if _client is None:
|
|
46
|
+
from .client import create_rpc_client
|
|
47
|
+
_client = create_rpc_client(transport="web_lttx")
|
|
48
|
+
client = _client
|
|
49
|
+
return client.request("cfquant.notify.send", params, timeout=timeout + 5)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
__all__ = ["send", "close", "WebhookError"]
|
|
@@ -72,6 +72,14 @@ def restore_order_remark(data):
|
|
|
72
72
|
if is_unique_order_remark(internal):
|
|
73
73
|
data["cfquant_order_remark"] = internal
|
|
74
74
|
data["order_remark"] = original_order_remark(internal)
|
|
75
|
+
submission_id = _ORDER_REMARK_SUFFIX.search(internal).group(1)
|
|
76
|
+
data["cfquant_submission_id"] = submission_id
|
|
77
|
+
# The native remark identifies one submission, which QMT may split.
|
|
78
|
+
# Derive the child key from the final public ID, never list position.
|
|
79
|
+
data.pop("cfquant_child_id", None)
|
|
80
|
+
order_id = str(data.get("order_id") or "").strip()
|
|
81
|
+
if order_id.isdigit() and int(order_id) > 0:
|
|
82
|
+
data["cfquant_child_id"] = submission_id + ":" + str(int(order_id))
|
|
75
83
|
return data
|
|
76
84
|
|
|
77
85
|
|
|
@@ -153,7 +161,7 @@ class OrderCallbackCache(object):
|
|
|
153
161
|
matches = self._matches(scope, refs, data)
|
|
154
162
|
remark = order_remark_key(data)
|
|
155
163
|
local = sorted(ref for ref in refs if ref[0] == "local")
|
|
156
|
-
identity = (
|
|
164
|
+
identity = (local or sorted(refs))[0]
|
|
157
165
|
key = (scope, identity)
|
|
158
166
|
if len(matches) == 1:
|
|
159
167
|
key = matches[0]
|
|
@@ -187,4 +195,22 @@ class OrderCallbackCache(object):
|
|
|
187
195
|
data[name] = value
|
|
188
196
|
if is_unique_order_remark(remark):
|
|
189
197
|
data["cfquant_order_remark"] = remark
|
|
198
|
+
# A broker-only DEAL can recover its child from this typed index.
|
|
199
|
+
# Shared submission metadata alone cannot choose that child.
|
|
200
|
+
order_id = str(data.get("order_id") or "").strip()
|
|
201
|
+
if not (order_id.isdigit() and int(order_id) > 0):
|
|
202
|
+
saved_id = saved.get("order_id")
|
|
203
|
+
if str(saved_id or "").isdigit() and int(saved_id) > 0:
|
|
204
|
+
data["order_id"] = int(saved_id)
|
|
190
205
|
return restore_order_remark(data)
|
|
206
|
+
|
|
207
|
+
def submission_is_ambiguous(self, data, account_type):
|
|
208
|
+
"""Missing child IDs must not fall back to a split submission's first ID."""
|
|
209
|
+
with self.lock:
|
|
210
|
+
scope, _ = self._identity(data, account_type)
|
|
211
|
+
remark = order_remark_key(data)
|
|
212
|
+
ids = {str(record["data"].get("order_id")) for key, record in self.records.items()
|
|
213
|
+
if key[0] == scope and order_remark_key(record["data"]) == remark
|
|
214
|
+
and str(record["data"].get("order_id") or "").isdigit()
|
|
215
|
+
and int(record["data"]["order_id"]) > 0}
|
|
216
|
+
return len(ids) > 1
|
|
@@ -27,6 +27,7 @@ DEFAULT_BRIDGE_ID = "default"
|
|
|
27
27
|
DEFAULT_ACCOUNT_TYPE = "STOCK"
|
|
28
28
|
PENDING_MATCH_TTL_SECONDS = 10 * 60
|
|
29
29
|
CANONICAL_ORDER_ID_FIELD = "canonical_order_id"
|
|
30
|
+
ORDER_IDENTITY_VERSION = 2
|
|
30
31
|
ORDER_REF_FIELDS = (
|
|
31
32
|
"order_ref",
|
|
32
33
|
"m_strOrderRef",
|
|
@@ -165,6 +166,42 @@ def reconcile_order_id(data, order_id):
|
|
|
165
166
|
return mismatched
|
|
166
167
|
|
|
167
168
|
|
|
169
|
+
def native_order_id(data):
|
|
170
|
+
for name in ("m_nRef", "m_nOrderID", "m_strOrderRef", "m_strOrderID", "order_id"):
|
|
171
|
+
value = positive_order_id(data.get(name))
|
|
172
|
+
if value is not None:
|
|
173
|
+
return value
|
|
174
|
+
return None
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def same_broker_order(record, data):
|
|
178
|
+
"""Only child-scoped, versioned broker evidence can justify a local alias."""
|
|
179
|
+
if record.get("order_identity_version") != ORDER_IDENTITY_VERSION:
|
|
180
|
+
return False
|
|
181
|
+
left = normalize_order_ref(first_value(record, ("order_sysid", "m_strOrderSysID")))
|
|
182
|
+
right = normalize_order_ref(first_value(data, ("order_sysid", "m_strOrderSysID")))
|
|
183
|
+
left_stock = normalize_text(record.get("stock_code")).upper()
|
|
184
|
+
right_stock = normalize_text(data.get("stock_code")).upper()
|
|
185
|
+
return bool(left and left == right and left_stock and left_stock == right_stock)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def verified_child_alias(record, data):
|
|
189
|
+
return (record.get("order_identity_version") == ORDER_IDENTITY_VERSION
|
|
190
|
+
and native_order_id(data) in (record.get("child_local_ids") or [])) or same_broker_order(record, data)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def submission_metadata(record):
|
|
194
|
+
"""Copy shared text/context without carrying a sibling's identity or size."""
|
|
195
|
+
result = dict(record)
|
|
196
|
+
for name in ORDER_REF_FIELDS + ("order_refs", CANONICAL_ORDER_ID_FIELD,
|
|
197
|
+
"callback_bound_at", "order_volume", "order_identity_version", "child_local_ids",
|
|
198
|
+
"query_order_id",
|
|
199
|
+
"cfquant_child_id", "cfquant_callback_order_id",
|
|
200
|
+
"cfquant_order_id_reconciled"):
|
|
201
|
+
result.pop(name, None)
|
|
202
|
+
return result
|
|
203
|
+
|
|
204
|
+
|
|
168
205
|
def stock_code_base(value):
|
|
169
206
|
text = normalize_text(value)
|
|
170
207
|
if not text:
|
|
@@ -413,6 +450,13 @@ def apply_record_to_callback(data, record, match_info=None):
|
|
|
413
450
|
data["client_order_id"] = normalize_text(record.get("client_order_id"))
|
|
414
451
|
|
|
415
452
|
order_id = canonical_order_id_from_record(record)
|
|
453
|
+
native_id = native_order_id(data)
|
|
454
|
+
if native_id is not None and (
|
|
455
|
+
(match_info or {}).get("preserve_order_id")
|
|
456
|
+
or (is_unique_order_remark(user_order_id) and native_id != order_id
|
|
457
|
+
and not verified_child_alias(record, data))
|
|
458
|
+
):
|
|
459
|
+
order_id = native_id
|
|
416
460
|
if order_id is not None:
|
|
417
461
|
reconcile_order_id(data, order_id)
|
|
418
462
|
order_id_ref = str(order_id)
|
|
@@ -511,6 +555,19 @@ class OrderMetaCache(object):
|
|
|
511
555
|
return self._upsert_locked(record)
|
|
512
556
|
|
|
513
557
|
def _upsert_locked(self, record):
|
|
558
|
+
if (is_unique_order_remark(user_order_id_from_data(record))
|
|
559
|
+
and record.get("order_identity_version") != ORDER_IDENTITY_VERSION):
|
|
560
|
+
# Old stores may have merged sibling IDs/sysids into one record.
|
|
561
|
+
# Retain submission metadata and its canonical ID, not unproven aliases.
|
|
562
|
+
canonical = canonical_order_id_from_record(record)
|
|
563
|
+
volume = record.get("order_volume")
|
|
564
|
+
record = submission_metadata(record)
|
|
565
|
+
if volume is not None:
|
|
566
|
+
record["order_volume"] = volume
|
|
567
|
+
if canonical is not None:
|
|
568
|
+
record.update(order_id=canonical, canonical_order_id=canonical,
|
|
569
|
+
order_ref=str(canonical), order_refs=[str(canonical)])
|
|
570
|
+
record["order_identity_version"] = ORDER_IDENTITY_VERSION
|
|
514
571
|
trade_day = current_trade_day()
|
|
515
572
|
self._prune_locked(trade_day, time.time())
|
|
516
573
|
status = normalize_text(record.get("status")).lower()
|
|
@@ -594,13 +651,18 @@ class OrderMetaCache(object):
|
|
|
594
651
|
loaded += 1
|
|
595
652
|
return {"loaded": loaded, "stale": stale}
|
|
596
653
|
|
|
597
|
-
def resolve_callback(self, data, bridge_id=None, account_type=None, account_id=None, allow_pending=True
|
|
654
|
+
def resolve_callback(self, data, bridge_id=None, account_type=None, account_id=None, allow_pending=True,
|
|
655
|
+
query=False):
|
|
598
656
|
callback_record = normalize_record(
|
|
599
657
|
data,
|
|
600
658
|
bridge_id=bridge_id or self.bridge_id,
|
|
601
659
|
account_type=account_type or data.get("account_type"),
|
|
602
660
|
account_id=account_id or data.get("account_id"),
|
|
603
661
|
)
|
|
662
|
+
# normalize_record also accepts stores and synthesizes m_strOrderRef
|
|
663
|
+
# from any reference, including a broker sysid. It is not native evidence.
|
|
664
|
+
if "m_strOrderRef" not in data:
|
|
665
|
+
callback_record.pop("m_strOrderRef", None)
|
|
604
666
|
with self.lock:
|
|
605
667
|
self._prune_locked(current_trade_day(), time.time())
|
|
606
668
|
record = None
|
|
@@ -612,6 +674,13 @@ class OrderMetaCache(object):
|
|
|
612
674
|
if record and user_order_id and is_unique_order_remark(user_order_id_from_data(record)):
|
|
613
675
|
if not order_remark_matches_reference(user_order_id_from_data(record), user_order_id):
|
|
614
676
|
record = None
|
|
677
|
+
if record and is_unique_order_remark(user_order_id_from_data(record)):
|
|
678
|
+
native_id = native_order_id(callback_record)
|
|
679
|
+
if not ((native_id is not None and native_id == canonical_order_id_from_record(record))
|
|
680
|
+
or verified_child_alias(record, callback_record)):
|
|
681
|
+
# Numeric local IDs and broker IDs share the legacy
|
|
682
|
+
# index. An untyped string hit is not child evidence.
|
|
683
|
+
record = None
|
|
615
684
|
if record:
|
|
616
685
|
confidence = "order_ref"
|
|
617
686
|
break
|
|
@@ -624,11 +693,14 @@ class OrderMetaCache(object):
|
|
|
624
693
|
record = None
|
|
625
694
|
if record:
|
|
626
695
|
confidence = "user_order_id"
|
|
627
|
-
if record is None and allow_pending:
|
|
696
|
+
if record is None and allow_pending and not query:
|
|
628
697
|
record, confidence = self._match_pending_locked(callback_record)
|
|
629
|
-
if record is None:
|
|
698
|
+
if record is None and not query:
|
|
630
699
|
record, confidence = self._match_known_locked(callback_record, order_refs)
|
|
631
700
|
|
|
701
|
+
if record and is_unique_order_remark(user_order_id_from_data(record)):
|
|
702
|
+
record = self._resolve_child_locked(record, callback_record, confidence, query)
|
|
703
|
+
|
|
632
704
|
bound_order_ref = ""
|
|
633
705
|
bound_order_refs = []
|
|
634
706
|
if record and order_refs:
|
|
@@ -652,8 +724,56 @@ class OrderMetaCache(object):
|
|
|
652
724
|
"bound_order_ref": bound_order_ref,
|
|
653
725
|
"bound_order_refs": bound_order_refs,
|
|
654
726
|
"match_confidence": confidence,
|
|
727
|
+
"preserve_order_id": query,
|
|
655
728
|
}
|
|
656
729
|
|
|
730
|
+
def _resolve_child_locked(self, record, data, confidence, query):
|
|
731
|
+
"""A UUID groups submissions; native IDs partition the real children."""
|
|
732
|
+
incoming_id = native_order_id(data)
|
|
733
|
+
canonical = canonical_order_id_from_record(record)
|
|
734
|
+
broker_matches = []
|
|
735
|
+
if same_broker_order(record, data):
|
|
736
|
+
broker_matches = [item for item in self._unique_records_locked()
|
|
737
|
+
if self._ctx(item) == self._ctx(record) and same_broker_order(item, data)]
|
|
738
|
+
broker_ambiguous = len({canonical_order_id_from_record(item) for item in broker_matches}) > 1
|
|
739
|
+
if incoming_id is None:
|
|
740
|
+
broker_id = normalize_order_ref(first_value(data, ("order_sysid", "m_strOrderSysID")))
|
|
741
|
+
if broker_id and (broker_ambiguous or not same_broker_order(record, data)):
|
|
742
|
+
return submission_metadata(record)
|
|
743
|
+
if confidence == "user_order_id":
|
|
744
|
+
key = self._user_key(record)
|
|
745
|
+
children = {canonical_order_id_from_record(item) for item in self._unique_records_locked()
|
|
746
|
+
if self._user_key(item) == key}
|
|
747
|
+
children.discard(None)
|
|
748
|
+
if len(children) > 1:
|
|
749
|
+
return submission_metadata(record)
|
|
750
|
+
return record
|
|
751
|
+
same_child = incoming_id == canonical or verified_child_alias(record, data)
|
|
752
|
+
if broker_ambiguous and incoming_id != canonical and incoming_id not in (record.get("child_local_ids") or []):
|
|
753
|
+
same_child = False
|
|
754
|
+
if query and record.get("query_order_id") not in (None, incoming_id):
|
|
755
|
+
# Two ORDER query rows are separate children even if the broker
|
|
756
|
+
# uses the same reference. Only callback references can be aliases.
|
|
757
|
+
same_child = False
|
|
758
|
+
if not same_child:
|
|
759
|
+
record = submission_metadata(record)
|
|
760
|
+
else:
|
|
761
|
+
record = dict(record)
|
|
762
|
+
# Queries provide the authoritative local order ID. A callback alias
|
|
763
|
+
# may reuse it only after matching a broker ID recorded for this child.
|
|
764
|
+
child_id = canonical if same_child and canonical is not None and not query else incoming_id
|
|
765
|
+
record.update(order_id=child_id, canonical_order_id=child_id,
|
|
766
|
+
order_identity_version=ORDER_IDENTITY_VERSION)
|
|
767
|
+
record["child_local_ids"] = sorted(set((record.get("child_local_ids") or []) + [child_id, incoming_id]))
|
|
768
|
+
if query:
|
|
769
|
+
record["query_order_id"] = incoming_id
|
|
770
|
+
if not same_child:
|
|
771
|
+
record.update(order_ref=str(child_id), order_refs=[str(child_id)])
|
|
772
|
+
for name in ("order_sysid", "m_strOrderSysID", "order_volume"):
|
|
773
|
+
if not is_empty(data.get(name)):
|
|
774
|
+
record[name] = data[name]
|
|
775
|
+
return record
|
|
776
|
+
|
|
657
777
|
def _unique_records_locked(self):
|
|
658
778
|
records = []
|
|
659
779
|
seen = set()
|
|
@@ -683,6 +803,11 @@ class OrderMetaCache(object):
|
|
|
683
803
|
continue
|
|
684
804
|
if not self._has_callback_metadata(record):
|
|
685
805
|
continue
|
|
806
|
+
if is_unique_order_remark(user_order_id_from_data(record)):
|
|
807
|
+
native_id = native_order_id(callback_record)
|
|
808
|
+
if not ((native_id is not None and native_id == canonical_order_id_from_record(record))
|
|
809
|
+
or verified_child_alias(record, callback_record)):
|
|
810
|
+
continue
|
|
686
811
|
evidence = self._context_match_evidence(record, callback_record)
|
|
687
812
|
if evidence < 0:
|
|
688
813
|
continue
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from .diagnostic_context import versioned_message
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
3
|
import os
|
|
4
|
+
import gc
|
|
4
5
|
import queue
|
|
5
6
|
import sys
|
|
6
7
|
import threading
|
|
@@ -516,47 +517,91 @@ class CfquantQmtBridge(object):
|
|
|
516
517
|
})
|
|
517
518
|
raise NotImplementedError("当前QMT环境未提供download_history_data2")
|
|
518
519
|
|
|
519
|
-
|
|
520
|
+
stock_list = params.get("stock_list", params.get("code_list", []))
|
|
521
|
+
if isinstance(stock_list, str):
|
|
522
|
+
stock_list = [item.strip() for item in stock_list.replace(",", ",").split(",") if item.strip()]
|
|
523
|
+
else:
|
|
524
|
+
stock_list = list(stock_list or [])
|
|
525
|
+
batches = self._history_download_batches(stock_list, params)
|
|
526
|
+
total = len(stock_list)
|
|
527
|
+
completed = 0
|
|
528
|
+
|
|
529
|
+
def callback(data, batch_offset=0, batch_index=0, batch_total=1):
|
|
520
530
|
if callback_event and client_id:
|
|
531
|
+
payload = dict(data) if isinstance(data, dict) else {"message": str(data)}
|
|
532
|
+
batch_finished = payload.get("finished")
|
|
533
|
+
try:
|
|
534
|
+
batch_finished = max(0, min(int(batch_finished), len(batches[batch_index])))
|
|
535
|
+
except (TypeError, ValueError, IndexError):
|
|
536
|
+
batch_finished = 0
|
|
537
|
+
payload.update({
|
|
538
|
+
"finished": min(total, batch_offset + batch_finished),
|
|
539
|
+
"total": total,
|
|
540
|
+
"batch_index": batch_index + 1,
|
|
541
|
+
"batch_total": batch_total,
|
|
542
|
+
"batch_finished": batch_finished,
|
|
543
|
+
"batch_size": len(batches[batch_index]) if batch_index < len(batches) else 0,
|
|
544
|
+
})
|
|
521
545
|
self._send_event(
|
|
522
546
|
client_id,
|
|
523
547
|
callback_event,
|
|
524
|
-
|
|
548
|
+
payload,
|
|
525
549
|
meta=self._download_event_meta(params, "history", "progress"),
|
|
526
550
|
)
|
|
527
551
|
|
|
528
|
-
callback_func = callback if callback_event else None
|
|
529
|
-
variants = []
|
|
530
|
-
incrementally = params.get("incrementally")
|
|
531
|
-
if incrementally is not None:
|
|
532
|
-
variants.append((
|
|
533
|
-
(
|
|
534
|
-
params.get("stock_list", params.get("code_list", [])),
|
|
535
|
-
params.get("period", "1d"),
|
|
536
|
-
params.get("start_time", ""),
|
|
537
|
-
params.get("end_time", ""),
|
|
538
|
-
callback_func,
|
|
539
|
-
incrementally,
|
|
540
|
-
),
|
|
541
|
-
{},
|
|
542
|
-
))
|
|
543
|
-
variants.append((
|
|
544
|
-
(
|
|
545
|
-
params.get("stock_list", params.get("code_list", [])),
|
|
546
|
-
params.get("period", "1d"),
|
|
547
|
-
params.get("start_time", ""),
|
|
548
|
-
params.get("end_time", ""),
|
|
549
|
-
callback_func,
|
|
550
|
-
),
|
|
551
|
-
{},
|
|
552
|
-
))
|
|
553
552
|
if emit_lifecycle:
|
|
554
553
|
self._send_download_event(client_id, params, "history", "submitted", {
|
|
555
554
|
"stage": "submitted",
|
|
556
555
|
"message": "history download request submitted",
|
|
556
|
+
"total": total,
|
|
557
|
+
"batch_total": len(batches),
|
|
558
|
+
"batch_size": self._history_download_batch_size(params),
|
|
557
559
|
})
|
|
558
560
|
try:
|
|
559
|
-
|
|
561
|
+
incrementally = params.get("incrementally")
|
|
562
|
+
batch_total = len(batches)
|
|
563
|
+
for batch_index, batch in enumerate(batches):
|
|
564
|
+
batch_offset = completed
|
|
565
|
+
callback_func = (
|
|
566
|
+
(lambda data, offset=batch_offset, index=batch_index:
|
|
567
|
+
callback(data, offset, index, batch_total))
|
|
568
|
+
if callback_event else None
|
|
569
|
+
)
|
|
570
|
+
variants = []
|
|
571
|
+
if incrementally is not None:
|
|
572
|
+
variants.append((
|
|
573
|
+
(
|
|
574
|
+
batch,
|
|
575
|
+
params.get("period", "1d"),
|
|
576
|
+
params.get("start_time", ""),
|
|
577
|
+
params.get("end_time", ""),
|
|
578
|
+
callback_func,
|
|
579
|
+
incrementally,
|
|
580
|
+
),
|
|
581
|
+
{},
|
|
582
|
+
))
|
|
583
|
+
variants.append((
|
|
584
|
+
(
|
|
585
|
+
batch,
|
|
586
|
+
params.get("period", "1d"),
|
|
587
|
+
params.get("start_time", ""),
|
|
588
|
+
params.get("end_time", ""),
|
|
589
|
+
callback_func,
|
|
590
|
+
),
|
|
591
|
+
{},
|
|
592
|
+
))
|
|
593
|
+
self._call_variants(func, variants)
|
|
594
|
+
completed += len(batch)
|
|
595
|
+
if emit_lifecycle:
|
|
596
|
+
self._send_download_event(client_id, params, "history", "batch_done", {
|
|
597
|
+
"stage": "batch_done",
|
|
598
|
+
"finished": completed,
|
|
599
|
+
"total": total,
|
|
600
|
+
"batch_index": batch_index + 1,
|
|
601
|
+
"batch_total": batch_total,
|
|
602
|
+
"batch_size": len(batch),
|
|
603
|
+
})
|
|
604
|
+
gc.collect()
|
|
560
605
|
except Exception as e:
|
|
561
606
|
if emit_lifecycle:
|
|
562
607
|
self._send_download_event(client_id, params, "history", "error", {
|
|
@@ -567,9 +612,39 @@ class CfquantQmtBridge(object):
|
|
|
567
612
|
if emit_lifecycle:
|
|
568
613
|
self._send_download_event(client_id, params, "history", "request_done", {
|
|
569
614
|
"stage": "request_done",
|
|
570
|
-
"result":
|
|
615
|
+
"result": None,
|
|
616
|
+
"finished": completed,
|
|
617
|
+
"total": total,
|
|
571
618
|
})
|
|
572
|
-
return
|
|
619
|
+
return None
|
|
620
|
+
|
|
621
|
+
@staticmethod
|
|
622
|
+
def _history_download_batch_size(params):
|
|
623
|
+
params = params or {}
|
|
624
|
+
value = params.get("download_batch_size", params.get("batch_size"))
|
|
625
|
+
if value is None:
|
|
626
|
+
value = os.environ.get("CFQUANT_HISTORY_DOWNLOAD_BATCH_SIZE")
|
|
627
|
+
if value is None or str(value).strip() == "":
|
|
628
|
+
period = str(params.get("period") or "").lower()
|
|
629
|
+
if period == "tick":
|
|
630
|
+
return 20
|
|
631
|
+
if period == "1m":
|
|
632
|
+
return 50
|
|
633
|
+
if period.endswith("m") or period.endswith("h"):
|
|
634
|
+
return 100
|
|
635
|
+
return 200
|
|
636
|
+
try:
|
|
637
|
+
value = int(value)
|
|
638
|
+
except (TypeError, ValueError):
|
|
639
|
+
return 50
|
|
640
|
+
return max(1, value)
|
|
641
|
+
|
|
642
|
+
@classmethod
|
|
643
|
+
def _history_download_batches(cls, stock_list, params):
|
|
644
|
+
if not stock_list:
|
|
645
|
+
return [stock_list]
|
|
646
|
+
size = cls._history_download_batch_size(params)
|
|
647
|
+
return [stock_list[index:index + size] for index in range(0, len(stock_list), size)]
|
|
573
648
|
|
|
574
649
|
def _get_instrument_detail(self, params):
|
|
575
650
|
func = getattr(self.context, "get_instrument_detail")
|