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