diff --git a/hikyuu_cpp/hikyuu/analysis/optimize.cpp b/hikyuu_cpp/hikyuu/analysis/optimize.cpp new file mode 100644 index 00000000..fc5aee12 --- /dev/null +++ b/hikyuu_cpp/hikyuu/analysis/optimize.cpp @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2019~2023, hikyuu.org + * + * History: + * 1. 20240907 added by fasiondog + */ + +#include "hikyuu/trade_manage/Performance.h" +#include "optimize.h" + +namespace hku { + +static std::pair runSysWithOptimizeParam( + const SYSPtr& sys, const Stock& stk, const KQuery& query, const Datetime& lastDate, + const vector>& params, const std::string& out_key) { + for (const auto& combinate_param : params) { + if (SystemPart::PART_ENVIRONMENT == combinate_param.first) { + auto ev = sys->getEV(); + if (ev) { + for (auto iter = combinate_param.second.begin(); + iter != combinate_param.second.end(); ++iter) { + ev->setParam(iter->first, iter->second); + } + } + } else if (SystemPart::PART_CONDITION == combinate_param.first) { + auto cn = sys->getCN(); + if (cn) { + for (auto iter = combinate_param.second.begin(); + iter != combinate_param.second.end(); ++iter) { + cn->setParam(iter->first, iter->second); + } + } + } else if (SystemPart::PART_SIGNAL == combinate_param.first) { + auto sg = sys->getSG(); + if (sg) { + for (auto iter = combinate_param.second.begin(); + iter != combinate_param.second.end(); ++iter) { + sg->setParam(iter->first, iter->second); + } + } + } else if (SystemPart::PART_STOPLOSS == combinate_param.first) { + auto st = sys->getST(); + if (st) { + for (auto iter = combinate_param.second.begin(); + iter != combinate_param.second.end(); ++iter) { + st->setParam(iter->first, iter->second); + } + } + } else if (SystemPart::PART_TAKEPROFIT == combinate_param.first) { + auto tp = sys->getTP(); + if (tp) { + for (auto iter = combinate_param.second.begin(); + iter != combinate_param.second.end(); ++iter) { + tp->setParam(iter->first, iter->second); + } + } + } else if (SystemPart::PART_MONEYMANAGER == combinate_param.first) { + auto mm = sys->getMM(); + if (mm) { + for (auto iter = combinate_param.second.begin(); + iter != combinate_param.second.end(); ++iter) { + mm->setParam(iter->first, iter->second); + } + } + } else if (SystemPart::PART_PROFITGOAL == combinate_param.first) { + auto pg = sys->getPG(); + if (pg) { + for (auto iter = combinate_param.second.begin(); + iter != combinate_param.second.end(); ++iter) { + pg->setParam(iter->first, iter->second); + } + } + } else { + HKU_WARN("Not support part: {}", int(combinate_param.first)); + } + } + + sys->run(stk, query); + Performance per; + per.statistics(sys->getTM(), lastDate); + + double val = per.get(out_key); + + return std::make_pair(val, sys); +} + +SYSPtr HKU_API findOptimizeParam(const SYSPtr& sys, const Stock& stk, const KQuery& query, + const OptimizeParamList& optParams, const string& sort_key) { + HKU_ASSERT(sys && sys->getTM()); + HKU_ASSERT(!stk.isNull()); + HKU_ASSERT(!optParams.empty()); + + string statistic_key = sort_key.empty() ? "帐户平均年收益率%" : sort_key; + HKU_CHECK(Performance::exist(statistic_key), + "Invalid sort key: {}! A statistical item does not exist!", statistic_key); + + auto date_list = StockManager::instance().getTradingCalendar(query); + HKU_CHECK(!date_list.empty(), + "Invalid query: {}! The statistical end date could not be determined!", query); + Datetime last_datetime = date_list.back(); + + sys->setNotSharedAll(); + sys->forceResetAll(); + + ThreadPool tg; + vector>> tasks; + vector> sys_param; + Parameter param; + while (true) { + size_t finished_count = 0; + for (const auto& opt_param : optParams) { + if (opt_param->type() == OptimizeParam::OPT_PARAM_INT) { + OptimizeParamInt* p = dynamic_cast(opt_param.get()); + int value = p->getValue(); + if (value != Null()) { + param.set(p->name(), value); + sys_param.emplace_back(std::make_pair(opt_param->part(), std::move(param))); + } else { + finished_count++; + } + } else if (opt_param->type() == OptimizeParam::OPT_PARAM_DOUBLE) { + OptimizeParamDouble* p = dynamic_cast(opt_param.get()); + double value = p->getValue(); + if (value != Null()) { + param.set(p->name(), value); + sys_param.emplace_back(std::make_pair(opt_param->part(), std::move(param))); + } else { + finished_count++; + } + } else if (opt_param->type() == OptimizeParam::OPT_PARAM_BOOL) { + OptimizeParamBool* p = dynamic_cast(opt_param.get()); + uint8_t value = p->getValue(); + if (value >= 0) { + param.set<>(p->name(), static_cast(value)); + sys_param.emplace_back(std::make_pair(opt_param->part(), std::move(param))); + } else { + finished_count++; + } + + } else if (opt_param->type() == OptimizeParam::OPT_PARAM_STRING) { + OptimizeParamString* p = dynamic_cast(opt_param.get()); + string value = p->getValue(); + if (!value.empty()) { + param.set(p->name(), value); + sys_param.emplace_back(std::make_pair(opt_param->part(), std::move(param))); + } else { + finished_count++; + } + } else { + finished_count++; + HKU_WARN("Ignore unknown opt param type: {}", int(opt_param->type())); + continue; + } + } + + if (!sys_param.empty()) { + tasks.emplace_back( + tg.submit([stk, query, &statistic_key, last_datetime, new_sys = sys->clone(), + new_params = std::move(sys_param)]() { + try { + return runSysWithOptimizeParam(new_sys, stk, query, last_datetime, new_params, + statistic_key); + } catch (const std::exception& e) { + HKU_ERROR(e.what()); + return std::make_pair(std::numeric_limits::min(), new_sys); + } catch (...) { + HKU_ERROR_UNKNOWN; + return std::make_pair(std::numeric_limits::min(), new_sys); + } + })); + } + + if (finished_count >= optParams.size()) { + break; + } + } + + vector> all_result; + for (auto& task : tasks) { + all_result.emplace_back(task.get()); + } + + SYSPtr ret; + if (!all_result.empty()) { + std::sort(all_result.begin(), all_result.end(), + [](const std::pair& a, const std::pair& b) { + return a.first > b.first; + }); + ret = all_result.front().second; + } + + return ret; +} + +} // namespace hku \ No newline at end of file diff --git a/hikyuu_cpp/hikyuu/analysis/optimize.h b/hikyuu_cpp/hikyuu/analysis/optimize.h new file mode 100644 index 00000000..e32db82e --- /dev/null +++ b/hikyuu_cpp/hikyuu/analysis/optimize.h @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2019~2023, hikyuu.org + * + * History: + * 1. 20240907 added by fasiondog + */ + +#pragma once + +#include "hikyuu/trade_sys/system/System.h" + +namespace hku { + +class HKU_API OptimizeParam { +public: + enum OPTIMIZE_PARAM_TYPE { + OPT_PARAM_INT, + OPT_PARAM_DOUBLE, + OPT_PARAM_BOOL, + OPT_PARAM_STRING, + OPT_PARAM_INVLID, + }; + + OptimizeParam() = default; + OptimizeParam(SystemPart part, const string& name_, OPTIMIZE_PARAM_TYPE type_) + : m_name(name_), m_part(part), m_type(type_) {} + virtual ~OptimizeParam() = default; + + SystemPart part() const noexcept { + return m_part; + } + + const string& name() const noexcept { + return m_name; + } + + OPTIMIZE_PARAM_TYPE type() const noexcept { + return m_type; + } + + virtual string str() const { + return fmt::format("OptimizeParam({}, {})", getSystemPartName(m_part), m_name); + } + +protected: + string m_name; + SystemPart m_part; + OPTIMIZE_PARAM_TYPE m_type{OPT_PARAM_INVLID}; +}; + +typedef std::shared_ptr OptimizeParamPtr; +typedef std::vector OptimizeParamList; + +class HKU_API OptimizeParamInt : public OptimizeParam { +public: + OptimizeParamInt() = default; + OptimizeParamInt(SystemPart part, const string& name, int start_, int end_, int step_ = 1) + : OptimizeParam(part, name, OPT_PARAM_INT), + m_start(start_), + m_end(end_), + m_step(step_), + m_current(start_) {} + virtual ~OptimizeParamInt() = default; + + int getValue() { + HKU_IF_RETURN(m_current >= m_end, Null()); + int ret = m_current; + m_current += m_step; + return ret; + } + + virtual string str() const override { + return fmt::format("OptimizeParamInt({}, {}, {}, {}, {})", getSystemPartName(m_part), + m_name, m_start, m_end, m_step); + } + +private: + int m_start{0}; + int m_end{0}; + int m_step{0}; + int m_current{0}; +}; + +class HKU_API OptimizeParamDouble : public OptimizeParam { +public: + OptimizeParamDouble() = default; + OptimizeParamDouble(SystemPart part, const string& name, double start_, double end_, + double step_) + : OptimizeParam(part, name, OPT_PARAM_DOUBLE), + m_start(start_), + m_end(end_), + m_step(step_), + m_current(start_) {} + virtual ~OptimizeParamDouble() = default; + + double getValue() { + HKU_IF_RETURN(m_current >= m_step, Null()); + double ret = m_current; + m_current += m_step; + return ret; + } + + virtual string str() const override { + return fmt::format("OptimizeParamDouble({}, {}, {}, {}, {})", getSystemPartName(m_part), + m_name, m_start, m_end, m_step); + } + +private: + double m_start{0.0}; + double m_end{0.0}; + double m_step{0.0}; + double m_current{0.0}; +}; + +class HKU_API OptimizeParamBool : public OptimizeParam { +public: + OptimizeParamBool() = default; + OptimizeParamBool(SystemPart part, const string& name) + : OptimizeParam(part, name, OPT_PARAM_BOOL) {} + virtual ~OptimizeParamBool() = default; + + uint8_t getValue() { + HKU_IF_RETURN(m_current >= 2, -1); + uint8_t ret = m_current; + m_current++; + return ret; + } + + virtual string str() const override { + return fmt::format("OptimizeParamBool({}, {})", getSystemPartName(m_part), m_name); + } + +private: + uint8_t m_current{0}; +}; + +class HKU_API OptimizeParamString : public OptimizeParam { +public: + OptimizeParamString() = default; + OptimizeParamString(SystemPart part, const string& name, const StringList& values_) + : OptimizeParam(part, name, OPT_PARAM_STRING), m_values(values_) {} + virtual ~OptimizeParamString() = default; + + string getValue() { + HKU_IF_RETURN(m_current >= m_values.size(), Null()); + string ret = m_values[m_current]; + m_current++; + return ret; + } + + virtual string str() const override { + return fmt::format("OptimizeParamString({}, {}, values=...)", getSystemPartName(m_part), + m_name); + } + +private: + StringList m_values; + size_t m_current; +}; + +SYSPtr HKU_API findOptimizeParam(const SYSPtr& sys, const Stock& stk, const KQuery& query, + const OptimizeParamList& optParams, + const string& sort_key = string()); + +} // namespace hku \ No newline at end of file diff --git a/hikyuu_cpp/unit_test/doc.h b/hikyuu_cpp/unit_test/doc.h index 0c50fc71..a9876eec 100644 --- a/hikyuu_cpp/unit_test/doc.h +++ b/hikyuu_cpp/unit_test/doc.h @@ -17,7 +17,8 @@ * 测试用例文档,用于组织和描述测试用例。 \n * * @section test_rule 测试工程的组织原则 - * 组织原则1:测试工程与源代码工程在物理上隔离,使用完全独立的并行目录,但内部目录结构应于源代码工程保持一致。 \n + * 组织原则1:测试工程与源代码工程在物理上隔离,使用完全独立的并行目录,但内部目录结构应于源代码工程保持一致。 + * \n * 组织原则2:针对一个模块(通常为一个类),应建立一个测试套件(test_suite),其命名规则为:test_模块名(类名)_suite, * 例如,针对类IniParser,建立一个测试套件:test_iniparser_suite。建议全部使用小写字母。 \n * 组织原则3:针对每一测试套件,使用一个单独的测试文件,文件命名规则:test_模块名(类名).cpp。 \n @@ -37,6 +38,7 @@ * @defgroup test_hikyuu_indicator_suite test_hikyuu_indicator_suite * @defgroup test_hikyuu_trade_manage_suite test_hikyuu_trade_manage_suite * @defgroup test_hikyuu_trade_sys_suite test_hikyuu_trade_sys_suite + * @defgroup test_hikyuu_analysis_suite test_hikyuu_analysis_suite * * */ diff --git a/hikyuu_cpp/unit_test/hikyuu/analysis/test_combinate.cpp b/hikyuu_cpp/unit_test/hikyuu/analysis/test_combinate.cpp index aa757b7f..4ae9485c 100644 --- a/hikyuu_cpp/unit_test/hikyuu/analysis/test_combinate.cpp +++ b/hikyuu_cpp/unit_test/hikyuu/analysis/test_combinate.cpp @@ -13,7 +13,7 @@ using namespace hku; /** * @defgroup test_hikyuu_combinate test_hikyuu_combinate - * @ingroup test_hikyuu_combinate_suite + * @ingroup test_hikyuu_analysis_suite * @{ */ diff --git a/hikyuu_cpp/unit_test/hikyuu/analysis/test_optimize.cpp b/hikyuu_cpp/unit_test/hikyuu/analysis/test_optimize.cpp new file mode 100644 index 00000000..b82404bf --- /dev/null +++ b/hikyuu_cpp/unit_test/hikyuu/analysis/test_optimize.cpp @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2019~2023, hikyuu.org + * + * History: + * 1. 20240907 added by fasiondog + */ + +#include "../test_config.h" + +#include + +using namespace hku; + +/** + * @defgroup test_hikyuu_optimize test_hikyuu_optimize + * @ingroup test_hikyuu_analysis_suite + * @{ + */ + +/** @par 检测点 */ +TEST_CASE("test_findOptimizeParam") {} + +/** @} */ \ No newline at end of file diff --git a/hikyuu_pywrap/_analysis.cpp b/hikyuu_pywrap/analysis/_analysis.cpp similarity index 99% rename from hikyuu_pywrap/_analysis.cpp rename to hikyuu_pywrap/analysis/_analysis.cpp index 28a6e71e..b9bc0023 100644 --- a/hikyuu_pywrap/_analysis.cpp +++ b/hikyuu_pywrap/analysis/_analysis.cpp @@ -7,7 +7,7 @@ #include #include -#include "pybind_utils.h" +#include "../pybind_utils.h" using namespace hku; namespace py = pybind11; diff --git a/hikyuu_pywrap/analysis/_optimize.cpp b/hikyuu_pywrap/analysis/_optimize.cpp new file mode 100644 index 00000000..c0c12144 --- /dev/null +++ b/hikyuu_pywrap/analysis/_optimize.cpp @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2019~2023, hikyuu.org + * + * History: + * 1. 20240907 added by fasiondog + */ + +#include +#include "../pybind_utils.h" + +using namespace hku; +namespace py = pybind11; + +class PyOptimizeParamInt { +public: + PyOptimizeParamInt(SystemPart part, const string& name, int start, int end, int step) { + m_param = std::make_shared(part, name, start, end, step); + } + + OptimizeParamPtr get() { + return m_param; + } + + string str() const { + return m_param->str(); + } + +private: + OptimizeParamPtr m_param; +}; + +class PyOptimizeParamDouble { +public: + PyOptimizeParamDouble(SystemPart part, const string& name, double start, double end, + double step) { + m_param = std::make_shared(part, name, start, end, step); + } + + OptimizeParamPtr get() { + return m_param; + } + + string str() const { + return m_param->str(); + } + +private: + OptimizeParamPtr m_param; +}; + +class PyOptimizeParamBool { +public: + PyOptimizeParamBool(SystemPart part, const string& name) { + m_param = std::make_shared(part, name); + } + + OptimizeParamPtr get() { + return m_param; + } + + string str() const { + return m_param->str(); + } + +private: + OptimizeParamPtr m_param; +}; + +class PyOptimizeParamString { +public: + PyOptimizeParamString(SystemPart part, const string& name, const StringList& values) { + m_param = std::make_shared(part, name, values); + } + + OptimizeParamPtr get() { + return m_param; + } + + string str() const { + return m_param->str(); + } + +private: + OptimizeParamPtr m_param; +}; + +void export_optimize(py::module& m) { + py::class_(m, "OptimizeParamInt") + .def(py::init(), py::arg("part"), py::arg("name"), + py::arg("start"), py::arg("end"), py::arg("step") = 1) + .def("__str__", &PyOptimizeParamInt::str) + .def("__call__", &PyOptimizeParamInt::get); + + py::class_(m, "OptimizeParamDouble") + .def(py::init(), py::arg("part"), + py::arg("name"), py::arg("start"), py::arg("end"), py::arg("step")) + .def("__str__", &PyOptimizeParamDouble::str) + .def("__call__", &PyOptimizeParamDouble::get); + + py::class_(m, "OptimizeParamBool") + .def(py::init(), py::arg("part"), py::arg("name")) + .def("__str__", &PyOptimizeParamBool::str) + .def("__call__", &PyOptimizeParamBool::get); + + py::class_(m, "OptimizeParamString") + .def(py::init(), py::arg("part"), + py::arg("name"), py::arg("values")) + .def("__str__", &PyOptimizeParamString::str) + .def("__call__", &PyOptimizeParamString::get); + + m.def("find_optimize_param", [](const SYSPtr& sys, const Stock& stk, const KQuery& query, + const py::sequence& opt_params, const string& sort_key) { + OptimizeParamList c_opt_params; + auto total = len(opt_params); + for (auto i = 0; i < total; ++i) { + const auto& obj = opt_params[i]; + if (py::isinstance(obj)) { + c_opt_params.push_back(obj.cast().get()); + } else if (py::isinstance(obj)) { + c_opt_params.push_back(obj.cast().get()); + } else if (py::isinstance(obj)) { + c_opt_params.push_back(obj.cast().get()); + } else if (py::isinstance(obj)) { + c_opt_params.push_back(obj.cast().get()); + } else { + HKU_THROW("Not OptimizeParam, index: {}", i); + } + } + return findOptimizeParam(sys, stk, query, c_opt_params, sort_key); + }); +} \ No newline at end of file diff --git a/hikyuu_pywrap/analysis/analysis_main.cpp b/hikyuu_pywrap/analysis/analysis_main.cpp new file mode 100644 index 00000000..2db48ea0 --- /dev/null +++ b/hikyuu_pywrap/analysis/analysis_main.cpp @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2019~2023, hikyuu.org + * + * History: + * 1. 20240907 added by fasiondog + */ + +#include + +namespace py = pybind11; + +void export_analysis(py::module& m); +void export_optimize(py::module& m); + +void export_analysis_main(py::module& m) { + export_analysis(m); + export_optimize(m); +} diff --git a/hikyuu_pywrap/main.cpp b/hikyuu_pywrap/main.cpp index 7b311e94..ceb9488f 100644 --- a/hikyuu_pywrap/main.cpp +++ b/hikyuu_pywrap/main.cpp @@ -42,7 +42,7 @@ void export_SystemPart(py::module& m); void export_trade_manage_main(py::module& m); void export_trade_sys_main(py::module& m); void export_global_main(py::module& m); -void export_analysis(py::module& m); +void export_analysis_main(py::module& m); void export_StrategeContext(py::module& m); void export_strategy_main(py::module& m); @@ -99,7 +99,7 @@ PYBIND11_MODULE(core, m) { export_trade_manage_main(m); export_trade_sys_main(m); // must after export_trade_manage_main - export_analysis(m); + export_analysis_main(m); export_strategy_main(m); export_global_main(m);