fundCalculator.dos 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. module fundit::fundCalculator
  2. /*
  3. * RBSA 计算
  4. * @param: ret: historical return (double) vector which contains the same number of return as index
  5. * index_ret: historical index returen matrix which each row is an index
  6. * isLongShort: bool
  7. * @return: table
  8. *
  9. * Create 20240703 模仿python代码在Dolphin中实现,具体计算逻辑完全不懂 Joey
  10. * 原代码见: http://gogs.fundit.cn/FundIt/FinanceCalcPython/src/dev36/pf_scical/v1/calc_rbsa_use_osqp.py
  11. * Python官方示例见:https://osqp.org/docs/examples/least-squares.html
  12. * Dolphin官方示例见:https://docs.dolphindb.cn/zh/funcs/o/osqp.html
  13. * 取数据脚本:SELECT fund_id, GROUP_CONCAT(ret_1w SEPARATOR ' ')
  14. * FROM mfdb.`fund_performance_weekly`
  15. * WHERE fund_id IN ( 'MF00003PW1', 'IN00000008', 'IN00000077', 'IN0000009M', 'IN0000007G')
  16. * AND year_week BETWEEN 202411 AND 202422
  17. * GROUP BY fund_id
  18. *
  19. */
  20. def cal_rbsa(ret, index_ret, isLongShort) {
  21. // 窗口长度
  22. m = ret.size()
  23. // 指数个数
  24. n = index_ret.cols()
  25. P0 = matrix(float, n, m+n)
  26. P1 = concatMatrix([matrix(float, m, n), eye(m)])
  27. P = concatMatrix([P0, P1], false)
  28. q = array(float, m+n, (m+n)*10, 0)
  29. A0 = concatMatrix( [matrix(index_ret), -eye(m)])
  30. A1 = concatMatrix( [matrix(take(1, n)).transpose(), matrix(float, 1, m)])
  31. A2 = concatMatrix( [eye(n), matrix(float, n, m)])
  32. A = concatMatrix( [A0, A1, A2], false)
  33. // join 1 是为了限制所有权重加总为100%
  34. // 下限
  35. lb =(ret join 1) join array(float, n, n*10, iif(isLongShort == false, 0, -2))
  36. // 上限
  37. ub=(ret join 1) join array(float, n, n*10, iif(isLongShort == false, 1, 2))
  38. res = osqp( q, P, A, lb, ub)
  39. return res
  40. }
  41. /*
  42. * 滚动 rbsa
  43. * @param: ret: return table, at least with "date" and "ret" as columns
  44. * index_ret: index return pivot table, with "date" and all index ids as columns
  45. * isLongShort: boolean. true means weightings could be negative values
  46. * window: number of return in a window
  47. * step: rolling step
  48. * @return: table, with "date", "status" and "weights" columns. "weights" contains space-delimited numbers
  49. */
  50. def cal_rolling_rbsa(ret, index_ret, isLongShort, window, step) {
  51. t = SELECT *
  52. FROM ret INNER JOIN index_ret ON ret.date = index_ret.date
  53. ORDER BY ret.date
  54. t.nullFill!(0)
  55. // 指数个数
  56. n = index_ret.cols() - 1
  57. // 计算起始位置
  58. i = t.size() % step
  59. // 运行rbsa计算次数
  60. cnt = (t.size() - i) / step
  61. tb = table(max(cnt,1):0, ["date", "status", "weights"], [STRING, STRING, STRING])
  62. if(t.size() >= window && cnt > 0) {
  63. do {
  64. v_ret = t.ret[i:(i+window)]
  65. t_index_ret = t.slice( i:(i+window), ret.cols(): )
  66. // 传入window个收益
  67. res = cal_rbsa(v_ret, t_index_ret, isLongShort)
  68. // 取窗口内最后(新)的日期
  69. tb.tableInsert(t.date[i+window-1], res[0], res[1][0:n].round(6).concat(" ")$STRING)
  70. // 往前推进step个收益
  71. i = i + step
  72. cnt -= 1
  73. } while( cnt > 0)
  74. } else {
  75. tb.tableInsert(null, "error", "The number of joined returns must not be less than window size.")
  76. }
  77. return tb
  78. }
  79. /*
  80. * 年化收益率计算(只支持月收益)
  81. *
  82. * date_rets 是个日期排正序的收益率表
  83. */
  84. def cal_ret_annualized(date_rets, isGIPS) {
  85. date_format = "y-M"
  86. t = SELECT fund_id, (1 + ret).prod() - 1 AS ret_a, end_date.max().datetimeParse(date_format) - end_date.min().datetimeParse(date_format) AS date_diff
  87. FROM date_rets
  88. GROUP BY fund_id
  89. // GIPS 规则是不够一年不年化
  90. if(isGIPS == true) {
  91. UPDATE t SET ret_a = (1 + ret_a).pow(12 \ date_diff) - 1
  92. WHERE date_diff > 12
  93. } else {
  94. UPDATE t SET ret_a = (1 + ret_a).pow(12 \ date_diff) - 1
  95. }
  96. return t
  97. }