|
@@ -27,7 +27,37 @@ def get_annulization_multiple(freq) {
|
|
return ret;
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+/*
|
|
|
|
+ * 取主基准和BFI的历史月收益率
|
|
|
|
+ *
|
|
|
|
+ * @param benchmarks <TABLE>: entity-benchmark 的对应关系表
|
|
|
|
+ * @param end_day <DATE>: 收益的截止日期
|
|
|
|
+ *
|
|
|
|
+ * @return <TABLE>: benchmark_id, end_date, ret
|
|
|
|
+ *
|
|
|
|
+ */
|
|
|
|
+def get_benchmark_return(benchmarks, end_day) {
|
|
|
|
+
|
|
|
|
+ s_index_ids = '';
|
|
|
|
+ s_factor_ids = '';
|
|
|
|
+
|
|
|
|
+ // 前缀为 IN 的 benchmark id
|
|
|
|
+ t_index_id = SELECT DISTINCT benchmark_id FROM benchmarks WHERE benchmark_id LIKE 'IN%';
|
|
|
|
+ s_index_ids = iif(isVoid(t_index_id), "", "'" + t_index_id.benchmark_id.concat("','") + "'");
|
|
|
|
+
|
|
|
|
+ // 前缀为 FA 的 benchmark id
|
|
|
|
+ t_factor_id = SELECT DISTINCT benchmark_id FROM benchmarks WHERE benchmark_id LIKE 'FA%';
|
|
|
|
+ s_factor_ids = iif(isVoid(t_factor_id), "", "'" + t_factor_id.benchmark_id.concat("','") + "'");
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ // 目前指数的月度业绩存在 fund_performance 表
|
|
|
|
+ t_bmk = SELECT fund_id AS benchmark_id, temporalParse(end_date, 'yyyy-MM') AS end_date, ret FROM get_monthly_ret('IX', s_index_ids, 1990.01.01, end_day, true);
|
|
|
|
+
|
|
|
|
+ // 而因子的月度业绩存在 cm_factor_performance 表
|
|
|
|
+ INSERT INTO t_bmk SELECT factor_id, temporalParse(end_date, 'yyyy-MM') AS end_date, ret FROM get_monthly_ret('FA', s_factor_ids, 1990.01.01, end_day, true);
|
|
|
|
|
|
|
|
+ return t_bmk;
|
|
|
|
+}
|
|
/*
|
|
/*
|
|
* Trailing Return, Standard Deviation, Skewness, Kurtosis, Max Drawdown, VaR, CVaR
|
|
* Trailing Return, Standard Deviation, Skewness, Kurtosis, Max Drawdown, VaR, CVaR
|
|
* @param ret: 收益表,需要有 entity_id, price_dat, end_date, nav
|
|
* @param ret: 收益表,需要有 entity_id, price_dat, end_date, nav
|
|
@@ -66,7 +96,7 @@ def cal_basic_performance(ret, freq) {
|
|
WHERE ret.ret > - 1
|
|
WHERE ret.ret > - 1
|
|
GROUP BY entity_id;
|
|
GROUP BY entity_id;
|
|
|
|
|
|
- return (SELECT * FROM t LEFT JOIN t1 ON t.entity_id = t1.entity_id AND t.end_date = t1.end_date AND t.price_date = t1.price_date);
|
|
|
|
|
|
+ return (SELECT * FROM t LEFT JOIN t1 ON t.entity_id = t1.entity_id AND t.end_date = t1.end_date AND t.price_date = t1.price_date);
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
@@ -122,23 +152,24 @@ def cal_omega_sortino_kappa(ret, risk_free) {
|
|
* Alpha & Beta
|
|
* Alpha & Beta
|
|
* NOTE: alpha of Java version is noncompliant-GIPS annulized number
|
|
* NOTE: alpha of Java version is noncompliant-GIPS annulized number
|
|
*/
|
|
*/
|
|
-def cal_alpha_beta(ret, bmk_ret, risk_free) {
|
|
|
|
|
|
+def cal_alpha_beta(ret, benchmarks, bmk_ret, risk_free) {
|
|
|
|
|
|
- t = SELECT t.entity_id, t.end_date, t.ret, bmk.ret AS ret_bmk
|
|
|
|
|
|
+ t = SELECT t.entity_id, t.end_date, t.ret, bm.benchmark_id, bmk.ret AS ret_bmk
|
|
FROM ret t
|
|
FROM ret t
|
|
- INNER JOIN bmk_ret bmk ON t.end_date = bmk.end_date
|
|
|
|
|
|
+ INNER JOIN benchmarks bm ON t.entity_id = bm.entity_id
|
|
|
|
+ INNER JOIN bmk_ret bmk ON t.end_date = bmk.end_date AND bm.benchmark_id = bmk.benchmark_id
|
|
WHERE t.ret > -1
|
|
WHERE t.ret > -1
|
|
AND bmk.ret > -1;
|
|
AND bmk.ret > -1;
|
|
|
|
|
|
- beta = SELECT ret.beta(ret_bmk) AS beta FROM t GROUP BY entity_id;
|
|
|
|
|
|
+ beta = SELECT entity_id, benchmark_id, ret.beta(ret_bmk) AS beta FROM t GROUP BY entity_id, benchmark_id;
|
|
|
|
|
|
- alpha = SELECT t.entity_id, (t.ret - rfr.ret).mean() - beta.beta[0] * (t.ret_bmk - rfr.ret).mean() AS alpha
|
|
|
|
|
|
+ alpha = SELECT t.entity_id, t.benchmark_id, beta.beta[0] AS beta, (t.ret - rfr.ret).mean() - beta.beta[0] * (t.ret_bmk - rfr.ret).mean() AS alpha
|
|
FROM t
|
|
FROM t
|
|
- INNER JOIN beta beta ON t.entity_id = beta.entity_id
|
|
|
|
|
|
+ INNER JOIN beta beta ON t.entity_id = beta.entity_id AND t.benchmark_id = beta.benchmark_id
|
|
INNER JOIN risk_free rfr ON t.end_date = rfr.end_date
|
|
INNER JOIN risk_free rfr ON t.end_date = rfr.end_date
|
|
- GROUP BY t.entity_id;
|
|
|
|
|
|
+ GROUP BY t.entity_id, t.benchmark_id;
|
|
|
|
|
|
- return ( SELECT * FROM beta AS b INNER JOIN alpha AS a ON a.entity_id = b.entity_id );
|
|
|
|
|
|
+ return alpha;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
/*
|
|
@@ -146,25 +177,60 @@ def cal_alpha_beta(ret, bmk_ret, risk_free) {
|
|
* TODO: Information Ratio is way off!
|
|
* TODO: Information Ratio is way off!
|
|
* Not sure how to describe a giant number("inf"), for now 999 is used
|
|
* Not sure how to describe a giant number("inf"), for now 999 is used
|
|
*/
|
|
*/
|
|
-def cal_benchmark_tracking(ret, bmk_ret) {
|
|
|
|
|
|
+def cal_benchmark_tracking(ret, benchmarks, bmk_ret) {
|
|
|
|
|
|
- t0 = SELECT t.entity_id, t.end_date, t.ret, bmk.ret AS ret_bmk, count(entity_id) AS cnt, (t.ret - bmk.ret) AS exc_ret
|
|
|
|
|
|
+ t0 = SELECT t.entity_id, t.end_date, t.price_date,
|
|
|
|
+ t.ret, bmk.ret AS ret_bmk, count(t.entity_id) AS cnt, (t.ret - bmk.ret) AS exc_ret, bm.benchmark_id
|
|
FROM ret t
|
|
FROM ret t
|
|
- INNER JOIN bmk_ret bmk ON t.end_date = bmk.end_date
|
|
|
|
|
|
+ INNER JOIN benchmarks bm ON t.entity_id = bm.entity_id
|
|
|
|
+ INNER JOIN bmk_ret bmk ON t.end_date = bmk.end_date AND bm.benchmark_id = bmk.benchmark_id
|
|
WHERE t.ret > -1
|
|
WHERE t.ret > -1
|
|
AND bmk.ret > -1
|
|
AND bmk.ret > -1
|
|
- CONTEXT BY t.entity_id;
|
|
|
|
|
|
+ CONTEXT BY t.entity_id, bm.benchmark_id;
|
|
|
|
|
|
- t = SELECT entity_id,
|
|
|
|
|
|
+ t = SELECT entity_id, end_date.max() AS end_date, price_date.max() AS price_date, price_date.min() AS min_date, benchmark_id,
|
|
exc_ret.bucketCount(0:999, 1) \ cnt[0] AS winrate,
|
|
exc_ret.bucketCount(0:999, 1) \ cnt[0] AS winrate,
|
|
exc_ret.std() AS track_error,
|
|
exc_ret.std() AS track_error,
|
|
exc_ret.mean() / exc_ret.std() AS info
|
|
exc_ret.mean() / exc_ret.std() AS info
|
|
- FROM t0 GROUP BY entity_id
|
|
|
|
|
|
+ FROM t0 GROUP BY entity_id, benchmark_id;
|
|
|
|
|
|
return t;
|
|
return t;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
/*
|
|
|
|
+ * Upside/Down Capture Return/Ratio
|
|
|
|
+ *
|
|
|
|
+ */
|
|
|
|
+def cal_capture_ratio(ret, benchmarks, bmk_ret) {
|
|
|
|
+
|
|
|
|
+ t1 = SELECT t.entity_id, (1+t.ret).prod() AS upside_ret, (1+bmk.ret).prod() AS bmk_upside_ret, bmk.end_date.count() AS bmk_upside_cnt, bm.benchmark_id
|
|
|
|
+ FROM ret t
|
|
|
|
+ INNER JOIN benchmarks bm ON t.entity_id = bm.entity_id
|
|
|
|
+ INNER JOIN bmk_ret bmk ON t.end_date = bmk.end_date AND bm.benchmark_id = bmk.benchmark_id
|
|
|
|
+ WHERE t.ret > -1
|
|
|
|
+ AND bmk.ret >= 0
|
|
|
|
+ GROUP BY t.entity_id, bm.benchmark_id;
|
|
|
|
+
|
|
|
|
+ t2 = SELECT t.entity_id, (1+t.ret).prod() AS downside_ret, (1+bmk.ret).prod() AS bmk_downside_ret, bmk.end_date.count() AS bmk_downside_cnt, bm.benchmark_id
|
|
|
|
+ FROM ret t
|
|
|
|
+ INNER JOIN benchmarks bm ON t.entity_id = bm.entity_id
|
|
|
|
+ INNER JOIN bmk_ret bmk ON t.end_date = bmk.end_date AND bm.benchmark_id = bmk.benchmark_id
|
|
|
|
+ WHERE t.ret > -1
|
|
|
|
+ AND bmk.ret < 0
|
|
|
|
+ GROUP BY t.entity_id, bm.benchmark_id;
|
|
|
|
+
|
|
|
|
+ t = SELECT iif(isNull(t1.entity_id), t2.entity_id, t1.entity_id) AS entity_id,
|
|
|
|
+ iif(isNull(t1.benchmark_id), t2.benchmark_id, t1.benchmark_id) AS benchmark_id,
|
|
|
|
+ t1.upside_ret.pow(1 \ t1.bmk_upside_cnt)-1 AS upside_capture_ret,
|
|
|
|
+ (t1.upside_ret.pow(1 \ t1.bmk_upside_cnt)-1)/(t1.bmk_upside_ret.pow(1 \ t1.bmk_upside_cnt)-1) AS upside_capture_ratio,
|
|
|
|
+ t2.downside_ret.pow(1 \ t2.bmk_downside_cnt)-1 AS downside_capture_ret,
|
|
|
|
+ (t2.downside_ret.pow(1 \ t2.bmk_downside_cnt)-1)/(t2.bmk_downside_ret.pow(1 \ t2.bmk_downside_cnt)-1) AS downside_capture_ratio
|
|
|
|
+ FROM t1 FULL JOIN t2 ON t1.entity_id = t2.entity_id AND t1.benchmark_id = t2.benchmark_id;
|
|
|
|
+
|
|
|
|
+ return t;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/*
|
|
* Sharpe Ratio
|
|
* Sharpe Ratio
|
|
* NOTE: Java version is noncompliant-GIPS annulized number
|
|
* NOTE: Java version is noncompliant-GIPS annulized number
|
|
*/
|
|
*/
|
|
@@ -192,10 +258,11 @@ def cal_treynor(ret, risk_free, beta) {
|
|
AND rfr.ret > -1
|
|
AND rfr.ret > -1
|
|
CONTEXT BY t.entity_id;
|
|
CONTEXT BY t.entity_id;
|
|
|
|
|
|
- treynor = SELECT t.entity_id, ((1 + t.ret).prod().pow(12\iif(t.cnt[0]<12, 12, t.cnt[0])) - (1 + t.rfr_ret).prod().pow(12\iif(t.cnt[0]<12, 12, t.cnt[0]))) / beta.beta[0] AS treynor
|
|
|
|
|
|
+ treynor = SELECT t.entity_id, beta.benchmark_id,
|
|
|
|
+ ((1 + t.ret).prod().pow(12\iif(t.cnt[0]<12, 12, t.cnt[0])) - (1 + t.rfr_ret).prod().pow(12\iif(t.cnt[0]<12, 12, t.cnt[0]))) / beta.beta[0] AS treynor
|
|
FROM t
|
|
FROM t
|
|
INNER JOIN beta AS beta ON t.entity_id = beta.entity_id
|
|
INNER JOIN beta AS beta ON t.entity_id = beta.entity_id
|
|
- GROUP BY t.entity_id;
|
|
|
|
|
|
+ GROUP BY t.entity_id, beta.benchmark_id;
|
|
|
|
|
|
return treynor;
|
|
return treynor;
|
|
}
|
|
}
|
|
@@ -206,12 +273,12 @@ def cal_treynor(ret, risk_free, beta) {
|
|
*/
|
|
*/
|
|
def cal_jensen(ret, bmk_ret, risk_free, beta) {
|
|
def cal_jensen(ret, bmk_ret, risk_free, beta) {
|
|
|
|
|
|
- jensen = SELECT t.entity_id, t.ret.mean() - rfr.ret.mean() - beta.beta[0] * (bmk.ret.mean() - rfr.ret.mean()) AS jensen
|
|
|
|
|
|
+ jensen = SELECT t.entity_id, t.ret.mean() - rfr.ret.mean() - beta.beta[0] * (bmk.ret.mean() - rfr.ret.mean()) AS jensen, beta.benchmark_id
|
|
FROM ret t
|
|
FROM ret t
|
|
- INNER JOIN bmk_ret bmk ON t.end_date = bmk.end_date
|
|
|
|
- INNER JOIN risk_free rfr ON t.end_date = rfr.end_date
|
|
|
|
INNER JOIN beta beta ON t.entity_id = beta.entity_id
|
|
INNER JOIN beta beta ON t.entity_id = beta.entity_id
|
|
- GROUP BY t.entity_id;
|
|
|
|
|
|
+ INNER JOIN bmk_ret bmk ON t.end_date = bmk.end_date AND beta.benchmark_id = bmk.benchmark_id
|
|
|
|
+ INNER JOIN risk_free rfr ON t.end_date = rfr.end_date
|
|
|
|
+ GROUP BY t.entity_id, beta.benchmark_id;
|
|
|
|
|
|
return jensen;
|
|
return jensen;
|
|
}
|
|
}
|
|
@@ -234,13 +301,14 @@ def cal_calmar(ret_a){
|
|
* NOTE: M2 = sharpe * std(benchmark) + risk_free_rate
|
|
* NOTE: M2 = sharpe * std(benchmark) + risk_free_rate
|
|
* NOTE: Java version is noncompliant-GIPS annulized number
|
|
* NOTE: Java version is noncompliant-GIPS annulized number
|
|
*/
|
|
*/
|
|
-def cal_m2(ret, bmk_ret, risk_free) {
|
|
|
|
|
|
+def cal_m2(ret, benchmarks, bmk_ret, risk_free) {
|
|
|
|
|
|
- m2 = SELECT t.entity_id, (t.ret - rfr.ret).mean() / t.ret.std() * bmk.ret.std() + rfr.ret.mean() AS m2
|
|
|
|
|
|
+ m2 = SELECT t.entity_id, (t.ret - rfr.ret).mean() / t.ret.std() * bmk.ret.std() + rfr.ret.mean() AS m2, bm.benchmark_id
|
|
FROM ret t
|
|
FROM ret t
|
|
- INNER JOIN bmk_ret bmk ON t.end_date = bmk.end_date
|
|
|
|
|
|
+ INNER JOIN benchmarks bm ON t.entity_id = bm.entity_id
|
|
|
|
+ INNER JOIN bmk_ret bmk ON t.end_date = bmk.end_date AND bm.benchmark_id = bmk.benchmark_id
|
|
INNER JOIN risk_free rfr ON t.end_date = rfr.end_date
|
|
INNER JOIN risk_free rfr ON t.end_date = rfr.end_date
|
|
- GROUP BY t.entity_id;
|
|
|
|
|
|
+ GROUP BY t.entity_id, bm.benchmark_id;
|
|
|
|
|
|
return m2;
|
|
return m2;
|
|
}
|
|
}
|
|
@@ -267,13 +335,12 @@ def cal_ms_return(ret, risk_free) {
|
|
return r;
|
|
return r;
|
|
}
|
|
}
|
|
|
|
|
|
-
|
|
|
|
/*
|
|
/*
|
|
- * Monthly Since_inception_date Indicator Calculation
|
|
|
|
- * @param: ret <TABLE>: 收益表,NEED COLUMNS entity_id, price_dat, end_date, nav
|
|
|
|
|
|
+ * Calculation for monthly indicators which need benchmark
|
|
|
|
+ * @param ret <TABLE>: 收益表,NEED COLUMNS entity_id, price_dat, end_date, nav
|
|
|
|
+ * @param benchmarks <TABLE>: entity-benchmark mapping table
|
|
* @param index_ret <TABLE>: historical benchmark return table, NEED COLUMNS fund_id, end_date, ret
|
|
* @param index_ret <TABLE>: historical benchmark return table, NEED COLUMNS fund_id, end_date, ret
|
|
* @param risk_free <TABLE>: historical risk free rate table, NEED COLUMNS fund_id, end_date, ret
|
|
* @param risk_free <TABLE>: historical risk free rate table, NEED COLUMNS fund_id, end_date, ret
|
|
- * @param freq <CHAR>: 数据频率,d, w, m, q, s, a
|
|
|
|
*
|
|
*
|
|
* @return: indicators table
|
|
* @return: indicators table
|
|
*
|
|
*
|
|
@@ -282,24 +349,16 @@ def cal_ms_return(ret, risk_free) {
|
|
* TODO: some datapoints require more data, we need a way to disable calculation for them
|
|
* TODO: some datapoints require more data, we need a way to disable calculation for them
|
|
*
|
|
*
|
|
*/
|
|
*/
|
|
-def cal_indicators(mutable ret, index_ret, risk_free, freq) {
|
|
|
|
-
|
|
|
|
- if (! freq IN ['d', 'w', 'm', 'q', 's', 'a']) return null;
|
|
|
|
|
|
+def cal_indicators_with_benchmark(mutable ret, benchmarks, index_ret, risk_free) {
|
|
|
|
|
|
// sorting for correct first() and last() value
|
|
// sorting for correct first() and last() value
|
|
ret.sortBy!(['entity_id', 'price_date'], [1, 1]);
|
|
ret.sortBy!(['entity_id', 'price_date'], [1, 1]);
|
|
|
|
|
|
- // 收益、标准差、偏度、峰度、最大回撤、VaR, CVaR
|
|
|
|
- rtn = cal_basic_performance(ret, freq);
|
|
|
|
-
|
|
|
|
// alpha, beta
|
|
// alpha, beta
|
|
- alpha_beta = cal_alpha_beta(ret, index_ret, risk_free);
|
|
|
|
|
|
+ alpha_beta = cal_alpha_beta(ret, benchmarks, index_ret, risk_free);
|
|
|
|
|
|
// 胜率、跟踪误差、信息比率
|
|
// 胜率、跟踪误差、信息比率
|
|
- bmk_tracking = cal_benchmark_tracking(ret, index_ret);
|
|
|
|
-
|
|
|
|
- // 夏普
|
|
|
|
- sharpe = cal_sharpe(ret, rtn, risk_free);
|
|
|
|
|
|
+ bmk_tracking = cal_benchmark_tracking(ret, benchmarks, index_ret);
|
|
|
|
|
|
// 特雷诺
|
|
// 特雷诺
|
|
treynor = cal_treynor(ret, risk_free, alpha_beta);
|
|
treynor = cal_treynor(ret, risk_free, alpha_beta);
|
|
@@ -307,60 +366,108 @@ def cal_indicators(mutable ret, index_ret, risk_free, freq) {
|
|
// 詹森指数
|
|
// 詹森指数
|
|
jensen = cal_jensen(ret, index_ret, risk_free, alpha_beta);
|
|
jensen = cal_jensen(ret, index_ret, risk_free, alpha_beta);
|
|
|
|
|
|
|
|
+ // M2
|
|
|
|
+ m2 = cal_m2(ret, benchmarks, index_ret, risk_free);
|
|
|
|
+
|
|
|
|
+ // 上下行捕获率、收益
|
|
|
|
+ capture_r = cal_capture_ratio(ret, benchmarks, index_ret);
|
|
|
|
+
|
|
|
|
+ r = SELECT * FROM bmk_tracking a1
|
|
|
|
+ LEFT JOIN alpha_beta ON a1.entity_id = alpha_beta.entity_id AND a1.benchmark_id = alpha_beta.benchmark_id
|
|
|
|
+ LEFT JOIN treynor ON a1.entity_id = treynor.entity_id AND a1.benchmark_id = treynor.benchmark_id
|
|
|
|
+ LEFT JOIN jensen ON a1.entity_id = jensen.entity_id AND a1.benchmark_id = jensen.benchmark_id
|
|
|
|
+ LEFT JOIN m2 ON a1.entity_id = m2.entity_id AND a1.benchmark_id = m2.benchmark_id
|
|
|
|
+ LEFT JOIN capture_r ON a1.entity_id = capture_r.entity_id AND a1.benchmark_id = capture_r.benchmark_id;
|
|
|
|
+
|
|
|
|
+ // 年化各数据点
|
|
|
|
+ // GIPS RULE: NO annulization for data less than 1 year
|
|
|
|
+ plainAnnu = get_annulization_multiple('m');
|
|
|
|
+ sqrtAnnu = sqrt(get_annulization_multiple('m'));
|
|
|
|
+
|
|
|
|
+ r.addColumn(['alpha_a', 'jensen_a', 'track_error_a', 'info_a', 'm2_a'],
|
|
|
|
+ [DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE]);
|
|
|
|
+
|
|
|
|
+ UPDATE r
|
|
|
|
+ SET alpha_a = alpha * iif(price_date.month() - min_date.month() >= 11, plainAnnu, 1),
|
|
|
|
+ jensen_a = jensen * iif(price_date.month() - min_date.month() >= 11, plainAnnu, 1),
|
|
|
|
+ track_error_a = track_error * iif(price_date.month() - min_date.month() >= 11, sqrtAnnu, 1),
|
|
|
|
+ info_a = info * iif(price_date.month() - min_date.month() >= 11, sqrtAnnu, 1),
|
|
|
|
+ m2_a = m2 * iif(price_date.month() - min_date.month() >= 11, plainAnnu, 1);
|
|
|
|
+
|
|
|
|
+ return r.dropColumns!(['end_date', 'price_date', 'min_date']);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * Monthly standard indicator calculation
|
|
|
|
+ * @param: ret <TABLE>: 收益表,NEED COLUMNS entity_id, price_dat, end_date, nav
|
|
|
|
+ * @param benchmarks <TABLE>: entity-benchmark mapping table
|
|
|
|
+ * @param index_ret <TABLE>: historical benchmark return table, NEED COLUMNS fund_id, end_date, ret
|
|
|
|
+ * @param risk_free <TABLE>: historical risk free rate table, NEED COLUMNS fund_id, end_date, ret
|
|
|
|
+ * @param freq <CHAR>: 数据频率,d, w, m, q, s, a
|
|
|
|
+ *
|
|
|
|
+ * @return: indicators table
|
|
|
|
+ *
|
|
|
|
+ *
|
|
|
|
+ * Create 20240904 模仿Java & python代码在Dolphin中实现,具体计算逻辑可能会有不同 Joey
|
|
|
|
+ * TODO: some datapoints require more data, we need a way to disable calculation for them
|
|
|
|
+ *
|
|
|
|
+ */
|
|
|
|
+def cal_indicators(mutable ret, benchmarks, benchmark_ret, risk_free) {
|
|
|
|
+
|
|
|
|
+ // sorting for correct first() and last() value
|
|
|
|
+ ret.sortBy!(['entity_id', 'price_date'], [1, 1]);
|
|
|
|
+
|
|
|
|
+ // 收益、标准差、偏度、峰度、最大回撤、VaR, CVaR
|
|
|
|
+ rtn = cal_basic_performance(ret, 'm');
|
|
|
|
+
|
|
|
|
+ // 夏普
|
|
|
|
+ sharpe = cal_sharpe(ret, rtn, risk_free);
|
|
|
|
+
|
|
// 卡玛比率
|
|
// 卡玛比率
|
|
calmar = cal_calmar(rtn);
|
|
calmar = cal_calmar(rtn);
|
|
|
|
|
|
// 整合后的下行标准差、欧米伽、索提诺、卡帕
|
|
// 整合后的下行标准差、欧米伽、索提诺、卡帕
|
|
lpms = cal_omega_sortino_kappa(ret, risk_free);
|
|
lpms = cal_omega_sortino_kappa(ret, risk_free);
|
|
|
|
|
|
- // M2
|
|
|
|
- m2 = cal_m2(ret, index_ret, risk_free);
|
|
|
|
|
|
+ // 需要基准的指标们
|
|
|
|
+ indicator_with_benchmark = cal_indicators_with_benchmark(ret, benchmarks, benchmark_ret, risk_free);
|
|
|
|
|
|
r = SELECT * FROM rtn a1
|
|
r = SELECT * FROM rtn a1
|
|
- LEFT JOIN alpha_beta ON a1.entity_id = alpha_beta.entity_id
|
|
|
|
- LEFT JOIN bmk_tracking ON a1.entity_id = bmk_tracking.entity_id
|
|
|
|
LEFT JOIN sharpe ON a1.entity_id = sharpe.entity_id
|
|
LEFT JOIN sharpe ON a1.entity_id = sharpe.entity_id
|
|
- LEFT JOIN treynor ON a1.entity_id = treynor.entity_id
|
|
|
|
- LEFT JOIN jensen ON a1.entity_id = jensen.entity_id
|
|
|
|
LEFT JOIN calmar ON a1.entity_id = calmar.entity_id
|
|
LEFT JOIN calmar ON a1.entity_id = calmar.entity_id
|
|
LEFT JOIN lpms ON a1.entity_id = lpms.entity_id
|
|
LEFT JOIN lpms ON a1.entity_id = lpms.entity_id
|
|
- LEFT JOIN m2 ON a1.entity_id = m2.entity_id
|
|
|
|
|
|
+ LEFT JOIN indicator_with_benchmark ON a1.entity_id = indicator_with_benchmark.entity_id;
|
|
|
|
|
|
// 年化各数据点
|
|
// 年化各数据点
|
|
// GIPS RULE: NO annulization for data less than 1 year
|
|
// GIPS RULE: NO annulization for data less than 1 year
|
|
- plainAnnu = get_annulization_multiple(freq);
|
|
|
|
- sqrtAnnu = sqrt(get_annulization_multiple(freq));
|
|
|
|
|
|
+ plainAnnu = get_annulization_multiple('m');
|
|
|
|
+ sqrtAnnu = sqrt(get_annulization_multiple('m'));
|
|
|
|
|
|
- r.addColumn(['std_dev_a', 'ds_dev_a', 'alpha_a', 'sharpe_a', 'sortino_a', 'jensen_a', 'track_error_a', 'info_a', 'm2_a'],
|
|
|
|
- [DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE]);
|
|
|
|
|
|
+ r.addColumn(['std_dev_a', 'ds_dev_a', 'sharpe_a', 'sortino_a'],
|
|
|
|
+ [DOUBLE, DOUBLE, DOUBLE, DOUBLE]);
|
|
|
|
|
|
UPDATE r
|
|
UPDATE r
|
|
SET std_dev_a = std_dev * iif(price_date.month() - min_date.month() >= 11, sqrtAnnu, 1),
|
|
SET std_dev_a = std_dev * iif(price_date.month() - min_date.month() >= 11, sqrtAnnu, 1),
|
|
ds_dev_a = ds_dev * iif(price_date.month() - min_date.month() >= 11, sqrtAnnu, 1),
|
|
ds_dev_a = ds_dev * iif(price_date.month() - min_date.month() >= 11, sqrtAnnu, 1),
|
|
- alpha_a = alpha * iif(price_date.month() - min_date.month() >= 11, plainAnnu, 1),
|
|
|
|
sharpe_a = sharpe * iif(price_date.month() - min_date.month() >= 11, sqrtAnnu, 1),
|
|
sharpe_a = sharpe * iif(price_date.month() - min_date.month() >= 11, sqrtAnnu, 1),
|
|
- sortino_a = sortino * iif(price_date.month() - min_date.month() >= 11, sqrtAnnu, 1),
|
|
|
|
- jensen_a = jensen * iif(price_date.month() - min_date.month() >= 11, plainAnnu, 1),
|
|
|
|
- track_error_a = track_error * iif(price_date.month() - min_date.month() >= 11, sqrtAnnu, 1),
|
|
|
|
- info_a = info * iif(price_date.month() - min_date.month() >= 11, sqrtAnnu, 1),
|
|
|
|
- m2_a = m2 * iif(price_date.month() - min_date.month() >= 11, plainAnnu, 1);
|
|
|
|
|
|
+ sortino_a = sortino * iif(price_date.month() - min_date.month() >= 11, sqrtAnnu, 1);
|
|
|
|
|
|
return r;
|
|
return r;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+
|
|
/*
|
|
/*
|
|
* Calculate trailing 6m, ytd, 1y, 2y, 3y, 4y, 5y, 10y and since inception indicators
|
|
* Calculate trailing 6m, ytd, 1y, 2y, 3y, 4y, 5y, 10y and since inception indicators
|
|
*
|
|
*
|
|
* @param: entity_info <TABLE>: basic information of entity, NEED COLUMNS entity_id, inception_date
|
|
* @param: entity_info <TABLE>: basic information of entity, NEED COLUMNS entity_id, inception_date
|
|
|
|
+ * @param benchmarks <TABLE>: entity-benchmark mapping table
|
|
* @param: ret <TABLE>: 收益表,NEED COLUMNS entity_id, price_dat, end_date, nav
|
|
* @param: ret <TABLE>: 收益表,NEED COLUMNS entity_id, price_dat, end_date, nav
|
|
* @param: end_day <DATE>: 计算截止日期
|
|
* @param: end_day <DATE>: 计算截止日期
|
|
* @param index_ret <TABLE>: historical benchmark return table, NEED COLUMNS fund_id, end_date, ret
|
|
* @param index_ret <TABLE>: historical benchmark return table, NEED COLUMNS fund_id, end_date, ret
|
|
* @param risk_free <TABLE>: historical risk free rate table, NEED COLUMNS fund_id, end_date, ret
|
|
* @param risk_free <TABLE>: historical risk free rate table, NEED COLUMNS fund_id, end_date, ret
|
|
- * @param freq <CHAR>: 数据频率,d, w, m, q, s, a
|
|
|
|
-
|
|
|
|
*
|
|
*
|
|
*/
|
|
*/
|
|
-def cal_all_trailing_indicators(entity_info, mutable tb_ret, end_day, bmk_ret, risk_free_rate, freq) {
|
|
|
|
|
|
+def cal_trailing_indicators(entity_info, benchmarks, mutable tb_ret, end_day, bmk_ret, risk_free_rate) {
|
|
|
|
|
|
r_incep = null;
|
|
r_incep = null;
|
|
r_ytd = null;
|
|
r_ytd = null;
|
|
@@ -377,41 +484,41 @@ def cal_all_trailing_indicators(entity_info, mutable tb_ret, end_day, bmk_ret, r
|
|
|
|
|
|
// since inception
|
|
// since inception
|
|
if(tb_ret.size() > 0) {
|
|
if(tb_ret.size() > 0) {
|
|
- r_incep = cal_indicators(tb_ret, bmk_ret, risk_free_rate, 'm');
|
|
|
|
|
|
+ r_incep = cal_indicators(tb_ret, benchmarks, bmk_ret, risk_free_rate);
|
|
}
|
|
}
|
|
|
|
|
|
// ytd
|
|
// ytd
|
|
tb_ret_ytd = SELECT * FROM tb_ret WHERE end_date >= end_day.yearBegin().month();
|
|
tb_ret_ytd = SELECT * FROM tb_ret WHERE end_date >= end_day.yearBegin().month();
|
|
if(tb_ret_ytd.size() > 0) {
|
|
if(tb_ret_ytd.size() > 0) {
|
|
- r_ytd = cal_indicators(tb_ret_ytd, bmk_ret, risk_free_rate, 'm');
|
|
|
|
|
|
+ r_ytd = cal_indicators(tb_ret_ytd, benchmarks, bmk_ret, risk_free_rate);
|
|
}
|
|
}
|
|
|
|
|
|
// trailing 6m
|
|
// trailing 6m
|
|
tb_ret_6m = SELECT * FROM tb_ret r INNER JOIN entity_info ei ON r.entity_id = ei.entity_id
|
|
tb_ret_6m = SELECT * FROM tb_ret r INNER JOIN entity_info ei ON r.entity_id = ei.entity_id
|
|
WHERE r.end_date > end_day.month()-6 AND (end_day.month() - ei.inception_date.month()) >= 6;
|
|
WHERE r.end_date > end_day.month()-6 AND (end_day.month() - ei.inception_date.month()) >= 6;
|
|
if(tb_ret_6m.size() > 0) {
|
|
if(tb_ret_6m.size() > 0) {
|
|
- r_6m = cal_indicators(tb_ret_6m, bmk_ret, risk_free_rate, 'm');
|
|
|
|
|
|
+ r_6m = cal_indicators(tb_ret_6m, benchmarks, bmk_ret, risk_free_rate);
|
|
}
|
|
}
|
|
|
|
|
|
// trailing 1y
|
|
// trailing 1y
|
|
tb_ret_1y = SELECT * FROM tb_ret r INNER JOIN entity_info ei ON r.entity_id = ei.entity_id
|
|
tb_ret_1y = SELECT * FROM tb_ret r INNER JOIN entity_info ei ON r.entity_id = ei.entity_id
|
|
WHERE r.end_date > end_day.month()-12 AND (end_day.month() - ei.inception_date.month()) >= 12;
|
|
WHERE r.end_date > end_day.month()-12 AND (end_day.month() - ei.inception_date.month()) >= 12;
|
|
if(tb_ret_1y.size() > 0) {
|
|
if(tb_ret_1y.size() > 0) {
|
|
- r_1y = cal_indicators(tb_ret_1y, bmk_ret, risk_free_rate, 'm');
|
|
|
|
|
|
+ r_1y = cal_indicators(tb_ret_1y, benchmarks, bmk_ret, risk_free_rate);
|
|
}
|
|
}
|
|
|
|
|
|
// trailing 2y
|
|
// trailing 2y
|
|
tb_ret_2y = SELECT * FROM tb_ret r INNER JOIN entity_info ei ON r.entity_id = ei.entity_id
|
|
tb_ret_2y = SELECT * FROM tb_ret r INNER JOIN entity_info ei ON r.entity_id = ei.entity_id
|
|
WHERE r.end_date > end_day.month()-24 AND (end_day.month() - ei.inception_date.month()) >= 24;
|
|
WHERE r.end_date > end_day.month()-24 AND (end_day.month() - ei.inception_date.month()) >= 24;
|
|
if(tb_ret_2y.size() > 0) {
|
|
if(tb_ret_2y.size() > 0) {
|
|
- r_2y = cal_indicators(tb_ret_2y, bmk_ret, risk_free_rate, 'm');
|
|
|
|
|
|
+ r_2y = cal_indicators(tb_ret_2y, benchmarks, bmk_ret, risk_free_rate);
|
|
}
|
|
}
|
|
|
|
|
|
// trailing 3y
|
|
// trailing 3y
|
|
tb_ret_3y = SELECT * FROM tb_ret r INNER JOIN entity_info ei ON r.entity_id = ei.entity_id
|
|
tb_ret_3y = SELECT * FROM tb_ret r INNER JOIN entity_info ei ON r.entity_id = ei.entity_id
|
|
WHERE r.end_date > end_day.month()-36 AND (end_day.month() - ei.inception_date.month()) >= 36;
|
|
WHERE r.end_date > end_day.month()-36 AND (end_day.month() - ei.inception_date.month()) >= 36;
|
|
if(tb_ret_3y.size() > 0) {
|
|
if(tb_ret_3y.size() > 0) {
|
|
- r_3y = cal_indicators(tb_ret_3y, bmk_ret, risk_free_rate, 'm');
|
|
|
|
|
|
+ r_3y = cal_indicators(tb_ret_3y, benchmarks, bmk_ret, risk_free_rate);
|
|
r_ms_3y = cal_ms_return(tb_ret_3y, risk_free_rate);
|
|
r_ms_3y = cal_ms_return(tb_ret_3y, risk_free_rate);
|
|
}
|
|
}
|
|
|
|
|
|
@@ -419,14 +526,14 @@ def cal_all_trailing_indicators(entity_info, mutable tb_ret, end_day, bmk_ret, r
|
|
tb_ret_4y = SELECT * FROM tb_ret r INNER JOIN entity_info ei ON r.entity_id = ei.entity_id
|
|
tb_ret_4y = SELECT * FROM tb_ret r INNER JOIN entity_info ei ON r.entity_id = ei.entity_id
|
|
WHERE r.end_date > end_day.month()-48 AND (end_day.month() - ei.inception_date.month()) >= 48;
|
|
WHERE r.end_date > end_day.month()-48 AND (end_day.month() - ei.inception_date.month()) >= 48;
|
|
if(tb_ret_4y.size() > 0) {
|
|
if(tb_ret_4y.size() > 0) {
|
|
- r_4y = cal_indicators(tb_ret_4y, bmk_ret, risk_free_rate, 'm');
|
|
|
|
|
|
+ r_4y = cal_indicators(tb_ret_4y, benchmarks, bmk_ret, risk_free_rate);
|
|
}
|
|
}
|
|
|
|
|
|
// trailing 5y
|
|
// trailing 5y
|
|
tb_ret_5y = SELECT * FROM tb_ret r INNER JOIN entity_info ei ON r.entity_id = ei.entity_id
|
|
tb_ret_5y = SELECT * FROM tb_ret r INNER JOIN entity_info ei ON r.entity_id = ei.entity_id
|
|
WHERE r.end_date > end_day.month()-60 AND (end_day.month() - ei.inception_date.month()) >= 60;
|
|
WHERE r.end_date > end_day.month()-60 AND (end_day.month() - ei.inception_date.month()) >= 60;
|
|
if(tb_ret_5y.size() > 0) {
|
|
if(tb_ret_5y.size() > 0) {
|
|
- r_5y = cal_indicators(tb_ret_5y, bmk_ret, risk_free_rate, 'm');
|
|
|
|
|
|
+ r_5y = cal_indicators(tb_ret_5y, benchmarks, bmk_ret, risk_free_rate);
|
|
r_ms_5y = cal_ms_return(tb_ret_5y, risk_free_rate);
|
|
r_ms_5y = cal_ms_return(tb_ret_5y, risk_free_rate);
|
|
}
|
|
}
|
|
|
|
|
|
@@ -434,7 +541,7 @@ def cal_all_trailing_indicators(entity_info, mutable tb_ret, end_day, bmk_ret, r
|
|
tb_ret_10y = SELECT * FROM tb_ret r INNER JOIN entity_info ei ON r.entity_id = ei.entity_id
|
|
tb_ret_10y = SELECT * FROM tb_ret r INNER JOIN entity_info ei ON r.entity_id = ei.entity_id
|
|
WHERE r.end_date > end_day.month()-120 AND (end_day.month() - ei.inception_date.month()) >= 120;
|
|
WHERE r.end_date > end_day.month()-120 AND (end_day.month() - ei.inception_date.month()) >= 120;
|
|
if(tb_ret_10y.size() > 0) {
|
|
if(tb_ret_10y.size() > 0) {
|
|
- r_10y = cal_indicators(tb_ret_10y, bmk_ret, risk_free_rate, 'm');
|
|
|
|
|
|
+ r_10y = cal_indicators(tb_ret_10y, benchmarks, bmk_ret, risk_free_rate);
|
|
r_ms_10y = cal_ms_return(tb_ret_10y, risk_free_rate);
|
|
r_ms_10y = cal_ms_return(tb_ret_10y, risk_free_rate);
|
|
}
|
|
}
|
|
|
|
|
|
@@ -449,8 +556,10 @@ def cal_all_trailing_indicators(entity_info, mutable tb_ret, end_day, bmk_ret, r
|
|
* @param end_day <DATE>: 要计算的日期
|
|
* @param end_day <DATE>: 要计算的日期
|
|
* @param isFromNav <BOOL>: 用净值实时计算还是从表中取月收益
|
|
* @param isFromNav <BOOL>: 用净值实时计算还是从表中取月收益
|
|
* @param isFromSQL <BOOL>: TODO: 从MySQL还是本地DolphinDB取净值/收益数据
|
|
* @param isFromSQL <BOOL>: TODO: 从MySQL还是本地DolphinDB取净值/收益数据
|
|
|
|
+ *
|
|
|
|
+ * TODO: primary_benchmark_id seems not be used as benchmark, when it is FA00000VNB
|
|
*
|
|
*
|
|
- * Example: cal_fund_indicators('FD', "'HF000004KN','HF000103EU','HF00018WXG'", 2024.06.28, true);
|
|
|
|
|
|
+ * Example: cal_fund_indicators('HF', "'HF000004KN','HF000103EU','HF00018WXG'", 2024.06.28, true);
|
|
*
|
|
*
|
|
*/
|
|
*/
|
|
def cal_fund_indicators(entity_type, fund_ids, end_day, isFromNav) {
|
|
def cal_fund_indicators(entity_type, fund_ids, end_day, isFromNav) {
|
|
@@ -469,12 +578,16 @@ def cal_fund_indicators(entity_type, fund_ids, end_day, isFromNav) {
|
|
tb_ret = get_monthly_ret('FD', fund_ids, very_old_date, end_day, true);
|
|
tb_ret = get_monthly_ret('FD', fund_ids, very_old_date, end_day, true);
|
|
tb_ret.rename!(['fund_id'], ['entity_id']);
|
|
tb_ret.rename!(['fund_id'], ['entity_id']);
|
|
}
|
|
}
|
|
-
|
|
|
|
- bmk_ret = SELECT fund_id, temporalParse(end_date, 'yyyy-MM') AS end_date, ret FROM get_monthly_ret('IX', "'IN00000008'", very_old_date, end_day, true);
|
|
|
|
|
|
+
|
|
|
|
+ // 取基金和基准的对照表
|
|
|
|
+ primary_benchmark = SELECT entity_id, iif(benchmark_id.isNull(), 'IN00000008', benchmark_id) AS benchmark_id FROM fund_info;
|
|
|
|
+
|
|
|
|
+ // 取所有出现的基准月收益
|
|
|
|
+ bmk_ret = get_benchmark_return(primary_benchmark, end_day);
|
|
|
|
|
|
risk_free_rate = SELECT fund_id, temporalParse(end_date, 'yyyy-MM') AS end_date, ret FROM get_risk_free_rate(very_old_date, end_day);
|
|
risk_free_rate = SELECT fund_id, temporalParse(end_date, 'yyyy-MM') AS end_date, ret FROM get_risk_free_rate(very_old_date, end_day);
|
|
|
|
|
|
- return cal_all_trailing_indicators(fund_info, tb_ret, end_day, bmk_ret, risk_free_rate, 'm');
|
|
|
|
|
|
+ return cal_trailing_indicators(fund_info, primary_benchmark, tb_ret, end_day, bmk_ret, risk_free_rate);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
/*
|
|
@@ -513,7 +626,7 @@ def cal_portfolio_indicators(portfolio_ids, end_day, cal_method, isFromNav) {
|
|
}
|
|
}
|
|
|
|
|
|
// 沪深300做基准,同SQL保持一致
|
|
// 沪深300做基准,同SQL保持一致
|
|
- bmk_ret = SELECT fund_id, temporalParse(end_date, 'yyyy-MM') AS end_date, ret FROM get_monthly_ret('IX', "'IN00000008'", very_old_date, end_day, true);
|
|
|
|
|
|
+ bmk_ret = SELECT fund_id AS benchmark_id, 'PBI' AS benchmark_type, temporalParse(end_date, 'yyyy-MM') AS end_date, ret FROM get_monthly_ret('IX', "'IN00000008'", very_old_date, end_day, true);
|
|
|
|
|
|
risk_free_rate = SELECT fund_id, temporalParse(end_date, 'yyyy-MM') AS end_date, ret FROM get_risk_free_rate(very_old_date, end_day);
|
|
risk_free_rate = SELECT fund_id, temporalParse(end_date, 'yyyy-MM') AS end_date, ret FROM get_risk_free_rate(very_old_date, end_day);
|
|
|
|
|