|
@@ -76,7 +76,7 @@ def cal_basic_performance(ret, freq) {
|
|
* NOTE: risk free rate is used as Minimal Accepted Rate (MAR) here
|
|
* NOTE: risk free rate is used as Minimal Accepted Rate (MAR) here
|
|
*
|
|
*
|
|
*/
|
|
*/
|
|
-def cal_LPM(ret, risk_free_rate) {
|
|
|
|
|
|
+def cal_LPM(ret, risk_free) {
|
|
|
|
|
|
t = SELECT *, count(entity_id) AS cnt FROM ret WHERE ret > -1 CONTEXT BY entity_id;
|
|
t = SELECT *, count(entity_id) AS cnt FROM ret WHERE ret > -1 CONTEXT BY entity_id;
|
|
|
|
|
|
@@ -85,7 +85,7 @@ def cal_LPM(ret, risk_free_rate) {
|
|
(sum2(rfr.ret - t.ret) \ (t.cnt[0])).pow(1\2) AS lpm2,
|
|
(sum2(rfr.ret - t.ret) \ (t.cnt[0])).pow(1\2) AS lpm2,
|
|
(sum3(rfr.ret - t.ret) \ (t.cnt[0])).pow(1\3) AS lpm3
|
|
(sum3(rfr.ret - t.ret) \ (t.cnt[0])).pow(1\3) AS lpm3
|
|
FROM t
|
|
FROM t
|
|
- INNER JOIN risk_free_rate rfr ON t.end_date = rfr.end_date
|
|
|
|
|
|
+ INNER JOIN risk_free rfr ON t.end_date = rfr.end_date
|
|
WHERE t.ret < rfr.ret
|
|
WHERE t.ret < rfr.ret
|
|
GROUP BY t.entity_id;
|
|
GROUP BY t.entity_id;
|
|
|
|
|
|
@@ -100,9 +100,9 @@ def cal_LPM(ret, risk_free_rate) {
|
|
* Java'version of Kappa could be very wrong
|
|
* Java'version of Kappa could be very wrong
|
|
*
|
|
*
|
|
*/
|
|
*/
|
|
-def cal_omega_sortino_kappa(ret, risk_free_rate) {
|
|
|
|
|
|
+def cal_omega_sortino_kappa(ret, risk_free) {
|
|
|
|
|
|
- lpm = cal_LPM(ret, risk_free_rate);
|
|
|
|
|
|
+ lpm = cal_LPM(ret, risk_free);
|
|
|
|
|
|
tb = SELECT t.entity_id,
|
|
tb = SELECT t.entity_id,
|
|
l.lpm2[0] AS ds_dev,
|
|
l.lpm2[0] AS ds_dev,
|
|
@@ -111,7 +111,7 @@ def cal_omega_sortino_kappa(ret, risk_free_rate) {
|
|
(t.ret - rfr.ret ).mean() \ l.lpm3[0] AS kappa
|
|
(t.ret - rfr.ret ).mean() \ l.lpm3[0] AS kappa
|
|
FROM ret t
|
|
FROM ret t
|
|
INNER JOIN lpm l ON t.entity_id = l.entity_id
|
|
INNER JOIN lpm l ON t.entity_id = l.entity_id
|
|
- INNER JOIN risk_free_rate 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;
|
|
|
|
|
|
return tb;
|
|
return tb;
|
|
@@ -135,7 +135,7 @@ def cal_alpha_beta(ret, bmk_ret, risk_free) {
|
|
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.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
|
|
- INNER JOIN risk_free_rate 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;
|
|
|
|
|
|
return ( SELECT * FROM beta AS b INNER JOIN alpha AS a ON a.entity_id = b.entity_id );
|
|
return ( SELECT * FROM beta AS b INNER JOIN alpha AS a ON a.entity_id = b.entity_id );
|
|
@@ -168,12 +168,12 @@ def cal_benchmark_tracking(ret, bmk_ret) {
|
|
* Sharpe Ratio
|
|
* Sharpe Ratio
|
|
* NOTE: Java version is noncompliant-GIPS annulized number
|
|
* NOTE: Java version is noncompliant-GIPS annulized number
|
|
*/
|
|
*/
|
|
-def cal_sharpe(ret, std_dev, risk_free_rate) {
|
|
|
|
|
|
+def cal_sharpe(ret, std_dev, risk_free) {
|
|
|
|
|
|
sharpe = SELECT t.entity_id, (t.ret - rfr.ret).mean() / std.std_dev[0] AS sharpe
|
|
sharpe = SELECT t.entity_id, (t.ret - rfr.ret).mean() / std.std_dev[0] AS sharpe
|
|
FROM ret t
|
|
FROM ret t
|
|
INNER JOIN std_dev std ON t.entity_id = std.entity_id
|
|
INNER JOIN std_dev std ON t.entity_id = std.entity_id
|
|
- INNER JOIN risk_free_rate rfr ON t.end_date = rfr.end_date
|
|
|
|
|
|
+ INNER JOIN risk_free rfr ON t.end_date = rfr.end_date
|
|
WHERE std.std_dev[0] <> 0
|
|
WHERE std.std_dev[0] <> 0
|
|
GROUP BY t.entity_id;
|
|
GROUP BY t.entity_id;
|
|
|
|
|
|
@@ -183,11 +183,11 @@ def cal_sharpe(ret, std_dev, risk_free_rate) {
|
|
/*
|
|
/*
|
|
* Treynor Ratio
|
|
* Treynor Ratio
|
|
*/
|
|
*/
|
|
-def cal_treynor(ret, risk_free_rate, beta) {
|
|
|
|
|
|
+def cal_treynor(ret, risk_free, beta) {
|
|
|
|
|
|
t = SELECT *, count(entity_id) AS cnt
|
|
t = SELECT *, count(entity_id) AS cnt
|
|
FROM ret t
|
|
FROM ret t
|
|
- INNER JOIN risk_free_rate rfr ON t.end_date = rfr.end_date
|
|
|
|
|
|
+ INNER JOIN risk_free rfr ON t.end_date = rfr.end_date
|
|
WHERE t.ret > -1
|
|
WHERE t.ret > -1
|
|
AND rfr.ret > -1
|
|
AND rfr.ret > -1
|
|
CONTEXT BY t.entity_id;
|
|
CONTEXT BY t.entity_id;
|
|
@@ -204,12 +204,12 @@ def cal_treynor(ret, risk_free_rate, beta) {
|
|
* Jensen's Alpha
|
|
* Jensen's Alpha
|
|
* TODO: the result is slightly off
|
|
* TODO: the result is slightly off
|
|
*/
|
|
*/
|
|
-def cal_jensen(ret, bmk_ret, risk_free_rate, 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
|
|
FROM ret t
|
|
FROM ret t
|
|
INNER JOIN bmk_ret bmk ON t.end_date = bmk.end_date
|
|
INNER JOIN bmk_ret bmk ON t.end_date = bmk.end_date
|
|
- INNER JOIN risk_free_rate rfr ON t.end_date = rfr.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;
|
|
GROUP BY t.entity_id;
|
|
|
|
|
|
@@ -234,12 +234,12 @@ 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_rate) {
|
|
|
|
|
|
+def cal_m2(ret, 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
|
|
FROM ret t
|
|
FROM ret t
|
|
INNER JOIN bmk_ret bmk ON t.end_date = bmk.end_date
|
|
INNER JOIN bmk_ret bmk ON t.end_date = bmk.end_date
|
|
- INNER JOIN risk_free_rate 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;
|
|
|
|
|
|
return m2;
|
|
return m2;
|
|
@@ -255,13 +255,13 @@ def cal_m2(ret, bmk_ret, risk_free_rate) {
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*/
|
|
*/
|
|
-def cal_ms_return(ret, risk_free_rate) {
|
|
|
|
|
|
+def cal_ms_return(ret, risk_free) {
|
|
|
|
|
|
r = SELECT t.entity_id, t.end_date.max() AS end_date, t.price_date.max() AS price_date, t.price_date.min() AS min_date,
|
|
r = SELECT t.entity_id, t.end_date.max() AS end_date, t.price_date.max() AS price_date, t.price_date.min() AS min_date,
|
|
((1 + t.ret)\(1 + rfr.ret)).prod().pow(12\(t.end_date.max() - t.end_date.min()))-1 AS ms_ret_a,
|
|
((1 + t.ret)\(1 + rfr.ret)).prod().pow(12\(t.end_date.max() - t.end_date.min()))-1 AS ms_ret_a,
|
|
(1 + t.ret).pow(-2).mean().pow(-12/2)-1 AS ms_rar_a
|
|
(1 + t.ret).pow(-2).mean().pow(-12/2)-1 AS ms_rar_a
|
|
FROM ret t
|
|
FROM ret t
|
|
- INNER JOIN risk_free_rate 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;
|
|
|
|
|
|
return r;
|
|
return r;
|
|
@@ -442,7 +442,7 @@ def cal_all_trailing_indicators(entity_info, mutable tb_ret, end_day, bmk_ret, r
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
/*
|
|
- * Calculate fund indicators for month-end production
|
|
|
|
|
|
+ * Calculate fund indicators for one date
|
|
*
|
|
*
|
|
* @param entity_type <STRING>: MF, HF
|
|
* @param entity_type <STRING>: MF, HF
|
|
* @param fund_ids <STRING>: 逗号和单引号分隔的fund_id
|
|
* @param fund_ids <STRING>: 逗号和单引号分隔的fund_id
|
|
@@ -450,6 +450,8 @@ def cal_all_trailing_indicators(entity_info, mutable tb_ret, end_day, bmk_ret, r
|
|
* @param isFromNav <BOOL>: 用净值实时计算还是从表中取月收益
|
|
* @param isFromNav <BOOL>: 用净值实时计算还是从表中取月收益
|
|
* @param isFromSQL <BOOL>: TODO: 从MySQL还是本地DolphinDB取净值/收益数据
|
|
* @param isFromSQL <BOOL>: TODO: 从MySQL还是本地DolphinDB取净值/收益数据
|
|
*
|
|
*
|
|
|
|
+ * Example: cal_fund_indicators('FD', "'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) {
|
|
|
|
|
|
@@ -475,6 +477,17 @@ def cal_fund_indicators(entity_type, fund_ids, end_day, isFromNav) {
|
|
return cal_all_trailing_indicators(fund_info, tb_ret, end_day, bmk_ret, risk_free_rate, 'm');
|
|
return cal_all_trailing_indicators(fund_info, tb_ret, end_day, bmk_ret, risk_free_rate, 'm');
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+/*
|
|
|
|
+ * Calculate portfolio indicators for one date
|
|
|
|
+ *
|
|
|
|
+ * @param portfolio_ids <STRING>: comma-delimited portfolio ids
|
|
|
|
+ * @param end_day <DATE>: the date
|
|
|
|
+ * @param cal_method <INT>: calculate based on cumulative nav (1) or nav (2)
|
|
|
|
+ * @param isFromNav <BOOL>: calculate returns from NAV on-the-fly (true) or get from monthly return table (false)
|
|
|
|
+ *
|
|
|
|
+ * Example: cal_portfolio_indicators('166002,166114', 2024.08.31, 1, true);
|
|
|
|
+ *
|
|
|
|
+ */
|
|
def cal_portfolio_indicators(portfolio_ids, end_day, cal_method, isFromNav) {
|
|
def cal_portfolio_indicators(portfolio_ids, end_day, cal_method, isFromNav) {
|
|
|
|
|
|
very_old_date = 1990.01.01;
|
|
very_old_date = 1990.01.01;
|
|
@@ -484,7 +497,7 @@ def cal_portfolio_indicators(portfolio_ids, end_day, cal_method, isFromNav) {
|
|
|
|
|
|
if(isFromNav == true) {
|
|
if(isFromNav == true) {
|
|
// 从净值开始计算收益
|
|
// 从净值开始计算收益
|
|
- tb_raw_ret = SELECT * FROM cal_portfolio_return(portfolio_ids, very_old_date, cal_method) WHERE price_date <= end_day;
|
|
|
|
|
|
+ tb_raw_ret = SELECT * FROM cal_portfolio_nav(portfolio_ids, very_old_date, cal_method) WHERE price_date <= end_day;
|
|
|
|
|
|
// funky thing is you can't use "AS" for the grouping columns?
|
|
// funky thing is you can't use "AS" for the grouping columns?
|
|
tb_ret = SELECT portfolio_id, price_date.month(), price_date.last() AS price_date, (1+ret).prod()-1 AS ret, nav.last() AS nav
|
|
tb_ret = SELECT portfolio_id, price_date.month(), price_date.last() AS price_date, (1+ret).prod()-1 AS ret, nav.last() AS nav
|
|
@@ -499,7 +512,7 @@ def cal_portfolio_indicators(portfolio_ids, end_day, cal_method, isFromNav) {
|
|
tb_ret.rename!(['portfolio_id'], ['entity_id']);
|
|
tb_ret.rename!(['portfolio_id'], ['entity_id']);
|
|
}
|
|
}
|
|
|
|
|
|
-
|
|
|
|
|
|
+ // 沪深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, 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);
|