mirror of
https://gitee.com/fasiondog/hikyuu.git
synced 2024-12-03 20:37:50 +08:00
Merge pull request #141 from fasiondog/feature/tools
Feature/tools add MDD/MRR indicator, Performance 增加单笔最大盈利/亏损比例统计
This commit is contained in:
commit
9da3d5b21c
@ -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个周期
|
||||
|
@ -116,6 +116,7 @@ class UsePytdxImportToH5Thread(QThread):
|
||||
return
|
||||
|
||||
if task_count == 0:
|
||||
self.send_message(['INFO', '未选择需要导入的行情数据!'])
|
||||
return
|
||||
|
||||
use_tdx_number = min(
|
||||
|
@ -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"
|
||||
|
29
hikyuu_cpp/hikyuu/indicator/crt/MDD.h
Normal file
29
hikyuu_cpp/hikyuu/indicator/crt/MDD.h
Normal file
@ -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
|
29
hikyuu_cpp/hikyuu/indicator/crt/MRR.h
Normal file
29
hikyuu_cpp/hikyuu/indicator/crt/MRR.h
Normal file
@ -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
|
53
hikyuu_cpp/hikyuu/indicator/imp/IMdd.cpp
Normal file
53
hikyuu_cpp/hikyuu/indicator/imp/IMdd.cpp
Normal file
@ -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<IMdd>();
|
||||
return Indicator(p);
|
||||
}
|
||||
|
||||
} // namespace hku
|
23
hikyuu_cpp/hikyuu/indicator/imp/IMdd.h
Normal file
23
hikyuu_cpp/hikyuu/indicator/imp/IMdd.h
Normal file
@ -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
|
53
hikyuu_cpp/hikyuu/indicator/imp/IMrr.cpp
Normal file
53
hikyuu_cpp/hikyuu/indicator/imp/IMrr.cpp
Normal file
@ -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<IMrr>();
|
||||
return Indicator(p);
|
||||
}
|
||||
|
||||
} // namespace hku
|
23
hikyuu_cpp/hikyuu/indicator/imp/IMrr.h
Normal file
23
hikyuu_cpp/hikyuu/indicator/imp/IMrr.h
Normal file
@ -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
|
@ -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["亏损交易最大持仓时间"]) {
|
||||
|
@ -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<price_t> 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 时, 正常关联数据 */
|
||||
|
79
hikyuu_cpp/unit_test/hikyuu/indicator/test_MDD.cpp
Normal file
79
hikyuu_cpp/unit_test/hikyuu/indicator/test_MDD.cpp
Normal file
@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright (c) 2023 hikyuu.org
|
||||
*
|
||||
* Created on: 2023-12-24
|
||||
* Author: fasiondog
|
||||
*/
|
||||
#include "doctest/doctest.h"
|
||||
#include <fstream>
|
||||
#include <hikyuu/StockManager.h>
|
||||
#include <hikyuu/indicator/crt/MDD.h>
|
||||
|
||||
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<price_t> 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 */
|
||||
|
||||
/** @} */
|
80
hikyuu_cpp/unit_test/hikyuu/indicator/test_MRR.cpp
Normal file
80
hikyuu_cpp/unit_test/hikyuu/indicator/test_MRR.cpp
Normal file
@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright (c) 2023 hikyuu.org
|
||||
*
|
||||
* Created on: 2023-12-24
|
||||
* Author: fasiondog
|
||||
*/
|
||||
|
||||
#include "doctest/doctest.h"
|
||||
#include <fstream>
|
||||
#include <hikyuu/StockManager.h>
|
||||
#include <hikyuu/indicator/crt/MRR.h>
|
||||
|
||||
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<price_t> 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 */
|
||||
|
||||
/** @} */
|
@ -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])
|
||||
|
||||
当前价格相对历史最低值的盈利百分比,可用于计算历史最高盈利比例)");
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user