diff --git a/.github/workflows/macos_arm64.yml b/.github/workflows/macos_arm64.yml new file mode 100644 index 00000000..3045d16c --- /dev/null +++ b/.github/workflows/macos_arm64.yml @@ -0,0 +1,53 @@ +name: macOS_arm64 + +on: + push: + branches: + - master + pull_request: + branches: + - master + +jobs: + build: + strategy: + fail-fast: false + matrix: + os: [macos-14] + arch: [arm64] + kind: [shared] + + runs-on: ${{ matrix.os }} + + concurrency: + group: ${{ github.ref }}-${{ github.base_ref }}-${{ github.head_ref }}-macOS-${{ matrix.arch }}-${{ matrix.kind }} + cancel-in-progress: true + steps: + - uses: actions/checkout@v4 + + - name: Cache packages + id: cache-xmake-macosx + uses: actions/cache@v4 + env: + cache-name: cache-node-modules + with: + path: ~/.xmake + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-build-${{ env.cache-name }}- + ${{ runner.os }}-build- + ${{ runner.os }}- + + - uses: xmake-io/github-action-setup-xmake@v1 + with: + xmake-version: 2.9.4 + actions-cache-folder: '.xmake-cache' + actions-cache-key: 'macosx-arm64' + + - name: config + run: | + xmake f -c -k ${{ matrix.kind }} -y -vD --mysql=n --hdf5=n + + - name: build + run: | + xmake -bvD core diff --git a/.github/workflows/macos_x86_64.yml b/.github/workflows/macos_x86_64.yml new file mode 100644 index 00000000..fd9ab682 --- /dev/null +++ b/.github/workflows/macos_x86_64.yml @@ -0,0 +1,53 @@ +name: macOS_x86_64 + +on: + push: + branches: + - master + pull_request: + branches: + - master + +jobs: + build: + strategy: + fail-fast: false + matrix: + os: [macos-12] + arch: [x86_64] + kind: [shared] + + runs-on: ${{ matrix.os }} + + concurrency: + group: ${{ github.ref }}-${{ github.base_ref }}-${{ github.head_ref }}-macOS-${{ matrix.arch }}-${{ matrix.kind }} + cancel-in-progress: true + steps: + - uses: actions/checkout@v4 + + - name: Cache packages + id: cache-xmake-macosx + uses: actions/cache@v4 + env: + cache-name: cache-node-modules + with: + path: ~/.xmake + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-build-${{ env.cache-name }}- + ${{ runner.os }}-build- + ${{ runner.os }}- + + - uses: xmake-io/github-action-setup-xmake@v1 + with: + xmake-version: 2.9.4 + actions-cache-folder: '.xmake-cache' + actions-cache-key: 'macosx-x64' + + - name: config + run: | + xmake f -c -k ${{ matrix.kind }} -y -vD --mysql=n --hdf5=n + + - name: build + run: | + xmake -bvD core diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 592eda61..c0142c7f 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -34,7 +34,7 @@ jobs: - uses: xmake-io/github-action-setup-xmake@v1 with: - xmake-version: 2.9.2 + xmake-version: 2.9.3 actions-cache-folder: '.xmake-cache' actions-cache-key: 'ubuntu' diff --git a/.github/workflows/ubuntu_aarch64.yml b/.github/workflows/ubuntu_aarch64.yml index 0184d18c..10c8f107 100644 --- a/.github/workflows/ubuntu_aarch64.yml +++ b/.github/workflows/ubuntu_aarch64.yml @@ -41,8 +41,9 @@ jobs: - uses: xmake-io/github-action-setup-xmake@v1 with: - xmake-version: 2.9.2 + xmake-version: 2.9.3 actions-cache-folder: '.xmake-cache' + actions-cache-key: 'ubuntu-aarch64' - name: Installation musl run: | diff --git a/.github/workflows/ubuntu_python.yml b/.github/workflows/ubuntu_python.yml index 7e90c977..3a60f600 100644 --- a/.github/workflows/ubuntu_python.yml +++ b/.github/workflows/ubuntu_python.yml @@ -34,7 +34,7 @@ jobs: - uses: xmake-io/github-action-setup-xmake@v1 with: - xmake-version: 2.9.2 + xmake-version: 2.9.3 actions-cache-folder: '.xmake-cache' actions-cache-key: 'ubuntu' diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 330cc1ae..2bd81888 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -26,17 +26,19 @@ jobs: - uses: xmake-io/github-action-setup-xmake@v1 with: - xmake-version: 2.9.2 + xmake-version: 2.9.3 + actions-cache-folder: '.xmake-cache' + actions-cache-key: 'windows' - name: configure shell: cmd run: | - xmake f -c --feedback=n -k shared -y -vD + xmake f -c --feedback=n -y -vD - name: build shell: cmd run: | - xmake -b small-test + xmake -bvD small-test - name: test shell: cmd diff --git a/.github/workflows/windows_python.yml b/.github/workflows/windows_python.yml index cba1625f..9afd91b7 100644 --- a/.github/workflows/windows_python.yml +++ b/.github/workflows/windows_python.yml @@ -26,14 +26,16 @@ jobs: - uses: xmake-io/github-action-setup-xmake@v1 with: - xmake-version: 2.9.2 + xmake-version: 2.9.3 + actions-cache-folder: '.xmake-cache' + actions-cache-key: 'windows' - name: configure shell: cmd run: | - xmake f -c -k shared -y + xmake f -c -k shared -y --runtimes="MD" - name: build shell: cmd run: | - xmake -b core \ No newline at end of file + xmake -b core diff --git a/.gitignore b/.gitignore index 21281b29..5c82358b 100644 --- a/.gitignore +++ b/.gitignore @@ -77,4 +77,5 @@ hikyuu/cpp/libhku_hdf5_hl.so.200.1.0 hikyuu/cpp/libhku_hdf5.so.200 hikyuu/cpp/libhku_hdf5.so.200.2.0 hikyuu/include -.virtual_documents \ No newline at end of file +.virtual_documents +hikyuu_cpp/hikyuu/utilities/config.h diff --git a/README.rst b/README.rst deleted file mode 100644 index 5ac522e9..00000000 --- a/README.rst +++ /dev/null @@ -1,113 +0,0 @@ -.. image:: https://img2.imgtp.com/2024/05/29/mSBrbO7R.png - :target: https://gitee.com/fasiondog/hikyuu - :align: left - :alt: Hikyuu - ------------ - -.. image:: https://static.pepy.tech/badge/hikyuu - :target: https://pepy.tech/project/hikyuu - -.. image:: https://static.pepy.tech/badge/hikyuu/month - :target: https://pepy.tech/project/hikyuu - -.. image:: https://static.pepy.tech/badge/hikyuu/week - :target: https://pepy.tech/project/hikyuu - -.. image:: https://github.com/fasiondog/hikyuu/workflows/win-build/badge.svg - :target: https://github.com/fasiondog/hikyuu/actions - -.. image:: https://github.com/fasiondog/hikyuu/workflows/ubuntu-build/badge.svg - :target: https://github.com/fasiondog/hikyuu/actions - -.. image:: https://github.com/fasiondog/hikyuu/workflows/ubuntu-aarch64-build/badge.svg - :target: https://github.com/fasiondog/hikyuu/actions - -.. image:: https://img.shields.io/github/license/mashape/apistatus.svg - :target: https://github.com/fasiondog/hikyuu/blob/master/LICENSE.txt - :alt: GitHub - -Hikyuu Quant Framework是一款基于C++/Python的开源量化交易研究框架,用于策略分析及回测(目前主要用于国内A股市场)。其核心思想基于当前成熟的系统化交易方法,将整个系统化交易抽象为由市场环境判断策略、系统有效条件、信号指示器、止损/止盈策略、资金管理策略、盈利目标策略、移滑价差算法七大组件,你可以分别构建这些组件的策略资产库,在实际研究中对它们自由组合来观察系统的有效性、稳定性以及单一种类策略的效果。 - -详细文档: ``_ - - -感谢网友提供的 Hikyuu Ubuntu虚拟机环境, 百度网盘下载(提取码: ht8j): ``_ - - -示例: - -:: - - #创建模拟交易账户进行回测,初始资金30万 - my_tm = crtTM(init_cash = 300000) - - #创建信号指示器(以5日EMA为快线,5日EMA自身的10日EMA作为慢线,快线向上穿越慢线时买入,反之卖出) - my_sg = SG_Flex(EMA(CLOSE(), n=5), slow_n=10) - - #固定每次买入1000股 - my_mm = MM_FixedCount(1000) - - #创建交易系统并运行 - sys = SYS_Simple(tm = my_tm, sg = my_sg, mm = my_mm) - sys.run(sm['sz000001'], Query(-150)) - -.. figure:: https://img2.imgtp.com/2024/05/29/xTEvXesP.png - :width: 600px - -完整示例参见:``_ - - -为什么选择 Hikyuu? --------------------- - -- **组合灵活,分类构建策略资产库** Hikyuu对系统化交易方法进行了良好的抽象,包含了九大策略组件:市场环境判断策略、系统有效条件、信号指示器、止损/止盈策略、资金管理策略、盈利目标策略、移滑价差算法、交易对象选择策略、资金分配策略。可以在此基础上构建自己的策略库,并进行灵活的组合和测试。在进行策略探索时,可以更加专注于某一方面的策略性能与影响。其主要功能模块如下: - - .. figure:: https://img2.imgtp.com/2024/05/29/9SrY9vI1.png - :width: 600px - -- **性能保障,打造自己的专属应用** 目前项目包含了3个主要组成部分:基于C++的核心库、对C++进行包装的Python库(hikyuu)、基于Python的交互式工具。 - - - AMD 7950x 实测:A股全市场(1913万日K线)仅加载全部日线计算 20日 MA 并求最后 MA 累积和,首次执行含数据加载 耗时 6秒,数据加载完毕后计算耗时 166 毫秒,详见: `性能实测 `_ - - - C++核心库,提供了整体的策略框架,在保证性能的同时,已经考虑了对多线程和多核处理的支持,在未来追求更高运算速度提供便利。C++核心库,可以单独剥离使用,自行构建自己的客户端工具。 - - - Python库(hikyuu),提供了对C++库的包装,同时集成了talib库(如TA_SMA,对应talib.SMA),可以与numpy、pandas数据结构进行互相转换,为使用其他成熟的python数据分析工具提供了便利。 - - - hikyuu.interactive 交互式探索工具,提供了K线、指标、系统信号等的基本绘图功能,用于对量化策略的探索和回测。 - -- **代码简洁,探索更便捷、自由** 同时支持面向对象和命令行编程范式。其中,命令行在进行策略探索时,代码简洁、探索更便捷、自由。 - -- **安全、自由、隐私,搭建自己的专属云量化平台** 结合 Python + Jupyter 的强大能力与云服务器,可以搭建自己专属的云量化平台。将Jupyter部署在云服务器上,随时随地的访问自己的云平台,即刻实现自己新的想法,如下图所示通过手机访问自己的云平台。结合Python强大成熟的数据分析、人工智能工具(如 numpy、scipy、pandas、TensorFlow)搭建更强大的人工智能平台。 - -- **数据存储方式可扩展** 目前支持本地HDF5格式、MySQL存储。默认使用HDF5,数据文件体积小、速度更快、备份更便利。截止至2017年4月21日,沪市日线数据文件149M、深市日线数据文件184M、5分钟线数据各不到2G。 - -.. image:: https://api.star-history.com/svg?repos=fasiondog/hikyuu&type=Date - :target: https://star-history.com/#fasiondog/hikyuu&Date - :alt: Star History Chart - - -想要更多了解Hikyuu?请使用以下方式联系: --------------------------------------------------- - -**加入知识星球** 更多示例与策略部件的及时分享(您的加入将视为对项目的捐赠) - - .. figure:: https://img2.imgtp.com/2024/05/29/3sEP6Re0.png - - -**项目交流和问题答复将转移至知识星球-【Hikyuu量化】。** - -- 关注公众号: - - .. figure:: https://img2.imgtp.com/2024/05/29/1NQztICj.jpg - - -- 加入微信群(请注明“加入hikyuu”): - - .. figure:: https://img2.imgtp.com/2024/05/29/HD0dAgbn.jpg - - -- QQ交流群(逐渐废弃):114910869, 或扫码加入: - - .. figure:: https://img2.imgtp.com/2024/05/29/xAH2PesY.png - diff --git a/config.h.in b/config.h.in index 798234db..0344c552 100644 --- a/config.h.in +++ b/config.h.in @@ -19,18 +19,6 @@ // 检查下标越界 #define CHECK_ACCESS_BOUND ${CHECK_ACCESS_BOUND} -// 默认激活的日志级别 -#define LOG_ACTIVE_LEVEL ${LOG_ACTIVE_LEVEL} - -// 是否使用 spdlog -#define USE_SPDLOG_LOGGER ${USE_SPDLOG_LOGGER} - -// 使用异步 logger -#define HKU_USE_SPDLOG_ASYNC_LOGGER ${USE_SPDLOG_ASYNC_LOGGER} - -// spdlog默认日志级别 -#define SPDLOG_ACTIVE_LEVEL ${LOG_ACTIVE_LEVEL} - // 启用MSVC内存泄漏检查 #define ENABLE_MSVC_LEAK_DETECT ${ENABLE_MSVC_LEAK_DETECT} diff --git a/config_utils.h.in b/config_utils.h.in new file mode 100644 index 00000000..3479a6c5 --- /dev/null +++ b/config_utils.h.in @@ -0,0 +1,41 @@ +#pragma once +#ifndef HKU_UTILS_CONFIG_H_ +#define HKU_UTILS_CONFIG_H_ + +#include "osdef.h" + +// clang-format off + +${define HKU_ENABLE_MYSQL} +#if HKU_ENABLE_MYSQL && HKU_OS_WINDOWS +#ifndef NOMINMAX +#define NOMINMAX +#endif +#endif + +${define HKU_ENABLE_SQLITE} +${define HKU_ENABLE_SQLCIPHER} +${define HKU_SQL_TRACE} + +${define HKU_SUPPORT_DATETIME} + +${define HKU_ENABLE_INI_PARSER} + +${define HKU_ENABLE_STACK_TRACE} + +${define HKU_CLOSE_SPEND_TIME} + +${define HKU_USE_SPDLOG_ASYNC_LOGGER} +${define HKU_LOG_ACTIVE_LEVEL} + +${define HKU_ENABLE_MO} + +${define HKU_ENABLE_HTTP_CLIENT} +${define HKU_ENABLE_HTTP_CLIENT_SSL} +${define HKU_ENABLE_HTTP_CLIENT_ZIP} + +${define HKU_ENABLE_NODE} + +// clang-format on + +#endif /* HKU_UTILS_CONFIG_H_ */ \ No newline at end of file diff --git a/copy_dependents.lua b/copy_dependents.lua new file mode 100644 index 00000000..f9e7e3e4 --- /dev/null +++ b/copy_dependents.lua @@ -0,0 +1,84 @@ + +-- 拷贝依赖的第三方库头文件及lib到指定目录 +task("copy_dependents") + set_category("plugin") + + -- 设置运行脚本 + -- destpath 目标目录 + -- onlylib 只拷贝lib库 + on_run(function(target, destpath, onlylib) + local libdir = destpath .. '/lib' + + -- 将依赖的库拷贝至build的输出目录 + for libname, pkg in pairs(target:pkgs()) do + if pkg:installdir() == nil then + print(libname .. ": Not found installdir, maybe it is system lib!"); + goto continue + end + + print("dependent package: " .. pkg:installdir()) + --local linkdirs = pkg:get("linkdirs") + -- 部分库没有 linkdirs ,如:MNN, Paddle-lite,所以使用 includedirs + local pkg_path = pkg:get("includedirs") + if pkg_path == nil then + pkg_path = pkg:get("sysincludedirs") -- xmake 2.3.9 改为了 sysincludedirs + end + + if pkg_path == nil then + goto continue + end + + -- 安装模式下拷贝所有依赖库的头文件 + if not onlylib then + if type(pkg_path) == 'string' then + local pos = string.find(pkg_path, "opencv") + if pos == nil then + os.trycp(pkg_path, destpath) + else + os.trycp(pkg_path .. "/opencv2", destpath .. "/include") + end + elseif type(pkg_path) == 'table' then + for i=1, #pkg_path do + local pos = string.find(pkg_path[i], "hku_utils") + if pos == nil then + pos = string.find(pkg_path[i], "opencv") + if pos == nil then + os.trycp(pkg_path[i], destpath) + else + os.trycp(pkg_path[i] .. "/opencv2", destpath .. "/include") + end + else + for _, filedir in ipairs(os.dirs(pkg_path[i] .. "/*")) do + local pos = string.find(filedir, "hikyuu") + if pos == nil then + os.trycp(filedir, destpath .. "/include") + else + os.trycp(filedir .. "/utilities", destpath .. "/include/hikyuu") + end + end + end + end + end + end + + -- 拷贝依赖的库文件 + os.trycp(pkg:installdir() .. "/lib/*", libdir) + if is_plat("windows") then + os.trycp(pkg:installdir() .. "/bin/*.dll", libdir) + end + + :: continue :: + end + end) + + set_menu { + -- usage + usage = "xmake copy_dependents [options]" + + -- description + , description = "拷贝依赖的第三方库头文件及lib到指定目录!" + + -- options + , options = {} + } +task_end() \ No newline at end of file diff --git a/docs/source/release.rst b/docs/source/release.rst index 77af448d..029a3200 100644 --- a/docs/source/release.rst +++ b/docs/source/release.rst @@ -1,6 +1,14 @@ 版本发布说明 ======================= +2.1.1 - 2024年8月9日 +------------------------- + +1. 预加载历史财务信息 +2. fixed windows下 MySQL blob 数据读取错误导致读取历史财务信息时消耗巨大内存 +3. HikyuuTdx 读取配置文件放在 output 重定向之前,防止配置文件读取失败没有提示 + + 2.1.0 - 2024年6月18日 ------------------------- diff --git a/hikyuu/README.rst b/hikyuu/README.rst deleted file mode 100644 index 883fcfb0..00000000 --- a/hikyuu/README.rst +++ /dev/null @@ -1,79 +0,0 @@ -.. image:: https://hikyuu.org/images/00000_title.png - :target: https://hikyuu.org - :align: center - :alt: Hikyuu - -.. image:: https://travis-ci.org/fasiondog/hikyuu.svg?branch=master - :target: https://travis-ci.org/fasiondog/hikyuu - -.. image:: https://img.shields.io/github/license/mashape/apistatus.svg - :target: https://github.com/fasiondog/hikyuu/blob/master/LICENSE.txt - :alt: GitHub - -.. image:: https://img.shields.io/badge/license-Anti%20996-blue.svg - :target: https://github.com/996icu/996.ICU/blob/master/LICENSE - :alt: GitHub - - ------------ - -Hikyuu Quant Framework是一款基于C++/Python的开源量化交易研究框架,用于策略分析及回测(仅受限于数据,如有数据也可用于期货等)。其核心思想基于当前成熟的系统化交易方法,将整个系统化交易抽象为由市场环境判断策略、系统有效条件、信号指示器、止损/止盈策略、资金管理策略、盈利目标策略、移滑价差算法七大组件,你可以分别构建这些组件的策略资产库,在实际研究中对它们自由组合来观察系统的有效性、稳定性以及单一种类策略的效果。 - - -祝贺 HIKYUU 入选 GITEE 最有价值开源项目 GVP ------------------------------------------------ - -.. image:: https://hikyuu.org/images/gitee_GVP.jpg - :target: https://gitee.com/gvp - :alt: Gitee - - -**给作者加点油,每天扫扫红包,或者请作者喝杯咖啡** - -.. image:: https://hikyuu.org/images/juanzeng.jpg - - -示例: - -:: - - #创建模拟交易账户进行回测,初始资金30万 - my_tm = crtTM(init_cash = 300000) - - #创建信号指示器(以5日EMA为快线,5日EMA自身的10日EMA作为慢线,快线向上穿越慢线时买入,反之卖出) - my_sg = SG_Flex(EMA(CLOSE(), n=5), slow_n=10) - - #固定每次买入1000股 - my_mm = MM_FixedCount(1000) - - #创建交易系统并运行 - sys = SYS_Simple(tm = my_tm, sg = my_sg, mm = my_mm) - sys.run(sm['sz000001'], Query(-150)) - -.. figure:: https://hikyuu.org/images/10000-overview.png - :width: 600px - -完整示例参见:``_ - - -为什么选择 Hikyuu? --------------------- - -- **组合灵活,分类构建策略资产库** Hikyuu对系统化交易方法进行了良好的抽象,包含了九大策略组件:市场环境判断策略、系统有效条件、信号指示器、止损/止盈策略、资金管理策略、盈利目标策略、移滑价差算法、交易对象选择策略、资金分配策略。可以在此基础上构建自己的策略库,并进行灵活的组合和测试。在进行策略探索时,可以更加专注于某一方面的策略性能与影响。其主要功能模块如下: - - .. figure:: https://hikyuu.org/images/10002-function-arc.png - :width: 600px - -- **性能保障,打造自己的专属应用** 目前项目包含了3个主要组成部分:基于C++的核心库、对C++进行包装的Python库(hikyuu)、基于Python的交互式工具。 - - - C++核心库,提供了整体的策略框架,在保证性能的同时,已经考虑了对多线程和多核处理的支持,在未来追求更高运算速度提供便利。C++核心库,可以单独剥离使用,自行构建自己的客户端工具。 - - - Python库(hikyuu),提供了对C++库的包装,同时集成了talib库(如TA_SMA,对应talib.SMA),可以与numpy、pandas数据结构进行互相转换,为使用其他成熟的python数据分析工具提供了便利。 - - - hikyuu.interactive 交互式探索工具,提供了K线、指标、系统信号等的基本绘图功能,用于对量化策略的探索和回测。 - -- **代码简洁,探索更便捷、自由** 同时支持面向对象和命令行编程范式。其中,命令行在进行策略探索时,代码简洁、探索更便捷、自由。 - -- **安全、自由、隐私,搭建自己的专属云量化平台** 结合 Python + Jupyter 的强大能力与云服务器,可以搭建自己专属的云量化平台。将Jupyter部署在云服务器上,随时随地的访问自己的云平台,即刻实现自己新的想法,如下图所示通过手机访问自己的云平台。结合Python强大成熟的数据分析、人工智能工具(如 numpy、scipy、pandas、TensorFlow)搭建更强大的人工智能平台。 - -- **数据存储方式可扩展** 目前支持本地HDF5格式、MySQL存储。默认使用HDF5,数据文件体积小、速度更快、备份更便利。截止至2017年4月21日,沪市日线数据文件149M、深市日线数据文件184M、5分钟线数据各不到2G。 \ No newline at end of file diff --git a/hikyuu/examples/notebook/006-TradeManager.ipynb b/hikyuu/examples/notebook/006-TradeManager.ipynb index f0c18449..d9d1dc5e 100644 --- a/hikyuu/examples/notebook/006-TradeManager.ipynb +++ b/hikyuu/examples/notebook/006-TradeManager.ipynb @@ -9,25 +9,25 @@ "name": "stderr", "output_type": "stream", "text": [ - "2024-04-06 01:24:41,451 [INFO] hikyuu version: 2.0.0_202404040113_RELEASE_windows_x64 [] (D:\\workspace\\hikyuu\\hikyuu\\__init__.py:93) [hikyuu::hku_info]\n" + "2024-08-20 16:00:57,364 [INFO] hikyuu version: 2.1.1_202408182226_RELEASE_windows_x64 [] (D:\\workspace\\hikyuu\\hikyuu\\__init__.py:97) [hikyuu::hku_info]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ - "2024-04-06 01:24:41.867 [HKU-I] - Using MYSQL BaseInfoDriver (BaseInfoDriver.cpp:58)\n", - "2024-04-06 01:24:41.890 [HKU-I] - Loading market information... (StockManager.cpp:532)\n", - "2024-04-06 01:24:41.902 [HKU-I] - Loading stock type information... (StockManager.cpp:545)\n", - "2024-04-06 01:24:41.914 [HKU-I] - Loading stock information... (StockManager.cpp:460)\n", - "2024-04-06 01:24:42.064 [HKU-I] - Loading stock weight... (StockManager.cpp:562)\n", - "2024-04-06 01:24:43.250 [HKU-I] - Loading KData... (StockManager.cpp:133)\n", - "2024-04-06 01:24:43.958 [HKU-I] - Preloading all day kdata to buffer! (StockManager.cpp:171)\n", - "2024-04-06 01:24:43.959 [HKU-I] - Preloading all week kdata to buffer! (StockManager.cpp:174)\n", - "2024-04-06 01:24:43.959 [HKU-I] - Preloading all month kdata to buffer! (StockManager.cpp:177)\n", - "2024-04-06 01:24:43.963 [HKU-I] - 0.71s Loaded Data. (StockManager.cpp:150)\n", - "CPU times: total: 500 ms\n", - "Wall time: 3.35 s\n" + "2024-08-20 16:00:57.865 [HKU-I] - Using MYSQL BaseInfoDriver (BaseInfoDriver.cpp:58)\n", + "2024-08-20 16:00:57.883 [HKU-I] - Loading market information... (StockManager.cpp:481)\n", + "2024-08-20 16:00:57.890 [HKU-I] - Loading stock type information... (StockManager.cpp:494)\n", + "2024-08-20 16:00:57.897 [HKU-I] - Loading stock information... (StockManager.cpp:409)\n", + "2024-08-20 16:00:58.045 [HKU-I] - Loading stock weight... (StockManager.cpp:511)\n", + "2024-08-20 16:00:59.275 [HKU-I] - Loading KData... (StockManager.cpp:134)\n", + "2024-08-20 16:01:00.831 [HKU-I] - Preloading all day kdata to buffer! (StockManager.cpp:179)\n", + "2024-08-20 16:01:00.832 [HKU-I] - Preloading all week kdata to buffer! (StockManager.cpp:179)\n", + "2024-08-20 16:01:00.832 [HKU-I] - Preloading all month kdata to buffer! (StockManager.cpp:179)\n", + "2024-08-20 16:01:00.902 [HKU-I] - 1.63s Loaded Data. (StockManager.cpp:159)\n", + "CPU times: total: 625 ms\n", + "Wall time: 4.01 s\n" ] } ], @@ -78,7 +78,7 @@ " current borrow_cash: 0.00,\n", " current borrow_asset: 0.00,\n", " Position: \n", - " SZ000001 平安银行 2017-01-03 00:00:00 1762 100.00 911.00 1046.00 135.00 14.82% 0.14%\n", + " SZ000001 平安银行 2017-01-03 00:00:00 1854 100.00 911.00 1029.00 118.00 12.95% 0.12%\n", " Short Position: \n", " Borrow Stock: \n", "}\n" @@ -148,21 +148,21 @@ " SZ000001\n", " 平安银行\n", " 2017-01-03\n", - " 1762\n", + " 1854\n", " 100\n", " 911.0\n", - " 1046.0\n", - " 135.0\n", - " 14.81888\n", + " 1029.0\n", + " 118.0\n", + " 12.952799\n", " \n", " \n", "\n", "" ], "text/plain": [ - " 证券名称 买入日期 已持仓天数 持仓数量 投入金额 当前市值 盈亏金额 盈亏比例\n", - "证券代码 \n", - "SZ000001 平安银行 2017-01-03 1762 100 911.0 1046.0 135.0 14.81888" + " 证券名称 买入日期 已持仓天数 持仓数量 投入金额 当前市值 盈亏金额 盈亏比例\n", + "证券代码 \n", + "SZ000001 平安银行 2017-01-03 1854 100 911.0 1029.0 118.0 12.952799" ] }, "execution_count": 3, @@ -259,7 +259,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ @@ -271,7 +271,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 7, "metadata": {}, "outputs": [], "source": [ @@ -298,28 +298,30 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "买入:SZ000001 11.470 1000\n", - "卖出:SZ000001 11.140 1000\n", - "买入:SZ000001 11.500 1000\n", - "卖出:SZ000001 11.290 1000\n", - "买入:SZ000001 10.580 1000\n", - "卖出:SZ000001 10.460 1000\n", - "买入:SZ000001 9.420 1000\n", - "卖出:SZ000001 9.100 1000\n", - "买入:SZ000001 9.330 1000\n", - "卖出:SZ000001 9.160 1000\n", - "买入:SZ000001 9.330 1000\n", - "卖出:SZ000001 10.550 1000\n", - "买入:SZ000001 10.560 1000\n", - "卖出:SZ000001 10.350 1000\n", - "买入:SZ000001 10.560 1000\n" + "买入:SZ000001, 价格: 9.33, 数量: 1000.0 预期止损价: 0.0, 预期目标价: nan, 信号来源: SystemPart.SIGNAL\n", + "卖出:SZ000001, 价格: 9.16, 数量: 1000.0, 信号来源: SystemPart.SIGNAL\n", + "买入:SZ000001, 价格: 9.33, 数量: 1000.0 预期止损价: 0.0, 预期目标价: nan, 信号来源: SystemPart.SIGNAL\n", + "卖出:SZ000001, 价格: 10.55, 数量: 1000.0, 信号来源: SystemPart.SIGNAL\n", + "买入:SZ000001, 价格: 10.56, 数量: 1000.0 预期止损价: 0.0, 预期目标价: nan, 信号来源: SystemPart.SIGNAL\n", + "卖出:SZ000001, 价格: 10.35, 数量: 1000.0, 信号来源: SystemPart.SIGNAL\n", + "买入:SZ000001, 价格: 10.56, 数量: 1000.0 预期止损价: 0.0, 预期目标价: nan, 信号来源: SystemPart.SIGNAL\n", + "卖出:SZ000001, 价格: 10.43, 数量: 1000.0, 信号来源: SystemPart.SIGNAL\n", + "买入:SZ000001, 价格: 10.58, 数量: 1000.0 预期止损价: 0.0, 预期目标价: nan, 信号来源: SystemPart.SIGNAL\n", + "卖出:SZ000001, 价格: 11.12, 数量: 1000.0, 信号来源: SystemPart.SIGNAL\n", + "买入:SZ000001, 价格: 10.4, 数量: 1000.0 预期止损价: 0.0, 预期目标价: nan, 信号来源: SystemPart.SIGNAL\n", + "卖出:SZ000001, 价格: 9.94, 数量: 1000.0, 信号来源: SystemPart.SIGNAL\n", + "买入:SZ000001, 价格: 10.31, 数量: 1000.0 预期止损价: 0.0, 预期目标价: nan, 信号来源: SystemPart.SIGNAL\n", + "卖出:SZ000001, 价格: 10.12, 数量: 1000.0, 信号来源: SystemPart.SIGNAL\n", + "买入:SZ000001, 价格: 10.22, 数量: 1000.0 预期止损价: 0.0, 预期目标价: nan, 信号来源: SystemPart.SIGNAL\n", + "卖出:SZ000001, 价格: 10.11, 数量: 1000.0, 信号来源: SystemPart.SIGNAL\n", + "买入:SZ000001, 价格: 10.13, 数量: 1000.0 预期止损价: 0.0, 预期目标价: nan, 信号来源: SystemPart.SIGNAL\n" ] } ], diff --git a/hikyuu/examples/notebook/008-Pickle.ipynb b/hikyuu/examples/notebook/008-Pickle.ipynb index ca4707a5..fd7e0f39 100644 --- a/hikyuu/examples/notebook/008-Pickle.ipynb +++ b/hikyuu/examples/notebook/008-Pickle.ipynb @@ -5,24 +5,29 @@ "execution_count": 1, "metadata": {}, "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2024-08-20 15:45:43,093 [INFO] hikyuu version: 2.1.1_202408182226_RELEASE_windows_x64 [] (D:\\workspace\\hikyuu\\hikyuu\\__init__.py:97) [hikyuu::hku_info]\n" + ] + }, { "name": "stdout", "output_type": "stream", "text": [ - "warning: can't import TA-Lib, will be ignored! You can fetch ta-lib from https://www.lfd.uci.edu/~gohlke/pythonlibs/#ta-lib\n", - "std::cout are redirected to python::stdout\n", - "std::cerr are redirected to python::stderr\n", - "2023-10-14 02:24:48.199 [HKU-I] - Using SQLITE3 BaseInfoDriver (BaseInfoDriver.cpp:58)\n", - "2023-10-14 02:24:48.200 [HKU-I] - Loading market information... (StockManager.cpp:499)\n", - "2023-10-14 02:24:48.200 [HKU-I] - Loading stock type information... (StockManager.cpp:512)\n", - "2023-10-14 02:24:48.200 [HKU-I] - Loading stock information... (StockManager.cpp:426)\n", - "2023-10-14 02:24:48.252 [HKU-I] - Loading stock weight... (StockManager.cpp:529)\n", - "2023-10-14 02:24:48.630 [HKU-I] - Loading KData... (StockManager.cpp:134)\n", - "2023-10-14 02:24:48.638 [HKU-I] - Preloading all day kdata to buffer! (StockManager.cpp:157)\n", - "2023-10-14 02:24:48.639 [HKU-I] - Preloading all week kdata to buffer! (StockManager.cpp:160)\n", - "2023-10-14 02:24:48.639 [HKU-I] - Preloading all month kdata to buffer! (StockManager.cpp:163)\n", - "2023-10-14 02:24:48.659 [HKU-I] - 0.03s Loaded Data. (StockManager.cpp:145)\n", - "Wall time: 1.16 s\n" + "2024-08-20 15:45:43.596 [HKU-I] - Using MYSQL BaseInfoDriver (BaseInfoDriver.cpp:58)\n", + "2024-08-20 15:45:43.615 [HKU-I] - Loading market information... (StockManager.cpp:481)\n", + "2024-08-20 15:45:43.621 [HKU-I] - Loading stock type information... (StockManager.cpp:494)\n", + "2024-08-20 15:45:43.628 [HKU-I] - Loading stock information... (StockManager.cpp:409)\n", + "2024-08-20 15:45:43.785 [HKU-I] - Loading stock weight... (StockManager.cpp:511)\n", + "2024-08-20 15:45:45.041 [HKU-I] - Loading KData... (StockManager.cpp:134)\n", + "2024-08-20 15:45:46.483 [HKU-I] - Preloading all day kdata to buffer! (StockManager.cpp:179)\n", + "2024-08-20 15:45:46.483 [HKU-I] - Preloading all week kdata to buffer! (StockManager.cpp:179)\n", + "2024-08-20 15:45:46.484 [HKU-I] - Preloading all month kdata to buffer! (StockManager.cpp:179)\n", + "2024-08-20 15:45:46.552 [HKU-I] - 1.51s Loaded Data. (StockManager.cpp:159)\n", + "CPU times: total: 516 ms\n", + "Wall time: 3.78 s\n" ] } ], @@ -42,21 +47,9 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 3, "metadata": {}, - "outputs": [ - { - "ename": "UnicodeDecodeError", - "evalue": "'utf-8' codec can't decode byte 0x9c in position 133: invalid start byte", - "output_type": "error", - "traceback": [ - "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[1;31mUnicodeDecodeError\u001b[0m Traceback (most recent call last)", - "\u001b[1;32m~\\AppData\\Local\\Temp\\ipykernel_7616\\6354363.py\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[0;32m 2\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 3\u001b[0m \u001b[1;32mwith\u001b[0m \u001b[0mopen\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"temp\"\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34m'wb'\u001b[0m\u001b[1;33m)\u001b[0m \u001b[1;32mas\u001b[0m \u001b[0mf\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 4\u001b[1;33m \u001b[0mpickle\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdump\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mk\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mf\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", - "\u001b[1;31mUnicodeDecodeError\u001b[0m: 'utf-8' codec can't decode byte 0x9c in position 133: invalid start byte" - ] - } - ], + "outputs": [], "source": [ "import pickle\n", "\n", @@ -66,7 +59,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 4, "metadata": {}, "outputs": [], "source": [ @@ -75,22 +68,21 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ - "k2 = KData()\n", - "hku_load(k2, \"temp\")" + "k2 = hku_load(\"temp\")" ] }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 6, "metadata": {}, "outputs": [ { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAA9cAAAMTCAYAAACrDuSLAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjYuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8o6BhiAAAACXBIWXMAAA9hAAAPYQGoP6dpAACl3ElEQVR4nOzdeXgTdeLH8U8PaAhHQIIIhYCIlnqhrYCuCN4CP3QFb7bifVQBLe6i4gEi4CKuqLBWwRMryCqeqAgiiLgCQqXIWqsIEiiHBDAgJaXH/P4YE5o2PdM2Sft+PU+eJjOTyXeSTJpPvleUYRiGAAAAAABAjUWHugAAAAAAAEQ6wjUAAAAAAEEiXAMAAAAAECTCNQAAAAAAQSJcAwAAAAAQJMI1AAAAAABBIlwDAAAAABAkwjUAAKhz+fn5Onz4cKiLAQBAnSFcAwAQRrZv367x48dr48aNlW5bUFCg2bNnB/2Yv/32m5555hkZhiFJcjqdevLJJ6t8/yVLlqiwsFCSdNppp/nKtHz5cm3atEmS9Prrr+vYY48NuqyhsHXrVqWlpcntdvstX7Roke677z5J0oYNG7Rq1apQFA8AECYI1wAAhJHt27frscceq1K4XrlypW655Rbdc889kqRt27YpKiqqSpe+ffv69uN0OpWWlqaioiJJ0qZNmzR27Fi/x9q6das++OADPfzww7r44ouVmJio/Px8FRQUaOTIkb6QWVhYqOLiYm3evFmXX365PvroI0nSqlWrdN5559XKcxRIfn6+nnrqKX3//fd+y8ePH1/l5yQqKkq//vprmX0/88wzWrhwoVq0aOG3/JdfftGcOXMkSd99953OO+88jRs3TsXFxb5tDh48qCeffFI///xz7R80ACCsEK4BAD7Lly/XkCFD5HA41KRJE7Vs2VLJycl+gePcc89V165dy91HZesLCgp0zDHHaPTo0b5lr7zyik455RRZLBZ16tRJY8eOLdOE2OPxaOzYsXI4HIqLi9NJJ50UsNY2NzdXw4YNU9u2bWW1WtWvXz+tXr26zHb//e9/de6556pFixZq06aNhg0bph07dpTZriplK/nY1113nW688cZyj782nXPOOXrllVc0ffp0PfjggzrmmGOUnZ1d5tK+fXuNHz/eb5k3FJanuLhYt9xyi84880zZbDb95S9/0axZsxQTE6N77rlHM2bM0IEDB9SkSRO9+eabev/99/Xbb7/57v/QQw9pyJAhuueee2QYhhYuXKiLLrqozp6Le++9V2+++aZOOOGEMuvat28f8HkpeXn99dcD7nfPnj2aNWuWHn/8ccXExPits1qtys/PlyRdf/31+uKLL5SRkaGcnBzfNs2bN5fL5dKQIUOUl5dXi0cMAAg3saEuAAAgPLz44ou688471bdvX40bN05t27bV9u3b9dFHH2nv3r0VBubqmD9/vn777TelpqZKkp577jndc889uvXWW/X444/rq6++0hNPPKG9e/fqhRde8N1v2LBh+vTTT/Xoo48qISFBL7/8sm644QY1bdpU1157rSTJ7XbrnHPOUVFRkaZMmSKLxaJJkybpggsuUGZmpo4//nhJ0urVq3X++eerT58+evXVV7V9+3Y98sgj+v7775WZmakmTZpUq2wrVqzQ7NmzlZGRoUOHDumGG26oleeqKq6//nr9+uuvWr9+vaKiotSjR48y28TGxqp9+/Zl1r322mu66aabfLe9x/3GG28oKipKf/nLX3TLLbfoxBNPVOvWrf3ue9FFF6l37966+uqr9csvv+if//ynli9frv3792vNmjW6+OKL1bRpU/3www9yuVzavn27brnlFt1yyy1++/n666/Vp0+foJ6DL774Qi+++KLWrVunuLi4gMcf6HkpaefOnQGXT5kyRYmJibryyivldDrlcDh865o3by6PxyPJfO+1bNlSzz77rFasWKH9+/f7jmvSpEl6//33NW7cOE2dOrWmhwkACHcGAACGYXTs2NHo0qWLUVBQUOF2/fv3N7p06VLj9X379jUuueQSwzAMY//+/UbLli2NoUOH+m0zfPhwIzo62nA6nYZhGMbSpUsNScZzzz3n2+bw4cNG9+7dje7du/uWjRs3zoiKijKysrJ8y37++WcjOjrauO222/zK2LlzZ+PgwYO+Za+88oohyXjzzTerVTbDMAybzWaccsopxoIFCwxJxg033FDu8Vfm22+/NSQZn376abXuV1xcXO66+Ph4Iz09vczy33//3cjOzjbeeecdQ5KxYcMGIzs72/j888+NqKioCt8LHTt2NObMmWM8//zzxgUXXGBccMEFxoknnmhIMk488UTfstdff90YOnSo0bdvX2Pz5s3G5s2bjSVLlhiSjC+//NLweDzVOs5A+vXrZwwZMiTgunHjxhnx8fGV7sP7Htu8ebNv2aZNmwyLxWKsWLHCOHjwoNGxY0fjtttuMx5//HHj9ttvN3r37m1IMlq2bGlIMqKjo40OHToYffr0MV5//XW//b/88suG1Wo1du3aFdSxAgDCF83CAQCSzL6hdW39+vVasWKF7r77bknmgFAHDhzQrbfe6rfd9ddfr+LiYi1atEiSWdsdExPj19y6SZMmuuaaa7Rx40b98ssvvu169eqlU0891bdd9+7dddZZZ+nTTz+VJLlcLi1fvlzXXXedrFarb7trr71WTZo08W1X1bJJ0rJly7R+/Xr93//9X7BPkc/AgQMD9gm2WCySpD/++EMPP/yw71JysK0LL7xQDz/8cKWPYbPZ1KNHD3Xp0kWSlJCQoB49esjhcMgwDL344ovauXOn32XHjh164YUXtGvXLp177rlKTU3V559/rscee0x79+6VxWLR0UcfrVtvvVWff/65TjrpJL3//vsqKipS165d1bVrV0VHRysqKkq9e/cOWNNcHT/99JOWL1+u22+/Paj9lGYYhm6//XYNGDBA3bp105QpU1RUVKS//e1vWrlypWJiYpScnCxJ+s9//qNNmzbJ4/Fo+/btWrlypYYPH+63v5SUFEnSW2+9VavlBACED5qFAwAkSddcc41mzpypyy+/XM8995y6detW7raGYeiPP/4IuM47KFYg//73v9WlSxdfCM3KypJkjjBd0imnnCJJvr6rWVlZOu6449SyZctyt+vcubOys7PLNDv2bvf111/r4MGDWr9+vQzDKPOYzZo103HHHef3mFUpW6BtasOsWbP8Bh3zio42fxcvKirSxo0bdfDgQS1YsEC33nprmabbNXX88cfr3nvv1QMPPKARI0aUWd++fXs9//zz6tChgw4fPqypU6dq4sSJ+ve//62nn35aXbt21d13362vvvpKbrdbxx57rF+//V9//VXt27f3/VAQjMWLFysuLk79+vULel8lFRUVafXq1dq/f78++ugjFRUV6c0331T//v3Vv39/SeaAZunp6erRo0eZbhMFBQW+ZvaS1LRpU/Xr10+LFy/WqFGjarWsAIDwQLgGAEgyR0QuLi7Wyy+/rM8++0wpKSl66KGH1L179zLbOp3OMkG3JG9NaElut1tvvvmmHn74YV9A9A6AZbfb/bY96qijJEm///67b7vS25Tebu/evSoqKqpwO7fbXe5jerfbvXt3tcpWVzp16lRhP2Gbzaa33npLv/76qxYsWFDp/lJTU3393CVzdOvSPwr8+uuv2rt3r3r37q0nnnhCTzzxRLn7i401v0IMHDhQ3377rWbPnq2rrrpKTz/9tPr376+xY8fq6quv1rPPPquHHnpIJ598snbu3KljjjlGWVlZtfaDRGZmpk488US/Vgil5ebmKioqqlr7jY2N1U8//aQWLVro5ptv1t69ezVs2LAy20hSRkaGJDNsb9q0Sb/88osKCwvL9ONOSkrSG2+8Ua1yAAAiB+EaACDJrLmdNWuWRowYoUmTJmn27NmaM2eOJk6cqH/84x9+2x5zzDF6++23A+5n5MiR2rdvX5nlr7/+ugoLC/1qlr1zI3vDtpc3CHmXFxYWltmm9Hbl7au625V8zKqULVKMHz9e11xzje/2scceK6fTqY8//liffPKJJLPG+pprrtEzzzyjDh06VLi/cePGafz48fr73/+u448/Xt27d9fQoUM1efJkXXbZZZLM4Ot9vk444QR99dVXuuqqq/Tf//631kYO/+2333T00UdXuE379u21bNmyCrdZvXp1mYHo2rdvr3fffVcLFy70TfE1b948/fOf/9SmTZu0f/9+SeaI8klJSTruuOP0l7/8Rd26dQs4anm7du18P94AABoewjUAwE/Pnj31n//8R99//72uueYajRkzRl26dNHVV1/t2yYuLi5gk2XJrFENFK7T09N19dVXq127dn7bSmYtcNu2bX3Lvff31hrbbLaANcUltyu5r0DbRUdHq02bNpVuV/Ixq1K2cGIYRrk1tIFGC58/f74effRRnXzyyZKkzZs3q2vXrr4a12+++SZgy4UrrrjCd/0vf/mLCgoKlJWVpffff1833HCDXC6Xb33btm19I49/8MEHOvfcc7VmzRo99dRTQR+vZI4V0KZNmwq3qelo4Zs2bdKtt96qp59+WoZh6NNPP1WTJk2Ulpam4447Tt27d9fVV1+tc845RxMnTpQkPfDAA/r444/1/vvvl9lfs2bNlJ+fr+Li4oj7cQYAUDk+2QEAAZ1yyilauHChJGnu3LlB7evzzz/Xjz/+qLvuustvuTfw/O9///Nb7r3ds2dP33YbN270zSlccruoqCidcsopatmypTp27FhmX97tevToobi4uHIf0+Px6JdffvF7zKqULZzcfffd5U4pFchNN92kPXv2aPr06ZLMpugltW7dWna7vcylZF/iv/71r2rXrp1OO+00GYahyy+/XO3atfNdvLW7w4cP1/z58/XUU0/Jbrfr7LPProUjNsN7yTBfm+677z7t27dPI0aMUNeuXXXLLbeoQ4cOGj58uM4++2y1b99effr00YoVKySZU249/fTTZQbB89q9e7eOOuoogjUANFB8ugMAJEk7duwos+zw4cOSzBq3YDz//PNKSkrSmWee6bd84MCBiomJ0Wuvvea3/PXXX1fLli01YMAASdLgwYPl8Xg0b9483zYFBQWaO3eu+vXrp/bt2/u2++qrr7Rp0ybfdr/88otWrFihq666SpLZ9DkhIUFz5sxRQUGBb7t58+YpPz/ft11VyxZqhmFIkv7xj39o3rx5vnmXq6J169Zq2rRpuesTExMDjli+ZMkS3zbLli3T119/LavVqpycHBmGIcMwNGHCBPXq1cvXAqB///7q0qWLnnzySd155521FjA7deqkrVu31sq+Srvtttv0+uuva/ny5XK73dq+fbvOOussv20GDhyoFStW6Nprr9U///lPzZ8/X+ecc07AH3mcTqc6d+5cJ2UFAIQezcIBAJLMaZiuueYanXPOOWrdurWcTqdmzJihpk2b+qbOqolt27bpww8/1MyZM8us69Spk0aOHKlnn31WrVq10nnnnadly5bptdde0/Tp032Dpl199dV6+umnNWLECLlcLnXt2lUvvfSSnE6n3nzzTd/+xo4dq//85z8aOHCgHnroIUVFRWnSpEnq0qWLRo8e7dvun//8p4YOHarLLrtMt912m5xOpx599FFdccUVOvfcc6tVtlDbsmWLJGnJkiVasmRJmVGrg7F27dqAfYcvvfRSv9uvvfaa4uLi9OGHH+qOO+7Qd999pylTpvj6cktScXGxOnfurJycnFodXb1///565plntGXLloAD6QVj0KBBvusHDhzQ4sWL9ccff2jIkCG+5YcOHVJxcbGWL1+ub775RieffLKWLFmiiy66SAcOHFDz5s192y5durRWp2sDAIQXwjUAQJJ0xx13aMmSJXr33XeVl5enjh07qm/fvpo3b15QTaBfeOEFtWrVStddd13A9U899ZTatGmjl156Senp6erevbtefvll3Xzzzb5tYmJi9Omnn+rvf/+7Jk6cKI/Ho169emnJkiU644wzfNt16dJFy5Yt0+jRo3XnnXcqLi5OAwYM0FNPPaVWrVr5trv88ss1b948Pf7447ruuuvUrl073X333Ro3bly1y1ZThw8f9rUMKOnQoUOSzGbq5U13JpmtCWJiYpSTk6MWLVpo4cKFOv3002tUFm/td2lWq1UtWrQoszwmJsbv9syZMzVs2DCNHz9eEydOVFFRkcaMGeM3PdY999yjpUuXqmfPnrrxxhvVtWvXGpe3pPPPP1/NmzfXhx9+qJEjRwbcprCwUD/++GOF+3E6nX63i4qKNG/ePH399df673//q/Xr18tms+m+++7TkCFDtHLlSj388MP68ssv1b9/f61cudJ335ycHB133HF+wfrHH3/Uxo0by/wwAQBoQAwAAOrI4cOHjfbt2xujR48OdVHCzt13321IqvFl6dKlvn1lZ2cbhmFUex/Z2dnGiy++aPztb38zWrZs6dvfjh07Kr3vuHHj/I7nyy+/NC6++GKjTZs2Rq9evYymTZsao0aNMjwej3H77bcbUVFRxmuvvWYcOHDASEpKMlq1amXMnj27Vp7Lv//970bXrl2NgoKCMuvGjRtXredk8+bNvvsmJycbQ4YMMZ555hnju+++Mw4ePGi8+OKLRlJSkhEVFWVcfvnlxo8//mgUFxcbgwYNMo4++mjj9ddfN3r37m3cfvvtfuW44YYbjOTk5Fo5XgBAeKLmGgBQZ9555x399ttvfvMrwzRx4kQ98MADNb5/yVHXvYOvZWdnV2sfBw8e1LPPPqsmTZoEHL37q6++CjhaeMmR4++//37NnTtXLpdLN998s+bMmaO2bdvqvffeU25urvr27asNGzbo9ddf1/XXXy9JWrhwoa6++moNHz5cxx57bLkjz1fVmDFj9MYbb2jGjBm69957y6yPj4/Xtm3bKtzHsmXLdN555/ktW7Nmjd/tgoICvf322zrzzDP15ptv+o1A/u677+rRRx/V/fffr+bNm2vMmDG+devWrdPcuXP9mskDABqeKMMopy0YAABBeuedd7R7927CdQM2a9YsNW3aVEOHDg3YD/1f//qXLrjggjL9rIuKijR//ny/oB6MpUuXavDgwVqxYkWtNDevLX/88YeSk5N11VVX+abrAgA0TIRrAAAAAACCxFRcAAAAAAAEiXANAAAAAECQCNcAAAAAAASpQY0WXlxc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dJNCQkIqvNq1a1dt3+pqLisrS0OGDHHv7/Tp03XhwoWa/KgAvwvG+pOkDz/8UF26dFFkZKQ6d+6sjRs31nifSktLtWzZMvXo0aPGfYCaCNZ6kaTz58/rmWee0dChQyssy87O1rBhw3TZZZepVatWeuGFF3za78qOERctWqSOHTsqKipK7du319KlS33ariTJAGjQ/vznP5t58+aZzMxMs379ehMXF2eSkpKMMcasXbvWREZGmpUrV5odO3aYvn37ms6dO5uSkhJjjDGPPfaYefDBB80333xjvv76azNw4EDTsmVLk5eXZ4wxZsuWLeapp54y27dvN59//rnp0qWL6dWrV43GtWLFCnPZZZeZRYsWme+//95s2LDB/Pjjj1X22b17t+natatZt26dyczMNM8++6wJCQkxa9euda+zbt0607t3bxMREWGaNGlSmx8ZYEkw1lxOTo7Zv3+/x6tr165m1qxZVfarruZKSkpM27ZtzbBhw0xGRob54IMPjNPpNHPmzLHwEwRqLxjrb/v27aZJkybmmWeeMTt27DAPP/ywsdlsZt++fdX2Xb58ubnuuutMRESEadeunYWfDFBRMNbLhQsXzMKFC43L5TIRERFmwIABFZZ37drV3HLLLSYjI8MsX77chIaGmjVr1lS77eqOEadOnWo+/PBDs2vXLvP0008bSebjjz+udrsXI1wDDdyRI0c83r/99tsmNDTUnDt3zvTs2dNMmTLFvWzPnj1Gkvniiy+89j169KiRZD799FOvn7V27VojyZw+fbrKMZ04ccJER0ebN954w6d9OXHihDl79qxH26BBg8zdd9/tfj906FAza9Yss2jRIsI1AiIYa+6PPvvsMxMdHW1OnDhR5XrV1dxvv/1mJJndu3e7l6ekpLgPzoC6Foz1d9ddd3nURGlpqenYsaNJSUmpdn/i4+PNc889Z2bNmkW4ht8FY72cOXPGdOrUyaxYscKMHTu2Qrj+8MMPTXh4uDl27Ji7bdSoUSYxMbHa/fX1GPG6666rUZ1ejMvCgQauTZs2Hu9tNptKS0t1+vRpfffddx6X23Tq1EktW7ZUenp6pX0lqaSkxOtnlZSUyGaz6bLLLqtyTO+++66aNm2q0aNH+7Qvl19+eYVt22w2j/H885//1Lx589S0aVOftg34SzDW3B899dRTSk5O1uWXX17letXVXIsW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\n", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAA9MAAAMTCAYAAACi5UTxAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy81sbWrAAAACXBIWXMAAA9hAAAPYQGoP6dpAACdi0lEQVR4nOzdeXhU5eH28TsQyTASBmVcIHAAxYa4ICQCLggoLsCPqlhcG1HrgoFIDbYUcUFRoVTf4oJGsbgVxLVVSyuuKGrFJRHQGiMIOBCxMqITIA6Q5Lx/PJ0tmSyTbSbJ93Ndc5E5z5kzz5mNuefZkmzbtgUAAAAAAOqtQ7wrAAAAAABAa0OYBgAAAAAgRoRpAAAAAABiRJgGAAAAACBGhGkAAAAAAGJEmAYAAAAAIEaEaQAAAAAAYkSYBgAAzW7Pnj3au3dvvKsBAECTIUwDAJBAvv32W916663asGFDnfvu27dPTz75ZKPv8/vvv9c999wj27YlSR6PR3/605/qffs333xT5eXlkqRBgwYF67Rq1Spt3LhRkvTEE0+oX79+ja5rPGzZskV5eXny+XwR21977TVdf/31kqTPP/9cH374YTyqBwCIE8I0AAAJ5Ntvv9Vtt91WrzC9evVqXXHFFfrtb38rSdq6dauSkpLqdRk+fHjwOB6PR3l5eaqoqJAkbdy4UbNmzYq4ry1btuill17STTfdpDPOOEMZGRnas2eP9u3bp2uvvTYYKsvLy1VZWalNmzbpnHPO0T/+8Q9J0ocffqhTTjmlSR6jaPbs2aO7775bn332WcT2W2+9td6PSVJSkjZv3lzt2Pfcc49WrFihLl26RGz/+uuv9dRTT0mSPv30U51yyimaPXu2Kisrg/vs3r1bf/rTn7R+/fqmP2kAQFwRpgEAQatWrdKECRNkWZb2228/paamKisrKyJgjBo1Sn379q3xGHWV79u3T4ceeqimT58e3Pboo4/qmGOOkcPhUK9evTRr1qxqXYL9fr9mzZoly7KUkpKio446KmqrbElJiS6++GJ1795dTqdTI0aM0EcffVRtv3//+98aNWqUunTpogMOOEAXX3yxtm3bVm2/+tQt/L4vuugiXXbZZTWef1M6+eST9eijj+r+++/XDTfcoEMPPVRFRUXVLocccohuvfXWiG2BEFiTyspKXXHFFTr++OPlcrl04okn6pFHHlHHjh3129/+VgsXLtTOnTu13377aenSpXrxxRf1/fffB29/4403asKECfrtb38r27a1YsUKnX766c32WFx33XVaunSpfvGLX1QrO+SQQ6I+LuGXJ554Iupxf/jhBz3yyCO6/fbb1bFjx4gyp9OpPXv2SJIuueQSvfXWW1qyZImKi4uD++y///7yer2aMGGCysrKmvCMAQDxlhzvCgAAEsPDDz+sa665RsOHD9fs2bPVvXt3ffvtt/rHP/6hHTt21BqQY/HCCy/o+++/V05OjiTpvvvu029/+1tdeeWVuv322/Xuu+9q3rx52rFjhx566KHg7S6++GK98soruuWWW5Senq7Fixfr0ksvVadOnXThhRdKknw+n04++WRVVFRo/vz5cjgcuvPOOzV69GgVFhbqiCOOkCR99NFHOvXUUzVs2DA99thj+vbbb3XzzTfrs88+U2Fhofbbb7+Y6vbee+/pySef1JIlS/Tzzz/r0ksvbZLHqj4uueQSbd68WevWrVNSUpIGDBhQbZ/k5GQdcsgh1coef/xxXX755cHrgfP+61//qqSkJJ144om64oordOSRR6pbt24Rtz399NM1dOhQnX/++fr666/1xz/+UatWrVJpaak++eQTnXHGGerUqZO++OILeb1effvtt7riiit0xRVXRBzn/fff17Bhwxr1GLz11lt6+OGHtWbNGqWkpEQ9/2iPS7jvvvsu6vb58+crIyNDEydOlMfjkWVZwbL9999ffr9fknntpaam6t5779V7772n0tLS4HndeeedevHFFzV79mzdddddDT1NAECisQEAsG27Z8+edp8+fex9+/bVut/IkSPtPn36NLh8+PDh9plnnmnbtm2Xlpbaqamp9rnnnhuxz6RJk+wOHTrYHo/Htm3bXrlypS3Jvu+++4L77N271+7fv7/dv3//4LbZs2fbSUlJ9tq1a4Pb1q9fb3fo0MG+6qqrIurYu3dve/fu3cFtjz76qC3JXrp0aUx1s23bdrlc9jHHHGMvX77clmRfeumlNZ5/XT7++GNbkv3KK6/EdLvKysoay9LS0uz8/Pxq23/66Se7qKjIfv75521J9ueff24XFRXZb7zxhp2UlFTra6Fnz572U089ZT/44IP26NGj7dGjR9tHHnmkLck+8sgjg9ueeOIJ+9xzz7WHDx9ub9q0yd60aZP95ptv2pLsd955x/b7/TGdZzQjRoywJ0yYELVs9uzZdlpaWp3HCLzGNm3aFNy2ceNG2+Fw2O+99569e/duu2fPnvZVV11l33777fbVV19tDx061JZkp6am2pLsDh062D169LCHDRtmP/HEExHHX7x4se10Ou3//ve/jTpXAEDioJs3AECSGdvZ3NatW6f33ntPU6dOlWQmcNq5c6euvPLKiP0uueQSVVZW6rXXXpNkWrM7duwY0X16v/320wUXXKANGzbo66+/Du43ZMgQDRw4MLhf//79dcIJJ+iVV16RJHm9Xq1atUoXXXSRnE5ncL8LL7xQ++23X3C/+tZNkt5++22tW7dO//d//9fYhyho7NixUcf0OhwOSdKuXbt00003BS/hk2Oddtppuummm+q8D5fLpQEDBqhPnz6SpPT0dA0YMECWZcm2bT388MP67rvvIi7btm3TQw89pP/+978aNWqUcnJy9MYbb+i2227Tjh075HA4dPDBB+vKK6/UG2+8oaOOOkovvviiKioq1LdvX/Xt21cdOnRQUlKShg4dGrUlORZfffWVVq1apauvvrpRx6nKtm1dffXVGjNmjA477DDNnz9fFRUV+vWvf63Vq1erY8eOysrKkiQ9++yz2rhxo/x+v7799lutXr1akyZNijhedna2JOnpp59u0noCAOKHbt4AAEnSBRdcoEWLFumcc87Rfffdp8MOO6zGfW3b1q5du6KWBSaxiuaBBx5Qnz59gqFz7dq1kswM0OGOOeYYSQqOPV27dq0OP/xwpaam1rhf7969VVRUVK0bcWC/999/X7t379a6detk23a1++zcubMOP/zwiPusT92i7dMUHnnkkYhJwgI6dDC/g1dUVGjDhg3avXu3li9friuvvLJaV+yGOuKII3Tddddp5syZys3NrVZ+yCGH6MEHH1SPHj20d+9e3XXXXbrjjjv0wAMP6M9//rP69u2rqVOn6t1335XP51O/fv0ixt1v3rxZhxxySPCHgcZ4/fXXlZKSohEjRjT6WOEqKir00UcfqbS0VP/4xz9UUVGhpUuXauTIkRo5cqQkMwFZfn6+BgwYUG0YxL59+4Ld5iWpU6dOGjFihF5//XVNmzatSesKAIgPwjQAQJKZsbiyslKLFy/Wq6++quzsbN14443q379/tX09Hk+1YBsu0NIZzufzaenSpbrpppuCgTAwYZXb7Y7Y98ADD5Qk/fTTT8H9qu5Tdb8dO3aooqKi1v18Pl+N9xnYb/v27THVrbn06tWr1nG+LpdLTz/9tDZv3qzly5fXebycnJzgOHXJzD5d9UeAzZs3a8eOHRo6dKjmzZunefPm1Xi85GTzFWLs2LH6+OOP9eSTT+q8887Tn//8Z40cOVKzZs3S+eefr3vvvVc33nijjj76aH333Xc69NBDtXbt2ib7AaKwsFBHHnlkRC+DqkpKSpSUlBTTcZOTk/XVV1+pS5cu+s1vfqMdO3bo4osvrraPJC1ZskSSCdcbN27U119/rfLy8mrjsDMzM/XXv/41pnoAABIXYRoAIMm0zD7yyCPKzc3VnXfeqSeffFJPPfWU7rjjDv3+97+P2PfQQw/Vc889F/U41157rX788cdq25944gmVl5dHtBwH1iYOhOuAQPAJbC8vL6+2T9X9ajpWrPuF32d96tZa3HrrrbrggguC1/v16yePx6N//vOf+te//iXJtEhfcMEFuueee9SjR49ajzd79mzdeuut+t3vfqcjjjhC/fv317nnnqu5c+fqrLPOkmSCbuDx+sUvfqF3331X5513nv7973832cze33//vQ4++OBa9znkkEP09ttv17rPRx99VG3iuEMOOUR/+9vftGLFiuCSW88884z++Mc/auPGjSotLZVkZnzPzMzU4YcfrhNPPFGHHXZY1FnFDzrooOCPNQCA1o8wDQCIcOyxx+rZZ5/VZ599pgsuuEAzZsxQnz59dP755wf3SUlJidoFWTItptHCdH5+vs4//3wddNBBEftKppW3e/fuwe2B2wdahV0uV9SW4PD9wo8Vbb8OHTrogAMOqHO/8PusT90SiW3bNbbARpvN+4UXXtAtt9yio48+WpK0adMm9e3bN9ii+sEHH0TtmfCrX/0q+PeJJ56offv2ae3atXrxxRd16aWXyuv1Bsu7d+8enBn8pZde0qhRo/TJJ5/o7rvvbvT5Smas/wEHHFDrPg2dzXvjxo268sor9ec//1m2beuVV17Rfvvtp7y8PB1++OHq37+/zj//fJ188sm64447JEkzZ87UP//5T7344ovVjte5c2ft2bNHlZWVre7HGABAdXySAwCiOuaYY7RixQpJ0rJlyxp1rDfeeENffvmlpkyZErE9EHD+85//RGwPXD/22GOD+23YsCG4pm/4fklJSTrmmGOUmpqqnj17VjtWYL8BAwYoJSWlxvv0+/36+uuvI+6zPnVLJFOnTq1xiadoLr/8cv3www+6//77JZmu5eG6desmt9td7RI+Fvjss8/WQQcdpEGDBsm2bZ1zzjk66KCDgpdA6+2kSZP0wgsv6O6775bb7dZJJ53UBGdswnp4eG9K119/vX788Ufl5uaqb9++uuKKK9SjRw9NmjRJJ510kg455BANGzZM7733niSzBNaf//znapPWBWzfvl0HHnggQRoA2gg+zQEAkqRt27ZV27Z3715JpkWtMR588EFlZmbq+OOPj9g+duxYdezYUY8//njE9ieeeEKpqakaM2aMJGn8+PHy+/165plngvvs27dPy5Yt04gRI3TIIYcE93v33Xe1cePG4H5ff/213nvvPZ133nmSTFfm9PR0PfXUU9q3b19wv2eeeUZ79uwJ7lffusWbbduSpN///vd65plnguse10e3bt3UqVOnGsszMjKizij+5ptvBvd5++239f7778vpdKq4uFi2bcu2bc2ZM0dDhgwJtvCPHDlSffr00Z/+9Cddc801TRYoe/XqpS1btjTJsaq66qqr9MQTT2jVqlXy+Xz69ttvdcIJJ0TsM3bsWL333nu68MIL9cc//lEvvPCCTj755Kg/6ng8HvXu3btZ6goAaHl08wYASDLLIl1wwQU6+eST1a1bN3k8Hi1cuFCdOnUKLmXVEFu3btXLL7+sRYsWVSvr1auXrr32Wt17773q2rWrTjnlFL399tt6/PHHdf/99wcnOTv//PP15z//Wbm5ufJ6verbt6/+8pe/yOPxaOnSpcHjzZo1S88++6zGjh2rG2+8UUlJSbrzzjvVp08fTZ8+PbjfH//4R5177rk666yzdNVVV8nj8eiWW27Rr371K40aNSqmusXbN998I0l688039eabb1abVboxCgoKoo79/eUvfxlx/fHHH1dKSopefvllTZ48WZ9++qnmz58fHIstSZWVlerdu7eKi4ubdPbzkSNH6p577tE333wTdeK7xhg3blzw7507d+r111/Xrl27NGHChOD2n3/+WZWVlVq1apU++OADHX300XrzzTd1+umna+fOndp///2D+65cubJJl08DAMQXYRoAIEmaPHmy3nzzTf3tb39TWVmZevbsqeHDh+uZZ55pVJfmhx56SF27dtVFF10Utfzuu+/WAQccoL/85S/Kz89X//79tXjxYv3mN78J7tOxY0e98sor+t3vfqc77rhDfr9fQ4YM0ZtvvqnjjjsuuF+fPn309ttva/r06brmmmuUkpKiMWPG6O6771bXrl2D+51zzjl65plndPvtt+uiiy7SQQcdpKlTp2r27Nkx162h9u7dG2z5D/fzzz9LMt3Oa1p+TDK9BTp27Kji4mJ16dJFK1as0ODBgxtUl0DrdlVOp1NdunSptr1jx44R1xctWqSLL75Yt956q+644w5VVFRoxowZEctV/fa3v9XKlSt17LHH6rLLLlPfvn0bXN9wp556qvbff3+9/PLLuvbaa6PuU15eri+//LLW43g8nojrFRUVeuaZZ/T+++/r3//+t9atWyeXy6Xrr79eEyZM0OrVq3XTTTfpnXfe0ciRI7V69ergbYuLi3X44YdHBOkvv/xSGzZsqPZDBACgFbMBAGgme/futQ855BB7+vTp8a5Kwpk6daotqcGXlStXBo9VVFRk27Yd8zGKiorshx9+2P71r39tp6amBo+3bdu2Om87e/bsiPN555137DPOOMM+4IAD7CFDhtidOnWyp02bZvv9fvvqq6+2k5KS7Mcff9zeuXOnnZmZaXft2tV+8sknm+Sx/N3vfmf37dvX3rdvX7Wy2bNnx/SYbNq0KXjbrKwse8KECfY999xjf/rpp/bu3bvthx9+2M7MzLSTkpLsc845x/7yyy/tyspKe9y4cfbBBx9sP/HEE/bQoUPtq6++OqIel156qZ2VldUk5wsASAy0TAMAms3zzz+v77//PmJ9Yxh33HGHZs6c2eDbh8+KHpgsraioKKZj7N69W/fee6/222+/qLNrv/vuu1Fn8w6f2f0Pf/iDli1bJq/Xq9/85jd66qmn1L17d/39739XSUmJhg8frs8//1xPPPGELrnkEknSihUrdP7552vSpEnq169fjTPD19eMGTP017/+VQsXLtR1111XrTwtLU1bt26t9Rhvv/22TjnllIhtn3zyScT1ffv26bnnntPxxx+vpUuXRswQ/re//U233HKL/vCHP2j//ffXjBkzgmVr1qzRsmXLIrq9AwBavyTbrqFvFwAAjfT8889r+/bthOk27JFHHlGnTp107rnnRh1H/v/+3//T6NGjq42Trqio0AsvvBARzBtj5cqVGj9+vN57770m6T7eVHbt2qWs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C01Y7dzZfJmsa2Ktb7814yajBa2awvR555mLbZsvqzU9D4HxhB9/bL4wdu5svmjn5lZfm3r4cBOOapKdbb7IVuX3m/Geged8+XITVDp1MuNp77svckzpe++ZsY4Oh9nv178O/TgRTdUwnZoaOfayf//QhFHhYfr++029/H7zI0pNj1FgUq+rrjLH3n9/8xr75JPIetT1Wn7gAXM+KSkmTNx4owm1AeHPWbhoYfqHH8w8BF26mDWETz21+oR3VYWH6V27zLktXmzmHbjxRjOm3+k0r98PPjDHLygw4+wLCmz7m29qfoyuv94cd+9eE/TS0szzN2SIbb/2WmQ93njD3KamH8Q2bDDl4fMrBIQ/ZwENfV1GEwhl4etM27Z5nFJTzY8jBx0UOUFh1dd3v37RH6OsrNBtliyx7SOPNO+Bww+PXCN6y5ZQ2aGHmrXLw+c4KC428wy4XKZOQ4fa9jPPhMrDn7OABx4wdezUyTxehYWR5/2Xv5jnvq4f9WzbrL9+ySWh66+9FprzIRCmt241P5YceGBoUr7i4tCY6YD/+7/QDy23324+Y23b/NgWCNNDhkT+uFnb53m010dAtDBd13vats1nw7BhkZ9RX39tPlNSU82PkFU/K6No6TyYZNuBju2tX2lpqVwul3w+n7oGptEHgLburLPMuDcADffOO2YMYnq6GRdadYmhp58269s29TrVrcnnn5suvBs2mK6da9dKF14YKt+3z3Rhzcw0Xbh37JCWLIlbdeNm82bTzXXDBtPl/fTTzbI/d95plmr661/NWryvv272+/FHs7092rDBdNdftUq67jrz/nrhBTM++fHHzTJ86elm6aQEHC/brE491XTrPu888zPBnDlmmMVdd5nXU8+eppv2W29Jf/mL9K9/mc+x3r3N43rllaEx5+PHm2EAKSmmi3dyshlGsHu3mSvjL3+J66k2pZbOg4yZBgAA7VtFhRnrPH68CczR1upFpGiTWu23n1kbd8cOM6730UfjU7dEkpFh5he49Vbzutq2zUzKdv/9ZmKpRx6JnBCrvZo500xgVVQUGkd72WVmPPftt5txvu1JRYV5LE44wYyXfugh81pavtyM7d6500zQ9dxz5r12771mArrAmOSkpMi1zjt2NOujH3ts5P3UNAEZ6o2WaQBo7WiZBhrP768+0RPQHMInhYJRWVl94sT2bt8+04Ic7bUSPokhItAyDQAA0NII0mgpBOnqCNLV1TZzNUE6YRCmAaA18ngkr9f87fOFlrdwu80yRQAAAGhWhGkAaG08HjN2qqwstC0ry/zrdJpxVgRqAACAZkWYBoDWxus1QXrJksgJgIqKpOxsU06YBgAAaFaEaQBorTIyzBI0AAAAaHGM9gcAAAAAIEaEaQAAAAAAYkSYBgAAAAAgRoRpAAAAAABiRJgGAAAAACBGhGkAgJGTE+8aAAAAtBqEaQCAUVIS7xoAAAC0GoRpAAAAAABiRJgGAAAAACBGhGkAaKM8Po8KtxWqcFuhJj47UYXbCuXxeeJdLQAAgDYhOd4VAAA0PY/Po/SF6fKX+4PbXih6QY5kh4pzi2W5rDjWDgAAoPWjZRoA2iBvmTciSAf4y/3ylnnjUCMAAIC2hTANAAAAAECMCNMA2pWc5aylDAAAgMYjTANoV0p2spYyAAAAGo8wDQAAAABAjAjTAAAAAADEiDANAAAAAECMCNMAAAAAAMSIMA0AAAAAQIwI0wAAAAAAxCg53hUAgObm8XnkLfNKknx+nwq3FUqS3E63LJcVz6rFn8cjec1jI59PKiyU3G7JauePCwAAQB0I0wDajpwcKT8/YpPH51H6wnT5y/3BbVmLsiRJjmSHinOL22+g9nikjAyprCy0LStLcjqloiICNQAAQC0I0wDajpKSapu8Zd6IIB3OX+6Xt8zbJsO02+mWI9lR7dwdyQ65nW5zxes1Qfqmm6Q+fcy2b76R7rjDlBGmAQAAakSYBoCqorRwtzaWy1JxbnGwe3veijwtGLMgsmu7221aoe+4I/LGTqcpAwAAQI0I0wBQVZQW7oRUVFTrdctlBYOzy+FSZo/MyP0ty9wmMGY6L09asIAx0wAAAPVAmAaA1ibQopydXb0s1lZlywoFZ5dLysysfX8AAABIYmksAGiQnOU58bvzQItyQYG5/OpXob+ZOAwAAKBF0DINAA1QsjPOXcHDW5Sffz6+dQEAAGiHCNMAWrdo6yRLjPsFAABAsyJMA2i9alonWQqulex2RV8iSqqyTFQiCf+BIBw/EAAAACQMwjSA1iuwTvKSJSZUBxQVmcm5vF5ZVmbUJaIkRS4TlSii/UAQ8L8fCAjUAAAA8UeYBtD6ZWTUOgt1nUtEJZJ6/EBAmAYAAIg/wjQAJKI6fiAAAABAfMW8NNYzzzyjgQMHyul0yrIszZkzR7ZtR+yzceNGnXbaaVqyZEm17UlJSRGXo48+OmKfVatWKSsrSw6HQ0cddZReffXVBpwWACBmaWnxrgEAAECrEXOY/vLLLzVr1iytXr1aN954o+bMmaOHHnpIkrRhwwZdeeWVGjRokN58881qt92xY4c6dOigr776SuvXr9f69ev1yiuvBMs3bdqkcePG6bTTTtPHH3+skSNHasKECdq8eXPDzxAAUD/5+fGuAQAAQKsRc5iePXu2LrzwQg0cOFCTJ0/WmWeeqddff12S9MYbb2j79u1atWpV1Nvu2LFD3bp10xFHHKH+/furf//+6t27d7D8/vvvV//+/TV//nwdc8wxuu+++3TggQfqsccea+DpAWgVcnLiXQMAAAAgJjGH6aoqKirUvXt3SdLkyZP10ksvadCgQVH33bFjh9zumpehWblypcaMGRO8npycrBEjRmj16tVR99+zZ49KS0sjLgBaoZKSeNcA9ZCznB89AAAAAhocpnfv3q3Fixfrww8/VG5uriQpKSmp1tv88MMPWr9+vTp37qz+/fvrmmuu0fbt24PlGzduVL9+/SJuY1mWtm7dGvV48+bNk8vlCl7CW7kBAE3D4/OocFuhvtj+hQq3FapwW6E8Pk+8qwUAABBXDZrN2+FwaM+ePeratavy8/N17LHH1ut2Z599to4//nh16NBBa9eu1c0336w1a9bo/fffV8eOHbVr1y45nc6I2zidTu3Zsyfq8W644QZNnz49eL20tJRADQBNyOPzKH1huvzlfklS1qIsSZIj2aHi3OLEW6cbAACghTQoTK9Zs0Y+n0+ffPKJpk2bps8//1xz586t83a9evVSr169JEmDBw/W4YcfrhEjRqiwsFBDhgxRSkqK9u7dG3Ebv99fLWAHpKSkKCUlpSGnAAAx8/g88pZ5JUk+v0+F2wrldrrbdKD0lnmDQTqcv9wvb5m3TZ87AABAbRoUpgcMGCBJGjZsmJxOp6666irdfPPN6ty5c0zHyfzfGqrffPONhgwZorS0NG3ZsiViny1btuiwww5rSDUBoP48HslrgrJ8PqmwUHK7JcuExaottJJppaWFFgAAoH1q9ARkycnJsm1bFRUVMd/2o48+kiT1799fkjR8+PDgzOCSmdzs7bff1ujRoxtbTQCJyOMxoTUQXgsLzbZ41CMjQ8rK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Vq9eLUlauXJlRHm3bt2UmZkZUT5mzJhgeXJyskaMGBEsBwAAAAAgETR4nendu3fr6aef1ocffqj58+dr06ZNkqR+/fpF7GdZlrZu3aoff/xRP/30U43lkrRx48ao5WvXro1ahz179mjPnj3B66WlpQ09HQAAAAAA6q1BLdMOh0NdunTR9OnT9cADD+jYY4/Vrl27JElOpzNiX6fTqT179tRZLkm7du2qtbyqefPmyeVyBS+9e/duyOkAAAAAABCTBrVMr1mzRj6fT5988ommTZumzz//XOecc44kae/evRH7+v1+OZ1OpaSk1FouSSkpKbWWV3XDDTdo+vTpweulpaUEagAAAABAs2tQmB4wYIAkadiwYXI6nbrqqqs0depUSdKWLVt0+OGHB/fdsmWLzj//fLndbqWkpGjLli0Rx9qyZYuysrIkSWlpaVHLDzvssKj1SElJCYZ0AACAqrbt3KZtu7ZJknx+nwq3FapHlx7qkdojzjVLHDnLc5jADQAaoNETkCUnJ8u2bblcLvXt2zc4s7ckffXVV9q6datGjx6tDh066IQTTogoD7Rujx49WpI0fPjwiPKKigq9/fbbwXIAAIBYPFzwsLIWZSlrUZZWeVYpa1GWHi54ON7VSiglO0viXQUAaJViapkuLS1Vbm6usrOz1bNnT61du1YzZszQRRddFBxDfcMNN2jQoEHq16+f8vLyNH78eB1zzDGSpOnTp2vChAkaMWKEjj/+eN12221KT0/XuHHjJEnTpk3T0KFDNWfOHJ177rl68MEHVVlZqcsuu6zJTxwAALR9k7Mm66z0syRJeSvytGDMAvXo0g5apT0eyes1f/t8UmGh5HZLlhXfegFAGxJTmHY4HNq3b58mTZokn8+nPn366Nprr9X1118vScrNzdX27ds1ZcoU+f1+nX322Vq4cGHw9r/85S917733as6cOfrxxx916qmnavny5erYsaMkafDgwVq2bJlmzpypuXPnaujQoXrttdeUmprahKcMAADaix6poS7dLodLmT0y41yjFuDxSBkZUllZaFtWluR0SkVFBGoAaCIxhelOnTpp2bJlNZYnJSVpzpw5mjNnTo37TJ06NTi+OpqJEydq4sSJsVQLAAAAAV6vCdJLlphQLZkQnZ1tygjTANAkGrzONAAAAOIsJ0fKr2HysIwMKbPmlniPzyNvmTc4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fKALL7xQkydP1ocffijLsjRu3Djt3LlTkrRp0yaNGzdOp512mj7++GONHDlSEyZM0ObNm5vmbAEAAABEYFZrI388QwwQm5gnIFu6dKl69+4tSRo4cKB8Pp8uueQSlZaW6oEHHtCyZcs0btw4SdKjjz6qk046SevWrdPAgQN11113ady4cbruuuskSYsXL9ahhx6q559/Xpdffrnuv/9+9e/fX/Pnz5ck3XfffXr55Zf12GOP6bbbbmuiUwYAAAAQwKzWQMPE3DIdCNIBDodDlZWVWrNmjSoqKjR48OBg2QknnKCUlBR99NFHkqSVK1dq7NixwfJu3bopMzNTq1evDpaPGTMmWJ6cnKwRI0YEywEAAAA0PctlKbNHpjJ7ZAZntSZIA7Vr1ARktm1r8eLFGjZsWDBkf/PNN8HysrIylZeX6/vvv9ePP/6on376Sf369Ys4hmVZ2rp1qyRp48aNtZZXtWfPHpWWlkZcAAAAAABobg0O0/v27dPVV1+tlStXauHCherbt6+OO+443XTTTdqyZYtKS0s1ZcoU2batjh07ateuXZIkp9MZcRyn06k9e/ZIknbt2lVreVXz5s2Ty+UKXqq2mgMAAAAA0BwaFKa3bt2qUaNGafny5Xrrrbd03HHHKSkpSU899ZT27Nkjy7LUvXt3ud1ude3aVQcffLBSUlIkmRm/w/n9/mCATklJqbW8qhtuuEE+ny942bJlS0NOBwAAAACAmMQcpr/66isNGzZMqampWrt2rY4//vhg2RFHHKHCwkJ999132r59u2bOnKmffvpJgwcPltvtVkpKSrXAu2XLFh122GGSpLS0tFrLq0pJSVHXrl0jLgAAAADiLCenafYBEljMYfriiy/WCSecoH/96186+OCDo+5zyCGHqFu3bnrwwQc1YMAADRo0SB06dNAJJ5yg119/Pbifz+fTJ598otGjR0uShg8fHlFeUVGht99+O1gOAAAAoBUoKWmafYAEFtPSWF999ZUKCgo0c+ZMbdy4MaLsoIMO0sqVK3XwwQera9eu+uc//6n58+frpZdeCu4zffp0TZgwQSNGjNDxxx+v2267Tenp6cGltKZNm6ahQ4dqzpw5Ovfcc/Xggw+qsrJSl112WePPFAAAAECTyFmew7rMaPdiCtPfffedJOm8886rVnb//fdrx44duueee/Tzzz9r0KBBeuml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hWnfvn2OxuREsObrdrsVFhbm9Th06JBffWuavzNnzlR6erpcLpeuuOIKrVmzxq9tBirU8q1p/s6ePbvaNisfxcXFfo0rEMGasSS9/vrr6tSpk2JiYtS6dWvl5OT41a+mOVzpzTffVEpKit9jCVSo5Vvba/Do0aOrzd/Vq1f7Paa6Csa8+/bt6/PnOiMjo9a+7733nnr06KHo6GhdcsklWrFihR8p1J9Qy7O2+dvY5zhfgjFzSXrrrbfUuXNnxcTE6LLLLtOyZcv8PqYNGzaoZ8+eat68uRITE/Xqq6/63bcmoZhVTeev7du3a8CAAWrevLlSU1P15JNP+r1dD0PIueaaa+yxxx6z/Px8W7dunaWnp9v1119vZmarVq2y6Ohoe+WVV+zjjz+23r17W4cOHay8vNzMzB588EG7++677f3337ctW7bYgAED7KKLLrJvv/222n6mTJlikqx///6Oxvfcc8+ZJMvIyPBr/RdeeMEuv/xyi4qK8tln8ODB1q1bN3v//fdt9erV1qJFC3v88ccdjcmJUMq3oKDAOnbsaG+++abl5+fbggULLCwszFatWlVt3c8//9yaNWtmkqy4uNjRmJwI1nxbtGhhr776qu3cudPzqNxvTWqbv3fddZe9++679umnn9r48eMtIiLCCgoK/BpTIEIp39rmb0lJidf2du7caePGjbNevXo5icyxYM34xRdftBYtWtiiRYvs888/t3fffde2bdtWa7/a5vCbb75pPXr0sKioKGvWrJlfY6mLUMrXn9fgX/3qVzZx4kSveXzs2DF/46qzYMx737591X62O3bsaDNmzKix3969e61FixY2YcIE+/jjj23WrFkWHh5umzZtCiCZwIRSnv7M38Y+x/kSjJkXFRVZRESEzZw50/Lz8y07O9vCw8Nt48aNtfZ9++23LTo62ubMmWMFBQW2adMmy8vLc5iKb6GWVU3nr6NHj1pqaqrdcsst9tFHH9lTTz1lERER9sorr/gTlQfFdAjau3ev1/NXX33VwsPD7fjx49alSxebOHGiZ1lhYaFJ8kzIc/t++eWXJsneeecdr/Zt27ZZUlKSXXfddY6Kva+++sqSk5Nt2LBhfhfTXbt2tYULF9qMGTOq9cnPzzdJ9sknn3japk6dam632+8xORVK+ZaUlFR7UzZw4ED7zW9+49VWUVFhvXv3tptvvrnBi+lgzPf06dMmKaA3ADXN33OVl5dbXFycPfXUU473469Qytff+VuptLTU4uPjbf369Y7241QwZlxSUmJxcXG2bNkyx8dT2xwePHiwzZgxwxYtWtQoxXQo5evPHO7Zs6c9/fTTjrZbn4Ix73Nt2LDB4uLirKSkpMb1Jk+ebB06dLCKigpP28CBA2348OGO9xmoUMrT6WtwY5zjfAnGzFetWmWtWrXyauvSpYstXLiwxn5nz561iy++2GbPnl3rPgIRSlmZ1Xz+evbZZy0xMdFOnDjhabvnnnusS5cutW63Kv7MOwS1adPG63lMTIwqKip05MgRffrppxo8eLBnWWZmpi666CLl5eWdt68klZeXe9oqKio0btw4zZw5UxdddJGjsWVlZWnEiBHq1KmT330+/vhjTZ48WZGRkdWW5eTk6MILL1SXLl08bf369dPevXt14MABR2PzVyjl26pVK7Vo0aLamKqOR/r+z7hOnDihe++919F4AhGM+ZaWlkqSkpKSHB9PTfP3XGam8vJyJSYmOt6Pv0IpX3/nb6U//elP6tChgwYOHOhoP04FY8arV69WbGysRo4c6fh4apvD//znP/XYY48pNjbW8bYDEUr5+jOHS0tLA3rtqS/BmPe5Zs+erQkTJqhVq1Y1rrd9+3Z17txZYWFhnra+ffvqww8/DGi/gQilPJ2+BjfGOc6XYMz82muvVUREhF577TWVl5dr/fr12r17t2644YYa+23cuFEHDhxQVlaWX/txKpSykmo+f23fvl2ZmZlq3ry5p61v377Kz8/X6dOn/RqbxDXTIc/MtHTpUvXo0UNfffWVJCk9Pd1rHbfbfd5rYF988UU1b95cV199tadt3rx5OnXqlCZMmOBoLCtWrNCWLVs0Z84cR/2qnvTOtWvXLp/HI6lBr+utFAr5VrV7925t2LBB/fr187Tt2LFD06ZN0+LFixUe3rgvGcGSb+X1Qunp6UpNTdXQoUPPex3QuWqav1V9/fXXuv/+++V2uzV8+HC/x1YXoZBvVb7mb6WTJ0/q2Wef1aRJkxxvty6CJeO8vDx17NhRTz75pNLS0tS2bVtNnz5dZ8+erbVvbXPY3zneEEIh36p8zeGSkhKNGTNGiYmJ6tWrl/7+97872mZ9Cpa8q8rLy1NeXp7Gjx9f67qJiYnas2ePV1tZWZm+/vrrgPZdVz/0PM9V02twU5zjfAmWzJOSkrRs2TLdcccdioyM1KBBg/TEE08oMzOzxn55eXlq27at1q5dq4yMDKWmpuq3v/2tjh075ve+/fVDz0qq+fyUmJio4uJimZmnraysTBUVFV7Xidcmwu818YNz9uxZ3XfffcrJydHmzZs9P2gul8trPZfL5fM3MC+99JJmzpypp556yvPbyffee08LFy5UXl6eIiL8nz47d+5UVlaWVq9erfj4+Doclbdjx475PB5Jjn6rFIhQy7eoqEhDhgxR165dNXbsWEnSqVOndPvtt2vSpEnq3r27cnNzA9p2IIIpX7fbrby8PLlcLu3evVsLFizQL37xC33++edKS0urw1FKmzdv1oABA3T27Fm1b99er7/+erVjbAihlq+v+VvVypUrFRUV1ahv4oIp4wMHDuizzz5Tamqq1qxZo23btikrK0vR0dGaPXt24AfZhEIt3/PN4XXr1snlcqmkpETLly/X8OHD9Y9//ENDhgzxe3z1IZjyruq5557T0KFDlZqaWuu6N998s4YOHaply5ZpxIgRysvL04svvqhmzZoFtO+6CIU8qzrf/G2qc5wvwZT5tm3bdNttt2natGkaNmyYcnJylJWVpUsvvVTXXHPNefsdOHBApaWlWrNmjVasWKH9+/dr/PjxOnPmjKObctUmFLKqzfDhwzV37lw99thjeuihh1RUVKSFCxdKkrPXBEd/FI4fjOLiYuvVq5e1bt3a3n//fTMz++CDD0ySffHFF17r9uzZ07KysjzPT548affcc49FRUXZkiVLvLZ54YUX2ksvveRpGzNmjNf1Do8++qg1a9bM83j00UetrKzMfvrTn9rDDz/sWe+RRx7xunZh+fLlXv3Gjh1b7ZjO7WNmdu+991rv3r292nbs2GGSbOvWrX5lFYhQy3fNmjWWkJBgN9xwg5WVlXnaR44caX379rXvvvvOzMxycnIa/Jpps+DK15eysjJr2bKl53qdQOevmdnx48dt+/bttnnzZps6darFxMTY2rVr/YkpYKGW7/nmb1VXXXWVTZ061Z946kWwZTxgwADLyMjwuqnbQw89ZOnp6WZWtzlc6eWXX26Ua6bNQi9ff+Zwpf79+3tuCNRYgi3vSqWlpRYdHW1vv/22V3tNec+dO9eio6MtLCzM2rZta+PGjWvQ+6z4Ekp5mtU8f5viHOdLsGV+6623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", "text/plain": [ "
" ] @@ -127,7 +119,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.16" + "version": "3.11.7" } }, "nbformat": 4, diff --git a/hikyuu/extend.py b/hikyuu/extend.py index 8e78ad22..bea676d6 100644 --- a/hikyuu/extend.py +++ b/hikyuu/extend.py @@ -1,10 +1,12 @@ # # 对 C++ 引出类和函数进行扩展, pybind11 对小函数到导出效率不如 python 直接执行 # + +# 优先加载 hikyuu 库,防止 windows 公共依赖库不同导致DLL初始化失败 +from .core import * import numpy as np import pandas as pd from datetime import * -from .core import * # ------------------------------------------------------------------ # 增加Datetime、Stock的hash支持,以便可做为dict的key diff --git a/hikyuu/gui/HikyuuTDX.py b/hikyuu/gui/HikyuuTDX.py index 18770211..9aba0f83 100644 --- a/hikyuu/gui/HikyuuTDX.py +++ b/hikyuu/gui/HikyuuTDX.py @@ -8,6 +8,10 @@ import datetime import multiprocessing from configparser import ConfigParser from logging.handlers import QueueListener + +# 优先加载,处理 VS 17.10 升级后依赖 dll 不兼容问题 +import hikyuu + import PyQt5 from PyQt5.QtWidgets import QApplication, QMainWindow, QFileDialog, QMessageBox @@ -240,6 +244,13 @@ class MyMainWindow(QMainWindow, Ui_MainWindow): self.mp_log_q_lisener.start() def initUI(self): + # 读取配置文件放在 output 重定向之前,防止配置文件读取失败没有提示 + # 读取保存的配置文件信息,如果不存在,则使用默认配置 + this_dir = self.getUserConfigDir() + import_config = ConfigParser() + if os.path.exists(this_dir + '/importdata-gui.ini'): + import_config.read(this_dir + '/importdata-gui.ini', encoding='utf-8') + self._is_sched_import_running = False self._is_collect_running = False self._stream = None @@ -272,12 +283,6 @@ class MyMainWindow(QMainWindow, Ui_MainWindow): self.time_start_dateEdit.setMinimumDate(today - datetime.timedelta(300)) self.collect_status_label.setText("已停止") - # 读取保存的配置文件信息,如果不存在,则使用默认配置 - this_dir = self.getUserConfigDir() - import_config = ConfigParser() - if os.path.exists(this_dir + '/importdata-gui.ini'): - import_config.read(this_dir + '/importdata-gui.ini', encoding='utf-8') - # 初始化导入行情数据类型配置 self.import_stock_checkBox.setChecked(import_config.getboolean('quotation', 'stock', fallback=True)) self.import_fund_checkBox.setChecked(import_config.getboolean('quotation', 'fund', fallback=True)) diff --git a/hikyuu/strategy/demo/__init__.py b/hikyuu/strategy/demo/__init__.py deleted file mode 100644 index 24133630..00000000 --- a/hikyuu/strategy/demo/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf8 -*- -# cp936 diff --git a/hikyuu/strategy/strategy.py b/hikyuu/strategy/strategy.py index 1f8a7efe..4cd8d76e 100644 --- a/hikyuu/strategy/strategy.py +++ b/hikyuu/strategy/strategy.py @@ -2,26 +2,30 @@ # -*- coding: utf8 -*- # cp936 -from hikyuu import StrategyBase, Query -from hikyuu import StrategyContext, StockManager +from hikyuu import Strategy, Query, Datetime, TimeDelta, Seconds, Minutes +from hikyuu import sm -class TestStrategy(StrategyBase): - def __init__(self): - super(self.__class__, self).__init__() - self.stock_list = ['sh600000', 'sz000001'] - self.ktype_list = [Query.MIN, Query.DAY] - - def init(self): - print("strategy init") - - def on_bar(self, ktype): - print("on bar {}".format(ktype)) - print("{}".format(len(StockManager.instance()))) - for s in self.sm: - print(s) +def on_change(stk, spot): + print(stk.market_code, stk.name, spot.close, spot.bid1, spot.ask1) +def on_spot(rev_time): + print("rev_time:", rev_time) + + +def my_func(): + print("calculate:", Datetime.now()) + for s in sm: + print(s) + + +# 注意:每一个Strategy 只能作为独立进程执行,即 python xxx.py 的方式执行! +# 以 Strategy 方式运行示例 if __name__ == '__main__': - s = TestStrategy() - s.run() \ No newline at end of file + s = Strategy(['sh600000', 'sz000001'], [Query.MIN, Query.DAY]) + # s.run_daily_at(my_func, Datetime.now() - Datetime.today() + Seconds(5)) + s.on_change(on_change) + s.on_received_spot(on_spot) + s.run_daily(my_func, Minutes(1)) + s.start() diff --git a/hikyuu/trade_manage/broker.py b/hikyuu/trade_manage/broker.py index d6195ecb..a55f8770 100644 --- a/hikyuu/trade_manage/broker.py +++ b/hikyuu/trade_manage/broker.py @@ -29,7 +29,9 @@ # 1. 20170704, Added by fasiondog # =============================================================================== +import json from hikyuu import OrderBrokerBase +from hikyuu.util import hku_error class OrderBrokerWrap(OrderBrokerBase): @@ -44,15 +46,23 @@ class OrderBrokerWrap(OrderBrokerBase): super(OrderBrokerWrap, self).__init__(name) self._broker = broker - def _buy(self, datetime, market, code, price, num): + def _buy(self, datetime, market, code, price, num, stoploss, goal_price, part_from): """实现 OrderBrokerBase 的 _buy 接口""" - self._broker.buy('{}{}'.format(market, code), price, num) - return datetime + self._broker.buy('{}{}'.format(market, code), price, num, stoploss, goal_price, part_from) - def _sell(self, datetime, market, code, price, num): + def _sell(self, datetime, market, code, price, num, stoploss, goal_price, part_from): """实现 OrderBrokerBase 的 _sell 接口""" - self._broker.sell('{}{}'.format(market, code), price, num) - return datetime + self._broker.sell('{}{}'.format(market, code), price, num, stoploss, goal_price, part_from) + + def _get_asset_info(self): + try: + if hasattr(self._broker, "get_asset_info"): + ret = self._broker.get_asset_info() + return json.dumps(ret) if type(ret) == dict else str(ret) + return str() + except Exception as e: + hku_error(e) + return str() class TestOrderBroker: @@ -61,11 +71,11 @@ class TestOrderBroker: def __init__(self): pass - def buy(self, code, price, num): - print("买入:%s %.3f %i" % (code, price, num)) + def buy(self, code, price, num, stoploss, goal_price, part_from): + print(f"买入:{code}, 价格: {price}, 数量: {num} 预期止损价: {stoploss}, 预期目标价: {goal_price}, 信号来源: {part_from}") - def sell(self, code, price, num): - print("卖出:%s %.3f %i" % (code, price, num)) + def sell(self, code, price, num, stoploss, goal_price, part_from): + print(f"卖出:{code}, 价格: {price}, 数量: {num}, 信号来源: {part_from}") def crtOB(broker, name="NO_NAME"): diff --git a/hikyuu/trade_manage/broker_easytrader.py b/hikyuu/trade_manage/broker_easytrader.py index ebf39f1c..d38a87d3 100644 --- a/hikyuu/trade_manage/broker_easytrader.py +++ b/hikyuu/trade_manage/broker_easytrader.py @@ -4,14 +4,52 @@ # Create on: 2024-01-30 # Author: fasiondog +from hikyuu import Datetime, hku_info + + class EasyTraderOrderBroker: def __init__(self, user): self.user = user + self.buffer = {} - def buy(self, code, price, num): + def buy(self, code, price, num, stoploss, goal_price, part_from): self.user.buy(code[2:], price=price, amount=num) print("买入:%s %.3f %i" % (code, price, num)) + self.buffer[code] = (num, stoploss, goal_price) - def sell(self, code, price, num): + def sell(self, code, price, num, stoploss, goal_price, part_from): self.user.sell(code[2:], price=price, amount=num) print("卖出:%s %.3f %i" % (code, price, num)) + if code in self.buffer: + old_num = self.buffer[code][0] + if old_num == num: + self.buffer.pop(code) + else: + self.buffer[code] = (old_num - num, stoploss, goal_price) + + def get_asset_info(self): + balance = self.user.balance + cash = 0.0 + for item in balance: + cash += item['可用资金'] + + positions = [] + for v in self.user.position: + if v["交易市场"] == "沪A": + market = "SH" + elif v["交易市场"] == "深A": + market = "SZ" + else: + hku_info(f"Ignored not supported market: {v['交易市场']}") + continue + + code = v["证券代码"] + market_code = f"{market}{code}" + if market_code in self.buffer: + stoploss, goal_price = self.buffer[market_code] + else: + stoploss, goal_price = 0.0, 0.0 + positions.append(dict(market=market, code=code, number=( + v['当前持仓'] + v['买入冻结'] - v['卖出冻结']), stoploss=stoploss, goal_price=goal_price)) + + return dict(datetime=str(Datetime.now()), cash=cash, positions=positions) diff --git a/hikyuu/trade_manage/broker_mail.py b/hikyuu/trade_manage/broker_mail.py index 64be7f1a..83889bf8 100644 --- a/hikyuu/trade_manage/broker_mail.py +++ b/hikyuu/trade_manage/broker_mail.py @@ -24,24 +24,25 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -#=============================================================================== +# =============================================================================== # History # 1. 20170704, Added by fasiondog -#=============================================================================== +# =============================================================================== import smtplib from email.mime.text import MIMEText from email.header import Header - + + class MailOrderBroker: """ 邮件订单代理 """ - + def __init__(self, host, sender, pwd, receivers): """ 邮件订单代理,执行买入/卖出操作时发送 Email - + :param str host: smtp服务器地址 :param int port: smtp服务器端口 :param str sender: 发件邮箱(既用户名) @@ -52,10 +53,10 @@ class MailOrderBroker: self._pwd = pwd self._sender = sender self._receivers = receivers - + def _sendmail(self, title, msg): """发送邮件 - + :param str title: 邮件标题 :param str msg: 邮件内容 """ @@ -70,14 +71,13 @@ class MailOrderBroker: smtpObj.connect(self._host, 25) smtpObj.login(self._sender, self._pwd) smtpObj.sendmail(self._sender, self._receivers, message.as_string()) - - - def buy(self, code, price, num): + + def buy(self, code, price, num, stoploss, goal_price, part_from): """执行买入操作,向指定的邮箱发送邮件,格式如下:: - + 邮件标题:【Hkyuu提醒】买入 证券代码 邮件内容:买入:证券代码,价格:买入的价格,数量:买入数量 - + :param str code: 证券代码 :param float price: 买入价格 :param int num: 买入数量 @@ -85,14 +85,13 @@ class MailOrderBroker: action = "买入:{},价格:{},数量:{} ".format(code, price, num) title = "【Hkyuu提醒】买入 {}".format(code) self._sendmail(title, action) - - def sell(self, code, price, num): + def sell(self, code, price, num, stoploss, goal_price, part_from): """执行卖出操作,向指定的邮箱发送邮件,格式如下:: - + 邮件标题:【Hkyuu提醒】卖出 证券代码 邮件内容:卖出:证券代码,价格:卖出的价格,数量:卖出数量 - + :param str code: 证券代码 :param float price: 卖出价格 :param int num: 卖出数量 @@ -100,5 +99,3 @@ class MailOrderBroker: title = "【Hkyuu提醒】卖出 {}".format(code) action = "卖出:{},价格:{},数量:{} ".format(code, price, num) self._sendmail(title, action) - - \ No newline at end of file diff --git a/hikyuu_cpp/demo/demo.cpp b/hikyuu_cpp/demo/demo1.cpp similarity index 94% rename from hikyuu_cpp/demo/demo.cpp rename to hikyuu_cpp/demo/demo1.cpp index 49550c7b..83bd837e 100644 --- a/hikyuu_cpp/demo/demo.cpp +++ b/hikyuu_cpp/demo/demo1.cpp @@ -35,6 +35,7 @@ int main(int argc, char* argv[]) { std::cout << k[i] << std::endl; } + // 启动行情接收(只是计算回测可以不需要) startSpotAgent(true); while (true) { diff --git a/hikyuu_cpp/demo/demo2.cpp b/hikyuu_cpp/demo/demo2.cpp new file mode 100644 index 00000000..ea76e49e --- /dev/null +++ b/hikyuu_cpp/demo/demo2.cpp @@ -0,0 +1,66 @@ +// demo.cpp : 定义控制台应用程序的入口点。 +// + +#include +#include +#include + +#if defined(_WIN32) +#include +#endif + +using namespace hku; + +static void changed(const Stock& stk, const SpotRecord& spot) { + HKU_INFO("{} {} 当前收盘价: {}", stk.market_code(), stk.name(), spot.close); +} + +static void changed2(const Stock& stk, const SpotRecord& spot) { + if (stk.market_code() == "SZ000001") { + HKU_INFO("strategy 2 process sz000001"); + } +} + +static void my_process1() { + HKU_INFO("{}", getStock("sh000001")); +} + +static void my_process2() { + HKU_INFO("run at time: {} {}", Datetime::now(), getStock("sh000001").name()); +} + +int main(int argc, char* argv[]) { +#if defined(_WIN32) + // Windows 下设置控制台程序输出代码页为 UTF8 + auto old_cp = GetConsoleOutputCP(); + SetConsoleOutputCP(CP_UTF8); +#endif + + Strategy stg({"sh000001", "sz000001"}, {KQuery::DAY}, "test"); + + // stock 数据变化接收,通常用于调测,直接一般不需要 + stg.onChange(changed); + + // 每日开盘期间,按间隔时间循环执行 + stg.runDaily(my_process1, Minutes(1)); + + // 每日定点执行 + stg.runDailyAt(my_process2, Datetime::now() - Datetime::today() + Seconds(20)); + + auto t = std::thread([]() { + // 以线程的方式执行另一个策略 + // 注意:同一进程内的所有 strategy 共享的是同一个上下文, + // 即使后续创建的 strategy 指定了新的上下文,但不会生效!!! + Strategy stg2("stratege2"); + stg2.onChange(changed2); + stg2.start(); + }); + + // 启动策略 + stg.start(); + +#if defined(_WIN32) + SetConsoleOutputCP(old_cp); +#endif + return 0; +} diff --git a/hikyuu_cpp/demo/xmake.lua b/hikyuu_cpp/demo/xmake.lua index 3b000680..59fb328a 100644 --- a/hikyuu_cpp/demo/xmake.lua +++ b/hikyuu_cpp/demo/xmake.lua @@ -1,10 +1,8 @@ -target("demo") +target("demo1") set_kind("binary") set_default(false) - add_options("hdf5", "mysql", "sqlite", "tdx", "feedback", "stacktrace", "spend_time") - - add_packages("boost", "spdlog", "fmt", "flatbuffers") + add_packages("boost", "spdlog", "fmt") add_includedirs("..") if is_plat("windows") then @@ -14,13 +12,35 @@ target("demo") if is_plat("windows") and get_config("kind") == "shared" then add_defines("HKU_API=__declspec(dllimport)") + add_defines("HKU_UTILS_API=__declspec(dllimport)") add_defines("SQLITE_API=__declspec(dllimport)") end - -- add files - add_files("./*.cpp") - add_deps("hikyuu") - + + add_files("./demo1.cpp") target_end() + +target("demo2") + set_kind("binary") + set_default(false) + + add_packages("boost", "spdlog", "fmt") + add_includedirs("..") + + if is_plat("windows") then + add_cxflags("-wd4267") + add_cxflags("-wd4251") + end + + if is_plat("windows") and get_config("kind") == "shared" then + add_defines("HKU_API=__declspec(dllimport)") + add_defines("HKU_UTILS_API=__declspec(dllimport)") + add_defines("SQLITE_API=__declspec(dllimport)") + end + + add_deps("hikyuu") + + add_files("./demo2.cpp") +target_end() diff --git a/hikyuu_cpp/hikyuu/DataType.h b/hikyuu_cpp/hikyuu/DataType.h index f2c2987e..42011faf 100644 --- a/hikyuu_cpp/hikyuu/DataType.h +++ b/hikyuu_cpp/hikyuu/DataType.h @@ -29,7 +29,8 @@ #include #include -#include "Log.h" +#include "config.h" +#include "utilities/Log.h" #include "utilities/osdef.h" #include "utilities/cppdef.h" #include "utilities/datetime/Datetime.h" diff --git a/hikyuu_cpp/hikyuu/GlobalInitializer.cpp b/hikyuu_cpp/hikyuu/GlobalInitializer.cpp index 2aef8bf0..0f6e0fcb 100644 --- a/hikyuu_cpp/hikyuu/GlobalInitializer.cpp +++ b/hikyuu_cpp/hikyuu/GlobalInitializer.cpp @@ -20,7 +20,8 @@ #include #endif -#include "Log.h" +#include "utilities/Log.h" +#include "utilities/os.h" #include "hikyuu.h" #include "GlobalInitializer.h" #include "StockManager.h" @@ -56,12 +57,11 @@ void GlobalInitializer::init() { fmt::print("Initialize hikyuu_{} ...\n", getVersionWithBuild()); #endif - initLogger(); -#if defined(_DEBUG) || defined(DEBUG) - set_log_level(LOG_LEVEL::LOG_TRACE); -#else - set_log_level(LOG_LEVEL::LOG_INFO); -#endif + if (createDir(fmt::format("{}/.hikyuu", getUserDir()))) { + initLogger(false, fmt::format("{}/.hikyuu/hikyuu.log", getUserDir())); + } else { + initLogger(); + } #if HKU_ENABLE_SEND_FEEDBACK sendFeedback(); @@ -74,18 +74,20 @@ void GlobalInitializer::init() { } void GlobalInitializer::clean() { - if (CanUpgrade()) { +#if HKU_ENABLE_SEND_FEEDBACK + if (runningInPython() && CanUpgrade()) { fmt::print( "\n====================================================================\n" "The new version of Hikyuu is {}, and you can run the upgrade command:\n" - "Hikyuu 的最新新版本是 {}, 您可以运行升级命令:\n" + "Hikyuu 的最新版本是 {}, 您可以运行升级命令:\n" "pip install hikyuu --upgrade\n" "========================================================\n\n", getLatestVersion(), getLatestVersion()); } +#endif - releaseGlobalTaskGroup(); releaseScheduler(); + releaseGlobalTaskGroup(); releaseGlobalSpotAgent(); IndicatorImp::releaseDynEngine(); @@ -103,9 +105,7 @@ void GlobalInitializer::clean() { H5close(); #endif -#if USE_SPDLOG_LOGGER spdlog::drop_all(); -#endif #ifdef MSVC_LEAKER_DETECT // MSVC 内存泄露检测,输出至 VS 的输出窗口 diff --git a/hikyuu_cpp/hikyuu/KDataImp.cpp b/hikyuu_cpp/hikyuu/KDataImp.cpp index df4e5f9b..bcca0799 100644 --- a/hikyuu_cpp/hikyuu/KDataImp.cpp +++ b/hikyuu_cpp/hikyuu/KDataImp.cpp @@ -238,7 +238,6 @@ void KDataImp::_recoverBackward() { Datetime end_date(m_buffer.back().datetime.date() + bd::days(1)); StockWeightList weightList = m_stock.getWeight(start_date, end_date); StockWeightList::const_reverse_iterator weightIter = weightList.rbegin(); - StockWeightList::const_reverse_iterator pre_weightIter; size_t pre_pos = total - 1; for (; weightIter != weightList.rend(); ++weightIter) { @@ -360,7 +359,6 @@ void KDataImp::_recoverEqualBackward() { Datetime end_date(m_buffer.back().datetime.date() + bd::days(1)); StockWeightList weightList = m_stock.getWeight(start_date, end_date); StockWeightList::const_reverse_iterator weightIter = weightList.rbegin(); - StockWeightList::const_reverse_iterator pre_weightIter; size_t pre_pos = total - 1; for (; weightIter != weightList.rend(); ++weightIter) { diff --git a/hikyuu_cpp/hikyuu/KRecord.h b/hikyuu_cpp/hikyuu/KRecord.h index 8de33a1e..03623065 100644 --- a/hikyuu_cpp/hikyuu/KRecord.h +++ b/hikyuu_cpp/hikyuu/KRecord.h @@ -23,7 +23,7 @@ public: price_t openPrice; ///< 开盘价 price_t highPrice; ///< 最高价 price_t lowPrice; ///< 最低价 - price_t closePrice; ///< 最低价 + price_t closePrice; ///< 收盘价 price_t transAmount; ///< 成交金额(千元) price_t transCount; ///< 成交量(手),日线以下为股数 diff --git a/hikyuu_cpp/hikyuu/Log.cpp b/hikyuu_cpp/hikyuu/Log.cpp deleted file mode 100644 index 78cb743d..00000000 --- a/hikyuu_cpp/hikyuu/Log.cpp +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Log.cpp - * - * Created on: 2013-2-1 - * Author: fasiondog - */ - -#include -#include "config.h" -#include "utilities/os.h" -#include "GlobalInitializer.h" -#include "Log.h" - -#if USE_SPDLOG_LOGGER -// 使用 stdout_color 将无法将日志输出重定向至 python -#include -#include -#include "spdlog/sinks/ostream_sink.h" -#include "spdlog/sinks/rotating_file_sink.h" - -#if HKU_USE_SPDLOG_ASYNC_LOGGER -#include -#endif /* HKU_USE_SPDLOG_ASYNC_LOGGER */ -#endif /* #if USE_SPDLOG_LOGGER */ - -namespace hku { - -static std::thread::id g_main_thread_id = std::this_thread::get_id(); - -bool isLogInMainThread() { - return std::this_thread::get_id() == g_main_thread_id; -} - -static LOG_LEVEL g_log_level = LOG_LEVEL::LOG_TRACE; - -std::string g_unknown_error_msg{"Unknown error!"}; - -LOG_LEVEL get_log_level() { - return g_log_level; -} - -/********************************************** - * Use SPDLOG for logging - *********************************************/ -#if USE_SPDLOG_LOGGER -std::shared_ptr getHikyuuLogger() { - return spdlog::get("hikyuu"); -} - -#if HKU_USE_SPDLOG_ASYNC_LOGGER -void initLogger(bool inJupyter) { - std::string logname = "hikyuu"; - spdlog::drop(logname); - std::shared_ptr logger = spdlog::get(logname); - if (logger) { - spdlog::drop(logname); - } - spdlog::sink_ptr stdout_sink; - if (inJupyter) { - stdout_sink = std::make_shared(std::cout, true); - } else { - stdout_sink = std::make_shared(); - } - stdout_sink->set_level(spdlog::level::trace); - - spdlog::init_thread_pool(8192, 1); - std::vector sinks{stdout_sink}; - logger = std::make_shared(logname, sinks.begin(), sinks.end(), - spdlog::thread_pool(), - spdlog::async_overflow_policy::block); - - logger->set_level(spdlog::level::trace); - logger->flush_on(spdlog::level::trace); - logger->set_pattern("%Y-%m-%d %H:%M:%S.%e [%^HKU-%L%$] - %v [%!]"); - spdlog::register_logger(logger); -} - -#else /* #if HKU_USE_SPDLOG_ASYNC_LOGGER */ - -void initLogger(bool inJupyter) { - std::string logname = "hikyuu"; - spdlog::drop(logname); - std::shared_ptr logger = spdlog::get(logname); - if (logger) { - spdlog::drop(logname); - } - spdlog::sink_ptr stdout_sink; - if (inJupyter) { - stdout_sink = std::make_shared(std::cout, true); - } else { - stdout_sink = std::make_shared(); - } - stdout_sink->set_level(spdlog::level::trace); - std::string log_filename = fmt::format("{}/.hikyuu/hikyuu.log", getUserDir()); - auto rotating_sink = - std::make_shared(log_filename, 1024 * 1024 * 10, 3); - rotating_sink->set_level(spdlog::level::warn); - std::vector sinks{stdout_sink, rotating_sink}; - logger = std::make_shared(logname, sinks.begin(), sinks.end()); - logger->set_level(spdlog::level::trace); - logger->flush_on(spdlog::level::trace); - // logger->set_pattern("%Y-%m-%d %H:%M:%S.%e [%^HKU-%L%$] - %v [%!] (%@)"); - // logger->set_pattern("%Y-%m-%d %H:%M:%S.%e [%^HKU-%L%$] - %v (%@)"); - logger->set_pattern("%Y-%m-%d %H:%M:%S.%e [%^HKU-%L%$] - %v (%s:%#)"); - spdlog::register_logger(logger); -} - -#endif /* #if HKU_USE_SPDLOG_ASYNC_LOGGER */ - -void set_log_level(LOG_LEVEL level) { - g_log_level = level; - getHikyuuLogger()->set_level((spdlog::level::level_enum)level); -} - -#else /* #if USE_SPDLOG_LOGGER */ -/********************************************** - * Use SPDLOG for logging - *********************************************/ -void initLogger(bool inJupyter) {} - -void set_log_level(LOG_LEVEL level) { - g_log_level = level; -} - -std::string HKU_API getLocalTime() { - auto now = std::chrono::system_clock::now(); - uint64_t dis_millseconds = - std::chrono::duration_cast(now.time_since_epoch()).count() - - std::chrono::duration_cast(now.time_since_epoch()).count() * 1000; - time_t tt = std::chrono::system_clock::to_time_t(now); -#ifdef _WIN32 - struct tm now_time; - localtime_s(&now_time, &tt); -#else - struct tm now_time; - localtime_r(&tt, &now_time); -#endif - return fmt::format("{:%Y-%m-%d %H:%M:%S}.{:<3d}", now_time, dis_millseconds); -} - -#endif /* #if USE_SPDLOG_LOGGER */ - -} // namespace hku diff --git a/hikyuu_cpp/hikyuu/Stock.h b/hikyuu_cpp/hikyuu/Stock.h index 1561f88d..e8ee4445 100644 --- a/hikyuu_cpp/hikyuu/Stock.h +++ b/hikyuu_cpp/hikyuu/Stock.h @@ -278,7 +278,7 @@ struct HKU_API Stock::Data { mutable vector m_history_finance; // 历史财务信息 [财务报告日期, 字段1, 字段2, ...] - mutable bool m_history_finance_ready{false}; + mutable std::atomic_bool m_history_finance_ready{false}; mutable std::mutex m_history_finance_mutex; price_t m_tick; diff --git a/hikyuu_cpp/hikyuu/StockManager.cpp b/hikyuu_cpp/hikyuu/StockManager.cpp index fcf32951..a7db183f 100644 --- a/hikyuu_cpp/hikyuu/StockManager.cpp +++ b/hikyuu_cpp/hikyuu/StockManager.cpp @@ -15,8 +15,8 @@ #include #include -#include "utilities/IniParser.h" -#include "utilities/thread/ThreadPool.h" +#include "hikyuu/utilities/ini_parser/IniParser.h" +#include "hikyuu/utilities/thread/ThreadPool.h" #include "StockManager.h" #include "global/GlobalTaskGroup.h" #include "global/schedule/inner_tasks.h" @@ -98,8 +98,14 @@ void StockManager::init(const Parameter& baseInfoParam, const Parameter& blockPa const Parameter& hikyuuParam, const StrategyContext& context) { HKU_WARN_IF_RETURN(m_initializing, void(), "The last initialization has not finished. Please try again later!"); + + // 防止重复 init + if (m_thread_id != std::thread::id()) { + return; + } m_initializing = true; m_thread_id = std::this_thread::get_id(); + m_baseInfoDriverParam = baseInfoParam; m_blockDriverParam = blockParam; m_kdataDriverParam = kdataParam; @@ -134,7 +140,12 @@ void StockManager::init(const Parameter& baseInfoParam, const Parameter& blockPa HKU_INFO("Loading KData..."); std::chrono::system_clock::time_point start_time = std::chrono::system_clock::now(); - setKDataDriver(DataDriverFactory::getKDataDriverPool(m_kdataDriverParam)); + auto driver = DataDriverFactory::getKDataDriverPool(m_kdataDriverParam); + HKU_CHECK(driver, "driver is null!"); + if (m_kdataDriverParam != driver->getPrototype()->getParameter()) { + m_kdataDriverParam = driver->getPrototype()->getParameter(); + } + setKDataDriver(driver); // 加载 block,须在 stock 的 kdatadriver 被设置之后调用 m_blockDriver->load(); @@ -142,6 +153,11 @@ void StockManager::init(const Parameter& baseInfoParam, const Parameter& blockPa // 加载 K 线至缓存 loadAllKData(); + // 加载历史财务信息 + loadHistoryFinance(); + + initInnerTask(); + // add special Market, for temp csv file m_marketInfoDict["TMP"] = MarketInfo("TMP", "Temp Csv file", "temp load from csv file", "000001", Null(), @@ -161,114 +177,47 @@ void StockManager::setKDataDriver(const KDataDriverConnectPoolPtr& driver) { } void StockManager::loadAllKData() { - auto driver = DataDriverFactory::getKDataDriverPool(m_kdataDriverParam); - HKU_ERROR_IF_RETURN(!driver, void(), "kdata driver is null!"); - - if (m_kdataDriverParam != driver->getPrototype()->getParameter()) { - m_kdataDriverParam = driver->getPrototype()->getParameter(); + const auto& ktypes = KQuery::getAllKType(); + vector low_ktypes; + low_ktypes.reserve(ktypes.size()); + for (const auto& ktype : ktypes) { + auto& back = low_ktypes.emplace_back(ktype); + to_lower(back); + HKU_INFO_IF(m_preloadParam.tryGet(back, false), "Preloading all {} kdata to buffer!", + back); } - bool preload_day = m_preloadParam.tryGet("day", false); - HKU_INFO_IF(preload_day, "Preloading all day kdata to buffer!"); - - bool preload_week = m_preloadParam.tryGet("week", false); - HKU_INFO_IF(preload_week, "Preloading all week kdata to buffer!"); - - bool preload_month = m_preloadParam.tryGet("month", false); - HKU_INFO_IF(preload_month, "Preloading all month kdata to buffer!"); - - bool preload_quarter = m_preloadParam.tryGet("quarter", false); - HKU_INFO_IF(preload_quarter, "Preloading all quarter kdata to buffer!"); - - bool preload_halfyear = m_preloadParam.tryGet("halfyear", false); - HKU_INFO_IF(preload_halfyear, "Preloading all halfyear kdata to buffer!"); - - bool preload_year = m_preloadParam.tryGet("year", false); - HKU_INFO_IF(preload_year, "Preloading all year kdata to buffer!"); - - bool preload_min = m_preloadParam.tryGet("min", false); - HKU_INFO_IF(preload_min, "Preloading all 1 min kdata to buffer!"); - - bool preload_min5 = m_preloadParam.tryGet("min5", false); - HKU_INFO_IF(preload_min5, "Preloading all 5 min kdata to buffer!"); - - bool preload_min15 = m_preloadParam.tryGet("min15", false); - HKU_INFO_IF(preload_min15, "Preloading all 15 min kdata to buffer!"); - - bool preload_min30 = m_preloadParam.tryGet("min30", false); - HKU_INFO_IF(preload_min30, "Preloading all 30 min kdata to buffer!"); - - bool preload_min60 = m_preloadParam.tryGet("min60", false); - HKU_INFO_IF(preload_min60, "Preloading all 60 min kdata to buffer!"); - - bool preload_hour2 = m_preloadParam.tryGet("hour2", false); - HKU_INFO_IF(preload_hour2, "Preloading all 120 min kdata to buffer!"); - + auto driver = DataDriverFactory::getKDataDriverPool(m_kdataDriverParam); if (!driver->getPrototype()->canParallelLoad()) { for (auto iter = m_stockDict.begin(); iter != m_stockDict.end(); ++iter) { - if (preload_day) - iter->second.loadKDataToBuffer(KQuery::DAY); - if (preload_week) - iter->second.loadKDataToBuffer(KQuery::WEEK); - if (preload_month) - iter->second.loadKDataToBuffer(KQuery::MONTH); - if (preload_quarter) - iter->second.loadKDataToBuffer(KQuery::QUARTER); - if (preload_halfyear) - iter->second.loadKDataToBuffer(KQuery::HALFYEAR); - if (preload_year) - iter->second.loadKDataToBuffer(KQuery::YEAR); - if (preload_min) - iter->second.loadKDataToBuffer(KQuery::MIN); - if (preload_min5) - iter->second.loadKDataToBuffer(KQuery::MIN5); - if (preload_min15) - iter->second.loadKDataToBuffer(KQuery::MIN15); - if (preload_min30) - iter->second.loadKDataToBuffer(KQuery::MIN30); - if (preload_min60) - iter->second.loadKDataToBuffer(KQuery::MIN60); - if (preload_hour2) - iter->second.loadKDataToBuffer(KQuery::HOUR2); + for (size_t i = 0, len = ktypes.size(); i < len; i++) { + const auto& low_ktype = low_ktypes[i]; + if (m_preloadParam.tryGet(low_ktype, false)) { + iter->second.loadKDataToBuffer(ktypes[i]); + } + } } } else { // 异步并行加载 - auto& tg = *getGlobalTaskGroup(); + auto* tg = getGlobalTaskGroup(); for (auto iter = m_stockDict.begin(); iter != m_stockDict.end(); ++iter) { - if (preload_day) - tg.submit([=]() mutable { iter->second.loadKDataToBuffer(KQuery::DAY); }); - if (preload_week) - tg.submit([=]() mutable { iter->second.loadKDataToBuffer(KQuery::WEEK); }); - if (preload_month) - tg.submit([=]() mutable { iter->second.loadKDataToBuffer(KQuery::MONTH); }); - if (preload_quarter) - tg.submit([=]() mutable { iter->second.loadKDataToBuffer(KQuery::QUARTER); }); - if (preload_halfyear) - tg.submit([=]() mutable { iter->second.loadKDataToBuffer(KQuery::HALFYEAR); }); - if (preload_year) - tg.submit([=]() mutable { iter->second.loadKDataToBuffer(KQuery::YEAR); }); - if (preload_min) - tg.submit([=]() mutable { iter->second.loadKDataToBuffer(KQuery::MIN); }); - if (preload_min5) - tg.submit([=]() mutable { iter->second.loadKDataToBuffer(KQuery::MIN5); }); - if (preload_min15) - tg.submit([=]() mutable { iter->second.loadKDataToBuffer(KQuery::MIN15); }); - if (preload_min30) - tg.submit([=]() mutable { iter->second.loadKDataToBuffer(KQuery::MIN30); }); - if (preload_min60) - tg.submit([=]() mutable { iter->second.loadKDataToBuffer(KQuery::MIN60); }); - if (preload_hour2) - tg.submit([=]() mutable { iter->second.loadKDataToBuffer(KQuery::HOUR2); }); + for (size_t i = 0, len = ktypes.size(); i < len; i++) { + const auto& low_ktype = low_ktypes[i]; + if (m_preloadParam.tryGet(low_ktype, false)) { + tg->submit( + [=, ktype = ktypes[i]]() mutable { iter->second.loadKDataToBuffer(ktype); }); + } + } } } - - initInnerTask(); } void StockManager::reload() { - loadAllHolidays(); + HKU_IF_RETURN(m_initializing, void()); + m_initializing = true; + loadAllHolidays(); loadAllMarketInfos(); loadAllStockTypeInfo(); loadAllStocks(); @@ -310,6 +259,9 @@ void StockManager::reload() { } } } + + loadHistoryFinance(); + m_initializing = false; } string StockManager::tmpdir() const { @@ -529,6 +481,7 @@ void StockManager::loadAllStocks() { stock.m_data->m_precision = info.precision; stock.m_data->m_minTradeNumber = info.minTradeNumber; stock.m_data->m_maxTradeNumber = info.maxTradeNumber; + stock.m_data->m_history_finance_ready = false; } } } @@ -615,4 +568,12 @@ vector> StockManager::getHistoryFinanceAllFields() con return ret; } +void StockManager::loadHistoryFinance() { + auto* tg = getGlobalTaskGroup(); + std::lock_guard lock1(*m_stockDict_mutex); + for (auto iter = m_stockDict.begin(); iter != m_stockDict.end(); ++iter) { + tg->submit([=]() { iter->second.getHistoryFinance(); }); + } +} + } // namespace hku diff --git a/hikyuu_cpp/hikyuu/StockManager.h b/hikyuu_cpp/hikyuu/StockManager.h index 2cd32999..c6acd0e8 100644 --- a/hikyuu_cpp/hikyuu/StockManager.h +++ b/hikyuu_cpp/hikyuu/StockManager.h @@ -255,6 +255,9 @@ private: /** 加载历史财经字段索引 */ void loadHistoryFinanceField(); + /** 加载历史财务数据 */ + void loadHistoryFinance(); + private: StockManager(); diff --git a/hikyuu_cpp/hikyuu/StrategyContext.cpp b/hikyuu_cpp/hikyuu/StrategyContext.cpp index 97256913..6a730f8e 100644 --- a/hikyuu_cpp/hikyuu/StrategyContext.cpp +++ b/hikyuu_cpp/hikyuu/StrategyContext.cpp @@ -29,11 +29,15 @@ void StrategyContext::setKTypeList(const vector& ktypeList) { }); } -bool StrategyContext::isAll() const { +bool StrategyContext::isAll() const noexcept { return std::find_if(m_stockCodeList.begin(), m_stockCodeList.end(), [](string val) { to_upper(val); return val == "ALL"; }) != m_stockCodeList.end(); } +bool StrategyContext::isValid() const noexcept { + return m_stockCodeList.empty() || m_ktypeList.empty(); +} + } // namespace hku \ No newline at end of file diff --git a/hikyuu_cpp/hikyuu/StrategyContext.h b/hikyuu_cpp/hikyuu/StrategyContext.h index de34b2af..8eb91a5f 100644 --- a/hikyuu_cpp/hikyuu/StrategyContext.h +++ b/hikyuu_cpp/hikyuu/StrategyContext.h @@ -24,9 +24,11 @@ public: virtual ~StrategyContext() = default; - bool isAll() const; + bool isAll() const noexcept; - Datetime startDatetime() const { + bool isValid() const noexcept; + + Datetime startDatetime() const noexcept { return m_startDatetime; } diff --git a/hikyuu_cpp/hikyuu/analysis/combinate.h b/hikyuu_cpp/hikyuu/analysis/combinate.h index c8ffb975..02906d76 100644 --- a/hikyuu_cpp/hikyuu/analysis/combinate.h +++ b/hikyuu_cpp/hikyuu/analysis/combinate.h @@ -10,7 +10,7 @@ #include "hikyuu/indicator/Indicator.h" #include "hikyuu/trade_sys/system/System.h" #include "hikyuu/trade_manage/Performance.h" -#include "../Log.h" +#include "hikyuu/utilities/Log.h" namespace hku { diff --git a/hikyuu_cpp/hikyuu/data_driver/base_info/mysql/MySQLBaseInfoDriver.cpp b/hikyuu_cpp/hikyuu/data_driver/base_info/mysql/MySQLBaseInfoDriver.cpp index 975513fb..2be59990 100644 --- a/hikyuu_cpp/hikyuu/data_driver/base_info/mysql/MySQLBaseInfoDriver.cpp +++ b/hikyuu_cpp/hikyuu/data_driver/base_info/mysql/MySQLBaseInfoDriver.cpp @@ -9,8 +9,8 @@ #include #include "MySQLBaseInfoDriver.h" +#include "hikyuu/utilities/Log.h" #include "../../../StockManager.h" -#include "../../../Log.h" #include "../table/MarketInfoTable.h" #include "../table/StockTypeInfoTable.h" #include "../table/StockWeightTable.h" @@ -281,12 +281,9 @@ Parameter MySQLBaseInfoDriver::getFinanceInfo(const string &market, const string << "f.zhuyinglirun, f.yingshouzhangkuan, f.yingyelirun, f.touzishouyu," << "f.jingyingxianjinliu, f.zongxianjinliu, f.cunhuo, f.lirunzonghe," << "f.shuihoulirun, f.jinglirun, f.weifenpeilirun, f.meigujingzichan," - << "f.baoliu2 from stkfinance f, stock s, market m " - << "where m.market='" << market << "'" - << " and s.code = '" << code << "'" - << " and s.marketid = m.marketid" - << " and f.stockid = s.stockid" - << " order by updated_date DESC limit 1"; + << "f.baoliu2 from stkfinance f, stock s, market m " << "where m.market='" << market << "'" + << " and s.code = '" << code << "'" << " and s.marketid = m.marketid" + << " and f.stockid = s.stockid" << " order by updated_date DESC limit 1"; auto con = m_pool->getConnect(); diff --git a/hikyuu_cpp/hikyuu/data_driver/base_info/table/HistoryFinanceTable.h b/hikyuu_cpp/hikyuu/data_driver/base_info/table/HistoryFinanceTable.h index 2120830c..1f469e76 100644 --- a/hikyuu_cpp/hikyuu/data_driver/base_info/table/HistoryFinanceTable.h +++ b/hikyuu_cpp/hikyuu/data_driver/base_info/table/HistoryFinanceTable.h @@ -13,8 +13,8 @@ namespace hku { struct HistoryFinanceTable { TABLE_BIND4(HistoryFinanceTable, HistoryFinance, file_date, report_date, market_code, values) - uint64_t file_date; - uint64_t report_date; + uint64_t file_date{0}; + uint64_t report_date{0}; std::string market_code; // std::vector values; std::vector values; diff --git a/hikyuu_cpp/hikyuu/data_driver/kdata/cvs/KDataTempCsvDriver.cpp b/hikyuu_cpp/hikyuu/data_driver/kdata/cvs/KDataTempCsvDriver.cpp index 6ab9fcd9..8f6c1ec1 100644 --- a/hikyuu_cpp/hikyuu/data_driver/kdata/cvs/KDataTempCsvDriver.cpp +++ b/hikyuu_cpp/hikyuu/data_driver/kdata/cvs/KDataTempCsvDriver.cpp @@ -8,10 +8,9 @@ #include #include #include +#include "hikyuu/utilities/Log.h" #include "KDataTempCsvDriver.h" -#include "../../../Log.h" - namespace hku { KDataTempCsvDriver::~KDataTempCsvDriver() {} diff --git a/hikyuu_cpp/hikyuu/data_driver/kdata/mysql/MySQLKDataDriver.cpp b/hikyuu_cpp/hikyuu/data_driver/kdata/mysql/MySQLKDataDriver.cpp index d7bd927e..2e29c7cb 100644 --- a/hikyuu_cpp/hikyuu/data_driver/kdata/mysql/MySQLKDataDriver.cpp +++ b/hikyuu_cpp/hikyuu/data_driver/kdata/mysql/MySQLKDataDriver.cpp @@ -6,7 +6,7 @@ */ #include -#include "../../../Log.h" +#include "hikyuu/utilities/Log.h" #include "MySQLKDataDriver.h" #include "KRecordTable.h" diff --git a/hikyuu_cpp/hikyuu/doc.h b/hikyuu_cpp/hikyuu/doc.h index 42d488ec..b61d9b2b 100644 --- a/hikyuu_cpp/hikyuu/doc.h +++ b/hikyuu_cpp/hikyuu/doc.h @@ -137,8 +137,8 @@ * @details 合成多因子 * @ingroup TradeSystem * - * @defgroup SystemInstance SystemInstance 系统实例 - * @details 系统实例 + * @defgroup Stratgy Strategy 策略运行时 + * @details 策略运行时 * @ingroup Hikyuu * * @defgroup Agent Agent 对外数据接收发送代理 diff --git a/hikyuu_cpp/hikyuu/global/GlobalSpotAgent.cpp b/hikyuu_cpp/hikyuu/global/GlobalSpotAgent.cpp index f2707b5c..2a6637ff 100644 --- a/hikyuu_cpp/hikyuu/global/GlobalSpotAgent.cpp +++ b/hikyuu_cpp/hikyuu/global/GlobalSpotAgent.cpp @@ -171,68 +171,74 @@ void HKU_API startSpotAgent(bool print) { agent.setPrintFlag(print); - const auto& preloadParam = sm.getPreloadParameter(); - if (preloadParam.tryGet("min", false)) { - agent.addProcess(std::bind(updateStockMinData, std::placeholders::_1, KQuery::MIN)); - } + // 防止调用 stopSpotAgent 后重新 startSpotAgent + static std::atomic_bool g_init_spot_agent{false}; + if (!g_init_spot_agent) { + const auto& preloadParam = sm.getPreloadParameter(); + if (preloadParam.tryGet("min", false)) { + agent.addProcess(std::bind(updateStockMinData, std::placeholders::_1, KQuery::MIN)); + } - if (preloadParam.tryGet("day", false)) { - agent.addProcess(updateStockDayData); - } + if (preloadParam.tryGet("day", false)) { + agent.addProcess(updateStockDayData); + } - if (preloadParam.tryGet("week", false)) { - agent.addProcess(std::bind(updateStockDayUpData, std::placeholders::_1, KQuery::WEEK)); - } + if (preloadParam.tryGet("week", false)) { + agent.addProcess(std::bind(updateStockDayUpData, std::placeholders::_1, KQuery::WEEK)); + } - if (preloadParam.tryGet("month", false)) { - agent.addProcess(std::bind(updateStockDayUpData, std::placeholders::_1, KQuery::MONTH)); - } + if (preloadParam.tryGet("month", false)) { + agent.addProcess(std::bind(updateStockDayUpData, std::placeholders::_1, KQuery::MONTH)); + } - if (preloadParam.tryGet("quarter", false)) { - agent.addProcess(std::bind(updateStockDayUpData, std::placeholders::_1, KQuery::QUARTER)); - } + if (preloadParam.tryGet("quarter", false)) { + agent.addProcess( + std::bind(updateStockDayUpData, std::placeholders::_1, KQuery::QUARTER)); + } - if (preloadParam.tryGet("halfyear", false)) { - agent.addProcess(std::bind(updateStockDayUpData, std::placeholders::_1, KQuery::HALFYEAR)); - } + if (preloadParam.tryGet("halfyear", false)) { + agent.addProcess( + std::bind(updateStockDayUpData, std::placeholders::_1, KQuery::HALFYEAR)); + } - if (preloadParam.tryGet("year", false)) { - agent.addProcess(std::bind(updateStockDayUpData, std::placeholders::_1, KQuery::YEAR)); - } + if (preloadParam.tryGet("year", false)) { + agent.addProcess(std::bind(updateStockDayUpData, std::placeholders::_1, KQuery::YEAR)); + } - if (preloadParam.tryGet("min5", false)) { - agent.addProcess(std::bind(updateStockMinData, std::placeholders::_1, KQuery::MIN5)); - } + if (preloadParam.tryGet("min5", false)) { + agent.addProcess(std::bind(updateStockMinData, std::placeholders::_1, KQuery::MIN5)); + } - if (preloadParam.tryGet("min15", false)) { - agent.addProcess(std::bind(updateStockMinData, std::placeholders::_1, KQuery::MIN15)); - } + if (preloadParam.tryGet("min15", false)) { + agent.addProcess(std::bind(updateStockMinData, std::placeholders::_1, KQuery::MIN15)); + } - if (preloadParam.tryGet("min30", false)) { - agent.addProcess(std::bind(updateStockMinData, std::placeholders::_1, KQuery::MIN30)); - } + if (preloadParam.tryGet("min30", false)) { + agent.addProcess(std::bind(updateStockMinData, std::placeholders::_1, KQuery::MIN30)); + } - if (preloadParam.tryGet("min60", false)) { - agent.addProcess(std::bind(updateStockMinData, std::placeholders::_1, KQuery::MIN60)); - } - if (preloadParam.tryGet("min3", false)) { - agent.addProcess(std::bind(updateStockMinData, std::placeholders::_1, KQuery::MIN3)); - } + if (preloadParam.tryGet("min60", false)) { + agent.addProcess(std::bind(updateStockMinData, std::placeholders::_1, KQuery::MIN60)); + } + if (preloadParam.tryGet("min3", false)) { + agent.addProcess(std::bind(updateStockMinData, std::placeholders::_1, KQuery::MIN3)); + } - if (preloadParam.tryGet("hour2", false)) { - agent.addProcess(std::bind(updateStockMinData, std::placeholders::_1, KQuery::HOUR2)); - } + if (preloadParam.tryGet("hour2", false)) { + agent.addProcess(std::bind(updateStockMinData, std::placeholders::_1, KQuery::HOUR2)); + } - if (preloadParam.tryGet("hour4", false)) { - agent.addProcess(std::bind(updateStockMinData, std::placeholders::_1, KQuery::HOUR4)); - } + if (preloadParam.tryGet("hour4", false)) { + agent.addProcess(std::bind(updateStockMinData, std::placeholders::_1, KQuery::HOUR4)); + } - if (preloadParam.tryGet("hour6", false)) { - agent.addProcess(std::bind(updateStockMinData, std::placeholders::_1, KQuery::HOUR6)); - } + if (preloadParam.tryGet("hour6", false)) { + agent.addProcess(std::bind(updateStockMinData, std::placeholders::_1, KQuery::HOUR6)); + } - if (preloadParam.tryGet("hour12", false)) { - agent.addProcess(std::bind(updateStockMinData, std::placeholders::_1, KQuery::HOUR12)); + if (preloadParam.tryGet("hour12", false)) { + agent.addProcess(std::bind(updateStockMinData, std::placeholders::_1, KQuery::HOUR12)); + } } agent.start(); diff --git a/hikyuu_cpp/hikyuu/global/GlobalSpotAgent.h b/hikyuu_cpp/hikyuu/global/GlobalSpotAgent.h index f87a0050..5df25ed9 100644 --- a/hikyuu_cpp/hikyuu/global/GlobalSpotAgent.h +++ b/hikyuu_cpp/hikyuu/global/GlobalSpotAgent.h @@ -5,6 +5,7 @@ * Author: fasiondog */ +#pragma once #include "agent/SpotAgent.h" namespace hku { diff --git a/hikyuu_cpp/hikyuu/global/GlobalTaskGroup.cpp b/hikyuu_cpp/hikyuu/global/GlobalTaskGroup.cpp index d7ab1d1d..d7b7a1f3 100644 --- a/hikyuu_cpp/hikyuu/global/GlobalTaskGroup.cpp +++ b/hikyuu_cpp/hikyuu/global/GlobalTaskGroup.cpp @@ -8,14 +8,14 @@ */ #include "hikyuu/GlobalInitializer.h" +#include "hikyuu/utilities/Log.h" #include "GlobalTaskGroup.h" -#include "../Log.h" namespace hku { -static StealThreadPool* g_threadPool; +static TaskGroup* g_threadPool; -StealThreadPool* getGlobalTaskGroup() { +TaskGroup* getGlobalTaskGroup() { static std::once_flag oc; std::call_once(oc, [&]() { auto cpu_num = std::thread::hardware_concurrency(); @@ -24,7 +24,7 @@ StealThreadPool* getGlobalTaskGroup() { } else if (cpu_num > 1) { cpu_num--; } - g_threadPool = new StealThreadPool(cpu_num); + g_threadPool = new TaskGroup(cpu_num); }); return g_threadPool; } diff --git a/hikyuu_cpp/hikyuu/global/GlobalTaskGroup.h b/hikyuu_cpp/hikyuu/global/GlobalTaskGroup.h index 69d345ba..72ab4bad 100644 --- a/hikyuu_cpp/hikyuu/global/GlobalTaskGroup.h +++ b/hikyuu_cpp/hikyuu/global/GlobalTaskGroup.h @@ -11,15 +11,17 @@ #ifndef HKU_GLOBAL_TASK_GROUP #define HKU_GLOBAL_TASK_GROUP -#include "../utilities/thread/StealThreadPool.h" +#include "../utilities/thread/thread.h" namespace hku { +using TaskGroup = ThreadPool; + /** * 获取全局线程池任务组 * @note 请使用 future 获取任务返回 */ -StealThreadPool* getGlobalTaskGroup(); +TaskGroup* getGlobalTaskGroup(); template using task_handle = std::future; diff --git a/hikyuu_cpp/hikyuu/global/SpotRecord.h b/hikyuu_cpp/hikyuu/global/SpotRecord.h new file mode 100644 index 00000000..ef69983b --- /dev/null +++ b/hikyuu_cpp/hikyuu/global/SpotRecord.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2024 hikyuu.org + * + * Created on: 2024-08-15 + * Author: fasiondog + */ + +#pragma once + +#include "hikyuu/DataType.h" + +namespace hku { + +/** + * 接收外部实时数据结构 + * @ingroup Agent + */ +struct HKU_API SpotRecord { + string market; ///< 市场标识 + string code; ///< 证券代码 + string name; ///< 证券名称 + Datetime datetime; ///< 数据时间 + price_t yesterday_close; ///< 昨日收盘价 + price_t open; ///< 开盘价 + price_t high; ///< 最高价 + price_t low; ///< 最低价 + price_t close; ///< 收盘价 + price_t amount; ///< 成交金额 (千元) + price_t volume; ///< 成交量(手) + price_t bid1; ///< 买一价 + price_t bid1_amount; ///< 买一数量(手) + price_t bid2; ///< 买二价 + price_t bid2_amount; ///< 买二数量 + price_t bid3; ///< 买三价 + price_t bid3_amount; ///< 买三数量 + price_t bid4; ///< 买四价 + price_t bid4_amount; ///< 买四数量 + price_t bid5; ///< 买五价 + price_t bid5_amount; ///< 买五数量 + price_t ask1; ///< 卖一价 + price_t ask1_amount; ///< 卖一数量 + price_t ask2; ///< 卖二价 + price_t ask2_amount; ///< 卖二数量 + price_t ask3; ///< 卖三价 + price_t ask3_amount; ///< 卖三数量 + price_t ask4; ///< 卖四价 + price_t ask4_amount; ///< 卖四数量 + price_t ask5; ///< 卖五价 + price_t ask5_amount; ///< 卖五数量 +}; + +} // namespace hku \ No newline at end of file diff --git a/hikyuu_cpp/hikyuu/global/agent/SpotAgent.cpp b/hikyuu_cpp/hikyuu/global/agent/SpotAgent.cpp index 86325e79..5de1f3f6 100644 --- a/hikyuu_cpp/hikyuu/global/agent/SpotAgent.cpp +++ b/hikyuu_cpp/hikyuu/global/agent/SpotAgent.cpp @@ -8,6 +8,7 @@ #include #include #include +#include "spot_generated.h" #include "SpotAgent.h" using namespace hikyuu::flat; @@ -33,6 +34,7 @@ SpotAgent::~SpotAgent() { } void SpotAgent::start() { + stop(); if (m_stop) { m_stop = false; m_receiveThread = std::thread([this]() { work_thread(); }); @@ -156,13 +158,11 @@ void SpotAgent::work_thread() { while (!m_stop && rv != 0) { rv = nng_dial(sock, ms_pubUrl.c_str(), nullptr, 0); HKU_WARN_IF(m_print && rv != 0, - "Faied connect quotation server {}, will retry after 5 seconds! You Maybe need " - "start the collection service first.", - ms_pubUrl); + "Faied connect quotation server {}, will retry after 5 seconds!", ms_pubUrl); std::this_thread::sleep_for(std::chrono::seconds(5)); } - HKU_INFO_IF(m_print, "Ready to receive quotation ..."); + HKU_INFO_IF(m_print, "Ready to receive quotation from {} ...", ms_pubUrl); while (!m_stop) { char* buf = nullptr; @@ -186,7 +186,7 @@ void SpotAgent::work_thread() { for (auto& task : m_process_task_list) { task.get(); } - HKU_INFO_IF(m_print, "received count: {}", m_batch_count); + HKU_TRACE_IF(m_print, "received count: {}", m_batch_count); m_batch_count = 0; // 执行后处理 for (const auto& postProcess : m_postProcessList) { @@ -215,21 +215,25 @@ void SpotAgent::work_thread() { void SpotAgent::addProcess(std::function process) { HKU_CHECK(m_stop, "SpotAgent is running, please stop agent first!"); + std::lock_guard lock(m_mutex); m_processList.push_back(process); } void SpotAgent::addPostProcess(std::function func) { HKU_CHECK(m_stop, "SpotAgent is running, please stop agent first!"); + std::lock_guard lock(m_mutex); m_postProcessList.push_back(func); } void SpotAgent::clearProcessList() { HKU_CHECK(m_stop, "SpotAgent is running, please stop agent first!"); + std::lock_guard lock(m_mutex); m_processList.clear(); } void SpotAgent::clearPostProcessList() { HKU_CHECK(m_stop, "SpotAgent is running, please stop agent first!"); + std::lock_guard lock(m_mutex); m_postProcessList.clear(); } diff --git a/hikyuu_cpp/hikyuu/global/agent/SpotAgent.h b/hikyuu_cpp/hikyuu/global/agent/SpotAgent.h index eb4558d6..cafc4b4e 100644 --- a/hikyuu_cpp/hikyuu/global/agent/SpotAgent.h +++ b/hikyuu_cpp/hikyuu/global/agent/SpotAgent.h @@ -9,50 +9,18 @@ #include #include -#include "spot_generated.h" #include "../../DataType.h" #include "../../utilities/thread/ThreadPool.h" +#include "../SpotRecord.h" + +namespace hikyuu { +namespace flat { +struct Spot; +} +} // namespace hikyuu namespace hku { -/** - * 接收外部实时数据结构 - * @ingroup Agent - */ -struct HKU_API SpotRecord { - string market; ///< 市场标识 - string code; ///< 证券代码 - string name; ///< 证券名称 - Datetime datetime; ///< 数据时间 - price_t yesterday_close; ///< 昨日收盘价 - price_t open; ///< 开盘价 - price_t high; ///< 最高价 - price_t low; ///< 最低价 - price_t close; ///< 收盘价 - price_t amount; ///< 成交金额 (千元) - price_t volume; ///< 成交量(手) - price_t bid1; ///< 买一价 - price_t bid1_amount; ///< 买一数量(手) - price_t bid2; ///< 买二价 - price_t bid2_amount; ///< 买二数量 - price_t bid3; ///< 买三价 - price_t bid3_amount; ///< 买三数量 - price_t bid4; ///< 买四价 - price_t bid4_amount; ///< 买四数量 - price_t bid5; ///< 买五价 - price_t bid5_amount; ///< 买五数量 - price_t ask1; ///< 卖一价 - price_t ask1_amount; ///< 卖一数量 - price_t ask2; ///< 卖二价 - price_t ask2_amount; ///< 卖二数量 - price_t ask3; ///< 卖三价 - price_t ask3_amount; ///< 卖三数量 - price_t ask4; ///< 卖四价 - price_t ask4_amount; ///< 卖四数量 - price_t ask5; ///< 卖五价 - price_t ask5_amount; ///< 卖五数量 -}; - /** * 接收外部实时数据代理 * @ingroup Agent @@ -77,6 +45,7 @@ public: /** 设置是否打印数据接收进展情况,主要用于在交互环境下关闭打印 */ void setPrintFlag(bool print) { + std::lock_guard lock(m_mutex); m_print = print; } @@ -136,14 +105,18 @@ private: enum STATUS { WAITING, RECEIVING }; // 等待新的批次数据,正在接收批次数据中 enum STATUS m_status = WAITING; // 当前内部状态 std::atomic_bool m_stop = true; // 结束代理工作标识 - bool m_print = true; // 是否打印接收进度,防止的交互模式的影响 + int m_revTimeout = 100; // 连接数据服务超时时长(毫秒) size_t m_batch_count = 0; // 记录本次批次接收的数据数量 std::thread m_receiveThread; // 数据接收线程 ThreadPool m_tg; // 数据处理任务线程池 + vector> m_process_task_list; + + // 下面属性被修改时需要加锁,以便可以使用多线程方式运行 strategy + std::mutex m_mutex; + bool m_print = true; // 是否打印接收进度,防止的交互模式的影响 list> m_processList; // 已注册的 spot 处理函数列表 list> m_postProcessList; // 已注册的批次后处理函数列表 - vector> m_process_task_list; }; } // namespace hku \ No newline at end of file diff --git a/hikyuu_cpp/hikyuu/global/schedule/inner_tasks.cpp b/hikyuu_cpp/hikyuu/global/schedule/inner_tasks.cpp index f899ce7d..a9622c1e 100644 --- a/hikyuu_cpp/hikyuu/global/schedule/inner_tasks.cpp +++ b/hikyuu_cpp/hikyuu/global/schedule/inner_tasks.cpp @@ -15,7 +15,6 @@ namespace hku { void initInnerTask() { auto* tm = getScheduler(); tm->addFuncAtTimeEveryDay(Datetime::min(), Datetime::max(), TimeDelta(), reloadHikyuuTask); - tm->start(); } void reloadHikyuuTask() { diff --git a/hikyuu_cpp/hikyuu/global/schedule/scheduler.cpp b/hikyuu_cpp/hikyuu/global/schedule/scheduler.cpp index bf0037c1..90d58f4e 100644 --- a/hikyuu_cpp/hikyuu/global/schedule/scheduler.cpp +++ b/hikyuu_cpp/hikyuu/global/schedule/scheduler.cpp @@ -5,10 +5,11 @@ * Author: fasiondog */ -#include +#include "hikyuu/GlobalInitializer.h" #include +#include "hikyuu/utilities/Log.h" +#include "hikyuu/global/GlobalTaskGroup.h" #include "scheduler.h" -#include "../../Log.h" namespace hku { @@ -16,7 +17,8 @@ static TimerManager *g_scheduler; TimerManager *getScheduler() { static std::once_flag oc; - std::call_once(oc, [&]() { g_scheduler = new TimerManager(2); }); + // 使用内部公共任务组,减少内部线程 + std::call_once(oc, [&]() { g_scheduler = new TimerManager(getGlobalTaskGroup()); }); return g_scheduler; } diff --git a/hikyuu_cpp/hikyuu/global/sysinfo.cpp b/hikyuu_cpp/hikyuu/global/sysinfo.cpp index e1e5ff83..d30d45ef 100644 --- a/hikyuu_cpp/hikyuu/global/sysinfo.cpp +++ b/hikyuu_cpp/hikyuu/global/sysinfo.cpp @@ -10,17 +10,16 @@ #include #include #include -#include #include #include "hikyuu/version.h" #include "hikyuu/DataType.h" #include "hikyuu/utilities/os.h" -#include "node/NodeClient.h" +#include "hikyuu/utilities/http_client/HttpClient.h" #include "sysinfo.h" using json = nlohmann::json; -#define FEEDBACK_SERVER_ADDR "tcp://1.tcp.cpolar.cn:20981" +#define FEEDBACK_SERVER_ADDR "http://hikyuu.cpolar.cn" namespace hku { @@ -51,7 +50,11 @@ bool HKU_API pythonInJupyter() { void HKU_API setPythonInJupyter(bool injupyter) { g_pythonInJupyter = injupyter; - initLogger(injupyter); + if (createDir(fmt::format("{}/.hikyuu", getUserDir()))) { + initLogger(injupyter, fmt::format("{}/.hikyuu/hikyuu.log", getUserDir())); + } else { + initLogger(injupyter); + } } bool HKU_API CanUpgrade() { @@ -113,27 +116,17 @@ void sendFeedback() { saveUUID(uid); } - NodeClient client(FEEDBACK_SERVER_ADDR); - client.dial(); - - json req, res; - req["cmd"] = 2; - client.post(req, res); - std::string host = res["host"].get(); - uint64_t port = res["port"].get(); - g_latest_version = res.contains("last_version") ? res["last_version"].get() : 0; - client.close(); - - client.setServerAddr(fmt::format("tcp://{}:{}", host, port)); - client.dial(); - req["cmd"] = 1; + HttpClient client(FEEDBACK_SERVER_ADDR, 2000); + json req; req["uid"] = boost::uuids::to_string(uid); req["part"] = "hikyuu"; req["version"] = HKU_VERSION; req["build"] = fmt::format("{}", HKU_VERSION_BUILD); req["platform"] = getPlatform(); req["arch"] = getCpuArch(); - client.post(req, res); + auto res = client.post("/hku/visit", req); + json r = res.json(); + g_latest_version = r["data"]["last_version"].get(); } catch (...) { // do nothing @@ -146,24 +139,12 @@ void sendFeedback() { void sendPythonVersionFeedBack(int major, int minor, int micro) { std::thread t([=]() { try { - NodeClient client(FEEDBACK_SERVER_ADDR); - client.dial(); - - json req, res; - req["cmd"] = 2; - client.post(req, res); - std::string host = res["host"].get(); - uint64_t port = res["port"].get(); - g_latest_version = res.contains("last_version") ? res["last_version"].get() : 0; - client.close(); - - client.setServerAddr(fmt::format("tcp://{}:{}", host, port)); - client.dial(); - req["cmd"] = 3; + HttpClient client(FEEDBACK_SERVER_ADDR, 2000); + json req; req["major"] = major; req["minor"] = minor; req["micro"] = micro; - client.post(req, res); + client.post("/hku/pyver", req); } catch (...) { // do nothing } diff --git a/hikyuu_cpp/hikyuu/hikyuu.cpp b/hikyuu_cpp/hikyuu/hikyuu.cpp index 6f36639e..a37c9d5d 100644 --- a/hikyuu_cpp/hikyuu/hikyuu.cpp +++ b/hikyuu_cpp/hikyuu/hikyuu.cpp @@ -11,7 +11,7 @@ #include #include -#include "utilities/IniParser.h" +#include "utilities/ini_parser/IniParser.h" #include "hikyuu.h" #include "version.h" @@ -40,8 +40,8 @@ void hikyuu_init(const string& config_file_name, bool ignore_preload, hkuParam.set("tmpdir", config.get("hikyuu", "tmpdir", ".")); hkuParam.set("datadir", config.get("hikyuu", "datadir", ".")); - hkuParam.set("quotation_server", config.get("hikyuu", "quotation_server", - "ipc:///tmp/hikyuu_real.ipc")); + hkuParam.set("quotation_server", + config.get("hikyuu", "quotation_server", "ipc:///tmp/hikyuu_real.ipc")); if (!config.hasSection("baseinfo")) { HKU_FATAL("Missing configure of baseinfo!"); @@ -73,17 +73,17 @@ void hikyuu_init(const string& config_file_name, bool ignore_preload, for (auto iter = option->begin(); iter != option->end(); ++iter) { try { - preloadParam.set(*iter, - ignore_preload ? false : config.getBool("preload", *iter)); - } catch (...) { - if (!ignore_preload) { - // 获取预加载的最大数量 - try { - preloadParam.set(*iter, config.getInt("preload", *iter)); - } catch (...) { - HKU_WARN("Invalid option: {}", *iter); - } + auto pos = (*iter).find("max"); + if (pos == std::string::npos) { + preloadParam.set(*iter, + ignore_preload ? false : config.getBool("preload", *iter)); + } else if (!ignore_preload) { + preloadParam.set(*iter, config.getInt("preload", *iter)); } + } catch (const std::exception& e) { + HKU_ERROR("proload param ({}) error! {}!", *iter, e.what()); + } catch (...) { + HKU_ERROR("proload param ({})! Unknown error!", *iter); } } diff --git a/hikyuu_cpp/hikyuu/hikyuu.h b/hikyuu_cpp/hikyuu/hikyuu.h index da871a1c..600e0baa 100644 --- a/hikyuu_cpp/hikyuu/hikyuu.h +++ b/hikyuu_cpp/hikyuu/hikyuu.h @@ -16,6 +16,7 @@ #include "indicator/build_in.h" #include "trade_manage/build_in.h" #include "trade_sys/all.h" +#include "strategy/Strategy.h" namespace hku { diff --git a/hikyuu_cpp/hikyuu/indicator/IndicatorImp.cpp b/hikyuu_cpp/hikyuu/indicator/IndicatorImp.cpp index 61398ae5..462c4c05 100644 --- a/hikyuu_cpp/hikyuu/indicator/IndicatorImp.cpp +++ b/hikyuu_cpp/hikyuu/indicator/IndicatorImp.cpp @@ -7,10 +7,10 @@ #include #include #include +#include "hikyuu/utilities/Log.h" #include "Indicator.h" #include "IndParam.h" #include "../Stock.h" -#include "../Log.h" #include "../GlobalInitializer.h" #include "imp/ICval.h" diff --git a/hikyuu_cpp/hikyuu/strategy/AccountTradeManager.cpp b/hikyuu_cpp/hikyuu/strategy/AccountTradeManager.cpp deleted file mode 100644 index 689c3237..00000000 --- a/hikyuu_cpp/hikyuu/strategy/AccountTradeManager.cpp +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright(C) 2021 hikyuu.org - * - * Create on: 2021-03-23 - * Author: fasiondog - */ - -#include -#include "../utilities/arithmetic.h" -#include "AccountTradeManager.h" - -using nlohmann::json; - -namespace hku { - -#define HKU_SERVER_URL "http://localhost:9001" -#define HKU_SERVER_LOGIN_API "/hku/account/v1/login" - -#define HTTP_STATUS_OK 200 -#define HTTP_STATUS_BAD_REQUEST 400 - -static string getHttpClientErrorMsg(httplib::Error err) { - string result; - if (httplib::Error::Success == err) { - result = "Success"; - } else if (httplib::Error::Unknown == err) { - result = "Unknonw error"; - } else if (httplib::Error::Connection == err) { - result = "Connection error"; - } else if (httplib::Error::BindIPAddress == err) { - result = "BindIPAddress error"; - } else if (httplib::Error::Read == err) { - result = "Read error"; - } else if (httplib::Error::Write == err) { - result = "Write error"; - } else if (httplib::Error::ExceedRedirectCount == err) { - result = "ExceedRedirectCount error"; - } else if (httplib::Error::Canceled == err) { - result = "Canceled error"; - } else if (httplib::Error::SSLConnection == err) { - result = "SSLConnection error"; - } else if (httplib::Error::SSLLoadingCerts == err) { - result = "SSLLoadingCerts error"; - } else if (httplib::Error::SSLServerVerification == err) { - result = "SSLServerVerification error"; - } else if (httplib::Error::UnsupportedMultipartBoundaryChars == err) { - result = "UnsupportedMultipartBoundaryChars error"; - } else if (httplib::Error::Compression == err) { - result = "Compression error"; - } else { - result = "Other unknonw error"; - } - return result; -} - -AccountTradeManager::AccountTradeManager(const string& name, const string& pwd) -: TradeManagerBase(name, TC_Zero()), - m_client(std::make_unique(HKU_SERVER_URL)), - m_user(name), - m_password(pwd) { - trim(m_user); - if (m_user.empty()) { - HKU_ERROR("User name is empty."); - return; - } - - string req(fmt::format(R"({{"user":"{}", "password":"{}"}})", name, pwd)); - auto res = m_client->Post(HKU_SERVER_LOGIN_API, req.c_str(), req.size(), "application/json"); - if (!res) { - HKU_ERROR("http client err: {}", getHttpClientErrorMsg(res.error())); - return; - } - - if (HTTP_STATUS_OK == res->status) { - auto data = json::parse(res->body); - m_token = data["hku_token"].get(); - } else if (HTTP_STATUS_BAD_REQUEST == res->status) { - HKU_ERROR("Bad request: {}", res->body); - } else { - HKU_ERROR("http response status: {}", res->status); - } -} - -} // namespace hku \ No newline at end of file diff --git a/hikyuu_cpp/hikyuu/strategy/BrokerTradeManager.cpp b/hikyuu_cpp/hikyuu/strategy/BrokerTradeManager.cpp new file mode 100644 index 00000000..e62e2053 --- /dev/null +++ b/hikyuu_cpp/hikyuu/strategy/BrokerTradeManager.cpp @@ -0,0 +1,282 @@ +/* + * Copyright (c) 2024 hikyuu.org + * + * Created on: 2024-08-16 + * Author: fasiondog + */ + +#include +#include "hikyuu/trade_manage/crt/TC_Zero.h" +#include "BrokerTradeManager.h" + +namespace hku { + +using json = nlohmann::json; + +BrokerTradeManager::BrokerTradeManager(const OrderBrokerPtr& broker, const TradeCostPtr& costfunc, + const string& name) +: TradeManagerBase(name, costfunc) { + HKU_ASSERT(broker); + m_broker_list.emplace_back(broker); + + m_datetime = Datetime::now(); + m_broker_last_datetime = m_datetime; +} + +void BrokerTradeManager::_reset() { + HKU_WARN("The subclass does not implement a reset method"); + m_datetime = Datetime::max(); + m_cash = 0.0; + m_position.clear(); +} + +shared_ptr BrokerTradeManager::_clone() { + BrokerTradeManager* p = new BrokerTradeManager(); + p->m_datetime = m_datetime; + p->m_cash = m_cash; + p->m_position = m_position; + return shared_ptr(p); +} + +void BrokerTradeManager::fetchAssetInfoFromBroker(const OrderBrokerPtr& broker) { + HKU_CHECK(broker, "broker is null!"); + + auto brk_asset = broker->getAssetInfo(); + if (brk_asset.empty()) { + m_datetime = Datetime::now(); + m_cash = 0.0; + m_position.clear(); + return; + } + + try { + json asset(brk_asset); + m_datetime = asset.contains("datetime") + ? m_datetime = Datetime(asset["datetime"].get()) + : m_datetime = Datetime::now(); + + m_cash = asset["cash"]; + + auto& positions = asset["positions"]; + for (auto iter = positions.cbegin(); iter != positions.cend(); ++iter) { + const auto& jpos = *iter; + auto market = jpos["market"].get(); + auto code = jpos["code"].get(); + Stock stock = getStock(fmt::format("{}{}", market, code)); + if (stock.isNull()) { + HKU_WARN("Not found stock: {}{}", market, code); + continue; + } + + PositionRecord pos; + pos.stock = stock; + pos.takeDatetime = m_datetime; + pos.number = jpos["number"].get(); + pos.stoploss = jpos["stoploss"].get(); + pos.goalPrice = jpos["goal_price"].get(); + pos.totalNumber = pos.number; + price_t cost_price = jpos["cost_price"].get(); + pos.buyMoney = pos.number * cost_price; + pos.totalRisk = (pos.stoploss - cost_price) * pos.number; + m_position[stock.id()] = pos; + } + + } catch (const std::exception& e) { + HKU_ERROR(e.what()); + } + + m_broker_last_datetime = m_datetime; +} + +PositionRecordList BrokerTradeManager::getPositionList() const { + PositionRecordList result; + position_map_type::const_iterator iter = m_position.begin(); + for (; iter != m_position.end(); ++iter) { + result.push_back(iter->second); + } + return result; +} + +bool BrokerTradeManager::checkin(const Datetime& datetime, price_t cash) { + HKU_IF_RETURN(datetime < m_datetime, false); + m_cash += cash; + return true; +} + +TradeRecord BrokerTradeManager::buy(const Datetime& datetime, const Stock& stock, price_t realPrice, + double number, price_t stoploss, price_t goalPrice, + price_t planPrice, SystemPart from) { + TradeRecord result; + result.business = BUSINESS_INVALID; + + HKU_ERROR_IF_RETURN(stock.isNull(), result, "{} Stock is Null!", datetime); + HKU_ERROR_IF_RETURN(datetime < lastDatetime(), result, + "{} {} datetime must be >= lastDatetime({})!", datetime, + stock.market_code(), lastDatetime()); + HKU_ERROR_IF_RETURN(number == 0.0, result, "{} {} numer is zero!", datetime, + stock.market_code()); + HKU_ERROR_IF_RETURN(number < stock.minTradeNumber(), result, + "{} {} Buy number({}) must be >= minTradeNumber({})!", datetime, + stock.market_code(), number, stock.minTradeNumber()); + HKU_ERROR_IF_RETURN(number > stock.maxTradeNumber(), result, + "{} {} Buy number({}) must be <= maxTradeNumber({})!", datetime, + stock.market_code(), number, stock.maxTradeNumber()); + + CostRecord cost = getBuyCost(datetime, stock, realPrice, number); + + // 实际交易需要的现金=交易数量*实际交易价格+交易总成本 + int precision = getParam("precision"); + // price_t money = roundEx(realPrice * number * stock.unit() + cost.total, precision); + price_t money = roundEx(realPrice * number * stock.unit(), precision); + + HKU_WARN_IF_RETURN(m_cash < roundEx(money + cost.total, precision), result, + "{} {} Can't buy, need cash({:<.4f}) > current cash({:<.4f})!", datetime, + stock.market_code(), roundEx(money + cost.total, precision), m_cash); + + // 更新现金 + m_cash = roundEx(m_cash - money - cost.total, precision); + + // 加入交易记录 + result = TradeRecord(stock, datetime, BUSINESS_BUY, planPrice, realPrice, goalPrice, number, + cost, stoploss, m_cash, from); + + // 更新当前持仓记录 + position_map_type::iterator pos_iter = m_position.find(stock.id()); + if (pos_iter == m_position.end()) { + m_position[stock.id()] = PositionRecord( + stock, datetime, Null(), number, stoploss, goalPrice, number, money, cost.total, + roundEx((realPrice - stoploss) * number * stock.unit(), precision), 0.0); + } else { + PositionRecord& position = pos_iter->second; + position.number += number; + position.stoploss = stoploss; + position.goalPrice = goalPrice; + position.totalNumber += number; + position.buyMoney = roundEx(money + position.buyMoney, precision); + position.totalCost = roundEx(cost.total + position.totalCost, precision); + position.totalRisk = + roundEx(position.totalRisk + (realPrice - stoploss) * number * stock.unit(), precision); + } + + if (datetime > m_broker_last_datetime) { + list::const_iterator broker_iter = m_broker_list.begin(); + for (; broker_iter != m_broker_list.end(); ++broker_iter) { + (*broker_iter) + ->buy(datetime, stock.market(), stock.code(), realPrice, number, stoploss, goalPrice, + from); + if (datetime > m_broker_last_datetime) { + m_broker_last_datetime = datetime; + } + } + } + + return result; +} + +TradeRecord BrokerTradeManager::sell(const Datetime& datetime, const Stock& stock, + price_t realPrice, double number, price_t stoploss, + price_t goalPrice, price_t planPrice, SystemPart from) { + HKU_CHECK(!std::isnan(number), "sell number should be a valid double!"); + TradeRecord result; + + HKU_ERROR_IF_RETURN(stock.isNull(), result, "{} Stock is Null!", datetime); + HKU_ERROR_IF_RETURN(datetime < lastDatetime(), result, + "{} {} datetime must be >= lastDatetime({})!", datetime, + stock.market_code(), lastDatetime()); + HKU_ERROR_IF_RETURN(number == 0.0, result, "{} {} number is zero!", datetime, + stock.market_code()); + + // 对于分红扩股造成不满足最小交易量整数倍的情况,只能通过number=MAX_DOUBLE的方式全仓卖出 + HKU_ERROR_IF_RETURN(number < stock.minTradeNumber(), result, + "{} {} Sell number({}) must be >= minTradeNumber({})!", datetime, + stock.market_code(), number, stock.minTradeNumber()); + HKU_ERROR_IF_RETURN(number != MAX_DOUBLE && number > stock.maxTradeNumber(), result, + "{} {} Sell number({}) must be <= maxTradeNumber({})!", datetime, + stock.market_code(), number, stock.maxTradeNumber()); + + // 未持仓 + position_map_type::iterator pos_iter = m_position.find(stock.id()); + HKU_TRACE_IF_RETURN(pos_iter == m_position.end(), result, + "{} {} This stock was not bought never! ({}, {:<.4f}, {}, {})", datetime, + stock.market_code(), datetime, realPrice, number, getSystemPartName(from)); + + PositionRecord& position = pos_iter->second; + + // 调整欲卖出的数量,如果卖出数量等于MAX_DOUBLE,则表示卖出全部 + double real_number = number == MAX_DOUBLE ? position.number : number; + + // 欲卖出的数量大于当前持仓的数量 + HKU_ERROR_IF_RETURN(position.number < real_number, result, + "{} {} Try to sell number({}) > number of position({})!", datetime, + stock.market_code(), real_number, position.number); + + CostRecord cost = getSellCost(datetime, stock, realPrice, real_number); + + int precision = getParam("precision"); + price_t money = roundEx(realPrice * real_number * stock.unit(), precision); + + // 更新现金余额 + m_cash = roundEx(m_cash + money - cost.total, precision); + + // 更新交易记录 + result = TradeRecord(stock, datetime, BUSINESS_SELL, planPrice, realPrice, goalPrice, + real_number, cost, stoploss, m_cash, from); + + // 更新当前持仓情况 + position.number -= real_number; + position.stoploss = stoploss; + position.goalPrice = goalPrice; + // position.buyMoney = position.buyMoney; + position.totalCost = roundEx(position.totalCost + cost.total, precision); + position.sellMoney = roundEx(position.sellMoney + money, precision); + + if (position.number == 0) { + // 删除当前持仓 + m_position.erase(stock.id()); + } + + if (datetime > m_broker_last_datetime) { + list::const_iterator broker_iter = m_broker_list.begin(); + for (; broker_iter != m_broker_list.end(); ++broker_iter) { + (*broker_iter) + ->sell(datetime, stock.market(), stock.code(), realPrice, real_number, stoploss, + goalPrice, from); + if (datetime > m_broker_last_datetime) { + m_broker_last_datetime = datetime; + } + } + } + + return result; +} + +FundsRecord BrokerTradeManager::getFunds(KQuery::KType inktype) const { + FundsRecord funds; + int precision = getParam("precision"); + + string ktype(inktype); + to_upper(ktype); + + price_t value{0.0}; // 当前市值 + position_map_type::const_iterator iter = m_position.begin(); + for (; iter != m_position.end(); ++iter) { + const PositionRecord& record = iter->second; + auto price = record.stock.getMarketValue(lastDatetime(), ktype); + value = roundEx((value + record.number * price * record.stock.unit()), precision); + } + + funds.cash = m_cash; + funds.market_value = value; + funds.short_market_value = 0.0; + funds.base_cash = m_cash; + funds.base_asset = 0.0; + funds.borrow_cash = 0.0; + funds.borrow_asset = 0.0; + return funds; +} + +FundsRecord BrokerTradeManager::getFunds(const Datetime& datetime, KQuery::KType ktype) { + return (datetime >= m_datetime) ? getFunds(ktype) : FundsRecord(); +} + +} // namespace hku \ No newline at end of file diff --git a/hikyuu_cpp/hikyuu/strategy/AccountTradeManager.h b/hikyuu_cpp/hikyuu/strategy/BrokerTradeManager.h similarity index 75% rename from hikyuu_cpp/hikyuu/strategy/AccountTradeManager.h rename to hikyuu_cpp/hikyuu/strategy/BrokerTradeManager.h index 6540b593..a43ddd00 100644 --- a/hikyuu_cpp/hikyuu/strategy/AccountTradeManager.h +++ b/hikyuu_cpp/hikyuu/strategy/BrokerTradeManager.h @@ -1,28 +1,36 @@ /* - * Copyright(C) 2021 hikyuu.org + * Copyright (c) 2024 hikyuu.org * - * Create on: 2021-03-23 - * Author: fasiondog + * Created on: 2024-08-16 + * Author: fasiondog */ #pragma once -#include -#include "../trade_manage/TradeManagerBase.h" +#include "hikyuu/trade_manage/TradeManagerBase.h" namespace hku { -class HKU_API AccountTradeManager : public TradeManagerBase { +class HKU_API BrokerTradeManager : public TradeManagerBase { public: - AccountTradeManager() = default; - AccountTradeManager(const string& name, const string& pwd); - virtual ~AccountTradeManager() = default; + BrokerTradeManager() = default; + explicit BrokerTradeManager(const OrderBrokerPtr& broker, + const TradeCostPtr& costfunc = TC_Zero(), + const string& name = "SYS"); + virtual ~BrokerTradeManager() {} - virtual void _reset() override {} + virtual void _reset() override; - virtual shared_ptr _clone() override { - return std::make_shared(); - } + virtual shared_ptr _clone() override; + + virtual void fetchAssetInfoFromBroker(const OrderBrokerPtr& broker) override; + + /** + * 根据权息信息更新当前持仓与交易情况 + * @note 必须按时间顺序调用 + * @param datetime 当前时刻 + */ + virtual void updateWithWeight(const Datetime& datetime) override {} /** * 获取指定对象的保证金比率 @@ -36,26 +44,22 @@ public: /** 初始资金 */ virtual price_t initCash() const override { - HKU_WARN("The subclass does not implement this method"); - return 0.0; + return m_cash; } /** 账户建立日期 */ virtual Datetime initDatetime() const override { - HKU_WARN("The subclass does not implement this method"); - return Datetime(); + return m_datetime; } /** 第一笔买入交易发生日期,如未发生交易返回Null() */ virtual Datetime firstDatetime() const override { - HKU_WARN("The subclass does not implement this method"); - return Datetime(); + return m_datetime; } /** 最后一笔交易日期,注意和交易类型无关,如未发生交易返回账户建立日期 */ virtual Datetime lastDatetime() const override { - HKU_WARN("The subclass does not implement this method"); - return Datetime(); + return m_datetime; } /** @@ -63,8 +67,7 @@ public: * @note 仅返回当前信息,不会根据权息进行调整 */ virtual price_t currentCash() const override { - HKU_WARN("The subclass does not implement this method"); - return 0.0; + return m_cash; } /** @@ -72,8 +75,7 @@ public: * @note 如果不带日期参数,无法根据权息信息调整持仓 */ virtual price_t cash(const Datetime& datetime, KQuery::KType ktype = KQuery::DAY) override { - HKU_WARN("The subclass does not implement this method"); - return 0.0; + return (datetime >= m_datetime) ? currentCash() : 0.0; } /** @@ -83,8 +85,7 @@ public: * @return true 是 | false 否 */ virtual bool have(const Stock& stock) const override { - HKU_WARN("The subclass does not implement this method"); - return false; + return m_position.count(stock.id()) ? true : false; } /** @@ -93,49 +94,48 @@ public: * @param stock 指定证券 * @return true 是 | false 否 */ - virtual bool haveShort(const Stock& stock) const override { + virtual bool haveShort(const Stock& stock) const { HKU_WARN("The subclass does not implement this method"); return false; } /** 当前持有的证券种类数量 */ - virtual size_t getStockNumber() const override { - HKU_WARN("The subclass does not implement this method"); - return 0; + virtual size_t getStockNumber() const { + return m_position.size(); } /** 当前空头持有的证券种类数量 */ - virtual size_t getShortStockNumber() const override { + virtual size_t getShortStockNumber() const { HKU_WARN("The subclass does not implement this method"); return 0; } /** 获取指定时刻的某证券持有数量 */ - virtual double getHoldNumber(const Datetime& datetime, const Stock& stock) override { + virtual double getHoldNumber(const Datetime& datetime, const Stock& stock) { HKU_WARN("The subclass does not implement this method"); return 0.0; } /** 获取指定时刻的空头某证券持有数量 */ - virtual double getShortHoldNumber(const Datetime& datetime, const Stock& stock) override { + virtual double getShortHoldNumber(const Datetime& datetime, const Stock& stock) { HKU_WARN("The subclass does not implement this method"); return 0.0; } /** 获取指定时刻已借入的股票数量 */ - virtual double getDebtNumber(const Datetime& datetime, const Stock& stock) override { + virtual double getDebtNumber(const Datetime& datetime, const Stock& stock) { HKU_WARN("The subclass does not implement this method"); return 0.0; } /** 获取指定时刻已借入的现金额 */ - virtual price_t getDebtCash(const Datetime& datetime) override { + virtual price_t getDebtCash(const Datetime& datetime) { HKU_WARN("The subclass does not implement this method"); return 0.0; } /** 获取全部交易记录 */ - virtual TradeRecordList getTradeList() const override { + virtual TradeRecordList getTradeList() const { HKU_WARN("The subclass does not implement this method"); return TradeRecordList(); } @@ -146,50 +146,54 @@ public: * @param end 结束日期 * @return 交易记录列表 */ - virtual TradeRecordList getTradeList(const Datetime& start, - const Datetime& end) const override { + virtual TradeRecordList getTradeList(const Datetime& start, const Datetime& end) const { HKU_WARN("The subclass does not implement this method"); return TradeRecordList(); } /** 获取当前全部持仓记录 */ - virtual PositionRecordList getPositionList() const override { - HKU_WARN("The subclass does not implement this method"); - return PositionRecordList(); - } + virtual PositionRecordList getPositionList() const override; /** 获取全部历史持仓记录,即已平仓记录 */ - virtual PositionRecordList getHistoryPositionList() const override { + virtual PositionRecordList getHistoryPositionList() const { HKU_WARN("The subclass does not implement this method"); return PositionRecordList(); } /** 获取当前全部空头仓位记录 */ - virtual PositionRecordList getShortPositionList() const override { + virtual PositionRecordList getShortPositionList() const { HKU_WARN("The subclass does not implement this method"); return PositionRecordList(); } /** 获取全部空头历史仓位记录 */ - virtual PositionRecordList getShortHistoryPositionList() const override { + virtual PositionRecordList getShortHistoryPositionList() const { HKU_WARN("The subclass does not implement this method"); return PositionRecordList(); } - /** 获取指定证券的当前持仓记录,如当前未持有该票,返回Null() */ - virtual PositionRecord getPosition(const Datetime&, const Stock&) override { + /** + * 获取指定证券的持仓记录 + * @param date 指定日期 + * @param stock 指定的证券 + */ + virtual PositionRecord getPosition(const Datetime& date, const Stock& stock) { HKU_WARN("The subclass does not implement this method"); return PositionRecord(); } - /** 获取指定证券的当前空头仓位持仓记录,如当前未持有该票,返回Null() */ - virtual PositionRecord getShortPosition(const Stock&) const override { + /** + * 获取指定证券的空头持仓记录 + * @param date 指定日期 + * @param stock 指定的证券 + */ + virtual PositionRecord getShortPosition(const Stock&) const { HKU_WARN("The subclass does not implement this method"); return PositionRecord(); } /** 获取当前借入的股票列表 */ - virtual BorrowRecordList getBorrowStockList() const override { + virtual BorrowRecordList getBorrowStockList() const { HKU_WARN("The subclass does not implement this method"); return BorrowRecordList(); } @@ -200,10 +204,7 @@ public: * @param cash 存入的资金量 * @return true | false */ - virtual bool checkin(const Datetime& datetime, price_t cash) override { - HKU_WARN("The subclass does not implement this method"); - return false; - } + virtual bool checkin(const Datetime& datetime, price_t cash) override; /** * 取出资金 @@ -212,8 +213,8 @@ public: * @return true | false */ virtual bool checkout(const Datetime& datetime, price_t cash) override { - HKU_WARN("The subclass does not implement this method"); - return false; + m_cash = (cash > m_cash) ? 0.0 : m_cash - cash; + return true; } /** @@ -225,7 +226,7 @@ public: * @return true | false */ virtual bool checkinStock(const Datetime& datetime, const Stock& stock, price_t price, - double number) override { + double number) { HKU_WARN("The subclass does not implement this method"); return false; } @@ -240,7 +241,7 @@ public: * @note 应该不会被用到 */ virtual bool checkoutStock(const Datetime& datetime, const Stock& stock, price_t price, - double number) override { + double number) { HKU_WARN("The subclass does not implement this method"); return false; } @@ -259,10 +260,7 @@ public: */ virtual TradeRecord buy(const Datetime& datetime, const Stock& stock, price_t realPrice, double number, price_t stoploss = 0.0, price_t goalPrice = 0.0, - price_t planPrice = 0.0, SystemPart from = PART_INVALID) override { - HKU_WARN("The subclass does not implement this method"); - return TradeRecord(); - } + price_t planPrice = 0.0, SystemPart from = PART_INVALID) override; /** * 卖出操作 @@ -279,10 +277,7 @@ public: virtual TradeRecord sell(const Datetime& datetime, const Stock& stock, price_t realPrice, double number = MAX_DOUBLE, price_t stoploss = 0.0, price_t goalPrice = 0.0, price_t planPrice = 0.0, - SystemPart from = PART_INVALID) override { - HKU_WARN("The subclass does not implement this method"); - return TradeRecord(); - } + SystemPart from = PART_INVALID) override; /** * 卖空 @@ -298,8 +293,7 @@ public: */ virtual TradeRecord sellShort(const Datetime& datetime, const Stock& stock, price_t realPrice, double number, price_t stoploss = 0.0, price_t goalPrice = 0.0, - price_t planPrice = 0.0, - SystemPart from = PART_INVALID) override { + price_t planPrice = 0.0, SystemPart from = PART_INVALID) { HKU_WARN("The subclass does not implement this method"); return TradeRecord(); } @@ -319,7 +313,7 @@ public: virtual TradeRecord buyShort(const Datetime& datetime, const Stock& stock, price_t realPrice, double number = MAX_DOUBLE, price_t stoploss = 0.0, price_t goalPrice = 0.0, price_t planPrice = 0.0, - SystemPart from = PART_INVALID) override { + SystemPart from = PART_INVALID) { HKU_WARN("The subclass does not implement this method"); return TradeRecord(); } @@ -330,7 +324,7 @@ public: * @param cash 借入的现金 * @return true | false */ - virtual bool borrowCash(const Datetime& datetime, price_t cash) override { + virtual bool borrowCash(const Datetime& datetime, price_t cash) { HKU_WARN("The subclass does not implement this method"); return false; } @@ -341,7 +335,7 @@ public: * @param cash 归还现金 * @return true | false */ - virtual bool returnCash(const Datetime& datetime, price_t cash) override { + virtual bool returnCash(const Datetime& datetime, price_t cash) { HKU_WARN("The subclass does not implement this method"); return false; } @@ -355,7 +349,7 @@ public: * @return true | false */ virtual bool borrowStock(const Datetime& datetime, const Stock& stock, price_t price, - double number) override { + double number) { HKU_WARN("The subclass does not implement this method"); return false; } @@ -369,11 +363,18 @@ public: * @return true | false */ virtual bool returnStock(const Datetime& datetime, const Stock& stock, price_t price, - double number) override { + double number) { HKU_WARN("The subclass does not implement this method"); return false; } + /** + * 获取账户当前时刻的资产详情 + * @param ktype 日期的类型 + * @return 资产详情 + */ + virtual FundsRecord getFunds(KQuery::KType ktype = KQuery::DAY) const override; + /** * 获取指定时刻的资产市值详情 * @param datetime 必须大于帐户建立的初始日期,或为Null() @@ -382,10 +383,7 @@ public: * @note 当datetime等于Null()时,与getFunds(KType)同 */ virtual FundsRecord getFunds(const Datetime& datetime, - KQuery::KType ktype = KQuery::DAY) override { - HKU_WARN("The subclass does not implement this method"); - return FundsRecord(); - } + KQuery::KType ktype = KQuery::DAY) override; /** * 直接加入交易记录 @@ -393,13 +391,24 @@ public: * @param tr 待加入的交易记录 * @return bool true 成功 | false 失败 */ - virtual bool addTradeRecord(const TradeRecord& tr) override { + virtual bool addTradeRecord(const TradeRecord& tr) { + HKU_WARN("The subclass does not implement this method"); + return false; + } + + /** + * 直接加入持仓记录 + * @param pr 持仓记录 + * @return true 成功 + * @return false 失败 + */ + virtual bool addPosition(const PositionRecord& pr) { HKU_WARN("The subclass does not implement this method"); return false; } /** 字符串输出 */ - virtual string str() const override { + virtual string str() const { HKU_WARN("The subclass does not implement this method"); return string(); } @@ -408,22 +417,17 @@ public: * 以csv格式输出交易记录、未平仓记录、已平仓记录、资产净值曲线 * @param path 输出文件所在目录 */ - virtual void tocsv(const string& path) override { + virtual void tocsv(const string& path) { HKU_WARN("The subclass does not implement this method"); } private: - string getToken(); + Datetime m_datetime; // 当前日期 -private: - std::unique_ptr m_client; - string m_user; - string m_password; - string m_token; + price_t m_cash{0.0}; // 当前可用现金 + + typedef map position_map_type; + position_map_type m_position; // 当前持仓交易对象的持仓记录 }; -inline TMPtr crtAccountTM(const string& name, const string& pwd) { - return std::make_shared(name, pwd); -} - } // namespace hku \ No newline at end of file diff --git a/hikyuu_cpp/hikyuu/strategy/Strategy.cpp b/hikyuu_cpp/hikyuu/strategy/Strategy.cpp new file mode 100644 index 00000000..d5e5d460 --- /dev/null +++ b/hikyuu_cpp/hikyuu/strategy/Strategy.cpp @@ -0,0 +1,264 @@ +/* + * Copyright(C) 2021 hikyuu.org + * + * Create on: 2021-02-16 + * Author: fasiondog + */ + +#include +#include +#include "hikyuu/utilities/os.h" +#include "hikyuu/utilities/ini_parser/IniParser.h" +#include "hikyuu/global/GlobalSpotAgent.h" +#include "hikyuu/global/schedule/scheduler.h" +#include "hikyuu/global/GlobalTaskGroup.h" +#include "hikyuu/global/sysinfo.h" +#include "hikyuu/hikyuu.h" +#include "Strategy.h" + +// python 中运行拉回主线程循环,非 python 环境则直接执行 +#define EVENT(func) \ + if (runningInPython()) { \ + event(func); \ + } else { \ + func(); \ + } + +namespace hku { + +std::atomic_bool Strategy::ms_keep_running = true; + +void Strategy::sig_handler(int sig) { + if (sig == SIGINT) { + ms_keep_running = false; + exit(0); + } +} + +Strategy::Strategy() : Strategy("Strategy", "") {} + +Strategy::Strategy(const string& name, const string& config_file) +: m_name(name), m_config_file(config_file) { + if (m_config_file.empty()) { + string home = getUserDir(); + HKU_ERROR_IF(home == "", "Failed get user home path!"); +#if HKU_OS_WINOWS + m_config_file = format("{}\\{}", home, ".hikyuu\\hikyuu.ini"); +#else + m_config_file = format("{}/{}", home, ".hikyuu/hikyuu.ini"); +#endif + } +} + +Strategy::Strategy(const vector& codeList, const vector& ktypeList, + const string& name, const string& config_file) +: Strategy(name, config_file) { + m_context.setStockCodeList(codeList); + m_context.setKTypeList(ktypeList); +} + +Strategy::~Strategy() { + ms_keep_running = false; + CLS_INFO("Quit Strategy {}!", m_name); +} + +void Strategy::run() { + CLS_IF_RETURN(m_running, void()); + + StockManager& sm = StockManager::instance(); + + // sm 尚未初始化,则初始化 + if (sm.thread_id() == std::thread::id()) { + // 注册 ctrl-c 终止信号 + std::signal(SIGINT, sig_handler); + + CLS_INFO("{} is running! You can press Ctrl-C to terminte ...", m_name); + + // 初始化 + hikyuu_init(m_config_file, false, m_context); + } else { + m_context = sm.getStrategyContext(); + } + + CLS_CHECK(!m_context.getStockCodeList().empty(), "The context does not contain any stocks!"); + CLS_CHECK(!m_context.getKTypeList().empty(), "The K type list was empty!"); + + // 先将行情接收代理停止,以便后面加入处理函数 + stopSpotAgent(); + + auto& agent = *getGlobalSpotAgent(); + agent.addProcess([this](const SpotRecord& spot) { receivedSpot(spot); }); + agent.addPostProcess([this](Datetime revTime) { + if (m_on_recieved_spot) { + EVENT([=]() { m_on_recieved_spot(revTime); }); + } + }); + startSpotAgent(true); + + m_running = true; +} + +void Strategy::start() { + CLS_CHECK(m_running, "No handler functions are registered!"); + CLS_INFO("start even loop ..."); + _startEventLoop(); +} + +void Strategy::onChange(std::function&& changeFunc) { + if (!m_running) { + run(); + } + m_on_change = changeFunc; +} + +void Strategy::onReceivedSpot(std::function&& recievedFucn) { + if (!m_running) { + run(); + } + m_on_recieved_spot = recievedFucn; +} + +void Strategy::receivedSpot(const SpotRecord& spot) { + Stock stk = getStock(format("{}{}", spot.market, spot.code)); + if (!stk.isNull()) { + if (m_on_change) { + EVENT([=]() { m_on_change(stk, spot); }); + } + } +} + +void Strategy::runDaily(std::function&& func, const TimeDelta& delta, + const std::string& market) { + if (!m_running) { + run(); + } + + try { + auto* scheduler = getScheduler(); + auto new_func = [=]() { + const auto& sm = StockManager::instance(); + auto today = Datetime::today(); + int day = today.dayOfWeek(); + if (day == 0 || day == 6 || sm.isHoliday(today)) { + return; + } + + auto market_info = sm.getMarketInfo(market); + Datetime open1 = today + market_info.openTime1(); + Datetime close1 = today + market_info.closeTime1(); + Datetime open2 = today + market_info.openTime2(); + Datetime close2 = today + market_info.closeTime2(); + Datetime now = Datetime::now(); + if ((now >= open1 && now <= close1) || (now >= open2 && now <= close2)) { + EVENT(func); + } + }; + + const auto& sm = StockManager::instance(); + auto market_info = sm.getMarketInfo(market); + auto today = Datetime::today(); + auto now = Datetime::now(); + TimeDelta now_time = now - today; + if (now_time >= market_info.closeTime2()) { + scheduler->addFuncAtTime(today.nextDay() + market_info.openTime1(), [=]() { + new_func(); + auto* sched = getScheduler(); + sched->addDurationFunc(std::numeric_limits::max(), delta, new_func); + }); + + } else if (now_time >= market_info.openTime2()) { + int64_t ticks = now_time.ticks() - market_info.openTime2().ticks(); + int64_t delta_ticks = delta.ticks(); + if (ticks % delta_ticks == 0) { + scheduler->addDurationFunc(std::numeric_limits::max(), delta, new_func); + } else { + auto delay = TimeDelta::fromTicks((ticks / delta_ticks + 1) * delta_ticks - ticks); + scheduler->addFuncAtTime(now + delay, [=]() { + new_func(); + auto* sched = getScheduler(); + sched->addDurationFunc(std::numeric_limits::max(), delta, new_func); + }); + } + + } else if (now_time >= market_info.closeTime1()) { + scheduler->addFuncAtTime(today + market_info.openTime2(), [=]() { + new_func(); + auto* sched = getScheduler(); + sched->addDurationFunc(std::numeric_limits::max(), delta, new_func); + }); + + } else if (now_time < market_info.closeTime1() && now_time >= market_info.openTime1()) { + int64_t ticks = now_time.ticks() - market_info.openTime1().ticks(); + int64_t delta_ticks = delta.ticks(); + if (ticks % delta_ticks == 0) { + scheduler->addDurationFunc(std::numeric_limits::max(), delta, new_func); + } else { + auto delay = TimeDelta::fromTicks((ticks / delta_ticks + 1) * delta_ticks - ticks); + scheduler->addFuncAtTime(now + delay, [=]() { + new_func(); + auto* sched = getScheduler(); + sched->addDurationFunc(std::numeric_limits::max(), delta, new_func); + }); + } + + } else if (now_time < market_info.openTime1()) { + scheduler->addFuncAtTime(today + market_info.openTime1(), [=]() { + new_func(); + auto* sched = getScheduler(); + sched->addDurationFunc(std::numeric_limits::max(), delta, new_func); + }); + + } else { + CLS_ERROR("Unknown process! now_time: {}", now_time); + } + } catch (const std::exception& e) { + CLS_THROW(e.what()); + } +} + +void Strategy::runDailyAt(std::function&& func, const TimeDelta& delta, + bool ignoreHoliday) { + if (!m_running) { + run(); + } + + auto new_func = [=]() { + if (!ignoreHoliday) { + EVENT(func); + return; + } + + const auto& sm = StockManager::instance(); + auto today = Datetime::today(); + int day = today.dayOfWeek(); + if (day != 0 && day != 6 && !sm.isHoliday(today)) { + EVENT(func); + } + }; + + auto* scheduler = getScheduler(); + scheduler->addFuncAtTimeEveryDay(delta, new_func); +} + +/* + * 在主线程中处理事件队列,避免 python GIL + */ +void Strategy::_startEventLoop() { + while (ms_keep_running) { + event_type task; + m_event_queue.wait_and_pop(task); + if (task.isNullTask()) { + ms_keep_running = false; + } else { + try { + task(); + } catch (const std::exception& e) { + CLS_ERROR("Failed run task! {}", e.what()); + } catch (...) { + CLS_ERROR("Failed run task! Unknow error!"); + } + } + } +} + +} // namespace hku \ No newline at end of file diff --git a/hikyuu_cpp/hikyuu/strategy/Strategy.h b/hikyuu_cpp/hikyuu/strategy/Strategy.h new file mode 100644 index 00000000..2f0d13c3 --- /dev/null +++ b/hikyuu_cpp/hikyuu/strategy/Strategy.h @@ -0,0 +1,132 @@ +/* + * Copyright(C) 2021 hikyuu.org + * + * Create on: 2021-02-16 + * Author: fasiondog + */ + +#pragma once + +#include +#include "../DataType.h" +#include "../StrategyContext.h" +#include "../global/SpotRecord.h" +#include "../utilities/thread/FuncWrapper.h" +#include "../utilities/thread/ThreadSafeQueue.h" +#include "../trade_sys/portfolio/Portfolio.h" + +namespace hku { + +/** + * @brief 策略运行时 + * @ingroup Stratgy + */ +class HKU_API Strategy { + CLASS_LOGGER_IMP(Strategy) + +public: + Strategy(); + explicit Strategy(const string& name, const string& config_file = ""); + Strategy(const vector& codeList, const vector& ktypeList, + const string& name = "Strategy", const string& config_file = ""); + + virtual ~Strategy(); + + const string& name() const { + return m_name; + } + + void name(const string& name) { + m_name = name; + } + + const StrategyContext& context() const { + return m_context; + } + + /** + * 每日开盘时间内,以 delta 为周期循环定时执行指定任务 + * @param func 待执行的任务 + * @param delta 间隔时间 + * @param market 指定的市场 + */ + void runDaily(std::function&& func, const TimeDelta& delta, + const std::string& market = "SH"); + + /** + * 每日在指定时刻执行任务 + * @param func 待执行的任务 + * @param delta 指定时刻 + * @param ignoreHoliday 忽略节假日,即节假日不执行 + */ + void runDailyAt(std::function&& func, const TimeDelta& delta, + bool ignoreHoliday = true); + + /** + * 正确数据发生变化调用,即接收到相应行情数据变更 + * @note 通常用于调试。且只要收到行情采集消息就会触发,不受开、闭市时间限制 + * @param stk 数据发生变化的 stock + * @param spot 接收到的具体数据 + */ + void onChange(std::function&& changeFunc); + + /** + * 一批行情数据接受完毕后通知 + * @note 通常仅用于调试打印,该批行情数据中不一定含有上下文中包含的 stock + * 且只要收到行情采集消息就会触发,不受开、闭市时间限制。 + */ + void onReceivedSpot(std::function&& recievedFucn); + + /** + * 启动策略执行,必须在已注册相关处理函数后执行 + */ + void start(); + +private: + string m_name; + string m_config_file; + StrategyContext m_context; + std::function m_on_recieved_spot; + std::function m_on_change; + bool m_running{false}; + +private: + void run(); + void receivedSpot(const SpotRecord& spot); + +private: + static std::atomic_bool ms_keep_running; + static void sig_handler(int sig); + + typedef FuncWrapper event_type; + ThreadSafeQueue m_event_queue; // 消息队列 + + /** 先消息队列提交任务后返回的对应 future 的类型 */ + template + using event_handle = std::future; + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4996) +#endif + + /** 向线程池提交任务 */ + template + event_handle::type> event(FunctionType f) { + typedef typename std::result_of::type result_type; + std::packaged_task task(f); + event_handle res(task.get_future()); + m_event_queue.push(std::move(task)); + return res; + } + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + void _startEventLoop(); +}; + +typedef shared_ptr StrategyPtr; + +} // namespace hku \ No newline at end of file diff --git a/hikyuu_cpp/hikyuu/strategy/StrategyBase.cpp b/hikyuu_cpp/hikyuu/strategy/StrategyBase.cpp deleted file mode 100644 index 0175d8c2..00000000 --- a/hikyuu_cpp/hikyuu/strategy/StrategyBase.cpp +++ /dev/null @@ -1,300 +0,0 @@ -/* - * Copyright(C) 2021 hikyuu.org - * - * Create on: 2021-02-16 - * Author: fasiondog - */ - -#include -#include -#include "../utilities/os.h" -#include "../utilities/IniParser.h" -#include "../global/schedule/scheduler.h" -#include "StrategyBase.h" - -namespace hku { - -std::atomic_bool StrategyBase::ms_keep_running = true; - -void StrategyBase::sig_handler(int sig) { - if (sig == SIGINT) { - ms_keep_running = false; - exit(0); - } -} - -StrategyBase::StrategyBase() : StrategyBase("Strategy") {} - -StrategyBase::StrategyBase(const string& name) { - string home = getUserDir(); - HKU_ERROR_IF(home == "", "Failed get user home path!"); -#if HKU_OS_WINOWS - m_config_file = format("{}\\{}", home, ".hikyuu\\hikyuu.ini"); -#else - m_config_file = format("{}/{}", home, ".hikyuu/hikyuu.ini"); -#endif - _initDefaultParam(); -} - -StrategyBase::StrategyBase(const string& name, const string& config_file) -: m_name(name), m_config_file(config_file) { - _initDefaultParam(); -} - -StrategyBase::~StrategyBase() { - HKU_INFO("[Strategy {}] Quit Strategy!", m_name); -} - -void StrategyBase::_initDefaultParam() { - setParam("enable_market_event", false); - setParam("enable_30_seconds_clock", false); - setParam("enable_1min_clock", false); - setParam("enable_3min_clock", false); - setParam("enable_5min_clock", false); - setParam("enable_10min_clock", false); - setParam("enable_15min_clock", false); - setParam("enable_30min_clock", false); - setParam("enable_60min_clock", false); - setParam("enable_2hour_clock", false); -} - -void StrategyBase::_run(bool forTest) { - // 调用 strategy 自身的初始化方法 - init(); - - StockManager& sm = StockManager::instance(); - - // 非独立进程方式运行 Stratege 或 重复执行,则直接返回 - if (sm.thread_id() == std::this_thread::get_id()) { - return; - } - - // 注册 ctrl-c 终止信号 - std::signal(SIGINT, sig_handler); - - HKU_INFO("[Strategy {}] strategy is running! You can press Ctrl-C to terminte ...", m_name); - - // 加载上下文指定的证券数据 - IniParser config; - try { - config.read(m_config_file); - - } catch (std::exception& e) { - HKU_FATAL("[Strategy {}] Failed read configure file (\"{}\")! {}", m_name, m_config_file, - e.what()); - HKU_INFO("[Strategy {}] Exit Strategy", m_name); - exit(1); - } catch (...) { - HKU_FATAL("[Strategy {}] Failed read configure file (\"{}\")! Unknow error!", m_name, - m_config_file); - HKU_INFO("[Strategy {}] Exit Strategy", m_name); - exit(1); - } - - Parameter baseParam, blockParam, kdataParam, preloadParam, hkuParam; - - hkuParam.set("tmpdir", config.get("hikyuu", "tmpdir", ".")); - hkuParam.set("datadir", config.get("hikyuu", "datadir", ".")); - hkuParam.set("quotation_server", - config.get("hikyuu", "quotation_server", "ipc:///tmp/hikyuu_real.ipc")); - - if (!config.hasSection("baseinfo")) { - HKU_FATAL("Missing configure of baseinfo!"); - exit(1); - } - - IniParser::StringListPtr option = config.getOptionList("baseinfo"); - for (auto iter = option->begin(); iter != option->end(); ++iter) { - string value = config.get("baseinfo", *iter); - baseParam.set(*iter, value); - } - - IniParser::StringListPtr block_config = config.getOptionList("block"); - for (auto iter = block_config->begin(); iter != block_config->end(); ++iter) { - string value = config.get("block", *iter); - blockParam.set(*iter, value); - } - - option = config.getOptionList("kdata"); - for (auto iter = option->begin(); iter != option->end(); ++iter) { - kdataParam.set(*iter, config.get("kdata", *iter)); - } - - // 设置预加载参数,只加载指定的 ktype 至内存 - auto ktype_list = m_context.getKTypeList(); - if (ktype_list.empty()) { - // 如果为空,则默认加载日线数据 - ktype_list.push_back(KQuery::DAY); - } - - // 不使用默认的预加载模式 - for (auto ktype : ktype_list) { - to_lower(ktype); - preloadParam.set(ktype, false); - } - - sm.init(baseParam, blockParam, kdataParam, preloadParam, hkuParam, m_context); - - const auto& stk_code_list = getStockCodeList(); - m_stock_list.reserve(stk_code_list.size()); - for (const auto& code : stk_code_list) { - Stock stk = getStock(code); - if (!stk.isNull()) { - m_stock_list.push_back(stk); - } else { - HKU_WARN("[Strategy {}] Invalid code: {}, can't find the stock!", m_name, code); - } - } - HKU_WARN_IF(m_stock_list.empty(), "[Strategy {}] stock list is empty!", m_name); - - // 借助 Stock.setKRecordList 方法进行预加载(同步方式,不需要异步加载) - // 只从 context 指定起始日期开始加载 - size_t ktype_count = ktype_list.size(); - vector k_buffer(ktype_count); - for (auto& stk : m_stock_list) { - // 保留原始 KDataDriver,因为使用 stock.setKRecordList 将会把 stock 的 KDataDriver 设置为 - // DoNothing - auto old_driver = stk.getKDataDirver(); - - for (size_t i = 0; i < ktype_count; i++) { - k_buffer[i] = stk.getKRecordList( - KQueryByDate(m_context.startDatetime(), Null(), ktype_list[i])); - } - for (size_t i = 0; i < ktype_count; i++) { - stk.setKRecordList(std::move(k_buffer[i]), ktype_list[i]); - } - - // 恢复 KDataDriver - stk.setKDataDriver(old_driver); - } - - // 计算每个类型当前最后的日期 - for (const auto& ktype : ktype_list) { - Datetime last_date = Datetime::min(); - for (auto& stk : m_stock_list) { - size_t count = stk.getCount(ktype); - if (count > 1) { - auto kr = stk.getKRecord(count - 1, ktype); - if (kr.datetime > last_date) { - last_date = kr.datetime; - } - } - } - m_ref_last_time[ktype] = last_date == Datetime::min() ? Null() : last_date; - } - - if (!forTest) { - // 启动行情接收代理 - auto& agent = *getGlobalSpotAgent(); - agent.addProcess([this](const SpotRecord& spot) { this->receivedSpot(spot); }); - agent.addPostProcess([this](Datetime revTime) { this->finishReceivedSpot(revTime); }); - startSpotAgent(false); - - _addTimer(); - - HKU_INFO("start even loop ..."); - _startEventLoop(); - } -} - -void StrategyBase::receivedSpot(const SpotRecord& spot) { - Stock stk = getStock(format("{}{}", spot.market, spot.code)); - if (!stk.isNull()) { - m_spot_map[stk] = spot; - } -} - -void StrategyBase::finishReceivedSpot(Datetime revTime) { - HKU_IF_RETURN(m_stock_list.empty(), void()); - event([this]() { this->onTick(); }); - - Stock& ref_stk = m_stock_list[0]; - const auto& ktype_list = getKTypeList(); - for (const auto& ktype : ktype_list) { - size_t count = ref_stk.getCount(ktype); - if (count > 0) { - KRecord k = ref_stk.getKRecord(count - 1, ktype); - if (k.datetime != m_ref_last_time[ktype]) { - m_ref_last_time[ktype] = k.datetime; - event([this, ktype]() { this->onBar(ktype); }); - } - } - } -} - -void StrategyBase::_addTimer() { - std::unordered_set market_set; - for (auto& stk : m_stock_list) { - market_set.insert(stk.market()); - } - - const auto& sm = StockManager::instance(); - TimeDelta openTime(0, 23, 59, 59, 999, 999), closeTime(0); - for (const auto& market : market_set) { - auto market_info = sm.getMarketInfo(market); - if (market_info.openTime1() < market_info.closeTime1()) { - if (market_info.openTime1() < openTime) { - openTime = market_info.openTime1(); - } - if (market_info.closeTime1() > closeTime) { - closeTime = market_info.closeTime1(); - } - } - if (market_info.openTime2() < market_info.closeTime2()) { - if (market_info.openTime2() < openTime) { - openTime = market_info.openTime2(); - } - if (market_info.closeTime2() > closeTime) { - closeTime = market_info.closeTime2(); - } - } - } - - HKU_ERROR_IF_RETURN(openTime >= closeTime, void(), "Invalid market openTime: {}, closeTime: {}", - openTime, closeTime); - - auto* scheduler = getScheduler(); - if (getParam("enable_market_event")) { - scheduler->addFuncAtTimeEveryDay( - openTime, [this]() { this->event([this]() { this->onMarketOpen(); }); }); - scheduler->addFuncAtTimeEveryDay( - closeTime, [this]() { this->event([this]() { this->onMarketClose(); }); }); - } - - _addClockEvent("enable_30_seconds_clock", Seconds(30), openTime, closeTime); - _addClockEvent("enable_1min_clock", Minutes(1), openTime, closeTime); - _addClockEvent("enable_3min_clock", Minutes(3), openTime, closeTime); - _addClockEvent("enable_5min_clock", Minutes(5), openTime, closeTime); - _addClockEvent("enable_10min_clock", Minutes(10), openTime, closeTime); - _addClockEvent("enable_15min_clock", Minutes(15), openTime, closeTime); - _addClockEvent("enable_30min_clock", Minutes(30), openTime, closeTime); - _addClockEvent("enable_60min_clock", Minutes(60), openTime, closeTime); - _addClockEvent("enable_2hour_clock", Hours(2), openTime, closeTime); -} - -void StrategyBase::_addClockEvent(const string& enable, TimeDelta delta, TimeDelta openTime, - TimeDelta closeTime) { - auto* scheduler = getScheduler(); - if (getParam(enable)) { - int repeat = static_cast((closeTime - openTime) / delta); - scheduler->addFunc(Datetime::min(), Datetime::max(), openTime, closeTime, repeat, delta, - [this, delta]() { this->onClock(delta); }); - } -} - -/* - * 在主线程中处理事件队列,避免 python GIL - */ -void StrategyBase::_startEventLoop() { - while (ms_keep_running) { - event_type task; - m_event_queue.wait_and_pop(task); - if (task.isNullTask()) { - ms_keep_running = false; - } else { - task(); - } - } -} - -} // namespace hku \ No newline at end of file diff --git a/hikyuu_cpp/hikyuu/strategy/StrategyBase.h b/hikyuu_cpp/hikyuu/strategy/StrategyBase.h deleted file mode 100644 index 20b99194..00000000 --- a/hikyuu_cpp/hikyuu/strategy/StrategyBase.h +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright(C) 2021 hikyuu.org - * - * Create on: 2021-02-16 - * Author: fasiondog - */ - -#pragma once - -#include -#include "../DataType.h" -#include "../StrategyContext.h" -#include "../utilities/Parameter.h" -#include "../utilities/thread/FuncWrapper.h" -#include "../utilities/thread/ThreadSafeQueue.h" -#include "../global/GlobalSpotAgent.h" -#include "../trade_sys/portfolio/Portfolio.h" - -namespace hku { - -class HKU_API StrategyBase { - PARAMETER_SUPPORT - -public: - StrategyBase(); - explicit StrategyBase(const string& name); - StrategyBase(const string& name, const string& config_file); - - virtual ~StrategyBase(); - - const string& name() const { - return m_name; - } - - void name(const string& name) { - m_name = name; - } - - StockManager& getSM() { - return StockManager::instance(); - } - - TMPtr getTM() const { - return m_tm; - } - - void setTM(const TMPtr& tm) { - m_tm = tm; - } - - const StrategyContext& context() const { - return m_context; - } - - void context(const StrategyContext& context) { - m_context = context; - } - - Datetime startDatetime() const { - return m_context.startDatetime(); - } - - void startDatetime(const Datetime& d) { - m_context.startDatetime(d); - } - - void setStockCodeList(vector&& stockList) { - m_context.setStockCodeList(std::move(stockList)); - } - - void setStockCodeList(const vector& stockList) { - m_context.setStockCodeList(stockList); - } - - const vector& getStockCodeList() const { - return m_context.getStockCodeList(); - } - - void setKTypeList(const vector& ktypeList) { - m_context.setKTypeList(ktypeList); - } - - const vector& getKTypeList() const { - return m_context.getKTypeList(); - } - - void run() { - _run(false); - } - - void receivedSpot(const SpotRecord& spot); - void finishReceivedSpot(Datetime revTime); - - virtual void init() {} - virtual void onTick() {} - virtual void onBar(const KQuery::KType& ktype){}; - - virtual void onMarketOpen() {} - virtual void onMarketClose() {} - virtual void onClock(TimeDelta detla) {} - -private: - string m_name; - string m_config_file; - StrategyContext m_context; - TMPtr m_tm; - - StockList m_stock_list; - std::unordered_map m_ref_last_time; - std::unordered_map m_spot_map; - -private: - void _initDefaultParam(); - - void _addTimer(); - void _addClockEvent(const string& enable, TimeDelta delta, TimeDelta openTime, - TimeDelta closeTime); - - void _run(bool forTest); - -private: - static std::atomic_bool ms_keep_running; - static void sig_handler(int sig); - - typedef FuncWrapper event_type; - ThreadSafeQueue m_event_queue; // 消息队列 - - /** 先消息队列提交任务后返回的对应 future 的类型 */ - template - using event_handle = std::future; - -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable : 4996) -#endif - - /** 向线程池提交任务 */ - template - event_handle::type> event(FunctionType f) { - typedef typename std::result_of::type result_type; - std::packaged_task task(f); - event_handle res(task.get_future()); - m_event_queue.push(std::move(task)); - return res; - } - -#ifdef _MSC_VER -#pragma warning(pop) -#endif - - void _startEventLoop(); -}; - -typedef shared_ptr StrategyPtr; - -} // namespace hku \ No newline at end of file diff --git a/hikyuu_cpp/hikyuu/trade_manage/FundsRecord.cpp b/hikyuu_cpp/hikyuu/trade_manage/FundsRecord.cpp index dffda018..2542e5ca 100644 --- a/hikyuu_cpp/hikyuu/trade_manage/FundsRecord.cpp +++ b/hikyuu_cpp/hikyuu/trade_manage/FundsRecord.cpp @@ -23,18 +23,9 @@ HKU_API std::ostream& operator<<(std::ostream& os, const FundsRecord& funds) { return os; } -FundsRecord::FundsRecord() -: cash(0.0), - market_value(0.0), - short_market_value(0.0), - base_cash(0.0), - base_asset(0.0), - borrow_cash(0.0), - borrow_asset(0.0) {} - -FundsRecord ::FundsRecord(price_t cash, price_t market_value, price_t short_market_value, - price_t base_cash, price_t base_asset, price_t borrow_cash, - price_t borrow_asset) +FundsRecord::FundsRecord(price_t cash, price_t market_value, price_t short_market_value, + price_t base_cash, price_t base_asset, price_t borrow_cash, + price_t borrow_asset) : cash(cash), market_value(market_value), short_market_value(short_market_value), diff --git a/hikyuu_cpp/hikyuu/trade_manage/FundsRecord.h b/hikyuu_cpp/hikyuu/trade_manage/FundsRecord.h index c9e2f582..00c12d6c 100644 --- a/hikyuu_cpp/hikyuu/trade_manage/FundsRecord.h +++ b/hikyuu_cpp/hikyuu/trade_manage/FundsRecord.h @@ -20,17 +20,17 @@ namespace hku { */ class HKU_API FundsRecord { public: - FundsRecord(); + FundsRecord() = default; FundsRecord(price_t cash, price_t market_value, price_t short_market_value, price_t base_cash, price_t base_asset, price_t borrow_cash, price_t borrow_asset); - price_t cash; /**< 当前现金 */ - price_t market_value; /**< 当前多头市值 */ - price_t short_market_value; /**< 当前空头仓位市值 */ - price_t base_cash; /**< 当前投入本金principal */ - price_t base_asset; /**< 当前投入的资产价值 */ - price_t borrow_cash; /**< 当前借入的资金,即负债 */ - price_t borrow_asset; /**< 当前借入证券资产价值 */ + price_t cash{0.0}; /**< 当前现金 */ + price_t market_value{0.0}; /**< 当前多头市值 */ + price_t short_market_value{0.0}; /**< 当前空头仓位市值 */ + price_t base_cash{0.0}; /**< 当前投入本金principal */ + price_t base_asset{0.0}; /**< 当前投入的资产价值 */ + price_t borrow_cash{0.0}; /**< 当前借入的资金,即负债 */ + price_t borrow_asset{0.0}; /**< 当前借入证券资产价值 */ // 当前总资产 = 现金 + 多头市值 + 空头数量×(借入价格 - 当前价格) // = cash + market_value + borrow_asset - short_market_value diff --git a/hikyuu_cpp/hikyuu/trade_manage/OrderBrokerBase.cpp b/hikyuu_cpp/hikyuu/trade_manage/OrderBrokerBase.cpp index 7ea38c6e..ff135130 100644 --- a/hikyuu_cpp/hikyuu/trade_manage/OrderBrokerBase.cpp +++ b/hikyuu_cpp/hikyuu/trade_manage/OrderBrokerBase.cpp @@ -5,10 +5,43 @@ * Author: fasiondog */ +#include #include "OrderBrokerBase.h" namespace hku { +using json = nlohmann::json; + +BrokerPositionRecord::BrokerPositionRecord(const Stock& stock_, price_t number_, price_t money_) +: stock(stock_), number(number_), money(money_) {} + +BrokerPositionRecord::BrokerPositionRecord(BrokerPositionRecord&& rv) +: stock(std::move(rv.stock)), number(rv.number), money(rv.money) { + rv.number = 0.0; + rv.money = 0.0; +} + +BrokerPositionRecord& BrokerPositionRecord::operator=(BrokerPositionRecord&& rv) { + if (this != &rv) { + stock = std::move(rv.stock); + number = rv.number; + money = rv.money; + rv.number = 0.0; + rv.money = 0.0; + } + return *this; +} + +string BrokerPositionRecord::str() const { + return fmt::format("BrokerPositionRecord({}, {:<.4f}, {:<.4f})", stock.market_code(), number, + money); +} + +HKU_API std::ostream& operator<<(std::ostream& os, const BrokerPositionRecord& pos) { + os << pos.str(); + return os; +} + HKU_API std::ostream& operator<<(std::ostream& os, const OrderBrokerBase& broker) { os << "OrderBroker(" << broker.name() << ")"; return os; @@ -25,34 +58,40 @@ OrderBrokerBase::OrderBrokerBase(const string& name) : m_name(name) {} OrderBrokerBase::~OrderBrokerBase() {} -Datetime OrderBrokerBase::buy(Datetime datetime, const string& market, const string& code, - price_t price, double num) { - Datetime tradetime; +void OrderBrokerBase::buy(Datetime datetime, const string& market, const string& code, + price_t price, double num, price_t stoploss, price_t goalPrice, + SystemPart from) noexcept { try { - tradetime = _buy(datetime, market, code, price, num); + _buy(datetime, market, code, price, num, stoploss, goalPrice, from); } catch (const std::exception& e) { HKU_ERROR(e.what()); - tradetime = Null(); } catch (...) { HKU_ERROR_UNKNOWN; - tradetime = Null(); } - return tradetime; } -Datetime OrderBrokerBase::sell(Datetime datetime, const string& market, const string& code, - price_t price, double num) { - Datetime tradetime; +void OrderBrokerBase::sell(Datetime datetime, const string& market, const string& code, + price_t price, double num, price_t stoploss, price_t goalPrice, + SystemPart from) noexcept { try { - tradetime = _sell(datetime, market, code, price, num); + _sell(datetime, market, code, price, num, stoploss, goalPrice, from); } catch (const std::exception& e) { HKU_ERROR(e.what()); - tradetime = Null(); } catch (...) { HKU_ERROR_UNKNOWN; - tradetime = Null(); } - return tradetime; +} + +string OrderBrokerBase::getAssetInfo() noexcept { + string ret; + try { + ret = _getAssetInfo(); + } catch (const std::exception& e) { + HKU_ERROR(e.what()); + } catch (...) { + HKU_ERROR_UNKNOWN; + } + return ret; } } /* namespace hku */ diff --git a/hikyuu_cpp/hikyuu/trade_manage/OrderBrokerBase.h b/hikyuu_cpp/hikyuu/trade_manage/OrderBrokerBase.h index 2c2055b6..0597eed3 100644 --- a/hikyuu_cpp/hikyuu/trade_manage/OrderBrokerBase.h +++ b/hikyuu_cpp/hikyuu/trade_manage/OrderBrokerBase.h @@ -11,9 +11,28 @@ #include "../DataType.h" #include "../utilities/Parameter.h" +#include "../trade_sys/system/SystemPart.h" namespace hku { +struct HKU_API BrokerPositionRecord { + Stock stock; + price_t number{0.0}; // 数量 + price_t money{0.0}; // 买入花费总资金 + + BrokerPositionRecord() = default; + BrokerPositionRecord(const Stock& stock, price_t number, price_t money); + BrokerPositionRecord(const BrokerPositionRecord&) = default; + BrokerPositionRecord(BrokerPositionRecord&& rv); + + BrokerPositionRecord& operator=(const BrokerPositionRecord&) = default; + BrokerPositionRecord& operator=(BrokerPositionRecord&& rv); + + string str() const; +}; + +HKU_API std::ostream& operator<<(std::ostream& os, const BrokerPositionRecord&); + /** * 订单代理基类,实现实际的订单操作及程序化的订单。 * @details 可通过向 TradeManager.regBroker 向 TradeManager 注册多个订单代理实例。 @@ -49,10 +68,12 @@ public: * @param code 证券代码 * @param price 买入价格 * @param num 买入数量 - * @return 操作执行的时刻。实盘时,应返回委托单时间或服务器交易时间。 + * @param stoploss 预期的止损价 + * @param goalPrice 预期的目标价位 + * @param from 系统部件来源 */ - Datetime buy(Datetime datetime, const string& market, const string& code, price_t price, - double num); + void buy(Datetime datetime, const string& market, const string& code, price_t price, double num, + price_t stoploss, price_t goalPrice, SystemPart from) noexcept; /** * 执行卖出操作 @@ -61,34 +82,65 @@ public: * @param code 证券代码 * @param price 卖出价格 * @param num 卖出数量 - * @return 操作执行的时刻。实盘时,应返回委托单时间或服务器交易时间。 + * @param stoploss 新的预期止损价 + * @param goalPrice 新的预期目标价位 + * @param from 系统部件来源 */ - Datetime sell(Datetime datetime, const string& market, const string& code, price_t price, - double num); + void sell(Datetime datetime, const string& market, const string& code, price_t price, + double num, price_t stoploss, price_t goalPrice, SystemPart from) noexcept; /** - * 执行实际买入操作 + * 获取当前资产信息 + * @return string json字符串 + *
+     * 接口规范:
+     * {
+     *   "datetime": "2001-01-01 18:00:00.12345",
+     *   "cash": 0.0,
+     *   "positions": [
+     *       {"market": "SZ", "code": "000001", "number": 100.0, "stoploss": 0.0, "goal_price": 0.0,
+     *        "cost_price": 0.0},
+     *       {"market": "SH", "code": "600001", "number": 100.0, "stoploss": 0.0, "goal_price": 0.0,
+     *        "cost_price": 0.0},
+     *    ]
+     * }
+     *
+     * 说明:
+     * cash: 当前可用资金
+     * number 应该为:现有持仓 + 正在买入 - 正在卖出
+     * cost_price: 每股买入成本价
+     * 
+ */ + string getAssetInfo() noexcept; + + /** + * 子类实现接口,执行实际买入操作 * @param datetime 策略指示时间 * @param market 市场标识 * @param code 证券代码 * @param price 买入价格 * @param num 买入数量 - * @return 操作执行的时刻。实盘时,应返回委托单时间或服务器交易时间。 + * @param stoploss 预期的止损价 + * @param goalPrice 预期的目标价位 + * @param from 系统部件来源 */ - virtual Datetime _buy(Datetime datetime, const string& market, const string& code, - price_t price, double num) = 0; + virtual void _buy(Datetime datetime, const string& market, const string& code, price_t price, + double num, price_t stoploss, price_t goalPrice, SystemPart from) = 0; /** - * 执行实际卖出操作 + * 子类实现接口,执行实际卖出操作 * @param datetime 策略指示时间 * @param market 市场标识 * @param code 证券代码 * @param price 卖出价格 * @param num 卖出数量 - * @return 操作执行的时刻。实盘时,应返回委托单时间或服务器交易时间。 */ - virtual Datetime _sell(Datetime datetime, const string& market, const string& code, - price_t price, double num) = 0; + virtual void _sell(Datetime datetime, const string& market, const string& code, price_t price, + double num, price_t stoploss, price_t goalPrice, SystemPart from) = 0; + + virtual string _getAssetInfo() { + return string(); + } protected: string m_name; diff --git a/hikyuu_cpp/hikyuu/trade_manage/PositionRecord.cpp b/hikyuu_cpp/hikyuu/trade_manage/PositionRecord.cpp index fa41d3e5..8296f048 100644 --- a/hikyuu_cpp/hikyuu/trade_manage/PositionRecord.cpp +++ b/hikyuu_cpp/hikyuu/trade_manage/PositionRecord.cpp @@ -65,7 +65,7 @@ HKU_API std::ostream& operator<<(std::ostream& os, const PositionRecord& record) return os; } -string PositionRecord::toString() const { +string PositionRecord::str() const { int precision = 2; std::string market(""), code(""), name(""); if (!stock.isNull()) { diff --git a/hikyuu_cpp/hikyuu/trade_manage/PositionRecord.h b/hikyuu_cpp/hikyuu/trade_manage/PositionRecord.h index ea4247df..67c8c804 100644 --- a/hikyuu_cpp/hikyuu/trade_manage/PositionRecord.h +++ b/hikyuu_cpp/hikyuu/trade_manage/PositionRecord.h @@ -26,19 +26,19 @@ public: price_t buyMoney, price_t totalCost, price_t totalRisk, price_t sellMoney); /** 仅用于python的__str__ */ - string toString() const; + string str() const; - Stock stock; ///< 交易对象 - Datetime takeDatetime; ///< 初次建仓日期 - Datetime cleanDatetime; ///< 平仓日期,当前持仓记录中为Null() - double number; ///< 当前持仓数量 - price_t stoploss; ///< 当前止损价 - price_t goalPrice; ///< 当前的目标价格 - double totalNumber; ///< 累计持仓数量 - price_t buyMoney; ///< 累计买入资金 - price_t totalCost; ///< 累计交易总成本 - price_t totalRisk; ///< 累计交易风险 = 各次 (买入价格-止损)*买入数量, 不包含交易成本 - price_t sellMoney; ///< 累计卖出资金 + Stock stock; ///< 交易对象 + Datetime takeDatetime; ///< 初次建仓日期 + Datetime cleanDatetime; ///< 平仓日期,当前持仓记录中为Null() + double number{0.0}; ///< 当前持仓数量 + price_t stoploss{0.0}; ///< 当前止损价 + price_t goalPrice{0.0}; ///< 当前的目标价格 + double totalNumber{0.0}; ///< 累计持仓数量 + price_t buyMoney{0.0}; ///< 累计买入资金 + price_t totalCost{0.0}; ///< 累计交易总成本 + price_t totalRisk{0.0}; ///< 累计交易风险 = 各次 (买入价格-止损)*买入数量, 不包含交易成本 + price_t sellMoney{0.0}; ///< 累计卖出资金 //=================== // 序列化支持 diff --git a/hikyuu_cpp/hikyuu/trade_manage/TradeManager.cpp b/hikyuu_cpp/hikyuu/trade_manage/TradeManager.cpp index bc2212b2..f1e1be52 100644 --- a/hikyuu_cpp/hikyuu/trade_manage/TradeManager.cpp +++ b/hikyuu_cpp/hikyuu/trade_manage/TradeManager.cpp @@ -865,14 +865,14 @@ TradeRecord TradeManager::buy(const Datetime& datetime, const Stock& stock, pric roundEx(position.totalRisk + (realPrice - stoploss) * number * stock.unit(), precision); } - if (result.datetime > m_broker_last_datetime) { + if (datetime > m_broker_last_datetime) { list::const_iterator broker_iter = m_broker_list.begin(); - Datetime realtime, nulltime; for (; broker_iter != m_broker_list.end(); ++broker_iter) { - realtime = - (*broker_iter)->buy(datetime, stock.market(), stock.code(), realPrice, number); - if (realtime != nulltime && realtime > m_broker_last_datetime) { - m_broker_last_datetime = realtime; + (*broker_iter) + ->buy(datetime, stock.market(), stock.code(), realPrice, number, stoploss, goalPrice, + from); + if (datetime > m_broker_last_datetime) { + m_broker_last_datetime = datetime; } } } @@ -955,14 +955,14 @@ TradeRecord TradeManager::sell(const Datetime& datetime, const Stock& stock, pri returnCash(datetime, m_borrow_cash < m_cash ? m_borrow_cash : m_cash); } - if (result.datetime > m_broker_last_datetime) { + if (datetime > m_broker_last_datetime) { list::const_iterator broker_iter = m_broker_list.begin(); - Datetime realtime, nulltime; for (; broker_iter != m_broker_list.end(); ++broker_iter) { - realtime = - (*broker_iter)->sell(datetime, stock.market(), stock.code(), realPrice, real_number); - if (realtime != nulltime && realtime > m_broker_last_datetime) { - m_broker_last_datetime = realtime; + (*broker_iter) + ->sell(datetime, stock.market(), stock.code(), realPrice, real_number, stoploss, + goalPrice, from); + if (datetime > m_broker_last_datetime) { + m_broker_last_datetime = datetime; } } } @@ -1070,14 +1070,14 @@ TradeRecord TradeManager::sellShort(const Datetime& datetime, const Stock& stock position.sellMoney = roundEx(position.sellMoney + money, precision); } - if (result.datetime > m_broker_last_datetime) { + if (datetime > m_broker_last_datetime) { list::const_iterator broker_iter = m_broker_list.begin(); - Datetime realtime, nulltime; for (; broker_iter != m_broker_list.end(); ++broker_iter) { - realtime = - (*broker_iter)->sell(datetime, stock.market(), stock.code(), realPrice, number); - if (realtime != nulltime && realtime > m_broker_last_datetime) { - m_broker_last_datetime = realtime; + (*broker_iter) + ->sell(datetime, stock.market(), stock.code(), realPrice, number, stoploss, goalPrice, + from); + if (datetime > m_broker_last_datetime) { + m_broker_last_datetime = datetime; } } } @@ -1144,14 +1144,14 @@ TradeRecord TradeManager::buyShort(const Datetime& datetime, const Stock& stock, m_short_position.erase(stock.id()); } - if (result.datetime > m_broker_last_datetime) { + if (datetime > m_broker_last_datetime) { list::const_iterator broker_iter = m_broker_list.begin(); - Datetime realtime, nulltime; for (; broker_iter != m_broker_list.end(); ++broker_iter) { - realtime = - (*broker_iter)->buy(datetime, stock.market(), stock.code(), realPrice, number); - if (realtime != nulltime && realtime > m_broker_last_datetime) { - m_broker_last_datetime = realtime; + (*broker_iter) + ->buy(datetime, stock.market(), stock.code(), realPrice, number, stoploss, goalPrice, + from); + if (datetime > m_broker_last_datetime) { + m_broker_last_datetime = datetime; } } } @@ -1709,6 +1709,26 @@ void TradeManager::tocsv(const string& path) { file.close(); } +bool TradeManager::addPosition(const PositionRecord& pr) { + HKU_ERROR_IF_RETURN(pr.stock.isNull(), false, "Invalid postion record! stock is null!"); + HKU_ERROR_IF_RETURN(pr.cleanDatetime != Null(), false, + "Position cleanDatetime({}) must be Null!", pr.cleanDatetime); + HKU_ERROR_IF_RETURN(pr.takeDatetime < initDatetime(), false, + "Poistion takeDatetime({}) > initDatetime({})", pr.takeDatetime, + initDatetime()); + HKU_ERROR_IF_RETURN(!m_trade_list.empty(), false, "Exist trade list!"); + + auto iter = m_position.find(pr.stock.id()); + HKU_ERROR_IF_RETURN(iter != m_position.end(), false, "The stock({}) has position!", + pr.stock.market_code()); + + m_position[pr.stock.id()] = pr; + if (pr.takeDatetime > m_init_datetime) { + m_init_datetime = pr.takeDatetime; + } + return true; +} + bool TradeManager::addTradeRecord(const TradeRecord& tr) { HKU_IF_RETURN(BUSINESS_INIT == tr.business, _add_init_tr(tr)); HKU_ERROR_IF_RETURN(tr.datetime < lastDatetime(), false, diff --git a/hikyuu_cpp/hikyuu/trade_manage/TradeManager.h b/hikyuu_cpp/hikyuu/trade_manage/TradeManager.h index 8700d69a..2e4d5bd1 100644 --- a/hikyuu_cpp/hikyuu/trade_manage/TradeManager.h +++ b/hikyuu_cpp/hikyuu/trade_manage/TradeManager.h @@ -344,6 +344,15 @@ public: */ virtual bool addTradeRecord(const TradeRecord& tr) override; + /** + * 直接加入持仓记录 + * @note 特殊用途构建初始持仓,可能引起混乱 + * @param pr 持仓记录 + * @return true 成功 + * @return false 失败 + */ + virtual bool addPosition(const PositionRecord& pr) override; + /** 字符串输出 */ virtual string str() const override; diff --git a/hikyuu_cpp/hikyuu/trade_manage/TradeManagerBase.h b/hikyuu_cpp/hikyuu/trade_manage/TradeManagerBase.h index 46f7584d..0ab8d5a1 100644 --- a/hikyuu_cpp/hikyuu/trade_manage/TradeManagerBase.h +++ b/hikyuu_cpp/hikyuu/trade_manage/TradeManagerBase.h @@ -668,6 +668,17 @@ public: return false; } + /** + * 直接加入持仓记录 + * @param pr 持仓记录 + * @return true 成功 + * @return false 失败 + */ + virtual bool addPosition(const PositionRecord& pr) { + HKU_WARN("The subclass does not implement this method"); + return false; + } + /** 字符串输出 */ virtual string str() const { HKU_WARN("The subclass does not implement this method"); @@ -682,6 +693,14 @@ public: HKU_WARN("The subclass does not implement this method"); } + /** + * 从订单代理实例同步当前账户资产信息(包含资金、持仓等) + * @param broker 订单代理实例 + */ + virtual void fetchAssetInfoFromBroker(const OrderBrokerPtr& broker) { + HKU_WARN("The subclass does not implement this method"); + } + protected: string m_name; // 账户名称 TradeCostPtr m_costfunc; // 成本算法 diff --git a/hikyuu_cpp/hikyuu/trade_manage/imp/FixedATradeCost.cpp b/hikyuu_cpp/hikyuu/trade_manage/imp/FixedATradeCost.cpp index 0d874ade..f4c76754 100644 --- a/hikyuu_cpp/hikyuu/trade_manage/imp/FixedATradeCost.cpp +++ b/hikyuu_cpp/hikyuu/trade_manage/imp/FixedATradeCost.cpp @@ -6,8 +6,8 @@ */ #include "FixedATradeCost.h" +#include "hikyuu/utilities/Log.h" #include "../../StockTypeInfo.h" -#include "../../Log.h" #if HKU_SUPPORT_SERIALIZATION BOOST_CLASS_EXPORT(hku::FixedATradeCost) diff --git a/hikyuu_cpp/hikyuu/trade_sys/system/System.cpp b/hikyuu_cpp/hikyuu/trade_sys/system/System.cpp index bef25c41..df1bc232 100644 --- a/hikyuu_cpp/hikyuu/trade_sys/system/System.cpp +++ b/hikyuu_cpp/hikyuu/trade_sys/system/System.cpp @@ -382,8 +382,15 @@ void System::run(const KData& kdata, bool reset, bool resetAll) { size_t total = kdata.size(); auto const* ks = kdata.data(); auto const* src_ks = m_src_kdata.data(); + + // 适应 strategy 模式下运行时同步资产信息可能造成的偏差 + Datetime tm_init_datetime = m_tm->initDatetime(); + if (KQuery::getKTypeInMin(kdata.getQuery().kType()) >= 1440) { + tm_init_datetime = tm_init_datetime.startOfDay(); + } + for (size_t i = 0; i < total; ++i) { - if (ks[i].datetime >= m_tm->initDatetime()) { + if (ks[i].datetime >= tm_init_datetime) { auto tr = _runMoment(ks[i], src_ks[i]); if (trace) { HKU_INFO_IF(!tr.isNull(), "{}", tr); diff --git a/hikyuu_cpp/hikyuu/utilities/FilterNode.h b/hikyuu_cpp/hikyuu/utilities/FilterNode.h new file mode 100644 index 00000000..8df20f36 --- /dev/null +++ b/hikyuu_cpp/hikyuu/utilities/FilterNode.h @@ -0,0 +1,267 @@ +/* + * Copyright (c) 2023 hikyuu.org + * + * Created on: 2023-01-13 + * Author: fasiondog + */ + +#pragma once + +#include +#include +#include +#include +#include "thread/ThreadPool.h" +#include "any_to_string.h" +#include "Log.h" + +namespace hku { + +/** + * @brief 过滤节点 + */ +class FilterNode { +public: + FilterNode() = default; + FilterNode(const FilterNode&) = default; + virtual ~FilterNode() = default; + + /** + * @brief 构造函数 + * @param exclusive 是否排他。为 true 时,只执行第一个遇到的满足过滤条件的子节点 + */ + explicit FilterNode(bool exclusive) : m_exclusive(exclusive) {} + + FilterNode(FilterNode&& rv) + : m_value(std::move(rv.m_value)), + m_children(std::move(rv.m_children)), + m_exclusive(rv.m_exclusive) {} + + FilterNode& operator=(const FilterNode& rv) { + if (this == &rv) + return *this; + m_value = rv.m_value; + m_children = rv.m_children; + m_exclusive = rv.m_exclusive; + return *this; + } + + FilterNode& operator=(FilterNode&& rv) { + if (this == &rv) + return *this; + m_value = std::move(rv.m_value); + m_children = std::move(rv.m_children); + m_exclusive = rv.m_exclusive; + return *this; + } + + using ptr_t = std::shared_ptr; + + ptr_t addChild(const ptr_t& child) { + HKU_CHECK(child, "Invalid input child! child is null!"); + m_children.push_front(child); + return child; + } + + bool exclusive() const { + return m_exclusive; + } + + void exclusive(bool exclusive) { + m_exclusive = exclusive; + } + + using const_iterator = std::forward_list::const_iterator; + using iterator = std::forward_list::iterator; + const_iterator cbegin() const { + return m_children.cbegin(); + } + + const_iterator cend() const { + return m_children.cend(); + } + + iterator begin() { + return m_children.begin(); + } + + iterator end() { + return m_children.end(); + } + + bool run(const any_t& data) noexcept { + if (_filter(data)) { + _process(data); + for (auto& node : m_children) { + if (node->run(data) && m_exclusive) { + return true; + } + } + return true; + } + return false; + } + + virtual bool filter(const any_t& data) { + return true; + } + + virtual void process(const any_t& data) {} + + template + ValueT value() const { + return any_cast(m_value); + } + + template + void value(const ValueT& value) { + m_value = value; + } + + bool has_value() const { +#if !HKU_OS_IOS && CPP_STANDARD >= CPP_STANDARD_17 + return m_value.has_value(); +#else + return !m_value.empty(); +#endif + } + +private: + bool _filter(const any_t& data) noexcept { + try { + return filter(data); + } catch (const std::exception& e) { + HKU_WARN("Node filter exist error! {}", e.what()); + } catch (...) { + HKU_WARN("Node filter exist unknown error!"); + } + return false; + } + + void _process(const any_t& data) noexcept { + try { + process(data); + } catch (const std::exception& e) { + HKU_WARN("Node process exist error! {}", e.what()); + } catch (...) { + HKU_WARN("Node process exist unknown error!"); + } + } + +protected: + any_t m_value; + +private: + std::forward_list m_children; + bool m_exclusive = false; +}; + +template <> +inline const any_t& FilterNode::value() const { + return m_value; +} + +typedef std::shared_ptr FilterNodePtr; + +/** + * @brief 绑定过滤节点,通过 std::function 绑定自定义的 filter 和 process 处理函数 + */ +class BindFilterNode : public FilterNode { +public: + BindFilterNode() = default; + virtual ~BindFilterNode() = default; + + using filter_func = std::function; + using process_func = std::function; + + explicit BindFilterNode(const process_func& process) : FilterNode(false), m_process(process) {} + explicit BindFilterNode(process_func&& process) + : FilterNode(false), m_process(std::move(process)) {} + + BindFilterNode(const filter_func& filter, const process_func& process, bool exclusive = false) + : FilterNode(exclusive), m_filter(filter), m_process(process) {} + + BindFilterNode(filter_func&& filter, process_func&& process, bool exclusive = false) + : FilterNode(exclusive), m_filter(std::move(filter)), m_process(std::move(process)) {} + + virtual bool filter(const any_t& data) { + return m_filter ? m_filter(this, data) : true; + } + + virtual void process(const any_t& data) { + if (m_process) { + m_process(this, data); + } + } + +private: + filter_func m_filter; + process_func m_process; +}; + +/** + * @brief 异步串行事件处理器 + * @tparam EventT + */ +template +class AsyncSerialEventProcessor { +public: + /** + * @brief 构造函数 + * @param quit_wait 退出时等待所有任务完成 + */ + explicit AsyncSerialEventProcessor(bool quit_wait = true) : m_quit_wait(quit_wait) { + m_tg = std::unique_ptr(new ThreadPool(1)); + } + + /** 析构函数 */ + virtual ~AsyncSerialEventProcessor() { + if (m_quit_wait) { + m_tg->join(); + } else { + m_tg->stop(); + } + } + + /** + * @brief 添加事件处理节点 + * + * @param event 事件 + * @param action 对应的处理节点 + * @return 返回加入的节点 + */ + FilterNodePtr addAction(const EventT& event, const FilterNodePtr& action) { + HKU_CHECK(action, "Input action is null!"); + std::lock_guard lock(m_mutex); + auto iter = m_trees.find(event); + if (iter != m_trees.end()) { + iter->second->addChild(action); + } else { + m_trees[event] = action; + } + return action; + } + + /** + * @brief 分派事件消息 + * + * @param event 事件 + * @param data 事件附加信息 + */ + void dispatch(const EventT& event, const any_t& data) { + m_tg->submit([=] { + auto iter = m_trees.find(event); + HKU_WARN_IF_RETURN(iter == m_trees.end(), void(), + "There is no matching handling method for the event({})!", event); + iter->second->run(data); + }); + } + +private: + mutable std::mutex m_mutex; + std::unordered_map m_trees; + std::unique_ptr m_tg; + bool m_quit_wait = true; +}; + +} // namespace hku \ No newline at end of file diff --git a/hikyuu_cpp/hikyuu/utilities/LRUCache11.h b/hikyuu_cpp/hikyuu/utilities/LRUCache11.h new file mode 100644 index 00000000..e7832dcb --- /dev/null +++ b/hikyuu_cpp/hikyuu/utilities/LRUCache11.h @@ -0,0 +1,230 @@ +/* + * LRUCache11 - a templated C++11 based LRU cache class that allows + * specification of + * key, value and optionally the map container type (defaults to + * std::unordered_map) + * By using the std::unordered_map and a linked list of keys it allows O(1) insert, delete + * and + * refresh operations. + * + * This is a header-only library and all you need is the LRUCache11.hpp file + * + * Github: https://github.com/mohaps/lrucache11 + * + * This is a follow-up to the LRUCache project - + * https://github.com/mohaps/lrucache + * + * Copyright (c) 2012-22 SAURAV MOHAPATRA + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#pragma once +#include +#include +#include +#include +#include +#include +#include + +namespace lru11 { +/* + * a noop lockable concept that can be used in place of std::mutex + */ +class NullLock { +public: + void lock() {} + void unlock() {} + bool try_lock() { + return true; + } +}; + +/** + * error raised when a key not in cache is passed to get() + */ +class KeyNotFound : public std::invalid_argument { +public: + KeyNotFound() : std::invalid_argument("key_not_found") {} +}; + +template +struct KeyValuePair { +public: + K key; + V value; + + KeyValuePair(K k, V v) : key(std::move(k)), value(std::move(v)) {} +}; + +/** + * The LRU Cache class templated by + * Key - key type + * Value - value type + * MapType - an associative container like std::unordered_map + * LockType - a lock type derived from the Lock class (default: + *NullLock = no synchronization) + * + * The default NullLock based template is not thread-safe, however passing + *Lock=std::mutex will make it + * thread-safe + */ +template >::iterator>> +class Cache { +public: + typedef KeyValuePair node_type; + typedef std::list> list_type; + typedef Map map_type; + typedef Lock lock_type; + using Guard = std::lock_guard; + /** + * the maxSize is the soft limit of keys and (maxSize + elasticity) is the + * hard limit + * the cache is allowed to grow till (maxSize + elasticity) and is pruned back + * to maxSize keys + * set maxSize = 0 for an unbounded cache (but in that case, you're better off + * using a std::unordered_map + * directly anyway! :) + */ + explicit Cache(size_t maxSize = 64, size_t elasticity = 10) + : maxSize_(maxSize), elasticity_(elasticity) {} + virtual ~Cache() = default; + size_t size() const { + Guard g(lock_); + return cache_.size(); + } + bool empty() const { + Guard g(lock_); + return cache_.empty(); + } + void clear() { + Guard g(lock_); + cache_.clear(); + keys_.clear(); + } + void insert(const Key& k, const Value& v) { + Guard g(lock_); + const auto iter = cache_.find(k); + if (iter != cache_.end()) { + iter->second->value = v; + keys_.splice(keys_.begin(), keys_, iter->second); + return; + } + + keys_.emplace_front(k, v); + cache_[k] = keys_.begin(); + prune(); + } + void insert(const Key& k, Value&& v) { + Guard g(lock_); + const auto iter = cache_.find(k); + if (iter != cache_.end()) { + iter->second->value = std::move(v); + keys_.splice(keys_.begin(), keys_, iter->second); + return; + } + + keys_.emplace_front(k, std::move(v)); + cache_[k] = keys_.begin(); + prune(); + } + bool tryGet(const Key& kIn, Value& vOut) { + Guard g(lock_); + const auto iter = cache_.find(kIn); + if (iter == cache_.end()) { + return false; + } + keys_.splice(keys_.begin(), keys_, iter->second); + vOut = iter->second->value; + return true; + } + /** + * The const reference returned here is only + * guaranteed to be valid till the next insert/delete + * 修改为非常量引用,以便修改。但请注意这是危险操作! + */ + Value& get(const Key& k) { + Guard g(lock_); + const auto iter = cache_.find(k); + if (iter == cache_.end()) { + throw KeyNotFound(); + } + keys_.splice(keys_.begin(), keys_, iter->second); + return iter->second->value; + } + /** + * returns a copy of the stored object (if found) + */ + Value getCopy(const Key& k) { + return get(k); + } + bool remove(const Key& k) { + Guard g(lock_); + auto iter = cache_.find(k); + if (iter == cache_.end()) { + return false; + } + keys_.erase(iter->second); + cache_.erase(iter); + return true; + } + bool contains(const Key& k) const { + Guard g(lock_); + return cache_.find(k) != cache_.end(); + } + + size_t getMaxSize() const { + return maxSize_; + } + size_t getElasticity() const { + return elasticity_; + } + size_t getMaxAllowedSize() const { + return maxSize_ + elasticity_; + } + template + void cwalk(F& f) const { + Guard g(lock_); + std::for_each(keys_.begin(), keys_.end(), f); + } + +protected: + size_t prune() { + size_t maxAllowed = maxSize_ + elasticity_; + if (maxSize_ == 0 || cache_.size() < maxAllowed) { + return 0; + } + size_t count = 0; + while (cache_.size() > maxSize_) { + cache_.erase(keys_.back().key); + keys_.pop_back(); + ++count; + } + return count; + } + +private: + // Disallow copying. + Cache(const Cache&) = delete; + Cache& operator=(const Cache&) = delete; + + mutable Lock lock_; + Map cache_; + list_type keys_; + size_t maxSize_; + size_t elasticity_; +}; + +} // namespace lru11 \ No newline at end of file diff --git a/hikyuu_cpp/hikyuu/utilities/Log.cpp b/hikyuu_cpp/hikyuu/utilities/Log.cpp new file mode 100644 index 00000000..4cce287a --- /dev/null +++ b/hikyuu_cpp/hikyuu/utilities/Log.cpp @@ -0,0 +1,86 @@ +/* + * Log.cpp + * + * Created on: 2013-2-1 + * Author: fasiondog + */ + +#include +#include "hikyuu/GlobalInitializer.h" +#include "os.h" +#include "Log.h" + +// 使用 stdout_color 将无法将日志输出重定向至 python +#include +#include +#include "spdlog/sinks/ostream_sink.h" +#include "spdlog/sinks/rotating_file_sink.h" + +#if HKU_USE_SPDLOG_ASYNC_LOGGER +#include +#endif /* HKU_USE_SPDLOG_ASYNC_LOGGER */ + +namespace hku { + +static LOG_LEVEL g_log_level = LOG_LEVEL::LOG_TRACE; + +std::string g_unknown_error_msg{"Unknown error!"}; + +LOG_LEVEL get_log_level() { + return g_log_level; +} + +void set_log_level(LOG_LEVEL level) { + g_log_level = level; + getHikyuuLogger()->set_level((spdlog::level::level_enum)level); +} + +std::shared_ptr getHikyuuLogger() { + auto logger = spdlog::get("hikyuu"); + return logger ? logger : spdlog::default_logger(); +} + +void HKU_UTILS_API initLogger(bool not_use_color, const std::string& filename) { + std::string logname("hikyuu"); + spdlog::drop(logname); + std::shared_ptr logger = spdlog::get(logname); + if (logger) { + spdlog::drop(logname); + } + + spdlog::sink_ptr stdout_sink; + if (not_use_color) { + stdout_sink = std::make_shared(std::cout, true); + } else { + stdout_sink = std::make_shared(); + } + stdout_sink->set_level(spdlog::level::trace); + + std::string logfile = filename.empty() ? "./hikyuu.log" : filename; + auto rotating_sink = + std::make_shared(logfile, 1024 * 1024 * 10, 3); + rotating_sink->set_level(spdlog::level::warn); + + std::vector sinks{stdout_sink}; + if (rotating_sink) { + sinks.emplace_back(rotating_sink); + } + +#if HKU_USE_SPDLOG_ASYNC_LOGGER + spdlog::init_thread_pool(8192, 1); + logger = std::make_shared(logname, sinks.begin(), sinks.end(), + spdlog::thread_pool(), + spdlog::async_overflow_policy::block); +#else + logger = std::make_shared(logname, sinks.begin(), sinks.end()); +#endif + + logger->set_level(spdlog::level::trace); + logger->flush_on(spdlog::level::trace); + logger->set_pattern("%Y-%m-%d %H:%M:%S.%e [%^HKU-%L%$] - %v (%s:%#)"); + // logger->set_pattern("%^%Y-%m-%d %H:%M:%S.%e [HKU-%L] - %v (%s:%#)%$"); + // spdlog::register_logger(logger); + spdlog::set_default_logger(logger); +} + +} // namespace hku diff --git a/hikyuu_cpp/hikyuu/Log.h b/hikyuu_cpp/hikyuu/utilities/Log.h similarity index 75% rename from hikyuu_cpp/hikyuu/Log.h rename to hikyuu_cpp/hikyuu/utilities/Log.h index e3989287..598d0353 100644 --- a/hikyuu_cpp/hikyuu/Log.h +++ b/hikyuu_cpp/hikyuu/utilities/Log.h @@ -16,13 +16,19 @@ #include "config.h" #include "exception.h" +#ifndef HKU_LOG_ACTIVE_LEVEL +#define HKU_LOG_ACTIVE_LEVEL 0 +#endif + // clang-format off -#if USE_SPDLOG_LOGGER - #include - #include - #if HKU_USE_SPDLOG_ASYNC_LOGGER - #include "spdlog/async.h" - #endif +#ifndef SPDLOG_ACTIVE_LEVEL +#define SPDLOG_ACTIVE_LEVEL HKU_LOG_ACTIVE_LEVEL +#endif + +#include +#include +#if HKU_USE_SPDLOG_ASYNC_LOGGER + #include "spdlog/async.h" #endif // clang-format on @@ -30,30 +36,24 @@ #include #include -#ifdef HKU_ENABLE_STACK_TRACE +#ifndef HKU_ENABLE_STACK_TRACE +#define HKU_ENABLE_STACK_TRACE 0 +#endif + +#if HKU_ENABLE_STACK_TRACE #include #endif -#ifndef HKU_API -#define HKU_API +#ifndef HKU_UTILS_API +#define HKU_UTILS_API #endif namespace hku { -/** - * @ingroup Utilities - * @addtogroup logging Logging tools 日志工具 - * @details 打印等级: - * TRACE < DEBUG < INFO < WARN < ERROR < FATAL - * @{ - */ - -bool HKU_API isLogInMainThread(); - /********************************************** * Use SPDLOG for logging *********************************************/ -#if USE_SPDLOG_LOGGER + /** 日志级别 */ enum LOG_LEVEL { LOG_TRACE = SPDLOG_LEVEL_TRACE, ///< 跟踪 @@ -65,19 +65,27 @@ enum LOG_LEVEL { LOG_OFF = SPDLOG_LEVEL_OFF, ///< 关闭日志打印 }; +/** + * 初始化 logger + * @param not_use_color 不使用彩色输出 + * @param filename 日志文件名,为空时默认为当前目录下 "./hikyuu.log",需自行保存存放目录存在且可写入 + */ +void HKU_UTILS_API initLogger(bool not_use_color = false, + const std::string& filename = std::string()); + /** * 获取当前日志级别 * @return */ -LOG_LEVEL HKU_API get_log_level(); +LOG_LEVEL HKU_UTILS_API get_log_level(); /** * 设置日志级别 * @param level 指定的日志级别 */ -void HKU_API set_log_level(LOG_LEVEL level); +void HKU_UTILS_API set_log_level(LOG_LEVEL level); -std::shared_ptr HKU_API getHikyuuLogger(); +std::shared_ptr HKU_UTILS_API getHikyuuLogger(); #define HKU_TRACE(...) SPDLOG_LOGGER_TRACE(hku::getHikyuuLogger(), __VA_ARGS__) #define HKU_DEBUG(...) SPDLOG_LOGGER_DEBUG(hku::getHikyuuLogger(), __VA_ARGS__) @@ -86,76 +94,6 @@ std::shared_ptr HKU_API getHikyuuLogger(); #define HKU_ERROR(...) SPDLOG_LOGGER_ERROR(hku::getHikyuuLogger(), __VA_ARGS__) #define HKU_FATAL(...) SPDLOG_LOGGER_CRITICAL(hku::getHikyuuLogger(), __VA_ARGS__) -void initLogger(bool inJupyter = false); - -#else -enum LOG_LEVEL { - LOG_TRACE = 0, - LOG_DEBUG = 1, - LOG_INFO = 2, - LOG_WARN = 3, - LOG_ERROR = 4, - LOG_FATAL = 5, - LOG_OFF = 6, -}; - -LOG_LEVEL HKU_API get_log_level(); -void HKU_API set_log_level(LOG_LEVEL level); -void initLogger(bool inJupyter = false); - -/** 获取系统当前时间,精确到毫秒,如:2001-01-02 13:01:02.001 */ -std::string HKU_API getLocalTime(); - -#if LOG_ACTIVE_LEVEL <= 0 -#define HKU_TRACE(...) \ - fmt::print("[{}] [HKU-T] - {} ({}:{})\n", getLocalTime(), fmt::format(__VA_ARGS__), __FILE__, \ - __LINE__); -#else -#define HKU_TRACE(...) -#endif - -#if LOG_ACTIVE_LEVEL <= 1 -#define HKU_DEBUG(...) \ - fmt::print("[{}] [HKU-D] - {} ({}:{})\n", getLocalTime(), fmt::format(__VA_ARGS__), __FILE__, \ - __LINE__); -#else -#define HKU_DEBUG(...) -#endif - -#if LOG_ACTIVE_LEVEL <= 2 -#define HKU_INFO(...) \ - fmt::print("[{}] [HKU-I] - {} ({}:{})\n", getLocalTime(), fmt::format(__VA_ARGS__), __FILE__, \ - __LINE__); -#else -#define HKU_INFO(...) -#endif - -#if LOG_ACTIVE_LEVEL <= 3 -#define HKU_WARN(...) \ - fmt::print("[{}] [HKU-W] - {} ({}:{})\n", getLocalTime(), fmt::format(__VA_ARGS__), __FILE__, \ - __LINE__); -#else -#define HKU_WARN(...) -#endif - -#if LOG_ACTIVE_LEVEL <= 4 -#define HKU_ERROR(...) \ - fmt::print("[{}] [HKU-E] - {} ({}:{})\n", getLocalTime(), fmt::format(__VA_ARGS__), __FILE__, \ - __LINE__); -#else -#define HKU_ERROR(...) -#endif - -#if LOG_ACTIVE_LEVEL <= 5 -#define HKU_FATAL(...) \ - fmt::print("[{}] [HKU-F] - {} ({}:{})\n", getLocalTime(), fmt::format(__VA_ARGS__), __FILE__, \ - __LINE__); -#else -#define HKU_FATAL(...) -#endif - -#endif /* USE_SPDLOG_LOGGER */ - /////////////////////////////////////////////////////////////////////////////// // // clang/gcc 下使用 __PRETTY_FUNCTION__ 会包含函数参数,可以在编译时指定 @@ -166,7 +104,7 @@ std::string HKU_API getLocalTime(); #define HKU_FUNCTION __FUNCTION__ #endif -#ifndef HKU_ENABLE_STACK_TRACE +#if !HKU_ENABLE_STACK_TRACE /** * 若表达式为 false,将抛出 hku::exception 异常, 并附带传入信息 * @note 用于外部入参及结果检查 @@ -212,23 +150,9 @@ std::string HKU_API getLocalTime(); __FILE__, __LINE__)); \ } \ } while (0) -#endif // #ifndef HKU_ENABLE_STACK_TRACE +#endif // #if !HKU_ENABLE_STACK_TRACE -#if HKU_DEBUG_MODE -#define HKU_ASSERT_DEBUG(expr) -#else -/** 仅在 debug 模式下生效 */ -#define HKU_ASSERT_DEBUG(expr) \ - do { \ - if (!(expr)) { \ - std::string err_msg(fmt::format("HKU_ASSERT({})", #expr)); \ - throw hku::exception( \ - fmt::format("{} [{}] ({}:{})", err_msg, HKU_FUNCTION, __FILE__, __LINE__)); \ - } \ - } while (0) - -#endif /* #if HKU_DEBUG_MODE */ -#ifdef HKU_ENABLE_STACK_TRACE +#if HKU_ENABLE_STACK_TRACE /** * 若表达式为 false,将抛出 hku::exception 异常 * @note 仅用于内部入参检查,编译时可通过 HKU_DISABLE_ASSERT 宏关闭 @@ -253,9 +177,9 @@ std::string HKU_API getLocalTime(); } \ } while (0) -#endif // #ifndef HKU_ENABLE_STACK_TRACE +#endif // #if HKU_ENABLE_STACK_TRACE -#ifndef HKU_ENABLE_STACK_TRACE +#if !HKU_ENABLE_STACK_TRACE /** 抛出 hku::exception 及传入信息 */ #define HKU_THROW(...) \ do { \ @@ -286,7 +210,7 @@ std::string HKU_API getLocalTime(); throw except( \ fmt::format("EXCEPTION: {} [{}] ({}:{})", errmsg, HKU_FUNCTION, __FILE__, __LINE__)); \ } while (0) -#endif // #ifndef HKU_ENABLE_STACK_TRACE +#endif // #if !HKU_ENABLE_STACK_TRACE /** * 满足指定条件时,打印 TRACE 信息 @@ -420,12 +344,66 @@ std::string HKU_API getLocalTime(); /** 用于 catch (...) 中打印,减少编译后代码大小 */ extern std::string g_unknown_error_msg; +#define HKU_THROW_UNKNOWN HKU_THROW(g_unknown_error_msg); #define HKU_TRACE_UNKNOWN HKU_TRACE(g_unknown_error_msg) #define HKU_DEBUG_UNKNOWN HKU_DEBUG(g_unknown_error_msg) #define HKU_INFO_UNKNOWN HKU_INFO(g_unknown_error_msg) #define HKU_ERROR_UNKNOWN HKU_ERROR(g_unknown_error_msg) #define HKU_FATAL_UNKNOWN HKU_FATAL(g_unknown_error_msg) +#if CPP_STANDARD >= CPP_STANDARD_17 +#define CLASS_LOGGER_IMP(cls) \ +protected: \ + inline static const char* ms_logger = #cls; +#else +#define CLASS_LOGGER_IMP(cls) \ +protected: \ + const char* ms_logger = #cls; +#endif + +#define CLS_TRACE(...) HKU_TRACE(fmt::format("[{}] {}", ms_logger, fmt::format(__VA_ARGS__))) +#define CLS_DEBUG(...) HKU_DEBUG(fmt::format("[{}] {}", ms_logger, fmt::format(__VA_ARGS__))) +#define CLS_INFO(...) HKU_INFO(fmt::format("[{}] {}", ms_logger, fmt::format(__VA_ARGS__))) +#define CLS_WARN(...) HKU_WARN(fmt::format("[{}] {}", ms_logger, fmt::format(__VA_ARGS__))) +#define CLS_ERROR(...) HKU_ERROR(fmt::format("[{}] {}", ms_logger, fmt::format(__VA_ARGS__))) +#define CLS_FATAL(...) HKU_FATAL(fmt::format("[{}] {}", ms_logger, fmt::format(__VA_ARGS__))) + +#define CLS_TRACE_IF(expr, ...) \ + HKU_TRACE_IF(expr, fmt::format("[{}] {}", ms_logger, fmt::format(__VA_ARGS__))) +#define CLS_DEBUG_IF(expr, ...) \ + HKU_DEBUG_IF(expr, fmt::format("[{}] {}", ms_logger, fmt::format(__VA_ARGS__))) +#define CLS_INFO_IF(expr, ...) \ + HKU_INFO_IF(expr, fmt::format("[{}] {}", ms_logger, fmt::format(__VA_ARGS__))) +#define CLS_WARN_IF(expr, ...) \ + HKU_WARN_IF(expr, fmt::format("[{}] {}", ms_logger, fmt::format(__VA_ARGS__))) +#define CLS_ERROR_IF(expr, ...) \ + HKU_ERROR_IF(expr, fmt::format("[{}] {}", ms_logger, fmt::format(__VA_ARGS__))) +#define CLS_FATAL_IF(expr, ...) \ + HKU_FATAL_IF(expr, fmt::format("[{}] {}", ms_logger, fmt::format(__VA_ARGS__))) + +#define CLS_IF_RETURN(expr, ret) HKU_IF_RETURN(expr, ret) +#define CLS_TRACE_IF_RETURN(expr, ret, ...) \ + HKU_TRACE_IF_RETURN(expr, ret, fmt::format("[{}] {}", ms_logger, fmt::format(__VA_ARGS__))) +#define CLS_DEBUG_IF_RETURN(expr, ret, ...) \ + HKU_DEBUG_IF_RETURN(expr, ret, fmt::format("[{}] {}", ms_logger, fmt::format(__VA_ARGS__))) +#define CLS_INFO_IF_RETURN(expr, ret, ...) \ + HKU_INFO_IF_RETURN(expr, ret, fmt::format("[{}] {}", ms_logger, fmt::format(__VA_ARGS__))) +#define CLS_WARN_IF_RETURN(expr, ret, ...) \ + HKU_WARN_IF_RETURN(expr, ret, fmt::format("[{}] {}", ms_logger, fmt::format(__VA_ARGS__))) +#define CLS_ERROR_IF_RETURN(expr, ret, ...) \ + HKU_ERROR_IF_RETURN(expr, ret, fmt::format("[{}] {}", ms_logger, fmt::format(__VA_ARGS__))) +#define CLS_FATAL_IF_RETURN(expr, ret, ...) \ + HKU_FATAL_IF_RETURN(expr, ret, fmt::format("[{}] {}", ms_logger, fmt::format(__VA_ARGS__))) + +#define CLS_ASSERT HKU_ASSERT +#define CLS_CHECK(expr, ...) \ + HKU_CHECK(expr, fmt::format("[{}] {}", ms_logger, fmt::format(__VA_ARGS__))) +#define CLS_CHECK_THROW(expr, except, ...) \ + HKU_CHECK_THROW(expr, except, fmt::format("[{}] {}", ms_logger, fmt::format(__VA_ARGS__))) +#define CLS_THROW(...) HKU_THROW(fmt::format("[{}] {}", ms_logger, fmt::format(__VA_ARGS__))) +#define CLS_THROW_EXCEPTION(except, ...) \ + HKU_THROW_EXCEPTION(except, fmt::format("[{}] {}", ms_logger, fmt::format(__VA_ARGS__))) + /** @} */ } /* namespace hku */ diff --git a/hikyuu_cpp/hikyuu/utilities/Null.h b/hikyuu_cpp/hikyuu/utilities/Null.h index aed19e66..1d28d5ef 100644 --- a/hikyuu_cpp/hikyuu/utilities/Null.h +++ b/hikyuu_cpp/hikyuu/utilities/Null.h @@ -10,6 +10,7 @@ #define NULL_H_ #include +#include #include #include #include "osdef.h" diff --git a/hikyuu_cpp/hikyuu/utilities/Parameter.cpp b/hikyuu_cpp/hikyuu/utilities/Parameter.cpp index 2aff1f3d..abb683ba 100644 --- a/hikyuu_cpp/hikyuu/utilities/Parameter.cpp +++ b/hikyuu_cpp/hikyuu/utilities/Parameter.cpp @@ -6,8 +6,8 @@ */ #include +#include "hikyuu/utilities/Log.h" #include "hikyuu/Block.h" -#include "../Log.h" #include "Parameter.h" namespace hku { diff --git a/hikyuu_cpp/hikyuu/utilities/Parameter.h b/hikyuu_cpp/hikyuu/utilities/Parameter.h index 8b8fb569..6b9042e2 100644 --- a/hikyuu_cpp/hikyuu/utilities/Parameter.h +++ b/hikyuu_cpp/hikyuu/utilities/Parameter.h @@ -17,7 +17,8 @@ #include #include -#include "../config.h" +#include "hikyuu/config.h" +#include "hikyuu/utilities/config.h" #if HKU_SUPPORT_SERIALIZATION #include diff --git a/hikyuu_cpp/hikyuu/utilities/ResourcePool.h b/hikyuu_cpp/hikyuu/utilities/ResourcePool.h new file mode 100644 index 00000000..f246ceaf --- /dev/null +++ b/hikyuu_cpp/hikyuu/utilities/ResourcePool.h @@ -0,0 +1,636 @@ +/* + * ResourcePool.h + * + * Copyright (c) 2019, hikyuu.org + * + * Created on: 2019-8-5 + * Author: fasiondog + */ +#pragma once +#ifndef HKU_UTILS_RESOURCE_POOL_H +#define HKU_UTILS_RESOURCE_POOL_H + +#include +#include +#include +#include +#include +#include +#include "Parameter.h" +#include "Log.h" + +namespace hku { + +/** + * 资源获取超时异常 + */ +class GetResourceTimeoutException : public hku::exception { +public: + GetResourceTimeoutException(const char *msg) + : hku::exception(fmt::format("GetResourceTimeoutException {}", msg)) {} + + GetResourceTimeoutException(const std::string &msg) + : hku::exception(fmt::format("GetResourceTimeoutException {}", msg)) {} + + virtual ~GetResourceTimeoutException() {} +}; + +/** + * 新资源创建失败异常 + */ +class CreateResourceException : public hku::exception { +public: + CreateResourceException(const char *msg) + : hku::exception(fmt::format("CreateResourceException {}", msg)) {} + + CreateResourceException(const std::string &msg) + : hku::exception(fmt::format("CreateResourceException {}", msg)) {} + + virtual ~CreateResourceException() {} +}; + +/** + * 通用共享资源池 + * @ingroup Utilities + */ +template +class ResourcePool { +public: + ResourcePool() = delete; + ResourcePool(const ResourcePool &) = delete; + ResourcePool &operator=(const ResourcePool &) = delete; + + /** + * 构造函数 + * @param param 连接参数 + * @param maxPoolSize 允许的最大共享资源数,为 0 表示不限制 + * @param maxIdleNum 运行的最大空闲资源数,为 0 表示用完即刻释放,无缓存 + */ + explicit ResourcePool(const Parameter ¶m, size_t maxPoolSize = 0, size_t maxIdleNum = 100) + : m_maxPoolSize(maxPoolSize), m_maxIdelSize(maxIdleNum), m_count(0), m_param(param) {} + + /** + * 析构函数,释放所有缓存的资源 + */ + virtual ~ResourcePool() { + std::unique_lock lock(m_mutex); + + // 将所有已分配资源的 closer 和 pool 解绑 + for (auto iter = m_closer_set.begin(); iter != m_closer_set.end(); ++iter) { + (*iter)->unbind(); + } + + // 删除所有空闲资源 + while (!m_resourceList.empty()) { + ResourceType *p = m_resourceList.front(); + m_resourceList.pop(); + if (p) { + delete p; + } + } + } + + /** 获取当前允许的最大资源数 */ + size_t maxPoolSize() const { + return m_maxIdelSize; + } + + /** 获取当前允许的最大空闲资源数 */ + size_t maxIdleSize() const { + return m_maxIdelSize; + } + + /** 设置最大资源数 */ + void maxPoolSize(size_t num) { + std::lock_guard lock(m_mutex); + m_maxPoolSize = num; + } + + /** 设置允许的最大空闲资源数 */ + void maxIdleSize(size_t num) { + std::lock_guard lock(m_mutex); + m_maxIdelSize = num; + } + + /** 资源实例指针类型 */ + typedef std::shared_ptr ResourcePtr; + + /** + * 获取可用资源,如超出允许的最大资源数将返回空指针 + * @exception CreateResourceException 新资源创建可能抛出异常 + */ + ResourcePtr get() { + std::lock_guard lock(m_mutex); + ResourcePtr result; + ResourceType *p = nullptr; + if (m_resourceList.empty()) { + if (m_maxPoolSize > 0 && m_count >= m_maxPoolSize) { + return result; + } + try { + p = new ResourceType(m_param); + } catch (const std::exception &e) { + HKU_THROW_EXCEPTION(CreateResourceException, "Failed create a new Resource! {}", + e.what()); + } catch (...) { + HKU_THROW_EXCEPTION(CreateResourceException, + "Failed create a new Resource! Unknown error!"); + } + m_count++; + result = ResourcePtr(p, ResourceCloser(this)); + m_closer_set.insert(std::get_deleter(result)); + return result; + } + p = m_resourceList.front(); + m_resourceList.pop(); + result = ResourcePtr(p, ResourceCloser(this)); + m_closer_set.insert(std::get_deleter(result)); + return result; + } + + /** + * 在指定的超时时间内获取可用资源 + * @param ms_timeout 超时时间,单位毫秒 + * @exception GetResourceTimeoutException, CreateResourceException + */ + ResourcePtr getWaitFor(uint64_t ms_timeout) { // NOSONAR + std::unique_lock lock(m_mutex); + ResourcePtr result; + ResourceType *p = nullptr; + if (m_resourceList.empty()) { + if (m_maxPoolSize > 0 && m_count >= m_maxPoolSize) { + // HKU_TRACE("超出最大资源数,等待空闲资源"); + if (ms_timeout > 0) { + if (m_cond.wait_for(lock, + std::chrono::duration(ms_timeout), + [&] { return !m_resourceList.empty(); })) { + HKU_CHECK_THROW(!m_resourceList.empty(), GetResourceTimeoutException, + "Failed get resource!"); + } else { + HKU_THROW_EXCEPTION(GetResourceTimeoutException, "Failed get resource!"); + } + } else { + m_cond.wait(lock, [this] { return !m_resourceList.empty(); }); + } + } else { + try { + p = new ResourceType(m_param); + } catch (const std::exception &e) { + HKU_THROW_EXCEPTION(CreateResourceException, "Failed create a new Resource! {}", + e.what()); + } catch (...) { + HKU_THROW_EXCEPTION(CreateResourceException, + "Failed create a new Resource! Unknown error!"); + } + m_count++; + result = ResourcePtr(p, ResourceCloser(this)); + m_closer_set.insert(std::get_deleter(result)); + return result; + } + } + p = m_resourceList.front(); + m_resourceList.pop(); + result = ResourcePtr(p, ResourceCloser(this)); + m_closer_set.insert(std::get_deleter(result)); + return result; + } + + /** + * 获取可用资源,如超出允许的最大资源数,将阻塞等待直到获得空闲资源 + * @exception CreateResourceException 新资源创建可能抛出异常 + */ + ResourcePtr getAndWait() { + return getWaitFor(0); + } + + /** 当前活动的资源数, 即全部资源数(含空闲及被使用的资源) */ + size_t count() const { + return m_count; + } + + /** 当前空闲的资源数 */ + size_t idleCount() const { + return m_resourceList.size(); + } + + /** 释放当前所有的空闲资源 */ + void releaseIdleResource() { + std::lock_guard lock(m_mutex); + _releaseIdleResourceNoLock(); + } + +private: + void _releaseIdleResourceNoLock() { + while (!m_resourceList.empty()) { + ResourceType *p = m_resourceList.front(); + m_resourceList.pop(); + m_count--; + if (p) { + delete p; + } + } + } + +private: + size_t m_maxPoolSize; // 允许的最大共享资源数 + size_t m_maxIdelSize; // 允许的最大空闲资源数 + size_t m_count; // 当前活动的资源数 + Parameter m_param; + std::mutex m_mutex; + std::condition_variable m_cond; + std::queue m_resourceList; + + class ResourceCloser { + public: + explicit ResourceCloser(ResourcePool *pool) : m_pool(pool) { // NOSONAR + } + + void operator()(ResourceType *conn) { // NOSONAR + if (conn) { + // 如果绑定了 pool,则归还资源;否则删除 + if (m_pool) { + // HKU_DEBUG("retuan to pool"); + m_pool->returnResource(conn, this); + } else { + // HKU_DEBUG("delete resource not in pool"); + delete conn; + } + } + } + + // 解绑资源池 + void unbind() { + m_pool = nullptr; + } + + private: + ResourcePool *m_pool; + }; + + /** 归还至资源池 */ + void returnResource(ResourceType *p, ResourceCloser *closer) { + std::unique_lock lock(m_mutex); + if (p) { + if (m_resourceList.size() < m_maxIdelSize) { + m_resourceList.push(p); + m_cond.notify_all(); + } else { + delete p; + m_count--; + } + } else { + m_count--; + // HKU_WARN("Trying to return an empty pointer!"); + } + if (closer) { + m_closer_set.erase(closer); // 移除该 closer + } + } + + std::unordered_set m_closer_set; // 占用资源的 closer +}; + +/** + * @brief 带版本的资源接口,可由需要版本管理的资源继承 + * @details 自带的 getVersion 和 setVerion 方法由 ResourceVersionPool 调用,不建议带有其他用途 + */ +class ResourceWithVersion { +public: + /** 默认构造函数 */ + ResourceWithVersion() : m_version(0) {} + + /** 析构函数 */ + virtual ~ResourceWithVersion() {} + + /** 获取资源版本 */ + int getVersion() const { + return m_version; + } + + /** 设置资源版本 **/ + void setVersion(int version) { + m_version = version; + } + +protected: + int m_version; +}; + +/** + * 通用版本的共享资源池,当资源池参数变更时,保证新资源使用新参数,老版本的资源再使用完毕后被自动回收 + * @details 要求资源类具备 int getVersion() 和 void setVersion(int) 另个接口函数 + * @ingroup Utilities + */ +template +class ResourceVersionPool { +public: + ResourceVersionPool() = delete; + ResourceVersionPool(const ResourceVersionPool &) = delete; + ResourceVersionPool &operator=(const ResourceVersionPool &) = delete; + + /** + * 构造函数 + * @param param 连接参数 + * @param maxPoolSize 允许的最大共享资源数,为 0 表示不限制 + * @param maxIdleNum 运行的最大空闲资源数,为 0 表示用完即刻释放,无缓存 + */ + explicit ResourceVersionPool(const Parameter ¶m, size_t maxPoolSize = 0, + size_t maxIdleNum = 100) + : m_maxPoolSize(maxPoolSize), + m_maxIdelSize(maxIdleNum), + m_count(0), + m_param(param), + m_version(0) {} + + /** + * 析构函数,释放所有缓存的资源 + */ + virtual ~ResourceVersionPool() { + std::unique_lock lock(m_mutex); + + // 将所有已分配资源的 closer 和 pool 解绑 + for (auto iter = m_closer_set.begin(); iter != m_closer_set.end(); ++iter) { + (*iter)->unbind(); + } + + // 删除所有空闲资源 + while (!m_resourceList.empty()) { + ResourceType *p = m_resourceList.front(); + m_resourceList.pop(); + if (p) { + delete p; + } + } + } + + /** 获取当前允许的最大资源数 */ + size_t maxPoolSize() const { + return m_maxIdelSize; + } + + /** 获取当前允许的最大空闲资源数 */ + size_t maxIdleSize() const { + return m_maxIdelSize; + } + + /** 设置最大资源数 */ + void maxPoolSize(size_t num) { + std::lock_guard lock(m_mutex); + m_maxPoolSize = num; + } + + /** 设置允许的最大空闲资源数 */ + void maxIdleSize(size_t num) { + std::lock_guard lock(m_mutex); + m_maxIdelSize = num; + } + + /** 指定参数是否存在 */ + bool haveParam(const std::string &name) { + std::lock_guard lock(m_mutex); + return m_param.have(name); + } + + /** 获取指定参数的值,如参数不存在或类型不匹配抛出异常 */ + template + ValueType getParam(const std::string &name) { + std::lock_guard lock(m_mutex); + return m_param.get(name); + } + + /** + * @brief 设定指定参数的值,参数仅在生成新的资源时生效 + * @details 在原本存在该参数的情况下,新设定的值类型须和原有参数类型相同,否则将抛出异常 + * @param name 参数名 + * @param value 参数值 + * @exception std::logic_error + */ + template + void setParam(const std::string &name, const ValueType &value) { + std::lock_guard lock(m_mutex); + // 如果参数未实际发送变化,则直接返回 + HKU_IF_RETURN(m_param.have(name) && value == m_param.get(name), void()); + m_param.set(name, value); + m_version++; + _releaseIdleResourceNoLock(); // 释放当前空闲资源,以便新参数值生效 + } + + /** + * @brief 设置资源参数,参数仅在生成新的资源时生效 + * @param param 参数对象 + */ + void setParameter(const Parameter ¶m) { + std::lock_guard lock(m_mutex); + m_param = param; + m_version++; + _releaseIdleResourceNoLock(); // 释放当前空闲资源,以便新参数值生效 + } + + /** + * @brief 设置资源参数,参数仅在生成新的资源时生效 + * @param param 参数对象 + */ + void setParameter(Parameter &¶m) { + std::lock_guard lock(m_mutex); + m_param = std::move(param); + m_version++; + _releaseIdleResourceNoLock(); // 释放当前空闲资源,以便新参数值生效 + } + + /** 获取当前资源池版本 */ + int getVersion() { + std::lock_guard lock(m_mutex); + return m_version; + } + + /** 递增当前资源池版本,相当于通知资源池资源版本发生变化 */ + void incVersion(int version) { + std::lock_guard lock(m_mutex); + m_version++; + } + + /** 资源实例指针类型 */ + typedef std::shared_ptr ResourcePtr; + + /** + * 获取可用资源,如超出允许的最大资源数将返回空指针 + * @exception CreateResourceException 新资源创建可能抛出异常 + */ + ResourcePtr get() { + std::lock_guard lock(m_mutex); + ResourcePtr result; + ResourceType *p = nullptr; + if (m_resourceList.empty()) { + if (m_maxPoolSize > 0 && m_count >= m_maxPoolSize) { + return result; + } + try { + p = new ResourceType(m_param); + p->setVersion(m_version); + } catch (const std::exception &e) { + HKU_THROW_EXCEPTION(CreateResourceException, "Failed create a new Resource! {}", + e.what()); + } catch (...) { + HKU_THROW_EXCEPTION(CreateResourceException, + "Failed create a new Resource! Unknown error!"); + } + m_count++; + result = ResourcePtr(p, ResourceCloser(this)); + m_closer_set.insert(std::get_deleter(result)); + return result; + } + p = m_resourceList.front(); + m_resourceList.pop(); + result = ResourcePtr(p, ResourceCloser(this)); + m_closer_set.insert(std::get_deleter(result)); + return result; + } + + /** + * 在指定的超时时间内获取可用资源 + * @param ms_timeout 超时时间,单位毫秒 + * @exception GetResourceTimeoutException, CreateResourceException + */ + ResourcePtr getWaitFor(uint64_t ms_timeout) { // NOSONAR + std::unique_lock lock(m_mutex); + ResourcePtr result; + ResourceType *p = nullptr; + if (m_resourceList.empty()) { + if (m_maxPoolSize > 0 && m_count >= m_maxPoolSize) { + // HKU_TRACE("超出最大资源数,等待空闲资源"); + if (ms_timeout > 0) { + if (m_cond.wait_for(lock, + std::chrono::duration(ms_timeout), + [&] { return !m_resourceList.empty(); })) { + HKU_CHECK_THROW(!m_resourceList.empty(), GetResourceTimeoutException, + "Failed get resource!"); + } else { + HKU_THROW_EXCEPTION(GetResourceTimeoutException, "Failed get resource!"); + } + } else { + m_cond.wait(lock, [this] { return !m_resourceList.empty(); }); + } + } else { + try { + p = new ResourceType(m_param); + p->setVersion(m_version); + } catch (const std::exception &e) { + HKU_THROW_EXCEPTION(CreateResourceException, "Failed create a new Resource! {}", + e.what()); + } catch (...) { + HKU_THROW_EXCEPTION(CreateResourceException, + "Failed create a new Resource! Unknown error!"); + } + m_count++; + result = ResourcePtr(p, ResourceCloser(this)); + m_closer_set.insert(std::get_deleter(result)); + return result; + } + } + p = m_resourceList.front(); + m_resourceList.pop(); + result = ResourcePtr(p, ResourceCloser(this)); + m_closer_set.insert(std::get_deleter(result)); + return result; + } + + /** + * 获取可用资源,如超出允许的最大资源数,将阻塞等待直到获得空闲资源 + * @exception CreateResourceException 新资源创建可能抛出异常 + */ + ResourcePtr getAndWait() { + return getWaitFor(0); + } + + /** 当前活动的资源数, 即全部资源数(含空闲及被使用的资源) */ + size_t count() const { + return m_count; + } + + /** 当前空闲的资源数 */ + size_t idleCount() const { + return m_resourceList.size(); + } + + /** 释放当前所有的空闲资源 */ + void releaseIdleResource() { + std::lock_guard lock(m_mutex); + _releaseIdleResourceNoLock(); + } + +private: + void _releaseIdleResourceNoLock() { + while (!m_resourceList.empty()) { + ResourceType *p = m_resourceList.front(); + m_resourceList.pop(); + m_count--; + if (p) { + delete p; + } + } + } + +private: + size_t m_maxPoolSize; // 允许的最大共享资源数 + size_t m_maxIdelSize; // 允许的最大空闲资源数 + size_t m_count; // 当前活动的资源数 + Parameter m_param; + std::mutex m_mutex; + std::condition_variable m_cond; + std::queue m_resourceList; + int m_version; + + class ResourceCloser { + public: + explicit ResourceCloser(ResourceVersionPool *pool) : m_pool(pool) { // NOSONAR + } + + void operator()(ResourceType *conn) { // NOSONAR + if (conn) { + // 如果绑定了 pool,则归还资源;否则删除 + if (m_pool) { + // HKU_DEBUG("retuan to pool"); + m_pool->returnResource(conn, this); + } else { + // HKU_DEBUG("delete resource not in pool"); + delete conn; + } + } + } + + // 解绑资源池 + void unbind() { + m_pool = nullptr; + } + + private: + ResourceVersionPool *m_pool; + }; + + /** 归还至资源池 */ + void returnResource(ResourceType *p, ResourceCloser *closer) { + std::unique_lock lock(m_mutex); + if (p) { + // 当前归还资源的版本和资源池版本相等,且空闲资源列表小于最大空闲资源数时,接受归还的资源 + if (p->getVersion() == m_version && m_resourceList.size() < m_maxIdelSize) { + m_resourceList.push(p); + m_cond.notify_all(); + } else { + delete p; + m_count--; + } + } else { + m_count--; + // HKU_WARN("Trying to return an empty pointer!"); + } + if (closer) { + m_closer_set.erase(closer); // 移除该 closer + } + } + + std::unordered_set m_closer_set; // 占用资源的 closer +}; + +} // namespace hku + +#endif /* HKU_UTILS_RESOURCE_POOL_H */ \ No newline at end of file diff --git a/hikyuu_cpp/hikyuu/utilities/SpendTimer.cpp b/hikyuu_cpp/hikyuu/utilities/SpendTimer.cpp index 1596a5cd..edcf72d3 100644 --- a/hikyuu_cpp/hikyuu/utilities/SpendTimer.cpp +++ b/hikyuu_cpp/hikyuu/utilities/SpendTimer.cpp @@ -126,15 +126,15 @@ double SpendTimer::value() const { return sec.count(); } -void HKU_API close_spend_time() { // NOSONAR +void HKU_UTILS_API close_spend_time() { // NOSONAR SpendTimer::ms_closed = true; } -void HKU_API open_spend_time() { // NOSONAR +void HKU_UTILS_API open_spend_time() { // NOSONAR SpendTimer::ms_closed = false; } -bool HKU_API get_spend_time_status() { +bool HKU_UTILS_API get_spend_time_status() { return !SpendTimer::ms_closed; } diff --git a/hikyuu_cpp/hikyuu/utilities/SpendTimer.h b/hikyuu_cpp/hikyuu/utilities/SpendTimer.h index 43bb31d4..694f2b31 100644 --- a/hikyuu_cpp/hikyuu/utilities/SpendTimer.h +++ b/hikyuu_cpp/hikyuu/utilities/SpendTimer.h @@ -21,9 +21,10 @@ #include #include #include +#include "hikyuu/utilities/config.h" -#ifndef HKU_API -#define HKU_API +#ifndef HKU_UTILS_API +#define HKU_UTILS_API #endif #ifdef __ANDROID__ #include @@ -138,7 +139,7 @@ namespace hku { * @note 不建议直接使用,应使用相关工具宏 * @see SPEND_TIME, SPEND_TIME_MSG, SPEND_TIMG_CONTROL */ -class HKU_API SpendTimer { +class HKU_UTILS_API SpendTimer { public: /** 构造函数,记录当前系统时间 */ explicit SpendTimer() @@ -237,19 +238,19 @@ private: std::vector m_keep_desc; static bool ms_closed; - friend void HKU_API close_spend_time(); - friend void HKU_API open_spend_time(); - friend bool HKU_API get_spend_time_status(); + friend void HKU_UTILS_API close_spend_time(); + friend void HKU_UTILS_API open_spend_time(); + friend bool HKU_UTILS_API get_spend_time_status(); }; /** 全局关闭耗时打印输出 */ -void HKU_API close_spend_time(); +void HKU_UTILS_API close_spend_time(); /** 全局开启耗时打印输出 */ -void HKU_API open_spend_time(); +void HKU_UTILS_API open_spend_time(); /** 获取全局耗时打印输出状态:true - 开启,false - 关闭 */ -bool HKU_API get_spend_time_status(); +bool HKU_UTILS_API get_spend_time_status(); /** * 耗时计时器开启关闭状态看守,记录之前的耗时开关状态,并置为指定状态,释放时恢复原状态 diff --git a/hikyuu_cpp/hikyuu/utilities/TimerManager.h b/hikyuu_cpp/hikyuu/utilities/TimerManager.h index 81eac01c..522caf56 100644 --- a/hikyuu_cpp/hikyuu/utilities/TimerManager.h +++ b/hikyuu_cpp/hikyuu/utilities/TimerManager.h @@ -11,7 +11,7 @@ #include #include #include "hikyuu/utilities/datetime/Datetime.h" -#include "hikyuu/Log.h" +#include "hikyuu/utilities/Log.h" #include "thread/ThreadPool.h" #include "cppdef.h" @@ -35,7 +35,23 @@ public: * @param work_num 定时任务执行线程池线程数量 */ explicit TimerManager(size_t work_num = 1) - : m_stop(true), m_current_timer_id(-1), m_work_num(work_num) { + : m_stop(true), + m_current_timer_id(-1), + m_work_num(work_num), + m_tg(nullptr), + m_use_extend_tg(false) { + HKU_ASSERT(work_num >= 1); + start(); + } + + /** + * 指定线程池方式构造,以便共享其他线程池 + * @note 请自行保证 tg 的生命周期在 TimerManager 存活期间始终有效 + * @param tg 指定任务组线程池 + */ + explicit TimerManager(ThreadPool* tg) + : m_stop(true), m_current_timer_id(-1), m_work_num(1), m_tg(tg), m_use_extend_tg(true) { + HKU_ASSERT(m_tg); start(); } @@ -60,11 +76,7 @@ public: std::priority_queue new_queue; m_queue.swap(new_queue); if (!m_tg) { -#if CPP_STANDARD >= CPP_STANDARD_14 - m_tg = std::make_unique(m_work_num); -#else - m_tg = std::unique_ptr(new ThreadPool(m_work_num)); -#endif + m_tg = new ThreadPool(m_work_num); } /* @@ -152,9 +164,10 @@ public: m_detect_thread.join(); } - if (m_tg) { + if (!m_use_extend_tg && m_tg) { m_tg->stop(); - m_tg.reset(); + delete m_tg; + m_tg = nullptr; } } @@ -399,7 +412,7 @@ private: // 分配 timer_id int getNewTimerId() { int max_int = std::numeric_limits::max(); - HKU_WARN_IF_RETURN(m_timers.size() >= max_int, -1, "Timer queue is full!"); + HKU_WARN_IF_RETURN(m_timers.size() >= size_t(max_int), -1, "Timer queue is full!"); if (m_current_timer_id >= max_int) { m_current_timer_id = 0; @@ -532,7 +545,8 @@ private: std::unordered_map m_timers; int m_current_timer_id; size_t m_work_num; // 任务执行线程池线程数量 - std::unique_ptr m_tg; + ThreadPool* m_tg{nullptr}; + bool m_use_extend_tg{false}; }; } // namespace hku \ No newline at end of file diff --git a/hikyuu_cpp/hikyuu/utilities/any_to_string.h b/hikyuu_cpp/hikyuu/utilities/any_to_string.h new file mode 100644 index 00000000..64341c5c --- /dev/null +++ b/hikyuu_cpp/hikyuu/utilities/any_to_string.h @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2023 hikyuu.org + * + * Created on: 2023-01-15 + * Author: fasiondog + */ + +#pragma once + +#include + +#include "osdef.h" +#include "cppdef.h" +#if !HKU_OS_IOS && CPP_STANDARD >= CPP_STANDARD_17 +#include +#else +#include +#endif + +#if defined(HKU_SUPPORT_DATETIME) +#include "hikyuu/utilities/datetime/Datetime.h" +#endif + +namespace hku { + +#if !HKU_OS_IOS && CPP_STANDARD >= CPP_STANDARD_17 +using any_t = std::any; +using std::any_cast; +#else +using any_t = boost::any; +using boost::any_cast; +#endif + +//------------------------------------------------------------------------------ +// +// 常见基本类型包装的 any_t 和 std::string 的互相转换函数 +// any_to_string 要用户自定义类型需包含从 std::string 进行构造的构造函数 +// string_to_any 需要用户自定义实现 std::to_string 特化方法 +// +//------------------------------------------------------------------------------ + +template +inline std::string any_to_string(const any_t& data) { + return any_cast(data).str(); +} + +template +inline any_t string_to_any(const std::string& data) { + return any_t(ValueT(data)); +} + +template <> +inline std::string any_to_string(const any_t& data) { + return std::to_string(any_cast(data)); +} + +template <> +inline std::string any_to_string(const any_t& data) { + return std::to_string(any_cast(data)); +} + +template <> +inline std::string any_to_string(const any_t& data) { + return std::to_string(any_cast(data)); +} + +template <> +inline std::string any_to_string(const any_t& data) { + return std::to_string(any_cast(data)); +} + +template <> +inline std::string any_to_string(const any_t& data) { + return std::to_string(any_cast(data)); +} + +template <> +inline std::string any_to_string(const any_t& data) { + return std::to_string(any_cast(data)); +} + +template <> +inline std::string any_to_string(const any_t& data) { + return std::to_string(any_cast(data)); +} + +template <> +inline std::string any_to_string(const any_t& data) { + return std::to_string(any_cast(data)); +} + +template <> +inline std::string any_to_string(const any_t& data) { + return std::to_string(any_cast(data)); +} + +template <> +inline any_t string_to_any(const std::string& data) { + return any_t(std::stoi(data)); +} + +template <> +inline any_t string_to_any(const std::string& data) { + return any_t(std::stol(data)); +} + +template <> +inline any_t string_to_any(const std::string& data) { + return any_t(std::stoll(data)); +} + +template <> +inline any_t string_to_any(const std::string& data) { + return any_t((unsigned int)(std::stoul(data))); +} + +template <> +inline any_t string_to_any(const std::string& data) { + return any_t(std::stoul(data)); +} + +template <> +inline any_t string_to_any(const std::string& data) { + return any_t(std::stoull(data)); +} + +template <> +inline any_t string_to_any(const std::string& data) { + return any_t(std::stof(data)); +} + +template <> +inline any_t string_to_any(const std::string& data) { + return any_t(std::stod(data)); +} + +template <> +inline any_t string_to_any(const std::string& data) { + return any_t(std::stold(data)); +} + +} // namespace hku diff --git a/hikyuu_cpp/hikyuu/utilities/arithmetic.cpp b/hikyuu_cpp/hikyuu/utilities/arithmetic.cpp index 8b00b660..df8bffe0 100644 --- a/hikyuu_cpp/hikyuu/utilities/arithmetic.cpp +++ b/hikyuu_cpp/hikyuu/utilities/arithmetic.cpp @@ -17,6 +17,13 @@ namespace hku { +template double HKU_UTILS_API roundEx(double number, int ndigits); +template float HKU_UTILS_API roundEx(float number, int ndigits); +template double HKU_UTILS_API roundUp(double number, int ndigits); +template float HKU_UTILS_API roundUp(float number, int ndigits); +template double HKU_UTILS_API roundDown(double number, int ndigits); +template float HKU_UTILS_API roundDown(float number, int ndigits); + #if defined(_MSC_VER) /** * 将UTF8编码的字符串转换为GB2312编码的字符串 @@ -24,7 +31,7 @@ namespace hku { * @return 以GB2312编码的字符串 * @note 仅在Windows平台下生效 */ -std::string HKU_API utf8_to_gb(const char *szinput) { +std::string HKU_UTILS_API utf8_to_gb(const char *szinput) { wchar_t *strSrc; char *szRes; std::string nullStr; @@ -60,7 +67,7 @@ std::string HKU_API utf8_to_gb(const char *szinput) { return result; } -std::string HKU_API utf8_to_gb(const std::string &szinput) { +std::string HKU_UTILS_API utf8_to_gb(const std::string &szinput) { return utf8_to_gb(szinput.c_str()); } @@ -70,7 +77,7 @@ std::string HKU_API utf8_to_gb(const std::string &szinput) { * @return 以UTF8编码的字符串 * @note 仅在Windows平台下生效 */ -std::string HKU_API gb_to_utf8(const char *szinput) { +std::string HKU_UTILS_API gb_to_utf8(const char *szinput) { wchar_t *strSrc; char *szRes; std::string nullstr; @@ -107,12 +114,12 @@ std::string HKU_API gb_to_utf8(const char *szinput) { return result; } -std::string HKU_API gb_to_utf8(const std::string &szinput) { +std::string HKU_UTILS_API gb_to_utf8(const std::string &szinput) { return gb_to_utf8(szinput.c_str()); } #else /* else for defined(_MSC_VER) */ -std::string HKU_API utf8_to_gb(const std::string &szinput) { +std::string HKU_UTILS_API utf8_to_gb(const std::string &szinput) { char *inbuf = const_cast(szinput.c_str()); size_t inlen = strlen(inbuf); size_t outlen = inlen; @@ -128,7 +135,7 @@ std::string HKU_API utf8_to_gb(const std::string &szinput) { return result; } -std::string HKU_API gb_to_utf8(const std::string &szinput) { +std::string HKU_UTILS_API gb_to_utf8(const std::string &szinput) { char *inbuf = const_cast(szinput.c_str()); size_t inlen = strlen(inbuf); size_t outlen = inlen * 2; diff --git a/hikyuu_cpp/hikyuu/utilities/arithmetic.h b/hikyuu_cpp/hikyuu/utilities/arithmetic.h index fdf929a8..906191ff 100644 --- a/hikyuu_cpp/hikyuu/utilities/arithmetic.h +++ b/hikyuu_cpp/hikyuu/utilities/arithmetic.h @@ -15,11 +15,12 @@ #include #include #include -#include #include -#ifndef HKU_API -#define HKU_API +#include "string_view.h" + +#ifndef HKU_UTILS_API +#define HKU_UTILS_API #endif namespace hku { @@ -30,13 +31,13 @@ namespace hku { */ #if defined(_MSC_VER) -std::string HKU_API utf8_to_gb(const char* szinput); -std::string HKU_API utf8_to_gb(const std::string& szinput); -std::string HKU_API gb_to_utf8(const char* szinput); -std::string HKU_API gb_to_utf8(const std::string& szinput); +std::string HKU_UTILS_API utf8_to_gb(const char *szinput); +std::string HKU_UTILS_API utf8_to_gb(const std::string &szinput); +std::string HKU_UTILS_API gb_to_utf8(const char *szinput); +std::string HKU_UTILS_API gb_to_utf8(const std::string &szinput); #else -std::string HKU_API utf8_to_gb(const std::string& szinput); -std::string HKU_API gb_to_utf8(const std::string& szinput); +std::string HKU_UTILS_API utf8_to_gb(const std::string &szinput); +std::string HKU_UTILS_API gb_to_utf8(const std::string &szinput); #endif #define UTF8ToGB hku::utf8_to_gb @@ -81,18 +82,17 @@ std::string HKU_API gb_to_utf8(const std::string& szinput); * @param ndigits 保留小数位数 * @return 处理过的数据 */ -// double HKU_API roundEx(double number, int ndigits = 0); template ValueT roundEx(ValueT number, int ndigits = 0) { // 切换至:ROUND_HALF_EVEN 银行家舍入法 ValueT pow1, pow2, y, z; ValueT x = number; if (ndigits >= 0) { - pow1 = std::pow(10.0, (ValueT)ndigits); + pow1 = pow(ValueT(10.0), ValueT(ndigits)); pow2 = 1.0; y = (x * pow1) * pow2; } else { - pow1 = std::pow(10.0, (ValueT)-ndigits); + pow1 = pow(ValueT(10.0), ValueT(-ndigits)); pow2 = 1.0; y = x / pow1; } @@ -189,17 +189,17 @@ ValueT roundDown(ValueT number, int ndigits = 0) { #endif /** 转小写字符串 */ -inline void to_lower(std::string& s) { +inline void to_lower(std::string &s) { std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) { return std::tolower(c); }); } /** 转大写字符串 */ -inline void to_upper(std::string& s) { +inline void to_upper(std::string &s) { std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) { return std::toupper(c); }); } /** 删除字符串两端空格 */ -inline void trim(std::string& s) { +inline void trim(std::string &s) { if (s.empty()) { return; } @@ -210,12 +210,13 @@ inline void trim(std::string& s) { s.erase(s.find_last_not_of("\n") + 1); } +#if CPP_STANDARD >= CPP_STANDARD_17 /** * 分割字符串 * @param str 待封的字符串 * @param c 分割符 */ -inline std::vector split(const std::string& str, char c) { +inline std::vector split(const std::string &str, char c) { std::vector result; std::string_view view(str); size_t prepos = 0; @@ -226,9 +227,7 @@ inline std::vector split(const std::string& str, char c) { pos = view.find_first_of(c, prepos); } - if (prepos < str.size() - 1) { - result.emplace_back(str.substr(prepos)); - } + result.emplace_back(view.substr(prepos)); return result; } @@ -239,7 +238,7 @@ inline std::vector split(const std::string& str, char c) { * @return string_view 组成的 vector * @note 注意返回结果的生命周期应小于输入的字符串相同! */ -inline std::vector split(const std::string_view& view, char c) { +inline std::vector split(const std::string_view &view, char c) { std::vector result; size_t prepos = 0; size_t pos = view.find_first_of(c); @@ -249,14 +248,12 @@ inline std::vector split(const std::string_view& view, char c) pos = view.find_first_of(c, prepos); } - if (prepos < view.size() - 1) { - result.emplace_back(view.substr(prepos)); - } + result.emplace_back(view.substr(prepos)); return result; } -inline std::vector split(const std::string_view& str, - const std::string& split_str) { +inline std::vector split(const std::string_view &str, + const std::string &split_str) { std::vector result; size_t split_str_len = split_str.size(); if (split_str_len == 0) { @@ -272,25 +269,64 @@ inline std::vector split(const std::string_view& str, pos = str.find(split_str, prepos); } - if (prepos < str.size() - 1) { - result.emplace_back(str.substr(prepos)); - } + result.emplace_back(str.substr(prepos)); return result; } +#else +/** + * 分割字符串 + * @param str 待封的字符串 + * @param c 分割符 + */ +inline std::vector split(const std::string &str, char c) { + std::vector result; + size_t prepos = 0; + size_t pos = str.find_first_of(c); + while (pos != std::string::npos) { + result.emplace_back(str.substr(prepos, pos - prepos)); + prepos = pos + 1; + pos = str.find_first_of(c, prepos); + } + + result.emplace_back(str.substr(prepos)); + return result; +} + +inline std::vector split(const std::string &str, const std::string &split_str) { + std::vector result; + size_t split_str_len = split_str.size(); + if (split_str_len == 0) { + result.emplace_back(str); + return result; + } + + size_t prepos = 0; + size_t pos = str.find(split_str); + while (pos != std::string::npos) { + result.emplace_back(str.substr(prepos, pos - prepos)); + prepos = pos + split_str_len; + pos = str.find(split_str, prepos); + } + + result.emplace_back(str.substr(prepos)); + return result; +} +#endif /* #if CPP_STANDARD >= CPP_STANDARD_17 */ + /** * byte 转 16 进制字符串, 如 "abcd" 转换为 "61626364" * @param in_byte 输入的 byte 数组 * @param in_len byte 数组长度 */ -inline std::string byteToHexStr(const char* bytes, size_t in_len) { +inline std::string byteToHexStr(const char *bytes, size_t in_len) { std::string hexstr; - const unsigned char* in_byte = (const unsigned char*)bytes; + const unsigned char *in_byte = (const unsigned char *)bytes; if (in_byte == nullptr) { return hexstr; } - char* buf = new char[2 * in_len + 1]; + char *buf = new char[2 * in_len + 1]; size_t buf_ix = 0; for (size_t i = 0; i < in_len; ++i) { @@ -311,7 +347,7 @@ inline std::string byteToHexStr(const char* bytes, size_t in_len) { * byte 转 16 进制字符串, 如 "abcd" 转换为 "61626364" * @param in_byte std::string 格式的输入 */ -inline std::string byteToHexStr(const std::string& bytes) { +inline std::string byteToHexStr(const std::string &bytes) { return byteToHexStr(bytes.c_str(), bytes.size()); } @@ -320,14 +356,14 @@ inline std::string byteToHexStr(const std::string& bytes) { * @param in_byte 输入的 byte 数组 * @param in_len byte 数组长度 */ -inline std::string byteToHexStrForPrint(const char* bytes, size_t in_len) { +inline std::string byteToHexStrForPrint(const char *bytes, size_t in_len) { std::string hexstr; - const unsigned char* in_byte = (const unsigned char*)bytes; + const unsigned char *in_byte = (const unsigned char *)bytes; if (in_byte == nullptr) { return hexstr; } - char* buf = new char[5 * in_len + 1]; + char *buf = new char[5 * in_len + 1]; size_t buf_ix = 0; for (size_t i = 0; i < in_len; ++i) { @@ -355,7 +391,7 @@ inline std::string byteToHexStrForPrint(const char* bytes, size_t in_len) { * byte 转 16 进制字符串, 如 "abcd" 转换为 "61626364" * @param in_byte 输入的 byte 数组 */ -inline std::string byteToHexStrForPrint(const std::string& bytes) { +inline std::string byteToHexStrForPrint(const std::string &bytes) { return byteToHexStrForPrint(bytes.c_str(), bytes.size()); } diff --git a/hikyuu_cpp/hikyuu/utilities/base64.cpp b/hikyuu_cpp/hikyuu/utilities/base64.cpp new file mode 100644 index 00000000..09dcb1f6 --- /dev/null +++ b/hikyuu_cpp/hikyuu/utilities/base64.cpp @@ -0,0 +1,108 @@ +/* + * Copyright (c) hikyuu.org + * + * Created on: 2020-6-2 + * Author: fasiondog + */ + +#include "base64.h" +#include "Log.h" + +namespace hku { + +static const std::string base64_chars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + +std::string base64_encode(unsigned char const* bytes_to_encode, size_t in_len) { // NOSONAR + HKU_CHECK(bytes_to_encode, "Input null ptr!"); + std::string ret; + HKU_IF_RETURN(in_len == 0, ret); + + int i = 0; + unsigned char char_array_3[3]; + unsigned char char_array_4[4]; + + while (in_len--) { + char_array_3[i++] = *(bytes_to_encode++); + if (i == 3) { + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; + + for (i = 0; (i < 4); i++) + ret += base64_chars[char_array_4[i]]; + i = 0; + } + } + + if (i) { + for (int j = i; j < 3; j++) + char_array_3[j] = '\0'; + + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; + + for (int j = 0; (j < i + 1); j++) + ret += base64_chars[char_array_4[j]]; + + while ((i++ < 3)) + ret += '='; + } + + return ret; +} + +static inline bool is_base64(unsigned char c) { + return (isalnum(c) || (c == '+') || (c == '/')); +} + +std::string base64_decode(unsigned char const* encoded_string, size_t in_len) { + HKU_CHECK(encoded_string, "Input null ptr!"); + std::string ret; + HKU_IF_RETURN(in_len == 0, ret); + + int i = 0; + int in_ = 0; + unsigned char char_array_4[4], char_array_3[3]; + + while (in_len-- && (encoded_string[in_] != '=') && is_base64(encoded_string[in_])) { + char_array_4[i++] = encoded_string[in_]; + in_++; + if (i == 4) { + for (i = 0; i < 4; i++) + char_array_4[i] = (unsigned char)base64_chars.find(char_array_4[i]); + + char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); + char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; + + for (i = 0; (i < 3); i++) + ret += char_array_3[i]; + i = 0; + } + } + + if (i) { + for (int j = i; j < 4; j++) + char_array_4[j] = 0; + + for (int j = 0; j < 4; j++) + char_array_4[j] = (unsigned char)base64_chars.find(char_array_4[j]); + + char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); + char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; + + for (int j = 0; (j < i - 1); j++) + ret += char_array_3[j]; + } + + return ret; +} + +} // namespace hku \ No newline at end of file diff --git a/hikyuu_cpp/hikyuu/utilities/base64.h b/hikyuu_cpp/hikyuu/utilities/base64.h new file mode 100644 index 00000000..54ea3d99 --- /dev/null +++ b/hikyuu_cpp/hikyuu/utilities/base64.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) hikyuu.org + * + * Created on: 2020-6-2 + * Author: fasiondog + */ + +#pragma once +#ifndef HKU_UTILS_BASE64_H +#define HKU_UTILS_BASE64_H + +#include +#include "string_view.h" + +#ifndef HKU_UTILS_API +#define HKU_UTILS_API +#endif + +namespace hku { + +/** + * 将二进制 bytes 数组编码成 base64 字符串 + * @param bytes_to_encode 内存起始地址 + * @param in_len 待计算的字节长度 + */ +std::string HKU_UTILS_API base64_encode(unsigned char const* bytes_to_encode, size_t in_len); + +/** + * 字符串编码为 base64 + * @param bytes_to_encode 内存起始地址 + * @param in_len 待计算的字节长度 + * @note 通过 func(unsigned char *, unsigned int) 函数实现,而不是直接只提供 string_view + * 版本的原因是:c++17 string_view 处理 nullptr 时,程序会直接挂掉,无异常 + */ +inline std::string base64_encode(string_view src) { + return base64_encode((unsigned char const*)src.data(), src.size()); +} + +/** + * 将 base64 字符串解码 + * @param encoded_string base64 编码的字符串 + * @param in_len 字符串长度 + * @return string 实际解码后的二进制内容保存在返回的字符串对象中 + * @note 如果传入的base64编码字符串中含有非法字符,不会告警,仅处理到能处理的字符 + */ +std::string HKU_UTILS_API base64_decode(unsigned char const* encoded_string, size_t in_len); + +/** + * 将 base64 字符串解码 + * @param encoded_string base64 编码的字符串 + * @return string 实际解码后的二进制内容保存在返回的字符串对象中 + */ +inline std::string base64_decode(string_view encoded_string) { + return base64_decode((unsigned char const*)encoded_string.data(), encoded_string.size()); +} + +} // namespace hku + +#endif // HKU_UTILS_BASE64_H \ No newline at end of file diff --git a/hikyuu_cpp/hikyuu/utilities/datetime/Datetime.cpp b/hikyuu_cpp/hikyuu/utilities/datetime/Datetime.cpp index 57dcca66..b072ece4 100644 --- a/hikyuu_cpp/hikyuu/utilities/datetime/Datetime.cpp +++ b/hikyuu_cpp/hikyuu/utilities/datetime/Datetime.cpp @@ -11,18 +11,22 @@ #include #include "hikyuu/utilities/Null.h" -#include "hikyuu/Log.h" +#include "hikyuu/utilities/Log.h" #include "hikyuu/utilities/arithmetic.h" #include "Datetime.h" namespace hku { -HKU_API std::ostream& operator<<(std::ostream& out, const Datetime& d) { +HKU_UTILS_API std::ostream &operator<<(std::ostream &out, const Datetime &d) { out << d.str(); return out; } Datetime Datetime::fromHex(uint64_t time) { + if (Null() == time) { + return Datetime(); + } + uint64_t second = 0xFFULL & time; uint64_t minute = (0xFF00ULL & time) >> 8; uint64_t hour = (0xFF0000ULL & time) >> 16; @@ -88,7 +92,7 @@ Datetime::Datetime(unsigned long long datetime) { } } -Datetime::Datetime(const std::string& ts) { +Datetime::Datetime(const std::string &ts) { std::string timeStr(ts); trim(timeStr); if ("+infinity" == timeStr) { @@ -113,7 +117,7 @@ bool Datetime::isNull() const { return (m_data == null_date) ? true : false; } -Datetime& Datetime::operator=(const Datetime& d) { +Datetime &Datetime::operator=(const Datetime &d) { if (this == &d) return *this; m_data = d.m_data; @@ -155,7 +159,7 @@ uint64_t Datetime::number() const noexcept { return (unsigned long long)year() * 100000000ULL + (unsigned long long)month() * 1000000ULL + (unsigned long long)day() * 10000ULL + (unsigned long long)hour() * 100ULL + (unsigned long long)minute(); - } catch (const std::exception& e) { + } catch (const std::exception &e) { HKU_ERROR(e.what()); return Null(); } catch (...) { @@ -169,7 +173,7 @@ uint64_t Datetime::ym() const noexcept { try { HKU_IF_RETURN(isNull(), Null()); return (unsigned long long)year() * 100ULL + (unsigned long long)month(); - } catch (const std::exception& e) { + } catch (const std::exception &e) { HKU_ERROR(e.what()); return Null(); } catch (...) { @@ -184,7 +188,7 @@ uint64_t Datetime::ymd() const noexcept { HKU_IF_RETURN(isNull(), Null()); return (unsigned long long)year() * 10000ULL + (unsigned long long)month() * 100ULL + (unsigned long long)day(); - } catch (const std::exception& e) { + } catch (const std::exception &e) { HKU_ERROR(e.what()); return Null(); } catch (...) { @@ -199,7 +203,7 @@ uint64_t Datetime::ymdh() const noexcept { HKU_IF_RETURN(isNull(), Null()); return (unsigned long long)year() * 1000000ULL + (unsigned long long)month() * 10000ULL + (unsigned long long)day() * 100ULL + (unsigned long long)hour(); - } catch (const std::exception& e) { + } catch (const std::exception &e) { HKU_ERROR(e.what()); return Null(); } catch (...) { @@ -215,7 +219,7 @@ uint64_t Datetime::ymdhm() const noexcept { return (unsigned long long)year() * 100000000LL + (unsigned long long)month() * 1000000LL + (unsigned long long)day() * 10000LL + (unsigned long long)hour() * 100LL + (unsigned long long)minute(); - } catch (const std::exception& e) { + } catch (const std::exception &e) { HKU_ERROR(e.what()); return Null(); } catch (...) { @@ -232,7 +236,7 @@ uint64_t Datetime::ymdhms() const noexcept { (unsigned long long)month() * 100000000ULL + (unsigned long long)day() * 1000000ULL + (unsigned long long)hour() * 10000ULL + (unsigned long long)minute() * 100ULL + (unsigned long long)second(); - } catch (const std::exception& e) { + } catch (const std::exception &e) { HKU_ERROR(e.what()); return Null(); } catch (...) { @@ -255,7 +259,7 @@ uint64_t Datetime::hex() const noexcept { ret |= (low_y << 40); ret |= (high_y << 48); return ret; - } catch (const std::exception& e) { + } catch (const std::exception &e) { HKU_ERROR(e.what()); return Null(); } catch (...) { @@ -269,7 +273,7 @@ uint64_t Datetime::ticks() const noexcept { HKU_IF_RETURN(isNull(), Null()); TimeDelta d = (*this) - Datetime::min(); return d.ticks(); - } catch (const std::exception& e) { + } catch (const std::exception &e) { HKU_ERROR(e.what()); return Null(); } catch (...) { @@ -337,7 +341,7 @@ Datetime Datetime::today() { return Datetime(x.year(), x.month(), x.day()); } -DatetimeList HKU_API getDateRange(const Datetime& start, const Datetime& end) { +DatetimeList HKU_UTILS_API getDateRange(const Datetime &start, const Datetime &end) { DatetimeList result; bd::date start_day = start.date(); bd::date end_day = end.date(); diff --git a/hikyuu_cpp/hikyuu/utilities/datetime/Datetime.h b/hikyuu_cpp/hikyuu/utilities/datetime/Datetime.h index 349931a0..7da8ccdc 100644 --- a/hikyuu_cpp/hikyuu/utilities/datetime/Datetime.h +++ b/hikyuu_cpp/hikyuu/utilities/datetime/Datetime.h @@ -17,14 +17,19 @@ #include #include #include + +#include "hikyuu/utilities/config.h" +#if !HKU_SUPPORT_DATETIME +#error "Don't support datetime, you can config with --datetime=y" +#endif #include "TimeDelta.h" #if defined(_MSC_VER) #pragma warning(disable : 4251) #endif -#ifndef HKU_API -#define HKU_API +#ifndef HKU_UTILS_API +#define HKU_UTILS_API #endif namespace hku { @@ -37,7 +42,7 @@ namespace bd = boost::gregorian; * @details 构造失败将抛出异常 std::out_of_range * @ingroup DataType */ -class HKU_API Datetime { +class HKU_UTILS_API Datetime { public: /** 返回所能表示的最小日期:1400-Jan-01 00:00:00 */ static Datetime min(); @@ -61,7 +66,7 @@ public: /** 默认构造函数,Null */ Datetime(); - Datetime(const Datetime&); + Datetime(const Datetime &); /** * 构造函数 @@ -78,10 +83,10 @@ public: long millisec = 0, long microsec = 0); /** 从boost::gregorian::date构造日期类型 */ - explicit Datetime(const bd::date&); + explicit Datetime(const bd::date &); /** 从boost::posix_time::ptime构造 */ - explicit Datetime(const bt::ptime&); + explicit Datetime(const bt::ptime &); /** * 通过数字方式构造日期类型 @@ -103,9 +108,9 @@ public: * 4、"20010101T181159" * */ - explicit Datetime(const std::string&); + explicit Datetime(const std::string &); - Datetime& operator=(const Datetime&); + Datetime &operator=(const Datetime &); /** 年份,如果是 Null 将抛出异常 */ long year() const; @@ -283,7 +288,7 @@ private: bt::ptime m_data; }; -HKU_API std::ostream& operator<<(std::ostream&, const Datetime&); +HKU_UTILS_API std::ostream &operator<<(std::ostream &, const Datetime &); /** * 日期列表 @@ -297,41 +302,41 @@ typedef std::vector DatetimeList; * @param end 结束日期 * @return [start, end)范围内的日历日期 */ -DatetimeList HKU_API getDateRange(const Datetime& start, const Datetime& end); +DatetimeList HKU_UTILS_API getDateRange(const Datetime &start, const Datetime &end); /////////////////////////////////////////////////////////////////////////////// // // 关系比较函数, 不直接在类中定义是为了支持 Null<>() == d,Null可以放在左边 // /////////////////////////////////////////////////////////////////////////////// -bool operator==(const Datetime&, const Datetime&); -bool operator!=(const Datetime&, const Datetime&); -bool operator>(const Datetime&, const Datetime&); -bool operator<(const Datetime&, const Datetime&); -bool operator>=(const Datetime&, const Datetime&); -bool operator<=(const Datetime&, const Datetime&); +bool operator==(const Datetime &, const Datetime &); +bool operator!=(const Datetime &, const Datetime &); +bool operator>(const Datetime &, const Datetime &); +bool operator<(const Datetime &, const Datetime &); +bool operator>=(const Datetime &, const Datetime &); +bool operator<=(const Datetime &, const Datetime &); -inline bool operator==(const Datetime& d1, const Datetime& d2) { +inline bool operator==(const Datetime &d1, const Datetime &d2) { return d1.ptime() == d2.ptime(); } -inline bool operator!=(const Datetime& d1, const Datetime& d2) { +inline bool operator!=(const Datetime &d1, const Datetime &d2) { return d1.ptime() != d2.ptime(); } -inline bool operator>(const Datetime& d1, const Datetime& d2) { +inline bool operator>(const Datetime &d1, const Datetime &d2) { return d1.ptime() > d2.ptime(); } -inline bool operator<(const Datetime& d1, const Datetime& d2) { +inline bool operator<(const Datetime &d1, const Datetime &d2) { return d1.ptime() < d2.ptime(); } -inline bool operator>=(const Datetime& d1, const Datetime& d2) { +inline bool operator>=(const Datetime &d1, const Datetime &d2) { return d1.ptime() >= d2.ptime(); } -inline bool operator<=(const Datetime& d1, const Datetime& d2) { +inline bool operator<=(const Datetime &d1, const Datetime &d2) { return d1.ptime() <= d2.ptime(); } @@ -340,11 +345,11 @@ inline bool operator<=(const Datetime& d1, const Datetime& d2) { // 加、减法运算补充 // /////////////////////////////////////////////////////////////////////////////// -inline Datetime operator+(const TimeDelta& delta, const Datetime& date) { +inline Datetime operator+(const TimeDelta &delta, const Datetime &date) { return date + delta; } -inline TimeDelta operator-(const Datetime& d1, const Datetime& d2) { +inline TimeDelta operator-(const Datetime &d1, const Datetime &d2) { return TimeDelta(d1.ptime() - d2.ptime()); } @@ -359,11 +364,11 @@ inline Datetime::Datetime() { m_data = bt::ptime(d, bt::time_duration(0, 0, 0)); } -inline Datetime::Datetime(const Datetime& d) : m_data(d.m_data) {} +inline Datetime::Datetime(const Datetime &d) : m_data(d.m_data) {} -inline Datetime::Datetime(const bd::date& d) : m_data(bt::ptime(d, bt::time_duration(0, 0, 0))) {} +inline Datetime::Datetime(const bd::date &d) : m_data(bt::ptime(d, bt::time_duration(0, 0, 0))) {} -inline Datetime::Datetime(const bt::ptime& d) : m_data(d) {} +inline Datetime::Datetime(const bt::ptime &d) : m_data(d) {} inline bt::ptime Datetime::ptime() const { return m_data; @@ -404,21 +409,26 @@ namespace std { template <> class hash { public: - size_t operator()(hku::Datetime const& d) const noexcept { + size_t operator()(hku::Datetime const &d) const noexcept { return d.ticks(); // or use boost::hash_combine } }; + +inline string to_string(const hku::Datetime &date) { + return date.str(); +} + } // namespace std #if FMT_VERSION >= 90000 template <> struct fmt::formatter { - constexpr auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { + constexpr auto parse(format_parse_context &ctx) -> decltype(ctx.begin()) { return ctx.end(); } template - auto format(const hku::Datetime& d, FormatContext& ctx) const -> decltype(ctx.out()) { + auto format(const hku::Datetime &d, FormatContext &ctx) const -> decltype(ctx.out()) { return fmt::format_to(ctx.out(), "{}", d.str()); } }; diff --git a/hikyuu_cpp/hikyuu/utilities/datetime/TimeDelta.cpp b/hikyuu_cpp/hikyuu/utilities/datetime/TimeDelta.cpp index 9313b429..73c2c6b3 100644 --- a/hikyuu_cpp/hikyuu/utilities/datetime/TimeDelta.cpp +++ b/hikyuu_cpp/hikyuu/utilities/datetime/TimeDelta.cpp @@ -10,7 +10,7 @@ #include #include "TimeDelta.h" #include "hikyuu/utilities/arithmetic.h" -#include "hikyuu/Log.h" +#include "hikyuu/utilities/Log.h" namespace hku { @@ -39,6 +39,24 @@ TimeDelta::TimeDelta(bt::time_duration td) { m_duration = td; } +/** 从字符串构造,格式:-1 days, hh:mm:ss.000000) */ +TimeDelta::TimeDelta(const std::string& delta) { + std::string val(delta); + std::string errmsg(fmt::format("Invalid format: {}", delta)); + to_lower(val); + auto vals = split(val, ' '); + HKU_CHECK(vals.size() == 3, errmsg); + int64_t days = std::stoll(std::string(vals[0])); + vals = split(vals[2], ':'); + HKU_CHECK(vals.size() == 3, errmsg); + int64_t hours = std::stoll(std::string(vals[0])); + int64_t minutes = std::stoll(std::string(vals[1])); + int64_t microseconds = static_cast(std::stod(std::string(vals[2])) * 1000000.0); + int64_t total = (((days * 24) + hours) * 60 + minutes) * 60000000LL + microseconds; + HKU_CHECK(total >= m_min_micro_seconds && total <= m_max_micro_seconds, "Out of total range!"); + m_duration = bt::time_duration(0, 0, 0, total); +} + TimeDelta TimeDelta::fromTicks(int64_t ticks) { HKU_CHECK(ticks >= m_min_micro_seconds && ticks <= m_max_micro_seconds, "Out of total range!"); return TimeDelta(bt::time_duration(0, 0, 0, ticks)); @@ -120,28 +138,28 @@ int64_t TimeDelta::microseconds() const { return std::abs(ticks() % 1000); } -TimeDelta HKU_API Hours(int64_t hours) { +TimeDelta HKU_UTILS_API Hours(int64_t hours) { HKU_CHECK(hours >= TimeDelta::minTicks() / 3600000000LL && hours <= TimeDelta::maxTicks() / 3600000000LL, "Out of total range!"); return TimeDelta::fromTicks(hours * 3600000000LL); } -TimeDelta HKU_API Minutes(int64_t mins) { +TimeDelta HKU_UTILS_API Minutes(int64_t mins) { HKU_CHECK( mins >= TimeDelta::minTicks() / 60000000LL && mins <= TimeDelta::maxTicks() / 60000000LL, "Out of total range!"); return TimeDelta::fromTicks(mins * 60000000LL); } -TimeDelta HKU_API Seconds(int64_t secs) { +TimeDelta HKU_UTILS_API Seconds(int64_t secs) { HKU_CHECK( secs >= TimeDelta::minTicks() / 1000000LL && secs <= TimeDelta::maxTicks() / 1000000LL, "Out of total range!"); return TimeDelta::fromTicks(secs * 1000000LL); } -TimeDelta HKU_API Milliseconds(int64_t milliseconds) { +TimeDelta HKU_UTILS_API Milliseconds(int64_t milliseconds) { HKU_CHECK(milliseconds >= TimeDelta::minTicks() / 1000LL && milliseconds <= TimeDelta::maxTicks() / 1000LL, "Out of total range!"); @@ -149,12 +167,12 @@ TimeDelta HKU_API Milliseconds(int64_t milliseconds) { } TimeDelta TimeDelta::operator*(double p) const { - return TimeDelta::fromTicks(static_cast(roundEx(double(ticks()) * p, 0))); + return TimeDelta::fromTicks(static_cast(roundEx(double(ticks()) * p, 0))); } TimeDelta TimeDelta::operator/(double p) const { HKU_CHECK(p != 0, "Attempt to divide by 0!"); - return TimeDelta::fromTicks(static_cast(roundEx(double(ticks()) / p, 0))); + return TimeDelta::fromTicks(static_cast(roundEx(double(ticks()) / p, 0))); } TimeDelta TimeDelta::floorDiv(double p) const { diff --git a/hikyuu_cpp/hikyuu/utilities/datetime/TimeDelta.h b/hikyuu_cpp/hikyuu/utilities/datetime/TimeDelta.h index 802e182c..510c0dd6 100644 --- a/hikyuu_cpp/hikyuu/utilities/datetime/TimeDelta.h +++ b/hikyuu_cpp/hikyuu/utilities/datetime/TimeDelta.h @@ -20,8 +20,8 @@ #include #include -#ifndef HKU_API -#define HKU_API +#ifndef HKU_UTILS_API +#define HKU_UTILS_API #endif namespace hku { @@ -33,7 +33,7 @@ namespace bd = boost::gregorian; * 时长,用于时间计算 * @ingroup DataType */ -class HKU_API TimeDelta { +class HKU_UTILS_API TimeDelta { public: /** * 构造函数 @@ -58,11 +58,14 @@ public: /** 通过 boost::posix_time::time_duration 构造 */ explicit TimeDelta(bt::time_duration td); + /** 从字符串构造,格式:-1 days, hh:mm:ss.000000) */ + explicit TimeDelta(const std::string& delta); + /** 赋值构造函数 */ - TimeDelta(const TimeDelta&) = default; + TimeDelta(const TimeDelta &) = default; /** 赋值拷贝函数 */ - TimeDelta& operator=(const TimeDelta& other) { + TimeDelta &operator=(const TimeDelta &other) { if (this != &other) { m_duration = other.m_duration; } @@ -259,8 +262,8 @@ private: static constexpr const int64_t m_one_day_ticks = 24 * 60 * 60 * 1000000LL; }; -std::ostream& operator<<(std::ostream& out, TimeDelta td); -inline std::ostream& operator<<(std::ostream& out, TimeDelta td) { +std::ostream &operator<<(std::ostream &out, TimeDelta td); +inline std::ostream &operator<<(std::ostream &out, TimeDelta td) { out << td.str(); return out; } @@ -280,28 +283,28 @@ inline TimeDelta Days(int64_t days) { * @param hours 小时数 * @ingroup DataType */ -TimeDelta HKU_API Hours(int64_t hours); +TimeDelta HKU_UTILS_API Hours(int64_t hours); /** * TimeDelta 快捷创建函数 * @param mins 分钟数 * @ingroup DataType */ -TimeDelta HKU_API Minutes(int64_t mins); +TimeDelta HKU_UTILS_API Minutes(int64_t mins); /** * TimeDelta 快捷创建函数 * @param secs 秒数 * @ingroup DataType */ -TimeDelta HKU_API Seconds(int64_t secs); +TimeDelta HKU_UTILS_API Seconds(int64_t secs); /** * TimeDelta 快捷创建函数 * @param milliseconds 毫秒数 * @ingroup DataType */ -TimeDelta HKU_API Milliseconds(int64_t milliseconds); +TimeDelta HKU_UTILS_API Milliseconds(int64_t milliseconds); /** * TimeDelta 快捷创建函数 @@ -315,15 +318,23 @@ inline TimeDelta Microseconds(int64_t microsecs) { } /* namespace hku */ +namespace std { + +inline string to_string(const hku::TimeDelta &delta) { + return delta.str(); +} + +} // namespace std + #if FMT_VERSION >= 90000 template <> struct fmt::formatter { - constexpr auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { + constexpr auto parse(format_parse_context &ctx) -> decltype(ctx.begin()) { return ctx.end(); } template - auto format(const hku::TimeDelta& d, FormatContext& ctx) const -> decltype(ctx.out()) { + auto format(const hku::TimeDelta &d, FormatContext &ctx) const -> decltype(ctx.out()) { return fmt::format_to(ctx.out(), "{}", d.str()); } }; diff --git a/hikyuu_cpp/hikyuu/utilities/db_connect/DBCondition.h b/hikyuu_cpp/hikyuu/utilities/db_connect/DBCondition.h index a9a0b166..59898048 100644 --- a/hikyuu_cpp/hikyuu/utilities/db_connect/DBCondition.h +++ b/hikyuu_cpp/hikyuu/utilities/db_connect/DBCondition.h @@ -13,23 +13,23 @@ #include #include #include -#include "../../Log.h" +#include "hikyuu/utilities/Log.h" -#ifndef HKU_API -#define HKU_API +#ifndef HKU_UTILS_API +#define HKU_UTILS_API #endif namespace hku { struct ASC { - explicit ASC(const char* name) : name(name) {} - explicit ASC(const std::string& name) : name(name) {} + explicit ASC(const char *name) : name(name) {} + explicit ASC(const std::string &name) : name(name) {} std::string name; }; struct DESC { - explicit DESC(const char* name) : name(name) {} - explicit DESC(const std::string& name) : name(name) {} + explicit DESC(const char *name) : name(name) {} + explicit DESC(const std::string &name) : name(name) {} std::string name; }; @@ -38,50 +38,50 @@ struct LIMIT { int limit = 1; }; -class HKU_API DBCondition { +class HKU_UTILS_API DBCondition { public: DBCondition() = default; - DBCondition(const DBCondition&) = default; - DBCondition(DBCondition&& rv) : m_condition(std::move(rv.m_condition)) {} + DBCondition(const DBCondition &) = default; + DBCondition(DBCondition &&rv) : m_condition(std::move(rv.m_condition)) {} - explicit DBCondition(const char* cond) : m_condition(cond) {} - explicit DBCondition(const std::string& cond) : m_condition(cond) {} + explicit DBCondition(const char *cond) : m_condition(cond) {} + explicit DBCondition(const std::string &cond) : m_condition(cond) {} - DBCondition& operator=(const DBCondition&) = default; - DBCondition& operator=(DBCondition&& rv) { + DBCondition &operator=(const DBCondition &) = default; + DBCondition &operator=(DBCondition &&rv) { if (this != &rv) { m_condition = std::move(rv.m_condition); } return *this; } - DBCondition& operator&(const DBCondition& other); - DBCondition& operator|(const DBCondition& other); + DBCondition &operator&(const DBCondition &other); + DBCondition &operator|(const DBCondition &other); enum ORDERBY { ORDER_ASC, ORDER_DESC }; - void orderBy(const std::string& field, ORDERBY order) { + void orderBy(const std::string &field, ORDERBY order) { m_condition = order == ORDERBY::ORDER_ASC ? fmt::format("{} order by {} ASC", m_condition, field) : fmt::format("{} order by {} DESC", m_condition, field); } - DBCondition& operator+(const ASC& asc) { + DBCondition &operator+(const ASC &asc) { orderBy(asc.name, ORDER_ASC); return *this; } - DBCondition& operator+(const DESC& desc) { + DBCondition &operator+(const DESC &desc) { orderBy(desc.name, ORDER_DESC); return *this; } - DBCondition& operator+(const LIMIT& limit) { + DBCondition &operator+(const LIMIT &limit) { m_condition = fmt::format("{} limit {}", m_condition, limit.limit); return *this; } - const std::string& str() const { + const std::string &str() const { return m_condition; } @@ -90,27 +90,27 @@ private: }; struct Field { - explicit Field(const char* name) : name(name) {} - explicit Field(const std::string& name) : name(name) {} + explicit Field(const char *name) : name(name) {} + explicit Field(const std::string &name) : name(name) {} // in 和 not_in 不支持 字符串,一般不会用到 in ("stra", "strb") 的 SQL 操作 template - DBCondition in(const std::vector& vals) { + DBCondition in(const std::vector &vals) { HKU_CHECK(!vals.empty(), "input vals can't be empty!"); return DBCondition(fmt::format("({} in ({}))", name, fmt::join(vals, ","))); } template - DBCondition not_in(const std::vector& vals) { + DBCondition not_in(const std::vector &vals) { HKU_CHECK(!vals.empty(), "input vals can't be empty!"); return DBCondition(fmt::format("({} not in ({}))", name, fmt::join(vals, ","))); } - DBCondition like(const std::string& pattern) { + DBCondition like(const std::string &pattern) { return DBCondition(fmt::format(R"(({} like "{}"))", name, pattern)); } - DBCondition like(const char* pattern) { + DBCondition like(const char *pattern) { return DBCondition(fmt::format(R"(({} like "{}"))", name, pattern)); } @@ -120,7 +120,7 @@ struct Field { // linux下类成员函数模板特化必须放在类外实现 // 否则编译时会报:explicit specialization in non-namespace scope template <> -inline DBCondition Field::in(const std::vector& vals) { +inline DBCondition Field::in(const std::vector &vals) { HKU_CHECK(!vals.empty(), "input vals can't be empty!"); std::ostringstream out; out << "(" << name << " in ("; @@ -133,7 +133,7 @@ inline DBCondition Field::in(const std::vector& vals) } template <> -inline DBCondition Field::not_in(const std::vector& vals) { +inline DBCondition Field::not_in(const std::vector &vals) { HKU_CHECK(!vals.empty(), "input vals can't be empty!"); std::ostringstream out; out << "(" << name << " not in ("; @@ -145,103 +145,103 @@ inline DBCondition Field::not_in(const std::vector& va return DBCondition(out.str()); } -inline std::ostream& operator<<(std::ostream& out, const DBCondition& d) { +inline std::ostream &operator<<(std::ostream &out, const DBCondition &d) { out << d.str(); return out; } template -inline DBCondition operator==(const Field& field, T val) { +inline DBCondition operator==(const Field &field, T val) { std::ostringstream out; out << "(" << field.name << "=" << val << ")"; return DBCondition(out.str()); } template -inline DBCondition operator!=(const Field& field, T val) { +inline DBCondition operator!=(const Field &field, T val) { std::ostringstream out; out << "(" << field.name << "<>" << val << ")"; return DBCondition(out.str()); } template -inline DBCondition operator>(const Field& field, T val) { +inline DBCondition operator>(const Field &field, T val) { std::ostringstream out; out << "(" << field.name << ">" << val << ")"; return DBCondition(out.str()); } template -inline DBCondition operator>=(const Field& field, T val) { +inline DBCondition operator>=(const Field &field, T val) { std::ostringstream out; out << "(" << field.name << ">=" << val << ")"; return DBCondition(out.str()); } template -inline DBCondition operator<(const Field& field, T val) { +inline DBCondition operator<(const Field &field, T val) { std::ostringstream out; out << "(" << field.name << "<" << val << ")"; return DBCondition(out.str()); } template -inline DBCondition operator<=(const Field& field, T val) { +inline DBCondition operator<=(const Field &field, T val) { std::ostringstream out; out << "(" << field.name << "<=" << val << ")"; return DBCondition(out.str()); } template <> -inline DBCondition operator!=(const Field& field, const char* val) { +inline DBCondition operator!=(const Field &field, const char *val) { return DBCondition(fmt::format(R"(({}<>"{}"))", field.name, val)); } template <> -inline DBCondition operator>(const Field& field, const char* val) { +inline DBCondition operator>(const Field &field, const char *val) { return DBCondition(fmt::format(R"(({}>"{}"))", field.name, val)); } template <> -inline DBCondition operator<(const Field& field, const char* val) { +inline DBCondition operator<(const Field &field, const char *val) { return DBCondition(fmt::format(R"(({}<"{}"))", field.name, val)); } template <> -inline DBCondition operator>=(const Field& field, const char* val) { +inline DBCondition operator>=(const Field &field, const char *val) { return DBCondition(fmt::format(R"(({}>="{}"))", field.name, val)); } template <> -inline DBCondition operator<=(const Field& field, const char* val) { +inline DBCondition operator<=(const Field &field, const char *val) { return DBCondition(fmt::format(R"(({}<="{}"))", field.name, val)); } -inline DBCondition operator==(const Field& field, const std::string& val) { +inline DBCondition operator==(const Field &field, const std::string &val) { return DBCondition(fmt::format(R"(({}="{}"))", field.name, val)); } -inline DBCondition operator!=(const Field& field, const std::string& val) { +inline DBCondition operator!=(const Field &field, const std::string &val) { return DBCondition(fmt::format(R"(({}<>"{}"))", field.name, val)); } -inline DBCondition operator>(const Field& field, const std::string& val) { +inline DBCondition operator>(const Field &field, const std::string &val) { return DBCondition(fmt::format(R"(({}>"{}"))", field.name, val)); } -inline DBCondition operator<(const Field& field, const std::string& val) { +inline DBCondition operator<(const Field &field, const std::string &val) { return DBCondition(fmt::format(R"(({}<"{}"))", field.name, val)); } -inline DBCondition operator>=(const Field& field, const std::string& val) { +inline DBCondition operator>=(const Field &field, const std::string &val) { return DBCondition(fmt::format(R"(({}>="{}"))", field.name, val)); } -inline DBCondition operator<=(const Field& field, const std::string& val) { +inline DBCondition operator<=(const Field &field, const std::string &val) { return DBCondition(fmt::format(R"(({}<="{}"))", field.name, val)); } -inline DBCondition operator==(const Field& field, const char* val) { +inline DBCondition operator==(const Field &field, const char *val) { return DBCondition(fmt::format(R"(({}="{}"))", field.name, val)); } diff --git a/hikyuu_cpp/hikyuu/utilities/db_connect/DBConnect.h b/hikyuu_cpp/hikyuu/utilities/db_connect/DBConnect.h index 30d8a9ee..e0a33ae7 100644 --- a/hikyuu_cpp/hikyuu/utilities/db_connect/DBConnect.h +++ b/hikyuu_cpp/hikyuu/utilities/db_connect/DBConnect.h @@ -17,4 +17,14 @@ #include "TableMacro.h" #include "DBUpgrade.h" +#include "hikyuu/utilities/config.h" +#if HKU_ENABLE_MYSQL +#include "mysql/MySQLConnect.h" +#endif + +#if HKU_ENABLE_SQLITE +#include "sqlite/SQLiteConnect.h" +#include "sqlite/SQLiteUtil.h" +#endif + #endif /* HIKYUU_DB_CONNECT_H */ \ No newline at end of file diff --git a/hikyuu_cpp/hikyuu/utilities/db_connect/DBConnectBase.h b/hikyuu_cpp/hikyuu/utilities/db_connect/DBConnectBase.h index a15f04eb..fb6ea81b 100644 --- a/hikyuu_cpp/hikyuu/utilities/db_connect/DBConnectBase.h +++ b/hikyuu_cpp/hikyuu/utilities/db_connect/DBConnectBase.h @@ -10,8 +10,8 @@ #ifndef HIKYUU_DB_CONNECT_DBCONNECTBASE_H #define HIKYUU_DB_CONNECT_DBCONNECTBASE_H -#include "../../DataType.h" #include "../../utilities/Parameter.h" +#include "../Null.h" #include "DBCondition.h" #include "SQLStatementBase.h" #include "SQLException.h" @@ -25,7 +25,7 @@ class SQLResultSet; * 数据库连接基类 * @ingroup DBConnect */ -class HKU_API DBConnectBase : public std::enable_shared_from_this { +class HKU_UTILS_API DBConnectBase : public std::enable_shared_from_this { PARAMETER_SUPPORT // NOSONAR public : @@ -297,7 +297,7 @@ private: }; /** @ingroup DBConnect */ -typedef shared_ptr DBConnectPtr; +typedef std::shared_ptr DBConnectPtr; //------------------------------------------------------------------------- // inline方法实现 @@ -366,19 +366,16 @@ void DBConnectBase::save(T &item, bool autotrans) { if (autotrans) { commit(); } - } catch (hku::SQLException &e) { if (autotrans) { rollback(); } SQL_THROW(e.errcode(), "failed save! sql: {}! {}", st->getSqlString(), e.what()); - } catch (std::exception &e) { if (autotrans) { rollback(); } HKU_THROW("failed save! sql: {}! {}", st->getSqlString(), e.what()); - } catch (...) { if (autotrans) { rollback(); @@ -409,19 +406,16 @@ void DBConnectBase::batchSave(InputIterator first, InputIterator last, bool auto if (autotrans) { commit(); } - } catch (hku::SQLException &e) { if (autotrans) { rollback(); } SQL_THROW(e.errcode(), "failed batch save! sql: {}! {}", st->getSqlString(), e.what()); - } catch (std::exception &e) { if (autotrans) { rollback(); } HKU_THROW("failed batch save! sql: {}! {}", st->getSqlString(), e.what()); - } catch (...) { if (autotrans) { rollback(); @@ -431,7 +425,7 @@ void DBConnectBase::batchSave(InputIterator first, InputIterator last, bool auto } template -void DBConnectBase::load(T &item, const string &where) { +void DBConnectBase::load(T &item, const std::string &where) { std::ostringstream sql; if (where != "") { sql << T::getSelectSQL() << " where " << where << " limit 1"; @@ -451,7 +445,7 @@ void DBConnectBase::load(T &item, const DBCondition &cond) { } template -void DBConnectBase::batchLoad(Container &container, const string &where) { +void DBConnectBase::batchLoad(Container &container, const std::string &where) { std::ostringstream sql; if (where != "") { sql << Container::value_type::getSelectSQL() << " where " << where; @@ -513,19 +507,16 @@ void DBConnectBase::batchUpdate(InputIterator first, InputIterator last, bool au if (autotrans) { commit(); } - } catch (hku::SQLException &e) { if (autotrans) { rollback(); } SQL_THROW(e.errcode(), "failed batch save! sql: {}! {}", st->getSqlString(), e.what()); - } catch (std::exception &e) { if (autotrans) { rollback(); } HKU_THROW("failed batch update! sql: {}! {}", st->getSqlString(), e.what()); - } catch (...) { if (autotrans) { rollback(); @@ -570,19 +561,16 @@ void DBConnectBase::remove(T &item, bool autotrans) { commit(); } item.rowid(0); - } catch (hku::SQLException &e) { if (autotrans) { rollback(); } SQL_THROW(e.errcode(), "failed delete! sql: {}! {}", st->getSqlString(), e.what()); - } catch (std::exception &e) { if (autotrans) { rollback(); } HKU_THROW("failed delete! sql: {}! {}", st->getSqlString(), e.what()); - } catch (...) { if (autotrans) { rollback(); @@ -610,19 +598,16 @@ void DBConnectBase::batchRemove(InputIterator first, InputIterator last, bool au if (autotrans) { commit(); } - } catch (hku::SQLException &e) { if (autotrans) { rollback(); } SQL_THROW(e.errcode(), "failed batch delete! {}", e.what()); - } catch (std::exception &e) { if (autotrans) { rollback(); } HKU_THROW("failed batch delete! {}", e.what()); - } catch (...) { if (autotrans) { rollback(); @@ -645,13 +630,11 @@ inline void DBConnectBase::remove(const std::string &tablename, const std::strin if (autotrans) { commit(); } - } catch (hku::SQLException &e) { if (autotrans) { rollback(); } SQL_THROW(e.errcode(), "Failed exec sql: {}! {}", sql, e.what()); - } catch (std::exception &e) { if (autotrans) { rollback(); diff --git a/hikyuu_cpp/hikyuu/utilities/db_connect/DBUpgrade.cpp b/hikyuu_cpp/hikyuu/utilities/db_connect/DBUpgrade.cpp index 1302751a..b1d3b785 100644 --- a/hikyuu_cpp/hikyuu/utilities/db_connect/DBUpgrade.cpp +++ b/hikyuu_cpp/hikyuu/utilities/db_connect/DBUpgrade.cpp @@ -6,23 +6,51 @@ */ #include "DBUpgrade.h" -#include "../../debug.h" + +#include "hikyuu/utilities/config.h" +#if HKU_ENABLE_MYSQL +#include "mysql/MySQLConnect.h" +#endif + +#if HKU_ENABLE_SQLITE +#include "sqlite/SQLiteConnect.h" +#endif namespace hku { /* * 升级和创建数据库 */ -void HKU_API DBUpgrade(const DBConnectPtr &driver, const char *module_name, - const std::vector &upgrade_scripts, int start_version, - const char *create_script) { +void HKU_UTILS_API DBUpgrade(const DBConnectPtr &driver, const char *module_name, + const std::vector &upgrade_scripts, int start_version, + const char *create_script) { HKU_TRACE("check {} database version ...", module_name); // 如果模块版本表不存在,则创建该表 if (!driver->tableExist("module_version")) { - driver->exec( - "CREATE TABLE `module_version` (`id` INTEGER PRIMARY KEY AUTOINCREMENT,`module` TEXT, " - "`version` INTEGER NOT NULL);"); + bool need_create = true; +#if HKU_ENABLE_SQLITE + if (need_create && typeid(driver.get()) == typeid(SQLiteConnect *)) { + driver->exec( + "CREATE TABLE `module_version` (`id` INTEGER PRIMARY KEY AUTOINCREMENT,`module` " + "TEXT, " + "`version` INTEGER NOT NULL);"); + need_create = false; + } +#endif + +#if HKU_ENABLE_MYSQL + if (need_create && typeid(driver.get()) == typeid(MySQLConnect *)) { + driver->exec( + R"(CREATE TABLE `module_version` ( + `id` int NOT NULL AUTO_INCREMENT, + `module` varchar(20) DEFAULT NULL, + `version` int NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;)"); + need_create = false; + } +#endif } // 如果没有升级脚本,也没有创建脚本,则直接返回 @@ -75,7 +103,7 @@ void HKU_API DBUpgrade(const DBConnectPtr &driver, const char *module_name, // 当前版本已经大于等于待升至的版本,无需升级,直接返回 if (version >= to_version) { HKU_TRACE("current version({}) greater the upgrade version({}), ignored!", version, - to_version); + to_version); return; } diff --git a/hikyuu_cpp/hikyuu/utilities/db_connect/DBUpgrade.h b/hikyuu_cpp/hikyuu/utilities/db_connect/DBUpgrade.h index 883fa1d7..dd70ea46 100644 --- a/hikyuu_cpp/hikyuu/utilities/db_connect/DBUpgrade.h +++ b/hikyuu_cpp/hikyuu/utilities/db_connect/DBUpgrade.h @@ -21,8 +21,8 @@ namespace hku { * @param create_script 数据库创建脚本,若对应的数据库不存在则使用该脚本创建数据库 * @ingroup DataDriver */ -void HKU_API DBUpgrade(const DBConnectPtr &driver, const char *module_name, - const std::vector &upgrade_scripts, int start_version = 2, - const char *create_script = nullptr); +void HKU_UTILS_API DBUpgrade(const DBConnectPtr &driver, const char *module_name, + const std::vector &upgrade_scripts, int start_version = 2, + const char *create_script = nullptr); } // namespace hku \ No newline at end of file diff --git a/hikyuu_cpp/hikyuu/utilities/db_connect/SQLException.h b/hikyuu_cpp/hikyuu/utilities/db_connect/SQLException.h index ef43de49..445917f4 100644 --- a/hikyuu_cpp/hikyuu/utilities/db_connect/SQLException.h +++ b/hikyuu_cpp/hikyuu/utilities/db_connect/SQLException.h @@ -8,7 +8,7 @@ #pragma once #include -#include "hikyuu/exception.h" +#include "hikyuu/utilities/exception.h" namespace hku { diff --git a/hikyuu_cpp/hikyuu/utilities/db_connect/SQLResultSet.h b/hikyuu_cpp/hikyuu/utilities/db_connect/SQLResultSet.h index 812320e3..6ff58ec7 100644 --- a/hikyuu_cpp/hikyuu/utilities/db_connect/SQLResultSet.h +++ b/hikyuu_cpp/hikyuu/utilities/db_connect/SQLResultSet.h @@ -9,7 +9,7 @@ #include #include "hikyuu/utilities/arithmetic.h" -#include "hikyuu/Log.h" +#include "hikyuu/utilities/Log.h" #include "hikyuu/utilities/osdef.h" #include "DBConnectBase.h" diff --git a/hikyuu_cpp/hikyuu/utilities/db_connect/SQLStatementBase.h b/hikyuu_cpp/hikyuu/utilities/db_connect/SQLStatementBase.h index ecc1ace3..a76580c1 100644 --- a/hikyuu_cpp/hikyuu/utilities/db_connect/SQLStatementBase.h +++ b/hikyuu_cpp/hikyuu/utilities/db_connect/SQLStatementBase.h @@ -21,7 +21,7 @@ namespace hku { class DBConnectBase; /** @ingroup DBConnect */ -typedef shared_ptr DBConnectPtr; +typedef std::shared_ptr DBConnectPtr; /** @ingroup DBConnect */ class null_blob_exception : public exception { @@ -34,19 +34,19 @@ public: * SQL Statement 基类 * @ingroup DBConnect */ -class HKU_API SQLStatementBase { +class HKU_UTILS_API SQLStatementBase { public: /** * 构造函数 * @param driver 数据库连接 * @param sql_statement SQL语句 */ - SQLStatementBase(DBConnectBase *driver, const string &sql_statement); + SQLStatementBase(DBConnectBase *driver, const std::string &sql_statement); virtual ~SQLStatementBase() = default; /** 获取构建时传入的表达式SQL语句 */ - const string &getSqlString() const; + const std::string &getSqlString() const; /** 获取数据驱动 */ DBConnectBase *getConnect() const; @@ -163,7 +163,7 @@ protected: }; /** @ingroup DBConnect */ -typedef shared_ptr SQLStatementPtr; +typedef std::shared_ptr SQLStatementPtr; inline SQLStatementBase ::SQLStatementBase(DBConnectBase *driver, const std::string &sql_statement) : m_driver(driver), m_sql_string(sql_statement) { @@ -183,7 +183,7 @@ inline void SQLStatementBase::bind(int idx, float item) { } inline void SQLStatementBase::exec() { -#ifdef HKU_SQL_TRACE +#if HKU_SQL_TRACE HKU_DEBUG(m_sql_string); #endif sub_exec(); @@ -277,7 +277,7 @@ inline void SQLStatementBase::getColumn(int idx, std::vector &item) { template typename std::enable_if::is_integer>::type SQLStatementBase::getColumn( int idx, T &item) { - string tmp; + std::string tmp; try { sub_getColumnAsBlob(idx, tmp); } catch (null_blob_exception &) { diff --git a/hikyuu_cpp/hikyuu/utilities/db_connect/TableMacro.h b/hikyuu_cpp/hikyuu/utilities/db_connect/TableMacro.h index a6d9b956..b8a4e3eb 100644 --- a/hikyuu_cpp/hikyuu/utilities/db_connect/TableMacro.h +++ b/hikyuu_cpp/hikyuu/utilities/db_connect/TableMacro.h @@ -2168,8 +2168,7 @@ public: } #define TABLE_NO_AUTOID_BIND12(ROWID, table, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12) \ - pprivate: \ - uint64_t m_rowid = 0; \ + pprivate : uint64_t m_rowid = 0; \ \ public: \ bool valid() const { \ diff --git a/hikyuu_cpp/hikyuu/utilities/db_connect/mysql/MySQLConnect.cpp b/hikyuu_cpp/hikyuu/utilities/db_connect/mysql/MySQLConnect.cpp index aa09155c..fbf6a2e9 100755 --- a/hikyuu_cpp/hikyuu/utilities/db_connect/mysql/MySQLConnect.cpp +++ b/hikyuu_cpp/hikyuu/utilities/db_connect/mysql/MySQLConnect.cpp @@ -7,6 +7,7 @@ * Author: fasiondog */ +#include "hikyuu/utilities/config.h" #include "MySQLConnect.h" #ifdef __GNUC__ @@ -110,7 +111,7 @@ bool MySQLConnect::ping() { } int64_t MySQLConnect::exec(const std::string& sql_string) { -#ifdef HKU_SQL_TRACE +#if HKU_SQL_TRACE HKU_DEBUG(sql_string); #endif if (!m_mysql) { @@ -158,11 +159,11 @@ int64_t MySQLConnect::exec(const std::string& sql_string) { return affect_rows; } -SQLStatementPtr MySQLConnect::getStatement(const string& sql_statement) { - return make_shared(this, sql_statement); +SQLStatementPtr MySQLConnect::getStatement(const std::string& sql_statement) { + return std::make_shared(this, sql_statement); } -bool MySQLConnect::tableExist(const string& tablename) { +bool MySQLConnect::tableExist(const std::string& tablename) { bool result = false; try { SQLStatementPtr st = getStatement(fmt::format("SELECT 1 FROM {} LIMIT 1;", tablename)); diff --git a/hikyuu_cpp/hikyuu/utilities/db_connect/mysql/MySQLConnect.h b/hikyuu_cpp/hikyuu/utilities/db_connect/mysql/MySQLConnect.h index 477eabac..bf66202e 100755 --- a/hikyuu_cpp/hikyuu/utilities/db_connect/mysql/MySQLConnect.h +++ b/hikyuu_cpp/hikyuu/utilities/db_connect/mysql/MySQLConnect.h @@ -22,20 +22,20 @@ namespace hku { -class HKU_API MySQLConnect : public DBConnectBase { +class HKU_UTILS_API MySQLConnect : public DBConnectBase { public: - explicit MySQLConnect(const Parameter& param); + explicit MySQLConnect(const Parameter ¶m); virtual ~MySQLConnect(); - MySQLConnect(const MySQLConnect&) = delete; - MySQLConnect& operator=(const MySQLConnect&) = delete; + MySQLConnect(const MySQLConnect &) = delete; + MySQLConnect &operator=(const MySQLConnect &) = delete; virtual bool ping() override; - virtual int64_t exec(const std::string& sql_string) override; - virtual SQLStatementPtr getStatement(const std::string& sql_statement) override; - virtual bool tableExist(const std::string& tablename) override; - virtual void resetAutoIncrement(const std::string& tablename) override; + virtual int64_t exec(const std::string &sql_string) override; + virtual SQLStatementPtr getStatement(const std::string &sql_statement) override; + virtual bool tableExist(const std::string &tablename) override; + virtual void resetAutoIncrement(const std::string &tablename) override; virtual void transaction() noexcept override; virtual void commit() noexcept override; @@ -48,7 +48,7 @@ private: private: friend class MySQLStatement; - MYSQL* m_mysql; + MYSQL *m_mysql; }; } // namespace hku diff --git a/hikyuu_cpp/hikyuu/utilities/db_connect/mysql/MySQLStatement.cpp b/hikyuu_cpp/hikyuu/utilities/db_connect/mysql/MySQLStatement.cpp index 4e6a3a59..8598f661 100755 --- a/hikyuu_cpp/hikyuu/utilities/db_connect/mysql/MySQLStatement.cpp +++ b/hikyuu_cpp/hikyuu/utilities/db_connect/mysql/MySQLStatement.cpp @@ -7,6 +7,7 @@ * Author: fasiondog */ +#include #include "MySQLStatement.h" #include "MySQLConnect.h" @@ -230,7 +231,7 @@ void MySQLStatement::sub_bindDatetime(int idx, const Datetime& item) { m_param_bind[idx].is_null = 0; } -void MySQLStatement::sub_bindText(int idx, const string& item) { +void MySQLStatement::sub_bindText(int idx, const std::string& item) { HKU_CHECK(idx < m_param_bind.size(), "idx out of range! idx: {}, total: {}", idx, m_param_bind.size()); m_param_buffer.push_back(item); @@ -254,7 +255,7 @@ void MySQLStatement::sub_bindText(int idx, const char* item, size_t len) { m_param_bind[idx].is_null = 0; } -void MySQLStatement::sub_bindBlob(int idx, const string& item) { +void MySQLStatement::sub_bindBlob(int idx, const std::string& item) { HKU_CHECK(idx < m_param_bind.size(), "idx out of range! idx: {}, total: {}", idx, m_param_bind.size()); m_param_buffer.push_back(item); @@ -357,7 +358,7 @@ void MySQLStatement::sub_getColumnAsDatetime(int idx, Datetime& item) { } } -void MySQLStatement::sub_getColumnAsText(int idx, string& item) { +void MySQLStatement::sub_getColumnAsText(int idx, std::string& item) { HKU_CHECK(idx < m_result_buffer.size(), "idx out of range! idx: {}, total: {}", m_result_buffer.size()); @@ -369,7 +370,7 @@ void MySQLStatement::sub_getColumnAsText(int idx, string& item) { } try { - vector* p = boost::any_cast>(&(m_result_buffer[idx])); + std::vector* p = boost::any_cast>(&(m_result_buffer[idx])); std::ostringstream buf; for (unsigned long i = 0; i < m_result_length[idx]; i++) { buf << (*p)[i]; @@ -380,7 +381,7 @@ void MySQLStatement::sub_getColumnAsText(int idx, string& item) { } } -void MySQLStatement::sub_getColumnAsBlob(int idx, string& item) { +void MySQLStatement::sub_getColumnAsBlob(int idx, std::string& item) { HKU_CHECK(idx < m_result_buffer.size(), "idx out of range! idx: {}, total: {}", m_result_buffer.size()); @@ -392,7 +393,7 @@ void MySQLStatement::sub_getColumnAsBlob(int idx, string& item) { } try { - vector* p = boost::any_cast>(&m_result_buffer[idx]); + std::vector* p = boost::any_cast>(&m_result_buffer[idx]); std::ostringstream buf; for (unsigned long i = 0; i < m_result_length[idx]; i++) { buf << (*p)[i]; @@ -415,7 +416,11 @@ void MySQLStatement::sub_getColumnAsBlob(int idx, std::vector& item) { } try { - item = boost::any_cast>(m_result_buffer[idx]); + unsigned long len = m_result_length[idx]; + std::vector* p = boost::any_cast>(&m_result_buffer[idx]); + item.resize(len); + memcpy(item.data(), p->data(), len); + } catch (...) { HKU_THROW("Field type mismatch! idx: {}", idx); } diff --git a/hikyuu_cpp/hikyuu/utilities/db_connect/mysql/MySQLStatement.h b/hikyuu_cpp/hikyuu/utilities/db_connect/mysql/MySQLStatement.h index 66b6a111..3df3a012 100755 --- a/hikyuu_cpp/hikyuu/utilities/db_connect/mysql/MySQLStatement.h +++ b/hikyuu_cpp/hikyuu/utilities/db_connect/mysql/MySQLStatement.h @@ -26,16 +26,16 @@ typedef bool my_bool; #endif -#ifndef HKU_API -#define HKU_API +#ifndef HKU_UTILS_API +#define HKU_UTILS_API #endif namespace hku { -class HKU_API MySQLStatement : public SQLStatementBase { +class HKU_UTILS_API MySQLStatement : public SQLStatementBase { public: MySQLStatement() = delete; - MySQLStatement(DBConnectBase* driver, const std::string& sql_statement); + MySQLStatement(DBConnectBase *driver, const std::string &sql_statement); virtual ~MySQLStatement(); virtual void sub_exec() override; @@ -45,28 +45,28 @@ public: virtual void sub_bindNull(int idx) override; virtual void sub_bindInt(int idx, int64_t value) override; virtual void sub_bindDouble(int idx, double item) override; - virtual void sub_bindDatetime(int idx, const Datetime& item) override; - virtual void sub_bindText(int idx, const std::string& item) override; - virtual void sub_bindText(int idx, const char* item, size_t len) override; - virtual void sub_bindBlob(int idx, const std::string& item) override; - virtual void sub_bindBlob(int idx, const std::vector& item) override; + virtual void sub_bindDatetime(int idx, const Datetime &item) override; + virtual void sub_bindText(int idx, const std::string &item) override; + virtual void sub_bindText(int idx, const char *item, size_t len) override; + virtual void sub_bindBlob(int idx, const std::string &item) override; + virtual void sub_bindBlob(int idx, const std::vector &item) override; virtual int sub_getNumColumns() const override; - virtual void sub_getColumnAsInt64(int idx, int64_t& item) override; - virtual void sub_getColumnAsDouble(int idx, double& item) override; - virtual void sub_getColumnAsDatetime(int idx, Datetime& item) override; - virtual void sub_getColumnAsText(int idx, std::string& item) override; - virtual void sub_getColumnAsBlob(int idx, std::string& item) override; - virtual void sub_getColumnAsBlob(int idx, std::vector& item) override; + virtual void sub_getColumnAsInt64(int idx, int64_t &item) override; + virtual void sub_getColumnAsDouble(int idx, double &item) override; + virtual void sub_getColumnAsDatetime(int idx, Datetime &item) override; + virtual void sub_getColumnAsText(int idx, std::string &item) override; + virtual void sub_getColumnAsBlob(int idx, std::string &item) override; + virtual void sub_getColumnAsBlob(int idx, std::vector &item) override; private: void _reset(); void _bindResult(); private: - MYSQL* m_db; - MYSQL_STMT* m_stmt; - MYSQL_RES* m_meta_result; + MYSQL *m_db; + MYSQL_STMT *m_stmt; + MYSQL_RES *m_meta_result; bool m_needs_reset; bool m_has_bind_result; std::vector m_param_bind; diff --git a/hikyuu_cpp/hikyuu/utilities/db_connect/sqlite/SQLiteConnect.cpp b/hikyuu_cpp/hikyuu/utilities/db_connect/sqlite/SQLiteConnect.cpp index 2804a25f..b8ae369b 100644 --- a/hikyuu_cpp/hikyuu/utilities/db_connect/sqlite/SQLiteConnect.cpp +++ b/hikyuu_cpp/hikyuu/utilities/db_connect/sqlite/SQLiteConnect.cpp @@ -8,8 +8,8 @@ */ #include -#include "../../../config.h" -#include "../../../Log.h" +#include "hikyuu/utilities/config.h" +#include "hikyuu/utilities/Log.h" #include "SQLiteConnect.h" #include "SQLiteStatement.h" @@ -23,7 +23,7 @@ static int sqlite_busy_call_back(void *ptr, int count) { SQLiteConnect::SQLiteConnect(const Parameter ¶m) : DBConnectBase(param), m_db(nullptr) { try { - m_dbname = getParam("db"); + m_dbname = getParam("db"); int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_NOMUTEX; // 多线程模式下,不同数据库连接不能使用 SQLITE_OPEN_SHAREDCACHE // 将导致 table is locked! @@ -35,7 +35,7 @@ SQLiteConnect::SQLiteConnect(const Parameter ¶m) : DBConnectBase(param), m_d int rc = sqlite3_open_v2(m_dbname.c_str(), &m_db, flags, NULL); SQL_CHECK(rc == SQLITE_OK, rc, sqlite3_errmsg(m_db)); -#ifdef HKU_ENABLE_SQLCIPHER +#if HKU_ENABLE_SQLCIPHER if (haveParam("key")) { std::string key = getParam("key"); if (!key.empty()) { @@ -91,7 +91,7 @@ void SQLiteConnect::close() { } int64_t SQLiteConnect::exec(const std::string &sql_string) { -#ifdef HKU_SQL_TRACE +#if HKU_SQL_TRACE HKU_DEBUG(sql_string); #endif int rc = sqlite3_exec(m_db, sql_string.c_str(), NULL, NULL, NULL); @@ -134,7 +134,7 @@ SQLStatementPtr SQLiteConnect::getStatement(const std::string &sql_statement) { return std::make_shared(this, sql_statement); } -bool SQLiteConnect::tableExist(const string &tablename) { +bool SQLiteConnect::tableExist(const std::string &tablename) { SQLStatementPtr st = getStatement(fmt::format("select count(1) from sqlite_master where name='{}'", tablename)); st->exec(); diff --git a/hikyuu_cpp/hikyuu/utilities/db_connect/sqlite/SQLiteConnect.h b/hikyuu_cpp/hikyuu/utilities/db_connect/sqlite/SQLiteConnect.h index 694abce9..a1cd42c3 100644 --- a/hikyuu_cpp/hikyuu/utilities/db_connect/sqlite/SQLiteConnect.h +++ b/hikyuu_cpp/hikyuu/utilities/db_connect/sqlite/SQLiteConnect.h @@ -25,7 +25,7 @@ namespace hku { * SQLite连接 * @ingroup SQLite */ -class HKU_API SQLiteConnect : public DBConnectBase { +class HKU_UTILS_API SQLiteConnect : public DBConnectBase { public: /** * 构造函数 @@ -78,11 +78,11 @@ private: private: friend class SQLiteStatement; - string m_dbname; + std::string m_dbname; sqlite3 *m_db; }; -typedef shared_ptr SQLiteConnectPtr; +typedef std::shared_ptr SQLiteConnectPtr; } // namespace hku diff --git a/hikyuu_cpp/hikyuu/utilities/db_connect/sqlite/SQLiteStatement.cpp b/hikyuu_cpp/hikyuu/utilities/db_connect/sqlite/SQLiteStatement.cpp index b9fd634c..31d395d6 100644 --- a/hikyuu_cpp/hikyuu/utilities/db_connect/sqlite/SQLiteStatement.cpp +++ b/hikyuu_cpp/hikyuu/utilities/db_connect/sqlite/SQLiteStatement.cpp @@ -12,7 +12,7 @@ namespace hku { -SQLiteStatement::SQLiteStatement(DBConnectBase *driver, const string &sql_statement) +SQLiteStatement::SQLiteStatement(DBConnectBase *driver, const std::string &sql_statement) : SQLStatementBase(driver, sql_statement), m_needs_reset(false), m_step_status(SQLITE_DONE), @@ -20,7 +20,7 @@ SQLiteStatement::SQLiteStatement(DBConnectBase *driver, const string &sql_statem m_db((dynamic_cast(driver))->m_db), m_stmt(NULL) { int status = - sqlite3_prepare_v2(m_db, m_sql_string.c_str(), m_sql_string.size() + 1, &m_stmt, NULL); + sqlite3_prepare_v2(m_db, m_sql_string.c_str(), int(m_sql_string.size() + 1), &m_stmt, NULL); if (status != SQLITE_OK) { sqlite3_finalize(m_stmt); SQL_THROW(status, "Failed prepare sql statement: {}! error msg: {}", m_sql_string, @@ -103,7 +103,7 @@ void SQLiteStatement::sub_bindDatetime(int idx, const Datetime &item) { } } -void SQLiteStatement::sub_bindText(int idx, const string &item) { +void SQLiteStatement::sub_bindText(int idx, const std::string &item) { _reset(); int status = sqlite3_bind_text(m_stmt, idx + 1, item.c_str(), (int)item.size(), SQLITE_TRANSIENT); @@ -122,7 +122,7 @@ void SQLiteStatement::sub_bindDouble(int idx, double item) { SQL_CHECK(status == SQLITE_OK, status, sqlite3_errmsg(m_db)); } -void SQLiteStatement::sub_bindBlob(int idx, const string &item) { +void SQLiteStatement::sub_bindBlob(int idx, const std::string &item) { _reset(); int status = sqlite3_bind_blob(m_stmt, idx + 1, item.data(), (int)item.size(), SQLITE_TRANSIENT); diff --git a/hikyuu_cpp/hikyuu/utilities/db_connect/sqlite/SQLiteStatement.h b/hikyuu_cpp/hikyuu/utilities/db_connect/sqlite/SQLiteStatement.h index 72df5a86..516c4c48 100644 --- a/hikyuu_cpp/hikyuu/utilities/db_connect/sqlite/SQLiteStatement.h +++ b/hikyuu_cpp/hikyuu/utilities/db_connect/sqlite/SQLiteStatement.h @@ -19,7 +19,7 @@ namespace hku { * SQLite Statemen * @ingroup DBConnect */ -class HKU_API SQLiteStatement : public SQLStatementBase { +class HKU_UTILS_API SQLiteStatement : public SQLStatementBase { public: SQLiteStatement() = delete; @@ -28,7 +28,7 @@ public: * @param driver 数据库连接 * @param sql_statement SQL语句 */ - SQLiteStatement(DBConnectBase *driver, const string &sql_statement); + SQLiteStatement(DBConnectBase *driver, const std::string &sql_statement); /** 析构函数 */ virtual ~SQLiteStatement(); diff --git a/hikyuu_cpp/hikyuu/utilities/db_connect/sqlite/SQLiteUtil.cpp b/hikyuu_cpp/hikyuu/utilities/db_connect/sqlite/SQLiteUtil.cpp index 6a21c5bc..803d0508 100644 --- a/hikyuu_cpp/hikyuu/utilities/db_connect/sqlite/SQLiteUtil.cpp +++ b/hikyuu_cpp/hikyuu/utilities/db_connect/sqlite/SQLiteUtil.cpp @@ -5,6 +5,7 @@ * Author: fasiondog */ +#include "hikyuu/utilities/SpendTimer.h" #include "hikyuu/utilities/os.h" #include "SQLiteUtil.h" diff --git a/hikyuu_cpp/hikyuu/utilities/db_connect/sqlite/SQLiteUtil.h b/hikyuu_cpp/hikyuu/utilities/db_connect/sqlite/SQLiteUtil.h index 53c46b39..f5ac2d96 100644 --- a/hikyuu_cpp/hikyuu/utilities/db_connect/sqlite/SQLiteUtil.h +++ b/hikyuu_cpp/hikyuu/utilities/db_connect/sqlite/SQLiteUtil.h @@ -15,7 +15,7 @@ namespace hku { * @brief SQLite 其他相关操作方法集合 * @ingroup DBConnect */ -class HKU_API SQLiteUtil { +class HKU_UTILS_API SQLiteUtil { public: SQLiteUtil() = default; ~SQLiteUtil() = default; @@ -43,8 +43,8 @@ public: * @param n_page 分批备份时每次循环备份的 page 数,小于等于0时,一次性备份,不进行分批备份 * @param step_sleep 分批备份时每次循环后,休眠间隔时长(毫秒),以便让出cpu */ - static BackupResult onlineBackup(const std::shared_ptr& conn, - const std::string& dst, int n_page = -1, + static BackupResult onlineBackup(const std::shared_ptr &conn, + const std::string &dst, int n_page = -1, int step_sleep = 250) noexcept; /** * @brief 在线备份数据库,不影响其他数据库连接进行操作 @@ -53,7 +53,7 @@ public: * @param n_page 分批备份时每次循环备份的 page 数,小于等于0时,一次性备份,不进行分批备份 * @param step_sleep 分批备份时每次循环后,休眠间隔时长(毫秒),以便让出cpu */ - static BackupResult onlineBackup(const std::string& src, const std::string& dst, + static BackupResult onlineBackup(const std::string &src, const std::string &dst, int n_page = -1, int step_sleep = 250) noexcept; /** @@ -65,7 +65,7 @@ public: * @param save_bad 是否保存损坏的数据,将 dst 及 dst-journal 加上后缀 .bad 另存 * @return RecoverResult */ - static RecoverResult recoverFromBackup(const std::string& backup, const std::string& dst, + static RecoverResult recoverFromBackup(const std::string &backup, const std::string &dst, bool save_bad = false) noexcept; /** @@ -74,7 +74,7 @@ public: * @return true 指定的数据库文件及其日志文件都被成功删除或都不存在时,返回成功 * @return false 指定的数据文件及其日志文件,其中一个删除失败都会返回删除失败 */ - static bool removeDBFile(const std::string& dbfilename); + static bool removeDBFile(const std::string &dbfilename); }; } // namespace hku \ No newline at end of file diff --git a/hikyuu_cpp/hikyuu/utilities/exception.cpp b/hikyuu_cpp/hikyuu/utilities/exception.cpp new file mode 100644 index 00000000..a16c094f --- /dev/null +++ b/hikyuu_cpp/hikyuu/utilities/exception.cpp @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2024 hikyuu.org + * + * Created on: 2024-08-13 + * Author: fasiondog + */ + +#include "exception.h" + +namespace hku { + +const char *exception::what() const noexcept { + return m_msg.c_str(); +} + +} // namespace hku \ No newline at end of file diff --git a/hikyuu_cpp/hikyuu/exception.h b/hikyuu_cpp/hikyuu/utilities/exception.h similarity index 55% rename from hikyuu_cpp/hikyuu/exception.h rename to hikyuu_cpp/hikyuu/utilities/exception.h index 4a6faae9..66baf2dd 100644 --- a/hikyuu_cpp/hikyuu/exception.h +++ b/hikyuu_cpp/hikyuu/utilities/exception.h @@ -14,39 +14,38 @@ #include #include +#ifndef HKU_UTILS_API +#define HKU_UTILS_API +#endif + namespace hku { +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable : 4275) +#endif + /** * @ingroup Utilities * @addtogroup Exception Exception 异常处理 * @{ */ -#if !defined(__clang__) && !defined(__GNUC__) -class exception : public std::exception { -public: - exception() : std::exception("Unknown exception!") {} - exception(const std::string& msg) // cppcheck-suppress noExplicitConstructor - : std::exception(msg.c_str()) {} - exception(const char* msg) : std::exception(msg) {} // cppcheck-suppress noExplicitConstructor -}; - -#else -// llvm 中的 std::exception 不接受参数 -class exception : public std::exception { +class HKU_UTILS_API exception : public std::exception { public: exception() : m_msg("Unknown exception!") {} exception(const char *msg) : m_msg(msg) {} // cppcheck-suppress noExplicitConstructor exception(const std::string &msg) : m_msg(msg) {} // cppcheck-suppress noExplicitConstructor virtual ~exception() noexcept {} - virtual const char *what() const noexcept { - return m_msg.c_str(); - } + virtual const char *what() const noexcept; protected: std::string m_msg; }; -#endif /* #ifdef __clang__ */ + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif } /* namespace hku */ diff --git a/hikyuu_cpp/hikyuu/utilities/http_client/HttpClient.cpp b/hikyuu_cpp/hikyuu/utilities/http_client/HttpClient.cpp new file mode 100644 index 00000000..cc25828b --- /dev/null +++ b/hikyuu_cpp/hikyuu/utilities/http_client/HttpClient.cpp @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2024 hikyuu.org + * + * Created on: 2024-07-26 + * Author: fasiondog + */ + +#include "HttpClient.h" + +#if HKU_ENABLE_HTTP_CLIENT_ZIP +#include "gzip/compress.hpp" +#include "gzip/decompress.hpp" +#endif + +#include +#include "hikyuu/utilities/Log.h" +#include "hikyuu/utilities/os.h" +#include "url.h" + +namespace hku { + +HttpResponse::HttpResponse() { + NNG_CHECK(nng_http_res_alloc(&m_res)); +} + +HttpResponse::~HttpResponse() { + if (m_res) { + nng_http_res_free(m_res); + } +} + +void HttpResponse::reset() { + if (m_res) { + nng_http_res_free(m_res); + NNG_CHECK(nng_http_res_alloc(&m_res)); + } + m_body.clear(); +} + +HttpResponse::HttpResponse(HttpResponse&& rhs) : m_res(rhs.m_res), m_body(std::move(rhs.m_body)) { + rhs.m_res = nullptr; +} + +HttpResponse& HttpResponse::operator=(HttpResponse&& rhs) { + if (this != &rhs) { + if (m_res != nullptr) { + nng_http_res_free(m_res); + } + m_res = rhs.m_res; + rhs.m_res = nullptr; + m_body = std::move(rhs.m_body); + } + return *this; +} + +HttpClient::~HttpClient() { + reset(); +#if HKU_ENABLE_HTTP_CLIENT_SSL + m_tls_cfg.release(); +#endif +} + +void HttpClient::reset() { + m_client.release(); + m_conn.close(); + m_aio.release(); +} + +void HttpClient::setCaFile(const std::string& filename) { +#if HKU_ENABLE_HTTP_CLIENT_SSL + HKU_CHECK(!filename.empty(), "Input filename is empty!"); + HKU_IF_RETURN(filename == m_ca_file, void()); + HKU_CHECK(existFile(filename), "Not exist file: {}", filename); + m_tls_cfg.set_ca_file(filename); + m_ca_file = filename; + reset(); +#else + HKU_THROW("Not support https! Please complie with --http_client_ssl!"); +#endif +} + +void HttpClient::_connect() { + HKU_CHECK(m_url.valid(), "Invalid url: {}", m_url.raw_url()); + + m_client.set_url(m_url); + + if (m_url.is_https()) { +#if HKU_ENABLE_HTTP_CLIENT_SSL + auto* old_cfg = m_client.get_tls_cfg(); + if (!old_cfg || old_cfg != m_tls_cfg.get()) { + m_client.set_tls_cfg(m_tls_cfg.get()); + } +#endif + } + + m_aio.alloc(m_timeout_ms); + m_client.connect(m_aio); + + if (!m_conn.valid()) { + NNG_CHECK(m_aio.wait().result()); + m_conn = nng::http_conn((nng_http_conn*)m_aio.get_output(0)); + } +} + +HttpResponse HttpClient::request(const std::string& method, const std::string& path, + const HttpParams& params, const HttpHeaders& headers, + const char* body, size_t body_len, + const std::string& content_type) { + HKU_CHECK(m_url.valid(), "Invalid url: {}", m_url.raw_url()); + + HttpResponse res; + try { + std::ostringstream buf; + bool first = true; + for (auto iter = params.cbegin(); iter != params.cend(); ++iter) { + if (first) { + buf << "?"; + first = false; + } else { + buf << "&"; + } + buf << iter->first << "=" << iter->second; + } + + std::string uri = buf.str(); + uri = uri.empty() ? path : fmt::format("{}{}", path, uri); + res = _readResChunk(method, uri, headers, body, body_len, content_type); + + if (res.getHeader("Connection") == "close") { + // HKU_TRACE("Connect closed"); + reset(); + } + + } catch (const std::exception&) { + reset(); + throw; + } catch (...) { + reset(); + HKU_THROW_UNKNOWN; + } + return res; +} + +HttpResponse HttpClient::_readResChunk(const std::string& method, const std::string& uri, + const HttpHeaders& headers, const char* body, + size_t body_len, const std::string& content_type) { + HttpResponse res; + nng::http_req req(m_url); + req.set_method(method).set_uri(uri).add_headers(m_default_headers).add_headers(headers); + if (body != nullptr) { + HKU_CHECK(body_len > 0, "Body is not null, but len is zero!"); + req.add_header("Content-Type", content_type); + +#if HKU_ENABLE_HTTP_CLIENT_ZIP + if (req.get_header("Content-Encoding") == "gzip") { + gzip::Compressor comp(Z_DEFAULT_COMPRESSION); + std::string output; + comp.compress(output, body, body_len); + req.copy_data(output.data(), output.size()); + } else { + req.set_data(body, body_len); + } +#else + req.del_header("Content-Encoding").set_data(body, body_len); +#endif + } + + int count = 0; + while (count < 2) { + count++; + _connect(); + + m_conn.transact(req.get(), res.get(), m_aio); + int rv = m_aio.wait().result(); + if (0 == rv) { + break; + } else if (NNG_ETIMEDOUT == rv) { + throw HttpTimeoutException(); + } else if (NNG_ECLOSED == rv || NNG_ECONNSHUT == rv || NNG_ECONNREFUSED == rv) { + // HKU_DEBUG("rv: {}", nng_strerror(rv)); + reset(); + res.reset(); + } else { + HKU_THROW("[NNG_ERROR] {} ", nng_strerror(rv)); + } + } + + HKU_IF_RETURN(res.status() != NNG_HTTP_STATUS_OK, res); + + void* data; + size_t len; + nng_http_res_get_data(res.get(), &data, &len); + +#if HKU_ENABLE_HTTP_CLIENT_ZIP + if (res.getHeader("Content-Encoding") == "gzip") { + res.m_body = gzip::decompress((const char*)data, len); + } else { + res._resizeBody(len); + memcpy(res.m_body.data(), data, len); + } +#else + res._resizeBody(len); + memcpy(res.m_body.data(), data, len); +#endif + + return res; +} + +} // namespace hku \ No newline at end of file diff --git a/hikyuu_cpp/hikyuu/utilities/http_client/HttpClient.h b/hikyuu_cpp/hikyuu/utilities/http_client/HttpClient.h new file mode 100644 index 00000000..e20ee542 --- /dev/null +++ b/hikyuu_cpp/hikyuu/utilities/http_client/HttpClient.h @@ -0,0 +1,229 @@ +/* + * Copyright (c) 2024 hikyuu.org + * + * Created on: 2024-07-26 + * Author: fasiondog + */ + +#pragma once +#ifndef HKU_UTILS_HTTP_CLIENT_H +#define HKU_UTILS_HTTP_CLIENT_H + +#include "hikyuu/utilities/config.h" +#if !HKU_ENABLE_HTTP_CLIENT +#error "Don't enable http client, please config with --http_client=y" +#endif + +#include +#include +#include "nng_wrap.h" + +#ifndef HKU_UTILS_API +#define HKU_UTILS_API +#endif + +namespace hku { + +using json = nlohmann::json; + +class HKU_UTILS_API HttpClient; + +class HKU_UTILS_API HttpResponse final { + friend class HKU_UTILS_API HttpClient; + +public: + HttpResponse(); + ~HttpResponse(); + + HttpResponse(const HttpResponse&) = delete; + HttpResponse& operator=(const HttpResponse&) = delete; + + HttpResponse(HttpResponse&& rhs); + HttpResponse& operator=(HttpResponse&& rhs); + + const std::string& body() const noexcept { + return m_body; + } + + hku::json json() const { + return json::parse(m_body); + } + + int status() const noexcept { + return nng_http_res_get_status(m_res); + } + + std::string reason() noexcept { + return nng_http_res_get_reason(m_res); + } + + std::string getHeader(const std::string& key) noexcept { + const char* hdr = nng_http_res_get_header(m_res, key.c_str()); + return hdr ? std::string(hdr) : std::string(); + } + + size_t getContentLength() noexcept { + std::string slen = getHeader("Content-Length"); + return slen.empty() ? 0 : std::stoi(slen); + } + +private: + void _resizeBody(size_t len) { + m_body.resize(len); + } + + nng_http_res* get() const noexcept { + return m_res; + } + + void reset(); + +private: + nng_http_res* m_res{nullptr}; + std::string m_body; +}; + +class HKU_UTILS_API HttpClient { +public: + HttpClient() = default; + explicit HttpClient(const std::string& url, int32_t timeout_ms = NNG_DURATION_DEFAULT) + : m_url(nng::url(url)), m_timeout_ms(timeout_ms) {}; + virtual ~HttpClient(); + + HttpClient(const HttpClient&) = delete; + HttpClient& operator=(const HttpClient&) = delete; + + HttpClient(HttpClient&& rhs) + : m_default_headers(std::move(rhs.m_default_headers)), + m_url(std::move(rhs.m_url)), + m_client(std::move(rhs.m_client)), + m_aio(std::move(rhs.m_aio)), + m_conn(std::move(rhs.m_conn)), +#if HKU_ENABLE_HTTP_CLIENT_SSL + m_tls_cfg(std::move(rhs.m_tls_cfg)), + m_ca_file(std::move(rhs.m_ca_file)), +#endif + m_timeout_ms(rhs.m_timeout_ms) { + } + + HttpClient& operator=(HttpClient&& rhs) { + if (this != &rhs) { + m_default_headers = std::move(rhs.m_default_headers); + m_url = std::move(rhs.m_url); + m_client = (std::move(rhs.m_client)); + m_aio = std::move(rhs.m_aio); + m_conn = std::move(rhs.m_conn); +#if HKU_ENABLE_HTTP_CLIENT_SSL + m_tls_cfg = std::move(rhs.m_tls_cfg); + m_ca_file = std::move(rhs.m_ca_file); +#endif + m_timeout_ms = rhs.m_timeout_ms; + rhs.m_timeout_ms = NNG_DURATION_DEFAULT; + } + return *this; + } + + bool valid() const noexcept { + return m_url.valid(); + } + + const std::string& url() const noexcept { + return m_url.raw_url(); + } + + void setUrl(const std::string& url) noexcept { + m_url = nng::url(url); + reset(); + } + + // #define NNG_DURATION_INFINITE (-1) + // #define NNG_DURATION_DEFAULT (-2) + // #define NNG_DURATION_ZERO (0) + void setTimeout(int32_t ms) { + if (m_timeout_ms != ms) { + m_timeout_ms = ms; + reset(); + } + } + + void setDefaultHeaders(const HttpHeaders& headers) { + m_default_headers = headers; + } + + void setDefaultHeaders(HttpHeaders&& headers) { + m_default_headers = std::move(headers); + } + + void setCaFile(const std::string& filename); + + void reset(); + + HttpResponse request(const std::string& method, const std::string& path, + const HttpParams& params, const HttpHeaders& headers, const char* body, + size_t body_len, const std::string& content_type); + + HttpResponse get(const std::string& path, const HttpHeaders& headers = HttpHeaders()) { + return request("GET", path, HttpParams(), headers, nullptr, 0, ""); + } + + HttpResponse get(const std::string& path, const HttpParams& params, + const HttpHeaders& headers) { + return request("GET", path, params, headers, nullptr, 0, ""); + } + + HttpResponse post(const std::string& path, const HttpParams& params, const HttpHeaders& headers, + const char* body, size_t len, const std::string& content_type) { + return request("POST", path, params, headers, body, len, content_type); + } + + HttpResponse post(const std::string& path, const HttpHeaders& headers, const char* body, + size_t len, const std::string& content_type) { + return request("POST", path, HttpParams(), headers, body, len, content_type); + } + + HttpResponse post(const std::string& path, const HttpParams& params, const HttpHeaders& headers, + const std::string& content, const std::string& content_type = "text/plaint") { + return post(path, params, headers, content.data(), content.size(), content_type); + } + + HttpResponse post(const std::string& path, const HttpHeaders& headers, + const std::string& content, const std::string& content_type = "text/plaint") { + return post(path, HttpParams(), headers, content, content_type); + } + + HttpResponse post(const std::string& path, const HttpParams& params, const HttpHeaders& headers, + const json& body) { + return post(path, params, headers, body.dump(), "application/json"); + } + + HttpResponse post(const std::string& path, const HttpHeaders& headers, const json& body) { + return post(path, HttpParams(), headers, body); + } + + HttpResponse post(const std::string& path, const json& body) { + return post(path, HttpHeaders(), body); + } + +private: + void _connect(); + HttpResponse _readResChunk(const std::string& method, const std::string& uri, + const HttpHeaders& headers, const char* body, size_t body_len, + const std::string& content_type); + +private: + HttpHeaders m_default_headers; + nng::url m_url; + nng::http_client m_client; + nng::aio m_aio; + nng::http_conn m_conn; +#if HKU_ENABLE_HTTP_CLIENT_SSL + nng::tls_config m_tls_cfg; + std::string m_ca_file; +#endif + + int32_t m_timeout_ms{NNG_DURATION_DEFAULT}; +}; + +} // namespace hku + +#endif \ No newline at end of file diff --git a/hikyuu_cpp/hikyuu/utilities/http_client/nng_wrap.h b/hikyuu_cpp/hikyuu/utilities/http_client/nng_wrap.h new file mode 100644 index 00000000..7c704c6f --- /dev/null +++ b/hikyuu_cpp/hikyuu/utilities/http_client/nng_wrap.h @@ -0,0 +1,517 @@ +/* + * Copyright (c) 2024 hikyuu.org + * + * Created on: 2024-07-26 + * Author: fasiondog + */ + +#pragma once +#ifndef HKU_UTILS_NNG_WRAP_H +#define HKU_UTILS_NNG_WRAP_H + +#include +#include +#include +#include "hikyuu/utilities/Log.h" + +#include "hikyuu/utilities/config.h" +#if HKU_ENABLE_HTTP_CLIENT_SSL +#include +#endif + +namespace hku { + +struct HttpTimeoutException : hku::exception { + HttpTimeoutException() : hku::exception("Http timeout!") {} + virtual ~HttpTimeoutException() noexcept = default; +}; + +using HttpHeaders = std::map; +using HttpParams = std::map; + +} // namespace hku + +namespace hku { +namespace nng { + +#ifndef NNG_CHECK +#define NNG_CHECK(rv) \ + { \ + if (rv != 0) { \ + HKU_THROW("[NNG_ERROR] {} ", nng_strerror(rv)); \ + } \ + } +#endif + +#ifndef NNG_CHECK_M +#define NNG_CHECK_M(rv, ...) \ + { \ + if (rv != 0) { \ + HKU_THROW("{} | [NNG_ERROR] {}", fmt::format(__VA_ARGS__), nng_strerror(rv)); \ + } \ + } +#endif + +class url final { +public: + url() = default; + explicit url(const std::string& url_) noexcept : m_rawurl(url_) { + nng_url_parse(&m_url, m_rawurl.c_str()); + } + + url(const url&) = delete; + url(url&& rhs) noexcept : m_rawurl(std::move(rhs.m_rawurl)), m_url(rhs.m_url) { + rhs.m_url = nullptr; + } + + url& operator=(const url&) = delete; + url& operator=(url&& rhs) noexcept { + if (this != &rhs) { + if (m_url != nullptr) { + nng_url_free(m_url); + } + m_url = rhs.m_url; + m_rawurl = std::move(rhs.m_rawurl); + rhs.m_url = nullptr; + } + return *this; + } + + ~url() { + if (m_url) { + nng_url_free(m_url); + } + } + + const std::string& raw_url() const noexcept { + return m_rawurl; + } + + nng_url* get() const noexcept { + return m_url; + } + + nng_url* operator->() const noexcept { + return m_url; + } + + bool valid() const noexcept { + return m_url != nullptr; + } + + bool is_https() const noexcept { + return m_url == nullptr ? false : strcmp("https", m_url->u_scheme) == 0; + } + +private: + std::string m_rawurl; + nng_url* m_url{nullptr}; +}; + +class aio final { +public: + aio() = default; + aio(const aio&) = delete; + ~aio() { + if (m_aio) { + nng_aio_free(m_aio); + } + } + + aio(aio&& rhs) : m_aio(rhs.m_aio), m_timeout(rhs.m_timeout) { + rhs.m_aio = nullptr; + rhs.m_timeout = NNG_DURATION_DEFAULT; + } + + aio& operator=(const aio&) = delete; + + aio& operator=(aio&& rhs) { + if (this != &rhs) { + m_aio = rhs.m_aio; + m_timeout = rhs.m_timeout; + rhs.m_aio = nullptr; + rhs.m_timeout = NNG_DURATION_DEFAULT; + } + return *this; + } + + bool valid() const noexcept { + return m_aio != nullptr; + } + + nng_aio* get() const noexcept { + return m_aio; + } + + nng_aio* operator->() const noexcept { + return m_aio; + } + + void alloc(int32_t timeout_ms) { + if (m_aio == nullptr) { + NNG_CHECK(nng_aio_alloc(&m_aio, NULL, NULL)); + } + set_timeout(timeout_ms); + } + + void release() { + if (m_aio) { + nng_aio_free(m_aio); + m_aio = nullptr; + } + } + + aio& wait() { + nng_aio_wait(m_aio); + return *this; + } + + int result() { + // 直接返回结果,在调用处判断异常,才知道是在具体哪里 + return nng_aio_result(m_aio); + } + + void* get_output(unsigned index) { + return nng_aio_get_output(m_aio, index); + } + + /* + * 0 - 恢复默认值 + * <0 - 不限制 + */ + void set_timeout(int32_t ms) { + if (ms != m_timeout) { + m_timeout = ms; + nng_aio_set_timeout(m_aio, ms); + } + } + + int32_t get_timeout() const noexcept { + return m_timeout; + } + + void set_iov(unsigned n, const nng_iov* iov) { + NNG_CHECK(nng_aio_set_iov(m_aio, n, iov)); + } + +private: + nng_aio* m_aio{nullptr}; + int32_t m_timeout{NNG_DURATION_DEFAULT}; +}; + +#if HKU_ENABLE_HTTP_CLIENT_SSL +class tls_config final { +public: + tls_config() = default; + + tls_config(const tls_config& th) : m_cfg(th.m_cfg) { + if (m_cfg) { + nng_tls_config_hold(th.m_cfg); + } + } + + tls_config(tls_config&& rhs) : m_cfg(rhs.m_cfg) { + rhs.m_cfg = nullptr; + } + + ~tls_config() { + if (m_cfg) { + nng_tls_config_free(m_cfg); + } + } + + tls_config& operator=(const tls_config& th) { + if (this != &th) { + m_cfg = th.m_cfg; + if (m_cfg) { + nng_tls_config_hold(m_cfg); + } + } + return *this; + } + + tls_config& operator=(tls_config&& rhs) { + if (this != &rhs) { + m_cfg = rhs.m_cfg; + rhs.m_cfg = nullptr; + } + return *this; + } + + void release() { + if (m_cfg) { + nng_tls_config_free(m_cfg); + m_cfg = nullptr; + } + } + + nng_tls_config* get() const noexcept { + return m_cfg; + } + + tls_config& set_ca_file(const std::string& filename) { + NNG_CHECK(alloc()); + NNG_CHECK(nng_tls_config_ca_file(m_cfg, filename.c_str())); + return *this; + } + +private: + int alloc() { + return m_cfg ? 0 : nng_tls_config_alloc(&m_cfg, NNG_TLS_MODE_CLIENT); + } + +private: + nng_tls_config* m_cfg{nullptr}; +}; +#endif // #if HKU_ENABLE_HTTP_CLIENT_SSL + +class http_client final { +public: + http_client() = default; + ~http_client() { + if (m_client) { + nng_http_client_free(m_client); + } + } + + http_client(const http_client&) = delete; + http_client& operator=(const http_client&) = delete; + + http_client(http_client&& rhs) + : m_client(rhs.m_client), m_aio(rhs.m_aio), m_tls_cfg(rhs.m_tls_cfg) { + rhs.m_client = nullptr; + rhs.m_aio = nullptr; + rhs.m_tls_cfg = nullptr; + } + + http_client& operator=(http_client&& rhs) { + if (this != &rhs) { + m_client = rhs.m_client; + m_aio = rhs.m_aio; + m_tls_cfg = rhs.m_tls_cfg; + rhs.m_client = nullptr; + rhs.m_aio = nullptr; + rhs.m_tls_cfg = nullptr; + } + return *this; + } + + void set_url(const nng::url& url) { +#if !HKU_ENABLE_HTTP_CLIENT_SSL + if (url.is_https()) { + HKU_THROW("Not support https: ({})! Please compile with --http_client_ssl", + url.raw_url()); + } +#endif + if (!m_client) { + NNG_CHECK(nng_http_client_alloc(&m_client, url.get())); + m_tls_cfg = nullptr; + m_aio = nullptr; + } + } + + void connect(const aio& aio) { + if (m_aio != aio.get()) { + nng_http_client_connect(m_client, aio.get()); + m_aio = aio.get(); + } + } + + void set_tls_cfg(nng_tls_config* cfg) { + if (cfg != m_tls_cfg) { + NNG_CHECK(nng_http_client_set_tls(m_client, cfg)); + m_tls_cfg = cfg; + } + } + + nng_tls_config* get_tls_cfg() const noexcept { + return m_tls_cfg; + } + + nng_http_client* get() const noexcept { + return m_client; + } + + nng_http_client* operator->() const noexcept { + return m_client; + } + + explicit operator bool() const noexcept { + return m_client != nullptr; + } + + void release() { + if (m_client) { + nng_http_client_free(m_client); + m_client = nullptr; + m_aio = nullptr; + m_tls_cfg = nullptr; + } + } + +private: + nng_http_client* m_client{nullptr}; + nng_aio* m_aio{nullptr}; + nng_tls_config* m_tls_cfg{nullptr}; +}; + +class http_req final { +public: + http_req() = default; + explicit http_req(const url& url) { + NNG_CHECK(nng_http_req_alloc(&m_req, url.get())); + } + + http_req(const http_req&) = delete; + http_req(http_req&& rhs) : m_req(rhs.m_req) { + rhs.m_req = nullptr; + } + + ~http_req() { + if (m_req) { + nng_http_req_free(m_req); + } + } + + http_req& operator=(const http_req&) = delete; + http_req& operator=(http_req&& rhs) { + if (this != &rhs) { + if (m_req) { + nng_http_req_free(m_req); + } + m_req = rhs.m_req; + rhs.m_req = nullptr; + } + return *this; + } + + nng_http_req* get() const noexcept { + return m_req; + } + + http_req& set_method(const std::string& method) { + NNG_CHECK(nng_http_req_set_method(m_req, method.c_str())); + return *this; + } + + http_req& set_uri(const std::string& uri) { + NNG_CHECK(nng_http_req_set_uri(m_req, uri.c_str())); + return *this; + } + + http_req& add_header(const std::string& key, const std::string& val) { + NNG_CHECK_M(nng_http_req_add_header(m_req, key.c_str(), val.c_str()), + "Failed add head {}: {}", key, val); + return *this; + } + + http_req& add_headers(const HttpHeaders& headers) { + for (auto iter = headers.cbegin(); iter != headers.cend(); ++iter) { + NNG_CHECK_M(nng_http_req_add_header(m_req, iter->first.c_str(), iter->second.c_str()), + "Failed add header {}: {}", iter->first, iter->second); + } + return *this; + } + + std::string get_header(const std::string& key) { + const char* head = nng_http_req_get_header(m_req, key.c_str()); + return head ? std::string(head) : std::string(); + } + + http_req& del_header(const std::string& key) { + nng_http_req_del_header(m_req, key.c_str()); + return *this; + } + + /* 注: data 需要自行管理且在 req 释放之前应该一直存在,主要避免拷贝 */ + http_req& set_data(const char* data, size_t len) { + if (data != nullptr && len != 0) { + NNG_CHECK(nng_http_req_set_data(m_req, data, len)); + } + return *this; + } + + http_req& copy_data(const char* data, size_t len) { + if (data != nullptr && len != 0) { + NNG_CHECK(nng_http_req_copy_data(m_req, data, len)); + } + return *this; + } + +private: + nng_http_req* m_req{nullptr}; +}; + +class http_conn final { +public: + http_conn() = default; + explicit http_conn(nng_http_conn* conn_) noexcept : m_conn(conn_) {} + + http_conn(const http_conn&) = delete; + + http_conn(http_conn&& rhs) noexcept : m_conn(rhs.m_conn) { + rhs.m_conn = nullptr; + } + + http_conn& operator=(const http_conn& rhs) = delete; + + http_conn& operator=(http_conn&& rhs) noexcept { + if (this != &rhs) { + if (m_conn != nullptr) { + nng_http_conn_close(m_conn); + } + m_conn = rhs.m_conn; + rhs.m_conn = nullptr; + } + return *this; + } + + ~http_conn() { + if (m_conn) { + nng_http_conn_close(m_conn); + } + } + + void close() { + if (m_conn) { + nng_http_conn_close(m_conn); + m_conn = nullptr; + } + } + + nng_http_conn* get() const noexcept { + return m_conn; + } + + nng_http_conn* operator->() const noexcept { + return m_conn; + } + + bool valid() const noexcept { + return m_conn != nullptr; + } + + void write_req(const http_req& req, const aio& aio) { + nng_http_conn_write_req(m_conn, req.get(), aio.get()); + } + + void read_res(nng_http_res* res, const aio& aio) { + nng_http_conn_read_res(m_conn, res, aio.get()); + } + + void read_all(const aio& aio) { + nng_http_conn_read_all(m_conn, aio.get()); + } + + void transact(nng_http_req* req, nng_http_res* res, const aio& aio) { + nng_http_conn_transact(m_conn, req, res, aio.get()); + } + +private: + nng_http_conn* m_conn{nullptr}; +}; + +} // namespace nng +} // namespace hku + +#endif \ No newline at end of file diff --git a/hikyuu_cpp/hikyuu/utilities/http_client/url.cpp b/hikyuu_cpp/hikyuu/utilities/http_client/url.cpp new file mode 100644 index 00000000..26d79d16 --- /dev/null +++ b/hikyuu_cpp/hikyuu/utilities/http_client/url.cpp @@ -0,0 +1,60 @@ +/* + * Copyright(C) 2021 hikyuu.org + * + * Create on: 2021-03-07 + * Author: fasiondog + */ + +#ifndef _CRT_SECURE_NO_WARNINGS +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include "url.h" + +namespace hku { + +#define IS_ALPHA(c) (((c) >= 'a' && (c) <= 'z') || ((c) >= 'A' && (c) <= 'Z')) +#define IS_NUM(c) ((c) >= '0' && (c) <= '9') +#define IS_ALPHANUM(c) (IS_ALPHA(c) || IS_NUM(c)) +#define IS_HEX(c) (IS_NUM(c) || ((c) >= 'a' && (c) <= 'f') || ((c) >= 'A' && (c) <= 'F')) + +static inline bool is_unambiguous(char c) { + return IS_ALPHANUM(c) || c == '-' || c == '_' || c == '.' || c == '~'; +} + +static inline unsigned char hex2i(char hex) { + return hex <= '9' ? hex - '0' : hex <= 'F' ? hex - 'A' + 10 : hex - 'a' + 10; +} + +std::string url_escape(const char* istr) { + std::string ostr; + const char* p = istr; + char szHex[4] = {0}; + while (*p != '\0') { + if (is_unambiguous(*p)) { + ostr += *p; + } else { + snprintf(szHex, 4, "%%%02X", *p); + ostr += szHex; + } + ++p; + } + return ostr; +} + +std::string url_unescape(const char* istr) { + std::string ostr; + const char* p = istr; + while (*p != '\0') { + if (*p == '%' && IS_HEX(p[1]) && IS_HEX(p[2])) { + ostr += ((hex2i(p[1]) << 4) | hex2i(p[2])); + p += 3; + } else { + ostr += *p; + ++p; + } + } + return ostr; +} + +} // namespace hku \ No newline at end of file diff --git a/hikyuu_cpp/hikyuu/utilities/http_client/url.h b/hikyuu_cpp/hikyuu/utilities/http_client/url.h new file mode 100644 index 00000000..aa432066 --- /dev/null +++ b/hikyuu_cpp/hikyuu/utilities/http_client/url.h @@ -0,0 +1,25 @@ +/* + * Copyright(C) 2021 hikyuu.org + * + * Create on: 2021-03-07 + * Author: fasiondog + */ + +#pragma once +#ifndef HKU_UTILS_URL_H +#define HKU_UTILS_URL_H + +#include + +#ifndef HKU_UTILS_API +#define HKU_UTILS_API +#endif + +namespace hku { + +std::string HKU_UTILS_API url_escape(const char* istr); +std::string HKU_UTILS_API url_unescape(const char* istr); + +} // namespace hku + +#endif \ No newline at end of file diff --git a/hikyuu_cpp/hikyuu/utilities/IniParser.cpp b/hikyuu_cpp/hikyuu/utilities/ini_parser/IniParser.cpp similarity index 99% rename from hikyuu_cpp/hikyuu/utilities/IniParser.cpp rename to hikyuu_cpp/hikyuu/utilities/ini_parser/IniParser.cpp index 1e6d69bc..9f005a7f 100644 --- a/hikyuu_cpp/hikyuu/utilities/IniParser.cpp +++ b/hikyuu_cpp/hikyuu/utilities/ini_parser/IniParser.cpp @@ -1,4 +1,4 @@ -/* +/* * IniFile.cpp * * Created on: 2010-5-19 @@ -9,7 +9,7 @@ #include #include -#include "arithmetic.h" +#include "../arithmetic.h" #include "IniParser.h" namespace hku { diff --git a/hikyuu_cpp/hikyuu/utilities/IniParser.h b/hikyuu_cpp/hikyuu/utilities/ini_parser/IniParser.h similarity index 89% rename from hikyuu_cpp/hikyuu/utilities/IniParser.h rename to hikyuu_cpp/hikyuu/utilities/ini_parser/IniParser.h index 7be0afaf..9d1b88cb 100644 --- a/hikyuu_cpp/hikyuu/utilities/IniParser.h +++ b/hikyuu_cpp/hikyuu/utilities/ini_parser/IniParser.h @@ -1,4 +1,4 @@ -/* +/* * IniFile.h * * Created on: 2010-5-19 @@ -9,6 +9,11 @@ #ifndef INIPARSER_H_ #define INIPARSER_H_ +#include "hikyuu/utilities/config.h" +#if !HKU_ENABLE_INI_PARSER +#error "Don't enable ini_parser, please config with --ini_parser=y" +#endif + #include #include #include @@ -21,8 +26,8 @@ #pragma warning(disable : 4290) #endif -#ifndef HKU_API -#define HKU_API +#ifndef HKU_UTILS_API +#define HKU_UTILS_API #endif namespace hku { @@ -52,7 +57,7 @@ namespace hku { * @ingroup Utilities */ -class HKU_API IniParser { +class HKU_UTILS_API IniParser { public: typedef std::list StringList; typedef std::shared_ptr > StringListPtr; @@ -75,7 +80,7 @@ public: std::string get(const std::string& section, const std::string& option, const std::string& default_str = std::string()) const; - //以下默认值类型使用string的原因是因为int/float/double/bool类型没有空对象 + // 以下默认值类型使用string的原因是因为int/float/double/bool类型没有空对象 int getInt(const std::string& section, const std::string& option, const std::string& default_str = std::string()) const; diff --git a/hikyuu_cpp/hikyuu/utilities/md5.cpp b/hikyuu_cpp/hikyuu/utilities/md5.cpp new file mode 100644 index 00000000..86e65a30 --- /dev/null +++ b/hikyuu_cpp/hikyuu/utilities/md5.cpp @@ -0,0 +1,291 @@ +/* + * Copyright (c) hikyuu + * Created on: 2021/12/06 + * Author: fasiondog + * + * 注: boost的md5计算在windows和linux平台上结果不一致,所以改从 dlib 移植 + */ + +#include "arithmetic.h" +#include "md5.h" +#include "Log.h" + +namespace hku { + +inline uint32_t F(uint32_t x, uint32_t y, uint32_t z) { + return ((x & y) | ((~x) & z)); +} + +// ------------------------------------------------------------------------------------ + +inline uint32_t G(uint32_t x, uint32_t y, uint32_t z) { + return ((x & z) | (y & (~z))); +} + +// ------------------------------------------------------------------------------------ + +inline uint32_t H(uint32_t x, uint32_t y, uint32_t z) { + return (x ^ y ^ z); +} + +// ------------------------------------------------------------------------------------ + +inline uint32_t I(uint32_t x, uint32_t y, uint32_t z) { + return (y ^ (x | (~z))); +} + +// ------------------------------------------------------------------------------------ + +inline uint32_t rotate_left(uint32_t x, uint32_t n) { + return ((x << n) | (x >> (32 - n))); +} + +// ------------------------------------------------------------------------------------ + +inline void FF(uint32_t& a, uint32_t b, uint32_t c, uint32_t d, uint32_t x, uint32_t s, + uint32_t ac) { + a += F(b, c, d) + x + ac; + a = rotate_left(a, s); + a += b; +} + +// ------------------------------------------------------------------------------------ + +inline void GG(uint32_t& a, uint32_t b, uint32_t c, uint32_t d, uint32_t x, uint32_t s, + uint32_t ac) { + a += G(b, c, d) + x + ac; + a = rotate_left(a, s); + a += b; +} + +// ------------------------------------------------------------------------------------ + +inline void HH(uint32_t& a, uint32_t b, uint32_t c, uint32_t d, uint32_t x, uint32_t s, + uint32_t ac) { + a += H(b, c, d) + x + ac; + a = rotate_left(a, s); + a += b; +} + +// ------------------------------------------------------------------------------------ + +inline void II(uint32_t& a, uint32_t b, uint32_t c, uint32_t d, uint32_t x, uint32_t s, + uint32_t ac) { + a += I(b, c, d) + x + ac; + a = rotate_left(a, s); + a += b; +} + +void scramble_block(uint32_t& a, uint32_t& b, uint32_t& c, uint32_t& d, uint32_t* x) { + const uint32_t S11 = 7; + const uint32_t S12 = 12; + const uint32_t S13 = 17; + const uint32_t S14 = 22; + const uint32_t S21 = 5; + const uint32_t S22 = 9; + const uint32_t S23 = 14; + const uint32_t S24 = 20; + const uint32_t S31 = 4; + const uint32_t S32 = 11; + const uint32_t S33 = 16; + const uint32_t S34 = 23; + const uint32_t S41 = 6; + const uint32_t S42 = 10; + const uint32_t S43 = 15; + const uint32_t S44 = 21; + + // round 1 + FF(a, b, c, d, x[0], S11, 0xd76aa478); // 1 + FF(d, a, b, c, x[1], S12, 0xe8c7b756); // 2 + FF(c, d, a, b, x[2], S13, 0x242070db); // 3 + FF(b, c, d, a, x[3], S14, 0xc1bdceee); // 4 + FF(a, b, c, d, x[4], S11, 0xf57c0faf); // 5 + FF(d, a, b, c, x[5], S12, 0x4787c62a); // 6 + FF(c, d, a, b, x[6], S13, 0xa8304613); // 7 + FF(b, c, d, a, x[7], S14, 0xfd469501); // 8 + FF(a, b, c, d, x[8], S11, 0x698098d8); // 9 + FF(d, a, b, c, x[9], S12, 0x8b44f7af); // 10 + FF(c, d, a, b, x[10], S13, 0xffff5bb1); // 11 + FF(b, c, d, a, x[11], S14, 0x895cd7be); // 12 + FF(a, b, c, d, x[12], S11, 0x6b901122); // 13 + FF(d, a, b, c, x[13], S12, 0xfd987193); // 14 + FF(c, d, a, b, x[14], S13, 0xa679438e); // 15 + FF(b, c, d, a, x[15], S14, 0x49b40821); // 16 + + // Round 2 + GG(a, b, c, d, x[1], S21, 0xf61e2562); // 17 + GG(d, a, b, c, x[6], S22, 0xc040b340); // 18 + GG(c, d, a, b, x[11], S23, 0x265e5a51); // 19 + GG(b, c, d, a, x[0], S24, 0xe9b6c7aa); // 20 + GG(a, b, c, d, x[5], S21, 0xd62f105d); // 21 + GG(d, a, b, c, x[10], S22, 0x2441453); // 22 + GG(c, d, a, b, x[15], S23, 0xd8a1e681); // 23 + GG(b, c, d, a, x[4], S24, 0xe7d3fbc8); // 24 + GG(a, b, c, d, x[9], S21, 0x21e1cde6); // 25 + GG(d, a, b, c, x[14], S22, 0xc33707d6); // 26 + GG(c, d, a, b, x[3], S23, 0xf4d50d87); // 27 + GG(b, c, d, a, x[8], S24, 0x455a14ed); // 28 + GG(a, b, c, d, x[13], S21, 0xa9e3e905); // 29 + GG(d, a, b, c, x[2], S22, 0xfcefa3f8); // 30 + GG(c, d, a, b, x[7], S23, 0x676f02d9); // 31 + GG(b, c, d, a, x[12], S24, 0x8d2a4c8a); // 32 + + // Round 3 + HH(a, b, c, d, x[5], S31, 0xfffa3942); // 33 + HH(d, a, b, c, x[8], S32, 0x8771f681); // 34 + HH(c, d, a, b, x[11], S33, 0x6d9d6122); // 35 + HH(b, c, d, a, x[14], S34, 0xfde5380c); // 36 + HH(a, b, c, d, x[1], S31, 0xa4beea44); // 37 + HH(d, a, b, c, x[4], S32, 0x4bdecfa9); // 38 + HH(c, d, a, b, x[7], S33, 0xf6bb4b60); // 39 + HH(b, c, d, a, x[10], S34, 0xbebfbc70); // 40 + HH(a, b, c, d, x[13], S31, 0x289b7ec6); // 41 + HH(d, a, b, c, x[0], S32, 0xeaa127fa); // 42 + HH(c, d, a, b, x[3], S33, 0xd4ef3085); // 43 + HH(b, c, d, a, x[6], S34, 0x4881d05); // 44 + HH(a, b, c, d, x[9], S31, 0xd9d4d039); // 45 + HH(d, a, b, c, x[12], S32, 0xe6db99e5); // 46 + HH(c, d, a, b, x[15], S33, 0x1fa27cf8); // 47 + HH(b, c, d, a, x[2], S34, 0xc4ac5665); // 48 + + // Round 4 + II(a, b, c, d, x[0], S41, 0xf4292244); // 49 + II(d, a, b, c, x[7], S42, 0x432aff97); // 50 + II(c, d, a, b, x[14], S43, 0xab9423a7); // 51 + II(b, c, d, a, x[5], S44, 0xfc93a039); // 52 + II(a, b, c, d, x[12], S41, 0x655b59c3); // 53 + II(d, a, b, c, x[3], S42, 0x8f0ccc92); // 54 + II(c, d, a, b, x[10], S43, 0xffeff47d); // 55 + II(b, c, d, a, x[1], S44, 0x85845dd1); // 56 + II(a, b, c, d, x[8], S41, 0x6fa87e4f); // 57 + II(d, a, b, c, x[15], S42, 0xfe2ce6e0); // 58 + II(c, d, a, b, x[6], S43, 0xa3014314); // 59 + II(b, c, d, a, x[13], S44, 0x4e0811a1); // 60 + II(a, b, c, d, x[4], S41, 0xf7537e82); // 61 + II(d, a, b, c, x[11], S42, 0xbd3af235); // 62 + II(c, d, a, b, x[2], S43, 0x2ad7d2bb); // 63 + II(b, c, d, a, x[9], S44, 0xeb86d391); // 64 +} + +std::string HKU_UTILS_API md5(const unsigned char* input, size_t len) { + HKU_CHECK(input, "char *buf is null!"); + // make a temp version of input with enough space for padding and len appended + unsigned long extra_len = 64 - len % 64; + if (extra_len <= 8) + extra_len += 64; + unsigned char* temp = new unsigned char[extra_len + len]; + + // number of 16 word blocks + const unsigned long N = (extra_len + static_cast(len)) / 64; + + const unsigned char* input2 = input; + unsigned char* temp2 = temp; + unsigned char* end = temp + len; + + // copy input into temp + while (temp2 != end) { + *temp2 = *input2; + ++temp2; + ++input2; + } + + // pad temp + end += extra_len - 8; + *temp2 = static_cast(0x80); + ++temp2; + while (temp2 != end) { + *temp2 = 0; + ++temp2; + } + + // make len the number of bits in the original message + // but first multiply len by 8 and since len is only 32 bits the number might + // overflow so we will carry out the multiplication manually and end up with + // the result in the base 65536 number with three digits + // result = low + high*65536 + upper*65536*65536 + unsigned long low = len & 0xFFFF; + unsigned long high = static_cast(len) >> 16; + unsigned long upper; + unsigned long tmp; + tmp = low * 8; + low = tmp & 0xFFFF; + tmp = high * 8 + (tmp >> 16); + high = tmp & 0xFFFF; + upper = tmp >> 16; + + // append the length + *temp2 = static_cast(low & 0xFF); + ++temp2; + *temp2 = static_cast((low >> 8) & 0xFF); + ++temp2; + *temp2 = static_cast((high) & 0xFF); + ++temp2; + *temp2 = static_cast((high >> 8) & 0xFF); + ++temp2; + *temp2 = static_cast((upper) & 0xFF); + ; + ++temp2; + *temp2 = static_cast((upper >> 8) & 0xFF); + ; + ++temp2; + *temp2 = 0; + ++temp2; + *temp2 = 0; + + uint32_t a = 0x67452301; + uint32_t b = 0xefcdab89; + uint32_t c = 0x98badcfe; + uint32_t d = 0x10325476; + + // an array of 16 words + uint32_t x[16]; + + for (unsigned long i = 0; i < N; ++i) { + // copy a block of 16 words from m into x + for (unsigned long j = 0; j < 16; ++j) { + x[j] = ((static_cast(temp[4 * (j + 16 * i) + 3]) << 24) | + (static_cast(temp[4 * (j + 16 * i) + 2]) << 16) | + (static_cast(temp[4 * (j + 16 * i) + 1]) << 8) | + (static_cast(temp[4 * (j + 16 * i)]))); + } + + uint32_t aa = a; + uint32_t bb = b; + uint32_t cc = c; + uint32_t dd = d; + + scramble_block(a, b, c, d, x); + + a = a + aa; + b = b + bb; + c = c + cc; + d = d + dd; + } + + unsigned char output[16]; + // put a, b, c, and d into output + output[0] = static_cast((a) & 0xFF); + output[1] = static_cast((a >> 8) & 0xFF); + output[2] = static_cast((a >> 16) & 0xFF); + output[3] = static_cast((a >> 24) & 0xFF); + + output[4] = static_cast((b) & 0xFF); + output[5] = static_cast((b >> 8) & 0xFF); + output[6] = static_cast((b >> 16) & 0xFF); + output[7] = static_cast((b >> 24) & 0xFF); + + output[8] = static_cast((c) & 0xFF); + output[9] = static_cast((c >> 8) & 0xFF); + output[10] = static_cast((c >> 16) & 0xFF); + output[11] = static_cast((c >> 24) & 0xFF); + + output[12] = static_cast((d) & 0xFF); + output[13] = static_cast((d >> 8) & 0xFF); + output[14] = static_cast((d >> 16) & 0xFF); + output[15] = static_cast((d >> 24) & 0xFF); + + delete[] temp; + return byteToHexStr((const char*)output, 16); +} + +} // namespace hku \ No newline at end of file diff --git a/hikyuu_cpp/hikyuu/utilities/md5.h b/hikyuu_cpp/hikyuu/utilities/md5.h new file mode 100644 index 00000000..e2552e2e --- /dev/null +++ b/hikyuu_cpp/hikyuu/utilities/md5.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019~2021, hikyuu + * + * Created on: 2021/12/06 + * Author: fasiondog + */ + +#pragma once +#ifndef HKU_UTILS_MD5_H +#define HKU_UTILS_MD5_H + +#include + +#ifndef HKU_UTILS_API +#define HKU_UTILS_API +#endif + +namespace hku { + +/** + * @brief 计算 md5 值 + * + * @param input 待计算数据起始指针 + * @param len 待计算数据字节长度 + * @return std::string + */ +std::string HKU_UTILS_API md5(const unsigned char* input, size_t len); + +/** + * @brief 计算字符串 md5 + * + * @param src 待计算的字符串 + * @return std::string + */ +inline std::string md5(const std::string& src) { + return md5((const unsigned char*)src.data(), src.size()); +} + +} // namespace hku + +#endif // #define HKU_UTILS_MD5_H \ No newline at end of file diff --git a/hikyuu_cpp/hikyuu/utilities/mo/mo.cpp b/hikyuu_cpp/hikyuu/utilities/mo/mo.cpp new file mode 100644 index 00000000..4b314fb1 --- /dev/null +++ b/hikyuu_cpp/hikyuu/utilities/mo/mo.cpp @@ -0,0 +1,23 @@ +/* + * Copyright(C) 2021 hikyuu.org + * + * Create on: 2021-05-02 + * Author: fasiondog + */ + +#include "hikyuu/utilities/os.h" +#include "hikyuu/utilities/Log.h" +#include "mo.h" + +namespace hku { + +std::unordered_map MOHelper::ms_dict; + +void MOHelper::init() { + HKU_WARN_IF_RETURN(!existFile("i8n/zh_CN.mo"), void(), + "There is no internationalized language file: i8n/zh_CN.mo"); + ms_dict["zh_cn"] = moFileLib::moFileReader(); + ms_dict["zh_cn"].ReadFile("i8n/zh_CN.mo"); +} + +} // namespace hku \ No newline at end of file diff --git a/hikyuu_cpp/hikyuu/utilities/mo/mo.h b/hikyuu_cpp/hikyuu/utilities/mo/mo.h new file mode 100644 index 00000000..7fe168fc --- /dev/null +++ b/hikyuu_cpp/hikyuu/utilities/mo/mo.h @@ -0,0 +1,48 @@ +/* + * Copyright(C) 2021 hikyuu.org + * + * Create on: 2021-05-01 + * Author: fasiondog + */ + +#pragma once + +#include "hikyuu/utilities/config.h" +#if !HKU_ENABLE_MO +#error "Don't enable mo, please config with --mo=y" +#endif + +#include +#include "hikyuu/utilities/string_view.h" +#include "moFileReader.h" + +#if defined(_MSC_VER) +// moFileReader.hpp 最后打开了4251告警,这里关闭 +#pragma warning(disable : 4251) +#endif /* _MSC_VER */ + +#ifndef HKU_UTILS_API +#define HKU_UTILS_API +#endif + +namespace hku { + +class HKU_UTILS_API MOHelper { +public: + static void init(); + + static std::string translate(const std::string &lang, const char *id) { + auto iter = ms_dict.find(lang); + return iter != ms_dict.end() ? ms_dict[lang].Lookup(id) : std::string(id); + } + + static std::string translate(const std::string &lang, const char *ctx, const char *id) { + auto iter = ms_dict.find(lang); + return iter != ms_dict.end() ? ms_dict[lang].LookupWithContext(ctx, id) : std::string(id); + } + +private: + static std::unordered_map ms_dict; +}; + +} // namespace hku \ No newline at end of file diff --git a/hikyuu_cpp/hikyuu/utilities/mo/moFileReader.h b/hikyuu_cpp/hikyuu/utilities/mo/moFileReader.h new file mode 100644 index 00000000..b40be05e --- /dev/null +++ b/hikyuu_cpp/hikyuu/utilities/mo/moFileReader.h @@ -0,0 +1,836 @@ +/* + * moFileReader - A simple .mo-File-Reader + * Copyright (C) 2009 Domenico Gentner (scorcher24@gmail.com) + * Copyright (C) 2018-2021 Edgar (Edgar@AnotherFoxGuy.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The names of its contributors may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef __MOFILEREADER_SINGLE_INCLUDE_H_INCLUDED__ +#define __MOFILEREADER_SINGLE_INCLUDE_H_INCLUDED__ + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wsign-compare" +#endif + +#if defined(_MSC_VER) +#pragma warning(disable : 4267) +#endif /* _MSC_VER */ + +#include // this is for memset when compiling with gcc. +#include +#include +#include +#include +#include + +//------------------------------------------------------------- +// Path-Seperators are different on other OS. +//------------------------------------------------------------- +#ifndef MO_PATHSEP +#ifdef WIN32 +#define MO_PATHSEP std::string("\\") +#else +#define MO_PATHSEP std::string("/") +#endif +#endif + +//------------------------------------------------------------- +// Defines the beginning of the namespace moFileLib. +//------------------------------------------------------------- +#ifndef MO_BEGIN_NAMESPACE +#define MO_BEGIN_NAMESPACE namespace moFileLib { +#endif +//------------------------------------------------------------- +// Ends the current namespace. +//------------------------------------------------------------- +#ifndef MO_END_NAMESPACE +#define MO_END_NAMESPACE } +#endif + +/** \mainpage moFileReaderSDK + * + * + *

Include in project

+ * + * Usage of this library is quite easy, simply add moFileReader.hpp to your project. Thats all you + * have to do. You can safely exclude mo.cpp, since this file keeps the entry-points of the .exe + * only. + * + *

Usage

+ * + * This is moFileReader, a simple gettext-replacement. The usage of this library is, hopefully, + * fairly simple: \code + * + * // Instanciate the class + * moFileLib::moFileReader reader; + * + * // Load a .mo-File. + * if ( reader.ReadFile("myTranslationFile.mo") != moFileLib::moFileReader::EC_SUCCESS ) + * { + * // Error Handling + * } + * + * // Now, you can lookup the strings you stored in the .mo-File: + * std::cout << reader.Lookup("MyTranslationString") << std::endl; + * + * \endcode + * Thats all! This small code has no dependencies, except the C/C++-runtime of your compiler, + * so it should work on all machines where a C++-runtime is provided. + * + * \note We do not yet support .mo-Files with reversed magic-numbers, since I don't have + * a file to test it and I hate to release stuff I wasn't able to test. + * + *

Changelog

+ * + * - Version 1.2.0 + * - Proper implementation of contexts strings + * Now it uses a separate 2D map for storing strings with context + * - Fixed MagicNumber check not working on Linux + * - Removed duplicate code + * - Added option to disable convenience Class + * + * - Version 1.1.0 + * - Converted library to a header-only library + * + * - Version 1.0.0 + * - Added new function: LookupWithContext + * - Added unit-tests + * - Added support for packaging with Conan + * - Moved project to https://github.com/AnotherFoxGuy/MofileReader + * + * - Version 0.1.2 + * - Generic improvements to the documentation. + * - Generic improvements to the code + * - Fixed a bug in mo.cpp which caused the application not to print the help + * message if only --export or --lookup where missing. + * - Added -h, --help and -? to moReader[.exe]. It will print the help-screen. + * - Added --version and -v to moReader[.exe]. It will print some informations about the + * program. + * - Added --license to moReader[.exe]. This will print its license. + * - --export gives now a feedback about success or failure. + * - The HTML-Dump-Method outputs now the whole table from the empty msgid in a nice html-table, + * not only a few hardcoded. + * - I had an issue-report that the Error-Constants can collide with foreign code under certain + * conditions, so I added a patch which renamed the error-constants to more compatible names. + * + * - Version 0.1.1 + * - Added the ability to export mo's as HTML. + * - Fixed a bug causing a crash when passing an invalid value to moFileReader::Lookup(). + * - Added a new file, moFileConfig.h, holding the macros for the project. + * - Added the ability to be configured by cmake. + * - Added some more inline-functions, which really enhance the singleton. + * + * - Version 0.1.0 + * - Initial Version and release to http://googlecode.com + * + * + *

Credits

+ * + * Gettext is part of the GNU-Tools and (C) by the Free Software + * Foundation.\n Visual C++ Express is a registered Trademark of Microsoft, One Microsoft Way, + * Redmond, USA.\n All other Trademarks are property of their respective owners. \n \n Thanks for + * using this piece of OpenSource-Software.\n Submit patches and/or bugs on + * https://github.com/AnotherFoxGuy/MofileReader. Send your flames, dumb comments etc to /dev/null, + * thank you. + */ + +/** \namespace moFileLib + * \brief This is the only namespace of this small sourcecode. + */ +MO_BEGIN_NAMESPACE + +const std::string g_css = R"( +body { + background-color: black; + color: silver; +} +table { + width: 80%; +} +th { + background-color: orange; + color: black; +} +hr { + color: red; + width: 80%; + size: 5px; +} +a:link{ + color: gold; +} +a:visited{ + color: grey; +} +a:hover{ + color:blue; +} +.copyleft{ + font-size: 12px; + text-align: center; +})"; + +/** + * \brief Keeps the Description of translated and original strings. + * + * + * To load a String from the file, we need its offset and its length. + * This struct helps us grouping this information. + */ +struct moTranslationPairInformation { + /// \brief Constructor + moTranslationPairInformation() : m_orLength(0), m_orOffset(0), m_trLength(0), m_trOffset(0) {} + + /// \brief Length of the Original String + int m_orLength; + + /// \brief Offset of the Original String (absolute) + int m_orOffset; + + /// \brief Length of the Translated String + int m_trLength; + + /// \brief Offset of the Translated String (absolute) + int m_trOffset; +}; + +/** + * \brief Describes the "Header" of a .mo-File. + * + * + * The File info keeps the header of a .mo-file and + * a list of the string-descriptions. + * The typedef is for the type of the string-list. + * The constructor ensures, that all members get a nice + * initial value. + */ +struct moFileInfo { + /// \brief Type for the list of all Translation-Pair-Descriptions. + typedef std::deque moTranslationPairList; + + /// \brief Constructor + moFileInfo() + : m_magicNumber(0), + m_fileVersion(0), + m_numStrings(0), + m_offsetOriginal(0), + m_offsetTranslation(0), + m_sizeHashtable(0), + m_offsetHashtable(0), + m_reversed(false) {} + + /// \brief The Magic Number, compare it to g_MagicNumber. + int m_magicNumber; + + /// \brief The File Version, 0 atm according to the manpage. + int m_fileVersion; + + /// \brief Number of Strings in the .mo-file. + int m_numStrings; + + /// \brief Offset of the Table of the Original Strings + int m_offsetOriginal; + + /// \brief Offset of the Table of the Translated Strings + int m_offsetTranslation; + + /// \brief Size of 1 Entry in the Hashtable. + int m_sizeHashtable; + + /// \brief The Offset of the Hashtable. + int m_offsetHashtable; + + /** \brief Tells you if the bytes are reversed + * \note When this is true, the bytes are reversed and the Magic number is like g_MagicReversed + */ + bool m_reversed; + + /// \brief A list containing offset and length of the strings in the file. + moTranslationPairList m_translationPairInformation; +}; + +/** + * \brief This class is a gettext-replacement. + * + * + * The usage is quite simple:\n + * Tell the class which .mo-file it shall load via + * moFileReader::ReadFile(). The method will attempt to load + * the file, all translations will be stored in memory. + * Afterwards you can lookup the strings with moFileReader::Lookup() just + * like you would do with gettext. + * Additionally, you can call moFileReader::ReadFile() for as much files as you + * like. But please be aware, that if there are duplicated keys (original strings), + * that they will replace each other in the lookup-table. There is no check done, if a + * key already exists. + * + * \note If you add "Lookup" to the keywords of the gettext-parser (like poEdit), + * it will recognize the Strings loaded with an instance of this class. + * \note I strongly recommend poEdit from Vaclav Slavik for editing .po-Files, + * get it at http://poedit.net for various systems :). + */ +class moFileReader { +protected: + /// \brief Type for the map which holds the translation-pairs later. + typedef std::map moLookupList; + + /// \brief Type for the 2D map which holds the translation-pairs later. + typedef std::map moContextLookupList; + +public: + /// \brief The Magic Number describes the endianess of bytes on the system. + static const unsigned int MagicNumber = 0x950412DE; + + /// \brief If the Magic Number is Reversed, we need to swap the bytes. + static const unsigned int MagicReversed = 0xDE120495; + + /// \brief The character that is used to separate context strings + static const char ContextSeparator = '\x04'; + + /// \brief The possible errorcodes for methods of this class + enum eErrorCode { + /// \brief Indicated success + EC_SUCCESS = 0, + + /// \brief Indicates an error + EC_ERROR, + + /// \brief The given File was not found. + EC_FILENOTFOUND, + + /// \brief The file is invalid. + EC_FILEINVALID, + + /// \brief Empty Lookup-Table (returned by ExportAsHTML()) + EC_TABLEEMPTY, + + /// \brief The magic number did not match + EC_MAGICNUMBER_NOMATCH, + + /** + * \brief The magic number is reversed. + * \note This is an error until the class supports it. + */ + EC_MAGICNUMBER_REVERSED, + }; + + /** \brief Reads a .mo-file + * \param[in] _filename The path to the file to load. + * \return SUCCESS on success or one of the other error-codes in eErrorCode on error. + * + * This is the core-feature. This method loads the .mo-file and stores + * all translation-pairs in a map. You can access this map via the method + * moFileReader::Lookup(). + */ + moFileReader::eErrorCode ParseData(const std::string &data) { + // Opening the file. + std::stringstream stream(data); + + return ReadStream(stream); + } + + /** \brief Reads a .mo-file + * \param[in] _filename The path to the file to load. + * \return SUCCESS on success or one of the other error-codes in eErrorCode on error. + * + * This is the core-feature. This method loads the .mo-file and stores + * all translation-pairs in a map. You can access this map via the method + * moFileReader::Lookup(). + */ + eErrorCode ReadFile(const char *filename) { + // Opening the file. + std::ifstream stream(filename, std::ios_base::binary | std::ios_base::in); + if (!stream.is_open()) { + m_error = std::string("Cannot open File ") + std::string(filename); + return moFileReader::EC_FILENOTFOUND; + } + + eErrorCode res = ReadStream(stream); + stream.close(); + + return res; + } + + /** \brief Reads data from a stream + * \param[in] stream + * \return SUCCESS on success or one of the other error-codes in eErrorCode on error. + * + */ + template + eErrorCode ReadStream(T &stream) { + // Creating a file-description. + moFileInfo moInfo; + + // Reference to the List inside moInfo. + moFileInfo::moTranslationPairList &TransPairInfo = moInfo.m_translationPairInformation; + + // Read in all the 4 bytes of fire-magic, offsets and stuff... + stream.read((char *)&moInfo.m_magicNumber, 4); + stream.read((char *)&moInfo.m_fileVersion, 4); + stream.read((char *)&moInfo.m_numStrings, 4); + stream.read((char *)&moInfo.m_offsetOriginal, 4); + stream.read((char *)&moInfo.m_offsetTranslation, 4); + stream.read((char *)&moInfo.m_sizeHashtable, 4); + stream.read((char *)&moInfo.m_offsetHashtable, 4); + + if (stream.bad()) { + m_error = + "Stream bad during reading. The .mo-file seems to be invalid or has bad " + "descriptions!"; + return moFileReader::EC_FILEINVALID; + } + + // Checking the Magic Number + if (MagicNumber != moInfo.m_magicNumber) { + if (MagicReversed != moInfo.m_magicNumber) { + m_error = "The Magic Number does not match in all cases!"; + return moFileReader::EC_MAGICNUMBER_NOMATCH; + } else { + moInfo.m_reversed = true; + m_error = "Magic Number is reversed. We do not support this yet!"; + return moFileReader::EC_MAGICNUMBER_REVERSED; + } + } + + // Now we search all Length & Offsets of the original strings + for (int i = 0; i < moInfo.m_numStrings; i++) { + moTranslationPairInformation _str; + stream.read((char *)&_str.m_orLength, 4); + stream.read((char *)&_str.m_orOffset, 4); + if (stream.bad()) { + m_error = + "Stream bad during reading. The .mo-file seems to be invalid or has bad " + "descriptions!"; + return moFileReader::EC_FILEINVALID; + } + + TransPairInfo.push_back(_str); + } + + // Get all Lengths & Offsets of the translated strings + // Be aware: The Descriptors already exist in our list, so we just mod. refs from the deque. + for (int i = 0; i < moInfo.m_numStrings; i++) { + moTranslationPairInformation &_str = TransPairInfo[i]; + stream.read((char *)&_str.m_trLength, 4); + stream.read((char *)&_str.m_trOffset, 4); + if (stream.bad()) { + m_error = + "Stream bad during reading. The .mo-file seems to be invalid or has bad " + "descriptions!"; + return moFileReader::EC_FILEINVALID; + } + } + + // Normally you would read the hash-table here, but we don't use it. :) + + // Now to the interesting part, we read the strings-pairs now + for (int i = 0; i < moInfo.m_numStrings; i++) { + // We need a length of +1 to catch the trailing \0. + int orLength = TransPairInfo[i].m_orLength + 1; + int trLength = TransPairInfo[i].m_trLength + 1; + + int orOffset = TransPairInfo[i].m_orOffset; + int trOffset = TransPairInfo[i].m_trOffset; + + // Original + char *original = new char[orLength]; + memset(original, 0, sizeof(char) * orLength); + + stream.seekg(orOffset); + stream.read(original, orLength); + + if (stream.bad()) { + m_error = + "Stream bad during reading. The .mo-file seems to be invalid or has bad " + "descriptions!"; + return moFileReader::EC_FILEINVALID; + } + + // Translation + char *translation = new char[trLength]; + memset(translation, 0, sizeof(char) * trLength); + + stream.seekg(trOffset); + stream.read(translation, trLength); + + if (stream.bad()) { + m_error = + "Stream bad during reading. The .mo-file seems to be invalid or has bad " + "descriptions!"; + return moFileReader::EC_FILEINVALID; + } + + std::string original_str = original; + std::string translation_str = translation; + auto ctxSeparator = original_str.find(ContextSeparator); + + // Store it in the map. + if (ctxSeparator == std::string::npos) { + m_lookup[original_str] = translation_str; + numStrings++; + } else { + // try-catch for handling out_of_range exceptions + try { + m_lookup_context[original_str.substr(0, ctxSeparator)][original_str.substr( + ctxSeparator + 1, original_str.length())] = translation_str; + numStrings++; + } catch (...) { + m_error = + "Stream bad during reading. The .mo-file seems to be invalid or has bad " + "descriptions!"; + return moFileReader::EC_ERROR; + } + } + + // Cleanup... + delete[] original; + delete[] translation; + } + + // Done :) + return moFileReader::EC_SUCCESS; + } + + /** \brief Returns the searched translation or returns the input. + * \param[in] id The id of the translation to search for. + * \return The value you passed in via _id or the translated string. + */ + std::string Lookup(const char *id) const { + if (m_lookup.empty()) + return id; + auto iterator = m_lookup.find(id); + + return iterator == m_lookup.end() ? id : iterator->second; + } + + /** \brief Returns the searched translation or returns the input, restricted to the context + * given by context. See https://www.gnu.org/software/gettext/manual/html_node/Contexts.html for + * more info. \param[in] context Restrict to the context given. \param[in] id The id of the + * translation to search for. \return The value you passed in via _id or the translated string. + */ + std::string LookupWithContext(const char *context, const char *id) const { + if (m_lookup_context.empty()) + return id; + auto iterator = m_lookup_context.find(context); + + if (iterator == m_lookup_context.end()) + return id; + auto iterator2 = iterator->second.find(id); + + return iterator2 == iterator->second.end() ? id : iterator2->second; + } + + /// \brief Returns the Error Description. + const std::string &GetErrorDescription() const { + return m_error; + } + + /// \brief Empties the Lookup-Table. + void ClearTable() { + m_lookup.clear(); + m_lookup_context.clear(); + numStrings = 0; + } + + /** \brief Returns the Number of Entries in our Lookup-Table. + * \note The mo-File-table always contains an empty msgid, which contains informations + * about the tranlsation-project. So the real number of strings is always minus 1. + */ + unsigned int GetNumStrings() const { + return numStrings; + } + + /** \brief Exports the whole content of the .mo-File as .html + * \param[in] infile The .mo-File to export. + * \param[in] filename Where to store the .html-file. If empty, the path and filename of the + * _infile with .html appended. \param[in,out] css The css-script for the visual style of the + * file, in case you don't like mine ;). + * \see g_css for the possible and used css-values. + */ + static eErrorCode ExportAsHTML(const std::string &infile, const std::string &filename = "", + const std::string &css = g_css) { + // Read the file + moFileReader reader; + moFileReader::eErrorCode r = reader.ReadFile(infile.c_str()); + if (r != moFileReader::EC_SUCCESS) { + return r; + } + if (reader.m_lookup.empty()) { + return moFileReader::EC_TABLEEMPTY; + } + + // Beautify Output + std::string fname; + size_t pos = infile.find_last_of(MO_PATHSEP); + if (pos != std::string::npos) { + fname = infile.substr(pos + 1, infile.length()); + } else { + fname = infile; + } + + // if there is no filename given, we set it to the .mo + html, e.g. test.mo.html + std::string htmlfile(filename); + if (htmlfile.empty()) { + htmlfile = infile + std::string(".html"); + } + + // Ok, now prepare output. + std::ofstream stream(htmlfile.c_str()); + if (stream.is_open()) { + stream + << R"()" + << std::endl; + stream << "" << std::endl; + stream << R"()" + << std::endl; + stream << "Dump of " << fname << "" << std::endl; + stream << "" << std::endl; + stream << "
" << std::endl; + stream << "

" << fname << "

" << std::endl; + stream << R"()" << std::endl; + + std::stringstream parsee; + parsee << reader.Lookup(""); + + while (!parsee.eof()) { + char buffer[1024]; + parsee.getline(buffer, 1024); + std::string name; + std::string value; + + reader.GetPoEditorString(buffer, name, value); + if (!(name.empty() || value.empty())) { + stream << "" + << std::endl; + } + } + stream << "
Project Info
" << name << "" << value << "
" << std::endl; + stream << "
" << std::endl; + + // Now output the content + stream << R"()" << std::endl; + for (const auto &it : reader.m_lookup) { + if (!it.first.empty()) // Skip the empty msgid, its the table we handled above. + { + stream << "" + << std::endl; + } + } + stream << "
Content
" << it.first << "" << it.second << "

" << std::endl; + + // Separate tables for each context + for (const auto &it : reader.m_lookup_context) { + stream << R"(" + << std::endl; + for (const auto &its : it.second) { + stream << "" + << std::endl; + } + stream << "
)" << it.first << "
" << its.first << "" << its.second << "

" << std::endl; + } + + stream << "
" << std::endl; + stream << "
File generated by moFileReaderSDK
" + << std::endl; + stream << "" << std::endl; + stream.close(); + } else { + return moFileReader::EC_FILENOTFOUND; + } + + return moFileReader::EC_SUCCESS; + } + +protected: + /// \brief Keeps the last error as String. + std::string m_error; + + /** \brief Swap the endianness of a 4 byte WORD. + * \param[in] in The value to swap. + * \return The swapped value. + */ + unsigned long SwapBytes(unsigned long in) { + unsigned long b0 = (in >> 0) & 0xff; + unsigned long b1 = (in >> 8) & 0xff; + unsigned long b2 = (in >> 16) & 0xff; + unsigned long b3 = (in >> 24) & 0xff; + + return (b0 << 24) | (b1 << 16) | (b2 << 8) | b3; + } + +private: + // Holds the lookup-table + moLookupList m_lookup; + moContextLookupList m_lookup_context; + + int numStrings = 0; + + // Replaces < with ( to satisfy html-rules. + static void MakeHtmlConform(std::string &_inout) { + std::string temp = _inout; + for (unsigned int i = 0; i < temp.length(); i++) { + if (temp[i] == '>') { + _inout.replace(i, 1, ")"); + } + if (temp[i] == '<') { + _inout.replace(i, 1, "("); + } + } + } + + // Extracts a value-pair from the po-edit-information + bool GetPoEditorString(const char *_buffer, std::string &_name, std::string &_value) { + std::string line(_buffer); + size_t first = line.find_first_of(':'); + + if (first != std::string::npos) { + _name = line.substr(0, first); + _value = line.substr(first + 1, line.length()); + + // Replace <> with () for Html-Conformity. + MakeHtmlConform(_value); + MakeHtmlConform(_name); + + // Remove spaces from front and end. + Trim(_value); + Trim(_name); + + return true; + } + return false; + } + + // Removes spaces from front and end. + static void Trim(std::string &_in) { + if (_in.empty()) { + return; + } + + _in.erase(0, _in.find_first_not_of(" ")); + _in.erase(_in.find_last_not_of(" ") + 1); + + /*while (_in[0] == ' ') + { + _in = _in.substr(1, _in.length()); + } + while (_in[_in.length()] == ' ') + { + _in = _in.substr(0, _in.length() - 1); + }*/ + } +}; + +#ifndef MO_NO_CONVENIENCE_CLASS +/** \brief Convenience Class + * + * + * This class derives from moFileReader and builds a singleton to access its methods + * in a global manner. + * \note This class is a Singleton. Please access it via moFileReaderSingleton::GetInstance() + * or use the provided wrappers:\n + * - moReadMoFile() + * - _() + * - moFileClearTable() + * - moFileGetErrorDescription() + * - moFileGetNumStrings(); + */ +class moFileReaderSingleton : public moFileReader { +private: + // Private Contructor and Copy-Constructor to avoid + // that this class is instanced. + moFileReaderSingleton() {} + + moFileReaderSingleton(const moFileReaderSingleton &); + + moFileReaderSingleton &operator=(const moFileReaderSingleton &) { + return *this; + } + +public: + /** \brief Singleton-Accessor. + * \return A static instance of moFileReaderSingleton. + */ + static moFileReaderSingleton &GetInstance() { + static moFileReaderSingleton theoneandonly; + return theoneandonly; + } +}; + +/** \brief Reads the .mo-File. + * \param[in] _filename The path to the file to use. + * \see moFileReader::ReadFile() for details. + */ +inline moFileReader::eErrorCode moReadMoFile(const char *_filename) { + moFileReader::eErrorCode r = moFileReaderSingleton::GetInstance().ReadFile(_filename); + return r; +} + +/** \brief Looks for the spec. string to translate. + * \param[in] id The string-id to search. + * \return The translation if found, otherwise it returns id. + */ +inline std::string _(const char *id) { + std::string r = moFileReaderSingleton::GetInstance().Lookup(id); + return r; +} + +/// \brief Resets the Lookup-Table. +inline void moFileClearTable() { + moFileReaderSingleton::GetInstance().ClearTable(); +} + +/// \brief Returns the last known error as string or an empty class. +inline std::string moFileGetErrorDescription() { + std::string r = moFileReaderSingleton::GetInstance().GetErrorDescription(); + return r; +} + +/// \brief Returns the number of entries loaded from the .mo-File. +inline int moFileGetNumStrings() { + int r = moFileReaderSingleton::GetInstance().GetNumStrings(); + return r; +} +#endif + +MO_END_NAMESPACE + +#if defined(_MSC_VER) +#pragma warning(default : 4251) +#endif /* _MSC_VER */ + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +#endif /* __MOFILEREADER_SINGLE_INCLUDE_H_INCLUDED__ */ \ No newline at end of file diff --git a/hikyuu_cpp/hikyuu/global/node/NodeClient.h b/hikyuu_cpp/hikyuu/utilities/node/NodeClient.h similarity index 79% rename from hikyuu_cpp/hikyuu/global/node/NodeClient.h rename to hikyuu_cpp/hikyuu/utilities/node/NodeClient.h index b7512784..f42d5b61 100644 --- a/hikyuu_cpp/hikyuu/global/node/NodeClient.h +++ b/hikyuu_cpp/hikyuu/utilities/node/NodeClient.h @@ -7,6 +7,11 @@ #pragma once +#include "hikyuu/utilities/config.h" +#if !HKU_ENABLE_NODE +#error "Don't enable node client, please config with --node=y" +#endif + #include #include #include @@ -59,13 +64,11 @@ public: return true; + } catch (const std::exception& e) { + HKU_ERROR_IF(m_show_log, "Failed dail server: {}! {}", m_server_addr, e.what()); } catch (...) { + HKU_ERROR_IF(m_show_log, "Failed dail server: {}! Unknown error!", m_server_addr); } - // } catch (const std::exception& e) { - // HKU_ERROR("Failed dail server: {}! {}", m_server_addr, e.what()); - // } catch (...) { - // HKU_ERROR("Failed dail server: {}! Unknown error!", m_server_addr); - // } m_connected = false; nng_close(m_socket); @@ -101,6 +104,10 @@ public: return _send(req) && _recv(res); } + void showLog(bool show) { + m_show_log = show; + } + private: bool _send(const json& req) const noexcept { bool success = false; @@ -118,13 +125,11 @@ private: NODE_NNG_CHECK(rv, "Failed nng_sendmsg!"); success = true; + } catch (const std::exception& e) { + HKU_ERROR_IF(m_show_log, "Failed send result! {}", e.what()); } catch (...) { + HKU_ERROR_IF(m_show_log, "Failed send result! Unknown error!"); } - // } catch (const std::exception& e) { - // HKU_ERROR("Failed send result! {}", e.what()); - // } catch (...) { - // HKU_ERROR("Failed send result! Unknown error!"); - // } if (!success) { nng_msg_free(msg); @@ -137,21 +142,22 @@ private: bool success = false; nng_msg* msg{nullptr}; int rv = nng_recvmsg(m_socket, &msg, 0); - // HKU_ERROR_IF_RETURN(rv != 0, success, "Failed nng_recvmsg! {}", nng_strerror(rv)); - HKU_IF_RETURN(rv != 0, success); + if (rv != 0) { + HKU_ERROR_IF(m_show_log, "Failed nng_recvmsg! {}", nng_strerror(rv)); + return success; + } + m_last_ack_time = Datetime::now(); try { res = decodeMsg(msg); success = true; + } catch (const std::exception& e) { + HKU_ERROR_IF(m_show_log, "Failed recv response! {}", e.what()); } catch (...) { + HKU_ERROR_IF(m_show_log, "Failed recv response! Unknown error!"); } - // } catch (const std::exception& e) { - // HKU_ERROR("Failed recv response! {}", e.what()); - // } catch (...) { - // HKU_ERROR("Failed recv response! Unknown error!"); - // } nng_msg_free(msg); return success; @@ -161,8 +167,9 @@ private: std::mutex m_mutex; std::string m_server_addr; // 服务端地址 nng_socket m_socket; - std::atomic_bool m_connected{false}; Datetime m_last_ack_time{Datetime::now()}; // 最后一次接收服务端响应的时间 + std::atomic_bool m_connected{false}; + std::atomic_bool m_show_log{true}; }; } // namespace hku \ No newline at end of file diff --git a/hikyuu_cpp/hikyuu/global/node/NodeError.h b/hikyuu_cpp/hikyuu/utilities/node/NodeError.h similarity index 97% rename from hikyuu_cpp/hikyuu/global/node/NodeError.h rename to hikyuu_cpp/hikyuu/utilities/node/NodeError.h index 1a7268ef..1e32608e 100644 --- a/hikyuu_cpp/hikyuu/global/node/NodeError.h +++ b/hikyuu_cpp/hikyuu/utilities/node/NodeError.h @@ -7,7 +7,7 @@ #pragma once -#include "hikyuu/exception.h" +#include namespace hku { diff --git a/hikyuu_cpp/hikyuu/global/node/NodeMessage.h b/hikyuu_cpp/hikyuu/utilities/node/NodeMessage.h similarity index 93% rename from hikyuu_cpp/hikyuu/global/node/NodeMessage.h rename to hikyuu_cpp/hikyuu/utilities/node/NodeMessage.h index b3260db8..68e727fc 100644 --- a/hikyuu_cpp/hikyuu/global/node/NodeMessage.h +++ b/hikyuu_cpp/hikyuu/utilities/node/NodeMessage.h @@ -12,6 +12,7 @@ #include #include #include +#include #include "NodeError.h" using json = nlohmann::json; @@ -26,8 +27,8 @@ namespace hku { * req -> * {"cmd": int, ...} * - * req -> - * {"cmd": int, "ret": code, "msg": str} + * <- res + * {"ret": code, "msg": str} // msg 存在错误时返回错误信息 (可选) * * */ diff --git a/hikyuu_cpp/hikyuu/utilities/node/NodeServer.h b/hikyuu_cpp/hikyuu/utilities/node/NodeServer.h new file mode 100644 index 00000000..9867d135 --- /dev/null +++ b/hikyuu_cpp/hikyuu/utilities/node/NodeServer.h @@ -0,0 +1,246 @@ +/* + * Copyright (c) 2022 hikyuu.org + * + * Created on: 2022-04-15 + * Author: fasiondog + */ + +#pragma once + +#include "hikyuu/utilities/config.h" +#if !HKU_ENABLE_NODE +#error "Don't enable node server, please config with --node=y" +#endif + +#include +#include +#include +#include + +#include +#if HKU_OS_WINDOWS +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#include +#else +#include +#endif + +#include "hikyuu/utilities/Log.h" +#include "NodeMessage.h" + +namespace hku { + +class NodeServer { + CLASS_LOGGER_IMP(NodeServer) + +public: + NodeServer() = default; + explicit NodeServer(const std::string& addr) : m_addr(addr) {} + virtual ~NodeServer() { + stop(); + } + + void setAddr(const std::string& addr) { + m_addr = addr; + } + + void regHandle(const std::string& cmd, const std::function& handle) { + m_handles[cmd] = handle; + } + + void regHandle(const std::string& cmd, std::function&& handle) { + m_handles[cmd] = std::move(handle); + } + + void start(size_t max_parrel = 128) { + CLS_CHECK(!m_addr.empty(), "You must set NodeServer's addr first!"); + + // 启动 node server + int rv = nng_rep0_open(&m_socket); + CLS_CHECK(0 == rv, "Failed open server socket! {}", nng_strerror(rv)); + rv = nng_listen(m_socket, m_addr.c_str(), &m_listener, 0); + CLS_CHECK(0 == rv, "Failed listen node server socket ({})! {}", m_addr, nng_strerror(rv)); + CLS_TRACE("channel lisenter server: {}", m_addr); + + m_works.resize(max_parrel); + for (size_t i = 0, total = m_works.size(); i < total; i++) { + Work* w = &m_works[i]; + rv = nng_aio_alloc(&w->aio, _serverCallback, w); + CLS_CHECK(0 == rv, "Failed create work {}! {}", i, nng_strerror(rv)); + rv = nng_ctx_open(&w->ctx, m_socket); + CLS_CHECK(0 == rv, "Failed open ctx {}! {}", i, nng_strerror(rv)); + w->state = Work::INIT; + w->server = this; + } + + for (size_t i = 0, total = m_works.size(); i < total; i++) { + _serverCallback(&m_works[i]); + } + } + + void loop() { + while (true) { + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + } + + void stop() { + HKU_IF_RETURN(m_works.empty(), void()); + for (size_t i = 0, total = m_works.size(); i < total; i++) { + Work* w = &m_works[i]; + w->server = nullptr; + w->state = Work::FINISH; + if (w->aio) { + nng_aio_stop(w->aio); + nng_aio_free(w->aio); + nng_ctx_close(w->ctx); + w->aio = nullptr; + } + } + + // 关闭 socket 服务节点 + nng_listener_close(m_listener); + nng_close(m_socket); + m_works.clear(); + CLS_INFO("stopped node server."); + } + +private: + struct Work { + enum { INIT, RECV, SEND, FINISH } state = INIT; + nng_aio* aio{nullptr}; + nng_ctx ctx; + NodeServer* server{nullptr}; + }; + + static void _serverCallback(void* arg) { + Work* work = static_cast(arg); + int rv = 0; + switch (work->state) { + case Work::INIT: + work->state = Work::RECV; + nng_ctx_recv(work->ctx, work->aio); + break; + + case Work::RECV: + _processRequest(work); + break; + + case Work::SEND: + if ((rv = nng_aio_result(work->aio)) != 0) { + CLS_FATAL("Failed nng_ctx_send! {}", nng_strerror(rv)); + work->state = Work::FINISH; + return; + } + work->state = Work::RECV; + nng_ctx_recv(work->ctx, work->aio); + break; + + case Work::FINISH: + break; + + default: + CLS_FATAL("nng bad state!"); + break; + } + } + + static void _processRequest(Work* work) { + NodeServer* server = work->server; + CLS_IF_RETURN(!server || !work->aio, void()); + nng_msg* msg = nullptr; + json res; + + try { + int rv = nng_aio_result(work->aio); + HKU_CHECK(rv == 0, "Failed nng_aio_result!"); + + msg = nng_aio_get_msg(work->aio); + json req = decodeMsg(msg); + NODE_CHECK(req.contains("cmd"), NodeErrorCode::MISSING_CMD, "Missing command!"); + + // 兼容老版本数字cmd + std::string cmd = req["cmd"].is_number() ? fmt::format("{}", req["cmd"].get()) + : req["cmd"].get(); + auto iter = server->m_handles.find(cmd); + NODE_CHECK(iter != server->m_handles.end(), NodeErrorCode::INVALID_CMD, + "The server does not know how to process the message: {}", cmd); + + // tcp 连接尝试获取客户端地址和端口加入 req 中 + req["remote_host"] = ""; + req["remote_port"] = 0; + nng_pipe p = nng_msg_get_pipe(msg); + if (nng_pipe_id(p) > 0) { + uint16_t port = 0; + nng_sockaddr ra; + rv = nng_pipe_get_addr(p, NNG_OPT_REMADDR, &ra); + if (rv == 0) { + if (ra.s_family == NNG_AF_INET) { + char ipAddr[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, (void*)&ra.s_in.sa_addr, ipAddr, INET_ADDRSTRLEN); + port = ntohs(ra.s_in.sa_port); + req["remote_host"] = ipAddr; + req["remote_port"] = port; + } else if (ra.s_family == NNG_AF_INET6) { + char ipAddr[INET6_ADDRSTRLEN]; + inet_ntop(AF_INET6, (void*)&ra.s_in.sa_addr, ipAddr, INET6_ADDRSTRLEN); + port = ntohs(ra.s_in6.sa_port); + req["remote_host"] = ipAddr; + req["remote_port"] = port; + } + } + } + + res = iter->second(std::move(req)); + res["ret"] = NodeErrorCode::SUCCESS; + encodeMsg(msg, res); + + nng_aio_set_msg(work->aio, msg); + work->state = Work::SEND; + nng_ctx_send(work->ctx, work->aio); + + } catch (const NodeNngError& e) { + CLS_FATAL(e.what()); + work->state = Work::FINISH; + + } catch (const NodeError& e) { + CLS_ERROR(e.what()); + res["ret"] = e.errcode(); + res["msg"] = e.what(); + encodeMsg(msg, res); + nng_aio_set_msg(work->aio, msg); + work->state = Work::SEND; + nng_ctx_send(work->ctx, work->aio); + + } catch (const std::exception& e) { + CLS_ERROR(e.what()); + res["ret"] = NodeErrorCode::UNKNOWN_ERROR; + res["msg"] = e.what(); + encodeMsg(msg, res); + nng_aio_set_msg(work->aio, msg); + work->state = Work::SEND; + nng_ctx_send(work->ctx, work->aio); + + } catch (...) { + std::string errmsg = "Unknown error!"; + CLS_ERROR(errmsg); + res["ret"] = NodeErrorCode::UNKNOWN_ERROR; + res["msg"] = errmsg; + nng_aio_set_msg(work->aio, msg); + work->state = Work::SEND; + nng_ctx_send(work->ctx, work->aio); + } + } + +private: + std::string m_addr; + nng_socket m_socket; + nng_listener m_listener; + std::vector m_works; + std::unordered_map> m_handles; +}; + +} // namespace hku \ No newline at end of file diff --git a/hikyuu_cpp/hikyuu/utilities/os.cpp b/hikyuu_cpp/hikyuu/utilities/os.cpp index c17e528f..b53e4d8e 100644 --- a/hikyuu_cpp/hikyuu/utilities/os.cpp +++ b/hikyuu_cpp/hikyuu/utilities/os.cpp @@ -33,7 +33,7 @@ #include #include #include "arithmetic.h" -#include "hikyuu/Log.h" +#include "Log.h" #include "os.h" #ifdef _MSC_VER @@ -43,7 +43,7 @@ namespace hku { -bool HKU_API existFile(const std::string& filename) noexcept { +bool HKU_UTILS_API existFile(const std::string &filename) noexcept { #ifdef _WIN32 auto attribs = GetFileAttributesA(HKU_PATH(filename).c_str()); return attribs != INVALID_FILE_ATTRIBUTES; @@ -53,7 +53,7 @@ bool HKU_API existFile(const std::string& filename) noexcept { #endif } -bool HKU_API createDir(const std::string& pathname) noexcept { +bool HKU_UTILS_API createDir(const std::string &pathname) noexcept { std::string npath = HKU_PATH(pathname); // 目录已存在,直接返回成功 @@ -76,36 +76,36 @@ bool HKU_API createDir(const std::string& pathname) noexcept { return access(npath.c_str(), 0) == 0; } -bool HKU_API isColorTerminal() noexcept { +bool HKU_UTILS_API isColorTerminal() noexcept { #if defined(_WIN32) || defined(__linux__) || defined(__ANDROID__) return true; #elif TARGET_OS_IPHONE return false; #else - static constexpr std::array Terms = { + static constexpr std::array Terms = { {"ansi", "color", "console", "cygwin", "gnome", "konsole", "kterm", "linux", "msys", "putty", "rxvt", "screen", "vt100", "xterm"}}; - const char* env_p = std::getenv("TERM"); + const char *env_p = std::getenv("TERM"); if (env_p == nullptr) { return false; } static const bool result = std::any_of(std::begin(Terms), std::end(Terms), - [&](const char* term) { return std::strstr(env_p, term) != nullptr; }); + [&](const char *term) { return std::strstr(env_p, term) != nullptr; }); return result; #endif } // 删除文件 -bool HKU_API removeFile(const std::string& filename) noexcept { +bool HKU_UTILS_API removeFile(const std::string &filename) noexcept { return std::remove(HKU_PATH(filename).c_str()) == 0; } #ifdef _WIN32 // 删除目录及其包含的文件和子目录 -bool HKU_API removeDir(const std::string& path) noexcept { +bool HKU_UTILS_API removeDir(const std::string &path) noexcept { std::string strPath = HKU_PATH(path); struct _finddata_t fb; // 查找相同属性文件的存储结构体 // 制作用于正则化路径 @@ -148,14 +148,14 @@ bool HKU_API removeDir(const std::string& path) noexcept { #else // #ifdef _WIN32 // 删除目录及其包含的文件和子目录 -bool HKU_API removeDir(const std::string& path) noexcept { +bool HKU_UTILS_API removeDir(const std::string &path) noexcept { std::string strPath(path); if (strPath.at(strPath.length() - 1) != '\\' && strPath.at(strPath.length() - 1) != '/') { strPath.append("/"); } - DIR* d = opendir(strPath.c_str()); // 打开这个目录 + DIR *d = opendir(strPath.c_str()); // 打开这个目录 if (d != NULL) { - struct dirent* dt = NULL; + struct dirent *dt = NULL; // 逐个读取目录中的文件到dt while (NULL != (dt = readdir(d))) { @@ -178,7 +178,7 @@ bool HKU_API removeDir(const std::string& path) noexcept { } #endif // #ifdef _WIN32 -bool HKU_API copyFile(const std::string& src, const std::string& dst, bool flush) noexcept { +bool HKU_UTILS_API copyFile(const std::string &src, const std::string &dst, bool flush) noexcept { bool success = false; try { std::ifstream srcio(HKU_PATH(src), std::ios::binary); @@ -194,8 +194,8 @@ bool HKU_API copyFile(const std::string& src, const std::string& dst, bool flush return success; } -bool HKU_API renameFile(const std::string& oldname, const std::string& newname, - bool overlay) noexcept { +bool HKU_UTILS_API renameFile(const std::string &oldname, const std::string &newname, + bool overlay) noexcept { // 先判定文件是否存在,保证 std::rename 的行为和系统无关 if (overlay) { HKU_ERROR_IF_RETURN(existFile(newname) && !removeFile(newname), false, @@ -214,7 +214,7 @@ bool HKU_API renameFile(const std::string& oldname, const std::string& newname, * 获取用户路径 */ static std::string _getUserDir() { - char* home = getenv("HOME"); + char *home = getenv("HOME"); if (home) { return std::string(home); } @@ -233,7 +233,7 @@ static std::string _getUserDir() { return std::string(); } -std::string HKU_API getUserDir() { +std::string HKU_UTILS_API getUserDir() { #ifdef _WIN32 return GBToUTF8(_getUserDir()); #else @@ -241,9 +241,9 @@ std::string HKU_API getUserDir() { #endif } -std::string HKU_API getCurrentDir() { +std::string HKU_UTILS_API getCurrentDir() { std::string ret; - char* buffer = NULL; + char *buffer = NULL; #if HKU_OS_WINSOWS buffer = _getcwd(buffer, 0); #else @@ -261,7 +261,7 @@ std::string HKU_API getCurrentDir() { return ret; } -uint64_t HKU_API getDiskFreeSpace(const char* path) { +uint64_t HKU_UTILS_API getDiskFreeSpace(const char *path) { #if HKU_OS_WINDOWS uint64_t freespace = Null(); std::string npath = HKU_PATH(path); @@ -283,7 +283,7 @@ uint64_t HKU_API getDiskFreeSpace(const char* path) { #endif } -std::string HKU_API getPlatform() { +std::string HKU_UTILS_API getPlatform() { std::string ret; #if HKU_OS_WINDOWS ret = "windows"; @@ -303,7 +303,7 @@ std::string HKU_API getPlatform() { return ret; } -std::string HKU_API getCpuArch() { +std::string HKU_UTILS_API getCpuArch() { std::string ret; #if HKU_ARCH_ARM ret = "arm"; diff --git a/hikyuu_cpp/hikyuu/utilities/os.h b/hikyuu_cpp/hikyuu/utilities/os.h index 9f4e30bf..021ac392 100644 --- a/hikyuu_cpp/hikyuu/utilities/os.h +++ b/hikyuu_cpp/hikyuu/utilities/os.h @@ -17,8 +17,8 @@ #include "arithmetic.h" #include -#ifndef HKU_API -#define HKU_API +#ifndef HKU_UTILS_API +#define HKU_UTILS_API #endif namespace hku { @@ -27,27 +27,27 @@ namespace hku { * 判断文件或目录是否存在 * @param filename 文件名或目录名 */ -bool HKU_API existFile(const std::string& filename) noexcept; +bool HKU_UTILS_API existFile(const std::string &filename) noexcept; /** * 创建目录 * @param pathname 路径名 * @return 如果目录已存在或者创建成功返回 true,否则返回 false */ -bool HKU_API createDir(const std::string& pathname) noexcept; +bool HKU_UTILS_API createDir(const std::string &pathname) noexcept; /** * 删除文件 * @param filename 文件名 * @return 删除失败或文件不存在时返回false */ -bool HKU_API removeFile(const std::string& filename) noexcept; +bool HKU_UTILS_API removeFile(const std::string &filename) noexcept; /** * 删除目录及其包含的文件和子目录 * @param path 待删除目录 */ -bool HKU_API removeDir(const std::string& path) noexcept; +bool HKU_UTILS_API removeDir(const std::string &path) noexcept; /** * 拷贝文件 @@ -55,7 +55,8 @@ bool HKU_API removeDir(const std::string& path) noexcept; * @param dst 目标文件 * @param flush 是否立即落盘 */ -bool HKU_API copyFile(const std::string& src, const std::string& dst, bool flush = false) noexcept; +bool HKU_UTILS_API copyFile(const std::string &src, const std::string &dst, + bool flush = false) noexcept; /** * 文件、目录改名或移动 @@ -65,23 +66,23 @@ bool HKU_API copyFile(const std::string& src, const std::string& dst, bool flush * @return true 成功 * @return false 失败 旧名文件不存在,或文件被占用等其他原因导致的失败 */ -bool HKU_API renameFile(const std::string& oldname, const std::string& newname, - bool overlay = false) noexcept; +bool HKU_UTILS_API renameFile(const std::string &oldname, const std::string &newname, + bool overlay = false) noexcept; /** * 获取用户路径 */ -std::string HKU_API getUserDir(); +std::string HKU_UTILS_API getUserDir(); /** * 获取程序当前所在路径 */ -std::string HKU_API getCurrentDir(); +std::string HKU_UTILS_API getCurrentDir(); /** * 输出终端是否支持彩色控制字符 */ -bool HKU_API isColorTerminal() noexcept; +bool HKU_UTILS_API isColorTerminal() noexcept; /** * @brief 获取硬盘剩余存储空间大小 @@ -90,12 +91,12 @@ bool HKU_API isColorTerminal() noexcept; * @param path 指定路径名 * @return uint64_t 获取失败时,返回 Null() */ -uint64_t HKU_API getDiskFreeSpace(const char* path); +uint64_t HKU_UTILS_API getDiskFreeSpace(const char *path); /** 获取当前系统名称 */ -std::string HKU_API getPlatform(); +std::string HKU_UTILS_API getPlatform(); /** 获取当前CPU架构名称 */ -std::string HKU_API getCpuArch(); +std::string HKU_UTILS_API getCpuArch(); } // namespace hku \ No newline at end of file diff --git a/hikyuu_cpp/hikyuu/utilities/snowflake.h b/hikyuu_cpp/hikyuu/utilities/snowflake.h new file mode 100644 index 00000000..577dbae4 --- /dev/null +++ b/hikyuu_cpp/hikyuu/utilities/snowflake.h @@ -0,0 +1,110 @@ +/* + * Copyright(C) 2021 hikyuu.org + * + * The code comes from: https://github.com/sniper00/snowflake-cpp + * Thanks sniper00 + * + * Create on: 2021-04-13 + * Author: fasiondog + */ + +#pragma once + +#include +#include +#include +#include + +namespace hku { + +class snowflake_nonlock { +public: + void lock() {} + void unlock() {} +}; + +template +class snowflake { + using lock_type = Lock; + static constexpr int64_t TWEPOCH = Twepoch; + static constexpr int64_t WORKER_ID_BITS = 5L; + static constexpr int64_t DATACENTER_ID_BITS = 5L; + static constexpr int64_t MAX_WORKER_ID = (1 << WORKER_ID_BITS) - 1; + static constexpr int64_t MAX_DATACENTER_ID = (1 << DATACENTER_ID_BITS) - 1; + static constexpr int64_t SEQUENCE_BITS = 12L; + static constexpr int64_t WORKER_ID_SHIFT = SEQUENCE_BITS; + static constexpr int64_t DATACENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS; + static constexpr int64_t TIMESTAMP_LEFT_SHIFT = + SEQUENCE_BITS + WORKER_ID_BITS + DATACENTER_ID_BITS; + static constexpr int64_t SEQUENCE_MASK = (1 << SEQUENCE_BITS) - 1; + + using time_point = std::chrono::time_point; + + time_point start_time_point_ = std::chrono::steady_clock::now(); + int64_t start_millsecond_ = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + + int64_t last_timestamp_ = -1; + int64_t workerid_ = 0; + int64_t datacenterid_ = 0; + int64_t sequence_ = 0; + lock_type lock_; + +public: + snowflake() = default; + + snowflake(const snowflake&) = delete; + + snowflake& operator=(const snowflake&) = delete; + + void init(int64_t workerid, int64_t datacenterid) { + std::lock_guard lock(lock_); + if (workerid > MAX_WORKER_ID || workerid < 0) { + throw std::runtime_error("worker Id can't be greater than 31 or less than 0"); + } + + if (datacenterid > MAX_DATACENTER_ID || datacenterid < 0) { + throw std::runtime_error("datacenter Id can't be greater than 31 or less than 0"); + } + + workerid_ = workerid; + datacenterid_ = datacenterid; + } + + int64_t nextid() { + std::lock_guard lock(lock_); + // std::chrono::steady_clock cannot decrease as physical time moves forward + auto timestamp = millsecond(); + if (last_timestamp_ == timestamp) { + sequence_ = (sequence_ + 1) & SEQUENCE_MASK; + if (sequence_ == 0) { + timestamp = wait_next_millis(last_timestamp_); + } + } else { + sequence_ = 0; + } + + last_timestamp_ = timestamp; + + return ((timestamp - TWEPOCH) << TIMESTAMP_LEFT_SHIFT) | + (datacenterid_ << DATACENTER_ID_SHIFT) | (workerid_ << WORKER_ID_SHIFT) | sequence_; + } + +private: + int64_t millsecond() const { + auto diff = std::chrono::duration_cast( + std::chrono::steady_clock::now() - start_time_point_); + return start_millsecond_ + diff.count(); + } + + int64_t wait_next_millis(int64_t last) const { + auto timestamp = millsecond(); + while (timestamp <= last) { + timestamp = millsecond(); + } + return timestamp; + } +}; + +} // namespace hku \ No newline at end of file diff --git a/hikyuu_cpp/hikyuu/utilities/string_view.h b/hikyuu_cpp/hikyuu/utilities/string_view.h new file mode 100644 index 00000000..c18a752d --- /dev/null +++ b/hikyuu_cpp/hikyuu/utilities/string_view.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2019~2021, hikyuu + * + * Created on: 2021/12/16 + * Author: fasiondog + */ + +#pragma once + +#include "cppdef.h" + +#if CPP_STANDARD >= CPP_STANDARD_17 +#include +#include +namespace hku { + +using std::string_view; + +} + +#else + +#include +#include +#include "Log.h" + +namespace hku { + +class string_view { +public: + constexpr string_view() noexcept = default; + + string_view(const char *data, std::size_t size) : _data(data), _size(size) { + HKU_CHECK_THROW(data, std::invalid_argument, "Input null ptr!"); + }; + + string_view(const char *data) : _data(data) { // NOSONAR + HKU_CHECK_THROW(data, std::invalid_argument, "Input null ptr!"); + _size = std::strlen(data); + } + + string_view(const std::string &str) : _data(str.data()), _size(str.size()) {} // NOSONAR + + constexpr string_view(const string_view &) noexcept = default; + + string_view &operator=(const string_view &) noexcept = default; + + constexpr const char &operator[](std::size_t pos) const { + return _data[pos]; + } + + constexpr const char *data() const noexcept { + return _data; + } + + constexpr std::size_t size() const noexcept { + return _size; + } + +private: + constexpr string_view(std::nullptr_t) = delete; + +private: + const char *_data = nullptr; + std::size_t _size = 0; +}; + +} // namespace hku + +#endif diff --git a/hikyuu_cpp/hikyuu/utilities/thread/MQStealThreadPool.h b/hikyuu_cpp/hikyuu/utilities/thread/MQStealThreadPool.h index c943f440..3c50803b 100644 --- a/hikyuu_cpp/hikyuu/utilities/thread/MQStealThreadPool.h +++ b/hikyuu_cpp/hikyuu/utilities/thread/MQStealThreadPool.h @@ -22,8 +22,8 @@ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wsign-compare" #endif -#ifndef HKU_API -#define HKU_API +#ifndef HKU_UTILS_API +#define HKU_UTILS_API #endif namespace hku { @@ -35,7 +35,7 @@ namespace hku { #ifdef _MSC_VER class MQStealThreadPool { #else -class HKU_API MQStealThreadPool { +class HKU_UTILS_API MQStealThreadPool { #endif public: /** diff --git a/hikyuu_cpp/hikyuu/utilities/thread/MQThreadPool.h b/hikyuu_cpp/hikyuu/utilities/thread/MQThreadPool.h index fb016d14..61245eb7 100644 --- a/hikyuu_cpp/hikyuu/utilities/thread/MQThreadPool.h +++ b/hikyuu_cpp/hikyuu/utilities/thread/MQThreadPool.h @@ -23,8 +23,8 @@ #pragma GCC diagnostic ignored "-Wsign-compare" #endif -#ifndef HKU_API -#define HKU_API +#ifndef HKU_UTILS_API +#define HKU_UTILS_API #endif namespace hku { @@ -38,7 +38,7 @@ namespace hku { #ifdef _MSC_VER class MQThreadPool { #else -class HKU_API MQThreadPool { +class HKU_UTILS_API MQThreadPool { #endif public: /** diff --git a/hikyuu_cpp/hikyuu/utilities/thread/StealThreadPool.h b/hikyuu_cpp/hikyuu/utilities/thread/StealThreadPool.h index b1682002..a286939a 100644 --- a/hikyuu_cpp/hikyuu/utilities/thread/StealThreadPool.h +++ b/hikyuu_cpp/hikyuu/utilities/thread/StealThreadPool.h @@ -24,8 +24,8 @@ #pragma GCC diagnostic ignored "-Wsign-compare" #endif -#ifndef HKU_API -#define HKU_API +#ifndef HKU_UTILS_API +#define HKU_UTILS_API #endif namespace hku { @@ -39,7 +39,7 @@ namespace hku { #ifdef _MSC_VER class StealThreadPool { #else -class HKU_API StealThreadPool { +class HKU_UTILS_API StealThreadPool { #endif public: /** diff --git a/hikyuu_cpp/hikyuu/utilities/thread/ThreadPool.h b/hikyuu_cpp/hikyuu/utilities/thread/ThreadPool.h index 401f4524..7241644e 100644 --- a/hikyuu_cpp/hikyuu/utilities/thread/ThreadPool.h +++ b/hikyuu_cpp/hikyuu/utilities/thread/ThreadPool.h @@ -25,8 +25,8 @@ #pragma GCC diagnostic ignored "-Wsign-compare" #endif -#ifndef HKU_API -#define HKU_API +#ifndef HKU_UTILS_API +#define HKU_UTILS_API #endif namespace hku { @@ -40,7 +40,7 @@ namespace hku { #ifdef _MSC_VER class ThreadPool { #else -class HKU_API ThreadPool { +class HKU_UTILS_API ThreadPool { #endif public: /** diff --git a/hikyuu_cpp/hikyuu/xmake.lua b/hikyuu_cpp/hikyuu/xmake.lua index dd5b20da..b0c91519 100644 --- a/hikyuu_cpp/hikyuu/xmake.lua +++ b/hikyuu_cpp/hikyuu/xmake.lua @@ -1,15 +1,8 @@ target("hikyuu") set_kind("$(kind)") - -- if is_mode("debug", "coverage", "asan", "msan", "tsan", "lsan") then - -- set_kind("static") - -- else - -- set_kind("shared") - -- end - add_options("hdf5", "mysql", "sqlite", "tdx", "feedback", "stacktrace", "spend_time", "log_level") - - add_packages("boost", "fmt", "spdlog", "flatbuffers", "nng", "nlohmann_json", "cpp-httplib") + add_packages("boost", "fmt", "spdlog", "flatbuffers", "nng", "nlohmann_json") if is_plat("windows", "linux", "cross") then add_packages("sqlite3") end @@ -20,6 +13,7 @@ target("hikyuu") set_configdir("./") add_configfiles("$(projectdir)/config.h.in") add_configfiles("$(projectdir)/version.h.in") + add_configfiles("$(projectdir)/config_utils.h.in", {prefixdir="utilities", filename="config.h"}) add_defines("CPPHTTPLIB_OPENSSL_SUPPORT", "CPPHTTPLIB_ZLIB_SUPPORT") @@ -38,6 +32,7 @@ target("hikyuu") if is_plat("windows") then if is_kind("shared") then add_defines("HKU_API=__declspec(dllexport)") + add_defines("HKU_UTILS_API=__declspec(dllexport)") end if get_config("hdf5") then if is_mode("release") then @@ -77,15 +72,17 @@ target("hikyuu") -- add files -- add_files("./**.cpp|data_driver/**.cpp|utilities/db_connect/mysql/*.cpp") - add_files("./**.cpp|data_driver/**.cpp|utilities/db_connect/mysql/*.cpp") + add_files("./**.cpp|data_driver/**.cpp|utilities/db_connect/mysql/**.cpp|utilities/mo/**.cpp") add_files("./data_driver/*.cpp") if get_config("hdf5") or get_config("sqlite") then add_files("./data_driver/base_info/sqlite/**.cpp") + add_files("./data_driver/block_info/sqlite/**.cpp") end if get_config("mysql") then add_files("./data_driver/base_info/mysql/**.cpp") + add_files("./data_driver/block_info/mysql/**.cpp") end - add_files("./data_driver/block_info/**.cpp") + add_files("./data_driver/block_info/qianlong/**.cpp") add_files("./data_driver/kdata/cvs/**.cpp") if get_config("sqlite") then add_files("./data_driver/kdata/sqlite/**.cpp") @@ -105,32 +102,19 @@ target("hikyuu") end after_build(function(target) - -- 不同平台的库后缀名 - local lib_suffix = ".so" - if is_plat("windows") then - lib_suffix = ".dll" - elseif is_plat("macosx") then - lib_suffix = ".dylib" - end - - local libdir = get_config("buildir") .. "/" .. get_config("mode") .. "/" .. get_config("plat") .. "/" .. - get_config("arch") .. "/lib" - -- 将依赖的库拷贝至build的输出目录 - for libname, pkg in pairs(target:pkgs()) do - local pkg_path = pkg:installdir() - if pkg_path ~= nil then - print("copy dependents: " .. pkg_path) - os.trycp(pkg_path .. "/bin/*" .. lib_suffix, libdir) - os.trycp(pkg_path .. "/lib/*" .. lib_suffix, libdir) - os.trycp(pkg_path .. "/lib/*.so.*", libdir) - end - end + local destpath = get_config("buildir") .. "/" .. get_config("mode") .. "/" .. get_config("plat") .. "/" .. get_config("arch") + print(destpath) + import("core.project.task") + task.run("copy_dependents", {}, target, destpath, true) end) - + after_install(function(target) local dst_path = target:installdir() .. "/include/hikyuu/python/" os.cp("$(projectdir)/hikyuu_pywrap/pybind_utils.h", dst_path) os.cp("$(projectdir)/hikyuu_pywrap/pickle_support.h", dst_path) - end) + local destpath = target:installdir() + import("core.project.task") + task.run("copy_dependents", {}, target, destpath, true) + end) target_end() diff --git a/hikyuu_cpp/unit_test/hikyuu/hikyuu/test_Block.cpp b/hikyuu_cpp/unit_test/hikyuu/hikyuu/test_Block.cpp index 9efb6946..9f201a8a 100644 --- a/hikyuu_cpp/unit_test/hikyuu/hikyuu/test_Block.cpp +++ b/hikyuu_cpp/unit_test/hikyuu/hikyuu/test_Block.cpp @@ -7,7 +7,7 @@ #include "doctest/doctest.h" #include -#include +#include "hikyuu/utilities/Log.h" #include #include diff --git a/hikyuu_cpp/unit_test/hikyuu/hikyuu/test_StockManager.cpp b/hikyuu_cpp/unit_test/hikyuu/hikyuu/test_StockManager.cpp index 11c9c1a0..81e19fe8 100644 --- a/hikyuu_cpp/unit_test/hikyuu/hikyuu/test_StockManager.cpp +++ b/hikyuu_cpp/unit_test/hikyuu/hikyuu/test_StockManager.cpp @@ -8,7 +8,7 @@ #include "doctest/doctest.h" #include #include -#include +#include using namespace hku; diff --git a/hikyuu_cpp/unit_test/hikyuu/utilities/datetime/test_Datetime.cpp b/hikyuu_cpp/unit_test/hikyuu/utilities/datetime/test_Datetime.cpp index f38e59d6..4f252ff5 100644 --- a/hikyuu_cpp/unit_test/hikyuu/utilities/datetime/test_Datetime.cpp +++ b/hikyuu_cpp/unit_test/hikyuu/utilities/datetime/test_Datetime.cpp @@ -9,7 +9,7 @@ #include #include -#include +#include "hikyuu/utilities/Log.h" using namespace hku; diff --git a/hikyuu_cpp/unit_test/hikyuu/utilities/db_connect/test_DBCondition.cpp b/hikyuu_cpp/unit_test/hikyuu/utilities/db_connect/test_DBCondition.cpp index a986a32f..a4e551ea 100644 --- a/hikyuu_cpp/unit_test/hikyuu/utilities/db_connect/test_DBCondition.cpp +++ b/hikyuu_cpp/unit_test/hikyuu/utilities/db_connect/test_DBCondition.cpp @@ -7,7 +7,7 @@ #include "doctest/doctest.h" #include -#include +#include "hikyuu/utilities/Log.h" #include using namespace hku; diff --git a/hikyuu_cpp/unit_test/hikyuu/utilities/test_Parameter.cpp b/hikyuu_cpp/unit_test/hikyuu/utilities/test_Parameter.cpp index f28316f2..e00984e0 100644 --- a/hikyuu_cpp/unit_test/hikyuu/utilities/test_Parameter.cpp +++ b/hikyuu_cpp/unit_test/hikyuu/utilities/test_Parameter.cpp @@ -6,7 +6,7 @@ */ #include "doctest/doctest.h" -#include +#include "hikyuu/utilities/Log.h" #include #include diff --git a/hikyuu_cpp/unit_test/hikyuu/utilities/test_TimerManager.cpp b/hikyuu_cpp/unit_test/hikyuu/utilities/test_TimerManager.cpp index 81c8b114..d4d88e80 100644 --- a/hikyuu_cpp/unit_test/hikyuu/utilities/test_TimerManager.cpp +++ b/hikyuu_cpp/unit_test/hikyuu/utilities/test_TimerManager.cpp @@ -7,7 +7,7 @@ #include "doctest/doctest.h" #include -#include +#include "hikyuu/utilities/Log.h" using namespace hku; diff --git a/hikyuu_cpp/unit_test/hikyuu/utilities/test_arithmetic.cpp b/hikyuu_cpp/unit_test/hikyuu/utilities/test_arithmetic.cpp index b621fa8f..b60fac2b 100644 --- a/hikyuu_cpp/unit_test/hikyuu/utilities/test_arithmetic.cpp +++ b/hikyuu_cpp/unit_test/hikyuu/utilities/test_arithmetic.cpp @@ -7,7 +7,7 @@ #include "doctest/doctest.h" #include -#include +#include "hikyuu/utilities/Log.h" using namespace hku; @@ -102,15 +102,16 @@ TEST_CASE("test_split_by_char") { x = "100.1."; splits = split(x, '.'); - CHECK_EQ(splits.size(), 2); + CHECK_EQ(splits.size(), 3); CHECK_EQ(splits[0], "100"); CHECK_EQ(splits[1], "1"); x = ".."; splits = split(x, '.'); - CHECK_EQ(splits.size(), 2); + CHECK_EQ(splits.size(), 3); CHECK_EQ(splits[0], ""); CHECK_EQ(splits[1], ""); + CHECK_EQ(splits[2], ""); } TEST_CASE("test_split_by_string") { @@ -129,9 +130,10 @@ TEST_CASE("test_split_by_string") { // 分割字符串长度为1 x = "100.1."; splits = split(x, "."); - CHECK_EQ(splits.size(), 2); + CHECK_EQ(splits.size(), 3); CHECK_EQ(splits[0], "100"); CHECK_EQ(splits[1], "1"); + CHECK_EQ(splits[2], ""); // 分割字符串长度为2 x = "100.1.234.1.56"; @@ -143,9 +145,10 @@ TEST_CASE("test_split_by_string") { x = ".."; splits = split(x, "."); - CHECK_EQ(splits.size(), 2); + CHECK_EQ(splits.size(), 3); CHECK_EQ(splits[0], ""); CHECK_EQ(splits[1], ""); + CHECK_EQ(splits[2], ""); } /** @} */ \ No newline at end of file diff --git a/hikyuu_cpp/unit_test/hikyuu/utilities/test_iniparser.cpp b/hikyuu_cpp/unit_test/hikyuu/utilities/test_iniparser.cpp index 023309be..7d7ba447 100644 --- a/hikyuu_cpp/unit_test/hikyuu/utilities/test_iniparser.cpp +++ b/hikyuu_cpp/unit_test/hikyuu/utilities/test_iniparser.cpp @@ -11,7 +11,7 @@ #include #include #include -#include +#include using namespace hku; using namespace std; diff --git a/hikyuu_cpp/unit_test/hikyuu/utilities/test_os.cpp b/hikyuu_cpp/unit_test/hikyuu/utilities/test_os.cpp index e7fe4f0a..e8522256 100644 --- a/hikyuu_cpp/unit_test/hikyuu/utilities/test_os.cpp +++ b/hikyuu_cpp/unit_test/hikyuu/utilities/test_os.cpp @@ -7,7 +7,7 @@ */ #include -#include +#include "hikyuu/utilities/Log.h" #include using namespace hku; diff --git a/hikyuu_cpp/unit_test/hikyuu/utilities/thread/test_ThreadPool.cpp b/hikyuu_cpp/unit_test/hikyuu/utilities/thread/test_ThreadPool.cpp index 829c502e..d319a62f 100644 --- a/hikyuu_cpp/unit_test/hikyuu/utilities/thread/test_ThreadPool.cpp +++ b/hikyuu_cpp/unit_test/hikyuu/utilities/thread/test_ThreadPool.cpp @@ -13,7 +13,7 @@ #include #include #include -#include +#include "hikyuu/utilities/Log.h" using namespace hku; diff --git a/hikyuu_cpp/unit_test/xmake.lua b/hikyuu_cpp/unit_test/xmake.lua index 25f95c12..40108e33 100644 --- a/hikyuu_cpp/unit_test/xmake.lua +++ b/hikyuu_cpp/unit_test/xmake.lua @@ -59,8 +59,6 @@ target("unit-test") set_kind("binary") set_default(false) - add_options("hdf5", "mysql", "sqlite", "tdx", "feedback", "stacktrace", "spend_time", "log_level") - add_packages("boost", "fmt", "spdlog", "doctest", "sqlite3") if get_config("mysql") then if is_plat("macosx") then @@ -81,6 +79,7 @@ target("unit-test") if is_plat("windows") and get_config("kind") == "shared" then add_defines("HKU_API=__declspec(dllimport)") + add_defines("HKU_UTILS_API=__declspec(dllimport)") end add_deps("hikyuu") @@ -106,8 +105,6 @@ target("small-test") set_kind("binary") set_default(false) - add_options("hdf5", "mysql", "sqlite", "tdx", "feedback", "stacktrace", "spend_time", "log_level") - add_packages("boost", "fmt", "spdlog", "doctest", "sqlite3") if get_config("mysql") then if is_plat("macosx") then @@ -130,8 +127,9 @@ target("small-test") add_cxflags("-Wno-sign-compare") end - if is_plat("windows") and is_mode("release") then + if is_plat("windows") and get_config("kind") == "shared" then add_defines("HKU_API=__declspec(dllimport)") + add_defines("HKU_UTILS_API=__declspec(dllimport)") end add_deps("hikyuu") @@ -152,8 +150,6 @@ target("real-test") set_kind("binary") set_default(false) - add_options("hdf5", "mysql", "sqlite", "tdx", "feedback", "stacktrace", "spend_time", "log_level") - add_packages("boost", "fmt", "spdlog", "doctest", "sqlite3") if get_config("mysql") then if is_plat("macosx") then @@ -174,6 +170,7 @@ target("real-test") if is_plat("windows") and get_config("kind") == "shared" then add_defines("HKU_API=__declspec(dllimport)") + add_defines("HKU_UTILS_API=__declspec(dllimport)") end add_defines("HKU_USE_REAL_DATA_TEST") diff --git a/hikyuu_pywrap/_Log.cpp b/hikyuu_pywrap/_Log.cpp index 4d4e3ba3..25fdcc2d 100644 --- a/hikyuu_pywrap/_Log.cpp +++ b/hikyuu_pywrap/_Log.cpp @@ -5,7 +5,7 @@ * Author: fasiondog */ -#include +#include "hikyuu/utilities/Log.h" #include "pybind_utils.h" using namespace hku; diff --git a/hikyuu_pywrap/global/_SpotRecord.cpp b/hikyuu_pywrap/global/_SpotRecord.cpp new file mode 100644 index 00000000..c9d2ffd8 --- /dev/null +++ b/hikyuu_pywrap/global/_SpotRecord.cpp @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2024 hikyuu.org + * + * Created on: 2024-08-13 + * Author: fasiondog + */ + +#include +#include "../pybind_utils.h" + +using namespace hku; +namespace py = pybind11; + +void export_SpotRecord(py::module& m) { + py::class_(m, "SpotRecord") + .def(py::init<>()) + .def_readwrite("market", &SpotRecord::market) + .def_readwrite("code", &SpotRecord::code) + .def_readwrite("name", &SpotRecord::name) + .def_readwrite("datetime", &SpotRecord::datetime) + .def_readwrite("yesterday_close", &SpotRecord::yesterday_close) + .def_readwrite("open", &SpotRecord::open) + .def_readwrite("high", &SpotRecord::high) + .def_readwrite("low", &SpotRecord::low) + .def_readwrite("close", &SpotRecord::close) + .def_readwrite("amount", &SpotRecord::amount) + .def_readwrite("volume", &SpotRecord::volume) + .def_readwrite("bid1", &SpotRecord::bid1) + .def_readwrite("bid1_amount", &SpotRecord::bid1_amount) + .def_readwrite("bid2", &SpotRecord::bid2) + .def_readwrite("bid2_amount", &SpotRecord::bid2_amount) + .def_readwrite("bid3", &SpotRecord::bid3) + .def_readwrite("bid3_amount", &SpotRecord::bid3_amount) + .def_readwrite("bid4", &SpotRecord::bid4) + .def_readwrite("bid4_amount", &SpotRecord::bid4_amount) + .def_readwrite("bid5", &SpotRecord::bid5) + .def_readwrite("bid5_amount", &SpotRecord::bid5_amount) + .def_readwrite("ask1", &SpotRecord::ask1) + .def_readwrite("ask1_amount", &SpotRecord::ask1_amount) + .def_readwrite("ask2", &SpotRecord::ask2) + .def_readwrite("ask2_amount", &SpotRecord::ask2_amount) + .def_readwrite("ask3", &SpotRecord::ask3) + .def_readwrite("ask3_amount", &SpotRecord::ask3_amount) + .def_readwrite("ask4", &SpotRecord::ask4) + .def_readwrite("ask4_amount", &SpotRecord::ask4_amount) + .def_readwrite("ask5", &SpotRecord::ask5) + .def_readwrite("ask5_amount", &SpotRecord::ask5_amount); +} \ No newline at end of file diff --git a/hikyuu_pywrap/global/agent_main.cpp b/hikyuu_pywrap/global/agent_main.cpp index bf39131a..2722e0cf 100644 --- a/hikyuu_pywrap/global/agent_main.cpp +++ b/hikyuu_pywrap/global/agent_main.cpp @@ -9,8 +9,10 @@ namespace py = pybind11; +void export_SpotRecord(py::module& m); void export_SpotAgent(py::module& m); void export_global_main(py::module& m) { + export_SpotRecord(m); export_SpotAgent(m); } diff --git a/hikyuu_pywrap/strategy/_AccountTradeManager.cpp b/hikyuu_pywrap/strategy/_AccountTradeManager.cpp deleted file mode 100644 index dc350029..00000000 --- a/hikyuu_pywrap/strategy/_AccountTradeManager.cpp +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright(C) 2021 hikyuu.org - * - * Create on: 2021-03-28 - * Author: fasiondog - */ - -#include "../pybind_utils.h" -#include - -using namespace hku; -namespace py = pybind11; - -void export_AccountTradeManger(py::module& m) { - m.def("crtAccountTM", crtAccountTM); -} \ No newline at end of file diff --git a/hikyuu_pywrap/strategy/_Strategy.cpp b/hikyuu_pywrap/strategy/_Strategy.cpp new file mode 100644 index 00000000..f2ea4bd9 --- /dev/null +++ b/hikyuu_pywrap/strategy/_Strategy.cpp @@ -0,0 +1,92 @@ +/* + * Copyright(C) 2021 hikyuu.org + * + * Create on: 2021-02-16 + * Author: fasiondog + */ + +#include +#include +#include + +namespace py = pybind11; +using namespace hku; + +void export_Strategy(py::module& m) { + py::class_(m, "Strategy") + .def(py::init<>()) + .def(py::init&, const vector&, const std::string&, + const std::string&>(), + py::arg("code_list"), py::arg("ktype_list"), py::arg("name") = "Strategy", + py::arg("config") = "") + .def_property("name", py::overload_cast<>(&Strategy::name, py::const_), + py::overload_cast(&Strategy::name), + py::return_value_policy::copy, "策略名称") + + .def_property_readonly("context", &Strategy::context, py::return_value_policy::copy, + "策略上下文") + + .def("start", &Strategy::start) + .def("on_change", + [](Strategy& self, py::object func) { + HKU_CHECK(py::hasattr(func, "__call__"), "func is not callable!"); + py::object c_func = func.attr("__call__"); + auto new_func = [=](const Stock& stk, const SpotRecord& spot) { + try { + c_func(stk, spot); + } catch (const std::exception& e) { + HKU_ERROR(e.what()); + } catch (...) { + HKU_ERROR("Unknown error!"); + } + }; + self.onChange(new_func); + }) + .def("on_received_spot", + [](Strategy& self, py::object func) { + HKU_CHECK(py::hasattr(func, "__call__"), "func is not callable!"); + py::object c_func = func.attr("__call__"); + auto new_func = [=](Datetime revTime) { + try { + c_func(revTime); + } catch (const std::exception& e) { + HKU_ERROR(e.what()); + } catch (...) { + HKU_ERROR("Unknown error!"); + } + }; + self.onReceivedSpot(new_func); + }) + .def("run_daily", + [](Strategy& self, py::object func, const TimeDelta& time) { + HKU_CHECK(py::hasattr(func, "__call__"), "func is not callable!"); + py::object c_func = func.attr("__call__"); + auto new_func = [=]() { + try { + c_func(); + } catch (const std::exception& e) { + HKU_ERROR(e.what()); + } catch (...) { + HKU_ERROR("Unknown error!"); + } + }; + self.runDaily(new_func, time); + }) + .def( + "run_daily_at", + [](Strategy& self, py::object func, const TimeDelta& time, bool ignore_holiday) { + HKU_CHECK(py::hasattr(func, "__call__"), "func is not callable!"); + py::object c_func = func.attr("__call__"); + auto new_func = [=]() { + try { + c_func(); + } catch (const std::exception& e) { + HKU_ERROR(e.what()); + } catch (...) { + HKU_ERROR("Unknown error!"); + } + }; + self.runDailyAt(new_func, time, ignore_holiday); + }, + py::arg("func"), py::arg("time"), py::arg("ignore_holiday") = true); +} \ No newline at end of file diff --git a/hikyuu_pywrap/strategy/_StrategyBase.cpp b/hikyuu_pywrap/strategy/_StrategyBase.cpp deleted file mode 100644 index 27e32487..00000000 --- a/hikyuu_pywrap/strategy/_StrategyBase.cpp +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright(C) 2021 hikyuu.org - * - * Create on: 2021-02-16 - * Author: fasiondog - */ - -#include -#include -#include - -namespace py = pybind11; -using namespace hku; - -class PyStrategyBase : public StrategyBase { -public: - using StrategyBase::StrategyBase; - - void init() override { - PYBIND11_OVERLOAD(void, StrategyBase, init); - } - - void onTick() override { - PYBIND11_OVERLOAD(void, StrategyBase, onTick); - } - - void onBar(const KQuery::KType& ktype) override { - PYBIND11_OVERLOAD(void, StrategyBase, onBar, ktype); - } - - void onMarketOpen() override { - PYBIND11_OVERLOAD(void, StrategyBase, onMarketOpen); - } - - void onMarketClose() override { - PYBIND11_OVERLOAD(void, StrategyBase, onMarketClose); - } - - void onClock(TimeDelta detla) override { - PYBIND11_OVERLOAD(void, StrategyBase, onClock, detla); - } -}; - -void export_Strategy(py::module& m) { - py::class_(m, "StrategyBase") - .def(py::init<>()) - .def_property("name", py::overload_cast<>(&StrategyBase::name, py::const_), - py::overload_cast(&StrategyBase::name), - py::return_value_policy::copy, "策略名称") - - .def_property_readonly("sm", &StrategyBase::getSM, py::return_value_policy::reference, - "获取 StockManager 实例") - .def_property("tm", &StrategyBase::getTM, &StrategyBase::setTM, "账户管理") - .def_property("start_datetime", py::overload_cast<>(&StrategyBase::startDatetime, py::const_), - py::overload_cast(&StrategyBase::startDatetime), "起始日期") - .def_property("stock_list", py::overload_cast<>(&StrategyBase::getStockCodeList, py::const_), - py::overload_cast&>(&StrategyBase::setStockCodeList), - py::return_value_policy::copy, "股票代码列表") - .def_property("ktype_list", py::overload_cast<>(&StrategyBase::getKTypeList, py::const_), - py::overload_cast&>(&StrategyBase::setKTypeList), - py::return_value_policy::copy, "需要的K线类型") - - .def("run", &StrategyBase::run) - .def("init", &StrategyBase::init) - .def("on_tick", &StrategyBase::onTick) - .def("on_bar", &StrategyBase::onBar) - .def("on_market_open", &StrategyBase::onMarketOpen) - .def("on_market_close", &StrategyBase::onMarketClose) - .def("on_clock", &StrategyBase::onClock); -} \ No newline at end of file diff --git a/hikyuu_pywrap/strategy/_strategy_main.cpp b/hikyuu_pywrap/strategy/_strategy_main.cpp index 9f9422f3..2dbe2bab 100644 --- a/hikyuu_pywrap/strategy/_strategy_main.cpp +++ b/hikyuu_pywrap/strategy/_strategy_main.cpp @@ -10,9 +10,7 @@ namespace py = pybind11; void export_Strategy(py::module& m); -void export_AccountTradeManger(py::module& m); void export_strategy_main(py::module& m) { export_Strategy(m); - export_AccountTradeManger(m); } \ No newline at end of file diff --git a/hikyuu_pywrap/trade_manage/_OrderBroker.cpp b/hikyuu_pywrap/trade_manage/_OrderBroker.cpp index 40344702..25560843 100644 --- a/hikyuu_pywrap/trade_manage/_OrderBroker.cpp +++ b/hikyuu_pywrap/trade_manage/_OrderBroker.cpp @@ -15,19 +15,33 @@ class PyOrderBrokerBase : public OrderBrokerBase { public: using OrderBrokerBase::OrderBrokerBase; - Datetime _buy(Datetime datetime, const string& market, const string& code, price_t price, - double num) override { - PYBIND11_OVERLOAD_PURE(Datetime, OrderBrokerBase, _buy, datetime, market, code, price, num); + void _buy(Datetime datetime, const string& market, const string& code, price_t price, + double num, price_t stoploss, price_t goalPrice, SystemPart from) override { + PYBIND11_OVERLOAD_PURE(void, OrderBrokerBase, _buy, datetime, market, code, price, num, + stoploss, goalPrice, from); } - Datetime _sell(Datetime datetime, const string& market, const string& code, price_t price, - double num) override { - PYBIND11_OVERLOAD_PURE(Datetime, OrderBrokerBase, _sell, datetime, market, code, price, - num); + void _sell(Datetime datetime, const string& market, const string& code, price_t price, + double num, price_t stoploss, price_t goalPrice, SystemPart from) override { + PYBIND11_OVERLOAD_PURE(void, OrderBrokerBase, _sell, datetime, market, code, price, num, + stoploss, goalPrice, from); + } + + string _getAssetInfo() override { + PYBIND11_OVERLOAD_NAME(string, OrderBrokerBase, "_get_asset_info", _getAssetInfo); } }; void export_OrderBroker(py::module& m) { + py::class_(m, "BrokerPositionRecord") + .def(py::init<>()) + .def(py::init()) + .def("__str__", &BrokerPositionRecord::str) + .def("__repr__", &BrokerPositionRecord::str) + .def_readwrite("stock", &BrokerPositionRecord::stock, "持仓对象") + .def_readwrite("number", &BrokerPositionRecord::number, "持仓数量") + .def_readwrite("money", &BrokerPositionRecord::money, "买入花费总资金"); + py::class_( m, "OrderBrokerBase", R"(订单代理包装基类,用户可以参考自定义自己的订单代理,加入额外的处理 @@ -54,9 +68,7 @@ void export_OrderBroker(py::module& m) { :param str market: 市场标识 :param str code: 证券代码 :param float price: 买入价格 - :param float num: 买入数量 - :return: 操作执行的时刻。实盘时,应返回委托单时间或服务器交易时间。 - :rtype: Datetime)") + :param float num: 买入数量)") .def("sell", &OrderBrokerBase::sell, R"(sell(self, datetime, market, code, price, num) @@ -66,9 +78,9 @@ void export_OrderBroker(py::module& m) { :param str market: 市场标识 :param str code: 证券代码 :param float price: 卖出价格 - :param float num: 卖出数量 - :return: 操作执行的时刻。实盘时,应返回委托单时间或服务器交易时间。 - "rtype: Datetime)") + :param float num: 卖出数量)") + + .def("get_asset_info", &OrderBrokerBase::getAssetInfo) .def("_buy", &OrderBrokerBase::_buy, R"(_buy(self, datetime, market, code, price, num) @@ -79,9 +91,7 @@ void export_OrderBroker(py::module& m) { :param str market: 市场标识 :param str code: 证券代码 :param float price: 买入价格 - :param float num: 买入数量 - :return: 操作执行的时刻。实盘时,应返回委托单时间或服务器交易时间。 - :rtype: Datetime)") + :param float num: 买入数量)") .def("_sell", &OrderBrokerBase::_sell, R"(_sell(self, datetime, market, code, price, num) @@ -92,7 +102,7 @@ void export_OrderBroker(py::module& m) { :param str market: 市场标识 :param str code: 证券代码 :param float price: 卖出价格 - :param float num: 卖出数量 - :return: 操作执行的时刻。实盘时,应返回委托单时间或服务器交易时间。 - "rtype: Datetime)"); + :param float num: 卖出数量)") + + .def("_get_asset_info", &OrderBrokerBase::_getAssetInfo); } diff --git a/hikyuu_pywrap/trade_manage/_PositionRecord.cpp b/hikyuu_pywrap/trade_manage/_PositionRecord.cpp index fc7f529a..d2b6937b 100644 --- a/hikyuu_pywrap/trade_manage/_PositionRecord.cpp +++ b/hikyuu_pywrap/trade_manage/_PositionRecord.cpp @@ -21,8 +21,8 @@ void export_PositionRecord(py::module& m) { .def(py::init()) - .def("__str__", &PositionRecord::toString) - .def("__repr__", &PositionRecord::toString) + .def("__str__", &PositionRecord::str) + .def("__repr__", &PositionRecord::str) .def_readwrite("stock", &PositionRecord::stock, "交易对象(Stock)") .def_readwrite("take_datetime", &PositionRecord::takeDatetime, "初次建仓时刻(Datetime)") diff --git a/hikyuu_pywrap/trade_manage/_TradeManager.cpp b/hikyuu_pywrap/trade_manage/_TradeManager.cpp index b24d730f..5b449a6f 100644 --- a/hikyuu_pywrap/trade_manage/_TradeManager.cpp +++ b/hikyuu_pywrap/trade_manage/_TradeManager.cpp @@ -216,6 +216,10 @@ public: PYBIND11_OVERRIDE_NAME(bool, TradeManagerBase, "add_trade_record", addTradeRecord, tr); } + bool addPosition(const PositionRecord& pr) override { + PYBIND11_OVERRIDE_NAME(bool, TradeManagerBase, "add_position", addPosition, pr); + } + string str() const override { PYBIND11_OVERRIDE_NAME(string, TradeManagerBase, "__str__", str, ); } @@ -536,6 +540,13 @@ void export_TradeManager(py::module& m) { :return: True(成功) | False(失败) :rtype: bool)") + .def("add_position", &TradeManagerBase::addPosition, R"(add_postion(self, position) + + 建立初始账户后,直接加入持仓记录,仅用于构建初始有持仓的账户 + + :param PositionRecord position: 持仓记录 + return True | False)") + .def("tocsv", &TradeManagerBase::tocsv, R"(tocsv(self, path) 以csv格式输出交易记录、未平仓记录、已平仓记录、资产净值曲线 diff --git a/hikyuu_pywrap/xmake.lua b/hikyuu_pywrap/xmake.lua index 9e023b97..1e4ae46c 100644 --- a/hikyuu_pywrap/xmake.lua +++ b/hikyuu_pywrap/xmake.lua @@ -9,11 +9,8 @@ target("core") -- --set_enable(false) --set_enable(false)会彻底禁用这个target,连target的meta也不会被加载,vcproj不会保留它 -- end - -- add_options("stacktrace") - add_options("hdf5", "mysql", "sqlite", "tdx", "feedback", "stacktrace", "spend_time", "log_level") - add_deps("hikyuu") - add_packages("boost", "fmt", "spdlog", "flatbuffers", "pybind11", "cpp-httplib") + add_packages("boost", "fmt", "spdlog", "flatbuffers", "pybind11") if is_plat("windows") then set_filename("core.pyd") add_cxflags("-wd4251") @@ -23,6 +20,7 @@ target("core") if is_plat("windows") and get_config("kind") == "shared" then add_defines("HKU_API=__declspec(dllimport)") + add_defines("HKU_UTILS_API=__declspec(dllimport)") add_cxflags("-wd4566") end @@ -32,11 +30,20 @@ target("core") add_cxflags("-Wno-error=parentheses-equality -Wno-error=missing-braces") end + if is_plat("linux", "cross") then + add_rpathdirs("$ORIGIN", "$ORIGIN/cpp") + end + + if is_plat("macosx") then + add_linkdirs("/usr/lib") + + -- macosx 下不能主动链接 python,所以需要使用如下编译选项 + add_shflags("-undefined dynamic_lookup") + end + add_includedirs("../hikyuu_cpp") add_files("./**.cpp") - add_rpathdirs("$ORIGIN", "$ORIGIN/lib", "$ORIGIN/../lib") - on_load("windows", "linux", "macosx", function(target) import("lib.detect.find_tool") if is_plat("windows") then diff --git a/readme.md b/readme.md new file mode 100644 index 00000000..34fa7b86 --- /dev/null +++ b/readme.md @@ -0,0 +1,67 @@ +![title](https://fasiondog.cn/wp-content/uploads/2024/05/00000_title-1.png) + +--- + +![img](https://static.pepy.tech/badge/hikyuu) ![img](https://static.pepy.tech/badge/hikyuu/month) ![img](https://static.pepy.tech/badge/hikyuu/week) ![img](https://github.com/fasiondog/hikyuu/workflows/win-build/badge.svg) ![img](https://github.com/fasiondog/hikyuu/workflows/ubuntu-build/badge.svg) ![img](https://img.shields.io/github/license/mashape/apistatus.svg) + +Hikyuu Quant Framework是一款基于C++/Python的开源量化交易研究框架,用于策略分析及回测(目前主要用于国内A股市场)。其核心思想基于当前成熟的系统化交易方法,将整个系统化交易抽象为由市场环境判断策略、系统有效条件、信号指示器、止损/止盈策略、资金管理策略、盈利目标策略、移滑价差算法七大组件,你可以分别构建这些组件的策略资产库,在实际研究中对它们自由组合来观察系统的有效性、稳定性以及单一种类策略的效果。 + +详细文档:[https://hikyuu.org/](https://hikyuu.org/) + +感谢网友提供的 Hikyuu Ubuntu虚拟机环境, 百度网盘下载(提取码: ht8j): [https://pan.baidu.com/s/1CAiUWDdgV0c0VhPpe4AgVw?pwd=ht8j](https://pan.baidu.com/s/1CAiUWDdgV0c0VhPpe4AgVw?pwd=ht8j) + +示例: + +```python + #创建模拟交易账户进行回测,初始资金30万 + my_tm = crtTM(init_cash = 300000) + + #创建信号指示器(以5日EMA为快线,5日EMA自身的10日EMA作为慢线,快线向上穿越慢线时买入,反之卖出) + my_sg = SG_Flex(EMA(CLOSE(), n=5), slow_n=10) + + #固定每次买入1000股 + my_mm = MM_FixedCount(1000) + + #创建交易系统并运行 + sys = SYS_Simple(tm = my_tm, sg = my_sg, mm = my_mm) + sys.run(sm['sz000001'], Query(-150)) +``` + +![img](https://fasiondog.cn/wp-content/uploads/2024/05/10000-overview.png) + +完整示例参见:[https://nbviewer.jupyter.org/github/fasiondog/hikyuu/blob/master/hikyuu/examples/notebook/000-Index.ipynb?flush_cache=True](https://nbviewer.jupyter.org/github/fasiondog/hikyuu/blob/master/hikyuu/examples/notebook/000-Index.ipynb?flush_cache=True) + +# 为什么选择 Hikyuu? + +* **组合灵活,分类构建策略资产库** Hikyuu对系统化交易方法进行了良好的抽象,包含了九大策略组件:市场环境判断策略、系统有效条件、信号指示器、止损/止盈策略、资金管理策略、盈利目标策略、移滑价差算法、交易对象选择策略、资金分配策略。可以在此基础上构建自己的策略库,并进行灵活的组合和测试。在进行策略探索时,可以更加专注于某一方面的策略性能与影响。其主要功能模块如下:![img](https://fasiondog.cn/wp-content/uploads/2024/05/10002-function-arc.png) +* **性能保障,打造自己的专属应用** 目前项目包含了3个主要组成部分:基于C++的核心库、对C++进行包装的Python库(hikyuu)、基于Python的交互式工具。 + * AMD 7950x 实测:A股全市场(1913万日K线)仅加载全部日线计算 20日 MA 并求最后 MA 累积和,首次执行含数据加载 耗时 6秒,数据加载完毕后计算耗时 166 毫秒,详见: [性能实测](https://mp.weixin.qq.com/s?__biz=MzkwMzY1NzYxMA==&mid=2247483768&idx=1&sn=33e40aa9633857fa7b4c7ded51c95ae7&chksm=c093a09df7e4298b3f543121ba01334c0f8bf76e75c643afd6fc53aea1792ebb92de9a32c2be&mpshare=1&scene=23&srcid=05297ByHT6DEv6XAmyje1oOr&sharer_shareinfo=b38f5f91b4efd8fb60303a4ef4774748&sharer_shareinfo_first=b38f5f91b4efd8fb60303a4ef4774748#rd) + * C++核心库,提供了整体的策略框架,在保证性能的同时,已经考虑了对多线程和多核处理的支持,在未来追求更高运算速度提供便利。C++核心库,可以单独剥离使用,自行构建自己的客户端工具。 + * Python库(hikyuu),提供了对C++库的包装,同时集成了talib库(如TA_SMA,对应talib.SMA),可以与numpy、pandas数据结构进行互相转换,为使用其他成熟的python数据分析工具提供了便利。 + * hikyuu.interactive 交互式探索工具,提供了K线、指标、系统信号等的基本绘图功能,用于对量化策略的探索和回测。 +* **代码简洁,探索更便捷、自由** 同时支持面向对象和命令行编程范式。其中,命令行在进行策略探索时,代码简洁、探索更便捷、自由。 +* **安全、自由、隐私,搭建自己的专属云量化平台** 结合 Python + Jupyter 的强大能力与云服务器,可以搭建自己专属的云量化平台。将Jupyter部署在云服务器上,随时随地的访问自己的云平台,即刻实现自己新的想法,如下图所示通过手机访问自己的云平台。结合Python强大成熟的数据分析、人工智能工具(如 numpy、scipy、pandas、TensorFlow)搭建更强大的人工智能平台。 +* **数据存储方式可扩展** 目前支持本地HDF5格式、MySQL存储。默认使用HDF5,数据文件体积小、速度更快、备份更便利。截止至2017年4月21日,沪市日线数据文件149M、深市日线数据文件184M、5分钟线数据各不到2G。 + +![img](https://api.star-history.com/svg?repos=fasiondog/hikyuu&type=Date "Star History Chart") + +# 想要更多了解Hikyuu?请使用以下方式联系: + +## **加入知识星球** + +更多示例与策略部件的及时分享(您的加入将视为对项目的捐赠)。**项目交流和问题答复将转移至知识星球-【Hikyuu量化】。** + +![img](https://fasiondog.cn/wp-content/uploads/2024/05/zhishixingqiu.png) + + +## 关注公众号: + +![img](https://fasiondog.cn/wp-content/uploads/2024/05/weixin_gongzhonghao.jpg) + +## 加入微信群(请注明“加入hikyuu”): + +![img](https://fasiondog.cn/wp-content/uploads/2024/05/weixin_group.jpg) + +## QQ交流群(逐渐废弃):114910869, 或扫码加入 + +![img](https://fasiondog.cn/wp-content/uploads/2024/05/10003-qq.png) diff --git a/requirements.txt b/requirements.txt index 777ed1bd..817b4f1d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ click +numpy<=1.26.4 matplotlib pandas>=0.17.1 pytdx diff --git a/setup.py b/setup.py index a0ace2fe..52b2ef12 100644 --- a/setup.py +++ b/setup.py @@ -123,8 +123,9 @@ def start_build(verbose=False, mode='release', feedback=True, worker_num=2, low_ if py_version != history_compile_info[ 'py_version'] or history_compile_info['mode'] != mode: clear_with_python_changed(mode) - cmd = "xmake f {} -c -y -m {} --feedback={} -k {} --low_precision={}".format( - "-v -D" if verbose else "", mode, feedback, "shared" if mode == 'release' else "static", low_precision) + cmd = "xmake f {} -c -y -m {} --feedback={} -k {} --low_precision={} --log_level={}".format( + "-v -D" if verbose else "", mode, feedback, "shared" if mode == 'release' else "static", low_precision, + 2 if mode == 'release' else 0) print(cmd) os.system(cmd) diff --git a/sub_setup.py b/sub_setup.py index 0ebd059b..1cda3cb5 100644 --- a/sub_setup.py +++ b/sub_setup.py @@ -48,46 +48,11 @@ hku_platforms = "Independant" hku_url = "http://hikyuu.org/" hku_description = "Hikyuu Quant Framework for System Trading Analysis and backtester" -with open("./hikyuu/README.rst", encoding='utf-8') as f: +with open("./readme.md", encoding='utf-8') as f: hku_long_description = f.read() hku_data_files = [] -# packages = [ -# 'hikyuu', -# 'hikyuu/analysis', -# 'hikyuu/config', -# 'hikyuu/config/block', -# 'hikyuu/cpp', -# 'hikyuu/data', -# 'hikyuu/data/mysql_upgrade', -# 'hikyuu/data/sqlite_upgrade', -# 'hikyuu/data/sqlite_mem_sql', -# 'hikyuu/data_driver', -# 'hikyuu/examples', -# 'hikyuu/examples/notebook', -# 'hikyuu/examples/notebook/images', -# 'hikyuu/examples/notebook/Demo', -# 'hikyuu/flat', -# 'hikyuu/fetcher', -# 'hikyuu/fetcher/proxy', -# 'hikyuu/fetcher/stock', -# 'hikyuu/gui', -# 'hikyuu/gui/data', -# 'hikyuu/indicator', -# 'hikyuu/draw', -# 'hikyuu/draw/drawplot', -# 'hikyuu/shell', -# 'hikyuu/strategy', -# 'hikyuu/strategy/demo', -# 'hikyuu/test', -# 'hikyuu/tools', -# 'hikyuu/trade_manage', -# 'hikyuu/trade_sys', -# 'hikyuu/util', -# 'hikyuu/include', -# ] - packages = ['hikyuu'] for root, dirs, files in os.walk('hikyuu'): for p in dirs: @@ -99,7 +64,8 @@ setup( name=hku_name, version=hku_version, description=hku_description, - long_description_content_type="text/x-rst", + # long_description_content_type="text/x-rst", + long_description_content_type='text/markdown', long_description=hku_long_description, author=hku_author, author_email=hku_author_email, diff --git a/test_data/stock.db b/test_data/stock.db index 547b57c6..97e92f2b 100644 Binary files a/test_data/stock.db and b/test_data/stock.db differ diff --git a/xmake.lua b/xmake.lua index 14be5417..4c51ea60 100644 --- a/xmake.lua +++ b/xmake.lua @@ -1,11 +1,18 @@ set_xmakever("2.8.2") -option("hdf5") - set_default(true) - set_showmenu(true) - set_category("hikyuu") - set_description("Enable hdf5 kdata engine.") -option_end() +-- project +set_project("hikyuu") + +add_rules("mode.debug", "mode.release") + +-- version +set_version("2.1.1", {build = "%Y%m%d%H%M"}) + +set_warnings("all") + +-- set language: C99, c++ standard +set_languages("cxx17", "c99") + option("mysql") set_default(true) @@ -36,73 +43,24 @@ option("mysql") end option_end() -option("sqlite") - set_default(true) - set_showmenu(true) - set_category("hikyuu") - set_description("Enable sqlite kdata engine.") -option_end() - -option("tdx") - set_default(true) - set_showmenu(true) - set_category("hikyuu") - set_description("Enable tdx kdata engine.") -option_end() +option("hdf5", {description = "Enable hdf5 kdata engine.", default = true}) +option("sqlite", {description = "Enable sqlite kdata engine.", default = true}) +option("tdx", {description = "Enable tdx kdata engine.", default = true}) +option("sql_trace", {description = "trace print sql", default = false}) -- 注意:stacktrace 在 windows 下会严重影响性能 -option("stacktrace") - set_default(false) - set_showmenu(true) - set_category("hikyuu") - set_description("Enable check/assert with stack trace info.") - add_defines("HKU_ENABLE_STACK_TRACE") -option_end() +option("stacktrace", {description = "Enable check/assert with stack trace info.", default = false}) +option("spend_time", {description = "Enable spend time.", default = true}) +option("feedback", {description = "Enable send feedback.", default = true}) +option("low_precision", {description = "Enable low precision.", default = false}) +option("log_level", {description = "set log level.", default = 2, values = {1, 2, 3, 4, 5, 6}}) +option("async_log", {description = "Use async log.", default = false}) +option("leak_check", {description = "Enable leak check for test", default = false}) -option("spend_time") - set_default(true) - set_showmenu(true) - set_category("hikyuu") - set_description("Enable spend time.") - add_defines("HKU_CLOSE_SPEND_TIME=0") -option_end() - -option("feedback") - set_default(true) - set_showmenu(true) - set_category("hikyuu") - set_description("Enable send feedback.") -option_end() - -option("low_precision") - set_default(false) - set_showmenu(true) - set_category("hikyuu") - set_description("Enable send feedback.") -option_end() - -option("log_level") - set_default("info") - set_values("trace", "debug", "info", "warn", "error", "fatal", "off") - set_showmenu(true) - set_category("hikyuu") - set_description("set log level") -option_end() - -option("leak_check") - set_default(false) - set_showmenu(true) - set_category("hikyuu") - set_description("Enable leak check for test") -option_end() - --- project -set_project("hikyuu") - -add_rules("mode.debug", "mode.release") - --- version -set_version("2.1.0", {build = "%Y%m%d%H%M"}) +-- 使用 serialize 时,建议使用静态库方式编译,boost serializasion 对 dll 的方式支持不好 +-- windows下如果使用 serialize 且希望使用动态库,需要设置 runtimes 参数为 "MD" +-- "MT" 方式下,serialize 会挂 +option("serialize", {description = "Enable support serialize object and pickle in python", default = true}) if get_config("leak_check") then -- 需要 export LD_PRELOAD=libasan.so @@ -112,39 +70,20 @@ if get_config("leak_check") then -- set_policy("build.sanitizer.thread", true) end -local level = get_config("log_level") -if is_mode("debug") then - level = "trace" -end -if level == "trace" then - set_configvar("LOG_ACTIVE_LEVEL", 0) -elseif level == "debug" then - set_configvar("LOG_ACTIVE_LEVEL", 1) -elseif level == "info" then - set_configvar("LOG_ACTIVE_LEVEL", 2) -elseif level == "warn" then - set_configvar("LOG_ACTIVE_LEVEL", 3) -elseif level == "error" then - set_configvar("LOG_ACTIVE_LEVEL", 4) -elseif level == "fatal" then - set_configvar("LOG_ACTIVE_LEVEL", 5) -else - set_configvar("LOG_ACTIVE_LEVEL", 6) +-- SPDLOG_ACTIVE_LEVEL 需要单独加 +local log_level = get_config("log_level") +if log_level == nil then + log_level = 2 end +add_defines("SPDLOG_ACTIVE_LEVEL=" .. log_level) if is_mode("debug") then set_configvar("HKU_DEBUG_MODE", 1) else set_configvar("HKU_DEBUG_MODE", 0) end -set_configvar("USE_SPDLOG_LOGGER", 1) -- 是否使用spdlog作为日志输出 -set_configvar("USE_SPDLOG_ASYNC_LOGGER", 0) -- 使用异步的spdlog set_configvar("CHECK_ACCESS_BOUND", 1) -if is_plat("macosx") or get_config("leak_check") then - set_configvar("SUPPORT_SERIALIZATION", 0) -else - set_configvar("SUPPORT_SERIALIZATION", is_mode("release") and 1 or 0) -end +set_configvar("SUPPORT_SERIALIZATION", get_config("serialize") and 1 or 0) set_configvar("SUPPORT_TEXT_ARCHIVE", 0) set_configvar("SUPPORT_XML_ARCHIVE", 1) set_configvar("SUPPORT_BINARY_ARCHIVE", 1) @@ -153,31 +92,32 @@ set_configvar("HKU_ENABLE_LEAK_DETECT", get_config("leak_check") and 1 or 0) set_configvar("HKU_ENABLE_SEND_FEEDBACK", get_config("feedback") and 1 or 0) set_configvar("HKU_ENABLE_HDF5_KDATA", get_config("hdf5") and 1 or 0) +set_configvar("HKU_ENABLE_MYSQL", get_config("mysql") and 1 or 0) set_configvar("HKU_ENABLE_MYSQL_KDATA", get_config("mysql") and 1 or 0) +set_configvar("HKU_ENABLE_SQLITE", (get_config("sqlite") or get_config("hdf5")) and 1 or 0) set_configvar("HKU_ENABLE_SQLITE_KDATA", get_config("sqlite") and 1 or 0) set_configvar("HKU_ENABLE_TDX_KDATA", get_config("tdx") and 1 or 0) set_configvar("HKU_USE_LOW_PRECISION", get_config("low_precision") and 1 or 0) -set_warnings("all") - --- set language: C99, c++ standard -set_languages("cxx17", "c99") - -if is_plat("windows") then - if is_mode("release") then - set_runtimes("MD") - else - set_runtimes("MDd") - end -end +set_configvar("HKU_SUPPORT_DATETIME", 1) +set_configvar("HKU_ENABLE_SQLCIPHER", 0) +set_configvar("HKU_SQL_TRACE", get_config("sql_trace")) +set_configvar("HKU_ENABLE_INI_PARSER", 1) +set_configvar("HKU_ENABLE_STACK_TRACE", get_config("stacktrace") and 1 or 0) +set_configvar("HKU_CLOSE_SPEND_TIME", get_config("spend_time") and 0 or 1) +set_configvar("HKU_USE_SPDLOG_ASYNC_LOGGER", get_config("async_log") and 1 or 0) +set_configvar("HKU_LOG_ACTIVE_LEVEL", get_config("log_level")) +set_configvar("HKU_ENABLE_MO", 0) +set_configvar("HKU_ENABLE_HTTP_CLIENT", 1) +set_configvar("HKU_ENABLE_HTTP_CLIENT_SSL", 0) +set_configvar("HKU_ENABLE_HTTP_CLIENT_ZIP", 0) local boost_version = "1.85.0" local hdf5_version = "1.12.2" local fmt_version = "10.2.1" local flatbuffers_version = "24.3.25" local nng_version = "1.8.0" -local cpp_httplib_version = "0.14.3" local sqlite_version = "3.46.0+0" local mysql_version = "8.0.31" if is_plat("windows") or (is_plat("linux", "cross") and is_arch("aarch64", "arm64.*")) then @@ -208,18 +148,18 @@ elseif is_plat("linux", "cross") then elseif is_plat("macosx") then if get_config("hdf5") then - add_requires("brew::hdf5") + add_requires("brew::hdf5", {alias = "hdf5"}) end if get_config("mysql") then - add_requires("brew::mysql-client") + add_requires("brew::mysql-client", {alias = "mysql"}) end end add_requires("boost " .. boost_version, { - system = false, debug = is_mode("debug"), configs = { shared = is_plat("windows"), + runtimes = get_config("runtimes"), multi = true, date_time = true, filesystem = false, @@ -229,23 +169,23 @@ add_requires("boost " .. boost_version, { }, }) -add_requires("spdlog", {system = false, configs = {header_only = true, fmt_external = true}}) -add_requireconfs("spdlog.fmt", {override = true, version = fmt_version, configs = {header_only = true}}) -add_requires("sqlite3 " .. sqlite_version, {system = false, configs = {shared = true, cxflags = "-fPIC"}}) -add_requires("flatbuffers v" .. flatbuffers_version, {system = false}) -add_requires("nng " .. nng_version, {system = false, configs = {cxflags = "-fPIC"}}) -add_requires("nlohmann_json", {system = false}) -add_requires("cpp-httplib " .. cpp_httplib_version, {system = false, configs = {zlib = true, ssl = true}}) -add_requires("zlib", {system = false}) +add_requires("fmt", {configs = {header_only = true}}) +add_requires("spdlog", {configs = {header_only = true, fmt_external = true}}) +add_requireconfs("spdlog.fmt", {override = true, configs = {header_only = true}}) +add_requires("sqlite3 " .. sqlite_version, {configs = {shared = true, safe_mode="2", cxflags = "-fPIC"}}) +add_requires("flatbuffers v" .. flatbuffers_version, {system = false, configs= {runtimes = get_config("runtimes")}}) +add_requires("nng " .. nng_version, {configs = {cxflags = "-fPIC"}}) +add_requires("nlohmann_json") add_defines("SPDLOG_DISABLE_DEFAULT_LOGGER") -- 禁用 spdlog 默认ogger set_objectdir("$(buildir)/$(mode)/$(plat)/$(arch)/.objs") set_targetdir("$(buildir)/$(mode)/$(plat)/$(arch)/lib") --- modifed to use boost static library, except boost.python, serialization +-- on windows dll, must use runtimes MD if is_plat("windows") and get_config("kind") == "shared" then - add_defines("BOOST_ALL_DYN_LINK") + set_config("runtimes", "MD") + set_runtimes("MD") end -- is release now @@ -268,7 +208,7 @@ if is_plat("windows") then end end -if not is_plat("windows") then +if is_plat("linux", "cross", "macosx") then -- disable some compiler errors add_cxflags("-Wno-error=deprecated-declarations", "-fno-strict-aliasing") add_cxflags("-ftemplate-depth=1023", "-pthread") @@ -282,6 +222,7 @@ end -- -- add_defines("HKU_ENABLE_SSE2", "HKU_ENABLE_SSE3", "HKU_ENABLE_SSE41", "HKU_ENABLE_AVX", "HKU_ENABLE_AVX2") -- end +includes("./copy_dependents.lua") includes("./hikyuu_cpp/hikyuu") includes("./hikyuu_pywrap") includes("./hikyuu_cpp/unit_test")