cfquant 0.2.68__tar.gz → 0.2.70__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.68 → cfquant-0.2.70}/PKG-INFO +1 -1
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/qmt_python_runtime.py +6 -1
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/qmt_strategy_deploy.py +11 -3
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_binding_python_runtime_browser.py +73 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_qmt_auto_login_integration.py +32 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_qmt_python_runtime.py +139 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_qmt_strategy_deploy.py +19 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/version.py +2 -2
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant.egg-info/PKG-INFO +1 -1
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant_web_server.py +163 -40
- {cfquant-0.2.68 → cfquant-0.2.70}/pyproject.toml +1 -1
- {cfquant-0.2.68 → cfquant-0.2.70}/qmt_scripts/CFQUANT_LITE.py +1 -1
- {cfquant-0.2.68 → cfquant-0.2.70}/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 +1 -1
- {cfquant-0.2.68 → cfquant-0.2.70}/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 +1 -1
- {cfquant-0.2.68 → cfquant-0.2.70}/web_dashboard/app.js +66 -44
- {cfquant-0.2.68 → cfquant-0.2.70}/web_dashboard/index.html +2 -2
- {cfquant-0.2.68 → cfquant-0.2.70}/LICENSE +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/LTtx/__init__.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/LTtx/tx/Config.txt +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/LTtx/tx/LTtx_server.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/LTtx/tx/__init__.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/LTtx/tx/tx.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/MANIFEST.in +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/README.md +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/__init__.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/_editable_install.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/account_routing.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/algo.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/algo_broker.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/batch_orders.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/build_info.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/callback_dispatcher.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/cftrader.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/channels.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/cli.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/client.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/conditional_orders.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/config.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/diagnostic_context.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/diagnostics.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/flow_control.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/hub.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/level2.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/log_management.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/logging_i18n.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/management.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/monitor_notifications.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/monitor_performance.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/normal_bridge.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/notify.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/order_identity.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/order_meta.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/pipe_bridge.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/pipe_client.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/pipe_hub.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/pipe_transport.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/planning_broker.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/position_sizing.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/protocol.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/qmt_binding_defaults.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/qmt_binding_settings.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/qmt_bridge.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/qmt_component_agent.js +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/qmt_component_login.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/qmt_credentials.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/qmt_dependencies.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/qmt_inner.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/qmt_inner_catalog.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/qmt_inner_runtime.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/qmt_mac_agent.js +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/qmt_native_files.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/qmt_native_worker.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/qmt_pipe_runtime.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/qmt_strategy_agent.js +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/qmt_strategy_package.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/qmt_strategy_recovery.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/qmt_strategy_runtime.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/qmt_update.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/qmt_vm_agent.js +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/qmt_vm_discovery.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/qmt_vm_runtime.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/qmt_vm_settings.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/qmt_vm_worker.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/quote_history.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/quote_leases.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/quote_service.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/remote_adapter.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/remote_management.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/remote_supervisor.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/rpc_timing.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/runtime_report.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/shared_quotes.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/smart_orders.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/stock_connect.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/subscription_bridge.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/subscription_client.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/subscription_manager.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/subscription_store.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/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.68 → cfquant-0.2.70}/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.68 → cfquant-0.2.70}/cfquant/tests/12_/350/256/242/345/215/225/345/244/207/346/263/250/346/265/213/350/257/225.py" +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/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.68 → cfquant-0.2.70}/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.68 → cfquant-0.2.70}/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.68 → cfquant-0.2.70}/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.68 → cfquant-0.2.70}/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.68 → cfquant-0.2.70}/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.68 → cfquant-0.2.70}/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.68 → cfquant-0.2.70}/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.68 → cfquant-0.2.70}/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" +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/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.68 → cfquant-0.2.70}/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.68 → cfquant-0.2.70}/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.68 → cfquant-0.2.70}/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.68 → cfquant-0.2.70}/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.68 → cfquant-0.2.70}/cfquant/tests/26_/345/205/250/346/216/250/350/256/242/351/230/205/346/265/213/350/257/225.py" +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/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" +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/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.68 → cfquant-0.2.70}/cfquant/tests/29_Level2/350/241/214/346/203/205/346/265/213/350/257/225.py" +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/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.68 → cfquant-0.2.70}/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.68 → cfquant-0.2.70}/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.68 → cfquant-0.2.70}/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.68 → cfquant-0.2.70}/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.68 → cfquant-0.2.70}/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.68 → cfquant-0.2.70}/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.68 → cfquant-0.2.70}/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.68 → cfquant-0.2.70}/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.68 → cfquant-0.2.70}/cfquant/tests/6_/345/233/236/350/260/203/346/265/213/350/257/225.py" +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/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.68 → cfquant-0.2.70}/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.68 → cfquant-0.2.70}/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.68 → cfquant-0.2.70}/cfquant/tests/_helpers.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/qmt_vm_native_fixture.js +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/readme.md +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_account_query_timing.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_account_subscription_probe.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_activity_log_browser.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_advanced_parity.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_algo.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_algo_broker.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_api_tester_browser.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_api_tester_web.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_auto_login_app.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_binding_delete_confirmation.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_binding_live_refresh_browser.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_binding_runtime_browser.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_binding_vm_browser.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_binding_webhook_browser.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_callback_recovery.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_callback_requests.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_cftrader.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_cftrader_tutorial.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_cftrader_web.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_credit_compat.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_dataframe_encoding.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_diagnostic_context.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_history_download_memory.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_json_serialization.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_latency_flow_control.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_lite_account_routing.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_lite_deployment_paths.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_lite_parity.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_lite_qmt_dispatch.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_local_trading_calendar.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_log_management.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_management.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_management_batch.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_market_data_fallback.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_market_timing.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_monitor_notifications.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_monitor_performance.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_navigation_browser.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_notify.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_order_identity.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_order_time_callback.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_pipe_shutdown.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_project_updater.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_python_api_reference.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_qmt_auto_login_browser.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_qmt_binding_defaults.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_qmt_binding_settings.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_qmt_component_login.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_qmt_dependencies.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_qmt_fileless.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_qmt_inner.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_qmt_inner_reference.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_qmt_inner_web.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_qmt_native_files.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_qmt_native_reuse.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_qmt_native_worker.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_qmt_package_import.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_qmt_strategy_browser.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_qmt_strategy_capabilities.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_qmt_strategy_recovery.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_qmt_strategy_web.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_qmt_update.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_qmt_vm_discovery.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_qmt_vm_native.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_qmt_vm_runtime.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_qmt_vm_settings.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_quote_latency.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_quote_latency_encoding.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_quote_service.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_remote_management.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_remote_management_browser.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_remote_version_check.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_setup_qmt_prompt.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_shared_quotes.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_shared_quotes_generated.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_smart_orders.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_smart_orders_api.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_smart_orders_docs.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_split_order_identity.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_startup_recovery.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_stock_connect.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_stock_connect_browser.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_strategy_recovery_api.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_subscription_congestion.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_subscription_persistence.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_table_conversion.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_trade_strategy_callback.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_trader_subscription_recovery.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_trading_dates.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_trading_monitor.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_trading_monitor_browser.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_tutorial_reader.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_tx_auto_import.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_tx_shutdown.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_version_check.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tests/test_version_check_browser.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tools/THIRD_PARTY.md +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tools/upx/COPYING +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tools/upx/LICENSE +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tools/upx/NEWS +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tools/upx/README +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tools/upx/THANKS.txt +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tools/upx/upx-doc.html +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tools/upx/upx-doc.txt +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tools/upx/upx.1 +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tools/upx/upx.exe +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/trading_calendar.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/trading_monitor.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tx.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/tx_trade_bridge.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/webhook.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/xtconstant.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/xtdata.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/xttrader.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant/xttype.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant.egg-info/SOURCES.txt +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant.egg-info/dependency_links.txt +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant.egg-info/entry_points.txt +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant.egg-info/requires.txt +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant.egg-info/top_level.txt +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/cfquant_pipe_hub.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/qmt_scripts/CFQUANT.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/qmt_scripts/CFQUANT_CTYPE_ALL_LOWLAT.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/qmt_scripts/CFQUANT_TRADE_LOWLAT.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/qmt_scripts/__init__.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/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.68 → cfquant-0.2.70}/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.68 → cfquant-0.2.70}/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.68 → cfquant-0.2.70}/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.68 → cfquant-0.2.70}/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.68 → cfquant-0.2.70}/requirements.txt +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/setup.cfg +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/web_dashboard/__init__.py +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/web_dashboard/activity-log.js +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/web_dashboard/api-tester.js +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/web_dashboard/avatars/amber-pulse.svg +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/web_dashboard/avatars/copper-grid.svg +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/web_dashboard/avatars/market-blue.svg +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/web_dashboard/avatars/rose-circuit.svg +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/web_dashboard/avatars/signal-green.svg +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/web_dashboard/avatars/slate-wave.svg +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/web_dashboard/avatars/teal-orbit.svg +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/web_dashboard/avatars/violet-node.svg +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/web_dashboard/cfquant-architecture.html +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/web_dashboard/cftrader-api-data.js +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/web_dashboard/docs.js +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/web_dashboard/favicon.svg +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/web_dashboard/image/qmt_deploy/image-10.png +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/web_dashboard/image/qmt_deploy/image-11.png +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/web_dashboard/image/qmt_deploy/image-12.png +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/web_dashboard/image/qmt_deploy/image-13.png +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/web_dashboard/image/qmt_deploy/image-14.png +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/web_dashboard/image/qmt_deploy/image-15.png +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/web_dashboard/image/qmt_deploy/image-16.png +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/web_dashboard/image/qmt_deploy/image-17.png +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/web_dashboard/image/qmt_deploy/image-18.png +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/web_dashboard/image/qmt_deploy/image-19.png +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/web_dashboard/image/qmt_deploy/image-20.png +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/web_dashboard/image/qmt_deploy/image-21.png +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/web_dashboard/image/qmt_deploy/image-4.png +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/web_dashboard/image/qmt_deploy/image-5.png +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/web_dashboard/image/qmt_deploy/image-6.png +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/web_dashboard/image/qmt_deploy/image-7.png +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/web_dashboard/image/qmt_deploy/image-8.png +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/web_dashboard/image/qmt_deploy/image-9.png +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/web_dashboard/navigation.js +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/web_dashboard/notify-api-data.js +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/web_dashboard/python-api-data.js +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/web_dashboard/python-api.js +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/web_dashboard/qmt-inner-api-data.js +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/web_dashboard/remote-management.js +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/web_dashboard/smart-order-api-data.js +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/web_dashboard/styles.css +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/web_dashboard/trading-monitor.js +0 -0
- {cfquant-0.2.68 → cfquant-0.2.70}/web_dashboard/vendor/mermaid.min.js +0 -0
|
@@ -655,7 +655,12 @@ class QmtPythonRuntime:
|
|
|
655
655
|
if (account_id or self.account_id) else None
|
|
656
656
|
)
|
|
657
657
|
finally:
|
|
658
|
-
|
|
658
|
+
# A managed Web binding can supply a bootstrap callback that starts
|
|
659
|
+
# QMT, waits for the first login to create Config.xml, and then
|
|
660
|
+
# performs the final runtime switch/restart. Keep the standalone
|
|
661
|
+
# API's old safe behavior (leave a brand-new, unconfigured QMT
|
|
662
|
+
# stopped) when no callback was supplied.
|
|
663
|
+
if restart and (not deferred_account_config or was_running or restart_callback):
|
|
659
664
|
_callback(progress_callback, QmtPythonProgress(runtime, "restarting", message="正在重新启动 QMT"))
|
|
660
665
|
if restart_callback:
|
|
661
666
|
restarted = restart_callback()
|
|
@@ -82,7 +82,7 @@ STATES = {
|
|
|
82
82
|
"waiting_start": "QMT 已启动,等待托管策略启动及通道连接",
|
|
83
83
|
"waiting_manual_start": "QMT 已启动,自启未开启,请在模型交易中运行托管策略",
|
|
84
84
|
"running": "托管策略已运行",
|
|
85
|
-
"waiting_account": "
|
|
85
|
+
"waiting_account": "等待 QMT 登录生成账号绑定键;首次登录后请正常退出 QMT,再重新启动以完成策略部署。仍无法识别时再补充模型账号 Key",
|
|
86
86
|
"configured": "模型已配置,等待下次启动 QMT 生效",
|
|
87
87
|
"disabled": "自动运行已关闭,托管策略已禁用",
|
|
88
88
|
"error": "策略部署失败",
|
|
@@ -915,15 +915,16 @@ class QmtStrategyManager:
|
|
|
915
915
|
self._save()
|
|
916
916
|
|
|
917
917
|
def prepare_for_start(self, directory):
|
|
918
|
-
"""Finish
|
|
918
|
+
"""Finish offline deployment, or allow login to supply a missing key."""
|
|
919
919
|
root = qmt_root(directory)
|
|
920
|
+
waiting_account = []
|
|
920
921
|
with self.lock:
|
|
921
922
|
if self.load_error:
|
|
922
923
|
raise ValueError(self.load_error)
|
|
923
924
|
jobs = [job for job in self.jobs.values()
|
|
924
925
|
if os.path.normcase(str(qmt_root(job["root"]))) == os.path.normcase(str(root))]
|
|
925
926
|
if not jobs:
|
|
926
|
-
return
|
|
927
|
+
return {"waiting_account": waiting_account}
|
|
927
928
|
if self.process_checker(root):
|
|
928
929
|
raise RuntimeError("QMT 尚未完全退出,无法完成启动前策略部署")
|
|
929
930
|
try:
|
|
@@ -957,6 +958,12 @@ class QmtStrategyManager:
|
|
|
957
958
|
job["deploy_retry_after"] = time.time() + min(10.0, 2.0 * count)
|
|
958
959
|
self._state(job, "error", str(error))
|
|
959
960
|
raise
|
|
961
|
+
if job.get("state") == "waiting_account":
|
|
962
|
+
# QMT creates its account authorization on first login.
|
|
963
|
+
# Do not fabricate a model key or make login depend on
|
|
964
|
+
# the model that can only be prepared after that login.
|
|
965
|
+
waiting_account.append(job["account_id"])
|
|
966
|
+
continue
|
|
960
967
|
if (job.get("state") not in ("configured", "waiting_import", "disabled")
|
|
961
968
|
or (job.get("enabled") and any(
|
|
962
969
|
not role.get("model_prepared") or role.get("reimport_required")
|
|
@@ -965,6 +972,7 @@ class QmtStrategyManager:
|
|
|
965
972
|
(job.get("message") or job.get("state", "")))
|
|
966
973
|
finally:
|
|
967
974
|
self._save()
|
|
975
|
+
return {"waiting_account": waiting_account}
|
|
968
976
|
|
|
969
977
|
def _advance(self, job):
|
|
970
978
|
root = qmt_root(job["root"])
|
|
@@ -81,6 +81,15 @@ def test_selected_executor_is_applied_through_existing_job_until_restart_finishe
|
|
|
81
81
|
expect(button).to_be_disabled()
|
|
82
82
|
assert not page.locator("#bindingPythonRuntimeProgressBar").evaluate("node => node.classList.contains('is-done')")
|
|
83
83
|
expect(page.locator("#bindingPythonRuntimeProgressMeta")).to_contain_text("等待切换")
|
|
84
|
+
for phase in ("extract", "activating", "restarting"):
|
|
85
|
+
page.evaluate("""phase => bindingPythonRuntimeProgressEvent({
|
|
86
|
+
meta: {job_id: state.bindingPythonRuntimeJobId, stage: phase},
|
|
87
|
+
data: {phase, percent: 0, eta: null}
|
|
88
|
+
})""", phase)
|
|
89
|
+
expect(page.locator("#bindingPythonRuntimeProgressMeta")).not_to_contain_text("0%")
|
|
90
|
+
expect(page.locator("#bindingPythonRuntimeProgressMeta")).not_to_contain_text("剩余约 0")
|
|
91
|
+
assert page.locator("#bindingPythonRuntimeProgressBar").evaluate("node => node.getBoundingClientRect().width > 0")
|
|
92
|
+
assert page.evaluate("!!state.bindingPythonRuntimeJobId")
|
|
84
93
|
status["binding"].update(selected_runtime="py312", configured_runtime="py312", state="ready")
|
|
85
94
|
status["binding"]["runtimes"]["py312"]["installed"] = True
|
|
86
95
|
page.evaluate("""bindingPythonRuntimeProgressEvent({
|
|
@@ -95,6 +104,45 @@ def test_selected_executor_is_applied_through_existing_job_until_restart_finishe
|
|
|
95
104
|
assert not errors
|
|
96
105
|
|
|
97
106
|
|
|
107
|
+
def test_lost_job_record_releases_busy_state_without_claiming_success(page, frontend_url):
|
|
108
|
+
errors = open_runtime(page, frontend_url, runtime_status(config_exists=False))
|
|
109
|
+
page.route("**/api/qmt/python-runtime/download", lambda route: route.fulfill(json={"ok": True, "data": {"state": "queued"}}))
|
|
110
|
+
page.route("**/api/qmt/python-runtime/jobs?*", lambda route: route.fulfill(status=404, json={"ok": False, "error": "job not found"}))
|
|
111
|
+
page.locator("#bindingPythonRuntimeDownloadBtn").click()
|
|
112
|
+
page.wait_for_function("!state.bindingPythonRuntimeJobId && state.bindingPythonRuntimeProgress?.meta?.stage === 'error'")
|
|
113
|
+
expect(page.locator("#bindingPythonRuntimeProgressText")).to_contain_text("记录已失效")
|
|
114
|
+
expect(page.locator("#bindingPythonRuntimeDownloadBtn")).to_be_enabled()
|
|
115
|
+
assert not errors
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def test_save_rechecks_completed_runtime_job_even_without_progress_notification(page, frontend_url):
|
|
119
|
+
errors = open_runtime(page, frontend_url, runtime_status(config_exists=False))
|
|
120
|
+
page.route("**/api/qmt/python-runtime/jobs?*", lambda route: route.fulfill(json={
|
|
121
|
+
"ok": True, "data": {"state": "done", "result": {"account_config_required": True}}}))
|
|
122
|
+
saved = []
|
|
123
|
+
def save(route):
|
|
124
|
+
body = route.request.post_data_json
|
|
125
|
+
saved.append(body)
|
|
126
|
+
row = dict(body, account_key="TEST_ONLY|STOCK|test", bridge_id="test")
|
|
127
|
+
route.fulfill(json={"ok": True, "data": {
|
|
128
|
+
"account": row, "account_configs": {row["account_key"]: row}, "account_pairs": {},
|
|
129
|
+
"bridges": {"test": {"name": "Test"}}, "qmt_strategy_deploy": {"enabled": False},
|
|
130
|
+
"qmt_auto_login": {"enabled": False}}})
|
|
131
|
+
page.route("**/api/account-config", save)
|
|
132
|
+
page.locator("#bindingQmtAutoLogin").uncheck()
|
|
133
|
+
page.evaluate("""() => {
|
|
134
|
+
fillQmtStrategySettings('binding', {enabled: false});
|
|
135
|
+
state.bindingPythonRuntimeJobId = 'missed-done';
|
|
136
|
+
state.bindingPythonRuntimeJobContext = bindingPythonRuntimeContextKey();
|
|
137
|
+
state.bindingPythonRuntimeJobRuntime = 'py36';
|
|
138
|
+
}""")
|
|
139
|
+
page.locator('#bindingForm [type="submit"]').click()
|
|
140
|
+
expect(page.locator("#bindingDialogOverlay")).not_to_be_visible()
|
|
141
|
+
assert len(saved) == 1
|
|
142
|
+
assert not page.evaluate("state.bindingPythonRuntimeJobId")
|
|
143
|
+
assert not errors
|
|
144
|
+
|
|
145
|
+
|
|
98
146
|
def test_directory_change_invalidates_options_and_discards_late_response(page, frontend_url):
|
|
99
147
|
errors = open_runtime(page, frontend_url, runtime_status())
|
|
100
148
|
pending = []
|
|
@@ -129,3 +177,28 @@ def test_missing_account_config_shows_capabilities_but_requires_login(page, fron
|
|
|
129
177
|
page.wait_for_function("state.bindingPythonRuntimeJobId")
|
|
130
178
|
assert submitted[0]["allow_missing_account_config"] is True
|
|
131
179
|
assert not errors
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
@pytest.mark.parametrize("socket_throws", [False, True], ids=["socket-disconnected", "socket-unavailable"])
|
|
183
|
+
def test_poll_completion_before_first_login_unlocks_binding(page, frontend_url, socket_throws):
|
|
184
|
+
status = runtime_status(config_exists=False)
|
|
185
|
+
errors = open_runtime(page, frontend_url, status)
|
|
186
|
+
submitted = []
|
|
187
|
+
page.route("**/api/qmt/python-runtime/download", lambda route: (
|
|
188
|
+
submitted.append(route.request.post_data_json),
|
|
189
|
+
route.fulfill(json={"ok": True, "data": {"state": "queued"}})))
|
|
190
|
+
# The last progress event is not a terminal event. HTTP state/result must
|
|
191
|
+
# recover completion even if the WebSocket notification never arrived.
|
|
192
|
+
page.route("**/api/qmt/python-runtime/jobs?*", lambda route: route.fulfill(json={
|
|
193
|
+
"ok": True, "data": {"state": "done", "progress": {"phase": "installed", "percent": 100},
|
|
194
|
+
"result": {"account_config_required": True, "restarted": None}}}))
|
|
195
|
+
if socket_throws:
|
|
196
|
+
page.evaluate("() => { window.WebSocket = function() { throw new Error('WebSocket unavailable'); }; }")
|
|
197
|
+
page.locator("#bindingPythonRuntimeDownloadBtn").click()
|
|
198
|
+
page.wait_for_function("state.bindingPythonRuntimeJobId === '' && state.bindingPythonRuntimeProgress?.meta?.stage === 'done'")
|
|
199
|
+
assert len(submitted) == 1
|
|
200
|
+
assert page.evaluate("state.bindingPythonRuntimeDeferred")
|
|
201
|
+
assert not page.evaluate("state.bindingPythonRuntimeChoiceDirty")
|
|
202
|
+
expect(page.locator("#bindingPythonRuntimeProgressMeta")).to_contain_text("100%")
|
|
203
|
+
expect(page.locator("#bindingPythonRuntimeProgressText")).to_contain_text("登录")
|
|
204
|
+
assert not errors
|
|
@@ -8,6 +8,7 @@ from types import SimpleNamespace
|
|
|
8
8
|
import pytest
|
|
9
9
|
|
|
10
10
|
from cfquant.tests.test_qmt_strategy_web import web_config, login_credentials
|
|
11
|
+
from cfquant.tests.test_qmt_strategy_deploy import deployment
|
|
11
12
|
from cfquant.tests.test_management import service
|
|
12
13
|
from cfquant.qmt_credentials import QmtCredentialStore
|
|
13
14
|
from cfquant.trading_calendar import is_trading_day
|
|
@@ -286,6 +287,37 @@ def test_failed_offline_deployment_blocks_launch_and_login(web_config, monkeypat
|
|
|
286
287
|
assert not result["started"] and not result["configured"]
|
|
287
288
|
|
|
288
289
|
|
|
290
|
+
@pytest.mark.parametrize("auto_fill", [True, False])
|
|
291
|
+
@pytest.mark.parametrize("restart", [True, False])
|
|
292
|
+
def test_first_login_without_model_key_launches_and_reports_pending_deployment(
|
|
293
|
+
web_config, deployment, monkeypatch, tmp_path, auto_fill, restart):
|
|
294
|
+
web, _ = web_config
|
|
295
|
+
manager, row, infos, running, root = deployment
|
|
296
|
+
(root / "config" / "indexUserConfig.xml").write_text(
|
|
297
|
+
'<ICUserConfigFile><FormulaCatalog/><strategyTrade/></ICUserConfigFile>', encoding="utf-8")
|
|
298
|
+
manager.configure(row, infos)
|
|
299
|
+
row["qmt_auto_login"] = {"enabled": True, "auto_fill_credentials": auto_fill, "auto_update": True}
|
|
300
|
+
monkeypatch.setenv("CFQUANT_QMT_AUTO_START", "saved")
|
|
301
|
+
monkeypatch.setattr(web, "QMT_AUTO_LOGIN_STATE_FILE", str(tmp_path / "sessions.json"))
|
|
302
|
+
monkeypatch.setattr(web, "QMT_COMPONENT_JOBS", {})
|
|
303
|
+
monkeypatch.setattr(web, "QMT_STRATEGIES", manager)
|
|
304
|
+
paths = {"bin_dir": str(root / "bin.x64"), "exe_path": "fixture.exe"}
|
|
305
|
+
monkeypatch.setattr(web, "_qmt_auto_login_paths", lambda _: paths)
|
|
306
|
+
monkeypatch.setattr(web, "_qmt_component_login_preflight", lambda *a: None)
|
|
307
|
+
monkeypatch.setattr(web, "_qmt_auto_login_processes", lambda *a, **k: [])
|
|
308
|
+
monkeypatch.setattr(web, "_qmt_auto_login_stop_xtitclient", lambda *a, **k: {})
|
|
309
|
+
calls = []
|
|
310
|
+
monkeypatch.setattr(web, "_qmt_auto_login_start_xtitclient", lambda *a, **k: calls.append("start") or {"pid": 456})
|
|
311
|
+
monkeypatch.setattr(web, "_qmt_queue_component_login", lambda *a: calls.append("login"))
|
|
312
|
+
result = web.qmt_auto_login_apply_for_account(row, restart=restart)
|
|
313
|
+
assert result["configured"] and not result.get("error")
|
|
314
|
+
assert result["strategy_deploy_deferred"]
|
|
315
|
+
assert calls == ["start", "login"]
|
|
316
|
+
session = web._qmt_auto_login_saved_session(row)
|
|
317
|
+
assert session["strategy_deploy_deferred"] and not session["error"]
|
|
318
|
+
assert "首次登录" in session["message"]
|
|
319
|
+
|
|
320
|
+
|
|
289
321
|
def test_worker_records_success_and_supplies_saved_password_only_on_request(web_config, monkeypatch, tmp_path):
|
|
290
322
|
import cfquant.qmt_component_login as component
|
|
291
323
|
web, config = web_config
|
|
@@ -250,6 +250,36 @@ def test_missing_account_config_can_install_runtime_before_first_login(tmp_path,
|
|
|
250
250
|
assert not (root / "userdata" / "users" / "55009948" / "Config.xml").exists()
|
|
251
251
|
|
|
252
252
|
|
|
253
|
+
def test_missing_account_config_invokes_managed_restart_callback(tmp_path, monkeypatch):
|
|
254
|
+
root = tmp_path / "qmt"
|
|
255
|
+
(root / "bin.x64").mkdir(parents=True)
|
|
256
|
+
manager = QmtPythonRuntime(root, account_id="55009948")
|
|
257
|
+
archive = manager.download_dir / "Python36-managed-bootstrap.zip"
|
|
258
|
+
archive.parent.mkdir(parents=True)
|
|
259
|
+
with zipfile.ZipFile(archive, "w") as bundle:
|
|
260
|
+
bundle.writestr("python36.dll", "new-dll")
|
|
261
|
+
bundle.writestr("Lib/site.py", "# test")
|
|
262
|
+
package = _package("py36", archive)
|
|
263
|
+
monkeypatch.setattr(manager, "fetch_package", lambda runtime: package)
|
|
264
|
+
monkeypatch.setattr(manager, "download", lambda *args, **kwargs: archive)
|
|
265
|
+
monkeypatch.setattr(manager, "_qmt_processes", lambda: [])
|
|
266
|
+
calls = []
|
|
267
|
+
|
|
268
|
+
def restart_callback():
|
|
269
|
+
calls.append("restart")
|
|
270
|
+
return {"started": True, "bootstrap_completed": True}
|
|
271
|
+
|
|
272
|
+
result = manager.download_and_switch(
|
|
273
|
+
"py36", account_id="55009948", restart=True,
|
|
274
|
+
allow_missing_account_config=True, restart_callback=restart_callback,
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
assert calls == ["restart"]
|
|
278
|
+
assert result["account_config_required"] is True
|
|
279
|
+
assert result["restarted"]["bootstrap_completed"] is True
|
|
280
|
+
assert not (root / "userdata" / "users" / "55009948" / "Config.xml").exists()
|
|
281
|
+
|
|
282
|
+
|
|
253
283
|
def test_runtime_status_without_account_still_exposes_capabilities(tmp_path, monkeypatch):
|
|
254
284
|
import cfquant_web_server as web
|
|
255
285
|
manager = runtime_fixture(tmp_path)
|
|
@@ -307,3 +337,112 @@ def test_runtime_job_rejects_unsupported_selection_before_scheduling(tmp_path, m
|
|
|
307
337
|
with pytest.raises(QmtPythonRuntimeError, match="only supports"):
|
|
308
338
|
web.start_qmt_python_runtime_job({"qmt_dir": str(manager.qmt_dir), "account_id": "TEST_ONLY", "runtime": "py312"})
|
|
309
339
|
assert not jobs
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def test_runtime_job_completion_survives_notification_failure(tmp_path, monkeypatch):
|
|
343
|
+
import cfquant_web_server as web
|
|
344
|
+
manager = runtime_fixture(tmp_path)
|
|
345
|
+
jobs = {}
|
|
346
|
+
monkeypatch.setattr(web, "QMT_PYTHON_RUNTIME_JOBS", jobs)
|
|
347
|
+
monkeypatch.setattr(web.threading.Thread, "start", lambda self: None)
|
|
348
|
+
def unavailable(*args, **kwargs):
|
|
349
|
+
raise RuntimeError("callback transport unavailable")
|
|
350
|
+
monkeypatch.setattr(web, "emit_download_progress", unavailable)
|
|
351
|
+
result = {"account_config_required": True, "installed": {"installed": True}, "restarted": None}
|
|
352
|
+
def switch(self, runtime, **kwargs):
|
|
353
|
+
kwargs["progress_callback"]({"phase": "download", "percent": 50})
|
|
354
|
+
kwargs["progress_callback"]({"phase": "installed", "percent": 100})
|
|
355
|
+
return result
|
|
356
|
+
monkeypatch.setattr(QmtPythonRuntime, "download_and_switch", switch)
|
|
357
|
+
body = {"qmt_dir": str(manager.qmt_dir), "account_id": "TEST_ONLY", "runtime": "py36", "job_id": "notify-failure"}
|
|
358
|
+
web.start_qmt_python_runtime_job(body)
|
|
359
|
+
web._run_qmt_python_runtime_job(body["job_id"], body)
|
|
360
|
+
job = web._qmt_python_job_snapshot(body["job_id"])
|
|
361
|
+
assert job["state"] == "done"
|
|
362
|
+
assert job["result"] == result
|
|
363
|
+
assert job["progress"]["phase"] == "done"
|
|
364
|
+
assert job["progress"]["percent"] == 100
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def test_runtime_job_initialization_failure_is_terminal(tmp_path, monkeypatch):
|
|
368
|
+
import cfquant_web_server as web
|
|
369
|
+
manager = runtime_fixture(tmp_path)
|
|
370
|
+
monkeypatch.setattr(web, "QMT_PYTHON_RUNTIME_JOBS", {})
|
|
371
|
+
monkeypatch.setattr(web.threading.Thread, "start", lambda self: None)
|
|
372
|
+
monkeypatch.setattr(web, "emit_download_progress", lambda *a, **kw: None)
|
|
373
|
+
body = {"qmt_dir": str(manager.qmt_dir), "runtime": "py36", "job_id": "init-failure"}
|
|
374
|
+
web.start_qmt_python_runtime_job(body)
|
|
375
|
+
def invalid(_):
|
|
376
|
+
raise ValueError("QMT directory unavailable")
|
|
377
|
+
monkeypatch.setattr(web, "_qmt_python_runtime_row", invalid)
|
|
378
|
+
web._run_qmt_python_runtime_job(body["job_id"], body)
|
|
379
|
+
assert web._qmt_python_job_snapshot(body["job_id"])["state"] == "error"
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
@pytest.mark.parametrize("restart_failed", [False, True])
|
|
383
|
+
def test_runtime_job_uses_managed_restart_without_inline_login_or_bypassing_errors(tmp_path, monkeypatch, restart_failed):
|
|
384
|
+
from types import SimpleNamespace
|
|
385
|
+
import cfquant_web_server as web
|
|
386
|
+
manager = runtime_fixture(tmp_path)
|
|
387
|
+
row = {"account_id": "TEST_ONLY", "qmt_dir": str(manager.qmt_dir)}
|
|
388
|
+
monkeypatch.setattr(web, "WEB_CONFIG", SimpleNamespace(account_configs=lambda: {"test": row}))
|
|
389
|
+
monkeypatch.setattr(web, "QMT_PYTHON_RUNTIME_JOBS", {})
|
|
390
|
+
monkeypatch.setattr(web.threading.Thread, "start", lambda self: None)
|
|
391
|
+
monkeypatch.setattr(web, "emit_download_progress", lambda *a, **kw: None)
|
|
392
|
+
monkeypatch.setattr(QmtPythonRuntime, "_qmt_processes", lambda self: [])
|
|
393
|
+
monkeypatch.setattr(QmtPythonRuntime, "restart_qmt", lambda self: pytest.fail("must not bypass managed restart"))
|
|
394
|
+
restart = {"configured": False, "error": "deployment write failed"} if restart_failed else {
|
|
395
|
+
"configured": True, "pid": 456, "login_state": "logging_in"}
|
|
396
|
+
monkeypatch.setattr(web, "qmt_auto_login_restart_for_account", lambda *a, **kw: restart)
|
|
397
|
+
body = {"qmt_dir": str(manager.qmt_dir), "account_id": "TEST_ONLY", "runtime": "py36",
|
|
398
|
+
"reuse_installed": True, "restart": True, "job_id": "managed-restart"}
|
|
399
|
+
web.start_qmt_python_runtime_job(body)
|
|
400
|
+
web._run_qmt_python_runtime_job(body["job_id"], body)
|
|
401
|
+
job = web._qmt_python_job_snapshot(body["job_id"])
|
|
402
|
+
assert job["state"] == ("error" if restart_failed else "done")
|
|
403
|
+
if restart_failed:
|
|
404
|
+
assert "deployment write failed" in job["error"]
|
|
405
|
+
else:
|
|
406
|
+
assert job["result"]["restarted"] == restart
|
|
407
|
+
assert manager.binding_status()["state"] == "ready"
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
def test_runtime_bootstrap_starts_stops_and_starts_managed_qmt(tmp_path, monkeypatch):
|
|
411
|
+
from types import SimpleNamespace
|
|
412
|
+
import cfquant_web_server as web
|
|
413
|
+
|
|
414
|
+
row = {
|
|
415
|
+
"account_key": "bootstrap-account",
|
|
416
|
+
"account_id": "55009948",
|
|
417
|
+
"qmt_dir": str(tmp_path / "qmt"),
|
|
418
|
+
"qmt_strategy": {"enabled": True},
|
|
419
|
+
"qmt_auto_login": {"enabled": True, "auto_fill_credentials": True},
|
|
420
|
+
}
|
|
421
|
+
manager = SimpleNamespace(
|
|
422
|
+
binding_status=lambda account_id=None: {"state": "account_config_missing"},
|
|
423
|
+
switch_account=lambda runtime, account_id=None: {"runtime": runtime, "account_id": account_id},
|
|
424
|
+
)
|
|
425
|
+
paths = {"bin_dir": str(tmp_path / "qmt" / "bin.x64"), "exe_path": "fixture.exe"}
|
|
426
|
+
monkeypatch.setattr(web, "_qmt_auto_login_paths", lambda _: paths)
|
|
427
|
+
monkeypatch.setattr(web, "_qmt_wait_for_runtime_bootstrap_login",
|
|
428
|
+
lambda row, paths: {"login_succeeded": True, "login_state": "logged_in"})
|
|
429
|
+
monkeypatch.setattr(web, "_qmt_auto_login_stop_xtitclient",
|
|
430
|
+
lambda *args, **kwargs: {"exit_confirmed": True})
|
|
431
|
+
calls = []
|
|
432
|
+
|
|
433
|
+
def restart(account_row, settings, reason):
|
|
434
|
+
calls.append(reason)
|
|
435
|
+
if reason.endswith("first"):
|
|
436
|
+
return {"configured": True, "pid": 101, "strategy_deploy_deferred": True}
|
|
437
|
+
return {"configured": True, "pid": 202, "strategy_deploy_deferred": False}
|
|
438
|
+
|
|
439
|
+
monkeypatch.setattr(web, "qmt_auto_login_restart_for_account", restart)
|
|
440
|
+
result = web._qmt_python_runtime_bootstrap_restart(
|
|
441
|
+
row, row["qmt_auto_login"], manager, "py36", row["account_id"],
|
|
442
|
+
body={"allow_missing_account_config": True},
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
assert calls == ["python-runtime-bootstrap-first", "python-runtime-bootstrap-final"]
|
|
446
|
+
assert result["bootstrap_completed"] is True
|
|
447
|
+
assert result["pid"] == 202
|
|
448
|
+
assert result["runtime_switch"]["runtime"] == "py36"
|
|
@@ -488,6 +488,25 @@ def test_no_account_binding_is_never_fabricated(deployment):
|
|
|
488
488
|
_account_binding(document, ACCOUNT, "STOCK", ACCOUNT_KEY)
|
|
489
489
|
|
|
490
490
|
|
|
491
|
+
def test_first_login_can_start_and_next_start_prepares_real_binding(deployment):
|
|
492
|
+
manager, row, infos, running, root = deployment
|
|
493
|
+
config = root / "config" / "indexUserConfig.xml"
|
|
494
|
+
config.write_text('<ICUserConfigFile><FormulaCatalog/><strategyTrade/></ICUserConfigFile>', encoding="utf-8")
|
|
495
|
+
manager.configure(row, infos)
|
|
496
|
+
prepared = manager.prepare_for_start(root)
|
|
497
|
+
assert prepared["waiting_account"]
|
|
498
|
+
assert not models(root)
|
|
499
|
+
finish_import(manager, root)
|
|
500
|
+
auth = root / "userdata" / "users" / "test_user" / "authAndConfig.xml"
|
|
501
|
+
auth.parent.mkdir(parents=True)
|
|
502
|
+
auth.write_text('<TTAuthAndConfigFile><TTAlgoConfigFile><AccountAuth key="%s" strategys="1"/>'
|
|
503
|
+
'</TTAlgoConfigFile></TTAuthAndConfigFile>' % ACCOUNT_KEY, encoding="utf-8")
|
|
504
|
+
assert not manager.prepare_for_start(root)["waiting_account"]
|
|
505
|
+
role = next(iter(manager.jobs.values()))["roles"][0]
|
|
506
|
+
assert role["model_prepared"]
|
|
507
|
+
assert models(root)[role["name"]].getAttribute("m_strAccountKey") == ACCOUNT_KEY
|
|
508
|
+
|
|
509
|
+
|
|
491
510
|
def test_first_start_has_a_credit_model_even_without_an_existing_model(deployment):
|
|
492
511
|
manager, row, infos, running, root = deployment
|
|
493
512
|
config = root / "config" / "indexUserConfig.xml"
|