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