cfquant 0.2.49__tar.gz → 0.2.50__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 (178) hide show
  1. {cfquant-0.2.49 → cfquant-0.2.50}/PKG-INFO +1 -1
  2. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/normal_bridge.py +1 -0
  3. cfquant-0.2.50/cfquant/order_identity.py +173 -0
  4. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/order_meta.py +2 -1
  5. {cfquant-0.2.49 → cfquant-0.2.50}/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
  6. cfquant-0.2.50/cfquant/tests/test_advanced_parity.py +153 -0
  7. cfquant-0.2.50/cfquant/tests/test_lite_parity.py +140 -0
  8. cfquant-0.2.50/cfquant/tests/test_trade_strategy_callback.py +205 -0
  9. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/tx_trade_bridge.py +39 -11
  10. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/version.py +2 -2
  11. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant.egg-info/PKG-INFO +1 -1
  12. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant.egg-info/SOURCES.txt +3 -0
  13. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant_web_server.py +14 -5
  14. {cfquant-0.2.49 → cfquant-0.2.50}/pyproject.toml +1 -1
  15. {cfquant-0.2.49 → cfquant-0.2.50}/qmt_scripts/CFQUANT_LITE.py +916 -18
  16. {cfquant-0.2.49 → cfquant-0.2.50}/qmt_scripts/CFQUANT_TRADE_LOWLAT.py +9 -2
  17. {cfquant-0.2.49 → cfquant-0.2.50}/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 +916 -18
  18. {cfquant-0.2.49 → cfquant-0.2.50}/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 +916 -18
  19. {cfquant-0.2.49 → cfquant-0.2.50}/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
  20. {cfquant-0.2.49 → cfquant-0.2.50}/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
  21. {cfquant-0.2.49 → cfquant-0.2.50}/web_dashboard/app.js +1 -1
  22. {cfquant-0.2.49 → cfquant-0.2.50}/web_dashboard/index.html +2 -2
  23. cfquant-0.2.49/cfquant/order_identity.py +0 -55
  24. {cfquant-0.2.49 → cfquant-0.2.50}/LICENSE +0 -0
  25. {cfquant-0.2.49 → cfquant-0.2.50}/LTtx/__init__.py +0 -0
  26. {cfquant-0.2.49 → cfquant-0.2.50}/LTtx/tx/Config.txt +0 -0
  27. {cfquant-0.2.49 → cfquant-0.2.50}/LTtx/tx/LTtx_server.py +0 -0
  28. {cfquant-0.2.49 → cfquant-0.2.50}/LTtx/tx/__init__.py +0 -0
  29. {cfquant-0.2.49 → cfquant-0.2.50}/LTtx/tx/tx.py +0 -0
  30. {cfquant-0.2.49 → cfquant-0.2.50}/MANIFEST.in +0 -0
  31. {cfquant-0.2.49 → cfquant-0.2.50}/README.md +0 -0
  32. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/__init__.py +0 -0
  33. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/_editable_install.py +0 -0
  34. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/account_routing.py +0 -0
  35. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/batch_orders.py +0 -0
  36. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/build_info.py +0 -0
  37. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/callback_dispatcher.py +0 -0
  38. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/cftrader.py +0 -0
  39. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/channels.py +0 -0
  40. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/cli.py +0 -0
  41. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/client.py +0 -0
  42. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/config.py +0 -0
  43. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/hub.py +0 -0
  44. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/level2.py +0 -0
  45. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/log_management.py +0 -0
  46. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/logging_i18n.py +0 -0
  47. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/management.py +0 -0
  48. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/pipe_bridge.py +0 -0
  49. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/pipe_client.py +0 -0
  50. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/pipe_hub.py +0 -0
  51. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/pipe_transport.py +0 -0
  52. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/protocol.py +0 -0
  53. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/qmt_bridge.py +0 -0
  54. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/qmt_strategy_deploy.py +0 -0
  55. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/qmt_strategy_package.py +0 -0
  56. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/qmt_strategy_runtime.py +0 -0
  57. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/runtime_report.py +0 -0
  58. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/stock_connect.py +0 -0
  59. {cfquant-0.2.49 → cfquant-0.2.50}/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
  60. {cfquant-0.2.49 → cfquant-0.2.50}/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
  61. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/tests/12_/350/256/242/345/215/225/345/244/207/346/263/250/346/265/213/350/257/225.py" +0 -0
  62. {cfquant-0.2.49 → cfquant-0.2.50}/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
  63. {cfquant-0.2.49 → cfquant-0.2.50}/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
  64. {cfquant-0.2.49 → cfquant-0.2.50}/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
  65. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/tests/16_/345/220/257/345/212/250/345/256/211/350/243/205/346/265/213/350/257/225.py" +0 -0
  66. {cfquant-0.2.49 → cfquant-0.2.50}/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
  67. {cfquant-0.2.49 → cfquant-0.2.50}/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
  68. {cfquant-0.2.49 → cfquant-0.2.50}/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
  69. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/tests/1_/350/241/214/346/203/205/346/216/245/346/224/266/346/265/213/350/257/225.py" +0 -0
  70. {cfquant-0.2.49 → cfquant-0.2.50}/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
  71. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/tests/22_/350/277/236/346/216/245/350/257/212/346/226/255/346/265/213/350/257/225.py" +0 -0
  72. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/tests/23_/347/244/272/344/276/213/351/205/215/347/275/256/346/265/213/350/257/225.py" +0 -0
  73. {cfquant-0.2.49 → cfquant-0.2.50}/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
  74. {cfquant-0.2.49 → cfquant-0.2.50}/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
  75. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/tests/26_/345/205/250/346/216/250/350/256/242/351/230/205/346/265/213/350/257/225.py" +0 -0
  76. {cfquant-0.2.49 → cfquant-0.2.50}/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
  77. {cfquant-0.2.49 → cfquant-0.2.50}/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
  78. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/tests/29_Level2/350/241/214/346/203/205/346/265/213/350/257/225.py" +0 -0
  79. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/tests/2_/346/225/260/346/215/256/350/216/267/345/217/226/346/265/213/350/257/225.py" +0 -0
  80. {cfquant-0.2.49 → cfquant-0.2.50}/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
  81. {cfquant-0.2.49 → cfquant-0.2.50}/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
  82. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/tests/32_/345/274/202/346/255/245/344/270/213/345/215/225/346/265/213/350/257/225.py" +0 -0
  83. {cfquant-0.2.49 → cfquant-0.2.50}/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
  84. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/tests/3_/346/225/260/346/215/256/344/270/213/350/275/275/346/265/213/350/257/225.py" +0 -0
  85. {cfquant-0.2.49 → cfquant-0.2.50}/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
  86. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/tests/5_/347/234/237/345/256/236/344/270/213/345/215/225/346/265/213/350/257/225.py" +0 -0
  87. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/tests/6_/345/233/236/350/260/203/346/265/213/350/257/225.py" +0 -0
  88. {cfquant-0.2.49 → cfquant-0.2.50}/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
  89. {cfquant-0.2.49 → cfquant-0.2.50}/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
  90. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/tests/9_/345/217/257/346/222/244/350/256/242/345/215/225/346/265/213/350/257/225.py" +0 -0
  91. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/tests/_helpers.py +0 -0
  92. {cfquant-0.2.49 → cfquant-0.2.50}/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
  93. {cfquant-0.2.49 → cfquant-0.2.50}/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
  94. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/tests/readme.md +0 -0
  95. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/tests/test_api_tester_browser.py +0 -0
  96. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/tests/test_api_tester_web.py +0 -0
  97. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/tests/test_binding_delete_confirmation.py +0 -0
  98. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/tests/test_callback_requests.py +0 -0
  99. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/tests/test_cftrader.py +0 -0
  100. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/tests/test_cftrader_tutorial.py +0 -0
  101. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/tests/test_cftrader_web.py +0 -0
  102. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/tests/test_credit_compat.py +0 -0
  103. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/tests/test_json_serialization.py +0 -0
  104. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/tests/test_lite_account_routing.py +0 -0
  105. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/tests/test_lite_deployment_paths.py +0 -0
  106. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/tests/test_local_trading_calendar.py +0 -0
  107. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/tests/test_log_management.py +0 -0
  108. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/tests/test_management.py +0 -0
  109. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/tests/test_management_batch.py +0 -0
  110. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/tests/test_market_data_fallback.py +0 -0
  111. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/tests/test_order_identity.py +0 -0
  112. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/tests/test_order_time_callback.py +0 -0
  113. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/tests/test_project_updater.py +0 -0
  114. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/tests/test_python_api_reference.py +0 -0
  115. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/tests/test_qmt_strategy_browser.py +0 -0
  116. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/tests/test_qmt_strategy_deploy.py +0 -0
  117. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/tests/test_qmt_strategy_web.py +0 -0
  118. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/tests/test_setup_qmt_prompt.py +0 -0
  119. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/tests/test_startup_recovery.py +0 -0
  120. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/tests/test_stock_connect.py +0 -0
  121. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/tests/test_stock_connect_browser.py +0 -0
  122. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/tests/test_trading_dates.py +0 -0
  123. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/tests/test_tutorial_reader.py +0 -0
  124. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/tests/test_tx_auto_import.py +0 -0
  125. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/trading_calendar.py +0 -0
  126. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/tx.py +0 -0
  127. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/xtconstant.py +0 -0
  128. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/xtdata.py +0 -0
  129. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/xttrader.py +0 -0
  130. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant/xttype.py +0 -0
  131. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant.egg-info/dependency_links.txt +0 -0
  132. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant.egg-info/entry_points.txt +0 -0
  133. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant.egg-info/requires.txt +0 -0
  134. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant.egg-info/top_level.txt +0 -0
  135. {cfquant-0.2.49 → cfquant-0.2.50}/cfquant_pipe_hub.py +0 -0
  136. {cfquant-0.2.49 → cfquant-0.2.50}/qmt_scripts/CFQUANT.py +0 -0
  137. {cfquant-0.2.49 → cfquant-0.2.50}/qmt_scripts/CFQUANT_CTYPE_ALL_LOWLAT.py +0 -0
  138. {cfquant-0.2.49 → cfquant-0.2.50}/qmt_scripts/__init__.py +0 -0
  139. {cfquant-0.2.49 → cfquant-0.2.50}/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
  140. {cfquant-0.2.49 → cfquant-0.2.50}/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
  141. {cfquant-0.2.49 → cfquant-0.2.50}/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
  142. {cfquant-0.2.49 → cfquant-0.2.50}/requirements.txt +0 -0
  143. {cfquant-0.2.49 → cfquant-0.2.50}/setup.cfg +0 -0
  144. {cfquant-0.2.49 → cfquant-0.2.50}/web_dashboard/__init__.py +0 -0
  145. {cfquant-0.2.49 → cfquant-0.2.50}/web_dashboard/api-tester.js +0 -0
  146. {cfquant-0.2.49 → cfquant-0.2.50}/web_dashboard/avatars/amber-pulse.svg +0 -0
  147. {cfquant-0.2.49 → cfquant-0.2.50}/web_dashboard/avatars/copper-grid.svg +0 -0
  148. {cfquant-0.2.49 → cfquant-0.2.50}/web_dashboard/avatars/market-blue.svg +0 -0
  149. {cfquant-0.2.49 → cfquant-0.2.50}/web_dashboard/avatars/rose-circuit.svg +0 -0
  150. {cfquant-0.2.49 → cfquant-0.2.50}/web_dashboard/avatars/signal-green.svg +0 -0
  151. {cfquant-0.2.49 → cfquant-0.2.50}/web_dashboard/avatars/slate-wave.svg +0 -0
  152. {cfquant-0.2.49 → cfquant-0.2.50}/web_dashboard/avatars/teal-orbit.svg +0 -0
  153. {cfquant-0.2.49 → cfquant-0.2.50}/web_dashboard/avatars/violet-node.svg +0 -0
  154. {cfquant-0.2.49 → cfquant-0.2.50}/web_dashboard/cftrader-api-data.js +0 -0
  155. {cfquant-0.2.49 → cfquant-0.2.50}/web_dashboard/docs.js +0 -0
  156. {cfquant-0.2.49 → cfquant-0.2.50}/web_dashboard/favicon.svg +0 -0
  157. {cfquant-0.2.49 → cfquant-0.2.50}/web_dashboard/image/qmt_deploy/image-10.png +0 -0
  158. {cfquant-0.2.49 → cfquant-0.2.50}/web_dashboard/image/qmt_deploy/image-11.png +0 -0
  159. {cfquant-0.2.49 → cfquant-0.2.50}/web_dashboard/image/qmt_deploy/image-12.png +0 -0
  160. {cfquant-0.2.49 → cfquant-0.2.50}/web_dashboard/image/qmt_deploy/image-13.png +0 -0
  161. {cfquant-0.2.49 → cfquant-0.2.50}/web_dashboard/image/qmt_deploy/image-14.png +0 -0
  162. {cfquant-0.2.49 → cfquant-0.2.50}/web_dashboard/image/qmt_deploy/image-15.png +0 -0
  163. {cfquant-0.2.49 → cfquant-0.2.50}/web_dashboard/image/qmt_deploy/image-16.png +0 -0
  164. {cfquant-0.2.49 → cfquant-0.2.50}/web_dashboard/image/qmt_deploy/image-17.png +0 -0
  165. {cfquant-0.2.49 → cfquant-0.2.50}/web_dashboard/image/qmt_deploy/image-18.png +0 -0
  166. {cfquant-0.2.49 → cfquant-0.2.50}/web_dashboard/image/qmt_deploy/image-19.png +0 -0
  167. {cfquant-0.2.49 → cfquant-0.2.50}/web_dashboard/image/qmt_deploy/image-20.png +0 -0
  168. {cfquant-0.2.49 → cfquant-0.2.50}/web_dashboard/image/qmt_deploy/image-21.png +0 -0
  169. {cfquant-0.2.49 → cfquant-0.2.50}/web_dashboard/image/qmt_deploy/image-4.png +0 -0
  170. {cfquant-0.2.49 → cfquant-0.2.50}/web_dashboard/image/qmt_deploy/image-5.png +0 -0
  171. {cfquant-0.2.49 → cfquant-0.2.50}/web_dashboard/image/qmt_deploy/image-6.png +0 -0
  172. {cfquant-0.2.49 → cfquant-0.2.50}/web_dashboard/image/qmt_deploy/image-7.png +0 -0
  173. {cfquant-0.2.49 → cfquant-0.2.50}/web_dashboard/image/qmt_deploy/image-8.png +0 -0
  174. {cfquant-0.2.49 → cfquant-0.2.50}/web_dashboard/image/qmt_deploy/image-9.png +0 -0
  175. {cfquant-0.2.49 → cfquant-0.2.50}/web_dashboard/python-api-data.js +0 -0
  176. {cfquant-0.2.49 → cfquant-0.2.50}/web_dashboard/python-api.js +0 -0
  177. {cfquant-0.2.49 → cfquant-0.2.50}/web_dashboard/styles.css +0 -0
  178. {cfquant-0.2.49 → cfquant-0.2.50}/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.50
4
4
  Summary: QMT local bridge and xtquant-compatible Python client.
5
5
  License: MIT
6
6
  Requires-Python: >=3.8
@@ -893,6 +893,7 @@ class NormalQmtBridge(TxTradeBridge):
893
893
  self._resolve_pending_sync_order_callback(data)
894
894
  relay_sync_order_callback(self.context, data)
895
895
  self._handle_async_order_callback(data)
896
+ self._remember_order_callback_fields(data)
896
897
  payload = {
897
898
  "type": "event",
898
899
  "event": event_name,
@@ -0,0 +1,173 @@
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
+
16
+
17
+ def original_order_remark(value):
18
+ text = str(value or "")
19
+ match = _ORDER_REMARK_SUFFIX.search(text)
20
+ return text[:match.start()] if match else text
21
+
22
+
23
+ def is_unique_order_remark(value):
24
+ return bool(_ORDER_REMARK_SUFFIX.search(str(value or "")))
25
+
26
+
27
+ def prepare_order_remark(params, fallback=""):
28
+ original = next((params[name] for name in ("order_remark", "remark", "strategy_name")
29
+ if params.get(name) not in (None, "")), "")
30
+ original = str(original or "")
31
+ internal = params.get("cfquant_order_remark")
32
+ if not (is_unique_order_remark(internal) and
33
+ original_order_remark(internal) in (original, original or str(fallback or ""))):
34
+ original = original or str(fallback or "")
35
+ internal = original + "__cfq_" + uuid.uuid4().hex
36
+ params["cfquant_order_remark"] = internal
37
+ return internal
38
+
39
+
40
+ def order_remark_key(data):
41
+ getter = data.get if isinstance(data, dict) else lambda name: getattr(data, name, None)
42
+ values = [getter(name) for name in (
43
+ "cfquant_order_remark", "user_order_id", "client_order_id",
44
+ "order_remark", "m_strRemark", "m_strOrderRemark", "remark",
45
+ )]
46
+ for value in values:
47
+ if is_unique_order_remark(value):
48
+ return str(value)
49
+ return next((str(value) for value in values if value not in (None, "")), "")
50
+
51
+
52
+ def restore_order_remark(data):
53
+ if isinstance(data, dict):
54
+ internal = order_remark_key(data)
55
+ if is_unique_order_remark(internal):
56
+ data["cfquant_order_remark"] = internal
57
+ data["order_remark"] = original_order_remark(internal)
58
+ return data
59
+
60
+
61
+ def order_remark_matches_reference(expected, actual):
62
+ """Compare remarks only AFTER an order reference has matched.
63
+
64
+ Some DEAL reports echo the public remark without our UUID. A different
65
+ UUID still identifies a different submission, even with the same text.
66
+ """
67
+ return (not actual or expected == actual or
68
+ (is_unique_order_remark(expected) and
69
+ not is_unique_order_remark(actual) and
70
+ original_order_remark(expected) == actual))
71
+
72
+
73
+ class OrderCallbackCache(object):
74
+ """Bounded, daily order metadata indexed by separate local/broker IDs."""
75
+
76
+ def __init__(self, max_orders=4096):
77
+ self.max_orders = max_orders
78
+ self.lock = threading.RLock()
79
+ self.day = ""
80
+ self.records = OrderedDict()
81
+ self.by_ref = {}
82
+
83
+ def _identity(self, data, account_type):
84
+ day = time.strftime("%Y%m%d")
85
+ if day != self.day:
86
+ self.records.clear()
87
+ self.by_ref.clear()
88
+ self.day = day
89
+ # Historical query rows must not seed today's broker-ID mapping.
90
+ for name in ("order_date", "trade_date", "m_strInsertDate", "m_strTradeDate", "m_strTradingDay"):
91
+ value = str(data.get(name) or "").replace("-", "").replace("/", "")
92
+ if len(value) == 8 and value.isdigit() and value != day:
93
+ return None, set()
94
+ account = str(data.get("account_id") or data.get("m_strAccountID") or "").strip()
95
+ stock = str(data.get("stock_code") or data.get("m_strInstrumentID") or "").strip().upper()
96
+ market = str(data.get("market") or data.get("m_strExchangeID") or "").strip().upper()
97
+ if "." in stock:
98
+ stock, market = stock.rsplit(".", 1)
99
+ if not account or not stock:
100
+ return None, set()
101
+ scope = (account_type, account, stock, market)
102
+ refs = set()
103
+ for name in ("order_id", "m_nRef", "m_nOrderID", "m_strOrderRef", "m_strOrderID"):
104
+ value = str(data.get(name) or "").strip()
105
+ if value.isdigit() and int(value) > 0:
106
+ refs.add(("local", str(int(value))))
107
+ for name in ("order_sysid", "m_strOrderSysID"):
108
+ value = str(data.get(name) or "").strip()
109
+ if value and value not in ("0", "-1"):
110
+ refs.add(("broker", value))
111
+ return scope, refs
112
+
113
+ def _matches(self, scope, refs, data):
114
+ groups = [self.by_ref[(scope, ref)] for ref in refs if (scope, ref) in self.by_ref]
115
+ candidates = set.intersection(*groups) if groups else set()
116
+ matches = []
117
+ for key in candidates:
118
+ record = self.records[key]
119
+ expected, actual = order_remark_key(record["data"]), order_remark_key(data)
120
+ if expected and not order_remark_matches_reference(expected, actual):
121
+ continue
122
+ local = {ref for ref in refs if ref[0] == "local"}
123
+ known = {ref for ref in record["refs"] if ref[0] == "local"}
124
+ if local and known and not local.intersection(known):
125
+ continue
126
+ matches.append(key)
127
+ return matches
128
+
129
+ def remember(self, data, account_type):
130
+ if not (data.get("strategy_name") or data.get("m_strStrategyName") or order_remark_key(data)):
131
+ return
132
+ with self.lock:
133
+ scope, refs = self._identity(data, account_type)
134
+ if scope is None or not refs:
135
+ return
136
+ matches = self._matches(scope, refs, data)
137
+ remark = order_remark_key(data)
138
+ local = sorted(ref for ref in refs if ref[0] == "local")
139
+ identity = ("remark", remark) if is_unique_order_remark(remark) else (local or sorted(refs))[0]
140
+ key = (scope, identity)
141
+ if len(matches) == 1:
142
+ key = matches[0]
143
+ record = self.records.get(key, {"data": {}, "refs": set()})
144
+ record["data"].update({name: value for name, value in data.items() if value not in (None, "")})
145
+ record["refs"].update(refs)
146
+ self.records[key] = record
147
+ self.records.move_to_end(key)
148
+ for ref in refs:
149
+ self.by_ref.setdefault((scope, ref), set()).add(key)
150
+ while len(self.records) > self.max_orders:
151
+ old_key, old = self.records.popitem(last=False)
152
+ for ref in old["refs"]:
153
+ index = (old_key[0], ref)
154
+ self.by_ref[index].discard(old_key)
155
+ if not self.by_ref[index]:
156
+ del self.by_ref[index]
157
+
158
+ def enrich(self, data, account_type):
159
+ with self.lock:
160
+ scope, refs = self._identity(data, account_type)
161
+ matches = self._matches(scope, refs, data) if scope is not None else []
162
+ if len(matches) != 1:
163
+ return data
164
+ saved = self.records[matches[0]]["data"]
165
+ strategy = saved.get("strategy_name") or saved.get("m_strStrategyName")
166
+ remark = order_remark_key(saved)
167
+ for name, value in (("strategy_name", strategy), ("m_strStrategyName", strategy),
168
+ ("order_remark", remark), ("m_strRemark", remark), ("m_strOrderRemark", remark)):
169
+ if value and not data.get(name):
170
+ data[name] = value
171
+ if is_unique_order_remark(remark):
172
+ data["cfquant_order_remark"] = remark
173
+ 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"
@@ -1091,7 +1091,7 @@ def test_local_query_does_not_swallow_internal_type_error():
1091
1091
  ("xtdata.get_full_tick", {}, "trade"),
1092
1092
  ("xtdata.get_instrument_detail", {}, "trade"),
1093
1093
  ("xtdata.get_cb_info", {}, "trade"),
1094
- ("xtdata.get_local_data", {}, "trade"),
1094
+ ("xtdata.get_local_data", {}, "normal"),
1095
1095
  ("xtdata.subscribe_quote", {}, "normal"),
1096
1096
  ("xtdata.subscribe_whole_quote", {}, "normal"),
1097
1097
  ("xtdata.get_market_data_ex", {"callback_event": "probe"}, "normal"),
@@ -0,0 +1,153 @@
1
+ """Offline regressions for split-QMT account setup and downloaded data."""
2
+ import ast
3
+ import os
4
+ from pathlib import Path
5
+ from types import SimpleNamespace
6
+ import tokenize
7
+
8
+ import pandas as pd
9
+ import pytest
10
+
11
+ import cfquant_web_server as web
12
+ from cfquant.normal_bridge import NormalQmtBridge
13
+ from cfquant.pipe_bridge import PipeNormalQmtBridge, PipeTradeBridge
14
+ from cfquant.tx_trade_bridge import TxTradeBridge
15
+ from cfquant.tests.test_lite_parity import LITE_PATHS, _lite_bridge_class
16
+
17
+
18
+ ROOT = Path(__file__).resolve().parents[2]
19
+ TRADE_ENTRIES = sorted((ROOT / "qmt_scripts").rglob("CFQUANT_TRADE_LOWLAT*.py"))
20
+
21
+
22
+ @pytest.mark.parametrize("path", TRADE_ENTRIES, ids=lambda path: path.stem)
23
+ @pytest.mark.parametrize("source", ["runtime", "environment"])
24
+ def test_advanced_trade_entry_binds_credit_account_before_init(monkeypatch, path, source):
25
+ for name in ("CFQUANT_ACCOUNT_ID", "CFQUANT_ACCOUNT_TYPE"):
26
+ monkeypatch.delenv(name, raising=False)
27
+ identity = {"account_id": "C123", "account_type": "CREDIT"}
28
+ if source == "environment":
29
+ for name, value in identity.items():
30
+ monkeypatch.setenv("CFQUANT_" + name.upper(), value)
31
+ with tokenize.open(str(path)) as stream:
32
+ tree = ast.parse(stream.read())
33
+ bridge = SimpleNamespace(account_type="", run_forever=lambda **kwargs: None)
34
+ bound = []
35
+ bridge.set_context = lambda context: bound.append((bridge.account_id, bridge.account_type))
36
+
37
+ def start(context, **kwargs):
38
+ bridge.account_id = kwargs["account_id"]
39
+ return bridge
40
+
41
+ namespace = dict(os=os, BRIDGE_ID="test", RUNTIME_CONFIG={}, RUNTIME_CHANNELS={},
42
+ QMT_MARKET="", USER_BRIDGE_ID="default", BRIDGE_CHANNELS={"trade": "trade"},
43
+ _load_runtime_config=lambda: identity if source == "runtime" else {},
44
+ _env_allows_runtime_override=lambda *args: True,
45
+ _print_log=lambda *args: None, _ENTRY_VERSION="test",
46
+ start_tx_trade_bridge=start)
47
+ # Execute account setup and bridge construction from each shipped entry,
48
+ # omitting imports and transport startup so this cannot contact a live QMT.
49
+ nodes = []
50
+ for node in tree.body:
51
+ if isinstance(node, ast.Assign) and any(
52
+ isinstance(target, ast.Name) and target.id in ("DEFAULT_ACCOUNT_ID", "DEFAULT_ACCOUNT_TYPE")
53
+ for target in node.targets
54
+ ):
55
+ nodes.append(node)
56
+ elif isinstance(node, ast.FunctionDef) and node.name in ("_apply_runtime_config", "_config_bool", "init"):
57
+ nodes.append(node)
58
+ exec(compile(ast.Module(body=nodes, type_ignores=[]), str(path), "exec"), namespace)
59
+ namespace["_apply_runtime_config"]()
60
+ nodes = [node for node in tree.body if (
61
+ isinstance(node, ast.Assign) and isinstance(node.value, ast.Call)
62
+ and isinstance(node.value.func, ast.Name) and node.value.func.id == "start_tx_trade_bridge"
63
+ ) or (isinstance(node, ast.If) and "DEFAULT_ACCOUNT_TYPE" in ast.unparse(node.test))]
64
+ exec(compile(ast.Module(body=nodes, type_ignores=[]), str(path), "exec"), namespace)
65
+ namespace["init"](object())
66
+ assert bound == [("C123", "CREDIT")]
67
+
68
+
69
+ @pytest.mark.parametrize("mode", ["lttx", "ctypes"])
70
+ @pytest.mark.parametrize("requested_channel", [None, "trade"])
71
+ @pytest.mark.parametrize("read_action", [
72
+ "get_local_data", "get_market_data", "get_market_data_ex", "get_financial_data",
73
+ "get_financial_data_ori", "get_financial_data_raw",
74
+ ])
75
+ def test_download_then_read_uses_same_qmt_storage(monkeypatch, mode, requested_channel, read_action):
76
+ stores = {"normal": {}, "trade": {}}
77
+ if mode == "ctypes":
78
+ stores["trade"] = stores["normal"]
79
+ frames = {"000001.SZ": pd.DataFrame({"close": [11.6]})}
80
+
81
+ def functions(store):
82
+ return {
83
+ "download_history_data": lambda *args: store.update(history=frames),
84
+ "download_financial_data": lambda *args: store.update(financial=frames),
85
+ "get_market_data_ex": lambda *args: store.get("history", {}),
86
+ "get_financial_data": lambda *args: store.get("financial", {}),
87
+ "get_raw_financial_data": lambda *args: store.get("financial", {}),
88
+ }
89
+
90
+ normal_cls, trade_cls = (NormalQmtBridge, TxTradeBridge) if mode == "lttx" else (PipeNormalQmtBridge, PipeTradeBridge)
91
+ bridges = {channel: cls(object(), globals_dict=functions(stores[channel]), show=False)
92
+ for channel, cls in (("normal", normal_cls), ("trade", trade_cls))}
93
+ monkeypatch.setattr(web, "WEB_CONFIG", None)
94
+ monkeypatch.setattr(web, "resolve_bridge_id", lambda **kwargs: "test")
95
+ monkeypatch.setattr(web, "resolve_account_mode", lambda *args, **kwargs: mode)
96
+ monkeypatch.setattr(web, "resolve_market_route_for_request", lambda **kwargs: ("test", None))
97
+ calls = []
98
+
99
+ def request(bridge_id, channel, action, params, **kwargs):
100
+ calls.append((action, channel))
101
+ return bridges[channel]._dispatch(action, params, {"client_id": "offline"})
102
+
103
+ monkeypatch.setattr(web.CLIENTS, "request", request)
104
+ params = {"stock_code": "000001.SZ", "stock_list": ["000001.SZ"],
105
+ "field_list": ["close"], "period": "1d"}
106
+ download = "download_financial_data" if "financial" in read_action else "download_history_data"
107
+ web.account_request("C123", "test", requested_channel, "xtdata." + download, params)
108
+ result = web.account_request("C123", "test", requested_channel, "xtdata." + read_action, params)["result"]
109
+ assert result, calls
110
+ assert calls == [("xtdata." + download, "normal"), ("xtdata." + read_action, "normal")]
111
+
112
+
113
+ @pytest.mark.parametrize("action", ["xtdata.get_market_data", "xtdata.get_market_data_ex"])
114
+ @pytest.mark.parametrize("period", ["l2quote", "l2order", "l2transaction"])
115
+ def test_level2_market_reads_keep_trade_channel(action, period):
116
+ assert web.route_channel_for_account("C123", mode="lttx", action=action,
117
+ params={"period": period}) == "trade"
118
+
119
+
120
+ BRIDGES = [TxTradeBridge, NormalQmtBridge, PipeTradeBridge, PipeNormalQmtBridge]
121
+ BRIDGES += [_lite_bridge_class(path) for path in LITE_PATHS]
122
+
123
+
124
+ @pytest.mark.parametrize("bridge_cls", BRIDGES)
125
+ @pytest.mark.parametrize("raw", [
126
+ {"success": "false", "msg": "rejected", "apply_id": "-1"},
127
+ {"success": True, "msg": "accepted", "apply_id": []},
128
+ {"success": True, "msg": "accepted", "apply_id": True},
129
+ {"success": True, "apply_id": "A1"},
130
+ ])
131
+ def test_all_modes_reject_malformed_smt_response(bridge_cls, raw):
132
+ bridge = bridge_cls(object(), show=False, globals_dict={"smt_appointment_cancel": lambda *args: raw})
133
+ bridge._send_trader_event = lambda *args: pytest.fail("malformed response delivered")
134
+ with pytest.raises(RuntimeError, match="business response"):
135
+ bridge._dispatch("xttrader.smt_appointment_cancel", {
136
+ "account": {"account_id": "C123", "account_type": "CREDIT"}, "seq": 1, "args": ["A1"],
137
+ }, {"client_id": "offline"})
138
+
139
+
140
+ @pytest.mark.parametrize("bridge_cls", BRIDGES)
141
+ def test_all_modes_accept_native_smt_fields(bridge_cls):
142
+ raw = type("BrokerResponse", (), dict(__slots__=(), m_bSuccess=True,
143
+ m_strMsg="accepted", m_strApplyID="000123", m_nSeq=987))()
144
+ bridge = bridge_cls(object(), show=False, globals_dict={"smt_appointment_cancel": lambda *args: raw})
145
+ events = []
146
+ bridge._send_trader_event = lambda client, event, data: events.append(data)
147
+ result = bridge._dispatch("xttrader.smt_appointment_cancel", {
148
+ "account": {"account_id": "C123", "account_type": "CREDIT"}, "seq": 1, "args": ["A1"],
149
+ }, {"client_id": "offline"})
150
+ assert result == {"seq": 1, "accepted": True}
151
+ assert len(events) == 1
152
+ assert events[0]["success"] is True and events[0]["apply_id"] == "000123"
153
+ assert events[0]["seq"] == 1
@@ -0,0 +1,140 @@
1
+ """Focused runtime checks for the standalone Lite compatibility adapters."""
2
+
3
+ import ast
4
+ from pathlib import Path
5
+ from types import SimpleNamespace
6
+
7
+ import pandas as pd
8
+
9
+ from cfquant import level2
10
+ from cfquant.tx_trade_bridge import TxTradeBridge
11
+
12
+
13
+ ROOT = Path(__file__).resolve().parents[2]
14
+ LITE_PATHS = sorted((ROOT / "qmt_scripts").rglob("CFQUANT_LITE*.py"))
15
+
16
+
17
+ def _lite_bridge_class(path):
18
+ source = path.read_bytes().decode("gbk")
19
+ tree = ast.parse(source)
20
+ class_node = next(
21
+ node for node in tree.body
22
+ if isinstance(node, ast.ClassDef) and node.name == "TxTradeBridge"
23
+ )
24
+ methods = {
25
+ "_dispatch_xtdata_compat", "_get_market_data", "_get_market_data_ex",
26
+ "_get_local_data", "_call_local_data", "_dispatch_xttrader_compat",
27
+ "_query_account_id", "_credit_account_id", "_call_trade_detail_data",
28
+ "_credit_row_data", "_credit_field_value", "_format_credit_rows",
29
+ "_query_credit_detail", "_query_qmt_objects", "_query_stk_compacts",
30
+ "_smt_request", "_smt_response_data", "_format_transfer_result", "_format_compat_query_objects",
31
+ "_format_compat_query_object", "_known_query_fields", "_plain_object",
32
+ "_first_present",
33
+ }
34
+ class_node.bases = [ast.Name(id="BaseTradeBridge", ctx=ast.Load())]
35
+ class_node.body = [
36
+ node for node in class_node.body
37
+ if isinstance(node, ast.FunctionDef) and node.name in methods
38
+ ]
39
+ constants = {
40
+ "SMT_ASYNC_ARGUMENT_COUNTS", "XTTRADER_TRANSFER_RESULT_METHODS",
41
+ "LITE_OBJECT_QUERY_METHODS", "_LITE_CREDIT_FIELD_ALIASES",
42
+ "L2_GET_PERIODS", "L2_PERIODS", "KLINE_PERIODS",
43
+ "XTDATA_COMPAT_CANDIDATES", "XTDATA_MAINCHAIN_UNSUPPORTED",
44
+ }
45
+ body = [
46
+ node for node in tree.body
47
+ if isinstance(node, ast.Assign)
48
+ and any(isinstance(target, ast.Name) and target.id in constants for target in node.targets)
49
+ ]
50
+ body.append(class_node)
51
+ module = ast.Module(body=body, type_ignores=[])
52
+ namespace = dict(vars(__import__("cfquant.tx_trade_bridge", fromlist=["*"])))
53
+ namespace.update(
54
+ BaseTradeBridge=TxTradeBridge,
55
+ market_data_result_needs_fallback=level2.market_data_result_needs_fallback,
56
+ )
57
+ exec(compile(ast.fix_missing_locations(module), str(path), "exec"), namespace)
58
+ return namespace["TxTradeBridge"]
59
+
60
+
61
+ class _FakeQmt:
62
+ def __init__(self):
63
+ self.calls = []
64
+
65
+ def get_market_data(self, *args):
66
+ self.calls.append(("legacy", args))
67
+ return pd.DataFrame()
68
+
69
+ def get_market_data_ex(self, *args):
70
+ self.calls.append(("modern", args))
71
+ return {"000001.SZ": pd.DataFrame({"close": [11.6]})}
72
+
73
+ def get_trade_detail_data(self, account_id, account_type, detail_type, strategy_name):
74
+ self.calls.append(("detail", account_id, account_type, detail_type, strategy_name))
75
+ if detail_type == "account":
76
+ return [SimpleNamespace(m_strAccountID=account_id, m_dBalance=100.0, m_dFinUsedQuota=8.0)]
77
+ return []
78
+
79
+ def get_unclosed_compacts(self, account_id, account_type):
80
+ self.calls.append(("compact", account_id, account_type))
81
+ return [SimpleNamespace(m_strAccountID=account_id, m_strInstrumentID="000001", m_eCompactType=1)]
82
+
83
+
84
+ def _bridge(path):
85
+ bridge = _lite_bridge_class(path)(_FakeQmt(), account_id="CREDIT-1", show=False)
86
+ bridge._log = lambda *args, **kwargs: None
87
+ bridge.tx = SimpleNamespace(push=lambda *args, **kwargs: None)
88
+ return bridge
89
+
90
+
91
+ def test_all_lite_entries_contain_shared_data_and_credit_adapters():
92
+ expected = {
93
+ "_get_market_data", "_get_local_data", "_get_instrument_detail",
94
+ "_get_cb_info", "_get_divid_factors", "_get_stock_list_in_sector",
95
+ "_query_credit_detail", "_query_stk_compacts", "_call_trade_detail_data",
96
+ }
97
+ for path in LITE_PATHS:
98
+ tree = ast.parse(path.read_bytes().decode("gbk"))
99
+ bridge = next(node for node in tree.body if isinstance(node, ast.ClassDef) and node.name == "TxTradeBridge")
100
+ names = {node.name for node in bridge.body if isinstance(node, ast.FunctionDef)}
101
+ assert expected <= names, path
102
+
103
+
104
+ def test_lite_credit_detail_supports_qmt_four_argument_signature():
105
+ bridge = _bridge(LITE_PATHS[0])
106
+ result = bridge._dispatch(
107
+ "xttrader.query_credit_detail",
108
+ {"account": {"account_id": "CREDIT-1", "account_type": "CREDIT"}},
109
+ {"client_id": "test"},
110
+ )
111
+ assert result[0]["account_id"] == "CREDIT-1"
112
+ assert result[0]["account_type"] == 3
113
+ assert result[0]["cfquant_qmt_fields"]["m_dFinUsedQuota"] == 8.0
114
+ assert result[0]["cfquant_source"] == "get_trade_detail_data"
115
+ assert ("detail", "CREDIT-1", "credit", "account", "") in bridge.context.calls
116
+
117
+
118
+ def test_lite_credit_compacts_and_market_fallback_match_common_contract():
119
+ bridge = _bridge(LITE_PATHS[0])
120
+ compact = bridge._dispatch(
121
+ "xttrader.query_stk_compacts",
122
+ {"account_id": "CREDIT-1", "account_type": "CREDIT"},
123
+ {"client_id": "test"},
124
+ )
125
+ assert compact[0]["account_id"] == "CREDIT-1"
126
+ assert compact[0]["instrument_id"] == "000001"
127
+
128
+ result = bridge._dispatch(
129
+ "xtdata.get_market_data",
130
+ {"field_list": ["close"], "stock_list": ["000001.SZ"], "period": "1d"},
131
+ {"client_id": "test"},
132
+ )
133
+ assert "close" in result
134
+ local = bridge._dispatch(
135
+ "xtdata.get_local_data",
136
+ {"field_list": ["close"], "stock_list": ["000001.SZ"], "period": "1d"},
137
+ {"client_id": "test"},
138
+ )
139
+ assert "000001.SZ" in local
140
+ assert any(call[0] == "modern" and len(call[1]) == 9 for call in bridge.context.calls)