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.
Files changed (203) hide show
  1. {cfquant-0.2.54 → cfquant-0.2.55}/PKG-INFO +1 -1
  2. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/_editable_install.py +7 -0
  3. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/qmt_component_login.py +21 -10
  4. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/qmt_credentials.py +8 -2
  5. cfquant-0.2.55/cfquant/qmt_dependencies.py +137 -0
  6. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_qmt_auto_login_browser.py +24 -0
  7. cfquant-0.2.55/cfquant/tests/test_qmt_dependencies.py +55 -0
  8. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/version.py +2 -2
  9. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant.egg-info/PKG-INFO +1 -1
  10. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant.egg-info/SOURCES.txt +2 -0
  11. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant_web_server.py +31 -18
  12. {cfquant-0.2.54 → cfquant-0.2.55}/pyproject.toml +1 -1
  13. {cfquant-0.2.54 → cfquant-0.2.55}/qmt_scripts/CFQUANT_LITE.py +1 -1
  14. {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
  15. {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
  16. {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/app.js +78 -2
  17. {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/index.html +6 -2
  18. {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/styles.css +22 -0
  19. {cfquant-0.2.54 → cfquant-0.2.55}/LICENSE +0 -0
  20. {cfquant-0.2.54 → cfquant-0.2.55}/LTtx/__init__.py +0 -0
  21. {cfquant-0.2.54 → cfquant-0.2.55}/LTtx/tx/Config.txt +0 -0
  22. {cfquant-0.2.54 → cfquant-0.2.55}/LTtx/tx/LTtx_server.py +0 -0
  23. {cfquant-0.2.54 → cfquant-0.2.55}/LTtx/tx/__init__.py +0 -0
  24. {cfquant-0.2.54 → cfquant-0.2.55}/LTtx/tx/tx.py +0 -0
  25. {cfquant-0.2.54 → cfquant-0.2.55}/MANIFEST.in +0 -0
  26. {cfquant-0.2.54 → cfquant-0.2.55}/README.md +0 -0
  27. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/__init__.py +0 -0
  28. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/account_routing.py +0 -0
  29. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/batch_orders.py +0 -0
  30. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/build_info.py +0 -0
  31. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/callback_dispatcher.py +0 -0
  32. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/cftrader.py +0 -0
  33. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/channels.py +0 -0
  34. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/cli.py +0 -0
  35. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/client.py +0 -0
  36. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/config.py +0 -0
  37. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/diagnostics.py +0 -0
  38. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/flow_control.py +0 -0
  39. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/hub.py +0 -0
  40. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/level2.py +0 -0
  41. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/log_management.py +0 -0
  42. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/logging_i18n.py +0 -0
  43. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/management.py +0 -0
  44. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/normal_bridge.py +0 -0
  45. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/order_identity.py +0 -0
  46. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/order_meta.py +0 -0
  47. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/pipe_bridge.py +0 -0
  48. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/pipe_client.py +0 -0
  49. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/pipe_hub.py +0 -0
  50. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/pipe_transport.py +0 -0
  51. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/protocol.py +0 -0
  52. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/qmt_binding_settings.py +0 -0
  53. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/qmt_bridge.py +0 -0
  54. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/qmt_component_agent.js +0 -0
  55. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/qmt_pipe_runtime.py +0 -0
  56. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/qmt_strategy_deploy.py +0 -0
  57. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/qmt_strategy_package.py +0 -0
  58. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/qmt_strategy_runtime.py +0 -0
  59. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/quote_history.py +0 -0
  60. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/rpc_timing.py +0 -0
  61. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/runtime_report.py +0 -0
  62. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/stock_connect.py +0 -0
  63. {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
  64. {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
  65. {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
  66. {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
  67. {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
  68. {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
  69. {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
  70. {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
  71. {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
  72. {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
  73. {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
  74. {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
  75. {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
  76. {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
  77. {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
  78. {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
  79. {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
  80. {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
  81. {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
  82. {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
  83. {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
  84. {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
  85. {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
  86. {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
  87. {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
  88. {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
  89. {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
  90. {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
  91. {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
  92. {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
  93. {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
  94. {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
  95. {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
  96. {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
  97. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/_helpers.py +0 -0
  98. {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
  99. {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
  100. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/readme.md +0 -0
  101. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_advanced_parity.py +0 -0
  102. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_api_tester_browser.py +0 -0
  103. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_api_tester_web.py +0 -0
  104. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_binding_delete_confirmation.py +0 -0
  105. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_binding_runtime_browser.py +0 -0
  106. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_callback_requests.py +0 -0
  107. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_cftrader.py +0 -0
  108. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_cftrader_tutorial.py +0 -0
  109. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_cftrader_web.py +0 -0
  110. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_credit_compat.py +0 -0
  111. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_dataframe_encoding.py +0 -0
  112. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_json_serialization.py +0 -0
  113. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_latency_flow_control.py +0 -0
  114. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_lite_account_routing.py +0 -0
  115. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_lite_deployment_paths.py +0 -0
  116. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_lite_parity.py +0 -0
  117. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_lite_qmt_dispatch.py +0 -0
  118. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_local_trading_calendar.py +0 -0
  119. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_log_management.py +0 -0
  120. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_management.py +0 -0
  121. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_management_batch.py +0 -0
  122. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_market_data_fallback.py +0 -0
  123. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_market_timing.py +0 -0
  124. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_order_identity.py +0 -0
  125. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_order_time_callback.py +0 -0
  126. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_project_updater.py +0 -0
  127. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_python_api_reference.py +0 -0
  128. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_qmt_auto_login_integration.py +0 -0
  129. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_qmt_binding_settings.py +0 -0
  130. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_qmt_component_login.py +0 -0
  131. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_qmt_fileless.py +0 -0
  132. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_qmt_strategy_browser.py +0 -0
  133. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_qmt_strategy_deploy.py +0 -0
  134. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_qmt_strategy_web.py +0 -0
  135. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_quote_latency.py +0 -0
  136. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_quote_latency_encoding.py +0 -0
  137. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_setup_qmt_prompt.py +0 -0
  138. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_startup_recovery.py +0 -0
  139. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_stock_connect.py +0 -0
  140. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_stock_connect_browser.py +0 -0
  141. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_table_conversion.py +0 -0
  142. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_trade_strategy_callback.py +0 -0
  143. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_trading_dates.py +0 -0
  144. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_tutorial_reader.py +0 -0
  145. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_tx_auto_import.py +0 -0
  146. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tests/test_tx_shutdown.py +0 -0
  147. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/trading_calendar.py +0 -0
  148. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tx.py +0 -0
  149. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/tx_trade_bridge.py +0 -0
  150. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/xtconstant.py +0 -0
  151. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/xtdata.py +0 -0
  152. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/xttrader.py +0 -0
  153. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant/xttype.py +0 -0
  154. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant.egg-info/dependency_links.txt +0 -0
  155. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant.egg-info/entry_points.txt +0 -0
  156. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant.egg-info/requires.txt +0 -0
  157. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant.egg-info/top_level.txt +0 -0
  158. {cfquant-0.2.54 → cfquant-0.2.55}/cfquant_pipe_hub.py +0 -0
  159. {cfquant-0.2.54 → cfquant-0.2.55}/qmt_scripts/CFQUANT.py +0 -0
  160. {cfquant-0.2.54 → cfquant-0.2.55}/qmt_scripts/CFQUANT_CTYPE_ALL_LOWLAT.py +0 -0
  161. {cfquant-0.2.54 → cfquant-0.2.55}/qmt_scripts/CFQUANT_TRADE_LOWLAT.py +0 -0
  162. {cfquant-0.2.54 → cfquant-0.2.55}/qmt_scripts/__init__.py +0 -0
  163. {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
  164. {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
  165. {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
  166. {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
  167. {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
  168. {cfquant-0.2.54 → cfquant-0.2.55}/requirements.txt +0 -0
  169. {cfquant-0.2.54 → cfquant-0.2.55}/setup.cfg +0 -0
  170. {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/__init__.py +0 -0
  171. {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/api-tester.js +0 -0
  172. {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/avatars/amber-pulse.svg +0 -0
  173. {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/avatars/copper-grid.svg +0 -0
  174. {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/avatars/market-blue.svg +0 -0
  175. {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/avatars/rose-circuit.svg +0 -0
  176. {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/avatars/signal-green.svg +0 -0
  177. {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/avatars/slate-wave.svg +0 -0
  178. {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/avatars/teal-orbit.svg +0 -0
  179. {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/avatars/violet-node.svg +0 -0
  180. {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/cftrader-api-data.js +0 -0
  181. {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/docs.js +0 -0
  182. {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/favicon.svg +0 -0
  183. {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/image/qmt_deploy/image-10.png +0 -0
  184. {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/image/qmt_deploy/image-11.png +0 -0
  185. {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/image/qmt_deploy/image-12.png +0 -0
  186. {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/image/qmt_deploy/image-13.png +0 -0
  187. {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/image/qmt_deploy/image-14.png +0 -0
  188. {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/image/qmt_deploy/image-15.png +0 -0
  189. {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/image/qmt_deploy/image-16.png +0 -0
  190. {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/image/qmt_deploy/image-17.png +0 -0
  191. {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/image/qmt_deploy/image-18.png +0 -0
  192. {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/image/qmt_deploy/image-19.png +0 -0
  193. {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/image/qmt_deploy/image-20.png +0 -0
  194. {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/image/qmt_deploy/image-21.png +0 -0
  195. {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/image/qmt_deploy/image-4.png +0 -0
  196. {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/image/qmt_deploy/image-5.png +0 -0
  197. {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/image/qmt_deploy/image-6.png +0 -0
  198. {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/image/qmt_deploy/image-7.png +0 -0
  199. {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/image/qmt_deploy/image-8.png +0 -0
  200. {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/image/qmt_deploy/image-9.png +0 -0
  201. {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/python-api-data.js +0 -0
  202. {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/python-api.js +0 -0
  203. {cfquant-0.2.54 → cfquant-0.2.55}/web_dashboard/vendor/mermaid.min.js +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cfquant
3
- Version: 0.2.54
3
+ Version: 0.2.55
4
4
  Summary: QMT local bridge and xtquant-compatible Python client.
5
5
  License: MIT
6
6
  Requires-Python: >=3.8
@@ -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
- import frida # noqa: F401
22
- import win32api
23
- import win32crypt # noqa: F401
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
- import frida
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
- import win32api
89
- import win32event
90
- import win32gui
91
- import win32process
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
- import win32api
236
- import win32event
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
- import win32crypt
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
- import win32crypt
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]
@@ -1,5 +1,5 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  """cfquant core version metadata."""
3
3
 
4
- __version__ = "0.2.54"
5
- WEB_VERSION = "web_20261019_01"
4
+ __version__ = "0.2.55"
5
+ WEB_VERSION = "web_20261020_01"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cfquant
3
- Version: 0.2.54
3
+ Version: 0.2.55
4
4
  Summary: QMT local bridge and xtquant-compatible Python client.
5
5
  License: MIT
6
6
  Requires-Python: >=3.8
@@ -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
- _qmt_auto_login_update_session(row, {
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
- ensure_lttx_started("Web 启动预启动")
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():
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "cfquant"
7
- version = "0.2.54"
7
+ version = "0.2.55"
8
8
  description = "QMT local bridge and xtquant-compatible Python client."
9
9
  license = {text = "MIT"}
10
10
  readme = "README.md"
@@ -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.54"
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.54"
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.54"
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])