cfquant 0.2.66__tar.gz → 0.2.67__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.67}/MANIFEST.in +5 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/PKG-INFO +3 -1
- {cfquant-0.2.66 → cfquant-0.2.67}/README.md +1 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/batch_orders.py +4 -1
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/order_identity.py +27 -1
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/order_meta.py +128 -3
- cfquant-0.2.67/cfquant/qmt_mac_agent.js +87 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/qmt_python_runtime.py +46 -23
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/qmt_update.py +17 -4
- cfquant-0.2.67/cfquant/qmt_vm_agent.js +109 -0
- cfquant-0.2.67/cfquant/qmt_vm_discovery.py +178 -0
- cfquant-0.2.67/cfquant/qmt_vm_runtime.py +105 -0
- cfquant-0.2.67/cfquant/qmt_vm_settings.py +69 -0
- cfquant-0.2.67/cfquant/qmt_vm_worker.py +164 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/quote_service.py +11 -5
- {cfquant-0.2.66 → cfquant-0.2.67}/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.67}/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.67}/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.67}/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.67/cfquant/tests/qmt_vm_native_fixture.js +118 -0
- cfquant-0.2.67/cfquant/tests/test_auto_login_app.py +363 -0
- cfquant-0.2.67/cfquant/tests/test_binding_python_runtime_browser.py +124 -0
- cfquant-0.2.67/cfquant/tests/test_binding_vm_browser.py +90 -0
- cfquant-0.2.67/cfquant/tests/test_qmt_python_runtime.py +268 -0
- cfquant-0.2.67/cfquant/tests/test_qmt_vm_discovery.py +161 -0
- cfquant-0.2.67/cfquant/tests/test_qmt_vm_native.py +41 -0
- cfquant-0.2.67/cfquant/tests/test_qmt_vm_runtime.py +167 -0
- cfquant-0.2.67/cfquant/tests/test_qmt_vm_settings.py +86 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_quote_service.py +153 -0
- cfquant-0.2.67/cfquant/tests/test_split_order_identity.py +263 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_trading_monitor_browser.py +131 -0
- cfquant-0.2.67/cfquant/tests/test_version_check.py +75 -0
- cfquant-0.2.67/cfquant/tests/test_version_check_browser.py +128 -0
- cfquant-0.2.67/cfquant/tools/THIRD_PARTY.md +16 -0
- cfquant-0.2.67/cfquant/tools/upx/COPYING +339 -0
- cfquant-0.2.67/cfquant/tools/upx/LICENSE +138 -0
- cfquant-0.2.67/cfquant/tools/upx/NEWS +603 -0
- cfquant-0.2.67/cfquant/tools/upx/README +117 -0
- cfquant-0.2.67/cfquant/tools/upx/THANKS.txt +74 -0
- cfquant-0.2.67/cfquant/tools/upx/upx-doc.html +845 -0
- cfquant-0.2.67/cfquant/tools/upx/upx-doc.txt +893 -0
- cfquant-0.2.67/cfquant/tools/upx/upx.1 +1015 -0
- cfquant-0.2.67/cfquant/tools/upx/upx.exe +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tx_trade_bridge.py +13 -46
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/version.py +2 -2
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant.egg-info/PKG-INFO +3 -1
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant.egg-info/SOURCES.txt +26 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant.egg-info/requires.txt +1 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant_web_server.py +190 -19
- {cfquant-0.2.66 → cfquant-0.2.67}/pyproject.toml +4 -3
- {cfquant-0.2.66 → cfquant-0.2.67}/qmt_scripts/CFQUANT_LITE.py +41 -6
- {cfquant-0.2.66 → cfquant-0.2.67}/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 +41 -6
- {cfquant-0.2.66 → cfquant-0.2.67}/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 +41 -6
- {cfquant-0.2.66 → cfquant-0.2.67}/requirements.txt +1 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/app.js +418 -81
- {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/index.html +35 -3
- {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/styles.css +18 -1
- {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/trading-monitor.js +90 -20
- 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.67}/LICENSE +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/LTtx/__init__.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/LTtx/tx/Config.txt +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/LTtx/tx/LTtx_server.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/LTtx/tx/__init__.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/LTtx/tx/tx.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/__init__.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/_editable_install.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/account_routing.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/algo.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/algo_broker.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/build_info.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/callback_dispatcher.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/cftrader.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/channels.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/cli.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/client.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/conditional_orders.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/config.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/diagnostic_context.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/diagnostics.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/flow_control.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/hub.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/level2.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/log_management.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/logging_i18n.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/management.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/monitor_notifications.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/monitor_performance.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/normal_bridge.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/pipe_bridge.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/pipe_client.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/pipe_hub.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/pipe_transport.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/planning_broker.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/position_sizing.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/protocol.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/qmt_binding_defaults.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/qmt_binding_settings.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/qmt_bridge.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/qmt_component_agent.js +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/qmt_component_login.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/qmt_credentials.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/qmt_dependencies.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/qmt_inner.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/qmt_inner_catalog.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/qmt_inner_runtime.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/qmt_native_files.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/qmt_native_worker.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/qmt_pipe_runtime.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/qmt_strategy_agent.js +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/qmt_strategy_deploy.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/qmt_strategy_package.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/qmt_strategy_recovery.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/qmt_strategy_runtime.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/quote_history.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/quote_leases.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/remote_adapter.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/remote_management.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/remote_supervisor.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/rpc_timing.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/runtime_report.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/shared_quotes.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/smart_orders.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/stock_connect.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/subscription_bridge.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/subscription_client.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/subscription_manager.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/subscription_store.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/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.67}/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.67}/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.67}/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.67}/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.67}/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.67}/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.67}/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.67}/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.67}/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.67}/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.67}/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.67}/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.67}/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.67}/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.67}/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.67}/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.67}/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.67}/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.67}/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.67}/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.67}/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.67}/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.67}/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.67}/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.67}/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.67}/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.67}/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.67}/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.67}/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.67}/cfquant/tests/_helpers.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/readme.md +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_account_query_timing.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_account_subscription_probe.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_activity_log_browser.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_advanced_parity.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_algo.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_algo_broker.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_api_tester_browser.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_api_tester_web.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_binding_delete_confirmation.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_binding_live_refresh_browser.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_binding_runtime_browser.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_callback_recovery.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_callback_requests.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_cftrader.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_cftrader_tutorial.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_cftrader_web.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_credit_compat.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_dataframe_encoding.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_diagnostic_context.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_json_serialization.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_latency_flow_control.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_lite_account_routing.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_lite_deployment_paths.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_lite_parity.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_lite_qmt_dispatch.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_local_trading_calendar.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_log_management.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_management.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_management_batch.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_market_data_fallback.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_market_timing.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_monitor_notifications.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_monitor_performance.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_navigation_browser.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_order_identity.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_order_time_callback.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_pipe_shutdown.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_project_updater.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_python_api_reference.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_qmt_auto_login_browser.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_qmt_auto_login_integration.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_qmt_binding_defaults.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_qmt_binding_settings.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_qmt_component_login.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_qmt_dependencies.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_qmt_fileless.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_qmt_inner.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_qmt_inner_reference.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_qmt_inner_web.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_qmt_native_files.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_qmt_native_reuse.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_qmt_native_worker.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_qmt_package_import.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_qmt_strategy_browser.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_qmt_strategy_capabilities.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_qmt_strategy_deploy.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_qmt_strategy_recovery.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_qmt_strategy_web.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_qmt_update.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_quote_latency.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_quote_latency_encoding.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_remote_management.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_remote_management_browser.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_remote_version_check.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_setup_qmt_prompt.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_shared_quotes.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_shared_quotes_generated.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_smart_orders.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_smart_orders_api.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_smart_orders_docs.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_startup_recovery.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_stock_connect.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_stock_connect_browser.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_strategy_recovery_api.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_subscription_congestion.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_subscription_persistence.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_table_conversion.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_trade_strategy_callback.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_trader_subscription_recovery.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_trading_dates.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_trading_monitor.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_tutorial_reader.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_tx_auto_import.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_tx_shutdown.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/trading_calendar.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/trading_monitor.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tx.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/xtconstant.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/xtdata.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/xttrader.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/xttype.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant.egg-info/dependency_links.txt +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant.egg-info/entry_points.txt +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant.egg-info/top_level.txt +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/cfquant_pipe_hub.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/qmt_scripts/CFQUANT.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/qmt_scripts/CFQUANT_CTYPE_ALL_LOWLAT.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/qmt_scripts/CFQUANT_TRADE_LOWLAT.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/qmt_scripts/__init__.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/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.67}/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.67}/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.67}/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.67}/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.67}/setup.cfg +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/__init__.py +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/activity-log.js +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/api-tester.js +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/avatars/amber-pulse.svg +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/avatars/copper-grid.svg +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/avatars/market-blue.svg +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/avatars/rose-circuit.svg +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/avatars/signal-green.svg +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/avatars/slate-wave.svg +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/avatars/teal-orbit.svg +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/avatars/violet-node.svg +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/cfquant-architecture.html +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/cftrader-api-data.js +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/docs.js +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/favicon.svg +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/image/qmt_deploy/image-10.png +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/image/qmt_deploy/image-11.png +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/image/qmt_deploy/image-12.png +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/image/qmt_deploy/image-13.png +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/image/qmt_deploy/image-14.png +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/image/qmt_deploy/image-15.png +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/image/qmt_deploy/image-16.png +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/image/qmt_deploy/image-17.png +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/image/qmt_deploy/image-18.png +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/image/qmt_deploy/image-19.png +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/image/qmt_deploy/image-20.png +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/image/qmt_deploy/image-21.png +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/image/qmt_deploy/image-4.png +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/image/qmt_deploy/image-5.png +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/image/qmt_deploy/image-6.png +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/image/qmt_deploy/image-7.png +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/image/qmt_deploy/image-8.png +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/image/qmt_deploy/image-9.png +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/navigation.js +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/python-api-data.js +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/python-api.js +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/qmt-inner-api-data.js +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/remote-management.js +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/smart-order-api-data.js +0 -0
- {cfquant-0.2.66 → cfquant-0.2.67}/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.67
|
|
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) |
|
|
@@ -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)
|
|
@@ -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
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Windows x64 iptypes.h layouts. Only buffers returned to this QMT's utils.dll
|
|
4
|
+
// are changed; the OS interfaces and all other processes retain their own data.
|
|
5
|
+
const macLayouts = {
|
|
6
|
+
GetAdaptersInfo: {bufferArg: 0, sizeArg: 1, next: 0, length: 404, address: 408, minimum: 416},
|
|
7
|
+
GetAdaptersAddresses: {bufferArg: 3, sizeArg: 4, next: 8, length: 88, address: 80, minimum: 92}
|
|
8
|
+
};
|
|
9
|
+
const macListeners = [];
|
|
10
|
+
const macCalls = {};
|
|
11
|
+
|
|
12
|
+
function rewriteMacList(buffer, size, layout, bytes) {
|
|
13
|
+
if (buffer.isNull() || size < layout.minimum || size > 16 * 1024 * 1024) {
|
|
14
|
+
throw new Error('Invalid adapter buffer bounds');
|
|
15
|
+
}
|
|
16
|
+
const end = buffer.add(size);
|
|
17
|
+
const seen = new Set();
|
|
18
|
+
const addresses = [];
|
|
19
|
+
let node = buffer;
|
|
20
|
+
while (!node.isNull()) {
|
|
21
|
+
if (seen.size >= 512 || seen.has(node.toString()) || node.compare(buffer) < 0 ||
|
|
22
|
+
node.add(layout.minimum).compare(end) > 0) {
|
|
23
|
+
throw new Error('Invalid adapter linked list');
|
|
24
|
+
}
|
|
25
|
+
seen.add(node.toString());
|
|
26
|
+
if (layout === macLayouts.GetAdaptersAddresses &&
|
|
27
|
+
(node.readU32() < layout.minimum || node.add(node.readU32()).compare(end) > 0)) {
|
|
28
|
+
throw new Error('Unexpected IP_ADAPTER_ADDRESSES length');
|
|
29
|
+
}
|
|
30
|
+
const length = node.add(layout.length).readU32();
|
|
31
|
+
if (length > 8) throw new Error('Invalid hardware address length');
|
|
32
|
+
if (length === 6) addresses.push(node.add(layout.address));
|
|
33
|
+
node = node.add(layout.next).readPointer();
|
|
34
|
+
}
|
|
35
|
+
// Validate the whole result before making changes.
|
|
36
|
+
for (const address of addresses) address.writeByteArray(bytes);
|
|
37
|
+
return addresses.length;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function disposeMac() {
|
|
41
|
+
for (const listener of macListeners) listener.detach();
|
|
42
|
+
macListeners.length = 0;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
rpc.exports.configuremac = function (directory, mac) {
|
|
46
|
+
if (Process.platform !== 'windows' || Process.arch !== 'x64') throw new Error('Windows x64 required');
|
|
47
|
+
if (macListeners.length) throw new Error('MAC listeners already installed');
|
|
48
|
+
if (!/^(?:[0-9A-F]{2}-){5}[0-9A-F]{2}$/.test(mac)) throw new Error('Invalid MAC format');
|
|
49
|
+
const bytes = mac.split('-').map(value => parseInt(value, 16));
|
|
50
|
+
if ((bytes[0] & 1) || bytes.every(value => value === 0)) throw new Error('Unicast MAC required');
|
|
51
|
+
const normalize = value => value.replace(/\//g, '\\').replace(/\\+$/, '').toLowerCase();
|
|
52
|
+
const expected = normalize(directory) + '\\utils.dll';
|
|
53
|
+
const iphlp = Process.getModuleByName('iphlpapi.dll');
|
|
54
|
+
iphlp.ensureInitialized();
|
|
55
|
+
try {
|
|
56
|
+
for (const [name, layout] of Object.entries(macLayouts)) {
|
|
57
|
+
macCalls[name] = 0;
|
|
58
|
+
macListeners.push(Interceptor.attach(iphlp.getExportByName(name), {
|
|
59
|
+
onEnter(args) {
|
|
60
|
+
const caller = Process.findModuleByAddress(this.returnAddress);
|
|
61
|
+
this.applies = Boolean(caller && normalize(caller.path) === expected);
|
|
62
|
+
if (!this.applies) return;
|
|
63
|
+
this.buffer = args[layout.bufferArg];
|
|
64
|
+
this.sizePointer = args[layout.sizeArg];
|
|
65
|
+
this.capacity = this.sizePointer.isNull() ? 0 : this.sizePointer.readU32();
|
|
66
|
+
},
|
|
67
|
+
onLeave(retval) {
|
|
68
|
+
if (!this.applies || retval.toUInt32() !== 0 || this.buffer.isNull() || this.sizePointer.isNull()) return;
|
|
69
|
+
try {
|
|
70
|
+
const size = Math.min(this.capacity, this.sizePointer.readU32());
|
|
71
|
+
const count = rewriteMacList(this.buffer, size, layout, bytes);
|
|
72
|
+
macCalls[name]++;
|
|
73
|
+
if (macCalls[name] === 1) send({event: 'mac_override', api: name, adapters: count});
|
|
74
|
+
} catch (error) {
|
|
75
|
+
send({event: 'mac_error', api: name, detail: String(error)});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}));
|
|
79
|
+
}
|
|
80
|
+
return {ready: true, apis: Object.keys(macLayouts)};
|
|
81
|
+
} catch (error) {
|
|
82
|
+
disposeMac();
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
rpc.exports.macstatus = function () { return {active: macListeners.length === 2, calls: macCalls}; };
|
|
87
|
+
rpc.exports.disposemac = disposeMac;
|
|
@@ -89,14 +89,10 @@ def _version_tuple(value):
|
|
|
89
89
|
different files. Unknown values deliberately return ``None`` so callers
|
|
90
90
|
can choose the conservative py36 runtime.
|
|
91
91
|
"""
|
|
92
|
-
|
|
93
|
-
if not
|
|
92
|
+
match = re.fullmatch(r"\s*(\d+)\.(\d+)\.(\d+)(?:\.(\d+))?\s*", str(value or ""))
|
|
93
|
+
if not match:
|
|
94
94
|
return None
|
|
95
|
-
|
|
96
|
-
numbers = [int(part) for part in parts[:4]]
|
|
97
|
-
except ValueError:
|
|
98
|
-
return None
|
|
99
|
-
return tuple((numbers + [0, 0, 0, 0])[:4])
|
|
95
|
+
return tuple(int(part or 0) for part in match.groups())
|
|
100
96
|
|
|
101
97
|
|
|
102
98
|
class QmtPythonRuntime:
|
|
@@ -288,6 +284,8 @@ class QmtPythonRuntime:
|
|
|
288
284
|
# binding on the native/default py36 runtime until it is verified.
|
|
289
285
|
if configured_runtime:
|
|
290
286
|
selected = configured_runtime if py312_supported or configured_runtime == "py36" else "py36"
|
|
287
|
+
elif not py312_supported:
|
|
288
|
+
selected = "py36"
|
|
291
289
|
inspected = {
|
|
292
290
|
runtime: self.inspect(runtime, refresh_remote=False)
|
|
293
291
|
for runtime in ("py36", "py312")
|
|
@@ -526,6 +524,9 @@ class QmtPythonRuntime:
|
|
|
526
524
|
account = str(account_id if account_id is not None else self.account_id) if (account_id is not None or self.account_id is not None) else None
|
|
527
525
|
if not account:
|
|
528
526
|
raise QmtPythonRuntimeError("account_id is required to switch QMT Python runtime")
|
|
527
|
+
self.validate_selection(runtime, account_id=account)
|
|
528
|
+
if not self.inspect(runtime, refresh_remote=False)["installed"]:
|
|
529
|
+
raise QmtPythonRuntimeError("QMT Python library is not installed: %s" % runtime)
|
|
529
530
|
config = self.qmt_dir / "userdata" / "users" / account / "Config.xml"
|
|
530
531
|
if not config.is_file():
|
|
531
532
|
raise QmtPythonRuntimeError("QMT account Config.xml not found: %s" % account)
|
|
@@ -533,13 +534,16 @@ class QmtPythonRuntime:
|
|
|
533
534
|
path_value = (self.bin_dir / "python312" if runtime == "py312" else self.bin_dir).as_posix()
|
|
534
535
|
if not re.search(r"<python\b[^>]*>", text):
|
|
535
536
|
raise QmtPythonRuntimeError("QMT account Config.xml has no python settings")
|
|
536
|
-
|
|
537
|
-
if updated == text:
|
|
537
|
+
if not re.search(r'<python\b[^>]*\blibs_path="[^"]*"', text):
|
|
538
538
|
raise QmtPythonRuntimeError("cannot update QMT python libs_path")
|
|
539
|
+
updated = re.sub(r'(<python\b[^>]*\blibs_path=")[^"]*(")', r'\g<1>' + path_value + r'\2', text, count=1)
|
|
539
540
|
if re.search(r'(<python\b[^>]*\bm_nPythonVersion=")[^"]*(")', updated):
|
|
540
541
|
updated = re.sub(r'(<python\b[^>]*\bm_nPythonVersion=")[^"]*(")', r'\g<1>' + ("1" if runtime == "py312" else "0") + r'\2', updated, count=1)
|
|
541
542
|
else:
|
|
542
543
|
updated = re.sub(r'(<python\b)', r'\1 m_nPythonVersion="%s"' % ("1" if runtime == "py312" else "0"), updated, count=1)
|
|
544
|
+
if updated == text:
|
|
545
|
+
return {"account_id": account, "runtime": runtime, "config": str(config),
|
|
546
|
+
"backup": None, "libs_path": path_value, "unchanged": True}
|
|
543
547
|
self.backup_dir.mkdir(parents=True, exist_ok=True)
|
|
544
548
|
backup = self.backup_dir / ("Config-%s-%s.xml" % (account, time.strftime("%Y%m%d-%H%M%S")))
|
|
545
549
|
shutil.copy2(config, backup)
|
|
@@ -589,31 +593,49 @@ class QmtPythonRuntime:
|
|
|
589
593
|
time.sleep(0.25)
|
|
590
594
|
raise QmtPythonRuntimeError("QMT did not start after runtime switch")
|
|
591
595
|
|
|
596
|
+
def validate_selection(self, runtime, *, account_id=None):
|
|
597
|
+
runtime = _canonical_runtime(runtime)
|
|
598
|
+
effective_account = account_id if account_id is not None else self.account_id
|
|
599
|
+
binding = self.binding_status(account_id=effective_account)
|
|
600
|
+
if runtime == "py312" and not binding["py312_supported"]:
|
|
601
|
+
raise QmtPythonRuntimeError(
|
|
602
|
+
"QMT %s only supports the default py36 runtime; py312 is unavailable"
|
|
603
|
+
% (binding.get("qmt_version") or "unknown"))
|
|
604
|
+
if effective_account:
|
|
605
|
+
if not binding["config_exists"]:
|
|
606
|
+
raise QmtPythonRuntimeError("QMT account Config.xml not found: %s; log in to QMT first" % effective_account)
|
|
607
|
+
text = Path(binding["config"]).read_text(encoding="utf-8")
|
|
608
|
+
if not re.search(r'<python\b[^>]*\blibs_path="[^"]*"', text):
|
|
609
|
+
raise QmtPythonRuntimeError("QMT account Config.xml has no Python library settings")
|
|
610
|
+
return binding
|
|
611
|
+
|
|
592
612
|
def download_and_switch(self, runtime="py312", *, restart=True, account_id=None,
|
|
593
613
|
progress_callback=None, cancel_event=None,
|
|
594
|
-
restart_callback=None):
|
|
614
|
+
restart_callback=None, reuse_installed=False):
|
|
595
615
|
runtime = _canonical_runtime(runtime)
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
616
|
+
binding = self.validate_selection(runtime, account_id=account_id)
|
|
617
|
+
reuse = bool(reuse_installed and binding["runtimes"][runtime]["installed"])
|
|
618
|
+
package, archive = None, None
|
|
619
|
+
if reuse:
|
|
620
|
+
_callback(progress_callback, QmtPythonProgress(runtime, "reusing", message="Python 库已安装,准备切换执行器"))
|
|
621
|
+
else:
|
|
622
|
+
package = self.fetch_package(runtime)
|
|
623
|
+
archive = self.download(runtime, package=package, progress_callback=progress_callback,
|
|
624
|
+
cancel_event=cancel_event)
|
|
625
|
+
if cancel_event and cancel_event.is_set():
|
|
626
|
+
raise QmtPythonRuntimeError("QMT Python runtime operation cancelled")
|
|
627
|
+
_callback(progress_callback, QmtPythonProgress(runtime, "activating", message="正在停止 QMT 并配置执行器"))
|
|
607
628
|
if restart and self._qmt_processes():
|
|
608
629
|
self._stop_for_switch()
|
|
609
630
|
elif not restart and self._qmt_processes():
|
|
610
631
|
raise QmtPythonRuntimeError(
|
|
611
632
|
"QMT is running; set restart=True before activating a Python runtime")
|
|
612
633
|
try:
|
|
613
|
-
installed = self.install(runtime, package=package, progress_callback=progress_callback)
|
|
634
|
+
installed = self.inspect(runtime, refresh_remote=False) if reuse else self.install(runtime, package=package, progress_callback=progress_callback)
|
|
614
635
|
switched = self.switch_account(runtime, account_id=account_id) if (account_id or self.account_id) else None
|
|
615
636
|
finally:
|
|
616
637
|
if restart:
|
|
638
|
+
_callback(progress_callback, QmtPythonProgress(runtime, "restarting", message="正在重新启动 QMT"))
|
|
617
639
|
if restart_callback:
|
|
618
640
|
restarted = restart_callback()
|
|
619
641
|
elif self._qmt_processes():
|
|
@@ -622,7 +644,8 @@ class QmtPythonRuntime:
|
|
|
622
644
|
restarted = self.restart_qmt()
|
|
623
645
|
else:
|
|
624
646
|
restarted = None
|
|
625
|
-
return {"runtime": runtime, "package": package.as_dict()
|
|
647
|
+
return {"runtime": runtime, "package": package.as_dict() if package else None,
|
|
648
|
+
"archive": str(archive) if archive else None, "reused_installed": reuse,
|
|
626
649
|
"installed": installed, "switched": switched, "restarted": restarted}
|
|
627
650
|
|
|
628
651
|
def _stop_for_switch(self):
|
|
@@ -135,7 +135,7 @@ def same_path(left, right):
|
|
|
135
135
|
|
|
136
136
|
|
|
137
137
|
class UpdateProcesses:
|
|
138
|
-
def __init__(self, directory, pid):
|
|
138
|
+
def __init__(self, directory, pid, launch_client=None):
|
|
139
139
|
import psutil
|
|
140
140
|
self.psutil = psutil
|
|
141
141
|
self.directory = Path(directory).resolve()
|
|
@@ -148,6 +148,7 @@ class UpdateProcesses:
|
|
|
148
148
|
self.owners = {} # PID -> (creation time, installer flag)
|
|
149
149
|
self.native = NativeDialogs()
|
|
150
150
|
self.launch = None
|
|
151
|
+
self.launch_client = launch_client
|
|
151
152
|
|
|
152
153
|
@property
|
|
153
154
|
def pid(self):
|
|
@@ -218,7 +219,18 @@ class UpdateProcesses:
|
|
|
218
219
|
new = matches[0]
|
|
219
220
|
if new.create_time() <= self.started:
|
|
220
221
|
raise QmtUpdateError("update_process_changed")
|
|
221
|
-
|
|
222
|
+
if self.launch_client is not None:
|
|
223
|
+
# Installer-started terminals have not received launch hooks.
|
|
224
|
+
new.terminate()
|
|
225
|
+
try:
|
|
226
|
+
new.wait(timeout=10)
|
|
227
|
+
except self.psutil.TimeoutExpired:
|
|
228
|
+
raise QmtUpdateError("update_restart_failed") from None
|
|
229
|
+
matches = []
|
|
230
|
+
if not matches and self.launch_client is not None:
|
|
231
|
+
self.launch = self.launch_client()
|
|
232
|
+
new = self.psutil.Process(int(self.launch['pid']))
|
|
233
|
+
elif not matches:
|
|
222
234
|
self.launch = subprocess.Popen([str(self.exe)], cwd=str(self.directory))
|
|
223
235
|
new = self.psutil.Process(self.launch.pid)
|
|
224
236
|
self.qmt = new
|
|
@@ -325,14 +337,15 @@ class UpdateMonitor:
|
|
|
325
337
|
|
|
326
338
|
def manage_qmt_startup(directory, pid, login, *, auto_update=False,
|
|
327
339
|
should_cancel=lambda: False, on_wait=lambda phase: None,
|
|
328
|
-
on_pid=lambda pid: None):
|
|
340
|
+
on_pid=lambda pid: None, launch_client=None):
|
|
329
341
|
"""Supervise update/restart and rerun login only for the resulting new PID.
|
|
330
342
|
|
|
331
343
|
login is None for a client with its own automatic login; no Qt injection or
|
|
332
344
|
credential access happens in that mode. The worker continues observing the
|
|
333
345
|
updater after login so its late dialogs cannot be left blocking the client.
|
|
334
346
|
"""
|
|
335
|
-
processes = UpdateProcesses(directory, pid)
|
|
347
|
+
processes = (UpdateProcesses(directory, pid, launch_client=launch_client) if launch_client is not None
|
|
348
|
+
else UpdateProcesses(directory, pid))
|
|
336
349
|
monitor = UpdateMonitor(processes, auto_update, should_cancel, on_wait, on_pid)
|
|
337
350
|
while not should_cancel():
|
|
338
351
|
try:
|