task_monthlyPerformance.dos 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527
  1. module fundit::task_monthlyPerformance
  2. use fundit::sqlUtilities;
  3. use fundit::operationDataPuller;
  4. use fundit::performanceDataPuller;
  5. use fundit::indicatorCalculator;
  6. use fundit::dataSaver;
  7. use fundit::bfiMatcher;
  8. use fundit::rankingCalculator;
  9. use fundit::navCalculator;
  10. /*
  11. * [定时任务] 计算基金一、二级分类排名并存入数据库
  12. *
  13. * @param entity_type <STRING>: 'MF', 'HF' (MF=HF)
  14. * @param end_date <MONTH>:
  15. * @param isFromMySQL <BOOL>: false 时读取dolphin本地的收益及指标表,用于初始化数据
  16. *
  17. * NOTE: 与 MySQL 不同,这里的 rank 最小值是0,不是1
  18. *
  19. * Example: CalEntityRankingTask('MF', 2024.09M, true);
  20. * CalEntityRankingTask('CO', 2024.10M, true);
  21. */
  22. def CalEntityRankingTask(entityType, endDate, isFromMySQL=true) {
  23. //entityType='MF'
  24. //endDate = 2024.11M
  25. //isFromMySQL = true
  26. if(!(entityType in ['MF', 'HF', 'PL', 'CO'])) return NULL;
  27. entity_info = get_entity_info(entityType, NULL);
  28. v_ranking_tables = cal_indicator_ranking('strategy', entityType, entity_info, endDate, isFromMySQL);
  29. save_ranking_tables(entityType, 'strategy', v_ranking_tables);
  30. }
  31. /*
  32. * [定时任务] 计算基金BFI排名并存入数据库
  33. *
  34. * @param entityType <STRING>: 'MF', 'HF' (MF=HF), 'PL'
  35. * @param endDate <MONTH>:
  36. * @param isFromMySQL <BOOL>: false 时读取dolphin本地的收益及指标表,用于初始化数据
  37. *
  38. *
  39. * Example: CalEntityBfiRankingTask('MF', 2024.09M, true);
  40. * CalEntityBfiRankingTask('PL', 2024.10M, true);
  41. */
  42. def CalEntityBfiRankingTask(entityType, endDate, isFromMySQL=true) {
  43. if(!(entityType in ['MF', 'HF', 'PL'])) return NULL;
  44. entity_info = get_entity_info(entityType, NULL);
  45. // 16 sec
  46. v_ranking_tables = cal_indicator_ranking('bfi', entityType, entity_info, endDate, isFromMySQL);
  47. // 39 sec
  48. save_ranking_tables(entityType, 'bfi', v_ranking_tables);
  49. }
  50. /*
  51. * Private Method: 计算相对排名并存入数据库
  52. *
  53. *
  54. */
  55. def cal_and_save_relative_ranking(entity_type, benchmark_ranking, entity_ranking, ranking_by, isFromMySQL=true) {
  56. // benchmark_ranking= tb_fund_ranking
  57. t_entity_ranking = entity_ranking;
  58. cal_relative_ranking(benchmark_ranking, t_entity_ranking, isFromMySQL);
  59. t_entity_ranking.rename!('category_id', iif(ranking_by=='bfi', 'factor_id', ranking_by));
  60. save_relative_ranking_table(entity_type, t_entity_ranking, ranking_by);
  61. }
  62. /*
  63. *
  64. * [定时任务] 以公募基金为评级参考,计算组合、私有基金收益及指标排名
  65. *
  66. * @param entityType <STRING>: PF
  67. *
  68. * TODO: customer fund
  69. * TODO: 计算单个组合时总耗时1.5min, 大部分时间用来获取Mysql数据
  70. *
  71. * Example: CalRelativeRankingTask('PF', NULL, 2024.09M, true);
  72. * CalRelativeRankingTask('PF', 143109, 2024.09M, true);
  73. */
  74. def CalRelativeRankingTask(entity_type, entity_ids, end_date, isFromMySQL=true) {
  75. // entity_type = 'PF'
  76. // end_date = 2024.09M
  77. // isFromMySQL = true
  78. // ranking_by = 'bfi'
  79. // entity_ids = 143109
  80. entity_info = get_entity_info(entity_type, entity_ids);
  81. if(entity_type == 'PF')
  82. entity_info = SELECT * FROM entity_info WHERE portfolio_type IN (1, 2) // 1: 用户组合、2:客户真实组合,忽略客户推荐组合、总览综合等虚拟组合
  83. v_ranking_by = ['strategy', 'substrategy', 'bfi'];
  84. // 暂时以公募混合基金为排名参考
  85. for(ranking_by in v_ranking_by) {
  86. if(ranking_by == 'strategy') {
  87. v_category = EXEC DISTINCT strategy FROM entity_info WHERE strategy IS NOT NULL;
  88. tb_fund_ranking = get_fund_indicator_ranking(NULL, end_date, v_category, isFromMySQL);
  89. UPDATE tb_fund_ranking SET category_id = strategy$STRING;
  90. } else if(ranking_by == 'substrategy') {
  91. v_category = EXEC DISTINCT substrategy FROM entity_info WHERE substrategy IS NOT NULL;
  92. tb_fund_ranking = get_fund_indicator_substrategy_ranking(NULL, end_date, v_category, isFromMySQL);
  93. UPDATE tb_fund_ranking SET category_id = substrategy$STRING;
  94. } else if(ranking_by == 'bfi') {
  95. if(entity_ids != NULL || entity_ids != '')
  96. v_category = EXEC DISTINCT factor_id FROM get_entity_bfi_factors(entity_type, entity_ids, end_date, end_date);
  97. else
  98. v_category = NULL;
  99. tb_fund_ranking = get_fund_bfi_bm_indicator_ranking(NULL, end_date, v_category, isFromMySQL);
  100. UPDATE tb_fund_ranking SET category_id = factor_id;
  101. }
  102. if(tb_fund_ranking.isVoid() || tb_fund_ranking.size() == 0) return;
  103. entity_ranking = transform_data_for_ranking(entity_type, entity_info, end_date, ranking_by, isFromMySQL);
  104. cal_and_save_relative_ranking(entity_type, tb_fund_ranking, entity_ranking, ranking_by, isFromMySQL);
  105. }
  106. }
  107. /*
  108. * 计算并存储基金经理和公司月度净值
  109. *
  110. * @return <TABLE>: [COLUMNS] entity_id, curve_type, strategy, effective_date, price_date, ret, nav
  111. *
  112. */
  113. def cal_and_save_mc_monthly_nav(entity_type, entity_date, is_save_local) {
  114. rt = '';
  115. if( !(entity_type in ['PL', 'CO']) ) return rt;
  116. if(entity_date.isVoid() || entity_date.size() == 0) return rt;
  117. // 准备类似MySQL结构的数据表
  118. tb_entity_nav = create_mc_fitted_curve();
  119. // 暂时与 MySQL 保持一致,只计算公募,私募,公私募综合三条时间序列。未来可细化至公、私募+主策略
  120. d_curve_type = dict(INT, INT);
  121. d_curve_type[1] = 1; // 私募
  122. d_curve_type[4] = 2; // 公募
  123. d_curve_type[7] = -99; // 公私募综合
  124. // 分批跑
  125. i = 0;
  126. batch_size = 1000;
  127. all_entity_id = entity_date.entity_id.distinct();
  128. do { // 14 sec
  129. tb_entity = SELECT entity_id, effective_date FROM entity_date
  130. WHERE entity_id IN all_entity_id[i : min(all_entity_id.size(), i+batch_size)];
  131. if(tb_entity.isVoid() || tb_entity.size() == 0) break;
  132. s_json = tb_entity.toStdJson();
  133. t_ret = get_mc_average_return(entity_type, 'm', s_json, 0, 1, true);
  134. for(cur in d_curve_type.keys()) {
  135. tmp = SELECT entity_id, cur AS curve_type, 0 AS strategy, effective_date, price_date, ret, incl_cal_cnt
  136. FROM t_ret WHERE raise_type = d_curve_type[cur] AND strategy = -99; // 目前只需要全策略
  137. // 计算月收益
  138. tb_nav = cal_mc_nav_by_return(entity_type, tmp, 'm');
  139. INSERT INTO tb_entity_nav
  140. SELECT entity_id, curve_type, strategy, effective_date AS end_date, nav, incl_cal_cnt
  141. FROM ej(tb_nav, tmp, ['entity_id', 'curve_type', 'strategy', 'effective_date']);
  142. }
  143. i += batch_size;
  144. } while (i <= all_entity_id.size());
  145. if(! tb_entity_nav.isVoid() && tb_entity_nav.size() > 0) {
  146. // save data to MySQL (12 sec)
  147. try {
  148. tb_entity_nav.rename!('entity_id', iif(entity_type == 'PL', 'fund_manager_id', 'company_id'));
  149. save_and_sync(tb_entity_nav, iif(entity_type == 'PL', 'raw_db.fund_manager_fitted_curve', 'raw_db.company_fitted_curve'), );
  150. // 数据初始化时将指标存入本地
  151. if(is_save_local == true) {
  152. save_table(tb_entity_nav, iif(entity_type == 'PL', 'mfdb.fund_manager_fitted_curve', 'mfdb.company_fitted_curve'), false);
  153. }
  154. } catch(ex) {
  155. //TODO: Log errors
  156. rt += ex;
  157. }
  158. }
  159. return rt;
  160. }
  161. /*
  162. * 计算并存储基金经理/公司的月度收益及指标
  163. *
  164. * @param entity_date <TABLE>: entity_id, curve_type, strategy, price_date
  165. * @param monthly_returns <TABLE>: entity_id, curve_type, strategy, end_date, price_date, nav, ret
  166. * @param indicator_type <STRING>: PBI, BFI
  167. *
  168. */
  169. def cal_and_save_mc_indicator(entity_type, entity_date, monthly_returns, indicator_type, is_save_local) {
  170. rt = '';
  171. if(!(entity_type IN ['PL', 'CO'] && indicator_type IN ['PBI', 'BFI'])) return rt;
  172. if(entity_date.isVoid() || entity_date.size() == 0) return rt;
  173. d_indicators = cal_mc_monthly_indicators(entity_type, indicator_type, monthly_returns);
  174. if(d_indicators.isVoid() || d_indicators["7"].isVoid()) break;
  175. // cal_mc_monthly_indicators 返回个两重字典,分别对应 curve_type 和不同区间的数据表,将同样区间的数据表(但不同curve_type)合并
  176. // curve_type: 1:私募,4:公募,7:公私募综合
  177. trailing_num = d_indicators["7"].keys().size();
  178. for(k in d_indicators["7"].keys()) {
  179. if(!d_indicators["1"].isVoid())
  180. d_indicators["7"][k].append!(d_indicators["1"][k]);
  181. if(!d_indicators["4"].isVoid())
  182. d_indicators["7"][k].append!(d_indicators["4"][k]);
  183. }
  184. indicators = d_indicators["7"];
  185. d_indicators = null;
  186. //
  187. // 按照 MySQL 建好各表
  188. if(indicator_type == 'PBI') {
  189. entity_info = SELECT entity_id, curve_type, strategy, price_date.temporalParse('yyyy-MM') AS price_date FROM entity_date;
  190. tb_mc_performance = create_mc_performance();
  191. tb_mc_indicator = create_mc_indicator();
  192. tb_mc_risk_stats = create_mc_risk_stats();
  193. tb_mc_riskadjret_stats = create_mc_riskadjret_stats();
  194. tb_mc_style_stats = create_mc_style_stats();
  195. // 写入数据
  196. generate_entity_performance(entity_info, indicators, true, tb_mc_performance, ['curve_type', 'strategy']);
  197. generate_entity_indicator(entity_info, indicators, true, tb_mc_indicator, ['curve_type', 'strategy']);
  198. generate_entity_risk_stats(entity_info, indicators, true, tb_mc_risk_stats, ['curve_type', 'strategy']);
  199. generate_entity_riskadjret_stats(entity_info, indicators, true, tb_mc_riskadjret_stats, ['curve_type', 'strategy']);
  200. generate_entity_style_stats(entity_info, indicators, true, tb_mc_style_stats, ['curve_type', 'strategy']);
  201. if(! tb_mc_performance.isVoid() && tb_mc_performance.size() > 0) {
  202. // save data to MySQL (13s)
  203. try {
  204. chg_columns_for_mysql(tb_mc_performance, iif(entity_type == 'PL', 'manager_id', 'company_id'));
  205. save_and_sync(tb_mc_performance, iif(entity_type == 'PL', 'raw_db.manager_performance', 'raw_db.company_performance'), );
  206. chg_columns_for_mysql(tb_mc_indicator, iif(entity_type == 'PL', 'manager_id', 'company_id'));
  207. save_and_sync(tb_mc_indicator, iif(entity_type == 'PL', 'raw_db.manager_indicator', 'raw_db.company_indicator'), );
  208. chg_columns_for_mysql(tb_mc_risk_stats, iif(entity_type == 'PL', 'manager_id', 'company_id'));
  209. save_and_sync(tb_mc_risk_stats, iif(entity_type == 'PL', 'raw_db.manager_risk_stats', 'raw_db.company_risk_stats'), );
  210. chg_columns_for_mysql(tb_mc_riskadjret_stats, iif(entity_type == 'PL', 'manager_id', 'company_id'));
  211. save_and_sync(tb_mc_riskadjret_stats, iif(entity_type == 'PL', 'raw_db.manager_riskadjret_stats', 'raw_db.company_riskadjret_stats'), );
  212. chg_columns_for_mysql(tb_mc_style_stats, iif(entity_type == 'PL', 'manager_id', 'company_id'));
  213. save_and_sync(tb_mc_style_stats, iif(entity_type == 'PL', 'raw_db.manager_style_stats', 'raw_db.company_style_stats'), );
  214. // 数据初始化时将指标存入本地
  215. if(is_save_local == true) {
  216. save_table(tb_mc_performance, iif(entity_type == 'PL', 'mfdb.manager_performance', 'mfdb.company_performance'), false);
  217. save_table(tb_mc_indicator, iif(entity_type == 'PL', 'mfdb.manager_indicator', 'mfdb.company_indicator'), false);
  218. save_table(tb_mc_risk_stats, iif(entity_type == 'PL', 'mfdb.manager_risk_stats', 'mfdb.company_risk_stats'), false);
  219. save_table(tb_mc_riskadjret_stats, iif(entity_type == 'PL', 'mfdb.manager_riskadjret_stats', 'mfdb.company_riskadjret_stats'), false);
  220. save_table(tb_mc_style_stats, iif(entity_type == 'PL', 'mfdb.manager_style_stats', 'mfdb.company_style_stats'), false);
  221. }
  222. } catch(ex) {
  223. //TODO: Log errors
  224. rt += ex;
  225. }
  226. }
  227. } else {
  228. entity_info = SELECT entity_id, curve_type, strategy, end_date.temporalParse('yyyy-MM') AS end_date, factor_id AS benchmark_id FROM entity_date;
  229. tb_mc_bfi_indicator = create_mc_bfi_indicator();
  230. //tb_mc_bfi_indicator.rename!('factor_id', 'benchmark_id');
  231. generate_entity_bfi_indicator(entity_info, indicators, true, tb_mc_bfi_indicator, ['curve_type', 'strategy']);
  232. if(! tb_mc_bfi_indicator.isVoid() && tb_mc_bfi_indicator.size() > 0) {
  233. // save data to MySQL
  234. try {
  235. chg_columns_for_mysql(tb_mc_bfi_indicator, iif(entity_type == 'PL', 'manager_id', 'company_id'));
  236. save_and_sync(tb_mc_bfi_indicator, iif(entity_type == 'PL', 'raw_db.manager_ty_bfi_bm_indicator', 'raw_db.company_ty_bfi_bm_indicator'), );
  237. // 数据初始化时将指标存入本地
  238. if(is_save_local == true)
  239. save_table(tb_mc_bfi_indicator, iif(entity_type == 'PL', 'mfdb.manager_ty_bfi_bm_indicator', 'mfdb.company_ty_bfi_bm_indicator'), false);
  240. } catch(ex) {
  241. //TODO: Log errors
  242. rt = ex;
  243. }
  244. }
  245. }
  246. return rt;
  247. }
  248. /*
  249. * [定时任务]: 基金经理/公司月净值计算
  250. *
  251. * Example: CalMCMonthlyNavTask('CO', 2024.12.04);
  252. */
  253. def CalMCMonthlyNavTask(entity_type, updatetime) {
  254. //updatetime = 2024.11.05;
  255. //entity_type = 'CO';
  256. if(!(entity_type IN ['PL', 'CO'])) return;
  257. is_save_local = iif(updatetime <= get_ini_data_const()['updatetime'], true, false);
  258. // 31 sec 简化起见,不区分curve_type, strategy; TODO: 性能能否优化?
  259. if(entity_type == 'PL') {
  260. entity_date = get_manager_list_by_fund_updatetime(updatetime);
  261. entity_date.rename!('manager_id', 'entity_id');
  262. }
  263. else {
  264. entity_date = get_company_list_by_fund_updatetime(updatetime);
  265. entity_date.rename!('company_id', 'entity_id');
  266. }
  267. // 15 sec
  268. cal_and_save_mc_monthly_nav(entity_type, entity_date, is_save_local);
  269. entity_date = null;
  270. }
  271. /*
  272. * [定时任务]: 基金经理/公司月收益及指标(含标准及BFI)计算
  273. *
  274. * @param entity_type <STRING>: PL, CO
  275. *
  276. * Example: CalMCIndicatorTask('CO', 2024.11.04);
  277. * CalMCIndicatorTask('PL', 2024.11.04);
  278. */
  279. def CalMCIndicatorTask(entity_type, updatetime) {
  280. // entity_type = 'PL';
  281. // updatetime = 2024.11.01
  282. rt = '';
  283. is_save_local = iif(updatetime <= get_ini_data_const()['updatetime'], true, false);
  284. // 3 sec
  285. entity_date = get_entity_list_by_nav_updatetime(entity_type, NULL, updatetime, true);
  286. i = 0;
  287. batch_size = 1000;
  288. v_entity_id = entity_date.entity_id.distinct();
  289. max_cnt = v_entity_id.size();
  290. ver_old_month = 1900.01M;
  291. do {
  292. // 取完整月净值
  293. tb_entity_date = SELECT entity_id, curve_type, strategy, ver_old_month AS end_date
  294. FROM entity_date WHERE entity_id in v_entity_id[i : min(i + batch_size, max_cnt)];
  295. if(tb_entity_date.isVoid() || tb_entity_date.size() == 0) break;
  296. s_json = tb_entity_date.toStdJson();
  297. tb_nav = get_mc_nav_for_return_calculation('PL', s_json, 0);
  298. v_end_date = tb_nav.end_date.temporalParse('yyyy-MM');
  299. tb_nav.replaceColumn!('end_date', v_end_date);
  300. tb_nav.join!(v_end_date.temporalFormat('yyyy-MM-dd').temporalParse('yyyy-MM-dd').businessMonthEnd() AS price_date);
  301. tb_nav.sortBy!(['entity_id', 'curve_type', 'strategy', 'end_date']);
  302. // 计算月度收益
  303. tb_monthly_ret = SELECT entity_id, curve_type, strategy, end_date, price_date, cumulative_nav AS nav, cumulative_nav.ratios()-1 AS ret
  304. FROM tb_nav
  305. CONTEXT BY entity_id, curve_type, strategy;
  306. // 40+ min
  307. cal_and_save_mc_indicator(entity_type, entity_date, tb_monthly_ret, 'PBI', is_save_local);
  308. i += batch_size;
  309. } while (i < max_cnt);
  310. }
  311. /*
  312. * [定时任务]: 基金经理月BFI指标计算
  313. *
  314. *
  315. * Example: CalManagerBfiIndicatorTask(2024.11.04);
  316. */
  317. def CalManagerBfiIndicatorTask(updatetime) {
  318. // updatetime = 2024.11.01
  319. rt = '';
  320. entity_type = 'PL';
  321. is_save_local = iif(updatetime <= get_ini_data_const()['updatetime'], true, false);
  322. // BFI indicator 计算由 bfi matching 表更新驱动
  323. entity_date = get_mc_bfi_factors(entity_type, NULL, 1990.01M, today().month(), updatetime);
  324. entity_date.join!(entity_date.end_date AS price_date);
  325. i = 0;
  326. batch_size = 1000;
  327. v_entity_id = entity_date.entity_id.distinct();
  328. max_cnt = v_entity_id.size();
  329. do {
  330. // 取完整月收益 1+ min
  331. tb_monthly_ret = get_monthly_ret(entity_type, v_entity_id[i : min(i + batch_size, max_cnt)], 1900.01.01, today(), true);
  332. v_end_date = tb_monthly_ret.end_date.temporalParse('yyyy-MM');
  333. tb_monthly_ret.replaceColumn!('end_date', v_end_date);
  334. UPDATE tb_monthly_ret SET price_date = end_date.temporalFormat('yyyy-MM-dd').temporalParse('yyyy-MM-dd').businessMonthEnd() WHERE price_date IS NULL;
  335. // 40+ min
  336. cal_and_save_mc_indicator(entity_type, entity_date, tb_monthly_ret, 'BFI', is_save_local);
  337. i += batch_size;
  338. } while (i < max_cnt);
  339. }
  340. /*
  341. *
  342. * [定时任务]: 基金经理的BFI MATCHING
  343. *
  344. * it takes 9.5 hours
  345. */
  346. def MatchManagerBFITask(updatetime) {
  347. rt = '';
  348. is_save_local = iif(updatetime <= get_ini_data_const()['updatetime'], true, false);
  349. entity_type = 'PL';
  350. // 31 sec
  351. entity_date = get_mc_performance_by_updatetime(entity_type, updatetime);
  352. if(entity_date.isVoid() || entity_date.size() == 0) return rt;
  353. i = 0;
  354. batch_size = 1000;
  355. max_cnt = entity_date.size();
  356. do {
  357. t_entity_date = entity_date[i : min(max_cnt, i + batch_size)];
  358. // 22 min per 1000 records, way too slow
  359. t_bfi = match_mc_bfi(entity_type, t_entity_date);
  360. t_max_r2 = SELECT entity_id , curve_type, strategy, factor_id.first() AS factor_id, end_date,
  361. string(NULL) AS performance_flag, coe.first() AS coe, r2.first() AS r2, concat(factor_name, ",") AS rz_portrait
  362. FROM ej(t_bfi, get_bfi_index_list(), 'factor_id')
  363. GROUP BY entity_id, curve_type, strategy, end_date;
  364. try {
  365. // 高度怀疑 pf_manager_factor_bfi 表只是中间表,没有用,这里就不存了
  366. // 有效 factors 存到 xxx_factor_bfi_by_category_group 表
  367. chg_columns_for_mysql(t_bfi, 'manager_id');
  368. save_and_sync(t_bfi, 'raw_db.pf_manager_factor_bfi_by_category_group', );
  369. // 有效因子中 R2 最大的因子存 xxx_max_r2
  370. chg_columns_for_mysql(t_max_r2, 'manager_id');
  371. save_and_sync(t_max_r2, 'raw_db.pf_manager_factor_bfi_max_r2', );
  372. } catch (ex) {
  373. //TODO: Log errors
  374. rt += ex;
  375. }
  376. i += batch_size
  377. } while (i < max_cnt);
  378. return rt;
  379. }