cfquant 0.2.63__tar.gz → 0.2.65__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 (278) hide show
  1. {cfquant-0.2.63 → cfquant-0.2.65}/PKG-INFO +3 -1
  2. {cfquant-0.2.63 → cfquant-0.2.65}/README.md +2 -0
  3. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/__init__.py +7 -0
  4. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/_editable_install.py +9 -1
  5. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/algo.py +2 -2
  6. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/algo_broker.py +3 -1
  7. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/batch_orders.py +10 -2
  8. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/callback_dispatcher.py +5 -5
  9. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/cftrader.py +66 -1
  10. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/client.py +38 -4
  11. cfquant-0.2.65/cfquant/conditional_orders.py +149 -0
  12. cfquant-0.2.65/cfquant/diagnostic_context.py +35 -0
  13. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/flow_control.py +2 -2
  14. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/hub.py +2 -1
  15. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/management.py +3 -1
  16. cfquant-0.2.65/cfquant/monitor_notifications.py +593 -0
  17. cfquant-0.2.65/cfquant/monitor_performance.py +81 -0
  18. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/normal_bridge.py +145 -10
  19. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/pipe_bridge.py +1 -0
  20. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/pipe_hub.py +132 -9
  21. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/pipe_transport.py +14 -1
  22. cfquant-0.2.65/cfquant/planning_broker.py +90 -0
  23. cfquant-0.2.65/cfquant/position_sizing.py +161 -0
  24. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/protocol.py +10 -5
  25. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/qmt_bridge.py +10 -1
  26. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/qmt_component_login.py +19 -13
  27. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/qmt_dependencies.py +3 -1
  28. cfquant-0.2.65/cfquant/qmt_inner.py +341 -0
  29. cfquant-0.2.65/cfquant/qmt_inner_catalog.py +55 -0
  30. cfquant-0.2.65/cfquant/qmt_inner_runtime.py +186 -0
  31. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/qmt_native_worker.py +25 -13
  32. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/qmt_pipe_runtime.py +4 -2
  33. cfquant-0.2.65/cfquant/qmt_python_runtime.py +641 -0
  34. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/qmt_strategy_agent.js +62 -13
  35. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/qmt_strategy_deploy.py +89 -4
  36. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/qmt_strategy_recovery.py +9 -5
  37. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/qmt_strategy_runtime.py +14 -2
  38. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/qmt_update.py +3 -1
  39. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/quote_service.py +20 -2
  40. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/remote_management.py +3 -1
  41. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/shared_quotes.py +16 -3
  42. cfquant-0.2.65/cfquant/smart_orders.py +512 -0
  43. cfquant-0.2.65/cfquant/subscription_bridge.py +167 -0
  44. cfquant-0.2.65/cfquant/subscription_client.py +190 -0
  45. cfquant-0.2.65/cfquant/subscription_manager.py +214 -0
  46. cfquant-0.2.65/cfquant/subscription_store.py +181 -0
  47. {cfquant-0.2.63 → cfquant-0.2.65}/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 +42 -0
  48. {cfquant-0.2.63 → cfquant-0.2.65}/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 +23 -0
  49. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/26_/345/205/250/346/216/250/350/256/242/351/230/205/346/265/213/350/257/225.py +58 -0
  50. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/28_Level2/346/216/245/345/217/243/351/200/202/351/205/215/346/265/213/350/257/225.py +5 -0
  51. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_account_query_timing.py +40 -0
  52. cfquant-0.2.65/cfquant/tests/test_activity_log_browser.py +256 -0
  53. cfquant-0.2.65/cfquant/tests/test_auto_login_app.py +166 -0
  54. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_cftrader_tutorial.py +19 -32
  55. cfquant-0.2.65/cfquant/tests/test_diagnostic_context.py +30 -0
  56. cfquant-0.2.65/cfquant/tests/test_monitor_notifications.py +63 -0
  57. cfquant-0.2.65/cfquant/tests/test_monitor_performance.py +49 -0
  58. cfquant-0.2.65/cfquant/tests/test_navigation_browser.py +96 -0
  59. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_qmt_binding_settings.py +2 -1
  60. cfquant-0.2.65/cfquant/tests/test_qmt_inner.py +352 -0
  61. cfquant-0.2.65/cfquant/tests/test_qmt_inner_reference.py +116 -0
  62. cfquant-0.2.65/cfquant/tests/test_qmt_inner_web.py +99 -0
  63. cfquant-0.2.65/cfquant/tests/test_qmt_python_runtime.py +133 -0
  64. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_qmt_strategy_capabilities.py +76 -7
  65. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_qmt_strategy_deploy.py +19 -0
  66. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_quote_service.py +77 -0
  67. cfquant-0.2.65/cfquant/tests/test_smart_orders.py +386 -0
  68. cfquant-0.2.65/cfquant/tests/test_smart_orders_api.py +134 -0
  69. cfquant-0.2.65/cfquant/tests/test_smart_orders_docs.py +66 -0
  70. cfquant-0.2.65/cfquant/tests/test_subscription_persistence.py +107 -0
  71. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_trade_strategy_callback.py +4 -0
  72. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_trading_monitor.py +96 -1
  73. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_trading_monitor_browser.py +40 -0
  74. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_tx_shutdown.py +2 -1
  75. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/trading_monitor.py +203 -7
  76. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tx_trade_bridge.py +10 -2
  77. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/version.py +2 -2
  78. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/xttrader.py +20 -8
  79. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant.egg-info/PKG-INFO +3 -1
  80. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant.egg-info/SOURCES.txt +34 -0
  81. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant_web_server.py +633 -16
  82. {cfquant-0.2.63 → cfquant-0.2.65}/pyproject.toml +1 -1
  83. {cfquant-0.2.63 → cfquant-0.2.65}/qmt_scripts/CFQUANT.py +27 -0
  84. {cfquant-0.2.63 → cfquant-0.2.65}/qmt_scripts/CFQUANT_CTYPE_ALL_LOWLAT.py +27 -0
  85. {cfquant-0.2.63 → cfquant-0.2.65}/qmt_scripts/CFQUANT_LITE.py +689 -30
  86. {cfquant-0.2.63 → cfquant-0.2.65}/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 +27 -0
  87. {cfquant-0.2.63 → cfquant-0.2.65}/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 +27 -0
  88. {cfquant-0.2.63 → cfquant-0.2.65}/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 +689 -30
  89. {cfquant-0.2.63 → cfquant-0.2.65}/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 +689 -30
  90. cfquant-0.2.65/web_dashboard/activity-log.js +162 -0
  91. {cfquant-0.2.63 → cfquant-0.2.65}/web_dashboard/api-tester.js +1 -1
  92. {cfquant-0.2.63 → cfquant-0.2.65}/web_dashboard/app.js +537 -104
  93. cfquant-0.2.65/web_dashboard/cfquant-architecture.html +14837 -0
  94. {cfquant-0.2.63 → cfquant-0.2.65}/web_dashboard/docs.js +2 -2
  95. {cfquant-0.2.63 → cfquant-0.2.65}/web_dashboard/index.html +174 -117
  96. cfquant-0.2.65/web_dashboard/navigation.js +151 -0
  97. {cfquant-0.2.63 → cfquant-0.2.65}/web_dashboard/python-api.js +31 -9
  98. cfquant-0.2.65/web_dashboard/qmt-inner-api-data.js +4229 -0
  99. cfquant-0.2.65/web_dashboard/smart-order-api-data.js +94 -0
  100. {cfquant-0.2.63 → cfquant-0.2.65}/web_dashboard/styles.css +802 -24
  101. cfquant-0.2.65/web_dashboard/trading-monitor.js +718 -0
  102. cfquant-0.2.63/web_dashboard/trading-monitor.js +0 -282
  103. {cfquant-0.2.63 → cfquant-0.2.65}/LICENSE +0 -0
  104. {cfquant-0.2.63 → cfquant-0.2.65}/LTtx/__init__.py +0 -0
  105. {cfquant-0.2.63 → cfquant-0.2.65}/LTtx/tx/Config.txt +0 -0
  106. {cfquant-0.2.63 → cfquant-0.2.65}/LTtx/tx/LTtx_server.py +0 -0
  107. {cfquant-0.2.63 → cfquant-0.2.65}/LTtx/tx/__init__.py +0 -0
  108. {cfquant-0.2.63 → cfquant-0.2.65}/LTtx/tx/tx.py +0 -0
  109. {cfquant-0.2.63 → cfquant-0.2.65}/MANIFEST.in +0 -0
  110. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/account_routing.py +0 -0
  111. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/build_info.py +0 -0
  112. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/channels.py +0 -0
  113. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/cli.py +0 -0
  114. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/config.py +0 -0
  115. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/diagnostics.py +0 -0
  116. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/level2.py +0 -0
  117. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/log_management.py +0 -0
  118. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/logging_i18n.py +0 -0
  119. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/order_identity.py +0 -0
  120. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/order_meta.py +0 -0
  121. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/pipe_client.py +0 -0
  122. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/qmt_binding_defaults.py +0 -0
  123. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/qmt_binding_settings.py +0 -0
  124. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/qmt_component_agent.js +0 -0
  125. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/qmt_credentials.py +0 -0
  126. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/qmt_native_files.py +0 -0
  127. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/qmt_strategy_package.py +0 -0
  128. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/quote_history.py +0 -0
  129. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/quote_leases.py +0 -0
  130. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/remote_adapter.py +0 -0
  131. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/remote_supervisor.py +0 -0
  132. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/rpc_timing.py +0 -0
  133. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/runtime_report.py +0 -0
  134. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/stock_connect.py +0 -0
  135. {cfquant-0.2.63 → cfquant-0.2.65}/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
  136. {cfquant-0.2.63 → cfquant-0.2.65}/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
  137. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/12_/350/256/242/345/215/225/345/244/207/346/263/250/346/265/213/350/257/225.py" +0 -0
  138. {cfquant-0.2.63 → cfquant-0.2.65}/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
  139. {cfquant-0.2.63 → cfquant-0.2.65}/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
  140. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/16_/345/220/257/345/212/250/345/256/211/350/243/205/346/265/213/350/257/225.py" +0 -0
  141. {cfquant-0.2.63 → cfquant-0.2.65}/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
  142. {cfquant-0.2.63 → cfquant-0.2.65}/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
  143. {cfquant-0.2.63 → cfquant-0.2.65}/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
  144. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/1_/350/241/214/346/203/205/346/216/245/346/224/266/346/265/213/350/257/225.py" +0 -0
  145. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/20_/350/201/224/350/260/203/351/227/256/351/242/230/344/277/256/345/244/215/346/265/213/350/257/225.py" +0 -0
  146. {cfquant-0.2.63 → cfquant-0.2.65}/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
  147. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/22_/350/277/236/346/216/245/350/257/212/346/226/255/346/265/213/350/257/225.py" +0 -0
  148. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/23_/347/244/272/344/276/213/351/205/215/347/275/256/346/265/213/350/257/225.py" +0 -0
  149. {cfquant-0.2.63 → cfquant-0.2.65}/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
  150. {cfquant-0.2.63 → cfquant-0.2.65}/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
  151. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/29_Level2/350/241/214/346/203/205/346/265/213/350/257/225.py" +0 -0
  152. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/2_/346/225/260/346/215/256/350/216/267/345/217/226/346/265/213/350/257/225.py" +0 -0
  153. {cfquant-0.2.63 → cfquant-0.2.65}/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
  154. {cfquant-0.2.63 → cfquant-0.2.65}/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
  155. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/32_/345/274/202/346/255/245/344/270/213/345/215/225/346/265/213/350/257/225.py" +0 -0
  156. {cfquant-0.2.63 → cfquant-0.2.65}/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
  157. {cfquant-0.2.63 → cfquant-0.2.65}/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
  158. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/3_/346/225/260/346/215/256/344/270/213/350/275/275/346/265/213/350/257/225.py" +0 -0
  159. {cfquant-0.2.63 → cfquant-0.2.65}/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
  160. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/5_/347/234/237/345/256/236/344/270/213/345/215/225/346/265/213/350/257/225.py" +0 -0
  161. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/6_/345/233/236/350/260/203/346/265/213/350/257/225.py" +0 -0
  162. {cfquant-0.2.63 → cfquant-0.2.65}/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
  163. {cfquant-0.2.63 → cfquant-0.2.65}/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
  164. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/9_/345/217/257/346/222/244/350/256/242/345/215/225/346/265/213/350/257/225.py" +0 -0
  165. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/_helpers.py +0 -0
  166. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/readme.md +0 -0
  167. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_account_subscription_probe.py +0 -0
  168. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_advanced_parity.py +0 -0
  169. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_algo.py +0 -0
  170. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_algo_broker.py +0 -0
  171. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_api_tester_browser.py +0 -0
  172. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_api_tester_web.py +0 -0
  173. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_binding_delete_confirmation.py +0 -0
  174. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_binding_live_refresh_browser.py +0 -0
  175. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_binding_runtime_browser.py +0 -0
  176. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_callback_recovery.py +0 -0
  177. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_callback_requests.py +0 -0
  178. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_cftrader.py +0 -0
  179. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_cftrader_web.py +0 -0
  180. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_credit_compat.py +0 -0
  181. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_dataframe_encoding.py +0 -0
  182. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_json_serialization.py +0 -0
  183. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_latency_flow_control.py +0 -0
  184. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_lite_account_routing.py +0 -0
  185. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_lite_deployment_paths.py +0 -0
  186. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_lite_parity.py +0 -0
  187. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_lite_qmt_dispatch.py +0 -0
  188. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_local_trading_calendar.py +0 -0
  189. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_log_management.py +0 -0
  190. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_management.py +0 -0
  191. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_management_batch.py +0 -0
  192. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_market_data_fallback.py +0 -0
  193. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_market_timing.py +0 -0
  194. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_order_identity.py +0 -0
  195. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_order_time_callback.py +0 -0
  196. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_pipe_shutdown.py +0 -0
  197. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_project_updater.py +0 -0
  198. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_python_api_reference.py +0 -0
  199. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_qmt_auto_login_browser.py +0 -0
  200. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_qmt_auto_login_integration.py +0 -0
  201. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_qmt_binding_defaults.py +0 -0
  202. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_qmt_component_login.py +0 -0
  203. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_qmt_dependencies.py +0 -0
  204. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_qmt_fileless.py +0 -0
  205. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_qmt_native_files.py +0 -0
  206. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_qmt_native_reuse.py +0 -0
  207. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_qmt_native_worker.py +0 -0
  208. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_qmt_strategy_browser.py +0 -0
  209. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_qmt_strategy_recovery.py +0 -0
  210. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_qmt_strategy_web.py +0 -0
  211. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_qmt_update.py +0 -0
  212. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_quote_latency.py +0 -0
  213. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_quote_latency_encoding.py +0 -0
  214. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_remote_management.py +0 -0
  215. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_remote_management_browser.py +0 -0
  216. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_remote_version_check.py +0 -0
  217. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_setup_qmt_prompt.py +0 -0
  218. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_shared_quotes.py +0 -0
  219. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_shared_quotes_generated.py +0 -0
  220. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_startup_recovery.py +0 -0
  221. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_stock_connect.py +0 -0
  222. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_stock_connect_browser.py +0 -0
  223. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_strategy_recovery_api.py +0 -0
  224. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_subscription_congestion.py +0 -0
  225. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_table_conversion.py +0 -0
  226. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_trader_subscription_recovery.py +0 -0
  227. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_trading_dates.py +0 -0
  228. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_tutorial_reader.py +0 -0
  229. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tests/test_tx_auto_import.py +0 -0
  230. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/trading_calendar.py +0 -0
  231. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/tx.py +0 -0
  232. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/xtconstant.py +0 -0
  233. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/xtdata.py +0 -0
  234. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant/xttype.py +0 -0
  235. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant.egg-info/dependency_links.txt +0 -0
  236. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant.egg-info/entry_points.txt +0 -0
  237. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant.egg-info/requires.txt +0 -0
  238. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant.egg-info/top_level.txt +0 -0
  239. {cfquant-0.2.63 → cfquant-0.2.65}/cfquant_pipe_hub.py +0 -0
  240. {cfquant-0.2.63 → cfquant-0.2.65}/qmt_scripts/CFQUANT_TRADE_LOWLAT.py +0 -0
  241. {cfquant-0.2.63 → cfquant-0.2.65}/qmt_scripts/__init__.py +0 -0
  242. {cfquant-0.2.63 → cfquant-0.2.65}/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
  243. {cfquant-0.2.63 → cfquant-0.2.65}/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
  244. {cfquant-0.2.63 → cfquant-0.2.65}/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
  245. {cfquant-0.2.63 → cfquant-0.2.65}/requirements.txt +0 -0
  246. {cfquant-0.2.63 → cfquant-0.2.65}/setup.cfg +0 -0
  247. {cfquant-0.2.63 → cfquant-0.2.65}/web_dashboard/__init__.py +0 -0
  248. {cfquant-0.2.63 → cfquant-0.2.65}/web_dashboard/avatars/amber-pulse.svg +0 -0
  249. {cfquant-0.2.63 → cfquant-0.2.65}/web_dashboard/avatars/copper-grid.svg +0 -0
  250. {cfquant-0.2.63 → cfquant-0.2.65}/web_dashboard/avatars/market-blue.svg +0 -0
  251. {cfquant-0.2.63 → cfquant-0.2.65}/web_dashboard/avatars/rose-circuit.svg +0 -0
  252. {cfquant-0.2.63 → cfquant-0.2.65}/web_dashboard/avatars/signal-green.svg +0 -0
  253. {cfquant-0.2.63 → cfquant-0.2.65}/web_dashboard/avatars/slate-wave.svg +0 -0
  254. {cfquant-0.2.63 → cfquant-0.2.65}/web_dashboard/avatars/teal-orbit.svg +0 -0
  255. {cfquant-0.2.63 → cfquant-0.2.65}/web_dashboard/avatars/violet-node.svg +0 -0
  256. {cfquant-0.2.63 → cfquant-0.2.65}/web_dashboard/cftrader-api-data.js +0 -0
  257. {cfquant-0.2.63 → cfquant-0.2.65}/web_dashboard/favicon.svg +0 -0
  258. {cfquant-0.2.63 → cfquant-0.2.65}/web_dashboard/image/qmt_deploy/image-10.png +0 -0
  259. {cfquant-0.2.63 → cfquant-0.2.65}/web_dashboard/image/qmt_deploy/image-11.png +0 -0
  260. {cfquant-0.2.63 → cfquant-0.2.65}/web_dashboard/image/qmt_deploy/image-12.png +0 -0
  261. {cfquant-0.2.63 → cfquant-0.2.65}/web_dashboard/image/qmt_deploy/image-13.png +0 -0
  262. {cfquant-0.2.63 → cfquant-0.2.65}/web_dashboard/image/qmt_deploy/image-14.png +0 -0
  263. {cfquant-0.2.63 → cfquant-0.2.65}/web_dashboard/image/qmt_deploy/image-15.png +0 -0
  264. {cfquant-0.2.63 → cfquant-0.2.65}/web_dashboard/image/qmt_deploy/image-16.png +0 -0
  265. {cfquant-0.2.63 → cfquant-0.2.65}/web_dashboard/image/qmt_deploy/image-17.png +0 -0
  266. {cfquant-0.2.63 → cfquant-0.2.65}/web_dashboard/image/qmt_deploy/image-18.png +0 -0
  267. {cfquant-0.2.63 → cfquant-0.2.65}/web_dashboard/image/qmt_deploy/image-19.png +0 -0
  268. {cfquant-0.2.63 → cfquant-0.2.65}/web_dashboard/image/qmt_deploy/image-20.png +0 -0
  269. {cfquant-0.2.63 → cfquant-0.2.65}/web_dashboard/image/qmt_deploy/image-21.png +0 -0
  270. {cfquant-0.2.63 → cfquant-0.2.65}/web_dashboard/image/qmt_deploy/image-4.png +0 -0
  271. {cfquant-0.2.63 → cfquant-0.2.65}/web_dashboard/image/qmt_deploy/image-5.png +0 -0
  272. {cfquant-0.2.63 → cfquant-0.2.65}/web_dashboard/image/qmt_deploy/image-6.png +0 -0
  273. {cfquant-0.2.63 → cfquant-0.2.65}/web_dashboard/image/qmt_deploy/image-7.png +0 -0
  274. {cfquant-0.2.63 → cfquant-0.2.65}/web_dashboard/image/qmt_deploy/image-8.png +0 -0
  275. {cfquant-0.2.63 → cfquant-0.2.65}/web_dashboard/image/qmt_deploy/image-9.png +0 -0
  276. {cfquant-0.2.63 → cfquant-0.2.65}/web_dashboard/python-api-data.js +0 -0
  277. {cfquant-0.2.63 → cfquant-0.2.65}/web_dashboard/remote-management.js +0 -0
  278. {cfquant-0.2.63 → cfquant-0.2.65}/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.63
3
+ Version: 0.2.65
4
4
  Summary: QMT local bridge and xtquant-compatible Python client.
5
5
  License: MIT
6
6
  Requires-Python: >=3.8
@@ -48,6 +48,7 @@ Requires-Dist: pyzmq>=25; extra == "zmq"
48
48
  |---|---|
49
49
  | MiniQMT / `xtquant` 兼容调用 | 原有外部 Python 策略可以沿用接近 `xtdata`、`XtQuantTrader`、`StockAccount` 的写法,降低迁移成本。 |
50
50
  | 大 QMT 本地桥接 | 把大 QMT 内部的行情、查询、交易、撤单和回调能力桥接给外部 Python 程序和 Web 控制台。 |
51
+ | 大 QMT 原生接口类 | 独立的 `QmtInnerApi` 面向外部数据查询、研究和实盘交易,按系统、行情、交易、回调、扩展数据与因子细分,保留原生函数名与参数,详见 [内置接口说明](docs/大QMT内置接口.md)。 |
51
52
  | Web 控制台 | 在浏览器里完成账号绑定、QMT 入口脚本指引、在线检测、接口调试、版本更新和回滚。 |
52
53
  | 多账号 / 多 QMT 路由 | 按 `bridge_id`、`account_type`、`account_id` 路由请求,适配多资金账号、普通账户、信用账户和多 QMT 终端。 |
53
54
  | 通用、极致、高级三种模式 | 普通环境快速跑通;受限 QMT 使用自包含入口;低延迟场景可拆分普通桥和交易桥。 |
@@ -397,6 +398,7 @@ restart_cfquant.bat 重启
397
398
  | QMT 综合部署教程 | [QMT 部署教程](docs/QMT部署教程.md) |
398
399
  | cftrader 批量同步/异步下单与撤单 | [cftrader 批量交易与撤单](docs/cftrader批量交易.md),含 100 单本地基准 |
399
400
  | cftrader TWAP / VWAP / POV | [算法交易与共享全推行情](docs/algorithm_trading.md),含自动订阅、生命周期和接口示例 |
401
+ | 条件单 / 仓位计算 / 下单计划 | [条件单、仓位计算与下单计划](docs/conditional_orders.md),含止盈止损、到时平仓、回撤 / 保本保护、风险仓位及 Python / HTTP 接口 |
400
402
  | 通用模式部署 | [通用模式部署指南](docs/通用模式部署指南.md) |
401
403
  | 极致模式部署 | [极致模式部署指南](docs/极致模式部署指南.md) |
402
404
  | 高级模式部署 | [高级模式部署指南](docs/高级模式部署指南.md) |
@@ -23,6 +23,7 @@
23
23
  |---|---|
24
24
  | MiniQMT / `xtquant` 兼容调用 | 原有外部 Python 策略可以沿用接近 `xtdata`、`XtQuantTrader`、`StockAccount` 的写法,降低迁移成本。 |
25
25
  | 大 QMT 本地桥接 | 把大 QMT 内部的行情、查询、交易、撤单和回调能力桥接给外部 Python 程序和 Web 控制台。 |
26
+ | 大 QMT 原生接口类 | 独立的 `QmtInnerApi` 面向外部数据查询、研究和实盘交易,按系统、行情、交易、回调、扩展数据与因子细分,保留原生函数名与参数,详见 [内置接口说明](docs/大QMT内置接口.md)。 |
26
27
  | Web 控制台 | 在浏览器里完成账号绑定、QMT 入口脚本指引、在线检测、接口调试、版本更新和回滚。 |
27
28
  | 多账号 / 多 QMT 路由 | 按 `bridge_id`、`account_type`、`account_id` 路由请求,适配多资金账号、普通账户、信用账户和多 QMT 终端。 |
28
29
  | 通用、极致、高级三种模式 | 普通环境快速跑通;受限 QMT 使用自包含入口;低延迟场景可拆分普通桥和交易桥。 |
@@ -372,6 +373,7 @@ restart_cfquant.bat 重启
372
373
  | QMT 综合部署教程 | [QMT 部署教程](docs/QMT部署教程.md) |
373
374
  | cftrader 批量同步/异步下单与撤单 | [cftrader 批量交易与撤单](docs/cftrader批量交易.md),含 100 单本地基准 |
374
375
  | cftrader TWAP / VWAP / POV | [算法交易与共享全推行情](docs/algorithm_trading.md),含自动订阅、生命周期和接口示例 |
376
+ | 条件单 / 仓位计算 / 下单计划 | [条件单、仓位计算与下单计划](docs/conditional_orders.md),含止盈止损、到时平仓、回撤 / 保本保护、风险仓位及 Python / HTTP 接口 |
375
377
  | 通用模式部署 | [通用模式部署指南](docs/通用模式部署指南.md) |
376
378
  | 极致模式部署 | [极致模式部署指南](docs/极致模式部署指南.md) |
377
379
  | 高级模式部署 | [高级模式部署指南](docs/高级模式部署指南.md) |
@@ -11,6 +11,8 @@ cfquant: 面向 QMT 的 xtquant 兼容层。
11
11
 
12
12
  from . import xtconstant, xtdata, xttrader, xttype
13
13
  from . import cftrader
14
+ from .qmt_inner import QmtInnerApi
15
+ from .qmt_python_runtime import QmtPythonRuntime, QmtPythonRuntimeError, QmtPythonPackage, QmtPythonProgress
14
16
  from .client import CfquantError, CfquantTimeout, configure, get_client
15
17
  from .version import __version__
16
18
  from .build_info import build_identity, get_git_commit
@@ -25,6 +27,11 @@ __all__ = [
25
27
  "xtdata",
26
28
  "xttrader",
27
29
  "cftrader",
30
+ "QmtInnerApi",
31
+ "QmtPythonRuntime",
32
+ "QmtPythonRuntimeError",
33
+ "QmtPythonPackage",
34
+ "QmtPythonProgress",
28
35
  "xttype",
29
36
  "configure",
30
37
  "get_client",
@@ -12,7 +12,15 @@ from pathlib import Path
12
12
  if __package__:
13
13
  from .qmt_dependencies import FRIDA_REQUIREMENT, FRIDA_VERSION, report_startup_dependencies
14
14
  else:
15
- from qmt_dependencies import FRIDA_REQUIREMENT, FRIDA_VERSION, report_startup_dependencies
15
+ # The Windows launcher executes this file by path (``python
16
+ # cfquant\\_editable_install.py``), so Python puts the package directory on
17
+ # sys.path and leaves ``__package__`` empty. Import through the package in
18
+ # that mode as well; qmt_dependencies uses relative imports and cannot be
19
+ # loaded as a top-level module.
20
+ _PROJECT_ROOT = Path(__file__).resolve().parent.parent
21
+ if str(_PROJECT_ROOT) not in sys.path:
22
+ sys.path.insert(0, str(_PROJECT_ROOT))
23
+ from cfquant.qmt_dependencies import FRIDA_REQUIREMENT, FRIDA_VERSION, report_startup_dependencies
16
24
 
17
25
 
18
26
  PACKAGE_NAME = "cfquant"
@@ -2,7 +2,7 @@
2
2
  import copy
3
3
  import datetime as dt
4
4
  import json
5
- import logging
5
+ from .diagnostic_context import get_logger
6
6
  import math
7
7
  import os
8
8
  import sqlite3
@@ -488,7 +488,7 @@ class AlgoEngine:
488
488
  except Exception:
489
489
  # Persistence is a prerequisite for further order submission.
490
490
  task["status"] = "PAUSED"
491
- logging.getLogger(__name__).exception("algorithm persistence failed")
491
+ get_logger(__name__).exception("algorithm persistence failed")
492
492
  finally:
493
493
  with self._lock:
494
494
  self._busy.discard(algo_id)
@@ -1,4 +1,6 @@
1
1
  """Existing cfquant account/market routes adapted to the execution engine."""
2
+
3
+ from .diagnostic_context import VersionedErrorMixin
2
4
  import datetime as dt
3
5
  import math
4
6
  import threading
@@ -10,7 +12,7 @@ from .xttype import XtAsset, XtOrder, XtPosition
10
12
  from .batch_orders import prepare_batch_orders, prepare_batch_cancels
11
13
 
12
14
 
13
- class AlgoOrderRejected(RuntimeError):
15
+ class AlgoOrderRejected(VersionedErrorMixin, RuntimeError):
14
16
  dispatched = False
15
17
 
16
18
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  import math
4
4
  import os
5
+ import re
5
6
  import time
6
7
  from collections.abc import Mapping
7
8
  from numbers import Integral, Real
@@ -19,6 +20,13 @@ _BATCH_ORDER_FIELDS = {"stock_code", "order_type", "order_volume", "price_type",
19
20
  "strategy_name", "order_remark", "cfquant_order_remark"}
20
21
  _BATCH_REQUIRED_FIELDS = {"stock_code", "order_type", "order_volume", "price_type", "price"}
21
22
  _BATCH_CANCEL_FIELDS = {"order_id", "stock_code", "market", "order_remark"}
23
+ _VERSION_SUFFIX = re.compile(r"\s+\[cfquant\s+[^\]]+\]\s*$")
24
+
25
+
26
+ def _batch_error_text(error):
27
+ """Keep row-level compatibility while request errors retain diagnostics."""
28
+ text = str(error) or type(error).__name__
29
+ return _VERSION_SUFFIX.sub("", text)
22
30
 
23
31
 
24
32
  def batch_positive_id(value):
@@ -206,14 +214,14 @@ def batch_result(account, batch_id, asynchronous, results, operation="order"):
206
214
 
207
215
  def batch_unknown(rows, error):
208
216
  for row in rows:
209
- row.update(status="unknown", ok=None, error=str(error) or type(error).__name__)
217
+ row.update(status="unknown", ok=None, error=_batch_error_text(error))
210
218
 
211
219
 
212
220
  def batch_rejected(rows, error):
213
221
  """Mark a rejection known to happen before QMT dispatch."""
214
222
  details = getattr(error, "error_details", None)
215
223
  details = details if isinstance(details, dict) else {}
216
- message = str(error) or type(error).__name__
224
+ message = _batch_error_text(error)
217
225
  for row in rows:
218
226
  row.update(status="failed", ok=False, error=message, dispatched=False,
219
227
  safe_to_retry=details.get("safe_to_retry") is True,
@@ -1,7 +1,7 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  """Serial user callbacks, independent of the RPC receive thread."""
3
3
  from collections import deque
4
- import logging
4
+ from .diagnostic_context import get_logger
5
5
  import os
6
6
  import tempfile
7
7
  import threading
@@ -90,7 +90,7 @@ class CallbackDispatcher(object):
90
90
  item[1] = item[3] = None
91
91
  if self._queue and len(self._queue) >= 1000 and time.monotonic() - self._last_warning >= 5:
92
92
  self._last_warning = time.monotonic()
93
- logging.getLogger(__name__).warning("callback backlog count=%s oldest_ms=%.1f spooled_bytes=%s",
93
+ get_logger(__name__).warning("callback backlog count=%s oldest_ms=%.1f spooled_bytes=%s",
94
94
  len(self._queue), (time.monotonic() - self._queue[0][2]) * 1000, self._spool_live)
95
95
 
96
96
  def _take_message(self, item):
@@ -168,7 +168,7 @@ class CallbackDispatcher(object):
168
168
  if message is None:
169
169
  raise ValueError("invalid preserved callback message")
170
170
  except Exception as error:
171
- logging.getLogger(__name__).exception("callback backlog failed; connection must be restarted")
171
+ get_logger(__name__).exception("callback backlog failed; connection must be restarted")
172
172
  with self._condition:
173
173
  if generation != self._generation:
174
174
  continue # An old connection must not close its replacement.
@@ -185,7 +185,7 @@ class CallbackDispatcher(object):
185
185
  if self.is_current_callback():
186
186
  self._dispatch(message)
187
187
  except Exception:
188
- logging.getLogger(__name__).exception("callback dispatch failed event=%s", message.get("event"))
188
+ get_logger(__name__).exception("callback dispatch failed event=%s", message.get("event"))
189
189
  finally:
190
190
  self._last_callback_ms = (time.monotonic() - started) * 1000
191
191
  del self._context.generation
@@ -215,4 +215,4 @@ def dispatch_callbacks(callbacks, message, is_current):
215
215
  try:
216
216
  callback(payload)
217
217
  except Exception:
218
- logging.getLogger(__name__).exception("user callback failed event=%s", event)
218
+ get_logger(__name__).exception("user callback failed event=%s", event)
@@ -5,6 +5,7 @@ from .batch_orders import (
5
5
  prepare_batch_cancels, prepare_batch_orders, validate_batch_cancel_response, validate_batch_response,
6
6
  )
7
7
  from .protocol import new_id
8
+ from .position_sizing import calculate_position
8
9
  from .xttrader import XtQuantTrader, _account_payload
9
10
  import threading
10
11
 
@@ -26,7 +27,7 @@ def _algo_rpc(action, params):
26
27
  return client.request(action, params)
27
28
 
28
29
 
29
- __all__ = ["CfQuantTrader"]
30
+ __all__ = ["CfQuantTrader", "calculate_position"]
30
31
 
31
32
  class CfQuantTrader:
32
33
  """Use CfQuantTrader(trader) with an existing cfquant XtQuantTrader instance.
@@ -57,6 +58,70 @@ class CfQuantTrader:
57
58
  def order_twap(self, account, stock_code, order_type, order_volume, limit_price, **options):
58
59
  return self.order_algo(account, stock_code, order_type, order_volume, "TWAP", limit_price, **options)
59
60
 
61
+ calculate_position = staticmethod(calculate_position)
62
+
63
+ def order_condition(self, account, stock_code, conditions, execution, **options):
64
+ """Create a persistent sell protection group; manage it with query/pause/cancel_algo."""
65
+ payload = _account_payload(self._trader._resolve_account(account))
66
+ params = dict(options, account=payload, stock_code=stock_code, conditions=conditions, execution=execution)
67
+ params.setdefault("request_key", new_id("condition_request"))
68
+ task_id = _algo_rpc("cftrader.order_condition", params)
69
+ self._algo_accounts[task_id] = payload
70
+ return task_id
71
+
72
+ def order_stop_loss(self, account, stock_code, order_volume, stop_price, limit_price, **options):
73
+ return self.order_condition(account, stock_code, [{"type": "stop_loss", "price": stop_price}],
74
+ {"limit_price": limit_price}, order_volume=order_volume, **options)
75
+
76
+ def order_take_profit(self, account, stock_code, order_volume, take_profit_price, limit_price, **options):
77
+ return self.order_condition(account, stock_code, [{"type": "take_profit", "price": take_profit_price}],
78
+ {"limit_price": limit_price}, order_volume=order_volume, **options)
79
+
80
+ def order_trailing_stop(self, account, stock_code, order_volume, drawdown_percent, limit_price,
81
+ activation_price=None, **options):
82
+ rule = dict(type="trailing_stop", drawdown_percent=drawdown_percent)
83
+ if activation_price is not None:
84
+ rule["activation_price"] = activation_price
85
+ return self.order_condition(account, stock_code, [rule], {"limit_price": limit_price},
86
+ order_volume=order_volume, **options)
87
+
88
+ def order_breakeven_stop(self, account, stock_code, order_volume, reference_price, activation_percent,
89
+ limit_price, offset_amount=0, **options):
90
+ return self.order_condition(account, stock_code,
91
+ [dict(type="breakeven_stop", activation_percent=activation_percent, offset_amount=offset_amount)],
92
+ {"limit_price": limit_price}, order_volume=order_volume, reference_price=reference_price, **options)
93
+
94
+ def order_time_exit(self, account, stock_code, order_volume, at, limit_price, only_if_loss=False, **options):
95
+ return self.order_condition(account, stock_code, [dict(type="time_exit", at=at, only_if_loss=only_if_loss)],
96
+ {"limit_price": limit_price}, order_volume=order_volume, **options)
97
+
98
+ def preview_order_plan(self, account, stock_code, sizing, **options):
99
+ """Persist a preview without ordering; account balances are read by the service."""
100
+ payload = _account_payload(self._trader._resolve_account(account))
101
+ params = dict(options, account=payload, stock_code=stock_code, sizing=sizing)
102
+ params.setdefault("request_key", new_id("plan_request"))
103
+ plan = _algo_rpc("cftrader.preview_order_plan", params)
104
+ self._algo_accounts[plan["algo_id"]] = payload
105
+ return plan
106
+
107
+ def _plan_control(self, action, plan_id=None, account=None):
108
+ payload = _account_payload(self._trader._resolve_account(account or self._algo_accounts.get(plan_id)))
109
+ return _algo_rpc("cftrader." + action, dict(plan_id=plan_id, account=payload))
110
+
111
+ def submit_order_plan(self, plan_id, account=None):
112
+ """Revalidate and submit once. Repeating the same plan_id returns its current state."""
113
+ return self._plan_control("submit_order_plan", plan_id, account)
114
+
115
+ def query_order_plan(self, plan_id, account=None):
116
+ return self._plan_control("query_order_plan", plan_id, account)
117
+
118
+ def list_order_plans(self, account=None):
119
+ return self._plan_control("list_order_plans", account=account)
120
+
121
+ def cancel_order_plan(self, plan_id, account=None):
122
+ """Cancel the entry remainder; retain protection for confirmed fills."""
123
+ return self._plan_control("cancel_order_plan", plan_id, account)
124
+
60
125
  def order_vwap(self, account, stock_code, order_type, order_volume, limit_price, **options):
61
126
  return self.order_algo(account, stock_code, order_type, order_volume, "VWAP", limit_price, **options)
62
127
 
@@ -10,14 +10,16 @@ from .config import get_config
10
10
  from .callback_dispatcher import CallbackDispatcher, dispatch_callbacks
11
11
  from .protocol import decode_value, dumps_message, loads_message, new_id, pack_request
12
12
  from .rpc_timing import RpcTiming
13
+ from .diagnostic_context import CFQUANT_VERSION, VersionedErrorMixin
13
14
 
14
15
 
15
- class CfquantError(RuntimeError):
16
+ class CfquantError(VersionedErrorMixin, RuntimeError):
16
17
  """RPC failure retaining the server's structured rejection details."""
17
18
 
18
19
  def __init__(self, message="", details=None):
19
20
  super(CfquantError, self).__init__(message)
20
21
  self.error_details = dict(details) if isinstance(details, dict) else {}
22
+ self.error_details.setdefault("cfquant_version", CFQUANT_VERSION)
21
23
  self.code = self.error_details.get("code", "")
22
24
  self.stage = self.error_details.get("stage", "")
23
25
  self.dispatched = self.error_details.get("dispatched")
@@ -25,7 +27,7 @@ class CfquantError(RuntimeError):
25
27
  self.retry_when = self.error_details.get("retry_when", "")
26
28
 
27
29
 
28
- class CfquantTimeout(TimeoutError):
30
+ class CfquantTimeout(VersionedErrorMixin, TimeoutError):
29
31
  pass
30
32
 
31
33
 
@@ -157,7 +159,7 @@ class LTtxRpcClient(object):
157
159
  if not getattr(self._tx, "run_on", True):
158
160
  raise CfquantError("cfquant client closed during connection startup")
159
161
  except BaseException:
160
- self.close()
162
+ LTtxRpcClient.close(self)
161
163
  raise
162
164
  self._started = True
163
165
  self._closed = False
@@ -286,7 +288,7 @@ class LTtxRpcClient(object):
286
288
  break
287
289
  with self._lock:
288
290
  if self._tx is expected_tx:
289
- self.close()
291
+ LTtxRpcClient.close(self)
290
292
 
291
293
  def _dispatch_event(self, msg):
292
294
  dispatch_callbacks(self._callbacks, msg, self._event_dispatcher.is_current_callback)
@@ -355,6 +357,38 @@ class WebLttxRpcClient(LTtxRpcClient):
355
357
  timeout=timeout,
356
358
  client_id=client_id,
357
359
  )
360
+ self._subscription_session = None
361
+ self._subscription_protocol = None
362
+ self._subscription_init_lock = threading.Lock()
363
+
364
+ def _subscription_raw_request(self, action, params, timeout=None):
365
+ return super(WebLttxRpcClient, self).request(action, params, timeout=timeout)
366
+
367
+ def request(self, action, params=None, timeout=None, request_channel=None, response_meta=None):
368
+ from .subscription_client import managed_request
369
+ handled, result = managed_request(self, self._subscription_raw_request, action, params, timeout)
370
+ if handled:
371
+ return result
372
+ return super(WebLttxRpcClient, self).request(action, params, timeout, request_channel, response_meta)
373
+
374
+ def _dispatch_event(self, msg):
375
+ if msg.get('event') == 'cfquant.managed_subscription':
376
+ data = msg.get('data')
377
+ if isinstance(data, dict):
378
+ if self._subscription_session is None or data.get('key') not in self._subscription_session.specs:
379
+ return
380
+ msg = dict(msg, event=data.get('event'), data=data.get('payload'))
381
+ sid = (msg.get('meta') or {}).get('managed_subscription_id')
382
+ if sid is not None and (self._subscription_session is None or str(sid) not in self._subscription_session.specs):
383
+ return
384
+ super(WebLttxRpcClient, self)._dispatch_event(msg)
385
+
386
+ def close(self):
387
+ session = getattr(self, '_subscription_session', None)
388
+ if session is not None:
389
+ self._subscription_session = None
390
+ session.close()
391
+ super(WebLttxRpcClient, self).close()
358
392
 
359
393
 
360
394
  def _registry_has_web_route(registry):
@@ -0,0 +1,149 @@
1
+ """Validated, serializable conditions evaluated against observed quote prices."""
2
+ import copy
3
+ import math
4
+
5
+ from .algo import positive
6
+ from .algo_broker import execution_time
7
+
8
+
9
+ def validate_conditions(conditions, reference_price=None):
10
+ if not isinstance(conditions, list) or not 1 <= len(conditions) <= 32:
11
+ raise ValueError("conditions must contain 1 to 32 rules (any matching rule triggers the group)")
12
+ reference = positive(reference_price, "reference_price") if reference_price is not None else None
13
+ result = []
14
+ fields = {
15
+ "stop_loss": {"price", "percent"}, "take_profit": {"price", "percent"},
16
+ "time_exit": {"at", "only_if_loss"}, "holding_exit": {"seconds", "trading_days", "opened_at", "exit_time"},
17
+ "trailing_stop": {"drawdown_percent", "drawdown_amount", "activation_price", "activation_percent"},
18
+ "breakeven_stop": {"activation_percent", "activation_price", "offset_percent", "offset_amount", "protect_price"},
19
+ "step_stop": {"steps"},
20
+ }
21
+ for index, original in enumerate(conditions):
22
+ if not isinstance(original, dict):
23
+ raise ValueError("each condition must be an object")
24
+ rule = copy.deepcopy(original)
25
+ kind = rule.get("type")
26
+ if kind not in fields or set(rule) - (fields.get(kind, set()) | {"type", "name"}):
27
+ raise ValueError("unknown condition type or fields")
28
+ rule.setdefault("name", "%s_%d" % (kind, index + 1))
29
+ if not isinstance(rule["name"], str) or not rule["name"]:
30
+ raise ValueError("condition name must be nonempty text")
31
+ if kind in ("stop_loss", "take_profit"):
32
+ if ("price" in rule) == ("percent" in rule):
33
+ raise ValueError(kind + " requires exactly one of price or percent")
34
+ if "percent" in rule:
35
+ pct = positive(rule["percent"], "percent")
36
+ if reference is None or (kind == "stop_loss" and pct >= 1):
37
+ raise ValueError("percentage conditions require reference_price; stop percent must be below 1")
38
+ rule["price"] = reference * (1 + pct if kind == "take_profit" else 1 - pct)
39
+ rule["price"] = positive(rule["price"], "condition price")
40
+ elif kind == "time_exit":
41
+ rule["at"] = execution_time(rule.get("at"))
42
+ if rule["at"] is None:
43
+ raise ValueError("time_exit requires at")
44
+ if not isinstance(rule.get("only_if_loss", False), bool):
45
+ raise ValueError("only_if_loss must be boolean")
46
+ if rule.get("only_if_loss") and reference is None:
47
+ raise ValueError("only_if_loss requires reference_price")
48
+ elif kind == "holding_exit":
49
+ if ("seconds" in rule) == ("trading_days" in rule):
50
+ raise ValueError("holding_exit requires exactly one of seconds or trading_days")
51
+ rule["opened_at"] = execution_time(rule.get("opened_at"))
52
+ if rule["opened_at"] is None:
53
+ raise ValueError("holding_exit requires opened_at")
54
+ if "seconds" in rule:
55
+ rule["at"] = rule["opened_at"] + positive(rule["seconds"], "seconds")
56
+ else:
57
+ rule["trading_days"] = positive(rule["trading_days"], "trading_days", integer=True)
58
+ if rule["trading_days"] > 2500:
59
+ raise ValueError("trading_days must not exceed 2500")
60
+ import datetime
61
+ datetime.time.fromisoformat(rule.setdefault("exit_time", "14:50"))
62
+ elif kind in ("trailing_stop", "breakeven_stop"):
63
+ if "activation_price" in rule and "activation_percent" in rule:
64
+ raise ValueError("use one activation threshold")
65
+ if "activation_percent" in rule:
66
+ if reference is None:
67
+ raise ValueError("activation_percent requires reference_price")
68
+ rule["activation_price"] = reference * (1 + positive(rule["activation_percent"], "activation_percent", allow_zero=True))
69
+ if "activation_price" in rule:
70
+ rule["activation_price"] = positive(rule["activation_price"], "activation_price")
71
+ if kind == "trailing_stop":
72
+ if ("drawdown_percent" in rule) == ("drawdown_amount" in rule):
73
+ raise ValueError("trailing_stop requires exactly one drawdown threshold")
74
+ key = "drawdown_percent" if "drawdown_percent" in rule else "drawdown_amount"
75
+ rule[key] = positive(rule[key], key)
76
+ if key == "drawdown_percent" and rule[key] >= 1:
77
+ raise ValueError("drawdown_percent must be below 1")
78
+ else:
79
+ if "activation_price" not in rule:
80
+ raise ValueError("breakeven_stop requires activation_price or activation_percent")
81
+ offsets = set(rule) & {"offset_amount", "offset_percent", "protect_price"}
82
+ if len(offsets) > 1:
83
+ raise ValueError("use one breakeven protection offset")
84
+ if "protect_price" not in rule:
85
+ if reference is None:
86
+ raise ValueError("breakeven_stop requires reference_price or protect_price")
87
+ rule["protect_price"] = reference + (reference * positive(rule["offset_percent"], "offset_percent", allow_zero=True)
88
+ if "offset_percent" in rule else positive(rule.get("offset_amount", 0), "offset_amount", allow_zero=True))
89
+ rule["protect_price"] = positive(rule["protect_price"], "protect_price")
90
+ if rule["protect_price"] >= rule["activation_price"]:
91
+ raise ValueError("protect_price must be below activation_price")
92
+ elif kind == "step_stop":
93
+ if reference is None or not isinstance(rule.get("steps"), list) or not rule["steps"]:
94
+ raise ValueError("step_stop requires reference_price and steps")
95
+ previous_gain = previous_stop = -1
96
+ for step in rule["steps"]:
97
+ if not isinstance(step, dict) or set(step) != {"gain_percent", "protect_percent"}:
98
+ raise ValueError("each step requires gain_percent and protect_percent")
99
+ gain = positive(step["gain_percent"], "gain_percent", allow_zero=True)
100
+ protect = positive(step["protect_percent"], "protect_percent", allow_zero=True)
101
+ if gain <= previous_gain or protect < previous_stop or protect >= gain:
102
+ raise ValueError("steps must have increasing gains and nondecreasing protection below each gain")
103
+ previous_gain, previous_stop = gain, protect
104
+ result.append(rule)
105
+ return result
106
+
107
+
108
+ def evaluate_conditions(rules, states, price, now, reference, trigger_on_create=True):
109
+ """Mutate persisted states; return the first trigger. Observed highs never fall."""
110
+ triggered = None
111
+ def above(value, threshold):
112
+ return value >= threshold or math.isclose(value, threshold, rel_tol=1e-12, abs_tol=1e-10)
113
+ def below(value, threshold):
114
+ return value <= threshold or math.isclose(value, threshold, rel_tol=1e-12, abs_tol=1e-10)
115
+ for index, rule in enumerate(rules):
116
+ state = states.setdefault(str(index), {})
117
+ kind = rule["type"]
118
+ hit = False
119
+ if kind in ("stop_loss", "take_profit"):
120
+ hit = below(price, rule["price"]) if kind == "stop_loss" else above(price, rule["price"])
121
+ elif kind in ("time_exit", "holding_exit"):
122
+ if now >= rule["at"]:
123
+ # Evaluate the loss gate once, at the first executable fresh snapshot.
124
+ if "time_decision" not in state:
125
+ state["time_decision"] = not rule.get("only_if_loss") or price < reference
126
+ hit = state["time_decision"]
127
+ elif kind in ("trailing_stop", "breakeven_stop"):
128
+ if above(price, rule.get("activation_price", 0)):
129
+ state.setdefault("activated_at", now)
130
+ if "activated_at" in state:
131
+ if kind == "trailing_stop":
132
+ state["high"] = max(state.get("high", price), price)
133
+ candidate = (state["high"] * (1 - rule["drawdown_percent"]) if "drawdown_percent" in rule
134
+ else state["high"] - rule["drawdown_amount"])
135
+ else:
136
+ candidate = rule["protect_price"]
137
+ state["stop"] = max(state.get("stop", candidate), candidate)
138
+ hit = below(price, state["stop"])
139
+ else:
140
+ for step in rule["steps"]:
141
+ if above(price, reference * (1 + step["gain_percent"])):
142
+ state["stop"] = max(state.get("stop", 0), reference * (1 + step["protect_percent"]))
143
+ hit = "stop" in state and below(price, state["stop"])
144
+ if not hit:
145
+ state["armed"] = True
146
+ if hit and (trigger_on_create or state.get("armed") or kind in ("time_exit", "holding_exit")) and triggered is None:
147
+ triggered = dict(index=index, name=rule["name"], type=kind, price=price, at=now,
148
+ threshold=state.get("stop", rule.get("price", rule.get("at"))))
149
+ return triggered
@@ -0,0 +1,35 @@
1
+ """Version context for cfquant diagnostics without changing application logging."""
2
+ import logging
3
+
4
+ from .version import __version__ as CFQUANT_VERSION
5
+
6
+
7
+ def versioned_message(message):
8
+ text = str(message)
9
+ tag = "[cfquant %s]" % CFQUANT_VERSION
10
+ return text if tag in text else "%s %s" % (text, tag)
11
+
12
+
13
+ class VersionedErrorMixin:
14
+ cfquant_version = CFQUANT_VERSION
15
+
16
+ def __str__(self):
17
+ # Keep .args and structured error codes intact for existing callers.
18
+ return versioned_message(super(VersionedErrorMixin, self).__str__())
19
+
20
+
21
+ class _VersionFilter(logging.Filter):
22
+ def filter(self, record):
23
+ record.msg = versioned_message(record.getMessage())
24
+ record.args = ()
25
+ record.cfquant_version = CFQUANT_VERSION
26
+ return True
27
+
28
+
29
+ _VERSION_FILTER = _VersionFilter()
30
+
31
+
32
+ def get_logger(name):
33
+ logger = logging.getLogger(name)
34
+ logger.addFilter(_VERSION_FILTER)
35
+ return logger
@@ -1,6 +1,6 @@
1
1
  """Ordered, bounded background work. No QMT API calls run in these workers."""
2
2
  from collections import deque
3
- import logging
3
+ from .diagnostic_context import get_logger
4
4
  import threading
5
5
  import time
6
6
 
@@ -163,7 +163,7 @@ class KeyedExecutor(object):
163
163
  try:
164
164
  item[2](error)
165
165
  except Exception:
166
- logging.getLogger(__name__).exception("Web route could not deliver request failure")
166
+ get_logger(__name__).exception("Web route could not deliver request failure")
167
167
 
168
168
  def status(self):
169
169
  with self._condition:
@@ -7,6 +7,7 @@ import threading
7
7
  import time
8
8
 
9
9
  from .protocol import loads_message
10
+ from .diagnostic_context import versioned_message
10
11
 
11
12
 
12
13
  def default_status_file(filename):
@@ -263,7 +264,7 @@ class CfquantHub(object):
263
264
 
264
265
  def _log(self, msg):
265
266
  if self.show:
266
- print("%s %s" % (self._timestamp_ms(), msg), flush=True)
267
+ print("%s %s" % (self._timestamp_ms(), versioned_message(msg)), flush=True)
267
268
 
268
269
  def _timestamp_ms(self):
269
270
  now = time.time()
@@ -5,6 +5,8 @@ The service has its own source directory. Updating it never installs packages
5
5
  into, or reloads modules in, the calling application's Python environment.
6
6
  """
7
7
 
8
+ from .diagnostic_context import VersionedErrorMixin
9
+
8
10
  import importlib.util
9
11
  import json
10
12
  import os
@@ -23,7 +25,7 @@ import urllib.request
23
25
  _UNSET = object()
24
26
 
25
27
 
26
- class ManagementError(RuntimeError):
28
+ class ManagementError(VersionedErrorMixin, RuntimeError):
27
29
  """Management failure, including the server's status and response details."""
28
30
 
29
31
  def __init__(self, message, code="management_error", status=None, details=None):