task_fundPerformance.dos 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. module fundit::task_fundPerformance
  2. use fundit::sqlUtilities;
  3. use fundit::operationDataPuller;
  4. use fundit::performanceDataPuller;
  5. use fundit::dataSaver;
  6. use fundit::returnCalculator;
  7. use fundit::indicatorCalculator;
  8. use fundit::rbsaCalculator;
  9. use fundit::bfiMatcher;
  10. use fundit::ms_dataPuller;
  11. /*
  12. * [定时任务]:最新净值触发的业绩指标计算
  13. *
  14. * @param entityType <STRING>: 'MF', 'HF'...
  15. * @param date <DATETIME>: 净值更新时间, 为空时缺省为当前时间-1天;为1990.01.01或更早日期时代表初始化,指标会被存入本地数据库
  16. *
  17. * NOTE: 与Java不同的是当月indicator计算每日触发,不必等到Month-end production
  18. *
  19. * Example: calFundPerformanceTask('MF', 2024.10.28);
  20. * calFundPerformanceTask('MI', 2024.10.28);
  21. */
  22. def calFundPerformanceTask(entityType, date) {
  23. rt = '';
  24. if(!(entityType IN ['MF', 'HF', 'MI', 'FI'])) return null;
  25. if(date.isNothing() || date.isNull())
  26. end_day = temporalAdd(now(), -1d);
  27. else
  28. end_day = date;
  29. // 取有最新净值变动的基金列表 (1s)
  30. tb_cal_funds = get_entity_list_by_nav_updatetime(entityType, NULL, end_day, true);
  31. if(tb_cal_funds.isVoid() || tb_cal_funds.size() == 0 ) return;
  32. // 按照 MySQL 建好各表
  33. tb_fund_performance = create_entity_performance();
  34. tb_fund_indicator = create_entity_indicator();
  35. tb_fund_risk_stats = create_entity_risk_stats();
  36. tb_fund_riskadjret_stats = create_entity_riskadjret_stats();
  37. tb_fund_style_stats = create_entity_style_stats();
  38. tb_fund_performance_weekly = create_entity_performance_weekly();
  39. tb_fund_latest_performance = create_entity_latest_performance();
  40. // 分批跑
  41. i = 0;
  42. batch_size = 1000;
  43. do {
  44. funds = tb_cal_funds[i : min(tb_cal_funds.size(), i+batch_size)];
  45. if(funds.isVoid() || funds.size() == 0) break;
  46. // 200ms
  47. fund_info = SELECT entity_id, price_date, inception_date, benchmark_id, ini_value
  48. FROM ej(funds, get_entity_info(entityType, funds.entity_id), 'entity_id');
  49. // 计算月收益 (12s)
  50. rets = mix_monthly_returns(entityType, fund_info);
  51. if(!rets.isVoid() && rets.size() > 0) {
  52. // 计算月度指标 (56s)
  53. rets.rename!('cumulative_nav', 'nav');
  54. indicators = cal_monthly_indicators(entityType, 'PBI', rets);
  55. // 仿照MySQL的表结构准备好记录 (1s)
  56. generate_entity_performance(fund_info, indicators, true, tb_fund_performance);
  57. generate_entity_indicator(fund_info, indicators, true, tb_fund_indicator);
  58. generate_entity_risk_stats(fund_info, indicators, true, tb_fund_risk_stats);
  59. generate_entity_riskadjret_stats(fund_info, indicators, true, tb_fund_riskadjret_stats);
  60. generate_entity_style_stats(fund_info, indicators, true, tb_fund_style_stats);
  61. }
  62. // 计算周收益 (8s)
  63. rets_w = cal_weekly_returns(entityType, fund_info);
  64. if(! rets_w.isVoid() && rets_w.size() > 0) {
  65. generate_entity_performance_weekly(fund_info, rets_w, true, tb_fund_performance_weekly);
  66. }
  67. // 计算最新收益 (69s)
  68. perf_latest = cal_latest_performance(entityType, fund_info, true);
  69. if(! perf_latest.isVoid() && perf_latest.size() > 0) {
  70. generate_entity_latest_performance(fund_info, perf_latest, true, tb_fund_latest_performance);
  71. }
  72. i += batch_size;
  73. // } while (i < batch_size);
  74. } while (i <= tb_cal_funds.size());
  75. if(! tb_fund_performance.isVoid() && tb_fund_performance.size() > 0) {
  76. // save data to MySQL (13s)
  77. try {
  78. chg_columns_for_mysql(tb_fund_performance, 'fund_id');
  79. save_and_sync(tb_fund_performance, 'raw_db.fund_performance', 'raw_db.fund_performance');
  80. chg_columns_for_mysql(tb_fund_indicator, 'fund_id');
  81. save_and_sync(tb_fund_indicator, 'raw_db.fund_indicator', 'raw_db.fund_indicator');
  82. chg_columns_for_mysql(tb_fund_risk_stats, 'fund_id');
  83. // mfdb.fund_performance 表中 maxdrawdown_6m & maxdrawdown_ytd 是虚拟列,这里用数据列顺序强行写入真实列 6m_maxdrawdown & ytd_maxdrawdown (DolphinDB 不允许字段名以数字开头)
  84. save_and_sync(tb_fund_risk_stats, 'raw_db.fund_risk_stats', 'raw_db.fund_risk_stats');
  85. chg_columns_for_mysql(tb_fund_riskadjret_stats, 'fund_id');
  86. save_and_sync(tb_fund_riskadjret_stats, 'raw_db.fund_riskadjret_stats', 'raw_db.fund_riskadjret_stats');
  87. chg_columns_for_mysql(tb_fund_style_stats, 'fund_id');
  88. save_and_sync(tb_fund_style_stats, 'raw_db.fund_style_stats', 'raw_db.fund_style_stats');
  89. save_and_sync(tb_fund_performance_weekly, 'raw_db.fund_performance_weekly', 'raw_db.fund_performance_weekly');
  90. save_and_sync(tb_fund_latest_performance, 'raw_db.fund_latest_performance', 'raw_db.fund_latest_nav_performance');
  91. // 数据初始化时将指标存入本地,做排名之用
  92. if(end_day <= 1990.01.01) {
  93. save_table(tb_fund_performance, 'raw_db.fund_performance', false);
  94. save_table(tb_fund_indicator, 'raw_db.fund_indicator', false);
  95. save_table(tb_fund_risk_stats, 'raw_db.fund_risk_stats', false);
  96. save_table(tb_fund_riskadjret_stats, 'raw_db.fund_riskadjret_stats', false);
  97. save_table(tb_fund_style_stats, 'raw_db.fund_style_stats', false);
  98. }
  99. } catch(ex) {
  100. //TODO: Log errors
  101. rt = ex;
  102. }
  103. }
  104. return rt;
  105. }
  106. /*
  107. * 根据收益更新日期计算 RBSA
  108. *
  109. * Example: CalFundRBSATask('MF', ['MF00003PW1'], 2024.10.14T10:00:00);
  110. */
  111. def CalFundRBSATask(entityType, entityIds, updateTime) {
  112. // entityType = 'MF'
  113. //entityIds = ['MF00003PW1']
  114. //updateTime = 2024.10.14T10:00:00
  115. tb_result = table(100:0,
  116. ["entity_id", "asset_type_id", "index_id", "effective_date", "level", "alternative_id", "weighting"],
  117. [iif(entityType=='PF', INT, STRING), STRING, STRING, STRING, INT, STRING, DOUBLE]);
  118. t = get_entity_list_by_weekly_return_updatetime(entityType, entityIds, updateTime, true);
  119. window = 48;
  120. step = 13;
  121. if(t.isVoid() || t.size() == 0) return;
  122. d_rbsa = get_rbsa_index();
  123. for(entity in t) {
  124. //entity=t[0]
  125. for(asset_type in d_rbsa.keys()) {
  126. //asset_type=d_rbsa.keys()[3]
  127. // 起始日期是最早更新日期再向前推一个时间窗口
  128. res = cal_entity_RBSA(entityType, entity.entity_id, d_rbsa[asset_type], 'w',
  129. t.price_date.temporalAdd(-window, 'w')[0], today(), true, window, step);
  130. // 每日任务只负责更新最新的rbsa结果
  131. latest_date = (EXEC price_date.max() AS price_date FROM res)[0];
  132. tb_result.tableInsert(SELECT entity_id, asset_type, index_id, price_date, level, alternative_id, weights
  133. FROM res WHERE price_date = latest_date);
  134. }
  135. }
  136. save_and_sync(tb_result, 'raw_db.pf_fund_rbsa_breakdown', 'raw_db.pf_fund_rbsa_breakdown');
  137. }
  138. /*
  139. * 【临时】用于数据初始化:只计算收益
  140. *
  141. * @param entityType <STRING>: 'MF', 'HF'...
  142. * @param date <DATETIME>: 净值更新时间
  143. *
  144. */
  145. def ms_calFundReturns() {
  146. rt = '';
  147. very_old_date = 1990.01.01;
  148. // 取基金列表 (27s)
  149. tb_cal_funds = ms_get_fund_list_by_nav_updatetime(NULL, very_old_date);
  150. if(tb_cal_funds.isVoid() || tb_cal_funds.size() == 0 ) return;
  151. tb_fund_performance = create_entity_performance();
  152. tb_fund_indicator = create_entity_indicator();
  153. tb_fund_risk_stats = create_entity_risk_stats();
  154. tb_fund_riskadjret_stats = create_entity_riskadjret_stats();
  155. tb_fund_style_stats = create_entity_style_stats();
  156. tb_fund_performance_weekly = create_entity_performance_weekly();
  157. tb_fund_latest_performance = create_entity_latest_performance();
  158. // 分批跑
  159. i = 0;
  160. batch_size = 1000;
  161. do {
  162. funds = tb_cal_funds[i : min(tb_cal_funds.size(), i+batch_size)];
  163. if(funds.isVoid() || funds.size() == 0) break;
  164. // 200ms
  165. fund_info = SELECT entity_id, price_date, inception_date, benchmark_id, ini_value
  166. FROM ej(funds, ms_get_fund_info(funds.entity_id), 'entity_id', 'fund_id');
  167. // 计算月收益 (19s)
  168. tb_nav = ms_get_fund_monthly_nav(fund_info.entity_id);
  169. rets = cal_monthly_returns_by_nav(fund_info, tb_nav);
  170. if(!rets.isVoid() && rets.size() > 0) {
  171. // 计算月度指标 (67s)
  172. rets.rename!('cumulative_nav', 'nav');
  173. indicators = cal_monthly_indicators('MF', 'PBI', rets);
  174. // 仿照MySQL的表结构准备好记录 (1s)
  175. generate_entity_performance(fund_info, indicators, true, tb_fund_performance);
  176. generate_entity_indicator(fund_info, indicators, true, tb_fund_indicator);
  177. generate_entity_risk_stats(fund_info, indicators, true, tb_fund_risk_stats);
  178. generate_entity_riskadjret_stats(fund_info, indicators, true, tb_fund_riskadjret_stats);
  179. generate_entity_style_stats(fund_info, indicators, true, tb_fund_style_stats);
  180. }
  181. // 计算周收益 (49s)
  182. rets_w = cal_weekly_returns('MF', fund_info);
  183. if(! rets_w.isVoid() && rets_w.size() > 0) {
  184. generate_entity_performance_weekly(fund_info, rets_w, true, tb_fund_performance_weekly);
  185. }
  186. // 计算最新收益 (23s)
  187. perf_latest = cal_latest_performance('MF', fund_info, true);
  188. if(! perf_latest.isVoid() && perf_latest.size() > 0) {
  189. generate_entity_latest_performance(fund_info, perf_latest, true, tb_fund_latest_performance);
  190. }
  191. i += batch_size;
  192. // } while (i < batch_size);
  193. } while (i <= tb_cal_funds.size());
  194. if(! tb_fund_performance.isVoid() && tb_fund_performance.size() > 0) {
  195. // save data to MySQL (26m)
  196. try {
  197. chg_columns_for_mysql(tb_fund_performance, 'fund_id');
  198. save_and_sync(tb_fund_performance, 'raw_db.fund_performance', 'raw_db.fund_performance');
  199. chg_columns_for_mysql(tb_fund_indicator, 'fund_id');
  200. save_and_sync(tb_fund_indicator, 'raw_db.fund_indicator', 'raw_db.fund_indicator');
  201. chg_columns_for_mysql(tb_fund_risk_stats, 'fund_id');
  202. // mfdb.fund_performance 表中 maxdrawdown_6m & maxdrawdown_ytd 是虚拟列,这里用数据列顺序强行写入真实列 6m_maxdrawdown & ytd_maxdrawdown (DolphinDB 不允许字段名以数字开头)
  203. save_and_sync(tb_fund_risk_stats, 'raw_db.fund_risk_stats', 'raw_db.fund_risk_stats');
  204. chg_columns_for_mysql(tb_fund_riskadjret_stats, 'fund_id');
  205. save_and_sync(tb_fund_riskadjret_stats, 'raw_db.fund_riskadjret_stats', 'raw_db.fund_riskadjret_stats');
  206. chg_columns_for_mysql(tb_fund_style_stats, 'fund_id');
  207. save_and_sync(tb_fund_style_stats, 'raw_db.fund_style_stats', 'raw_db.fund_style_stats');
  208. save_and_sync(tb_fund_performance_weekly, 'raw_db.fund_performance_weekly', 'raw_db.fund_performance_weekly');
  209. save_and_sync(tb_fund_latest_performance, 'raw_db.fund_latest_performance', 'raw_db.fund_latest_nav_performance');
  210. } catch(ex) {
  211. //TODO: Log errors
  212. rt = ex;
  213. }
  214. }
  215. return rt;
  216. }
  217. /*
  218. * 实验性质的API
  219. *
  220. *
  221. */
  222. def calFundIndexCorrelation(entityType, date) {
  223. if(find(['HF', 'MF'], entityType) < 0) return null;
  224. // 取有最新净值变动的基金列表 (1s)
  225. tb_cal_funds = get_entity_list_by_nav_updatetime(entityType, NULL, date, true);
  226. if(tb_cal_funds.isVoid() || tb_cal_funds.size() == 0 ) return;
  227. // tb_fund_index_coe = create_entity_index_coe();
  228. // (7m)
  229. coe = cal_entity_index_coe(entityType, tb_cal_funds[0:1000]);
  230. return coe;
  231. }