Ver Fonte

小修小补,<TAB>变空格

Joey há 1 semana atrás
pai
commit
11c93c9e34
1 ficheiros alterados com 187 adições e 53 exclusões
  1. 187 53
      modules/indicatorCalculator.dos

+ 187 - 53
modules/indicatorCalculator.dos

@@ -1,5 +1,8 @@
 module fundit::indicatorCalculator
 
+use fundit::dataPuller
+use fundit::returnCalculator
+use fundit::navCalculator
 /*
  *  Annulized multiple
  */
@@ -8,17 +11,17 @@ def get_annulization_multiple(freq) {
   ret = 1;
   
   if (freq == 'd') {
-  	ret = 252; // We have differences here between Java and DolphinDB, Java uses 365.25 days
+    ret = 252; // We have differences here between Java and DolphinDB, Java uses 365.25 days
   } else if (freq == 'w') {
-  	ret = 52;
+    ret = 52;
   } else if (freq == 'm') {
-  	ret = 12;
+    ret = 12;
   } else if (freq == 'q') {
-  	ret = 4;
+    ret = 4;
   } else if (freq == 's') {
-  	ret = 2;
+    ret = 2;
   } else if (freq == 'a') {
-  	ret = 1;
+    ret = 1;
   }
   
   return ret;
@@ -38,21 +41,21 @@ def get_annulization_multiple(freq) {
  */
 def cal_basic_performance(ret, freq) {
 
-    t =	SELECT entity_id, max(end_date) AS end_date, max(price_date) AS price_date, min(price_date) AS min_date,
-	           //(nav.last() \ nav.first() - 1).round(6) AS trailing_ret,
-	           ((1+ret).prod()-1).round(6) AS trailing_ret,
-	           iif(price_date.max().month()-price_date.min().month()>12,
-	               //(nav.last() \ nav.first()).pow(365 \(max(price_date) - min(price_date)))-1, 
-	               //(nav.last() \ nav.first() - 1)).round(6) AS trailing_ret_a,
+    t = SELECT entity_id, max(end_date) AS end_date, max(price_date) AS price_date, min(price_date) AS min_date,
+               //(nav.last() \ nav.first() - 1).round(6) AS trailing_ret,
+               ((1+ret).prod()-1).round(6) AS trailing_ret,
+               iif(price_date.max().month()-price_date.min().month()>12,
+                   //(nav.last() \ nav.first()).pow(365 \(max(price_date) - min(price_date)))-1, 
+                   //(nav.last() \ nav.first() - 1)).round(6) AS trailing_ret_a,
                    ((1+ret).prod()-1) * sqrt(get_annulization_multiple(freq)),
-	               ((1+ret).prod()-1)).round(6) AS trailing_ret_a,
+                   ((1+ret).prod()-1)).round(6) AS trailing_ret_a,
                ret.std() AS std_dev,
                ret.skew(false) AS skewness,
                ret.kurtosis(false) - 3 AS kurtosis,
                ret.min() AS wrst_month,
                max( 1 - nav \ nav.cummax() ) AS drawdown
         FROM ret
-	    GROUP BY entity_id;
+        GROUP BY entity_id;
 
     // var & cvar require return NOT NULL
     // NOTE: DolphinDB supports 4 different ways: normal, logNormal, historical, monteCarlo. we use historical
@@ -61,7 +64,7 @@ def cal_basic_performance(ret, freq) {
                 ret.CVaR('historical', 0.95) AS cvar
          FROM ret
          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);
 
@@ -74,8 +77,8 @@ def cal_basic_performance(ret, freq) {
  * 
  */
 def cal_LPM(ret, risk_free_rate) {
-	
-	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;
 
     lpm = SELECT t.entity_id, max(t.end_date) AS end_date,
                  (sum (rfr.ret - t.ret) \ (t.cnt[0])).pow(1\1) AS lpm1, 
@@ -111,7 +114,7 @@ def cal_omega_sortino_kappa(ret, risk_free_rate) {
               INNER JOIN risk_free_rate rfr ON t.end_date = rfr.end_date
               GROUP BY t.entity_id;
 
-	return tb;
+    return tb;
 }
 
 
@@ -121,7 +124,7 @@ def cal_omega_sortino_kappa(ret, risk_free_rate) {
  */
 def cal_alpha_beta(ret, 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, bmk.ret AS ret_bmk
         FROM ret t
         INNER JOIN bmk_ret bmk ON t.end_date = bmk.end_date
         WHERE t.ret > -1
@@ -145,7 +148,7 @@ def cal_alpha_beta(ret, bmk_ret, risk_free) {
  */
 def cal_benchmark_tracking(ret, 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.ret, bmk.ret AS ret_bmk, count(entity_id) AS cnt, (t.ret - bmk.ret) AS exc_ret
           FROM ret t
           INNER JOIN bmk_ret bmk ON t.end_date = bmk.end_date
           WHERE t.ret > -1
@@ -167,7 +170,7 @@ def cal_benchmark_tracking(ret, bmk_ret) {
  */
 def cal_sharpe(ret, std_dev, risk_free_rate) {
 
-	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
              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
@@ -182,13 +185,13 @@ def cal_sharpe(ret, std_dev, risk_free_rate) {
  */
 def cal_treynor(ret, risk_free_rate, beta) {
 
-	t = SELECT *, count(entity_id) AS cnt 
-	   FROM ret t 
-	   INNER JOIN risk_free_rate rfr ON t.end_date = rfr.end_date 
-	   WHERE t.ret > -1
-	     AND rfr.ret > -1
-	   CONTEXT BY t.entity_id;
-	   
+    t = SELECT *, count(entity_id) AS cnt 
+       FROM ret t 
+       INNER JOIN risk_free_rate rfr ON t.end_date = rfr.end_date 
+       WHERE t.ret > -1
+         AND rfr.ret > -1
+       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
               FROM t
               INNER JOIN beta AS beta ON t.entity_id = beta.entity_id
@@ -203,7 +206,7 @@ def cal_treynor(ret, risk_free_rate, beta) {
  */
 def cal_jensen(ret, bmk_ret, risk_free_rate, 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
              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
@@ -220,7 +223,7 @@ def cal_jensen(ret, bmk_ret, risk_free_rate, beta) {
  */
 def cal_calmar(ret_a){
 
-	calmar = SELECT entity_id, trailing_ret_a \ drawdown AS calmar
+    calmar = SELECT entity_id, trailing_ret_a \ drawdown AS calmar
              FROM ret_a;
 
     return calmar;
@@ -242,6 +245,28 @@ def cal_m2(ret, bmk_ret, risk_free_rate) {
     return m2;
 }
 
+/*
+ *    Morningstar Return, Morningstar Risk-Adjusted Return
+ *    
+ *    TODO: Tax and loads are NOT taken care of
+ *    TODO: Assume Chinese methodology using 3, 5, 10 as number of traling years
+ *    
+ *    NOTE: Morningstar methodology requires monthly return for calculation, so that "12" is hard-coded here
+ * 
+ * 
+ */
+def cal_ms_return(ret, risk_free_rate) {
+
+    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).pow(-2).mean().pow(-12/2)-1 AS ms_rar_a
+        FROM ret t
+        INNER JOIN risk_free_rate rfr ON t.end_date = rfr.end_date
+        GROUP BY t.entity_id;
+
+    return r;
+}
+
 
 /*
  *   Monthly Since_inception_date Indicator Calculation
@@ -326,6 +351,7 @@ def cal_indicators(mutable ret, index_ret, risk_free, freq) {
 /*
  *   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: ret <TABLE>: 收益表,NEED COLUMNS entity_id, price_dat, end_date, nav 
  *   @param: end_day <DATE>: 计算截止日期
  *   @param index_ret <TABLE>: historical benchmark return table, NEED COLUMNS fund_id, end_date, ret
@@ -334,43 +360,151 @@ def cal_indicators(mutable ret, index_ret, risk_free, freq) {
 
  * 
  */
-def cal_all_trailing_indicators(mutable tb_ret, end_day, bmk_ret, risk_free_rate, freq) {
-    
+def cal_all_trailing_indicators(entity_info, mutable tb_ret, end_day, bmk_ret, risk_free_rate, freq) {
+
+    r_incep = null;
+    r_ytd = null;
+    r_6m = null;
+    r_1y = null;
+    r_2y = null;
+    r_3y = null;
+    r_4y = null;
+    r_5y = null;
+    r_10y = null;
+    r_ms_3y = null;
+    r_ms_5y = null;
+    r_ms_10y = null;
+
     // since inception
-    r_incep = cal_indicators(tb_ret, bmk_ret, risk_free_rate, 'm');
-    
+    if(tb_ret.size() > 0) {
+        r_incep = cal_indicators(tb_ret, bmk_ret, risk_free_rate, 'm');
+    }
+
     // ytd
     tb_ret_ytd = SELECT * FROM tb_ret WHERE end_date >= end_day.yearBegin().month();
-    r_ytd = cal_indicators(tb_ret_ytd, bmk_ret, risk_free_rate, 'm');
-    
+    if(tb_ret_ytd.size() > 0) {
+        r_ytd = cal_indicators(tb_ret_ytd, bmk_ret, risk_free_rate, 'm');
+    }
+
     // trailing 6m
-    tb_ret_6m = SELECT * FROM tb_ret WHERE end_date > end_day.month()-6;
-    r_6m = cal_indicators(tb_ret_6m, bmk_ret, risk_free_rate, 'm');
+    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;
+    if(tb_ret_6m.size() > 0) {
+        r_6m = cal_indicators(tb_ret_6m, bmk_ret, risk_free_rate, 'm');
+    }
     
     // trailing 1y
-    tb_ret_1y = SELECT * FROM tb_ret WHERE end_date > end_day.month()-12;
-    r_1y = cal_indicators(tb_ret_1y, bmk_ret, risk_free_rate, 'm');
+    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;
+    if(tb_ret_1y.size() > 0) {
+        r_1y = cal_indicators(tb_ret_1y, bmk_ret, risk_free_rate, 'm');
+    }
     
     // trailing 2y
-    tb_ret_2y = SELECT * FROM tb_ret WHERE end_date > end_day.month()-24;
-    r_2y = cal_indicators(tb_ret_2y, bmk_ret, risk_free_rate, 'm');
+    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;
+    if(tb_ret_2y.size() > 0) {
+        r_2y = cal_indicators(tb_ret_2y, bmk_ret, risk_free_rate, 'm');
+    }
     
     // trailing 3y
-    tb_ret_3y = SELECT * FROM tb_ret WHERE end_date > end_day.month()-36;
-    r_3y = cal_indicators(tb_ret_3y, bmk_ret, risk_free_rate, 'm');
-    
+    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;
+    if(tb_ret_3y.size() > 0) {
+        r_3y = cal_indicators(tb_ret_3y, bmk_ret, risk_free_rate, 'm');
+        r_ms_3y = cal_ms_return(tb_ret_3y, risk_free_rate);
+    }
+
     // trailing 4y
-    tb_ret_4y = SELECT * FROM tb_ret WHERE end_date > end_day.month()-48;
-    r_4y = cal_indicators(tb_ret_4y, bmk_ret, risk_free_rate, 'm');
+    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;
+    if(tb_ret_4y.size() > 0) {
+        r_4y = cal_indicators(tb_ret_4y, bmk_ret, risk_free_rate, 'm');
+    }
     
     // trailing 5y
-    tb_ret_5y = SELECT * FROM tb_ret WHERE end_date > end_day.month()-60;
-    r_5y = cal_indicators(tb_ret_5y, bmk_ret, risk_free_rate, 'm');
-    
+    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;
+    if(tb_ret_5y.size() > 0) {
+        r_5y = cal_indicators(tb_ret_5y, bmk_ret, risk_free_rate, 'm');
+        r_ms_5y = cal_ms_return(tb_ret_5y, risk_free_rate);
+    }
+
     // trailing 10y
-    tb_ret_10y = SELECT * FROM tb_ret WHERE end_date > end_day.month()-120;
-    r_10y = cal_indicators(tb_ret_10y, bmk_ret, risk_free_rate, 'm');
+    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;
+    if(tb_ret_10y.size() > 0) {
+        r_10y = cal_indicators(tb_ret_10y, bmk_ret, risk_free_rate, 'm');
+        r_ms_10y = cal_ms_return(tb_ret_10y, risk_free_rate);
+    }
+
+    return r_incep, r_ytd, r_6m, r_1y, r_2y, r_3y, r_4y, r_5y, r_10y, r_ms_3y, r_ms_5y, r_ms_10y;
+}
+
+/*
+ *   Calculate fund indicators for month-end production
+ * 
+ *   @param entity_type <STRING>: MF, HF
+ *   @param fund_ids <STRING>: 逗号和单引号分隔的fund_id
+ *   @param end_day <DATE>: 要计算的日期
+ *   @param isFromNav <BOOL>: 用净值实时计算还是从表中取月收益
+ *   @param isFromSQL <BOOL>: TODO: 从MySQL还是本地DolphinDB取净值/收益数据
+ * 
+ */
+def cal_fund_indicators(entity_type, fund_ids, end_day, isFromNav) {
+
+    very_old_date = 1990.01.01;
+
+    fund_info = get_fund_info(fund_ids);
+    fund_info.rename!('fund_id', 'entity_id');
+
+    if(isFromNav == true) {
+        // 从净值开始计算收益
+        tb_ret = SELECT * FROM cal_fund_monthly_returns(entity_type, fund_ids, true) WHERE price_date <= end_day;
+        tb_ret.rename!(['fund_id', 'cumulative_nav'], ['entity_id', 'nav']);
+    } else {
+        // 从fund_performance表里读月收益
+        tb_ret = get_monthly_ret('FD', fund_ids, very_old_date, end_day, true);
+        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); 
 
-    return r_incep, r_ytd, r_6m, r_1y, r_2y, r_3y, r_4y, r_5y, r_10y;
+    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');
 }
 
+def cal_portfolio_indicators(portfolio_ids, end_day, cal_method, isFromNav) {
+
+    very_old_date = 1990.01.01;
+
+    portfolio_info = get_portfolio_info(portfolio_ids);
+    portfolio_info.rename!('portfolio_id', 'entity_id');
+
+    if(isFromNav == true) {
+        // 从净值开始计算收益
+        tb_raw_ret = SELECT * FROM cal_portfolio_return(portfolio_ids, very_old_date, cal_method) WHERE price_date <= end_day;
+       
+        // 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
+                 FROM tb_raw_ret
+                 WHERE price_date <= end_day
+                 GROUP BY portfolio_id, price_date.month();
+        tb_ret.rename!(['portfolio_id', 'month_price_date'], ['entity_id', 'end_date']);
+
+    } else {
+        // 从pf_portfolio_performance表里读月收益
+        tb_ret = get_monthly_ret('PF', portfolio_ids, very_old_date, end_day, true);
+        tb_ret.rename!(['portfolio_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); 
+
+    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(portfolio_info, tb_ret, end_day, bmk_ret, risk_free_rate, 'm');
+
+    
+}