bfiMatcher.dos 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. module fundit::bfiMatcher
  2. use fundit::sqlUtilities;
  3. use fundit::operationDataPuller;
  4. use fundit::performanceDataPuller;
  5. /*
  6. * 返回预设的指标最小值
  7. *
  8. */
  9. def get_min_threshold(data_name) {
  10. ret = 0;
  11. if(data_name == 'correlation') { ret = 0.64; }
  12. else if(data_name == 'ret_count') { ret = 48; }
  13. return ret;
  14. }
  15. /*
  16. * 取BFI所需要的指数/因子ID
  17. *
  18. * NOTE: Java使用的逻辑如下(除了FA),暂时没有差别
  19. * SELECT a.fund_Id FROM mfdb.fund_performance AS a
  20. LEFT JOIN pfdb.cm_class_asset_index AS b ON a.fund_id = b.index_id
  21. WHERE a.end_date = 'YYYY-MM' AND a.fund_id LIKE 'IN%' AND b.isvalid = 1 ORDER BY a.fund_id ASC
  22. */
  23. def get_bfi_index_list() {
  24. return ['FA00000VML','FA00000VMM','FA00000VMN','FA00000VMO','FA00000WKG','FA00000WKH','IN00000008','IN0000000D','IN0000000M','IN0000000S',
  25. 'IN0000000T','IN0000000U','IN0000000V','IN0000000W','IN0000000X','IN0000000Y','IN0000000Z','IN00000010','IN00000011','IN00000012',
  26. 'IN00000013','IN00000014','IN00000015','IN00000016','IN00000017','IN00000077','IN00000078','IN00000079','IN0000007A','IN0000007B',
  27. 'IN0000007C','IN0000007D','IN0000007E','IN0000007F','IN0000007G','IN0000007M','IN0000007N','IN0000007O','IN00000080','IN00000088',
  28. 'IN0000008O','IN0000009M','IN0000028E','IN000002CM'];
  29. }
  30. defg regressionT(y, x) {
  31. r = SELECT beta, tstat FROM ols(y, x, true, 1) WHERE rowNo(beta) = 1;
  32. return r[0]['tstat'];
  33. }
  34. /*
  35. * 计算 bfi-matching 所需要的数据指标(月度)
  36. *
  37. *
  38. */
  39. def cal_monthly_closity(ret1, ret2, win) {
  40. t0 = SELECT end_date.month() AS end_date, end_date as price_date, ret1.ret AS ret1, ret2.ret AS ret2, tmoving(count, end_date, end_date, win) AS ret_count
  41. FROM ret1
  42. INNER JOIN ret2 ON ret1.end_date = ret2.end_date
  43. ORDER BY end_date;
  44. t = SELECT end_date, price_date,
  45. tmcorr(t0.end_date, ret1, ret2, win) AS corr,
  46. iif(tmstd(end_date, ret1-ret2, win) == 0, null, tmavg(end_date, ret1-ret2, win)\tmstd(end_date, ret1-ret2, win)) AS info,
  47. tmoving(regressionT, end_date, [ret1, ret2], win) AS t_value,
  48. tmbeta(end_date, ret1, ret2, win) AS beta // 用 ols() 算的值和这个一样
  49. FROM t0
  50. WHERE ret_count >= get_min_threshold('ret_count')
  51. ORDER BY end_date;
  52. UPDATE t SET corr = NULL WHERE corr < get_min_threshold('correlation');
  53. return SELECT end_date.month().last() AS end_date,
  54. corr.last() AS corr,
  55. info.last() * sqrt(get_annulization_multiple('w')) AS info, // annuulized info ratio
  56. t_value.last() AS t_value,
  57. beta.last() AS beta
  58. FROM t
  59. GROUP BY end_date.month();
  60. }
  61. /*
  62. * 计算目标和BFI所用指数因子的相关系数
  63. *
  64. * @param entity_info <TABLE>: NEED COLUMNS entity_id, inception_date, price_date
  65. *
  66. * TODO: correlation is OK; beta, info, t_value are way off!
  67. *
  68. * NOTE: 与Java把月末日期作为截止日期不同的是,这里用每月最后一个周五作为截止日,所以数值会与MySQL中存储的略为不同
  69. *
  70. */
  71. def cal_entity_index_coe(entity_type, entity_info) {
  72. // entity_info = get_fund_info(['MF00003PW1', 'MF00003PWC']).join(take(2024.10.31, 2) AS price_date).rename!('fund_id', 'entity_id');
  73. if(entity_info.isVoid() || entity_info.size() == 0) return null;
  74. // 简单起见,取数据集中最新日期作为截止日期
  75. end_day = entity_info.price_date.max();
  76. ret_entity = get_entity_weekly_rets(entity_type, entity_info);
  77. if(ret_entity.isVoid() || ret_entity.size() == 0) return null;
  78. // 取BFI用得到的指数/因子列表
  79. v_indexes = get_bfi_index_list();
  80. // 手搓一个带日期的数据表
  81. index_info = table(v_indexes AS entity_id, take(end_day, v_indexes.size()) AS price_date);
  82. ret_index = get_entity_weekly_rets('MI', index_info);
  83. if(ret_index.isVoid() || ret_index.size() == 0) return null;
  84. // 两次循环遍历所有entity和指数
  85. entity_coe = table(1000:0, ['entity_id', 'index_id', 'end_date', 'coe_1y', 'coe_3y', 'coe_5y', 'info_ratio_1y', 'info_ratio_3y', 'info_ratio_5y',
  86. 't_value_1y', 't_value_3y', 't_value_5y', 'beta_1y', 'beta_3y', 'beta_5y'],
  87. [SYMBOL, SYMBOL, MONTH, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE,
  88. DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE]);
  89. for(entity in entity_info.entity_id) {
  90. ret1 = SELECT fund_id AS entity_id, price_date.weekEnd(4) AS end_date, price_date.weekEnd(4) AS price_date, ret_1w AS ret
  91. FROM ret_entity WHERE fund_id = entity AND price_date.weekEnd(4) <= end_day;
  92. for(index in v_indexes) {
  93. ret2 = SELECT index_id AS benchmark_id, price_date.weekEnd(4) AS end_date, price_date.weekEnd(4) AS price_date, ret_1w AS ret
  94. FROM ret_index WHERE index_id = index AND price_date.weekEnd(4) <= end_day;
  95. benchmarks = table(take(entity, ret1.size()) AS entity_id, take(index, ret1.size()) AS benchmark_id, ret1.price_date.weekEnd(4) AS end_date);
  96. closity_1y = cal_monthly_closity(ret1, ret2, 1y);
  97. closity_3y = cal_monthly_closity(ret1, ret2, 3y);
  98. closity_5y = cal_monthly_closity(ret1, ret2, 5y);
  99. INSERT INTO entity_coe
  100. SELECT entity, index, c1.end_date, c1.corr AS coe_1y, c3.corr AS coe_3y, c5.corr AS coe_5y,
  101. c1.info AS info_ratio_1y, c3.info AS info_ratio_3y, c5.info AS info_ratio_5y,
  102. c1.t_value AS t_value_1y, c3.t_value AS t_value_3y, c5.t_value AS t_value_5y,
  103. c1.beta AS beta_1y, c3.beta AS beta_3y, c5.beta AS beta_5y
  104. FROM closity_1y c1
  105. LEFT JOIN closity_3y c3 ON c1.end_date = c3.end_date
  106. LEFT JOIN closity_5y c5 ON c1.end_date = c5.end_date;
  107. }
  108. }
  109. return entity_coe;
  110. }