cfquant 0.2.54__tar.gz → 0.2.55__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.54 → cfquant-0.2.55}/PKG-INFO +1 -1
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/_editable_install.py +7 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/qmt_component_login.py +21 -10
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/qmt_credentials.py +8 -2
- cfquant-0.2.55/cfquant/qmt_dependencies.py +137 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_qmt_auto_login_browser.py +24 -0
- cfquant-0.2.55/cfquant/tests/test_qmt_dependencies.py +55 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/version.py +2 -2
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant.egg-info/PKG-INFO +1 -1
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant.egg-info/SOURCES.txt +2 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant_web_server.py +31 -18
- {cfquant-0.2.54 → cfquant-0.2.55}/pyproject.toml +1 -1
- {cfquant-0.2.54 → cfquant-0.2.55}/qmt_scripts/CFQUANT_LITE.py +1 -1
- {cfquant-0.2.54 → cfquant-0.2.55}/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.54 → cfquant-0.2.55}/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.54 → cfquant-0.2.55}/web_dashboard/app.js +78 -2
- {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/index.html +6 -2
- {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/styles.css +22 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/LICENSE +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/LTtx/__init__.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/LTtx/tx/Config.txt +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/LTtx/tx/LTtx_server.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/LTtx/tx/__init__.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/LTtx/tx/tx.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/MANIFEST.in +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/README.md +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/__init__.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/account_routing.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/batch_orders.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/build_info.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/callback_dispatcher.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/cftrader.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/channels.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/cli.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/client.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/config.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/diagnostics.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/flow_control.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/hub.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/level2.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/log_management.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/logging_i18n.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/management.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/normal_bridge.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/order_identity.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/order_meta.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/pipe_bridge.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/pipe_client.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/pipe_hub.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/pipe_transport.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/protocol.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/qmt_binding_settings.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/qmt_bridge.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/qmt_component_agent.js +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/qmt_pipe_runtime.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/qmt_strategy_deploy.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/qmt_strategy_package.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/qmt_strategy_runtime.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/quote_history.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/rpc_timing.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/runtime_report.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/stock_connect.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/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.54 → cfquant-0.2.55}/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.54 → cfquant-0.2.55}/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.54 → cfquant-0.2.55}/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.54 → cfquant-0.2.55}/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.54 → cfquant-0.2.55}/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.54 → cfquant-0.2.55}/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.54 → cfquant-0.2.55}/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.54 → cfquant-0.2.55}/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.54 → cfquant-0.2.55}/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.54 → cfquant-0.2.55}/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.54 → cfquant-0.2.55}/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.54 → cfquant-0.2.55}/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.54 → cfquant-0.2.55}/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.54 → cfquant-0.2.55}/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.54 → cfquant-0.2.55}/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.54 → cfquant-0.2.55}/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.54 → cfquant-0.2.55}/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.54 → cfquant-0.2.55}/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.54 → cfquant-0.2.55}/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.54 → cfquant-0.2.55}/cfquant/tests/29_Level2/350/241/214/346/203/205/346/265/213/350/257/225.py" +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/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.54 → cfquant-0.2.55}/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.54 → cfquant-0.2.55}/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.54 → cfquant-0.2.55}/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.54 → cfquant-0.2.55}/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.54 → cfquant-0.2.55}/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.54 → cfquant-0.2.55}/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.54 → cfquant-0.2.55}/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.54 → cfquant-0.2.55}/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.54 → cfquant-0.2.55}/cfquant/tests/6_/345/233/236/350/260/203/346/265/213/350/257/225.py" +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/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.54 → cfquant-0.2.55}/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.54 → cfquant-0.2.55}/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.54 → cfquant-0.2.55}/cfquant/tests/_helpers.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/log/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_20260911_024617.md" +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/log/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_20260921_175732.md" +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/readme.md +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_advanced_parity.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_api_tester_browser.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_api_tester_web.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_binding_delete_confirmation.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_binding_runtime_browser.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_callback_requests.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_cftrader.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_cftrader_tutorial.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_cftrader_web.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_credit_compat.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_dataframe_encoding.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_json_serialization.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_latency_flow_control.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_lite_account_routing.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_lite_deployment_paths.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_lite_parity.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_lite_qmt_dispatch.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_local_trading_calendar.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_log_management.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_management.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_management_batch.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_market_data_fallback.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_market_timing.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_order_identity.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_order_time_callback.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_project_updater.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_python_api_reference.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_qmt_auto_login_integration.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_qmt_binding_settings.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_qmt_component_login.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_qmt_fileless.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_qmt_strategy_browser.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_qmt_strategy_deploy.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_qmt_strategy_web.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_quote_latency.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_quote_latency_encoding.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_setup_qmt_prompt.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_startup_recovery.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_stock_connect.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_stock_connect_browser.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_table_conversion.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_trade_strategy_callback.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_trading_dates.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_tutorial_reader.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_tx_auto_import.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_tx_shutdown.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/trading_calendar.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tx.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tx_trade_bridge.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/xtconstant.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/xtdata.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/xttrader.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/xttype.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant.egg-info/dependency_links.txt +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant.egg-info/entry_points.txt +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant.egg-info/requires.txt +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant.egg-info/top_level.txt +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/cfquant_pipe_hub.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/qmt_scripts/CFQUANT.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/qmt_scripts/CFQUANT_CTYPE_ALL_LOWLAT.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/qmt_scripts/CFQUANT_TRADE_LOWLAT.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/qmt_scripts/__init__.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/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.54 → cfquant-0.2.55}/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.54 → cfquant-0.2.55}/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.54 → cfquant-0.2.55}/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.54 → cfquant-0.2.55}/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.54 → cfquant-0.2.55}/requirements.txt +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/setup.cfg +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/__init__.py +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/api-tester.js +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/avatars/amber-pulse.svg +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/avatars/copper-grid.svg +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/avatars/market-blue.svg +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/avatars/rose-circuit.svg +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/avatars/signal-green.svg +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/avatars/slate-wave.svg +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/avatars/teal-orbit.svg +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/avatars/violet-node.svg +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/cftrader-api-data.js +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/docs.js +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/favicon.svg +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/image/qmt_deploy/image-10.png +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/image/qmt_deploy/image-11.png +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/image/qmt_deploy/image-12.png +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/image/qmt_deploy/image-13.png +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/image/qmt_deploy/image-14.png +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/image/qmt_deploy/image-15.png +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/image/qmt_deploy/image-16.png +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/image/qmt_deploy/image-17.png +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/image/qmt_deploy/image-18.png +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/image/qmt_deploy/image-19.png +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/image/qmt_deploy/image-20.png +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/image/qmt_deploy/image-21.png +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/image/qmt_deploy/image-4.png +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/image/qmt_deploy/image-5.png +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/image/qmt_deploy/image-6.png +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/image/qmt_deploy/image-7.png +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/image/qmt_deploy/image-8.png +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/image/qmt_deploy/image-9.png +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/python-api-data.js +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/python-api.js +0 -0
- {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/vendor/mermaid.min.js +0 -0
|
@@ -9,6 +9,11 @@ import sys
|
|
|
9
9
|
import tempfile
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
|
|
12
|
+
if __package__:
|
|
13
|
+
from .qmt_dependencies import report_startup_dependencies
|
|
14
|
+
else:
|
|
15
|
+
from qmt_dependencies import report_startup_dependencies
|
|
16
|
+
|
|
12
17
|
|
|
13
18
|
PACKAGE_NAME = "cfquant"
|
|
14
19
|
|
|
@@ -503,6 +508,7 @@ def ensure_cfquant_installed(project_root, python_exe=None):
|
|
|
503
508
|
print("cfquant installed=%s project=%s" % (installed or "missing", expected))
|
|
504
509
|
if installed == expected:
|
|
505
510
|
print("Package versions match; skip installation.")
|
|
511
|
+
report_startup_dependencies(python_exe)
|
|
506
512
|
return 0
|
|
507
513
|
|
|
508
514
|
command = editable_install_args(project_root, python_exe)
|
|
@@ -526,6 +532,7 @@ def ensure_cfquant_installed(project_root, python_exe=None):
|
|
|
526
532
|
print("[ERROR] Installed cfquant version does not match the project after installation.", file=sys.stderr)
|
|
527
533
|
return 1
|
|
528
534
|
print("%s package is installed." % PACKAGE_NAME)
|
|
535
|
+
report_startup_dependencies(python_exe)
|
|
529
536
|
return 0
|
|
530
537
|
|
|
531
538
|
|
|
@@ -18,9 +18,12 @@ class QmtNotReady(RuntimeError):
|
|
|
18
18
|
|
|
19
19
|
def verify_environment(directory):
|
|
20
20
|
"""Validate dependencies and native ABI before stopping a running terminal."""
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
try:
|
|
22
|
+
from .qmt_dependencies import LOGIN_MODULES, import_login_module
|
|
23
|
+
except ImportError:
|
|
24
|
+
from qmt_dependencies import LOGIN_MODULES, import_login_module
|
|
25
|
+
modules = {name: import_login_module(name) for name in LOGIN_MODULES}
|
|
26
|
+
win32api = modules["win32api"]
|
|
24
27
|
directory = Path(directory)
|
|
25
28
|
for module in ("Qt5Core.dll", "Qt5Widgets.dll"):
|
|
26
29
|
version = win32api.GetFileVersionInfo(str(directory / module), "\\")
|
|
@@ -83,12 +86,16 @@ def select_binding(config_path, name):
|
|
|
83
86
|
|
|
84
87
|
class QtLoginBackend:
|
|
85
88
|
def __init__(self, directory, timeout=40, expected_pid=None):
|
|
86
|
-
|
|
89
|
+
try:
|
|
90
|
+
from .qmt_dependencies import import_login_module
|
|
91
|
+
except ImportError:
|
|
92
|
+
from qmt_dependencies import import_login_module
|
|
87
93
|
import psutil
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
94
|
+
frida = import_login_module("frida")
|
|
95
|
+
win32api = import_login_module("win32api")
|
|
96
|
+
win32event = import_login_module("win32event")
|
|
97
|
+
win32gui = import_login_module("win32gui")
|
|
98
|
+
win32process = import_login_module("win32process")
|
|
92
99
|
self.gui, self.process = win32gui, win32process
|
|
93
100
|
self.timeout = timeout
|
|
94
101
|
self.hwnd = None
|
|
@@ -232,8 +239,12 @@ class QtLoginBackend:
|
|
|
232
239
|
self.failure_hwnd = None
|
|
233
240
|
|
|
234
241
|
def close(self):
|
|
235
|
-
|
|
236
|
-
|
|
242
|
+
try:
|
|
243
|
+
from .qmt_dependencies import import_login_module
|
|
244
|
+
except ImportError:
|
|
245
|
+
from qmt_dependencies import import_login_module
|
|
246
|
+
win32api = import_login_module("win32api")
|
|
247
|
+
win32event = import_login_module("win32event")
|
|
237
248
|
try:
|
|
238
249
|
if self.session:
|
|
239
250
|
self.session.detach()
|
|
@@ -6,6 +6,8 @@ import os
|
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
import threading
|
|
8
8
|
|
|
9
|
+
from .qmt_dependencies import QmtDependencyError, import_login_module
|
|
10
|
+
|
|
9
11
|
_LOCK = threading.RLock()
|
|
10
12
|
|
|
11
13
|
|
|
@@ -17,12 +19,12 @@ def directory_key(directory):
|
|
|
17
19
|
|
|
18
20
|
|
|
19
21
|
def _protect(value, entropy):
|
|
20
|
-
|
|
22
|
+
win32crypt = import_login_module("win32crypt")
|
|
21
23
|
return win32crypt.CryptProtectData(value, "CFQuant QMT login", entropy, None, None, 1)
|
|
22
24
|
|
|
23
25
|
|
|
24
26
|
def _unprotect(value, entropy):
|
|
25
|
-
|
|
27
|
+
win32crypt = import_login_module("win32crypt")
|
|
26
28
|
return win32crypt.CryptUnprotectData(value, entropy, None, None, 1)[1]
|
|
27
29
|
|
|
28
30
|
|
|
@@ -50,6 +52,8 @@ class QmtCredentialStore:
|
|
|
50
52
|
key = directory_key(directory)
|
|
51
53
|
try:
|
|
52
54
|
encrypted = _protect(password.encode("utf-8"), key.encode("ascii"))
|
|
55
|
+
except QmtDependencyError:
|
|
56
|
+
raise
|
|
53
57
|
except Exception:
|
|
54
58
|
raise RuntimeError("无法使用当前 Windows 用户加密 QMT 密码,请检查 pywin32 安装") from None
|
|
55
59
|
with _LOCK:
|
|
@@ -68,5 +72,7 @@ class QmtCredentialStore:
|
|
|
68
72
|
return ""
|
|
69
73
|
try:
|
|
70
74
|
return _unprotect(base64.b64decode(entry["secret"]), key.encode("ascii")).decode("utf-8")
|
|
75
|
+
except QmtDependencyError:
|
|
76
|
+
raise
|
|
71
77
|
except Exception:
|
|
72
78
|
raise RuntimeError("无法解密 QMT 密码,请使用保存密码时的 Windows 用户运行,或重新保存密码") from None
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""Read-only checks and repair instructions for optional QMT login components."""
|
|
2
|
+
import copy
|
|
3
|
+
import importlib
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
import threading
|
|
9
|
+
import time
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
LOGIN_MODULES = ("frida", "win32api", "win32crypt", "win32event", "win32gui", "win32process")
|
|
13
|
+
_LOCK = threading.Lock()
|
|
14
|
+
_CACHE = {}
|
|
15
|
+
_PROBE_CODE = r'''
|
|
16
|
+
import importlib, importlib.metadata, json, pathlib, sys
|
|
17
|
+
checks = []
|
|
18
|
+
for name in MODULES:
|
|
19
|
+
try:
|
|
20
|
+
module = importlib.import_module(name)
|
|
21
|
+
checks.append(dict(name=name, ok=True, path=getattr(module, '__file__', '')))
|
|
22
|
+
except Exception as error:
|
|
23
|
+
checks.append(dict(name=name, ok=False, error=str(error), error_type=type(error).__name__))
|
|
24
|
+
packages = {}
|
|
25
|
+
for name in ('pywin32', 'frida'):
|
|
26
|
+
try:
|
|
27
|
+
packages[name] = importlib.metadata.version(name)
|
|
28
|
+
except importlib.metadata.PackageNotFoundError:
|
|
29
|
+
packages[name] = ''
|
|
30
|
+
prefix = pathlib.Path(sys.prefix)
|
|
31
|
+
conda = (prefix / 'conda-meta').is_dir()
|
|
32
|
+
print(json.dumps(dict(python_executable=sys.executable, python_version=sys.version.split()[0],
|
|
33
|
+
prefix=str(prefix), environment='conda' if conda else 'venv' if sys.prefix != sys.base_prefix else 'python',
|
|
34
|
+
global_python=sys.prefix == sys.base_prefix,
|
|
35
|
+
conda_pywin32=conda and bool(list((prefix / 'conda-meta').glob('pywin32-*.json'))),
|
|
36
|
+
supported=sys.platform == 'win32', checks=checks, packages=packages), ensure_ascii=True))
|
|
37
|
+
'''
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _ps_quote(value):
|
|
41
|
+
return "'" + str(value).replace("'", "''") + "'"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _finish_report(report):
|
|
45
|
+
report["checked_at"] = time.time()
|
|
46
|
+
report["ok"] = bool(report.get("supported") and not report.get("error")
|
|
47
|
+
and all(item["ok"] for item in report.get("checks", [])))
|
|
48
|
+
failed = [item for item in report.get("checks", []) if not item["ok"]]
|
|
49
|
+
if not report.get("supported") and not report.get("error"):
|
|
50
|
+
report["message"] = "当前系统不支持 QMT 组件自动登录,请手动启动并登录 QMT。"
|
|
51
|
+
elif report["ok"]:
|
|
52
|
+
report["message"] = "QMT 自动登录组件检测通过。"
|
|
53
|
+
else:
|
|
54
|
+
names = "、".join(item["name"] for item in failed) or "Python 环境"
|
|
55
|
+
report["message"] = "QMT 自动登录组件不可用(%s)。可取消自动启动,保存配置后手动启动并登录 QMT。" % names
|
|
56
|
+
commands = []
|
|
57
|
+
python = "& " + _ps_quote(report["python_executable"])
|
|
58
|
+
if report.get("supported") and failed:
|
|
59
|
+
if any(item["name"].startswith("win32") for item in failed):
|
|
60
|
+
if report.get("conda_pywin32"):
|
|
61
|
+
commands.append("conda install --prefix %s --force-reinstall 'pywin32>=306'" % _ps_quote(report["prefix"]))
|
|
62
|
+
else:
|
|
63
|
+
commands.append(python + " -m pip install --no-cache-dir --force-reinstall 'pywin32>=306'")
|
|
64
|
+
if report.get("global_python"):
|
|
65
|
+
commands.append(python + " -m pywin32_postinstall -install")
|
|
66
|
+
if any(item["name"] == "frida" for item in failed):
|
|
67
|
+
commands.append(python + " -m pip install --no-cache-dir --force-reinstall 'frida>=17.0,<18'")
|
|
68
|
+
commands.append(python + " -c " + _ps_quote("import " + ", ".join(LOGIN_MODULES) + "; print('QMT login components OK')"))
|
|
69
|
+
report["repair_commands"] = commands
|
|
70
|
+
report["repair_note"] = (
|
|
71
|
+
"先停止 CFQuant,再在此电脑的 PowerShell 中执行;Conda 命令请使用 Anaconda PowerShell Prompt。"
|
|
72
|
+
"验证通过后重启 CFQuant,再重新检测。若仍报 DLL 错误,需要检查旧 DLL 残留;"
|
|
73
|
+
"虚拟环境中不要运行 pywin32_postinstall。"
|
|
74
|
+
)
|
|
75
|
+
return report
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def check_login_dependencies(python_exe=None, refresh=False, timeout=15):
|
|
79
|
+
"""Probe the actual interpreter without installing packages or loading DLLs here."""
|
|
80
|
+
executable = str(python_exe or sys.executable)
|
|
81
|
+
with _LOCK:
|
|
82
|
+
if not refresh and executable in _CACHE:
|
|
83
|
+
return copy.deepcopy(_CACHE[executable])
|
|
84
|
+
report = {"python_executable": executable, "supported": sys.platform == "win32", "checks": []}
|
|
85
|
+
if not report["supported"]:
|
|
86
|
+
_CACHE[executable] = _finish_report(report)
|
|
87
|
+
return copy.deepcopy(_CACHE[executable])
|
|
88
|
+
try:
|
|
89
|
+
run_kwargs = {}
|
|
90
|
+
if os.name == "nt":
|
|
91
|
+
run_kwargs["creationflags"] = getattr(subprocess, "CREATE_NO_WINDOW", 0)
|
|
92
|
+
completed = subprocess.run(
|
|
93
|
+
[executable, "-c", "MODULES = %r\n" % (LOGIN_MODULES,) + _PROBE_CODE],
|
|
94
|
+
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, encoding="utf-8",
|
|
95
|
+
errors="replace", timeout=timeout,
|
|
96
|
+
**run_kwargs,
|
|
97
|
+
)
|
|
98
|
+
if completed.returncode:
|
|
99
|
+
raise RuntimeError((completed.stderr or completed.stdout or "Python dependency probe failed")[-4000:])
|
|
100
|
+
# Startup hooks may print before our JSON report.
|
|
101
|
+
report.update(json.loads(completed.stdout.strip().splitlines()[-1]))
|
|
102
|
+
except Exception as error:
|
|
103
|
+
report["error"] = str(error)
|
|
104
|
+
_CACHE[executable] = _finish_report(report)
|
|
105
|
+
return copy.deepcopy(_CACHE[executable])
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class QmtDependencyError(RuntimeError):
|
|
109
|
+
pass
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def import_login_module(name):
|
|
113
|
+
"""Keep in-process import errors actionable, including stale loaded DLLs."""
|
|
114
|
+
try:
|
|
115
|
+
return importlib.import_module(name)
|
|
116
|
+
except (ImportError, OSError) as error:
|
|
117
|
+
raise QmtDependencyError(
|
|
118
|
+
"QMT 自动登录组件 %s 无法加载;运行环境:%s;原因:%s。"
|
|
119
|
+
"请查看自动登录组件检测中的修复说明,或取消自动启动后手动登录 QMT。"
|
|
120
|
+
% (name, sys.executable, error)
|
|
121
|
+
) from error
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def report_startup_dependencies(python_exe=None):
|
|
125
|
+
report = check_login_dependencies(python_exe, refresh=True)
|
|
126
|
+
if not report.get("supported"):
|
|
127
|
+
return report
|
|
128
|
+
level = "INFO" if report["ok"] else "WARN"
|
|
129
|
+
print("[%s] %s Python=%s" % (level, report["message"], report["python_executable"]))
|
|
130
|
+
for item in report["checks"]:
|
|
131
|
+
if not item["ok"]:
|
|
132
|
+
print("[WARN] %s: %s" % (item["name"], item["error"]))
|
|
133
|
+
if report.get("error"):
|
|
134
|
+
print("[WARN] %s" % report["error"])
|
|
135
|
+
for command in report["repair_commands"]:
|
|
136
|
+
print("[REPAIR] %s" % command)
|
|
137
|
+
return report
|
|
@@ -173,3 +173,27 @@ def test_login_result_is_visible_in_status_panel(page, frontend_url):
|
|
|
173
173
|
assert '已登录' in result['success']
|
|
174
174
|
assert '请修改保存的密码' in result['failure']
|
|
175
175
|
assert errors == []
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def test_dependency_failure_shows_repair_command_and_manual_login_action(page, frontend_url):
|
|
179
|
+
_, errors = open_app(page, frontend_url, setup_required=False)
|
|
180
|
+
saved_binding(page)
|
|
181
|
+
page.evaluate("""() => {
|
|
182
|
+
state.qmtLoginDependencies = null;
|
|
183
|
+
renderQmtDependencyStatus({
|
|
184
|
+
supported: true, ok: false, python_executable: 'C:/Python311/python.exe',
|
|
185
|
+
environment: 'python', python_version: '3.11.9',
|
|
186
|
+
checks: [{name: 'win32api', ok: false, error: 'DLL load failed while importing win32api'}],
|
|
187
|
+
repair_commands: ["& 'C:/Python311/python.exe' -m pip install --force-reinstall pywin32"],
|
|
188
|
+
repair_note: '先停止 CFQuant 后执行。',
|
|
189
|
+
message: 'QMT 自动登录组件不可用(win32api)。',
|
|
190
|
+
});
|
|
191
|
+
}""")
|
|
192
|
+
panel = page.locator('#bindingQmtDependencies')
|
|
193
|
+
expect(panel).to_be_visible()
|
|
194
|
+
expect(panel).to_contain_text('win32api')
|
|
195
|
+
expect(panel).to_contain_text('C:/Python311/python.exe')
|
|
196
|
+
expect(panel.locator('[data-qmt-dependency-action="manual"]')).to_have_count(1)
|
|
197
|
+
panel.locator('[data-qmt-dependency-action="manual"]').click()
|
|
198
|
+
expect(page.locator('#bindingQmtAutoLogin')).not_to_be_checked()
|
|
199
|
+
assert errors == []
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import subprocess
|
|
3
|
+
|
|
4
|
+
from cfquant import qmt_dependencies
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def test_dependency_probe_reports_loaded_modules_and_versions(monkeypatch):
|
|
8
|
+
monkeypatch.setattr(qmt_dependencies.sys, "platform", "win32")
|
|
9
|
+
qmt_dependencies._CACHE.clear()
|
|
10
|
+
payload = {
|
|
11
|
+
"python_executable": "C:/Python311/python.exe",
|
|
12
|
+
"python_version": "3.11.9",
|
|
13
|
+
"prefix": "C:/Python311",
|
|
14
|
+
"environment": "python",
|
|
15
|
+
"conda_pywin32": False,
|
|
16
|
+
"supported": True,
|
|
17
|
+
"checks": [
|
|
18
|
+
{"name": "frida", "ok": True, "path": "frida.pyd"},
|
|
19
|
+
{"name": "win32api", "ok": False, "error": "DLL load failed while importing win32api"},
|
|
20
|
+
],
|
|
21
|
+
"packages": {"pywin32": "306", "frida": "17.16.4"},
|
|
22
|
+
}
|
|
23
|
+
monkeypatch.setattr(qmt_dependencies.subprocess, "run", lambda *args, **kwargs: subprocess.CompletedProcess(
|
|
24
|
+
args, 0, stdout=json.dumps(payload), stderr=""
|
|
25
|
+
))
|
|
26
|
+
|
|
27
|
+
report = qmt_dependencies.check_login_dependencies("C:/Python311/python.exe", refresh=True)
|
|
28
|
+
|
|
29
|
+
assert report["ok"] is False
|
|
30
|
+
assert report["python_executable"] == "C:/Python311/python.exe"
|
|
31
|
+
assert report["repair_commands"]
|
|
32
|
+
assert "pip install" in report["repair_commands"][0]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_conda_dependency_failure_generates_conda_repair_command(monkeypatch):
|
|
36
|
+
monkeypatch.setattr(qmt_dependencies.sys, "platform", "win32")
|
|
37
|
+
qmt_dependencies._CACHE.clear()
|
|
38
|
+
payload = {
|
|
39
|
+
"python_executable": "C:/Miniconda/envs/qmt/python.exe",
|
|
40
|
+
"python_version": "3.12.4",
|
|
41
|
+
"prefix": "C:/Miniconda/envs/qmt",
|
|
42
|
+
"environment": "conda",
|
|
43
|
+
"conda_pywin32": True,
|
|
44
|
+
"supported": True,
|
|
45
|
+
"checks": [{"name": "win32api", "ok": False, "error": "DLL load failed"}],
|
|
46
|
+
"packages": {"pywin32": "306", "frida": "17.16.4"},
|
|
47
|
+
}
|
|
48
|
+
monkeypatch.setattr(qmt_dependencies.subprocess, "run", lambda *args, **kwargs: subprocess.CompletedProcess(
|
|
49
|
+
args, 0, stdout=json.dumps(payload), stderr=""
|
|
50
|
+
))
|
|
51
|
+
|
|
52
|
+
report = qmt_dependencies.check_login_dependencies(payload["python_executable"], refresh=True)
|
|
53
|
+
|
|
54
|
+
assert report["repair_commands"][0].startswith("conda install --prefix")
|
|
55
|
+
assert "--force-reinstall" in report["repair_commands"][0]
|
|
@@ -41,6 +41,7 @@ cfquant/qmt_bridge.py
|
|
|
41
41
|
cfquant/qmt_component_agent.js
|
|
42
42
|
cfquant/qmt_component_login.py
|
|
43
43
|
cfquant/qmt_credentials.py
|
|
44
|
+
cfquant/qmt_dependencies.py
|
|
44
45
|
cfquant/qmt_pipe_runtime.py
|
|
45
46
|
cfquant/qmt_strategy_deploy.py
|
|
46
47
|
cfquant/qmt_strategy_package.py
|
|
@@ -130,6 +131,7 @@ cfquant/tests/test_qmt_auto_login_browser.py
|
|
|
130
131
|
cfquant/tests/test_qmt_auto_login_integration.py
|
|
131
132
|
cfquant/tests/test_qmt_binding_settings.py
|
|
132
133
|
cfquant/tests/test_qmt_component_login.py
|
|
134
|
+
cfquant/tests/test_qmt_dependencies.py
|
|
133
135
|
cfquant/tests/test_qmt_fileless.py
|
|
134
136
|
cfquant/tests/test_qmt_strategy_browser.py
|
|
135
137
|
cfquant/tests/test_qmt_strategy_deploy.py
|
|
@@ -18,7 +18,8 @@ import os
|
|
|
18
18
|
from pathlib import Path
|
|
19
19
|
from cfquant.diagnostics import latency_report, SCHEMA as QUOTE_LATENCY_SCHEMA
|
|
20
20
|
from cfquant.stock_connect import connect_account_type, normalize_connect_code, validate_connect_order, validate_connect_market
|
|
21
|
-
from cfquant.qmt_binding_settings import normalize_qmt_runtime_settings, qmt_runtime_identity
|
|
21
|
+
from cfquant.qmt_binding_settings import normalize_qmt_runtime_settings, qmt_runtime_identity
|
|
22
|
+
from cfquant.qmt_dependencies import check_login_dependencies, report_startup_dependencies
|
|
22
23
|
import posixpath
|
|
23
24
|
import queue
|
|
24
25
|
import re
|
|
@@ -11601,20 +11602,27 @@ def qmt_auto_login_apply_for_account(row, request=None, restart=False, reason="s
|
|
|
11601
11602
|
if pid:
|
|
11602
11603
|
result["login_state"] = "logging_in"
|
|
11603
11604
|
_qmt_queue_component_login(login_row, paths, pid)
|
|
11604
|
-
except Exception as error:
|
|
11605
|
-
result.update({
|
|
11606
|
-
"configured": False,
|
|
11607
|
-
"error": str(error),
|
|
11608
|
-
"message": "QMT 自动启动配置失败: %s" % error,
|
|
11609
|
-
})
|
|
11610
|
-
|
|
11605
|
+
except Exception as error:
|
|
11606
|
+
result.update({
|
|
11607
|
+
"configured": False,
|
|
11608
|
+
"error": str(error),
|
|
11609
|
+
"message": "QMT 自动启动配置失败: %s" % error,
|
|
11610
|
+
})
|
|
11611
|
+
from cfquant.qmt_dependencies import QmtDependencyError
|
|
11612
|
+
if isinstance(error, QmtDependencyError):
|
|
11613
|
+
result["dependency_error"] = True
|
|
11614
|
+
result["message"] = "自动登录组件不可用,请手动启动并登录 QMT;绑定保存状态请以配置结果为准。"
|
|
11615
|
+
result["dependencies"] = check_login_dependencies(refresh=True)
|
|
11616
|
+
_qmt_auto_login_update_session(row, {
|
|
11611
11617
|
"enabled": True,
|
|
11612
11618
|
"state": "error",
|
|
11613
11619
|
"qmt_dir": qmt_dir,
|
|
11614
11620
|
"restart_times": list(settings["restart_times"]),
|
|
11615
|
-
"error": str(error),
|
|
11616
|
-
"message": result["message"],
|
|
11617
|
-
|
|
11621
|
+
"error": str(error),
|
|
11622
|
+
"message": result["message"],
|
|
11623
|
+
"dependency_error": result.get("dependency_error", False),
|
|
11624
|
+
"dependencies": result.get("dependencies"),
|
|
11625
|
+
})
|
|
11618
11626
|
return result
|
|
11619
11627
|
|
|
11620
11628
|
|
|
@@ -15091,8 +15099,8 @@ def management_qmt_start_policy():
|
|
|
15091
15099
|
return None
|
|
15092
15100
|
|
|
15093
15101
|
|
|
15094
|
-
def management_status():
|
|
15095
|
-
return {
|
|
15102
|
+
def management_status():
|
|
15103
|
+
return {
|
|
15096
15104
|
"api_version": 1,
|
|
15097
15105
|
"ready": MANAGEMENT_READY,
|
|
15098
15106
|
"boot_id": MANAGEMENT_BOOT_ID,
|
|
@@ -15102,7 +15110,8 @@ def management_status():
|
|
|
15102
15110
|
"running_from_source": bool(_RUNNING_FROM_SOURCE),
|
|
15103
15111
|
"core_version": current_core_version(),
|
|
15104
15112
|
"web_version": WEB_VERSION,
|
|
15105
|
-
"auto_start_qmt": management_qmt_start_policy(),
|
|
15113
|
+
"auto_start_qmt": management_qmt_start_policy(),
|
|
15114
|
+
"qmt_login_dependencies": check_login_dependencies(),
|
|
15106
15115
|
"setup": WEB_CONFIG.setup_info(),
|
|
15107
15116
|
"account_configs": WEB_CONFIG.account_configs(),
|
|
15108
15117
|
"lttx_host": LTTX_HOST,
|
|
@@ -17023,8 +17032,10 @@ class CfquantWebHandler(BaseHTTPRequestHandler):
|
|
|
17023
17032
|
return
|
|
17024
17033
|
query = urllib.parse.parse_qs(parsed.query)
|
|
17025
17034
|
try:
|
|
17026
|
-
if parsed.path == "/api/health":
|
|
17027
|
-
self._write_json(ok({"status": "ok"}))
|
|
17035
|
+
if parsed.path == "/api/health":
|
|
17036
|
+
self._write_json(ok({"status": "ok"}))
|
|
17037
|
+
elif parsed.path == "/api/qmt-auto-login/dependencies":
|
|
17038
|
+
self._write_json(ok(check_login_dependencies(refresh=parse_bool(query.get("refresh", ["0"])[0]))))
|
|
17028
17039
|
elif parsed.path == "/api/management/status":
|
|
17029
17040
|
self._write_json(ok(management_status()))
|
|
17030
17041
|
elif parsed.path == "/api/internal/runtime-route":
|
|
@@ -17060,7 +17071,8 @@ class CfquantWebHandler(BaseHTTPRequestHandler):
|
|
|
17060
17071
|
"web_auth": WEB_CONFIG.web_auth_info(include_username=True),
|
|
17061
17072
|
"user_profile": user_profile_response(),
|
|
17062
17073
|
"transport": WEB_CONFIG.transport_info(),
|
|
17063
|
-
"python_environment": WEB_CONFIG.python_environment_info(),
|
|
17074
|
+
"python_environment": WEB_CONFIG.python_environment_info(),
|
|
17075
|
+
"qmt_login_dependencies": check_login_dependencies(),
|
|
17064
17076
|
"pipe_hub": PIPE_HUB.status(),
|
|
17065
17077
|
"log_cleanup": log_cleanup_info(),
|
|
17066
17078
|
"qmt_log_language": qmt_log_language_info(),
|
|
@@ -17702,7 +17714,8 @@ def main(argv=None):
|
|
|
17702
17714
|
"cfquant web port %s is already occupied by another process; stop it or set CFQUANT_WEB_PORT"
|
|
17703
17715
|
% args.port
|
|
17704
17716
|
)
|
|
17705
|
-
|
|
17717
|
+
report_startup_dependencies()
|
|
17718
|
+
ensure_lttx_started("Web 启动预启动")
|
|
17706
17719
|
# Older updater processes only copied raw entries. Refresh managed sources
|
|
17707
17720
|
# before auto-login so QMT imports the selected mode's current package.
|
|
17708
17721
|
for result in refresh_bound_qmt_strategies():
|
|
@@ -700,7 +700,7 @@ def _resolve_batch_order_ids(bridge, account, pending, before_ids):
|
|
|
700
700
|
time.sleep(min(0.05, remaining))
|
|
701
701
|
# END GENERATED CFTRADER BATCH
|
|
702
702
|
|
|
703
|
-
CORE_VERSION = "0.2.
|
|
703
|
+
CORE_VERSION = "0.2.55"
|
|
704
704
|
LITE_ENTRY_VERSION = "lite_20261014_03"
|
|
705
705
|
|
|
706
706
|
_CANCELABLE_ORDER_STATUS_VALUES = set([48, 49, 50, 55])
|
|
@@ -714,7 +714,7 @@ def _resolve_batch_order_ids(bridge, account, pending, before_ids):
|
|
|
714
714
|
time.sleep(min(0.05, remaining))
|
|
715
715
|
# END GENERATED CFTRADER BATCH
|
|
716
716
|
|
|
717
|
-
CORE_VERSION = "0.2.
|
|
717
|
+
CORE_VERSION = "0.2.55"
|
|
718
718
|
LITE_ENTRY_VERSION = "lite_20261014_03"
|
|
719
719
|
|
|
720
720
|
_CANCELABLE_ORDER_STATUS_VALUES = set([48, 49, 50, 55])
|
|
@@ -714,7 +714,7 @@ def _resolve_batch_order_ids(bridge, account, pending, before_ids):
|
|
|
714
714
|
time.sleep(min(0.05, remaining))
|
|
715
715
|
# END GENERATED CFTRADER BATCH
|
|
716
716
|
|
|
717
|
-
CORE_VERSION = "0.2.
|
|
717
|
+
CORE_VERSION = "0.2.55"
|
|
718
718
|
LITE_ENTRY_VERSION = "lite_20261014_03"
|
|
719
719
|
|
|
720
720
|
_CANCELABLE_ORDER_STATUS_VALUES = set([48, 49, 50, 55])
|