Browse Source

支持基金经理和公司月度净值计算

Joey 4 months ago
parent
commit
378233e989

+ 13 - 0
modules/dataSaver.dos

@@ -93,6 +93,19 @@ def create_entity_nav(is_id_integer=false) {
                 [iif(is_id_integer, INT, SYMBOL), DATE, DOUBLE]);
 }
 
+
+/*
+ *   建表 XXXX_fitted_curve
+ */
+def create_entity_fitted_curve() {
+
+    return table(1000:0, 
+                ['entity_id', 'curve_type', 'strategy', 'end_date', 'cumulative_nav', 'fund_num'],
+                [SYMBOL, INT, INT, STRING, DOUBLE, INT]);
+}
+
+
+
 /*
  *   建表 XXXX_performance
  */

+ 44 - 0
modules/navCalculator.dos

@@ -298,3 +298,47 @@ def cal_nav_by_return(entity_type, entity_cal_dates, holdings) {
 	return cal_nav_by_return('PF', tb_port_first_cal_date, tb_holdings);
 
  }
+
+
+
+/*
+ *   通用净值计算,由收益反推 
+ *    
+ *   @param entity_type <STRING>: PL, CO
+ *   @param entity_ret <TABLE>: [COLUMNS] entity_id, curve_type, strategy, end_date, ret
+ * 
+ *   NOTE: 如果没有成立日,则无法计算
+ */
+def cal_mc_nav_by_return(entity_type, entity_ret) {
+
+	t_nav = table(1000:0, ['entity_id', 'curve_type', 'strategy', 'end_date', 'ret', 'nav'], [SYMBOL, INT, INT, STRING, DOUBLE, DOUBLE]);
+
+	if(!(entity_type IN ['PL', 'CO'])) return t_nav;
+	if(entity_ret.isVoid() || entity_ret.size() == 0) return t_nav;
+
+	s_json = (SELECT entity_id AS entity_id, curve_type, strategy, end_date.min() AS end_date 
+	          FROM entity_ret
+	          GROUP BY entity_id, curve_type, strategy).toStdJson();
+
+	// 取净值前值
+	t_pre_nav = get_mc_nav_for_return_calculation(entity_type, s_json, 1);
+
+	// 没有前值时候, 做一个假记录,把净值1和日期填入
+	INSERT INTO t_pre_nav
+		SELECT entity_id, curve_type, strategy, end_date.min() AS end_date, 1 AS cumulative_nav
+        FROM entity_ret
+        WHERE NOT exists (SELECT * FROM t_pre_nav 
+	                      WHERE entity_ret.entity_id = t_pre_nav.entity_id
+	                        AND entity_ret.curve_type = t_pre_nav.curve_type
+	                        AND entity_ret.strategy = t_pre_nav.strategy)
+	
+	t_nav = entity_ret.join(take(double(NULL), entity_ret.size()) AS nav);
+
+    // 通过收益反算净值: nav_i = nav_0 * ∏(1 + ret_i)
+    UPDATE t_nav 
+        SET nav = (t_pre_nav.cumulative_nav * (1+ret).cumprod()).round(6) 
+    FROM ej(t_nav, t_pre_nav, 'entity_id')
+    CONTEXT BY entity_id;
+
+    return t_nav;
+}

+ 130 - 0
modules/performanceDataPuller.dos

@@ -477,6 +477,33 @@ def get_nav_for_return_calculation(entity_type, freq, json_query, pre_nav_incld=
     return t;
 }
 
+
+
+/*
+ *  取基金经理/公司从某日期后的所有净值及前值
+ *  
+ *  @param entity_type <STRING>: PL, CO
+ *  @param pre_nav_incld <INT>: 0- no pre_nav; 1- pre_nav only; 2- pre_nav + afters
+ *  @param json_query <JSON>: [{entity_id:xxx, curve_type:1, strategy:0, end_date: yyyy-mm}]
+ * 
+ *  Example: get_mc_nav_for_return_calculation('PL', '[{"entity_id":"PL000000NS", "curve_type":"4", "strategy":"0", "end_date":"2024-07"}]', 2);
+ *           get_mc_nav_for_return_calculation('CO', '[{"entity_id":"CO00000017", "curve_type":"4", "strategy":"0", "end_date":"2024-07"}]', 1);
+ *  
+ */
+def get_mc_nav_for_return_calculation(entity_type, json_query, pre_nav_incld=2) {
+
+    s_query = "CALL pfdb.sp_get_mc_nav_for_return_cal('" + entity_type + "', " + pre_nav_incld + ", '" + json_query + "')";
+
+    conn = connect_mysql();
+
+    t = odbc::query(conn, s_query);
+
+    conn.close();
+
+    return t;
+}
+
+
 /*
  *   取主基准和BFI的历史月收益率
  *   
@@ -1009,3 +1036,106 @@ def get_category_avg_weekly_return(category_type, begin_day, trim_pct=5, min_cnt
     return t
 }
 
+
+/*
+ *  取基金经理或公司月收益
+ *  
+ * 
+ *  Example: get_mc_monthly_return('manager', '[{"entity_id": "PL000000NS","end_date": "2024-07"},{"entity_id": "PL00000ICF","end_date": "2023-12"}]', 0, 1, true);
+ *           get_mc_monthly_return('company', '[{"entity_id": "CO00000017","end_date": "2024-07"},{"entity_id": "CO0001003W","end_date": "2023-06"}]', 0, 1, true);
+ */
+def get_mc_monthly_return(entity_type, json_query, trim_pct=0, min_cnt=1, isFromMySQL=true) {
+
+    t = null;
+
+    if(isFromMySQL == true) {
+
+        s_query = "CALL pfdb.sp_get_mc_avg_monthly_return('" + entity_type + "', '" + json_query + "', " + trim_pct + ", " + min_cnt + ")";
+
+	    conn = connect_mysql();
+	
+	    t = odbc::query(conn, s_query);
+	
+	    conn.close();
+
+    }
+
+    return t
+}
+
+
+/*
+ *   取有基金月收益更新的基金公司列表
+ *   
+ *   NOTE: 月收益表 isvalid = 0 的记录也会被返回
+ * 
+ *   Example: get_company_list_by_fund_updatetime(2024.11.14 11:10:00, true);
+ * 
+ */
+def get_company_list_by_fund_updatetime(updatetime, isFromMySQL=true) {
+
+
+    if(isFromMySQL == true) {
+
+        s_query = "SELECT ci.company_id, MIN(fp.end_date) AS end_date
+                   FROM mfdb.company_information ci
+                   INNER JOIN mfdb.fund_information fi ON ci.company_id = fi.advisor_id
+                   INNER JOIN mfdb.fund_performance fp ON fi.fund_id = fp.fund_id
+                   WHERE ci.isvalid = 1
+                     AND fi.isvalid = 1
+                     AND fp.updatetime >= '" + updatetime + "'
+                   GROUP BY ci.company_id;";
+     
+        conn = connect_mysql()
+     
+        t = odbc::query(conn, s_query)
+     
+        conn.close()
+
+    } else {
+        
+    
+    }
+
+    return t
+}
+
+
+/*
+ *   取有基金月收益更新的基金经理列表
+ *   
+ *   NOTE: 月收益表 isvalid = 0 的记录也会被返回
+ * 
+ *   Example: get_manager_list_by_fund_updatetime(2024.11.01, true);
+ * 
+ */
+def get_manager_list_by_fund_updatetime(updatetime, isFromMySQL=true) {
+
+
+    if(isFromMySQL == true) {
+
+        s_query = "SELECT mi.personnel_id AS manager_id, MIN(fp.end_date) AS end_date
+                   FROM mfdb.personnel_information mi
+                   INNER JOIN mfdb.fund_manager_mapping fmp ON mi.personnel_id = fmp.fund_manager_id
+                   INNER JOIN mfdb.fund_information fi ON fmp.fund_id = fi.fund_id
+                   INNER JOIN mfdb.fund_performance fp ON fi.fund_id = fp.fund_id
+                   WHERE mi.isvalid = 1
+                     AND fmp.isvalid = 1
+                     AND fi.isvalid = 1
+                     AND fp.updatetime >= '" + updatetime + "'
+                     AND fp.end_date <= DATE_FORMAT(IFNULL(fmp.management_end_date, CURRENT_DATE), '%y-%m')
+                   GROUP BY mi.personnel_id;";
+     
+        conn = connect_mysql()
+     
+        t = odbc::query(conn, s_query)
+     
+        conn.close()
+
+    } else {
+        
+    
+    }
+
+    return t
+}

+ 126 - 2
modules/task_monthlyPerformance.dos

@@ -1,13 +1,13 @@
 module fundit::task_monthlyPerformance
 
-
+use fundit::sqlUtilities;
 use fundit::operationDataPuller;
 use fundit::performanceDataPuller;
 use fundit::indicatorCalculator;
 use fundit::dataSaver;
 use fundit::bfiMatcher;
 use fundit::rankingCalculator;
-
+use fundit::navCalculator;
 
 
 /*
@@ -136,3 +136,127 @@ def CalRelativeRankingTask(entity_type, entity_ids, end_date, isFromMySQL=true)
     }
 	
 }
+
+/*
+ *  计算基金经理和公司净值(月度)
+ * 
+ * 
+ */
+def cal_and_save_mc_nav(entity_type, entity_date, is_save_local) {
+
+	rt = '';
+
+	if(entity_type == 'PL') s_entity_type = 'manager';
+	else if(entity_type == 'CO') s_entity_type = 'company';
+	else return rt;
+
+	if(entity_date.isVoid() || entity_date.size() == 0) return rt;
+
+    // 准备类似MySQL结构的数据表
+    tb_entity_nav = create_entity_fitted_curve();
+
+    // 暂时与 MySQL 保持一致,只计算公募,私募,公私募综合三条时间序列。未来可细化至公、私募+主策略
+    d_curve_type = dict(INT, INT);
+    d_curve_type[1] = 1; // 私募
+    d_curve_type[4] = 2; // 公募
+    d_curve_type[7] = -99; // 公私募综合
+
+    // 分批跑
+    i = 0;
+    batch_size = 1000;
+
+    all_entity_id = entity_date.entity_id.distinct();
+
+    do { // 14 sec
+
+        tb_entity = SELECT * FROM entity_date
+                    WHERE entity_id IN all_entity_id[i : min(all_entity_id.size(), i+batch_size)];
+
+        if(tb_entity.isVoid() || tb_entity.size() == 0) break;
+
+		s_json = tb_entity.toStdJson();
+		t_ret = get_mc_monthly_return(s_entity_type, s_json, 0, 1, true);
+
+        
+        for(cur in d_curve_type.keys()) {
+
+			tmp = SELECT entity_id, cur AS curve_type, 0 AS strategy, end_date, price_date, ret, incl_cal_cnt 
+			      FROM t_ret WHERE raise_type = d_curve_type[cur] AND strategy = -99; // 目前只需要全策略
+			// 取净值前值
+        	tb_nav = cal_mc_nav_by_return(entity_type, tmp);
+
+	        INSERT INTO tb_entity_nav 
+	        	SELECT entity_id, curve_type, strategy, end_date, nav, incl_cal_cnt 
+	            FROM ej(tb_nav, tmp, ['entity_id', 'curve_type', 'strategy', 'end_date']);
+        }
+
+        i += batch_size;
+
+    } while (i <= all_entity_id.size());
+
+
+    if(! tb_entity_nav.isVoid() && tb_entity_nav.size() > 0) {
+
+        // save data to MySQL  (12 sec)
+        try {
+
+            tb_entity_nav.rename!('entity_id', iif(entity_type == 'PL', 'fund_manager_id', 'company_id'));
+            save_and_sync(tb_entity_nav, iif(entity_type == 'PL', 'raw_db.fund_manager_fitted_curve', 'raw_db.company_fitted_curve'), );
+
+            // 数据初始化时将指标存入本地
+            if(is_save_local == true) {
+            	save_table(tb_entity_nav, iif(entity_type == 'PL', 'mfdb.fund_manager_fitted_curve', 'mfdb.company_fitted_curve'), false);
+            }
+
+        } catch(ex) {
+
+            //TODO: Log errors
+            rt = ex;
+        }
+    }
+
+    return rt;
+	
+}
+
+/*
+ *  [定时任务]: 基金经理月净值计算
+ * 
+ * 
+ */
+def CalManagerNavTask(updatetime) {
+//updatetime = 2024.11.05;
+
+	is_save_local = iif(updatetime <= get_ini_data_const()['updatetime'], true, false);
+
+	// 31 sec
+	entity_date = get_manager_list_by_fund_updatetime(updatetime);
+
+	entity_date.rename!('manager_id', 'entity_id');
+
+	cal_and_save_mc_nav('PL', entity_date, is_save_local);
+
+	entity_date = null;
+}
+
+
+/*
+ *  [定时任务]: 基金公司月净值计算
+ * 
+ * 
+ */
+def CalCompanyNavTask(updatetime) {
+//updatetime = 2024.11.05;
+
+	is_save_local = iif(updatetime <= get_ini_data_const()['updatetime'], true, false);
+
+	// 31 sec
+	entity_date = get_company_list_by_fund_updatetime(updatetime);
+
+	entity_date.rename!('company_id', 'entity_id');
+
+	cal_and_save_mc_nav('CO', entity_date, is_save_local);
+
+	entity_date = null;
+}
+