cfquant 0.2.56__tar.gz → 0.2.57__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 (217) hide show
  1. {cfquant-0.2.56 → cfquant-0.2.57}/PKG-INFO +2 -1
  2. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/_editable_install.py +40 -2
  3. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/management.py +16 -0
  4. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/qmt_component_agent.js +11 -2
  5. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/qmt_component_login.py +109 -41
  6. cfquant-0.2.57/cfquant/qmt_update.py +366 -0
  7. cfquant-0.2.57/cfquant/remote_adapter.py +265 -0
  8. cfquant-0.2.57/cfquant/remote_management.py +407 -0
  9. cfquant-0.2.57/cfquant/remote_supervisor.py +120 -0
  10. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/16_/345/220/257/345/212/250/345/256/211/350/243/205/346/265/213/350/257/225.py +36 -1
  11. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/test_qmt_auto_login_browser.py +87 -0
  12. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/test_qmt_auto_login_integration.py +121 -3
  13. cfquant-0.2.57/cfquant/tests/test_qmt_component_login.py +347 -0
  14. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/test_qmt_strategy_browser.py +6 -5
  15. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/test_qmt_strategy_web.py +3 -3
  16. cfquant-0.2.57/cfquant/tests/test_qmt_update.py +74 -0
  17. cfquant-0.2.57/cfquant/tests/test_remote_management.py +352 -0
  18. cfquant-0.2.57/cfquant/tests/test_remote_management_browser.py +40 -0
  19. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/version.py +2 -2
  20. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant.egg-info/PKG-INFO +2 -1
  21. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant.egg-info/SOURCES.txt +8 -0
  22. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant.egg-info/requires.txt +3 -0
  23. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant_web_server.py +1261 -1065
  24. {cfquant-0.2.56 → cfquant-0.2.57}/pyproject.toml +2 -1
  25. {cfquant-0.2.56 → cfquant-0.2.57}/qmt_scripts/CFQUANT_LITE.py +1 -1
  26. {cfquant-0.2.56 → cfquant-0.2.57}/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
  27. {cfquant-0.2.56 → cfquant-0.2.57}/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
  28. {cfquant-0.2.56 → cfquant-0.2.57}/requirements.txt +2 -0
  29. {cfquant-0.2.56 → cfquant-0.2.57}/web_dashboard/app.js +66 -24
  30. {cfquant-0.2.56 → cfquant-0.2.57}/web_dashboard/index.html +68 -24
  31. cfquant-0.2.57/web_dashboard/remote-management.js +52 -0
  32. {cfquant-0.2.56 → cfquant-0.2.57}/web_dashboard/styles.css +4 -0
  33. cfquant-0.2.56/cfquant/tests/test_qmt_component_login.py +0 -165
  34. {cfquant-0.2.56 → cfquant-0.2.57}/LICENSE +0 -0
  35. {cfquant-0.2.56 → cfquant-0.2.57}/LTtx/__init__.py +0 -0
  36. {cfquant-0.2.56 → cfquant-0.2.57}/LTtx/tx/Config.txt +0 -0
  37. {cfquant-0.2.56 → cfquant-0.2.57}/LTtx/tx/LTtx_server.py +0 -0
  38. {cfquant-0.2.56 → cfquant-0.2.57}/LTtx/tx/__init__.py +0 -0
  39. {cfquant-0.2.56 → cfquant-0.2.57}/LTtx/tx/tx.py +0 -0
  40. {cfquant-0.2.56 → cfquant-0.2.57}/MANIFEST.in +0 -0
  41. {cfquant-0.2.56 → cfquant-0.2.57}/README.md +0 -0
  42. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/__init__.py +0 -0
  43. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/account_routing.py +0 -0
  44. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/batch_orders.py +0 -0
  45. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/build_info.py +0 -0
  46. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/callback_dispatcher.py +0 -0
  47. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/cftrader.py +0 -0
  48. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/channels.py +0 -0
  49. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/cli.py +0 -0
  50. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/client.py +0 -0
  51. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/config.py +0 -0
  52. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/diagnostics.py +0 -0
  53. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/flow_control.py +0 -0
  54. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/hub.py +0 -0
  55. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/level2.py +0 -0
  56. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/log_management.py +0 -0
  57. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/logging_i18n.py +0 -0
  58. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/normal_bridge.py +0 -0
  59. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/order_identity.py +0 -0
  60. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/order_meta.py +0 -0
  61. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/pipe_bridge.py +0 -0
  62. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/pipe_client.py +0 -0
  63. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/pipe_hub.py +0 -0
  64. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/pipe_transport.py +0 -0
  65. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/protocol.py +0 -0
  66. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/qmt_binding_settings.py +0 -0
  67. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/qmt_bridge.py +0 -0
  68. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/qmt_credentials.py +0 -0
  69. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/qmt_dependencies.py +0 -0
  70. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/qmt_pipe_runtime.py +0 -0
  71. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/qmt_strategy_deploy.py +0 -0
  72. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/qmt_strategy_package.py +0 -0
  73. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/qmt_strategy_runtime.py +0 -0
  74. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/quote_history.py +0 -0
  75. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/rpc_timing.py +0 -0
  76. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/runtime_report.py +0 -0
  77. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/stock_connect.py +0 -0
  78. {cfquant-0.2.56 → cfquant-0.2.57}/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
  79. {cfquant-0.2.56 → cfquant-0.2.57}/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
  80. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/12_/350/256/242/345/215/225/345/244/207/346/263/250/346/265/213/350/257/225.py" +0 -0
  81. {cfquant-0.2.56 → cfquant-0.2.57}/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
  82. {cfquant-0.2.56 → cfquant-0.2.57}/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
  83. {cfquant-0.2.56 → cfquant-0.2.57}/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
  84. {cfquant-0.2.56 → cfquant-0.2.57}/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
  85. {cfquant-0.2.56 → cfquant-0.2.57}/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
  86. {cfquant-0.2.56 → cfquant-0.2.57}/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
  87. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/1_/350/241/214/346/203/205/346/216/245/346/224/266/346/265/213/350/257/225.py" +0 -0
  88. {cfquant-0.2.56 → cfquant-0.2.57}/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
  89. {cfquant-0.2.56 → cfquant-0.2.57}/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
  90. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/22_/350/277/236/346/216/245/350/257/212/346/226/255/346/265/213/350/257/225.py" +0 -0
  91. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/23_/347/244/272/344/276/213/351/205/215/347/275/256/346/265/213/350/257/225.py" +0 -0
  92. {cfquant-0.2.56 → cfquant-0.2.57}/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
  93. {cfquant-0.2.56 → cfquant-0.2.57}/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
  94. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/26_/345/205/250/346/216/250/350/256/242/351/230/205/346/265/213/350/257/225.py" +0 -0
  95. {cfquant-0.2.56 → cfquant-0.2.57}/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
  96. {cfquant-0.2.56 → cfquant-0.2.57}/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
  97. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/29_Level2/350/241/214/346/203/205/346/265/213/350/257/225.py" +0 -0
  98. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/2_/346/225/260/346/215/256/350/216/267/345/217/226/346/265/213/350/257/225.py" +0 -0
  99. {cfquant-0.2.56 → cfquant-0.2.57}/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
  100. {cfquant-0.2.56 → cfquant-0.2.57}/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
  101. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/32_/345/274/202/346/255/245/344/270/213/345/215/225/346/265/213/350/257/225.py" +0 -0
  102. {cfquant-0.2.56 → cfquant-0.2.57}/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
  103. {cfquant-0.2.56 → cfquant-0.2.57}/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
  104. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/3_/346/225/260/346/215/256/344/270/213/350/275/275/346/265/213/350/257/225.py" +0 -0
  105. {cfquant-0.2.56 → cfquant-0.2.57}/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
  106. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/5_/347/234/237/345/256/236/344/270/213/345/215/225/346/265/213/350/257/225.py" +0 -0
  107. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/6_/345/233/236/350/260/203/346/265/213/350/257/225.py" +0 -0
  108. {cfquant-0.2.56 → cfquant-0.2.57}/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
  109. {cfquant-0.2.56 → cfquant-0.2.57}/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
  110. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/9_/345/217/257/346/222/244/350/256/242/345/215/225/346/265/213/350/257/225.py" +0 -0
  111. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/_helpers.py +0 -0
  112. {cfquant-0.2.56 → cfquant-0.2.57}/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
  113. {cfquant-0.2.56 → cfquant-0.2.57}/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
  114. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/readme.md +0 -0
  115. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/test_account_query_timing.py +0 -0
  116. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/test_advanced_parity.py +0 -0
  117. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/test_api_tester_browser.py +0 -0
  118. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/test_api_tester_web.py +0 -0
  119. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/test_binding_delete_confirmation.py +0 -0
  120. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/test_binding_live_refresh_browser.py +0 -0
  121. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/test_binding_runtime_browser.py +0 -0
  122. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/test_callback_recovery.py +0 -0
  123. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/test_callback_requests.py +0 -0
  124. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/test_cftrader.py +0 -0
  125. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/test_cftrader_tutorial.py +0 -0
  126. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/test_cftrader_web.py +0 -0
  127. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/test_credit_compat.py +0 -0
  128. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/test_dataframe_encoding.py +0 -0
  129. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/test_json_serialization.py +0 -0
  130. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/test_latency_flow_control.py +0 -0
  131. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/test_lite_account_routing.py +0 -0
  132. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/test_lite_deployment_paths.py +0 -0
  133. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/test_lite_parity.py +0 -0
  134. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/test_lite_qmt_dispatch.py +0 -0
  135. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/test_local_trading_calendar.py +0 -0
  136. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/test_log_management.py +0 -0
  137. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/test_management.py +0 -0
  138. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/test_management_batch.py +0 -0
  139. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/test_market_data_fallback.py +0 -0
  140. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/test_market_timing.py +0 -0
  141. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/test_order_identity.py +0 -0
  142. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/test_order_time_callback.py +0 -0
  143. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/test_project_updater.py +0 -0
  144. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/test_python_api_reference.py +0 -0
  145. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/test_qmt_binding_settings.py +0 -0
  146. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/test_qmt_dependencies.py +0 -0
  147. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/test_qmt_fileless.py +0 -0
  148. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/test_qmt_strategy_deploy.py +0 -0
  149. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/test_quote_latency.py +0 -0
  150. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/test_quote_latency_encoding.py +0 -0
  151. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/test_remote_version_check.py +0 -0
  152. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/test_setup_qmt_prompt.py +0 -0
  153. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/test_startup_recovery.py +0 -0
  154. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/test_stock_connect.py +0 -0
  155. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/test_stock_connect_browser.py +0 -0
  156. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/test_table_conversion.py +0 -0
  157. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/test_trade_strategy_callback.py +0 -0
  158. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/test_trader_subscription_recovery.py +0 -0
  159. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/test_trading_dates.py +0 -0
  160. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/test_tutorial_reader.py +0 -0
  161. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/test_tx_auto_import.py +0 -0
  162. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tests/test_tx_shutdown.py +0 -0
  163. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/trading_calendar.py +0 -0
  164. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tx.py +0 -0
  165. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/tx_trade_bridge.py +0 -0
  166. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/xtconstant.py +0 -0
  167. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/xtdata.py +0 -0
  168. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/xttrader.py +0 -0
  169. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant/xttype.py +0 -0
  170. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant.egg-info/dependency_links.txt +0 -0
  171. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant.egg-info/entry_points.txt +0 -0
  172. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant.egg-info/top_level.txt +0 -0
  173. {cfquant-0.2.56 → cfquant-0.2.57}/cfquant_pipe_hub.py +0 -0
  174. {cfquant-0.2.56 → cfquant-0.2.57}/qmt_scripts/CFQUANT.py +0 -0
  175. {cfquant-0.2.56 → cfquant-0.2.57}/qmt_scripts/CFQUANT_CTYPE_ALL_LOWLAT.py +0 -0
  176. {cfquant-0.2.56 → cfquant-0.2.57}/qmt_scripts/CFQUANT_TRADE_LOWLAT.py +0 -0
  177. {cfquant-0.2.56 → cfquant-0.2.57}/qmt_scripts/__init__.py +0 -0
  178. {cfquant-0.2.56 → cfquant-0.2.57}/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
  179. {cfquant-0.2.56 → cfquant-0.2.57}/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
  180. {cfquant-0.2.56 → cfquant-0.2.57}/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
  181. {cfquant-0.2.56 → cfquant-0.2.57}/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
  182. {cfquant-0.2.56 → cfquant-0.2.57}/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
  183. {cfquant-0.2.56 → cfquant-0.2.57}/setup.cfg +0 -0
  184. {cfquant-0.2.56 → cfquant-0.2.57}/web_dashboard/__init__.py +0 -0
  185. {cfquant-0.2.56 → cfquant-0.2.57}/web_dashboard/api-tester.js +0 -0
  186. {cfquant-0.2.56 → cfquant-0.2.57}/web_dashboard/avatars/amber-pulse.svg +0 -0
  187. {cfquant-0.2.56 → cfquant-0.2.57}/web_dashboard/avatars/copper-grid.svg +0 -0
  188. {cfquant-0.2.56 → cfquant-0.2.57}/web_dashboard/avatars/market-blue.svg +0 -0
  189. {cfquant-0.2.56 → cfquant-0.2.57}/web_dashboard/avatars/rose-circuit.svg +0 -0
  190. {cfquant-0.2.56 → cfquant-0.2.57}/web_dashboard/avatars/signal-green.svg +0 -0
  191. {cfquant-0.2.56 → cfquant-0.2.57}/web_dashboard/avatars/slate-wave.svg +0 -0
  192. {cfquant-0.2.56 → cfquant-0.2.57}/web_dashboard/avatars/teal-orbit.svg +0 -0
  193. {cfquant-0.2.56 → cfquant-0.2.57}/web_dashboard/avatars/violet-node.svg +0 -0
  194. {cfquant-0.2.56 → cfquant-0.2.57}/web_dashboard/cftrader-api-data.js +0 -0
  195. {cfquant-0.2.56 → cfquant-0.2.57}/web_dashboard/docs.js +0 -0
  196. {cfquant-0.2.56 → cfquant-0.2.57}/web_dashboard/favicon.svg +0 -0
  197. {cfquant-0.2.56 → cfquant-0.2.57}/web_dashboard/image/qmt_deploy/image-10.png +0 -0
  198. {cfquant-0.2.56 → cfquant-0.2.57}/web_dashboard/image/qmt_deploy/image-11.png +0 -0
  199. {cfquant-0.2.56 → cfquant-0.2.57}/web_dashboard/image/qmt_deploy/image-12.png +0 -0
  200. {cfquant-0.2.56 → cfquant-0.2.57}/web_dashboard/image/qmt_deploy/image-13.png +0 -0
  201. {cfquant-0.2.56 → cfquant-0.2.57}/web_dashboard/image/qmt_deploy/image-14.png +0 -0
  202. {cfquant-0.2.56 → cfquant-0.2.57}/web_dashboard/image/qmt_deploy/image-15.png +0 -0
  203. {cfquant-0.2.56 → cfquant-0.2.57}/web_dashboard/image/qmt_deploy/image-16.png +0 -0
  204. {cfquant-0.2.56 → cfquant-0.2.57}/web_dashboard/image/qmt_deploy/image-17.png +0 -0
  205. {cfquant-0.2.56 → cfquant-0.2.57}/web_dashboard/image/qmt_deploy/image-18.png +0 -0
  206. {cfquant-0.2.56 → cfquant-0.2.57}/web_dashboard/image/qmt_deploy/image-19.png +0 -0
  207. {cfquant-0.2.56 → cfquant-0.2.57}/web_dashboard/image/qmt_deploy/image-20.png +0 -0
  208. {cfquant-0.2.56 → cfquant-0.2.57}/web_dashboard/image/qmt_deploy/image-21.png +0 -0
  209. {cfquant-0.2.56 → cfquant-0.2.57}/web_dashboard/image/qmt_deploy/image-4.png +0 -0
  210. {cfquant-0.2.56 → cfquant-0.2.57}/web_dashboard/image/qmt_deploy/image-5.png +0 -0
  211. {cfquant-0.2.56 → cfquant-0.2.57}/web_dashboard/image/qmt_deploy/image-6.png +0 -0
  212. {cfquant-0.2.56 → cfquant-0.2.57}/web_dashboard/image/qmt_deploy/image-7.png +0 -0
  213. {cfquant-0.2.56 → cfquant-0.2.57}/web_dashboard/image/qmt_deploy/image-8.png +0 -0
  214. {cfquant-0.2.56 → cfquant-0.2.57}/web_dashboard/image/qmt_deploy/image-9.png +0 -0
  215. {cfquant-0.2.56 → cfquant-0.2.57}/web_dashboard/python-api-data.js +0 -0
  216. {cfquant-0.2.56 → cfquant-0.2.57}/web_dashboard/python-api.js +0 -0
  217. {cfquant-0.2.56 → cfquant-0.2.57}/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.56
3
+ Version: 0.2.57
4
4
  Summary: QMT local bridge and xtquant-compatible Python client.
5
5
  License: MIT
6
6
  Requires-Python: >=3.8
@@ -9,6 +9,7 @@ License-File: LICENSE
9
9
  Requires-Dist: pandas>=1.0
10
10
  Requires-Dist: packaging>=20
11
11
  Requires-Dist: psutil>=5.8
12
+ Requires-Dist: tomli>=1.1.0; python_version < "3.11"
12
13
  Requires-Dist: pycryptodome>=3.20
13
14
  Requires-Dist: pytz
14
15
  Requires-Dist: tabulate
@@ -16,6 +16,7 @@ else:
16
16
 
17
17
 
18
18
  PACKAGE_NAME = "cfquant"
19
+ TOMLI_REQUIREMENT = "tomli>=1.1.0"
19
20
 
20
21
 
21
22
  def _hidden_subprocess_kwargs():
@@ -118,6 +119,21 @@ def requirements_install_args(project_root, python_exe=None):
118
119
  return command
119
120
 
120
121
 
122
+ def tomli_install_args(project_root=None, python_exe=None):
123
+ """Build the bootstrap command needed before parsing TOML on old Python."""
124
+ command = [
125
+ python_exe or sys.executable,
126
+ "-m",
127
+ "pip",
128
+ "install",
129
+ "--disable-pip-version-check",
130
+ "--no-input",
131
+ ]
132
+ command.extend(pip_index_args())
133
+ command.append(TOMLI_REQUIREMENT)
134
+ return command
135
+
136
+
121
137
  def _output_tail(value, limit=6000):
122
138
  text = str(value or "")
123
139
  if len(text) <= int(limit):
@@ -478,7 +494,12 @@ def project_package_version(project_root):
478
494
  try:
479
495
  import tomllib
480
496
  except ImportError:
481
- from pip._vendor import tomli as tomllib
497
+ try:
498
+ import tomli as tomllib
499
+ except ImportError:
500
+ # Keep compatibility with pip versions that vendor tomli while
501
+ # preferring the project's explicit conditional dependency.
502
+ from pip._vendor import tomli as tomllib
482
503
  with (Path(project_root) / "pyproject.toml").open("rb") as stream:
483
504
  return str(tomllib.load(stream)["project"]["version"])
484
505
 
@@ -503,7 +524,24 @@ def ensure_cfquant_installed(project_root, python_exe=None):
503
524
  return 2
504
525
 
505
526
  python_exe = python_exe or sys.executable
506
- expected = project_package_version(project_root)
527
+ try:
528
+ expected = project_package_version(project_root)
529
+ except ImportError:
530
+ # project_package_version runs before the project itself is installed.
531
+ # Bootstrap tomli here so a clean Python 3.8-3.10 environment can still
532
+ # complete its first startup without requiring a manual pip command.
533
+ print("TOML parser unavailable; installing %s before startup check." % TOMLI_REQUIREMENT)
534
+ bootstrap = run(tomli_install_args(project_root, python_exe), project_root)
535
+ if bootstrap.returncode != 0:
536
+ print("[ERROR] Could not install %s; cannot read pyproject.toml." % TOMLI_REQUIREMENT,
537
+ file=sys.stderr)
538
+ return bootstrap.returncode
539
+ try:
540
+ expected = project_package_version(project_root)
541
+ except ImportError:
542
+ print("[ERROR] %s is still unavailable after bootstrap installation." % TOMLI_REQUIREMENT,
543
+ file=sys.stderr)
544
+ return 1
507
545
  installed = installed_package_version(python_exe)
508
546
  print("cfquant installed=%s project=%s" % (installed or "missing", expected))
509
547
  if installed == expected:
@@ -100,6 +100,7 @@ class RuntimeManager:
100
100
  self._process = None
101
101
  self.accounts = AccountManager(self)
102
102
  self.updates = UpdateManager(self)
103
+ self.remote = RemoteManager(self)
103
104
  # Local service requests should never be sent to an HTTP proxy.
104
105
  self._opener = urllib.request.build_opener(urllib.request.ProxyHandler({}))
105
106
 
@@ -337,6 +338,21 @@ class RuntimeManager:
337
338
  configure(host=info["lttx_host"], port=info["lttx_port"], transport="web_lttx")
338
339
 
339
340
 
341
+ class RemoteManager:
342
+ """Opt-in connection using a project API key created on the official site."""
343
+
344
+ def __init__(self, runtime):
345
+ self.runtime = runtime
346
+
347
+ def status(self):
348
+ return self.runtime._request("GET", "/api/remote-management")
349
+
350
+ def configure(self, *, enabled=True, api_key=None, permissions=None):
351
+ body = {"enabled": bool(enabled)}
352
+ body.update({k: v for k, v in {"api_key": api_key, "permissions": permissions}.items() if v is not None})
353
+ return self.runtime._request("POST", "/api/remote-management", body)
354
+
355
+
340
356
  class AccountManager:
341
357
  def __init__(self, runtime):
342
358
  self.runtime = runtime
@@ -115,10 +115,19 @@ function noticeKind(rows) {
115
115
  }
116
116
 
117
117
  function submit(root) {
118
+ const rows = walk(root);
119
+ const edits = rows.filter(row => row.echo_mode === 2 && row.visible && row.enabled);
120
+ // Check on the GUI thread as well: an update can disable the button after
121
+ // Python has inspected it. A declined submission is safe to retry later.
122
+ if (edits.length !== 1 || !hasText(ptr(edits[0].address)) ||
123
+ !rows.some(row => row.visible && row.enabled && /^登\s*录(?:[((]\d+[))])?$/.test(row.label || ''))) {
124
+ return {submitted: false};
125
+ }
118
126
  passwordEditor(root);
119
127
  const login = fn(Process.getModuleByName('LoginDialog.dll'),
120
128
  '?slot_loginClicked@CLoginDialog@TC@@QEAAXXZ', 'void', ['pointer']);
121
129
  login(root);
130
+ return {submitted: true};
122
131
  }
123
132
 
124
133
  function loginIdentity(root) {
@@ -190,7 +199,7 @@ rpc.exports = {
190
199
  const edits = rows.filter(row => row.echo_mode === 2 && row.visible);
191
200
  if (edits.length > 1) throw new Error('Multiple password editors are visible');
192
201
  return {is_login: true, has_password: edits.length === 1 && hasText(ptr(edits[0].address)),
193
- busy: edits.length === 0, login_identity: loginIdentity(root), notice: noticeKind(rows),
202
+ busy: edits.length === 0 || !edits[0].enabled, login_identity: loginIdentity(root), notice: noticeKind(rows),
194
203
  login_enabled: rows.some(row => row.visible && row.enabled && /^登\s*录(?:[((]\d+[))])?$/.test(row.label || ''))};
195
204
  });
196
205
  },
@@ -210,7 +219,7 @@ rpc.exports = {
210
219
  return {acknowledged: true};
211
220
  });
212
221
  },
213
- submit(hwnd) { return onGui(hwnd, () => { submit(getRoot(hwnd)); return {submitted: true}; }); },
222
+ submit(hwnd) { return onGui(hwnd, () => submit(getRoot(hwnd))); },
214
223
  close(hwnd) { return onGui(hwnd, () => ({closed: closeWidget(getRoot(hwnd))})); },
215
224
  clicklabel(hwnd, text) {
216
225
  return onGui(hwnd, () => {
@@ -16,6 +16,10 @@ class QmtNotReady(RuntimeError):
16
16
  pass
17
17
 
18
18
 
19
+ class QmtProcessExited(RuntimeError):
20
+ pass
21
+
22
+
19
23
  def verify_environment(directory):
20
24
  """Validate dependencies and native ABI before stopping a running terminal."""
21
25
  try:
@@ -37,8 +41,13 @@ def verify_environment(directory):
37
41
  raise RuntimeError("QMT 组件自动登录需要 64 位客户端")
38
42
 
39
43
 
40
- def auto_login(backend, password_provider):
41
- """Replace existing password contents and submit the supplied credential once."""
44
+ def auto_login(backend, password_provider, *, retry_not_ready=False, login_account=None,
45
+ should_cancel=lambda: False, on_wait=lambda phase: None):
46
+ """Wait through startup/update when managed, and submit the credential once.
47
+
48
+ Readiness retries never force a disabled button. After submission, a timeout
49
+ only renews observation: it is not evidence that another submission is safe.
50
+ """
42
51
  attempts = []
43
52
  password_requested = False
44
53
 
@@ -46,28 +55,59 @@ def auto_login(backend, password_provider):
46
55
  return {"outcome": outcome, "login_succeeded": outcome in ("logged_in", "already_logged_in"),
47
56
  "attempts": attempts, "password_requested": password_requested}
48
57
 
49
- if backend.already_logged_in():
50
- return result("already_logged_in")
51
- state = backend.state()
52
- if state.get("busy"):
53
- return result("login_not_ready")
54
- if state.get("notice") in ("challenge", "network_error"):
55
- return result(state["notice"])
56
- if state.get("notice") == "credential_error":
57
- backend.acknowledge_failure()
58
- password_requested = True
59
- password = password_provider()
60
- if not password:
61
- return result("password_required")
62
- try:
63
- backend.fill(password)
64
- finally:
65
- password = None
66
- if not backend.state()["login_enabled"]:
67
- return result("login_not_ready")
68
- backend.submit()
69
- attempts.append("provided")
70
- return result(backend.wait_outcome())
58
+ while True:
59
+ if should_cancel():
60
+ return result("cancelled")
61
+ if backend.already_logged_in():
62
+ if login_account is not None:
63
+ backend.select_account(login_account)
64
+ return result("already_logged_in")
65
+ state = backend.state()
66
+ if state.get("notice") in ("challenge", "network_error"):
67
+ return result(state["notice"])
68
+ if not state.get("busy"):
69
+ if not password_requested or not state.get("has_password"):
70
+ if not password_requested:
71
+ if state.get("notice") == "credential_error":
72
+ backend.acknowledge_failure()
73
+ if login_account is not None:
74
+ backend.select_account(login_account, replace=True)
75
+ if should_cancel():
76
+ return result("cancelled")
77
+ password_requested = True
78
+ password = password_provider()
79
+ if not password:
80
+ return result("password_required")
81
+ try:
82
+ backend.fill(password)
83
+ finally:
84
+ password = None
85
+ state = backend.state()
86
+ if state.get("login_enabled") and not state.get("busy"):
87
+ if should_cancel():
88
+ return result("cancelled")
89
+ try:
90
+ backend.submit()
91
+ except QmtNotReady:
92
+ # The button can become disabled between status and submit.
93
+ pass
94
+ else:
95
+ attempts.append("provided")
96
+ break
97
+ if not retry_not_ready:
98
+ return result("login_not_ready")
99
+ on_wait("waiting_ready")
100
+ time.sleep(0.5)
101
+
102
+ on_wait("logging_in")
103
+ while True:
104
+ if should_cancel():
105
+ return result("cancelled")
106
+ outcome = backend.wait_outcome()
107
+ if outcome != "timeout" or not retry_not_ready:
108
+ return result(outcome)
109
+ on_wait("logging_in")
110
+ time.sleep(0.5)
71
111
 
72
112
 
73
113
  def select_binding(config_path, name):
@@ -104,6 +144,7 @@ class QtLoginBackend:
104
144
  self.session = None
105
145
  self.mutex = None
106
146
  self.messages = []
147
+ self.should_cancel = lambda: False
107
148
  self.foreground_before = win32gui.GetForegroundWindow()
108
149
  expected = (directory / "XtItClient.exe").resolve()
109
150
  matches = []
@@ -114,6 +155,8 @@ class QtLoginBackend:
114
155
  except (psutil.NoSuchProcess, psutil.AccessDenied):
115
156
  continue
116
157
  if not matches:
158
+ if expected_pid is not None and not psutil.pid_exists(expected_pid):
159
+ raise QmtProcessExited("QMT process exited")
117
160
  raise QmtNotReady("QMT process is not ready")
118
161
  if len(matches) != 1:
119
162
  raise RuntimeError("Expected exactly one running QMT for this binding directory")
@@ -167,11 +210,18 @@ class QtLoginBackend:
167
210
  return result
168
211
 
169
212
  def already_logged_in(self):
213
+ if self.session.is_detached:
214
+ raise QmtProcessExited("QMT automation session detached")
215
+ if not self.existing_main or self.hwnd is not None:
216
+ for hwnd in self.windows():
217
+ if self.api.status(str(hwnd))["result"].get("is_main"):
218
+ self.existing_main, self.main_hwnd, self.hwnd = True, hwnd, None
219
+ break
170
220
  return self.existing_main and self.hwnd is None
171
221
 
172
222
  def state(self):
173
223
  state = self.api.status(str(self.hwnd))["result"]
174
- if not state.get("is_login") or not self.identity or state.get("login_identity") != self.identity:
224
+ if not state.get("is_login") or state.get("login_identity") != self.identity:
175
225
  raise RuntimeError("Login window or selected account changed; stopping")
176
226
  state.pop("login_identity", None)
177
227
  for hwnd in self.windows():
@@ -197,8 +247,9 @@ class QtLoginBackend:
197
247
  def submit(self):
198
248
  state = self.state()
199
249
  if not state["has_password"] or not state["login_enabled"]:
200
- raise RuntimeError("Login is not ready")
201
- self.api.submit(str(self.hwnd))
250
+ raise QmtNotReady("Login is not ready")
251
+ if not self.api.submit(str(self.hwnd))["result"]["submitted"]:
252
+ raise QmtNotReady("Login is not ready")
202
253
 
203
254
  def fill(self, password):
204
255
  self.state()
@@ -207,6 +258,10 @@ class QtLoginBackend:
207
258
  def wait_outcome(self):
208
259
  deadline = time.monotonic() + self.timeout
209
260
  while time.monotonic() < deadline:
261
+ if self.should_cancel():
262
+ return "cancelled"
263
+ if self.session.is_detached:
264
+ raise QmtProcessExited("QMT automation session detached")
210
265
  windows = self.windows()
211
266
  for hwnd in windows:
212
267
  caption = self.gui.GetWindowText(hwnd)
@@ -291,28 +346,41 @@ def main():
291
346
 
292
347
 
293
348
  def login_running_qmt(directory, login_account="", password_provider=lambda: "", expected_pid=None,
294
- startup_timeout=60, timeout=40):
295
- """Wait for a freshly launched QMT, then execute one bounded login flow."""
296
- deadline = time.monotonic() + startup_timeout
349
+ startup_timeout=60, timeout=40, *, should_cancel=lambda: False,
350
+ on_wait=lambda phase: None, before_poll=lambda: None):
351
+ """Wait for QMT startup/updates and observe login until success or rejection.
352
+
353
+ The web worker uses startup_timeout=None to wait while its process is alive.
354
+ Cancellation stops waiting when that worker is disabled or superseded.
355
+ """
356
+ cancelled = should_cancel
357
+ def should_cancel():
358
+ if cancelled():
359
+ return True
360
+ before_poll()
361
+ return cancelled()
362
+
363
+ deadline = None if startup_timeout is None else time.monotonic() + startup_timeout
364
+ backend = None
297
365
  while True:
366
+ if should_cancel():
367
+ return {"outcome": "cancelled", "login_succeeded": False, "attempts": []}
298
368
  try:
299
369
  backend = QtLoginBackend(Path(directory), timeout, expected_pid=expected_pid)
300
370
  break
371
+ except QmtProcessExited:
372
+ return {"outcome": "process_exited", "login_succeeded": False, "attempts": []}
301
373
  except QmtNotReady:
302
- if time.monotonic() >= deadline:
374
+ if deadline is not None and time.monotonic() >= deadline:
303
375
  return {"outcome": "startup_timeout", "login_succeeded": False, "attempts": []}
376
+ on_wait("waiting_ready")
304
377
  time.sleep(0.5)
305
378
  try:
306
- if not backend.already_logged_in() and backend.state().get("busy"):
307
- outcome = backend.wait_outcome()
308
- if outcome == "logged_in":
309
- # Reopen only the automation session to verify the resulting user.
310
- backend.close()
311
- backend = QtLoginBackend(Path(directory), timeout, expected_pid=expected_pid)
312
- elif outcome != "credential_error":
313
- return {"outcome": outcome, "login_succeeded": False, "attempts": []}
314
- backend.select_account(login_account, replace=True)
315
- return auto_login(backend, password_provider)
379
+ backend.should_cancel = should_cancel
380
+ return auto_login(backend, password_provider, retry_not_ready=True, login_account=login_account,
381
+ should_cancel=should_cancel, on_wait=on_wait)
382
+ except QmtProcessExited:
383
+ return {"outcome": "process_exited", "login_succeeded": False, "attempts": []}
316
384
  finally:
317
385
  backend.close()
318
386