cfquant 0.2.66__tar.gz → 0.2.68__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 (312) hide show
  1. {cfquant-0.2.66 → cfquant-0.2.68}/MANIFEST.in +5 -0
  2. {cfquant-0.2.66 → cfquant-0.2.68}/PKG-INFO +3 -1
  3. {cfquant-0.2.66 → cfquant-0.2.68}/README.md +1 -0
  4. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/__init__.py +2 -1
  5. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/batch_orders.py +4 -1
  6. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/client.py +2 -0
  7. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/monitor_notifications.py +3 -30
  8. cfquant-0.2.68/cfquant/notify.py +52 -0
  9. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/order_identity.py +27 -1
  10. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/order_meta.py +128 -3
  11. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/qmt_bridge.py +105 -30
  12. cfquant-0.2.68/cfquant/qmt_mac_agent.js +87 -0
  13. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/qmt_python_runtime.py +76 -29
  14. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/qmt_update.py +17 -4
  15. cfquant-0.2.68/cfquant/qmt_vm_agent.js +109 -0
  16. cfquant-0.2.68/cfquant/qmt_vm_discovery.py +178 -0
  17. cfquant-0.2.68/cfquant/qmt_vm_runtime.py +105 -0
  18. cfquant-0.2.68/cfquant/qmt_vm_settings.py +69 -0
  19. cfquant-0.2.68/cfquant/qmt_vm_worker.py +164 -0
  20. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/quote_service.py +11 -5
  21. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/12_/350/256/242/345/215/225/345/244/207/346/263/250/346/265/213/350/257/225.py +6 -0
  22. {cfquant-0.2.66 → cfquant-0.2.68}/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
  23. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/26_/345/205/250/346/216/250/350/256/242/351/230/205/346/265/213/350/257/225.py +8 -4
  24. {cfquant-0.2.66 → cfquant-0.2.68}/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
  25. cfquant-0.2.68/cfquant/tests/qmt_vm_native_fixture.js +118 -0
  26. cfquant-0.2.68/cfquant/tests/test_auto_login_app.py +363 -0
  27. cfquant-0.2.68/cfquant/tests/test_binding_python_runtime_browser.py +131 -0
  28. cfquant-0.2.68/cfquant/tests/test_binding_vm_browser.py +90 -0
  29. cfquant-0.2.68/cfquant/tests/test_binding_webhook_browser.py +106 -0
  30. cfquant-0.2.68/cfquant/tests/test_history_download_memory.py +68 -0
  31. cfquant-0.2.68/cfquant/tests/test_notify.py +214 -0
  32. cfquant-0.2.68/cfquant/tests/test_qmt_python_runtime.py +309 -0
  33. cfquant-0.2.68/cfquant/tests/test_qmt_vm_discovery.py +161 -0
  34. cfquant-0.2.68/cfquant/tests/test_qmt_vm_native.py +41 -0
  35. cfquant-0.2.68/cfquant/tests/test_qmt_vm_runtime.py +167 -0
  36. cfquant-0.2.68/cfquant/tests/test_qmt_vm_settings.py +86 -0
  37. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_quote_service.py +153 -0
  38. cfquant-0.2.68/cfquant/tests/test_split_order_identity.py +263 -0
  39. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_trading_monitor.py +57 -0
  40. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_trading_monitor_browser.py +204 -0
  41. cfquant-0.2.68/cfquant/tests/test_version_check.py +75 -0
  42. cfquant-0.2.68/cfquant/tests/test_version_check_browser.py +128 -0
  43. cfquant-0.2.68/cfquant/tools/THIRD_PARTY.md +16 -0
  44. cfquant-0.2.68/cfquant/tools/upx/COPYING +339 -0
  45. cfquant-0.2.68/cfquant/tools/upx/LICENSE +138 -0
  46. cfquant-0.2.68/cfquant/tools/upx/NEWS +603 -0
  47. cfquant-0.2.68/cfquant/tools/upx/README +117 -0
  48. cfquant-0.2.68/cfquant/tools/upx/THANKS.txt +74 -0
  49. cfquant-0.2.68/cfquant/tools/upx/upx-doc.html +845 -0
  50. cfquant-0.2.68/cfquant/tools/upx/upx-doc.txt +893 -0
  51. cfquant-0.2.68/cfquant/tools/upx/upx.1 +1015 -0
  52. cfquant-0.2.68/cfquant/tools/upx/upx.exe +0 -0
  53. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/trading_monitor.py +16 -4
  54. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tx_trade_bridge.py +122 -76
  55. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/version.py +2 -2
  56. cfquant-0.2.68/cfquant/webhook.py +151 -0
  57. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/xtdata.py +2 -0
  58. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant.egg-info/PKG-INFO +3 -1
  59. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant.egg-info/SOURCES.txt +32 -0
  60. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant.egg-info/requires.txt +1 -0
  61. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant_web_server.py +288 -24
  62. {cfquant-0.2.66 → cfquant-0.2.68}/pyproject.toml +4 -3
  63. {cfquant-0.2.66 → cfquant-0.2.68}/qmt_scripts/CFQUANT_LITE.py +148 -36
  64. {cfquant-0.2.66 → cfquant-0.2.68}/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 +148 -36
  65. {cfquant-0.2.66 → cfquant-0.2.68}/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 +148 -36
  66. {cfquant-0.2.66 → cfquant-0.2.68}/requirements.txt +1 -0
  67. {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/api-tester.js +1 -0
  68. {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/app.js +557 -83
  69. {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/index.html +76 -5
  70. cfquant-0.2.68/web_dashboard/notify-api-data.js +38 -0
  71. {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/python-api.js +4 -4
  72. {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/styles.css +26 -1
  73. {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/trading-monitor.js +191 -31
  74. cfquant-0.2.66/cfquant/tests/test_auto_login_app.py +0 -166
  75. cfquant-0.2.66/cfquant/tests/test_qmt_python_runtime.py +0 -133
  76. {cfquant-0.2.66 → cfquant-0.2.68}/LICENSE +0 -0
  77. {cfquant-0.2.66 → cfquant-0.2.68}/LTtx/__init__.py +0 -0
  78. {cfquant-0.2.66 → cfquant-0.2.68}/LTtx/tx/Config.txt +0 -0
  79. {cfquant-0.2.66 → cfquant-0.2.68}/LTtx/tx/LTtx_server.py +0 -0
  80. {cfquant-0.2.66 → cfquant-0.2.68}/LTtx/tx/__init__.py +0 -0
  81. {cfquant-0.2.66 → cfquant-0.2.68}/LTtx/tx/tx.py +0 -0
  82. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/_editable_install.py +0 -0
  83. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/account_routing.py +0 -0
  84. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/algo.py +0 -0
  85. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/algo_broker.py +0 -0
  86. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/build_info.py +0 -0
  87. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/callback_dispatcher.py +0 -0
  88. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/cftrader.py +0 -0
  89. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/channels.py +0 -0
  90. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/cli.py +0 -0
  91. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/conditional_orders.py +0 -0
  92. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/config.py +0 -0
  93. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/diagnostic_context.py +0 -0
  94. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/diagnostics.py +0 -0
  95. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/flow_control.py +0 -0
  96. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/hub.py +0 -0
  97. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/level2.py +0 -0
  98. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/log_management.py +0 -0
  99. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/logging_i18n.py +0 -0
  100. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/management.py +0 -0
  101. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/monitor_performance.py +0 -0
  102. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/normal_bridge.py +0 -0
  103. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/pipe_bridge.py +0 -0
  104. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/pipe_client.py +0 -0
  105. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/pipe_hub.py +0 -0
  106. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/pipe_transport.py +0 -0
  107. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/planning_broker.py +0 -0
  108. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/position_sizing.py +0 -0
  109. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/protocol.py +0 -0
  110. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/qmt_binding_defaults.py +0 -0
  111. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/qmt_binding_settings.py +0 -0
  112. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/qmt_component_agent.js +0 -0
  113. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/qmt_component_login.py +0 -0
  114. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/qmt_credentials.py +0 -0
  115. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/qmt_dependencies.py +0 -0
  116. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/qmt_inner.py +0 -0
  117. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/qmt_inner_catalog.py +0 -0
  118. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/qmt_inner_runtime.py +0 -0
  119. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/qmt_native_files.py +0 -0
  120. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/qmt_native_worker.py +0 -0
  121. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/qmt_pipe_runtime.py +0 -0
  122. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/qmt_strategy_agent.js +0 -0
  123. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/qmt_strategy_deploy.py +0 -0
  124. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/qmt_strategy_package.py +0 -0
  125. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/qmt_strategy_recovery.py +0 -0
  126. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/qmt_strategy_runtime.py +0 -0
  127. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/quote_history.py +0 -0
  128. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/quote_leases.py +0 -0
  129. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/remote_adapter.py +0 -0
  130. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/remote_management.py +0 -0
  131. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/remote_supervisor.py +0 -0
  132. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/rpc_timing.py +0 -0
  133. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/runtime_report.py +0 -0
  134. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/shared_quotes.py +0 -0
  135. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/smart_orders.py +0 -0
  136. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/stock_connect.py +0 -0
  137. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/subscription_bridge.py +0 -0
  138. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/subscription_client.py +0 -0
  139. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/subscription_manager.py +0 -0
  140. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/subscription_store.py +0 -0
  141. {cfquant-0.2.66 → cfquant-0.2.68}/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
  142. {cfquant-0.2.66 → cfquant-0.2.68}/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
  143. {cfquant-0.2.66 → cfquant-0.2.68}/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
  144. {cfquant-0.2.66 → cfquant-0.2.68}/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
  145. {cfquant-0.2.66 → cfquant-0.2.68}/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
  146. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/16_/345/220/257/345/212/250/345/256/211/350/243/205/346/265/213/350/257/225.py" +0 -0
  147. {cfquant-0.2.66 → cfquant-0.2.68}/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
  148. {cfquant-0.2.66 → cfquant-0.2.68}/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
  149. {cfquant-0.2.66 → cfquant-0.2.68}/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
  150. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/1_/350/241/214/346/203/205/346/216/245/346/224/266/346/265/213/350/257/225.py" +0 -0
  151. {cfquant-0.2.66 → cfquant-0.2.68}/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
  152. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/22_/350/277/236/346/216/245/350/257/212/346/226/255/346/265/213/350/257/225.py" +0 -0
  153. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/23_/347/244/272/344/276/213/351/205/215/347/275/256/346/265/213/350/257/225.py" +0 -0
  154. {cfquant-0.2.66 → cfquant-0.2.68}/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
  155. {cfquant-0.2.66 → cfquant-0.2.68}/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
  156. {cfquant-0.2.66 → cfquant-0.2.68}/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
  157. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/29_Level2/350/241/214/346/203/205/346/265/213/350/257/225.py" +0 -0
  158. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/2_/346/225/260/346/215/256/350/216/267/345/217/226/346/265/213/350/257/225.py" +0 -0
  159. {cfquant-0.2.66 → cfquant-0.2.68}/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
  160. {cfquant-0.2.66 → cfquant-0.2.68}/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
  161. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/32_/345/274/202/346/255/245/344/270/213/345/215/225/346/265/213/350/257/225.py" +0 -0
  162. {cfquant-0.2.66 → cfquant-0.2.68}/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
  163. {cfquant-0.2.66 → cfquant-0.2.68}/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
  164. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/3_/346/225/260/346/215/256/344/270/213/350/275/275/346/265/213/350/257/225.py" +0 -0
  165. {cfquant-0.2.66 → cfquant-0.2.68}/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
  166. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/5_/347/234/237/345/256/236/344/270/213/345/215/225/346/265/213/350/257/225.py" +0 -0
  167. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/6_/345/233/236/350/260/203/346/265/213/350/257/225.py" +0 -0
  168. {cfquant-0.2.66 → cfquant-0.2.68}/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
  169. {cfquant-0.2.66 → cfquant-0.2.68}/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
  170. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/9_/345/217/257/346/222/244/350/256/242/345/215/225/346/265/213/350/257/225.py" +0 -0
  171. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/_helpers.py +0 -0
  172. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/readme.md +0 -0
  173. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_account_query_timing.py +0 -0
  174. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_account_subscription_probe.py +0 -0
  175. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_activity_log_browser.py +0 -0
  176. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_advanced_parity.py +0 -0
  177. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_algo.py +0 -0
  178. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_algo_broker.py +0 -0
  179. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_api_tester_browser.py +0 -0
  180. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_api_tester_web.py +0 -0
  181. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_binding_delete_confirmation.py +0 -0
  182. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_binding_live_refresh_browser.py +0 -0
  183. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_binding_runtime_browser.py +0 -0
  184. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_callback_recovery.py +0 -0
  185. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_callback_requests.py +0 -0
  186. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_cftrader.py +0 -0
  187. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_cftrader_tutorial.py +0 -0
  188. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_cftrader_web.py +0 -0
  189. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_credit_compat.py +0 -0
  190. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_dataframe_encoding.py +0 -0
  191. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_diagnostic_context.py +0 -0
  192. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_json_serialization.py +0 -0
  193. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_latency_flow_control.py +0 -0
  194. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_lite_account_routing.py +0 -0
  195. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_lite_deployment_paths.py +0 -0
  196. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_lite_parity.py +0 -0
  197. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_lite_qmt_dispatch.py +0 -0
  198. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_local_trading_calendar.py +0 -0
  199. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_log_management.py +0 -0
  200. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_management.py +0 -0
  201. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_management_batch.py +0 -0
  202. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_market_data_fallback.py +0 -0
  203. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_market_timing.py +0 -0
  204. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_monitor_notifications.py +0 -0
  205. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_monitor_performance.py +0 -0
  206. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_navigation_browser.py +0 -0
  207. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_order_identity.py +0 -0
  208. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_order_time_callback.py +0 -0
  209. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_pipe_shutdown.py +0 -0
  210. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_project_updater.py +0 -0
  211. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_python_api_reference.py +0 -0
  212. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_qmt_auto_login_browser.py +0 -0
  213. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_qmt_auto_login_integration.py +0 -0
  214. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_qmt_binding_defaults.py +0 -0
  215. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_qmt_binding_settings.py +0 -0
  216. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_qmt_component_login.py +0 -0
  217. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_qmt_dependencies.py +0 -0
  218. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_qmt_fileless.py +0 -0
  219. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_qmt_inner.py +0 -0
  220. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_qmt_inner_reference.py +0 -0
  221. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_qmt_inner_web.py +0 -0
  222. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_qmt_native_files.py +0 -0
  223. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_qmt_native_reuse.py +0 -0
  224. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_qmt_native_worker.py +0 -0
  225. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_qmt_package_import.py +0 -0
  226. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_qmt_strategy_browser.py +0 -0
  227. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_qmt_strategy_capabilities.py +0 -0
  228. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_qmt_strategy_deploy.py +0 -0
  229. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_qmt_strategy_recovery.py +0 -0
  230. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_qmt_strategy_web.py +0 -0
  231. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_qmt_update.py +0 -0
  232. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_quote_latency.py +0 -0
  233. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_quote_latency_encoding.py +0 -0
  234. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_remote_management.py +0 -0
  235. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_remote_management_browser.py +0 -0
  236. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_remote_version_check.py +0 -0
  237. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_setup_qmt_prompt.py +0 -0
  238. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_shared_quotes.py +0 -0
  239. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_shared_quotes_generated.py +0 -0
  240. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_smart_orders.py +0 -0
  241. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_smart_orders_api.py +0 -0
  242. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_smart_orders_docs.py +0 -0
  243. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_startup_recovery.py +0 -0
  244. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_stock_connect.py +0 -0
  245. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_stock_connect_browser.py +0 -0
  246. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_strategy_recovery_api.py +0 -0
  247. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_subscription_congestion.py +0 -0
  248. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_subscription_persistence.py +0 -0
  249. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_table_conversion.py +0 -0
  250. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_trade_strategy_callback.py +0 -0
  251. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_trader_subscription_recovery.py +0 -0
  252. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_trading_dates.py +0 -0
  253. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_tutorial_reader.py +0 -0
  254. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_tx_auto_import.py +0 -0
  255. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tests/test_tx_shutdown.py +0 -0
  256. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/trading_calendar.py +0 -0
  257. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/tx.py +0 -0
  258. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/xtconstant.py +0 -0
  259. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/xttrader.py +0 -0
  260. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant/xttype.py +0 -0
  261. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant.egg-info/dependency_links.txt +0 -0
  262. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant.egg-info/entry_points.txt +0 -0
  263. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant.egg-info/top_level.txt +0 -0
  264. {cfquant-0.2.66 → cfquant-0.2.68}/cfquant_pipe_hub.py +0 -0
  265. {cfquant-0.2.66 → cfquant-0.2.68}/qmt_scripts/CFQUANT.py +0 -0
  266. {cfquant-0.2.66 → cfquant-0.2.68}/qmt_scripts/CFQUANT_CTYPE_ALL_LOWLAT.py +0 -0
  267. {cfquant-0.2.66 → cfquant-0.2.68}/qmt_scripts/CFQUANT_TRADE_LOWLAT.py +0 -0
  268. {cfquant-0.2.66 → cfquant-0.2.68}/qmt_scripts/__init__.py +0 -0
  269. {cfquant-0.2.66 → cfquant-0.2.68}/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
  270. {cfquant-0.2.66 → cfquant-0.2.68}/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
  271. {cfquant-0.2.66 → cfquant-0.2.68}/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
  272. {cfquant-0.2.66 → cfquant-0.2.68}/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
  273. {cfquant-0.2.66 → cfquant-0.2.68}/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
  274. {cfquant-0.2.66 → cfquant-0.2.68}/setup.cfg +0 -0
  275. {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/__init__.py +0 -0
  276. {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/activity-log.js +0 -0
  277. {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/avatars/amber-pulse.svg +0 -0
  278. {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/avatars/copper-grid.svg +0 -0
  279. {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/avatars/market-blue.svg +0 -0
  280. {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/avatars/rose-circuit.svg +0 -0
  281. {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/avatars/signal-green.svg +0 -0
  282. {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/avatars/slate-wave.svg +0 -0
  283. {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/avatars/teal-orbit.svg +0 -0
  284. {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/avatars/violet-node.svg +0 -0
  285. {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/cfquant-architecture.html +0 -0
  286. {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/cftrader-api-data.js +0 -0
  287. {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/docs.js +0 -0
  288. {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/favicon.svg +0 -0
  289. {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/image/qmt_deploy/image-10.png +0 -0
  290. {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/image/qmt_deploy/image-11.png +0 -0
  291. {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/image/qmt_deploy/image-12.png +0 -0
  292. {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/image/qmt_deploy/image-13.png +0 -0
  293. {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/image/qmt_deploy/image-14.png +0 -0
  294. {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/image/qmt_deploy/image-15.png +0 -0
  295. {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/image/qmt_deploy/image-16.png +0 -0
  296. {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/image/qmt_deploy/image-17.png +0 -0
  297. {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/image/qmt_deploy/image-18.png +0 -0
  298. {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/image/qmt_deploy/image-19.png +0 -0
  299. {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/image/qmt_deploy/image-20.png +0 -0
  300. {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/image/qmt_deploy/image-21.png +0 -0
  301. {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/image/qmt_deploy/image-4.png +0 -0
  302. {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/image/qmt_deploy/image-5.png +0 -0
  303. {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/image/qmt_deploy/image-6.png +0 -0
  304. {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/image/qmt_deploy/image-7.png +0 -0
  305. {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/image/qmt_deploy/image-8.png +0 -0
  306. {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/image/qmt_deploy/image-9.png +0 -0
  307. {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/navigation.js +0 -0
  308. {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/python-api-data.js +0 -0
  309. {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/qmt-inner-api-data.js +0 -0
  310. {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/remote-management.js +0 -0
  311. {cfquant-0.2.66 → cfquant-0.2.68}/web_dashboard/smart-order-api-data.js +0 -0
  312. {cfquant-0.2.66 → cfquant-0.2.68}/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.68
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) |
@@ -12,7 +12,7 @@ cfquant: 面向 QMT 的 xtquant 兼容层。
12
12
  import sys as _sys
13
13
 
14
14
  from . import xtconstant, xtdata, xttrader, xttype
15
- from . import cftrader
15
+ from . import cftrader, notify
16
16
  from .qmt_inner import QmtInnerApi
17
17
  from .client import CfquantError, CfquantTimeout, configure, get_client
18
18
  from .version import __version__
@@ -28,6 +28,7 @@ __all__ = [
28
28
  "xtdata",
29
29
  "xttrader",
30
30
  "cftrader",
31
+ "notify",
31
32
  "QmtInnerApi",
32
33
  "xttype",
33
34
  "configure",
@@ -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)
@@ -472,6 +472,8 @@ def configure(**kwargs):
472
472
  from .config import configure as configure_config
473
473
 
474
474
  configure_config(**kwargs)
475
+ from . import notify
476
+ notify.close()
475
477
  global _default_client
476
478
  with _client_lock:
477
479
  if _default_client is not None:
@@ -6,11 +6,9 @@ never perform SMTP or HTTP work on a QMT callback/status thread.
6
6
  """
7
7
 
8
8
  import copy
9
- import base64
10
9
  from datetime import datetime, timedelta, timezone
11
10
  from email.message import EmailMessage
12
11
  import hashlib
13
- import hmac
14
12
  import json
15
13
  import os
16
14
  from pathlib import Path
@@ -20,9 +18,7 @@ import ssl
20
18
  import tempfile
21
19
  import threading
22
20
  import time
23
- import urllib.error
24
- import urllib.parse
25
- import urllib.request
21
+ from .webhook import send_webhook
26
22
 
27
23
 
28
24
  SHANGHAI = timezone(timedelta(hours=8))
@@ -566,28 +562,5 @@ class MonitorNotificationService:
566
562
  server.send_message(msg)
567
563
 
568
564
  def _send_webhook(self, config, event):
569
- url = str(config.get("url") or "").strip()
570
- if not url:
571
- raise ValueError("webhook url is required")
572
- kind = str(config.get("type") or "generic").lower()
573
- if kind == "dingtalk" and config.get("secret"):
574
- timestamp = str(int(time.time() * 1000))
575
- secret = str(config.get("secret"))
576
- raw = "%s\n%s" % (timestamp, secret)
577
- sign = base64.b64encode(hmac.new(secret.encode("utf-8"), raw.encode("utf-8"), hashlib.sha256).digest()).decode("ascii")
578
- separator = "&" if "?" in url else "?"
579
- url = "%s%stimestamp=%s&sign=%s" % (url, separator, urllib.parse.quote(timestamp), urllib.parse.quote(sign))
580
- text = self._format_text(event)
581
- if kind == "feishu":
582
- body = {"msg_type": "text", "content": {"text": text}}
583
- elif kind in ("dingtalk", "wecom"):
584
- body = {"msgtype": "text", "text": {"content": text}}
585
- else:
586
- body = {"event_type": event.get("event_type"), "title": event.get("title"),
587
- "message": event.get("message"), "payload": event.get("payload"),
588
- "occurred_at": event.get("occurred_at")}
589
- request = urllib.request.Request(url, data=json.dumps(body, ensure_ascii=False, default=str).encode("utf-8"),
590
- headers={"Content-Type": "application/json; charset=utf-8", "User-Agent": "cfquant-monitor"}, method="POST")
591
- with urllib.request.urlopen(request, timeout=8) as response:
592
- if int(getattr(response, "status", 200)) >= 400:
593
- raise urllib.error.HTTPError(url, response.status, "webhook returned %s" % response.status, response.headers, None)
565
+ payload = {key: event.get(key) for key in ("event_type", "title", "message", "payload", "occurred_at")}
566
+ return send_webhook(config, self._format_text(event), payload=payload)
@@ -0,0 +1,52 @@
1
+ """User-authored messages: explicit Webhook first, otherwise account binding."""
2
+
3
+ import atexit
4
+ import threading
5
+
6
+ from .webhook import WebhookError, normalize_webhook, send_webhook, validate_message
7
+
8
+ _client = None
9
+ _client_lock = threading.Lock()
10
+
11
+
12
+ def close():
13
+ """Release the notification RPC connection; the next call reconnects."""
14
+ global _client
15
+ with _client_lock:
16
+ client, _client = _client, None
17
+ if client is not None:
18
+ client.close()
19
+
20
+
21
+ atexit.register(close)
22
+
23
+
24
+ def send(message, account_id=None, *, title="", webhook_url=None, provider=None,
25
+ secret=None, account_type=None, account_key=None, bridge_id=None, timeout=8):
26
+ """Send text synchronously; returns sent/provider/source, or raises on failure.
27
+
28
+ webhook_url and provider must be supplied together and override the binding
29
+ completely (including its secret). Account-based sends require cfquant Web
30
+ and LTtx, but do not require QMT to be online. No automatic retry is performed.
31
+ """
32
+ timeout = validate_message(message, title, timeout)
33
+ if webhook_url is not None or provider is not None or secret is not None:
34
+ if not webhook_url or not provider:
35
+ raise ValueError("调用时覆盖推送配置必须同时提供 webhook_url 和 provider")
36
+ config = normalize_webhook({"enabled": True, "url": webhook_url, "provider": provider, "secret": secret})
37
+ return dict(send_webhook(config, message, title=title, timeout=timeout), source="explicit")
38
+ if not str(account_id or "").strip() and not str(account_key or "").strip():
39
+ raise ValueError("请指定资金账号 account_id 或 account_key,或提供 webhook_url 和 provider")
40
+ params = {"message": message, "title": title, "timeout": timeout}
41
+ params.update({key: value for key, value in dict(account_id=account_id, account_type=account_type,
42
+ account_key=account_key, bridge_id=bridge_id).items() if value is not None})
43
+ global _client
44
+ with _client_lock:
45
+ if _client is None:
46
+ from .client import create_rpc_client
47
+ _client = create_rpc_client(transport="web_lttx")
48
+ client = _client
49
+ return client.request("cfquant.notify.send", params, timeout=timeout + 5)
50
+
51
+
52
+ __all__ = ["send", "close", "WebhookError"]
@@ -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
@@ -1,6 +1,7 @@
1
1
  from .diagnostic_context import versioned_message
2
2
  # -*- coding: utf-8 -*-
3
3
  import os
4
+ import gc
4
5
  import queue
5
6
  import sys
6
7
  import threading
@@ -516,47 +517,91 @@ class CfquantQmtBridge(object):
516
517
  })
517
518
  raise NotImplementedError("当前QMT环境未提供download_history_data2")
518
519
 
519
- def callback(data):
520
+ stock_list = params.get("stock_list", params.get("code_list", []))
521
+ if isinstance(stock_list, str):
522
+ stock_list = [item.strip() for item in stock_list.replace(",", ",").split(",") if item.strip()]
523
+ else:
524
+ stock_list = list(stock_list or [])
525
+ batches = self._history_download_batches(stock_list, params)
526
+ total = len(stock_list)
527
+ completed = 0
528
+
529
+ def callback(data, batch_offset=0, batch_index=0, batch_total=1):
520
530
  if callback_event and client_id:
531
+ payload = dict(data) if isinstance(data, dict) else {"message": str(data)}
532
+ batch_finished = payload.get("finished")
533
+ try:
534
+ batch_finished = max(0, min(int(batch_finished), len(batches[batch_index])))
535
+ except (TypeError, ValueError, IndexError):
536
+ batch_finished = 0
537
+ payload.update({
538
+ "finished": min(total, batch_offset + batch_finished),
539
+ "total": total,
540
+ "batch_index": batch_index + 1,
541
+ "batch_total": batch_total,
542
+ "batch_finished": batch_finished,
543
+ "batch_size": len(batches[batch_index]) if batch_index < len(batches) else 0,
544
+ })
521
545
  self._send_event(
522
546
  client_id,
523
547
  callback_event,
524
- data,
548
+ payload,
525
549
  meta=self._download_event_meta(params, "history", "progress"),
526
550
  )
527
551
 
528
- callback_func = callback if callback_event else None
529
- variants = []
530
- incrementally = params.get("incrementally")
531
- if incrementally is not None:
532
- variants.append((
533
- (
534
- params.get("stock_list", params.get("code_list", [])),
535
- params.get("period", "1d"),
536
- params.get("start_time", ""),
537
- params.get("end_time", ""),
538
- callback_func,
539
- incrementally,
540
- ),
541
- {},
542
- ))
543
- variants.append((
544
- (
545
- params.get("stock_list", params.get("code_list", [])),
546
- params.get("period", "1d"),
547
- params.get("start_time", ""),
548
- params.get("end_time", ""),
549
- callback_func,
550
- ),
551
- {},
552
- ))
553
552
  if emit_lifecycle:
554
553
  self._send_download_event(client_id, params, "history", "submitted", {
555
554
  "stage": "submitted",
556
555
  "message": "history download request submitted",
556
+ "total": total,
557
+ "batch_total": len(batches),
558
+ "batch_size": self._history_download_batch_size(params),
557
559
  })
558
560
  try:
559
- result = self._call_variants(func, variants)
561
+ incrementally = params.get("incrementally")
562
+ batch_total = len(batches)
563
+ for batch_index, batch in enumerate(batches):
564
+ batch_offset = completed
565
+ callback_func = (
566
+ (lambda data, offset=batch_offset, index=batch_index:
567
+ callback(data, offset, index, batch_total))
568
+ if callback_event else None
569
+ )
570
+ variants = []
571
+ if incrementally is not None:
572
+ variants.append((
573
+ (
574
+ batch,
575
+ params.get("period", "1d"),
576
+ params.get("start_time", ""),
577
+ params.get("end_time", ""),
578
+ callback_func,
579
+ incrementally,
580
+ ),
581
+ {},
582
+ ))
583
+ variants.append((
584
+ (
585
+ batch,
586
+ params.get("period", "1d"),
587
+ params.get("start_time", ""),
588
+ params.get("end_time", ""),
589
+ callback_func,
590
+ ),
591
+ {},
592
+ ))
593
+ self._call_variants(func, variants)
594
+ completed += len(batch)
595
+ if emit_lifecycle:
596
+ self._send_download_event(client_id, params, "history", "batch_done", {
597
+ "stage": "batch_done",
598
+ "finished": completed,
599
+ "total": total,
600
+ "batch_index": batch_index + 1,
601
+ "batch_total": batch_total,
602
+ "batch_size": len(batch),
603
+ })
604
+ gc.collect()
560
605
  except Exception as e:
561
606
  if emit_lifecycle:
562
607
  self._send_download_event(client_id, params, "history", "error", {
@@ -567,9 +612,39 @@ class CfquantQmtBridge(object):
567
612
  if emit_lifecycle:
568
613
  self._send_download_event(client_id, params, "history", "request_done", {
569
614
  "stage": "request_done",
570
- "result": result,
615
+ "result": None,
616
+ "finished": completed,
617
+ "total": total,
571
618
  })
572
- return result
619
+ return None
620
+
621
+ @staticmethod
622
+ def _history_download_batch_size(params):
623
+ params = params or {}
624
+ value = params.get("download_batch_size", params.get("batch_size"))
625
+ if value is None:
626
+ value = os.environ.get("CFQUANT_HISTORY_DOWNLOAD_BATCH_SIZE")
627
+ if value is None or str(value).strip() == "":
628
+ period = str(params.get("period") or "").lower()
629
+ if period == "tick":
630
+ return 20
631
+ if period == "1m":
632
+ return 50
633
+ if period.endswith("m") or period.endswith("h"):
634
+ return 100
635
+ return 200
636
+ try:
637
+ value = int(value)
638
+ except (TypeError, ValueError):
639
+ return 50
640
+ return max(1, value)
641
+
642
+ @classmethod
643
+ def _history_download_batches(cls, stock_list, params):
644
+ if not stock_list:
645
+ return [stock_list]
646
+ size = cls._history_download_batch_size(params)
647
+ return [stock_list[index:index + size] for index in range(0, len(stock_list), size)]
573
648
 
574
649
  def _get_instrument_detail(self, params):
575
650
  func = getattr(self.context, "get_instrument_detail")