Joey il y a 2 mois
Parent
commit
6276b4e69a

+ 78 - 11
modules/dataSaver.dos

@@ -345,6 +345,22 @@ def create_entity_style_stats(is_id_integer=false) {
 
 
 /*
+ *   建表 XXX_ms_stats
+ */
+def create_entity_ms_stats(is_id_integer=false) {
+
+    return table(1000:0,
+               ['entity_id', 'end_date',
+                'ms_return_3y', 'ms_rar_3y', 'ms_risk_3y',
+                'ms_return_5y', 'ms_rar_5y', 'ms_risk_5y',
+                'ms_return_10y', 'ms_rar_10y', 'ms_risk_10y'],
+               [iif(is_id_integer, INT, SYMBOL), MONTH,
+                DOUBLE, DOUBLE, DOUBLE,
+                DOUBLE, DOUBLE, DOUBLE,
+                DOUBLE, DOUBLE, DOUBLE]);
+}
+
+/*
  *   建基金经理/公司表 XXX_style_stats
  */
 def create_mc_style_stats() {
@@ -727,7 +743,7 @@ def generate_entity_performance(entity_info, indicators, isToMySQL, mutable enti
 
     t = null;
 
-    if(extra_keys.isNothing() || extra_keys.isVoid()) v_extra_keys = array(STRING);
+    if(extra_keys.isNothing() || extra_keys.isVoid() || extra_keys.size() == 0) v_extra_keys = array(STRING);
     else v_extra_keys = extra_keys;
 
     if(isToMySQL) {
@@ -735,7 +751,7 @@ def generate_entity_performance(entity_info, indicators, isToMySQL, mutable enti
        if(indicators['PBI-3M'].isVoid() || indicators['PBI-3M'].size() == 0) return;
 
         t = sql(select =(sqlCol('entity_id'),
-         				 sqlCol(extra_keys.join('end_date')),
+         				 sqlCol(v_extra_keys.join('end_date')),
         				 sqlCol('price_date'), 
                          sqlCol(['nav', 'ret', 'ret', 'trailing_ret', 'trailing_ret_a'], , ['cumulative_nav', 'ret_1m', 'ret_1m_a', 'ret_3m', 'ret_3m_a'])
                         ),
@@ -778,7 +794,7 @@ def generate_entity_risk_stats(entity_info, indicators, isToMySQL, mutable entit
 
     t = null;
 
-    if(extra_keys.isNothing() || extra_keys.isVoid()) v_extra_keys = array(STRING);
+    if(extra_keys.isNothing() || extra_keys.isVoid() || extra_keys.size() == 0) v_extra_keys = array(STRING);
     else v_extra_keys = extra_keys;
 
 	if(indicators['PBI-6M'].isVoid() || indicators['PBI-6M'].size() == 0) return;
@@ -786,7 +802,7 @@ def generate_entity_risk_stats(entity_info, indicators, isToMySQL, mutable entit
     if(isToMySQL) {
 
         t = sql(select =(sqlCol('entity_id'),
-         				 sqlCol(extra_keys.join('end_date')),
+         				 sqlCol(v_extra_keys.join('end_date')),
                          sqlCol(['std_dev_a', 'ds_dev_a', 'alpha_a', 'winrate', 'beta', 'skewness', 'kurtosis', 'wrst_month', 'drawdown'],
                          , ['stddev_6m', 'downsidedev_6m', 'alpha_6m', 'winrate_6m', 'beta_6m', 'skewness_6m', 'kurtosis_6m', 'worstmonth_6m', 'maxdrawdown_6m'])
                         ),
@@ -838,7 +854,7 @@ def generate_entity_riskadjret_stats(entity_info, indicators, isToMySQL, mutable
 
     t = null;
 
-    if(extra_keys.isNothing() || extra_keys.isVoid()) v_extra_keys = array(STRING);
+    if(extra_keys.isNothing() || extra_keys.isVoid() || extra_keys.size() == 0) v_extra_keys = array(STRING);
     else v_extra_keys = extra_keys;
 
    if(indicators['PBI-6M'].isVoid() || indicators['PBI-6M'].size() == 0) return;
@@ -846,7 +862,7 @@ def generate_entity_riskadjret_stats(entity_info, indicators, isToMySQL, mutable
     if(isToMySQL) {
 
         t = sql(select =(sqlCol('entity_id'),
-         				 sqlCol(extra_keys.join('end_date')),
+         				 sqlCol(v_extra_keys.join('end_date')),
                          sqlCol(['sharpe_a', 'sortino_a', 'treynor', 'jensen_a', 'calmar', 'omega', 'kappa'],
                          , ['sharperatio_6m', 'sortinoratio_6m', 'treynorratio_6m', 'jensen_6m', 'calmarratio_6m', 'omegaratio_6m', 'kapparatio_6m'])
                         ),
@@ -895,7 +911,7 @@ def generate_entity_indicator(entity_info, indicators, isToMySQL, mutable entity
 
     t = null;
 
-    if(extra_keys.isNothing() || extra_keys.isVoid()) v_extra_keys = array(STRING);
+    if(extra_keys.isNothing() || extra_keys.isVoid() || extra_keys.size() == 0) v_extra_keys = array(STRING);
     else v_extra_keys = extra_keys;
 
    if(indicators['PBI-6M'].isVoid() || indicators['PBI-6M'].size() == 0) return;
@@ -903,7 +919,7 @@ def generate_entity_indicator(entity_info, indicators, isToMySQL, mutable entity
     if(isToMySQL) {
 
         t = sql(select =(sqlCol('entity_id'),
-         				 sqlCol(extra_keys.join('end_date')),
+         				 sqlCol(v_extra_keys.join('end_date')),
                          sqlCol(['info_a', 'm2_a', 'track_error_a'],
                          , ['info_ratio_6m', 'm2_6m', 'tracking_error_6m'])
                         ),
@@ -953,7 +969,7 @@ def generate_entity_style_stats(entity_info, indicators, isToMySQL, mutable enti
 
     t = null;
 
-    if(extra_keys.isNothing() || extra_keys.isVoid()) v_extra_keys = array(STRING);
+    if(extra_keys.isNothing() || extra_keys.isVoid() || extra_keys.size() == 0) v_extra_keys = array(STRING);
     else v_extra_keys = extra_keys;
 
    if(indicators['PBI-6M'].isVoid() || indicators['PBI-6M'].size() == 0) return;
@@ -961,7 +977,7 @@ def generate_entity_style_stats(entity_info, indicators, isToMySQL, mutable enti
     if(isToMySQL) {
 
         t = sql(select =(sqlCol('entity_id'),
-         				 sqlCol(extra_keys.join('end_date')),
+         				 sqlCol(v_extra_keys.join('end_date')),
                          sqlCol(['upside_capture_ret', 'downside_capture_ret', 'upside_capture_ratio', 'downside_capture_ratio'],
                          , ['upsidecapture_ret_6m', 'downsidecapture_ret_6m', 'upsidecapture_ratio_6m', 'downsidecapture_ratio_6m'])
                         ),
@@ -999,6 +1015,57 @@ def generate_entity_style_stats(entity_info, indicators, isToMySQL, mutable enti
 
 
 /*
+ *   按照 XXX_ms_stats 表结构准备数据记录
+ * 
+ * 
+ */
+def generate_entity_ms_stats(entity_info, indicators, isToMySQL, mutable entity_ms_stats, extra_keys=[]) {
+
+    t = null;
+
+    if(extra_keys.isNothing() || extra_keys.isVoid() || extra_keys.size() == 0) v_extra_keys = array(STRING);
+    else v_extra_keys = extra_keys;
+
+   if(indicators['PBI-3Y'].isVoid() || indicators['PBI-3Y'].size() == 0) return;
+
+    if(isToMySQL) {
+
+        t = sql(select =(sqlCol('entity_id'),
+         				 sqlCol(v_extra_keys.join('end_date')),
+                         sqlCol(['ms_ret_a', 'ms_rar_a', 'ms_risk_a'],
+                         , ['ms_return_3y', 'ms_rar_3y', 'ms_risk_3y'])
+                        ),
+        		from = ej(indicators['PBI-3Y'] AS a, entity_info AS fi, ['entity_id'].join(v_extra_keys)),
+        		where = < end_date >= fi_price_date.month()> // 过滤掉不必更新的旧记录
+        	   ).eval();
+        	   
+        v_trailing = ['5y', '10y'];
+        for(tr in v_trailing) {
+
+			col_ms_ret = 'ms_return_' + tr;
+			col_ms_rar = 'ms_rar_' + tr;
+			col_ms_risk = 'ms_risk_' + tr;
+
+			t.addColumn([col_ms_ret, col_ms_rar, col_ms_risk],
+						[DOUBLE, DOUBLE, DOUBLE]);
+
+			if(!indicators['PBI-'+tr.upper()].isVoid()) {
+
+				sqlUpdate(table = t,
+				          updates = [<ms_ret_a as _$col_ms_ret>, <ms_rar_a as _$col_ms_rar>, <ms_risk_a as _$col_ms_risk>],
+				          from = <ej(t, indicators['PBI-'+tr.upper()], ['entity_id'].join(v_extra_keys).join(['end_date']))>
+						 ).eval();
+			}
+	    }
+
+        INSERT INTO entity_ms_stats SELECT * FROM t;
+
+    } else {
+        
+    }
+}
+
+/*
  *   按照 XXX_bfi_bm_indicator 表结构准备数据记录
  *   
  *   @param entity_info <TABLE>: [COLUMNS] entity_id, end_date, benchmark_id, inception_date, ini_value [,curve_type, strategy]
@@ -1011,7 +1078,7 @@ def generate_entity_bfi_indicator(entity_info, indicators, isToMySQL, mutable en
 
     t = null;
 
-    if(extra_keys.isNothing() || extra_keys.isVoid()) v_extra_keys = array(STRING);
+    if(extra_keys.isNothing() || extra_keys.isVoid() || extra_keys.size() == 0) v_extra_keys = array(STRING);
     else v_extra_keys = extra_keys;
 
 	v_cols_from = ['upside_capture_ret', 'downside_capture_ret', 'upside_capture_ratio', 'downside_capture_ratio', 'alpha_a', 'winrate', 'beta', 'info_a', 'track_error_a', 'jensen_a'];

+ 14 - 7
modules/indicatorCalculator.dos

@@ -695,13 +695,16 @@ def cal_ms_return(ret, risk_free, trailing_month) {
 
     r = SELECT t.entity_id, t.end_date,
                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
+                   ((1 + t.ret)\(1 + rfr.ret)).mprod(win).pow(12\(1+t.end_date.mmax(win) - t.end_date.mmin(win)))-1) AS ms_ret_a,
+               ((1 + t.ret)\(1 + rfr.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
         WHERE t.ret > -1
         CONTEXT BY t.entity_id;
 
+    r.addColumn('ms_risk_a', DOUBLE);
+    UPDATE r SET ms_risk_a = ms_ret_a - ms_rar_a;
+
     return r;
 }
 
@@ -858,6 +861,7 @@ def cal_indicators_with_benchmark(entity_info, benchmark_mapping, end_day, tb_re
  *   
  *   @return: indicators table
  *   
+ *   TODO: 目前只能使用单一无风险利率,无法根据基金信息自由选择更匹配的指数
  *   
  *   Create  20240904  模仿Java & python代码在Dolphin中实现,具体计算逻辑可能会有不同                          Joey
  *
@@ -1120,7 +1124,7 @@ def cal_mc_monthly_indicators(entity_type, indicator_type, monthly_returns) {
 
     } else {
         // 主基准, 对应公募混合基金平均 FA00000VNB 
-        benchmark = SELECT DISTINCT entity_id, end_date, iif(benchmark_id.isVoid(), 'FA00000VNB', benchmark_id) AS benchmark_id
+        benchmark = SELECT DISTINCT entity_id, curve_type, strategy, end_date, iif(benchmark_id.isVoid(), 'FA00000VNB', benchmark_id) AS benchmark_id
                     FROM ej(entity_info, monthly_returns, ['entity_id', 'curve_type', 'strategy']) ;
 
     }
@@ -1142,19 +1146,22 @@ def cal_mc_monthly_indicators(entity_type, indicator_type, monthly_returns) {
 
 		t_ei = SELECT entity_id, inception_date, benchmark_id, ini_value FROM entity_info WHERE curve_type = cur AND strategy = 0;
 		t_mr = SELECT entity_id, end_date, price_date, ret, nav FROM monthly_returns WHERE curve_type = cur AND strategy = 0;
+		t_bk = SELECT entity_id, end_date, benchmark_id FROM benchmark WHERE curve_type = cur AND strategy = 0;
+
+		if(t_ei.isVoid() || t_ei.size() == 0 || t_mr.isVoid() || t_mr.size() == 0) continue;
 		
 	    if(indicator_type == 'BFI') {
-	
-	        v_indicators = cal_trailing_bfi_indicators(t_ei, benchmark, end_day, t_mr, bmk_ret, risk_free_rate);
+
+	        v_indicators = cal_trailing_bfi_indicators(t_ei, t_bk, end_day, t_mr, bmk_ret, risk_free_rate);
 
 	        for(tb in v_indicators)
-		        tb.cj(table(cur AS curve_type, 0 AS strategy));
+		        UPDATE tb SET curve_type = cur, strategy = 0;
 	
 	        v_table_name = ['BFI-INCEP', 'BFI-YTD', 'BFI-3M', 'BFI-6M', 'BFI-1Y', 'BFI-2Y', 'BFI-3Y', 'BFI-4Y', 'BFI-5Y', 'BFI-10Y'];
 	
 	    } else {
 	
-	        v_indicators = cal_trailing_indicators(t_ei, benchmark, end_day, t_mr, bmk_ret, risk_free_rate);
+	        v_indicators = cal_trailing_indicators(t_ei, t_bk, end_day, t_mr, bmk_ret, risk_free_rate);
 
 	        for(tb in v_indicators)
 				UPDATE tb SET curve_type = cur, strategy = 0;

+ 4 - 4
modules/navCalculator.dos

@@ -111,9 +111,9 @@ def cal_entity_nav_by_return(entity_type, entity_ret, freq) {
  *   @return <TABLE>: [COLUMNS] entity_id, price_date, ret
  */
 def cal_nav_by_return(entity_type, entity_cal_dates, holdings) {
-// entity_type = 'FA'
-// entity_cal_dates=t_factor
-// holdings = t
+// entity_type = 'PF'
+// entity_cal_dates=tb_port_first_cal_date
+// holdings = tb_holdings
 
     // 组合收益计算: RET = ∑( weight_i * ret_i )
     tb_portfolio_ret = SELECT entity_id, price_date, (weight * ret).sum() AS ret
@@ -129,7 +129,7 @@ def cal_nav_by_return(entity_type, entity_cal_dates, holdings) {
     tb_pre_nav = get_entity_nav_by_date(entity_type, s_json, true);
 
     INSERT INTO tb_pre_nav
-    	SELECT entity_id, first_cal_date, NULL
+    	SELECT entity_id, first_cal_date, double(NULL) AS cumulative_nav, double(NULL) AS nav
     	FROM entity_cal_dates
     	WHERE NOT exists( SELECT * FROM tb_pre_nav WHERE tb_pre_nav.entity_id = entity_cal_dates.entity_id);
    

+ 29 - 4
modules/performanceDataPuller.dos

@@ -670,9 +670,11 @@ def get_portfolio_list_by_fund_nav_updatetime(portfolio_ids, updatetime, isFromM
 }
 
 /*
- *   根据指数净值更新日期,取受影响的BFI因子列表
+ *   根据指数净值更新日期,取受影响的合成因子列表
  * 
- *   Example: get_bfi_factor_list_by_index_nav_updatetime(['FA00000VMH','FA00000VMK'], 2024.11.08, true);
+ *   Modify  20241218   去掉 factor_type = 5的限制,支持非BFI因子使用 cm_factor_index_map
+ * 
+ *   Example: get_bfi_factor_list_by_index_nav_updatetime(['FA00000VMH','FA00000VMK','FA00000SMB'], 2024.11.08, true);
  *            get_bfi_factor_list_by_index_nav_updatetime(NULL, NULL, true);
  */
 def get_bfi_factor_list_by_index_nav_updatetime(factor_ids, updatetime,  isFromMySQL) {
@@ -690,8 +692,7 @@ def get_bfi_factor_list_by_index_nav_updatetime(factor_ids, updatetime,  isFromM
 				   FROM pfdb.cm_factor_information fi
 				   INNER JOIN pfdb.cm_factor_index_map map ON fi.factor_id = map.factor_id
 				   INNER JOIN mfdb.market_indexes mi ON map.index_id = mi.index_id
-				   WHERE fi.isvalid = 1
-				     AND fi.factor_type = 5" +  // 5: bfi factor
+				   WHERE fi.isvalid = 1" +
 				     sql_entity_id + "
 				     AND map.isvalid = 1
 				     AND mi.isvalid = 1
@@ -791,6 +792,30 @@ def get_entity_nav_by_date(entity_type, s_json, isFromMySQL=true) {
 
 
 /*
+ *  同步专用:取Json中指定的证券当日净值及isvalid, createtime、updatetime
+ * 
+ *  TODO: need adding index on json table
+ *
+ *  Example: sync_entity_nav_by_date('MF', '[{"entity_id": "MF00003PW1","price_date": "2024.10.25"},{"entity_id": "MF00003PW2","price_date": "2024.03.13"}]');
+ *           sync_entity_nav_by_date('MI', '[{"entity_id": "IN00000008","price_date": "2000.01.25"}]');
+ *  		 sync_entity_nav_by_date('PF', '[{"entity_id": 166002,"price_date": "2024.10.25"},{"entity_id": 166114,"price_date": "2024.03.13"}]');
+ */
+def sync_entity_nav_by_date(entity_type, s_json) {
+
+    t = null;
+
+    s_query = "CALL pfdb.sp_sync_nav_for_dolphin('" + entity_type + "','" + s_json + "');";
+
+    conn = connect_mysql();
+
+    t = odbc::query(conn, s_query);
+ 
+    conn.close();
+
+    return t;
+}
+
+/*
  *  通用取Json中指定日期后(含当日)的证券收益
  *  
  *  @param freq <STRING>: m, w

+ 118 - 12
modules/task_portfolioPerformance.dos

@@ -377,31 +377,137 @@ def cal_and_save_factor_nav(cal_factor_info, is_save_local) {
 }
 
 
+/*
+ *  计算基于指数组合的因子日收益
+ * 
+ * 
+ */
+def cal_and_save_synthesis_factor_nav(updatetime, is_save_local) {
+
+	// factor_type = 5: 根据成分指数净值更新日期,取有影响的因子
+	tb_cal_factors = get_bfi_factor_list_by_index_nav_updatetime(NULL, updatetime, true);
+
+    if(tb_cal_factors.isVoid() || tb_cal_factors.size() == 0) return null;
+
+    // 26 min
+    cal_and_save_factor_nav(tb_cal_factors, is_save_local);
+
+    return tb_cal_factors;
+}
+
+/*
+ *  计算基于指数移动收益的因子日收益
+ * 
+ *  TODO: 算法暂时与Java相近,虽然看起来比较可疑
+ */
+def cal_moving_avg_factor_ret(updatetime) {
+
+	t_factor_return = table(100:0, ['factor', 'price_date', 'ret'], [SYMBOL, DATE, DOUBLE]);
+
+	index_ids = ['IN0000007N']; // 中证全指
+
+	factor_momentum = 'FA000000MT'; // 动量因子
+	factor_reverse =  'FA000000RV'; // 反转因子
+
+	t_index_date = get_entity_list_by_nav_updatetime('MI', index_ids, updatetime, true);
+
+	if(t_index_date.isVoid() || t_index_date.size() == 0) return t_factor_return;
+
+	// 倒着多取1年的净值
+	s_json = (SELECT entity_id AS sec_id, price_date.temporalAdd(-1y) AS price_date FROM t_index_date).toStdJson();
+	t_index_nav = get_nav_for_return_calculation('MI', 'd', s_json, pre_nav_incld=2);
+
+	if(t_index_nav.isVoid() || t_index_nav.size() == 0) return t_factor_return;
+
+	// 取上交所交易日历
+	v_trade_day = getMarketCalendar('SSE', t_index_nav.price_date.min(), today());
+
+	t_index_nav = SELECT * FROM t_index_nav WHERE price_date in v_trade_day;
+
+	t_index_ret = SELECT sec_id AS entity_id, price_date, cumulative_nav.ratios()-1 AS ret FROM t_index_nav.sortBy!(['sec_id', 'price_date']);
+
+    // 反转因子:中证全指过去1个月(Java 是20日)的平均日收益
+    t_ret = SELECT factor_reverse AS factor_id , rt.price_date, ret
+            FROM (
+	        	  SELECT entity_id, price_date, tmavg(price_date, ret, 1M) AS ret 
+	              FROM t_index_ret CONTEXT BY rt.entity_id ) rt
+		    INNER JOIN t_index_date dt ON rt.entity_id = dt.entity_id
+		    WHERE rt.price_date >= dt.price_date;
+
+	t_factor_return.tableInsert(t_ret);
+
+	// 动量因子:中证全指过去1年 (Java是220个交易日中200个交易日?没看明白)的平均日收益
+    t_ret = SELECT factor_momentum AS factor_id, rt.price_date, ret
+            FROM (
+	        	  SELECT entity_id, price_date, tmavg(price_date, ret, 1y) AS ret 
+	              FROM t_index_ret CONTEXT BY rt.entity_id ) rt
+		    INNER JOIN t_index_date dt ON rt.entity_id = dt.entity_id
+		    WHERE rt.price_date >= dt.price_date;
+
+	t_factor_return.tableInsert(t_ret);
+
+	return t_factor_return;
+}
+
+
+/*
+ *  计算基于债券的因子日收益
+ * 
+ *  TODO: 算法看起来比较可疑,并且债指2022-03以后就不再有 duration, convexity 数据
+ */
+def cal_bond_factor_ret(updatetime) {
+
+	t_factor_return = table(100:0, ['factor', 'price_date', 'ret'], [SYMBOL, DATE, DOUBLE]);
+
+	index_ids = ['IN0000007A', 'IN0000008I', 'IN0000008B', 'IN0000008C']; // 中证国债、长期国债、中高信用、中低信用
+
+	factor_term = 'FA000000ST'; // 期限因子
+	factor_credit_spread =  'FA000000CD'; // 信用利差因子
+	factor_hybond =  'FA00000HYB'; // 高收益债因子
+
+	t_index_date = get_entity_list_by_nav_updatetime('MI', index_ids, updatetime, true);
+
+	if(t_index_date.isVoid() || t_index_date.size() == 0) return t_factor_return;
+
+	// 倒着多取1年的净值
+	s_json = (SELECT entity_id AS sec_id, price_date.temporalAdd(-1y) AS price_date FROM t_index_date).toStdJson();
+	t_index_nav = get_nav_for_return_calculation('MI', 'd', s_json, pre_nav_incld=2);
+
+	if(t_index_nav.isVoid() || t_index_nav.size() == 0) return t_factor_return;
+
+	// 取上交所交易日历
+	v_trade_day = getMarketCalendar('SSE', t_index_nav.price_date.min(), today());
+
+	t_index_nav = SELECT * FROM t_index_nav WHERE price_date in v_trade_day;
+
+	t_index_ret = SELECT sec_id AS entity_id, price_date, cumulative_nav.ratios()-1 AS ret FROM t_index_nav.sortBy!(['sec_id', 'price_date']);
+
+    // TODO: ret = R_1 * W_1 - R_2 * W_2; 其中: R_x 代表收益,W_x 代表权重,D_x 代表久期; 
+    // W_1 = D_2 /(D_2 - D_1), W_2 = D_1/(D_2 - D_1) ??? JAVA 就是这个权重逻辑
+
+	return t_factor_return;
+}
+
+
 
 /*
- *   [定时任务]批量计算bfi因子净值、收益及指标
+ *   [定时任务]批量计算因子净值、收益及指标
  *   
  *   @param updatetime <DATETIME>: 成分指数净值更新时间,忽略或传入1989.01.01及更早的日期被认为在做数据初始化
  *   
- *   TODO: 由减运算合成的BFI还未涉及,如FA00000SMB
- * 
+ *   TODO: 非BFI的因子还未涉及,需要参考 PerformanceAttributionFactorServiceImpl
+ *  
  * 
  *   Example: CalFactorPerformanceTask(2024.10.28);
  *            CalFactorPerformanceTask(1989.01.01);  -- 【初始化专用】 (1.3 min)
  */
 def CalFactorPerformanceTask(updatetime) {
-
+//updatetime=2024.10.28
 	rt = '';
-
-	// 根据成分指数净值更新日期,取有影响的因子
-	tb_cal_factors = get_bfi_factor_list_by_index_nav_updatetime(NULL, updatetime, true);
-
-    if(tb_cal_factors.isVoid() || tb_cal_factors.size() == 0) return rt;
-
     is_save_local = iif(updatetime <= get_ini_data_const()['date'], true, false);
 
-    // 26 min
-    rt = cal_and_save_factor_nav(tb_cal_factors, is_save_local);
+	tb_cal_factors = cal_and_save_synthesis_factor_nav(updatetime, is_save_local);
+
     // 9 min
     tb_cal_factors.rename!(['factor_id', 'first_cal_date', 'latest_cal_date'], ['entity_id', 'start_cal_date', 'end_cal_date']);
     rt = rt + '; ' + cal_and_save_entity_indicators('FA', tb_cal_factors, is_save_local);