task_portfolioPerformance.dos 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574
  1. module fundit::task_portfolioPerformance
  2. use fundit::sqlUtilities;
  3. use fundit::operationDataPuller;
  4. use fundit::performanceDataPuller;
  5. use fundit::portfolioDataPuller;
  6. use fundit::dataSaver;
  7. use fundit::navCalculator;
  8. use fundit::returnCalculator;
  9. use fundit::indicatorCalculator;
  10. /*
  11. * 计算组合历史净值(不存数据库)
  12. *
  13. * @param portfolio_ids <STRING|VECTOR>: 组合IDS,为空时跑全集(但不建议,因为可能会很吃内存)
  14. * @param updatetime <DATETIME>: 持仓证券净值更新时间,忽略时跑全历史
  15. *
  16. * @return <TABLE>: portfolio_id, price_date, ret, nav
  17. *
  18. * Example:calPortfolioNAV([143109, 145041]);
  19. * calPortfolioNAV([143109, 145041], 2024.10.28);
  20. */
  21. def calPortfolioNAV(portfolio_ids, updatetime=1900.01.01) {
  22. // portfolio_ids=[364743, 364744];
  23. // updatetime=1900.01.01;
  24. port_info = get_portfolio_list_by_fund_nav_updatetime(portfolio_ids, updatetime, true);
  25. tb_nav = cal_portfolio_nav(port_info);
  26. return tb_nav;
  27. }
  28. /*
  29. * 计算组合历史收益和指标(不存数据库)
  30. *
  31. * @param navs <TABLE>: NEED COLUMNS portfolio_id, price_date, ret, nav
  32. *
  33. * @return <DICTIONARY>:
  34. *
  35. * Example:calPortfolioPerformance(calPortfolioNAV([143109, 145041]));
  36. */
  37. def calPortfolioPerformance(navs) {
  38. if(navs.isVoid() || navs.size() == 0) return;
  39. tb_navs = navs;
  40. tb_navs.rename!(['portfolio_id'], ['entity_id']);
  41. port_ids = EXEC DISTINCT entity_id from tb_navs;
  42. port_info = get_entity_info('PF', port_ids);
  43. // 这个函数会根据情况加入成立日当月的初始净值,比直接用navs表中可能带的ret更方便
  44. tb_navs.rename!('nav', 'cumulative_nav');
  45. tb_month_ret = cal_monthly_returns_by_nav(port_info, tb_navs);
  46. tb_month_ret.rename!('cumulative_nav', 'nav');
  47. indicators = cal_monthly_indicators('PF', 'PBI', tb_month_ret);
  48. return indicators;
  49. }
  50. /*
  51. * 计算组合历史收益和指标(不存数据库)
  52. *
  53. * @param navs <TABLE>: NEED COLUMNS entity_id, price_date, ret, nav
  54. *
  55. * @return <DICTIONARY>:
  56. *
  57. * Example:calEntityPerformance('PF', calPortfolioNAV([143109, 145041]));
  58. */
  59. def calEntityPerformance(entity_type, navs) {
  60. if(navs.isVoid() || navs.size() == 0) return;
  61. tb_navs = navs;
  62. //tb_navs.rename!(['portfolio_id'], ['entity_id']);
  63. entity_ids = EXEC DISTINCT entity_id from tb_navs;
  64. entity_info = get_entity_info(entity_type, entity_ids);
  65. // 这个函数会根据情况加入成立日当月的初始净值,比直接用navs表中可能带的ret更方便
  66. tb_navs.rename!('nav', 'cumulative_nav');
  67. tb_month_ret = cal_monthly_returns_by_nav(entity_info, tb_navs);
  68. tb_month_ret.rename!('cumulative_nav', 'nav');
  69. indicators = cal_monthly_indicators(entity_type, 'PBI', tb_month_ret);
  70. return indicators;
  71. }
  72. /*
  73. * 计算组合净值并存入数据库
  74. *
  75. */
  76. def cal_and_save_portfolio_nav(cal_portfolio_info, is_save_local) {
  77. rt = '';
  78. // 准备类似MySQL结构的数据表
  79. tb_portfolio_nav = create_entity_nav(true);
  80. // 分批跑
  81. i = 0;
  82. batch_size = 1000;
  83. all_portfolio_id = EXEC DISTINCT portfolio_id FROM cal_portfolio_info;
  84. do { // 先把净值算出来存入数据库,落袋为安
  85. portfolio_info = SELECT * FROM cal_portfolio_info
  86. WHERE portfolio_id IN all_portfolio_id[i : min(all_portfolio_id.size(), i+batch_size)];
  87. if(portfolio_info.isVoid() || portfolio_info.size() == 0) break;
  88. // 30 sec per 1000 portfolios
  89. tb_ret = cal_portfolio_nav(portfolio_info);
  90. INSERT INTO tb_portfolio_nav SELECT entity_id, price_date, nav FROM tb_ret;
  91. i += batch_size;
  92. } while (i <= cal_portfolio_info.size());
  93. if(! tb_portfolio_nav.isVoid() && tb_portfolio_nav.size() > 0) {
  94. // save data to MySQL (12 sec)
  95. try {
  96. tb_portfolio_nav.rename!('entity_id', 'portfolio_id');
  97. save_and_sync(tb_portfolio_nav, 'raw_db.pf_portfolio_nav', 'raw_db.pf_portfolio_nav');
  98. // 数据初始化时将指标存入本地
  99. if(is_save_local == true) {
  100. save_table(tb_portfolio_nav, 'pfdb.pf_portfolio_nav', false);
  101. }
  102. } catch(ex) {
  103. //TODO: Log errors
  104. rt = ex;
  105. }
  106. }
  107. return rt;
  108. }
  109. /*
  110. * 通用计算标准指标并存入数据库
  111. *
  112. * @param entity_type <STRING>
  113. * @param cal_entity_info <TABLE>: [COLUMNS] entity_id, start_cal_date
  114. * @param is_save_local <BOOL>
  115. *
  116. */
  117. def cal_and_save_entity_indicators(entity_type, cal_entity_info, is_save_local) {
  118. // cal_entity_info = tb_cal_factors
  119. // entity_type = 'FA'
  120. rt = '';
  121. is_id_interger = iif(entity_type == 'PF', true, false);
  122. // 准备类似MySQL结构的数据表
  123. tb_entity_performance = create_entity_performance(is_id_interger);
  124. tb_entity_indicator = create_entity_indicator(is_id_interger);
  125. tb_entity_risk_stats = create_entity_risk_stats(is_id_interger);
  126. tb_entity_riskadjret_stats = create_entity_riskadjret_stats(is_id_interger);
  127. tb_entity_style_stats = create_entity_style_stats(is_id_interger);
  128. tb_entity_performance_weekly = create_entity_performance_weekly(is_id_interger);
  129. tb_entity_latest_performance = create_entity_latest_performance(is_id_interger);
  130. // 分批跑
  131. i = 0;
  132. batch_size = 1000;
  133. all_entity_id = EXEC DISTINCT entity_id FROM cal_entity_info;
  134. do {
  135. cal_entity = SELECT * FROM cal_entity_info
  136. WHERE entity_id IN all_entity_id[i : min(all_entity_id.size(), i+batch_size)];
  137. if(cal_entity.isVoid() || cal_entity.size() == 0) break;
  138. // 取数据库月度净值及前值 5 sec
  139. s_json = (SELECT entity_id, 1900.01.01 AS price_date FROM cal_entity GROUP BY entity_id).rename!('entity_id', 'sec_id').toStdJson();
  140. tb_monthly_nav = get_nav_for_return_calculation(entity_type, 'm', s_json);
  141. // 把组合 entity id 字段从字符串换回整型,不然后面Join table的时候会出错
  142. if(entity_type=='PF') {
  143. v_entity_id = tb_monthly_nav.sec_id$INT;
  144. tb_monthly_nav.replaceColumn!('sec_id', v_entity_id);
  145. }
  146. tb_monthly_nav.dropColumns!('nav').rename!(['sec_id', 'cumulative_nav'], ['entity_id', 'nav']);
  147. // 计算各标准指标
  148. indicators = calEntityPerformance(entity_type, tb_monthly_nav);
  149. // 仿照MySQL的表结构准备好记录 (1s)
  150. entity_info = (SELECT entity_id, start_cal_date.min() AS price_date FROM cal_entity GROUP BY entity_id);
  151. generate_entity_performance(entity_info, indicators, true, tb_entity_performance);
  152. generate_entity_indicator(entity_info, indicators, true, tb_entity_indicator);
  153. generate_entity_risk_stats(entity_info, indicators, true, tb_entity_risk_stats);
  154. generate_entity_riskadjret_stats(entity_info, indicators, true, tb_entity_riskadjret_stats);
  155. generate_entity_style_stats(entity_info, indicators, true, tb_entity_style_stats);
  156. // 计算周收益 (49s)
  157. entity_info = SELECT * FROM ej(entity_info, get_entity_info(entity_type, all_entity_id[i : min(all_entity_id.size(), i+batch_size)]), 'entity_id')
  158. rets_w = cal_weekly_returns(entity_type, entity_info);
  159. if(! rets_w.isVoid() && rets_w.size() > 0) {
  160. // 把 entity id 字段从字符串换回整型,不然后面Join table的时候会出错
  161. if(entity_type == 'PF') {
  162. v_entity_id = rets_w.entity_id$INT;
  163. rets_w.replaceColumn!('entity_id', v_entity_id);
  164. }
  165. generate_entity_performance_weekly(entity_info, rets_w, true, tb_entity_performance_weekly);
  166. }
  167. // 计算最新收益 (23s)
  168. perf_latest = cal_latest_performance(entity_type, entity_info, true);
  169. if(! perf_latest.isVoid() && perf_latest.size() > 0) {
  170. generate_entity_latest_performance(entity_info, perf_latest, true, tb_entity_latest_performance);
  171. }
  172. i += batch_size;
  173. } while (i <= cal_entity_info.size());
  174. if(! tb_entity_performance.isVoid() && tb_entity_performance.size() > 0) {
  175. // save data to MySQL
  176. try {
  177. des = get_performance_table_description(entity_type)[0];
  178. chg_columns_for_mysql(tb_entity_performance, des.sec_id_col);
  179. tb_entity_performance.rename!('cumulative_nav', des.cumulative_nav_col);
  180. save_and_sync(tb_entity_performance, des.table_name.strReplace('pfdb', 'raw_db').strReplace('mfdb', 'raw_db'), );
  181. if(is_save_local == true) save_table(tb_entity_performance, des.table_name, false);
  182. des = get_indicator_table_description(entity_type)[0];
  183. chg_columns_for_mysql(tb_entity_indicator, des.sec_id_col);
  184. save_and_sync(tb_entity_indicator, des.table_name.strReplace('pfdb', 'raw_db').strReplace('mfdb', 'raw_db'), );
  185. if(is_save_local == true) save_table(tb_entity_indicator, des.table_name, false);
  186. des = get_risk_stats_table_description(entity_type)[0];
  187. chg_columns_for_mysql(tb_entity_risk_stats, des.sec_id_col);
  188. save_and_sync(tb_entity_risk_stats, des.table_name.strReplace('pfdb', 'raw_db').strReplace('mfdb', 'raw_db'), );
  189. if(is_save_local == true) save_table(tb_entity_risk_stats, des.table_name, false);
  190. des = get_riskadjret_stats_table_description(entity_type)[0];
  191. chg_columns_for_mysql(tb_entity_riskadjret_stats, des.sec_id_col);
  192. save_and_sync(tb_entity_riskadjret_stats, des.table_name.strReplace('pfdb', 'raw_db').strReplace('mfdb', 'raw_db'), );
  193. if(is_save_local == true) save_table(tb_entity_riskadjret_stats, des.table_name, false);
  194. des = get_capture_style_table_description(entity_type)[0];
  195. chg_columns_for_mysql(tb_entity_style_stats, des.sec_id_col);
  196. save_and_sync(tb_entity_style_stats, des.table_name.strReplace('pfdb', 'raw_db').strReplace('mfdb', 'raw_db'), );
  197. if(is_save_local == true) save_table(tb_entity_style_stats, des.table_name, false);
  198. des = get_performance_weekly_table_description(entity_type)[0];
  199. tb_entity_performance_weekly.rename!('cumulative_nav', des.cumulative_nav_col);
  200. save_and_sync(tb_entity_performance_weekly, des.table_name.strReplace('pfdb', 'raw_db').strReplace('mfdb', 'raw_db'), );
  201. if(is_save_local == true) save_table(tb_entity_performance_weekly, des.table_name, false);
  202. des = get_latest_performance_table_description(entity_type)[0];
  203. tb_entity_latest_performance.rename!('cumulative_nav', des.cumulative_nav_col);
  204. save_and_sync(tb_entity_latest_performance, des.table_name.strReplace('pfdb', 'raw_db').strReplace('mfdb', 'raw_db'), );
  205. if(is_save_local == true) save_table(tb_entity_latest_performance, des.table_name, false);
  206. } catch(ex) {
  207. //TODO: Log errors
  208. rt = ex;
  209. }
  210. }
  211. return rt;
  212. }
  213. /*
  214. * [定时任务]批量计算组合净值、收益及指标
  215. *
  216. * @param updatetime <DATETIME>: 持仓证券净值更新时间,忽略或传入1989.01.01及更早的日期被认为在做数据初始化
  217. *
  218. *
  219. * Example: CalPortfolioPerformanceTask(2024.10.28);
  220. * CalPortfolioPerformanceTask(1989.01.01); -- 【初始化专用】 (45min)
  221. */
  222. def CalPortfolioPerformanceTask(updatetime) {
  223. rt = '';
  224. // 3 min
  225. tb_cal_ports = get_portfolio_list_by_fund_nav_updatetime(NULL, updatetime, true);
  226. if(tb_cal_ports.isVoid() || tb_cal_ports.size() == 0) return;
  227. is_save_local = iif(updatetime <= get_ini_data_const()['date'], true, false);
  228. // 26 min
  229. rt = cal_and_save_portfolio_nav(tb_cal_ports, is_save_local);
  230. // 9 min
  231. tb_cal_ports.rename!('portfolio_id', 'entity_id');
  232. rt = rt + '; ' + cal_and_save_entity_indicators('PF', tb_cal_ports, is_save_local);
  233. return rt;
  234. }
  235. /*
  236. * 批量计算BFI因子净值
  237. *
  238. * Example: cal_and_save_factor_nav(get_bfi_factor_list_by_index_nav_updatetime(['FA00000VMJ'], updatetime, true);, false);
  239. *
  240. */
  241. def cal_and_save_factor_nav(cal_factor_info, is_save_local) {
  242. ret = ''
  243. t_factor_value = table(100:0, ['factor_id', 'price_date', 'factor_value'], [SYMBOL, DATE, DOUBLE]);
  244. // 因子个数有限,用循环更简便
  245. for(factor in cal_factor_info) {
  246. v_factor_id = array(STRING, 0).append!(factor.factor_id);
  247. // 取因子成分指数
  248. tb_holdings = get_fixed_weight_portfolio_holding('FA', v_factor_id);
  249. UPDATE tb_holdings SET first_cal_date = first_cal_date, latest_cal_date = latest_cal_date
  250. FROM ej(tb_holdings, cal_factor_info, 'entity_id', 'factor_id');
  251. s_json = (SELECT sec_id, first_cal_date.min() AS price_date FROM tb_holdings GROUP BY sec_id).toStdJson();
  252. // 取含前值的成分指数点位
  253. tb_nav = get_nav_for_return_calculation('MI', 'd', s_json).sortBy!(['sec_id', 'price_date'], [1, 1]);
  254. // 计算每期收益
  255. UPDATE tb_nav SET ret = cumulative_nav.ratios() - 1 CONTEXT BY sec_id;
  256. t = SELECT h.entity_id, n.price_date, h.sec_id, n.ret, h.weight/100 AS weight
  257. FROM tb_holdings AS h
  258. INNER JOIN tb_nav AS n ON h.sec_id = n.sec_id
  259. ORDER BY h.entity_id, h.sec_id, n.price_date;
  260. t_factor = SELECT factor_id AS entity_id, first_cal_date, latest_cal_date FROM cal_factor_info WHERE factor_id = factor.factor_id;
  261. t_tmp = cal_nav_by_return('FA', t_factor, t);
  262. if(!t_tmp.isVoid() && t_tmp.size() > 0) {
  263. INSERT INTO t_factor_value
  264. SELECT entity_id AS factor_id, price_date, nav AS factor_value FROM t_tmp;
  265. }
  266. }
  267. if(! t_factor_value.isVoid() && t_factor_value.size() > 0) {
  268. save_and_sync(t_factor_value, 'raw_db.cm_factor_value', 'raw_db.cm_factor_value');
  269. if(is_save_local == true) {
  270. save_table(t_factor_value, 'pfdb.cm_factor_value', false);
  271. }
  272. }
  273. }
  274. /*
  275. * 计算基于指数组合的因子日收益
  276. *
  277. *
  278. */
  279. def cal_and_save_synthesis_factor_nav(updatetime, is_save_local) {
  280. // factor_type = 5: 根据成分指数净值更新日期,取有影响的因子
  281. tb_cal_factors = get_bfi_factor_list_by_index_nav_updatetime(NULL, updatetime, true);
  282. if(tb_cal_factors.isVoid() || tb_cal_factors.size() == 0) return null;
  283. // 26 min
  284. cal_and_save_factor_nav(tb_cal_factors, is_save_local);
  285. return tb_cal_factors;
  286. }
  287. /*
  288. * 计算基于指数移动收益的因子日收益
  289. *
  290. * TODO: 算法暂时与Java相近,虽然看起来比较可疑
  291. */
  292. def cal_moving_avg_factor_ret(updatetime) {
  293. t_factor_return = table(100:0, ['factor', 'price_date', 'ret'], [SYMBOL, DATE, DOUBLE]);
  294. index_ids = ['IN0000007N']; // 中证全指
  295. factor_momentum = 'FA000000MT'; // 动量因子
  296. factor_reverse = 'FA000000RV'; // 反转因子
  297. t_index_date = get_entity_list_by_nav_updatetime('MI', index_ids, updatetime, true);
  298. if(t_index_date.isVoid() || t_index_date.size() == 0) return t_factor_return;
  299. // 倒着多取1年的净值
  300. s_json = (SELECT entity_id AS sec_id, price_date.temporalAdd(-1y) AS price_date FROM t_index_date).toStdJson();
  301. t_index_nav = get_nav_for_return_calculation('MI', 'd', s_json, pre_nav_incld=2);
  302. if(t_index_nav.isVoid() || t_index_nav.size() == 0) return t_factor_return;
  303. // 取上交所交易日历
  304. v_trade_day = getMarketCalendar('SSE', t_index_nav.price_date.min(), today());
  305. t_index_nav = SELECT * FROM t_index_nav WHERE price_date in v_trade_day;
  306. t_index_ret = SELECT sec_id AS entity_id, price_date, cumulative_nav.ratios()-1 AS ret FROM t_index_nav.sortBy!(['sec_id', 'price_date']);
  307. // 反转因子:中证全指过去1个月(Java 是20日)的平均日收益
  308. t_ret = SELECT factor_reverse AS factor_id , rt.price_date, ret
  309. FROM (
  310. SELECT entity_id, price_date, tmavg(price_date, ret, 1M) AS ret
  311. FROM t_index_ret CONTEXT BY rt.entity_id ) rt
  312. INNER JOIN t_index_date dt ON rt.entity_id = dt.entity_id
  313. WHERE rt.price_date >= dt.price_date;
  314. t_factor_return.tableInsert(t_ret);
  315. // 动量因子:中证全指过去1年 (Java是220个交易日中200个交易日?没看明白)的平均日收益
  316. t_ret = SELECT factor_momentum AS factor_id, rt.price_date, ret
  317. FROM (
  318. SELECT entity_id, price_date, tmavg(price_date, ret, 1y) AS ret
  319. FROM t_index_ret CONTEXT BY rt.entity_id ) rt
  320. INNER JOIN t_index_date dt ON rt.entity_id = dt.entity_id
  321. WHERE rt.price_date >= dt.price_date;
  322. t_factor_return.tableInsert(t_ret);
  323. return t_factor_return;
  324. }
  325. /*
  326. * 计算基于债券的因子日收益
  327. *
  328. * TODO: 算法看起来比较可疑,并且债指2022-03以后就不再有 duration, convexity 数据
  329. */
  330. def cal_bond_factor_ret(updatetime) {
  331. t_factor_return = table(100:0, ['factor', 'price_date', 'ret'], [SYMBOL, DATE, DOUBLE]);
  332. index_ids = ['IN0000007A', 'IN0000008I', 'IN0000008B', 'IN0000008C']; // 中证国债、长期国债、中高信用、中低信用
  333. factor_term = 'FA000000ST'; // 期限因子
  334. factor_credit_spread = 'FA000000CD'; // 信用利差因子
  335. factor_hybond = 'FA00000HYB'; // 高收益债因子
  336. t_index_date = get_entity_list_by_nav_updatetime('MI', index_ids, updatetime, true);
  337. if(t_index_date.isVoid() || t_index_date.size() == 0) return t_factor_return;
  338. // 倒着多取1年的净值
  339. s_json = (SELECT entity_id AS sec_id, price_date.temporalAdd(-1y) AS price_date FROM t_index_date).toStdJson();
  340. t_index_nav = get_nav_for_return_calculation('MI', 'd', s_json, pre_nav_incld=2);
  341. if(t_index_nav.isVoid() || t_index_nav.size() == 0) return t_factor_return;
  342. // 取上交所交易日历
  343. v_trade_day = getMarketCalendar('SSE', t_index_nav.price_date.min(), today());
  344. t_index_nav = SELECT * FROM t_index_nav WHERE price_date in v_trade_day;
  345. t_index_ret = SELECT sec_id AS entity_id, price_date, cumulative_nav.ratios()-1 AS ret FROM t_index_nav.sortBy!(['sec_id', 'price_date']);
  346. // TODO: ret = R_1 * W_1 - R_2 * W_2; 其中: R_x 代表收益,W_x 代表权重,D_x 代表久期;
  347. // W_1 = D_2 /(D_2 - D_1), W_2 = D_1/(D_2 - D_1) ??? JAVA 就是这个权重逻辑
  348. return t_factor_return;
  349. }
  350. /*
  351. * [定时任务]批量计算因子净值、收益及指标
  352. *
  353. * @param updatetime <DATETIME>: 成分指数净值更新时间,忽略或传入1989.01.01及更早的日期被认为在做数据初始化
  354. *
  355. * TODO: 非BFI的因子还未涉及,需要参考 PerformanceAttributionFactorServiceImpl
  356. *
  357. *
  358. * Example: CalFactorPerformanceTask(2024.10.28);
  359. * CalFactorPerformanceTask(1989.01.01); -- 【初始化专用】 (1.3 min)
  360. */
  361. def CalFactorPerformanceTask(updatetime) {
  362. //updatetime=2024.10.28
  363. rt = '';
  364. is_save_local = iif(updatetime <= get_ini_data_const()['date'], true, false);
  365. tb_cal_factors = cal_and_save_synthesis_factor_nav(updatetime, is_save_local);
  366. // 9 min
  367. tb_cal_factors.rename!(['factor_id', 'first_cal_date', 'latest_cal_date'], ['entity_id', 'start_cal_date', 'end_cal_date']);
  368. rt = rt + '; ' + cal_and_save_entity_indicators('FA', tb_cal_factors, is_save_local);
  369. return rt;
  370. }
  371. /*
  372. * [定时任务]批量计算各类平均指数的点位
  373. *
  374. * @param updatetime <DATETIME>: 成分指数净值更新时间,忽略或传入1989.01.01及更早的日期被认为在做数据初始化
  375. *
  376. * TODO: M* category average, manager, company
  377. *
  378. *
  379. * Example: CalCategoryAverageNavTask(2024.11.01); (22min)
  380. * CalCategoryAverageNavTask(1989.01.01); -- 【初始化专用】
  381. */
  382. def CalCategoryAverageNavTask(updatetime) {
  383. rt = '';
  384. v_category_type = ['strategy', 'substrategy', 'bfi'];
  385. // 取有周收益有更新的最早日期
  386. date_hedge_fund = get_oldest_date_by_weekly_return_updatetime('PF', updatetime, true);
  387. date_mutual_fund = get_oldest_date_by_weekly_return_updatetime('MF', updatetime, true);
  388. if(date_hedge_fund.isNull() && date_mutual_fund.isNull()) return rt;
  389. is_save_local = iif(updatetime <= get_ini_data_const()['date'], true, false);
  390. //
  391. for(category_type in v_category_type) {
  392. oldest_date = min([date_hedge_fund, date_mutual_fund]);
  393. // it could take mysql a few minutes to get results
  394. t_ret = get_category_avg_weekly_return(category_type, oldest_date, 5, 30, true);
  395. if(t_ret.isVoid() || t_ret.size() == 0) continue;
  396. t_ret.rename!('index_id', 'entity_id');
  397. t_tmp = cal_entity_nav_by_return('FI', t_ret, 'w');
  398. if(! t_tmp.isVoid() && t_tmp.size() > 0) {
  399. t_index_value = SELECT entity_id AS index_id, price_date, nav AS index_value, incl_cal_count AS incl_cal_fund_count, total_cnt AS total_fund_count
  400. FROM ej(t_tmp, t_ret, ['entity_id', 'price_date']);
  401. save_and_sync(t_index_value, 'raw_db.indexes_ty_index', );
  402. if(is_save_local == true) {
  403. save_table(t_index_value, 'mfdb.indexes_ty_index', false);
  404. }
  405. }
  406. }
  407. return rt;
  408. }