Browse Source

支持排名计算

Joey 5 months ago
parent
commit
bf3a845e48
4 changed files with 559 additions and 29 deletions
  1. 120 2
      modules/dataPuller.dos
  2. 279 25
      modules/rankingCalculator.dos
  3. 3 2
      modules/sqlUtilities.dos
  4. 157 0
      modules/task_monthlyPerformance.dos

+ 120 - 2
modules/dataPuller.dos

@@ -425,6 +425,8 @@ def get_index_info(index_ids) {
 /*
  *  取组合有效信息
  * 
+ *  NOTE: portfolio 的 strategy 统一为公募混合基金102, sub_strategy 用 sub_type (哪里维护的?)
+ * 
  *  Example: get_portfolio_info('166002,166114');
  *           get_portfolio_info(NULL);
  *  
@@ -433,9 +435,9 @@ def get_portfolio_info(portfolio_ids) {
 
     s_entity_ids = ids_to_string(portfolio_ids);
     
-    s_entity_sql = iif(s_entity_ids == NULL || s_entity_ids == '', '', " AND cpm.id IN (" + portfolio_ids + ")");
+    s_entity_sql = iif(s_entity_ids == NULL || s_entity_ids == '', '', " AND cpm.id IN (" + s_entity_ids + ")");
 
-    s_query = "SELECT cpm.id AS portfolio_id, cpm.userid, cpm.customer_id, cpm.inception_date, 1 AS ini_value, cpm.portfolio_source, cpm.portfolio_type
+    s_query = "SELECT cpm.id AS portfolio_id, cpm.userid, cpm.customer_id, cpm.inception_date, 1 AS ini_value, cpm.portfolio_source, cpm.portfolio_type, 102 AS strategy, sub_type AS substrategy
                FROM pfdb.`pf_customer_portfolio_map` cpm
                INNER JOIN pfdb.cm_user u ON cpm.userid = u.userid
                WHERE cpm.isvalid = 1
@@ -453,6 +455,37 @@ def get_portfolio_info(portfolio_ids) {
 }
 
 /*
+ *  取组合有效信息
+ * 
+ *  Example: get_cus_fund_info(['CF0000005V','CF000000CE']);
+ *           get_cus_fund_info(NULL);
+ *  
+ */
+def get_cus_fund_info(fund_ids) {
+
+    s_entity_ids = ids_to_string(fund_ids);
+    
+    s_entity_sql = iif(s_entity_ids == NULL || s_entity_ids == '', '', " AND fi.fund_id IN (" + s_entity_ids + ")");
+
+    s_query = "SELECT fi.fund_id, fi.userid, fi.inception_date, IFNULL(fi.primary_benchmark_id, 'IN00000008') AS benchmark_id, 
+                      IFNULL(initial_unit_value, 1) AS ini_value, raise_type, strategy, substrategy
+               FROM pfdb.pf_cus_fund_information fi
+               INNER JOIN pfdb.cm_user u ON fi.userid = u.userid
+               WHERE fi.isvalid = 1
+                 AND u.isvalid = 1" + 
+                 s_entity_sql + "
+               ORDER BY fi.fund_id"
+
+    conn = connect_mysql()
+
+    t = odbc::query(conn, s_query)
+
+    conn.close()
+
+    return t
+}
+
+/*
  *   取基金组合基础有效信息
  * 
  *   Example: get_entity_info('HF', ['HF000004KN','HF000103EU','HF00018WXG']);
@@ -480,6 +513,10 @@ def get_entity_info(entity_type, entity_ids) {
     	t = get_index_info(s_entity_ids);
         t.rename!('index_id', 'entity_id');    
    	
+    } else if(entity_type == 'CF') {
+
+    	t = get_cus_fund_info(s_entity_ids);
+    	t.rename!('fund_id', 'entity_id');
     }
 
 	return t;
@@ -918,6 +955,87 @@ def get_monthly_indicator_data(table_name, end_date, isFromMySQL=true) {
 }
 
 /*
+ *  取 pf_fund_indicator_ranking 表
+ *  
+ *  
+ *  Example: get_fund_indicator_ranking("'MF00003PW1'", 2024.09M, 102, true);
+ *           get_fund_indicator_ranking(NULL, 2023.09M, [1, 3], true);
+ */
+def get_fund_indicator_ranking(fund_ids, end_date, strategy, isFromMySQL=true) {
+
+    t = null;
+
+    s_entity_ids = ids_to_string(fund_ids);
+    sql_entity_id = '';
+    if(s_entity_ids != NULL) sql_entity_id = " AND fund_id IN (" + s_entity_ids + ")";
+
+    s_strategy_ids = ids_to_string(strategy);
+    sql_strategy_id = '';
+    if(s_strategy_ids != NULL) sql_strategy_id = " AND strategy IN (" + s_strategy_ids + ")";
+
+
+    if(isFromMySQL == true) {
+
+        s_query = "SELECT * 
+                   FROM pfdb.pf_fund_indicator_ranking 
+                   WHERE isvalid = 1 
+                     AND end_date = '" + end_date.temporalFormat('yyyy-MM') + "'" +
+                     sql_strategy_id +
+                     sql_entity_id;
+
+        conn = connect_mysql();
+     
+        t = odbc::query(conn, s_query);
+     
+        conn.close();
+    }
+
+    return t;
+
+}
+
+
+/*
+ *  取 pf_fund_indicator_substrategy_ranking 表
+ *  
+ *  
+ *  Example: get_fund_indicator_substrategy_ranking("'MF00003PW1'", 2024.09M, 23, true);
+ *           get_fund_indicator_substrategy_ranking(NULL, 2023.09M, [22, 23], true);
+ */
+def get_fund_indicator_substrategy_ranking(fund_ids, end_date, substrategy, isFromMySQL=true) {
+
+    t = null;
+
+    s_entity_ids = ids_to_string(fund_ids);
+    sql_entity_id = '';
+    if(s_entity_ids != NULL) sql_entity_id = " AND fund_id IN (" + s_entity_ids + ")";
+
+    s_strategy_ids = ids_to_string(substrategy);
+    sql_strategy_id = '';
+    if(s_strategy_ids != NULL) sql_strategy_id = " AND substrategy IN (" + s_strategy_ids + ")";
+
+
+    if(isFromMySQL == true) {
+
+        s_query = "SELECT * 
+                   FROM pfdb.pf_fund_indicator_substrategy_ranking 
+                   WHERE isvalid = 1 
+                     AND end_date = '" + end_date.temporalFormat('yyyy-MM') + "'" +
+                     sql_strategy_id +
+                     sql_entity_id;
+
+        conn = connect_mysql();
+     
+        t = odbc::query(conn, s_query);
+     
+        conn.close();
+    }
+
+    return t;
+
+}
+
+/*
  *  【Morningstar Integration】取某时间后净值更新的公募基金列表
  *
  *  @param entity_ids <STRING|VECTOR>:

+ 279 - 25
modules/rankingCalculator.dos

@@ -5,15 +5,10 @@ use fundit::dataPuller
 use fundit::dataSaver
 
 
-
-def cal_ranking() {
-	
-}
-
 /*
  *   计算收益率排名
  * 
- * 
+ *   TODO: 整合入 gen_ranking_sql
  */
 def cal_ret_ranking(entity_type, entity_info, end_date, isFromMySQL) {
 
@@ -184,6 +179,8 @@ defg perRank(x, is_ASC) {
  *   @param indicator_id <INT>:指标ID
  *   @param is_ASC <BOOL>: 是否排正序
  *   @param ranking_by <STRING>: 'strategy', 'substrategy', 'factor', 'catavg'
+ *   
+ *   TODO: bfi & category
  * 
  */
 def gen_ranking_sql(data_table, indicator_name, indicator_id, is_ASC, ranking_by) {
@@ -344,20 +341,9 @@ def gen_ranking_sql(data_table, indicator_name, indicator_id, is_ASC, ranking_by
 /*
  *  运行排名SQL脚本
  * 
- * 
+ *  NOTE: 没有用 parseExpr 来生成动态脚本的原因是数据表无法传入
  */
 def run_ranking_sql(data_table, indicator_name, indicator_id, is_ASC, mutable v_tables) {
-/*
-    entity_type = 'MF'
-    end_date = 2024.09M
-    isFromMySQL = true;
-    indicator_name = 'alpha'
-    indicator_id = 11
-    is_ASC = false;
-    groupby_field = 'strategy';
-    data_table = t;
-    v_tables = v_ranking_tables
-*/    
 
     tb_strategy_ranking = gen_ranking_sql(data_table, indicator_name, indicator_id, is_ASC, 'strategy')[0];
     v_tables[0].tableInsert(tb_strategy_ranking);
@@ -549,24 +535,292 @@ def cal_other_indicator_ranking(entity_type, entity_info, end_date, isFromMySQL)
     return v_ranking_tables;
 }
 
+
+/*
+ *   将源指标表横表变竖表,以方便排名计算
+ * 
+ * 
+ */
+def transform_data_for_ranking(entity_type, data_table, ranking_by, indicator_info) {
+
+    v_trailing = ['6m', '1y', '2y', '3y', '5y', '10y', 'ytd'];
+
+    // 只有 portfolio_id 是整型,其它的都是字符串
+    is_id_integer = false;
+    if(entity_type == 'PF') is_id_integer = true;
+
+    if(ranking_by == 'strategy') 
+		tb_ranking = create_entity_indicator_ranking(is_id_integer).rename!(ranking_by, 'category_id');
+	else if(ranking_by == 'substrategy')
+		tb_ranking = create_entity_indicator_substrategy_ranking(is_id_integer).rename!(ranking_by, 'category_id');
+	else if(ranking_by == 'factor_id')
+	    tb_ranking = NULL;
+
+    for(indicator in indicator_info) {
+
+        t = sql(select = (sqlCol('entity_id'), sqlCol('end_date'), sqlCol('category_id'),
+                          sqlCol(indicator.name + '_' + v_trailing,, 'indicator_' + v_trailing)
+                         ),
+                from = data_table
+        	   ).eval();
+
+        t.join!(table(take(indicator.id, t.size()) AS indicator_id,
+                      take(double(NULL), t.size()) AS indicator_1m,
+                      take(int(NULL), t.size()) AS absrank_1m,
+                      take(int(NULL), t.size()) AS perrank_1m,
+                      take(double(NULL), t.size()) AS indicator_3m,
+                      take(int(NULL), t.size()) AS absrank_3m,
+                      take(int(NULL), t.size()) AS perrank_3m,
+                      take(int(NULL), t.size()) AS absrank_6m,
+                      take(int(NULL), t.size()) AS perrank_6m,
+                      take(int(NULL), t.size()) AS absrank_1y,
+                      take(int(NULL), t.size()) AS perrank_1y,
+                      take(int(NULL), t.size()) AS absrank_2y,
+                      take(int(NULL), t.size()) AS perrank_2y,
+                      take(int(NULL), t.size()) AS absrank_3y,
+                      take(int(NULL), t.size()) AS perrank_3y,
+                      take(int(NULL), t.size()) AS absrank_5y,
+                      take(int(NULL), t.size()) AS perrank_5y,
+                      take(int(NULL), t.size()) AS absrank_10y,
+                      take(int(NULL), t.size()) AS perrank_10y,
+                      take(int(NULL), t.size()) AS absrank_ytd,
+                      take(int(NULL), t.size()) AS perrank_ytd)
+                );
+
+        INSERT INTO tb_ranking
+            SELECT * FROM (sql(select = sqlCol(tb_ranking.colNames()),
+                               from = t).eval());
+    }
+
+    return tb_ranking;
+}
+
+
+/*
+ *   将源风险指标表横表变竖表,以方便排名计算
+ * 
+ *   TODO: 一直缺 portfolio bfi indicator 计算!mysql 里的 pf_fund_bfi_bm_indicator_ranking 是错的... 
+ */
+def transform_risk_stats_for_ranking (entity_type, entity_info, end_date, ranking_by, isFromMySQL=true) {
+
+	table_desc = get_risk_stats_table_description(entity_type);
+
+	tb_data = get_monthly_indicator_data(table_desc.table_name[0], end_date, isFromMySQL);
+	sec_id_col = table_desc.sec_id_col[0];
+	tb_data.rename!(sec_id_col, 'entity_id');
+
+	data_table = SELECT * FROM entity_info en 
+                 INNER JOIN tb_data d ON en.entity_id = d.entity_id
+	             WHERE en.strategy IS NOT NULL
+
+    data_table.dropColumns!('id');
+    data_table.rename!(ranking_by, 'category_id');
+
+
+    // 目前SQL排名的指标
+    v_indicator_name = ['maxdrawdown', 'kurtosis', 'skewness', 'stddev', 'alpha', 'beta', 'downsidedev', 'maxdrawdown_months', 'maxdrawdown_recoverymonths', 'winrate'];
+    v_indicator_id = [2, 6, 9, 10, 11, 12, 21, 50, 52, 59];
+    v_is_ASC = [true, true, false, true, false, false, true, true, true, false];
+    t_indicator = table(v_indicator_name AS name, v_indicator_id AS id, v_is_ASC AS is_ASC);
+
+    tb_ranking = transform_data_for_ranking(entity_type, data_table, ranking_by, t_indicator).rename!('category_id', ranking_by);
+
+    return tb_ranking;
+}
+
+
+/*
+ *   将源风险调整指标表横表变竖表,以方便排名计算
+ * 
+ * 
+ */
+def transform_risk_adj_ret_stats_for_ranking (entity_type, entity_info, end_date, ranking_by, isFromMySQL=true) {
+
+	table_desc = get_riskadjret_stats_table_description(entity_type);
+
+	tb_data = get_monthly_indicator_data(table_desc.table_name[0], end_date, isFromMySQL);
+	sec_id_col = table_desc.sec_id_col[0];
+	tb_data.rename!(sec_id_col, 'entity_id');
+
+	data_table = SELECT * FROM entity_info en 
+                 INNER JOIN tb_data d ON en.entity_id = d.entity_id
+	             WHERE en.strategy IS NOT NULL
+
+    data_table.dropColumns!('id');
+    data_table.rename!(ranking_by, 'category_id');
+
+    // 目前SQL排名的指标
+    v_indicator_name = ['kapparatio', 'treynorratio', 'jensen', 'omegaratio', 'sharperatio', 'sortinoratio_MAR', 'calmarratio', 'sortinoratio'];
+    v_indicator_id = [14, 15, 16, 17, 18, 19, 40, 58];
+    v_is_ASC = [false, false, false, false, false, false, false, false];
+    t_indicator = table(v_indicator_name AS name, v_indicator_id AS id, v_is_ASC AS is_ASC);
+
+    tb_ranking = transform_data_for_ranking(entity_type, data_table, ranking_by, t_indicator).rename!('category_id', ranking_by);
+
+    return tb_ranking;
+}
+
+
+/*
+ *   将源杂项风险指标表横表变竖表,以方便排名计算
+ * 
+ * 
+ */
+def transform_other_indicator_for_ranking (entity_type, entity_info, end_date, ranking_by, isFromMySQL=true) {
+
+	table_desc = get_indicator_table_description(entity_type);
+
+	tb_data = get_monthly_indicator_data(table_desc.table_name[0], end_date, isFromMySQL);
+	sec_id_col = table_desc.sec_id_col[0];
+	tb_data.rename!(sec_id_col, 'entity_id');
+
+	data_table = SELECT * FROM entity_info en 
+                 INNER JOIN tb_data d ON en.entity_id = d.entity_id
+	             WHERE en.strategy IS NOT NULL
+
+    data_table.dropColumns!('id');
+    data_table.rename!(ranking_by, 'category_id');
+
+    // 目前SQL排名的指标
+    v_indicator_name = ['per_con', 'info_ratio', 'var', 'cvar', 'smddvar', 'smddcvar', 'smdd_lpm1', 'smdd_lpm2', 'smdd_downside_dev', 'tracking_error', 'm2'];
+    v_indicator_id = [37, 38, 41, 42, 43, 44, 45, 46, 47, 48, 49];
+    v_is_ASC = [false, false, true, true, true, true, true, true, true, true, false];
+    t_indicator = table(v_indicator_name AS name, v_indicator_id AS id, v_is_ASC AS is_ASC);
+
+    tb_ranking = transform_data_for_ranking(entity_type, data_table, ranking_by, t_indicator).rename!('category_id', ranking_by);
+
+    return tb_ranking;
+}
+
+/*
+ * 
+ *   参考某指定类排名,计算相对排名
+ *   
+ *   @param benchmark_ranking <TABLE>: 被参考的排名表,如公募混合基金
+ *   @param entity_ranking <TABLE>: 被计算的指标表,排名被填充在原表中
+ *   @param isFromMySQL <BOOL>
+ *   
+ * 
+ *   Example: cal_relative_ranking(get_fund_indicator_ranking(NULL, 2024.09M, 102, true), 
+ *                                 transform_risk_stats_for_ranking('PF', get_entity_info('PF', NULL), 2024.09M, true),
+ *                                 true);
+ */
+def cal_relative_ranking(benchmark_ranking, mutable entity_ranking, isFromMySQL=true) {
+
+    v_trailing = ['1m', '3m', '6m', '1y', '2y', '3y', '5y', '10y', 'ytd'];
+
+    for(tr in v_trailing) {
+
+        indicator_val_col = 'indicator_' + tr;
+        
+        // 乘上100,000 是为了满足 window join 的字段必须是INT或DURATION
+        tb_tmp = sql(select = (sqlCol(['entity_id', 'end_date', 'category_id', 'indicator_id']),
+                               sqlColAlias(makeCall(round, binaryExpr(sqlCol(indicator_val_col), 1000000, *), 0), indicator_val_col + '_int')),
+                     from = entity_ranking,
+                     where = < _$indicator_val_col is not null >,
+                     orderBy = sqlCol(['end_date', 'category_id', 'indicator_id', indicator_val_col])
+                    ).eval();
+                     
+        tb_tmp2 = sql(select = (sqlCol(['end_date', 'category_id', 'indicator_id']),
+                                sqlColAlias(makeCall(round, binaryExpr(sqlCol(indicator_val_col), 1000000, *), 0), indicator_val_col + '_int'),
+                                sqlCol('absrank_' + tr), sqlCol('perrank_' + tr)
+                                ),
+                     from = benchmark_ranking,
+                     where = < _$indicator_val_col is not null >,
+                     orderBy = sqlCol(['end_date', 'category_id', 'indicator_id', indicator_val_col])
+                    ).eval();
+
+        absrank_col = 'absrank_' + tr;
+        perrank_col = 'perrank_' + tr;
+        // 用 pwj 来找最接近的排名
+        tb_tmp_ranking = sql(select = (sqlCol(['entity_id', 'end_date', 'category_id', 'indicator_id']),
+                                       sqlCol(indicator_val_col + '_int'),
+                                       sqlCol(['absrank_max', 'perrank_max'])),
+                             from = pwj(tb_tmp, tb_tmp2,
+                                        window = 0:1, 
+                                        aggs = [<max(_$absrank_col) as 'absrank_max'>, <max(_$perrank_col) as 'perrank_max'>],
+                                        matchingCols = ['end_date', 'category_id', 'indicator_id', indicator_val_col + '_int'])
+        	                ).eval();
+
+        // 计算的结果填入排名表
+        sqlUpdate(table = entity_ranking,
+        	      updates = [<absrank_max as _$absrank_col>, <perrank_max as _$perrank_col>],
+        	      from = <ej(entity_ranking, tb_tmp_ranking, ['entity_id', 'end_date', 'category_id','indicator_id'])>
+        	     ).eval();
+
+    }
+
+}
+
+
 /*
  *  排名数据入库
  * 
  *  @param ranking_tables <VECTOR>: 包含4个数据表的向量,分别是一级策略排名,一级策略排名阈值,二级策略排名,二级策略排名阈值
  */
-def save_ranking_tables(ranking_tables) {
+def save_ranking_tables(entity_type, ranking_tables) {
 
     if(ranking_tables.isVoid()) return;
 
-    ranking_tables[0].rename!('entity_id', 'fund_id');
-    save_and_sync(ranking_tables[0], 'raw_db.pf_fund_indicator_ranking', 'raw_db.pf_fund_indicator_ranking');
+    source_table = '';
+    target_table = '';
+
+    if(entity_type IN ['MF', 'HF']) {
+
+        entity_id_col = 'fund_id';
+    	source_table = 'raw_db.pf_fund_indicator_ranking';
+    	target_table = 'raw_db.pf_fund_indicator_ranking'
+    	
+    }
 
-    save_and_sync(ranking_tables[1], 'raw_db.pf_fund_indicator_ranking_num', 'raw_db.pf_fund_indicator_ranking_num');
+    ranking_tables[0].rename!('entity_id', entity_id_col);
+    save_and_sync(ranking_tables[0], source_table, target_table);
+
+    save_and_sync(ranking_tables[1], source_table + '_num', target_table + '_num');
     
-    ranking_tables[2].rename!('entity_id', 'fund_id');
-    save_and_sync(ranking_tables[2], 'raw_db.pf_fund_indicator_substrategy_ranking', 'raw_db.pf_fund_indicator_substrategy_ranking');
+    ranking_tables[2].rename!('entity_id', entity_id_col);
+    save_and_sync(ranking_tables[2], source_table.replace!('_ranking', '_substrategy_ranking'), target_table.replace!('_ranking', '_substrategy_ranking'));
+
+    save_and_sync(ranking_tables[3], source_table + '_num', target_table + '_num');
+	
+}
+
+/*
+ *  参考排名数据入库
+ * 
+ *  @param ranking_tables <TABLE>: 
+ */
+def save_relative_ranking_table(entity_type, ranking_table, ranking_by) {
+
+    if(ranking_table.isVoid()) return;
+
+    source_table = '';
+    target_table = '';
+
+    if(entity_type == 'PF') {
+
+        entity_id_col = 'portfolio_id';
+
+        if(ranking_by == 'strategy') {
+	    	source_table = 'raw_db.pf_portfolio_indicator_ranking';
+    		target_table = 'raw_db.pf_portfolio_indicator_ranking';
+        } else if(ranking_by == 'substrategy') {
+	    	source_table = 'raw_db.pf_portfolio_indicator_substrategy_ranking';
+    		target_table = 'raw_db.pf_portfolio_indicator_substrategy_ranking';
+        } else if(ranking_by == 'factor_id') {
+	    	source_table = 'raw_db.pf_portfolio_bfi_bm_indicator_ranking';
+    		target_table = 'raw_db.pf_portfolio_bfi_bm_indicator_ranking';
+        }
+    	
+    } else if(entity_type == 'CF') {
+
+        entity_id_col = 'fund_id';
+    	source_table = 'raw_db.pf_cus_fund_indicator_ranking';
+    	target_table = 'raw_db.pf_cus_fund_indicator_ranking'
+    	
+    }
 
-    save_and_sync(ranking_tables[3], 'raw_db.pf_fund_indicator_substrategy_ranking_num', 'raw_db.pf_fund_indicator_substrategy_ranking_num');
+    save_and_sync(ranking_table, source_table, target_table);
 	
 }
 

+ 3 - 2
modules/sqlUtilities.dos

@@ -55,7 +55,8 @@ def load_table_from_local(server_name, table_name) {
  *   Example: ids_to_string("'a','b','c'");
  *            ids_to_string(['a', NULL, 'c']);
  *            ids_to_string([1,2,3]);
- *            ids_to_string('').isNull()
+ *            ids_to_string(12);
+ *            ids_to_string('').isNull();
  */
 def ids_to_string(ids) {
 
@@ -65,7 +66,7 @@ def ids_to_string(ids) {
 
     // 输入的 ids 是字符串标量
     if (ids.form() == 0) {
-        s_ids = ids.trim();
+        s_ids = (ids$STRING).trim();
     // 输入的 ids 是字符串向量
     } else if(ids.form() == 1) {
       if(ids.type() == 4) // INTEGER

+ 157 - 0
modules/task_monthlyPerformance.dos

@@ -0,0 +1,157 @@
+module fundit::task_monthlyPerformance
+
+use fundit::dataPuller;
+use fundit::dataSaver;
+use fundit::bfiMatcher;
+use fundit::rankingCalculator;
+
+/*
+ *   计算基金排名并存入数据库
+ * 
+ *   @param entity_type <STRING>: 目前传入'MF'和'HF'都可以将公私募同时跑
+ *   @param end_date <MONTH>: XXXX.YYM
+ *   @param isFromMySQL <BOOL>: false 时读取dolphin本地的收益及指标表,用于初始化数据
+ *   
+ *   NOTE: 在假设公募和私募分类互相独立成立的前提下,之用跑一次即可计算公募和私募的排名
+ *   TODO: BFI ranking
+ * 
+ *   Example: cal_entity_ranking('MF', 2024.09M, true);
+ */
+def CalEntityRanking(entity_type, end_date, isFromMySQL=true) {
+/*
+entity_type = 'MF';
+end_date = 2024.09M;
+isFromMySQL = true;
+*/
+	entity_info = get_entity_info(entity_type, NULL);
+
+	// 收益
+	v_ranking_tables = cal_ret_ranking(entity_type, entity_info, end_date, isFromMySQL);
+    save_ranking_tables(entity_type, v_ranking_tables);
+
+    // 风险相关的指标
+    v_ranking_tables = cal_risk_ranking(entity_type, entity_info, end_date, isFromMySQL);
+    save_ranking_tables(entity_type, v_ranking_tables);
+
+    // 风险调整收益指标
+    v_ranking_tables = cal_risk_adj_return_ranking(entity_type, entity_info, end_date, isFromMySQL);
+    save_ranking_tables(entity_type, v_ranking_tables);
+
+    // 杂项指标
+    v_ranking_tables = cal_other_indicator_ranking(entity_type, entity_info, end_date, isFromMySQL);
+    save_ranking_tables(entity_type, v_ranking_tables);
+
+/*
+    v_tables = NULL;
+
+    if(entity_type IN ['MF', 'HF', 'MI', 'FI']) {
+    	
+    	v_tables = ['mfdb.fund_performance', 'mfdb.fund_risk_stats', 'mfdb.fund_riskadjret_stats', 'mfdb.fund_indicator', 'mfdb.fund_style_stats'];
+    	
+    } else if(entity_type = 'PF') {
+
+    	v_tables = ['pfdb.pf_portfolio_performance', 'pfdb.pf_portfolio_risk_stats', 'pfdb.pf_portfolio_riskadjret_stats', 'pfdb.pf_portfolio_indicator', 'pfdb.pf_portfolio_style_stats'];
+
+    } else if(entity_type = 'CF') {
+
+    	v_tables = ['pfdb.pf_cus_fund_performance', 'pfdb.pf_cus_fund_risk_stats', 'pfdb.pf_cus_fund_riskadjret_stats', 'pfdb.pf_cus_fund_indicator', 'pfdb.pf_cus_fund_style_stats'];
+
+    } else if(entity_type = 'FA') {
+    	
+    	v_tables = ['pfdb.cm_factor_performance', 'pfdb.cm_factor_risk_stats', 'pfdb.cm_factor_riskadjret_stats', 'pfdb.cm_factor_indicator', 'pfdb.cm_factor_style_stats'];
+
+    } else if(entity_type = 'CI') {
+    	
+    	v_tables = ['pfdb.cm_udf_index_performance', 'pfdb.cm_udf_index_risk_stats', 'pfdb.cm_udf_index_riskadjret_stats', 'pfdb.cm_udf_index_indicator', 'pfdb.cm_udf_index_style_stats'];
+    }
+
+    if(v_tables.isNull()) return null;
+    
+	for(table in v_tables) {
+		
+        tb_data = get_monthly_indicator_data(table, end_date, isFromMySQL);
+        
+	}
+*/
+}
+
+/*
+ * 
+ *   以公募基金为评级参考,计算组合、私有基金收益及指标排名
+ * 
+ * 
+ *   Example: CalRelativeRanking('PF', 2024.09M, true);
+ */
+def CalRelativeRanking(entity_type, end_date, isFromMySQL=true) {
+
+	entity_info = get_entity_info(entity_type, NULL);
+
+	if(entity_type == 'PF')
+	    entity_info = SELECT * FROM entity_info WHERE portfolio_type IN (1, 2) // 1: 用户组合、2:客户真实组合,忽略客户推荐组合、总览综合等虚拟组合
+
+    v_ranking_by = ['strategy', 'substrategy'/*, 'factor_id'*/];
+
+    // 暂时以公募混合基金为排名参考
+    for(ranking_by in v_ranking_by) {
+
+    	if(ranking_by == 'strategy') {
+        
+        	v_category = EXEC DISTINCT strategy FROM entity_info WHERE strategy IS NOT NULL;
+	        tb_fund_ranking = get_fund_indicator_ranking(NULL, end_date, v_category, true)
+        
+    	} else if(ranking_by == 'substrategy') {
+
+   			v_category = EXEC DISTINCT substrategy FROM entity_info WHERE substrategy IS NOT NULL;
+	    	tb_fund_ranking = get_fund_indicator_substrategy_ranking(NULL, end_date, v_category, true)
+    	
+    	} else if(ranking_by == 'bfi') {
+
+    		tb_fund_ranking = null;
+	    }
+
+    	if(tb_fund_ranking == null) return;
+
+	    tb_fund_ranking.rename!(ranking_by, 'category_id');
+
+    	// 收益
+	    v_indicator_id = [1];
+    	benchmark_ranking = SELECT * FROM tb_fund_ranking WHERE indicator_id IN v_indicator_id;
+
+    
+	    // 风险指标
+    	v_indicator_id = [2, 6, 9, 10, 11, 12, 21, 50, 52, 59];
+	    benchmark_ranking = SELECT * FROM tb_fund_ranking WHERE indicator_id IN v_indicator_id;
+    
+	    entity_ranking = transform_risk_stats_for_ranking(entity_type, entity_info, end_date, ranking_by, isFromMySQL).rename!(ranking_by, 'category_id');
+
+	    cal_relative_ranking(benchmark_ranking, entity_ranking, isFromMySQL);
+
+	    entity_ranking.rename!('category_id', ranking_by);
+	    save_relative_ranking_table(entity_type, entity_ranking, ranking_by);
+
+
+    	// 风险调整收益指标
+	    v_indicator_id = [14, 15, 16, 17, 18, 19, 40, 58];
+    	benchmark_ranking = SELECT * FROM tb_fund_ranking WHERE indicator_id IN v_indicator_id;
+    
+	    entity_ranking = transform_risk_adj_ret_stats_for_ranking(entity_type, entity_info, end_date, ranking_by, isFromMySQL).rename!(ranking_by, 'category_id');
+
+    	cal_relative_ranking(benchmark_ranking, entity_ranking, isFromMySQL);
+
+	    entity_ranking.rename!('category_id', ranking_by);
+	    save_relative_ranking_table(entity_type, entity_ranking, ranking_by);    
+
+	    // 其它风险指标
+    	v_indicator_id = [37, 38, 41, 42, 43, 44, 45, 46, 47, 48, 49];
+	    benchmark_ranking = SELECT * FROM tb_fund_ranking WHERE indicator_id IN v_indicator_id;
+
+    	entity_ranking = transform_other_indicator_for_ranking(entity_type, entity_info, end_date, ranking_by, isFromMySQL).rename!(ranking_by, 'category_id');
+
+	    cal_relative_ranking(benchmark_ranking, entity_ranking, isFromMySQL);
+
+	    entity_ranking.rename!('category_id', ranking_by);
+	    save_relative_ranking_table(entity_type, entity_ranking, ranking_by);
+    }
+//    SELECT * FROM entity_ranking WHERE entity_id = 143109
+	
+}