瀏覽代碼

支持 fund_performance_weekly和fund_latest_performance

Joey 6 月之前
父節點
當前提交
beff72355a
共有 5 個文件被更改,包括 510 次插入345 次删除
  1. 232 0
      modules/dataSaver.dos
  2. 38 32
      modules/indicatorCalculator.dos
  3. 114 99
      modules/returnCalculator.dos
  4. 8 34
      modules/sqlUtilities.dos
  5. 118 180
      modules/task_fundPerformance.dos

+ 232 - 0
modules/dataSaver.dos

@@ -0,0 +1,232 @@
+module fundit::dataSaver
+
+use fundit::sqlUtilities
+
+
+
+/*
+ * 存数据表到mySQL或本地dolphindb,原数据会被替代!
+ *
+ * save_table(tb_fund_performance, "mfdb.fund_performance", false)
+ */
+
+def save_table(tb, table_name, isToMySQL) {
+
+
+    if(isToMySQL == true) {
+    
+        tb.addColumn("creatorid" "createtime" "updaterid" "updatetime" "isvalid", [INT, DATETIME, INT, DATETIME, INT])
+        
+        UPDATE tb SET creatorid = 888888, createtime = now(), updaterid = null, updatetime = null, isvalid = 1
+    
+        conn = connect_mysql()
+        
+        odbc::execute(conn, "TRUNCATE TABLE " + table_name + "_dolphin")
+        
+        odbc::append(conn, tb, table_name + "_dolphin", false)
+        
+        conn.close()
+    
+    } else {
+    
+        db = get_local_database("fundit", table_name.split(".")[0])
+    
+        saveTable(db, tb, table_name.split(".")[1])
+    }
+    
+}
+
+/*
+ * 存私募基金净值到本地dolphindb
+ *
+ * save_hedge_fund_nav_to_local(tb_nav)
+ */
+def save_hedge_fund_nav_to_local(tb_nav) {
+
+    save_table(tb_nav, "mfdb.nav", false)
+
+}
+
+/*
+ *  将数据存回MySQL并同步至正式表
+ * 
+ */
+def save_and_sync(table, table_name, sync_sp_name) {
+
+    save_table(table, table_name, true);
+    
+    call_mysql_sp(sync_sp_name);
+}
+
+
+/*
+ *   建表 XXXX_performance
+ */
+def create_entity_performance() {
+
+    return 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]);
+}
+
+/*
+ *   建表 XXX_indicator
+ */
+def create_entity_indicator() {
+
+    return table(1000:0,
+               ['entity_id', 'end_date',
+                'info_ratio_6m', 'm2_6m', 'tracking_error_6m',
+                'info_ratio_1y', 'm2_1y', 'tracking_error_1y',
+                'info_ratio_2y', 'm2_2y', 'tracking_error_2y', 'var_2y', 'cvar_2y', 
+                'info_ratio_3y', 'm2_3y', 'tracking_error_3y', 'var_3y', 'cvar_3y', 
+                'info_ratio_4y', 'm2_4y', 'tracking_error_4y', 'var_4y', 'cvar_4y', 
+                'info_ratio_5y', 'm2_5y', 'tracking_error_5y', 'var_5y', 'cvar_5y', 
+                'info_ratio_10y', 'm2_10y', 'tracking_error_10y', 'var_10y', 'cvar_10y', 
+                'info_ratio_ytd', 'm2_ytd', 'tracking_error_ytd', 
+                'info_ratio_incep', 'm2_incep','tracking_error_incep', 'var_incep', 'cvar_incep'],
+               [SYMBOL, MONTH,
+                DOUBLE, DOUBLE, DOUBLE,
+                DOUBLE, DOUBLE, DOUBLE,
+                DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE,
+                DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE,
+                DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE,
+                DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE,
+                DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE,
+                DOUBLE, DOUBLE, DOUBLE,
+                DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE]);
+}
+
+/*
+ *   建表 XXX_risk_stats
+ */
+def create_entity_risk_stats() {
+
+    return table(1000:0,
+               ['entity_id', 'end_date',
+                'stddev_6m', 'downsidedev_6m', 'alpha_6m', 'winrate_6m', 'beta_6m', 'skewness_6m', 'kurtosis_6m', 'worstmonth_6m', 'maxdrawdown_6m', 
+                'stddev_1y', 'downsidedev_1y', 'alpha_1y', 'winrate_1y', 'beta_1y', 'skewness_1y', 'kurtosis_1y', 'worstmonth_1y', 'maxdrawdown_1y', 
+                'stddev_2y', 'downsidedev_2y', 'alpha_2y', 'winrate_2y', 'beta_2y', 'skewness_2y', 'kurtosis_2y', 'worstmonth_2y', 'maxdrawdown_2y', 
+                'stddev_3y', 'downsidedev_3y', 'alpha_3y', 'winrate_3y', 'beta_3y', 'skewness_3y', 'kurtosis_3y', 'worstmonth_3y', 'maxdrawdown_3y', 
+                'stddev_4y', 'downsidedev_4y', 'alpha_4y', 'winrate_4y', 'beta_4y', 'skewness_4y', 'kurtosis_4y', 'worstmonth_4y', 'maxdrawdown_4y', 
+                'stddev_5y', 'downsidedev_5y', 'alpha_5y', 'winrate_5y', 'beta_5y', 'skewness_5y', 'kurtosis_5y', 'worstmonth_5y', 'maxdrawdown_5y', 
+                'stddev_10y', 'downsidedev_10y','alpha_10y', 'winrate_10y', 'beta_10y', 'skewness_10y', 'kurtosis_10y', 'worstmonth_10y', 'maxdrawdown_10y', 
+                'stddev_ytd', 'downsidedev_ytd', 'alpha_ytd', 'winrate_ytd', 'beta_ytd', 'skewness_ytd', 'kurtosis_ytd', 'worstmonth_ytd', 'maxdrawdown_ytd', 
+                'stddev_incep', 'downsidedev_incep', 'alpha_incep', 'winrate_incep', 'beta_incep', 'skewness_incep', 'kurtosis_incep', 'worstmonth_incep', 'maxdrawdown_incep'],
+               [SYMBOL, MONTH,
+                DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE,
+                DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE,
+                DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE,
+                DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE,
+                DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE,
+                DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE,
+                DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE,
+                DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE,
+                DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE]);
+
+}
+
+/*
+ *   建表 XXX_riskadjret_stats
+ */
+def create_entity_riskadjret_stats() {
+
+    return table(1000:0,
+               ['entity_id', 'end_date',
+                'sharperatio_6m', 'sortinoratio_6m', 'treynorratio_6m', 'jensen_6m', 'calmarratio_6m', 'omegaratio_6m', 'kapparatio_6m',
+                'sharperatio_1y', 'sortinoratio_1y', 'treynorratio_1y', 'jensen_1y', 'calmarratio_1y', 'omegaratio_1y', 'kapparatio_1y',
+                'sharperatio_2y', 'sortinoratio_2y', 'treynorratio_2y', 'jensen_2y', 'calmarratio_2y', 'omegaratio_2y', 'kapparatio_2y',
+                'sharperatio_3y', 'sortinoratio_3y', 'treynorratio_3y', 'jensen_3y', 'calmarratio_3y', 'omegaratio_3y', 'kapparatio_3y',
+                'sharperatio_4y', 'sortinoratio_4y', 'treynorratio_4y', 'jensen_4y', 'calmarratio_4y', 'omegaratio_4y', 'kapparatio_4y',
+                'sharperatio_5y', 'sortinoratio_5y', 'treynorratio_5y', 'jensen_5y', 'calmarratio_5y', 'omegaratio_5y', 'kapparatio_5y',
+                'sharperatio_10y', 'sortinoratio_10y', 'treynorratio_10y', 'jensen_10y', 'calmarratio_10y', 'omegaratio_10y', 'kapparatio_10y',
+                'sharperatio_ytd', 'sortinoratio_ytd', 'treynorratio_ytd', 'jensen_ytd', 'calmarratio_ytd', 'omegaratio_ytd', 'kapparatio_ytd',
+                'sharperatio_incep', 'sortinoratio_incep', 'treynorratio_incep', 'jensen_incep', 'calmarratio_incep', 'omegaratio_incep', 'kapparatio_incep'],
+               [SYMBOL, MONTH,
+                DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE,
+                DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE,
+                DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE,
+                DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE,
+                DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE,
+                DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE,
+                DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE,
+                DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE,
+                DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE]);
+}
+
+/*
+ *   建表 XXX_style_stats
+ */
+def create_entity_style_stats() {
+
+    return table(1000:0,
+               ['entity_id', 'end_date',
+                'upsidecapture_ret_6m', 'downsidecapture_ret_6m', 'upsidecapture_ratio_6m', 'downsidecapture_ratio_6m',
+                'upsidecapture_ret_1y', 'downsidecapture_ret_1y', 'upsidecapture_ratio_1y', 'downsidecapture_ratio_1y',
+                'upsidecapture_ret_2y', 'downsidecapture_ret_2y', 'upsidecapture_ratio_2y', 'downsidecapture_ratio_2y',
+                'upsidecapture_ret_3y', 'downsidecapture_ret_3y', 'upsidecapture_ratio_3y', 'downsidecapture_ratio_3y',
+                'upsidecapture_ret_4y', 'downsidecapture_ret_4y', 'upsidecapture_ratio_4y', 'downsidecapture_ratio_4y',
+                'upsidecapture_ret_5y', 'downsidecapture_ret_5y', 'upsidecapture_ratio_5y', 'downsidecapture_ratio_5y',
+                'upsidecapture_ret_10y', 'downsidecapture_ret_10y', 'upsidecapture_ratio_10y', 'downsidecapture_ratio_10y',
+                'upsidecapture_ret_ytd', 'downsidecapture_ret_ytd', 'upsidecapture_ratio_ytd', 'downsidecapture_ratio_ytd',
+                'upsidecapture_ret_incep', 'downsidecapture_ret_incep', 'upsidecapture_ratio_incep', 'downsidecapture_ratio_incep'],
+               [SYMBOL, MONTH,
+                DOUBLE, DOUBLE, DOUBLE, DOUBLE,
+                DOUBLE, DOUBLE, DOUBLE, DOUBLE,
+                DOUBLE, DOUBLE, DOUBLE, DOUBLE,
+                DOUBLE, DOUBLE, DOUBLE, DOUBLE,
+                DOUBLE, DOUBLE, DOUBLE, DOUBLE,
+                DOUBLE, DOUBLE, DOUBLE, DOUBLE,
+                DOUBLE, DOUBLE, DOUBLE, DOUBLE,
+                DOUBLE, DOUBLE, DOUBLE, DOUBLE, 
+                DOUBLE, DOUBLE, DOUBLE, DOUBLE]);
+}
+
+/*
+ *  建表 xxx_performance_weekly
+ */
+def create_entity_performance_weekly() {
+
+    return table(1000:0,
+               ['entity_id', 'year_week', 'end_year', 'week_of_year', 'price_date', 'cumulative_nav', 'ret_1w'],
+               [SYMBOL, STRING, STRING, SHORT, DATE, DOUBLE, DOUBLE]);
+	
+}
+
+/*
+ *  建表 xxx_latest_performance
+ */
+def create_entity_latest_performance() {
+
+    return table(1000:0,
+               ['entity_id', 'end_date', 'price_date', 'pre_price_date', 'nav', 'cumulative_nav',
+                'net_value_change', 'ret_1d', 'ret_1w', 'ret_1m', 'ret_3m', 'ret_6m',
+                'ret_1y', 'ret_2y', 'ret_3y', 'ret_4y', 'ret_5y', 'ret_10y', 'ret_ytd', 'ret_incep', 'ret_incep_a', 'ret_incep_a_all', 'ret_incep_a_gips',
+                'maxdrawdown_1m', 'maxdrawdown_3m', 'maxdrawdown_1y', 'maxdrawdown_incep', 'calmarratio_incep',
+                'ret_1y_a', 'ret_2y_a', 'ret_3y_a', 'ret_4y_a', 'ret_5y_a', 'ret_10y_a'],
+               [SYMBOL, STRING, DATE, DATE, DOUBLE, DOUBLE,
+                DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE,
+                DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE,
+                DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE,
+                DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE]);
+
+}
+
+
+/*
+ *   根据 mysql 表改动某些字段
+ */
+def chg_columns_for_mysql(mutable tb_mysql, id_col_name) {
+
+    tb_mysql.rename!('entity_id', id_col_name);
+
+    // 将 dolphinDB 的 MONTH 换成 MySQL 的 YYYY-MM 格式
+    v_end_date = EXEC end_date.temporalFormat('yyyy-MM') FROM tb_mysql;
+    tb_mysql.replaceColumn!('end_date', v_end_date);
+}

+ 38 - 32
modules/indicatorCalculator.dos

@@ -226,18 +226,13 @@ def cal_omega_sortino_kappa(ret, risk_free, trailing_month) {
 
     lpm = cal_LPM(ret, risk_free, trailing_month);
 
-    // 去掉一些 lpm 值太小的无意义数据
-    UPDATE lpm SET lpm1 = NULL WHERE lpm1.round(4) == 0;
-    UPDATE lpm SET lpm2 = NULL WHERE lpm2.round(4) == 0;
-    UPDATE lpm SET lpm3 = NULL WHERE lpm3.round(4) == 0;
-
     if(trailing_month == 'incep') {
 
         tb = SELECT t.entity_id, t.end_date,
                 l.lpm2 AS ds_dev,
-                (t.ret - rfr.ret ).cumavg() \ l.lpm1 + 1 AS omega,
-                (t.ret - rfr.ret ).cumavg() \ l.lpm2 AS sortino,
-                (t.ret - rfr.ret ).cumavg() \ l.lpm3 AS kappa
+                iif(l.lpm1.round(4) == 0, NULL, (t.ret - rfr.ret ).cumavg() \ l.lpm1 + 1) AS omega,
+                iif(l.lpm2.round(4) == 0, NULL, (t.ret - rfr.ret ).cumavg() \ l.lpm2) AS sortino,
+                iif(l.lpm3.round(4) == 0, NULL, (t.ret - rfr.ret ).cumavg() \ l.lpm3) AS kappa
               FROM ret t
               INNER JOIN lpm l ON t.entity_id = l.entity_id AND t.end_date = l.end_date
               INNER JOIN risk_free rfr ON t.end_date = rfr.end_date
@@ -248,9 +243,9 @@ def cal_omega_sortino_kappa(ret, risk_free, trailing_month) {
 
         tb = SELECT t.entity_id, t.end_date,
                 l.lpm2 AS ds_dev,
-                (t.ret - rfr.ret ).cumavg() \ l.lpm1 + 1 AS omega,
-                (t.ret - rfr.ret ).cumavg() \ l.lpm2 AS sortino,
-                (t.ret - rfr.ret ).cumavg() \ l.lpm3 AS kappa
+                iif(l.lpm1.round(4) == 0, NULL, (t.ret - rfr.ret ).cumavg() \ l.lpm1 + 1) AS omega,
+                iif(l.lpm2.round(4) == 0, NULL, (t.ret - rfr.ret ).cumavg() \ l.lpm2) AS sortino,
+                iif(l.lpm3.round(4) == 0, NULL, (t.ret - rfr.ret ).cumavg() \ l.lpm3) AS kappa
               FROM ret t
               INNER JOIN lpm l ON t.entity_id = l.entity_id AND t.end_date = l.end_date
               INNER JOIN risk_free rfr ON t.end_date = rfr.end_date
@@ -263,9 +258,9 @@ def cal_omega_sortino_kappa(ret, risk_free, trailing_month) {
         
         tb = SELECT t.entity_id, t.end_date,
                 l.lpm2 AS ds_dev,
-                (t.ret - rfr.ret ).mavg(win) \ l.lpm1 + 1 AS omega,
-                (t.ret - rfr.ret ).mavg(win) \ l.lpm2 AS sortino,
-                (t.ret - rfr.ret ).mavg(win) \ l.lpm3 AS kappa
+                iif(l.lpm1.round(4) == 0, NULL, (t.ret - rfr.ret ).mavg(win) \ l.lpm1 + 1) AS omega,
+                iif(l.lpm2.round(4) == 0, NULL, (t.ret - rfr.ret ).mavg(win) \ l.lpm2) AS sortino,
+                iif(l.lpm3.round(4) == 0, NULL, (t.ret - rfr.ret ).mavg(win) \ l.lpm3) AS kappa
               FROM ret t
               INNER JOIN lpm l ON t.entity_id = l.entity_id AND t.end_date = l.end_date
               INNER JOIN risk_free rfr ON t.end_date = rfr.end_date
@@ -484,11 +479,11 @@ def cal_capture_ratio(ret, benchmarks, bmk_ret, trailing_month) {
     }
 
     t = SELECT entity_id, end_date, 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,
-                    t1.downside_ret.pow(1 \ t1.bmk_downside_cnt)-1 AS downside_capture_ret,
-                   (t1.downside_ret.pow(1 \ t1.bmk_downside_cnt)-1)/(t1.bmk_downside_ret.pow(1 \ t1.bmk_downside_cnt)-1) AS downside_capture_ratio
-             FROM t1 
+               iif(t1.bmk_upside_cnt == 0, NULL, t1.upside_ret.pow(1 \ t1.bmk_upside_cnt)-1) AS upside_capture_ret,
+               iif(t1.bmk_upside_cnt == 0, NULL, (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,
+               iif(t1.bmk_downside_cnt == 0, NULL, t1.downside_ret.pow(1 \ t1.bmk_downside_cnt)-1) AS downside_capture_ret,
+               iif(t1.bmk_downside_cnt == 0, NULL, (t1.downside_ret.pow(1 \ t1.bmk_downside_cnt)-1)/(t1.bmk_downside_ret.pow(1 \ t1.bmk_downside_cnt)-1)) AS downside_capture_ratio
+             FROM t1
              ORDER BY entity_id, benchmark_id, end_date;
 
     return t;
@@ -502,30 +497,30 @@ def cal_sharpe(ret, std_dev, risk_free, trailing_month) {
 
     if(trailing_month == 'incep') {
 
-        sharpe = SELECT t.entity_id, t.end_date, (t.ret - rfr.ret).cumavg() / std.std_dev AS sharpe
+        sharpe = SELECT t.entity_id, t.end_date, (t.ret - rfr.ret).cumavg() \ std.std_dev AS sharpe
                  FROM ret t
                  INNER JOIN std_dev std ON t.entity_id = std.entity_id AND t.end_date = std.end_date
                  INNER JOIN risk_free rfr ON t.end_date = rfr.end_date
-                 WHERE std.std_dev <> 0 AND t.ret > -1
+                 WHERE std.std_dev.round(4) <> 0 AND t.ret > -1
                  CONTEXT BY t.entity_id;
                  
     } else if(trailing_month == 'ytd') {
 
-        sharpe = SELECT t.entity_id, t.end_date, (t.ret - rfr.ret).cumavg() / std.std_dev AS sharpe
+        sharpe = SELECT t.entity_id, t.end_date, (t.ret - rfr.ret).cumavg() \ std.std_dev AS sharpe
                  FROM ret t
                  INNER JOIN std_dev std ON t.entity_id = std.entity_id AND t.end_date = std.end_date
                  INNER JOIN risk_free rfr ON t.end_date = rfr.end_date
-                 WHERE std.std_dev <> 0 AND t.ret > -1
+                 WHERE std.std_dev.round(4) <> 0 AND t.ret > -1
                  CONTEXT BY t.entity_id, t.end_date.year();
     } else {
 
         win = trailing_month$STRING$INT;
 
-        sharpe = SELECT t.entity_id, t.end_date, (t.ret - rfr.ret).mavg(win) / std.std_dev AS sharpe
+        sharpe = SELECT t.entity_id, t.end_date, (t.ret - rfr.ret).mavg(win) \ std.std_dev AS sharpe
                  FROM ret t
                  INNER JOIN std_dev std ON t.entity_id = std.entity_id AND t.end_date = std.end_date
                  INNER JOIN risk_free rfr ON t.end_date = rfr.end_date
-                 WHERE std.std_dev <> 0 AND t.ret > -1
+                 WHERE std.std_dev.round(4) <> 0 AND t.ret > -1
                  CONTEXT BY t.entity_id;
     }
 
@@ -549,7 +544,8 @@ def cal_treynor(ret, risk_free, beta, trailing_month) {
             CONTEXT BY t.entity_id;
            
         treynor = SELECT t.entity_id, t.end_date, beta.benchmark_id,
-                        ((1 + t.ret).cumprod().pow(12\iif(t.cnt<12, 12, t.cnt)) - (1 + t.rfr_ret).cumprod().pow(12\iif(t.cnt<12, 12, t.cnt))) / beta.beta AS treynor
+                        iif(beta.beta.round(4) == 0, NULL, 
+                            ((1 + t.ret).cumprod().pow(12\iif(t.cnt<12, 12, t.cnt)) - (1 + t.rfr_ret).cumprod().pow(12\iif(t.cnt<12, 12, t.cnt))) \ beta.beta) AS treynor
                   FROM t
                   INNER JOIN beta AS beta ON t.entity_id = beta.entity_id AND t.end_date = beta.end_date
                   CONTEXT BY t.entity_id, beta.benchmark_id;
@@ -564,7 +560,8 @@ def cal_treynor(ret, risk_free, beta, trailing_month) {
             CONTEXT BY t.entity_id, t.end_date.year();
            
         treynor = SELECT t.entity_id, t.end_date, beta.benchmark_id,
-                        ((1 + t.ret).cumprod().pow(12\iif(t.cnt<12, 12, t.cnt)) - (1 + t.rfr_ret).cumprod().pow(12\iif(t.cnt<12, 12, t.cnt))) / beta.beta AS treynor
+                        iif(beta.beta.round(4) == 0, NULL, 
+                            ((1 + t.ret).cumprod().pow(12\iif(t.cnt<12, 12, t.cnt)) - (1 + t.rfr_ret).cumprod().pow(12\iif(t.cnt<12, 12, t.cnt))) \ beta.beta) AS treynor
                   FROM t
                   INNER JOIN beta AS beta ON t.entity_id = beta.entity_id AND t.end_date = beta.end_date
                   CONTEXT BY t.entity_id, beta.benchmark_id, t.end_date.year();
@@ -580,7 +577,8 @@ def cal_treynor(ret, risk_free, beta, trailing_month) {
             CONTEXT BY t.entity_id;
            
         treynor = SELECT t.entity_id, t.end_date, beta.benchmark_id,
-                        ((1 + t.ret).mprod(win).pow(12\iif(t.cnt<12, 12, t.cnt)) - (1 + t.rfr_ret).mprod(win).pow(12\iif(t.cnt<12, 12, t.cnt))) / beta.beta AS treynor
+                        iif(beta.beta.round(4) == 0, NULL, 
+                            ((1 + t.ret).mprod(win).pow(12\iif(t.cnt<12, 12, t.cnt)) - (1 + t.rfr_ret).mprod(win).pow(12\iif(t.cnt<12, 12, t.cnt))) \ beta.beta) AS treynor
                   FROM t
                   INNER JOIN beta AS beta ON t.entity_id = beta.entity_id AND t.end_date = beta.end_date
                   CONTEXT BY t.entity_id, beta.benchmark_id;
@@ -642,7 +640,9 @@ def cal_m2(ret, benchmarks, bmk_ret, risk_free, trailing_month) {
     if(trailing_month == 'incep') {
     	
         m2 = SELECT t.entity_id, t.end_date,
-                    iif(t.entity_id.cumcount() > 5, (t.ret - rfr.ret).cumavg() / t.ret.cumstd() * bmk.ret.cumstd() + rfr.ret.cumavg(), null) AS m2, bm.benchmark_id
+                    iif(t.entity_id.cumcount() > 5, 
+                        iif(t.ret.cumstd().round(4) == 0, NULL, (t.ret - rfr.ret).cumavg() \ t.ret.cumstd() * bmk.ret.cumstd() + rfr.ret.cumavg()),
+                        NULL) AS m2, bm.benchmark_id
              FROM ret t
              INNER JOIN benchmarks bm ON t.entity_id = bm.entity_id AND t.end_date = bm.end_date
              INNER JOIN bmk_ret bmk ON t.end_date = bmk.end_date AND bm.benchmark_id = bmk.benchmark_id
@@ -653,7 +653,9 @@ def cal_m2(ret, benchmarks, bmk_ret, risk_free, trailing_month) {
     } else if(trailing_month == 'ytd') {
 
         m2 = SELECT t.entity_id, t.end_date, 
-                    iif(t.entity_id.cumcount() > 5, (t.ret - rfr.ret).cumavg() / t.ret.cumstd() * bmk.ret.cumstd() + rfr.ret.cumavg(), null) AS m2, bm.benchmark_id
+                    iif(t.entity_id.cumcount() > 5,
+                        iif(t.ret.cumstd().round(4) == 0, NULL, (t.ret - rfr.ret).cumavg() \ t.ret.cumstd() * bmk.ret.cumstd() + rfr.ret.cumavg()),
+                        NULL) AS m2, bm.benchmark_id
              FROM ret t
              INNER JOIN benchmarks bm ON t.entity_id = bm.entity_id AND t.end_date = bm.end_date
              INNER JOIN bmk_ret bmk ON t.end_date = bmk.end_date AND bm.benchmark_id = bmk.benchmark_id
@@ -666,7 +668,9 @@ def cal_m2(ret, benchmarks, bmk_ret, risk_free, trailing_month) {
         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
+                    iif(t.entity_id.mcount(win) > 5,
+                        iif(t.ret.mstd(win) == 0, NULL, (t.ret - rfr.ret).mavg(win) \ t.ret.mstd(win) * bmk.ret.mstd(win) + rfr.ret.mavg(win)),
+                        NULL) AS m2, bm.benchmark_id
              FROM ret t
              INNER JOIN benchmarks bm ON t.entity_id = bm.entity_id AND t.end_date = bm.end_date
              INNER JOIN bmk_ret bmk ON t.end_date = bmk.end_date AND bm.benchmark_id = bmk.benchmark_id
@@ -695,7 +699,8 @@ def cal_ms_return(ret, risk_free, trailing_month) {
     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,
+               iif(t.end_date.mmax(win) == t.end_date.mmin(win), NULL,
+                   ((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,
                (1 + t.ret).pow(-2).mavg(win).pow(-12/2)-1 AS ms_rar_a
         FROM ret t
         INNER JOIN risk_free rfr ON t.end_date = rfr.end_date
@@ -1068,6 +1073,7 @@ def cal_monthly_indicators(entity_type, indicator_type, monthly_returns) {
     return dict(v_table_name, t0);
 	
 }
+
 /*
  *   Calculate historcial fund trailing indicators 
  * 

+ 114 - 99
modules/returnCalculator.dos

@@ -67,6 +67,8 @@ def mix_monthly_returns(entity_type, entity_info) {
      // 计算某净值日期所在月份及之后的所有月收益
      ret = cal_monthly_returns_by_nav(entity_info, tb_nav);
 
+     if(ret.isVoid() || ret.size() == 0) return ret;
+
     // 筛掉引入的前值,这些记录用来计算第一期收益后就不再有用
     // 不知道为什么 delete ret from ej(ret, tb_entities, 'entity_id') where ret.price_date < tb_entities.price_date 会报错
     ret = SELECT ret.* FROM ej(ret, entity_info, 'entity_id')
@@ -160,8 +162,54 @@ def cal_fund_monthly_returns(entity_type, fund_ids, isFromMySQL){
     return ( SELECT * FROM tb_rets WHERE cumulative_nav > 0 );
 }
 
+
 /*
- *  月末 fund_performance 表计算
+ *  根据基金净值序列计算周收益序列
+ * 
+ *  Create:  20240907                                                  Joey
+ *                    TODO: missing pulling data from local
+ *                    
+ *  @param entity_type <STRING>: NAV universe, 'HF','MF','PF','EQ'... defined in get_nav_table_description()
+ *  @param entity_info <TABLE>: COLUMN NEED entity_id, price_date, inception_date
+ *  
+ *  
+ */
+def cal_weekly_returns(entity_type, entity_info){
+
+    tb_rets_1w = null;
+
+    // 暂时只支持公募和私募基金
+    if(entity_type != 'HF' && entity_type != 'MF' ) return tb_rets_1w;
+    
+    // 将每支证券ID+某个日期转为JSON用于调用sp
+    s_json = (SELECT entity_id AS sec_id, price_date FROM entity_info).toStdJson();
+            
+     // 取基金组合在包括各自的某净值日期的前值及之后的所有净值
+     tb_nav = get_nav_for_return_calculation(entity_type, 'w', s_json);
+     tb_nav.rename!('sec_id', 'entity_id');
+
+    UPDATE tb_nav SET year_week = price_date.year()$STRING + (price_date.weekOfYear()$STRING).lpad(2, "0");
+    
+    tb_weekly_nav = SELECT entity_id, year_week, price_date.last() AS price_date, cumulative_nav.last() AS cumulative_nav
+                    FROM tb_nav n
+                    INNER JOIN entity_info ei ON n.entity_id = ei.entity_id
+                    WHERE n.price_date >= ei.inception_date // 筛掉成立日之前的净值
+                    GROUP BY entity_id, year_week
+                    ORDER BY entity_id, year_week;
+    
+    // 这里选最简单的计算方式:不补任何净值空洞,净值前值日期不做任何限制
+    // TODO: 可以考虑将月收益也改为这种方式
+    tb_rets_1w = SELECT entity_id, year_week, price_date, cumulative_nav, cumulative_nav.ratios()-1 AS ret_1w 
+                 FROM tb_weekly_nav
+                 ORDER BY entity_id, year_week;
+    
+    return tb_rets_1w;
+
+
+}
+
+/*
+ *  [RETIRED] 月末 fund_performance 表计算
  *  
  *  @param fund_ids <STRING>: 逗号分隔的ID
  *  @param end_date <STRING>: YYYY-MM 
@@ -203,7 +251,7 @@ def cal_fund_performance(fund_ids, month_end) {
 
 
 /*
- *  批量计算公募历史基金月度收益(fund_performance)
+ *  [RETIRED] 批量计算公募历史基金月度收益(fund_performance)
  *  NOTE: 任何数据频率快于或等于月度的净值数据都可以用此函数一次性计算完整历史记录。双月频、季频甚至更低频率的基金只能按月计算
  *
  *  cal_mutual_fund_performance("'HF000004KN','HF00018WXG','HF000103EU'", true)
@@ -255,105 +303,66 @@ def cal_mutual_fund_performance(fund_ids, isFromMySQL) {
                           WHERE c.price_date IS NOT NULL
                           ORDER BY a.fund_id, c.price_date
 
-/*    
-    // 把这些数据写回mySQL数据表
-    save_table(tb_fund_performance, "mfdb.fund_performance", true)
-
-    // 把这些数据写到本地数据表
-    save_table(tb_fund_performance, "mfdb.fund_performance", false)
-*/
-
     return tb_fund_performance
 }
 
 
 /*
- *  批量计算私募基金周收益
-
- *  TODO: 需要用每周最后一个交易日?
- *
- *  cal_hedge_fund_weekly_returns("'HF000004KN','HF00018WXG','HF000103EU'", true)
- *
- */
-def cal_hedge_fund_weekly_returns(fund_ids, isFromMySQL) {
-
-    // 用于保证老基金也能取到所有历史净值
-    very_old_price_date = 1990.01.01
-
-    tb_nav = get_nav_by_price_date('HF', fund_ids, very_old_price_date, isFromMySQL)
-
-    UPDATE tb_nav SET year_week = price_date.year()$STRING + (price_date.weekOfYear()$STRING).lpad(2, "0")
-    
-    tb_weekly_nav = SELECT fund_id, year_week, price_date.last() AS price_date, cumulative_nav.last() AS cumulative_nav
-                    FROM tb_nav
-                    GROUP BY fund_id, year_week
-                    ORDER BY fund_id, year_week
-    
-    // 这里选最简单的计算方式:不补任何净值空洞,净值前值日期不做任何限制
-    // TODO: 可以考虑将月收益也改为这种方式
-    tb_rets_1w = SELECT fund_id, year_week, price_date, cumulative_nav, cumulative_nav.ratios()-1 AS ret_1w 
-                 FROM tb_weekly_nav
-                 ORDER BY fund_id, year_week
-    
-    return tb_rets_1w
-
-}
-
-/*
- *  批量计算私募基金区间收益
- *  TODO: mySQL version 向前取4天,向后不做限制。这里的逻辑是向前取4个交易日
+ *  批量计算区间收益
+ *  TODO: mySQL version 向前取4天,向后不做限制。这里的逻辑是向前取4个交易日, 遇到大长假后可能取不到数据
+ *        【老程序,可优化】
  *
  *  get_trailing_return(tb_last_nav, tb_nav, -7d, "ret_1w")
  *
  */
 def get_trailing_return(table_last_nav, table_nav, duration, return_column_name) {
 
-    tb =  SELECT a.fund_id, a.price_date.last() AS price_date, a.cumulative_nav.last() \ b.cumulative_nav.last() - 1 AS ret
+    tb =  SELECT a.entity_id, a.price_date.last() AS price_date, a.cumulative_nav.last() \ b.cumulative_nav.last() - 1 AS ret
           FROM table_last_nav a
-          INNER JOIN table_nav b ON a.fund_id = b.fund_id 
+          INNER JOIN table_nav b ON a.entity_id = b.entity_id 
           WHERE b.price_date <= a.price_date.datetimeAdd(duration)
             AND b.price_date >= a.price_date.datetimeAdd(duration).datetimeAdd(-4d).businessDay()
-          GROUP by a.fund_id
-          ORDER BY fund_id
-    tb.rename!("ret", return_column_name)
+          GROUP BY a.entity_id
+          ORDER BY entity_id;
+
+    tb.rename!("ret", return_column_name);
 
-    return tb    
+    return tb;
 }
 
 /*
- *  批量计算私募基金最新收益
+ *  批量计算最新收益
  *
- *
- *  cal_hedge_fund_weekly_returns("'HF000004KN','HF00018WXG','HF000103EU'", true)
+   【老程序,可优化】
  *
  */
-def cal_hedge_fund_latest_returns(fund_ids, isFromMySQL) {
+def cal_latest_performance(entity_type, entity_info, isFromMySQL) {
 
     // 用于保证老基金也能取到所有历史净值
     very_old_price_date = 1990.01.01
 
-    tb_nav = get_nav_by_price_date('HF', fund_ids, very_old_price_date, isFromMySQL)
+    tb_nav = get_nav_by_price_date(entity_type, entity_info.entity_id, very_old_price_date, isFromMySQL)
     
-    tb_last_nav = SELECT fund_id, price_date.last() AS price_date, cumulative_nav.last() AS cumulative_nav
+    tb_last_nav = SELECT entity_id, price_date.last() AS price_date, cumulative_nav.last() AS cumulative_nav
                   FROM tb_nav
-                  GROUP BY fund_id
-                  ORDER BY fund_id
+                  GROUP BY entity_id
+                  ORDER BY entity_id
     
     // 近1期收益,对应mySQL fund_latest_nav_performance 中的 net_value_change
     // 因为是倒序,所以算出来的 ratios() = n0 / n1, 要把它改换成 n1 / n0 - 1 的收益
-    tb_last_return = SELECT TOP 2 fund_id, price_date.first() AS price_date, price_date AS pre_preice_date,
-                                  cumulative_nav.first() AS cumulative_nav, 1\cumulative_nav.ratios() - 1 AS net_value_change
+    tb_last_return = SELECT TOP 2 entity_id, price_date.first() AS price_date, price_date AS pre_preice_date,
+                                  nav.first() AS nav, cumulative_nav.first() AS cumulative_nav, 1\cumulative_nav.ratios() - 1 AS net_value_change
                      FROM ( SELECT * FROM tb_nav ORDER BY price_date DESC )
-                     CONTEXT BY fund_id
-                     ORDER BY fund_id
+                     CONTEXT BY entity_id
+                     ORDER BY entity_id
     tb_last_return = SELECT * FROM tb_last_return WHERE net_value_change IS NOT NULL
 
 
     // 近1交易日收益
-    tb_1d = SELECT a.fund_id, a.price_date, a.cumulative_nav \ b.cumulative_nav - 1 AS ret_1d
+    tb_1d = SELECT a.entity_id, a.price_date, a.cumulative_nav \ b.cumulative_nav - 1 AS ret_1d
             FROM tb_last_nav a
-            INNER JOIN tb_nav b ON a.fund_id = b.fund_id AND b.price_date = a.price_date.datetimeAdd(-1d).businessDay()
-            ORDER BY fund_id
+            INNER JOIN tb_nav b ON a.entity_id = b.entity_id AND b.price_date = a.price_date.datetimeAdd(-1d).businessDay()
+            ORDER BY entity_id
 
     // 近1周、1/3/6月、1/2/3/4/5/10年收益
     tb_1w = get_trailing_return(tb_last_nav, tb_nav, -7d, "ret_1w")
@@ -368,18 +377,17 @@ def cal_hedge_fund_latest_returns(fund_ids, isFromMySQL) {
     tb_10y = get_trailing_return(tb_last_nav, tb_nav, -10y, "ret_10y")
 
     // ytd return
-    tb_ytd =  SELECT a.fund_id, a.price_date.last() AS price_date, a.cumulative_nav.last() \ b.cumulative_nav.last() - 1 AS ret_ytd
+    tb_ytd =  SELECT a.entity_id, a.price_date.last() AS price_date, a.cumulative_nav.last() \ b.cumulative_nav.last() - 1 AS ret_ytd
               FROM tb_last_nav a
-              INNER JOIN tb_nav b ON a.fund_id = b.fund_id 
+              INNER JOIN tb_nav b ON a.entity_id = b.entity_id 
               WHERE b.price_date < a.price_date.yearBegin()
                 AND b.price_date >= a.price_date.yearBegin().datetimeAdd(-4d)
-              GROUP by a.fund_id
+              GROUP BY a.entity_id
 
     // since inception return
-    tb_fund_info = get_fund_info(fund_ids)
-    tb_incep = SELECT a.fund_id, a.price_date, -1 + cumulative_nav \ ini_value AS ret_incep, fi.inception_date
+    tb_incep = SELECT a.entity_id, a.price_date, -1 + cumulative_nav \ ini_value AS ret_incep, fi.inception_date
                FROM tb_last_nav a
-               INNER JOIN tb_fund_info fi ON a.fund_id = fi.fund_id
+               INNER JOIN entity_info fi ON a.entity_id = fi.entity_id
     
     // annulized since reception return
     UPDATE tb_incep SET ret_incep_a = (1 + ret_incep).pow(365.25\(price_date-inception_date)) - 1
@@ -387,46 +395,53 @@ def cal_hedge_fund_latest_returns(fund_ids, isFromMySQL) {
                         ret_incep_a_gips = iif((price_date-inception_date)<365, ret_incep, ret_incep_a)
 
     // 最大回撤
-    tb_drawdown_1m = SELECT a.fund_id, max( 1 - b.cumulative_nav \ b.cumulative_nav.cummax() ) AS drawdown_1m
+    tb_drawdown_1m = SELECT a.entity_id, max( 1 - b.cumulative_nav \ b.cumulative_nav.cummax() ) AS drawdown_1m
                      FROM tb_last_return a
-                     INNER JOIN tb_nav b ON a.fund_id = b.fund_id
+                     INNER JOIN tb_nav b ON a.entity_id = b.entity_id
                      WHERE b.price_date >= a.price_date.datetimeAdd(-1M)
-                     GROUP BY a.fund_id
+                     GROUP BY a.entity_id
 
-    tb_drawdown_3m = SELECT a.fund_id, max( 1 - b.cumulative_nav \ b.cumulative_nav.cummax() ) AS drawdown_3m
+    tb_drawdown_3m = SELECT a.entity_id, max( 1 - b.cumulative_nav \ b.cumulative_nav.cummax() ) AS drawdown_3m
                      FROM tb_last_return a
-                     INNER JOIN tb_nav b ON a.fund_id = b.fund_id
+                     INNER JOIN tb_nav b ON a.entity_id = b.entity_id
                      WHERE b.price_date >= a.price_date.datetimeAdd(-3M)
-                     GROUP BY a.fund_id
-
-    tb_drawdown_incep = SELECT fund_id, max( 1 - cumulative_nav \ cumulative_nav.cummax() ) AS drawdown_incep
-                        FROM tb_nav GROUP BY fund_id
+                     GROUP BY a.entity_id
 
-    tb_rets = SELECT a.fund_id, a.price_date.datetimeFormat("yyyy-MM") AS end_date, a.price_date, a.pre_preice_date, a.cumulative_nav,
+    tb_drawdown_1y = SELECT a.entity_id, max( 1 - b.cumulative_nav \ b.cumulative_nav.cummax() ) AS drawdown_1y
+                     FROM tb_last_return a
+                     INNER JOIN tb_nav b ON a.entity_id = b.entity_id
+                     WHERE b.price_date >= a.price_date.datetimeAdd(-1y)
+                     GROUP BY a.entity_id
+                     
+    tb_drawdown_incep = SELECT entity_id, max( 1 - cumulative_nav \ cumulative_nav.cummax() ) AS drawdown_incep
+                        FROM tb_nav GROUP BY entity_id
+
+    tb_rets = SELECT a.entity_id, a.price_date.datetimeFormat("yyyy-MM") AS end_date, a.price_date, a.pre_preice_date, a.nav, a.cumulative_nav,
                      a.net_value_change, d1.ret_1d, w1.ret_1w, m1.ret_1m, m3.ret_3m, m6.ret_6m,
                      y1.ret_1y, y2.ret_2y, y3.ret_3y, y4.ret_4y, y5.ret_5y, y10.ret_10y,
                      ytd.ret_ytd, incep.ret_incep, incep.ret_incep_a, incep.ret_incep_a_all, incep.ret_incep_a_gips,
-                     dd_m1.drawdown_1m AS maxdrawdown_1m, dd_m3.drawdown_3m AS maxdrawdown_3m,
+                     dd_m1.drawdown_1m AS maxdrawdown_1m, dd_m3.drawdown_3m AS maxdrawdown_3m, dd_y1.drawdown_1y AS maxdrawdown_1y,
                      dd_incep.drawdown_incep AS maxdrawdown_incep,
                      iif(dd_incep.drawdown_incep == 0, null,incep.ret_incep_a \ dd_incep.drawdown_incep) AS calmarratio_incep
               FROM tb_last_return a
-              LEFT JOIN tb_1d d1 ON a.fund_id = d1.fund_id
-              LEFT JOIN tb_1w w1 ON a.fund_id = w1.fund_id
-              LEFT JOIN tb_1m m1 ON a.fund_id = m1.fund_id
-              LEFT JOIN tb_3m m3 ON a.fund_id = m3.fund_id
-              LEFT JOIN tb_6m m6 ON a.fund_id = m6.fund_id
-              LEFT JOIN tb_1y y1 ON a.fund_id = y1.fund_id
-              LEFT JOIN tb_2y y2 ON a.fund_id = y2.fund_id
-              LEFT JOIN tb_3y y3 ON a.fund_id = y3.fund_id
-              LEFT JOIN tb_4y y4 ON a.fund_id = y4.fund_id
-              LEFT JOIN tb_5y y5 ON a.fund_id = y5.fund_id
-              LEFT JOIN tb_10y y10 ON a.fund_id = y10.fund_id
-              LEFT JOIN tb_ytd ytd ON a.fund_id = ytd.fund_id
-              LEFT JOIN tb_incep incep ON a.fund_id = incep.fund_id
-              LEFT JOIN tb_drawdown_1m dd_m1 ON a.fund_id = dd_m1.fund_id
-              LEFT JOIN tb_drawdown_3m dd_m3 ON a.fund_id = dd_m3.fund_id
-              LEFT JOIN tb_drawdown_incep dd_incep ON a.fund_id = dd_incep.fund_id
-              ORDER BY a.fund_id
+              LEFT JOIN tb_1d d1 ON a.entity_id = d1.entity_id
+              LEFT JOIN tb_1w w1 ON a.entity_id = w1.entity_id
+              LEFT JOIN tb_1m m1 ON a.entity_id = m1.entity_id
+              LEFT JOIN tb_3m m3 ON a.entity_id = m3.entity_id
+              LEFT JOIN tb_6m m6 ON a.entity_id = m6.entity_id
+              LEFT JOIN tb_1y y1 ON a.entity_id = y1.entity_id
+              LEFT JOIN tb_2y y2 ON a.entity_id = y2.entity_id
+              LEFT JOIN tb_3y y3 ON a.entity_id = y3.entity_id
+              LEFT JOIN tb_4y y4 ON a.entity_id = y4.entity_id
+              LEFT JOIN tb_5y y5 ON a.entity_id = y5.entity_id
+              LEFT JOIN tb_10y y10 ON a.entity_id = y10.entity_id
+              LEFT JOIN tb_ytd ytd ON a.entity_id = ytd.entity_id
+              LEFT JOIN tb_incep incep ON a.entity_id = incep.entity_id
+              LEFT JOIN tb_drawdown_1m dd_m1 ON a.entity_id = dd_m1.entity_id
+              LEFT JOIN tb_drawdown_3m dd_m3 ON a.entity_id = dd_m3.entity_id
+              LEFT JOIN tb_drawdown_1y dd_y1 ON a.entity_id = dd_y1.entity_id
+              LEFT JOIN tb_drawdown_incep dd_incep ON a.entity_id = dd_incep.entity_id
+              ORDER BY a.entity_id
 
     // 忽略掉非GIPS标准的所有年化收益字段(包括ytd_a)
     UPDATE tb_rets SET ret_1y_a = ret_1y, ret_2y_a = (1 + ret_2y).pow(1\2) - 1, ret_3y_a = (1 + ret_3y).pow(1\3) - 1,

+ 8 - 34
modules/sqlUtilities.dos

@@ -49,46 +49,20 @@ def load_table_from_local(server_name, table_name) {
 }
 
 /*
- * 存数据表到mySQL或本地dolphindb,原数据会被替代!
- *
- * save_table(tb_fund_performance, "mfdb.fund_performance", false)
+ *  调用mysql中无参数存储过程
+ * 
  */
+def call_mysql_sp(store_procedure) {
 
-def save_table(tb, table_name, isToMySQL) {
+    s_query = "CALL " + store_procedure + "();"
 
+    conn = connect_mysql()
 
-    if(isToMySQL == true) {
-    
-        tb.addColumn("creatorid" "createtime" "updaterid" "updatetime" "isvalid", [INT, DATETIME, INT, DATETIME, INT])
-        
-        UPDATE tb SET creatorid = 888888, createtime = now(), updaterid = null, updatetime = null, isvalid = 1
-    
-        conn = connect_mysql()
-        
-        odbc::execute(conn, "TRUNCATE TABLE " + table_name + "_dolphin")
-        
-        odbc::append(conn, tb, table_name + "_dolphin", false)
-        
-        conn.close()
-    
-    } else {
-    
-        db = get_local_database("fundit", table_name.split(".")[0])
-    
-        saveTable(db, tb, table_name.split(".")[1])
-    }
-    
-}
-
-/*
- * 存私募基金净值到本地dolphindb
- *
- * save_hedge_fund_nav_to_local(tb_nav)
- */
-def save_hedge_fund_nav_to_local(tb_nav) {
+    t = odbc::query(conn, s_query)
 
-    save_table(tb_nav, "mfdb.nav", false)
+    conn.close()
 
+    return t
 }
 
 /*

+ 118 - 180
modules/task_fundPerformance.dos

@@ -2,17 +2,18 @@ module fundit::task_fundPerformance
 
 use fundit::sqlUtilities
 use fundit::dataPuller
+use fundit::dataSaver
 use fundit::returnCalculator
 use fundit::indicatorCalculator
 use fundit::fundCalculator
 
 
 /*
- *   按照 fund_performance 表结构准备数据记录
+ *   按照 XXX_performance 表结构准备数据记录
  * 
  * 
  */
-def generate_fund_performance(fund_info, indicators, isToMySQL, mutable fund_performance) {
+def generate_entity_performance(entity_info, indicators, isToMySQL, mutable entity_performance) {
 
     t = null;
 
@@ -20,7 +21,7 @@ def generate_fund_performance(fund_info, indicators, isToMySQL, mutable fund_per
 
         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
+            INNER JOIN entity_info fi ON ind.entity_id = fi.entity_id
             WHERE ind.end_date >= fi.price_date.month(); // 过滤掉不必更新的旧记录
         
         UPDATE t
@@ -60,7 +61,7 @@ def generate_fund_performance(fund_info, indicators, isToMySQL, mutable fund_per
         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;
+        INSERT INTO entity_performance SELECT * FROM t;
 
     } else {
         
@@ -68,11 +69,11 @@ def generate_fund_performance(fund_info, indicators, isToMySQL, mutable fund_per
 }
 
 /*
- *   按照 fund_risk_stats 表结构准备数据记录
+ *   按照 XXX_risk_stats 表结构准备数据记录
  * 
  * 
  */
-def generate_fund_risk_stats(fund_info, indicators, isToMySQL, mutable fund_risk_stats) {
+def generate_entity_risk_stats(entity_info, indicators, isToMySQL, mutable entity_risk_stats) {
 
     t = null;
 
@@ -81,7 +82,7 @@ def generate_fund_risk_stats(fund_info, indicators, isToMySQL, mutable fund_risk
         t = SELECT entity_id, end_date, std_dev_a AS stddev_6m, ds_dev_a AS downsidedev_6m, alpha_a AS alpha_6m, winrate AS winrate_6m, beta AS beta_6m,
                                         skewness AS skewness_6m, kurtosis AS kurtosis_6m, wrst_month AS worstmonth_6m, drawdown AS maxdrawdown_6m
             FROM indicators['PBI-6M'] AS ind
-            INNER JOIN fund_info fi ON ind.entity_id = fi.entity_id
+            INNER JOIN entity_info fi ON ind.entity_id = fi.entity_id
             WHERE ind.end_date >= fi.price_date.month(); // 过滤掉不必更新的旧记录
         
         UPDATE t
@@ -124,7 +125,7 @@ def generate_fund_risk_stats(fund_info, indicators, isToMySQL, mutable fund_risk
             skewness_incep = skewness, kurtosis_incep = kurtosis, worstmonth_incep = wrst_month, maxdrawdown_incep = drawdown
         FROM ej(t, indicators['PBI-INCEP'], ['entity_id', 'end_date']);
         
-        INSERT INTO fund_risk_stats SELECT * FROM t;
+        INSERT INTO entity_risk_stats SELECT * FROM t;
 
     } else {
         
@@ -132,11 +133,11 @@ def generate_fund_risk_stats(fund_info, indicators, isToMySQL, mutable fund_risk
 }
 
 /*
- *   按照 fund_riskadjret_stats 表结构准备数据记录
+ *   按照 XXX_riskadjret_stats 表结构准备数据记录
  * 
  * 
  */
-def generate_fund_riskadjret_stats(fund_info, indicators, isToMySQL, mutable fund_riskadjret_stats) {
+def generate_entity_riskadjret_stats(entity_info, indicators, isToMySQL, mutable entity_riskadjret_stats) {
 
     t = null;
 
@@ -146,7 +147,7 @@ def generate_fund_riskadjret_stats(fund_info, indicators, isToMySQL, mutable fun
                    sharpe_a AS sharperatio_6m, sortino_a AS sortinoratio_6m, treynor AS treynorratio_6m, jensen_a AS jensen_6m,
                    calmar AS calmarratio_6m, omega AS omegaratio_6m, kappa AS kapparatio_6m
             FROM indicators['PBI-6M'] AS ind
-            INNER JOIN fund_info fi ON ind.entity_id = fi.entity_id
+            INNER JOIN entity_info fi ON ind.entity_id = fi.entity_id
             WHERE ind.end_date >= fi.price_date.month(); // 过滤掉不必更新的旧记录
         
         UPDATE t
@@ -189,7 +190,7 @@ def generate_fund_riskadjret_stats(fund_info, indicators, isToMySQL, mutable fun
             calmarratio_incep = calmar, omegaratio_incep = omega, kapparatio_incep = kappa
         FROM ej(t, indicators['PBI-INCEP'], ['entity_id', 'end_date']);
         
-        INSERT INTO fund_riskadjret_stats SELECT * FROM t;
+        INSERT INTO entity_riskadjret_stats SELECT * FROM t;
 
     } else {
         
@@ -197,11 +198,11 @@ def generate_fund_riskadjret_stats(fund_info, indicators, isToMySQL, mutable fun
 }
 
 /*
- *   按照 fund_indicator 表结构准备数据记录
+ *   按照 XXX_indicator 表结构准备数据记录
  * 
  * 
  */
-def generate_fund_indicator(fund_info, indicators, isToMySQL, mutable fund_indicator) {
+def generate_entity_indicator(entity_info, indicators, isToMySQL, mutable entity_indicator) {
 
     t = null;
 
@@ -209,7 +210,7 @@ def generate_fund_indicator(fund_info, indicators, isToMySQL, mutable fund_indic
 
         t = SELECT entity_id, end_date, info_a AS info_ratio_6m, m2_a AS m2_6m, track_error_a AS tracking_error_6m
             FROM indicators['PBI-6M'] AS ind
-            INNER JOIN fund_info fi ON ind.entity_id = fi.entity_id
+            INNER JOIN entity_info fi ON ind.entity_id = fi.entity_id
             WHERE ind.end_date >= fi.price_date.month(); // 过滤掉不必更新的旧记录
       
         UPDATE t
@@ -244,7 +245,7 @@ def generate_fund_indicator(fund_info, indicators, isToMySQL, mutable fund_indic
         SET info_ratio_incep = info_a, m2_incep = m2_a, tracking_error_incep = track_error_a, var_incep = var, cvar_incep = cvar
         FROM ej(t, indicators['PBI-INCEP'], ['entity_id', 'end_date']);
         
-        INSERT INTO fund_indicator SELECT * FROM t;
+        INSERT INTO entity_indicator SELECT * FROM t;
 
     } else {
         
@@ -252,11 +253,11 @@ def generate_fund_indicator(fund_info, indicators, isToMySQL, mutable fund_indic
 }
 
 /*
- *   按照 fund_style_stats 表结构准备数据记录
+ *   按照 XXX_style_stats 表结构准备数据记录
  * 
  * 
  */
-def generate_fund_style_stats(fund_info, indicators, isToMySQL, mutable fund_style_stats) {
+def generate_entity_style_stats(entity_info, indicators, isToMySQL, mutable entity_style_stats) {
 
     t = null;
 
@@ -265,7 +266,7 @@ def generate_fund_style_stats(fund_info, indicators, isToMySQL, mutable fund_sty
         t = SELECT entity_id, end_date, upside_capture_ret AS upsidecapture_ret_6m, downside_capture_ret AS downsidecapture_ret_6m,
                                         upside_capture_ratio AS upsidecapture_ratio_6m, downside_capture_ratio AS downsidecapture_ratio_6m
             FROM indicators['PBI-6M'] AS ind
-            INNER JOIN fund_info fi ON ind.entity_id = fi.entity_id
+            INNER JOIN entity_info fi ON ind.entity_id = fi.entity_id
             WHERE ind.end_date >= fi.price_date.month(); // 过滤掉不必更新的旧记录
         
         UPDATE t
@@ -308,152 +309,59 @@ def generate_fund_style_stats(fund_info, indicators, isToMySQL, mutable fund_sty
             upsidecapture_ratio_incep = upside_capture_ratio, downsidecapture_ratio_incep = downside_capture_ratio
         FROM ej(t, indicators['PBI-INCEP'], ['entity_id', 'end_date']);
         
-        INSERT INTO fund_style_stats SELECT * FROM t;
+        INSERT INTO entity_style_stats SELECT * FROM t;
 
     } else {
         
     }
 }
 
-/*
- *   建表 fund_performance
- */
-def create_fund_performance() {
-
-    return 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]);
-}
 
 /*
- *   建表 fund_indicator
+ *   按照 XXX_performance_weekly 表结构准备数据记录
+ * 
+ * 
  */
-def create_fund_indicator() {
-
-    return table(1000:0,
-               ['entity_id', 'end_date',
-                'info_ratio_6m', 'm2_6m', 'tracking_error_6m',
-                'info_ratio_1y', 'm2_1y', 'tracking_error_1y',
-                'info_ratio_2y', 'm2_2y', 'tracking_error_2y', 'var_2y', 'cvar_2y', 
-                'info_ratio_3y', 'm2_3y', 'tracking_error_3y', 'var_3y', 'cvar_3y', 
-                'info_ratio_4y', 'm2_4y', 'tracking_error_4y', 'var_4y', 'cvar_4y', 
-                'info_ratio_5y', 'm2_5y', 'tracking_error_5y', 'var_5y', 'cvar_5y', 
-                'info_ratio_10y', 'm2_10y', 'tracking_error_10y', 'var_10y', 'cvar_10y', 
-                'info_ratio_ytd', 'm2_ytd', 'tracking_error_ytd', 
-                'info_ratio_incep', 'm2_incep','tracking_error_incep', 'var_incep', 'cvar_incep'],
-               [SYMBOL, MONTH,
-                DOUBLE, DOUBLE, DOUBLE,
-                DOUBLE, DOUBLE, DOUBLE,
-                DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE,
-                DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE,
-                DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE,
-                DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE,
-                DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE,
-                DOUBLE, DOUBLE, DOUBLE,
-                DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE]);
-}
+def generate_entity_performance_weekly(entity_info, ret_w, isToMySQL, mutable entity_performance_weekly) {
 
-/*
- *   建表 fund_risk_stats
- */
-def create_fund_risk_stats() {
-
-    return table(1000:0,
-               ['entity_id', 'end_date',
-                'stddev_6m', 'downsidedev_6m', 'alpha_6m', 'winrate_6m', 'beta_6m', 'skewness_6m', 'kurtosis_6m', 'worstmonth_6m', 'maxdrawdown_6m', 
-                'stddev_1y', 'downsidedev_1y', 'alpha_1y', 'winrate_1y', 'beta_1y', 'skewness_1y', 'kurtosis_1y', 'worstmonth_1y', 'maxdrawdown_1y', 
-                'stddev_2y', 'downsidedev_2y', 'alpha_2y', 'winrate_2y', 'beta_2y', 'skewness_2y', 'kurtosis_2y', 'worstmonth_2y', 'maxdrawdown_2y', 
-                'stddev_3y', 'downsidedev_3y', 'alpha_3y', 'winrate_3y', 'beta_3y', 'skewness_3y', 'kurtosis_3y', 'worstmonth_3y', 'maxdrawdown_3y', 
-                'stddev_4y', 'downsidedev_4y', 'alpha_4y', 'winrate_4y', 'beta_4y', 'skewness_4y', 'kurtosis_4y', 'worstmonth_4y', 'maxdrawdown_4y', 
-                'stddev_5y', 'downsidedev_5y', 'alpha_5y', 'winrate_5y', 'beta_5y', 'skewness_5y', 'kurtosis_5y', 'worstmonth_5y', 'maxdrawdown_5y', 
-                'stddev_10y', 'downsidedev_10y','alpha_10y', 'winrate_10y', 'beta_10y', 'skewness_10y', 'kurtosis_10y', 'worstmonth_10y', 'maxdrawdown_10y', 
-                'stddev_ytd', 'downsidedev_ytd', 'alpha_ytd', 'winrate_ytd', 'beta_ytd', 'skewness_ytd', 'kurtosis_ytd', 'worstmonth_ytd', 'maxdrawdown_ytd', 
-                'stddev_incep', 'downsidedev_incep', 'alpha_incep', 'winrate_incep', 'beta_incep', 'skewness_incep', 'kurtosis_incep', 'worstmonth_incep', 'maxdrawdown_incep'],
-               [SYMBOL, MONTH,
-                DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE,
-                DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE,
-                DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE,
-                DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE,
-                DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE,
-                DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE,
-                DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE,
-                DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE,
-                DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE]);
+    t = null;
 
-}
+    if(isToMySQL) {
 
-/*
- *   建表 fund_riskadjret_stats
- */
-def create_fund_riskadjret_stats() {
-
-    return table(1000:0,
-               ['entity_id', 'end_date',
-                'sharperatio_6m', 'sortinoratio_6m', 'treynorratio_6m', 'jensen_6m', 'calmarratio_6m', 'omegaratio_6m', 'kapparatio_6m',
-                'sharperatio_1y', 'sortinoratio_1y', 'treynorratio_1y', 'jensen_1y', 'calmarratio_1y', 'omegaratio_1y', 'kapparatio_1y',
-                'sharperatio_2y', 'sortinoratio_2y', 'treynorratio_2y', 'jensen_2y', 'calmarratio_2y', 'omegaratio_2y', 'kapparatio_2y',
-                'sharperatio_3y', 'sortinoratio_3y', 'treynorratio_3y', 'jensen_3y', 'calmarratio_3y', 'omegaratio_3y', 'kapparatio_3y',
-                'sharperatio_4y', 'sortinoratio_4y', 'treynorratio_4y', 'jensen_4y', 'calmarratio_4y', 'omegaratio_4y', 'kapparatio_4y',
-                'sharperatio_5y', 'sortinoratio_5y', 'treynorratio_5y', 'jensen_5y', 'calmarratio_5y', 'omegaratio_5y', 'kapparatio_5y',
-                'sharperatio_10y', 'sortinoratio_10y', 'treynorratio_10y', 'jensen_10y', 'calmarratio_10y', 'omegaratio_10y', 'kapparatio_10y',
-                'sharperatio_ytd', 'sortinoratio_ytd', 'treynorratio_ytd', 'jensen_ytd', 'calmarratio_ytd', 'omegaratio_ytd', 'kapparatio_ytd',
-                'sharperatio_incep', 'sortinoratio_incep', 'treynorratio_incep', 'jensen_incep', 'calmarratio_incep', 'omegaratio_incep', 'kapparatio_incep'],
-               [SYMBOL, MONTH,
-                DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE,
-                DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE,
-                DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE,
-                DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE,
-                DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE,
-                DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE,
-                DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE,
-                DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE,
-                DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE]);
-}
+        t = SELECT entity_id, year_week, year_week.left(4)$INT AS end_year, year_week.right(2)$INT AS week_of_year, price_date,
+                   cumulative_nav, ret_1w
+            FROM ret_w r
+            INNER JOIN entity_info fi ON r.entity_id = fi.entity_id
+            WHERE r.price_date >= fi.price_date; // 过滤掉不必更新的旧记录
+        
+        INSERT INTO entity_performance_weekly SELECT * FROM t;
 
-/*
- *   建表 fund_style_stats
- */
-def create_fund_style_stats() {
-
-    return table(1000:0,
-               ['entity_id', 'end_date',
-                'upsidecapture_ret_6m', 'downsidecapture_ret_6m', 'upsidecapture_ratio_6m', 'downsidecapture_ratio_6m',
-                'upsidecapture_ret_1y', 'downsidecapture_ret_1y', 'upsidecapture_ratio_1y', 'downsidecapture_ratio_1y',
-                'upsidecapture_ret_2y', 'downsidecapture_ret_2y', 'upsidecapture_ratio_2y', 'downsidecapture_ratio_2y',
-                'upsidecapture_ret_3y', 'downsidecapture_ret_3y', 'upsidecapture_ratio_3y', 'downsidecapture_ratio_3y',
-                'upsidecapture_ret_4y', 'downsidecapture_ret_4y', 'upsidecapture_ratio_4y', 'downsidecapture_ratio_4y',
-                'upsidecapture_ret_5y', 'downsidecapture_ret_5y', 'upsidecapture_ratio_5y', 'downsidecapture_ratio_5y',
-                'upsidecapture_ret_10y', 'downsidecapture_ret_10y', 'upsidecapture_ratio_10y', 'downsidecapture_ratio_10y',
-                'upsidecapture_ret_ytd', 'downsidecapture_ret_ytd', 'upsidecapture_ratio_ytd', 'downsidecapture_ratio_ytd',
-                'upsidecapture_ret_incep', 'downsidecapture_ret_incep', 'upsidecapture_ratio_incep', 'downsidecapture_ratio_incep'],
-               [SYMBOL, MONTH,
-                DOUBLE, DOUBLE, DOUBLE, DOUBLE,
-                DOUBLE, DOUBLE, DOUBLE, DOUBLE,
-                DOUBLE, DOUBLE, DOUBLE, DOUBLE,
-                DOUBLE, DOUBLE, DOUBLE, DOUBLE,
-                DOUBLE, DOUBLE, DOUBLE, DOUBLE,
-                DOUBLE, DOUBLE, DOUBLE, DOUBLE,
-                DOUBLE, DOUBLE, DOUBLE, DOUBLE,
-                DOUBLE, DOUBLE, DOUBLE, DOUBLE, 
-                DOUBLE, DOUBLE, DOUBLE, DOUBLE]);
+    } else {
+        
+    }
 }
 
 /*
- *   根据 mysql 表改动某些字段
+ *   按照 XXX_latest_performance 表结构准备数据记录
+ * 
+ * 
  */
-def chg_columns_for_mysql(mutable tb_mysql) {
+def generate_entity_latest_performance(entity_info, perf_latest, isToMySQL, mutable entity_latest_performance) {
+
+    t = null;
+
+    if(isToMySQL) {
 
-    tb_mysql.rename!('entity_id', 'fund_id');
+        t = SELECT r.*
+            FROM perf_latest r
+            INNER JOIN entity_info fi ON r.entity_id = fi.entity_id
+            WHERE r.price_date >= fi.price_date; // 过滤掉不必更新的旧记录
+        
+        INSERT INTO entity_latest_performance SELECT * FROM t;
 
-    // 将 dolphinDB 的 MONTH 换成 MySQL 的 YYYY-MM 格式
-    v_end_date = EXEC end_date.temporalFormat('yyyy-MM') FROM tb_mysql;
-    tb_mysql.replaceColumn!('end_date', v_end_date);
+    } else {
+        
+    }
 }
 
 
@@ -473,17 +381,20 @@ def calFundPerformance(entityType, date) {
 
     if(find(['HF', 'MF'], entityType) < 0) return null;
 
-    // 取有最新净值变动的基金列表
+    // 取有最新净值变动的基金列表 (1s)
     tb_cal_funds = get_entity_list_by_nav_updatetime(entityType, NULL, date, true);
 
     if(tb_cal_funds.isVoid() || tb_cal_funds.size() == 0 ) return;
 
 
-    tb_fund_performance = create_fund_performance();
-    tb_fund_indicator = create_fund_indicator();
-    tb_fund_risk_stats = create_fund_risk_stats();
-    tb_fund_riskadjret_stats = create_fund_riskadjret_stats();
-    tb_fund_style_stats = create_fund_style_stats();
+    tb_fund_performance = create_entity_performance();
+    tb_fund_indicator = create_entity_indicator();
+    tb_fund_risk_stats = create_entity_risk_stats();
+    tb_fund_riskadjret_stats = create_entity_riskadjret_stats();
+    tb_fund_style_stats = create_entity_style_stats();
+
+    tb_fund_performance_weekly = create_entity_performance_weekly();
+    tb_fund_latest_performance = create_entity_latest_performance();
 
     // 分批跑
     i = 0;
@@ -492,55 +403,82 @@ def calFundPerformance(entityType, date) {
 
     do {
 
-        funds = tb_cal_funds[i:batch_size];
+        funds = tb_cal_funds[i : min(tb_cal_funds.size(), i+batch_size)];
+        //funds = SELECT * FROM tb_cal_funds WHERE entity_id in ('MF00003PXI', 'MF00003PXX')
+
+        if(funds.isVoid() || funds.size() == 0) break;
 
+        // 200ms
         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');
 
-        // 计算月收益
+        // 计算月收益 (12s)
         rets = mix_monthly_returns(entityType, fund_info);
 
-        // 计算月度指标
-        rets.rename!('cumulative_nav', 'nav');
-        indicators = cal_monthly_indicators(entityType, 'PBI', rets);
+        if(!rets.isVoid() && rets.size() > 0) {
+
+            // 计算月度指标 (56s)
+            rets.rename!('cumulative_nav', 'nav');
+            indicators = cal_monthly_indicators(entityType, 'PBI', rets);
 
-        // TODO: 最新更新的指标存入数据库
-        generate_fund_performance(fund_info, indicators, true, tb_fund_performance);
-        generate_fund_indicator(fund_info, indicators, true, tb_fund_indicator);
-        generate_fund_risk_stats(fund_info, indicators, true, tb_fund_risk_stats);
-        generate_fund_riskadjret_stats(fund_info, indicators, true, tb_fund_riskadjret_stats);
-        generate_fund_style_stats(fund_info, indicators, true, tb_fund_style_stats);
+            // 仿照MySQL的表结构准备好记录 (1s)
+            generate_entity_performance(fund_info, indicators, true, tb_fund_performance);
+            generate_entity_indicator(fund_info, indicators, true, tb_fund_indicator);
+            generate_entity_risk_stats(fund_info, indicators, true, tb_fund_risk_stats);
+            generate_entity_riskadjret_stats(fund_info, indicators, true, tb_fund_riskadjret_stats);
+            generate_entity_style_stats(fund_info, indicators, true, tb_fund_style_stats);
+
+        }
+        
+        // 计算周收益 (8s)
+        rets_w = cal_weekly_returns(entityType, fund_info);
+
+        if(! rets_w.isVoid() && rets_w.size() > 0) {
+            generate_entity_performance_weekly(fund_info, rets_w, true, tb_fund_performance_weekly);
+        }
+
+        // 计算最新收益 (69s)
+        perf_latest = cal_latest_performance(entityType, fund_info, true);
+
+        if(! perf_latest.isVoid() && perf_latest.size() > 0) {
+            generate_entity_latest_performance(fund_info, perf_latest, true, tb_fund_latest_performance);
+        }
 
         i += batch_size;
 
-    } while (i < batch_size);
-//    } while (i <= tb_cal_funds.size());
+//    } while (i < batch_size);
+    } while (i <= tb_cal_funds.size());
+
 
+    if(! tb_fund_performance.isVoid() && tb_fund_performance.size() > 0) {
 
-    // saverisk_stats
-    try {
+        // save data to MySQL  (13s)
+        try {
 
-        chg_columns_for_mysql(tb_fund_performance);
-        save_table(tb_fund_performance, 'mfdb.fund_performance', true);
+            chg_columns_for_mysql(tb_fund_performance, 'fund_id');
+            save_and_sync(tb_fund_performance, 'raw_db.fund_performance', 'raw_db.sync_fund_performance_from_dolphin');
 
-        chg_columns_for_mysql(tb_fund_indicator);
-        save_table(tb_fund_indicator, 'mfdb.fund_indicator', true);
+            chg_columns_for_mysql(tb_fund_indicator, 'fund_id');
+            save_table(tb_fund_indicator, 'raw_db.fund_indicator', true);
 
-        chg_columns_for_mysql(tb_fund_risk_stats);
-        save_table(tb_fund_risk_stats, 'mfdb.fund_risk_stats', true);
+            chg_columns_for_mysql(tb_fund_risk_stats, 'fund_id');
+            save_table(tb_fund_risk_stats, 'raw_db.fund_risk_stats', true);
 
-        chg_columns_for_mysql(tb_fund_riskadjret_stats);
-        save_table(tb_fund_riskadjret_stats, 'mfdb.fund_riskadjret_stats', true);
+            chg_columns_for_mysql(tb_fund_riskadjret_stats, 'fund_id');
+            save_table(tb_fund_riskadjret_stats, 'raw_db.fund_riskadjret_stats', true);
 
-        chg_columns_for_mysql(tb_fund_style_stats);
-        save_table(tb_fund_style_stats, 'mfdb.fund_style_stats', true);
+            chg_columns_for_mysql(tb_fund_style_stats, 'fund_id');
+            save_table(tb_fund_style_stats, 'raw_db.fund_style_stats', true);
 
-    } catch(ex) {
+            save_table(tb_fund_performance_weekly, 'raw_db.fund_performance_weekly', true);
 
-        //TODO: Log errors
+            save_table(tb_fund_latest_performance, 'raw_db.fund_latest_performance', true);
 
-        rt = ex;
+        } catch(ex) {
 
+            //TODO: Log errors
+            rt = ex;
+        }
     }
     
     return rt;