task_weeklyPerformnce.dos 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. module fundit::task_weeklyPerformance
  2. use fundit::sqlUtilities;
  3. use fundit::operationDataPuller;
  4. use fundit::performanceDataPuller;
  5. use fundit::indicatorCalculator;
  6. use fundit::rbsaCalculator;
  7. use fundit::bfiMatcher;
  8. use fundit::dataSaver;
  9. /*
  10. * 根据收益更新日期计算 RBSA
  11. *
  12. * @param entityType <STRING>: MF, HF, PF (MF和HF等效)
  13. *
  14. * Example: CalFundRBSATask('MF', ['MF00003PW1'], 2024.10.14T10:00:00);
  15. */
  16. def CalEntityRBSATask(entityType, entityIds, updateTime) {
  17. // entityType = 'MF'
  18. //entityIds = ['MF00003PW1']
  19. //updateTime = 2024.10.14T10:00:00
  20. tb_result = table(100:0,
  21. ["entity_id", "asset_type_id", "index_id", "effective_date", "level", "alternative_id", "weighting"],
  22. [iif(entityType=='PF', INT, STRING), STRING, STRING, STRING, INT, STRING, DOUBLE]);
  23. t = get_entity_list_by_weekly_return_updatetime(entityType, entityIds, updateTime, true);
  24. window = 48;
  25. step = 13;
  26. if(t.isVoid() || t.size() == 0) return;
  27. d_rbsa = get_rbsa_index();
  28. for(entity in t) {
  29. for(asset_type in d_rbsa.keys()) {
  30. // 起始日期是最早更新日期再向前推一个时间窗口
  31. res = cal_entity_RBSA(entityType, entity.entity_id, d_rbsa[asset_type], 'w',
  32. t.price_date.temporalAdd(-window, 'w')[0], today(), true, window, step);
  33. // 每日任务只负责更新最新的rbsa结果
  34. latest_date = (EXEC price_date.max() AS price_date FROM res)[0];
  35. tb_result.tableInsert(SELECT entity_id, asset_type, index_id, price_date, level, alternative_id, weights
  36. FROM res WHERE price_date = latest_date);
  37. }
  38. }
  39. save_and_sync(tb_result, 'raw_db.pf_fund_rbsa_breakdown', 'raw_db.pf_fund_rbsa_breakdown');
  40. }
  41. /*
  42. * [定时任务] 计算基金和组合的BFI
  43. *
  44. * @param entityType <STRING>: MF, HF, PF
  45. *
  46. *
  47. * TODO: max_r2 表在哪里被用到了?应该和基金推荐有关系
  48. *
  49. * Example: MatchEntityBFITask('MF', 2024.11.20);
  50. */
  51. def MatchEntityBFITask(entityType, date) {
  52. //entityType = 'MF'
  53. //date = 2024.11.20
  54. rt = '';
  55. if(find(['HF', 'MF', 'PF'], entityType) < 0) return null;
  56. // 取有最新净值变动的基金列表 (1s)
  57. tb_cal_entity = get_entity_list_by_nav_updatetime(entityType, NULL, date, true);
  58. if(tb_cal_entity.isVoid() || tb_cal_entity.size() == 0 ) return;
  59. i = 0;
  60. size = tb_cal_entity.size();
  61. batch_size = 1000;
  62. do {
  63. // 4 min per 1000 funds or 2 min per 1000 portfolios
  64. coe = cal_entity_index_coe(entityType, tb_cal_entity[i : min(size, i+batch_size)]);
  65. if(coe.isVoid() || coe.size() == 0) continue;
  66. entity_info = get_entity_info(entityType, tb_cal_entity[i : min(size, i+batch_size)].entity_id);
  67. bfi_raw = match_entity_bfi(entityType, entity_info, coe);
  68. // 先存到数据库,落袋为安
  69. try {
  70. // 筛掉 correlation 绝对值不够阈值的记录
  71. t_coe = SELECT entity_id, end_date, index_id,
  72. iif(coe_1y.abs() < get_min_threshold('correlation'), double(NULL), coe_1y) AS coe_1y,
  73. iif(coe_3y.abs() < get_min_threshold('correlation'), double(NULL), coe_3y) AS coe_3y,
  74. iif(coe_5y.abs() < get_min_threshold('correlation'), double(NULL), coe_5y) AS coe_5y,
  75. info_ratio_1y, info_ratio_3y, info_ratio_5y, t_value_1y, t_value_3y, t_value_5y, beta_1y, beta_3y, beta_5y
  76. FROM coe;
  77. DELETE FROM t_coe WHERE coe_1y IS NULL AND coe_3y IS NULL AND coe_5y IS NULL;
  78. // 候选因子
  79. t_bfi_candidates = SELECT entity_id, end_date, index_id AS factor_id, coe_1y AS coe, coe_1y.square() AS r2, 'w' AS performance_flag, t_value_1y, beta_1y
  80. FROM t_coe WHERE index_id LIKE 'FA%';
  81. chg_columns_for_mysql(t_coe, iif(entityType == 'PF', 'portfolio_id', 'fund_id'));
  82. // 只有基金需要存 index_coe 表
  83. if(entityType IN ['MF', 'HF']) save_and_sync(t_coe, 'raw_db.pf_fund_index_coe', );
  84. // 所有的 factors 存到 xxx_factor_bfi 表;NOTE: Java 把所有 factor 的数据都存起来,这里只存 correlation 达标的记录 (反正这个表没啥用?)
  85. chg_columns_for_mysql(t_bfi_candidates, iif(entityType == 'PF', 'portfolio_id', 'fund_id'));
  86. save_and_sync(t_bfi_candidates, iif(entityType == 'PF', 'raw_db.pf_portfolio_factor_bfi', 'raw_db.cm_fund_factor_bfi'), );
  87. if(bfi_raw.isVoid() || bfi_raw.size() == 0) continue;
  88. // 有效因子
  89. t_bfi = SELECT entity_id, end_date, factor_id, coe_1y AS coe, r2, performance_flag, t_value_1y, beta_1y
  90. FROM bfi_raw ORDER BY entity_id, end_date, r2 DESC;
  91. // 最大R2因子及所有有效因子标签
  92. t_max_r2 = SELECT entity_id, factor_id.first() AS factor_id, end_date,
  93. string(NULL) AS performance_flag, coe.first() AS coe, r2.first() AS r2, concat(factor_name, ",") AS rz_portrait
  94. FROM ej(t_bfi, get_bfi_index_list(), 'factor_id')
  95. GROUP BY entity_id, end_date;
  96. // 有效 factors 存到 xxx_factor_bfi_by_category_group 表
  97. chg_columns_for_mysql(t_bfi, iif(entityType == 'PF', 'portfolio_id', 'fund_id'));
  98. save_and_sync(t_bfi_candidates, iif(entityType == 'PF', 'raw_db.pf_portfolio_factor_bfi_by_category_group', 'raw_db.pf_fund_factor_bfi_by_category_group'), );
  99. // 有效因子中 R2 最大的因子存 xxx_max_r2
  100. chg_columns_for_mysql(t_max_r2, iif(entityType == 'PF', 'portfolio_id', 'fund_id'));
  101. save_and_sync(t_max_r2, iif(entityType == 'PF', 'raw_db.pf_portfolio_factor_bfi_max_r2', 'raw_db.pf_fund_factor_bfi_by_category_group_max_r2'), );
  102. } catch (ex) {
  103. //TODO: Log errors
  104. rt += ex + '\n';
  105. }
  106. i = i + batch_size;
  107. } while (i<size)
  108. return rt;
  109. }
  110. /*
  111. * [定时任务] 计算BFI指标并存入数据库
  112. *
  113. * @param entityType <STRING>: 'MF', 'HF', 'PF' (MF和HF等效)
  114. * @param date <DATETIME>: BFI更新时间, 为空时缺省为当前时间的前1天;为1989.01.01或更早日期时代表初始化,指标会被存入本地数据库
  115. *
  116. *
  117. * Example: calEntityBfiIndicatorTask('MF', 2024.10.28);
  118. * calEntityBfiIndicatorTask('PF', 2024.10.28);
  119. */
  120. def calEntityBfiIndicatorTask(entityType, date) {
  121. // entityType = 'MF'
  122. // date = 2024.10.01
  123. rt = '';
  124. if(!(entityType IN ['MF', 'HF', 'PF'])) return null;
  125. very_old_day = 1900.01.01;
  126. if(date.isNothing() || date.isNull())
  127. end_day = temporalAdd(now(), -1d);
  128. else
  129. end_day = date;
  130. // 1989.01.01及以前的日期被认为从本地读数据
  131. isFromMySQL = iif(end_day <= 1989.01.01, false, true);
  132. // 取有最新bfi变动的基金列表 (1s)
  133. tb_cal_entities = get_entity_bfi_factors(entityType, NULL, very_old_day.month(), today().month(), end_day);
  134. if(tb_cal_entities.isVoid() || tb_cal_entities.size() == 0 ) return;
  135. v_uniq_entity_id = EXEC DISTINCT entity_id FROM tb_cal_entities;
  136. // 按照 MySQL 建好各表
  137. tb_bfi_indicator = create_entity_bfi_indicator(iif(entityType=='PF', true, false));
  138. // 分批跑
  139. i = 0;
  140. batch_size = 100;
  141. do {
  142. entities = SELECT * FROM tb_cal_entities WHERE entity_id IN v_uniq_entity_id[i : min(v_uniq_entity_id.size(), i+batch_size)];
  143. if(entities.isVoid() || entities.size() == 0) break;
  144. // 200ms
  145. entity_info = SELECT entity_id, end_date.temporalParse('yyyy-MM') AS end_date, inception_date, factor_id AS benchmark_id, ini_value
  146. FROM ej(entities, get_entity_info(entityType, entities.entity_id), 'entity_id');
  147. // 取月收益 (12s)
  148. rets = get_monthly_ret(entityType, entity_info.entity_id, very_old_day, entity_info.end_date.max().temporalFormat('yyyy-MM-dd').temporalParse('yyyy-MM-dd').monthEnd(), isFromMySQL);
  149. // 把 yyyy-MM 格式的 end_date 改成 dolphin 的 MONTH
  150. v_end_date = rets.end_date.temporalParse('yyyy-MM');
  151. rets.replaceColumn!('end_date', v_end_date);
  152. if(!rets.isVoid() && rets.size() > 0) {
  153. // 计算月度指标 (5s)
  154. indicators = cal_monthly_indicators(entityType, 'BFI', rets);
  155. // 仿照MySQL的表结构准备好记录 (1s)
  156. generate_entity_bfi_indicator(entity_info, indicators, true, tb_bfi_indicator);
  157. }
  158. i += batch_size;
  159. } while (i <= v_uniq_entity_id.size());
  160. if(! tb_bfi_indicator.isVoid() && tb_bfi_indicator.size() > 0) {
  161. // save data to MySQL
  162. try {
  163. t_desc = get_bfi_indicator_table_description(entityType);
  164. chg_columns_for_mysql(tb_bfi_indicator, t_desc.sec_id_col[0]);
  165. db_name = t_desc.table_name[0].split('.')[0];
  166. save_and_sync(tb_bfi_indicator, t_desc.table_name[0].strReplace(db_name, 'raw_db'), t_desc.table_name[0].strReplace(db_name, 'raw_db'));
  167. // 数据初始化时将指标存入本地,做排名之用
  168. if(end_day <= get_ini_data_const['date']) {
  169. save_table(tb_bfi_indicator, t_desc.table_name[0], false);
  170. }
  171. } catch(ex) {
  172. //TODO: Log errors
  173. rt = ex;
  174. }
  175. }
  176. return rt;
  177. }