cfquant 0.2.66__tar.gz → 0.2.67__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (306) hide show
  1. {cfquant-0.2.66 → cfquant-0.2.67}/MANIFEST.in +5 -0
  2. {cfquant-0.2.66 → cfquant-0.2.67}/PKG-INFO +3 -1
  3. {cfquant-0.2.66 → cfquant-0.2.67}/README.md +1 -0
  4. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/batch_orders.py +4 -1
  5. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/order_identity.py +27 -1
  6. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/order_meta.py +128 -3
  7. cfquant-0.2.67/cfquant/qmt_mac_agent.js +87 -0
  8. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/qmt_python_runtime.py +46 -23
  9. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/qmt_update.py +17 -4
  10. cfquant-0.2.67/cfquant/qmt_vm_agent.js +109 -0
  11. cfquant-0.2.67/cfquant/qmt_vm_discovery.py +178 -0
  12. cfquant-0.2.67/cfquant/qmt_vm_runtime.py +105 -0
  13. cfquant-0.2.67/cfquant/qmt_vm_settings.py +69 -0
  14. cfquant-0.2.67/cfquant/qmt_vm_worker.py +164 -0
  15. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/quote_service.py +11 -5
  16. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/12_/350/256/242/345/215/225/345/244/207/346/263/250/346/265/213/350/257/225.py +6 -0
  17. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/20_/350/201/224/350/260/203/351/227/256/351/242/230/344/277/256/345/244/215/346/265/213/350/257/225.py +33 -5
  18. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/26_/345/205/250/346/216/250/350/256/242/351/230/205/346/265/213/350/257/225.py +8 -4
  19. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/27_/345/205/250/346/216/250/350/256/242/351/230/205/351/241/265/351/235/242/346/265/213/350/257/225.py +105 -1
  20. cfquant-0.2.67/cfquant/tests/qmt_vm_native_fixture.js +118 -0
  21. cfquant-0.2.67/cfquant/tests/test_auto_login_app.py +363 -0
  22. cfquant-0.2.67/cfquant/tests/test_binding_python_runtime_browser.py +124 -0
  23. cfquant-0.2.67/cfquant/tests/test_binding_vm_browser.py +90 -0
  24. cfquant-0.2.67/cfquant/tests/test_qmt_python_runtime.py +268 -0
  25. cfquant-0.2.67/cfquant/tests/test_qmt_vm_discovery.py +161 -0
  26. cfquant-0.2.67/cfquant/tests/test_qmt_vm_native.py +41 -0
  27. cfquant-0.2.67/cfquant/tests/test_qmt_vm_runtime.py +167 -0
  28. cfquant-0.2.67/cfquant/tests/test_qmt_vm_settings.py +86 -0
  29. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_quote_service.py +153 -0
  30. cfquant-0.2.67/cfquant/tests/test_split_order_identity.py +263 -0
  31. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_trading_monitor_browser.py +131 -0
  32. cfquant-0.2.67/cfquant/tests/test_version_check.py +75 -0
  33. cfquant-0.2.67/cfquant/tests/test_version_check_browser.py +128 -0
  34. cfquant-0.2.67/cfquant/tools/THIRD_PARTY.md +16 -0
  35. cfquant-0.2.67/cfquant/tools/upx/COPYING +339 -0
  36. cfquant-0.2.67/cfquant/tools/upx/LICENSE +138 -0
  37. cfquant-0.2.67/cfquant/tools/upx/NEWS +603 -0
  38. cfquant-0.2.67/cfquant/tools/upx/README +117 -0
  39. cfquant-0.2.67/cfquant/tools/upx/THANKS.txt +74 -0
  40. cfquant-0.2.67/cfquant/tools/upx/upx-doc.html +845 -0
  41. cfquant-0.2.67/cfquant/tools/upx/upx-doc.txt +893 -0
  42. cfquant-0.2.67/cfquant/tools/upx/upx.1 +1015 -0
  43. cfquant-0.2.67/cfquant/tools/upx/upx.exe +0 -0
  44. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tx_trade_bridge.py +13 -46
  45. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/version.py +2 -2
  46. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant.egg-info/PKG-INFO +3 -1
  47. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant.egg-info/SOURCES.txt +26 -0
  48. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant.egg-info/requires.txt +1 -0
  49. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant_web_server.py +190 -19
  50. {cfquant-0.2.66 → cfquant-0.2.67}/pyproject.toml +4 -3
  51. {cfquant-0.2.66 → cfquant-0.2.67}/qmt_scripts/CFQUANT_LITE.py +41 -6
  52. {cfquant-0.2.66 → cfquant-0.2.67}/qmt_scripts//345/220/214/350/264/246/345/217/267/347/213/254/347/253/213/345/270/202/345/234/272/CFQUANT_LITE_SH.py +41 -6
  53. {cfquant-0.2.66 → cfquant-0.2.67}/qmt_scripts//345/220/214/350/264/246/345/217/267/347/213/254/347/253/213/345/270/202/345/234/272/CFQUANT_LITE_SZ.py +41 -6
  54. {cfquant-0.2.66 → cfquant-0.2.67}/requirements.txt +1 -0
  55. {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/app.js +418 -81
  56. {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/index.html +35 -3
  57. {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/styles.css +18 -1
  58. {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/trading-monitor.js +90 -20
  59. cfquant-0.2.66/cfquant/tests/test_auto_login_app.py +0 -166
  60. cfquant-0.2.66/cfquant/tests/test_qmt_python_runtime.py +0 -133
  61. {cfquant-0.2.66 → cfquant-0.2.67}/LICENSE +0 -0
  62. {cfquant-0.2.66 → cfquant-0.2.67}/LTtx/__init__.py +0 -0
  63. {cfquant-0.2.66 → cfquant-0.2.67}/LTtx/tx/Config.txt +0 -0
  64. {cfquant-0.2.66 → cfquant-0.2.67}/LTtx/tx/LTtx_server.py +0 -0
  65. {cfquant-0.2.66 → cfquant-0.2.67}/LTtx/tx/__init__.py +0 -0
  66. {cfquant-0.2.66 → cfquant-0.2.67}/LTtx/tx/tx.py +0 -0
  67. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/__init__.py +0 -0
  68. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/_editable_install.py +0 -0
  69. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/account_routing.py +0 -0
  70. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/algo.py +0 -0
  71. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/algo_broker.py +0 -0
  72. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/build_info.py +0 -0
  73. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/callback_dispatcher.py +0 -0
  74. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/cftrader.py +0 -0
  75. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/channels.py +0 -0
  76. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/cli.py +0 -0
  77. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/client.py +0 -0
  78. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/conditional_orders.py +0 -0
  79. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/config.py +0 -0
  80. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/diagnostic_context.py +0 -0
  81. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/diagnostics.py +0 -0
  82. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/flow_control.py +0 -0
  83. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/hub.py +0 -0
  84. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/level2.py +0 -0
  85. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/log_management.py +0 -0
  86. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/logging_i18n.py +0 -0
  87. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/management.py +0 -0
  88. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/monitor_notifications.py +0 -0
  89. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/monitor_performance.py +0 -0
  90. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/normal_bridge.py +0 -0
  91. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/pipe_bridge.py +0 -0
  92. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/pipe_client.py +0 -0
  93. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/pipe_hub.py +0 -0
  94. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/pipe_transport.py +0 -0
  95. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/planning_broker.py +0 -0
  96. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/position_sizing.py +0 -0
  97. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/protocol.py +0 -0
  98. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/qmt_binding_defaults.py +0 -0
  99. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/qmt_binding_settings.py +0 -0
  100. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/qmt_bridge.py +0 -0
  101. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/qmt_component_agent.js +0 -0
  102. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/qmt_component_login.py +0 -0
  103. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/qmt_credentials.py +0 -0
  104. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/qmt_dependencies.py +0 -0
  105. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/qmt_inner.py +0 -0
  106. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/qmt_inner_catalog.py +0 -0
  107. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/qmt_inner_runtime.py +0 -0
  108. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/qmt_native_files.py +0 -0
  109. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/qmt_native_worker.py +0 -0
  110. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/qmt_pipe_runtime.py +0 -0
  111. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/qmt_strategy_agent.js +0 -0
  112. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/qmt_strategy_deploy.py +0 -0
  113. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/qmt_strategy_package.py +0 -0
  114. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/qmt_strategy_recovery.py +0 -0
  115. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/qmt_strategy_runtime.py +0 -0
  116. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/quote_history.py +0 -0
  117. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/quote_leases.py +0 -0
  118. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/remote_adapter.py +0 -0
  119. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/remote_management.py +0 -0
  120. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/remote_supervisor.py +0 -0
  121. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/rpc_timing.py +0 -0
  122. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/runtime_report.py +0 -0
  123. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/shared_quotes.py +0 -0
  124. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/smart_orders.py +0 -0
  125. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/stock_connect.py +0 -0
  126. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/subscription_bridge.py +0 -0
  127. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/subscription_client.py +0 -0
  128. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/subscription_manager.py +0 -0
  129. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/subscription_store.py +0 -0
  130. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/10_/345/221/275/344/273/244/350/241/214/346/216/245/345/217/243/346/265/213/350/257/225.py" +0 -0
  131. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/11_/346/231/256/351/200/232/346/241/245/346/216/245/346/264/276/345/217/221/346/265/213/350/257/225.py" +0 -0
  132. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/13_/347/256/241/351/201/223/344/270/255/345/277/203/350/277/236/346/216/245/346/265/213/350/257/225.py" +0 -0
  133. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/14_/350/277/220/350/241/214/346/227/266/346/212/245/345/221/212/346/265/213/350/257/225.py" +0 -0
  134. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/15_/347/275/221/351/241/265/347/212/266/346/200/201/347/274/223/345/255/230/346/265/213/350/257/225.py" +0 -0
  135. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/16_/345/220/257/345/212/250/345/256/211/350/243/205/346/265/213/350/257/225.py" +0 -0
  136. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/17_/345/244/247QMT/346/216/245/345/217/243/351/200/202/351/205/215/346/265/213/350/257/225.py" +0 -0
  137. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/18_/351/253/230/347/272/247/346/250/241/345/274/217/345/256/236/346/234/272/350/201/224/350/260/203.py" +0 -0
  138. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/19_/345/256/236/346/234/272/350/201/224/350/260/203/345/210/244/345/256/232/346/265/213/350/257/225.py" +0 -0
  139. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/1_/350/241/214/346/203/205/346/216/245/346/224/266/346/265/213/350/257/225.py" +0 -0
  140. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/21_/345/215/225/350/202/241/350/241/214/346/203/205/350/256/242/351/230/205/346/265/213/350/257/225.py" +0 -0
  141. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/22_/350/277/236/346/216/245/350/257/212/346/226/255/346/265/213/350/257/225.py" +0 -0
  142. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/23_/347/244/272/344/276/213/351/205/215/347/275/256/346/265/213/350/257/225.py" +0 -0
  143. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/24_/350/264/246/346/210/267/347/274/223/345/255/230/346/214/211/351/234/200/345/210/267/346/226/260/346/265/213/350/257/225.py" +0 -0
  144. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/25_/350/264/246/346/210/267/347/274/223/345/255/230/351/241/265/351/235/242/346/265/213/350/257/225.py" +0 -0
  145. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/28_Level2/346/216/245/345/217/243/351/200/202/351/205/215/346/265/213/350/257/225.py" +0 -0
  146. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/29_Level2/350/241/214/346/203/205/346/265/213/350/257/225.py" +0 -0
  147. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/2_/346/225/260/346/215/256/350/216/267/345/217/226/346/265/213/350/257/225.py" +0 -0
  148. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/30_cftrader/346/211/271/351/207/217/344/270/213/345/215/225/346/200/247/350/203/275/345/237/272/345/207/206.py" +0 -0
  149. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/31_cftrader/346/250/241/346/213/237/350/264/246/345/217/267/346/211/271/351/207/217/344/270/213/345/215/225/346/265/213/350/257/225.py" +0 -0
  150. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/32_/345/274/202/346/255/245/344/270/213/345/215/225/346/265/213/350/257/225.py" +0 -0
  151. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/33_/345/220/214/346/255/245/344/270/213/345/215/225/345/233/236/350/260/203/346/265/213/350/257/225.py" +0 -0
  152. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/34_/345/205/250/346/216/250/345/206/205/351/203/250/345/273/266/350/277/237/346/265/213/350/257/225.py" +0 -0
  153. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/3_/346/225/260/346/215/256/344/270/213/350/275/275/346/265/213/350/257/225.py" +0 -0
  154. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/4_/344/272/244/346/230/223/345/247/224/346/211/230/346/237/245/350/257/242/346/265/213/350/257/225.py" +0 -0
  155. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/5_/347/234/237/345/256/236/344/270/213/345/215/225/346/265/213/350/257/225.py" +0 -0
  156. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/6_/345/233/236/350/260/203/346/265/213/350/257/225.py" +0 -0
  157. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/7_/345/220/214/346/255/245/345/274/202/346/255/245/344/270/213/345/215/225/346/265/213/350/257/225.py" +0 -0
  158. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/8_/346/210/220/344/272/244/350/256/242/345/215/225/345/222/214/345/247/224/346/211/230/350/256/242/345/215/225/346/237/245/350/257/242/346/265/213/350/257/225.py" +0 -0
  159. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/9_/345/217/257/346/222/244/350/256/242/345/215/225/346/265/213/350/257/225.py" +0 -0
  160. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/_helpers.py +0 -0
  161. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/readme.md +0 -0
  162. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_account_query_timing.py +0 -0
  163. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_account_subscription_probe.py +0 -0
  164. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_activity_log_browser.py +0 -0
  165. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_advanced_parity.py +0 -0
  166. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_algo.py +0 -0
  167. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_algo_broker.py +0 -0
  168. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_api_tester_browser.py +0 -0
  169. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_api_tester_web.py +0 -0
  170. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_binding_delete_confirmation.py +0 -0
  171. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_binding_live_refresh_browser.py +0 -0
  172. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_binding_runtime_browser.py +0 -0
  173. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_callback_recovery.py +0 -0
  174. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_callback_requests.py +0 -0
  175. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_cftrader.py +0 -0
  176. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_cftrader_tutorial.py +0 -0
  177. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_cftrader_web.py +0 -0
  178. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_credit_compat.py +0 -0
  179. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_dataframe_encoding.py +0 -0
  180. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_diagnostic_context.py +0 -0
  181. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_json_serialization.py +0 -0
  182. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_latency_flow_control.py +0 -0
  183. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_lite_account_routing.py +0 -0
  184. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_lite_deployment_paths.py +0 -0
  185. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_lite_parity.py +0 -0
  186. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_lite_qmt_dispatch.py +0 -0
  187. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_local_trading_calendar.py +0 -0
  188. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_log_management.py +0 -0
  189. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_management.py +0 -0
  190. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_management_batch.py +0 -0
  191. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_market_data_fallback.py +0 -0
  192. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_market_timing.py +0 -0
  193. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_monitor_notifications.py +0 -0
  194. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_monitor_performance.py +0 -0
  195. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_navigation_browser.py +0 -0
  196. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_order_identity.py +0 -0
  197. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_order_time_callback.py +0 -0
  198. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_pipe_shutdown.py +0 -0
  199. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_project_updater.py +0 -0
  200. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_python_api_reference.py +0 -0
  201. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_qmt_auto_login_browser.py +0 -0
  202. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_qmt_auto_login_integration.py +0 -0
  203. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_qmt_binding_defaults.py +0 -0
  204. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_qmt_binding_settings.py +0 -0
  205. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_qmt_component_login.py +0 -0
  206. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_qmt_dependencies.py +0 -0
  207. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_qmt_fileless.py +0 -0
  208. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_qmt_inner.py +0 -0
  209. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_qmt_inner_reference.py +0 -0
  210. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_qmt_inner_web.py +0 -0
  211. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_qmt_native_files.py +0 -0
  212. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_qmt_native_reuse.py +0 -0
  213. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_qmt_native_worker.py +0 -0
  214. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_qmt_package_import.py +0 -0
  215. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_qmt_strategy_browser.py +0 -0
  216. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_qmt_strategy_capabilities.py +0 -0
  217. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_qmt_strategy_deploy.py +0 -0
  218. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_qmt_strategy_recovery.py +0 -0
  219. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_qmt_strategy_web.py +0 -0
  220. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_qmt_update.py +0 -0
  221. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_quote_latency.py +0 -0
  222. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_quote_latency_encoding.py +0 -0
  223. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_remote_management.py +0 -0
  224. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_remote_management_browser.py +0 -0
  225. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_remote_version_check.py +0 -0
  226. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_setup_qmt_prompt.py +0 -0
  227. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_shared_quotes.py +0 -0
  228. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_shared_quotes_generated.py +0 -0
  229. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_smart_orders.py +0 -0
  230. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_smart_orders_api.py +0 -0
  231. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_smart_orders_docs.py +0 -0
  232. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_startup_recovery.py +0 -0
  233. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_stock_connect.py +0 -0
  234. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_stock_connect_browser.py +0 -0
  235. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_strategy_recovery_api.py +0 -0
  236. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_subscription_congestion.py +0 -0
  237. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_subscription_persistence.py +0 -0
  238. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_table_conversion.py +0 -0
  239. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_trade_strategy_callback.py +0 -0
  240. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_trader_subscription_recovery.py +0 -0
  241. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_trading_dates.py +0 -0
  242. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_trading_monitor.py +0 -0
  243. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_tutorial_reader.py +0 -0
  244. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_tx_auto_import.py +0 -0
  245. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tests/test_tx_shutdown.py +0 -0
  246. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/trading_calendar.py +0 -0
  247. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/trading_monitor.py +0 -0
  248. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/tx.py +0 -0
  249. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/xtconstant.py +0 -0
  250. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/xtdata.py +0 -0
  251. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/xttrader.py +0 -0
  252. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant/xttype.py +0 -0
  253. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant.egg-info/dependency_links.txt +0 -0
  254. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant.egg-info/entry_points.txt +0 -0
  255. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant.egg-info/top_level.txt +0 -0
  256. {cfquant-0.2.66 → cfquant-0.2.67}/cfquant_pipe_hub.py +0 -0
  257. {cfquant-0.2.66 → cfquant-0.2.67}/qmt_scripts/CFQUANT.py +0 -0
  258. {cfquant-0.2.66 → cfquant-0.2.67}/qmt_scripts/CFQUANT_CTYPE_ALL_LOWLAT.py +0 -0
  259. {cfquant-0.2.66 → cfquant-0.2.67}/qmt_scripts/CFQUANT_TRADE_LOWLAT.py +0 -0
  260. {cfquant-0.2.66 → cfquant-0.2.67}/qmt_scripts/__init__.py +0 -0
  261. {cfquant-0.2.66 → cfquant-0.2.67}/qmt_scripts//345/220/214/350/264/246/345/217/267/347/213/254/347/253/213/345/270/202/345/234/272/CFQUANT_CTYPE_ALL_LOWLAT_SH.py" +0 -0
  262. {cfquant-0.2.66 → cfquant-0.2.67}/qmt_scripts//345/220/214/350/264/246/345/217/267/347/213/254/347/253/213/345/270/202/345/234/272/CFQUANT_CTYPE_ALL_LOWLAT_SZ.py" +0 -0
  263. {cfquant-0.2.66 → cfquant-0.2.67}/qmt_scripts//345/220/214/350/264/246/345/217/267/347/213/254/347/253/213/345/270/202/345/234/272/CFQUANT_TRADE_LOWLAT_SH.py" +0 -0
  264. {cfquant-0.2.66 → cfquant-0.2.67}/qmt_scripts//345/220/214/350/264/246/345/217/267/347/213/254/347/253/213/345/270/202/345/234/272/CFQUANT_TRADE_LOWLAT_SZ.py" +0 -0
  265. {cfquant-0.2.66 → cfquant-0.2.67}/qmt_scripts//345/220/214/350/264/246/345/217/267/347/213/254/347/253/213/345/270/202/345/234/272/readme.md" +0 -0
  266. {cfquant-0.2.66 → cfquant-0.2.67}/setup.cfg +0 -0
  267. {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/__init__.py +0 -0
  268. {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/activity-log.js +0 -0
  269. {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/api-tester.js +0 -0
  270. {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/avatars/amber-pulse.svg +0 -0
  271. {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/avatars/copper-grid.svg +0 -0
  272. {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/avatars/market-blue.svg +0 -0
  273. {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/avatars/rose-circuit.svg +0 -0
  274. {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/avatars/signal-green.svg +0 -0
  275. {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/avatars/slate-wave.svg +0 -0
  276. {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/avatars/teal-orbit.svg +0 -0
  277. {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/avatars/violet-node.svg +0 -0
  278. {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/cfquant-architecture.html +0 -0
  279. {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/cftrader-api-data.js +0 -0
  280. {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/docs.js +0 -0
  281. {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/favicon.svg +0 -0
  282. {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/image/qmt_deploy/image-10.png +0 -0
  283. {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/image/qmt_deploy/image-11.png +0 -0
  284. {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/image/qmt_deploy/image-12.png +0 -0
  285. {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/image/qmt_deploy/image-13.png +0 -0
  286. {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/image/qmt_deploy/image-14.png +0 -0
  287. {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/image/qmt_deploy/image-15.png +0 -0
  288. {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/image/qmt_deploy/image-16.png +0 -0
  289. {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/image/qmt_deploy/image-17.png +0 -0
  290. {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/image/qmt_deploy/image-18.png +0 -0
  291. {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/image/qmt_deploy/image-19.png +0 -0
  292. {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/image/qmt_deploy/image-20.png +0 -0
  293. {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/image/qmt_deploy/image-21.png +0 -0
  294. {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/image/qmt_deploy/image-4.png +0 -0
  295. {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/image/qmt_deploy/image-5.png +0 -0
  296. {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/image/qmt_deploy/image-6.png +0 -0
  297. {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/image/qmt_deploy/image-7.png +0 -0
  298. {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/image/qmt_deploy/image-8.png +0 -0
  299. {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/image/qmt_deploy/image-9.png +0 -0
  300. {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/navigation.js +0 -0
  301. {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/python-api-data.js +0 -0
  302. {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/python-api.js +0 -0
  303. {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/qmt-inner-api-data.js +0 -0
  304. {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/remote-management.js +0 -0
  305. {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/smart-order-api-data.js +0 -0
  306. {cfquant-0.2.66 → cfquant-0.2.67}/web_dashboard/vendor/mermaid.min.js +0 -0
@@ -6,7 +6,12 @@ include cfquant_pipe_hub.py
6
6
  recursive-include cfquant *.py
7
7
  include cfquant/qmt_component_agent.js
8
8
  include cfquant/qmt_strategy_agent.js
9
+ include cfquant/qmt_vm_agent.js
10
+ include cfquant/qmt_mac_agent.js
11
+ include cfquant/tools/THIRD_PARTY.md
12
+ recursive-include cfquant/tools/upx *
9
13
  recursive-include cfquant/tests *.py *.md
14
+ include cfquant/tests/qmt_vm_native_fixture.js
10
15
  recursive-include LTtx *.py
11
16
  include LTtx/tx/Config.txt
12
17
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cfquant
3
- Version: 0.2.66
3
+ Version: 0.2.67
4
4
  Summary: QMT local bridge and xtquant-compatible Python client.
5
5
  License: MIT
6
6
  Requires-Python: >=3.8
@@ -17,6 +17,7 @@ Requires-Dist: pandas-market-calendars
17
17
  Requires-Dist: importlib-resources>=5
18
18
  Requires-Dist: frida==17.16.4; sys_platform == "win32"
19
19
  Requires-Dist: pywin32>=306; sys_platform == "win32"
20
+ Requires-Dist: pefile>=2023.2.7; sys_platform == "win32"
20
21
  Provides-Extra: dev
21
22
  Requires-Dist: build>=1.0; extra == "dev"
22
23
  Requires-Dist: twine>=5.0; extra == "dev"
@@ -403,6 +404,7 @@ restart_cfquant.bat 重启
403
404
  | 极致模式部署 | [极致模式部署指南](docs/极致模式部署指南.md) |
404
405
  | 高级模式部署 | [高级模式部署指南](docs/高级模式部署指南.md) |
405
406
  | 账号和 QMT 目录配置 | [Web 账号运行配置说明](docs/Web账号运行配置说明.md) |
407
+ | 虚拟机检测适配与 MAC 注入 | [QMT 启动设置](docs/qmt_launch_settings.md),按绑定配置,默认关闭 |
406
408
  | 从 miniQMT 迁移 | [miniQMT 迁移到大 QMT 指南](docs/miniQMT迁移到大QMT指南.md) |
407
409
  | 官网 nativeApi 接口功能及适配状态 | [xtquant 原版接口适配清单](docs/xtquant原版接口适配清单.md) |
408
410
  | Level2 六类行情、订阅回调与千档边界 | [Level2 行情适配说明](docs/Level2行情适配说明.md) |
@@ -378,6 +378,7 @@ restart_cfquant.bat 重启
378
378
  | 极致模式部署 | [极致模式部署指南](docs/极致模式部署指南.md) |
379
379
  | 高级模式部署 | [高级模式部署指南](docs/高级模式部署指南.md) |
380
380
  | 账号和 QMT 目录配置 | [Web 账号运行配置说明](docs/Web账号运行配置说明.md) |
381
+ | 虚拟机检测适配与 MAC 注入 | [QMT 启动设置](docs/qmt_launch_settings.md),按绑定配置,默认关闭 |
381
382
  | 从 miniQMT 迁移 | [miniQMT 迁移到大 QMT 指南](docs/miniQMT迁移到大QMT指南.md) |
382
383
  | 官网 nativeApi 接口功能及适配状态 | [xtquant 原版接口适配清单](docs/xtquant原版接口适配清单.md) |
383
384
  | Level2 六类行情、订阅回调与千档边界 | [Level2 行情适配说明](docs/Level2行情适配说明.md) |
@@ -7,7 +7,7 @@ import time
7
7
  from collections.abc import Mapping
8
8
  from numbers import Integral, Real
9
9
  from .stock_connect import normalize_connect_code, validate_connect_market
10
- from .order_identity import prepare_order_remark, order_remark_key
10
+ from .order_identity import prepare_order_remark, order_remark_key, restore_order_remark
11
11
 
12
12
 
13
13
  CFTRADER_BATCH_ORDER_ACTIONS = frozenset(("cftrader.order_stock_batch", "cftrader.order_stock_batch_async"))
@@ -203,6 +203,9 @@ def batch_cancel_result_rows(cancels, seqs=None):
203
203
 
204
204
 
205
205
  def batch_result(account, batch_id, asynchronous, results, operation="order"):
206
+ if operation == "order":
207
+ for row in results:
208
+ restore_order_remark(row)
206
209
  counts = {name: sum(row["status"] == name for row in results)
207
210
  for name in ("submitted", "failed", "unknown", "skipped")}
208
211
  counts["rejected"] = sum(row.get("dispatched") is False for row in results)
@@ -72,6 +72,14 @@ def restore_order_remark(data):
72
72
  if is_unique_order_remark(internal):
73
73
  data["cfquant_order_remark"] = internal
74
74
  data["order_remark"] = original_order_remark(internal)
75
+ submission_id = _ORDER_REMARK_SUFFIX.search(internal).group(1)
76
+ data["cfquant_submission_id"] = submission_id
77
+ # The native remark identifies one submission, which QMT may split.
78
+ # Derive the child key from the final public ID, never list position.
79
+ data.pop("cfquant_child_id", None)
80
+ order_id = str(data.get("order_id") or "").strip()
81
+ if order_id.isdigit() and int(order_id) > 0:
82
+ data["cfquant_child_id"] = submission_id + ":" + str(int(order_id))
75
83
  return data
76
84
 
77
85
 
@@ -153,7 +161,7 @@ class OrderCallbackCache(object):
153
161
  matches = self._matches(scope, refs, data)
154
162
  remark = order_remark_key(data)
155
163
  local = sorted(ref for ref in refs if ref[0] == "local")
156
- identity = ("remark", remark) if is_unique_order_remark(remark) else (local or sorted(refs))[0]
164
+ identity = (local or sorted(refs))[0]
157
165
  key = (scope, identity)
158
166
  if len(matches) == 1:
159
167
  key = matches[0]
@@ -187,4 +195,22 @@ class OrderCallbackCache(object):
187
195
  data[name] = value
188
196
  if is_unique_order_remark(remark):
189
197
  data["cfquant_order_remark"] = remark
198
+ # A broker-only DEAL can recover its child from this typed index.
199
+ # Shared submission metadata alone cannot choose that child.
200
+ order_id = str(data.get("order_id") or "").strip()
201
+ if not (order_id.isdigit() and int(order_id) > 0):
202
+ saved_id = saved.get("order_id")
203
+ if str(saved_id or "").isdigit() and int(saved_id) > 0:
204
+ data["order_id"] = int(saved_id)
190
205
  return restore_order_remark(data)
206
+
207
+ def submission_is_ambiguous(self, data, account_type):
208
+ """Missing child IDs must not fall back to a split submission's first ID."""
209
+ with self.lock:
210
+ scope, _ = self._identity(data, account_type)
211
+ remark = order_remark_key(data)
212
+ ids = {str(record["data"].get("order_id")) for key, record in self.records.items()
213
+ if key[0] == scope and order_remark_key(record["data"]) == remark
214
+ and str(record["data"].get("order_id") or "").isdigit()
215
+ and int(record["data"]["order_id"]) > 0}
216
+ return len(ids) > 1
@@ -27,6 +27,7 @@ DEFAULT_BRIDGE_ID = "default"
27
27
  DEFAULT_ACCOUNT_TYPE = "STOCK"
28
28
  PENDING_MATCH_TTL_SECONDS = 10 * 60
29
29
  CANONICAL_ORDER_ID_FIELD = "canonical_order_id"
30
+ ORDER_IDENTITY_VERSION = 2
30
31
  ORDER_REF_FIELDS = (
31
32
  "order_ref",
32
33
  "m_strOrderRef",
@@ -165,6 +166,42 @@ def reconcile_order_id(data, order_id):
165
166
  return mismatched
166
167
 
167
168
 
169
+ def native_order_id(data):
170
+ for name in ("m_nRef", "m_nOrderID", "m_strOrderRef", "m_strOrderID", "order_id"):
171
+ value = positive_order_id(data.get(name))
172
+ if value is not None:
173
+ return value
174
+ return None
175
+
176
+
177
+ def same_broker_order(record, data):
178
+ """Only child-scoped, versioned broker evidence can justify a local alias."""
179
+ if record.get("order_identity_version") != ORDER_IDENTITY_VERSION:
180
+ return False
181
+ left = normalize_order_ref(first_value(record, ("order_sysid", "m_strOrderSysID")))
182
+ right = normalize_order_ref(first_value(data, ("order_sysid", "m_strOrderSysID")))
183
+ left_stock = normalize_text(record.get("stock_code")).upper()
184
+ right_stock = normalize_text(data.get("stock_code")).upper()
185
+ return bool(left and left == right and left_stock and left_stock == right_stock)
186
+
187
+
188
+ def verified_child_alias(record, data):
189
+ return (record.get("order_identity_version") == ORDER_IDENTITY_VERSION
190
+ and native_order_id(data) in (record.get("child_local_ids") or [])) or same_broker_order(record, data)
191
+
192
+
193
+ def submission_metadata(record):
194
+ """Copy shared text/context without carrying a sibling's identity or size."""
195
+ result = dict(record)
196
+ for name in ORDER_REF_FIELDS + ("order_refs", CANONICAL_ORDER_ID_FIELD,
197
+ "callback_bound_at", "order_volume", "order_identity_version", "child_local_ids",
198
+ "query_order_id",
199
+ "cfquant_child_id", "cfquant_callback_order_id",
200
+ "cfquant_order_id_reconciled"):
201
+ result.pop(name, None)
202
+ return result
203
+
204
+
168
205
  def stock_code_base(value):
169
206
  text = normalize_text(value)
170
207
  if not text:
@@ -413,6 +450,13 @@ def apply_record_to_callback(data, record, match_info=None):
413
450
  data["client_order_id"] = normalize_text(record.get("client_order_id"))
414
451
 
415
452
  order_id = canonical_order_id_from_record(record)
453
+ native_id = native_order_id(data)
454
+ if native_id is not None and (
455
+ (match_info or {}).get("preserve_order_id")
456
+ or (is_unique_order_remark(user_order_id) and native_id != order_id
457
+ and not verified_child_alias(record, data))
458
+ ):
459
+ order_id = native_id
416
460
  if order_id is not None:
417
461
  reconcile_order_id(data, order_id)
418
462
  order_id_ref = str(order_id)
@@ -511,6 +555,19 @@ class OrderMetaCache(object):
511
555
  return self._upsert_locked(record)
512
556
 
513
557
  def _upsert_locked(self, record):
558
+ if (is_unique_order_remark(user_order_id_from_data(record))
559
+ and record.get("order_identity_version") != ORDER_IDENTITY_VERSION):
560
+ # Old stores may have merged sibling IDs/sysids into one record.
561
+ # Retain submission metadata and its canonical ID, not unproven aliases.
562
+ canonical = canonical_order_id_from_record(record)
563
+ volume = record.get("order_volume")
564
+ record = submission_metadata(record)
565
+ if volume is not None:
566
+ record["order_volume"] = volume
567
+ if canonical is not None:
568
+ record.update(order_id=canonical, canonical_order_id=canonical,
569
+ order_ref=str(canonical), order_refs=[str(canonical)])
570
+ record["order_identity_version"] = ORDER_IDENTITY_VERSION
514
571
  trade_day = current_trade_day()
515
572
  self._prune_locked(trade_day, time.time())
516
573
  status = normalize_text(record.get("status")).lower()
@@ -594,13 +651,18 @@ class OrderMetaCache(object):
594
651
  loaded += 1
595
652
  return {"loaded": loaded, "stale": stale}
596
653
 
597
- def resolve_callback(self, data, bridge_id=None, account_type=None, account_id=None, allow_pending=True):
654
+ def resolve_callback(self, data, bridge_id=None, account_type=None, account_id=None, allow_pending=True,
655
+ query=False):
598
656
  callback_record = normalize_record(
599
657
  data,
600
658
  bridge_id=bridge_id or self.bridge_id,
601
659
  account_type=account_type or data.get("account_type"),
602
660
  account_id=account_id or data.get("account_id"),
603
661
  )
662
+ # normalize_record also accepts stores and synthesizes m_strOrderRef
663
+ # from any reference, including a broker sysid. It is not native evidence.
664
+ if "m_strOrderRef" not in data:
665
+ callback_record.pop("m_strOrderRef", None)
604
666
  with self.lock:
605
667
  self._prune_locked(current_trade_day(), time.time())
606
668
  record = None
@@ -612,6 +674,13 @@ class OrderMetaCache(object):
612
674
  if record and user_order_id and is_unique_order_remark(user_order_id_from_data(record)):
613
675
  if not order_remark_matches_reference(user_order_id_from_data(record), user_order_id):
614
676
  record = None
677
+ if record and is_unique_order_remark(user_order_id_from_data(record)):
678
+ native_id = native_order_id(callback_record)
679
+ if not ((native_id is not None and native_id == canonical_order_id_from_record(record))
680
+ or verified_child_alias(record, callback_record)):
681
+ # Numeric local IDs and broker IDs share the legacy
682
+ # index. An untyped string hit is not child evidence.
683
+ record = None
615
684
  if record:
616
685
  confidence = "order_ref"
617
686
  break
@@ -624,11 +693,14 @@ class OrderMetaCache(object):
624
693
  record = None
625
694
  if record:
626
695
  confidence = "user_order_id"
627
- if record is None and allow_pending:
696
+ if record is None and allow_pending and not query:
628
697
  record, confidence = self._match_pending_locked(callback_record)
629
- if record is None:
698
+ if record is None and not query:
630
699
  record, confidence = self._match_known_locked(callback_record, order_refs)
631
700
 
701
+ if record and is_unique_order_remark(user_order_id_from_data(record)):
702
+ record = self._resolve_child_locked(record, callback_record, confidence, query)
703
+
632
704
  bound_order_ref = ""
633
705
  bound_order_refs = []
634
706
  if record and order_refs:
@@ -652,8 +724,56 @@ class OrderMetaCache(object):
652
724
  "bound_order_ref": bound_order_ref,
653
725
  "bound_order_refs": bound_order_refs,
654
726
  "match_confidence": confidence,
727
+ "preserve_order_id": query,
655
728
  }
656
729
 
730
+ def _resolve_child_locked(self, record, data, confidence, query):
731
+ """A UUID groups submissions; native IDs partition the real children."""
732
+ incoming_id = native_order_id(data)
733
+ canonical = canonical_order_id_from_record(record)
734
+ broker_matches = []
735
+ if same_broker_order(record, data):
736
+ broker_matches = [item for item in self._unique_records_locked()
737
+ if self._ctx(item) == self._ctx(record) and same_broker_order(item, data)]
738
+ broker_ambiguous = len({canonical_order_id_from_record(item) for item in broker_matches}) > 1
739
+ if incoming_id is None:
740
+ broker_id = normalize_order_ref(first_value(data, ("order_sysid", "m_strOrderSysID")))
741
+ if broker_id and (broker_ambiguous or not same_broker_order(record, data)):
742
+ return submission_metadata(record)
743
+ if confidence == "user_order_id":
744
+ key = self._user_key(record)
745
+ children = {canonical_order_id_from_record(item) for item in self._unique_records_locked()
746
+ if self._user_key(item) == key}
747
+ children.discard(None)
748
+ if len(children) > 1:
749
+ return submission_metadata(record)
750
+ return record
751
+ same_child = incoming_id == canonical or verified_child_alias(record, data)
752
+ if broker_ambiguous and incoming_id != canonical and incoming_id not in (record.get("child_local_ids") or []):
753
+ same_child = False
754
+ if query and record.get("query_order_id") not in (None, incoming_id):
755
+ # Two ORDER query rows are separate children even if the broker
756
+ # uses the same reference. Only callback references can be aliases.
757
+ same_child = False
758
+ if not same_child:
759
+ record = submission_metadata(record)
760
+ else:
761
+ record = dict(record)
762
+ # Queries provide the authoritative local order ID. A callback alias
763
+ # may reuse it only after matching a broker ID recorded for this child.
764
+ child_id = canonical if same_child and canonical is not None and not query else incoming_id
765
+ record.update(order_id=child_id, canonical_order_id=child_id,
766
+ order_identity_version=ORDER_IDENTITY_VERSION)
767
+ record["child_local_ids"] = sorted(set((record.get("child_local_ids") or []) + [child_id, incoming_id]))
768
+ if query:
769
+ record["query_order_id"] = incoming_id
770
+ if not same_child:
771
+ record.update(order_ref=str(child_id), order_refs=[str(child_id)])
772
+ for name in ("order_sysid", "m_strOrderSysID", "order_volume"):
773
+ if not is_empty(data.get(name)):
774
+ record[name] = data[name]
775
+ return record
776
+
657
777
  def _unique_records_locked(self):
658
778
  records = []
659
779
  seen = set()
@@ -683,6 +803,11 @@ class OrderMetaCache(object):
683
803
  continue
684
804
  if not self._has_callback_metadata(record):
685
805
  continue
806
+ if is_unique_order_remark(user_order_id_from_data(record)):
807
+ native_id = native_order_id(callback_record)
808
+ if not ((native_id is not None and native_id == canonical_order_id_from_record(record))
809
+ or verified_child_alias(record, callback_record)):
810
+ continue
686
811
  evidence = self._context_match_evidence(record, callback_record)
687
812
  if evidence < 0:
688
813
  continue
@@ -0,0 +1,87 @@
1
+ 'use strict';
2
+
3
+ // Windows x64 iptypes.h layouts. Only buffers returned to this QMT's utils.dll
4
+ // are changed; the OS interfaces and all other processes retain their own data.
5
+ const macLayouts = {
6
+ GetAdaptersInfo: {bufferArg: 0, sizeArg: 1, next: 0, length: 404, address: 408, minimum: 416},
7
+ GetAdaptersAddresses: {bufferArg: 3, sizeArg: 4, next: 8, length: 88, address: 80, minimum: 92}
8
+ };
9
+ const macListeners = [];
10
+ const macCalls = {};
11
+
12
+ function rewriteMacList(buffer, size, layout, bytes) {
13
+ if (buffer.isNull() || size < layout.minimum || size > 16 * 1024 * 1024) {
14
+ throw new Error('Invalid adapter buffer bounds');
15
+ }
16
+ const end = buffer.add(size);
17
+ const seen = new Set();
18
+ const addresses = [];
19
+ let node = buffer;
20
+ while (!node.isNull()) {
21
+ if (seen.size >= 512 || seen.has(node.toString()) || node.compare(buffer) < 0 ||
22
+ node.add(layout.minimum).compare(end) > 0) {
23
+ throw new Error('Invalid adapter linked list');
24
+ }
25
+ seen.add(node.toString());
26
+ if (layout === macLayouts.GetAdaptersAddresses &&
27
+ (node.readU32() < layout.minimum || node.add(node.readU32()).compare(end) > 0)) {
28
+ throw new Error('Unexpected IP_ADAPTER_ADDRESSES length');
29
+ }
30
+ const length = node.add(layout.length).readU32();
31
+ if (length > 8) throw new Error('Invalid hardware address length');
32
+ if (length === 6) addresses.push(node.add(layout.address));
33
+ node = node.add(layout.next).readPointer();
34
+ }
35
+ // Validate the whole result before making changes.
36
+ for (const address of addresses) address.writeByteArray(bytes);
37
+ return addresses.length;
38
+ }
39
+
40
+ function disposeMac() {
41
+ for (const listener of macListeners) listener.detach();
42
+ macListeners.length = 0;
43
+ }
44
+
45
+ rpc.exports.configuremac = function (directory, mac) {
46
+ if (Process.platform !== 'windows' || Process.arch !== 'x64') throw new Error('Windows x64 required');
47
+ if (macListeners.length) throw new Error('MAC listeners already installed');
48
+ if (!/^(?:[0-9A-F]{2}-){5}[0-9A-F]{2}$/.test(mac)) throw new Error('Invalid MAC format');
49
+ const bytes = mac.split('-').map(value => parseInt(value, 16));
50
+ if ((bytes[0] & 1) || bytes.every(value => value === 0)) throw new Error('Unicast MAC required');
51
+ const normalize = value => value.replace(/\//g, '\\').replace(/\\+$/, '').toLowerCase();
52
+ const expected = normalize(directory) + '\\utils.dll';
53
+ const iphlp = Process.getModuleByName('iphlpapi.dll');
54
+ iphlp.ensureInitialized();
55
+ try {
56
+ for (const [name, layout] of Object.entries(macLayouts)) {
57
+ macCalls[name] = 0;
58
+ macListeners.push(Interceptor.attach(iphlp.getExportByName(name), {
59
+ onEnter(args) {
60
+ const caller = Process.findModuleByAddress(this.returnAddress);
61
+ this.applies = Boolean(caller && normalize(caller.path) === expected);
62
+ if (!this.applies) return;
63
+ this.buffer = args[layout.bufferArg];
64
+ this.sizePointer = args[layout.sizeArg];
65
+ this.capacity = this.sizePointer.isNull() ? 0 : this.sizePointer.readU32();
66
+ },
67
+ onLeave(retval) {
68
+ if (!this.applies || retval.toUInt32() !== 0 || this.buffer.isNull() || this.sizePointer.isNull()) return;
69
+ try {
70
+ const size = Math.min(this.capacity, this.sizePointer.readU32());
71
+ const count = rewriteMacList(this.buffer, size, layout, bytes);
72
+ macCalls[name]++;
73
+ if (macCalls[name] === 1) send({event: 'mac_override', api: name, adapters: count});
74
+ } catch (error) {
75
+ send({event: 'mac_error', api: name, detail: String(error)});
76
+ }
77
+ }
78
+ }));
79
+ }
80
+ return {ready: true, apis: Object.keys(macLayouts)};
81
+ } catch (error) {
82
+ disposeMac();
83
+ throw error;
84
+ }
85
+ };
86
+ rpc.exports.macstatus = function () { return {active: macListeners.length === 2, calls: macCalls}; };
87
+ rpc.exports.disposemac = disposeMac;
@@ -89,14 +89,10 @@ def _version_tuple(value):
89
89
  different files. Unknown values deliberately return ``None`` so callers
90
90
  can choose the conservative py36 runtime.
91
91
  """
92
- parts = re.findall(r"\d+", str(value or ""))
93
- if not parts:
92
+ match = re.fullmatch(r"\s*(\d+)\.(\d+)\.(\d+)(?:\.(\d+))?\s*", str(value or ""))
93
+ if not match:
94
94
  return None
95
- try:
96
- numbers = [int(part) for part in parts[:4]]
97
- except ValueError:
98
- return None
99
- return tuple((numbers + [0, 0, 0, 0])[:4])
95
+ return tuple(int(part or 0) for part in match.groups())
100
96
 
101
97
 
102
98
  class QmtPythonRuntime:
@@ -288,6 +284,8 @@ class QmtPythonRuntime:
288
284
  # binding on the native/default py36 runtime until it is verified.
289
285
  if configured_runtime:
290
286
  selected = configured_runtime if py312_supported or configured_runtime == "py36" else "py36"
287
+ elif not py312_supported:
288
+ selected = "py36"
291
289
  inspected = {
292
290
  runtime: self.inspect(runtime, refresh_remote=False)
293
291
  for runtime in ("py36", "py312")
@@ -526,6 +524,9 @@ class QmtPythonRuntime:
526
524
  account = str(account_id if account_id is not None else self.account_id) if (account_id is not None or self.account_id is not None) else None
527
525
  if not account:
528
526
  raise QmtPythonRuntimeError("account_id is required to switch QMT Python runtime")
527
+ self.validate_selection(runtime, account_id=account)
528
+ if not self.inspect(runtime, refresh_remote=False)["installed"]:
529
+ raise QmtPythonRuntimeError("QMT Python library is not installed: %s" % runtime)
529
530
  config = self.qmt_dir / "userdata" / "users" / account / "Config.xml"
530
531
  if not config.is_file():
531
532
  raise QmtPythonRuntimeError("QMT account Config.xml not found: %s" % account)
@@ -533,13 +534,16 @@ class QmtPythonRuntime:
533
534
  path_value = (self.bin_dir / "python312" if runtime == "py312" else self.bin_dir).as_posix()
534
535
  if not re.search(r"<python\b[^>]*>", text):
535
536
  raise QmtPythonRuntimeError("QMT account Config.xml has no python settings")
536
- updated = re.sub(r'(<python\b[^>]*\blibs_path=")[^"]*(")', r'\g<1>' + path_value + r'\2', text, count=1)
537
- if updated == text:
537
+ if not re.search(r'<python\b[^>]*\blibs_path="[^"]*"', text):
538
538
  raise QmtPythonRuntimeError("cannot update QMT python libs_path")
539
+ updated = re.sub(r'(<python\b[^>]*\blibs_path=")[^"]*(")', r'\g<1>' + path_value + r'\2', text, count=1)
539
540
  if re.search(r'(<python\b[^>]*\bm_nPythonVersion=")[^"]*(")', updated):
540
541
  updated = re.sub(r'(<python\b[^>]*\bm_nPythonVersion=")[^"]*(")', r'\g<1>' + ("1" if runtime == "py312" else "0") + r'\2', updated, count=1)
541
542
  else:
542
543
  updated = re.sub(r'(<python\b)', r'\1 m_nPythonVersion="%s"' % ("1" if runtime == "py312" else "0"), updated, count=1)
544
+ if updated == text:
545
+ return {"account_id": account, "runtime": runtime, "config": str(config),
546
+ "backup": None, "libs_path": path_value, "unchanged": True}
543
547
  self.backup_dir.mkdir(parents=True, exist_ok=True)
544
548
  backup = self.backup_dir / ("Config-%s-%s.xml" % (account, time.strftime("%Y%m%d-%H%M%S")))
545
549
  shutil.copy2(config, backup)
@@ -589,31 +593,49 @@ class QmtPythonRuntime:
589
593
  time.sleep(0.25)
590
594
  raise QmtPythonRuntimeError("QMT did not start after runtime switch")
591
595
 
596
+ def validate_selection(self, runtime, *, account_id=None):
597
+ runtime = _canonical_runtime(runtime)
598
+ effective_account = account_id if account_id is not None else self.account_id
599
+ binding = self.binding_status(account_id=effective_account)
600
+ if runtime == "py312" and not binding["py312_supported"]:
601
+ raise QmtPythonRuntimeError(
602
+ "QMT %s only supports the default py36 runtime; py312 is unavailable"
603
+ % (binding.get("qmt_version") or "unknown"))
604
+ if effective_account:
605
+ if not binding["config_exists"]:
606
+ raise QmtPythonRuntimeError("QMT account Config.xml not found: %s; log in to QMT first" % effective_account)
607
+ text = Path(binding["config"]).read_text(encoding="utf-8")
608
+ if not re.search(r'<python\b[^>]*\blibs_path="[^"]*"', text):
609
+ raise QmtPythonRuntimeError("QMT account Config.xml has no Python library settings")
610
+ return binding
611
+
592
612
  def download_and_switch(self, runtime="py312", *, restart=True, account_id=None,
593
613
  progress_callback=None, cancel_event=None,
594
- restart_callback=None):
614
+ restart_callback=None, reuse_installed=False):
595
615
  runtime = _canonical_runtime(runtime)
596
- effective_account = account_id if account_id is not None else self.account_id
597
- if runtime == "py312" and effective_account:
598
- binding = self.binding_status(account_id=effective_account)
599
- if not binding["py312_supported"]:
600
- version = binding.get("qmt_version") or "unknown"
601
- raise QmtPythonRuntimeError(
602
- "QMT %s only supports the default py36 runtime; py312 is unavailable" % version
603
- )
604
- package = self.fetch_package(runtime)
605
- archive = self.download(runtime, package=package, progress_callback=progress_callback,
606
- cancel_event=cancel_event)
616
+ binding = self.validate_selection(runtime, account_id=account_id)
617
+ reuse = bool(reuse_installed and binding["runtimes"][runtime]["installed"])
618
+ package, archive = None, None
619
+ if reuse:
620
+ _callback(progress_callback, QmtPythonProgress(runtime, "reusing", message="Python 库已安装,准备切换执行器"))
621
+ else:
622
+ package = self.fetch_package(runtime)
623
+ archive = self.download(runtime, package=package, progress_callback=progress_callback,
624
+ cancel_event=cancel_event)
625
+ if cancel_event and cancel_event.is_set():
626
+ raise QmtPythonRuntimeError("QMT Python runtime operation cancelled")
627
+ _callback(progress_callback, QmtPythonProgress(runtime, "activating", message="正在停止 QMT 并配置执行器"))
607
628
  if restart and self._qmt_processes():
608
629
  self._stop_for_switch()
609
630
  elif not restart and self._qmt_processes():
610
631
  raise QmtPythonRuntimeError(
611
632
  "QMT is running; set restart=True before activating a Python runtime")
612
633
  try:
613
- installed = self.install(runtime, package=package, progress_callback=progress_callback)
634
+ installed = self.inspect(runtime, refresh_remote=False) if reuse else self.install(runtime, package=package, progress_callback=progress_callback)
614
635
  switched = self.switch_account(runtime, account_id=account_id) if (account_id or self.account_id) else None
615
636
  finally:
616
637
  if restart:
638
+ _callback(progress_callback, QmtPythonProgress(runtime, "restarting", message="正在重新启动 QMT"))
617
639
  if restart_callback:
618
640
  restarted = restart_callback()
619
641
  elif self._qmt_processes():
@@ -622,7 +644,8 @@ class QmtPythonRuntime:
622
644
  restarted = self.restart_qmt()
623
645
  else:
624
646
  restarted = None
625
- return {"runtime": runtime, "package": package.as_dict(), "archive": str(archive),
647
+ return {"runtime": runtime, "package": package.as_dict() if package else None,
648
+ "archive": str(archive) if archive else None, "reused_installed": reuse,
626
649
  "installed": installed, "switched": switched, "restarted": restarted}
627
650
 
628
651
  def _stop_for_switch(self):
@@ -135,7 +135,7 @@ def same_path(left, right):
135
135
 
136
136
 
137
137
  class UpdateProcesses:
138
- def __init__(self, directory, pid):
138
+ def __init__(self, directory, pid, launch_client=None):
139
139
  import psutil
140
140
  self.psutil = psutil
141
141
  self.directory = Path(directory).resolve()
@@ -148,6 +148,7 @@ class UpdateProcesses:
148
148
  self.owners = {} # PID -> (creation time, installer flag)
149
149
  self.native = NativeDialogs()
150
150
  self.launch = None
151
+ self.launch_client = launch_client
151
152
 
152
153
  @property
153
154
  def pid(self):
@@ -218,7 +219,18 @@ class UpdateProcesses:
218
219
  new = matches[0]
219
220
  if new.create_time() <= self.started:
220
221
  raise QmtUpdateError("update_process_changed")
221
- else:
222
+ if self.launch_client is not None:
223
+ # Installer-started terminals have not received launch hooks.
224
+ new.terminate()
225
+ try:
226
+ new.wait(timeout=10)
227
+ except self.psutil.TimeoutExpired:
228
+ raise QmtUpdateError("update_restart_failed") from None
229
+ matches = []
230
+ if not matches and self.launch_client is not None:
231
+ self.launch = self.launch_client()
232
+ new = self.psutil.Process(int(self.launch['pid']))
233
+ elif not matches:
222
234
  self.launch = subprocess.Popen([str(self.exe)], cwd=str(self.directory))
223
235
  new = self.psutil.Process(self.launch.pid)
224
236
  self.qmt = new
@@ -325,14 +337,15 @@ class UpdateMonitor:
325
337
 
326
338
  def manage_qmt_startup(directory, pid, login, *, auto_update=False,
327
339
  should_cancel=lambda: False, on_wait=lambda phase: None,
328
- on_pid=lambda pid: None):
340
+ on_pid=lambda pid: None, launch_client=None):
329
341
  """Supervise update/restart and rerun login only for the resulting new PID.
330
342
 
331
343
  login is None for a client with its own automatic login; no Qt injection or
332
344
  credential access happens in that mode. The worker continues observing the
333
345
  updater after login so its late dialogs cannot be left blocking the client.
334
346
  """
335
- processes = UpdateProcesses(directory, pid)
347
+ processes = (UpdateProcesses(directory, pid, launch_client=launch_client) if launch_client is not None
348
+ else UpdateProcesses(directory, pid))
336
349
  monitor = UpdateMonitor(processes, auto_update, should_cancel, on_wait, on_pid)
337
350
  while not should_cancel():
338
351
  try: