mirror of
https://gitee.com/fasiondog/hikyuu.git
synced 2024-11-29 18:39:10 +08:00
Merge branch 'master' of https://github.com/fasiondog/hikyuu into feature/docs
This commit is contained in:
commit
286fc3fc9f
53
.github/workflows/macos_arm64.yml
vendored
Normal file
53
.github/workflows/macos_arm64.yml
vendored
Normal file
@ -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
|
53
.github/workflows/macos_x86_64.yml
vendored
Normal file
53
.github/workflows/macos_x86_64.yml
vendored
Normal file
@ -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
|
2
.github/workflows/ubuntu.yml
vendored
2
.github/workflows/ubuntu.yml
vendored
@ -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'
|
||||
|
||||
|
3
.github/workflows/ubuntu_aarch64.yml
vendored
3
.github/workflows/ubuntu_aarch64.yml
vendored
@ -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: |
|
||||
|
2
.github/workflows/ubuntu_python.yml
vendored
2
.github/workflows/ubuntu_python.yml
vendored
@ -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'
|
||||
|
||||
|
8
.github/workflows/windows.yml
vendored
8
.github/workflows/windows.yml
vendored
@ -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
|
||||
|
8
.github/workflows/windows_python.yml
vendored
8
.github/workflows/windows_python.yml
vendored
@ -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
|
||||
xmake -b core
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -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
|
||||
.virtual_documents
|
||||
hikyuu_cpp/hikyuu/utilities/config.h
|
||||
|
113
README.rst
113
README.rst
@ -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股市场)。其核心思想基于当前成熟的系统化交易方法,将整个系统化交易抽象为由市场环境判断策略、系统有效条件、信号指示器、止损/止盈策略、资金管理策略、盈利目标策略、移滑价差算法七大组件,你可以分别构建这些组件的策略资产库,在实际研究中对它们自由组合来观察系统的有效性、稳定性以及单一种类策略的效果。
|
||||
|
||||
详细文档: `<https://hikyuu.org/>`_
|
||||
|
||||
|
||||
感谢网友提供的 Hikyuu Ubuntu虚拟机环境, 百度网盘下载(提取码: ht8j): `<https://pan.baidu.com/s/1CAiUWDdgV0c0VhPpe4AgVw?pwd=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
|
||||
|
||||
完整示例参见:`<https://nbviewer.jupyter.org/github/fasiondog/hikyuu/blob/master/hikyuu/examples/notebook/000-Index.ipynb?flush_cache=True>`_
|
||||
|
||||
|
||||
为什么选择 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 毫秒,详见: `性能实测 <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。
|
||||
|
||||
.. 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
|
||||
|
12
config.h.in
12
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}
|
||||
|
||||
|
41
config_utils.h.in
Normal file
41
config_utils.h.in
Normal file
@ -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_ */
|
84
copy_dependents.lua
Normal file
84
copy_dependents.lua
Normal file
@ -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()
|
@ -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日
|
||||
-------------------------
|
||||
|
||||
|
@ -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
|
||||
|
||||
完整示例参见:`<https://nbviewer.jupyter.org/github/fasiondog/hikyuu/blob/master/hikyuu/examples/notebook/000-Index.ipynb?flush_cache=True>`_
|
||||
|
||||
|
||||
为什么选择 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。
|
@ -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 [<module>] (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 [<module>] (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 @@
|
||||
" <th>SZ000001</th>\n",
|
||||
" <td>平安银行</td>\n",
|
||||
" <td>2017-01-03</td>\n",
|
||||
" <td>1762</td>\n",
|
||||
" <td>1854</td>\n",
|
||||
" <td>100</td>\n",
|
||||
" <td>911.0</td>\n",
|
||||
" <td>1046.0</td>\n",
|
||||
" <td>135.0</td>\n",
|
||||
" <td>14.81888</td>\n",
|
||||
" <td>1029.0</td>\n",
|
||||
" <td>118.0</td>\n",
|
||||
" <td>12.952799</td>\n",
|
||||
" </tr>\n",
|
||||
" </tbody>\n",
|
||||
"</table>\n",
|
||||
"</div>"
|
||||
],
|
||||
"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"
|
||||
]
|
||||
}
|
||||
],
|
||||
|
File diff suppressed because one or more lines are too long
@ -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
|
||||
|
@ -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))
|
||||
|
@ -1,3 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf8 -*-
|
||||
# cp936
|
@ -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()
|
||||
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()
|
||||
|
@ -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"):
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
||||
|
@ -35,6 +35,7 @@ int main(int argc, char* argv[]) {
|
||||
std::cout << k[i] << std::endl;
|
||||
}
|
||||
|
||||
// 启动行情接收(只是计算回测可以不需要)
|
||||
startSpotAgent(true);
|
||||
|
||||
while (true) {
|
66
hikyuu_cpp/demo/demo2.cpp
Normal file
66
hikyuu_cpp/demo/demo2.cpp
Normal file
@ -0,0 +1,66 @@
|
||||
// demo.cpp : 定义控制台应用程序的入口点。
|
||||
//
|
||||
|
||||
#include <hikyuu/hikyuu.h>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include <Windows.h>
|
||||
#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;
|
||||
}
|
@ -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()
|
||||
|
@ -29,7 +29,8 @@
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "Log.h"
|
||||
#include "config.h"
|
||||
#include "utilities/Log.h"
|
||||
#include "utilities/osdef.h"
|
||||
#include "utilities/cppdef.h"
|
||||
#include "utilities/datetime/Datetime.h"
|
||||
|
@ -20,7 +20,8 @@
|
||||
#include <H5public.h>
|
||||
#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 的输出窗口
|
||||
|
@ -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) {
|
||||
|
@ -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; ///< 成交量(手),日线以下为股数
|
||||
|
||||
|
@ -1,143 +0,0 @@
|
||||
/*
|
||||
* Log.cpp
|
||||
*
|
||||
* Created on: 2013-2-1
|
||||
* Author: fasiondog
|
||||
*/
|
||||
|
||||
#include <thread>
|
||||
#include "config.h"
|
||||
#include "utilities/os.h"
|
||||
#include "GlobalInitializer.h"
|
||||
#include "Log.h"
|
||||
|
||||
#if USE_SPDLOG_LOGGER
|
||||
// 使用 stdout_color 将无法将日志输出重定向至 python
|
||||
#include <spdlog/sinks/stdout_color_sinks.h>
|
||||
#include <iostream>
|
||||
#include "spdlog/sinks/ostream_sink.h"
|
||||
#include "spdlog/sinks/rotating_file_sink.h"
|
||||
|
||||
#if HKU_USE_SPDLOG_ASYNC_LOGGER
|
||||
#include <spdlog/async.h>
|
||||
#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<spdlog::logger> 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<spdlog::logger> logger = spdlog::get(logname);
|
||||
if (logger) {
|
||||
spdlog::drop(logname);
|
||||
}
|
||||
spdlog::sink_ptr stdout_sink;
|
||||
if (inJupyter) {
|
||||
stdout_sink = std::make_shared<spdlog::sinks::ostream_sink_mt>(std::cout, true);
|
||||
} else {
|
||||
stdout_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
|
||||
}
|
||||
stdout_sink->set_level(spdlog::level::trace);
|
||||
|
||||
spdlog::init_thread_pool(8192, 1);
|
||||
std::vector<spdlog::sink_ptr> sinks{stdout_sink};
|
||||
logger = std::make_shared<spdlog::async_logger>(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<spdlog::logger> logger = spdlog::get(logname);
|
||||
if (logger) {
|
||||
spdlog::drop(logname);
|
||||
}
|
||||
spdlog::sink_ptr stdout_sink;
|
||||
if (inJupyter) {
|
||||
stdout_sink = std::make_shared<spdlog::sinks::ostream_sink_mt>(std::cout, true);
|
||||
} else {
|
||||
stdout_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
|
||||
}
|
||||
stdout_sink->set_level(spdlog::level::trace);
|
||||
std::string log_filename = fmt::format("{}/.hikyuu/hikyuu.log", getUserDir());
|
||||
auto rotating_sink =
|
||||
std::make_shared<spdlog::sinks::rotating_file_sink_mt>(log_filename, 1024 * 1024 * 10, 3);
|
||||
rotating_sink->set_level(spdlog::level::warn);
|
||||
std::vector<spdlog::sink_ptr> sinks{stdout_sink, rotating_sink};
|
||||
logger = std::make_shared<spdlog::logger>(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<std::chrono::milliseconds>(now.time_since_epoch()).count() -
|
||||
std::chrono::duration_cast<std::chrono::seconds>(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
|
@ -278,7 +278,7 @@ struct HKU_API Stock::Data {
|
||||
|
||||
mutable vector<HistoryFinanceInfo>
|
||||
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;
|
||||
|
@ -15,8 +15,8 @@
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#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<Datetime>(),
|
||||
@ -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<string> 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<bool>(back, false), "Preloading all {} kdata to buffer!",
|
||||
back);
|
||||
}
|
||||
|
||||
bool preload_day = m_preloadParam.tryGet<bool>("day", false);
|
||||
HKU_INFO_IF(preload_day, "Preloading all day kdata to buffer!");
|
||||
|
||||
bool preload_week = m_preloadParam.tryGet<bool>("week", false);
|
||||
HKU_INFO_IF(preload_week, "Preloading all week kdata to buffer!");
|
||||
|
||||
bool preload_month = m_preloadParam.tryGet<bool>("month", false);
|
||||
HKU_INFO_IF(preload_month, "Preloading all month kdata to buffer!");
|
||||
|
||||
bool preload_quarter = m_preloadParam.tryGet<bool>("quarter", false);
|
||||
HKU_INFO_IF(preload_quarter, "Preloading all quarter kdata to buffer!");
|
||||
|
||||
bool preload_halfyear = m_preloadParam.tryGet<bool>("halfyear", false);
|
||||
HKU_INFO_IF(preload_halfyear, "Preloading all halfyear kdata to buffer!");
|
||||
|
||||
bool preload_year = m_preloadParam.tryGet<bool>("year", false);
|
||||
HKU_INFO_IF(preload_year, "Preloading all year kdata to buffer!");
|
||||
|
||||
bool preload_min = m_preloadParam.tryGet<bool>("min", false);
|
||||
HKU_INFO_IF(preload_min, "Preloading all 1 min kdata to buffer!");
|
||||
|
||||
bool preload_min5 = m_preloadParam.tryGet<bool>("min5", false);
|
||||
HKU_INFO_IF(preload_min5, "Preloading all 5 min kdata to buffer!");
|
||||
|
||||
bool preload_min15 = m_preloadParam.tryGet<bool>("min15", false);
|
||||
HKU_INFO_IF(preload_min15, "Preloading all 15 min kdata to buffer!");
|
||||
|
||||
bool preload_min30 = m_preloadParam.tryGet<bool>("min30", false);
|
||||
HKU_INFO_IF(preload_min30, "Preloading all 30 min kdata to buffer!");
|
||||
|
||||
bool preload_min60 = m_preloadParam.tryGet<bool>("min60", false);
|
||||
HKU_INFO_IF(preload_min60, "Preloading all 60 min kdata to buffer!");
|
||||
|
||||
bool preload_hour2 = m_preloadParam.tryGet<bool>("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<bool>(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<bool>(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<std::pair<size_t, string>> StockManager::getHistoryFinanceAllFields() con
|
||||
return ret;
|
||||
}
|
||||
|
||||
void StockManager::loadHistoryFinance() {
|
||||
auto* tg = getGlobalTaskGroup();
|
||||
std::lock_guard<std::mutex> lock1(*m_stockDict_mutex);
|
||||
for (auto iter = m_stockDict.begin(); iter != m_stockDict.end(); ++iter) {
|
||||
tg->submit([=]() { iter->second.getHistoryFinance(); });
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace hku
|
||||
|
@ -255,6 +255,9 @@ private:
|
||||
/** 加载历史财经字段索引 */
|
||||
void loadHistoryFinanceField();
|
||||
|
||||
/** 加载历史财务数据 */
|
||||
void loadHistoryFinance();
|
||||
|
||||
private:
|
||||
StockManager();
|
||||
|
||||
|
@ -29,11 +29,15 @@ void StrategyContext::setKTypeList(const vector<KQuery::KType>& 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
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
||||
|
@ -9,8 +9,8 @@
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#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();
|
||||
|
||||
|
@ -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<float> values;
|
||||
std::vector<char> values;
|
||||
|
@ -8,10 +8,9 @@
|
||||
#include <fstream>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include "hikyuu/utilities/Log.h"
|
||||
#include "KDataTempCsvDriver.h"
|
||||
|
||||
#include "../../../Log.h"
|
||||
|
||||
namespace hku {
|
||||
|
||||
KDataTempCsvDriver::~KDataTempCsvDriver() {}
|
||||
|
@ -6,7 +6,7 @@
|
||||
*/
|
||||
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include "../../../Log.h"
|
||||
#include "hikyuu/utilities/Log.h"
|
||||
#include "MySQLKDataDriver.h"
|
||||
#include "KRecordTable.h"
|
||||
|
||||
|
@ -137,8 +137,8 @@
|
||||
* @details 合成多因子
|
||||
* @ingroup TradeSystem
|
||||
*
|
||||
* @defgroup SystemInstance SystemInstance 系统实例
|
||||
* @details 系统实例
|
||||
* @defgroup Stratgy Strategy 策略运行时
|
||||
* @details 策略运行时
|
||||
* @ingroup Hikyuu
|
||||
*
|
||||
* @defgroup Agent Agent 对外数据接收发送代理
|
||||
|
@ -171,68 +171,74 @@ void HKU_API startSpotAgent(bool print) {
|
||||
|
||||
agent.setPrintFlag(print);
|
||||
|
||||
const auto& preloadParam = sm.getPreloadParameter();
|
||||
if (preloadParam.tryGet<bool>("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<bool>("min", false)) {
|
||||
agent.addProcess(std::bind(updateStockMinData, std::placeholders::_1, KQuery::MIN));
|
||||
}
|
||||
|
||||
if (preloadParam.tryGet<bool>("day", false)) {
|
||||
agent.addProcess(updateStockDayData);
|
||||
}
|
||||
if (preloadParam.tryGet<bool>("day", false)) {
|
||||
agent.addProcess(updateStockDayData);
|
||||
}
|
||||
|
||||
if (preloadParam.tryGet<bool>("week", false)) {
|
||||
agent.addProcess(std::bind(updateStockDayUpData, std::placeholders::_1, KQuery::WEEK));
|
||||
}
|
||||
if (preloadParam.tryGet<bool>("week", false)) {
|
||||
agent.addProcess(std::bind(updateStockDayUpData, std::placeholders::_1, KQuery::WEEK));
|
||||
}
|
||||
|
||||
if (preloadParam.tryGet<bool>("month", false)) {
|
||||
agent.addProcess(std::bind(updateStockDayUpData, std::placeholders::_1, KQuery::MONTH));
|
||||
}
|
||||
if (preloadParam.tryGet<bool>("month", false)) {
|
||||
agent.addProcess(std::bind(updateStockDayUpData, std::placeholders::_1, KQuery::MONTH));
|
||||
}
|
||||
|
||||
if (preloadParam.tryGet<bool>("quarter", false)) {
|
||||
agent.addProcess(std::bind(updateStockDayUpData, std::placeholders::_1, KQuery::QUARTER));
|
||||
}
|
||||
if (preloadParam.tryGet<bool>("quarter", false)) {
|
||||
agent.addProcess(
|
||||
std::bind(updateStockDayUpData, std::placeholders::_1, KQuery::QUARTER));
|
||||
}
|
||||
|
||||
if (preloadParam.tryGet<bool>("halfyear", false)) {
|
||||
agent.addProcess(std::bind(updateStockDayUpData, std::placeholders::_1, KQuery::HALFYEAR));
|
||||
}
|
||||
if (preloadParam.tryGet<bool>("halfyear", false)) {
|
||||
agent.addProcess(
|
||||
std::bind(updateStockDayUpData, std::placeholders::_1, KQuery::HALFYEAR));
|
||||
}
|
||||
|
||||
if (preloadParam.tryGet<bool>("year", false)) {
|
||||
agent.addProcess(std::bind(updateStockDayUpData, std::placeholders::_1, KQuery::YEAR));
|
||||
}
|
||||
if (preloadParam.tryGet<bool>("year", false)) {
|
||||
agent.addProcess(std::bind(updateStockDayUpData, std::placeholders::_1, KQuery::YEAR));
|
||||
}
|
||||
|
||||
if (preloadParam.tryGet<bool>("min5", false)) {
|
||||
agent.addProcess(std::bind(updateStockMinData, std::placeholders::_1, KQuery::MIN5));
|
||||
}
|
||||
if (preloadParam.tryGet<bool>("min5", false)) {
|
||||
agent.addProcess(std::bind(updateStockMinData, std::placeholders::_1, KQuery::MIN5));
|
||||
}
|
||||
|
||||
if (preloadParam.tryGet<bool>("min15", false)) {
|
||||
agent.addProcess(std::bind(updateStockMinData, std::placeholders::_1, KQuery::MIN15));
|
||||
}
|
||||
if (preloadParam.tryGet<bool>("min15", false)) {
|
||||
agent.addProcess(std::bind(updateStockMinData, std::placeholders::_1, KQuery::MIN15));
|
||||
}
|
||||
|
||||
if (preloadParam.tryGet<bool>("min30", false)) {
|
||||
agent.addProcess(std::bind(updateStockMinData, std::placeholders::_1, KQuery::MIN30));
|
||||
}
|
||||
if (preloadParam.tryGet<bool>("min30", false)) {
|
||||
agent.addProcess(std::bind(updateStockMinData, std::placeholders::_1, KQuery::MIN30));
|
||||
}
|
||||
|
||||
if (preloadParam.tryGet<bool>("min60", false)) {
|
||||
agent.addProcess(std::bind(updateStockMinData, std::placeholders::_1, KQuery::MIN60));
|
||||
}
|
||||
if (preloadParam.tryGet<bool>("min3", false)) {
|
||||
agent.addProcess(std::bind(updateStockMinData, std::placeholders::_1, KQuery::MIN3));
|
||||
}
|
||||
if (preloadParam.tryGet<bool>("min60", false)) {
|
||||
agent.addProcess(std::bind(updateStockMinData, std::placeholders::_1, KQuery::MIN60));
|
||||
}
|
||||
if (preloadParam.tryGet<bool>("min3", false)) {
|
||||
agent.addProcess(std::bind(updateStockMinData, std::placeholders::_1, KQuery::MIN3));
|
||||
}
|
||||
|
||||
if (preloadParam.tryGet<bool>("hour2", false)) {
|
||||
agent.addProcess(std::bind(updateStockMinData, std::placeholders::_1, KQuery::HOUR2));
|
||||
}
|
||||
if (preloadParam.tryGet<bool>("hour2", false)) {
|
||||
agent.addProcess(std::bind(updateStockMinData, std::placeholders::_1, KQuery::HOUR2));
|
||||
}
|
||||
|
||||
if (preloadParam.tryGet<bool>("hour4", false)) {
|
||||
agent.addProcess(std::bind(updateStockMinData, std::placeholders::_1, KQuery::HOUR4));
|
||||
}
|
||||
if (preloadParam.tryGet<bool>("hour4", false)) {
|
||||
agent.addProcess(std::bind(updateStockMinData, std::placeholders::_1, KQuery::HOUR4));
|
||||
}
|
||||
|
||||
if (preloadParam.tryGet<bool>("hour6", false)) {
|
||||
agent.addProcess(std::bind(updateStockMinData, std::placeholders::_1, KQuery::HOUR6));
|
||||
}
|
||||
if (preloadParam.tryGet<bool>("hour6", false)) {
|
||||
agent.addProcess(std::bind(updateStockMinData, std::placeholders::_1, KQuery::HOUR6));
|
||||
}
|
||||
|
||||
if (preloadParam.tryGet<bool>("hour12", false)) {
|
||||
agent.addProcess(std::bind(updateStockMinData, std::placeholders::_1, KQuery::HOUR12));
|
||||
if (preloadParam.tryGet<bool>("hour12", false)) {
|
||||
agent.addProcess(std::bind(updateStockMinData, std::placeholders::_1, KQuery::HOUR12));
|
||||
}
|
||||
}
|
||||
|
||||
agent.start();
|
||||
|
@ -5,6 +5,7 @@
|
||||
* Author: fasiondog
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "agent/SpotAgent.h"
|
||||
|
||||
namespace hku {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 <typename ResultType>
|
||||
using task_handle = std::future<ResultType>;
|
||||
|
52
hikyuu_cpp/hikyuu/global/SpotRecord.h
Normal file
52
hikyuu_cpp/hikyuu/global/SpotRecord.h
Normal file
@ -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
|
@ -8,6 +8,7 @@
|
||||
#include <chrono>
|
||||
#include <nng/nng.h>
|
||||
#include <nng/protocol/pubsub0/sub.h>
|
||||
#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<void(const SpotRecord&)> process) {
|
||||
HKU_CHECK(m_stop, "SpotAgent is running, please stop agent first!");
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_processList.push_back(process);
|
||||
}
|
||||
|
||||
void SpotAgent::addPostProcess(std::function<void(Datetime)> func) {
|
||||
HKU_CHECK(m_stop, "SpotAgent is running, please stop agent first!");
|
||||
std::lock_guard<std::mutex> 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<std::mutex> lock(m_mutex);
|
||||
m_processList.clear();
|
||||
}
|
||||
|
||||
void SpotAgent::clearPostProcessList() {
|
||||
HKU_CHECK(m_stop, "SpotAgent is running, please stop agent first!");
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_postProcessList.clear();
|
||||
}
|
||||
|
||||
|
@ -9,50 +9,18 @@
|
||||
|
||||
#include <thread>
|
||||
#include <functional>
|
||||
#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<std::mutex> 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<std::future<void>> m_process_task_list;
|
||||
|
||||
// 下面属性被修改时需要加锁,以便可以使用多线程方式运行 strategy
|
||||
std::mutex m_mutex;
|
||||
bool m_print = true; // 是否打印接收进度,防止的交互模式的影响
|
||||
list<std::function<void(const SpotRecord&)>> m_processList; // 已注册的 spot 处理函数列表
|
||||
list<std::function<void(Datetime)>> m_postProcessList; // 已注册的批次后处理函数列表
|
||||
vector<std::future<void>> m_process_task_list;
|
||||
};
|
||||
|
||||
} // namespace hku
|
@ -15,7 +15,6 @@ namespace hku {
|
||||
void initInnerTask() {
|
||||
auto* tm = getScheduler();
|
||||
tm->addFuncAtTimeEveryDay(Datetime::min(), Datetime::max(), TimeDelta(), reloadHikyuuTask);
|
||||
tm->start();
|
||||
}
|
||||
|
||||
void reloadHikyuuTask() {
|
||||
|
@ -5,10 +5,11 @@
|
||||
* Author: fasiondog
|
||||
*/
|
||||
|
||||
#include <hikyuu/GlobalInitializer.h>
|
||||
#include "hikyuu/GlobalInitializer.h"
|
||||
#include <mutex>
|
||||
#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;
|
||||
}
|
||||
|
||||
|
@ -10,17 +10,16 @@
|
||||
#include <boost/uuid/uuid.hpp>
|
||||
#include <boost/uuid/uuid_io.hpp>
|
||||
#include <boost/uuid/uuid_generators.hpp>
|
||||
#include <httplib.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
#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<std::string>();
|
||||
uint64_t port = res["port"].get<uint64_t>();
|
||||
g_latest_version = res.contains("last_version") ? res["last_version"].get<int>() : 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<int>();
|
||||
|
||||
} 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<std::string>();
|
||||
uint64_t port = res["port"].get<uint64_t>();
|
||||
g_latest_version = res.contains("last_version") ? res["last_version"].get<int>() : 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
|
||||
}
|
||||
|
@ -11,7 +11,7 @@
|
||||
|
||||
#include <set>
|
||||
#include <fmt/format.h>
|
||||
#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<string>("tmpdir", config.get("hikyuu", "tmpdir", "."));
|
||||
hkuParam.set<string>("datadir", config.get("hikyuu", "datadir", "."));
|
||||
hkuParam.set<string>("quotation_server", config.get("hikyuu", "quotation_server",
|
||||
"ipc:///tmp/hikyuu_real.ipc"));
|
||||
hkuParam.set<string>("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<bool>(*iter,
|
||||
ignore_preload ? false : config.getBool("preload", *iter));
|
||||
} catch (...) {
|
||||
if (!ignore_preload) {
|
||||
// 获取预加载的最大数量
|
||||
try {
|
||||
preloadParam.set<int>(*iter, config.getInt("preload", *iter));
|
||||
} catch (...) {
|
||||
HKU_WARN("Invalid option: {}", *iter);
|
||||
}
|
||||
auto pos = (*iter).find("max");
|
||||
if (pos == std::string::npos) {
|
||||
preloadParam.set<bool>(*iter,
|
||||
ignore_preload ? false : config.getBool("preload", *iter));
|
||||
} else if (!ignore_preload) {
|
||||
preloadParam.set<int>(*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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
||||
|
@ -7,10 +7,10 @@
|
||||
#include <stdexcept>
|
||||
#include <algorithm>
|
||||
#include <forward_list>
|
||||
#include "hikyuu/utilities/Log.h"
|
||||
#include "Indicator.h"
|
||||
#include "IndParam.h"
|
||||
#include "../Stock.h"
|
||||
#include "../Log.h"
|
||||
#include "../GlobalInitializer.h"
|
||||
#include "imp/ICval.h"
|
||||
|
||||
|
@ -1,84 +0,0 @@
|
||||
/*
|
||||
* Copyright(C) 2021 hikyuu.org
|
||||
*
|
||||
* Create on: 2021-03-23
|
||||
* Author: fasiondog
|
||||
*/
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#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<httplib::Client>(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<string>();
|
||||
} else if (HTTP_STATUS_BAD_REQUEST == res->status) {
|
||||
HKU_ERROR("Bad request: {}", res->body);
|
||||
} else {
|
||||
HKU_ERROR("http response status: {}", res->status);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace hku
|
282
hikyuu_cpp/hikyuu/strategy/BrokerTradeManager.cpp
Normal file
282
hikyuu_cpp/hikyuu/strategy/BrokerTradeManager.cpp
Normal file
@ -0,0 +1,282 @@
|
||||
/*
|
||||
* Copyright (c) 2024 hikyuu.org
|
||||
*
|
||||
* Created on: 2024-08-16
|
||||
* Author: fasiondog
|
||||
*/
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#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<TradeManagerBase> BrokerTradeManager::_clone() {
|
||||
BrokerTradeManager* p = new BrokerTradeManager();
|
||||
p->m_datetime = m_datetime;
|
||||
p->m_cash = m_cash;
|
||||
p->m_position = m_position;
|
||||
return shared_ptr<TradeManagerBase>(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<string>())
|
||||
: 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<string>();
|
||||
auto code = jpos["code"].get<string>();
|
||||
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<double>();
|
||||
pos.stoploss = jpos["stoploss"].get<price_t>();
|
||||
pos.goalPrice = jpos["goal_price"].get<price_t>();
|
||||
pos.totalNumber = pos.number;
|
||||
price_t cost_price = jpos["cost_price"].get<price_t>();
|
||||
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<int>("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<Datetime>(), 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<OrderBrokerPtr>::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<int>("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<OrderBrokerPtr>::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<int>("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
|
@ -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 <httplib.h>
|
||||
#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<TradeManagerBase> _clone() override {
|
||||
return std::make_shared<AccountTradeManager>();
|
||||
}
|
||||
virtual shared_ptr<TradeManagerBase> _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<Datetime>() */
|
||||
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<PositionRecord>() */
|
||||
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<PositionRecord>() */
|
||||
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<Datetime>()
|
||||
@ -382,10 +383,7 @@ public:
|
||||
* @note 当datetime等于Null<Datetime>()时,与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<httplib::Client> m_client;
|
||||
string m_user;
|
||||
string m_password;
|
||||
string m_token;
|
||||
price_t m_cash{0.0}; // 当前可用现金
|
||||
|
||||
typedef map<uint64_t, PositionRecord> position_map_type;
|
||||
position_map_type m_position; // 当前持仓交易对象的持仓记录
|
||||
};
|
||||
|
||||
inline TMPtr crtAccountTM(const string& name, const string& pwd) {
|
||||
return std::make_shared<AccountTradeManager>(name, pwd);
|
||||
}
|
||||
|
||||
} // namespace hku
|
264
hikyuu_cpp/hikyuu/strategy/Strategy.cpp
Normal file
264
hikyuu_cpp/hikyuu/strategy/Strategy.cpp
Normal file
@ -0,0 +1,264 @@
|
||||
/*
|
||||
* Copyright(C) 2021 hikyuu.org
|
||||
*
|
||||
* Create on: 2021-02-16
|
||||
* Author: fasiondog
|
||||
*/
|
||||
|
||||
#include <csignal>
|
||||
#include <unordered_set>
|
||||
#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<string>& codeList, const vector<KQuery::KType>& 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<void(const Stock&, const SpotRecord& spot)>&& changeFunc) {
|
||||
if (!m_running) {
|
||||
run();
|
||||
}
|
||||
m_on_change = changeFunc;
|
||||
}
|
||||
|
||||
void Strategy::onReceivedSpot(std::function<void(const Datetime&)>&& 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<void()>&& 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<int>::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<int>::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<int>::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<int>::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<int>::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<int>::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<int>::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<void()>&& 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
|
132
hikyuu_cpp/hikyuu/strategy/Strategy.h
Normal file
132
hikyuu_cpp/hikyuu/strategy/Strategy.h
Normal file
@ -0,0 +1,132 @@
|
||||
/*
|
||||
* Copyright(C) 2021 hikyuu.org
|
||||
*
|
||||
* Create on: 2021-02-16
|
||||
* Author: fasiondog
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <future>
|
||||
#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<string>& codeList, const vector<KQuery::KType>& 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<void()>&& func, const TimeDelta& delta,
|
||||
const std::string& market = "SH");
|
||||
|
||||
/**
|
||||
* 每日在指定时刻执行任务
|
||||
* @param func 待执行的任务
|
||||
* @param delta 指定时刻
|
||||
* @param ignoreHoliday 忽略节假日,即节假日不执行
|
||||
*/
|
||||
void runDailyAt(std::function<void()>&& func, const TimeDelta& delta,
|
||||
bool ignoreHoliday = true);
|
||||
|
||||
/**
|
||||
* 正确数据发生变化调用,即接收到相应行情数据变更
|
||||
* @note 通常用于调试。且只要收到行情采集消息就会触发,不受开、闭市时间限制
|
||||
* @param stk 数据发生变化的 stock
|
||||
* @param spot 接收到的具体数据
|
||||
*/
|
||||
void onChange(std::function<void(const Stock&, const SpotRecord& spot)>&& changeFunc);
|
||||
|
||||
/**
|
||||
* 一批行情数据接受完毕后通知
|
||||
* @note 通常仅用于调试打印,该批行情数据中不一定含有上下文中包含的 stock
|
||||
* 且只要收到行情采集消息就会触发,不受开、闭市时间限制。
|
||||
*/
|
||||
void onReceivedSpot(std::function<void(const Datetime&)>&& recievedFucn);
|
||||
|
||||
/**
|
||||
* 启动策略执行,必须在已注册相关处理函数后执行
|
||||
*/
|
||||
void start();
|
||||
|
||||
private:
|
||||
string m_name;
|
||||
string m_config_file;
|
||||
StrategyContext m_context;
|
||||
std::function<void(const Datetime&)> m_on_recieved_spot;
|
||||
std::function<void(const Stock&, const SpotRecord& spot)> 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<event_type> m_event_queue; // 消息队列
|
||||
|
||||
/** 先消息队列提交任务后返回的对应 future 的类型 */
|
||||
template <typename ResultType>
|
||||
using event_handle = std::future<ResultType>;
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4996)
|
||||
#endif
|
||||
|
||||
/** 向线程池提交任务 */
|
||||
template <typename FunctionType>
|
||||
event_handle<typename std::result_of<FunctionType()>::type> event(FunctionType f) {
|
||||
typedef typename std::result_of<FunctionType()>::type result_type;
|
||||
std::packaged_task<result_type()> task(f);
|
||||
event_handle<result_type> 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<Strategy> StrategyPtr;
|
||||
|
||||
} // namespace hku
|
@ -1,300 +0,0 @@
|
||||
/*
|
||||
* Copyright(C) 2021 hikyuu.org
|
||||
*
|
||||
* Create on: 2021-02-16
|
||||
* Author: fasiondog
|
||||
*/
|
||||
|
||||
#include <csignal>
|
||||
#include <unordered_set>
|
||||
#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<bool>("enable_market_event", false);
|
||||
setParam<bool>("enable_30_seconds_clock", false);
|
||||
setParam<bool>("enable_1min_clock", false);
|
||||
setParam<bool>("enable_3min_clock", false);
|
||||
setParam<bool>("enable_5min_clock", false);
|
||||
setParam<bool>("enable_10min_clock", false);
|
||||
setParam<bool>("enable_15min_clock", false);
|
||||
setParam<bool>("enable_30min_clock", false);
|
||||
setParam<bool>("enable_60min_clock", false);
|
||||
setParam<bool>("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<string>("tmpdir", config.get("hikyuu", "tmpdir", "."));
|
||||
hkuParam.set<string>("datadir", config.get("hikyuu", "datadir", "."));
|
||||
hkuParam.set<string>("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<string>(*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<string>(*iter, value);
|
||||
}
|
||||
|
||||
option = config.getOptionList("kdata");
|
||||
for (auto iter = option->begin(); iter != option->end(); ++iter) {
|
||||
kdataParam.set<string>(*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<bool>(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<KRecordList> 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<Datetime>(), 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<Datetime>() : 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<string> 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<bool>("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<bool>(enable)) {
|
||||
int repeat = static_cast<int>((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
|
@ -1,156 +0,0 @@
|
||||
/*
|
||||
* Copyright(C) 2021 hikyuu.org
|
||||
*
|
||||
* Create on: 2021-02-16
|
||||
* Author: fasiondog
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <future>
|
||||
#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<string>&& stockList) {
|
||||
m_context.setStockCodeList(std::move(stockList));
|
||||
}
|
||||
|
||||
void setStockCodeList(const vector<string>& stockList) {
|
||||
m_context.setStockCodeList(stockList);
|
||||
}
|
||||
|
||||
const vector<string>& getStockCodeList() const {
|
||||
return m_context.getStockCodeList();
|
||||
}
|
||||
|
||||
void setKTypeList(const vector<KQuery::KType>& ktypeList) {
|
||||
m_context.setKTypeList(ktypeList);
|
||||
}
|
||||
|
||||
const vector<KQuery::KType>& 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<KQuery::KType, Datetime> m_ref_last_time;
|
||||
std::unordered_map<Stock, SpotRecord> 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<event_type> m_event_queue; // 消息队列
|
||||
|
||||
/** 先消息队列提交任务后返回的对应 future 的类型 */
|
||||
template <typename ResultType>
|
||||
using event_handle = std::future<ResultType>;
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4996)
|
||||
#endif
|
||||
|
||||
/** 向线程池提交任务 */
|
||||
template <typename FunctionType>
|
||||
event_handle<typename std::result_of<FunctionType()>::type> event(FunctionType f) {
|
||||
typedef typename std::result_of<FunctionType()>::type result_type;
|
||||
std::packaged_task<result_type()> task(f);
|
||||
event_handle<result_type> 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<StrategyBase> StrategyPtr;
|
||||
|
||||
} // namespace hku
|
@ -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),
|
||||
|
@ -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
|
||||
|
@ -5,10 +5,43 @@
|
||||
* Author: fasiondog
|
||||
*/
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#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<Datetime>();
|
||||
} catch (...) {
|
||||
HKU_ERROR_UNKNOWN;
|
||||
tradetime = Null<Datetime>();
|
||||
}
|
||||
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<Datetime>();
|
||||
} catch (...) {
|
||||
HKU_ERROR_UNKNOWN;
|
||||
tradetime = Null<Datetime>();
|
||||
}
|
||||
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 */
|
||||
|
@ -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字符串
|
||||
* <pre>
|
||||
* 接口规范:
|
||||
* {
|
||||
* "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: 每股买入成本价
|
||||
* </pre>
|
||||
*/
|
||||
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;
|
||||
|
@ -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()) {
|
||||
|
@ -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<Datetime>()
|
||||
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<Datetime>()
|
||||
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}; ///< 累计卖出资金
|
||||
|
||||
//===================
|
||||
// 序列化支持
|
||||
|
@ -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<OrderBrokerPtr>::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<OrderBrokerPtr>::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<OrderBrokerPtr>::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<OrderBrokerPtr>::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<Datetime>(), 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,
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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; // 成本算法
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
267
hikyuu_cpp/hikyuu/utilities/FilterNode.h
Normal file
267
hikyuu_cpp/hikyuu/utilities/FilterNode.h
Normal file
@ -0,0 +1,267 @@
|
||||
/*
|
||||
* Copyright (c) 2023 hikyuu.org
|
||||
*
|
||||
* Created on: 2023-01-13
|
||||
* Author: fasiondog
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
#include <forward_list>
|
||||
#include <unordered_map>
|
||||
#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<FilterNode>;
|
||||
|
||||
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<ptr_t>::const_iterator;
|
||||
using iterator = std::forward_list<ptr_t>::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 <typename ValueT>
|
||||
ValueT value() const {
|
||||
return any_cast<ValueT>(m_value);
|
||||
}
|
||||
|
||||
template <typename ValueT>
|
||||
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<ptr_t> m_children;
|
||||
bool m_exclusive = false;
|
||||
};
|
||||
|
||||
template <>
|
||||
inline const any_t& FilterNode::value() const {
|
||||
return m_value;
|
||||
}
|
||||
|
||||
typedef std::shared_ptr<FilterNode> FilterNodePtr;
|
||||
|
||||
/**
|
||||
* @brief 绑定过滤节点,通过 std::function 绑定自定义的 filter 和 process 处理函数
|
||||
*/
|
||||
class BindFilterNode : public FilterNode {
|
||||
public:
|
||||
BindFilterNode() = default;
|
||||
virtual ~BindFilterNode() = default;
|
||||
|
||||
using filter_func = std::function<bool(FilterNode*, const any_t&)>;
|
||||
using process_func = std::function<void(FilterNode*, const any_t&)>;
|
||||
|
||||
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 EventT>
|
||||
class AsyncSerialEventProcessor {
|
||||
public:
|
||||
/**
|
||||
* @brief 构造函数
|
||||
* @param quit_wait 退出时等待所有任务完成
|
||||
*/
|
||||
explicit AsyncSerialEventProcessor(bool quit_wait = true) : m_quit_wait(quit_wait) {
|
||||
m_tg = std::unique_ptr<ThreadPool>(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<std::mutex> 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<EventT, FilterNodePtr> m_trees;
|
||||
std::unique_ptr<ThreadPool> m_tg;
|
||||
bool m_quit_wait = true;
|
||||
};
|
||||
|
||||
} // namespace hku
|
230
hikyuu_cpp/hikyuu/utilities/LRUCache11.h
Normal file
230
hikyuu_cpp/hikyuu/utilities/LRUCache11.h
Normal file
@ -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 <mohaps@gmail.com>
|
||||
*
|
||||
* 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 <algorithm>
|
||||
#include <cstdint>
|
||||
#include <list>
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
|
||||
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 <typename K, typename V>
|
||||
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 <class Key, class Value, class Lock = NullLock,
|
||||
class Map =
|
||||
std::unordered_map<Key, typename std::list<KeyValuePair<Key, Value>>::iterator>>
|
||||
class Cache {
|
||||
public:
|
||||
typedef KeyValuePair<Key, Value> node_type;
|
||||
typedef std::list<KeyValuePair<Key, Value>> list_type;
|
||||
typedef Map map_type;
|
||||
typedef Lock lock_type;
|
||||
using Guard = std::lock_guard<lock_type>;
|
||||
/**
|
||||
* 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 <typename F>
|
||||
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
|
86
hikyuu_cpp/hikyuu/utilities/Log.cpp
Normal file
86
hikyuu_cpp/hikyuu/utilities/Log.cpp
Normal file
@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Log.cpp
|
||||
*
|
||||
* Created on: 2013-2-1
|
||||
* Author: fasiondog
|
||||
*/
|
||||
|
||||
#include <thread>
|
||||
#include "hikyuu/GlobalInitializer.h"
|
||||
#include "os.h"
|
||||
#include "Log.h"
|
||||
|
||||
// 使用 stdout_color 将无法将日志输出重定向至 python
|
||||
#include <spdlog/sinks/stdout_color_sinks.h>
|
||||
#include <iostream>
|
||||
#include "spdlog/sinks/ostream_sink.h"
|
||||
#include "spdlog/sinks/rotating_file_sink.h"
|
||||
|
||||
#if HKU_USE_SPDLOG_ASYNC_LOGGER
|
||||
#include <spdlog/async.h>
|
||||
#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<spdlog::logger> 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<spdlog::logger> logger = spdlog::get(logname);
|
||||
if (logger) {
|
||||
spdlog::drop(logname);
|
||||
}
|
||||
|
||||
spdlog::sink_ptr stdout_sink;
|
||||
if (not_use_color) {
|
||||
stdout_sink = std::make_shared<spdlog::sinks::ostream_sink_mt>(std::cout, true);
|
||||
} else {
|
||||
stdout_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
|
||||
}
|
||||
stdout_sink->set_level(spdlog::level::trace);
|
||||
|
||||
std::string logfile = filename.empty() ? "./hikyuu.log" : filename;
|
||||
auto rotating_sink =
|
||||
std::make_shared<spdlog::sinks::rotating_file_sink_mt>(logfile, 1024 * 1024 * 10, 3);
|
||||
rotating_sink->set_level(spdlog::level::warn);
|
||||
|
||||
std::vector<spdlog::sink_ptr> 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<spdlog::async_logger>(logname, sinks.begin(), sinks.end(),
|
||||
spdlog::thread_pool(),
|
||||
spdlog::async_overflow_policy::block);
|
||||
#else
|
||||
logger = std::make_shared<spdlog::logger>(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
|
@ -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 <spdlog/spdlog.h>
|
||||
#include <spdlog/fmt/ostr.h>
|
||||
#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 <spdlog/spdlog.h>
|
||||
#include <spdlog/fmt/ostr.h>
|
||||
#if HKU_USE_SPDLOG_ASYNC_LOGGER
|
||||
#include "spdlog/async.h"
|
||||
#endif
|
||||
// clang-format on
|
||||
|
||||
@ -30,30 +36,24 @@
|
||||
#include <fmt/format.h>
|
||||
#include <fmt/chrono.h>
|
||||
|
||||
#ifdef HKU_ENABLE_STACK_TRACE
|
||||
#ifndef HKU_ENABLE_STACK_TRACE
|
||||
#define HKU_ENABLE_STACK_TRACE 0
|
||||
#endif
|
||||
|
||||
#if HKU_ENABLE_STACK_TRACE
|
||||
#include <boost/stacktrace.hpp>
|
||||
#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<spdlog::logger> HKU_API getHikyuuLogger();
|
||||
std::shared_ptr<spdlog::logger> 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<spdlog::logger> 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 */
|
@ -10,6 +10,7 @@
|
||||
#define NULL_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
#include "osdef.h"
|
||||
|
@ -6,8 +6,8 @@
|
||||
*/
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include "hikyuu/utilities/Log.h"
|
||||
#include "hikyuu/Block.h"
|
||||
#include "../Log.h"
|
||||
#include "Parameter.h"
|
||||
|
||||
namespace hku {
|
||||
|
@ -17,7 +17,8 @@
|
||||
#include <map>
|
||||
#include <boost/any.hpp>
|
||||
|
||||
#include "../config.h"
|
||||
#include "hikyuu/config.h"
|
||||
#include "hikyuu/utilities/config.h"
|
||||
|
||||
#if HKU_SUPPORT_SERIALIZATION
|
||||
#include <boost/lexical_cast.hpp>
|
||||
|
636
hikyuu_cpp/hikyuu/utilities/ResourcePool.h
Normal file
636
hikyuu_cpp/hikyuu/utilities/ResourcePool.h
Normal file
@ -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 <thread>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <queue>
|
||||
#include <chrono>
|
||||
#include <unordered_set>
|
||||
#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 <typename ResourceType>
|
||||
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<std::mutex> 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<std::mutex> lock(m_mutex);
|
||||
m_maxPoolSize = num;
|
||||
}
|
||||
|
||||
/** 设置允许的最大空闲资源数 */
|
||||
void maxIdleSize(size_t num) {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_maxIdelSize = num;
|
||||
}
|
||||
|
||||
/** 资源实例指针类型 */
|
||||
typedef std::shared_ptr<ResourceType> ResourcePtr;
|
||||
|
||||
/**
|
||||
* 获取可用资源,如超出允许的最大资源数将返回空指针
|
||||
* @exception CreateResourceException 新资源创建可能抛出异常
|
||||
*/
|
||||
ResourcePtr get() {
|
||||
std::lock_guard<std::mutex> 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<ResourceCloser>(result));
|
||||
return result;
|
||||
}
|
||||
p = m_resourceList.front();
|
||||
m_resourceList.pop();
|
||||
result = ResourcePtr(p, ResourceCloser(this));
|
||||
m_closer_set.insert(std::get_deleter<ResourceCloser>(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在指定的超时时间内获取可用资源
|
||||
* @param ms_timeout 超时时间,单位毫秒
|
||||
* @exception GetResourceTimeoutException, CreateResourceException
|
||||
*/
|
||||
ResourcePtr getWaitFor(uint64_t ms_timeout) { // NOSONAR
|
||||
std::unique_lock<std::mutex> 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<uint64_t, std::milli>(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<ResourceCloser>(result));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
p = m_resourceList.front();
|
||||
m_resourceList.pop();
|
||||
result = ResourcePtr(p, ResourceCloser(this));
|
||||
m_closer_set.insert(std::get_deleter<ResourceCloser>(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<std::mutex> 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<ResourceType *> 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<std::mutex> 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<ResourceCloser *> 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 <typename ResourceType>
|
||||
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<std::mutex> 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<std::mutex> lock(m_mutex);
|
||||
m_maxPoolSize = num;
|
||||
}
|
||||
|
||||
/** 设置允许的最大空闲资源数 */
|
||||
void maxIdleSize(size_t num) {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_maxIdelSize = num;
|
||||
}
|
||||
|
||||
/** 指定参数是否存在 */
|
||||
bool haveParam(const std::string &name) {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
return m_param.have(name);
|
||||
}
|
||||
|
||||
/** 获取指定参数的值,如参数不存在或类型不匹配抛出异常 */
|
||||
template <typename ValueType>
|
||||
ValueType getParam(const std::string &name) {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
return m_param.get<ValueType>(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设定指定参数的值,参数仅在生成新的资源时生效
|
||||
* @details 在原本存在该参数的情况下,新设定的值类型须和原有参数类型相同,否则将抛出异常
|
||||
* @param name 参数名
|
||||
* @param value 参数值
|
||||
* @exception std::logic_error
|
||||
*/
|
||||
template <typename ValueType>
|
||||
void setParam(const std::string &name, const ValueType &value) {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
// 如果参数未实际发送变化,则直接返回
|
||||
HKU_IF_RETURN(m_param.have(name) && value == m_param.get<ValueType>(name), void());
|
||||
m_param.set<ValueType>(name, value);
|
||||
m_version++;
|
||||
_releaseIdleResourceNoLock(); // 释放当前空闲资源,以便新参数值生效
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置资源参数,参数仅在生成新的资源时生效
|
||||
* @param param 参数对象
|
||||
*/
|
||||
void setParameter(const Parameter ¶m) {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_param = param;
|
||||
m_version++;
|
||||
_releaseIdleResourceNoLock(); // 释放当前空闲资源,以便新参数值生效
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置资源参数,参数仅在生成新的资源时生效
|
||||
* @param param 参数对象
|
||||
*/
|
||||
void setParameter(Parameter &¶m) {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_param = std::move(param);
|
||||
m_version++;
|
||||
_releaseIdleResourceNoLock(); // 释放当前空闲资源,以便新参数值生效
|
||||
}
|
||||
|
||||
/** 获取当前资源池版本 */
|
||||
int getVersion() {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
return m_version;
|
||||
}
|
||||
|
||||
/** 递增当前资源池版本,相当于通知资源池资源版本发生变化 */
|
||||
void incVersion(int version) {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_version++;
|
||||
}
|
||||
|
||||
/** 资源实例指针类型 */
|
||||
typedef std::shared_ptr<ResourceType> ResourcePtr;
|
||||
|
||||
/**
|
||||
* 获取可用资源,如超出允许的最大资源数将返回空指针
|
||||
* @exception CreateResourceException 新资源创建可能抛出异常
|
||||
*/
|
||||
ResourcePtr get() {
|
||||
std::lock_guard<std::mutex> 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<ResourceCloser>(result));
|
||||
return result;
|
||||
}
|
||||
p = m_resourceList.front();
|
||||
m_resourceList.pop();
|
||||
result = ResourcePtr(p, ResourceCloser(this));
|
||||
m_closer_set.insert(std::get_deleter<ResourceCloser>(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在指定的超时时间内获取可用资源
|
||||
* @param ms_timeout 超时时间,单位毫秒
|
||||
* @exception GetResourceTimeoutException, CreateResourceException
|
||||
*/
|
||||
ResourcePtr getWaitFor(uint64_t ms_timeout) { // NOSONAR
|
||||
std::unique_lock<std::mutex> 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<uint64_t, std::milli>(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<ResourceCloser>(result));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
p = m_resourceList.front();
|
||||
m_resourceList.pop();
|
||||
result = ResourcePtr(p, ResourceCloser(this));
|
||||
m_closer_set.insert(std::get_deleter<ResourceCloser>(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<std::mutex> 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<ResourceType *> 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<std::mutex> 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<ResourceCloser *> m_closer_set; // 占用资源的 closer
|
||||
};
|
||||
|
||||
} // namespace hku
|
||||
|
||||
#endif /* HKU_UTILS_RESOURCE_POOL_H */
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -21,9 +21,10 @@
|
||||
#include <chrono>
|
||||
#include <vector>
|
||||
#include <fmt/format.h>
|
||||
#include "hikyuu/utilities/config.h"
|
||||
|
||||
#ifndef HKU_API
|
||||
#define HKU_API
|
||||
#ifndef HKU_UTILS_API
|
||||
#define HKU_UTILS_API
|
||||
#endif
|
||||
#ifdef __ANDROID__
|
||||
#include <android/log.h>
|
||||
@ -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<std::string> 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();
|
||||
|
||||
/**
|
||||
* 耗时计时器开启关闭状态看守,记录之前的耗时开关状态,并置为指定状态,释放时恢复原状态
|
||||
|
@ -11,7 +11,7 @@
|
||||
#include <unordered_map>
|
||||
#include <functional>
|
||||
#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<IntervalS> new_queue;
|
||||
m_queue.swap(new_queue);
|
||||
if (!m_tg) {
|
||||
#if CPP_STANDARD >= CPP_STANDARD_14
|
||||
m_tg = std::make_unique<ThreadPool>(m_work_num);
|
||||
#else
|
||||
m_tg = std::unique_ptr<ThreadPool>(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<int>::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<int, Timer*> m_timers;
|
||||
int m_current_timer_id;
|
||||
size_t m_work_num; // 任务执行线程池线程数量
|
||||
std::unique_ptr<ThreadPool> m_tg;
|
||||
ThreadPool* m_tg{nullptr};
|
||||
bool m_use_extend_tg{false};
|
||||
};
|
||||
|
||||
} // namespace hku
|
142
hikyuu_cpp/hikyuu/utilities/any_to_string.h
Normal file
142
hikyuu_cpp/hikyuu/utilities/any_to_string.h
Normal file
@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Copyright (c) 2023 hikyuu.org
|
||||
*
|
||||
* Created on: 2023-01-15
|
||||
* Author: fasiondog
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "osdef.h"
|
||||
#include "cppdef.h"
|
||||
#if !HKU_OS_IOS && CPP_STANDARD >= CPP_STANDARD_17
|
||||
#include <any>
|
||||
#else
|
||||
#include <boost/any.hpp>
|
||||
#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 <typename ValueT>
|
||||
inline std::string any_to_string(const any_t& data) {
|
||||
return any_cast<ValueT>(data).str();
|
||||
}
|
||||
|
||||
template <typename ValueT>
|
||||
inline any_t string_to_any(const std::string& data) {
|
||||
return any_t(ValueT(data));
|
||||
}
|
||||
|
||||
template <>
|
||||
inline std::string any_to_string<int>(const any_t& data) {
|
||||
return std::to_string(any_cast<int>(data));
|
||||
}
|
||||
|
||||
template <>
|
||||
inline std::string any_to_string<long>(const any_t& data) {
|
||||
return std::to_string(any_cast<long>(data));
|
||||
}
|
||||
|
||||
template <>
|
||||
inline std::string any_to_string<long long>(const any_t& data) {
|
||||
return std::to_string(any_cast<long long>(data));
|
||||
}
|
||||
|
||||
template <>
|
||||
inline std::string any_to_string<unsigned int>(const any_t& data) {
|
||||
return std::to_string(any_cast<unsigned int>(data));
|
||||
}
|
||||
|
||||
template <>
|
||||
inline std::string any_to_string<unsigned long>(const any_t& data) {
|
||||
return std::to_string(any_cast<unsigned long>(data));
|
||||
}
|
||||
|
||||
template <>
|
||||
inline std::string any_to_string<unsigned long long>(const any_t& data) {
|
||||
return std::to_string(any_cast<unsigned long long>(data));
|
||||
}
|
||||
|
||||
template <>
|
||||
inline std::string any_to_string<float>(const any_t& data) {
|
||||
return std::to_string(any_cast<float>(data));
|
||||
}
|
||||
|
||||
template <>
|
||||
inline std::string any_to_string<double>(const any_t& data) {
|
||||
return std::to_string(any_cast<double>(data));
|
||||
}
|
||||
|
||||
template <>
|
||||
inline std::string any_to_string<long double>(const any_t& data) {
|
||||
return std::to_string(any_cast<long double>(data));
|
||||
}
|
||||
|
||||
template <>
|
||||
inline any_t string_to_any<int>(const std::string& data) {
|
||||
return any_t(std::stoi(data));
|
||||
}
|
||||
|
||||
template <>
|
||||
inline any_t string_to_any<long>(const std::string& data) {
|
||||
return any_t(std::stol(data));
|
||||
}
|
||||
|
||||
template <>
|
||||
inline any_t string_to_any<long long>(const std::string& data) {
|
||||
return any_t(std::stoll(data));
|
||||
}
|
||||
|
||||
template <>
|
||||
inline any_t string_to_any<unsigned int>(const std::string& data) {
|
||||
return any_t((unsigned int)(std::stoul(data)));
|
||||
}
|
||||
|
||||
template <>
|
||||
inline any_t string_to_any<unsigned long>(const std::string& data) {
|
||||
return any_t(std::stoul(data));
|
||||
}
|
||||
|
||||
template <>
|
||||
inline any_t string_to_any<unsigned long long>(const std::string& data) {
|
||||
return any_t(std::stoull(data));
|
||||
}
|
||||
|
||||
template <>
|
||||
inline any_t string_to_any<float>(const std::string& data) {
|
||||
return any_t(std::stof(data));
|
||||
}
|
||||
|
||||
template <>
|
||||
inline any_t string_to_any<double>(const std::string& data) {
|
||||
return any_t(std::stod(data));
|
||||
}
|
||||
|
||||
template <>
|
||||
inline any_t string_to_any<long double>(const std::string& data) {
|
||||
return any_t(std::stold(data));
|
||||
}
|
||||
|
||||
} // namespace hku
|
@ -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<char *>(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<char *>(szinput.c_str());
|
||||
size_t inlen = strlen(inbuf);
|
||||
size_t outlen = inlen * 2;
|
||||
|
@ -15,11 +15,12 @@
|
||||
#include <cctype>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <algorithm>
|
||||
|
||||
#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 <typename ValueT>
|
||||
ValueT roundEx(ValueT number, int ndigits = 0) {
|
||||
// 切换至:ROUND_HALF_EVEN 银行家舍入法
|
||||
ValueT pow1, pow2, y, z;
|
||||
ValueT x = number;
|
||||
if (ndigits >= 0) {
|
||||
pow1 = std::pow<ValueT>(10.0, (ValueT)ndigits);
|
||||
pow1 = pow(ValueT(10.0), ValueT(ndigits));
|
||||
pow2 = 1.0;
|
||||
y = (x * pow1) * pow2;
|
||||
} else {
|
||||
pow1 = std::pow<ValueT>(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<std::string_view> split(const std::string& str, char c) {
|
||||
inline std::vector<std::string_view> split(const std::string &str, char c) {
|
||||
std::vector<std::string_view> result;
|
||||
std::string_view view(str);
|
||||
size_t prepos = 0;
|
||||
@ -226,9 +227,7 @@ inline std::vector<std::string_view> 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<std::string_view> split(const std::string& str, char c) {
|
||||
* @return string_view 组成的 vector
|
||||
* @note 注意返回结果的生命周期应小于输入的字符串相同!
|
||||
*/
|
||||
inline std::vector<std::string_view> split(const std::string_view& view, char c) {
|
||||
inline std::vector<std::string_view> split(const std::string_view &view, char c) {
|
||||
std::vector<std::string_view> result;
|
||||
size_t prepos = 0;
|
||||
size_t pos = view.find_first_of(c);
|
||||
@ -249,14 +248,12 @@ inline std::vector<std::string_view> 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<std::string_view> split(const std::string_view& str,
|
||||
const std::string& split_str) {
|
||||
inline std::vector<std::string_view> split(const std::string_view &str,
|
||||
const std::string &split_str) {
|
||||
std::vector<std::string_view> result;
|
||||
size_t split_str_len = split_str.size();
|
||||
if (split_str_len == 0) {
|
||||
@ -272,25 +269,64 @@ inline std::vector<std::string_view> 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<std::string> split(const std::string &str, char c) {
|
||||
std::vector<std::string> 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<std::string> split(const std::string &str, const std::string &split_str) {
|
||||
std::vector<std::string> 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());
|
||||
}
|
||||
|
||||
|
108
hikyuu_cpp/hikyuu/utilities/base64.cpp
Normal file
108
hikyuu_cpp/hikyuu/utilities/base64.cpp
Normal file
@ -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
|
59
hikyuu_cpp/hikyuu/utilities/base64.h
Normal file
59
hikyuu_cpp/hikyuu/utilities/base64.h
Normal file
@ -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 <memory>
|
||||
#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
|
@ -11,18 +11,22 @@
|
||||
|
||||
#include <fmt/format.h>
|
||||
#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<unsigned long long>() == 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<uint64_t>();
|
||||
} catch (...) {
|
||||
@ -169,7 +173,7 @@ uint64_t Datetime::ym() const noexcept {
|
||||
try {
|
||||
HKU_IF_RETURN(isNull(), Null<unsigned long long>());
|
||||
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<uint64_t>();
|
||||
} catch (...) {
|
||||
@ -184,7 +188,7 @@ uint64_t Datetime::ymd() const noexcept {
|
||||
HKU_IF_RETURN(isNull(), Null<unsigned long long>());
|
||||
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<uint64_t>();
|
||||
} catch (...) {
|
||||
@ -199,7 +203,7 @@ uint64_t Datetime::ymdh() const noexcept {
|
||||
HKU_IF_RETURN(isNull(), Null<unsigned long long>());
|
||||
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<uint64_t>();
|
||||
} 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<uint64_t>();
|
||||
} 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<uint64_t>();
|
||||
} 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<uint64_t>();
|
||||
} catch (...) {
|
||||
@ -269,7 +273,7 @@ uint64_t Datetime::ticks() const noexcept {
|
||||
HKU_IF_RETURN(isNull(), Null<uint64_t>());
|
||||
TimeDelta d = (*this) - Datetime::min();
|
||||
return d.ticks();
|
||||
} catch (const std::exception& e) {
|
||||
} catch (const std::exception &e) {
|
||||
HKU_ERROR(e.what());
|
||||
return Null<uint64_t>();
|
||||
} 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();
|
||||
|
@ -17,14 +17,19 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <fmt/ostream.h>
|
||||
|
||||
#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();
|
||||
|
||||
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"
|
||||
* </pre>
|
||||
*/
|
||||
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<Datetime> 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<hku::Datetime> {
|
||||
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<hku::Datetime> {
|
||||
constexpr auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
|
||||
constexpr auto parse(format_parse_context &ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.end();
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
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());
|
||||
}
|
||||
};
|
||||
|
@ -10,7 +10,7 @@
|
||||
#include <cstdint>
|
||||
#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<int64_t>(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<int64_t>(roundEx(double(ticks()) * p, 0)));
|
||||
return TimeDelta::fromTicks(static_cast<int64_t>(roundEx<double>(double(ticks()) * p, 0)));
|
||||
}
|
||||
|
||||
TimeDelta TimeDelta::operator/(double p) const {
|
||||
HKU_CHECK(p != 0, "Attempt to divide by 0!");
|
||||
return TimeDelta::fromTicks(static_cast<int64_t>(roundEx(double(ticks()) / p, 0)));
|
||||
return TimeDelta::fromTicks(static_cast<int64_t>(roundEx<double>(double(ticks()) / p, 0)));
|
||||
}
|
||||
|
||||
TimeDelta TimeDelta::floorDiv(double p) const {
|
||||
|
@ -20,8 +20,8 @@
|
||||
#include <fmt/ostream.h>
|
||||
#include <boost/date_time/posix_time/posix_time.hpp>
|
||||
|
||||
#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<hku::TimeDelta> {
|
||||
constexpr auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
|
||||
constexpr auto parse(format_parse_context &ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.end();
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
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());
|
||||
}
|
||||
};
|
||||
|
@ -13,23 +13,23 @@
|
||||
#include <vector>
|
||||
#include <fmt/format.h>
|
||||
#include <fmt/ranges.h>
|
||||
#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 <typename T>
|
||||
DBCondition in(const std::vector<T>& vals) {
|
||||
DBCondition in(const std::vector<T> &vals) {
|
||||
HKU_CHECK(!vals.empty(), "input vals can't be empty!");
|
||||
return DBCondition(fmt::format("({} in ({}))", name, fmt::join(vals, ",")));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
DBCondition not_in(const std::vector<T>& vals) {
|
||||
DBCondition not_in(const std::vector<T> &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<std::string>(const std::vector<std::string>& vals) {
|
||||
inline DBCondition Field::in<std::string>(const std::vector<std::string> &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<std::string>(const std::vector<std::string>& vals)
|
||||
}
|
||||
|
||||
template <>
|
||||
inline DBCondition Field::not_in<std::string>(const std::vector<std::string>& vals) {
|
||||
inline DBCondition Field::not_in<std::string>(const std::vector<std::string> &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<std::string>(const std::vector<std::string>& 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 <typename T>
|
||||
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 <typename T>
|
||||
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 <typename T>
|
||||
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 <typename T>
|
||||
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 <typename T>
|
||||
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 <typename T>
|
||||
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));
|
||||
}
|
||||
|
||||
|
@ -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 */
|
@ -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<DBConnectBase> {
|
||||
class HKU_UTILS_API DBConnectBase : public std::enable_shared_from_this<DBConnectBase> {
|
||||
PARAMETER_SUPPORT // NOSONAR
|
||||
|
||||
public :
|
||||
@ -297,7 +297,7 @@ private:
|
||||
};
|
||||
|
||||
/** @ingroup DBConnect */
|
||||
typedef shared_ptr<DBConnectBase> DBConnectPtr;
|
||||
typedef std::shared_ptr<DBConnectBase> 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 <typename T>
|
||||
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 <typename Container>
|
||||
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();
|
||||
|
@ -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<string> &upgrade_scripts, int start_version,
|
||||
const char *create_script) {
|
||||
void HKU_UTILS_API DBUpgrade(const DBConnectPtr &driver, const char *module_name,
|
||||
const std::vector<std::string> &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;
|
||||
}
|
||||
|
||||
|
@ -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<std::string> &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<std::string> &upgrade_scripts, int start_version = 2,
|
||||
const char *create_script = nullptr);
|
||||
|
||||
} // namespace hku
|
@ -8,7 +8,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include "hikyuu/exception.h"
|
||||
#include "hikyuu/utilities/exception.h"
|
||||
|
||||
namespace hku {
|
||||
|
||||
|
@ -9,7 +9,7 @@
|
||||
|
||||
#include <iterator>
|
||||
#include "hikyuu/utilities/arithmetic.h"
|
||||
#include "hikyuu/Log.h"
|
||||
#include "hikyuu/utilities/Log.h"
|
||||
#include "hikyuu/utilities/osdef.h"
|
||||
#include "DBConnectBase.h"
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user