rbsaCalculator.dos 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. module fundit::rbsaCalculator
  2. use fundit::performanceDataPuller
  3. use fundit::operationDataPuller
  4. /*
  5. * RBSA 计算
  6. * @param: ret <TABLE>: historical return (double) vector which contains the same number of return as index
  7. * index_ret <TABLE>: historical index return table which each row is an index
  8. * is_long <BOOL>: true - long-only, false - long-short
  9. * @return: table
  10. *
  11. * Create 20240703 模仿python代码在Dolphin中实现,具体计算逻辑完全不懂 Joey
  12. * 原代码见: http://gogs.fundit.cn/FundIt/FinanceCalcPython/src/dev36/pf_scical/v1/calc_rbsa_use_osqp.py
  13. * Python官方示例见:https://osqp.org/docs/examples/least-squares.html
  14. * Dolphin官方示例见:https://docs.dolphindb.cn/zh/funcs/o/osqp.html
  15. *
  16. */
  17. defg cal_rbsa(ret, index_ret, is_long) {
  18. // 窗口长度
  19. m = ret.size()
  20. // 指数个数
  21. n = index_ret.cols()
  22. P0 = matrix(float, n, m+n)
  23. P1 = concatMatrix([matrix(float, m, n), eye(m)])
  24. P = concatMatrix([P0, P1], false)
  25. q = array(float, m+n, (m+n)*10, 0)
  26. A0 = concatMatrix( [matrix(index_ret), -eye(m)])
  27. A1 = concatMatrix( [matrix(take(1, n)).transpose(), matrix(float, 1, m)])
  28. A2 = concatMatrix( [eye(n), matrix(float, n, m)])
  29. A = concatMatrix( [A0, A1, A2], false)
  30. // join 1 是为了限制所有权重加总为100%
  31. // 下限
  32. lb =(ret join 1) join array(float, n, n*10, iif(is_long == true, 0, -2))
  33. // 上限
  34. ub=(ret join 1) join array(float, n, n*10, iif(is_long == true, 1, 2))
  35. res = osqp( q, P, A, lb, ub)
  36. return res
  37. }
  38. /*
  39. * 滚动 rbsa
  40. * @param ret <TABLE>: return table, at least with "effective_date" and "ret" as columns
  41. * @param index_ret <TABLE>: index return table, with "effective_date" and all index ids as columns
  42. * @param is_long <BOOL>: boolean. true means weightings could be negative values
  43. * @param window <INT>: number of return in a window
  44. * @param step <INT>: rolling step
  45. *
  46. * TODO: use rolling()
  47. *
  48. * @return <TABLE> with "effective_date", "index_id" and "weights" columns
  49. */
  50. def cal_rolling_rbsa(ret, index_ret, is_long, window, step) {
  51. // 找到所有指数全有数据的最早日期
  52. v_start_date = EXEC effective_date.max() AS start_date
  53. FROM (SELECT entity_id, effective_date.min() AS effective_date FROM index_ret WHERE ret IS NOT NULL GROUP BY entity_id);
  54. m_index_ret = SELECT ret FROM index_ret WHERE effective_date >= v_start_date PIVOT BY effective_date, entity_id;
  55. t = SELECT * FROM ej(ret, m_index_ret, 'effective_date') ORDER BY ret.effective_date;
  56. t.nullFill!(0)
  57. // not sure why this doesn't work
  58. // rolling(cal_rbsa{,,is_long}, (t.ret, t.slice(, ret.cols():).matrix()), window, step)
  59. // 指数个数
  60. n = m_index_ret.cols() - 1
  61. // 计算起始位置
  62. i = (t.size() - window) % step
  63. // 运行rbsa计算次数
  64. cnt = (t.size() - i - window) / step;
  65. tb = table(max(cnt,1):0, ["effective_date", "price_date", "index_id", "weights", "alpha", "r2", "adj_r2"], [STRING, DATE, STRING, DOUBLE, DOUBLE, DOUBLE, DOUBLE]);
  66. if(t.size() >= max(window, step) && cnt > 0) {
  67. do {
  68. alpha = 0;
  69. r2 = 0;
  70. adj_r2 = 0;
  71. v_ret = t.ret[i:(i+window)];
  72. t_index_ret = t.slice( i:(i+window), ret.cols(): );
  73. // 传入window个收益
  74. res = cal_rbsa(v_ret, t_index_ret, is_long);
  75. if(res[0] == 'solved') {
  76. m_predict_ret = t_index_ret.matrix() ** res[1][0:n];
  77. alpha = v_ret.mean() - m_predict_ret.mean();
  78. SSR = sum2(m_predict_ret - v_ret.mean());
  79. SST = sum2(v_ret - v_ret.mean());
  80. if(SST == 0) {
  81. // 当SST=0, 先计算SSE再计算SST
  82. SSE = sum2(v_ret - m_predict_ret);
  83. SST = SSE + SSR;
  84. }
  85. if(SST != 0) {
  86. r2 = SSR/SST;
  87. adj_r2 = 1 - (1 - r2) * (window - 1) / (window - n - 1);
  88. }
  89. for(j in 1..n) {
  90. tb.tableInsert(t.effective_date[i+window-1], t.price_date[i+window-1], m_index_ret.colNames()[j], res[1][j-1].round(4), alpha, r2, adj_r2);
  91. }
  92. }
  93. // 往前推进step个收益
  94. i = i + step
  95. cnt -= 1
  96. } while( cnt >= 0)
  97. } else {
  98. tb.tableInsert(null, "error", "The number of returns must be greater than window size.")
  99. }
  100. return tb
  101. }
  102. /*
  103. * 计算单基金或组合的RBSA
  104. *
  105. * @param entity_type <STRING>: 目标基金/组合的类型
  106. * @param entity_id <STRING>: 目标基金/组合的ID
  107. * @param index_ids <VECTOR>: 基准指数IDs
  108. * @param freq <STRING>: m, w, d
  109. * @param start_day <DATE>
  110. * @param end_day <DATE>
  111. * @param is_long <BOOL>: 是否只考虑纯多头
  112. * @param window <INT>: 窗口(必须多于基准指数个数)
  113. * @param step <INT>: 步长
  114. *
  115. * @return <TABLE>: entity_id, effective_date, price_date, index_id, weights, alternative_id, level, alpha, r2, adj_r2
  116. *
  117. * TODO: 数字与界面和数据库都对不上
  118. *
  119. * Example: cal_entity_RBSA('MF', 'MF00003PW1', ['IN00000008', 'IN00000077', 'IN0000007G', 'IN0000009M'], 'w', 1900.01.01, 2024.11.15, true, 24, 24);
  120. * cal_entity_RBSA('PF', 166002, ['FA00000VML', 'FA00000VMM', 'FA00000VMN', 'FA00000VMO', 'IN0000007G'], 'w', 2020.01.01, 2024.11.08, true, 24, 24);
  121. * cal_entity_RBSA('MF', 'MF000200KQ', ['IN00000008', 'IN00000077', 'IN0000007G', 'IN0000009M'], 'w', 1900.01.01, 2024.11.16, true, 24, 24);
  122. */
  123. def cal_entity_RBSA(entity_type, entity_id, index_ids, freq='w', start_day=1900.01.01, end_day=2099.12.31, is_long=true, window=24, step=24) {
  124. // entity_type='MF'
  125. // entity_id= 'MF00003PW1'
  126. // index_ids=['IN00000008', 'IN00000077', 'IN0000007G', 'IN0000009M']
  127. // freq='w'
  128. // start_day=2001.01.19
  129. // end_day=2024.11.16
  130. // is_long=true
  131. // window=48
  132. // step=13
  133. tb_result = table(100:0, ["entity_id", "effective_date", "index_id", "weights", "alpha", "r2", "adj_r2"],
  134. [iif(entity_type=='PF', INT, STRING), STRING, STRING, DOUBLE, DOUBLE, DOUBLE, DOUBLE]);
  135. v_entity = array(iif(entity_type=='PF', INT, STRING));
  136. v_entity.append!(entity_id);
  137. entity_ret = get_entity_return(entity_type, v_entity, freq, start_day, end_day, true);
  138. // 数据长度不够,按照顺序依次分别用母基金(4), 指数(3)的数据来代替
  139. level = 1
  140. alternative_id = NULL;
  141. if(entity_ret.isVoid() || entity_ret.size() < window) {
  142. if(entity_type IN ['MF', 'HF']) {
  143. fund_info = get_fund_info(v_entity);
  144. p_fund_id = fund_info.p_fund_id[0];
  145. primary_benchmark_id = fund_info.benchmark_id[0];
  146. if(p_fund_id != NULL) {
  147. entity_ret = get_entity_return(entity_type, v_entity.replace(entity_id, p_fund_id) , freq, start_day, end_day, true);
  148. alternative_id = p_fund_id;
  149. level = 4;
  150. } else if(primary_benchmark_id != NULL) {
  151. entity_ret = get_entity_return(entity_type, v_entity.replace(entity_id, primary_benchmark_id) , freq, start_day, end_day, true);
  152. alternative_id = primary_benchmark_id;
  153. level = 3;
  154. } else {
  155. return tb_result;
  156. }
  157. } else if(entity_type == 'PF'){
  158. portfolio_info = get_portfolio_info(v_entity);
  159. primary_benchmark_id = portfolio_info.benchmark_id[0];
  160. if(primary_benchmark_id != NULL) {
  161. entity_ret = get_entity_return(entity_type, v_entity.replace(entity_id, primary_benchmark_id) , freq, start_day, end_day, true);
  162. alternative_id = primary_benchmark_id;
  163. level = 3;
  164. } else
  165. return tb_result;
  166. }
  167. }
  168. // 因为用来做基准指数的可能是指数、因子、基金等等任何时间序列数据,所以不用填 entity_type
  169. index_ret = get_entity_return(NULL, index_ids, freq, start_day, end_day, true);
  170. if(index_ret.isVoid() || index_ret.size() == 0) return tb_result;
  171. tb_result = SELECT entity_id, effective_date, price_date, index_id, weights, alternative_id, level, alpha, r2, adj_r2
  172. FROM cal_rolling_rbsa(entity_ret, index_ret, is_long, window, step);
  173. return tb_result;
  174. }