cfquant 0.2.38__tar.gz → 0.2.40__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.38 → cfquant-0.2.40}/PKG-INFO +1 -1
- {cfquant-0.2.38 → cfquant-0.2.40}/cfquant/__init__.py +14 -3
- cfquant-0.2.40/cfquant/build_info.py +65 -0
- cfquant-0.2.40/cfquant/callback_dispatcher.py +94 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/cfquant/client.py +57 -68
- {cfquant-0.2.38 → cfquant-0.2.40}/cfquant/pipe_client.py +63 -76
- {cfquant-0.2.38 → cfquant-0.2.40}/cfquant/qmt_bridge.py +18 -13
- cfquant-0.2.40/cfquant/tests/test_callback_requests.py +256 -0
- cfquant-0.2.40/cfquant/tests/test_order_time_callback.py +71 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/cfquant/tx_trade_bridge.py +17 -12
- {cfquant-0.2.38 → cfquant-0.2.40}/cfquant/version.py +2 -2
- {cfquant-0.2.38 → cfquant-0.2.40}/cfquant/xttrader.py +12 -3
- {cfquant-0.2.38 → cfquant-0.2.40}/cfquant/xttype.py +43 -4
- {cfquant-0.2.38 → cfquant-0.2.40}/cfquant.egg-info/PKG-INFO +1 -1
- {cfquant-0.2.38 → cfquant-0.2.40}/cfquant.egg-info/SOURCES.txt +4 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/cfquant_web_server.py +30 -14
- {cfquant-0.2.38 → cfquant-0.2.40}/pyproject.toml +1 -1
- {cfquant-0.2.38 → cfquant-0.2.40}/qmt_scripts/CFQUANT_LITE.py +27 -3
- {cfquant-0.2.38 → cfquant-0.2.40}/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 +27 -3
- {cfquant-0.2.38 → cfquant-0.2.40}/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 +27 -3
- {cfquant-0.2.38 → cfquant-0.2.40}/web_dashboard/app.js +142 -22
- {cfquant-0.2.38 → cfquant-0.2.40}/web_dashboard/index.html +12 -3
- {cfquant-0.2.38 → cfquant-0.2.40}/web_dashboard/styles.css +89 -33
- {cfquant-0.2.38 → cfquant-0.2.40}/LICENSE +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/LTtx/__init__.py +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/LTtx/tx/Config.txt +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/LTtx/tx/LTtx_server.py +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/LTtx/tx/__init__.py +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/LTtx/tx/tx.py +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/MANIFEST.in +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/README.md +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/cfquant/_editable_install.py +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/cfquant/account_routing.py +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/cfquant/batch_orders.py +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/cfquant/cftrader.py +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/cfquant/channels.py +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/cfquant/cli.py +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/cfquant/config.py +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/cfquant/hub.py +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/cfquant/level2.py +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/cfquant/log_management.py +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/cfquant/logging_i18n.py +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/cfquant/normal_bridge.py +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/cfquant/order_meta.py +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/cfquant/pipe_bridge.py +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/cfquant/pipe_hub.py +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/cfquant/pipe_transport.py +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/cfquant/protocol.py +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/cfquant/qmt_strategy_deploy.py +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/cfquant/qmt_strategy_package.py +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/cfquant/qmt_strategy_runtime.py +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/cfquant/runtime_report.py +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/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.38 → cfquant-0.2.40}/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.38 → cfquant-0.2.40}/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.38 → cfquant-0.2.40}/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.38 → cfquant-0.2.40}/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.38 → cfquant-0.2.40}/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.38 → cfquant-0.2.40}/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.38 → cfquant-0.2.40}/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.38 → cfquant-0.2.40}/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.38 → cfquant-0.2.40}/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.38 → cfquant-0.2.40}/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.38 → cfquant-0.2.40}/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.38 → cfquant-0.2.40}/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.38 → cfquant-0.2.40}/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.38 → cfquant-0.2.40}/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.38 → cfquant-0.2.40}/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.38 → cfquant-0.2.40}/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.38 → cfquant-0.2.40}/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.38 → cfquant-0.2.40}/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.38 → cfquant-0.2.40}/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.38 → cfquant-0.2.40}/cfquant/tests/29_Level2/350/241/214/346/203/205/346/265/213/350/257/225.py" +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/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.38 → cfquant-0.2.40}/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.38 → cfquant-0.2.40}/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.38 → cfquant-0.2.40}/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.38 → cfquant-0.2.40}/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.38 → cfquant-0.2.40}/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.38 → cfquant-0.2.40}/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.38 → cfquant-0.2.40}/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.38 → cfquant-0.2.40}/cfquant/tests/6_/345/233/236/350/260/203/346/265/213/350/257/225.py" +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/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.38 → cfquant-0.2.40}/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.38 → cfquant-0.2.40}/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.38 → cfquant-0.2.40}/cfquant/tests/_helpers.py +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/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.38 → cfquant-0.2.40}/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.38 → cfquant-0.2.40}/cfquant/tests/readme.md +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/cfquant/tests/test_api_tester_browser.py +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/cfquant/tests/test_api_tester_web.py +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/cfquant/tests/test_binding_delete_confirmation.py +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/cfquant/tests/test_cftrader.py +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/cfquant/tests/test_cftrader_tutorial.py +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/cfquant/tests/test_cftrader_web.py +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/cfquant/tests/test_credit_compat.py +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/cfquant/tests/test_json_serialization.py +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/cfquant/tests/test_log_management.py +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/cfquant/tests/test_management_batch.py +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/cfquant/tests/test_project_updater.py +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/cfquant/tests/test_python_api_reference.py +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/cfquant/tests/test_qmt_strategy_browser.py +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/cfquant/tests/test_qmt_strategy_deploy.py +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/cfquant/tests/test_qmt_strategy_web.py +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/cfquant/tests/test_tutorial_reader.py +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/cfquant/tests/test_tx_auto_import.py +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/cfquant/tx.py +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/cfquant/xtconstant.py +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/cfquant/xtdata.py +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/cfquant.egg-info/dependency_links.txt +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/cfquant.egg-info/entry_points.txt +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/cfquant.egg-info/requires.txt +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/cfquant.egg-info/top_level.txt +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/cfquant_pipe_hub.py +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/qmt_scripts/CFQUANT.py +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/qmt_scripts/CFQUANT_CTYPE_ALL_LOWLAT.py +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/qmt_scripts/CFQUANT_TRADE_LOWLAT.py +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/qmt_scripts/__init__.py +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/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.38 → cfquant-0.2.40}/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.38 → cfquant-0.2.40}/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.38 → cfquant-0.2.40}/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.38 → cfquant-0.2.40}/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.38 → cfquant-0.2.40}/requirements.txt +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/setup.cfg +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/web_dashboard/__init__.py +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/web_dashboard/api-tester.js +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/web_dashboard/avatars/amber-pulse.svg +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/web_dashboard/avatars/copper-grid.svg +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/web_dashboard/avatars/market-blue.svg +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/web_dashboard/avatars/rose-circuit.svg +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/web_dashboard/avatars/signal-green.svg +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/web_dashboard/avatars/slate-wave.svg +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/web_dashboard/avatars/teal-orbit.svg +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/web_dashboard/avatars/violet-node.svg +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/web_dashboard/cftrader-api-data.js +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/web_dashboard/favicon.svg +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/web_dashboard/image/qmt_deploy/image-10.png +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/web_dashboard/image/qmt_deploy/image-11.png +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/web_dashboard/image/qmt_deploy/image-12.png +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/web_dashboard/image/qmt_deploy/image-13.png +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/web_dashboard/image/qmt_deploy/image-14.png +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/web_dashboard/image/qmt_deploy/image-15.png +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/web_dashboard/image/qmt_deploy/image-16.png +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/web_dashboard/image/qmt_deploy/image-17.png +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/web_dashboard/image/qmt_deploy/image-18.png +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/web_dashboard/image/qmt_deploy/image-19.png +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/web_dashboard/image/qmt_deploy/image-20.png +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/web_dashboard/image/qmt_deploy/image-21.png +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/web_dashboard/image/qmt_deploy/image-4.png +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/web_dashboard/image/qmt_deploy/image-5.png +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/web_dashboard/image/qmt_deploy/image-6.png +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/web_dashboard/image/qmt_deploy/image-7.png +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/web_dashboard/image/qmt_deploy/image-8.png +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/web_dashboard/image/qmt_deploy/image-9.png +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/web_dashboard/python-api-data.js +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/web_dashboard/python-api.js +0 -0
- {cfquant-0.2.38 → cfquant-0.2.40}/web_dashboard/vendor/mermaid.min.js +0 -0
|
@@ -12,7 +12,13 @@ cfquant: 面向 QMT 的 xtquant 兼容层。
|
|
|
12
12
|
from . import xtconstant, xtdata, xttrader, xttype
|
|
13
13
|
from . import cftrader
|
|
14
14
|
from .client import CfquantError, CfquantTimeout, configure, get_client
|
|
15
|
-
from .version import __version__
|
|
15
|
+
from .version import __version__
|
|
16
|
+
from .build_info import build_identity, get_git_commit
|
|
17
|
+
|
|
18
|
+
_BUILD_IDENTITY = build_identity(__version__)
|
|
19
|
+
GIT_COMMIT = _BUILD_IDENTITY["git_commit"]
|
|
20
|
+
SHORT_GIT_COMMIT = _BUILD_IDENTITY["short_commit"]
|
|
21
|
+
BUILD_VERSION = _BUILD_IDENTITY["build_version"]
|
|
16
22
|
|
|
17
23
|
__all__ = [
|
|
18
24
|
"xtconstant",
|
|
@@ -24,5 +30,10 @@ __all__ = [
|
|
|
24
30
|
"get_client",
|
|
25
31
|
"CfquantError",
|
|
26
32
|
"CfquantTimeout",
|
|
27
|
-
"__version__",
|
|
28
|
-
|
|
33
|
+
"__version__",
|
|
34
|
+
"GIT_COMMIT",
|
|
35
|
+
"SHORT_GIT_COMMIT",
|
|
36
|
+
"BUILD_VERSION",
|
|
37
|
+
"get_git_commit",
|
|
38
|
+
"build_identity",
|
|
39
|
+
]
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""Build and source identity helpers."""
|
|
3
|
+
import os
|
|
4
|
+
import re
|
|
5
|
+
import subprocess
|
|
6
|
+
from functools import lru_cache
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
_COMMIT_RE = re.compile(r"^[0-9a-f]{40}$", re.IGNORECASE)
|
|
10
|
+
|
|
11
|
+
def _valid_commit(value):
|
|
12
|
+
value = str(value or "").strip().lower()
|
|
13
|
+
return value if _COMMIT_RE.fullmatch(value) else ""
|
|
14
|
+
|
|
15
|
+
def _read_git_dir(source_root):
|
|
16
|
+
marker = Path(source_root) / ".git"
|
|
17
|
+
if marker.is_dir():
|
|
18
|
+
return marker
|
|
19
|
+
if marker.is_file():
|
|
20
|
+
text = marker.read_text(encoding="utf-8", errors="replace").strip()
|
|
21
|
+
if text.lower().startswith("gitdir:"):
|
|
22
|
+
path = Path(text.split(":", 1)[1].strip())
|
|
23
|
+
return path if path.is_absolute() else (marker.parent / path).resolve()
|
|
24
|
+
return None
|
|
25
|
+
|
|
26
|
+
@lru_cache(maxsize=8)
|
|
27
|
+
def get_git_commit(source_root=None):
|
|
28
|
+
"""Return the full source commit, or an empty string when unavailable."""
|
|
29
|
+
override = _valid_commit(os.environ.get("CFQUANT_GIT_COMMIT"))
|
|
30
|
+
if override:
|
|
31
|
+
return override
|
|
32
|
+
root = Path(source_root or Path(__file__).resolve().parent.parent).resolve()
|
|
33
|
+
try:
|
|
34
|
+
result = subprocess.run(["git", "-C", str(root), "rev-parse", "HEAD"], stdout=subprocess.PIPE,
|
|
35
|
+
stderr=subprocess.DEVNULL, text=True, timeout=2, check=False)
|
|
36
|
+
commit = _valid_commit(result.stdout)
|
|
37
|
+
if commit:
|
|
38
|
+
return commit
|
|
39
|
+
except Exception:
|
|
40
|
+
pass
|
|
41
|
+
git_dir = _read_git_dir(root)
|
|
42
|
+
if not git_dir:
|
|
43
|
+
return ""
|
|
44
|
+
try:
|
|
45
|
+
head = (git_dir / "HEAD").read_text(encoding="ascii", errors="ignore").strip()
|
|
46
|
+
if head.startswith("ref: "):
|
|
47
|
+
ref = head[5:].strip()
|
|
48
|
+
ref_path = git_dir / ref
|
|
49
|
+
if ref_path.is_file():
|
|
50
|
+
return _valid_commit(ref_path.read_text(encoding="ascii", errors="ignore"))
|
|
51
|
+
packed = git_dir / "packed-refs"
|
|
52
|
+
if packed.is_file():
|
|
53
|
+
for line in packed.read_text(encoding="ascii", errors="ignore").splitlines():
|
|
54
|
+
parts = line.split()
|
|
55
|
+
if len(parts) == 2 and parts[1] == ref:
|
|
56
|
+
return _valid_commit(parts[0])
|
|
57
|
+
return _valid_commit(head)
|
|
58
|
+
except Exception:
|
|
59
|
+
return ""
|
|
60
|
+
|
|
61
|
+
def build_identity(version, source_root=None):
|
|
62
|
+
commit = get_git_commit(source_root)
|
|
63
|
+
short = commit[:7] if commit else ""
|
|
64
|
+
return {"git_commit": commit, "short_commit": short,
|
|
65
|
+
"build_version": "%s+g%s" % (version, short) if version and short else str(version or "")}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""Serial user callbacks, independent of the RPC receive thread."""
|
|
3
|
+
from collections import deque
|
|
4
|
+
import logging
|
|
5
|
+
import threading
|
|
6
|
+
|
|
7
|
+
from .protocol import decode_value
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class CallbackDispatcher(object):
|
|
11
|
+
def __init__(self, dispatch):
|
|
12
|
+
self._dispatch = dispatch
|
|
13
|
+
self._condition = threading.Condition()
|
|
14
|
+
self._queue = deque()
|
|
15
|
+
self._generation = 0
|
|
16
|
+
self._active = False
|
|
17
|
+
self._thread = None
|
|
18
|
+
self._context = threading.local()
|
|
19
|
+
|
|
20
|
+
def start(self):
|
|
21
|
+
with self._condition:
|
|
22
|
+
self._generation += 1
|
|
23
|
+
self._active = True
|
|
24
|
+
self._queue.clear()
|
|
25
|
+
return self._generation
|
|
26
|
+
|
|
27
|
+
def stop(self):
|
|
28
|
+
with self._condition:
|
|
29
|
+
self._active = False
|
|
30
|
+
self._generation += 1
|
|
31
|
+
self._queue.clear()
|
|
32
|
+
self._condition.notify_all()
|
|
33
|
+
# Never join: close/stop may be called by a callback itself.
|
|
34
|
+
|
|
35
|
+
def submit(self, generation, message):
|
|
36
|
+
with self._condition:
|
|
37
|
+
if not self._active or generation != self._generation:
|
|
38
|
+
return
|
|
39
|
+
# Do not block the receive thread on a slow user callback. Trade
|
|
40
|
+
# events cannot be silently dropped or coalesced.
|
|
41
|
+
self._queue.append((generation, message))
|
|
42
|
+
if self._thread is None:
|
|
43
|
+
self._thread = threading.Thread(target=self._run, name="cfquant-event-callback")
|
|
44
|
+
self._thread.daemon = True
|
|
45
|
+
self._thread.start()
|
|
46
|
+
self._condition.notify()
|
|
47
|
+
|
|
48
|
+
def is_current_callback(self):
|
|
49
|
+
generation = getattr(self._context, "generation", None)
|
|
50
|
+
with self._condition:
|
|
51
|
+
return generation is None or (self._active and generation == self._generation)
|
|
52
|
+
|
|
53
|
+
def check_request(self):
|
|
54
|
+
if not self.is_current_callback():
|
|
55
|
+
raise RuntimeError("callback belongs to a closed cfquant connection")
|
|
56
|
+
|
|
57
|
+
def _run(self):
|
|
58
|
+
while True:
|
|
59
|
+
with self._condition:
|
|
60
|
+
while self._active and not self._queue:
|
|
61
|
+
self._condition.wait()
|
|
62
|
+
if not self._active:
|
|
63
|
+
self._thread = None
|
|
64
|
+
return
|
|
65
|
+
generation, message = self._queue.popleft()
|
|
66
|
+
self._context.generation = generation
|
|
67
|
+
try:
|
|
68
|
+
if self.is_current_callback():
|
|
69
|
+
self._dispatch(message)
|
|
70
|
+
except Exception:
|
|
71
|
+
logging.getLogger(__name__).exception("callback dispatch failed event=%s", message.get("event"))
|
|
72
|
+
finally:
|
|
73
|
+
del self._context.generation
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def dispatch_callbacks(callbacks, message, is_current):
|
|
77
|
+
event = message.get("event")
|
|
78
|
+
data = decode_value(message.get("data"))
|
|
79
|
+
full_message = dict(message, data=data)
|
|
80
|
+
deliveries = [(list(callbacks.get("__event__", [])), full_message),
|
|
81
|
+
(list(callbacks.get(event, [])), data)]
|
|
82
|
+
if event and event.startswith("quote:"):
|
|
83
|
+
quote_message = dict(full_message)
|
|
84
|
+
if quote_message.get("subscription_id") is not None and quote_message.get("subscribe_id") is None:
|
|
85
|
+
quote_message["subscribe_id"] = quote_message["subscription_id"]
|
|
86
|
+
deliveries.append((list(callbacks.get("quote", [])), quote_message))
|
|
87
|
+
for handlers, payload in deliveries:
|
|
88
|
+
for callback in handlers:
|
|
89
|
+
if not is_current():
|
|
90
|
+
return
|
|
91
|
+
try:
|
|
92
|
+
callback(payload)
|
|
93
|
+
except Exception:
|
|
94
|
+
logging.getLogger(__name__).exception("user callback failed event=%s", event)
|
|
@@ -6,6 +6,7 @@ import threading
|
|
|
6
6
|
import time
|
|
7
7
|
|
|
8
8
|
from .config import get_config
|
|
9
|
+
from .callback_dispatcher import CallbackDispatcher, dispatch_callbacks
|
|
9
10
|
from .protocol import decode_value, dumps_message, loads_message, new_id, pack_request
|
|
10
11
|
|
|
11
12
|
|
|
@@ -114,9 +115,11 @@ class LTtxRpcClient(object):
|
|
|
114
115
|
self._pending_lock = threading.RLock()
|
|
115
116
|
self._started = False
|
|
116
117
|
self._recv_thread = None
|
|
118
|
+
self._event_dispatcher = CallbackDispatcher(self._dispatch_event)
|
|
117
119
|
|
|
118
120
|
def start(self):
|
|
119
121
|
with self._lock:
|
|
122
|
+
self._event_dispatcher.check_request()
|
|
120
123
|
if self._started:
|
|
121
124
|
return
|
|
122
125
|
txl = self._load_txl()
|
|
@@ -124,12 +127,14 @@ class LTtxRpcClient(object):
|
|
|
124
127
|
self._tx.start_tx()
|
|
125
128
|
self._tx.start_txg(self.client_id)
|
|
126
129
|
self._started = True
|
|
127
|
-
|
|
130
|
+
generation = self._event_dispatcher.start()
|
|
131
|
+
self._recv_thread = threading.Thread(target=self._recv_loop, args=(self._tx, generation))
|
|
128
132
|
self._recv_thread.daemon = True
|
|
129
133
|
self._recv_thread.start()
|
|
130
134
|
|
|
131
135
|
def close(self):
|
|
132
136
|
with self._lock:
|
|
137
|
+
self._event_dispatcher.stop()
|
|
133
138
|
self._started = False
|
|
134
139
|
tx = self._tx
|
|
135
140
|
self._tx = None
|
|
@@ -150,39 +155,40 @@ class LTtxRpcClient(object):
|
|
|
150
155
|
pass
|
|
151
156
|
self._pending.clear()
|
|
152
157
|
|
|
153
|
-
def request(self, action, params=None, timeout=None, request_channel=None):
|
|
154
|
-
effective_timeout = float(timeout or self.timeout)
|
|
155
|
-
request_id = new_id("req")
|
|
156
|
-
raw = pack_request(
|
|
157
|
-
action,
|
|
158
|
-
params=params,
|
|
158
|
+
def request(self, action, params=None, timeout=None, request_channel=None):
|
|
159
|
+
effective_timeout = float(timeout or self.timeout)
|
|
160
|
+
request_id = new_id("req")
|
|
161
|
+
raw = pack_request(
|
|
162
|
+
action,
|
|
163
|
+
params=params,
|
|
159
164
|
reply_channel=self.reply_channel,
|
|
160
165
|
client_id=self.client_id,
|
|
161
166
|
request_id=request_id,
|
|
162
|
-
timeout=effective_timeout,
|
|
163
|
-
)
|
|
164
|
-
self.start()
|
|
167
|
+
timeout=effective_timeout,
|
|
168
|
+
)
|
|
165
169
|
q = queue.Queue(maxsize=1)
|
|
166
|
-
with self._pending_lock:
|
|
167
|
-
self._pending[request_id] = q
|
|
168
170
|
try:
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
171
|
+
with self._lock:
|
|
172
|
+
self.start()
|
|
173
|
+
with self._pending_lock:
|
|
174
|
+
self._pending[request_id] = q
|
|
175
|
+
self._push("request", raw, request_channel or self.request_channel)
|
|
176
|
+
try:
|
|
177
|
+
msg = q.get(timeout=effective_timeout)
|
|
178
|
+
except queue.Empty:
|
|
179
|
+
raise CfquantTimeout("cfquant request timeout: %s" % action)
|
|
180
|
+
if not msg.get("ok"):
|
|
181
|
+
err = msg.get("error") or {}
|
|
182
|
+
raise CfquantError(err.get("message") or str(err))
|
|
183
|
+
return decode_value(msg.get("result"))
|
|
184
|
+
finally:
|
|
185
|
+
with self._pending_lock:
|
|
186
|
+
self._pending.pop(request_id, None)
|
|
187
|
+
|
|
188
|
+
def publish_event(self, channel, payload):
|
|
189
|
+
raw = dumps_message(payload)
|
|
190
|
+
self.start()
|
|
191
|
+
self._push("event", raw, channel)
|
|
186
192
|
|
|
187
193
|
def add_callback(self, event, callback):
|
|
188
194
|
if callback is None:
|
|
@@ -194,11 +200,11 @@ class LTtxRpcClient(object):
|
|
|
194
200
|
if callback in callbacks:
|
|
195
201
|
callbacks.remove(callback)
|
|
196
202
|
|
|
197
|
-
def _recv_loop(self):
|
|
203
|
+
def _recv_loop(self, expected_tx, generation):
|
|
198
204
|
while self._started:
|
|
199
205
|
try:
|
|
200
|
-
tx =
|
|
201
|
-
if tx is None:
|
|
206
|
+
tx = expected_tx
|
|
207
|
+
if tx is None or tx is not self._tx:
|
|
202
208
|
break
|
|
203
209
|
raw = tx.Q.get()
|
|
204
210
|
if raw is None:
|
|
@@ -206,44 +212,27 @@ class LTtxRpcClient(object):
|
|
|
206
212
|
msg = loads_message(raw)
|
|
207
213
|
if not msg:
|
|
208
214
|
continue
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
if
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
215
|
+
with self._lock:
|
|
216
|
+
if not self._started or tx is not self._tx:
|
|
217
|
+
return
|
|
218
|
+
msg_type = msg.get("type")
|
|
219
|
+
if msg_type == "response":
|
|
220
|
+
with self._pending_lock:
|
|
221
|
+
q = self._pending.pop(msg.get("id"), None)
|
|
222
|
+
if q:
|
|
223
|
+
q.put(msg)
|
|
224
|
+
elif msg_type == "event":
|
|
225
|
+
self._event_dispatcher.submit(generation, msg)
|
|
226
|
+
except Exception:
|
|
227
|
+
time.sleep(0.05)
|
|
228
|
+
if not self._started:
|
|
229
|
+
break
|
|
230
|
+
with self._lock:
|
|
231
|
+
if self._tx is expected_tx:
|
|
232
|
+
self.close()
|
|
221
233
|
|
|
222
234
|
def _dispatch_event(self, msg):
|
|
223
|
-
|
|
224
|
-
data = decode_value(msg.get("data"))
|
|
225
|
-
full_msg = dict(msg)
|
|
226
|
-
full_msg["data"] = data
|
|
227
|
-
for callback in list(self._callbacks.get("__event__", [])):
|
|
228
|
-
try:
|
|
229
|
-
callback(full_msg)
|
|
230
|
-
except Exception:
|
|
231
|
-
pass
|
|
232
|
-
for callback in list(self._callbacks.get(event, [])):
|
|
233
|
-
try:
|
|
234
|
-
callback(data)
|
|
235
|
-
except Exception:
|
|
236
|
-
pass
|
|
237
|
-
if event and event.startswith("quote:"):
|
|
238
|
-
quote_msg = dict(msg)
|
|
239
|
-
quote_msg["data"] = data
|
|
240
|
-
if quote_msg.get("subscription_id") is not None and quote_msg.get("subscribe_id") is None:
|
|
241
|
-
quote_msg["subscribe_id"] = quote_msg.get("subscription_id")
|
|
242
|
-
for callback in list(self._callbacks.get("quote", [])):
|
|
243
|
-
try:
|
|
244
|
-
callback(quote_msg)
|
|
245
|
-
except Exception:
|
|
246
|
-
pass
|
|
235
|
+
dispatch_callbacks(self._callbacks, msg, self._event_dispatcher.is_current_callback)
|
|
247
236
|
|
|
248
237
|
def _push(self, key, payload, channel):
|
|
249
238
|
tx = self._tx
|
|
@@ -3,6 +3,7 @@ import queue
|
|
|
3
3
|
import threading
|
|
4
4
|
|
|
5
5
|
from .config import get_config
|
|
6
|
+
from .callback_dispatcher import CallbackDispatcher, dispatch_callbacks
|
|
6
7
|
from .pipe_transport import (
|
|
7
8
|
DEFAULT_PIPE_NAME,
|
|
8
9
|
connect_pipe,
|
|
@@ -34,9 +35,11 @@ class PipeRpcClient(object):
|
|
|
34
35
|
self._pending_lock = threading.RLock()
|
|
35
36
|
self._started = False
|
|
36
37
|
self._recv_thread = None
|
|
38
|
+
self._event_dispatcher = CallbackDispatcher(self._dispatch_event)
|
|
37
39
|
|
|
38
40
|
def start(self):
|
|
39
41
|
with self._lock:
|
|
42
|
+
self._event_dispatcher.check_request()
|
|
40
43
|
if self._started and self._recv_thread is not None and self._recv_thread.is_alive():
|
|
41
44
|
return
|
|
42
45
|
if self._started:
|
|
@@ -55,57 +58,60 @@ class PipeRpcClient(object):
|
|
|
55
58
|
"client_id": self.client_id,
|
|
56
59
|
}))
|
|
57
60
|
self._started = True
|
|
58
|
-
|
|
61
|
+
generation = self._event_dispatcher.start()
|
|
62
|
+
self._recv_thread = threading.Thread(target=self._recv_loop, args=(self._rx_conn, generation))
|
|
59
63
|
self._recv_thread.daemon = True
|
|
60
64
|
self._recv_thread.start()
|
|
61
65
|
|
|
62
66
|
def close(self):
|
|
63
67
|
with self._lock:
|
|
64
|
-
self.
|
|
65
|
-
self.
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
68
|
+
self._event_dispatcher.stop()
|
|
69
|
+
self._started = False
|
|
70
|
+
self._close_conns_locked()
|
|
71
|
+
self._fail_pending("cfquant pipe client closed")
|
|
72
|
+
|
|
73
|
+
def request(self, action, params=None, timeout=None, request_channel=None):
|
|
74
|
+
effective_timeout = float(timeout or self.timeout)
|
|
75
|
+
request_id = new_id("req")
|
|
76
|
+
raw = pack_request(
|
|
77
|
+
action,
|
|
78
|
+
params=params,
|
|
74
79
|
reply_channel=self.reply_channel,
|
|
75
80
|
client_id=self.client_id,
|
|
76
81
|
request_id=request_id,
|
|
77
|
-
timeout=effective_timeout,
|
|
78
|
-
)
|
|
79
|
-
self.start()
|
|
82
|
+
timeout=effective_timeout,
|
|
83
|
+
)
|
|
80
84
|
q = queue.Queue(maxsize=1)
|
|
81
|
-
with self._pending_lock:
|
|
82
|
-
self._pending[request_id] = q
|
|
83
85
|
try:
|
|
84
|
-
|
|
85
|
-
self.
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
86
|
+
with self._lock:
|
|
87
|
+
self.start()
|
|
88
|
+
with self._pending_lock:
|
|
89
|
+
self._pending[request_id] = q
|
|
90
|
+
try:
|
|
91
|
+
self._send_request(raw, request_channel or self.request_channel)
|
|
92
|
+
except Exception:
|
|
93
|
+
self.close()
|
|
94
|
+
raise
|
|
95
|
+
try:
|
|
96
|
+
msg = q.get(timeout=effective_timeout)
|
|
97
|
+
except queue.Empty:
|
|
98
|
+
from .client import CfquantTimeout
|
|
99
|
+
|
|
100
|
+
raise CfquantTimeout("cfquant pipe request timeout: %s" % action)
|
|
101
|
+
if not msg.get("ok"):
|
|
102
|
+
err = msg.get("error") or {}
|
|
103
|
+
from .client import CfquantError
|
|
104
|
+
|
|
105
|
+
raise CfquantError(err.get("message") or str(err))
|
|
106
|
+
return decode_value(msg.get("result"))
|
|
107
|
+
finally:
|
|
108
|
+
with self._pending_lock:
|
|
109
|
+
self._pending.pop(request_id, None)
|
|
110
|
+
|
|
111
|
+
def publish_event(self, channel, payload):
|
|
112
|
+
raw = dumps_message(payload)
|
|
113
|
+
self.start()
|
|
114
|
+
self._send_request(raw, channel)
|
|
109
115
|
|
|
110
116
|
def add_callback(self, event, callback):
|
|
111
117
|
if callback is None:
|
|
@@ -131,7 +137,7 @@ class PipeRpcClient(object):
|
|
|
131
137
|
"payload": payload,
|
|
132
138
|
}))
|
|
133
139
|
|
|
134
|
-
def _recv_loop(self, expected_conn):
|
|
140
|
+
def _recv_loop(self, expected_conn, generation):
|
|
135
141
|
disconnect_message = "cfquant pipe connection closed"
|
|
136
142
|
while True:
|
|
137
143
|
try:
|
|
@@ -151,14 +157,17 @@ class PipeRpcClient(object):
|
|
|
151
157
|
msg = loads_message(payload)
|
|
152
158
|
if not msg:
|
|
153
159
|
continue
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
if
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
160
|
+
with self._lock:
|
|
161
|
+
if not self._started or self._rx_conn is not expected_conn:
|
|
162
|
+
return
|
|
163
|
+
msg_type = msg.get("type")
|
|
164
|
+
if msg_type == "response":
|
|
165
|
+
with self._pending_lock:
|
|
166
|
+
q = self._pending.pop(msg.get("id"), None)
|
|
167
|
+
if q:
|
|
168
|
+
q.put(msg)
|
|
169
|
+
elif msg_type == "event":
|
|
170
|
+
self._event_dispatcher.submit(generation, msg)
|
|
162
171
|
except Exception as e:
|
|
163
172
|
disconnect_message = "cfquant pipe receive failed: %s" % e
|
|
164
173
|
break
|
|
@@ -171,8 +180,9 @@ class PipeRpcClient(object):
|
|
|
171
180
|
if not self._started and self._rx_conn is None and self._tx_conn is None:
|
|
172
181
|
return
|
|
173
182
|
self._started = False
|
|
174
|
-
self.
|
|
175
|
-
|
|
183
|
+
self._event_dispatcher.stop()
|
|
184
|
+
self._close_conns_locked()
|
|
185
|
+
self._fail_pending(message)
|
|
176
186
|
|
|
177
187
|
def _close_conns_locked(self):
|
|
178
188
|
conns = [self._rx_conn, self._tx_conn]
|
|
@@ -197,27 +207,4 @@ class PipeRpcClient(object):
|
|
|
197
207
|
pass
|
|
198
208
|
|
|
199
209
|
def _dispatch_event(self, msg):
|
|
200
|
-
|
|
201
|
-
data = decode_value(msg.get("data"))
|
|
202
|
-
full_msg = dict(msg)
|
|
203
|
-
full_msg["data"] = data
|
|
204
|
-
for callback in list(self._callbacks.get("__event__", [])):
|
|
205
|
-
try:
|
|
206
|
-
callback(full_msg)
|
|
207
|
-
except Exception:
|
|
208
|
-
pass
|
|
209
|
-
for callback in list(self._callbacks.get(event, [])):
|
|
210
|
-
try:
|
|
211
|
-
callback(data)
|
|
212
|
-
except Exception:
|
|
213
|
-
pass
|
|
214
|
-
if event and event.startswith("quote:"):
|
|
215
|
-
quote_msg = dict(msg)
|
|
216
|
-
quote_msg["data"] = data
|
|
217
|
-
if quote_msg.get("subscription_id") is not None and quote_msg.get("subscribe_id") is None:
|
|
218
|
-
quote_msg["subscribe_id"] = quote_msg.get("subscription_id")
|
|
219
|
-
for callback in list(self._callbacks.get("quote", [])):
|
|
220
|
-
try:
|
|
221
|
-
callback(quote_msg)
|
|
222
|
-
except Exception:
|
|
223
|
-
pass
|
|
210
|
+
dispatch_callbacks(self._callbacks, msg, self._event_dispatcher.is_current_callback)
|
|
@@ -18,7 +18,7 @@ from .level2 import (
|
|
|
18
18
|
require_l2_callable,
|
|
19
19
|
thousand_price,
|
|
20
20
|
)
|
|
21
|
-
from .xttype import filter_cancelable_orders
|
|
21
|
+
from .xttype import _is_zero_time_value, filter_cancelable_orders
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
class CfquantQmtBridge(object):
|
|
@@ -1111,23 +1111,26 @@ class CfquantQmtBridge(object):
|
|
|
1111
1111
|
"order_time": self._first_value(obj, (
|
|
1112
1112
|
"time",
|
|
1113
1113
|
"order_time",
|
|
1114
|
-
"entrust_time",
|
|
1114
|
+
"entrust_time",
|
|
1115
1115
|
"insert_time",
|
|
1116
1116
|
"m_strOrderTime",
|
|
1117
1117
|
"m_strEntrustTime",
|
|
1118
1118
|
"m_strInsertTime",
|
|
1119
|
-
"m_nOrderTime",
|
|
1120
|
-
"m_nEntrustTime",
|
|
1121
|
-
"m_nInsertTime",
|
|
1122
|
-
)),
|
|
1119
|
+
"m_nOrderTime",
|
|
1120
|
+
"m_nEntrustTime",
|
|
1121
|
+
"m_nInsertTime",
|
|
1122
|
+
), skip_zero=True),
|
|
1123
1123
|
"order_date": self._first_value(obj, (
|
|
1124
1124
|
"order_date",
|
|
1125
1125
|
"entrust_date",
|
|
1126
|
+
"insert_date",
|
|
1126
1127
|
"m_strOrderDate",
|
|
1127
1128
|
"m_strEntrustDate",
|
|
1129
|
+
"m_strInsertDate",
|
|
1128
1130
|
"m_strTradingDay",
|
|
1129
1131
|
"m_nOrderDate",
|
|
1130
1132
|
"m_nEntrustDate",
|
|
1133
|
+
"m_nInsertDate",
|
|
1131
1134
|
)),
|
|
1132
1135
|
"direction": self._get_value(obj, "m_nDirection"),
|
|
1133
1136
|
"offset_flag": self._get_value(obj, "m_nOffsetFlag"),
|
|
@@ -1205,7 +1208,7 @@ class CfquantQmtBridge(object):
|
|
|
1205
1208
|
"m_strDealTime",
|
|
1206
1209
|
"m_nTradeTime",
|
|
1207
1210
|
"m_nDealTime",
|
|
1208
|
-
)),
|
|
1211
|
+
), skip_zero=True),
|
|
1209
1212
|
"trade_date": self._first_value(obj, (
|
|
1210
1213
|
"trade_date",
|
|
1211
1214
|
"deal_date",
|
|
@@ -1377,12 +1380,14 @@ class CfquantQmtBridge(object):
|
|
|
1377
1380
|
text = " ".join(str(value or "") for value in values).strip().lower()
|
|
1378
1381
|
return "cfquant" if "cfquant" in text else "other"
|
|
1379
1382
|
|
|
1380
|
-
def _first_value(self, obj, names):
|
|
1381
|
-
for name in names:
|
|
1382
|
-
value = self._get_value(obj, name)
|
|
1383
|
-
if value is not None and value != "":
|
|
1384
|
-
|
|
1385
|
-
|
|
1383
|
+
def _first_value(self, obj, names, skip_zero=False):
|
|
1384
|
+
for name in names:
|
|
1385
|
+
value = self._get_value(obj, name)
|
|
1386
|
+
if value is not None and value != "":
|
|
1387
|
+
if skip_zero and _is_zero_time_value(value):
|
|
1388
|
+
continue
|
|
1389
|
+
return value
|
|
1390
|
+
return None
|
|
1386
1391
|
|
|
1387
1392
|
def _object_to_dict(self, obj):
|
|
1388
1393
|
if hasattr(obj, "items"):
|