cfquant 0.2.49__tar.gz → 0.2.51__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 (180) hide show
  1. {cfquant-0.2.49 → cfquant-0.2.51}/PKG-INFO +1 -1
  2. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/build_info.py +2 -0
  3. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/normal_bridge.py +9 -1
  4. cfquant-0.2.51/cfquant/order_identity.py +190 -0
  5. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/order_meta.py +2 -1
  6. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/pipe_bridge.py +12 -2
  7. cfquant-0.2.51/cfquant/qmt_pipe_runtime.py +166 -0
  8. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/qmt_strategy_deploy.py +144 -29
  9. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/qmt_strategy_runtime.py +20 -11
  10. {cfquant-0.2.49 → cfquant-0.2.51}/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 +1 -1
  11. cfquant-0.2.51/cfquant/tests/test_advanced_parity.py +153 -0
  12. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/tests/test_lite_deployment_paths.py +18 -9
  13. cfquant-0.2.51/cfquant/tests/test_lite_parity.py +140 -0
  14. cfquant-0.2.51/cfquant/tests/test_qmt_fileless.py +271 -0
  15. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/tests/test_qmt_strategy_deploy.py +150 -16
  16. cfquant-0.2.51/cfquant/tests/test_trade_strategy_callback.py +273 -0
  17. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/tx_trade_bridge.py +65 -24
  18. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/version.py +2 -2
  19. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/xttype.py +2 -1
  20. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant.egg-info/PKG-INFO +1 -1
  21. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant.egg-info/SOURCES.txt +5 -0
  22. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant_web_server.py +37 -14
  23. {cfquant-0.2.49 → cfquant-0.2.51}/pyproject.toml +1 -1
  24. {cfquant-0.2.49 → cfquant-0.2.51}/qmt_scripts/CFQUANT_CTYPE_ALL_LOWLAT.py +248 -379
  25. {cfquant-0.2.49 → cfquant-0.2.51}/qmt_scripts/CFQUANT_LITE.py +1178 -568
  26. {cfquant-0.2.49 → cfquant-0.2.51}/qmt_scripts/CFQUANT_TRADE_LOWLAT.py +9 -2
  27. {cfquant-0.2.49 → cfquant-0.2.51}/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 +196 -367
  28. {cfquant-0.2.49 → cfquant-0.2.51}/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 +196 -367
  29. {cfquant-0.2.49 → cfquant-0.2.51}/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 +1178 -620
  30. {cfquant-0.2.49 → cfquant-0.2.51}/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 +1178 -620
  31. {cfquant-0.2.49 → cfquant-0.2.51}/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 +9 -2
  32. {cfquant-0.2.49 → cfquant-0.2.51}/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 +9 -2
  33. {cfquant-0.2.49 → cfquant-0.2.51}/web_dashboard/app.js +1 -1
  34. {cfquant-0.2.49 → cfquant-0.2.51}/web_dashboard/index.html +2 -2
  35. cfquant-0.2.49/cfquant/order_identity.py +0 -55
  36. {cfquant-0.2.49 → cfquant-0.2.51}/LICENSE +0 -0
  37. {cfquant-0.2.49 → cfquant-0.2.51}/LTtx/__init__.py +0 -0
  38. {cfquant-0.2.49 → cfquant-0.2.51}/LTtx/tx/Config.txt +0 -0
  39. {cfquant-0.2.49 → cfquant-0.2.51}/LTtx/tx/LTtx_server.py +0 -0
  40. {cfquant-0.2.49 → cfquant-0.2.51}/LTtx/tx/__init__.py +0 -0
  41. {cfquant-0.2.49 → cfquant-0.2.51}/LTtx/tx/tx.py +0 -0
  42. {cfquant-0.2.49 → cfquant-0.2.51}/MANIFEST.in +0 -0
  43. {cfquant-0.2.49 → cfquant-0.2.51}/README.md +0 -0
  44. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/__init__.py +0 -0
  45. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/_editable_install.py +0 -0
  46. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/account_routing.py +0 -0
  47. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/batch_orders.py +0 -0
  48. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/callback_dispatcher.py +0 -0
  49. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/cftrader.py +0 -0
  50. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/channels.py +0 -0
  51. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/cli.py +0 -0
  52. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/client.py +0 -0
  53. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/config.py +0 -0
  54. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/hub.py +0 -0
  55. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/level2.py +0 -0
  56. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/log_management.py +0 -0
  57. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/logging_i18n.py +0 -0
  58. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/management.py +0 -0
  59. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/pipe_client.py +0 -0
  60. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/pipe_hub.py +0 -0
  61. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/pipe_transport.py +0 -0
  62. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/protocol.py +0 -0
  63. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/qmt_bridge.py +0 -0
  64. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/qmt_strategy_package.py +0 -0
  65. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/runtime_report.py +0 -0
  66. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/stock_connect.py +0 -0
  67. {cfquant-0.2.49 → cfquant-0.2.51}/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
  68. {cfquant-0.2.49 → cfquant-0.2.51}/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
  69. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/tests/12_/350/256/242/345/215/225/345/244/207/346/263/250/346/265/213/350/257/225.py" +0 -0
  70. {cfquant-0.2.49 → cfquant-0.2.51}/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
  71. {cfquant-0.2.49 → cfquant-0.2.51}/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
  72. {cfquant-0.2.49 → cfquant-0.2.51}/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
  73. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/tests/16_/345/220/257/345/212/250/345/256/211/350/243/205/346/265/213/350/257/225.py" +0 -0
  74. {cfquant-0.2.49 → cfquant-0.2.51}/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
  75. {cfquant-0.2.49 → cfquant-0.2.51}/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
  76. {cfquant-0.2.49 → cfquant-0.2.51}/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
  77. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/tests/1_/350/241/214/346/203/205/346/216/245/346/224/266/346/265/213/350/257/225.py" +0 -0
  78. {cfquant-0.2.49 → cfquant-0.2.51}/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
  79. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/tests/22_/350/277/236/346/216/245/350/257/212/346/226/255/346/265/213/350/257/225.py" +0 -0
  80. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/tests/23_/347/244/272/344/276/213/351/205/215/347/275/256/346/265/213/350/257/225.py" +0 -0
  81. {cfquant-0.2.49 → cfquant-0.2.51}/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
  82. {cfquant-0.2.49 → cfquant-0.2.51}/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
  83. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/tests/26_/345/205/250/346/216/250/350/256/242/351/230/205/346/265/213/350/257/225.py" +0 -0
  84. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/tests/27_/345/205/250/346/216/250/350/256/242/351/230/205/351/241/265/351/235/242/346/265/213/350/257/225.py" +0 -0
  85. {cfquant-0.2.49 → cfquant-0.2.51}/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
  86. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/tests/29_Level2/350/241/214/346/203/205/346/265/213/350/257/225.py" +0 -0
  87. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/tests/2_/346/225/260/346/215/256/350/216/267/345/217/226/346/265/213/350/257/225.py" +0 -0
  88. {cfquant-0.2.49 → cfquant-0.2.51}/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
  89. {cfquant-0.2.49 → cfquant-0.2.51}/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
  90. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/tests/32_/345/274/202/346/255/245/344/270/213/345/215/225/346/265/213/350/257/225.py" +0 -0
  91. {cfquant-0.2.49 → cfquant-0.2.51}/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
  92. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/tests/3_/346/225/260/346/215/256/344/270/213/350/275/275/346/265/213/350/257/225.py" +0 -0
  93. {cfquant-0.2.49 → cfquant-0.2.51}/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
  94. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/tests/5_/347/234/237/345/256/236/344/270/213/345/215/225/346/265/213/350/257/225.py" +0 -0
  95. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/tests/6_/345/233/236/350/260/203/346/265/213/350/257/225.py" +0 -0
  96. {cfquant-0.2.49 → cfquant-0.2.51}/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
  97. {cfquant-0.2.49 → cfquant-0.2.51}/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
  98. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/tests/9_/345/217/257/346/222/244/350/256/242/345/215/225/346/265/213/350/257/225.py" +0 -0
  99. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/tests/_helpers.py +0 -0
  100. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/tests/log/cftrader/346/250/241/346/213/237/350/264/246/345/217/267/346/211/271/351/207/217/344/270/213/345/215/225/346/265/213/350/257/225_20260911_024617.md" +0 -0
  101. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/tests/log/cftrader/346/250/241/346/213/237/350/264/246/345/217/267/346/211/271/351/207/217/344/270/213/345/215/225/346/265/213/350/257/225_20260921_175732.md" +0 -0
  102. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/tests/readme.md +0 -0
  103. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/tests/test_api_tester_browser.py +0 -0
  104. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/tests/test_api_tester_web.py +0 -0
  105. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/tests/test_binding_delete_confirmation.py +0 -0
  106. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/tests/test_callback_requests.py +0 -0
  107. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/tests/test_cftrader.py +0 -0
  108. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/tests/test_cftrader_tutorial.py +0 -0
  109. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/tests/test_cftrader_web.py +0 -0
  110. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/tests/test_credit_compat.py +0 -0
  111. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/tests/test_json_serialization.py +0 -0
  112. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/tests/test_lite_account_routing.py +0 -0
  113. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/tests/test_local_trading_calendar.py +0 -0
  114. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/tests/test_log_management.py +0 -0
  115. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/tests/test_management.py +0 -0
  116. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/tests/test_management_batch.py +0 -0
  117. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/tests/test_market_data_fallback.py +0 -0
  118. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/tests/test_order_identity.py +0 -0
  119. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/tests/test_order_time_callback.py +0 -0
  120. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/tests/test_project_updater.py +0 -0
  121. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/tests/test_python_api_reference.py +0 -0
  122. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/tests/test_qmt_strategy_browser.py +0 -0
  123. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/tests/test_qmt_strategy_web.py +0 -0
  124. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/tests/test_setup_qmt_prompt.py +0 -0
  125. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/tests/test_startup_recovery.py +0 -0
  126. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/tests/test_stock_connect.py +0 -0
  127. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/tests/test_stock_connect_browser.py +0 -0
  128. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/tests/test_trading_dates.py +0 -0
  129. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/tests/test_tutorial_reader.py +0 -0
  130. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/tests/test_tx_auto_import.py +0 -0
  131. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/trading_calendar.py +0 -0
  132. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/tx.py +0 -0
  133. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/xtconstant.py +0 -0
  134. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/xtdata.py +0 -0
  135. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant/xttrader.py +0 -0
  136. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant.egg-info/dependency_links.txt +0 -0
  137. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant.egg-info/entry_points.txt +0 -0
  138. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant.egg-info/requires.txt +0 -0
  139. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant.egg-info/top_level.txt +0 -0
  140. {cfquant-0.2.49 → cfquant-0.2.51}/cfquant_pipe_hub.py +0 -0
  141. {cfquant-0.2.49 → cfquant-0.2.51}/qmt_scripts/CFQUANT.py +0 -0
  142. {cfquant-0.2.49 → cfquant-0.2.51}/qmt_scripts/__init__.py +0 -0
  143. {cfquant-0.2.49 → cfquant-0.2.51}/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
  144. {cfquant-0.2.49 → cfquant-0.2.51}/requirements.txt +0 -0
  145. {cfquant-0.2.49 → cfquant-0.2.51}/setup.cfg +0 -0
  146. {cfquant-0.2.49 → cfquant-0.2.51}/web_dashboard/__init__.py +0 -0
  147. {cfquant-0.2.49 → cfquant-0.2.51}/web_dashboard/api-tester.js +0 -0
  148. {cfquant-0.2.49 → cfquant-0.2.51}/web_dashboard/avatars/amber-pulse.svg +0 -0
  149. {cfquant-0.2.49 → cfquant-0.2.51}/web_dashboard/avatars/copper-grid.svg +0 -0
  150. {cfquant-0.2.49 → cfquant-0.2.51}/web_dashboard/avatars/market-blue.svg +0 -0
  151. {cfquant-0.2.49 → cfquant-0.2.51}/web_dashboard/avatars/rose-circuit.svg +0 -0
  152. {cfquant-0.2.49 → cfquant-0.2.51}/web_dashboard/avatars/signal-green.svg +0 -0
  153. {cfquant-0.2.49 → cfquant-0.2.51}/web_dashboard/avatars/slate-wave.svg +0 -0
  154. {cfquant-0.2.49 → cfquant-0.2.51}/web_dashboard/avatars/teal-orbit.svg +0 -0
  155. {cfquant-0.2.49 → cfquant-0.2.51}/web_dashboard/avatars/violet-node.svg +0 -0
  156. {cfquant-0.2.49 → cfquant-0.2.51}/web_dashboard/cftrader-api-data.js +0 -0
  157. {cfquant-0.2.49 → cfquant-0.2.51}/web_dashboard/docs.js +0 -0
  158. {cfquant-0.2.49 → cfquant-0.2.51}/web_dashboard/favicon.svg +0 -0
  159. {cfquant-0.2.49 → cfquant-0.2.51}/web_dashboard/image/qmt_deploy/image-10.png +0 -0
  160. {cfquant-0.2.49 → cfquant-0.2.51}/web_dashboard/image/qmt_deploy/image-11.png +0 -0
  161. {cfquant-0.2.49 → cfquant-0.2.51}/web_dashboard/image/qmt_deploy/image-12.png +0 -0
  162. {cfquant-0.2.49 → cfquant-0.2.51}/web_dashboard/image/qmt_deploy/image-13.png +0 -0
  163. {cfquant-0.2.49 → cfquant-0.2.51}/web_dashboard/image/qmt_deploy/image-14.png +0 -0
  164. {cfquant-0.2.49 → cfquant-0.2.51}/web_dashboard/image/qmt_deploy/image-15.png +0 -0
  165. {cfquant-0.2.49 → cfquant-0.2.51}/web_dashboard/image/qmt_deploy/image-16.png +0 -0
  166. {cfquant-0.2.49 → cfquant-0.2.51}/web_dashboard/image/qmt_deploy/image-17.png +0 -0
  167. {cfquant-0.2.49 → cfquant-0.2.51}/web_dashboard/image/qmt_deploy/image-18.png +0 -0
  168. {cfquant-0.2.49 → cfquant-0.2.51}/web_dashboard/image/qmt_deploy/image-19.png +0 -0
  169. {cfquant-0.2.49 → cfquant-0.2.51}/web_dashboard/image/qmt_deploy/image-20.png +0 -0
  170. {cfquant-0.2.49 → cfquant-0.2.51}/web_dashboard/image/qmt_deploy/image-21.png +0 -0
  171. {cfquant-0.2.49 → cfquant-0.2.51}/web_dashboard/image/qmt_deploy/image-4.png +0 -0
  172. {cfquant-0.2.49 → cfquant-0.2.51}/web_dashboard/image/qmt_deploy/image-5.png +0 -0
  173. {cfquant-0.2.49 → cfquant-0.2.51}/web_dashboard/image/qmt_deploy/image-6.png +0 -0
  174. {cfquant-0.2.49 → cfquant-0.2.51}/web_dashboard/image/qmt_deploy/image-7.png +0 -0
  175. {cfquant-0.2.49 → cfquant-0.2.51}/web_dashboard/image/qmt_deploy/image-8.png +0 -0
  176. {cfquant-0.2.49 → cfquant-0.2.51}/web_dashboard/image/qmt_deploy/image-9.png +0 -0
  177. {cfquant-0.2.49 → cfquant-0.2.51}/web_dashboard/python-api-data.js +0 -0
  178. {cfquant-0.2.49 → cfquant-0.2.51}/web_dashboard/python-api.js +0 -0
  179. {cfquant-0.2.49 → cfquant-0.2.51}/web_dashboard/styles.css +0 -0
  180. {cfquant-0.2.49 → cfquant-0.2.51}/web_dashboard/vendor/mermaid.min.js +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cfquant
3
- Version: 0.2.49
3
+ Version: 0.2.51
4
4
  Summary: QMT local bridge and xtquant-compatible Python client.
5
5
  License: MIT
6
6
  Requires-Python: >=3.8
@@ -29,6 +29,8 @@ def get_git_commit(source_root=None):
29
29
  override = _valid_commit(os.environ.get("CFQUANT_GIT_COMMIT"))
30
30
  if override:
31
31
  return override
32
+ if os.environ.get("CFQUANT_QMT_NO_FILE_IO") == "1":
33
+ return ""
32
34
  root = Path(source_root or Path(__file__).resolve().parent.parent).resolve()
33
35
  try:
34
36
  result = subprocess.run(["git", "-C", str(root), "rev-parse", "HEAD"], stdout=subprocess.PIPE,
@@ -10,7 +10,7 @@ import threading
10
10
  import time
11
11
 
12
12
  from . import order_meta
13
- from .order_identity import prepare_order_remark, order_remark_key, restore_order_remark
13
+ from .order_identity import prepare_order_remark, order_remark_key, restore_order_remark, restore_strategy_name
14
14
  from .level2 import L2_THOUSAND_SUBSCRIPTIONS, quote_callback_data, quote_plain, require_l2_callable, thousand_price
15
15
  from .protocol import loads_message, pack_event, pack_response
16
16
  from .tx_trade_bridge import TxTradeBridge, relay_sync_order_callback
@@ -191,6 +191,12 @@ class NormalQmtBridge(TxTradeBridge):
191
191
  if action == "cfquant.status":
192
192
  self._send_response(msg, self._status())
193
193
  return
194
+ if action == "cfquant.strategy.control":
195
+ try:
196
+ self._send_response(msg, self._dispatch(action, msg.get("params") or {}, msg))
197
+ except Exception as error:
198
+ self._send_error(msg, error)
199
+ return
194
200
  if self._try_enqueue_coalesced_request(msg):
195
201
  return
196
202
  try:
@@ -893,6 +899,8 @@ class NormalQmtBridge(TxTradeBridge):
893
899
  self._resolve_pending_sync_order_callback(data)
894
900
  relay_sync_order_callback(self.context, data)
895
901
  self._handle_async_order_callback(data)
902
+ self._remember_order_callback_fields(data)
903
+ restore_strategy_name(data)
896
904
  payload = {
897
905
  "type": "event",
898
906
  "event": event_name,
@@ -0,0 +1,190 @@
1
+ """Per-submission QMT remarks, compatible with embedded Python 3.6.
2
+
3
+ The public remark is user data, never a unique order key. Keep a UUID in the
4
+ native remark so independent QMT processes and restarts can recover both.
5
+ """
6
+
7
+ import re
8
+ import threading
9
+ import time
10
+ import uuid
11
+ from collections import OrderedDict
12
+
13
+
14
+ _ORDER_REMARK_SUFFIX = re.compile(r"__cfq_([0-9a-f]{32})$")
15
+ _STRATEGY_NAME_SUFFIX = re.compile(r"(?:&&&_cfq_|^cfq_)[0-9a-f]{12}\Z")
16
+
17
+
18
+ def restore_strategy_name(data):
19
+ """Remove our request token only at the public callback/object boundary.
20
+
21
+ Keep the full strategy until error-context matching has finished. Match
22
+ the generated suffix precisely so user underscores and delimiters survive.
23
+ """
24
+ if isinstance(data, dict):
25
+ for name in ("strategy_name", "m_strStrategyName", "mstrStrategyName", "strategyName"):
26
+ value = data.get(name)
27
+ if isinstance(value, str):
28
+ match = _STRATEGY_NAME_SUFFIX.search(value)
29
+ if match:
30
+ data[name] = value[:match.start()]
31
+ return data
32
+
33
+
34
+ def original_order_remark(value):
35
+ text = str(value or "")
36
+ match = _ORDER_REMARK_SUFFIX.search(text)
37
+ return text[:match.start()] if match else text
38
+
39
+
40
+ def is_unique_order_remark(value):
41
+ return bool(_ORDER_REMARK_SUFFIX.search(str(value or "")))
42
+
43
+
44
+ def prepare_order_remark(params, fallback=""):
45
+ original = next((params[name] for name in ("order_remark", "remark", "strategy_name")
46
+ if params.get(name) not in (None, "")), "")
47
+ original = str(original or "")
48
+ internal = params.get("cfquant_order_remark")
49
+ if not (is_unique_order_remark(internal) and
50
+ original_order_remark(internal) in (original, original or str(fallback or ""))):
51
+ original = original or str(fallback or "")
52
+ internal = original + "__cfq_" + uuid.uuid4().hex
53
+ params["cfquant_order_remark"] = internal
54
+ return internal
55
+
56
+
57
+ def order_remark_key(data):
58
+ getter = data.get if isinstance(data, dict) else lambda name: getattr(data, name, None)
59
+ values = [getter(name) for name in (
60
+ "cfquant_order_remark", "user_order_id", "client_order_id",
61
+ "order_remark", "m_strRemark", "m_strOrderRemark", "remark",
62
+ )]
63
+ for value in values:
64
+ if is_unique_order_remark(value):
65
+ return str(value)
66
+ return next((str(value) for value in values if value not in (None, "")), "")
67
+
68
+
69
+ def restore_order_remark(data):
70
+ if isinstance(data, dict):
71
+ internal = order_remark_key(data)
72
+ if is_unique_order_remark(internal):
73
+ data["cfquant_order_remark"] = internal
74
+ data["order_remark"] = original_order_remark(internal)
75
+ return data
76
+
77
+
78
+ def order_remark_matches_reference(expected, actual):
79
+ """Compare remarks only AFTER an order reference has matched.
80
+
81
+ Some DEAL reports echo the public remark without our UUID. A different
82
+ UUID still identifies a different submission, even with the same text.
83
+ """
84
+ return (not actual or expected == actual or
85
+ (is_unique_order_remark(expected) and
86
+ not is_unique_order_remark(actual) and
87
+ original_order_remark(expected) == actual))
88
+
89
+
90
+ class OrderCallbackCache(object):
91
+ """Bounded, daily order metadata indexed by separate local/broker IDs."""
92
+
93
+ def __init__(self, max_orders=4096):
94
+ self.max_orders = max_orders
95
+ self.lock = threading.RLock()
96
+ self.day = ""
97
+ self.records = OrderedDict()
98
+ self.by_ref = {}
99
+
100
+ def _identity(self, data, account_type):
101
+ day = time.strftime("%Y%m%d")
102
+ if day != self.day:
103
+ self.records.clear()
104
+ self.by_ref.clear()
105
+ self.day = day
106
+ # Historical query rows must not seed today's broker-ID mapping.
107
+ for name in ("order_date", "trade_date", "m_strInsertDate", "m_strTradeDate", "m_strTradingDay"):
108
+ value = str(data.get(name) or "").replace("-", "").replace("/", "")
109
+ if len(value) == 8 and value.isdigit() and value != day:
110
+ return None, set()
111
+ account = str(data.get("account_id") or data.get("m_strAccountID") or "").strip()
112
+ stock = str(data.get("stock_code") or data.get("m_strInstrumentID") or "").strip().upper()
113
+ market = str(data.get("market") or data.get("m_strExchangeID") or "").strip().upper()
114
+ if "." in stock:
115
+ stock, market = stock.rsplit(".", 1)
116
+ if not account or not stock:
117
+ return None, set()
118
+ scope = (account_type, account, stock, market)
119
+ refs = set()
120
+ for name in ("order_id", "m_nRef", "m_nOrderID", "m_strOrderRef", "m_strOrderID"):
121
+ value = str(data.get(name) or "").strip()
122
+ if value.isdigit() and int(value) > 0:
123
+ refs.add(("local", str(int(value))))
124
+ for name in ("order_sysid", "m_strOrderSysID"):
125
+ value = str(data.get(name) or "").strip()
126
+ if value and value not in ("0", "-1"):
127
+ refs.add(("broker", value))
128
+ return scope, refs
129
+
130
+ def _matches(self, scope, refs, data):
131
+ groups = [self.by_ref[(scope, ref)] for ref in refs if (scope, ref) in self.by_ref]
132
+ candidates = set.intersection(*groups) if groups else set()
133
+ matches = []
134
+ for key in candidates:
135
+ record = self.records[key]
136
+ expected, actual = order_remark_key(record["data"]), order_remark_key(data)
137
+ if expected and not order_remark_matches_reference(expected, actual):
138
+ continue
139
+ local = {ref for ref in refs if ref[0] == "local"}
140
+ known = {ref for ref in record["refs"] if ref[0] == "local"}
141
+ if local and known and not local.intersection(known):
142
+ continue
143
+ matches.append(key)
144
+ return matches
145
+
146
+ def remember(self, data, account_type):
147
+ if not (data.get("strategy_name") or data.get("m_strStrategyName") or order_remark_key(data)):
148
+ return
149
+ with self.lock:
150
+ scope, refs = self._identity(data, account_type)
151
+ if scope is None or not refs:
152
+ return
153
+ matches = self._matches(scope, refs, data)
154
+ remark = order_remark_key(data)
155
+ 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]
157
+ key = (scope, identity)
158
+ if len(matches) == 1:
159
+ key = matches[0]
160
+ record = self.records.get(key, {"data": {}, "refs": set()})
161
+ record["data"].update({name: value for name, value in data.items() if value not in (None, "")})
162
+ record["refs"].update(refs)
163
+ self.records[key] = record
164
+ self.records.move_to_end(key)
165
+ for ref in refs:
166
+ self.by_ref.setdefault((scope, ref), set()).add(key)
167
+ while len(self.records) > self.max_orders:
168
+ old_key, old = self.records.popitem(last=False)
169
+ for ref in old["refs"]:
170
+ index = (old_key[0], ref)
171
+ self.by_ref[index].discard(old_key)
172
+ if not self.by_ref[index]:
173
+ del self.by_ref[index]
174
+
175
+ def enrich(self, data, account_type):
176
+ with self.lock:
177
+ scope, refs = self._identity(data, account_type)
178
+ matches = self._matches(scope, refs, data) if scope is not None else []
179
+ if len(matches) != 1:
180
+ return data
181
+ saved = self.records[matches[0]]["data"]
182
+ strategy = saved.get("strategy_name") or saved.get("m_strStrategyName")
183
+ remark = order_remark_key(saved)
184
+ for name, value in (("strategy_name", strategy), ("m_strStrategyName", strategy),
185
+ ("order_remark", remark), ("m_strRemark", remark), ("m_strOrderRemark", remark)):
186
+ if value and not data.get(name):
187
+ data[name] = value
188
+ if is_unique_order_remark(remark):
189
+ data["cfquant_order_remark"] = remark
190
+ return restore_order_remark(data)
@@ -13,6 +13,7 @@ import re
13
13
  import threading
14
14
  from .stock_connect import connect_account_type
15
15
  from .order_identity import order_remark_key, restore_order_remark, is_unique_order_remark
16
+ from .order_identity import order_remark_matches_reference
16
17
  import time
17
18
 
18
19
 
@@ -609,7 +610,7 @@ class OrderMetaCache(object):
609
610
  for order_ref in order_refs:
610
611
  record = self.by_ref.get(self._ctx(callback_record) + (order_ref,))
611
612
  if record and user_order_id and is_unique_order_remark(user_order_id_from_data(record)):
612
- if user_order_id != user_order_id_from_data(record):
613
+ if not order_remark_matches_reference(user_order_id_from_data(record), user_order_id):
613
614
  record = None
614
615
  if record:
615
616
  confidence = "order_ref"
@@ -8,7 +8,9 @@ from .pipe_transport import DEFAULT_PIPE_NAME, PipeTxClient
8
8
  from .tx_trade_bridge import TxTradeBridge
9
9
 
10
10
 
11
- class PipeNormalQmtBridge(NormalQmtBridge):
11
+ class PipeNormalQmtBridge(NormalQmtBridge):
12
+ file_io_enabled = False
13
+
12
14
  def __init__(
13
15
  self,
14
16
  context,
@@ -48,6 +50,9 @@ class PipeNormalQmtBridge(NormalQmtBridge):
48
50
  self.connect_timeout_ms = int(connect_timeout_ms)
49
51
 
50
52
  def start(self):
53
+ runtime = self.globals_dict.get("_managed_runtime")
54
+ if runtime is not None:
55
+ runtime.attach_bridge(self)
51
56
  if self.running:
52
57
  return self
53
58
  self.running = True
@@ -103,7 +108,9 @@ class PipeNormalQmtBridge(NormalQmtBridge):
103
108
  return data
104
109
 
105
110
 
106
- class PipeTradeBridge(TxTradeBridge):
111
+ class PipeTradeBridge(TxTradeBridge):
112
+ file_io_enabled = False
113
+
107
114
  def __init__(
108
115
  self,
109
116
  context,
@@ -131,6 +138,9 @@ class PipeTradeBridge(TxTradeBridge):
131
138
  self.connect_timeout_ms = int(connect_timeout_ms)
132
139
 
133
140
  def start(self):
141
+ runtime = self.globals_dict.get("_managed_runtime")
142
+ if runtime is not None:
143
+ runtime.attach_bridge(self)
134
144
  if self.running:
135
145
  return self
136
146
  self.running = True
@@ -0,0 +1,166 @@
1
+ """In-memory managed strategy control over the existing Pipe connection (QMT 3.6+)."""
2
+
3
+ import functools as _cq_functools
4
+ import os as _cq_os
5
+ import sys as _cq_sys
6
+ import threading as _cq_threading
7
+ import time as _cq_time
8
+
9
+
10
+ class ManagedPipeRuntime(object):
11
+ def __init__(self, descriptor=None):
12
+ self.descriptor = dict(descriptor or {})
13
+ self.active = False
14
+ self.approved = False
15
+ self.revoked = False
16
+ self.namespace = {}
17
+ self.original_stop = None
18
+ self.failure = ""
19
+ self.calls = 0
20
+ self.bridges_closed = False
21
+ self.attached_bridges = []
22
+ self.mutex = _cq_threading.RLock()
23
+
24
+ def acquire(self):
25
+ if not self.descriptor:
26
+ self.active = True
27
+ return self
28
+ # sys survives strategy namespace reloads inside one QMT process.
29
+ # Across processes the Pipe hub arbitrates ownership of request channels.
30
+ registry = _cq_sys.__dict__.setdefault("_cfquant_pipe_strategies", {})
31
+ lock = _cq_sys.__dict__.setdefault("_cfquant_pipe_strategies_lock", _cq_threading.RLock())
32
+ with lock:
33
+ key = (self.descriptor["runtime_key"], self.descriptor["role"])
34
+ incompatible = any(scope[0] == key[0] and (
35
+ owner.descriptor.get("mode") != self.descriptor["mode"]
36
+ or owner.descriptor.get("generation") != self.descriptor["generation"])
37
+ for scope, owner in registry.items())
38
+ if key in registry or incompatible:
39
+ raise RuntimeError("Another cfquant strategy still owns this QMT account role.")
40
+ registry[key] = self
41
+ self.active = True
42
+ return self
43
+
44
+ def status(self):
45
+ bridges = [self.namespace.get(name) for name in ("_normal_bridge", "_trade_bridge")
46
+ if self.namespace.get(name) is not None]
47
+ ready = bool(bridges) and all(getattr(bridge, "running", False)
48
+ and getattr(bridge, "context", None) is not None for bridge in bridges)
49
+ state = "error" if self.failure else ("stopped" if not self.active or self.revoked
50
+ else ("running" if ready and self.approved else "loaded"))
51
+ return dict(self.descriptor, state=state, error=self.failure,
52
+ pid=_cq_os.getpid(), updated_at=_cq_time.time())
53
+
54
+ def control(self, desired):
55
+ if desired.get("runtime_key") != self.descriptor.get("runtime_key"):
56
+ raise ValueError("Managed strategy account scope mismatch")
57
+ accepted = desired.get("accepted_generations") or []
58
+ if not isinstance(accepted, (tuple, list)):
59
+ accepted = []
60
+ allowed = (desired.get("enabled") is True
61
+ and desired.get("mode") == self.descriptor["mode"]
62
+ and self.descriptor["generation"] in [desired.get("generation")] + accepted)
63
+ with self.mutex:
64
+ self.approved = allowed and self.active and not self.revoked
65
+ if not allowed and not self.revoked:
66
+ self.revoked = True
67
+ # Block requests immediately; let the control response leave before closing Pipe.
68
+ worker = _cq_threading.Timer(0.1, self.close)
69
+ worker.daemon = True
70
+ worker.start()
71
+ return self.status()
72
+
73
+ def dispatch(self, function):
74
+ dispatch_request = self.wrap(function, reject=True)
75
+ @_cq_functools.wraps(function)
76
+ def guarded(action, params, msg):
77
+ if action == "cfquant.strategy.control":
78
+ return self.control(params or {})
79
+ if action == "cfquant.status":
80
+ return function(action, params, msg)
81
+ if not self.approved:
82
+ raise RuntimeError("Managed strategy is awaiting account mode confirmation from cfquant Web.")
83
+ return dispatch_request(action, params, msg)
84
+ return guarded
85
+
86
+ def wrap(self, function, reject=False):
87
+ @_cq_functools.wraps(function)
88
+ def guarded(*args, **kwargs):
89
+ with self.mutex:
90
+ if not self.active or self.revoked:
91
+ if reject:
92
+ raise RuntimeError("The account's cfquant strategy mode has changed.")
93
+ return None
94
+ self.calls += 1
95
+ try:
96
+ return function(*args, **kwargs)
97
+ except Exception as error:
98
+ if function.__name__ == "init":
99
+ self.failure = str(error)
100
+ self.close()
101
+ raise
102
+ finally:
103
+ with self.mutex:
104
+ self.calls -= 1
105
+ self.release_if_idle()
106
+ return guarded
107
+
108
+ def bind(self, namespace):
109
+ if not self.descriptor:
110
+ return
111
+ self.namespace = namespace
112
+ self.original_stop = namespace.get("stop")
113
+ for name, value in list(namespace.items()):
114
+ if (name in ("init", "after_init", "handlebar", "cfquant_normal_timer", "cfquant_ctype_trade_timer")
115
+ or name.endswith("_callback")) and callable(value):
116
+ namespace[name] = self.wrap(value)
117
+ for name in ("_normal_bridge", "_trade_bridge"):
118
+ bridge = namespace.get(name)
119
+ if bridge is not None:
120
+ self.attach_bridge(bridge)
121
+ namespace["stop"] = self.close
122
+
123
+ def attach_bridge(self, bridge):
124
+ if bridge not in self.attached_bridges:
125
+ self.attached_bridges.append(bridge)
126
+ if not self.descriptor or getattr(bridge, "managed_runtime", None) is self:
127
+ return
128
+ bridge.managed_runtime = self
129
+ bridge._dispatch = self.dispatch(bridge._dispatch)
130
+ for method in ("start", "poll", "pump"):
131
+ function = getattr(bridge, method, None)
132
+ if callable(function):
133
+ setattr(bridge, method, self.wrap(function))
134
+
135
+ def release_if_idle(self):
136
+ if self.bridges_closed and self.calls == 0:
137
+ registry = _cq_sys.__dict__.get("_cfquant_pipe_strategies", {})
138
+ key = (self.descriptor.get("runtime_key"), self.descriptor.get("role"))
139
+ lock = _cq_sys.__dict__.setdefault("_cfquant_pipe_strategies_lock", _cq_threading.RLock())
140
+ with lock:
141
+ if registry.get(key) is self:
142
+ registry.pop(key, None)
143
+
144
+ def close(self, context=None):
145
+ with self.mutex:
146
+ if not self.active:
147
+ return
148
+ self.active = False
149
+ self.approved = False
150
+ try:
151
+ if self.original_stop is not None:
152
+ self.original_stop(context)
153
+ else:
154
+ bridges = list(self.attached_bridges)
155
+ for name in ("_normal_bridge", "_trade_bridge"):
156
+ bridge = self.namespace.get(name)
157
+ if bridge is not None and bridge not in bridges:
158
+ bridges.append(bridge)
159
+ for bridge in bridges:
160
+ bridge.close()
161
+ with self.mutex:
162
+ self.bridges_closed = True
163
+ self.release_if_idle()
164
+ except Exception as error:
165
+ self.failure = str(error)
166
+ print("cfquant strategy shutdown failed: %s" % error)