From 34821efb601dfbbaaadefeb6652d62120266be6b Mon Sep 17 00:00:00 2001 From: fasiondog Date: Fri, 22 Dec 2023 23:44:24 +0800 Subject: [PATCH 1/3] =?UTF-8?q?hikyuutdx=E6=9C=AA=E9=80=89=E6=8B=A9?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E6=97=B6=E6=B7=BB=E5=8A=A0=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hikyuu/gui/data/UsePytdxImportToH5Thread.py | 1 + 1 file changed, 1 insertion(+) diff --git a/hikyuu/gui/data/UsePytdxImportToH5Thread.py b/hikyuu/gui/data/UsePytdxImportToH5Thread.py index 089c8b47..968de30b 100644 --- a/hikyuu/gui/data/UsePytdxImportToH5Thread.py +++ b/hikyuu/gui/data/UsePytdxImportToH5Thread.py @@ -116,6 +116,7 @@ class UsePytdxImportToH5Thread(QThread): return if task_count == 0: + self.send_message(['INFO', '未选择需要导入的行情数据!']) return use_tdx_number = min( From c45c38ee985db50f537a092730f99902eceb1233 Mon Sep 17 00:00:00 2001 From: fasiondog Date: Sun, 24 Dec 2023 15:46:28 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=8D=95=E7=AC=94?= =?UTF-8?q?=E6=9C=80=E5=A4=A7=E7=9B=88=E5=88=A9/=E4=BA=8F=E6=8D=9F?= =?UTF-8?q?=E6=AF=94=E4=BE=8B=E7=BB=9F=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hikyuu_cpp/hikyuu/trade_manage/Performance.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/hikyuu_cpp/hikyuu/trade_manage/Performance.cpp b/hikyuu_cpp/hikyuu/trade_manage/Performance.cpp index 17636d0e..4952a0af 100644 --- a/hikyuu_cpp/hikyuu/trade_manage/Performance.cpp +++ b/hikyuu_cpp/hikyuu/trade_manage/Performance.cpp @@ -40,7 +40,9 @@ Performance::Performance() {"平均赢利/平均亏损比例", 0.}, {"净赢利/亏损比例", 0.}, {"最大单笔赢利", 0.}, + {"最大单笔盈利百分比%", 0.}, {"最大单笔亏损", 0.}, + {"最大单笔亏损百分比%", 0.}, {"赢利交易平均持仓时间", 0.}, {"赢利交易最大持仓时间", 0.}, {"亏损交易平均持仓时间", 0.}, @@ -198,6 +200,8 @@ void Performance ::statistics(const TradeManagerPtr& tm, const Datetime& datetim price_t profit = roundEx(pos.sellMoney - pos.totalCost - pos.buyMoney, precision); m_result["已平仓净利润总额"] = roundEx(m_result["已平仓净利润总额"] + profit, precision); + price_t profit_percent = profit / (pos.buyMoney + pos.totalCost) * 100.; + price_t r = roundEx(profit / pos.totalRisk, precision); total_r += r; @@ -209,6 +213,10 @@ void Performance ::statistics(const TradeManagerPtr& tm, const Datetime& datetim m_result["最大单笔赢利"] = profit; } + if (profit_percent > m_result["最大单笔盈利百分比%"]) { + m_result["最大单笔盈利百分比%"] = profit_percent; + } + int duration = (pos.cleanDatetime.date() - pos.takeDatetime.date()).days(); earn.total_duration += duration; if (duration > m_result["赢利交易最大持仓时间"]) { @@ -252,6 +260,10 @@ void Performance ::statistics(const TradeManagerPtr& tm, const Datetime& datetim m_result["最大单笔亏损"] = profit; } + if (profit_percent < m_result["最大单笔亏损百分比%"]) { + m_result["最大单笔亏损百分比%"] = profit_percent; + } + int duration = (pos.cleanDatetime.date() - pos.takeDatetime.date()).days(); loss.total_duration += duration; if (duration > m_result["亏损交易最大持仓时间"]) { From e3cbb2d0ec2bdecba4ee13c5816f5752b6d4463e Mon Sep 17 00:00:00 2001 From: fasiondog Date: Mon, 25 Dec 2023 00:28:22 +0800 Subject: [PATCH 3/3] =?UTF-8?q?add=20MDD=20=E7=9B=B8=E5=AF=B9=E5=8E=86?= =?UTF-8?q?=E5=8F=B2=E6=9C=80=E9=AB=98=E5=80=BC=E5=9B=9E=E6=92=A4=E7=99=BE?= =?UTF-8?q?=E5=88=86=E6=AF=94=EF=BC=9Badd=20MRR=20=E7=9B=B8=E5=AF=B9?= =?UTF-8?q?=E5=8E=86=E5=8F=B2=E6=9C=80=E4=BD=8E=E5=80=BC=E7=9B=88=E5=88=A9?= =?UTF-8?q?=E6=AF=94=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/source/indicator/indicator.rst | 10 +++ hikyuu_cpp/hikyuu/indicator/build_in.h | 2 + hikyuu_cpp/hikyuu/indicator/crt/MDD.h | 29 +++++++ hikyuu_cpp/hikyuu/indicator/crt/MRR.h | 29 +++++++ hikyuu_cpp/hikyuu/indicator/imp/IMdd.cpp | 53 ++++++++++++ hikyuu_cpp/hikyuu/indicator/imp/IMdd.h | 23 ++++++ hikyuu_cpp/hikyuu/indicator/imp/IMrr.cpp | 53 ++++++++++++ hikyuu_cpp/hikyuu/indicator/imp/IMrr.h | 23 ++++++ .../unit_test/hikyuu/indicator/test_MA.cpp | 7 +- .../unit_test/hikyuu/indicator/test_MDD.cpp | 79 ++++++++++++++++++ .../unit_test/hikyuu/indicator/test_MRR.cpp | 80 +++++++++++++++++++ hikyuu_pywrap/indicator/_build_in.cpp | 16 ++++ 12 files changed, 402 insertions(+), 2 deletions(-) create mode 100644 hikyuu_cpp/hikyuu/indicator/crt/MDD.h create mode 100644 hikyuu_cpp/hikyuu/indicator/crt/MRR.h create mode 100644 hikyuu_cpp/hikyuu/indicator/imp/IMdd.cpp create mode 100644 hikyuu_cpp/hikyuu/indicator/imp/IMdd.h create mode 100644 hikyuu_cpp/hikyuu/indicator/imp/IMrr.cpp create mode 100644 hikyuu_cpp/hikyuu/indicator/imp/IMrr.h create mode 100644 hikyuu_cpp/unit_test/hikyuu/indicator/test_MDD.cpp create mode 100644 hikyuu_cpp/unit_test/hikyuu/indicator/test_MRR.cpp diff --git a/docs/source/indicator/indicator.rst b/docs/source/indicator/indicator.rst index 21b67d79..fd70abd1 100644 --- a/docs/source/indicator/indicator.rst +++ b/docs/source/indicator/indicator.rst @@ -612,6 +612,11 @@ :rtype: Indicator +.. py:function:: MDD([ind]) + + 当前价格相对历史最高值的回撤百分比,通常用于计算最大回撤 + + .. py:function:: MIN(ind1, ind2) 求最小值, MIN(A,B)返回A和B中的较小值。 @@ -650,6 +655,11 @@ :rtype: Indicator +.. py:function:: MRR([ind]) + + 当前价格相对历史最低值的盈利百分比 + + .. py:function:: NDAY(x, y[, n=3]) 连大, NDAY(X,Y,N)表示条件X>Y持续存在N个周期 diff --git a/hikyuu_cpp/hikyuu/indicator/build_in.h b/hikyuu_cpp/hikyuu/indicator/build_in.h index d28ca6c4..ff27a505 100644 --- a/hikyuu_cpp/hikyuu/indicator/build_in.h +++ b/hikyuu_cpp/hikyuu/indicator/build_in.h @@ -58,8 +58,10 @@ #include "crt/MA.h" #include "crt/MACD.h" #include "crt/MAX.h" +#include "crt/MDD.h" #include "crt/MIN.h" #include "crt/MOD.h" +#include "crt/MRR.h" #include "crt/NDAY.h" #include "crt/NOT.h" #include "crt/POS.h" diff --git a/hikyuu_cpp/hikyuu/indicator/crt/MDD.h b/hikyuu_cpp/hikyuu/indicator/crt/MDD.h new file mode 100644 index 00000000..5cfc74d6 --- /dev/null +++ b/hikyuu_cpp/hikyuu/indicator/crt/MDD.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2023 hikyuu.org + * + * Created on: 2023-12-24 + * Author: fasiondog + */ + +#pragma once + +#include "../Indicator.h" + +namespace hku { + +/** + * 当前价格相对历史最高值的回撤百分比,通常用于计算最大回撤 + * @ingroup Indicator + */ +Indicator HKU_API MDD(); + +/** + * 当前价格相对历史最高值的回撤百分比 + * @param ind 待计算的数据 + * @ingroup Indicator + */ +inline Indicator MDD(const Indicator& ind) { + return MDD()(ind); +} + +} // namespace hku \ No newline at end of file diff --git a/hikyuu_cpp/hikyuu/indicator/crt/MRR.h b/hikyuu_cpp/hikyuu/indicator/crt/MRR.h new file mode 100644 index 00000000..c130c25f --- /dev/null +++ b/hikyuu_cpp/hikyuu/indicator/crt/MRR.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2023 hikyuu.org + * + * Created on: 2023-12-24 + * Author: fasiondog + */ + +#pragma once + +#include "../Indicator.h" + +namespace hku { + +/** + * 当前价格相对历史最低值的盈利百分比 + * @ingroup Indicator + */ +Indicator HKU_API MRR(); + +/** + * 当前价格相对历史最低值的盈利百分比 + * @param ind 待计算的数据 + * @ingroup Indicator + */ +inline Indicator MRR(const Indicator& ind) { + return MRR()(ind); +} + +} // namespace hku \ No newline at end of file diff --git a/hikyuu_cpp/hikyuu/indicator/imp/IMdd.cpp b/hikyuu_cpp/hikyuu/indicator/imp/IMdd.cpp new file mode 100644 index 00000000..7659e6a9 --- /dev/null +++ b/hikyuu_cpp/hikyuu/indicator/imp/IMdd.cpp @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2023 hikyuu.org + * + * Created on: 2023-12-24 + * Author: fasiondog + */ + +#include "IMdd.h" + +#if HKU_SUPPORT_SERIALIZATION +BOOST_CLASS_EXPORT(hku::IMdd) +#endif + +namespace hku { + +IMdd::IMdd() : IndicatorImp("MDD", 1) {} + +IMdd::~IMdd() {} + +bool IMdd::check() { + return true; +} + +void IMdd::_calculate(const Indicator& ind) { + m_discard = 0; + for (size_t i = 0, len = ind.discard(); i < len; i++) { + _set(0.0, i); + } + + size_t total = ind.size(); + if (ind.discard() < total) { + _set(0., ind.discard()); + } + + price_t pre_max = ind[ind.discard()]; + for (size_t i = ind.discard() + 1; i < total; i++) { + if (ind[i] >= pre_max || pre_max == 0.) { + _set(0., i); + } else { + _set((ind[i] / pre_max - 1.0) * 100., i); + } + if (ind[i] > pre_max) { + pre_max = ind[i]; + } + } +} + +Indicator HKU_API MDD() { + IndicatorImpPtr p = make_shared(); + return Indicator(p); +} + +} // namespace hku \ No newline at end of file diff --git a/hikyuu_cpp/hikyuu/indicator/imp/IMdd.h b/hikyuu_cpp/hikyuu/indicator/imp/IMdd.h new file mode 100644 index 00000000..2441d0b7 --- /dev/null +++ b/hikyuu_cpp/hikyuu/indicator/imp/IMdd.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2023 hikyuu.org + * + * Created on: 2023-12-24 + * Author: fasiondog + */ + +#pragma once + +#include "../Indicator.h" + +namespace hku { + +class IMdd : public IndicatorImp { + INDICATOR_IMP(IMdd) + INDICATOR_IMP_NO_PRIVATE_MEMBER_SERIALIZATION + +public: + IMdd(); + virtual ~IMdd(); +}; + +} // namespace hku \ No newline at end of file diff --git a/hikyuu_cpp/hikyuu/indicator/imp/IMrr.cpp b/hikyuu_cpp/hikyuu/indicator/imp/IMrr.cpp new file mode 100644 index 00000000..b0c4e138 --- /dev/null +++ b/hikyuu_cpp/hikyuu/indicator/imp/IMrr.cpp @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2023 hikyuu.org + * + * Created on: 2023-12-24 + * Author: fasiondog + */ + +#include "IMrr.h" + +#if HKU_SUPPORT_SERIALIZATION +BOOST_CLASS_EXPORT(hku::IMrr) +#endif + +namespace hku { + +IMrr::IMrr() : IndicatorImp("MRR", 1) {} + +IMrr::~IMrr() {} + +bool IMrr::check() { + return true; +} + +void IMrr::_calculate(const Indicator& ind) { + m_discard = 0; + for (size_t i = 0, len = ind.discard(); i < len; i++) { + _set(0.0, i); + } + + size_t total = ind.size(); + if (ind.discard() < total) { + _set(0., ind.discard()); + } + + price_t pre_min = ind[ind.discard()]; + for (size_t i = ind.discard() + 1; i < total; i++) { + if (ind[i] <= pre_min || pre_min == 0.) { + _set(0., i); + } else { + _set((ind[i] / pre_min - 1.0) * 100., i); + } + if (ind[i] < pre_min) { + pre_min = ind[i]; + } + } +} + +Indicator HKU_API MRR() { + IndicatorImpPtr p = make_shared(); + return Indicator(p); +} + +} // namespace hku \ No newline at end of file diff --git a/hikyuu_cpp/hikyuu/indicator/imp/IMrr.h b/hikyuu_cpp/hikyuu/indicator/imp/IMrr.h new file mode 100644 index 00000000..a55b6499 --- /dev/null +++ b/hikyuu_cpp/hikyuu/indicator/imp/IMrr.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2023 hikyuu.org + * + * Created on: 2023-12-24 + * Author: fasiondog + */ + +#pragma once + +#include "../Indicator.h" + +namespace hku { + +class IMrr : public IndicatorImp { + INDICATOR_IMP(IMrr) + INDICATOR_IMP_NO_PRIVATE_MEMBER_SERIALIZATION + +public: + IMrr(); + virtual ~IMrr(); +}; + +} // namespace hku \ No newline at end of file diff --git a/hikyuu_cpp/unit_test/hikyuu/indicator/test_MA.cpp b/hikyuu_cpp/unit_test/hikyuu/indicator/test_MA.cpp index 6f7ec75d..8633c938 100644 --- a/hikyuu_cpp/unit_test/hikyuu/indicator/test_MA.cpp +++ b/hikyuu_cpp/unit_test/hikyuu/indicator/test_MA.cpp @@ -41,9 +41,12 @@ TEST_CASE("test_MA") { CHECK_EQ(ma.empty(), false); CHECK_EQ(ma.size(), kdata.size()); CHECK_EQ(ma.discard(), 0); + std::vector expects{ + 2415.197, 2397.1715, 2395.89, 2392.8908, 2394.1114, + 2396.1477, 2395.6244, 2393.0338, 2389.7090, 2383.4041, + }; for (size_t i = 0; i < kdata.size(); ++i) { - HKU_INFO("i: {}, ma: {}, open: {}", i, ma[i], open[i]); - // CHECK_UNARY(std::isnan(ma[i])); + CHECK_EQ(ma[i], doctest::Approx(expects[i]).epsilon(0.0001)); } /** @arg n = 10 且数据大小刚好为10 时, 正常关联数据 */ diff --git a/hikyuu_cpp/unit_test/hikyuu/indicator/test_MDD.cpp b/hikyuu_cpp/unit_test/hikyuu/indicator/test_MDD.cpp new file mode 100644 index 00000000..21d3d14a --- /dev/null +++ b/hikyuu_cpp/unit_test/hikyuu/indicator/test_MDD.cpp @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2023 hikyuu.org + * + * Created on: 2023-12-24 + * Author: fasiondog + */ +#include "doctest/doctest.h" +#include +#include +#include + +using namespace hku; + +/** + * @defgroup test_indicator_MDD test_indicator_MDD + * @ingroup test_hikyuu_indicator_suite + * @{ + */ + +/** @par 检测点 */ +TEST_CASE("test_MDD") { + StockManager& sm = StockManager::instance(); + Stock stock = sm.getStock("sh000001"); + KData kdata; + Indicator open, ma; + + /** @arg n = 10 且数据大小刚好为10 时, 正常关联数据 */ + kdata = stock.getKData(KQuery(-10)); + auto c = kdata.close(); + auto m = MDD(c); + CHECK_EQ(m.name(), "MDD"); + CHECK_EQ(m.empty(), false); + CHECK_EQ(m.size(), kdata.size()); + CHECK_EQ(m.discard(), 0); + std::vector expects{0., 0., -0.72282, -0.60562, 0., + -3.27389, -1.0584, -2.1443, -3.2816, -3.5852}; + for (size_t i = 0, len = m.size(); i < len; i++) { + CHECK_EQ(m[i], doctest::Approx(expects[i]).epsilon(0.0001)); + } +} + +//----------------------------------------------------------------------------- +// test export +//----------------------------------------------------------------------------- +#if HKU_SUPPORT_SERIALIZATION + +/** @par 检测点 */ +TEST_CASE("test_MDD_export") { + StockManager& sm = StockManager::instance(); + string filename(sm.tmpdir()); + filename += "/MDD.xml"; + + Stock stock = sm.getStock("sh000001"); + KData kdata = stock.getKData(KQuery(-20)); + Indicator m1 = MDD(kdata.close()); + { + std::ofstream ofs(filename); + boost::archive::xml_oarchive oa(ofs); + oa << BOOST_SERIALIZATION_NVP(m1); + } + + Indicator m2; + { + std::ifstream ifs(filename); + boost::archive::xml_iarchive ia(ifs); + ia >> BOOST_SERIALIZATION_NVP(m2); + } + + CHECK_EQ(m2.name(), "MDD"); + CHECK_EQ(m1.size(), m2.size()); + CHECK_EQ(m1.discard(), m2.discard()); + CHECK_EQ(m1.getResultNumber(), m2.getResultNumber()); + for (size_t i = 0; i < m1.size(); ++i) { + CHECK_EQ(m1[i], doctest::Approx(m2[i])); + } +} +#endif /* #if HKU_SUPPORT_SERIALIZATION */ + +/** @} */ diff --git a/hikyuu_cpp/unit_test/hikyuu/indicator/test_MRR.cpp b/hikyuu_cpp/unit_test/hikyuu/indicator/test_MRR.cpp new file mode 100644 index 00000000..b8b6c888 --- /dev/null +++ b/hikyuu_cpp/unit_test/hikyuu/indicator/test_MRR.cpp @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2023 hikyuu.org + * + * Created on: 2023-12-24 + * Author: fasiondog + */ + +#include "doctest/doctest.h" +#include +#include +#include + +using namespace hku; + +/** + * @defgroup test_indicator_MRR test_indicator_MRR + * @ingroup test_hikyuu_indicator_suite + * @{ + */ + +/** @par 检测点 */ +TEST_CASE("test_MRR") { + StockManager& sm = StockManager::instance(); + Stock stock = sm.getStock("sh000001"); + KData kdata; + Indicator open, ma; + + /** @arg n = 10 且数据大小刚好为10 时, 正常关联数据 */ + kdata = stock.getKData(KQuery(-10)); + auto c = kdata.close(); + auto m = MRR(c); + CHECK_EQ(m.name(), "MRR"); + CHECK_EQ(m.empty(), false); + CHECK_EQ(m.size(), kdata.size()); + CHECK_EQ(m.discard(), 0); + std::vector expects{0., 0.1039, 0., 0.1181, 1.3515, 0., 2.2905, 1.1678, 0., 0.}; + for (size_t i = 0, len = m.size(); i < len; i++) { + CHECK_EQ(m[i], doctest::Approx(expects[i]).epsilon(0.0001)); + // std::cout << c[i] << " " << m[i] << std::endl; + } +} + +//----------------------------------------------------------------------------- +// test export +//----------------------------------------------------------------------------- +#if HKU_SUPPORT_SERIALIZATION + +/** @par 检测点 */ +TEST_CASE("test_MRR_export") { + StockManager& sm = StockManager::instance(); + string filename(sm.tmpdir()); + filename += "/MRR.xml"; + + Stock stock = sm.getStock("sh000001"); + KData kdata = stock.getKData(KQuery(-20)); + Indicator m1 = MRR(kdata.close()); + { + std::ofstream ofs(filename); + boost::archive::xml_oarchive oa(ofs); + oa << BOOST_SERIALIZATION_NVP(m1); + } + + Indicator m2; + { + std::ifstream ifs(filename); + boost::archive::xml_iarchive ia(ifs); + ia >> BOOST_SERIALIZATION_NVP(m2); + } + + CHECK_EQ(m2.name(), "MRR"); + CHECK_EQ(m1.size(), m2.size()); + CHECK_EQ(m1.discard(), m2.discard()); + CHECK_EQ(m1.getResultNumber(), m2.getResultNumber()); + for (size_t i = 0; i < m1.size(); ++i) { + CHECK_EQ(m1[i], doctest::Approx(m2[i])); + } +} +#endif /* #if HKU_SUPPORT_SERIALIZATION */ + +/** @} */ diff --git a/hikyuu_pywrap/indicator/_build_in.cpp b/hikyuu_pywrap/indicator/_build_in.cpp index 5300578d..338f3312 100644 --- a/hikyuu_pywrap/indicator/_build_in.cpp +++ b/hikyuu_pywrap/indicator/_build_in.cpp @@ -459,6 +459,12 @@ Indicator (*SLOPE3)(const Indicator&, int) = SLOPE; Indicator (*SLOPE4)(const Indicator&, const IndParam&) = SLOPE; Indicator (*SLOPE5)(const Indicator&, const Indicator&) = SLOPE; +Indicator (*MDD_1)() = MDD; +Indicator (*MDD_2)(const Indicator&) = MDD; + +Indicator (*MRR_1)() = MRR; +Indicator (*MRR_2)(const Indicator&) = MRR; + void export_Indicator_build_in() { def("KDATA", KDATA1); def("KDATA", KDATA3, R"(KDATA([data]) @@ -1597,4 +1603,14 @@ void export_Indicator_build_in() { :param Indicator data: 输入数据 :param int|Indicator|IndParam n: 时间窗口 :rtype: Indicator)"); + + def("MDD", MDD_1); + def("MDD", MDD_2, R"(MDD([data]) + + 当前价格相对历史最高值的回撤百分比,通常用于计算最大回撤)"); + + def("MRR", MRR_1); + def("MRR", MRR_2, R"(MRR([data]) + + 当前价格相对历史最低值的盈利百分比,可用于计算历史最高盈利比例)"); }