cfquant 0.2.67__tar.gz → 0.2.68__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.67 → cfquant-0.2.68}/PKG-INFO +1 -1
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/__init__.py +2 -1
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/client.py +2 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/monitor_notifications.py +3 -30
- cfquant-0.2.68/cfquant/notify.py +52 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/qmt_bridge.py +105 -30
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/qmt_python_runtime.py +34 -10
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_binding_python_runtime_browser.py +9 -2
- cfquant-0.2.68/cfquant/tests/test_binding_webhook_browser.py +106 -0
- cfquant-0.2.68/cfquant/tests/test_history_download_memory.py +68 -0
- cfquant-0.2.68/cfquant/tests/test_notify.py +214 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_qmt_python_runtime.py +41 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_trading_monitor.py +57 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_trading_monitor_browser.py +73 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/trading_monitor.py +16 -4
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tx_trade_bridge.py +109 -30
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/version.py +2 -2
- cfquant-0.2.68/cfquant/webhook.py +151 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/xtdata.py +2 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant.egg-info/PKG-INFO +1 -1
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant.egg-info/SOURCES.txt +6 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant_web_server.py +99 -6
- {cfquant-0.2.67 → cfquant-0.2.68}/pyproject.toml +1 -1
- {cfquant-0.2.67 → cfquant-0.2.68}/qmt_scripts/CFQUANT_LITE.py +108 -31
- {cfquant-0.2.67 → cfquant-0.2.68}/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 +108 -31
- {cfquant-0.2.67 → cfquant-0.2.68}/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 +108 -31
- {cfquant-0.2.67 → cfquant-0.2.68}/web_dashboard/api-tester.js +1 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/web_dashboard/app.js +148 -11
- {cfquant-0.2.67 → cfquant-0.2.68}/web_dashboard/index.html +44 -5
- cfquant-0.2.68/web_dashboard/notify-api-data.js +38 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/web_dashboard/python-api.js +4 -4
- {cfquant-0.2.67 → cfquant-0.2.68}/web_dashboard/styles.css +8 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/web_dashboard/trading-monitor.js +106 -16
- {cfquant-0.2.67 → cfquant-0.2.68}/LICENSE +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/LTtx/__init__.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/LTtx/tx/Config.txt +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/LTtx/tx/LTtx_server.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/LTtx/tx/__init__.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/LTtx/tx/tx.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/MANIFEST.in +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/README.md +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/_editable_install.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/account_routing.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/algo.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/algo_broker.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/batch_orders.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/build_info.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/callback_dispatcher.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/cftrader.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/channels.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/cli.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/conditional_orders.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/config.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/diagnostic_context.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/diagnostics.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/flow_control.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/hub.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/level2.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/log_management.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/logging_i18n.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/management.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/monitor_performance.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/normal_bridge.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/order_identity.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/order_meta.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/pipe_bridge.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/pipe_client.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/pipe_hub.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/pipe_transport.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/planning_broker.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/position_sizing.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/protocol.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/qmt_binding_defaults.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/qmt_binding_settings.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/qmt_component_agent.js +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/qmt_component_login.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/qmt_credentials.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/qmt_dependencies.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/qmt_inner.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/qmt_inner_catalog.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/qmt_inner_runtime.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/qmt_mac_agent.js +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/qmt_native_files.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/qmt_native_worker.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/qmt_pipe_runtime.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/qmt_strategy_agent.js +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/qmt_strategy_deploy.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/qmt_strategy_package.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/qmt_strategy_recovery.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/qmt_strategy_runtime.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/qmt_update.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/qmt_vm_agent.js +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/qmt_vm_discovery.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/qmt_vm_runtime.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/qmt_vm_settings.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/qmt_vm_worker.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/quote_history.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/quote_leases.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/quote_service.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/remote_adapter.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/remote_management.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/remote_supervisor.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/rpc_timing.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/runtime_report.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/shared_quotes.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/smart_orders.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/stock_connect.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/subscription_bridge.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/subscription_client.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/subscription_manager.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/subscription_store.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/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.67 → cfquant-0.2.68}/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.67 → cfquant-0.2.68}/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.67 → cfquant-0.2.68}/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.67 → cfquant-0.2.68}/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.67 → cfquant-0.2.68}/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.67 → cfquant-0.2.68}/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.67 → cfquant-0.2.68}/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.67 → cfquant-0.2.68}/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.67 → cfquant-0.2.68}/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.67 → cfquant-0.2.68}/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.67 → cfquant-0.2.68}/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.67 → cfquant-0.2.68}/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.67 → cfquant-0.2.68}/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.67 → cfquant-0.2.68}/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.67 → cfquant-0.2.68}/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.67 → cfquant-0.2.68}/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.67 → cfquant-0.2.68}/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.67 → cfquant-0.2.68}/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.67 → cfquant-0.2.68}/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.67 → cfquant-0.2.68}/cfquant/tests/29_Level2/350/241/214/346/203/205/346/265/213/350/257/225.py" +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/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.67 → cfquant-0.2.68}/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.67 → cfquant-0.2.68}/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.67 → cfquant-0.2.68}/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.67 → cfquant-0.2.68}/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.67 → cfquant-0.2.68}/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.67 → cfquant-0.2.68}/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.67 → cfquant-0.2.68}/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.67 → cfquant-0.2.68}/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.67 → cfquant-0.2.68}/cfquant/tests/6_/345/233/236/350/260/203/346/265/213/350/257/225.py" +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/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.67 → cfquant-0.2.68}/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.67 → cfquant-0.2.68}/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.67 → cfquant-0.2.68}/cfquant/tests/_helpers.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/qmt_vm_native_fixture.js +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/readme.md +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_account_query_timing.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_account_subscription_probe.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_activity_log_browser.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_advanced_parity.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_algo.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_algo_broker.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_api_tester_browser.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_api_tester_web.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_auto_login_app.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_binding_delete_confirmation.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_binding_live_refresh_browser.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_binding_runtime_browser.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_binding_vm_browser.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_callback_recovery.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_callback_requests.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_cftrader.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_cftrader_tutorial.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_cftrader_web.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_credit_compat.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_dataframe_encoding.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_diagnostic_context.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_json_serialization.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_latency_flow_control.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_lite_account_routing.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_lite_deployment_paths.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_lite_parity.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_lite_qmt_dispatch.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_local_trading_calendar.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_log_management.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_management.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_management_batch.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_market_data_fallback.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_market_timing.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_monitor_notifications.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_monitor_performance.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_navigation_browser.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_order_identity.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_order_time_callback.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_pipe_shutdown.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_project_updater.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_python_api_reference.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_qmt_auto_login_browser.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_qmt_auto_login_integration.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_qmt_binding_defaults.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_qmt_binding_settings.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_qmt_component_login.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_qmt_dependencies.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_qmt_fileless.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_qmt_inner.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_qmt_inner_reference.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_qmt_inner_web.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_qmt_native_files.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_qmt_native_reuse.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_qmt_native_worker.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_qmt_package_import.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_qmt_strategy_browser.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_qmt_strategy_capabilities.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_qmt_strategy_deploy.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_qmt_strategy_recovery.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_qmt_strategy_web.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_qmt_update.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_qmt_vm_discovery.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_qmt_vm_native.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_qmt_vm_runtime.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_qmt_vm_settings.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_quote_latency.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_quote_latency_encoding.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_quote_service.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_remote_management.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_remote_management_browser.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_remote_version_check.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_setup_qmt_prompt.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_shared_quotes.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_shared_quotes_generated.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_smart_orders.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_smart_orders_api.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_smart_orders_docs.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_split_order_identity.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_startup_recovery.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_stock_connect.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_stock_connect_browser.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_strategy_recovery_api.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_subscription_congestion.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_subscription_persistence.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_table_conversion.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_trade_strategy_callback.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_trader_subscription_recovery.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_trading_dates.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_tutorial_reader.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_tx_auto_import.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_tx_shutdown.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_version_check.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tests/test_version_check_browser.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tools/THIRD_PARTY.md +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tools/upx/COPYING +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tools/upx/LICENSE +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tools/upx/NEWS +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tools/upx/README +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tools/upx/THANKS.txt +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tools/upx/upx-doc.html +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tools/upx/upx-doc.txt +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tools/upx/upx.1 +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tools/upx/upx.exe +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/trading_calendar.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/tx.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/xtconstant.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/xttrader.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant/xttype.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant.egg-info/dependency_links.txt +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant.egg-info/entry_points.txt +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant.egg-info/requires.txt +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant.egg-info/top_level.txt +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/cfquant_pipe_hub.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/qmt_scripts/CFQUANT.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/qmt_scripts/CFQUANT_CTYPE_ALL_LOWLAT.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/qmt_scripts/CFQUANT_TRADE_LOWLAT.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/qmt_scripts/__init__.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/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.67 → cfquant-0.2.68}/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.67 → cfquant-0.2.68}/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.67 → cfquant-0.2.68}/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.67 → cfquant-0.2.68}/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.67 → cfquant-0.2.68}/requirements.txt +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/setup.cfg +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/web_dashboard/__init__.py +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/web_dashboard/activity-log.js +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/web_dashboard/avatars/amber-pulse.svg +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/web_dashboard/avatars/copper-grid.svg +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/web_dashboard/avatars/market-blue.svg +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/web_dashboard/avatars/rose-circuit.svg +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/web_dashboard/avatars/signal-green.svg +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/web_dashboard/avatars/slate-wave.svg +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/web_dashboard/avatars/teal-orbit.svg +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/web_dashboard/avatars/violet-node.svg +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/web_dashboard/cfquant-architecture.html +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/web_dashboard/cftrader-api-data.js +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/web_dashboard/docs.js +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/web_dashboard/favicon.svg +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/web_dashboard/image/qmt_deploy/image-10.png +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/web_dashboard/image/qmt_deploy/image-11.png +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/web_dashboard/image/qmt_deploy/image-12.png +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/web_dashboard/image/qmt_deploy/image-13.png +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/web_dashboard/image/qmt_deploy/image-14.png +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/web_dashboard/image/qmt_deploy/image-15.png +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/web_dashboard/image/qmt_deploy/image-16.png +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/web_dashboard/image/qmt_deploy/image-17.png +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/web_dashboard/image/qmt_deploy/image-18.png +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/web_dashboard/image/qmt_deploy/image-19.png +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/web_dashboard/image/qmt_deploy/image-20.png +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/web_dashboard/image/qmt_deploy/image-21.png +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/web_dashboard/image/qmt_deploy/image-4.png +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/web_dashboard/image/qmt_deploy/image-5.png +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/web_dashboard/image/qmt_deploy/image-6.png +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/web_dashboard/image/qmt_deploy/image-7.png +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/web_dashboard/image/qmt_deploy/image-8.png +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/web_dashboard/image/qmt_deploy/image-9.png +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/web_dashboard/navigation.js +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/web_dashboard/python-api-data.js +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/web_dashboard/qmt-inner-api-data.js +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/web_dashboard/remote-management.js +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/web_dashboard/smart-order-api-data.js +0 -0
- {cfquant-0.2.67 → cfquant-0.2.68}/web_dashboard/vendor/mermaid.min.js +0 -0
|
@@ -12,7 +12,7 @@ cfquant: 面向 QMT 的 xtquant 兼容层。
|
|
|
12
12
|
import sys as _sys
|
|
13
13
|
|
|
14
14
|
from . import xtconstant, xtdata, xttrader, xttype
|
|
15
|
-
from . import cftrader
|
|
15
|
+
from . import cftrader, notify
|
|
16
16
|
from .qmt_inner import QmtInnerApi
|
|
17
17
|
from .client import CfquantError, CfquantTimeout, configure, get_client
|
|
18
18
|
from .version import __version__
|
|
@@ -28,6 +28,7 @@ __all__ = [
|
|
|
28
28
|
"xtdata",
|
|
29
29
|
"xttrader",
|
|
30
30
|
"cftrader",
|
|
31
|
+
"notify",
|
|
31
32
|
"QmtInnerApi",
|
|
32
33
|
"xttype",
|
|
33
34
|
"configure",
|
|
@@ -6,11 +6,9 @@ never perform SMTP or HTTP work on a QMT callback/status thread.
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import copy
|
|
9
|
-
import base64
|
|
10
9
|
from datetime import datetime, timedelta, timezone
|
|
11
10
|
from email.message import EmailMessage
|
|
12
11
|
import hashlib
|
|
13
|
-
import hmac
|
|
14
12
|
import json
|
|
15
13
|
import os
|
|
16
14
|
from pathlib import Path
|
|
@@ -20,9 +18,7 @@ import ssl
|
|
|
20
18
|
import tempfile
|
|
21
19
|
import threading
|
|
22
20
|
import time
|
|
23
|
-
import
|
|
24
|
-
import urllib.parse
|
|
25
|
-
import urllib.request
|
|
21
|
+
from .webhook import send_webhook
|
|
26
22
|
|
|
27
23
|
|
|
28
24
|
SHANGHAI = timezone(timedelta(hours=8))
|
|
@@ -566,28 +562,5 @@ class MonitorNotificationService:
|
|
|
566
562
|
server.send_message(msg)
|
|
567
563
|
|
|
568
564
|
def _send_webhook(self, config, event):
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
raise ValueError("webhook url is required")
|
|
572
|
-
kind = str(config.get("type") or "generic").lower()
|
|
573
|
-
if kind == "dingtalk" and config.get("secret"):
|
|
574
|
-
timestamp = str(int(time.time() * 1000))
|
|
575
|
-
secret = str(config.get("secret"))
|
|
576
|
-
raw = "%s\n%s" % (timestamp, secret)
|
|
577
|
-
sign = base64.b64encode(hmac.new(secret.encode("utf-8"), raw.encode("utf-8"), hashlib.sha256).digest()).decode("ascii")
|
|
578
|
-
separator = "&" if "?" in url else "?"
|
|
579
|
-
url = "%s%stimestamp=%s&sign=%s" % (url, separator, urllib.parse.quote(timestamp), urllib.parse.quote(sign))
|
|
580
|
-
text = self._format_text(event)
|
|
581
|
-
if kind == "feishu":
|
|
582
|
-
body = {"msg_type": "text", "content": {"text": text}}
|
|
583
|
-
elif kind in ("dingtalk", "wecom"):
|
|
584
|
-
body = {"msgtype": "text", "text": {"content": text}}
|
|
585
|
-
else:
|
|
586
|
-
body = {"event_type": event.get("event_type"), "title": event.get("title"),
|
|
587
|
-
"message": event.get("message"), "payload": event.get("payload"),
|
|
588
|
-
"occurred_at": event.get("occurred_at")}
|
|
589
|
-
request = urllib.request.Request(url, data=json.dumps(body, ensure_ascii=False, default=str).encode("utf-8"),
|
|
590
|
-
headers={"Content-Type": "application/json; charset=utf-8", "User-Agent": "cfquant-monitor"}, method="POST")
|
|
591
|
-
with urllib.request.urlopen(request, timeout=8) as response:
|
|
592
|
-
if int(getattr(response, "status", 200)) >= 400:
|
|
593
|
-
raise urllib.error.HTTPError(url, response.status, "webhook returned %s" % response.status, response.headers, None)
|
|
565
|
+
payload = {key: event.get(key) for key in ("event_type", "title", "message", "payload", "occurred_at")}
|
|
566
|
+
return send_webhook(config, self._format_text(event), payload=payload)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""User-authored messages: explicit Webhook first, otherwise account binding."""
|
|
2
|
+
|
|
3
|
+
import atexit
|
|
4
|
+
import threading
|
|
5
|
+
|
|
6
|
+
from .webhook import WebhookError, normalize_webhook, send_webhook, validate_message
|
|
7
|
+
|
|
8
|
+
_client = None
|
|
9
|
+
_client_lock = threading.Lock()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def close():
|
|
13
|
+
"""Release the notification RPC connection; the next call reconnects."""
|
|
14
|
+
global _client
|
|
15
|
+
with _client_lock:
|
|
16
|
+
client, _client = _client, None
|
|
17
|
+
if client is not None:
|
|
18
|
+
client.close()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
atexit.register(close)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def send(message, account_id=None, *, title="", webhook_url=None, provider=None,
|
|
25
|
+
secret=None, account_type=None, account_key=None, bridge_id=None, timeout=8):
|
|
26
|
+
"""Send text synchronously; returns sent/provider/source, or raises on failure.
|
|
27
|
+
|
|
28
|
+
webhook_url and provider must be supplied together and override the binding
|
|
29
|
+
completely (including its secret). Account-based sends require cfquant Web
|
|
30
|
+
and LTtx, but do not require QMT to be online. No automatic retry is performed.
|
|
31
|
+
"""
|
|
32
|
+
timeout = validate_message(message, title, timeout)
|
|
33
|
+
if webhook_url is not None or provider is not None or secret is not None:
|
|
34
|
+
if not webhook_url or not provider:
|
|
35
|
+
raise ValueError("调用时覆盖推送配置必须同时提供 webhook_url 和 provider")
|
|
36
|
+
config = normalize_webhook({"enabled": True, "url": webhook_url, "provider": provider, "secret": secret})
|
|
37
|
+
return dict(send_webhook(config, message, title=title, timeout=timeout), source="explicit")
|
|
38
|
+
if not str(account_id or "").strip() and not str(account_key or "").strip():
|
|
39
|
+
raise ValueError("请指定资金账号 account_id 或 account_key,或提供 webhook_url 和 provider")
|
|
40
|
+
params = {"message": message, "title": title, "timeout": timeout}
|
|
41
|
+
params.update({key: value for key, value in dict(account_id=account_id, account_type=account_type,
|
|
42
|
+
account_key=account_key, bridge_id=bridge_id).items() if value is not None})
|
|
43
|
+
global _client
|
|
44
|
+
with _client_lock:
|
|
45
|
+
if _client is None:
|
|
46
|
+
from .client import create_rpc_client
|
|
47
|
+
_client = create_rpc_client(transport="web_lttx")
|
|
48
|
+
client = _client
|
|
49
|
+
return client.request("cfquant.notify.send", params, timeout=timeout + 5)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
__all__ = ["send", "close", "WebhookError"]
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from .diagnostic_context import versioned_message
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
3
|
import os
|
|
4
|
+
import gc
|
|
4
5
|
import queue
|
|
5
6
|
import sys
|
|
6
7
|
import threading
|
|
@@ -516,47 +517,91 @@ class CfquantQmtBridge(object):
|
|
|
516
517
|
})
|
|
517
518
|
raise NotImplementedError("当前QMT环境未提供download_history_data2")
|
|
518
519
|
|
|
519
|
-
|
|
520
|
+
stock_list = params.get("stock_list", params.get("code_list", []))
|
|
521
|
+
if isinstance(stock_list, str):
|
|
522
|
+
stock_list = [item.strip() for item in stock_list.replace(",", ",").split(",") if item.strip()]
|
|
523
|
+
else:
|
|
524
|
+
stock_list = list(stock_list or [])
|
|
525
|
+
batches = self._history_download_batches(stock_list, params)
|
|
526
|
+
total = len(stock_list)
|
|
527
|
+
completed = 0
|
|
528
|
+
|
|
529
|
+
def callback(data, batch_offset=0, batch_index=0, batch_total=1):
|
|
520
530
|
if callback_event and client_id:
|
|
531
|
+
payload = dict(data) if isinstance(data, dict) else {"message": str(data)}
|
|
532
|
+
batch_finished = payload.get("finished")
|
|
533
|
+
try:
|
|
534
|
+
batch_finished = max(0, min(int(batch_finished), len(batches[batch_index])))
|
|
535
|
+
except (TypeError, ValueError, IndexError):
|
|
536
|
+
batch_finished = 0
|
|
537
|
+
payload.update({
|
|
538
|
+
"finished": min(total, batch_offset + batch_finished),
|
|
539
|
+
"total": total,
|
|
540
|
+
"batch_index": batch_index + 1,
|
|
541
|
+
"batch_total": batch_total,
|
|
542
|
+
"batch_finished": batch_finished,
|
|
543
|
+
"batch_size": len(batches[batch_index]) if batch_index < len(batches) else 0,
|
|
544
|
+
})
|
|
521
545
|
self._send_event(
|
|
522
546
|
client_id,
|
|
523
547
|
callback_event,
|
|
524
|
-
|
|
548
|
+
payload,
|
|
525
549
|
meta=self._download_event_meta(params, "history", "progress"),
|
|
526
550
|
)
|
|
527
551
|
|
|
528
|
-
callback_func = callback if callback_event else None
|
|
529
|
-
variants = []
|
|
530
|
-
incrementally = params.get("incrementally")
|
|
531
|
-
if incrementally is not None:
|
|
532
|
-
variants.append((
|
|
533
|
-
(
|
|
534
|
-
params.get("stock_list", params.get("code_list", [])),
|
|
535
|
-
params.get("period", "1d"),
|
|
536
|
-
params.get("start_time", ""),
|
|
537
|
-
params.get("end_time", ""),
|
|
538
|
-
callback_func,
|
|
539
|
-
incrementally,
|
|
540
|
-
),
|
|
541
|
-
{},
|
|
542
|
-
))
|
|
543
|
-
variants.append((
|
|
544
|
-
(
|
|
545
|
-
params.get("stock_list", params.get("code_list", [])),
|
|
546
|
-
params.get("period", "1d"),
|
|
547
|
-
params.get("start_time", ""),
|
|
548
|
-
params.get("end_time", ""),
|
|
549
|
-
callback_func,
|
|
550
|
-
),
|
|
551
|
-
{},
|
|
552
|
-
))
|
|
553
552
|
if emit_lifecycle:
|
|
554
553
|
self._send_download_event(client_id, params, "history", "submitted", {
|
|
555
554
|
"stage": "submitted",
|
|
556
555
|
"message": "history download request submitted",
|
|
556
|
+
"total": total,
|
|
557
|
+
"batch_total": len(batches),
|
|
558
|
+
"batch_size": self._history_download_batch_size(params),
|
|
557
559
|
})
|
|
558
560
|
try:
|
|
559
|
-
|
|
561
|
+
incrementally = params.get("incrementally")
|
|
562
|
+
batch_total = len(batches)
|
|
563
|
+
for batch_index, batch in enumerate(batches):
|
|
564
|
+
batch_offset = completed
|
|
565
|
+
callback_func = (
|
|
566
|
+
(lambda data, offset=batch_offset, index=batch_index:
|
|
567
|
+
callback(data, offset, index, batch_total))
|
|
568
|
+
if callback_event else None
|
|
569
|
+
)
|
|
570
|
+
variants = []
|
|
571
|
+
if incrementally is not None:
|
|
572
|
+
variants.append((
|
|
573
|
+
(
|
|
574
|
+
batch,
|
|
575
|
+
params.get("period", "1d"),
|
|
576
|
+
params.get("start_time", ""),
|
|
577
|
+
params.get("end_time", ""),
|
|
578
|
+
callback_func,
|
|
579
|
+
incrementally,
|
|
580
|
+
),
|
|
581
|
+
{},
|
|
582
|
+
))
|
|
583
|
+
variants.append((
|
|
584
|
+
(
|
|
585
|
+
batch,
|
|
586
|
+
params.get("period", "1d"),
|
|
587
|
+
params.get("start_time", ""),
|
|
588
|
+
params.get("end_time", ""),
|
|
589
|
+
callback_func,
|
|
590
|
+
),
|
|
591
|
+
{},
|
|
592
|
+
))
|
|
593
|
+
self._call_variants(func, variants)
|
|
594
|
+
completed += len(batch)
|
|
595
|
+
if emit_lifecycle:
|
|
596
|
+
self._send_download_event(client_id, params, "history", "batch_done", {
|
|
597
|
+
"stage": "batch_done",
|
|
598
|
+
"finished": completed,
|
|
599
|
+
"total": total,
|
|
600
|
+
"batch_index": batch_index + 1,
|
|
601
|
+
"batch_total": batch_total,
|
|
602
|
+
"batch_size": len(batch),
|
|
603
|
+
})
|
|
604
|
+
gc.collect()
|
|
560
605
|
except Exception as e:
|
|
561
606
|
if emit_lifecycle:
|
|
562
607
|
self._send_download_event(client_id, params, "history", "error", {
|
|
@@ -567,9 +612,39 @@ class CfquantQmtBridge(object):
|
|
|
567
612
|
if emit_lifecycle:
|
|
568
613
|
self._send_download_event(client_id, params, "history", "request_done", {
|
|
569
614
|
"stage": "request_done",
|
|
570
|
-
"result":
|
|
615
|
+
"result": None,
|
|
616
|
+
"finished": completed,
|
|
617
|
+
"total": total,
|
|
571
618
|
})
|
|
572
|
-
return
|
|
619
|
+
return None
|
|
620
|
+
|
|
621
|
+
@staticmethod
|
|
622
|
+
def _history_download_batch_size(params):
|
|
623
|
+
params = params or {}
|
|
624
|
+
value = params.get("download_batch_size", params.get("batch_size"))
|
|
625
|
+
if value is None:
|
|
626
|
+
value = os.environ.get("CFQUANT_HISTORY_DOWNLOAD_BATCH_SIZE")
|
|
627
|
+
if value is None or str(value).strip() == "":
|
|
628
|
+
period = str(params.get("period") or "").lower()
|
|
629
|
+
if period == "tick":
|
|
630
|
+
return 20
|
|
631
|
+
if period == "1m":
|
|
632
|
+
return 50
|
|
633
|
+
if period.endswith("m") or period.endswith("h"):
|
|
634
|
+
return 100
|
|
635
|
+
return 200
|
|
636
|
+
try:
|
|
637
|
+
value = int(value)
|
|
638
|
+
except (TypeError, ValueError):
|
|
639
|
+
return 50
|
|
640
|
+
return max(1, value)
|
|
641
|
+
|
|
642
|
+
@classmethod
|
|
643
|
+
def _history_download_batches(cls, stock_list, params):
|
|
644
|
+
if not stock_list:
|
|
645
|
+
return [stock_list]
|
|
646
|
+
size = cls._history_download_batch_size(params)
|
|
647
|
+
return [stock_list[index:index + size] for index in range(0, len(stock_list), size)]
|
|
573
648
|
|
|
574
649
|
def _get_instrument_detail(self, params):
|
|
575
650
|
func = getattr(self.context, "get_instrument_detail")
|
|
@@ -593,7 +593,8 @@ class QmtPythonRuntime:
|
|
|
593
593
|
time.sleep(0.25)
|
|
594
594
|
raise QmtPythonRuntimeError("QMT did not start after runtime switch")
|
|
595
595
|
|
|
596
|
-
def validate_selection(self, runtime, *, account_id=None
|
|
596
|
+
def validate_selection(self, runtime, *, account_id=None,
|
|
597
|
+
allow_missing_account_config=False):
|
|
597
598
|
runtime = _canonical_runtime(runtime)
|
|
598
599
|
effective_account = account_id if account_id is not None else self.account_id
|
|
599
600
|
binding = self.binding_status(account_id=effective_account)
|
|
@@ -603,6 +604,8 @@ class QmtPythonRuntime:
|
|
|
603
604
|
% (binding.get("qmt_version") or "unknown"))
|
|
604
605
|
if effective_account:
|
|
605
606
|
if not binding["config_exists"]:
|
|
607
|
+
if allow_missing_account_config and binding["state"] == "account_config_missing":
|
|
608
|
+
return binding
|
|
606
609
|
raise QmtPythonRuntimeError("QMT account Config.xml not found: %s; log in to QMT first" % effective_account)
|
|
607
610
|
text = Path(binding["config"]).read_text(encoding="utf-8")
|
|
608
611
|
if not re.search(r'<python\b[^>]*\blibs_path="[^"]*"', text):
|
|
@@ -611,9 +614,19 @@ class QmtPythonRuntime:
|
|
|
611
614
|
|
|
612
615
|
def download_and_switch(self, runtime="py312", *, restart=True, account_id=None,
|
|
613
616
|
progress_callback=None, cancel_event=None,
|
|
614
|
-
restart_callback=None, reuse_installed=False
|
|
617
|
+
restart_callback=None, reuse_installed=False,
|
|
618
|
+
allow_missing_account_config=False):
|
|
615
619
|
runtime = _canonical_runtime(runtime)
|
|
616
|
-
binding = self.validate_selection(
|
|
620
|
+
binding = self.validate_selection(
|
|
621
|
+
runtime,
|
|
622
|
+
account_id=account_id,
|
|
623
|
+
allow_missing_account_config=allow_missing_account_config,
|
|
624
|
+
)
|
|
625
|
+
deferred_account_config = bool(
|
|
626
|
+
allow_missing_account_config
|
|
627
|
+
and (account_id or self.account_id)
|
|
628
|
+
and binding.get("state") == "account_config_missing"
|
|
629
|
+
)
|
|
617
630
|
reuse = bool(reuse_installed and binding["runtimes"][runtime]["installed"])
|
|
618
631
|
package, archive = None, None
|
|
619
632
|
if reuse:
|
|
@@ -624,17 +637,25 @@ class QmtPythonRuntime:
|
|
|
624
637
|
cancel_event=cancel_event)
|
|
625
638
|
if cancel_event and cancel_event.is_set():
|
|
626
639
|
raise QmtPythonRuntimeError("QMT Python runtime operation cancelled")
|
|
627
|
-
_callback(progress_callback, QmtPythonProgress(
|
|
628
|
-
|
|
640
|
+
_callback(progress_callback, QmtPythonProgress(
|
|
641
|
+
runtime, "activating",
|
|
642
|
+
message=("正在停止 QMT 并安装运行库" if deferred_account_config
|
|
643
|
+
else "正在停止 QMT 并配置执行器"),
|
|
644
|
+
))
|
|
645
|
+
was_running = bool(self._qmt_processes())
|
|
646
|
+
if restart and was_running:
|
|
629
647
|
self._stop_for_switch()
|
|
630
|
-
elif not restart and
|
|
648
|
+
elif not restart and was_running:
|
|
631
649
|
raise QmtPythonRuntimeError(
|
|
632
|
-
"QMT is running; set restart=True before activating a Python runtime")
|
|
650
|
+
"QMT is running; set restart=True before installing or activating a Python runtime")
|
|
633
651
|
try:
|
|
634
652
|
installed = self.inspect(runtime, refresh_remote=False) if reuse else self.install(runtime, package=package, progress_callback=progress_callback)
|
|
635
|
-
switched =
|
|
653
|
+
switched = None if deferred_account_config else (
|
|
654
|
+
self.switch_account(runtime, account_id=account_id)
|
|
655
|
+
if (account_id or self.account_id) else None
|
|
656
|
+
)
|
|
636
657
|
finally:
|
|
637
|
-
if restart:
|
|
658
|
+
if restart and (not deferred_account_config or was_running):
|
|
638
659
|
_callback(progress_callback, QmtPythonProgress(runtime, "restarting", message="正在重新启动 QMT"))
|
|
639
660
|
if restart_callback:
|
|
640
661
|
restarted = restart_callback()
|
|
@@ -646,7 +667,10 @@ class QmtPythonRuntime:
|
|
|
646
667
|
restarted = None
|
|
647
668
|
return {"runtime": runtime, "package": package.as_dict() if package else None,
|
|
648
669
|
"archive": str(archive) if archive else None, "reused_installed": reuse,
|
|
649
|
-
"installed": installed, "switched": switched, "restarted": restarted
|
|
670
|
+
"installed": installed, "switched": switched, "restarted": restarted,
|
|
671
|
+
"account_config_required": deferred_account_config,
|
|
672
|
+
"message": ("运行库已安装;请先在 QMT 登录此账号,再重新检测并应用执行器。"
|
|
673
|
+
if deferred_account_config else "")}
|
|
650
674
|
|
|
651
675
|
def _stop_for_switch(self):
|
|
652
676
|
for process in self._qmt_processes():
|
|
@@ -119,6 +119,13 @@ def test_missing_account_config_shows_capabilities_but_requires_login(page, fron
|
|
|
119
119
|
errors = open_runtime(page, frontend_url, runtime_status(config_exists=False))
|
|
120
120
|
expect(page.locator("#bindingPythonRuntimeSelect option")).to_have_count(2)
|
|
121
121
|
page.locator("#bindingPythonRuntimeSelect").select_option("py312")
|
|
122
|
-
|
|
123
|
-
|
|
122
|
+
submitted = []
|
|
123
|
+
page.route("**/api/qmt/python-runtime/download", lambda route: (
|
|
124
|
+
submitted.append(route.request.post_data_json),
|
|
125
|
+
route.fulfill(json={"ok": True, "data": {"state": "queued"}})))
|
|
126
|
+
expect(page.locator("#bindingPythonRuntimeDownloadBtn")).to_be_enabled()
|
|
127
|
+
expect(page.locator("#bindingPythonRuntimeChoiceHint")).to_contain_text("先下载")
|
|
128
|
+
page.locator("#bindingPythonRuntimeDownloadBtn").click()
|
|
129
|
+
page.wait_for_function("state.bindingPythonRuntimeJobId")
|
|
130
|
+
assert submitted[0]["allow_missing_account_config"] is True
|
|
124
131
|
assert not errors
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""Account Webhook editing and docs on desktop/mobile; all APIs are offline."""
|
|
2
|
+
import ast
|
|
3
|
+
import inspect
|
|
4
|
+
|
|
5
|
+
from cfquant import notify
|
|
6
|
+
from cfquant.tests.test_tutorial_reader import browser, page, frontend_url, expect
|
|
7
|
+
from cfquant.tests.test_binding_runtime_browser import open_bindings
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_new_binding_webhook_save_edit_preserves_credentials_and_clears(page, frontend_url, tmp_path):
|
|
11
|
+
_, errors = open_bindings(page, frontend_url)
|
|
12
|
+
page.locator('#openBindingDialogBtn').click()
|
|
13
|
+
page.locator('#bindingAccountId').fill('TEST_ONLY')
|
|
14
|
+
page.evaluate("fillQmtStrategySettings('binding', {enabled: false})")
|
|
15
|
+
page.locator('#bindingQmtAutoLogin').uncheck()
|
|
16
|
+
expect(page.locator('#bindingWebhookFields')).not_to_be_visible()
|
|
17
|
+
expect(page.locator('#bindingWebhookSaveBtn')).not_to_be_visible()
|
|
18
|
+
page.locator('#bindingWebhookEnabled').check()
|
|
19
|
+
page.locator('#bindingWebhookProvider').select_option('feishu')
|
|
20
|
+
page.locator('#bindingWebhookUrl').fill('https://example.invalid/PRIVATE_TOKEN')
|
|
21
|
+
page.locator('#bindingWebhookSecret').fill('PRIVATE_SECRET')
|
|
22
|
+
saved = []
|
|
23
|
+
rows = {}
|
|
24
|
+
|
|
25
|
+
def save(route):
|
|
26
|
+
body = route.request.post_data_json
|
|
27
|
+
saved.append(body)
|
|
28
|
+
row = dict(body, account_key='test:STOCK:TEST_ONLY', bridge_id='test', notification_webhook={
|
|
29
|
+
'enabled': True, 'provider': 'feishu', 'url': '', 'secret': '',
|
|
30
|
+
'url_configured': True, 'secret_configured': True})
|
|
31
|
+
rows[row['account_key']] = row
|
|
32
|
+
route.fulfill(json={'ok': True, 'data': {'account': row, 'account_configs': rows,
|
|
33
|
+
'account_pairs': {}, 'bridges': {'test': {'name': 'Test'}},
|
|
34
|
+
'qmt_strategy_deploy': {'enabled': False}, 'qmt_auto_login': {'enabled': False}}})
|
|
35
|
+
|
|
36
|
+
page.route('**/api/account-config', save)
|
|
37
|
+
page.locator('#bindingForm [type="submit"]').click()
|
|
38
|
+
expect(page.locator('#bindingDialogOverlay')).not_to_be_visible()
|
|
39
|
+
assert saved[0]['notification_webhook'] == {'enabled': True, 'provider': 'feishu',
|
|
40
|
+
'url': 'https://example.invalid/PRIVATE_TOKEN', 'secret': 'PRIVATE_SECRET', 'clear_secret': False}
|
|
41
|
+
assert page.locator('#bindingWebhookUrl').input_value() == ''
|
|
42
|
+
page.evaluate("""() => {
|
|
43
|
+
document.querySelector('#bindingQmtGuideOverlay').classList.add('hidden');
|
|
44
|
+
openBindingDialog({accountKey: 'test:STOCK:TEST_ONLY'});
|
|
45
|
+
}""")
|
|
46
|
+
expect(page.locator('#bindingWebhookProvider')).to_have_value('feishu')
|
|
47
|
+
expect(page.locator('#bindingWebhookUrlHint')).to_contain_text('已配置')
|
|
48
|
+
expect(page.locator('#bindingWebhookSecret')).to_have_value('')
|
|
49
|
+
updates = []
|
|
50
|
+
|
|
51
|
+
def partial(route):
|
|
52
|
+
body = route.request.post_data_json
|
|
53
|
+
updates.append(body)
|
|
54
|
+
row = rows[body['account_key']]
|
|
55
|
+
if body['notification_webhook'].get('clear'):
|
|
56
|
+
row['notification_webhook'] = {'enabled': False, 'provider': 'wecom',
|
|
57
|
+
'url_configured': False, 'secret_configured': False}
|
|
58
|
+
route.fulfill(json={'ok': True, 'data': {'account': row}})
|
|
59
|
+
|
|
60
|
+
page.route('**/api/account-config/webhook', partial)
|
|
61
|
+
page.locator('#bindingWebhookSaveBtn').click()
|
|
62
|
+
expect(page.locator('#bindingWebhookSaveStatus')).to_contain_text('已保存')
|
|
63
|
+
assert 'url' not in updates[0]['notification_webhook']
|
|
64
|
+
assert 'secret' not in updates[0]['notification_webhook']
|
|
65
|
+
assert len(saved) == 1
|
|
66
|
+
page.locator('#bindingWebhookProvider').select_option('dingtalk')
|
|
67
|
+
page.locator('#bindingWebhookSaveBtn').click()
|
|
68
|
+
expect(page.locator('#bindingSaveStatus')).to_contain_text('重新填写')
|
|
69
|
+
assert len(updates) == 1
|
|
70
|
+
page.locator('#bindingWebhookClearBtn').click()
|
|
71
|
+
page.locator('#bindingWebhookSaveBtn').click()
|
|
72
|
+
expect(page.locator('#bindingWebhookSaveStatus')).to_contain_text('已保存')
|
|
73
|
+
assert updates[-1]['notification_webhook'] == {'clear': True}
|
|
74
|
+
page.locator('#closeBindingDialogBtn').click()
|
|
75
|
+
page.mouse.move(page.viewport_size['width'] - 1, page.viewport_size['height'] - 1)
|
|
76
|
+
page.keyboard.press('Escape')
|
|
77
|
+
page.locator('#openBindingDialogBtn').click()
|
|
78
|
+
expect(page.locator('#bindingWebhookEnabled')).not_to_be_checked()
|
|
79
|
+
expect(page.locator('#bindingWebhookUrl')).to_have_value('')
|
|
80
|
+
page.locator('#bindingWebhookEnabled').check()
|
|
81
|
+
page.locator('#bindingWebhookSettings').scroll_into_view_if_needed()
|
|
82
|
+
page.screenshot(path=str(tmp_path / 'binding-webhook.png'))
|
|
83
|
+
assert page.locator('#bindingWebhookSettings').evaluate('node => node.scrollWidth <= node.clientWidth')
|
|
84
|
+
assert page.locator('#bindingDialogOverlay .binding-dialog').evaluate('node => node.scrollWidth <= node.clientWidth')
|
|
85
|
+
assert not errors
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def test_notification_docs_are_discoverable_and_examples_match_sdk(page, frontend_url):
|
|
89
|
+
requests, errors = open_bindings(page, frontend_url)
|
|
90
|
+
page.locator('.nav-item[data-view="api"]').click()
|
|
91
|
+
group = page.locator('[data-api-group="system"]')
|
|
92
|
+
if group.get_attribute('aria-expanded') != 'true':
|
|
93
|
+
group.click()
|
|
94
|
+
page.locator('[data-endpoint-id="notification_send"]').click()
|
|
95
|
+
docs = page.locator('#apiDocDetail')
|
|
96
|
+
expect(docs).to_contain_text('资金账号字符串')
|
|
97
|
+
code = docs.locator('pre[data-language="python"]').inner_text()
|
|
98
|
+
for call in (node for node in ast.walk(ast.parse(code)) if isinstance(node, ast.Call)):
|
|
99
|
+
if isinstance(call.func, ast.Attribute) and call.func.attr == 'send':
|
|
100
|
+
inspect.signature(notify.send).bind(*[None for _ in call.args], **{kw.arg: None for kw in call.keywords})
|
|
101
|
+
page.locator('[data-api-sdk-tutorial]').click()
|
|
102
|
+
expect(page.locator('.python-document-head h2')).to_have_text('send')
|
|
103
|
+
expect(page.locator('.python-breadcrumb')).to_contain_text('消息推送')
|
|
104
|
+
assert page.locator('[data-python-cftrader-guide]').count() == 0
|
|
105
|
+
assert all(method == 'GET' for method, path in requests)
|
|
106
|
+
assert not errors
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""Regression checks for bounded historical download requests."""
|
|
2
|
+
|
|
3
|
+
from types import SimpleNamespace
|
|
4
|
+
|
|
5
|
+
from cfquant.qmt_bridge import CfquantQmtBridge
|
|
6
|
+
from cfquant.tx_trade_bridge import TxTradeBridge
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _run_download(bridge_cls):
|
|
10
|
+
calls = []
|
|
11
|
+
events = []
|
|
12
|
+
|
|
13
|
+
def download(stock_list, period, start_time, end_time, callback, incrementally):
|
|
14
|
+
calls.append((list(stock_list), period, start_time, end_time, incrementally))
|
|
15
|
+
for index, stock_code in enumerate(stock_list, 1):
|
|
16
|
+
callback({
|
|
17
|
+
"finished": index,
|
|
18
|
+
"total": len(stock_list),
|
|
19
|
+
"stockcode": stock_code,
|
|
20
|
+
"message": "",
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
bridge = bridge_cls(
|
|
24
|
+
SimpleNamespace(),
|
|
25
|
+
show=False,
|
|
26
|
+
globals_dict={"download_history_data2": download},
|
|
27
|
+
)
|
|
28
|
+
bridge._send_event = lambda client, event, data, **kwargs: events.append((event, data, kwargs))
|
|
29
|
+
result = bridge._download_history_data2({
|
|
30
|
+
"stock_list": ["000001.SZ", "000002.SZ", "000003.SZ", "000004.SZ", "000005.SZ"],
|
|
31
|
+
"period": "1m",
|
|
32
|
+
"callback_event": "download:history",
|
|
33
|
+
"download_emit_lifecycle": True,
|
|
34
|
+
"download_batch_size": 2,
|
|
35
|
+
"incrementally": True,
|
|
36
|
+
}, {"client_id": "client-1"})
|
|
37
|
+
return result, calls, events
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_tx_trade_history_download_is_serially_batched_and_progress_is_aggregated():
|
|
41
|
+
result, calls, events = _run_download(TxTradeBridge)
|
|
42
|
+
|
|
43
|
+
assert result is None
|
|
44
|
+
assert [call[0] for call in calls] == [
|
|
45
|
+
["000001.SZ", "000002.SZ"],
|
|
46
|
+
["000003.SZ", "000004.SZ"],
|
|
47
|
+
["000005.SZ"],
|
|
48
|
+
]
|
|
49
|
+
progress = [data for _, data, meta in events if meta["meta"]["stage"] == "progress"]
|
|
50
|
+
assert progress[-1]["finished"] == 5
|
|
51
|
+
assert progress[-1]["total"] == 5
|
|
52
|
+
request_done = [data for _, data, meta in events if meta["meta"]["stage"] == "request_done"]
|
|
53
|
+
assert len(request_done) == 1
|
|
54
|
+
assert request_done[0]["finished"] == 5
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_legacy_qmt_bridge_uses_the_same_download_guard():
|
|
58
|
+
_, calls, _ = _run_download(CfquantQmtBridge)
|
|
59
|
+
assert [call[0] for call in calls] == [
|
|
60
|
+
["000001.SZ", "000002.SZ"],
|
|
61
|
+
["000003.SZ", "000004.SZ"],
|
|
62
|
+
["000005.SZ"],
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def test_minute_download_defaults_to_a_small_batch():
|
|
67
|
+
assert TxTradeBridge._history_download_batch_size({"period": "1m"}) == 50
|
|
68
|
+
assert TxTradeBridge._history_download_batch_size({"period": "1d"}) == 200
|