Merge pull request #141 from fasiondog/feature/tools

Feature/tools add MDD/MRR indicator, Performance 增加单笔最大盈利/亏损比例统计
This commit is contained in:
fasiondog 2023-12-25 01:25:03 +08:00 committed by GitHub
commit 9da3d5b21c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 415 additions and 2 deletions

View File

@ -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个周期

View File

@ -116,6 +116,7 @@ class UsePytdxImportToH5Thread(QThread):
return
if task_count == 0:
self.send_message(['INFO', ''])
return
use_tdx_number = min(

View File

@ -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"

View 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

View 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

View 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

View 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

View 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

View 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

View File

@ -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["亏损交易最大持仓时间"]) {

View File

@ -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 时, 正常关联数据 */

View 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 */
/** @} */

View 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 */
/** @} */

View File

@ -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])
)");
}