mirror of
https://gitee.com/fasiondog/hikyuu.git
synced 2024-12-04 12:57:45 +08:00
commit
b933865567
@ -448,23 +448,26 @@
|
||||
:param KData kdata: k线数据
|
||||
:rtype: Indicator
|
||||
|
||||
.. py:function:: IC(ind, stks, query, n, ref_stk)
|
||||
.. py:function:: IC(ind, stks, query, ref_stk[, n=1])
|
||||
|
||||
计算指定的因子相对于参考证券的 IC (实际为 RankIC)
|
||||
|
||||
:param sequence(stock)|Block stks 证券组合
|
||||
:param sequence | Block stks 证券组合
|
||||
:param Query query: 查询条件
|
||||
:param int n: 时间窗口
|
||||
:param Stock ref_stk: 参照证券,通常使用 sh000300 沪深300
|
||||
:param int n: 时间窗口(对应的 n 日收益率)
|
||||
:rtype: Indicator
|
||||
|
||||
|
||||
.. py:function:: ICIR(ic[,n])
|
||||
.. py:function:: ICIR(ind, stks, query, ref_stk[, n=1, rolling_n=120])
|
||||
|
||||
计算 IC 因子 IR = IC的多周期均值/IC的标准方差
|
||||
|
||||
:param Indicator: ic 已经计算出的 ic 值
|
||||
:param int n: 时间窗口
|
||||
:param sequence | Block stks 证券组合
|
||||
:param Query query: 查询条件
|
||||
:param Stock ref_stk: 参照证券,通常使用 sh000300 沪深300
|
||||
:param int n: 时间窗口(对应的 n 日收益率)
|
||||
:param int rolling_n: 滚动周期
|
||||
:rtype: Indicator
|
||||
|
||||
|
||||
|
@ -149,7 +149,7 @@ def se_add_stock_list(self, stk_list, proto_sys):
|
||||
SelectorBase.add_stock_list = se_add_stock_list
|
||||
|
||||
|
||||
def crtSE(calculate, get_selected_on_close, get_selected_on_open, is_match_af=None, params={}, name='crtSE', clone=None):
|
||||
def crtSE(calculate, get_selected, is_match_af=None, params={}, name='crtSE', clone=None):
|
||||
"""
|
||||
快速创建交易对象选择算法
|
||||
|
||||
@ -163,8 +163,7 @@ def crtSE(calculate, get_selected_on_close, get_selected_on_open, is_match_af=No
|
||||
"""
|
||||
meta_x = type(name, (SelectorBase, ), {'__init__': part_init})
|
||||
meta_x._calculate = calculate
|
||||
meta_x.get_selected_on_close = get_selected_on_close
|
||||
meta_x.get_selected_on_open = get_selected_on_open
|
||||
meta_x.get_selected = get_selected
|
||||
meta_x.is_match_af = lambda self, af: True if is_match_af is None else is_match_af
|
||||
meta_x._clone = lambda self: meta_x(self._name, self._params) if clone is None else clone
|
||||
return meta_x(name, params)
|
||||
|
@ -178,7 +178,7 @@ std::string HKU_API getLocalTime();
|
||||
#define HKU_CHECK(expr, ...) \
|
||||
do { \
|
||||
if (!(expr)) { \
|
||||
throw hku::exception(fmt::format("CHECK({}) {} [{}] ({}:{})", #expr, \
|
||||
throw hku::exception(fmt::format("HKU_CHECK({}) {} [{}] ({}:{})", #expr, \
|
||||
fmt::format(__VA_ARGS__), HKU_FUNCTION, __FILE__, \
|
||||
__LINE__)); \
|
||||
} \
|
||||
@ -191,8 +191,8 @@ std::string HKU_API getLocalTime();
|
||||
#define HKU_CHECK_THROW(expr, except, ...) \
|
||||
do { \
|
||||
if (!(expr)) { \
|
||||
throw except(fmt::format("CHECK({}) {} [{}] ({}:{})", #expr, fmt::format(__VA_ARGS__), \
|
||||
HKU_FUNCTION, __FILE__, __LINE__)); \
|
||||
throw except(fmt::format("HKU_CHECK({}) {} [{}] ({}:{})", #expr, \
|
||||
fmt::format(__VA_ARGS__), HKU_FUNCTION, __FILE__, __LINE__)); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
@ -202,19 +202,19 @@ std::string HKU_API getLocalTime();
|
||||
if (!(expr)) { \
|
||||
std::string errmsg = fmt::format(__VA_ARGS__); \
|
||||
errmsg = fmt::format("{}\n {}", errmsg, to_string(boost::stacktrace::stacktrace())); \
|
||||
throw hku::exception(fmt::format("CHECK({}) {} [{}] ({}:{})", #expr, errmsg, \
|
||||
throw hku::exception(fmt::format("HKU_CHECK({}) {} [{}] ({}:{})", #expr, errmsg, \
|
||||
HKU_FUNCTION, __FILE__, __LINE__)); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define HKU_CHECK_THROW(expr, except, ...) \
|
||||
do { \
|
||||
if (!(expr)) { \
|
||||
std::string errmsg = fmt::format(__VA_ARGS__); \
|
||||
errmsg = fmt::format("{}\n {}", errmsg, to_string(boost::stacktrace::stacktrace())); \
|
||||
throw except(fmt::format("CHECK({}) {} [{}] ({}:{})", #expr, errmsg, HKU_FUNCTION, \
|
||||
__FILE__, __LINE__)); \
|
||||
} \
|
||||
#define HKU_CHECK_THROW(expr, except, ...) \
|
||||
do { \
|
||||
if (!(expr)) { \
|
||||
std::string errmsg = fmt::format(__VA_ARGS__); \
|
||||
errmsg = fmt::format("{}\n {}", errmsg, to_string(boost::stacktrace::stacktrace())); \
|
||||
throw except(fmt::format("HKU_CHECK({}) {} [{}] ({}:{})", #expr, errmsg, HKU_FUNCTION, \
|
||||
__FILE__, __LINE__)); \
|
||||
} \
|
||||
} while (0)
|
||||
#endif // #ifndef HKU_ENABLE_STACK_TRACE
|
||||
|
||||
@ -229,35 +229,11 @@ std::string HKU_API getLocalTime();
|
||||
* 若表达式为 false,将抛出 hku::exception 异常
|
||||
* @note 仅用于内部入参检查,编译时可通过 HKU_DISABLE_ASSERT 宏关闭
|
||||
*/
|
||||
#define HKU_ASSERT(expr) \
|
||||
do { \
|
||||
if (!(expr)) { \
|
||||
std::string err_msg( \
|
||||
fmt::format("ASSERT({})\n{}", #expr, to_string(boost::stacktrace::stacktrace()))); \
|
||||
throw hku::exception( \
|
||||
fmt::format("{} [{}] ({}:{})", err_msg, HKU_FUNCTION, __FILE__, __LINE__)); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/**
|
||||
* 若表达式为 false,将抛出 hku::exception 异常, 并附带传入信息
|
||||
* @note 仅用于内部入参检查,编译时可通过 HKU_DISABLE_ASSERT 宏关闭
|
||||
*/
|
||||
#define HKU_ASSERT_M(expr, ...) \
|
||||
do { \
|
||||
if (!(expr)) { \
|
||||
std::string err_msg(fmt::format("ASSERT({}) {}\n{}", #expr, fmt::format(__VA_ARGS__), \
|
||||
to_string(boost::stacktrace::stacktrace()))); \
|
||||
throw hku::exception( \
|
||||
fmt::format("{} [{}] ({}:{})", err_msg, HKU_FUNCTION, __FILE__, __LINE__)); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#else
|
||||
#define HKU_ASSERT(expr) \
|
||||
do { \
|
||||
if (!(expr)) { \
|
||||
std::string err_msg(fmt::format("ASSERT({})", #expr)); \
|
||||
std::string err_msg(fmt::format("HKU_ASSERT({})\n{}", #expr, \
|
||||
to_string(boost::stacktrace::stacktrace()))); \
|
||||
throw hku::exception( \
|
||||
fmt::format("{} [{}] ({}:{})", err_msg, HKU_FUNCTION, __FILE__, __LINE__)); \
|
||||
} \
|
||||
@ -267,13 +243,39 @@ std::string HKU_API getLocalTime();
|
||||
* 若表达式为 false,将抛出 hku::exception 异常, 并附带传入信息
|
||||
* @note 仅用于内部入参检查,编译时可通过 HKU_DISABLE_ASSERT 宏关闭
|
||||
*/
|
||||
#define HKU_ASSERT_M(expr, ...) \
|
||||
do { \
|
||||
if (!(expr)) { \
|
||||
std::string err_msg(fmt::format("ASSERT({}) {}", #expr, fmt::format(__VA_ARGS__))); \
|
||||
throw hku::exception( \
|
||||
fmt::format("{} [{}] ({}:{})", err_msg, HKU_FUNCTION, __FILE__, __LINE__)); \
|
||||
} \
|
||||
#define HKU_ASSERT_M(expr, ...) \
|
||||
do { \
|
||||
if (!(expr)) { \
|
||||
std::string err_msg(fmt::format("HKU_ASSERT({}) {}\n{}", #expr, \
|
||||
fmt::format(__VA_ARGS__), \
|
||||
to_string(boost::stacktrace::stacktrace()))); \
|
||||
throw hku::exception( \
|
||||
fmt::format("{} [{}] ({}:{})", err_msg, HKU_FUNCTION, __FILE__, __LINE__)); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#else
|
||||
#define HKU_ASSERT(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)
|
||||
|
||||
/**
|
||||
* 若表达式为 false,将抛出 hku::exception 异常, 并附带传入信息
|
||||
* @note 仅用于内部入参检查,编译时可通过 HKU_DISABLE_ASSERT 宏关闭
|
||||
*/
|
||||
#define HKU_ASSERT_M(expr, ...) \
|
||||
do { \
|
||||
if (!(expr)) { \
|
||||
std::string err_msg( \
|
||||
fmt::format("HKU_ASSERT({}) {}", #expr, fmt::format(__VA_ARGS__))); \
|
||||
throw hku::exception( \
|
||||
fmt::format("{} [{}] ({}:{})", err_msg, HKU_FUNCTION, __FILE__, __LINE__)); \
|
||||
} \
|
||||
} while (0)
|
||||
#endif // #ifndef HKU_ENABLE_STACK_TRACE
|
||||
#endif /* #if HKU_DISABLE_ASSERT */
|
||||
|
@ -16,25 +16,25 @@ namespace hku {
|
||||
* @brief 计算指定的因子相对于参考证券的 IC (实际为 RankIC)
|
||||
* @param stks 证券组合
|
||||
* @param query 查询条件
|
||||
* @param n 时间窗口
|
||||
* @param ref_stk 参照证券,默认 sh000300 沪深300
|
||||
* @param n 时间窗口 (对应 n 日收益率)
|
||||
* @return Indicator
|
||||
* @ingroup Indicator
|
||||
*/
|
||||
Indicator HKU_API IC(const StockList& stks, const KQuery& query, int n = 1,
|
||||
const Stock& ref_stk = getStock("sh000300"));
|
||||
Indicator HKU_API IC(const StockList& stks, const KQuery& query,
|
||||
const Stock& ref_stk = getStock("sh000300"), int n = 1);
|
||||
|
||||
Indicator HKU_API IC(const Block& blk, const KQuery& query, int n = 1,
|
||||
const Stock& ref_stk = getStock("sh000300"));
|
||||
Indicator HKU_API IC(const Block& blk, const KQuery& query,
|
||||
const Stock& ref_stk = getStock("sh000300"), int n = 1);
|
||||
|
||||
inline Indicator IC(const Indicator& ind, const StockList& stks, const KQuery& query, int n = 1,
|
||||
const Stock& ref_stk = getStock("sh000300")) {
|
||||
return IC(stks, query, n, ref_stk)(ind);
|
||||
inline Indicator IC(const Indicator& ind, const StockList& stks, const KQuery& query,
|
||||
const Stock& ref_stk = getStock("sh000300"), int n = 1) {
|
||||
return IC(stks, query, ref_stk, n)(ind);
|
||||
}
|
||||
|
||||
inline Indicator IC(const Indicator& ind, const Block& blk, const KQuery& query, int n = 1,
|
||||
const Stock& ref_stk = getStock("sh000300")) {
|
||||
return IC(blk, query, n, ref_stk)(ind);
|
||||
inline Indicator IC(const Indicator& ind, const Block& blk, const KQuery& query,
|
||||
const Stock& ref_stk = getStock("sh000300"), int n = 1) {
|
||||
return IC(blk, query, ref_stk, n)(ind);
|
||||
}
|
||||
|
||||
} // namespace hku
|
@ -13,18 +13,34 @@
|
||||
namespace hku {
|
||||
|
||||
/**
|
||||
* @brief 因子 IR
|
||||
* @brief 计算指定的因子相对于参考证券的 ICIR (实际为 RankIC)
|
||||
* @details IR:信息比率(Information Ratio,简称IR)=
|
||||
* IC的多周期均值/IC的标准方差,代表因子获取稳定Alpha的能力。
|
||||
* @param ic 已经计算出的 ic 值
|
||||
* @param n 时间窗口
|
||||
* @param stks 证券组合
|
||||
* @param query 查询条件
|
||||
* @param ref_stk 参照证券,默认 sh000300 沪深300
|
||||
* @param n IC对应的N日收益率
|
||||
* @param rolling_n 滚动时间窗口
|
||||
* @return Indicator
|
||||
* @ingroup Indicator
|
||||
*/
|
||||
inline Indicator ICIR(const Indicator& ic, int n = 120) {
|
||||
Indicator x = MA(ic, n) / STDEV(ic, n);
|
||||
x.name("IR");
|
||||
inline Indicator ICIR(const Indicator& ind, const StockList& stks, const KQuery& query,
|
||||
const Stock& ref_stk = getStock("sh000300"), int n = 1, int rolling_n = 120) {
|
||||
Indicator ic = IC(ind, stks, query, ref_stk, n);
|
||||
Indicator x = MA(ic, rolling_n) / STDEV(ic, rolling_n);
|
||||
x.name("ICIR");
|
||||
x.setParam<int>("n", n);
|
||||
x.setParam<int>("rolling_n", rolling_n);
|
||||
return x;
|
||||
}
|
||||
|
||||
inline Indicator ICIR(const Indicator& ind, const Block& blk, const KQuery& query,
|
||||
const Stock& ref_stk = getStock("sh000300"), int n = 1, int rolling_n = 120) {
|
||||
Indicator ic = IC(ind, blk, query, ref_stk, n);
|
||||
Indicator x = MA(ic, rolling_n) / STDEV(ic, rolling_n);
|
||||
x.name("ICIR");
|
||||
x.setParam<int>("n", n);
|
||||
x.setParam<int>("rolling_n", rolling_n);
|
||||
return x;
|
||||
}
|
||||
|
||||
|
@ -80,14 +80,8 @@ void IIc::_calculate(const Indicator& inputInd) {
|
||||
for (size_t i = 0; i < stk_count; i++) {
|
||||
auto k = m_stks[i].getKData(m_query);
|
||||
all_inds[i] = ALIGN(ind(k), ref_dates, fill_null);
|
||||
if (all_inds[i].discard() > discard) {
|
||||
discard = all_inds[i].discard();
|
||||
}
|
||||
// 计算 n 日收益率,同时需要右移 n 位,即第 i 日的因子值和第 i + n 的收益率对应
|
||||
all_returns[i] = ALIGN(REF(ROCP(k.close(), n), n), ref_dates, fill_null);
|
||||
if (all_returns[i].discard() > discard) {
|
||||
discard = all_returns[i].discard();
|
||||
}
|
||||
}
|
||||
|
||||
m_discard = discard;
|
||||
@ -107,15 +101,22 @@ void IIc::_calculate(const Indicator& inputInd) {
|
||||
auto ic = hku::SPEARMAN(a, b, stk_count);
|
||||
dst[i] = ic[ic.size() - 1];
|
||||
}
|
||||
|
||||
for (size_t i = m_discard; i < days_total; i++) {
|
||||
if (!std::isnan(dst[i])) {
|
||||
m_discard = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Indicator HKU_API IC(const StockList& stks, const KQuery& query, int n, const Stock& ref_stk) {
|
||||
Indicator HKU_API IC(const StockList& stks, const KQuery& query, const Stock& ref_stk, int n) {
|
||||
return Indicator(make_shared<IIc>(stks, query, n, ref_stk));
|
||||
}
|
||||
|
||||
Indicator HKU_API IC(const Block& blk, const KQuery& query, int n, const Stock& ref_stk) {
|
||||
Indicator HKU_API IC(const Block& blk, const KQuery& query, const Stock& ref_stk, int n) {
|
||||
StockList stks = blk.getAllStocks();
|
||||
return IC(stks, query, n, ref_stk);
|
||||
return IC(stks, query, ref_stk, n);
|
||||
}
|
||||
|
||||
} // namespace hku
|
@ -38,8 +38,10 @@ void IMa::_calculate(const Indicator& indicator) {
|
||||
if (n <= 0) {
|
||||
price_t sum = 0.0;
|
||||
for (size_t i = m_discard; i < total; i++) {
|
||||
sum += src[i];
|
||||
dst[i] = sum / (i - m_discard + 1);
|
||||
if (!std::isnan(src[i])) {
|
||||
sum += src[i];
|
||||
dst[i] = sum / (i - m_discard + 1);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -49,13 +51,17 @@ void IMa::_calculate(const Indicator& indicator) {
|
||||
size_t count = 1;
|
||||
size_t first_end = startPos + n >= total ? total : startPos + n;
|
||||
for (size_t i = startPos; i < first_end; ++i) {
|
||||
sum += src[i];
|
||||
dst[i] = sum / count++;
|
||||
if (!std::isnan(src[i])) {
|
||||
sum += src[i];
|
||||
dst[i] = sum / count++;
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = first_end; i < total; ++i) {
|
||||
sum = src[i] + sum - src[i - n];
|
||||
dst[i] = sum / n;
|
||||
if (!std::isnan(src[i]) && !std::isnan(src[i - n])) {
|
||||
sum = src[i] + sum - src[i - n];
|
||||
dst[i] = sum / n;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -48,24 +48,46 @@ void IStdev::_calculate(const Indicator& data) {
|
||||
size_t first_end = start_pos + n >= total ? total : start_pos + n;
|
||||
price_t k = src[start_pos];
|
||||
for (size_t i = start_pos; i < first_end; i++) {
|
||||
num++;
|
||||
price_t d = src[i] - k;
|
||||
ex += d;
|
||||
price_t d_pow = std::pow(d, 2);
|
||||
pow_buf[i] = d_pow;
|
||||
ex2 += d_pow;
|
||||
dst[i] = num == 1 ? 0. : std::sqrt((ex2 - std::pow(ex, 2) / num) / (num - 1));
|
||||
if (!std::isnan(src[i])) {
|
||||
num++;
|
||||
price_t d = src[i] - k;
|
||||
ex += d;
|
||||
price_t d_pow = std::pow(d, 2);
|
||||
pow_buf[i] = d_pow;
|
||||
ex2 += d_pow;
|
||||
// dst[i] = num == 1 ? 0. : std::sqrt((ex2 - std::pow(ex, 2) / num) / (num - 1));
|
||||
if (num > 1) {
|
||||
dst[i] = std::sqrt((ex2 - std::pow(ex, 2) / num) / (num - 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = first_end; i < total; i++) {
|
||||
ex -= src[i - n] - k;
|
||||
ex2 -= pow_buf[i - n];
|
||||
price_t d = src[i] - k;
|
||||
ex += d;
|
||||
price_t d_pow = std::pow(d, 2);
|
||||
pow_buf[i] = d_pow;
|
||||
ex2 += d_pow;
|
||||
dst[i] = std::sqrt((ex2 - std::pow(ex, 2) / n) / (n - 1));
|
||||
if (!std::isnan(src[i])) {
|
||||
size_t j = i - n;
|
||||
for (; j < i; j++) {
|
||||
if (!std::isnan(src[j])) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (j == i) {
|
||||
continue;
|
||||
}
|
||||
// ex -= src[i - n] - k;
|
||||
// ex2 -= pow_buf[i - n];
|
||||
ex -= src[j] - k;
|
||||
ex2 -= pow_buf[j];
|
||||
price_t d = src[i] - k;
|
||||
ex += d;
|
||||
price_t d_pow = std::pow(d, 2);
|
||||
pow_buf[i] = d_pow;
|
||||
ex2 += d_pow;
|
||||
size_t num = i - j;
|
||||
if (num != 1) {
|
||||
dst[i] = std::sqrt((ex2 - std::pow(ex, 2) / num) / (num - 1));
|
||||
}
|
||||
// dst[i] = std::sqrt((ex2 - std::pow(ex, 2) / n) / (n - 1));
|
||||
}
|
||||
}
|
||||
|
||||
// 排除第一位的0值
|
||||
|
@ -31,8 +31,10 @@ string TradeManager::str() const {
|
||||
<< " params: " << getParameter() << strip << " name: " << name() << strip
|
||||
<< " init_date: " << initDatetime() << strip << " init_cash: " << initCash() << strip
|
||||
<< " firstDatetime: " << firstDatetime() << strip << " lastDatetime: " << lastDatetime()
|
||||
<< strip << " TradeCostFunc: " << costFunc() << strip << " current cash: " << currentCash()
|
||||
<< strip << " current market_value: " << funds.market_value << strip
|
||||
<< strip << " TradeCostFunc: " << costFunc() << strip << " current total funds: "
|
||||
<< funds.cash + funds.market_value + funds.borrow_asset - funds.short_market_value << strip
|
||||
<< " current cash: " << currentCash() << strip
|
||||
<< " current market_value: " << funds.market_value << strip
|
||||
<< " current short_market_value: " << funds.short_market_value << strip
|
||||
<< " current base_cash: " << funds.base_cash << strip
|
||||
<< " current base_asset: " << funds.base_asset << strip
|
||||
@ -1831,7 +1833,8 @@ bool TradeManager::_add_buy_tr(const TradeRecord& tr) {
|
||||
price_t money = roundEx(tr.realPrice * tr.number * tr.stock.unit(), precision);
|
||||
|
||||
HKU_WARN_IF_RETURN(m_cash < roundEx(money + tr.cost.total, precision), false,
|
||||
"Don't have enough money!");
|
||||
"Don't have enough money! {} < {}, {}", m_cash,
|
||||
roundEx(money + tr.cost.total, precision), tr);
|
||||
|
||||
m_cash = roundEx(m_cash - money - tr.cost.total, precision);
|
||||
new_tr.cash = m_cash;
|
||||
|
@ -113,33 +113,7 @@ TradeRecord::TradeRecord(const Stock& stock, const Datetime& datetime, BUSINESS
|
||||
from(from) {}
|
||||
|
||||
HKU_API std::ostream& operator<<(std::ostream& os, const TradeRecord& record) {
|
||||
Stock stock = record.stock;
|
||||
string market_code(""), name("");
|
||||
if (!stock.isNull()) {
|
||||
market_code = stock.market_code();
|
||||
name = stock.name();
|
||||
}
|
||||
|
||||
string strip(", ");
|
||||
os << std::fixed;
|
||||
os.precision(4);
|
||||
os << "Trade(" << record.datetime << strip << market_code << strip << name << strip
|
||||
<< getBusinessName(record.business) << strip << record.planPrice << strip
|
||||
<< record.realPrice;
|
||||
|
||||
if (std::isnan(record.goalPrice)) {
|
||||
os << strip << "NULL";
|
||||
} else {
|
||||
os << strip << record.goalPrice;
|
||||
}
|
||||
|
||||
os << strip << record.number << strip << record.cost.commission << strip << record.cost.stamptax
|
||||
<< strip << record.cost.transferfee << strip << record.cost.others << strip
|
||||
<< record.cost.total << strip << record.stoploss << strip << record.cash << strip
|
||||
<< getSystemPartName(record.from) << ")";
|
||||
|
||||
os.unsetf(std::ostream::floatfield);
|
||||
os.precision();
|
||||
os << record.toString();
|
||||
return os;
|
||||
}
|
||||
|
||||
@ -150,27 +124,20 @@ string TradeRecord::toString() const {
|
||||
name = stock.name();
|
||||
}
|
||||
|
||||
string strip(", ");
|
||||
std::stringstream os;
|
||||
os << std::fixed;
|
||||
os.precision(4);
|
||||
os << "Trade(" << datetime << strip << market_code << strip << name << strip
|
||||
<< getBusinessName(business) << strip << planPrice << strip << realPrice;
|
||||
|
||||
// if (goalPrice == Null<price_t>()) {
|
||||
if (std::isnan(goalPrice)) {
|
||||
os << strip << "nan";
|
||||
} else {
|
||||
os << strip << goalPrice;
|
||||
}
|
||||
|
||||
os << strip << goalPrice << strip << number << strip << cost.commission << strip
|
||||
<< cost.stamptax << strip << cost.transferfee << strip << cost.others << strip << cost.total
|
||||
<< strip << stoploss << strip << cash << strip << getSystemPartName(from) << ")";
|
||||
|
||||
os.unsetf(std::ostream::floatfield);
|
||||
os.precision();
|
||||
return os.str();
|
||||
#if HKU_OS_WINDOWS
|
||||
return fmt::format(
|
||||
"Trade({}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {})", datetime, market_code,
|
||||
StockManager::instance().runningInPython() && StockManager::instance().pythonInJupyter()
|
||||
? name
|
||||
: UTF8ToGB(name),
|
||||
getBusinessName(business), planPrice, realPrice, goalPrice, number, cost.commission,
|
||||
cost.stamptax, cost.transferfee, cost.others, getSystemPartName(from));
|
||||
#else
|
||||
return fmt::format("Trade({}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {})", datetime,
|
||||
market_code, name, getBusinessName(business), planPrice, realPrice,
|
||||
goalPrice, number, cost.commission, cost.stamptax, cost.transferfee,
|
||||
cost.others, getSystemPartName(from));
|
||||
#endif
|
||||
}
|
||||
|
||||
bool TradeRecord::isNull() const {
|
||||
|
@ -28,35 +28,43 @@ HKU_API std::ostream& operator<<(std::ostream& os, const AFPtr& af) {
|
||||
|
||||
AllocateFundsBase::AllocateFundsBase() : m_name("AllocateMoneyBase"), m_reserve_percent(0) {
|
||||
// 是否调整之前已经持仓策略的持仓。不调整时,仅使用总账户当前剩余资金进行分配,否则将使用总市值进行分配
|
||||
// 注意:无论是否调整已持仓策略,权重比例都是相对于总资产,不是针对剩余现金余额
|
||||
// 仅针对剩余现金比例调整没有意义,即使分配由于交易成本原因可能也无法完成实际交易
|
||||
// adjust_running_sys: True - 主动根据资产分配对已持仓策略进行增减仓
|
||||
// adjust_running_sys: False - 不会根据当前分配权重对已持仓策略进行强制加减仓
|
||||
setParam<bool>("adjust_running_sys", false);
|
||||
setParam<int>("max_sys_num", 1000000); // 允许运行的最大系统实例数
|
||||
setParam<double>("weight_unit", 0.0001); // 最小权重单位
|
||||
|
||||
// 是否过滤子类返回的比例权重列表中的 0 值(包含小于0)和 nan 值
|
||||
// 如:子类返回权重比例列表 [6, 2, 0, 0, 0], 则
|
||||
// 过滤 0 值,则实际调整后的权重为 Xi / sum(Xi):[6/8, 2/8]
|
||||
// 不过滤,m 设为非零元素个数,n为总元素个数,(Xi / Sum(Xi)) * (m / n):
|
||||
// [(6/8)*(2/5), (2/8)*(2/5), 0, 0, 0]
|
||||
// 即,保留分为5份后,仅在2份中保持相对比例
|
||||
setParam<bool>("filter_zero_weight", false);
|
||||
|
||||
setParam<double>("default_reserve_percent", 0.0); // 默认保留不参与重分配的资产比例
|
||||
setParam<bool>("trace", false); // 打印跟踪
|
||||
}
|
||||
|
||||
AllocateFundsBase::AllocateFundsBase(const string& name)
|
||||
: m_name("AllocateMoneyBase"), m_reserve_percent(0) {
|
||||
setParam<bool>("adjust_running_sys", false);
|
||||
setParam<int>("max_sys_num", 100000); // 最大系统实例数
|
||||
setParam<double>("weight_unit", 0.0001); // 最小权重单位
|
||||
setParam<bool>("filter_zero_weight", false);
|
||||
setParam<double>("default_reserve_percent", 0.0); // 默认保留不参与重分配的资产比例
|
||||
setParam<bool>("trace", false); // 打印跟踪
|
||||
}
|
||||
|
||||
AllocateFundsBase::~AllocateFundsBase() {}
|
||||
|
||||
void AllocateFundsBase::reset() {
|
||||
m_reserve_percent = getParam<double>("default_reserve_percent");
|
||||
_reset();
|
||||
|
||||
// 参数检查
|
||||
HKU_ERROR_IF(getParam<int>("max_sys_num") <= 0, R"(AF param["max_sys_num"]({}) need > 0!)",
|
||||
getParam<int>("max_sys_num"));
|
||||
HKU_ERROR_IF(
|
||||
getParam<double>("default_reserve_percent") >= 1.0,
|
||||
R"(AF param(default_reserve_percent)({}) >= 1.0, No asset adjustments will be made!)");
|
||||
HKU_CHECK(getParam<double>("default_reserve_percent") >= 0.0,
|
||||
R"(Invalid AF param["default_reserve_percent"] ({}))",
|
||||
getParam<double>("default_reserve_percent"));
|
||||
double default_reserve_percent = getParam<double>("default_reserve_percent");
|
||||
HKU_CHECK(default_reserve_percent >= 0.0 && default_reserve_percent < 1.0,
|
||||
R"(AF param(default_reserve_percent)({}) >= 1.0, No asset adjustments will be made!)",
|
||||
default_reserve_percent);
|
||||
|
||||
m_reserve_percent = default_reserve_percent;
|
||||
_reset();
|
||||
}
|
||||
|
||||
AFPtr AllocateFundsBase::clone() {
|
||||
@ -78,11 +86,11 @@ AFPtr AllocateFundsBase::clone() {
|
||||
p->m_query = m_query;
|
||||
p->m_reserve_percent = m_reserve_percent;
|
||||
|
||||
/* m_tm, m_shadow_tm 由 PF 运行时指定,不需要 clone
|
||||
/* m_tm, m_cash_tm 由 PF 运行时指定,不需要 clone
|
||||
if (m_tm)
|
||||
p->m_tm = m_tm->clone();
|
||||
if (m_shadow_tm)
|
||||
p->m_shadow_tm = m_shadow_tm->clone();*/
|
||||
if (m_cash_tm)
|
||||
p->m_cash_tm = m_cash_tm->clone();*/
|
||||
return p;
|
||||
}
|
||||
|
||||
@ -92,403 +100,372 @@ void AllocateFundsBase::setReservePercent(double percent) {
|
||||
m_reserve_percent = percent;
|
||||
}
|
||||
|
||||
void AllocateFundsBase::adjustFunds(const Datetime& date, const SystemList& se_list,
|
||||
const std::list<SYSPtr>& running_list,
|
||||
const SystemList& ignore_list) {
|
||||
void AllocateFundsBase::_check_weight(const SystemWeightList& sw_list) {
|
||||
price_t sum_weight = 0.0;
|
||||
for (const auto& sw : sw_list) {
|
||||
HKU_CHECK(!std::isnan(sw.weight) && sw.weight >= 0.0 && sw.weight <= 1.0,
|
||||
"Invalid weight ({}, {})", sw.sys->name(), sw.weight);
|
||||
sum_weight += sw.weight;
|
||||
}
|
||||
HKU_CHECK(sum_weight <= 1.001, "The cumulative weight exceeds 1! sum_weight: {}", sum_weight);
|
||||
}
|
||||
|
||||
SystemWeightList AllocateFundsBase::adjustFunds(const Datetime& date,
|
||||
const SystemWeightList& se_list,
|
||||
const std::unordered_set<SYSPtr>& running_list) {
|
||||
SystemWeightList result;
|
||||
if (getParam<bool>("adjust_running_sys")) {
|
||||
_adjust_with_running(date, se_list, running_list, ignore_list);
|
||||
result = _adjust_with_running(date, se_list, running_list);
|
||||
} else {
|
||||
_adjust_without_running(date, se_list, running_list);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
price_t AllocateFundsBase::_getTotalFunds(const Datetime& date,
|
||||
const std::list<SYSPtr>& running_list) {
|
||||
price_t total_value = 0;
|
||||
// 降序排列 SystemWeightList,并将权重调整为总权重为 1
|
||||
static void adjustWeight(SystemWeightList& sw_list, double can_allocate_weight, bool filter_zero) {
|
||||
std::sort(sw_list.begin(), sw_list.end(), [](const SystemWeight& a, const SystemWeight& b) {
|
||||
if (std::isnan(a.weight) && std::isnan(b.weight)) {
|
||||
return false;
|
||||
} else if (!std::isnan(a.weight) && std::isnan(b.weight)) {
|
||||
return true;
|
||||
} else if (std::isnan(a.weight) && !std::isnan(b.weight)) {
|
||||
return false;
|
||||
}
|
||||
return a.weight > b.weight;
|
||||
});
|
||||
|
||||
// 计算运行中的子系统总资产净值
|
||||
for (auto& sub_sys : running_list) {
|
||||
TMPtr sub_tm = sub_sys->getTM();
|
||||
KQuery sub_query = sub_sys->getTO().getQuery();
|
||||
FundsRecord funds = sub_tm->getFunds(date, sub_query.kType());
|
||||
total_value +=
|
||||
funds.cash + funds.market_value + funds.borrow_asset - funds.short_market_value;
|
||||
SystemWeightList new_list;
|
||||
new_list.reserve(sw_list.size());
|
||||
price_t sum = 0.0;
|
||||
for (size_t i = 0, total = sw_list.size(); i < total; i++) {
|
||||
const auto& item = sw_list[i];
|
||||
if (std::isnan(item.weight) || item.weight <= 0.0) {
|
||||
break;
|
||||
}
|
||||
sum += item.weight;
|
||||
new_list.emplace_back(item);
|
||||
}
|
||||
|
||||
// 加上当前总账户现金余额
|
||||
m_shadow_tm->updateWithWeight(date);
|
||||
total_value = roundDown(total_value + m_shadow_tm->cash(date, m_query.kType()),
|
||||
m_shadow_tm->getParam<int>("precision"));
|
||||
return total_value;
|
||||
double per_weight =
|
||||
filter_zero ? 1.0 / sum : (new_list.size() * can_allocate_weight) / (sum * sw_list.size());
|
||||
for (size_t i = 0, total = new_list.size(); i < total; i++) {
|
||||
new_list[i].weight = new_list[i].weight * per_weight;
|
||||
}
|
||||
|
||||
sw_list.swap(new_list);
|
||||
}
|
||||
|
||||
bool AllocateFundsBase::_returnAssets(const SYSPtr& sys, const Datetime& date) {
|
||||
KData kdata = sys->getTO();
|
||||
size_t pos = kdata.getPos(date);
|
||||
HKU_IF_RETURN(pos == Null<size_t>(), false);
|
||||
// 只对现金余额进行分配,此时的权重是针对余额的
|
||||
void AllocateFundsBase::_adjust_without_running(const Datetime& date,
|
||||
const SystemWeightList& se_list,
|
||||
const std::unordered_set<SYSPtr>& running_set) {
|
||||
HKU_CHECK(m_reserve_percent >= 0.0 && m_reserve_percent < 1.0,
|
||||
"Invalid reserve_percent({}) in AF, Calculations that will cause errors!",
|
||||
m_reserve_percent);
|
||||
|
||||
KRecord record = kdata.getKRecord(pos);
|
||||
Stock stock = kdata.getStock();
|
||||
KRecord srcRecord = stock.getKRecord(kdata.startPos() + pos);
|
||||
bool trace = getParam<bool>("trace");
|
||||
HKU_INFO_IF(trace, "[AF] {} _adjust_without_running", date);
|
||||
|
||||
// 如果存在持仓则卖出
|
||||
TMPtr tm = sys->getTM();
|
||||
if (tm->have(stock)) {
|
||||
TradeRecord tr = sys->sell(record, srcRecord, PART_ALLOCATEFUNDS);
|
||||
if (!tr.isNull()) {
|
||||
m_tm->addTradeRecord(tr);
|
||||
// 获取计划分配的资产权重,因为不调整已运行系统实现占位,将所有运行中的系统以0比例权重加在选中系统之前
|
||||
SystemWeightList new_se_list;
|
||||
for (const auto& running_sw : running_set) {
|
||||
new_se_list.emplace_back(running_sw, 0.0);
|
||||
}
|
||||
|
||||
for (const auto& sw : se_list) {
|
||||
if (running_set.find(sw.sys) == running_set.end()) {
|
||||
new_se_list.emplace_back(sw);
|
||||
}
|
||||
}
|
||||
|
||||
// 回收当前子账号资金至总账户
|
||||
price_t cash = tm->cash(date, kdata.getQuery().kType());
|
||||
if (cash > 0.0 && tm->checkout(date, cash)) {
|
||||
m_shadow_tm->checkin(date, cash);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 调整运行中子系统持仓
|
||||
void AllocateFundsBase::_adjust_with_running(const Datetime& date, const SystemList& se_list,
|
||||
const std::list<SYSPtr>& running_list,
|
||||
const SystemList& ignore_list) {
|
||||
// 计算当前选中系统列表的权重
|
||||
SystemWeightList sw_list = _allocateWeight(date, se_list);
|
||||
HKU_IF_RETURN(new_se_list.size() == 0, void());
|
||||
SystemWeightList sw_list = _allocateWeight(date, new_se_list);
|
||||
HKU_IF_RETURN(sw_list.size() == 0, void());
|
||||
|
||||
// 构建实际分配权重大于零的的系统集合,权重小于等于0的将被忽略
|
||||
std::unordered_set<System*> selected_sets;
|
||||
for (auto& sw : sw_list) {
|
||||
if (sw.getWeight() > 0.0) {
|
||||
selected_sets.insert(sw.getSYS().get());
|
||||
}
|
||||
}
|
||||
|
||||
std::unordered_set<System*> ignore_sets;
|
||||
for (auto& sys : ignore_list) {
|
||||
ignore_sets.insert(sys.get());
|
||||
}
|
||||
|
||||
std::unordered_set<System*> selected_running_sets;
|
||||
|
||||
// 如果当前运行的系统不在实际的选中系统集合且不在忽略列表中,则强制清仓卖出,并回收资产
|
||||
for (auto& sys : running_list) {
|
||||
// 不在忽略列表中
|
||||
if (ignore_sets.find(sys.get()) == ignore_sets.end()) {
|
||||
// 当前持仓的系统仍旧被选中,记入仍被选中的运行系统列表
|
||||
if (selected_sets.find(sys.get()) != selected_sets.end()) {
|
||||
selected_running_sets.insert(sys.get());
|
||||
} else {
|
||||
_returnAssets(sys, date);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 按权重升序排序(注意:无法保证等权重的相对顺序,即使用stable_sort也一样,后面要倒序遍历)
|
||||
std::sort(
|
||||
sw_list.begin(), sw_list.end(),
|
||||
std::bind(std::less<double>(), std::bind(&SystemWeight::m_weight, std::placeholders::_1),
|
||||
std::bind(&SystemWeight::m_weight, std::placeholders::_2)));
|
||||
|
||||
// 过滤掉超出允许范围的系统
|
||||
// 按权重从大到小遍历,构建不超过最大允许的运行子系统数的新权重列表(此时按从大到小顺序存放)
|
||||
// 同时,将超出最大允许的运行子系统数范围外的运行中子系统清仓回收资金
|
||||
int max_num = getParam<int>("max_sys_num");
|
||||
std::list<SystemWeight> new_sw_list; // 存放新的权重列表
|
||||
size_t count = 0;
|
||||
for (auto iter = sw_list.rbegin(); iter != sw_list.rend(); ++iter) {
|
||||
// 忽略小于等于零的权重
|
||||
if (iter->getWeight() <= 0.0) {
|
||||
break;
|
||||
}
|
||||
|
||||
// 小于最大允许运行数,直接保存至新的权重列表
|
||||
if (count < max_num) {
|
||||
new_sw_list.emplace_back(std::move(*iter));
|
||||
count++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 处理超出允许的最大系统数范围外的系统,尝试强制清仓,但不在放入权重列表(即后续不参与资金分配)
|
||||
if (selected_running_sets.find(iter->getSYS().get()) != selected_running_sets.end()) {
|
||||
_returnAssets(iter->getSYS(), date);
|
||||
}
|
||||
}
|
||||
|
||||
// 账户资金精度
|
||||
int precision = m_shadow_tm->getParam<int>("precision");
|
||||
|
||||
// 获取当前总账户资产净值,并计算每单位权重代表的资金
|
||||
price_t total_funds = _getTotalFunds(date, running_list);
|
||||
|
||||
// 计算需保留的资产
|
||||
HKU_ERROR_IF(m_reserve_percent < 0.0,
|
||||
"Invalid reserve_percent({}) in AF, Calculations that will cause errors!",
|
||||
m_reserve_percent);
|
||||
price_t reserve_funds = roundUp(total_funds * m_reserve_percent, precision);
|
||||
|
||||
// 计算每单位权重资产
|
||||
double weight_unit = getParam<double>("weight_unit");
|
||||
HKU_CHECK(weight_unit > 0, "Invalid weight_unit! {}", weight_unit);
|
||||
price_t per_weight_funds = total_funds * weight_unit;
|
||||
|
||||
// 计算可分配现金
|
||||
price_t can_allocate_cash =
|
||||
roundDown(m_shadow_tm->cash(date, m_query.kType()) - reserve_funds, precision);
|
||||
if (can_allocate_cash < 0.0) {
|
||||
can_allocate_cash = 0.0;
|
||||
}
|
||||
|
||||
// 缓存需要进一步处理的系统及其待补充资金的列表
|
||||
std::list<std::pair<SYSPtr, price_t>> wait_list;
|
||||
|
||||
// 按权重从大到小遍历
|
||||
// 1.如果子系统当前资产已经等于期望被分配的资产则不做处理
|
||||
// 2.如果子系统当前资产小于期望被分配的资产,则尝试补充资金,否则放入等待列表
|
||||
// 3.如果当前资产大于期望分配的资产,则子账户是否有现金可以取出抵扣,否则卖掉部分股票
|
||||
for (auto iter = new_sw_list.begin(); iter != new_sw_list.end(); ++iter) {
|
||||
// 选中系统的分配权重已经小于等于0,则退出
|
||||
if (iter->getWeight() <= 0.0) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto& sys = iter->getSYS();
|
||||
|
||||
// 如果在忽略列表中,则跳过
|
||||
if (ignore_sets.find(sys.get()) != ignore_sets.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 获取系统账户的当前资产市值
|
||||
TMPtr tm = sys->getTM();
|
||||
|
||||
// 更新子账号权息数据
|
||||
tm->updateWithWeight(date);
|
||||
|
||||
FundsRecord funds = tm->getFunds(date, m_query.kType());
|
||||
price_t funds_value =
|
||||
funds.cash + funds.market_value + funds.borrow_asset - funds.short_market_value;
|
||||
|
||||
price_t will_funds_value = (iter->getWeight() / weight_unit) * per_weight_funds;
|
||||
if (funds_value == will_funds_value) {
|
||||
// 如果当前资产已经等于期望分配的资产,则忽略
|
||||
continue;
|
||||
|
||||
} else if (funds_value < will_funds_value) {
|
||||
// 如果当前资产小于期望分配的资产,则补充现金
|
||||
price_t will_cash = roundUp(will_funds_value - funds_value, precision);
|
||||
|
||||
// 如果当前可用于分配的资金大于期望的资金,则尝试从总账户中将现金补充进子账户中
|
||||
if (will_cash > 0.0 && will_cash <= can_allocate_cash) {
|
||||
if (m_shadow_tm->checkout(date, will_cash)) {
|
||||
tm->checkin(date, will_cash);
|
||||
can_allocate_cash = roundDown(can_allocate_cash - will_cash, precision);
|
||||
}
|
||||
|
||||
} else {
|
||||
// 如果当前可用于分配的资金已经不足,则先全部转入子账户,并调整该系统实例权重。
|
||||
// 同时,将该系统实例放入带重分配列表中,等有需要腾出仓位的系统卖出后,再重新分配补充现金
|
||||
if (can_allocate_cash > 0.0 && m_shadow_tm->checkout(date, can_allocate_cash)) {
|
||||
tm->checkin(date, can_allocate_cash);
|
||||
can_allocate_cash = 0.0;
|
||||
}
|
||||
|
||||
// 缓存至等待列表,以便后续处理
|
||||
price_t reserve_cash = roundUp(will_cash - can_allocate_cash, precision);
|
||||
if (reserve_cash > 0.0) {
|
||||
wait_list.push_back(std::make_pair(sys, reserve_cash));
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// 如果当前资产大于期望分配的资产,则子账户是否有现金可以取出抵扣,否则卖掉部分股票
|
||||
price_t will_return_cash = roundDown(funds_value - will_funds_value, precision);
|
||||
if (will_return_cash <= 0.0) {
|
||||
continue;
|
||||
}
|
||||
price_t sub_cash = tm->currentCash();
|
||||
if (sub_cash >= will_return_cash) {
|
||||
if (tm->checkout(date, will_return_cash)) {
|
||||
m_shadow_tm->checkin(date, will_return_cash);
|
||||
can_allocate_cash = roundDown(can_allocate_cash + will_return_cash, precision);
|
||||
}
|
||||
|
||||
} else {
|
||||
// 计算需要卖出股票换取的资金
|
||||
price_t need_cash = will_return_cash - sub_cash;
|
||||
|
||||
// 计算并卖出部分股票以获取剩下需要返还的资金
|
||||
Stock stock = sys->getStock();
|
||||
PositionRecord position = tm->getPosition(date, stock);
|
||||
if (position.number > 0) {
|
||||
KData kdata = sys->getTO();
|
||||
size_t pos = kdata.getPos(date);
|
||||
if (pos == Null<size_t>()) {
|
||||
continue;
|
||||
}
|
||||
KRecord k = kdata.getKRecord(pos);
|
||||
KRecord srcK = stock.getKRecord(kdata.startPos() + pos);
|
||||
TradeRecord tr;
|
||||
double need_sell_num = sys->getParam<bool>("sell_delay")
|
||||
? need_cash / k.closePrice
|
||||
: need_cash / k.openPrice;
|
||||
if (position.number <= need_sell_num) {
|
||||
// 如果当前持仓数小于等于需要卖出的数量,则全部卖出
|
||||
tr = sys->sellForce(k, srcK, position.number, PART_ALLOCATEFUNDS);
|
||||
} else {
|
||||
if (position.number - need_sell_num >= stock.minTradeNumber()) {
|
||||
// 如果按需要卖出数量卖出后,可能剩余的数量大于等于最小交易数则按需要卖出的数量卖出
|
||||
tr = sys->sellForce(k, srcK, need_sell_num, PART_ALLOCATEFUNDS);
|
||||
} else {
|
||||
// 如果按需要卖出的数量卖出后,剩余的持仓数小于最小交易数量则全部卖出
|
||||
tr = sys->sellForce(k, srcK, position.number, PART_ALLOCATEFUNDS);
|
||||
}
|
||||
}
|
||||
if (!tr.isNull()) {
|
||||
m_tm->addTradeRecord(tr);
|
||||
}
|
||||
|
||||
// 卖出后,尝试将资金取出转移至总账户
|
||||
sub_cash = tm->currentCash();
|
||||
if (sub_cash > 0 && tm->checkout(date, sub_cash)) {
|
||||
m_shadow_tm->checkin(date, sub_cash);
|
||||
can_allocate_cash = roundDown(can_allocate_cash + sub_cash, precision);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 部分调整仓位的股票被卖出后,再次将资金分配至等待资金的子系统
|
||||
can_allocate_cash = m_shadow_tm->currentCash();
|
||||
for (auto iter = wait_list.begin(); iter != wait_list.end(); ++iter) {
|
||||
// 如果可分配的现金不足或选中系统的分配权重已经小于等于0,则退出
|
||||
if (can_allocate_cash <= 0.0) {
|
||||
break;
|
||||
}
|
||||
|
||||
// 获取系统账户的当前资产市值
|
||||
SYSPtr sys = iter->first;
|
||||
TMPtr tm = sys->getTM();
|
||||
|
||||
price_t need_cash = iter->second;
|
||||
if (need_cash <= can_allocate_cash) {
|
||||
if (m_shadow_tm->checkout(date, need_cash)) {
|
||||
tm->checkin(date, need_cash);
|
||||
can_allocate_cash = roundDown(can_allocate_cash - need_cash, precision);
|
||||
}
|
||||
} else {
|
||||
if (m_shadow_tm->checkout(date, can_allocate_cash)) {
|
||||
tm->checkin(date, can_allocate_cash);
|
||||
can_allocate_cash = 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AllocateFundsBase::_adjust_without_running(const Datetime& date, const SystemList& se_list,
|
||||
const std::list<SYSPtr>& running_list) {
|
||||
HKU_IF_RETURN(se_list.size() == 0, void());
|
||||
|
||||
// 如果运行中的系统数已大于等于允许的最大系统数,直接返回
|
||||
int max_num = getParam<int>("max_sys_num");
|
||||
HKU_IF_RETURN(running_list.size() >= max_num, void());
|
||||
|
||||
// 计算选中系统中当前正在运行中的子系统
|
||||
std::unordered_set<System*> hold_sets;
|
||||
for (auto& sys : running_list) {
|
||||
hold_sets.insert(sys.get());
|
||||
}
|
||||
|
||||
// 计算不包含运行中系统的子系统列表
|
||||
SystemList pure_se_list;
|
||||
for (auto& sys : se_list) {
|
||||
if (hold_sets.find(sys.get()) == hold_sets.end()) {
|
||||
pure_se_list.push_back(sys);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取计划分配的资产权重
|
||||
SystemWeightList sw_list = _allocateWeight(date, pure_se_list);
|
||||
HKU_IF_RETURN(sw_list.size() == 0, void());
|
||||
|
||||
// 按权重升序排序(注意:无法保证等权重的相对顺序,即使用stable_sort也一样,后面要倒序遍历)
|
||||
std::sort(
|
||||
sw_list.begin(), sw_list.end(),
|
||||
std::bind(std::less<double>(), std::bind(&SystemWeight::m_weight, std::placeholders::_1),
|
||||
std::bind(&SystemWeight::m_weight, std::placeholders::_2)));
|
||||
|
||||
// 检测是否有信号发生,过滤掉没有发生信号的系统 以及 权重为 0 的系统
|
||||
SystemWeightList new_sw_list;
|
||||
auto sw_iter = sw_list.rbegin();
|
||||
for (; sw_iter != sw_list.rend(); ++sw_iter) {
|
||||
if (sw_iter->getWeight() <= 0.0)
|
||||
break;
|
||||
if (sw_iter->getSYS()->getSG()->shouldBuy(date)) {
|
||||
new_sw_list.emplace_back(*sw_iter);
|
||||
}
|
||||
}
|
||||
|
||||
// 总账号资金精度
|
||||
int precision = m_shadow_tm->getParam<int>("precision");
|
||||
|
||||
// 获取当前总资产市值
|
||||
price_t total_funds = _getTotalFunds(date, running_list);
|
||||
|
||||
// 计算需保留的资产
|
||||
HKU_ERROR_IF(m_reserve_percent < 0.0,
|
||||
"Invalid reserve_percent({}) in AF, Calculations that will cause errors!",
|
||||
m_reserve_percent);
|
||||
// 获取当前总资产市值,计算剩余可分配权重与现金
|
||||
int precision = m_tm->getParam<int>("precision");
|
||||
FundsRecord funds = m_tm->getFunds(date, m_query.kType()); // 总资产从总账户获取
|
||||
price_t total_funds =
|
||||
funds.cash + funds.market_value + funds.borrow_asset - funds.short_market_value;
|
||||
price_t reserve_funds = total_funds * m_reserve_percent;
|
||||
|
||||
// 如果当前现金小于等于需保留的资产,则直接返回
|
||||
HKU_IF_RETURN(m_shadow_tm->cash(date, m_query.kType()) <= reserve_funds, void());
|
||||
|
||||
// 计算可用于分配的现金
|
||||
price_t can_allocate_cash =
|
||||
roundDown(m_shadow_tm->cash(date, m_query.kType()) - reserve_funds, precision);
|
||||
if (can_allocate_cash <= 0.0) {
|
||||
return;
|
||||
price_t can_allocate_cash = m_cash_tm->currentCash(); // 可分配资金从资金账户中获取
|
||||
if (can_allocate_cash + reserve_funds > total_funds) {
|
||||
can_allocate_cash = roundDown(total_funds - reserve_funds, precision);
|
||||
}
|
||||
double can_allocate_weight = 1.0 - m_reserve_percent;
|
||||
HKU_INFO_IF(trace,
|
||||
"can_allocate_weight: {:<.4f}, can_allocate_cash: {:<.2f}, current cash: {:<.2f}, "
|
||||
"total funds: {:<.2f}, "
|
||||
"reserved funds: {:<.2f}",
|
||||
can_allocate_weight, can_allocate_cash, funds.cash, total_funds, reserve_funds);
|
||||
HKU_IF_RETURN(can_allocate_cash <= 1.0, void());
|
||||
|
||||
// 调整权重(累积权重和归一)并按降序排列, 并过滤掉 0 值和 Nan 值
|
||||
adjustWeight(sw_list, can_allocate_weight, getParam<bool>("filter_zero_weight"));
|
||||
|
||||
// 遍历选中子系统列表,并将剩余现金按权重比例转入子账户
|
||||
double sum_weight = 0.0; // 由于不调整已运行系统,已运行系统实际占用比例可能和要求的比例不一致
|
||||
for (auto iter = sw_list.begin(), end_iter = sw_list.end(); iter != end_iter; ++iter) {
|
||||
if (can_allocate_cash <= 1.0 || sum_weight >= can_allocate_weight) {
|
||||
break;
|
||||
}
|
||||
|
||||
// 如果是运行中系统,不使用计算的权重,更新累积权重和
|
||||
if (running_set.find(iter->sys) != running_set.cend()) {
|
||||
FundsRecord sub_funds = m_tm->getFunds(date, m_query.kType());
|
||||
price_t sub_total_funds = sub_funds.cash + sub_funds.market_value +
|
||||
sub_funds.borrow_asset - sub_funds.short_market_value;
|
||||
sum_weight += sub_total_funds / total_funds;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 计算实际可用的权重
|
||||
price_t current_weight =
|
||||
iter->weight + sum_weight > 1.0 ? iter->weight + sum_weight - 1.0 : iter->weight;
|
||||
|
||||
// 再次遍历选中子系统列表,并将剩余现金按权重比例转入子账户
|
||||
double weight_unit = getParam<double>("weight_unit");
|
||||
price_t per_cash = total_funds * weight_unit; // 每单位权重资金
|
||||
size_t can_run_count = 0;
|
||||
for (auto sw_iter = new_sw_list.begin(), end_iter = new_sw_list.end(); sw_iter != end_iter;
|
||||
++sw_iter) {
|
||||
// 该系统期望分配的资金
|
||||
price_t will_cash = roundUp(per_cash * (sw_iter->getWeight() / weight_unit), precision);
|
||||
price_t will_cash = roundUp(total_funds * current_weight, precision);
|
||||
if (will_cash <= 0.0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 计算实际可分配的资金
|
||||
// 计算子账户实际可获取的的资金
|
||||
price_t need_cash = will_cash <= can_allocate_cash ? will_cash : can_allocate_cash;
|
||||
|
||||
// 检测是否可以发生交易,不能的话,忽略
|
||||
auto krecord = sw_iter->getSYS()->getTO().getKRecord(date);
|
||||
if (krecord.closePrice < need_cash) {
|
||||
// 如果需要的资金连一手都买不了,直接忽略跳过
|
||||
KRecord krecord =
|
||||
iter->sys->getStock().getKRecord(date, iter->sys->getTO().getQuery().kType());
|
||||
if (krecord.isValid() &&
|
||||
need_cash < (krecord.closePrice * iter->sys->getStock().minTradeNumber())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 尝试从总账户中取出资金存入子账户
|
||||
TMPtr sub_tm = sw_iter->getSYS()->getTM();
|
||||
if (m_shadow_tm->checkout(date, need_cash)) {
|
||||
TMPtr sub_tm = iter->sys->getTM();
|
||||
if (m_cash_tm->checkout(date, need_cash)) {
|
||||
sub_tm->checkin(date, need_cash);
|
||||
can_allocate_cash = roundDown(can_allocate_cash - need_cash, precision);
|
||||
if (can_allocate_cash <= 0.0) {
|
||||
continue;
|
||||
}
|
||||
HKU_INFO_IF(trace, "[AF] ({}, {}, weight: {:<.4f}) fetched cash: {}", iter->sys->name(),
|
||||
iter->sys->getStock().market_code(), current_weight, need_cash);
|
||||
|
||||
// 如果超出允许运行的最大系统数,跳出循环
|
||||
can_run_count++;
|
||||
if (can_run_count > max_num) {
|
||||
break;
|
||||
}
|
||||
// 计算剩余的可用于分配的资金
|
||||
can_allocate_cash = roundDown(can_allocate_cash - need_cash, precision);
|
||||
sum_weight += current_weight;
|
||||
|
||||
} else {
|
||||
HKU_DEBUG_IF(trace, "[AF] {} failed to fetch cash from total account ({})!",
|
||||
iter->sys->name(), m_cash_tm->currentCash());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SystemWeightList AllocateFundsBase::_adjust_with_running(
|
||||
const Datetime& date, const SystemWeightList& se_list,
|
||||
const std::unordered_set<SYSPtr>& running_set) {
|
||||
HKU_CHECK(m_reserve_percent >= 0.0 && m_reserve_percent < 1.0,
|
||||
"Invalid reserve_percent({}) in AF, Calculations that will cause errors!",
|
||||
m_reserve_percent);
|
||||
|
||||
SystemWeightList delay_list;
|
||||
|
||||
bool trace = getParam<bool>("trace");
|
||||
HKU_INFO_IF(trace, "[AF] {} _adjust_with_running", date);
|
||||
HKU_IF_RETURN(se_list.size() == 0, delay_list);
|
||||
|
||||
// 获取计划分配的资产权重
|
||||
SystemWeightList sw_list = _allocateWeight(date, se_list);
|
||||
HKU_IF_RETURN(sw_list.size() == 0, delay_list);
|
||||
|
||||
// 按权重降序排列
|
||||
double can_allocate_weight = 1.0 - m_reserve_percent;
|
||||
adjustWeight(sw_list, can_allocate_weight, getParam<bool>("filter_zero_weight"));
|
||||
|
||||
//-----------------------------------------------------------------
|
||||
// 先将已不在 sw_list 中的运行系统进行清仓,回收可分配资金
|
||||
//-----------------------------------------------------------------
|
||||
std::unordered_set<SYSPtr> running_in_sw_list;
|
||||
for (const auto& sw : sw_list) {
|
||||
if (running_set.find(sw.sys) != running_set.cend()) {
|
||||
running_in_sw_list.insert(sw.sys);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& sys : running_set) {
|
||||
if (running_in_sw_list.find(sys) == running_in_sw_list.cend()) {
|
||||
if (sys->getParam<bool>("buy_delay")) {
|
||||
delay_list.emplace_back(sys, MAX_DOUBLE);
|
||||
} else {
|
||||
// 非延迟卖出的系统,立即强制卖出并回收资金
|
||||
auto tr = sys->sellForceOnClose(date, MAX_DOUBLE, PART_ALLOCATEFUNDS);
|
||||
HKU_DEBUG_IF(trace && tr.isNull(), "[AF] failed to sell: {}", sys->name());
|
||||
if (!tr.isNull()) {
|
||||
auto sub_tm = sys->getTM();
|
||||
auto sub_cash = sub_tm->currentCash();
|
||||
if (sub_tm->checkout(date, sub_cash)) {
|
||||
m_cash_tm->checkin(date, sub_cash);
|
||||
m_tm->addTradeRecord(tr); // 向总账户加入交易记录
|
||||
HKU_INFO_IF(trace, "[AF] Adjust sell: {}, recycle cash: {:<.2f}",
|
||||
sys->name(), sub_cash);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------
|
||||
// 对于仍在选中系统中的运行系统,根据其权重进行减仓处理,回收可分配资金
|
||||
//-----------------------------------------------------------------
|
||||
// 获取当前总资产市值,计算需保留的资产
|
||||
int precision = m_cash_tm->getParam<int>("precision");
|
||||
FundsRecord funds = m_tm->getFunds(date, m_query.kType());
|
||||
price_t total_funds =
|
||||
funds.cash + funds.market_value + funds.borrow_asset - funds.short_market_value;
|
||||
price_t reserve_funds = roundEx(total_funds * m_reserve_percent, precision);
|
||||
|
||||
std::unordered_set<SYSPtr> reduced_running_set; // 缓存已执行过减仓的运行中系统
|
||||
for (auto iter = sw_list.begin(), end_iter = sw_list.end(); iter != end_iter; ++iter) {
|
||||
// 如果当前系统是运行中的系统
|
||||
if (running_set.find(iter->sys) != running_set.cend()) {
|
||||
TMPtr sub_tm = iter->sys->getTM();
|
||||
const KQuery& query = iter->sys->getTO().getQuery();
|
||||
FundsRecord sub_funds = sub_tm->getFunds(date, query.kType());
|
||||
price_t sub_total_funds = sub_funds.cash + sub_funds.market_value +
|
||||
sub_funds.borrow_asset - sub_funds.short_market_value;
|
||||
price_t sub_will_funds = total_funds * iter->weight;
|
||||
if (sub_total_funds > sub_will_funds) {
|
||||
reduced_running_set.insert(iter->sys); // 缓存执行了减仓的系统
|
||||
price_t need_back_funds = sub_total_funds - sub_will_funds;
|
||||
Stock stock = iter->sys->getStock();
|
||||
KRecord krecord = stock.getKRecord(date, query.kType());
|
||||
size_t need_back_shou =
|
||||
size_t(need_back_funds / krecord.closePrice / stock.minTradeNumber());
|
||||
if (need_back_shou > 0) {
|
||||
size_t need_back_num = need_back_shou * stock.minTradeNumber();
|
||||
size_t hold_num = sub_tm->getHoldNumber(date, stock);
|
||||
if (hold_num - need_back_num < stock.minTradeNumber()) {
|
||||
need_back_num = hold_num;
|
||||
}
|
||||
if (iter->sys->getParam<bool>("buy_delay")) {
|
||||
delay_list.emplace_back(iter->sys, need_back_num);
|
||||
} else {
|
||||
auto tr =
|
||||
iter->sys->sellForceOnClose(date, need_back_num, PART_ALLOCATEFUNDS);
|
||||
if (!tr.isNull()) {
|
||||
auto sub_tm = iter->sys->getTM();
|
||||
auto sub_cash = sub_tm->currentCash();
|
||||
if (sub_tm->checkout(date, sub_cash)) {
|
||||
m_cash_tm->checkin(date, sub_cash);
|
||||
m_tm->addTradeRecord(tr); // 向总账户加入交易记录
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------
|
||||
// 遍历当前选中系统,按指定权重分配资金
|
||||
//-----------------------------------------------------------------
|
||||
// 计算可用于分配的现金, 小于等于需保留的资产,则直接返回
|
||||
price_t current_cash = m_cash_tm->currentCash();
|
||||
price_t can_allocate_cash = roundDown(current_cash - reserve_funds, precision);
|
||||
|
||||
HKU_INFO_IF(trace,
|
||||
"can_allocate_weight: {:<.4f}, can_allocate_cash: {:<.2f}, current cash: {:<.2f}, "
|
||||
"total funds: {:<.2f}, "
|
||||
"reserved funds: {:<.2f}",
|
||||
can_allocate_weight, can_allocate_cash, funds.cash, total_funds, reserve_funds);
|
||||
|
||||
HKU_IF_RETURN(can_allocate_cash < 1.0, delay_list);
|
||||
|
||||
// 遍历选中子系统列表,并调整资产
|
||||
price_t sum_weight = 0.0;
|
||||
for (auto iter = sw_list.begin(), end_iter = sw_list.end(); iter != end_iter; ++iter) {
|
||||
if (sum_weight >= can_allocate_weight || can_allocate_cash < 1.0) {
|
||||
break;
|
||||
}
|
||||
|
||||
// 计算实际可用的权重
|
||||
price_t current_weight = iter->weight + sum_weight > can_allocate_weight
|
||||
? iter->weight + sum_weight - can_allocate_weight
|
||||
: iter->weight;
|
||||
|
||||
// 系统期望分配的资产额
|
||||
price_t will_funds = roundUp(total_funds * current_weight, precision);
|
||||
|
||||
// 如果该系统是当前运行中系统
|
||||
if (running_set.find(iter->sys) != running_set.cend()) {
|
||||
auto sub_tm = iter->sys->getTM();
|
||||
const KQuery& query = iter->sys->getTO().getQuery();
|
||||
FundsRecord sub_funds = sub_tm->getFunds(date, query.kType());
|
||||
price_t sub_total_funds = sub_funds.cash + sub_funds.market_value +
|
||||
sub_funds.borrow_asset - sub_funds.short_market_value;
|
||||
|
||||
// 如果是已经执行过减仓的系统
|
||||
if (reduced_running_set.find(iter->sys) != reduced_running_set.cend()) {
|
||||
// 剩余可分配资金不变,已占用权重按实际权重累积
|
||||
sum_weight += sub_total_funds / total_funds;
|
||||
|
||||
} else {
|
||||
// 未执行过减仓的系统,需要予以相应资金分配
|
||||
if (sub_total_funds >= will_funds) {
|
||||
sum_weight += sub_total_funds / total_funds;
|
||||
} else {
|
||||
price_t need_cash = will_funds - sub_total_funds;
|
||||
if (need_cash > can_allocate_cash) {
|
||||
need_cash = can_allocate_cash;
|
||||
}
|
||||
// 如果期望的资金连一手都买不起,则跳过
|
||||
const KQuery& query = iter->sys->getTO().getQuery();
|
||||
auto krecord = iter->sys->getStock().getKRecord(date, query.kType());
|
||||
if (krecord.isValid() &&
|
||||
need_cash < krecord.closePrice * iter->sys->getStock().minTradeNumber()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (m_cash_tm->checkout(date, need_cash)) {
|
||||
sub_tm->checkin(date, need_cash);
|
||||
can_allocate_cash = roundDown(can_allocate_cash - need_cash, precision);
|
||||
// 更新已分配的累积权重
|
||||
sum_weight += (sub_total_funds + need_cash) / total_funds;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// 非运行中的系统
|
||||
// 计算子账户实际可获取的的资金
|
||||
price_t need_cash = will_funds <= can_allocate_cash ? will_funds : can_allocate_cash;
|
||||
|
||||
// 如果期望的资金连一手都买不起,则跳过
|
||||
const KQuery& query = iter->sys->getTO().getQuery();
|
||||
auto krecord = iter->sys->getStock().getKRecord(date, query.kType());
|
||||
if (krecord.isValid() &&
|
||||
need_cash < krecord.closePrice * iter->sys->getStock().minTradeNumber()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 尝试从资金账户中取出资金存入子账户
|
||||
TMPtr sub_tm = iter->sys->getTM();
|
||||
if (m_cash_tm->checkout(date, need_cash)) {
|
||||
sub_tm->checkin(date, need_cash);
|
||||
HKU_INFO_IF(trace, "[AF] {} fetched cash: {}", iter->sys->name(), need_cash);
|
||||
|
||||
// 更新剩余可分配资金
|
||||
can_allocate_cash = roundDown(can_allocate_cash - need_cash, precision);
|
||||
|
||||
// 更新已分配的累积权重
|
||||
sum_weight += iter->weight;
|
||||
|
||||
} else {
|
||||
HKU_DEBUG_IF(trace, "[AF] {} failed to fetch cash from total account!",
|
||||
iter->sys->name());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return delay_list;
|
||||
}
|
||||
|
||||
} /* namespace hku */
|
||||
|
@ -10,7 +10,7 @@
|
||||
#define TRADE_SYS_ALLOCATEFUNDS_ALLOCATEFUNDSBASE_H_
|
||||
|
||||
#include "../../utilities/Parameter.h"
|
||||
#include "../allocatefunds/SystemWeight.h"
|
||||
#include "../selector/SystemWeight.h"
|
||||
|
||||
namespace hku {
|
||||
|
||||
@ -42,15 +42,15 @@ public:
|
||||
void name(const string& name);
|
||||
|
||||
/**
|
||||
* 执行资产分配调整
|
||||
* 执行资产分配调整,仅供 PF 调用
|
||||
* @param date 指定日期
|
||||
* @param se_list 系统实例选择器选出的系统实例
|
||||
* @param running_list 当前运行中的系统实例
|
||||
* @param ignore_list 忽略不进行调仓的运行中系统
|
||||
* @return
|
||||
* @return 需延迟执行卖出操作的系统列表,其中权重为相应需卖出的数量
|
||||
*/
|
||||
void adjustFunds(const Datetime& date, const SystemList& se_list,
|
||||
const std::list<SYSPtr>& running_list, const SystemList& ignore_list);
|
||||
SystemWeightList adjustFunds(const Datetime& date, const SystemWeightList& se_list,
|
||||
const std::unordered_set<SYSPtr>& running_list);
|
||||
|
||||
/** 获取交易账户 */
|
||||
const TMPtr& getTM() const;
|
||||
@ -59,9 +59,9 @@ public:
|
||||
void setTM(const TMPtr&);
|
||||
|
||||
/** 设置 Portfolio 的影子账户, 仅由 Portfolio 调用 */
|
||||
void setShadowTM(const TMPtr&);
|
||||
void setCashTM(const TMPtr&);
|
||||
|
||||
const TMPtr& getShadowTM(const TMPtr&) const;
|
||||
const TMPtr& getCashTM(const TMPtr&) const;
|
||||
|
||||
/** 获取关联查询条件 */
|
||||
const KQuery& getQuery() const;
|
||||
@ -95,34 +95,31 @@ public:
|
||||
|
||||
/**
|
||||
* 子类分配权重接口,获取实际分配资产的系统实例及其权重
|
||||
* @details 实际调用子类接口 _allocateWeight,并根据允许的最大持仓系统数参数对子类返回的
|
||||
* 系统实例及权重列表进行了截断处理
|
||||
* @details 实际调用子类接口 _allocateWeight
|
||||
* @param date 指定日期
|
||||
* @param se_list 系统实例选择器选出的系统实例
|
||||
* @return
|
||||
* @return 子类只需要返回每个系统的相对比例即可
|
||||
*/
|
||||
virtual SystemWeightList _allocateWeight(const Datetime& date, const SystemList& se_list) = 0;
|
||||
virtual SystemWeightList _allocateWeight(const Datetime& date,
|
||||
const SystemWeightList& se_list) = 0;
|
||||
|
||||
private:
|
||||
/* 同时调整已运行中的子系统(已分配资金或已持仓) */
|
||||
void _adjust_with_running(const Datetime& date, const SystemList& se_list,
|
||||
const std::list<SYSPtr>& running_list, const SystemList& ignore_list);
|
||||
SystemWeightList _adjust_with_running(const Datetime& date, const SystemWeightList& se_list,
|
||||
const std::unordered_set<SYSPtr>& running_list);
|
||||
|
||||
/* 不调整已在运行中的子系统 */
|
||||
void _adjust_without_running(const Datetime& date, const SystemList& se_list,
|
||||
const std::list<SYSPtr>& running_list);
|
||||
void _adjust_without_running(const Datetime& date, const SystemWeightList& se_list,
|
||||
const std::unordered_set<SYSPtr>& running_list);
|
||||
|
||||
/* 计算当前的资产总值 */
|
||||
price_t _getTotalFunds(const Datetime& date, const std::list<SYSPtr>& running_list);
|
||||
|
||||
/* 回收系统资产 */
|
||||
bool _returnAssets(const SYSPtr& sys, const Datetime& date);
|
||||
/* 检查分配的权重是否在 0 和 1 之间,如果存在错误,抛出异常,仅在 trace 时生效*/
|
||||
void _check_weight(const SystemWeightList&);
|
||||
|
||||
private:
|
||||
string m_name; // 组件名称
|
||||
KQuery m_query; // 查询条件
|
||||
TMPtr m_tm; // 运行期由PF设定,PF的实际账户
|
||||
TMPtr m_shadow_tm; // 运行期由PF设定,tm 的影子账户,由于协调分配资金
|
||||
string m_name; // 组件名称
|
||||
KQuery m_query; // 查询条件
|
||||
TMPtr m_tm; // 运行期由PF设定,PF的实际账户
|
||||
TMPtr m_cash_tm; // 运行期由PF设定,tm 的影子账户,由于协调分配资金
|
||||
double m_reserve_percent; // 保留资产比例,不参与资产分配
|
||||
|
||||
//============================================
|
||||
@ -137,7 +134,6 @@ private:
|
||||
ar& BOOST_SERIALIZATION_NVP(m_params);
|
||||
ar& BOOST_SERIALIZATION_NVP(m_query);
|
||||
ar& BOOST_SERIALIZATION_NVP(m_reserve_percent);
|
||||
ar& BOOST_SERIALIZATION_NVP(m_tm);
|
||||
}
|
||||
|
||||
template <class Archive>
|
||||
@ -146,7 +142,6 @@ private:
|
||||
ar& BOOST_SERIALIZATION_NVP(m_params);
|
||||
ar& BOOST_SERIALIZATION_NVP(m_query);
|
||||
ar& BOOST_SERIALIZATION_NVP(m_reserve_percent);
|
||||
ar& BOOST_SERIALIZATION_NVP(m_tm);
|
||||
}
|
||||
|
||||
BOOST_SERIALIZATION_SPLIT_MEMBER()
|
||||
@ -187,7 +182,7 @@ public: \
|
||||
virtual AFPtr _clone() override { \
|
||||
return AFPtr(new classname()); \
|
||||
} \
|
||||
virtual SystemWeightList _allocateWeight(const Datetime&, const SystemList&) override;
|
||||
virtual SystemWeightList _allocateWeight(const Datetime&, const SystemWeightList&) override;
|
||||
|
||||
typedef shared_ptr<AllocateFundsBase> AllocateFundsPtr;
|
||||
typedef shared_ptr<AllocateFundsBase> AFPtr;
|
||||
@ -211,12 +206,12 @@ inline void AllocateFundsBase::setTM(const TMPtr& tm) {
|
||||
m_tm = tm;
|
||||
}
|
||||
|
||||
inline void AllocateFundsBase::setShadowTM(const TMPtr& tm) {
|
||||
m_shadow_tm = tm;
|
||||
inline void AllocateFundsBase::setCashTM(const TMPtr& tm) {
|
||||
m_cash_tm = tm;
|
||||
}
|
||||
|
||||
inline const TMPtr& AllocateFundsBase::getShadowTM(const TMPtr&) const {
|
||||
return m_shadow_tm;
|
||||
inline const TMPtr& AllocateFundsBase::getCashTM(const TMPtr&) const {
|
||||
return m_cash_tm;
|
||||
}
|
||||
|
||||
inline const KQuery& AllocateFundsBase::getQuery() const {
|
||||
|
@ -1,111 +0,0 @@
|
||||
/*
|
||||
* SystemWeight.h
|
||||
*
|
||||
* Created on: 2018年1月29日
|
||||
* Author: fasiondog
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#ifndef TRADE_SYS_ALLOCATEFUNDS_SYSTEMWEIGHT_H_
|
||||
#define TRADE_SYS_ALLOCATEFUNDS_SYSTEMWEIGHT_H_
|
||||
|
||||
#include "../system/System.h"
|
||||
|
||||
namespace hku {
|
||||
|
||||
/**
|
||||
* 系统权重,权重有效范围为 [0.0, 1.0]
|
||||
* @details 用于指定系统对应权重告知资产分配算法
|
||||
* @ingroup AllocateFunds
|
||||
*/
|
||||
class HKU_API SystemWeight {
|
||||
public:
|
||||
/** 默认构造函数,缺省权重为1.0 */
|
||||
SystemWeight() : m_weight(1.0) {}
|
||||
|
||||
/**
|
||||
* @brief Construct a new System Weight object
|
||||
*
|
||||
* @param sys 对应的系统
|
||||
* @param weight 对应的权重,须在 [0.0, 1.0] 之间
|
||||
*/
|
||||
SystemWeight(const SystemPtr& sys, double weight) : m_sys(sys), m_weight(1.0) {
|
||||
HKU_CHECK_THROW(weight >= 0.0 && weight <= 1.0, std::out_of_range,
|
||||
"weigth ({}) is out of range [0, 1]!", weight);
|
||||
m_weight = weight;
|
||||
}
|
||||
|
||||
SystemWeight(const SystemWeight&) = default;
|
||||
SystemWeight(SystemWeight&& sw) : m_sys(std::move(sw.m_sys)), m_weight(sw.m_weight) {}
|
||||
|
||||
SystemWeight& operator=(const SystemWeight& other) = default;
|
||||
SystemWeight& operator=(SystemWeight&& other);
|
||||
|
||||
/** 析构函数 */
|
||||
virtual ~SystemWeight() {}
|
||||
|
||||
/** 修改对应的系统 */
|
||||
void setSYS(const SystemPtr& sys) {
|
||||
m_sys = sys;
|
||||
}
|
||||
|
||||
/** 获取对应的系统 */
|
||||
const SystemPtr& getSYS() const {
|
||||
return m_sys;
|
||||
}
|
||||
|
||||
/** 设置权重值,须在[0,1]之间 */
|
||||
void setWeight(double weight) {
|
||||
HKU_CHECK_THROW(weight >= 0.0 && weight <= 1.0, std::out_of_range,
|
||||
"weigth ({}) is out of range [0, 1]!", weight);
|
||||
m_weight = weight;
|
||||
}
|
||||
|
||||
/** 获取权重值 */
|
||||
double getWeight() const {
|
||||
return m_weight;
|
||||
}
|
||||
|
||||
public:
|
||||
SystemPtr m_sys;
|
||||
double m_weight;
|
||||
|
||||
private:
|
||||
//============================================
|
||||
// 序列化支持
|
||||
//============================================
|
||||
#if HKU_SUPPORT_SERIALIZATION
|
||||
private:
|
||||
friend class boost::serialization::access;
|
||||
template <class Archive>
|
||||
void save(Archive& ar, const unsigned int version) const {
|
||||
ar& BOOST_SERIALIZATION_NVP(m_sys);
|
||||
ar& BOOST_SERIALIZATION_NVP(m_weight);
|
||||
}
|
||||
|
||||
template <class Archive>
|
||||
void load(Archive& ar, const unsigned int version) {
|
||||
ar& BOOST_SERIALIZATION_NVP(m_sys);
|
||||
ar& BOOST_SERIALIZATION_NVP(m_weight);
|
||||
}
|
||||
|
||||
BOOST_SERIALIZATION_SPLIT_MEMBER()
|
||||
#endif /* HKU_SUPPORT_SERIALIZATION */
|
||||
};
|
||||
|
||||
typedef vector<SystemWeight> SystemWeightList;
|
||||
|
||||
HKU_API std::ostream& operator<<(std::ostream&, const SystemWeight&);
|
||||
|
||||
inline bool operator==(const SystemWeight& d1, const SystemWeight& d2) {
|
||||
return d1.getSYS() == d2.getSYS() && d1.getWeight() == d2.getWeight();
|
||||
}
|
||||
|
||||
} /* namespace hku */
|
||||
|
||||
#if FMT_VERSION >= 90000
|
||||
template <>
|
||||
struct fmt::formatter<hku::SystemWeight> : ostream_formatter {};
|
||||
#endif
|
||||
|
||||
#endif /* TRADE_SYS_ALLOCATEFUNDS_SYSTEMWEIGHT_H_ */
|
@ -18,11 +18,11 @@ EqualWeightAllocateFunds::EqualWeightAllocateFunds() : AllocateFundsBase("AF_Equ
|
||||
EqualWeightAllocateFunds::~EqualWeightAllocateFunds() {}
|
||||
|
||||
SystemWeightList EqualWeightAllocateFunds ::_allocateWeight(const Datetime& date,
|
||||
const SystemList& se_list) {
|
||||
const SystemWeightList& se_list) {
|
||||
SystemWeightList result;
|
||||
double weight = 1.0 / se_list.size();
|
||||
price_t weight = 1 / se_list.size();
|
||||
for (auto iter = se_list.begin(); iter != se_list.end(); ++iter) {
|
||||
result.emplace_back(*iter, weight);
|
||||
result.emplace_back(iter->sys, weight);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
@ -20,11 +20,11 @@ FixedWeightAllocateFunds::FixedWeightAllocateFunds() : AllocateFundsBase("AF_Fix
|
||||
FixedWeightAllocateFunds::~FixedWeightAllocateFunds() {}
|
||||
|
||||
SystemWeightList FixedWeightAllocateFunds ::_allocateWeight(const Datetime& date,
|
||||
const SystemList& se_list) {
|
||||
const SystemWeightList& se_list) {
|
||||
SystemWeightList result;
|
||||
double weight = getParam<double>("weight");
|
||||
price_t weight = getParam<double>("weight");
|
||||
for (auto iter = se_list.begin(); iter != se_list.end(); ++iter) {
|
||||
result.emplace_back(*iter, weight);
|
||||
result.emplace_back(iter->sys, weight);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
@ -243,7 +243,12 @@ Indicator MultiFactorBase::getIC(int ndays) {
|
||||
}
|
||||
|
||||
Indicator MultiFactorBase::getICIR(int ic_n, int ir_n) {
|
||||
return ICIR(getIC(ic_n), ir_n);
|
||||
Indicator ic = getIC(ic_n);
|
||||
Indicator x = MA(ic, ir_n) / STDEV(ic, ir_n);
|
||||
x.name("ICIR");
|
||||
x.setParam<int>("n", ic_n);
|
||||
x.setParam<int>("rolling_n", ir_n);
|
||||
return x;
|
||||
}
|
||||
|
||||
IndicatorList MultiFactorBase::_getAllReturns(int ndays) const {
|
||||
|
@ -39,54 +39,42 @@ IndicatorList ICIRMultiFactor::_calculate(const vector<IndicatorList>& all_stk_i
|
||||
size_t discard = 0;
|
||||
vector<Indicator> icir(ind_count);
|
||||
for (size_t ii = 0; ii < ind_count; ii++) {
|
||||
icir[ii] = ICIR(IC(m_inds[ii], m_stks, m_query, ic_n, m_ref_stk), ir_n);
|
||||
icir[ii] = ICIR(m_inds[ii], m_stks, m_query, m_ref_stk, ic_n, ir_n);
|
||||
if (icir[ii].discard() > discard) {
|
||||
discard = icir[ii].discard();
|
||||
}
|
||||
}
|
||||
|
||||
// 计算 IC 权重
|
||||
vector<value_t> sumByDate(days_total, 0.0);
|
||||
vector<size_t> countByDate(days_total, 0);
|
||||
for (size_t di = discard; di < days_total; di++) {
|
||||
for (size_t ii = 0; ii < ind_count; ii++) {
|
||||
const auto& value = icir[ii][di];
|
||||
if (!std::isnan(value)) {
|
||||
sumByDate[di] += value;
|
||||
countByDate[di] += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t di = discard; di < days_total; di++) {
|
||||
sumByDate[di] = (countByDate[di] == 0) ? Null<value_t>() : sumByDate[di] / countByDate[di];
|
||||
}
|
||||
|
||||
vector<Indicator> all_factors(stk_count);
|
||||
PriceList new_values(days_total);
|
||||
// 以 ICIR 为权重,计算加权后的合成因子
|
||||
IndicatorList all_factors(stk_count);
|
||||
PriceList new_values(days_total, 0.0);
|
||||
PriceList sum_weight(days_total, 0.0);
|
||||
for (size_t si = 0; si < stk_count; si++) {
|
||||
memset(new_values.data(), 0, sizeof(price_t) * days_total);
|
||||
memset(sum_weight.data(), 0, sizeof(price_t) * days_total);
|
||||
for (size_t di = 0; di < discard; di++) {
|
||||
new_values[di] = Null<price_t>();
|
||||
}
|
||||
for (size_t di = discard; di < days_total; di++) {
|
||||
for (size_t ii = 0; ii < ind_count; ii++) {
|
||||
const auto& value = all_stk_inds[si][ii][di];
|
||||
new_values[di] += value * sumByDate[di];
|
||||
new_values[di] += all_stk_inds[si][ii][di] * icir[ii][di];
|
||||
sum_weight[di] += std::abs(icir[ii][di]);
|
||||
}
|
||||
}
|
||||
for (size_t di = discard; di < days_total; di++) {
|
||||
new_values[di] =
|
||||
(countByDate[di] == 0) ? Null<value_t>() : new_values[di] / countByDate[di];
|
||||
}
|
||||
all_factors[si] = PRICELIST(new_values);
|
||||
all_factors[si].name("IC");
|
||||
|
||||
// 更新 discard
|
||||
for (size_t di = discard; di < days_total; di++) {
|
||||
if (!std::isnan(all_factors[si][di])) {
|
||||
all_factors[si].setDiscard(di);
|
||||
break;
|
||||
if (!std::isnan(new_values[di]) && sum_weight[di] != 0.0) {
|
||||
new_values[di] = new_values[di] / sum_weight[di];
|
||||
}
|
||||
if (di == days_total - 1 && std::isnan(all_factors[si][di])) {
|
||||
all_factors[si].setDiscard(di);
|
||||
}
|
||||
|
||||
all_factors[si] = PRICELIST(new_values);
|
||||
all_factors[si].name("ICIR");
|
||||
|
||||
const auto* data = all_factors[si].data();
|
||||
for (size_t di = discard; di < days_total; di++) {
|
||||
if (!std::isnan(data[di])) {
|
||||
all_factors[si].setDiscard(discard);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -35,59 +35,46 @@ IndicatorList ICMultiFactor::_calculate(const vector<IndicatorList>& all_stk_ind
|
||||
int ic_n = getParam<int>("ic_n");
|
||||
int ic_rolling_n = getParam<int>("ic_rolling_n");
|
||||
|
||||
// 计算每个原始因子的IC值
|
||||
// 计算每个原始因子的滚动IC值
|
||||
size_t discard = 0;
|
||||
IndicatorList ic(ind_count);
|
||||
for (size_t ii = 0; ii < ind_count; ii++) {
|
||||
ic[ii] = MA(IC(m_inds[ii], m_stks, m_query, ic_n, m_ref_stk), ic_rolling_n);
|
||||
ic[ii] = MA(IC(m_inds[ii], m_stks, m_query, m_ref_stk, ic_n), ic_rolling_n);
|
||||
if (ic[ii].discard() > discard) {
|
||||
discard = ic[ii].discard();
|
||||
}
|
||||
}
|
||||
|
||||
// 计算每个原始因子的滚动 IC 权重
|
||||
vector<value_t> sumByDate(days_total, 0.0); // 累积合、权重均值
|
||||
vector<size_t> countByDate(days_total, 0);
|
||||
for (size_t di = discard; di < days_total; di++) {
|
||||
for (size_t ii = 0; ii < ind_count; ii++) {
|
||||
const auto& value = ic[ii][di];
|
||||
if (!std::isnan(value)) {
|
||||
sumByDate[di] += value;
|
||||
countByDate[di] += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t di = discard; di < days_total; di++) {
|
||||
sumByDate[di] = (countByDate[di] == 0) ? Null<value_t>() : sumByDate[di] / countByDate[di];
|
||||
}
|
||||
|
||||
// 对每支证券计算根据滚动 IC 权重计算合成因子
|
||||
// 以滚动 IC 为权重,计算加权后的合成因子
|
||||
IndicatorList all_factors(stk_count);
|
||||
PriceList new_values(days_total, 0);
|
||||
PriceList new_values(days_total, 0.0);
|
||||
PriceList sum_weight(days_total, 0.0);
|
||||
for (size_t si = 0; si < stk_count; si++) {
|
||||
memset(new_values.data(), 0, sizeof(price_t) * days_total);
|
||||
memset(sum_weight.data(), 0, sizeof(price_t) * days_total);
|
||||
for (size_t di = 0; di < discard; di++) {
|
||||
new_values[di] = Null<price_t>();
|
||||
}
|
||||
for (size_t di = discard; di < days_total; di++) {
|
||||
for (size_t ii = 0; ii < ind_count; ii++) {
|
||||
const auto& value = all_stk_inds[si][ii][di];
|
||||
new_values[di] += value * sumByDate[di];
|
||||
new_values[di] += all_stk_inds[si][ii][di] * ic[ii][di];
|
||||
sum_weight[di] += std::abs(ic[ii][di]);
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t di = discard; di < days_total; di++) {
|
||||
new_values[di] =
|
||||
(countByDate[di] == 0) ? Null<value_t>() : new_values[di] / countByDate[di];
|
||||
if (!std::isnan(new_values[di]) && sum_weight[di] != 0.0) {
|
||||
new_values[di] = new_values[di] / sum_weight[di];
|
||||
}
|
||||
}
|
||||
|
||||
all_factors[si] = PRICELIST(new_values);
|
||||
all_factors[si].name("IC");
|
||||
|
||||
// 更新 discard
|
||||
const auto* data = all_factors[si].data();
|
||||
for (size_t di = discard; di < days_total; di++) {
|
||||
if (!std::isnan(all_factors[si][di])) {
|
||||
all_factors[si].setDiscard(di);
|
||||
break;
|
||||
}
|
||||
if (di == days_total - 1 && std::isnan(all_factors[si][di])) {
|
||||
all_factors[si].setDiscard(di);
|
||||
if (!std::isnan(data[di])) {
|
||||
all_factors[si].setDiscard(discard);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -112,7 +112,8 @@ double MoneyManagerBase::getBuyNumber(const Datetime& datetime, const Stock& sto
|
||||
double min_trade = stock.minTradeNumber();
|
||||
|
||||
if (n < min_trade) {
|
||||
HKU_TRACE("Ignore! Is less than the minimum number of transactions({})", min_trade);
|
||||
HKU_TRACE("Ignore! Is less than the minimum number of transactions({}<{}) {}", n, min_trade,
|
||||
stock.market_code());
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -33,11 +33,13 @@ HKU_API std::ostream& operator<<(std::ostream& os, const PortfolioPtr& pf) {
|
||||
|
||||
Portfolio::Portfolio()
|
||||
: m_name("Portfolio"), m_query(Null<KQuery>()), m_is_ready(false), m_need_calculate(true) {
|
||||
setParam<bool>("trace", false); // 打印跟踪
|
||||
setParam<int>("adjust_cycle", 1); // 调仓周期
|
||||
setParam<bool>("trace", false); // 打印跟踪
|
||||
}
|
||||
|
||||
Portfolio::Portfolio(const string& name)
|
||||
: m_name(name), m_query(Null<KQuery>()), m_is_ready(false), m_need_calculate(true) {
|
||||
setParam<int>("adjust_cycle", 1); // 调仓周期
|
||||
setParam<bool>("trace", false);
|
||||
}
|
||||
|
||||
@ -49,6 +51,7 @@ Portfolio::Portfolio(const TradeManagerPtr& tm, const SelectorPtr& se, const AFP
|
||||
m_query(Null<KQuery>()),
|
||||
m_is_ready(false),
|
||||
m_need_calculate(true) {
|
||||
setParam<int>("adjust_cycle", 1); // 调仓周期
|
||||
setParam<bool>("trace", false);
|
||||
}
|
||||
|
||||
@ -59,14 +62,13 @@ void Portfolio::reset() {
|
||||
m_pro_sys_list.clear();
|
||||
m_real_sys_list.clear();
|
||||
m_running_sys_set.clear();
|
||||
m_running_sys_list.clear();
|
||||
m_tmp_selected_list_on_open.clear();
|
||||
m_tmp_selected_list_on_close.clear();
|
||||
m_delay_adjust_sys_list.clear();
|
||||
m_tmp_selected_list.clear();
|
||||
m_tmp_will_remove_sys.clear();
|
||||
if (m_tm)
|
||||
m_tm->reset();
|
||||
if (m_shadow_tm)
|
||||
m_shadow_tm->reset();
|
||||
if (m_cash_tm)
|
||||
m_cash_tm->reset();
|
||||
if (m_se)
|
||||
m_se->reset();
|
||||
if (m_af)
|
||||
@ -88,8 +90,8 @@ PortfolioPtr Portfolio::clone() {
|
||||
p->m_af = m_af->clone();
|
||||
if (m_tm)
|
||||
p->m_tm = m_tm->clone();
|
||||
if (m_shadow_tm)
|
||||
p->m_shadow_tm = m_shadow_tm->clone();
|
||||
if (m_cash_tm)
|
||||
p->m_cash_tm = m_cash_tm->clone();
|
||||
return p;
|
||||
}
|
||||
|
||||
@ -118,9 +120,9 @@ bool Portfolio::_readyForRun() {
|
||||
reset();
|
||||
|
||||
// 将影子账户指定给资产分配器
|
||||
m_shadow_tm = m_tm->clone();
|
||||
m_cash_tm = m_tm->clone();
|
||||
m_af->setTM(m_tm);
|
||||
m_af->setShadowTM(m_shadow_tm);
|
||||
m_af->setCashTM(m_cash_tm);
|
||||
|
||||
// 为资金分配器设置关联查询条件
|
||||
m_af->setQuery(m_query);
|
||||
@ -144,7 +146,7 @@ bool Portfolio::_readyForRun() {
|
||||
// 为内部实际执行的系统创建初始资金为0的子账户
|
||||
sys->setTM(pro_tm->clone());
|
||||
sys->getTM()->name(fmt::format("TM_SUB{}", i));
|
||||
sys->name(fmt::format("PF_Real_{}_{}", i, sys->name()));
|
||||
sys->name(fmt::format("PF_Real_{}_{}_{}", i, sys->name(), sys->getStock().market_code()));
|
||||
|
||||
HKU_CHECK(sys->readyForRun() && pro_sys->readyForRun(),
|
||||
"Exist invalid system, it could not ready for run!");
|
||||
@ -160,132 +162,262 @@ bool Portfolio::_readyForRun() {
|
||||
return true;
|
||||
}
|
||||
|
||||
void Portfolio::_runMoment(const Datetime& date) {
|
||||
// 当前日期小于账户建立日期,直接忽略
|
||||
HKU_IF_RETURN(date < m_shadow_tm->initDatetime(), void());
|
||||
void Portfolio::run(const KQuery& query, int adjust_cycle, bool force) {
|
||||
HKU_CHECK(adjust_cycle > 0, "Invalid param adjust_cycle! {}", adjust_cycle);
|
||||
setParam<int>("adjust_cycle", adjust_cycle);
|
||||
|
||||
_runMomentOnOpen(date);
|
||||
_runMomentOnClose(date);
|
||||
|
||||
// 释放掉临时数据占用的内存
|
||||
m_running_sys_set = std::unordered_set<System*>();
|
||||
m_running_sys_list = std::list<SYSPtr>();
|
||||
m_tmp_selected_list_on_open = SystemList();
|
||||
m_tmp_selected_list_on_close = SystemList();
|
||||
m_tmp_will_remove_sys = SystemList();
|
||||
}
|
||||
|
||||
void Portfolio::_runMomentOnOpen(const Datetime& date) {
|
||||
// 从选股策略获取当前选中的系统列表
|
||||
m_tmp_selected_list_on_open = m_se->getSelectedOnOpen(date);
|
||||
|
||||
// 资产分配算法调整各子系统资产分配,忽略上一周期收盘时选中的系统
|
||||
m_af->adjustFunds(date, m_tmp_selected_list_on_open, m_running_sys_list,
|
||||
m_tmp_selected_list_on_close);
|
||||
|
||||
// 由于系统的交易指令可能被延迟执行,需要保存并判断
|
||||
// 遍历当前运行中的子系统,如果已没有分配资金和持仓,则放入待移除列表
|
||||
m_tmp_will_remove_sys.clear();
|
||||
int precision = m_shadow_tm->getParam<int>("precision");
|
||||
for (auto& running_sys : m_running_sys_list) {
|
||||
Stock stock = running_sys->getStock();
|
||||
TMPtr sub_tm = running_sys->getTM();
|
||||
PositionRecord position = sub_tm->getPosition(date, stock);
|
||||
price_t cash = sub_tm->cash(date, m_query.kType());
|
||||
|
||||
// 已没有持仓且没有现金,则放入待移除列表
|
||||
if (position.number == 0 && cash <= precision) {
|
||||
if (cash != 0) {
|
||||
sub_tm->checkout(date, cash);
|
||||
m_shadow_tm->checkin(date, cash);
|
||||
}
|
||||
m_tmp_will_remove_sys.push_back(running_sys);
|
||||
}
|
||||
}
|
||||
|
||||
// 依据待移除列表将系统从运行中系统列表里删除
|
||||
for (auto& sub_sys : m_tmp_will_remove_sys) {
|
||||
m_running_sys_list.remove(sub_sys);
|
||||
m_running_sys_set.erase(sub_sys.get());
|
||||
}
|
||||
|
||||
// 遍历本次选择的系统列表,如果存在分配资金且不在运行中列表内,则加入运行列表
|
||||
for (auto& sub_sys : m_tmp_selected_list_on_open) {
|
||||
price_t cash = sub_sys->getTM()->cash(date, m_query.kType());
|
||||
if (cash > 0.0 && m_running_sys_set.find(sub_sys.get()) == m_running_sys_set.end()) {
|
||||
m_running_sys_list.push_back(sub_sys);
|
||||
m_running_sys_set.insert(sub_sys.get());
|
||||
}
|
||||
}
|
||||
|
||||
// 在开盘时执行所有运行中的非延迟交易系统系统
|
||||
for (auto& sub_sys : m_running_sys_list) {
|
||||
if (!sub_sys->getParam<bool>("buy_delay")) {
|
||||
auto tr = sub_sys->runMoment(date);
|
||||
if (!tr.isNull()) {
|
||||
m_tm->addTradeRecord(tr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Portfolio::_runMomentOnClose(const Datetime& date) {
|
||||
// 从选股策略获取当前选中的系统列表
|
||||
m_tmp_selected_list_on_close = m_se->getSelectedOnClose(date);
|
||||
|
||||
// 资产分配算法调整各子系统资产分配,忽略开盘时选中的系统
|
||||
m_af->adjustFunds(date, m_tmp_selected_list_on_close, m_running_sys_list,
|
||||
m_tmp_selected_list_on_open);
|
||||
if (getParam<bool>("trace") &&
|
||||
(!m_tmp_selected_list_on_open.empty() || !m_tmp_selected_list_on_close.empty())) {
|
||||
HKU_INFO("{} ===========================================================", date);
|
||||
for (auto& sys : m_tmp_selected_list_on_open) {
|
||||
HKU_INFO("select on open: {}, cash: {}", sys->getTO().getStock(),
|
||||
sys->getTM()->cash(date, m_query.kType()));
|
||||
}
|
||||
for (auto& sys : m_tmp_selected_list_on_close) {
|
||||
HKU_INFO("select on close: {}, cash: {}", sys->getTO().getStock(),
|
||||
sys->getTM()->cash(date, m_query.kType()));
|
||||
}
|
||||
}
|
||||
|
||||
// 如果选中的系统不在已有列表中,且账户已经被分配了资金,则将其加入运行系统,并执行
|
||||
for (auto& sys : m_tmp_selected_list_on_close) {
|
||||
if (m_running_sys_set.find(sys.get()) == m_running_sys_set.end()) {
|
||||
TMPtr tm = sys->getTM();
|
||||
if (tm->cash(date, m_query.kType()) > 0.0) {
|
||||
m_running_sys_list.push_back(sys);
|
||||
m_running_sys_set.insert(sys.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 执行所有非延迟运行中系统
|
||||
for (auto& sub_sys : m_running_sys_list) {
|
||||
if (sub_sys->getParam<bool>("buy_delay")) {
|
||||
auto tr = sub_sys->runMoment(date);
|
||||
if (!tr.isNull()) {
|
||||
m_tm->addTradeRecord(tr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Portfolio::run(const KQuery& query, bool force) {
|
||||
setQuery(query);
|
||||
if (force) {
|
||||
m_need_calculate = true;
|
||||
}
|
||||
HKU_IF_RETURN(!m_need_calculate, void());
|
||||
HKU_ERROR_IF_RETURN(!_readyForRun(), void(),
|
||||
"readyForRun fails, check to see if a valid TradeManager, Selector, or "
|
||||
"AllocateFunds instance have been specified.");
|
||||
HKU_CHECK(_readyForRun(),
|
||||
"readyForRun fails, check to see if a valid TradeManager, Selector, or "
|
||||
"AllocateFunds instance have been specified.");
|
||||
|
||||
DatetimeList datelist = StockManager::instance().getTradingCalendar(query);
|
||||
for (const auto& date : datelist) {
|
||||
_runMoment(date);
|
||||
HKU_IF_RETURN(datelist.empty(), void());
|
||||
|
||||
size_t cur_adjust_ix = 0;
|
||||
for (size_t i = 0, total = datelist.size(); i < total; i++) {
|
||||
bool adjust = false;
|
||||
if (i == cur_adjust_ix) {
|
||||
adjust = true;
|
||||
cur_adjust_ix += adjust_cycle;
|
||||
}
|
||||
|
||||
const auto& date = datelist[i];
|
||||
_runMoment(date, adjust);
|
||||
}
|
||||
|
||||
m_need_calculate = false;
|
||||
|
||||
// 释放掉临时数据占用的内存
|
||||
m_tmp_selected_list = SystemWeightList();
|
||||
m_tmp_will_remove_sys = SystemWeightList();
|
||||
}
|
||||
|
||||
void Portfolio::_runMoment(const Datetime& date, bool adjust) {
|
||||
// 当前日期小于账户建立日期,直接忽略
|
||||
HKU_IF_RETURN(date < m_cash_tm->initDatetime(), void());
|
||||
|
||||
bool trace = getParam<bool>("trace");
|
||||
HKU_INFO_IF(trace, "{} ===========================================================", date);
|
||||
HKU_INFO_IF(trace && adjust, "[PF] Position adjustment will be made today.");
|
||||
HKU_INFO_IF(trace, "[PF] current running system size: {}", m_running_sys_set.size());
|
||||
|
||||
//---------------------------------------------------
|
||||
// 开盘前处理各个子账户、资金账户、总账户之间可能的误差
|
||||
//---------------------------------------------------
|
||||
int precision = m_tm->getParam<int>("precision");
|
||||
|
||||
// 更新所有运行中系统的权息
|
||||
price_t sum_cash = 0.0;
|
||||
for (auto& running_sys : m_running_sys_set) {
|
||||
TMPtr sub_tm = running_sys->getTM();
|
||||
sub_tm->updateWithWeight(date);
|
||||
sum_cash += sub_tm->currentCash();
|
||||
}
|
||||
|
||||
// 开盘前,调整账户权息,并进行轧差处理(平衡 sub_sys, cash_tm, tm 之间的误差)
|
||||
m_tm->updateWithWeight(date);
|
||||
|
||||
HKU_INFO_IF(trace, "[PF] The sum cash of sub_tm: {}, cash tm: {}, tm cash: {}", sum_cash,
|
||||
m_cash_tm->currentCash(), m_tm->currentCash());
|
||||
sum_cash += m_cash_tm->currentCash();
|
||||
|
||||
price_t diff = roundEx(std::abs(m_tm->currentCash() - sum_cash), precision);
|
||||
if (diff > 0.) {
|
||||
if (m_tm->currentCash() > sum_cash) {
|
||||
m_cash_tm->checkin(date, diff);
|
||||
} else if (m_tm->currentCash() < sum_cash) {
|
||||
if (!m_cash_tm->checkout(date, diff)) {
|
||||
m_tm->checkin(date, diff);
|
||||
}
|
||||
}
|
||||
HKU_INFO_IF(trace, "After compensate: the sum cash of sub_tm: {}, cash tm: {}, tm cash: {}",
|
||||
sum_cash, m_cash_tm->currentCash(), m_tm->currentCash());
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// 跟踪打印执行调仓前的资产情况
|
||||
//----------------------------------------------------------------------
|
||||
if (trace) {
|
||||
auto funds = m_tm->getFunds(date, m_query.kType());
|
||||
HKU_INFO("[PF] [beforce adjust] - total funds: {}, cash: {}, market_value: {}",
|
||||
funds.cash + funds.market_value, funds.cash, funds.market_value);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// 开盘时,优先处理上一交易日确定的需延迟调仓卖出的系统,在开盘时先卖出调整
|
||||
//----------------------------------------------------------------------
|
||||
for (auto& sys : m_delay_adjust_sys_list) {
|
||||
auto tr = sys.sys->sellForceOnOpen(date, sys.weight, PART_PORTFOLIO);
|
||||
HKU_DEBUG_IF(trace && tr.isNull(), "[PF] Failed to force sell: {}", sys.sys->name());
|
||||
if (!tr.isNull()) {
|
||||
HKU_INFO_IF(trace, "[PF] Delay adjust sell: {}", tr);
|
||||
m_tm->addTradeRecord(tr);
|
||||
|
||||
// 卖出后,尝试将资金取出转移至影子总账户
|
||||
TMPtr sub_tm = sys.sys->getTM();
|
||||
auto sub_cash = sub_tm->currentCash();
|
||||
if (sub_cash > 0.0 && sub_tm->checkout(date, sub_cash)) {
|
||||
m_cash_tm->checkin(date, sub_cash);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 清空,避免循环至下一个非调仓日时被重复处理
|
||||
m_delay_adjust_sys_list.clear();
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// 遍历当前运行中的子系统,如果已没有分配资金和持仓,则回收
|
||||
//---------------------------------------------------------------
|
||||
m_tmp_will_remove_sys.clear();
|
||||
for (auto& running_sys : m_running_sys_set) {
|
||||
Stock stock = running_sys->getStock();
|
||||
TMPtr sub_tm = running_sys->getTM();
|
||||
PositionRecord position = sub_tm->getPosition(date, stock);
|
||||
price_t cash = sub_tm->currentCash();
|
||||
|
||||
price_t min_cash = 1.0;
|
||||
KRecord krecord = stock.getKRecord(date);
|
||||
if (krecord.isValid()) {
|
||||
min_cash = krecord.openPrice * stock.minTradeNumber();
|
||||
}
|
||||
|
||||
// 如果系统的剩余资金小于交易一手的资金,则回收资金
|
||||
if (cash != 0 && cash <= min_cash) {
|
||||
sub_tm->checkout(date, cash);
|
||||
m_cash_tm->checkin(date, cash);
|
||||
HKU_INFO_IF(trace, "Recycle cash ({:<.2f}) from {}, current cash: {}", cash,
|
||||
running_sys->name(), m_cash_tm->currentCash());
|
||||
// 如果已经没有持仓,则回收
|
||||
if (position.number == 0) {
|
||||
m_tmp_will_remove_sys.emplace_back(running_sys, 0.);
|
||||
HKU_INFO_IF(trace, "[PF] Recycle running sys: {}", running_sys->name());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 依据待移除列表将系统从运行中系统列表里删除
|
||||
for (auto& sub_sys : m_tmp_will_remove_sys) {
|
||||
HKU_INFO_IF(trace, "Recycling system {}", sub_sys.sys->name());
|
||||
m_running_sys_set.erase(sub_sys.sys);
|
||||
}
|
||||
|
||||
//---------------------------------------------------
|
||||
// 调仓日,进行资金分配调整
|
||||
//---------------------------------------------------
|
||||
if (adjust) {
|
||||
// 从选股策略获取选中的系统列表
|
||||
m_tmp_selected_list = m_se->getSelected(date);
|
||||
|
||||
if (trace && !m_tmp_selected_list.empty()) {
|
||||
for (auto& sys : m_tmp_selected_list) {
|
||||
HKU_INFO("[PF] select: {}, score: {:<.4f}", sys.sys->name(), sys.weight);
|
||||
}
|
||||
}
|
||||
|
||||
// 资产分配算法调整各子系统资产分配
|
||||
m_delay_adjust_sys_list = m_af->adjustFunds(date, m_tmp_selected_list, m_running_sys_set);
|
||||
|
||||
// 如果选中的系统不在已有列表中,且账户已经被分配了资金,则将其加入运行系统,并执行
|
||||
for (auto& sys : m_tmp_selected_list) {
|
||||
if (m_running_sys_set.find(sys.sys) == m_running_sys_set.end()) {
|
||||
if (sys.sys->getTM()->cash(date, m_query.kType()) > 0.0) {
|
||||
m_running_sys_set.insert(sys.sys);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 从已运行系统列表中立即移除已没有持仓且没有资金的非延迟买入的系统
|
||||
m_tmp_will_remove_sys.clear();
|
||||
for (auto& sys : m_running_sys_set) {
|
||||
auto sub_tm = sys->getTM();
|
||||
if (!sys->getParam<bool>("buy_delay") && sub_tm->currentCash() < 1.0 &&
|
||||
0 == sub_tm->getHoldNumber(date, sys->getStock())) {
|
||||
m_tmp_will_remove_sys.emplace_back(sys, 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& sw : m_tmp_will_remove_sys) {
|
||||
m_running_sys_set.erase(sw.sys);
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// 跟踪打印执行调仓后的资产情况
|
||||
//----------------------------------------------------------------------
|
||||
if (trace) {
|
||||
auto funds = m_tm->getFunds(date, m_query.kType());
|
||||
HKU_INFO("[PF] [after adjust] - total funds: {}, cash: {}, market_value: {}",
|
||||
funds.cash + funds.market_value, funds.cash, funds.market_value);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// 执行所有运行中的系统,无论是延迟还是非延迟,当天运行中的系统都需要被执行一次
|
||||
//----------------------------------------------------------------------------
|
||||
for (auto& sub_sys : m_running_sys_set) {
|
||||
auto tr = sub_sys->runMoment(date);
|
||||
if (!tr.isNull()) {
|
||||
HKU_INFO_IF(trace, "[PF] {}", tr);
|
||||
m_tm->addTradeRecord(tr);
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// 跟踪各个子系统执行后的资产情况
|
||||
//----------------------------------------------------------------------
|
||||
if (trace) {
|
||||
auto funds = m_tm->getFunds(date, m_query.kType());
|
||||
HKU_INFO("[PF] [after run] - total funds: {}, cash: {}, market_value: {}",
|
||||
funds.cash + funds.market_value, funds.cash, funds.market_value);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// 跟踪打印持仓情况
|
||||
//----------------------------------------------------------------------
|
||||
if (trace && !m_running_sys_set.empty()) {
|
||||
HKU_INFO(
|
||||
"+------------+------------+------------+--------------+--------------+-------------+");
|
||||
HKU_INFO(
|
||||
"| code | name | position | market value | remain cash | close price |");
|
||||
HKU_INFO(
|
||||
"+------------+------------+------------+--------------+--------------+-------------+");
|
||||
|
||||
size_t count = 0;
|
||||
for (const auto& sys : m_running_sys_set) {
|
||||
Stock stk = sys->getStock();
|
||||
auto funds = sys->getTM()->getFunds(date, m_query.kType());
|
||||
size_t position = sys->getTM()->getHoldNumber(date, stk);
|
||||
#if HKU_OS_WINDOWS
|
||||
auto stk_name = StockManager::instance().runningInPython() &&
|
||||
StockManager::instance().pythonInJupyter()
|
||||
? stk.name()
|
||||
: UTF8ToGB(stk.name());
|
||||
if (stk_name.size() < 11) {
|
||||
for (size_t i = 0, total = 11 - stk_name.size(); i < total; i++) {
|
||||
stk_name.push_back(' ');
|
||||
}
|
||||
}
|
||||
HKU_INFO("| {:<11}| {}| {:<11}| {:<13.2f}| {:<13.2f}| {:<12.2f}|", stk.market_code(),
|
||||
stk_name, position, funds.market_value, funds.cash,
|
||||
stk.getKRecord(date, m_query.kType()).closePrice);
|
||||
#else
|
||||
auto stk_name = stk.name();
|
||||
HKU_INFO("| {:<11}| {:<11}| {:<11}| {:<13.2f}| {:<13.2f}| {:<12.2f}|",
|
||||
stk.market_code(), stk_name, position, funds.market_value, funds.cash,
|
||||
stk.getKRecord(date, m_query.kType()).closePrice);
|
||||
#endif
|
||||
HKU_INFO(
|
||||
"+------------+------------+------------+--------------+--------------+-------------"
|
||||
"+");
|
||||
if (++count >= 10) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} /* namespace hku */
|
||||
|
@ -55,7 +55,7 @@ public:
|
||||
* @param query 查询条件
|
||||
* @param force 是否强制重计算
|
||||
*/
|
||||
void run(const KQuery& query, bool force = false);
|
||||
void run(const KQuery& query, int adjust_cycle = 1, bool force = false);
|
||||
|
||||
/** 修改查询条件 */
|
||||
void setQuery(const KQuery& query);
|
||||
@ -99,14 +99,12 @@ private:
|
||||
/** 运行前准备 */
|
||||
bool _readyForRun();
|
||||
|
||||
void _runMoment(const Datetime& datetime);
|
||||
void _runMomentOnOpen(const Datetime& datetime);
|
||||
void _runMomentOnClose(const Datetime& datetime);
|
||||
void _runMoment(const Datetime& date, bool adjust);
|
||||
|
||||
protected:
|
||||
string m_name;
|
||||
TMPtr m_tm;
|
||||
TMPtr m_shadow_tm;
|
||||
TMPtr m_cash_tm; // 仅仅负责内部资金的管理(即只需要 checkout 到子账号, 从账户checkin现金)
|
||||
SEPtr m_se;
|
||||
AFPtr m_af;
|
||||
|
||||
@ -118,11 +116,10 @@ protected:
|
||||
SystemList m_real_sys_list; // 所有实际运行的子系统列表
|
||||
|
||||
// 用于中间计算的临时数据
|
||||
std::unordered_set<System*> m_running_sys_set; // 当前仍在运行的子系统集合
|
||||
std::list<SYSPtr> m_running_sys_list; // 当前仍在运行的子系统列表
|
||||
SystemList m_tmp_selected_list_on_open;
|
||||
SystemList m_tmp_selected_list_on_close;
|
||||
SystemList m_tmp_will_remove_sys;
|
||||
std::unordered_set<SYSPtr> m_running_sys_set;
|
||||
SystemWeightList m_delay_adjust_sys_list; // 延迟调仓卖出的系统列表
|
||||
SystemWeightList m_tmp_selected_list;
|
||||
SystemWeightList m_tmp_will_remove_sys;
|
||||
|
||||
//============================================
|
||||
// 序列化支持
|
||||
@ -135,7 +132,7 @@ private:
|
||||
ar& BOOST_SERIALIZATION_NVP(m_name);
|
||||
ar& BOOST_SERIALIZATION_NVP(m_params);
|
||||
ar& BOOST_SERIALIZATION_NVP(m_tm);
|
||||
ar& BOOST_SERIALIZATION_NVP(m_shadow_tm);
|
||||
ar& BOOST_SERIALIZATION_NVP(m_cash_tm);
|
||||
ar& BOOST_SERIALIZATION_NVP(m_se);
|
||||
ar& BOOST_SERIALIZATION_NVP(m_af);
|
||||
ar& BOOST_SERIALIZATION_NVP(m_query);
|
||||
@ -148,7 +145,7 @@ private:
|
||||
ar& BOOST_SERIALIZATION_NVP(m_name);
|
||||
ar& BOOST_SERIALIZATION_NVP(m_params);
|
||||
ar& BOOST_SERIALIZATION_NVP(m_tm);
|
||||
ar& BOOST_SERIALIZATION_NVP(m_shadow_tm);
|
||||
ar& BOOST_SERIALIZATION_NVP(m_cash_tm);
|
||||
ar& BOOST_SERIALIZATION_NVP(m_se);
|
||||
ar& BOOST_SERIALIZATION_NVP(m_af);
|
||||
ar& BOOST_SERIALIZATION_NVP(m_query);
|
||||
|
@ -6,7 +6,6 @@
|
||||
*/
|
||||
|
||||
#include "SelectorBase.h"
|
||||
#include "../portfolio/Portfolio.h"
|
||||
|
||||
namespace hku {
|
||||
|
||||
|
@ -10,9 +10,10 @@
|
||||
#define TRADE_SYS_SELECTOR_SELECTORBASE_H_
|
||||
|
||||
#include "../system/System.h"
|
||||
#include "../allocatefunds/AllocateFundsBase.h"
|
||||
#include "../../KData.h"
|
||||
#include "../../utilities/Parameter.h"
|
||||
#include "hikyuu/trade_sys/allocatefunds/AllocateFundsBase.h"
|
||||
#include "SystemWeight.h"
|
||||
|
||||
namespace hku {
|
||||
|
||||
@ -96,11 +97,8 @@ public:
|
||||
/** 子类计算接口 */
|
||||
virtual void _calculate() = 0;
|
||||
|
||||
/** 子类获取指定时刻开盘时选中的标的 */
|
||||
virtual SystemList getSelectedOnOpen(Datetime date) = 0;
|
||||
|
||||
/** 子类获取指定时刻收盘时选中的标的 */
|
||||
virtual SystemList getSelectedOnClose(Datetime date) = 0;
|
||||
virtual SystemWeightList getSelected(Datetime date) = 0;
|
||||
|
||||
virtual bool isMatchAF(const AFPtr& af) = 0;
|
||||
|
||||
@ -166,14 +164,13 @@ private: \
|
||||
#define SELECTOR_NO_PRIVATE_MEMBER_SERIALIZATION
|
||||
#endif
|
||||
|
||||
#define SELECTOR_IMP(classname) \
|
||||
public: \
|
||||
virtual SelectorPtr _clone() override { \
|
||||
return SelectorPtr(new classname()); \
|
||||
} \
|
||||
virtual SystemList getSelectedOnOpen(Datetime date) override; \
|
||||
virtual SystemList getSelectedOnClose(Datetime date) override; \
|
||||
virtual bool isMatchAF(const AFPtr& af) override; \
|
||||
#define SELECTOR_IMP(classname) \
|
||||
public: \
|
||||
virtual SelectorPtr _clone() override { \
|
||||
return SelectorPtr(new classname()); \
|
||||
} \
|
||||
virtual SystemWeightList getSelected(Datetime date) override; \
|
||||
virtual bool isMatchAF(const AFPtr& af) override; \
|
||||
virtual void _calculate() override;
|
||||
|
||||
/**
|
||||
|
@ -5,7 +5,7 @@
|
||||
* Author: fasiondog
|
||||
*/
|
||||
|
||||
#include "../allocatefunds/SystemWeight.h"
|
||||
#include "SystemWeight.h"
|
||||
|
||||
namespace hku {
|
||||
|
||||
@ -15,16 +15,16 @@ HKU_API std::ostream& operator<<(std::ostream& os, const SystemWeight& sw) {
|
||||
|
||||
string name("NULL");
|
||||
string stk_name("(Stock(NULL))");
|
||||
if (sw.getSYS()) {
|
||||
name = sw.getSYS()->name();
|
||||
if (sw.sys) {
|
||||
name = sw.sys->name();
|
||||
|
||||
Stock stk = sw.getSYS()->getStock();
|
||||
Stock stk = sw.sys->getStock();
|
||||
if (!stk.isNull()) {
|
||||
stk_name = "(Stock(" + stk.market_code() + "))";
|
||||
}
|
||||
}
|
||||
|
||||
os << "SystemWeight(sys: " << name << stk_name << ", weight: " << sw.getWeight() << ")";
|
||||
os << "SystemWeight(sys: " << name << stk_name << ", weight: " << sw.weight << ")";
|
||||
|
||||
os.unsetf(std::ostream::floatfield);
|
||||
os.precision();
|
||||
@ -33,8 +33,8 @@ HKU_API std::ostream& operator<<(std::ostream& os, const SystemWeight& sw) {
|
||||
|
||||
SystemWeight& SystemWeight::operator=(SystemWeight&& other) {
|
||||
if (this != &other) {
|
||||
m_sys = std::move(other.m_sys);
|
||||
m_weight = other.m_weight;
|
||||
sys = std::move(other.sys);
|
||||
weight = other.weight;
|
||||
}
|
||||
return *this;
|
||||
}
|
70
hikyuu_cpp/hikyuu/trade_sys/selector/SystemWeight.h
Normal file
70
hikyuu_cpp/hikyuu/trade_sys/selector/SystemWeight.h
Normal file
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* SystemWeight.h
|
||||
*
|
||||
* Created on: 2018年1月29日
|
||||
* Author: fasiondog
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#ifndef TRADE_SYS_ALLOCATEFUNDS_SYSTEMWEIGHT_H_
|
||||
#define TRADE_SYS_ALLOCATEFUNDS_SYSTEMWEIGHT_H_
|
||||
|
||||
#include "../system/System.h"
|
||||
|
||||
namespace hku {
|
||||
|
||||
/**
|
||||
* 系统权重,权重有效范围为 [0.0, 1.0]
|
||||
* @details 用于指定系统对应权重告知资产分配算法
|
||||
* @ingroup Selector
|
||||
*/
|
||||
struct HKU_API SystemWeight {
|
||||
SystemPtr sys;
|
||||
price_t weight{1.0};
|
||||
|
||||
SystemWeight() = default;
|
||||
SystemWeight(const SystemPtr& sys_, double weight_) : sys(sys_), weight(weight_) {}
|
||||
SystemWeight(const SystemWeight&) = default;
|
||||
SystemWeight(SystemWeight&& sw) : sys(std::move(sw.sys)), weight(sw.weight) {}
|
||||
SystemWeight& operator=(const SystemWeight& other) = default;
|
||||
SystemWeight& operator=(SystemWeight&& other);
|
||||
|
||||
private:
|
||||
//============================================
|
||||
// 序列化支持
|
||||
//============================================
|
||||
#if HKU_SUPPORT_SERIALIZATION
|
||||
private:
|
||||
friend class boost::serialization::access;
|
||||
template <class Archive>
|
||||
void save(Archive& ar, const unsigned int version) const {
|
||||
ar& BOOST_SERIALIZATION_NVP(sys);
|
||||
ar& BOOST_SERIALIZATION_NVP(weight);
|
||||
}
|
||||
|
||||
template <class Archive>
|
||||
void load(Archive& ar, const unsigned int version) {
|
||||
ar& BOOST_SERIALIZATION_NVP(sys);
|
||||
ar& BOOST_SERIALIZATION_NVP(weight);
|
||||
}
|
||||
|
||||
BOOST_SERIALIZATION_SPLIT_MEMBER()
|
||||
#endif /* HKU_SUPPORT_SERIALIZATION */
|
||||
};
|
||||
|
||||
typedef vector<SystemWeight> SystemWeightList;
|
||||
|
||||
HKU_API std::ostream& operator<<(std::ostream&, const SystemWeight&);
|
||||
|
||||
inline bool operator==(const SystemWeight& d1, const SystemWeight& d2) {
|
||||
return d1.sys == d2.sys && std::abs(d1.weight - d2.weight) < 0.0001;
|
||||
}
|
||||
|
||||
} /* namespace hku */
|
||||
|
||||
#if FMT_VERSION >= 90000
|
||||
template <>
|
||||
struct fmt::formatter<hku::SystemWeight> : ostream_formatter {};
|
||||
#endif
|
||||
|
||||
#endif /* TRADE_SYS_ALLOCATEFUNDS_SYSTEMWEIGHT_H_ */
|
@ -21,22 +21,10 @@ bool FixedSelector::isMatchAF(const AFPtr& af) {
|
||||
return true;
|
||||
}
|
||||
|
||||
SystemList FixedSelector::getSelectedOnOpen(Datetime date) {
|
||||
SystemList result;
|
||||
SystemWeightList FixedSelector::getSelected(Datetime date) {
|
||||
SystemWeightList result;
|
||||
for (auto& sys : m_real_sys_list) {
|
||||
if (!sys->getParam<bool>("buy_delay")) {
|
||||
result.emplace_back(sys);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
SystemList FixedSelector::getSelectedOnClose(Datetime date) {
|
||||
SystemList result;
|
||||
for (auto& sys : m_real_sys_list) {
|
||||
if (sys->getParam<bool>("buy_delay")) {
|
||||
result.emplace_back(sys);
|
||||
}
|
||||
result.emplace_back(sys, 1.0);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@ -24,14 +24,9 @@ bool SignalSelector::isMatchAF(const AFPtr& af) {
|
||||
return true;
|
||||
}
|
||||
|
||||
SystemList SignalSelector::getSelectedOnOpen(Datetime date) {
|
||||
auto iter = m_sys_dict_on_open.find(date);
|
||||
return iter != m_sys_dict_on_open.end() ? iter->second : SystemList();
|
||||
}
|
||||
|
||||
SystemList SignalSelector::getSelectedOnClose(Datetime date) {
|
||||
auto iter = m_sys_dict_on_close.find(date);
|
||||
return iter != m_sys_dict_on_close.end() ? iter->second : SystemList();
|
||||
SystemWeightList SignalSelector::getSelected(Datetime date) {
|
||||
auto iter = m_sys_dict.find(date);
|
||||
return iter != m_sys_dict.end() ? iter->second : SystemWeightList();
|
||||
}
|
||||
|
||||
void SignalSelector::_calculate() {
|
||||
@ -40,14 +35,12 @@ void SignalSelector::_calculate() {
|
||||
auto& sys = m_real_sys_list[i];
|
||||
auto sg = sys->getSG();
|
||||
auto dates = sg->getBuySignal();
|
||||
unordered_map<Datetime, SystemList>* date_dict;
|
||||
date_dict = sys->getParam<bool>("buy_delay") ? &m_sys_dict_on_close : &m_sys_dict_on_open;
|
||||
for (auto& date : dates) {
|
||||
auto iter = date_dict->find(date);
|
||||
if (iter != date_dict->end()) {
|
||||
iter->second.emplace_back(sys);
|
||||
auto iter = m_sys_dict.find(date);
|
||||
if (iter != m_sys_dict.end()) {
|
||||
iter->second.emplace_back(sys, 1.0);
|
||||
} else {
|
||||
(*date_dict)[date] = {sys};
|
||||
m_sys_dict[date] = {SystemWeight(sys, 1.0)};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,13 +20,11 @@ public:
|
||||
virtual ~SignalSelector();
|
||||
|
||||
virtual void _reset() override {
|
||||
m_sys_dict_on_open.clear();
|
||||
m_sys_dict_on_close.clear();
|
||||
m_sys_dict.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
unordered_map<Datetime, SystemList> m_sys_dict_on_open;
|
||||
unordered_map<Datetime, SystemList> m_sys_dict_on_close;
|
||||
unordered_map<Datetime, SystemWeightList> m_sys_dict;
|
||||
};
|
||||
|
||||
} // namespace hku
|
@ -671,43 +671,35 @@ void System::_submitBuyRequest(const KRecord& today, const KRecord& src_today, P
|
||||
src_today.closePrice - m_buyRequest.stoploss, m_buyRequest.from);
|
||||
}
|
||||
|
||||
TradeRecord System::sellForce(const KRecord& today, const KRecord& src_today, double num,
|
||||
Part from) {
|
||||
HKU_ASSERT_M(from == PART_ALLOCATEFUNDS || from == PART_PORTFOLIO,
|
||||
"Only Allocator or Portfolis can perform this operation!");
|
||||
TradeRecord result;
|
||||
if (getParam<bool>("sell_delay")) {
|
||||
if (m_sellRequest.valid) {
|
||||
if (m_sellRequest.count > getParam<int>("max_delay_count")) {
|
||||
// 超出最大延迟次数,清除买入请求
|
||||
m_sellRequest.clear();
|
||||
return result;
|
||||
}
|
||||
m_sellRequest.count++;
|
||||
TradeRecord System::_sellForce(const Datetime& date, double num, Part from, bool on_open) {
|
||||
TradeRecord record;
|
||||
size_t pos = m_kdata.getPos(date);
|
||||
HKU_TRACE_IF_RETURN(pos == Null<size_t>(), record,
|
||||
"Failed to sellForce {}, the day {} could'nt sell!", m_stock.market_code(),
|
||||
date);
|
||||
|
||||
} else {
|
||||
m_sellRequest.valid = true;
|
||||
m_sellRequest.business = BUSINESS_SELL;
|
||||
m_sellRequest.count = 1;
|
||||
}
|
||||
const auto& krecord = m_kdata.getKRecord(pos);
|
||||
const auto& src_krecord =
|
||||
m_stock.getKRecord(m_kdata.startPos() + pos, m_kdata.getQuery().kType());
|
||||
|
||||
PositionRecord position = m_tm->getPosition(today.datetime, m_stock);
|
||||
m_sellRequest.from = from;
|
||||
m_sellRequest.datetime = today.datetime;
|
||||
m_sellRequest.stoploss = position.stoploss;
|
||||
m_sellRequest.goal = position.goalPrice;
|
||||
m_sellRequest.number = num;
|
||||
return result;
|
||||
PositionRecord position = m_tm->getPosition(date, m_stock);
|
||||
price_t realPrice =
|
||||
_getRealSellPrice(krecord.datetime, on_open ? src_krecord.openPrice : src_krecord.closePrice);
|
||||
|
||||
} else {
|
||||
PositionRecord position = m_tm->getPosition(today.datetime, m_stock);
|
||||
price_t realPrice = _getRealSellPrice(today.datetime, src_today.closePrice);
|
||||
TradeRecord record = m_tm->sell(today.datetime, m_stock, realPrice, num, position.stoploss,
|
||||
position.goalPrice, src_today.closePrice, from);
|
||||
m_trade_list.push_back(record);
|
||||
_sellNotifyAll(record);
|
||||
return record;
|
||||
double min_num = m_stock.minTradeNumber();
|
||||
double real_sell_num = num;
|
||||
if (real_sell_num >= position.number) {
|
||||
real_sell_num = position.number;
|
||||
} else if (min_num > 1) {
|
||||
real_sell_num = static_cast<int64_t>(num / min_num) * min_num;
|
||||
}
|
||||
|
||||
record =
|
||||
m_tm->sell(date, m_stock, realPrice, real_sell_num, position.stoploss, position.goalPrice,
|
||||
on_open ? src_krecord.openPrice : src_krecord.closePrice, from);
|
||||
m_trade_list.push_back(record);
|
||||
_sellNotifyAll(record);
|
||||
return record;
|
||||
}
|
||||
|
||||
TradeRecord System::_sell(const KRecord& today, const KRecord& src_today, Part from) {
|
||||
|
@ -211,8 +211,17 @@ public:
|
||||
return _sell(today, src_today, from);
|
||||
}
|
||||
|
||||
// 强制卖出,用于资金分配管理器和资产组合指示系统进行强制卖出操作
|
||||
TradeRecord sellForce(const KRecord& today, const KRecord& src_today, double num, Part from);
|
||||
// 强制以开盘价卖出,仅供 PF/AF 内部调用
|
||||
TradeRecord sellForceOnOpen(const Datetime& date, double num, Part from) {
|
||||
HKU_ASSERT(from == PART_ALLOCATEFUNDS || from == PART_PORTFOLIO);
|
||||
return _sellForce(date, num, from, true);
|
||||
}
|
||||
|
||||
// 强制以收盘价卖出,仅供 PF/AF 内部调用
|
||||
TradeRecord sellForceOnClose(const Datetime& date, double num, Part from) {
|
||||
HKU_ASSERT(from == PART_ALLOCATEFUNDS || from == PART_PORTFOLIO);
|
||||
return _sellForce(date, num, from, false);
|
||||
}
|
||||
|
||||
private:
|
||||
bool _environmentIsValid(const Datetime& datetime);
|
||||
@ -264,6 +273,9 @@ private:
|
||||
|
||||
TradeRecord _runMoment(const KRecord& record, const KRecord& src_record);
|
||||
|
||||
// Portfolio | AllocateFunds 指示立即进行强制卖出,以便对 buy_delay 的系统进行资金调整
|
||||
TradeRecord _sellForce(const Datetime& date, double num, Part from, bool on_open);
|
||||
|
||||
protected:
|
||||
TradeManagerPtr m_tm;
|
||||
MoneyManagerPtr m_mm;
|
||||
|
@ -7,7 +7,7 @@ target("hikyuu")
|
||||
-- set_kind("shared")
|
||||
-- end
|
||||
|
||||
add_options("hdf5", "mysql", "sqlite", "tdx", "feedback", "stacktrace", "spend_time")
|
||||
add_options("hdf5", "mysql", "sqlite", "tdx", "feedback", "stacktrace", "spend_time", "log_level")
|
||||
|
||||
add_packages("boost", "fmt", "spdlog", "flatbuffers", "nng", "nlohmann_json", "cpp-httplib")
|
||||
if is_plat("windows", "linux", "cross") then
|
||||
@ -76,6 +76,7 @@ target("hikyuu")
|
||||
add_headerfiles("../(hikyuu/**.h)|**doc.h")
|
||||
|
||||
-- add files
|
||||
-- add_files("./**.cpp|data_driver/**.cpp|utilities/db_connect/mysql/*.cpp")
|
||||
add_files("./**.cpp|data_driver/**.cpp|utilities/db_connect/mysql/*.cpp")
|
||||
add_files("./data_driver/*.cpp")
|
||||
if get_config("hdf5") or get_config("sqlite") then
|
||||
|
@ -31,38 +31,38 @@ TEST_CASE("test_IC") {
|
||||
Indicator result;
|
||||
|
||||
/** @arg 传入非法 n */
|
||||
result = IC(MA(CLOSE()), stks, query, -1, ref_stk);
|
||||
result = IC(MA(CLOSE()), stks, query, ref_stk, -1);
|
||||
CHECK_EQ(result.name(), "IC");
|
||||
CHECK_UNARY(result.empty());
|
||||
|
||||
/** @arg 传入的 ref_stk 为 null */
|
||||
result = IC(stks, query, 1, Stock())(MA(CLOSE()));
|
||||
result = IC(stks, query, Stock(), 1)(MA(CLOSE()));
|
||||
CHECK_EQ(result.name(), "IC");
|
||||
CHECK_UNARY(result.empty());
|
||||
|
||||
/** @arg 传入空的 stks */
|
||||
result = IC(MA(CLOSE()), StockList(), query, 1, ref_stk);
|
||||
result = IC(MA(CLOSE()), StockList(), query, ref_stk, 1);
|
||||
CHECK_EQ(result.name(), "IC");
|
||||
CHECK_UNARY(!result.empty());
|
||||
CHECK_EQ(result.size(), ref_k.size());
|
||||
CHECK_EQ(result.discard(), result.size());
|
||||
|
||||
/** @arg 传入的 stks 数量不足,需要大于等于2 */
|
||||
result = IC(MA(CLOSE()), {sm["sh600004"]}, query, 1, ref_stk);
|
||||
result = IC(MA(CLOSE()), {sm["sh600004"]}, query, ref_stk, 1);
|
||||
CHECK_EQ(result.name(), "IC");
|
||||
CHECK_UNARY(!result.empty());
|
||||
CHECK_EQ(result.size(), ref_k.size());
|
||||
CHECK_EQ(result.discard(), result.size());
|
||||
|
||||
/** @arg ref_stk 数据长度不足 */
|
||||
result = IC(MA(CLOSE()), stks, KQuery(-1), 1, ref_stk);
|
||||
result = IC(MA(CLOSE()), stks, KQuery(-1), ref_stk, 1);
|
||||
CHECK_EQ(result.name(), "IC");
|
||||
CHECK_UNARY(!result.empty());
|
||||
CHECK_EQ(result.size(), 1);
|
||||
CHECK_EQ(result.discard(), result.size());
|
||||
|
||||
/** @arg 传入的 stks 中夹杂有 null stock,实际的 stks 长度小于2 */
|
||||
result = IC(MA(CLOSE()), {sm["sh600004"], Stock()}, KQuery(-2), 1, ref_stk);
|
||||
result = IC(MA(CLOSE()), {sm["sh600004"], Stock()}, KQuery(-2), ref_stk, 1);
|
||||
CHECK_EQ(result.name(), "IC");
|
||||
CHECK_UNARY(!result.empty());
|
||||
CHECK_EQ(result.size(), 2);
|
||||
@ -71,7 +71,7 @@ TEST_CASE("test_IC") {
|
||||
CHECK_UNARY(std::isnan(result[1]));
|
||||
|
||||
/** @arg 传入的 stks 长度为2,query 的长度为2*/
|
||||
result = IC(MA(CLOSE()), {sm["sh600004"], sm["sh600005"]}, KQuery(-2), 1, ref_stk);
|
||||
result = IC(MA(CLOSE()), {sm["sh600004"], sm["sh600005"]}, KQuery(-2), ref_stk, 1);
|
||||
CHECK_EQ(result.name(), "IC");
|
||||
CHECK_UNARY(!result.empty());
|
||||
CHECK_EQ(result.size(), 2);
|
||||
@ -80,13 +80,12 @@ TEST_CASE("test_IC") {
|
||||
CHECK_UNARY(std::isnan(result[1]));
|
||||
|
||||
/** @arg 正常执行 */
|
||||
result = IC(MA(CLOSE()), stks, query, 1, ref_stk);
|
||||
result = IC(MA(CLOSE()), stks, query, ref_stk, 1);
|
||||
CHECK_EQ(result.name(), "IC");
|
||||
CHECK_UNARY(!result.empty());
|
||||
CHECK_EQ(result.size(), ref_k.size());
|
||||
CHECK_EQ(result.discard(), 2);
|
||||
CHECK_EQ(result.discard(), 1);
|
||||
CHECK_UNARY(std::isnan(result[0]));
|
||||
CHECK_UNARY(std::isnan(result[1]));
|
||||
CHECK_EQ(result[2], doctest::Approx(0.8));
|
||||
CHECK_EQ(result[99], doctest::Approx(-1));
|
||||
}
|
||||
@ -108,7 +107,7 @@ TEST_CASE("test_IC_benchmark") {
|
||||
BENCHMARK_TIME_MSG(test_IC_benchmark, cycle, fmt::format("data len: {}", ref_k.size()));
|
||||
SPEND_TIME_CONTROL(false);
|
||||
for (int i = 0; i < cycle; i++) {
|
||||
Indicator ind = IC(MA(CLOSE()), stks, query, 1, ref_stk);
|
||||
Indicator ind = IC(MA(CLOSE()), stks, query, ref_stk, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -128,7 +127,7 @@ TEST_CASE("test_IC_export") {
|
||||
Stock ref_stk = sm["sh000001"];
|
||||
|
||||
KQuery query = KQuery(-200);
|
||||
Indicator x1 = IC(stks, query, 1, ref_stk)(MA(CLOSE()));
|
||||
Indicator x1 = IC(stks, query, ref_stk, 1)(MA(CLOSE()));
|
||||
|
||||
{
|
||||
std::ofstream ofs(filename);
|
||||
|
@ -30,7 +30,7 @@ TEST_CASE("test_AllocateFunds") {
|
||||
TMPtr subtm = crtTM(Datetime(200101010000L), 0);
|
||||
SYSPtr sys = SYS_Simple(subtm->clone());
|
||||
|
||||
SystemList se_list;
|
||||
SystemWeightList se_list;
|
||||
SystemList hold_list;
|
||||
SystemList ac_list;
|
||||
SystemWeightList sw_list;
|
||||
@ -48,9 +48,9 @@ TEST_CASE("test_AllocateFunds") {
|
||||
/** @arg 出入的se_list、hold_list均为空 */
|
||||
CHECK_EQ(se_list.size(), 0);
|
||||
CHECK_EQ(hold_list.size(), 0);
|
||||
sw_list = af->_allocateWeight(Datetime(201802100000L), se_list);
|
||||
// sw_list = af->_allocateWeight(Datetime(201802100000L), se_list);
|
||||
// ac_list = af->getAllocatedSystemList(Datetime(201802100000L), se_list, hold_list);
|
||||
CHECK_EQ(sw_list.size(), 0);
|
||||
// CHECK_EQ(sw_list.size(), 0);
|
||||
// CHECK_EQ(ac_list.size(), 0);
|
||||
|
||||
/** @arg 最大持仓系统数小于0 */
|
||||
|
@ -6,7 +6,7 @@
|
||||
*/
|
||||
|
||||
#include "doctest/doctest.h"
|
||||
#include <hikyuu/trade_sys/allocatefunds/SystemWeight.h>
|
||||
// #include <hikyuu/trade_sys/allocatefunds/SystemWeight.h>
|
||||
// #include<hikyuu / StockManager.h>
|
||||
#include <hikyuu/trade_manage/crt/crtTM.h>
|
||||
#include <hikyuu/trade_sys/system/crt/SYS_Simple.h>
|
||||
@ -21,16 +21,16 @@ using namespace hku;
|
||||
|
||||
/** @par 检测点,不自动调仓 */
|
||||
TEST_CASE("test_SystemWeight") {
|
||||
SystemWeight w;
|
||||
CHECK_EQ(w.getWeight(), 1.0);
|
||||
CHECK_THROWS(w.setWeight(1.1));
|
||||
w.setWeight(0.9);
|
||||
CHECK_EQ(w.getWeight(), 0.9);
|
||||
// SystemWeight w;
|
||||
// CHECK_EQ(w.getWeight(), 1.0);
|
||||
// // CHECK_THROWS(w.setWeight(1.1));
|
||||
// w.setWeight(0.9);
|
||||
// CHECK_EQ(w.getWeight(), 0.9);
|
||||
|
||||
TMPtr tm = crtTM(Datetime(200101010000L), 100000);
|
||||
SYSPtr sys = SYS_Simple(tm);
|
||||
// TMPtr tm = crtTM(Datetime(200101010000L), 100000);
|
||||
// SYSPtr sys = SYS_Simple(tm);
|
||||
|
||||
CHECK_THROWS(SystemWeight(sys, 1.1));
|
||||
// CHECK_THROWS(SystemWeight(sys, 1.1));
|
||||
// HKU_INFO("{}", SystemWeight(sys, 0.2));
|
||||
}
|
||||
|
||||
|
@ -100,7 +100,7 @@ TEST_CASE("test_MF_EqualWeight") {
|
||||
CHECK_UNARY(ind1.equal(ind2));
|
||||
CHECK_UNARY(all_factors[0].equal(ind2));
|
||||
auto ic1 = mf->getIC();
|
||||
auto ic2 = IC(MA(CLOSE()), stks, query, 1, ref_stk);
|
||||
auto ic2 = IC(MA(CLOSE()), stks, query, ref_stk, 1);
|
||||
CHECK_UNARY(ic1.equal(ic2));
|
||||
|
||||
CHECK_THROWS_AS(mf->getScore(Datetime(20111204)), std::exception);
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include <hikyuu/indicator/crt/ICIR.h>
|
||||
#include <hikyuu/indicator/crt/ROCR.h>
|
||||
#include <hikyuu/indicator/crt/KDATA.h>
|
||||
#include <hikyuu/indicator/crt/STDEV.h>
|
||||
#include <hikyuu/trade_sys/factor/crt/MF_ICIRWeight.h>
|
||||
|
||||
using namespace hku;
|
||||
@ -43,11 +44,11 @@ TEST_CASE("test_MF_ICIRWeight") {
|
||||
|
||||
auto stk = sm["sh600004"];
|
||||
auto ind1 = MA(ROCR(CLOSE(stk.getKData(query)), ndays));
|
||||
auto ic1 = ICIR(IC(MA(ROCR(CLOSE(), ndays)), stks, query, ndays, ref_stk), ic_rolling_n);
|
||||
auto ic1 = ICIR(MA(ROCR(CLOSE(), ndays)), stks, query, ref_stk, ndays, ic_rolling_n);
|
||||
auto ind2 = AMA(ROCR(CLOSE(stk.getKData(query)), ndays));
|
||||
auto ic2 = ICIR(IC(AMA(ROCR(CLOSE(), ndays)), stks, query, ndays, ref_stk), ic_rolling_n);
|
||||
auto ic2 = ICIR(AMA(ROCR(CLOSE(), ndays)), stks, query, ref_stk, ndays, ic_rolling_n);
|
||||
auto ind3 = EMA(ROCR(CLOSE(stk.getKData(query)), ndays));
|
||||
auto ic3 = ICIR(IC(EMA(ROCR(CLOSE(), ndays)), stks, query, ndays, ref_stk), ic_rolling_n);
|
||||
auto ic3 = ICIR(EMA(ROCR(CLOSE(), ndays)), stks, query, ref_stk, ndays, ic_rolling_n);
|
||||
|
||||
auto ind4 = mf->getFactor(stk);
|
||||
for (size_t i = 0; i < ind4.discard(); i++) {
|
||||
@ -58,9 +59,15 @@ TEST_CASE("test_MF_ICIRWeight") {
|
||||
CHECK_UNARY(std::isnan(ind4[i]));
|
||||
}
|
||||
for (size_t i = ind4.discard(), len = ref_dates.size(); i < len; i++) {
|
||||
Indicator::value_t w = (ic1[i] + ic2[i] + ic3[i]) / 3.0;
|
||||
CHECK_EQ(ind4[i], doctest::Approx((ind1[i] * w + ind2[i] * w + ind3[i] * w) / 3.0));
|
||||
Indicator::value_t w = (ind1[i] * ic1[i] + ind2[i] * ic2[i] + ind3[i] * ic3[i]) /
|
||||
(std::abs(ic1[i]) + std::abs(ic2[i]) + std::abs(ic3[i]));
|
||||
// HKU_INFO("{}: {}, {}, {}, {}, {}", i, w, ind4[i], ind1[i], ma_ic1[i], stdev_ic1[i]);
|
||||
// HKU_INFO("{}: {}, {}", i, w, ind4[i]);
|
||||
if (!std::isnan(ind4[i]) && !std::isnan(w)) {
|
||||
CHECK_EQ(ind4[i], doctest::Approx(w));
|
||||
}
|
||||
}
|
||||
// HKU_INFO("{}", ind4);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
@ -15,6 +15,7 @@
|
||||
#include <hikyuu/indicator/crt/IC.h>
|
||||
#include <hikyuu/indicator/crt/ROCR.h>
|
||||
#include <hikyuu/indicator/crt/KDATA.h>
|
||||
#include <hikyuu/indicator/crt/PRICELIST.h>
|
||||
#include <hikyuu/trade_sys/factor/crt/MF_ICWeight.h>
|
||||
|
||||
using namespace hku;
|
||||
@ -42,11 +43,11 @@ TEST_CASE("test_MF_ICWeight") {
|
||||
|
||||
auto stk = sm["sh600004"];
|
||||
auto ind1 = MA(ROCR(CLOSE(stk.getKData(query)), ndays));
|
||||
auto ic1 = MA(IC(MA(ROCR(CLOSE(), ndays)), stks, query, ndays, ref_stk), ic_rolling_n);
|
||||
auto ic1 = MA(IC(MA(ROCR(CLOSE(), ndays)), stks, query, ref_stk, ndays), ic_rolling_n);
|
||||
auto ind2 = AMA(ROCR(CLOSE(stk.getKData(query)), ndays));
|
||||
auto ic2 = MA(IC(AMA(ROCR(CLOSE(), ndays)), stks, query, ndays, ref_stk), ic_rolling_n);
|
||||
auto ic2 = MA(IC(AMA(ROCR(CLOSE(), ndays)), stks, query, ref_stk, ndays), ic_rolling_n);
|
||||
auto ind3 = EMA(ROCR(CLOSE(stk.getKData(query)), ndays));
|
||||
auto ic3 = MA(IC(EMA(ROCR(CLOSE(), ndays)), stks, query, ndays, ref_stk), ic_rolling_n);
|
||||
auto ic3 = MA(IC(EMA(ROCR(CLOSE(), ndays)), stks, query, ref_stk, ndays), ic_rolling_n);
|
||||
|
||||
auto ind4 = mf->getFactor(stk);
|
||||
for (size_t i = 0; i < ind4.discard(); i++) {
|
||||
@ -57,9 +58,12 @@ TEST_CASE("test_MF_ICWeight") {
|
||||
CHECK_UNARY(std::isnan(ind4[i]));
|
||||
}
|
||||
for (size_t i = ind4.discard(), len = ref_dates.size(); i < len; i++) {
|
||||
Indicator::value_t w = (ic1[i] + ic2[i] + ic3[i]) / 3.0;
|
||||
CHECK_EQ(ind4[i], doctest::Approx((ind1[i] * w + ind2[i] * w + ind3[i] * w) / 3.0));
|
||||
Indicator::value_t w = (ind1[i] * ic1[i] + ind2[i] * ic2[i] + ind3[i] * ic3[i]) /
|
||||
(std::abs(ic1[i]) + std::abs(ic2[i]) + std::abs(ic3[i]));
|
||||
// HKU_INFO("{}: {}, {}", i, w, ind4[i]);
|
||||
CHECK_EQ(ind4[i], doctest::Approx(w));
|
||||
}
|
||||
// HKU_INFO("{}", ind4);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
@ -29,24 +29,24 @@ TEST_CASE("test_SE_Fixed") {
|
||||
SYSPtr sys = SYS_Simple();
|
||||
SEPtr se = SE_Fixed();
|
||||
|
||||
/** @arg 试图加入一个不存在的stock */
|
||||
se->addStock(Stock(), sys);
|
||||
SystemList result = se->getSelectedOnOpen(Datetime(200001010000L));
|
||||
CHECK_EQ(result.size(), 0);
|
||||
// /** @arg 试图加入一个不存在的stock */
|
||||
// se->addStock(Stock(), sys);
|
||||
// SystemWeightList result = se->getSelectedOnOpen(Datetime(200001010000L));
|
||||
// CHECK_EQ(result.size(), 0);
|
||||
|
||||
/** @arg 试图加入一个空的系统策略原型 */
|
||||
se->addStock(sm["sh600000"], SYSPtr());
|
||||
result = se->getSelectedOnOpen(Datetime(200001010000L));
|
||||
CHECK_EQ(result.size(), 0);
|
||||
// /** @arg 试图加入一个空的系统策略原型 */
|
||||
// se->addStock(sm["sh600000"], SYSPtr());
|
||||
// result = se->getSelectedOnOpen(Datetime(200001010000L));
|
||||
// CHECK_EQ(result.size(), 0);
|
||||
|
||||
/** @arg 试图加入一个缺少MM的系统策略原型 */
|
||||
SGPtr sg = SG_Cross(MA(CLOSE(), 5), MA(CLOSE(), 10));
|
||||
MMPtr mm = MM_FixedCount(100);
|
||||
CHECK_UNARY(!se->addStock(sm["sh600000"], sys));
|
||||
// /** @arg 试图加入一个缺少MM的系统策略原型 */
|
||||
// SGPtr sg = SG_Cross(MA(CLOSE(), 5), MA(CLOSE(), 10));
|
||||
// MMPtr mm = MM_FixedCount(100);
|
||||
// CHECK_UNARY(!se->addStock(sm["sh600000"], sys));
|
||||
|
||||
/* @arg 试图加入一个未指定SG的系统原型 */
|
||||
sys->setMM(mm);
|
||||
CHECK_UNARY(!se->addStock(sm["sh600000"], sys));
|
||||
// /* @arg 试图加入一个未指定SG的系统原型 */
|
||||
// sys->setMM(mm);
|
||||
// CHECK_UNARY(!se->addStock(sm["sh600000"], sys));
|
||||
|
||||
// 目前必须有PF指定实际执行的子系统,下面代码无法执行
|
||||
// /** @arg getSelectedSystemList */
|
||||
|
@ -59,7 +59,7 @@ target("unit-test")
|
||||
set_kind("binary")
|
||||
set_default(false)
|
||||
|
||||
add_options("hdf5", "mysql", "sqlite", "tdx", "feedback", "stacktrace", "spend_time")
|
||||
add_options("hdf5", "mysql", "sqlite", "tdx", "feedback", "stacktrace", "spend_time", "log_level")
|
||||
|
||||
add_packages("boost", "fmt", "spdlog", "doctest", "sqlite3")
|
||||
if get_config("mysql") then
|
||||
@ -106,7 +106,7 @@ target("small-test")
|
||||
set_kind("binary")
|
||||
set_default(false)
|
||||
|
||||
add_options("hdf5", "mysql", "sqlite", "tdx", "feedback", "stacktrace", "spend_time")
|
||||
add_options("hdf5", "mysql", "sqlite", "tdx", "feedback", "stacktrace", "spend_time", "log_level")
|
||||
|
||||
add_packages("boost", "fmt", "spdlog", "doctest", "sqlite3")
|
||||
if get_config("mysql") then
|
||||
|
@ -1739,36 +1739,59 @@ void export_Indicator_build_in(py::module& m) {
|
||||
|
||||
m.def(
|
||||
"IC",
|
||||
[](const Indicator& ind, const py::object& stks, const KQuery& query, int n,
|
||||
const Stock& ref_stk) {
|
||||
[](const Indicator& ind, const py::object& stks, const KQuery& query, const Stock& ref_stk,
|
||||
int n) {
|
||||
if (py::isinstance<Block>(stks)) {
|
||||
const auto& blk = stks.cast<Block&>();
|
||||
return IC(ind, blk, query, n, ref_stk);
|
||||
return IC(ind, blk, query, ref_stk, n);
|
||||
}
|
||||
|
||||
if (py::isinstance<py::sequence>(stks)) {
|
||||
StockList c_stks = python_list_to_vector<Stock>(stks);
|
||||
return IC(ind, c_stks, query, n, ref_stk);
|
||||
return IC(ind, c_stks, query, ref_stk, n);
|
||||
}
|
||||
|
||||
HKU_THROW("Input stks must be Block or sequenc(Stock)!");
|
||||
},
|
||||
py::arg("ind"), py::arg("stks"), py::arg("query"), py::arg("n"), py::arg("ref_stk"),
|
||||
R"(IC(ind, stks, query, n, ref_stk)
|
||||
py::arg("ind"), py::arg("stks"), py::arg("query"), py::arg("ref_stk"), py::arg("n") = 1,
|
||||
R"(IC(ind, stks, query, ref_stk[, n=1])
|
||||
|
||||
计算指定的因子相对于参考证券的 IC (实际为 RankIC)
|
||||
|
||||
:param Indicator ind: 输入因子
|
||||
:param sequence(stock)|Block stks 证券组合
|
||||
:param Query query: 查询条件
|
||||
:param int n: 时间窗口
|
||||
:param Stock ref_stk: 参照证券,通常使用 sh000300 沪深300)");
|
||||
:param Stock ref_stk: 参照证券,通常使用 sh000300 沪深300
|
||||
:param int n: 时间窗口)");
|
||||
|
||||
m.def("ICIR", ICIR, py::arg("ic"), py::arg("n") = 120, R"(ICIR(ic[,n])
|
||||
m.def(
|
||||
"ICIR",
|
||||
[](const Indicator& ind, const py::object& stks, const KQuery& query, const Stock& ref_stk,
|
||||
int n, int rolling_n) {
|
||||
if (py::isinstance<Block>(stks)) {
|
||||
const auto& blk = stks.cast<Block&>();
|
||||
return ICIR(ind, blk, query, ref_stk, n, rolling_n);
|
||||
}
|
||||
|
||||
if (py::isinstance<py::sequence>(stks)) {
|
||||
StockList c_stks = python_list_to_vector<Stock>(stks);
|
||||
return ICIR(ind, c_stks, query, ref_stk, n, rolling_n);
|
||||
}
|
||||
|
||||
HKU_THROW("Input stks must be Block or sequenc(Stock)!");
|
||||
},
|
||||
py::arg("ind"), py::arg("stks"), py::arg("query"), py::arg("ref_stk"), py::arg("n") = 1,
|
||||
py::arg("rolling_n") = 120,
|
||||
R"(ICIR(ind, stks, query, ref_stk[, n=1, rolling_n=120])
|
||||
|
||||
计算 IC 因子 IR = IC的多周期均值/IC的标准方差
|
||||
|
||||
:param Indicator: ic 已经计算出的 ic 值
|
||||
:param int n: 时间窗口)");
|
||||
:param Indicator ind: 输入因子
|
||||
:param sequence(stock)|Block stks 证券组合
|
||||
:param Query query: 查询条件
|
||||
:param Stock ref_stk: 参照证券,通常使用 sh000300 沪深300
|
||||
:param int n: 计算IC时对应的 n 日收益率
|
||||
:param int rolling_n: 滚动周期)");
|
||||
|
||||
m.def("ZSCORE", ZSCORE_1, py::arg("out_extreme") = false, py::arg("nsigma") = 3.0,
|
||||
py::arg("recursive") = false);
|
||||
|
@ -416,7 +416,7 @@ void export_TradeManager(py::module& m) {
|
||||
:param ktype: K线类型
|
||||
:rtype: float)")
|
||||
|
||||
.def("get_funds", getFunds_1, py::arg("ktype"))
|
||||
.def("get_funds", getFunds_1, py::arg("ktype") = KQuery::DAY)
|
||||
.def("get_funds", getFunds_2, py::arg("datetime"), py::arg("ktype") = KQuery::DAY,
|
||||
R"(get_funds(self, [datetime, ktype = Query.DAY])
|
||||
|
||||
|
@ -25,29 +25,17 @@ public:
|
||||
PYBIND11_OVERLOAD(void, AllocateFundsBase, _reset, );
|
||||
}
|
||||
|
||||
SystemWeightList _allocateWeight(const Datetime& date, const SystemList& se_list) override {
|
||||
SystemWeightList _allocateWeight(const Datetime& date,
|
||||
const SystemWeightList& se_list) override {
|
||||
PYBIND11_OVERLOAD_PURE_NAME(SystemWeightList, AllocateFundsBase, "_allocate_weight",
|
||||
_allocateWeight, date, se_list);
|
||||
}
|
||||
};
|
||||
|
||||
void export_AllocateFunds(py::module& m) {
|
||||
py::class_<SystemWeight>(m, "SystemWeight",
|
||||
"系统权重系数结构,在资产分配时,指定对应系统的资产占比系数")
|
||||
.def(py::init<>())
|
||||
.def(py::init<const SystemPtr&, price_t>())
|
||||
.def("__str__", to_py_str<SystemWeight>)
|
||||
.def("__repr__", to_py_str<SystemWeight>)
|
||||
.def_property("sys", py::overload_cast<>(&SystemWeight::getSYS, py::const_),
|
||||
py::overload_cast<const SystemPtr&>(&SystemWeight::setSYS),
|
||||
"对应的 System 实例")
|
||||
.def_property("weight", &SystemWeight::getWeight, &SystemWeight::setWeight,
|
||||
"对应的权重系数,有效范围为 [0, 1]")
|
||||
|
||||
DEF_PICKLE(SystemWeight);
|
||||
|
||||
py::class_<AllocateFundsBase, AFPtr, PyAllocateFundsBase>(m, "AllocateFundsBase",
|
||||
R"(资产分配算法基类, 子类接口:
|
||||
py::class_<AllocateFundsBase, AFPtr, PyAllocateFundsBase>(
|
||||
m, "AllocateFundsBase",
|
||||
R"(资产分配算法基类, 子类接口:
|
||||
|
||||
- _allocateWeight : 【必须】子类资产分配调整实现
|
||||
- _clone : 【必须】克隆接口
|
||||
@ -62,6 +50,8 @@ void export_AllocateFunds(py::module& m) {
|
||||
.def_property("query", py::overload_cast<>(&AllocateFundsBase::getQuery, py::const_),
|
||||
py::overload_cast<const KQuery&>(&AllocateFundsBase::setQuery),
|
||||
py::return_value_policy::copy, "设置或获取查询条件")
|
||||
.def_property_readonly("tm", py::overload_cast<>(&AllocateFundsBase::getTM, py::const_),
|
||||
py::return_value_policy::copy)
|
||||
|
||||
.def("get_param", &AllocateFundsBase::getParam<boost::any>, R"(get_param(self, name)
|
||||
|
||||
@ -85,7 +75,8 @@ void export_AllocateFunds(py::module& m) {
|
||||
.def("clone", &AllocateFundsBase::clone, "克隆操作")
|
||||
.def("_reset", &AllocateFundsBase::_reset, "子类复位操作实现")
|
||||
.def("_allocate_weight", &AllocateFundsBase::_allocateWeight, py::arg("date"),
|
||||
py::arg("se_list"), R"(_allocate_weight(self, date, se_list)
|
||||
py::arg("se_list"),
|
||||
R"(_allocate_weight(self, date, se_list)
|
||||
|
||||
【重载接口】子类分配权重接口,获取实际分配资产的系统实例及其权重
|
||||
|
||||
|
@ -170,7 +170,7 @@ void export_MultiFactor(py::module& m) {
|
||||
// MF_EqualWeight
|
||||
IndicatorList c_inds = python_list_to_vector<Indicator>(inds);
|
||||
StockList c_stks = python_list_to_vector<Stock>(stks);
|
||||
return MF_ICWeight(c_inds, c_stks, query, ref_stk, ic_n);
|
||||
return MF_ICWeight(c_inds, c_stks, query, ref_stk, ic_n, ic_rolling_n);
|
||||
},
|
||||
py::arg("inds"), py::arg("stks"), py::arg("query"), py::arg("ref_stk"), py::arg("ic_n") = 5,
|
||||
py::arg("ic_rolling_n") = 120,
|
||||
@ -193,7 +193,7 @@ void export_MultiFactor(py::module& m) {
|
||||
// MF_EqualWeight
|
||||
IndicatorList c_inds = python_list_to_vector<Indicator>(inds);
|
||||
StockList c_stks = python_list_to_vector<Stock>(stks);
|
||||
return MF_ICIRWeight(c_inds, c_stks, query, ref_stk, ic_n);
|
||||
return MF_ICIRWeight(c_inds, c_stks, query, ref_stk, ic_n, ic_rolling_n);
|
||||
},
|
||||
py::arg("inds"), py::arg("stks"), py::arg("query"), py::arg("ref_stk"), py::arg("ic_n") = 5,
|
||||
py::arg("ic_rolling_n") = 120,
|
||||
|
@ -57,12 +57,15 @@ void export_Portfolio(py::module& m) {
|
||||
.def("reset", &Portfolio::reset, "复位操作")
|
||||
.def("clone", &Portfolio::clone, "克隆操作")
|
||||
|
||||
.def("run", &Portfolio::run, py::arg("query"), py::arg("force") = false, R"(run(self, query)
|
||||
.def("run", &Portfolio::run, py::arg("query"), py::arg("adjust_cycle") = 1,
|
||||
py::arg("force") = false,
|
||||
R"(run(self, query[, adjust_cycle=1, force=false])
|
||||
|
||||
运行投资组合策略。在查询条件及各组件没有变化时,PF在第二次执行时,默认不会实际进行计算。
|
||||
但由于各个组件的参数可能改变,此种情况无法自动判断是否需要重计算,可以手工指定进行强制计算。
|
||||
|
||||
:param Query query: 查询条件
|
||||
:param int adjust_cycle: 调仓周期
|
||||
:param bool force: 强制重新计算)")
|
||||
|
||||
DEF_PICKLE(Portfolio);
|
||||
|
@ -25,14 +25,9 @@ public:
|
||||
PYBIND11_OVERLOAD_PURE(void, SelectorBase, _calculate, );
|
||||
}
|
||||
|
||||
SystemList getSelectedOnOpen(Datetime date) override {
|
||||
PYBIND11_OVERLOAD_PURE_NAME(SystemList, SelectorBase, "get_selected_on_open",
|
||||
getSelectedOnOpen, date);
|
||||
}
|
||||
|
||||
SystemList getSelectedOnClose(Datetime date) override {
|
||||
PYBIND11_OVERLOAD_PURE_NAME(SystemList, SelectorBase, "get_selected_on_close",
|
||||
getSelectedOnClose, date);
|
||||
SystemWeightList getSelected(Datetime date) override {
|
||||
PYBIND11_OVERLOAD_PURE_NAME(SystemWeightList, SelectorBase, "get_selected", getSelected,
|
||||
date);
|
||||
}
|
||||
|
||||
bool isMatchAF(const AFPtr& af) override {
|
||||
@ -41,12 +36,22 @@ public:
|
||||
};
|
||||
|
||||
void export_Selector(py::module& m) {
|
||||
py::class_<SystemWeight>(m, "SystemWeight",
|
||||
"系统权重系数结构,在资产分配时,指定对应系统的资产占比系数")
|
||||
.def(py::init<>())
|
||||
.def(py::init<const SystemPtr&, price_t>())
|
||||
.def("__str__", to_py_str<SystemWeight>)
|
||||
.def("__repr__", to_py_str<SystemWeight>)
|
||||
.def_readwrite("sys", &SystemWeight::sys, "对应的 System 实例")
|
||||
.def_readwrite("weight", &SystemWeight::weight)
|
||||
|
||||
DEF_PICKLE(SystemWeight);
|
||||
|
||||
py::class_<SelectorBase, SEPtr, PySelectorBase>(
|
||||
m, "SelectorBase",
|
||||
R"(选择器策略基类,实现标的、系统策略的评估和选取算法,自定义选择器策略子类接口:
|
||||
|
||||
- get_selected_on_open - 【必须】获取指定时刻开盘时选择的系统实例列表
|
||||
- get_selected_on_close - 【必须】获取指定时刻收盘时选择的系统实例列表
|
||||
- get_selected - 【必须】获取指定时刻选择的系统实例列表
|
||||
- _calculate - 【必须】计算接口
|
||||
- _reset - 【可选】重置私有属性
|
||||
- _clone - 【必须】克隆接口)")
|
||||
@ -118,19 +123,10 @@ void export_Selector(py::module& m) {
|
||||
|
||||
:param AllocateFundsBase af: 资产分配算法)")
|
||||
|
||||
.def("get_selected_on_open", &SelectorBase::getSelectedOnOpen,
|
||||
R"(get_selected_on_open(self, datetime)
|
||||
.def("get_selected", &SelectorBase::getSelected,
|
||||
R"(get_selected(self, datetime)
|
||||
|
||||
【重载接口】获取指定时刻开盘时选取的系统实例
|
||||
|
||||
:param Datetime datetime: 指定时刻
|
||||
:return: 选取的系统实例列表
|
||||
:rtype: SystemList)")
|
||||
|
||||
.def("get_selected_on_close", &SelectorBase::getSelectedOnClose,
|
||||
R"(get_selected_on_close(self, datetime)
|
||||
|
||||
【重载接口】获取指定时刻收盘时选取的系统实例
|
||||
【重载接口】获取指定时刻选取的系统实例
|
||||
|
||||
:param Datetime datetime: 指定时刻
|
||||
:return: 选取的系统实例列表
|
||||
|
@ -10,7 +10,7 @@ target("core")
|
||||
-- end
|
||||
|
||||
-- add_options("stacktrace")
|
||||
add_options("hdf5", "mysql", "sqlite", "tdx", "feedback", "stacktrace", "spend_time")
|
||||
add_options("hdf5", "mysql", "sqlite", "tdx", "feedback", "stacktrace", "spend_time", "log_level")
|
||||
|
||||
add_deps("hikyuu")
|
||||
add_packages("boost", "fmt", "spdlog", "flatbuffers", "pybind11", "cpp-httplib")
|
||||
|
Loading…
Reference in New Issue
Block a user