task_fundPerformance.dos 12 KB

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