task_monthlyPerformance.dos 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529
  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, end_date, price_date, ret, nav
  111. *
  112. */
  113. def cal_and_save_mc_nav(entity_type, entity_date, is_save_local) {
  114. rt = '';
  115. if(entity_type == 'PL') s_entity_type = 'manager';
  116. else if(entity_type == 'CO') s_entity_type = 'company';
  117. else return rt;
  118. if(entity_date.isVoid() || entity_date.size() == 0) return rt;
  119. // 准备类似MySQL结构的数据表
  120. tb_entity_nav = create_mc_fitted_curve();
  121. // 暂时与 MySQL 保持一致,只计算公募,私募,公私募综合三条时间序列。未来可细化至公、私募+主策略
  122. d_curve_type = dict(INT, INT);
  123. d_curve_type[1] = 1; // 私募
  124. d_curve_type[4] = 2; // 公募
  125. d_curve_type[7] = -99; // 公私募综合
  126. // 分批跑
  127. i = 0;
  128. batch_size = 1000;
  129. all_entity_id = entity_date.entity_id.distinct();
  130. do { // 14 sec
  131. tb_entity = SELECT * FROM entity_date
  132. WHERE entity_id IN all_entity_id[i : min(all_entity_id.size(), i+batch_size)];
  133. if(tb_entity.isVoid() || tb_entity.size() == 0) break;
  134. s_json = tb_entity.toStdJson();
  135. t_ret = get_mc_monthly_return(s_entity_type, s_json, 0, 1, true);
  136. for(cur in d_curve_type.keys()) {
  137. tmp = SELECT entity_id, cur AS curve_type, 0 AS strategy, end_date, price_date, ret, incl_cal_cnt
  138. FROM t_ret WHERE raise_type = d_curve_type[cur] AND strategy = -99; // 目前只需要全策略
  139. // 计算月收益
  140. tb_nav = cal_mc_nav_by_return(entity_type, tmp);
  141. INSERT INTO tb_entity_nav
  142. SELECT entity_id, curve_type, strategy, end_date, nav, incl_cal_cnt
  143. FROM ej(tb_nav, tmp, ['entity_id', 'curve_type', 'strategy', 'end_date']);
  144. }
  145. i += batch_size;
  146. } while (i <= all_entity_id.size());
  147. if(! tb_entity_nav.isVoid() && tb_entity_nav.size() > 0) {
  148. // save data to MySQL (12 sec)
  149. try {
  150. tb_entity_nav.rename!('entity_id', iif(entity_type == 'PL', 'fund_manager_id', 'company_id'));
  151. save_and_sync(tb_entity_nav, iif(entity_type == 'PL', 'raw_db.fund_manager_fitted_curve', 'raw_db.company_fitted_curve'), );
  152. // 数据初始化时将指标存入本地
  153. if(is_save_local == true) {
  154. save_table(tb_entity_nav, iif(entity_type == 'PL', 'mfdb.fund_manager_fitted_curve', 'mfdb.company_fitted_curve'), false);
  155. }
  156. } catch(ex) {
  157. //TODO: Log errors
  158. rt += ex;
  159. }
  160. }
  161. return rt;
  162. }
  163. /*
  164. * 计算并存储基金经理/公司的月度收益及指标
  165. *
  166. * @param entity_date <TABLE>: entity_id, curve_type, strategy, price_date
  167. * @param monthly_returns <TABLE>: entity_id, curve_type, strategy, end_date, price_date, nav, ret
  168. * @param indicator_type <STRING>: PBI, BFI
  169. *
  170. */
  171. def cal_and_save_mc_indicator(entity_type, entity_date, monthly_returns, indicator_type, is_save_local) {
  172. rt = '';
  173. if(!(entity_type IN ['PL', 'CO'] && indicator_type IN ['PBI', 'BFI'])) return rt;
  174. if(entity_date.isVoid() || entity_date.size() == 0) return rt;
  175. d_indicators = cal_mc_monthly_indicators(entity_type, indicator_type, monthly_returns);
  176. if(d_indicators.isVoid() || d_indicators["7"].isVoid()) break;
  177. // cal_mc_monthly_indicators 返回个两重字典,分别对应 curve_type 和不同区间的数据表,将同样区间的数据表(但不同curve_type)合并
  178. // curve_type: 1:私募,4:公募,7:公私募综合
  179. trailing_num = d_indicators["7"].keys().size();
  180. for(k in d_indicators["7"].keys()) {
  181. if(!d_indicators["1"].isVoid())
  182. d_indicators["7"][k].append!(d_indicators["1"][k]);
  183. if(!d_indicators["4"].isVoid())
  184. d_indicators["7"][k].append!(d_indicators["4"][k]);
  185. }
  186. indicators = d_indicators["7"];
  187. d_indicators = null;
  188. //
  189. // 按照 MySQL 建好各表
  190. if(indicator_type == 'PBI') {
  191. entity_info = SELECT entity_id, curve_type, strategy, price_date.temporalParse('yyyy-MM') AS price_date FROM entity_date;
  192. tb_mc_performance = create_mc_performance();
  193. tb_mc_indicator = create_mc_indicator();
  194. tb_mc_risk_stats = create_mc_risk_stats();
  195. tb_mc_riskadjret_stats = create_mc_riskadjret_stats();
  196. tb_mc_style_stats = create_mc_style_stats();
  197. // 写入数据
  198. generate_entity_performance(entity_info, indicators, true, tb_mc_performance, ['curve_type', 'strategy']);
  199. generate_entity_indicator(entity_info, indicators, true, tb_mc_indicator, ['curve_type', 'strategy']);
  200. generate_entity_risk_stats(entity_info, indicators, true, tb_mc_risk_stats, ['curve_type', 'strategy']);
  201. generate_entity_riskadjret_stats(entity_info, indicators, true, tb_mc_riskadjret_stats, ['curve_type', 'strategy']);
  202. generate_entity_style_stats(entity_info, indicators, true, tb_mc_style_stats, ['curve_type', 'strategy']);
  203. if(! tb_mc_performance.isVoid() && tb_mc_performance.size() > 0) {
  204. // save data to MySQL (13s)
  205. try {
  206. chg_columns_for_mysql(tb_mc_performance, iif(entity_type == 'PL', 'manager_id', 'company_id'));
  207. save_and_sync(tb_mc_performance, iif(entity_type == 'PL', 'raw_db.manager_performance', 'raw_db.company_performance'), );
  208. chg_columns_for_mysql(tb_mc_indicator, iif(entity_type == 'PL', 'manager_id', 'company_id'));
  209. save_and_sync(tb_mc_indicator, iif(entity_type == 'PL', 'raw_db.manager_indicator', 'raw_db.company_indicator'), );
  210. chg_columns_for_mysql(tb_mc_risk_stats, iif(entity_type == 'PL', 'manager_id', 'company_id'));
  211. save_and_sync(tb_mc_risk_stats, iif(entity_type == 'PL', 'raw_db.manager_risk_stats', 'raw_db.company_risk_stats'), );
  212. chg_columns_for_mysql(tb_mc_riskadjret_stats, iif(entity_type == 'PL', 'manager_id', 'company_id'));
  213. save_and_sync(tb_mc_riskadjret_stats, iif(entity_type == 'PL', 'raw_db.manager_riskadjret_stats', 'raw_db.company_riskadjret_stats'), );
  214. chg_columns_for_mysql(tb_mc_style_stats, iif(entity_type == 'PL', 'manager_id', 'company_id'));
  215. save_and_sync(tb_mc_style_stats, iif(entity_type == 'PL', 'raw_db.manager_style_stats', 'raw_db.company_style_stats'), );
  216. // 数据初始化时将指标存入本地
  217. if(is_save_local == true) {
  218. save_table(tb_mc_performance, iif(entity_type == 'PL', 'mfdb.manager_performance', 'mfdb.company_performance'), false);
  219. save_table(tb_mc_indicator, iif(entity_type == 'PL', 'mfdb.manager_indicator', 'mfdb.company_indicator'), false);
  220. save_table(tb_mc_risk_stats, iif(entity_type == 'PL', 'mfdb.manager_risk_stats', 'mfdb.company_risk_stats'), false);
  221. save_table(tb_mc_riskadjret_stats, iif(entity_type == 'PL', 'mfdb.manager_riskadjret_stats', 'mfdb.company_riskadjret_stats'), false);
  222. save_table(tb_mc_style_stats, iif(entity_type == 'PL', 'mfdb.manager_style_stats', 'mfdb.company_style_stats'), false);
  223. }
  224. } catch(ex) {
  225. //TODO: Log errors
  226. rt += ex;
  227. }
  228. }
  229. } else {
  230. entity_info = SELECT entity_id, curve_type, strategy, end_date.temporalParse('yyyy-MM') AS end_date, factor_id AS benchmark_id FROM entity_date;
  231. tb_mc_bfi_indicator = create_mc_bfi_indicator();
  232. //tb_mc_bfi_indicator.rename!('factor_id', 'benchmark_id');
  233. generate_entity_bfi_indicator(entity_info, indicators, true, tb_mc_bfi_indicator, ['curve_type', 'strategy']);
  234. if(! tb_mc_bfi_indicator.isVoid() && tb_mc_bfi_indicator.size() > 0) {
  235. // save data to MySQL
  236. try {
  237. chg_columns_for_mysql(tb_mc_bfi_indicator, iif(entity_type == 'PL', 'manager_id', 'company_id'));
  238. 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'), );
  239. // 数据初始化时将指标存入本地
  240. if(is_save_local == true)
  241. save_table(tb_mc_bfi_indicator, iif(entity_type == 'PL', 'mfdb.manager_ty_bfi_bm_indicator', 'mfdb.company_ty_bfi_bm_indicator'), false);
  242. } catch(ex) {
  243. //TODO: Log errors
  244. rt = ex;
  245. }
  246. }
  247. }
  248. return rt;
  249. }
  250. /*
  251. * [定时任务]: 基金经理/公司月净值计算
  252. *
  253. * Example: CalMCNavTask('CO', 2024.11.04);
  254. */
  255. def CalMCNavTask(entity_type, updatetime) {
  256. //updatetime = 2024.11.05;
  257. //entity_type = 'CO';
  258. if(!(entity_type IN ['PL', 'CO'])) return;
  259. is_save_local = iif(updatetime <= get_ini_data_const()['updatetime'], true, false);
  260. // 31 sec 简化起见,不区分curve_type, strategy
  261. if(entity_type == 'PL') {
  262. entity_date = get_manager_list_by_fund_updatetime(updatetime);
  263. entity_date.rename!('manager_id', 'entity_id');
  264. }
  265. else {
  266. entity_date = get_company_list_by_fund_updatetime(updatetime);
  267. entity_date.rename!('company_id', 'entity_id');
  268. }
  269. // 15 sec
  270. cal_and_save_mc_nav(entity_type, entity_date, is_save_local);
  271. entity_date = null;
  272. }
  273. /*
  274. * [定时任务]: 基金经理/公司月收益及指标(含标准及BFI)计算
  275. *
  276. * @param entity_type <STRING>: PL, CO
  277. *
  278. * Example: CalMCIndicatorTask('CO', 2024.11.04);
  279. * CalMCIndicatorTask('PL', 2024.11.04);
  280. */
  281. def CalMCIndicatorTask(entity_type, updatetime) {
  282. // entity_type = 'PL';
  283. // updatetime = 2024.11.01
  284. rt = '';
  285. is_save_local = iif(updatetime <= get_ini_data_const()['updatetime'], true, false);
  286. // 3 sec
  287. entity_date = get_entity_list_by_nav_updatetime(entity_type, NULL, updatetime, true);
  288. i = 0;
  289. batch_size = 1000;
  290. v_entity_id = entity_date.entity_id.distinct();
  291. max_cnt = v_entity_id.size();
  292. ver_old_month = 1900.01M;
  293. do {
  294. // 取完整月净值
  295. tb_entity_date = SELECT entity_id, curve_type, strategy, ver_old_month AS end_date
  296. FROM entity_date WHERE entity_id in v_entity_id[i : min(i + batch_size, max_cnt)];
  297. if(tb_entity_date.isVoid() || tb_entity_date.size() == 0) break;
  298. s_json = tb_entity_date.toStdJson();
  299. tb_nav = get_mc_nav_for_return_calculation('PL', s_json, 0);
  300. v_end_date = tb_nav.end_date.temporalParse('yyyy-MM');
  301. tb_nav.replaceColumn!('end_date', v_end_date);
  302. tb_nav.join!(v_end_date.temporalFormat('yyyy-MM-dd').temporalParse('yyyy-MM-dd').businessMonthEnd() AS price_date);
  303. tb_nav.sortBy!(['entity_id', 'curve_type', 'strategy', 'end_date']);
  304. // 计算月度收益
  305. tb_monthly_ret = SELECT entity_id, curve_type, strategy, end_date, price_date, cumulative_nav AS nav, cumulative_nav.ratios()-1 AS ret
  306. FROM tb_nav
  307. CONTEXT BY entity_id, curve_type, strategy;
  308. // 40+ min
  309. cal_and_save_mc_indicator(entity_type, entity_date, tb_monthly_ret, 'PBI', is_save_local);
  310. i += batch_size;
  311. } while (i < max_cnt);
  312. }
  313. /*
  314. * [定时任务]: 基金经理月BFI指标计算
  315. *
  316. *
  317. * Example: CalManagerBfiIndicatorTask(2024.11.04);
  318. */
  319. def CalManagerBfiIndicatorTask(updatetime) {
  320. // updatetime = 2024.11.01
  321. rt = '';
  322. entity_type = 'PL';
  323. is_save_local = iif(updatetime <= get_ini_data_const()['updatetime'], true, false);
  324. // BFI indicator 计算由 bfi matching 表更新驱动
  325. entity_date = get_mc_bfi_factors(entity_type, NULL, 1990.01M, today().month(), updatetime);
  326. entity_date.join!(entity_date.end_date AS price_date);
  327. i = 0;
  328. batch_size = 1000;
  329. v_entity_id = entity_date.entity_id.distinct();
  330. max_cnt = v_entity_id.size();
  331. do {
  332. // 取完整月收益 1+ min
  333. tb_monthly_ret = get_monthly_ret(entity_type, v_entity_id[i : min(i + batch_size, max_cnt)], 1900.01.01, today(), true);
  334. v_end_date = tb_monthly_ret.end_date.temporalParse('yyyy-MM');
  335. tb_monthly_ret.replaceColumn!('end_date', v_end_date);
  336. UPDATE tb_monthly_ret SET price_date = end_date.temporalFormat('yyyy-MM-dd').temporalParse('yyyy-MM-dd').businessMonthEnd() WHERE price_date IS NULL;
  337. // 40+ min
  338. cal_and_save_mc_indicator(entity_type, entity_date, tb_monthly_ret, 'BFI', is_save_local);
  339. i += batch_size;
  340. } while (i < max_cnt);
  341. }
  342. /*
  343. *
  344. * [定时任务]: 基金经理的BFI MATCHING
  345. *
  346. * it takes 9.5 hours
  347. */
  348. def MatchManagerBFITask(updatetime) {
  349. rt = '';
  350. is_save_local = iif(updatetime <= get_ini_data_const()['updatetime'], true, false);
  351. entity_type = 'PL';
  352. // 31 sec
  353. entity_date = get_mc_performance_by_updatetime(entity_type, updatetime);
  354. if(entity_date.isVoid() || entity_date.size() == 0) return rt;
  355. i = 0;
  356. batch_size = 1000;
  357. max_cnt = entity_date.size();
  358. do {
  359. t_entity_date = entity_date[i : min(max_cnt, i + batch_size)];
  360. // 22 min per 1000 records, way too slow
  361. t_bfi = match_mc_bfi(entity_type, t_entity_date);
  362. t_max_r2 = SELECT entity_id , curve_type, strategy, factor_id.first() AS factor_id, end_date,
  363. string(NULL) AS performance_flag, coe.first() AS coe, r2.first() AS r2, concat(factor_name, ",") AS rz_portrait
  364. FROM ej(t_bfi, get_bfi_index_list(), 'factor_id')
  365. GROUP BY entity_id, curve_type, strategy, end_date;
  366. try {
  367. // 高度怀疑 pf_manager_factor_bfi 表只是中间表,没有用,这里就不存了
  368. // 有效 factors 存到 xxx_factor_bfi_by_category_group 表
  369. chg_columns_for_mysql(t_bfi, 'manager_id');
  370. save_and_sync(t_bfi, 'raw_db.pf_manager_factor_bfi_by_category_group', );
  371. // 有效因子中 R2 最大的因子存 xxx_max_r2
  372. chg_columns_for_mysql(t_max_r2, 'manager_id');
  373. save_and_sync(t_max_r2, 'raw_db.pf_manager_factor_bfi_max_r2', );
  374. } catch (ex) {
  375. //TODO: Log errors
  376. rt += ex;
  377. }
  378. i += batch_size
  379. } while (i < max_cnt);
  380. return rt;
  381. }