cfquant 0.2.47__tar.gz → 0.2.48__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.
- {cfquant-0.2.47 → cfquant-0.2.48}/PKG-INFO +1 -1
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/qmt_strategy_deploy.py +50 -17
- {cfquant-0.2.47 → cfquant-0.2.48}/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 +4 -4
- cfquant-0.2.48/cfquant/tests/test_lite_deployment_paths.py +140 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/tests/test_qmt_strategy_deploy.py +9 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/tests/test_qmt_strategy_web.py +2 -1
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/version.py +2 -2
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant.egg-info/PKG-INFO +1 -1
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant.egg-info/SOURCES.txt +1 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant_web_server.py +115 -79
- {cfquant-0.2.47 → cfquant-0.2.48}/pyproject.toml +1 -1
- {cfquant-0.2.47 → cfquant-0.2.48}/qmt_scripts/CFQUANT_LITE.py +1 -1
- {cfquant-0.2.47 → cfquant-0.2.48}/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 +1 -1
- {cfquant-0.2.47 → cfquant-0.2.48}/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 +1 -1
- {cfquant-0.2.47 → cfquant-0.2.48}/web_dashboard/app.js +1 -1
- {cfquant-0.2.47 → cfquant-0.2.48}/web_dashboard/index.html +2 -2
- {cfquant-0.2.47 → cfquant-0.2.48}/LICENSE +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/LTtx/__init__.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/LTtx/tx/Config.txt +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/LTtx/tx/LTtx_server.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/LTtx/tx/__init__.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/LTtx/tx/tx.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/MANIFEST.in +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/README.md +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/__init__.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/_editable_install.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/account_routing.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/batch_orders.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/build_info.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/callback_dispatcher.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/cftrader.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/channels.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/cli.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/client.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/config.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/hub.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/level2.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/log_management.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/logging_i18n.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/management.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/normal_bridge.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/order_identity.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/order_meta.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/pipe_bridge.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/pipe_client.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/pipe_hub.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/pipe_transport.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/protocol.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/qmt_bridge.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/qmt_strategy_package.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/qmt_strategy_runtime.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/runtime_report.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/stock_connect.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/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
- {cfquant-0.2.47 → cfquant-0.2.48}/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
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/tests/12_/350/256/242/345/215/225/345/244/207/346/263/250/346/265/213/350/257/225.py" +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/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
- {cfquant-0.2.47 → cfquant-0.2.48}/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
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/tests/16_/345/220/257/345/212/250/345/256/211/350/243/205/346/265/213/350/257/225.py" +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/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
- {cfquant-0.2.47 → cfquant-0.2.48}/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
- {cfquant-0.2.47 → cfquant-0.2.48}/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
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/tests/1_/350/241/214/346/203/205/346/216/245/346/224/266/346/265/213/350/257/225.py" +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/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
- {cfquant-0.2.47 → cfquant-0.2.48}/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
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/tests/22_/350/277/236/346/216/245/350/257/212/346/226/255/346/265/213/350/257/225.py" +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/tests/23_/347/244/272/344/276/213/351/205/215/347/275/256/346/265/213/350/257/225.py" +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/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
- {cfquant-0.2.47 → cfquant-0.2.48}/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
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/tests/26_/345/205/250/346/216/250/350/256/242/351/230/205/346/265/213/350/257/225.py" +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/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
- {cfquant-0.2.47 → cfquant-0.2.48}/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
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/tests/29_Level2/350/241/214/346/203/205/346/265/213/350/257/225.py" +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/tests/2_/346/225/260/346/215/256/350/216/267/345/217/226/346/265/213/350/257/225.py" +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/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
- {cfquant-0.2.47 → cfquant-0.2.48}/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
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/tests/32_/345/274/202/346/255/245/344/270/213/345/215/225/346/265/213/350/257/225.py" +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/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
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/tests/3_/346/225/260/346/215/256/344/270/213/350/275/275/346/265/213/350/257/225.py" +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/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
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/tests/5_/347/234/237/345/256/236/344/270/213/345/215/225/346/265/213/350/257/225.py" +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/tests/6_/345/233/236/350/260/203/346/265/213/350/257/225.py" +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/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
- {cfquant-0.2.47 → cfquant-0.2.48}/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
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/tests/9_/345/217/257/346/222/244/350/256/242/345/215/225/346/265/213/350/257/225.py" +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/tests/_helpers.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/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
- {cfquant-0.2.47 → cfquant-0.2.48}/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
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/tests/readme.md +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/tests/test_api_tester_browser.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/tests/test_api_tester_web.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/tests/test_binding_delete_confirmation.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/tests/test_callback_requests.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/tests/test_cftrader.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/tests/test_cftrader_tutorial.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/tests/test_cftrader_web.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/tests/test_credit_compat.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/tests/test_json_serialization.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/tests/test_lite_account_routing.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/tests/test_local_trading_calendar.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/tests/test_log_management.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/tests/test_management.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/tests/test_management_batch.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/tests/test_market_data_fallback.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/tests/test_order_identity.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/tests/test_order_time_callback.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/tests/test_project_updater.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/tests/test_python_api_reference.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/tests/test_qmt_strategy_browser.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/tests/test_setup_qmt_prompt.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/tests/test_startup_recovery.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/tests/test_stock_connect.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/tests/test_stock_connect_browser.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/tests/test_trading_dates.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/tests/test_tutorial_reader.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/tests/test_tx_auto_import.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/trading_calendar.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/tx.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/tx_trade_bridge.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/xtconstant.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/xtdata.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/xttrader.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant/xttype.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant.egg-info/dependency_links.txt +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant.egg-info/entry_points.txt +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant.egg-info/requires.txt +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant.egg-info/top_level.txt +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/cfquant_pipe_hub.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/qmt_scripts/CFQUANT.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/qmt_scripts/CFQUANT_CTYPE_ALL_LOWLAT.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/qmt_scripts/CFQUANT_TRADE_LOWLAT.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/qmt_scripts/__init__.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/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
- {cfquant-0.2.47 → cfquant-0.2.48}/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
- {cfquant-0.2.47 → cfquant-0.2.48}/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
- {cfquant-0.2.47 → cfquant-0.2.48}/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
- {cfquant-0.2.47 → cfquant-0.2.48}/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
- {cfquant-0.2.47 → cfquant-0.2.48}/requirements.txt +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/setup.cfg +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/web_dashboard/__init__.py +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/web_dashboard/api-tester.js +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/web_dashboard/avatars/amber-pulse.svg +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/web_dashboard/avatars/copper-grid.svg +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/web_dashboard/avatars/market-blue.svg +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/web_dashboard/avatars/rose-circuit.svg +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/web_dashboard/avatars/signal-green.svg +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/web_dashboard/avatars/slate-wave.svg +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/web_dashboard/avatars/teal-orbit.svg +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/web_dashboard/avatars/violet-node.svg +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/web_dashboard/cftrader-api-data.js +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/web_dashboard/docs.js +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/web_dashboard/favicon.svg +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/web_dashboard/image/qmt_deploy/image-10.png +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/web_dashboard/image/qmt_deploy/image-11.png +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/web_dashboard/image/qmt_deploy/image-12.png +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/web_dashboard/image/qmt_deploy/image-13.png +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/web_dashboard/image/qmt_deploy/image-14.png +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/web_dashboard/image/qmt_deploy/image-15.png +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/web_dashboard/image/qmt_deploy/image-16.png +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/web_dashboard/image/qmt_deploy/image-17.png +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/web_dashboard/image/qmt_deploy/image-18.png +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/web_dashboard/image/qmt_deploy/image-19.png +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/web_dashboard/image/qmt_deploy/image-20.png +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/web_dashboard/image/qmt_deploy/image-21.png +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/web_dashboard/image/qmt_deploy/image-4.png +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/web_dashboard/image/qmt_deploy/image-5.png +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/web_dashboard/image/qmt_deploy/image-6.png +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/web_dashboard/image/qmt_deploy/image-7.png +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/web_dashboard/image/qmt_deploy/image-8.png +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/web_dashboard/image/qmt_deploy/image-9.png +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/web_dashboard/python-api-data.js +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/web_dashboard/python-api.js +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/web_dashboard/styles.css +0 -0
- {cfquant-0.2.47 → cfquant-0.2.48}/web_dashboard/vendor/mermaid.min.js +0 -0
|
@@ -23,7 +23,10 @@ MODE_ALIASES = {
|
|
|
23
23
|
"ctypes": "ctypes", "pipe": "ctypes", "named_pipe": "ctypes",
|
|
24
24
|
"lite": "lite", "extreme": "lite", "extreme_lite": "lite",
|
|
25
25
|
"lite_extreme": "lite", "ultimate": "lite",
|
|
26
|
+
"lite_extreme_pipe": "lite", "extreme_pipe": "lite", "cfquant_lite": "lite",
|
|
27
|
+
"named-pipe": "ctypes", "universal": "ctypes", "universal_ctypes": "ctypes",
|
|
26
28
|
"lttx": "lttx", "socket": "lttx", "normal": "lttx",
|
|
29
|
+
"default": "lttx",
|
|
27
30
|
}
|
|
28
31
|
LEGACY_NAMES = {Path(name).stem for name in SCRIPTS.values()} | {"CFQUANT_TRADE_LOWLAT"}
|
|
29
32
|
LEGACY_NAMES |= {name + "_" + market for name in tuple(LEGACY_NAMES) for market in ("SH", "SZ")}
|
|
@@ -68,9 +71,42 @@ def normalize_strategy_mode(value):
|
|
|
68
71
|
return MODE_ALIASES[mode]
|
|
69
72
|
except KeyError:
|
|
70
73
|
raise ValueError("unknown QMT strategy mode: %s" % mode)
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
def
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def entry_script_for_mode(mode, role="normal", market=""):
|
|
77
|
+
"""One mapping for raw copies and the packages QMT actually imports."""
|
|
78
|
+
mode = normalize_strategy_mode(mode)
|
|
79
|
+
market = str(market or "").strip().upper()
|
|
80
|
+
if market and market not in ("SH", "SZ"):
|
|
81
|
+
raise ValueError("unknown QMT entry market: %s" % market)
|
|
82
|
+
filename = SCRIPTS[mode]
|
|
83
|
+
if mode == "lttx" and (role != "normal" or market):
|
|
84
|
+
filename = "CFQUANT_TRADE_LOWLAT.py"
|
|
85
|
+
if market:
|
|
86
|
+
return "同账号独立市场/%s_%s.py" % (Path(filename).stem, market)
|
|
87
|
+
return filename
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def read_entry_source(path):
|
|
91
|
+
"""Reject a generic entry accidentally saved under a Lite filename."""
|
|
92
|
+
path = Path(path)
|
|
93
|
+
with tokenize.open(str(path)) as stream:
|
|
94
|
+
source = stream.read()
|
|
95
|
+
if path.stem.startswith("CFQUANT_LITE"):
|
|
96
|
+
tree = ast.parse(source)
|
|
97
|
+
constants = {target.id for node in tree.body if isinstance(node, ast.Assign)
|
|
98
|
+
for target in node.targets if isinstance(target, ast.Name)}
|
|
99
|
+
imports_core = any(
|
|
100
|
+
(isinstance(node, ast.ImportFrom) and (node.module or "").split(".")[0] == "cfquant")
|
|
101
|
+
or (isinstance(node, ast.Import) and any(alias.name.split(".")[0] == "cfquant"
|
|
102
|
+
for alias in node.names))
|
|
103
|
+
for node in ast.walk(tree))
|
|
104
|
+
if "LITE_ENTRY_VERSION" not in constants or imports_core:
|
|
105
|
+
raise ValueError("极致模式入口内容不匹配,拒绝部署通用模式代码: %s" % path)
|
|
106
|
+
return source
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def qmt_root(directory, validate=False):
|
|
74
110
|
path = Path(str(directory)).expanduser().resolve()
|
|
75
111
|
if path.name.lower() in ("bin.x64", "bin", "python"):
|
|
76
112
|
path = path.parent
|
|
@@ -283,9 +319,8 @@ def _resolve_account_binding(root, document, account_id, account_type, supplied=
|
|
|
283
319
|
return None
|
|
284
320
|
|
|
285
321
|
|
|
286
|
-
def managed_source(source_path, identity_path, identity, descriptor):
|
|
287
|
-
|
|
288
|
-
source = stream.read()
|
|
322
|
+
def managed_source(source_path, identity_path, identity, descriptor):
|
|
323
|
+
source = read_entry_source(source_path)
|
|
289
324
|
lines = source.splitlines(keepends=True)
|
|
290
325
|
tree = ast.parse(source)
|
|
291
326
|
replacements = []
|
|
@@ -463,12 +498,13 @@ class QmtStrategyManager:
|
|
|
463
498
|
result.update(error=self.load_error, message=self.load_error)
|
|
464
499
|
return result
|
|
465
500
|
|
|
466
|
-
def configure(self, row, identities, force=False):
|
|
501
|
+
def configure(self, row, identities, force=False, scripts_dir=None):
|
|
467
502
|
if self.load_error:
|
|
468
503
|
raise ValueError(self.load_error)
|
|
469
504
|
settings = normalize_strategy_settings(row.get("qmt_strategy"))
|
|
470
505
|
mode = normalize_strategy_mode(row.get("mode"))
|
|
471
|
-
enabled = row.get("enabled", True) and settings["enabled"]
|
|
506
|
+
enabled = row.get("enabled", True) and settings["enabled"]
|
|
507
|
+
scripts_dir = Path(scripts_dir) if scripts_dir is not None else self.scripts_dir
|
|
472
508
|
with self.lock:
|
|
473
509
|
wanted = set()
|
|
474
510
|
grouped = {}
|
|
@@ -486,19 +522,16 @@ class QmtStrategyManager:
|
|
|
486
522
|
"account_key": row["account_key"]}])
|
|
487
523
|
market = info.get("market") or identity.get("market") or ""
|
|
488
524
|
role = market or info.get("qmt_role") or "normal"
|
|
489
|
-
filename =
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
source_path = self.scripts_dir / "同账号独立市场" / (Path(filename).stem + "_" + market + ".py")
|
|
495
|
-
if not source_path.is_file():
|
|
496
|
-
raise ValueError("QMT 内置脚本不存在: %s" % source_path)
|
|
525
|
+
filename = entry_script_for_mode(mode, role, market)
|
|
526
|
+
source_path = scripts_dir / filename
|
|
527
|
+
if not source_path.is_file():
|
|
528
|
+
raise ValueError("QMT 内置脚本不存在: %s" % source_path)
|
|
529
|
+
read_entry_source(source_path)
|
|
497
530
|
for key in ("updated_at", "updated_at_text"):
|
|
498
531
|
identity.pop(key, None)
|
|
499
532
|
group_key = _digest([os.path.normcase(str(root)), row["account_id"]])[:24]
|
|
500
533
|
grouped.setdefault(group_key, {"root": str(root), "roles": []})["roles"].append({
|
|
501
|
-
"role": role, "source": str(source_path), "identity": identity,
|
|
534
|
+
"role": role, "source": str(source_path), "entry_script": filename, "identity": identity,
|
|
502
535
|
"source_sha256": hashlib.sha256(source_path.read_bytes()).hexdigest(),
|
|
503
536
|
"account_key": settings["account_keys"].get(role, "")})
|
|
504
537
|
if not grouped:
|
|
@@ -372,7 +372,7 @@ def test_auto_deploy_local_core_skips_when_target_matches(tmp_path):
|
|
|
372
372
|
def test_local_entry_sync_overwrites_bound_qmt_python_script(tmp_path):
|
|
373
373
|
source = tmp_path / "source"
|
|
374
374
|
(source / "qmt_scripts").mkdir(parents=True)
|
|
375
|
-
(source / "qmt_scripts" / "CFQUANT_LITE.py").write_bytes(b"
|
|
375
|
+
(source / "qmt_scripts" / "CFQUANT_LITE.py").write_bytes(b'LITE_ENTRY_VERSION = "test"\n')
|
|
376
376
|
qmt_root = tmp_path / "QMT"
|
|
377
377
|
(qmt_root / "bin.x64").mkdir(parents=True)
|
|
378
378
|
(qmt_root / "python").mkdir()
|
|
@@ -389,7 +389,7 @@ def test_local_entry_sync_overwrites_bound_qmt_python_script(tmp_path):
|
|
|
389
389
|
)
|
|
390
390
|
|
|
391
391
|
assert result["updated"] is True
|
|
392
|
-
assert entry.read_bytes() == b"
|
|
392
|
+
assert entry.read_bytes() == b'LITE_ENTRY_VERSION = "test"\n'
|
|
393
393
|
assert result["entry_file"] == str(entry)
|
|
394
394
|
assert result["backup"]
|
|
395
395
|
assert (qmt_root / "bin.x64" / ".cfquant_updates" / "entry_backups").is_dir()
|
|
@@ -402,7 +402,7 @@ def test_auto_deploy_project_update_copies_bound_mode_entry(tmp_path, monkeypatc
|
|
|
402
402
|
for name in ("__init__.py", "client.py", "protocol.py"):
|
|
403
403
|
(source_core / name).write_text("# source\n", encoding="utf-8")
|
|
404
404
|
(source / "qmt_scripts").mkdir()
|
|
405
|
-
(source / "qmt_scripts" / "CFQUANT_LITE.py").write_bytes(b"latest
|
|
405
|
+
(source / "qmt_scripts" / "CFQUANT_LITE.py").write_bytes(b'LITE_ENTRY_VERSION = "latest"\n')
|
|
406
406
|
|
|
407
407
|
qmt_root = tmp_path / "QMT"
|
|
408
408
|
target_core = qmt_root / "bin.x64" / "cfquant"
|
|
@@ -423,7 +423,7 @@ def test_auto_deploy_project_update_copies_bound_mode_entry(tmp_path, monkeypatc
|
|
|
423
423
|
|
|
424
424
|
assert result["summary"]["ok"] is True
|
|
425
425
|
assert result["summary"]["entry_updated_count"] == 1
|
|
426
|
-
assert (qmt_root / "python" / "CFQUANT_LITE.py").read_bytes() == b"latest
|
|
426
|
+
assert (qmt_root / "python" / "CFQUANT_LITE.py").read_bytes() == b'LITE_ENTRY_VERSION = "latest"\n'
|
|
427
427
|
assert result["results"][0]["entry_script"] == "CFQUANT_LITE.py"
|
|
428
428
|
|
|
429
429
|
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""Verify the bytes copied and imported for Lite across binding/update paths."""
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
import json
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import shutil
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
from cfquant.qmt_strategy_deploy import QmtStrategyManager
|
|
11
|
+
from cfquant.qmt_strategy_package import _cipher
|
|
12
|
+
from cfquant.tests.test_qmt_strategy_deploy import deployment, finish_import, models, SCRIPTS
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def embedded_source(package):
|
|
16
|
+
content = json.loads(_cipher().decrypt(package.read_bytes()).decode("utf-8", "surrogateescape"))["content"]
|
|
17
|
+
return next(node.args[0].value for node in ast.walk(ast.parse(content))
|
|
18
|
+
if isinstance(node, ast.Call) and isinstance(node.func, ast.Name)
|
|
19
|
+
and node.func.id == "compile" and isinstance(node.args[0], ast.Constant))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def assert_lite_package(manager, root):
|
|
23
|
+
job = next(job for job in manager.jobs.values() if job.get("enabled"))
|
|
24
|
+
assert job["mode"] == "lite"
|
|
25
|
+
for role in job["roles"]:
|
|
26
|
+
source = embedded_source(root / "formulas" / (role["name"] + ".rzrk"))
|
|
27
|
+
assert "LITE_ENTRY_VERSION =" in source
|
|
28
|
+
assert "def account_route_client_ids(" in source
|
|
29
|
+
assert not any(isinstance(node, ast.ImportFrom) and (node.module or "").startswith("cfquant")
|
|
30
|
+
for node in ast.walk(ast.parse(source)))
|
|
31
|
+
assert Path(role["source"]).name.startswith("CFQUANT_LITE")
|
|
32
|
+
return job
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@pytest.fixture
|
|
36
|
+
def web_deployment(deployment, tmp_path, monkeypatch):
|
|
37
|
+
import cfquant_web_server as web
|
|
38
|
+
manager, row, infos, running, root = deployment
|
|
39
|
+
config = web.WebRuntimeConfig(str(tmp_path / "web.json"), str(tmp_path / "web.db"))
|
|
40
|
+
config._data["account_configs"] = {row["account_key"]: row}
|
|
41
|
+
monkeypatch.setattr(web, "WEB_CONFIG", config)
|
|
42
|
+
monkeypatch.setattr(web, "BASE_DIR", str(SCRIPTS.parent))
|
|
43
|
+
monkeypatch.setattr(web, "QMT_STRATEGIES", manager)
|
|
44
|
+
updater = web.CfquantUpdater(config)
|
|
45
|
+
monkeypatch.setattr(updater, "install_local_core_to_qmt_dir", lambda *args, **kwargs: {"skipped": True})
|
|
46
|
+
monkeypatch.setattr(web, "UPDATER", updater)
|
|
47
|
+
monkeypatch.setattr(web, "write_qmt_bridge_identity", lambda row: dict(infos[0]))
|
|
48
|
+
monkeypatch.setattr(web, "write_qmt_market_bridge_identities", lambda row: [])
|
|
49
|
+
monkeypatch.setattr(web, "sync_qmt_bridge_identities", lambda: [])
|
|
50
|
+
monkeypatch.setattr(web, "qmt_process_preflight", lambda **kwargs: None)
|
|
51
|
+
monkeypatch.setattr(web, "qmt_auto_login_apply_for_account", lambda *args, **kwargs: {})
|
|
52
|
+
monkeypatch.setattr(web, "ensure_account_runtime", lambda mode: {})
|
|
53
|
+
monkeypatch.setattr(web.ACCOUNT_CACHE, "prime_configured_accounts", lambda: None)
|
|
54
|
+
monkeypatch.setattr(web.STATUS_MONITOR, "wake", lambda: None)
|
|
55
|
+
monkeypatch.setattr(web.CALLBACKS, "refresh_channels", lambda channels: None)
|
|
56
|
+
return web, manager, row, infos, root
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@pytest.mark.parametrize("action", ["bind", "account_update", "project_update", "startup"])
|
|
60
|
+
def test_selected_lite_replaces_imported_generic_code(action, web_deployment, monkeypatch):
|
|
61
|
+
web, manager, row, infos, root = web_deployment
|
|
62
|
+
manager.configure(row, infos)
|
|
63
|
+
finish_import(manager, root)
|
|
64
|
+
manager.process_once()
|
|
65
|
+
generic_name = next(iter(manager.jobs.values()))["roles"][0]["name"]
|
|
66
|
+
# Simulate an older installation with a Lite binding and a generic import.
|
|
67
|
+
row["mode"] = "lite"
|
|
68
|
+
if action == "bind":
|
|
69
|
+
result = web.save_account_runtime_config({
|
|
70
|
+
key: row[key] for key in ("account_id", "account_type", "bridge_id", "qmt_dir", "mode")})
|
|
71
|
+
assert result["account"]["mode"] == "lite"
|
|
72
|
+
assert not result["qmt_strategy_deploy"].get("error")
|
|
73
|
+
elif action == "account_update":
|
|
74
|
+
monkeypatch.setattr(web, "account_config_for_request", lambda body: row)
|
|
75
|
+
result = web.update_account_qmt_core({"account_key": row["account_key"]})
|
|
76
|
+
assert not result["qmt_strategy_deploy"].get("error")
|
|
77
|
+
elif action == "project_update":
|
|
78
|
+
result = web.auto_deploy_qmt_core_for_all_accounts(str(SCRIPTS.parent), sync_entry_scripts=True)
|
|
79
|
+
assert result["summary"]["ok"]
|
|
80
|
+
else:
|
|
81
|
+
# Persisted jobs must be repaired on first start after an old updater.
|
|
82
|
+
manager = QmtStrategyManager(manager.path, SCRIPTS, lambda root: False)
|
|
83
|
+
monkeypatch.setattr(web, "QMT_STRATEGIES", manager)
|
|
84
|
+
result = web.refresh_bound_qmt_strategies()
|
|
85
|
+
assert not result[0].get("error")
|
|
86
|
+
assert_lite_package(manager, root)
|
|
87
|
+
assert models(root)[generic_name].getAttribute("startupAutorun") == "0"
|
|
88
|
+
if action != "startup":
|
|
89
|
+
assert (root / "python" / "CFQUANT_LITE.py").read_bytes() == (SCRIPTS / "CFQUANT_LITE.py").read_bytes()
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def test_edit_without_mode_preserves_lite_binding_and_payload(web_deployment):
|
|
93
|
+
web, manager, row, _, root = web_deployment
|
|
94
|
+
row["mode"] = "lite"
|
|
95
|
+
result = web.save_account_runtime_config({
|
|
96
|
+
key: row[key] for key in ("account_id", "account_type", "bridge_id", "qmt_dir")})
|
|
97
|
+
assert result["account"]["mode"] == "lite"
|
|
98
|
+
assert_lite_package(manager, root)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@pytest.mark.parametrize("single_account", [False, True])
|
|
102
|
+
def test_shared_qmt_directory_does_not_drop_lite_market_entries(single_account, web_deployment):
|
|
103
|
+
web, _, row, _, root = web_deployment
|
|
104
|
+
row.update(mode="lite", market_routing_enabled=True,
|
|
105
|
+
qmt_strategy={"enabled": False}, market_bridges={
|
|
106
|
+
market: {"qmt_dir": str(root), "bridge_id": "bridge_" + market}
|
|
107
|
+
for market in ("SH", "SZ")})
|
|
108
|
+
if single_account:
|
|
109
|
+
result = web.auto_deploy_qmt_core_for_account(row)
|
|
110
|
+
else:
|
|
111
|
+
# The earlier binding must not win solely because it shares a directory.
|
|
112
|
+
generic = dict(row, account_key="generic", account_id="222", mode="ctypes", market_routing_enabled=False)
|
|
113
|
+
web.WEB_CONFIG._data["account_configs"] = {"generic": generic, row["account_key"]: row}
|
|
114
|
+
result = web.auto_deploy_qmt_core_for_all_accounts(str(SCRIPTS.parent), sync_entry_scripts=True)
|
|
115
|
+
assert result["summary"]["ok"]
|
|
116
|
+
for name, directory in (("CFQUANT_LITE.py", SCRIPTS),
|
|
117
|
+
("CFQUANT_LITE_SH.py", SCRIPTS / "同账号独立市场"),
|
|
118
|
+
("CFQUANT_LITE_SZ.py", SCRIPTS / "同账号独立市场")):
|
|
119
|
+
assert (root / "python" / name).read_bytes() == (directory / name).read_bytes()
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def test_update_uses_new_source_and_rejects_mislabeled_generic_entry(web_deployment, tmp_path):
|
|
123
|
+
web, manager, row, _, root = web_deployment
|
|
124
|
+
row["mode"] = "lite"
|
|
125
|
+
source = tmp_path / "updated_project"
|
|
126
|
+
(source / "qmt_scripts").mkdir(parents=True)
|
|
127
|
+
entry = source / "qmt_scripts" / "CFQUANT_LITE.py"
|
|
128
|
+
entry.write_bytes((SCRIPTS / entry.name).read_bytes() + b'\nRELEASE_TEST_MARKER = "new-source"\n')
|
|
129
|
+
result = web.auto_deploy_qmt_core_for_all_accounts(str(source), sync_entry_scripts=True)
|
|
130
|
+
assert result["summary"]["ok"]
|
|
131
|
+
job = assert_lite_package(manager, root)
|
|
132
|
+
assert 'RELEASE_TEST_MARKER = "new-source"' in embedded_source(
|
|
133
|
+
root / "formulas" / (job["roles"][0]["name"] + ".rzrk"))
|
|
134
|
+
previous = (root / "python" / entry.name).read_bytes()
|
|
135
|
+
shutil.copyfile(SCRIPTS / "CFQUANT_CTYPE_ALL_LOWLAT.py", entry)
|
|
136
|
+
result = web.auto_deploy_qmt_core_for_all_accounts(str(source), sync_entry_scripts=True)
|
|
137
|
+
assert not result["summary"]["ok"]
|
|
138
|
+
assert result["summary"]["strategy_error_count"] == 1
|
|
139
|
+
assert "通用模式代码" in result["summary"]["message"]
|
|
140
|
+
assert (root / "python" / entry.name).read_bytes() == previous
|
|
@@ -15,6 +15,7 @@ from cfquant.qmt_strategy_deploy import (
|
|
|
15
15
|
QmtStrategyManager, _account_binding, _atomic_write, _read_document,
|
|
16
16
|
_write_json, _resolve_account_binding, account_qmt_roots, managed_source, normalize_strategy_settings,
|
|
17
17
|
normalize_strategy_mode,
|
|
18
|
+
entry_script_for_mode, read_entry_source,
|
|
18
19
|
_strategy_slot_name,
|
|
19
20
|
)
|
|
20
21
|
from cfquant.qmt_strategy_package import _cipher, build_package
|
|
@@ -31,6 +32,14 @@ def test_strategy_mode_aliases_are_canonical(value, expected):
|
|
|
31
32
|
assert normalize_strategy_mode(value) == expected
|
|
32
33
|
|
|
33
34
|
|
|
35
|
+
def test_lite_entry_mapping_rejects_generic_source(tmp_path):
|
|
36
|
+
assert entry_script_for_mode("extreme") == "CFQUANT_LITE.py"
|
|
37
|
+
source = tmp_path / "CFQUANT_LITE.py"
|
|
38
|
+
source.write_text("from cfquant import __version__\n", encoding="utf-8")
|
|
39
|
+
with pytest.raises(ValueError, match="通用模式代码"):
|
|
40
|
+
read_entry_source(source)
|
|
41
|
+
|
|
42
|
+
|
|
34
43
|
def test_strategy_name_contains_mode_and_shortens_long_account():
|
|
35
44
|
name = _strategy_slot_name("ACCOUNT-12345678901234567890", "lite")
|
|
36
45
|
assert name == "CFQ_67890_LITE"
|
|
@@ -282,8 +282,9 @@ def test_save_and_delete_call_deployment_manager(web_config, monkeypatch):
|
|
|
282
282
|
class Manager:
|
|
283
283
|
def reconcile(self, accounts):
|
|
284
284
|
calls.append(("reconcile", set(accounts)))
|
|
285
|
-
def configure(self, row, identities, force=False):
|
|
285
|
+
def configure(self, row, identities, force=False, scripts_dir=None):
|
|
286
286
|
assert force is True
|
|
287
|
+
assert scripts_dir == os.path.join(web.BASE_DIR, "qmt_scripts")
|
|
287
288
|
calls.append(("configure", row["qmt_strategy"], identities))
|
|
288
289
|
return {"enabled": True, "targets": [{"state": "waiting_exit"}]}
|
|
289
290
|
monkeypatch.setattr(web, "QMT_STRATEGIES", Manager())
|
|
@@ -99,6 +99,7 @@ cfquant/tests/test_cftrader_web.py
|
|
|
99
99
|
cfquant/tests/test_credit_compat.py
|
|
100
100
|
cfquant/tests/test_json_serialization.py
|
|
101
101
|
cfquant/tests/test_lite_account_routing.py
|
|
102
|
+
cfquant/tests/test_lite_deployment_paths.py
|
|
102
103
|
cfquant/tests/test_local_trading_calendar.py
|
|
103
104
|
cfquant/tests/test_log_management.py
|
|
104
105
|
cfquant/tests/test_management.py
|