Joey 6 hónapja
szülő
commit
81497ce0c3

+ 40 - 0
modules/fund_performance_dolphin.sql

@@ -0,0 +1,40 @@
+USE mfdb;
+
+CREATE TABLE `fund_performance_dolphin` (
+  `fund_id` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '基金id',
+  `end_date` varchar(7) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '截至日期',
+  `price_date` date DEFAULT NULL COMMENT '最近累计净值日期',
+  `cumulative_nav` decimal(22,6) DEFAULT NULL COMMENT '最近累计净值',
+  `ret_1m` decimal(22,6) DEFAULT NULL COMMENT '最近一个月收益率',
+  `ret_1m_a` decimal(22,6) DEFAULT NULL COMMENT '最近一个月收益率(年化)',
+  `ret_3m` decimal(22,6) DEFAULT NULL COMMENT '最近三个月收益率',
+  `ret_3m_a` decimal(22,6) DEFAULT NULL COMMENT '最近三个月收益率(年化)',
+  `ret_6m` decimal(22,6) DEFAULT NULL COMMENT '最近半年收益率',
+  `ret_6m_a` decimal(22,6) DEFAULT NULL COMMENT '最近半年收益率(年化)',
+  `ret_1y` decimal(22,6) DEFAULT NULL COMMENT '最近一年收益率',
+  `ret_1y_a` decimal(22,6) DEFAULT NULL COMMENT '最近一年收益率(年化)',
+  `ret_2y` decimal(22,6) DEFAULT NULL COMMENT '最近两年收益率',
+  `ret_2y_a` decimal(22,6) DEFAULT NULL COMMENT '最近两年收益率(年化)',
+  `ret_3y` decimal(22,6) DEFAULT NULL COMMENT '最近三年收益率',
+  `ret_3y_a` decimal(22,6) DEFAULT NULL COMMENT '最近三年收益率(年化)',
+  `ret_4y` DECIMAL(22,6) DEFAULT NULL COMMENT '最近四年收益率',
+  `ret_4y_a` DECIMAL(22,6) DEFAULT NULL COMMENT '最近四年收益率(年化)',
+  `ret_5y` decimal(22,6) DEFAULT NULL COMMENT '最近五年收益率',
+  `ret_5y_a` decimal(22,6) DEFAULT NULL COMMENT '最近五年收益率(年化)',
+  `ret_10y` decimal(22,6) DEFAULT NULL COMMENT '最近十年收益率',
+  `ret_10y_a` decimal(22,6) DEFAULT NULL COMMENT '最近十年收益率(年化)',
+  `ret_ytd` decimal(22,6) DEFAULT NULL COMMENT '今年以来收益率',
+  `ret_ytd_a` decimal(22,6) DEFAULT NULL COMMENT '今年以来收益率(年化)',
+  `ret_incep` decimal(22,6) DEFAULT NULL COMMENT '成立以来收益率',
+  `ret_incep_a` decimal(22,6) DEFAULT NULL COMMENT '成立以来收益率(一年以下不年化)',
+  `creatorid` int DEFAULT NULL COMMENT '创建者Id,默认第一次创建者名称,创建后不变更',
+  `createtime` datetime DEFAULT NULL COMMENT '创建时间,默认第一次创建的getdate()时间',
+  `updaterid` int DEFAULT NULL COMMENT '修改者Id;第一次创建时与Creator值相同,修改时与修改人值相同',
+  `updatetime` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间;第一次创建时与CreatTime值相同,修改时与修改时间相同',
+  `isvalid` tinyint NOT NULL DEFAULT '1' COMMENT '记录的有效性;1-有效;0-无效;',
+
+  PRIMARY KEY (`fund_id`,`end_date`),
+  KEY `idx_end_date` (`end_date`),
+  KEY `idx_fund_performance2` (`fund_id`,`price_date`),
+  KEY `idx_fund_performance3` (`updatetime`)
+) COMMENT='DolphinDB写回的基金历史业绩表'

+ 50 - 44
modules/indicatorCalculator.dos

@@ -84,10 +84,9 @@ def cal_basic_performance(entity_info, ret, trailing_month) {
 
     // accumulate 版的 skewness, kurtosis, var, cvar 似乎都不对劲,只好找个笨办法来实现
     if(trailing_month == 'incep') {
+
         // 需要至少6个数才计算标准差、峰度、偏度
-        t0 = SELECT price_date.max() AS price_date,
-                    prod(1+ret)-1 AS trailing_ret, 
-                    prod(1+ret)-1 AS trailing_ret_a,
+        t0 = SELECT price_date.max() AS price_date, nav, ret,
                     iif(count(entity_id) > 5, std(ret), null) AS std_dev,
                     iif(count(entity_id) > 5, skew(ret, false), null) AS skewness,
                     iif(count(entity_id) > 5, kurtosis(ret, false), null)-3 AS kurtosis,
@@ -99,10 +98,12 @@ def cal_basic_performance(entity_info, ret, trailing_month) {
              ORDER BY end_date;
 
         // 年化收益(给后面计算Calmar用)
+        t0.addColumn(['trailing_ret', 'trailing_ret_a'], [DOUBLE, DOUBLE]);
+        // MySQL 有bug导致首月ret_1m为空,所以用 prod(1+ret)-1算的有时不对
         UPDATE t0 
-            SET trailing_ret_a = (1+trailing_ret).pow(12\(t0.end_date - ei.inception_date.month()))-1
-        FROM ej(t0, entity_info ei, 'entity_id')
-        WHERE t0.end_date > ei.inception_date.month() + 12;
+        SET trailing_ret = nav\ini_value - 1, 
+            trailing_ret_a = iif(t0.end_date - ei.inception_date.month() > 12, (nav\ini_value).pow(12\(t0.end_date - ei.inception_date.month())) - 1, nav\ini_value - 1)
+        FROM ej(t0, entity_info ei, 'entity_id');
         
         // 不会用上面的办法算最大回撤, VaR, CVaR
         t_var = SELECT entity_id, end_date, ret,
@@ -122,7 +123,7 @@ def cal_basic_performance(entity_info, ret, trailing_month) {
 
     } else if(trailing_month == 'ytd') {
 
-        t1 = SELECT entity_id, end_date, price_date.cummax() AS price_date,
+        t1 = SELECT entity_id, end_date, price_date.cummax() AS price_date, nav, ret,
                     cumprod(1+ret)-1 AS trailing_ret,
                     cumprod(1+ret)-1 AS trailing_ret_a, // no need annulization for ytd
                     iif(cumcount(entity_id) > 5, cumstd(ret), null) AS std_dev,
@@ -136,12 +137,12 @@ def cal_basic_performance(entity_info, ret, trailing_month) {
 
     // trailing x month
     } else {
-        	
-        win = trailing_month$INT;
-        
-        t1 = SELECT entity_id, end_date, price_date.mmax(win) AS price_date,
+        // 先转成STRING,避免单字符被认为是CHAR而导致转整型出错的结果
+        win = trailing_month$STRING$INT;
+
+        t1 = SELECT entity_id, end_date, price_date.mmax(win) AS price_date, nav, ret,
                     mprod(1+ret, win)-1 AS trailing_ret,
-                    iif(trailing_month > 12,
+                    iif(win > 12,
                         mprod(1+ret, win).pow(12\win)-1,
                         mprod(1+ret, win)-1) AS trailing_ret_a,
                     mstd(ret, win) AS std_dev,
@@ -197,7 +198,7 @@ def cal_LPM(ret, risk_free, trailing_month) {
 
     } else {
 
-        win = trailing_month$INT;
+        win = trailing_month$STRING$INT;
         lpm = SELECT t.entity_id, t.end_date,
                      (msum (iif(rfr.ret > t.ret, rfr.ret - t.ret, 0), win) \ mcount(end_date, win)).pow(1\1) AS lpm1, 
                      (msum2(iif(rfr.ret > t.ret, rfr.ret - t.ret, 0), win) \ mcount(end_date, win)).pow(1\2) AS lpm2, 
@@ -253,7 +254,7 @@ def cal_omega_sortino_kappa(ret, risk_free, trailing_month) {
 
     } else {
 
-        win = trailing_month$INT;
+        win = trailing_month$STRING$INT;
         
         tb = SELECT t.entity_id, t.end_date,
                 l.lpm2 AS ds_dev,
@@ -325,7 +326,7 @@ def cal_benchmark_tracking(ret, benchmarks, bmk_ret, trailing_month) {
             ORDER BY entity_id, end_date, benchmark_id;
     } else {
 
-        win = trailing_month$INT;
+        win = trailing_month$STRING$INT;
 
         t0 = SELECT t.entity_id, t.end_date, t.price_date,
                     t.ret, bmk.ret AS ret_bmk,
@@ -393,7 +394,7 @@ def cal_alpha_beta(ret, benchmarks, bmk_ret, risk_free, trailing_month) {
 
     } else {
 
-        win = trailing_month$INT;
+        win = trailing_month$STRING$INT;
 
         beta = SELECT entity_id, end_date, benchmark_id, 
                       iif(mcount(end_date, win) > 5, ret.mbeta(ret_bmk, win), null) AS beta 
@@ -458,7 +459,7 @@ def cal_capture_ratio(ret, benchmarks, bmk_ret, trailing_month) {
 
     } else {
 
-        win = trailing_month$INT;
+        win = trailing_month$STRING$INT;
 
         t1 = SELECT t.entity_id, t.end_date,
                     (1 + iif(bmk.ret >= 0, t.ret, 0)).mprod(win) AS upside_ret,
@@ -513,7 +514,7 @@ def cal_sharpe(ret, std_dev, risk_free, trailing_month) {
                  CONTEXT BY t.entity_id, t.end_date.year();
     } else {
 
-        win = trailing_month$INT;
+        win = trailing_month$STRING$INT;
 
         sharpe = SELECT t.entity_id, t.end_date, (t.ret - rfr.ret).mavg(win) / std.std_dev AS sharpe
                  FROM ret t
@@ -564,7 +565,7 @@ def cal_treynor(ret, risk_free, beta, trailing_month) {
                   CONTEXT BY t.entity_id, beta.benchmark_id, t.end_date.year();
     } else {
 
-        win = trailing_month$INT;
+        win = trailing_month$STRING$INT;
 
         t = SELECT *, mcount(entity_id, win) AS cnt 
             FROM ret t 
@@ -611,7 +612,7 @@ def cal_jensen(ret, bmk_ret, risk_free, beta, trailing_month) {
 
     } else {
 
-        win = trailing_month$INT;
+        win = trailing_month$STRING$INT;
 
         jensen = SELECT t.entity_id, t.end_date, t.ret.mavg(win) - rfr.ret.mavg(win) - beta.beta * (bmk.ret.mavg(win) - rfr.ret.mavg(win)) AS jensen, beta.benchmark_id
                  FROM ret t
@@ -657,7 +658,7 @@ def cal_m2(ret, benchmarks, bmk_ret, risk_free, trailing_month) {
 
     } else {
 
-        win = trailing_month$INT;
+        win = trailing_month$STRING$INT;
 
         m2 = SELECT t.entity_id, t.end_date,
                     iif(t.entity_id.mcount(win) > 5, (t.ret - rfr.ret).mavg(win) / t.ret.mstd(win) * bmk.ret.mstd(win) + rfr.ret.mavg(win), null) AS m2, bm.benchmark_id
@@ -686,7 +687,7 @@ def cal_m2(ret, benchmarks, bmk_ret, risk_free, trailing_month) {
  */
 def cal_ms_return(ret, risk_free, trailing_month) {
 
-    win = trailing_month$INT;
+    win = trailing_month$STRING$INT;
 
     r = SELECT t.entity_id, t.end_date,
                ((1 + t.ret)\(1 + rfr.ret)).mprod(win).pow(12\(t.end_date.mmax(win) - t.end_date.mmin(win)))-1 AS ms_ret_a,
@@ -736,7 +737,7 @@ def get_effective_benchmarks(benchmarks, end_day, trailing_month, isEffectiveOnl
         	      HAVING bmk.end_date.cumcount() >= t.cnt * min_pct;
         } else {
 
-        	win = trailing_month$INT;
+        	win = trailing_month$STRING$INT;
 
         	t = SELECT entity_id, end_date, end_date.mcount(win) AS cnt FROM t_dates CONTEXT BY entity_id;
         	
@@ -900,7 +901,7 @@ def cal_indicators(entity_info, benchmarks, end_day, tb_ret, benchmark_ret, risk
 
 
 /*
- *   Calculate trailing 6m, ytd, 1y, 2y, 3y, 4y, 5y, 10y and since inception datapoints
+ *   Calculate trailing 3m, 6m, ytd, 1y, 2y, 3y, 4y, 5y, 10y and since inception datapoints
  *   
  *   @param: func <FUNCTION>: the calculation function
  *   @param: entity_info <TABLE>: basic information of entity, NEED COLUMNS entity_id, inception_date
@@ -909,14 +910,14 @@ def cal_indicators(entity_info, benchmarks, end_day, tb_ret, benchmark_ret, risk
  *   @param: ret <TABLE>: 收益表,NEED COLUMNS entity_id, price_dat, end_date, nav 
  *   @param bmk_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 periods <BOOL VECTOR>: 是否计算的区间向量,分别对应 incep, ytd, 6m, 1y, 2y, 3y, 4y, 5y, 10y
  *   
  * 
  */
-def cal_trailing(func, entity_info, benchmarks, end_day, tb_ret, bmk_ret, risk_free_rate) {
+def cal_trailing(func, entity_info, benchmarks, end_day, tb_ret, bmk_ret, risk_free_rate ) {
 
     r_incep = null;
     r_ytd = null;
+    r_3m = null;
     r_6m = null;
     r_1y = null;
     r_2y = null;
@@ -931,6 +932,9 @@ def cal_trailing(func, entity_info, benchmarks, end_day, tb_ret, bmk_ret, risk_f
     // ytd
     r_ytd = func(entity_info, benchmarks, end_day, tb_ret, bmk_ret, risk_free_rate, 'ytd');
 
+    // 3m 只需要支持收益计算
+    r_3m = cal_basic_performance(entity_info, tb_ret, '3');
+
     // 6m
     r_6m = func(entity_info, benchmarks, end_day, tb_ret, bmk_ret, risk_free_rate, '6');
 
@@ -952,12 +956,12 @@ def cal_trailing(func, entity_info, benchmarks, end_day, tb_ret, bmk_ret, risk_f
     // 10y
     r_10y = func(entity_info, benchmarks, end_day, tb_ret, bmk_ret, risk_free_rate, '120');
 
-    return r_incep, r_ytd, r_6m, r_1y, r_2y, r_3y, r_4y, r_5y, r_10y;
+    return r_incep, r_ytd, r_3m, r_6m, r_1y, r_2y, r_3y, r_4y, r_5y, r_10y;
 }
 
 
 /*
- *   Calculate trailing ytd, 6m, 1y, 2y, 3y, 4y, 5y, 10y and since inception standard indicators
+ *   Calculate trailing ytd, 3m, 6m, 1y, 2y, 3y, 4y, 5y, 10y and since inception standard indicators
  *   
  *   @param: entity_info <TABLE>: basic information of entity, NEED COLUMNS entity_id, inception_date
  *   @param benchmarks <TABLE>: entity-benchmark mapping table
@@ -974,7 +978,7 @@ def cal_trailing_indicators(entity_info, benchmarks, end_day, tb_ret, bmk_ret, r
 }
 
 /*
- *   Calculate trailing 6m, ytd, 1y, 2y, 3y, 4y, 5y, 10y and since inception bfi indicators
+ *   Calculate trailing ytd, 3m, 6m, 1y, 2y, 3y, 4y, 5y, 10y and since inception bfi indicators
  *   
  *   @param: entity_info <TABLE>: basic information of entity, NEED COLUMNS entity_id, inception_date
  *   @param benchmarks <TABLE>: entity-benchmark mapping table
@@ -983,6 +987,8 @@ def cal_trailing_indicators(entity_info, benchmarks, end_day, tb_ret, bmk_ret, r
  *   @param bmk_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
  *   
+ *   NOTE: 3m 的所有指标没有意义
+ *   
  * 
  */
 def cal_trailing_bfi_indicators(entity_info, benchmarks, end_day, tb_ret, bmk_ret, risk_free_rate) {
@@ -998,7 +1004,7 @@ def cal_trailing_bfi_indicators(entity_info, benchmarks, end_day, tb_ret, bmk_re
  *   @param indicator_type <STRING>: PBI, BFI
  *   @param monthly_returns <TABLE>: NEED COLUMN: entity_id, end_date, price_date, nav, ret
  * 
- *   @return <DICT TABLE>: ['PBI-INCEP', 'PBI-YTD', 'PBI-6M', 'PBI-1Y', 'PBI-2Y', 'PBI-3Y', 'PBI-4Y', 'PBI-5Y', 'PBI-10Y', 'MS-3Y', 'MS-5Y', 'MS-10Y']
+ *   @return <DICT TABLE>: ['PBI-INCEP', 'PBI-YTD', 'PBI-3M', 'PBI-6M', 'PBI-1Y', 'PBI-2Y', 'PBI-3Y', 'PBI-4Y', 'PBI-5Y', 'PBI-10Y']
  * 
  */
 def cal_monthly_indicators(entity_type, indicator_type, monthly_returns) {
@@ -1045,13 +1051,13 @@ def cal_monthly_indicators(entity_type, indicator_type, monthly_returns) {
 
         t0 = cal_trailing_bfi_indicators(entity_info, benchmark, end_day, monthly_returns, bmk_ret, risk_free_rate);
 
-        v_table_name = ['BFI-INCEP', 'BFI-YTD', 'BFI-6M', 'BFI-1Y', 'BFI-2Y', 'BFI-3Y', 'BFI-4Y', 'BFI-5Y', 'BFI-10Y'];
+        v_table_name = ['BFI-INCEP', 'BFI-YTD', 'BFI-3M', 'BFI-6M', 'BFI-1Y', 'BFI-2Y', 'BFI-3Y', 'BFI-4Y', 'BFI-5Y', 'BFI-10Y'];
 
     } else {
 
         t0 = cal_trailing_indicators(entity_info, benchmark, end_day, monthly_returns, bmk_ret, risk_free_rate);
 
-        v_table_name = ['PBI-INCEP', 'PBI-YTD', 'PBI-6M', 'PBI-1Y', 'PBI-2Y', 'PBI-3Y', 'PBI-4Y', 'PBI-5Y', 'PBI-10Y'];
+        v_table_name = ['PBI-INCEP', 'PBI-YTD', 'PBI-3M', 'PBI-6M', 'PBI-1Y', 'PBI-2Y', 'PBI-3Y', 'PBI-4Y', 'PBI-5Y', 'PBI-10Y'];
     }
 
     return dict(v_table_name, t0);
@@ -1060,32 +1066,32 @@ def cal_monthly_indicators(entity_type, indicator_type, monthly_returns) {
 /*
  *   Calculate historcial fund trailing indicators 
  * 
- *   @param fund_type <STRING>: MF, HF
+ *   @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取净值/收益数据
  *   
- *   @return <DICT TABLE>: ['PBI-INCEP', 'PBI-YTD', 'PBI-6M', 'PBI-1Y', 'PBI-2Y', 'PBI-3Y', 'PBI-4Y', 'PBI-5Y', 'PBI-10Y', 'MS-3Y', 'MS-5Y', 'MS-10Y']
+ *   @return <DICT TABLE>: ['PBI-INCEP', 'PBI-YTD', 'PBI-3M', 'PBI-6M', 'PBI-1Y', 'PBI-2Y', 'PBI-3Y', 'PBI-4Y', 'PBI-5Y', 'PBI-10Y', 'MS-3Y', 'MS-5Y', 'MS-10Y']
  *   
  * 
  *   Example: cal_fund_indicators('HF', "'HF000004KN','HF000103EU','HF00018WXG'", 2024.06.28, true);
  * 
  */
-def cal_fund_indicators(fund_type, fund_ids, end_day, isFromNav) {
+def cal_fund_indicators(entity_type, fund_ids, end_day, isFromNav) {
 
     very_old_date = 1990.01.01;
 
     if(isFromNav == true) {
 
         // 从净值开始计算收益
-        tb_ret = SELECT * FROM cal_fund_monthly_returns(fund_type, fund_ids, true) WHERE price_date <= end_day;
+        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(fund_type, fund_ids, very_old_date, end_day, true);
+        tb_ret = get_monthly_ret(entity_type, fund_ids, very_old_date, end_day, true);
     
         v_end_date = tb_ret.end_date.temporalParse('yyyy-MM');
         tb_ret.replaceColumn!('end_date', v_end_date);
@@ -1095,7 +1101,7 @@ def cal_fund_indicators(fund_type, fund_ids, end_day, isFromNav) {
     if(tb_ret.isVoid() || tb_ret.size() == 0) { return null; }
 
     // 标准的指标
-    d = cal_monthly_indicators(fund_type, 'PBI', tb_ret);
+    d = cal_monthly_indicators(entity_type, 'PBI', tb_ret);
 
     return d;
 
@@ -1104,32 +1110,32 @@ def cal_fund_indicators(fund_type, fund_ids, end_day, isFromNav) {
 /*
  *   Calculate historcial fund trailing BFI indicators 
  * 
- *   @param fund_type <STRING>: MF, HF
+ *   @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取净值/收益数据
  *   
- *   @return <DICT TABLE>: ['BFI-INCEP', 'BFI-YTD', 'BFI-6M', 'BFI-1Y', 'BFI-2Y', 'BFI-3Y', 'BFI-4Y', 'BFI-5Y', 'BFI-10Y']
+ *   @return <DICT TABLE>: ['BFI-INCEP', 'BFI-YTD', 'BFI-3M', 'BFI-6M', 'BFI-1Y', 'BFI-2Y', 'BFI-3Y', 'BFI-4Y', 'BFI-5Y', 'BFI-10Y']
  *   
  * 
  *   Example: cal_fund_bfi_indicators('MF', "'MF00003PW2', 'MF00003PW1', 'MF00003PXO'", 2024.08.31, true);
  * 
  */
-def cal_fund_bfi_indicators(fund_type, fund_ids, end_day, isFromNav) {
+def cal_fund_bfi_indicators(entity_type, fund_ids, end_day, isFromNav) {
 
     very_old_date = 1990.01.01;
 
     if(isFromNav == true) {
 
         // 从净值开始计算收益
-        tb_ret = SELECT * FROM cal_fund_monthly_returns(fund_type, fund_ids, true) WHERE price_date <= end_day;
+        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(fund_type, fund_ids, very_old_date, end_day, true);
+        tb_ret = get_monthly_ret(entity_type, fund_ids, very_old_date, end_day, true);
         tb_ret.rename!(['fund_id'], ['entity_id']);
     
         v_end_date = tb_ret.end_date.temporalParse('yyyy-MM');
@@ -1140,7 +1146,7 @@ def cal_fund_bfi_indicators(fund_type, fund_ids, end_day, isFromNav) {
     if(tb_ret.isVoid() || tb_ret.size() == 0) { return null; }
 
     // BFI指标
-    d = cal_monthly_indicators(fund_type, 'BFI', tb_ret);
+    d = cal_monthly_indicators(entity_type, 'BFI', tb_ret);
 
     return d;
 }

+ 5 - 3
modules/sqlUtilities.dos

@@ -95,18 +95,20 @@ def save_hedge_fund_nav_to_local(tb_nav) {
  *   未知形态的id转为MySQL需要的的逗号分隔字符串
  * 
  *   Example: ids_to_string("'a','b','c'");
- *            ids_to_string(['a', 'b', 'c']);
+ *            ids_to_string(['a', NULL, 'c']);
  */
 def ids_to_string(ids) {
 
     s_ids = '';
-    
+
+    if(ids.isVoid()) return s_ids;
+
     // 输入的 ids 是字符串标量
     if (ids.form() == 0) {
         s_ids = ids.trim();
     // 输入的 ids 是字符串向量
     } else if(ids.form() == 1) {
-        s_ids = "'" + ids.trim()concat("','") + "'";
+        s_ids = "'" + ids.concat("','").trim() + "'";
     // 缺省返回空
     } else {
     	s_ids = NULL;

+ 99 - 6
modules/task_fundPerformance.dos

@@ -1,10 +1,71 @@
 module fundit::task_fundPerformance
 
-use fundit::fundCalculator
+use fundit::sqlUtilities
 use fundit::dataPuller
 use fundit::returnCalculator
 use fundit::indicatorCalculator
+use fundit::fundCalculator
+
+
+/*
+ *   存 fund_performance 表
+ * 
+ * 
+ */
+def generate_fund_performance(fund_info, indicators, isToMySQL, mutable fund_performance) {
+
+    t = null;
+
+    if(isToMySQL) {
+
+        t = SELECT entity_id, end_date, price_date, nav AS cumulative_nav, ret AS ret_1m, ret AS ret_1m_a, trailing_ret AS ret_3m, trailing_ret_a AS ret_3m_a
+            FROM indicators['PBI-3M'] AS ind
+            INNER JOIN fund_info fi ON ind.entity_id = fi.entity_id
+            WHERE ind.end_date >= fi.price_date.month(); // 过滤掉不必更新的旧记录
+        
+        UPDATE t
+        SET ret_6m = trailing_ret, ret_6m_a = trailing_ret_a
+        FROM ej(t, indicators['PBI-6M'], ['entity_id', 'end_date']);
+        
+        UPDATE t
+        SET ret_1y = trailing_ret, ret_1y_a = trailing_ret_a
+        FROM ej(t, indicators['PBI-1Y'], ['entity_id', 'end_date']);
+        
+        UPDATE t
+        SET ret_2y = trailing_ret, ret_2y_a = trailing_ret_a
+        FROM ej(t, indicators['PBI-2Y'], ['entity_id', 'end_date']);
+        
+        UPDATE t
+        SET ret_3y = trailing_ret, ret_3y_a = trailing_ret_a
+        FROM ej(t, indicators['PBI-3Y'], ['entity_id', 'end_date']);
+        
+        UPDATE t
+        SET ret_4y = trailing_ret, ret_4y_a = trailing_ret_a
+        FROM ej(t, indicators['PBI-4Y'], ['entity_id', 'end_date']);
+        
+        UPDATE t
+        SET ret_5y = trailing_ret, ret_5y_a = trailing_ret_a
+        FROM ej(t, indicators['PBI-5Y'], ['entity_id', 'end_date']);
+        
+        UPDATE t
+        SET ret_10y = trailing_ret, ret_10y_a = trailing_ret_a
+        FROM ej(t, indicators['PBI-10Y'], ['entity_id', 'end_date']);
+        
+        UPDATE t
+        SET ret_ytd = trailing_ret, ret_ytd_a = trailing_ret_a
+        FROM ej(t, indicators['PBI-YTD'], ['entity_id', 'end_date']);
+        
+        // 取消了 ret_incep_a_all (没意义) 和 ret_incep_a_gips (ret_incep_a 与之相等)
+        UPDATE t
+        SET ret_incep = trailing_ret, ret_incep_a = trailing_ret_a
+        FROM ej(t, indicators['PBI-INCEP'], ['entity_id', 'end_date']);
+
+        INSERT INTO fund_performance SELECT * FROM t;
 
+    } else {
+        
+    }
+}
 
 /*
  *   定时任务:最新净值触发的业绩指标计算
@@ -16,6 +77,8 @@ use fundit::indicatorCalculator
  */
 def calFundPerformance(entityType, date) {
 
+    rt = '';
+
     very_old_date = 1990.01.01;
 
     if(find(['HF', 'MF'], entityType) < 0) return null;
@@ -23,29 +86,59 @@ def calFundPerformance(entityType, date) {
     // 取有最新净值变动的基金列表
     tb_cal_funds = get_entity_list_by_nav_updatetime(entityType, NULL, date, true);
 
+    if(tb_cal_funds.isVoid() || tb_cal_funds.size() == 0 ) return;
+
     // 分批跑
     i = 0;
     batch_size = 1000;
+    tb_fund_performance = table(1000:0, 
+                              ['entity_id', 'end_date', 'price_date', 'cumulative_nav',
+                               'ret_1m', 'ret_1m_a', 'ret_3m', 'ret_3m_a', 'ret_6m', 'ret_6m_a',
+                               'ret_1y', 'ret_1y_a', 'ret_2y', 'ret_2y_a', 'ret_3y', 'ret_3y_a', 'ret_4y', 'ret_4y_a', 
+                               'ret_5y', 'ret_5y_a', 'ret_10y', 'ret_10y_a', 'ret_ytd', 'ret_ytd_a', 'ret_incep', 'ret_incep_a'],
+                              [SYMBOL, MONTH, DATE, DOUBLE,
+                               DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE,
+                               DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE,
+                               DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE]);
+
     do {
 
-        funds = tb_cal_funds[i:batch_size]
+        funds = tb_cal_funds[i:batch_size];
 
-        // 计算月收益
-        rets = mix_monthly_returns(entityType, funds);
+        fund_info = SELECT entity_id, price_date, inception_date, benchmark_id, ini_value 
+                    FROM ej(funds, get_fund_info(funds.entity_id), 'entity_id', 'fund_id');
 
-        // TODO: 最新更新的收益存入数据库
+        // 计算月收益
+        rets = mix_monthly_returns(entityType, fund_info);
 
         // 计算月度指标
         rets.rename!('cumulative_nav', 'nav');
         indicators = cal_monthly_indicators(entityType, 'PBI', rets);
 
         // TODO: 最新更新的指标存入数据库
+        generate_fund_performance(fund_info, indicators, true, tb_fund_performance);
 
         i += batch_size;
 
     } while (i < batch_size);
 //    } while (i <= tb_cal_funds.size());
+
+    tb_fund_performance.rename!('entity_id', 'fund_id');
+    // 将 dolphinDB 的 MONTH 换成 MySQL 的 YYYY-MM 格式
+    v_end_date = EXEC end_date.temporalFormat('yyyy-MM') FROM tb_fund_performance;
+    tb_fund_performance.replaceColumn!('end_date', v_end_date);
+
+    try {
+        save_table(tb_fund_performance, 'mfdb.fund_performance', true);
+
+    } catch(ex) {
+
+        //TODO: Log errors
+
+        rt = ex;
+
+    }
     
-    return;
+    return rt;
 	
 }