浏览代码

支持bfi指标计算

Joey 1 周之前
父节点
当前提交
26ca9049e7
共有 1 个文件被更改,包括 225 次插入6 次删除
  1. 225 6
      modules/indicatorCalculator.dos

+ 225 - 6
modules/indicatorCalculator.dos

@@ -41,6 +41,8 @@ def get_benchmark_return(benchmarks, end_day) {
     s_index_ids = '';
     s_factor_ids = '';
 
+    if(benchmarks.isVoid() || benchmarks.size() == 0) { return null; }
+
     // 前缀为 IN 的 benchmark id
     t_index_id = SELECT DISTINCT benchmark_id FROM benchmarks WHERE benchmark_id LIKE 'IN%';
     s_index_ids = iif(isVoid(t_index_id), "", "'" + t_index_id.benchmark_id.concat("','") + "'");
@@ -191,7 +193,7 @@ def cal_benchmark_tracking(ret, benchmarks, bmk_ret) {
      t = SELECT entity_id, end_date.max() AS end_date, price_date.max() AS price_date, price_date.min() AS min_date, benchmark_id,
                 exc_ret.bucketCount(0:999, 1) \ cnt[0] AS winrate,
                 exc_ret.std() AS track_error, 
-                exc_ret.mean() / exc_ret.std() AS info
+                iif(exc_ret.std() == 0, null, exc_ret.mean() / exc_ret.std()) AS info
          FROM t0 GROUP BY entity_id, benchmark_id;
 
      return t;
@@ -401,9 +403,8 @@ def cal_indicators_with_benchmark(mutable ret, benchmarks, index_ret, risk_free)
  *   Monthly standard indicator calculation
  *   @param: ret <TABLE>: 收益表,NEED COLUMNS entity_id, price_dat, end_date, nav 
  *   @param benchmarks <TABLE>: entity-benchmark mapping table
- *   @param index_ret <TABLE>: historical benchmark return table, NEED COLUMNS fund_id, end_date, ret
+ *   @param benchmark_ret <TABLE>: historical benchmark return table, NEED COLUMNS fund_id, end_date, ret
  *   @param risk_free <TABLE>: historical risk free rate table, NEED COLUMNS fund_id, end_date, ret
- *   @param freq <CHAR>: 数据频率,d, w, m, q, s, a
  *   
  *   @return: indicators table
  *   
@@ -457,13 +458,34 @@ def cal_indicators(mutable ret, benchmarks, benchmark_ret, risk_free) {
 
 
 /*
+ *   Monthly BFI indicator calculation
+ *   @param: ret <TABLE>: 收益表,NEED COLUMNS entity_id, price_dat, end_date, nav 
+ *   @param benchmarks <TABLE>: entity-benchmark mapping table
+ *   @param benchmark_ret <TABLE>: historical benchmark return table, NEED COLUMNS fund_id, end_date, ret
+ *   @param risk_free <TABLE>: historical risk free rate table, NEED COLUMNS fund_id, end_date, ret
+ *   
+ *   @return: BFI indicators table
+ *   
+ *   
+ *   Create  20240914                                                                 Joey
+ *
+ */
+def cal_bfi_indicators(mutable ret, benchmarks, benchmark_ret, risk_free) {
+
+    // 需要基准的指标们
+    r = cal_indicators_with_benchmark(ret, benchmarks, benchmark_ret, risk_free);
+    
+    return r;
+}
+
+/*
  *   Calculate trailing 6m, ytd, 1y, 2y, 3y, 4y, 5y, 10y and since inception indicators
  *   
  *   @param: entity_info <TABLE>: basic information of entity, NEED COLUMNS entity_id, inception_date
  *   @param benchmarks <TABLE>: entity-benchmark mapping table
  *   @param: ret <TABLE>: 收益表,NEED COLUMNS entity_id, price_dat, end_date, nav 
  *   @param: end_day <DATE>: 计算截止日期
- *   @param index_ret <TABLE>: historical benchmark return table, NEED COLUMNS fund_id, end_date, ret
+ *   @param bmk_ret <TABLE>: historical benchmark return table, NEED COLUMNS fund_id, end_date, ret
  *   @param risk_free <TABLE>: historical risk free rate table, NEED COLUMNS fund_id, end_date, ret
  * 
  */
@@ -548,6 +570,96 @@ def cal_trailing_indicators(entity_info, benchmarks, mutable tb_ret, end_day, bm
     return r_incep, r_ytd, r_6m, r_1y, r_2y, r_3y, r_4y, r_5y, r_10y, r_ms_3y, r_ms_5y, r_ms_10y;
 }
 
+
+
+/*
+ *   Calculate trailing 6m, ytd, 1y, 2y, 3y, 4y, 5y, 10y and since inception BFI indicators
+ *   
+ *   @param: entity_info <TABLE>: basic information of entity, NEED COLUMNS entity_id, inception_date
+ *   @param benchmarks <TABLE>: entity-benchmark mapping table
+ *   @param: ret <TABLE>: 收益表,NEED COLUMNS entity_id, price_dat, end_date, nav 
+ *   @param: end_day <DATE>: 计算截止日期
+ *   @param bmk_ret <TABLE>: historical benchmark return table, NEED COLUMNS fund_id, end_date, ret
+ *   @param risk_free <TABLE>: historical risk free rate table, NEED COLUMNS fund_id, end_date, ret
+ *   
+ *   TODO: integrated with cal_trailing_indicators by "function pointer"
+ * 
+ */
+def cal_trailing_bfi_indicators(entity_info, benchmarks, mutable tb_ret, end_day, bmk_ret, risk_free_rate) {
+
+    r_incep = null;
+    r_ytd = null;
+    r_6m = null;
+    r_1y = null;
+    r_2y = null;
+    r_3y = null;
+    r_4y = null;
+    r_5y = null;
+    r_10y = null;
+
+    // since inception
+    if(tb_ret.size() > 0) {
+        r_incep = cal_bfi_indicators(tb_ret, benchmarks, bmk_ret, risk_free_rate);
+    }
+
+    // ytd
+    tb_ret_ytd = SELECT * FROM tb_ret WHERE end_date >= end_day.yearBegin().month();
+    if(tb_ret_ytd.size() > 0) {
+        r_ytd = cal_bfi_indicators(tb_ret_ytd, benchmarks, bmk_ret, risk_free_rate);
+    }
+
+    // trailing 6m
+    tb_ret_6m = SELECT * FROM tb_ret r INNER JOIN entity_info ei ON r.entity_id = ei.entity_id
+                WHERE r.end_date > end_day.month()-6 AND (end_day.month() - ei.inception_date.month()) >= 6;
+    if(tb_ret_6m.size() > 0) {
+        r_6m = cal_bfi_indicators(tb_ret_6m, benchmarks, bmk_ret, risk_free_rate);
+    }
+    
+    // trailing 1y
+    tb_ret_1y = SELECT * FROM tb_ret r INNER JOIN entity_info ei ON r.entity_id = ei.entity_id
+                WHERE r.end_date > end_day.month()-12 AND (end_day.month() - ei.inception_date.month()) >= 12;
+    if(tb_ret_1y.size() > 0) {
+        r_1y = cal_bfi_indicators(tb_ret_1y, benchmarks, bmk_ret, risk_free_rate);
+    }
+    
+    // trailing 2y
+    tb_ret_2y = SELECT * FROM tb_ret r INNER JOIN entity_info ei ON r.entity_id = ei.entity_id
+                WHERE r.end_date > end_day.month()-24 AND (end_day.month() - ei.inception_date.month()) >= 24;
+    if(tb_ret_2y.size() > 0) {
+        r_2y = cal_bfi_indicators(tb_ret_2y, benchmarks, bmk_ret, risk_free_rate);
+    }
+    
+    // trailing 3y
+    tb_ret_3y = SELECT * FROM tb_ret r INNER JOIN entity_info ei ON r.entity_id = ei.entity_id
+                WHERE r.end_date > end_day.month()-36 AND (end_day.month() - ei.inception_date.month()) >= 36;
+    if(tb_ret_3y.size() > 0) {
+        r_3y = cal_bfi_indicators(tb_ret_3y, benchmarks, bmk_ret, risk_free_rate);
+    }
+
+    // trailing 4y
+    tb_ret_4y = SELECT * FROM tb_ret r INNER JOIN entity_info ei ON r.entity_id = ei.entity_id
+                WHERE r.end_date > end_day.month()-48 AND (end_day.month() - ei.inception_date.month()) >= 48;
+    if(tb_ret_4y.size() > 0) {
+        r_4y = cal_bfi_indicators(tb_ret_4y, benchmarks, bmk_ret, risk_free_rate);
+    }
+    
+    // trailing 5y
+    tb_ret_5y = SELECT * FROM tb_ret r INNER JOIN entity_info ei ON r.entity_id = ei.entity_id
+                WHERE r.end_date > end_day.month()-60 AND (end_day.month() - ei.inception_date.month()) >= 60;
+    if(tb_ret_5y.size() > 0) {
+        r_5y = cal_bfi_indicators(tb_ret_5y, benchmarks, bmk_ret, risk_free_rate);
+    }
+
+    // trailing 10y
+    tb_ret_10y = SELECT * FROM tb_ret r INNER JOIN entity_info ei ON r.entity_id = ei.entity_id
+                WHERE r.end_date > end_day.month()-120 AND (end_day.month() - ei.inception_date.month()) >= 120;
+    if(tb_ret_10y.size() > 0) {
+        r_10y = cal_bfi_indicators(tb_ret_10y, benchmarks, bmk_ret, risk_free_rate);
+    }
+
+    return r_incep, r_ytd, r_6m, r_1y, r_2y, r_3y, r_4y, r_5y, r_10y;
+}
+
 /*
  *   Calculate fund indicators for one date
  * 
@@ -567,6 +679,9 @@ def cal_fund_indicators(entity_type, fund_ids, end_day, isFromNav) {
     very_old_date = 1990.01.01;
 
     fund_info = get_fund_info(fund_ids);
+    
+    if(fund_info.isVoid() || fund_info.size() == 0) { return null };
+    
     fund_info.rename!('fund_id', 'entity_id');
 
     if(isFromNav == true) {
@@ -591,6 +706,53 @@ def cal_fund_indicators(entity_type, fund_ids, end_day, isFromNav) {
 }
 
 /*
+ *   Calculate fund BFI indicators for one date
+ * 
+ *   @param entity_type <STRING>: MF, HF
+ *   @param fund_ids <STRING>: 逗号和单引号分隔的fund_id
+ *   @param end_day <DATE>: 要计算的日期
+ *   @param isFromNav <BOOL>: 用净值实时计算还是从表中取月收益
+ *   @param isFromSQL <BOOL>: TODO: 从MySQL还是本地DolphinDB取净值/收益数据
+ *   
+ *   TODO: primary_benchmark_id seems not be used as benchmark, when it is FA00000VNB
+ *   TODO: intergrate with cal_fund_indicators
+ * 
+ *   Example: cal_fund_bfi_indicators('MF', "'MF00003PW2', 'MF00003PW1', 'MF00003PXO'", 2024.08.31, true);
+ * 
+ */
+def cal_fund_bfi_indicators(entity_type, fund_ids, end_day, isFromNav) {
+
+    very_old_date = 1990.01.01;
+
+    fund_info = get_fund_info(fund_ids);
+    
+    if(fund_info.isVoid() || fund_info.size() == 0) { return null };
+    
+    fund_info.rename!('fund_id', 'entity_id');
+
+    if(isFromNav == true) {
+        // 从净值开始计算收益
+        tb_ret = SELECT * FROM cal_fund_monthly_returns(entity_type, fund_ids, true) WHERE price_date <= end_day;
+        tb_ret.rename!(['fund_id', 'cumulative_nav'], ['entity_id', 'nav']);
+    } else {
+        // 从fund_performance表里读月收益
+        tb_ret = get_monthly_ret('FD', fund_ids, very_old_date, end_day, true);
+        tb_ret.rename!(['fund_id'], ['entity_id']);
+    }
+
+    // 取基金和基准的对照表
+    bfi_benchmark = SELECT fund_id AS entity_id, factor_id AS benchmark_id FROM get_fund_bfi_factors(fund_ids, end_day.temporalFormat('yyyy-MM'));
+
+    if(bfi_benchmark.isVoid() || bfi_benchmark.size() == 0) { return null; }
+
+    bmk_ret = get_benchmark_return(bfi_benchmark, end_day);
+
+    risk_free_rate = SELECT fund_id, temporalParse(end_date, 'yyyy-MM') AS end_date, ret FROM get_risk_free_rate(very_old_date, end_day);
+
+    return cal_trailing_bfi_indicators(fund_info, bfi_benchmark, tb_ret, end_day, bmk_ret, risk_free_rate);
+}
+
+/*
  *  Calculate portfolio indicators for one date
  *  
  *  @param portfolio_ids <STRING>: comma-delimited portfolio ids
@@ -606,6 +768,9 @@ def cal_portfolio_indicators(portfolio_ids, end_day, cal_method, isFromNav) {
     very_old_date = 1990.01.01;
 
     portfolio_info = get_portfolio_info(portfolio_ids);
+
+    if(portfolio_info.isVoid() || portfolio_info.size() == 0) { return null };
+    
     portfolio_info.rename!('portfolio_id', 'entity_id');
 
     if(isFromNav == true) {
@@ -626,11 +791,65 @@ def cal_portfolio_indicators(portfolio_ids, end_day, cal_method, isFromNav) {
     }
 
     // 沪深300做基准,同SQL保持一致
-    bmk_ret = SELECT fund_id AS benchmark_id, 'PBI' AS benchmark_type, temporalParse(end_date, 'yyyy-MM') AS end_date, ret FROM get_monthly_ret('IX', "'IN00000008'", very_old_date, end_day, true); 
+    primary_benchmark = SELECT entity_id, 'IN00000008' AS benchmark_id FROM portfolio_info;
+
+    // 取所有出现的基准月收益
+    bmk_ret = get_benchmark_return(primary_benchmark, end_day);
 
     risk_free_rate = SELECT fund_id, temporalParse(end_date, 'yyyy-MM') AS end_date, ret FROM get_risk_free_rate(very_old_date, end_day);
 
-    return cal_all_trailing_indicators(portfolio_info, tb_ret, end_day, bmk_ret, risk_free_rate, 'm');
+    return cal_trailing_indicators(portfolio_info, primary_benchmark, tb_ret, end_day, bmk_ret, risk_free_rate);
+}
+
 
+/*
+ *  Calculate portfolio bfi indicators for one date
+ *  
+ *  @param portfolio_ids <STRING>: comma-delimited portfolio ids
+ *  @param end_day <DATE>: the date
+ *  @param cal_method <INT>: calculate based on cumulative nav (1) or nav (2)
+ *  @param isFromNav <BOOL>: calculate returns from NAV on-the-fly (true) or get from monthly return table (false)
+ *  
+ *  TODO: intergrate with cal_portfolio_indicators
+ *   
+ *  Example: cal_portfolio_bfi_indicators('166002,166114', 2024.08.31, 1, true);
+ * 
+ */
+def cal_portfolio_bfi_indicators(portfolio_ids, end_day, cal_method, isFromNav) {
+
+    very_old_date = 1990.01.01;
+
+    portfolio_info = get_portfolio_info(portfolio_ids);
+
+    if(portfolio_info.isVoid() || portfolio_info.size() == 0) { return null };
     
+    portfolio_info.rename!('portfolio_id', 'entity_id');
+
+    if(isFromNav == true) {
+        // 从净值开始计算收益
+        tb_raw_ret = SELECT * FROM cal_portfolio_nav(portfolio_ids, very_old_date, cal_method) WHERE price_date <= end_day;
+       
+        // funky thing is you can't use "AS" for the grouping columns?
+        tb_ret = SELECT portfolio_id, price_date.month(), price_date.last() AS price_date, (1+ret).prod()-1 AS ret, nav.last() AS nav
+                 FROM tb_raw_ret
+                 WHERE price_date <= end_day
+                 GROUP BY portfolio_id, price_date.month();
+        tb_ret.rename!(['portfolio_id', 'month_price_date'], ['entity_id', 'end_date']);
+
+    } else {
+        // 从pf_portfolio_performance表里读月收益
+        tb_ret = get_monthly_ret('PF', portfolio_ids, very_old_date, end_day, true);
+        tb_ret.rename!(['portfolio_id'], ['entity_id']);
+    }
+
+    // 取组合和基准的对照表
+    bfi_benchmark = SELECT portfolio_id AS entity_id, factor_id AS benchmark_id FROM get_portfolio_bfi_factors(portfolio_ids, end_day.temporalFormat('yyyy-MM'));
+
+    if(bfi_benchmark.isVoid() || bfi_benchmark.size() == 0) { return null; }
+
+    bmk_ret = get_benchmark_return(bfi_benchmark, end_day);
+
+    risk_free_rate = SELECT fund_id, temporalParse(end_date, 'yyyy-MM') AS end_date, ret FROM get_risk_free_rate(very_old_date, end_day);
+
+    return cal_trailing_bfi_indicators(portfolio_info, bfi_benchmark, tb_ret, end_day, bmk_ret, risk_free_rate);
 }