其他分享
首页 > 其他分享> > 内部收益率的计算

内部收益率的计算

作者:互联网

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# Copyright © 2022, 飞麦 <fitmap@qq.com>, All rights reserved.

# frozen_string_literal: true

require 'date'

# IRC = Internal Rate of Change, 内部变化率, 资产(含后续投入、抽回)基于货币的时间价值的真实变化率.
# IRR = Internal Rate of Return, 内部收益率, 资产(含后续投入、抽回)基于货币的时间价值的真实收益率.
# IRR = IRC - 1.0
# 参见: https://www.investopedia.com/terms/i/irr.asp
# 现金价值: 保险术语; 净值: 基金术语; 本质均为资产在特定时间的可变现价值.
# 日收益率 = 日变化率 - 1.0
# 年收益率 = 年变化率 - 1.0
# 年变化率 = 日变化率**365.2425
# 本模块的 xirr 函数类似于 Excel 的 xirr 函数
# 现金价值盈亏, 试算多少, 日变化率, 日变化率上限, 日变化率下限, 盈利时上限锁定或亏损时下限锁定, 是否退出迭代
MidEx = Struct.new(:cv_cmp, :try_cmp, :daily_rate, :dr_up_limit, :dr_down_limit, :limit_lock, :quit_iter)

# 内部日变化率计算模块(日收益率 = 日变化率 - 1.0)
module Xirr
  module_function

  # 根据不定期不定额资金收(+)支(-)情况,递交各日期之间的天数与收(+)支(-)金额
  def gen_inout_series(tday_inout_a)
    pday = nil
    tday_inout_a.each do |tday, inout|
      tday = Date.parse(tday) unless tday.is_a?(Date)
      day_num = pday ? (tday - pday).round : 0
      yield day_num, inout
      pday = tday
    end
  end

  # 根据假设的日变化率试算现金价值
  def try_cash_value(tday_inout_a, daily_rate)
    try_cv = 0.0
    gen_inout_series(tday_inout_a) do |day_num, inout|
      try_cv *= daily_rate**day_num
      try_cv -= inout
    end
    try_cv
  end

  # 根据不定期不定额资金收(+)支(-)情况及现金价值,计算整体是盈利(1)、亏损(-1)还是持平(0)
  def compare_cash_value(tday_inout_a)
    static_cv = 0.0 # 忽略现金的时间价值时的结果
    tday_inout_a.each { |_tday, inout| static_cv += inout }
    raise "Invalid static_cv=#{static_cv}" unless static_cv.finite?

    static_cv <=> 0.0
  end

  # 日变化率调整比率
  DAY_RATE_ADJUST = 1.001

  # 根据试算比较情况,设定日成长率的上下限
  def decide_rate_limit(mid)
    case mid.cv_cmp
    when 1
      mid.dr_down_limit = 1.0
      mid.dr_up_limit = 1.0 * DAY_RATE_ADJUST
    when -1
      mid.dr_down_limit = 1.0 / DAY_RATE_ADJUST
      mid.dr_up_limit = 1.0
    when 0
      mid.dr_down_limit = mid.dr_up_limit = 1.0
    else
      raise "Invalid mid.cv_cmp=#{mid.cv_cmp}"
    end
  end

  # 盈利时根据试算结果调整
  def win_adjust(mid)
    case mid.try_cmp
    when 1 # 试算现金价值偏多
      mid.dr_up_limit = mid.daily_rate
      mid.limit_lock = true # 上限锁定了
    when -1 # 试算现金价值偏少
      mid.dr_down_limit = mid.daily_rate
      # 上限未锁定时继续放大
      mid.dr_up_limit *= DAY_RATE_ADJUST unless mid.limit_lock
    when 0 # 试算现金价值正好
      mid.quit_iter = true
    else
      raise "Invalid mid.try_cmp=#{mid.try_cmp}"
    end
  end

  # 亏损时根据试算结果调整
  def loss_adjust(mid)
    case mid.try_cmp
    when -1 # 试算现金价值偏少
      mid.dr_down_limit = mid.daily_rate
      mid.limit_lock = true # 下限锁定了
    when 1 # 试算现金价值偏多
      mid.dr_up_limit = mid.daily_rate
      # 下限未锁定时继续缩小
      mid.dr_down_limit /= DAY_RATE_ADJUST unless mid.limit_lock
    when 0 # 试算现金价值正好
      mid.quit_iter = true
    else
      raise "Invalid mid.try_cmp=#{mid.try_cmp}"
    end
  end

  # 根据整体盈亏情况及试算结果, 决定日变化率的上下限
  def adjust_limit(mid)
    case mid.cv_cmp
    when 1 # 盈利时变化率为正
      win_adjust(mid)
    when -1 # 亏损时变化率为负
      loss_adjust(mid)
    when 0 # 不盈不亏时无需迭代
      mid.quit_iter = true
    else
      raise "Invalid mid.cv_cmp=#{mid.cv_cmp}"
    end
  end

  # 构造初始值
  def gen_mid
    mid = MidEx.new
    # 初始为未锁定
    mid.limit_lock = false
    # 初始化日变化率为不变
    mid.daily_rate = 1.0
    # 初始为未退出迭代
    mid.quit_iter = false
    mid
  end

  # 计算并比较试算结果与现金价值
  def try_and_compare(mid, tday_inout_a)
    # 设置 日变化率 = 日变化率上限与下限的几何平均
    mid.daily_rate = Math.sqrt(mid.dr_up_limit * mid.dr_down_limit)
    # 根据假设的日变化率试算现金价值结果
    try_cv = try_cash_value(tday_inout_a, mid.daily_rate)
    # 比较试算结果与现金价值
    mid.try_cmp = try_cv <=> 0.0
  end
end

# 内部日变化率计算模块(日收益率 = 日变化率 - 1.0)
module Xirr
  module_function

  # 显示某行内容
  def show(idx, tday, inout)
    "idx=#{idx} tday=#{tday} inout=#{inout}"
  end

  # 检查输入有效性
  def check(tday_inout_a)
    pday = nil
    tday_inout_a.each_with_index do |(tday, inout), idx|
      tday = Date.parse(tday) unless tday.is_a?(Date)
      raise "Invalid tday: #{show(idx, tday, inout)}" unless tday.is_a?(Date)
      raise "Invalid inout: #{show(idx, tday, inout)}" unless inout.finite?
      raise "Invalid tday sequence: prev=#{pday}#{show(idx, tday, inout)}" if pday && tday < pday

      pday = tday
    end
  end

  # 计算不定期不定额资金收(+)支(-)情况下达到现金价值的内部变化率
  # tday_inout_a 是个(日期、收支)的数组, 数组的每个元素也是数组, 包含: 日期、收支这两个元素
  # 买入资产或追加买入资产为支(-), 抽回资产及最终现金价值(净值)为收(+)
  def xirc(tday_inout_a)
    # 检查输入有效性
    check(tday_inout_a)
    # 构造初始值
    mid = gen_mid
    # 根据不定期不定额资金收(+)支(-)情况及现金价值,计算整体是盈利(1)、亏损(-1)还是持平(0)
    mid.cv_cmp = compare_cash_value(tday_inout_a)
    # 根据盈亏情况决定日成长率的上下限
    decide_rate_limit(mid)
    # 当日变化率的上下限之间仍然有双精度浮点数时不断迭代
    while mid.dr_up_limit > mid.dr_down_limit.next_float
      # 计算并比较试算结果与现金价值
      try_and_compare(mid, tday_inout_a)
      # 根据试算结果比较情况, 调整日变化率的上下限
      adjust_limit(mid)
      break if mid.quit_iter
    end
    mid.daily_rate # 年变化率 = 日变化率**365.2425, 年收益率 = 年变化率 - 1.0
  end

  # 计算不定期不定额资金收(+)支(-)情况下达到现金价值的内部收益率
  # tday_inout_a 是个(日期、收支)的数组, 数组的每个元素也是数组, 包含: 日期、收支这两个元素
  # 买入资产或追加买入资产为支(-), 抽回资产及最终现金价值(净值)为收(+)
  def xirr(tday_inout_a)
    xirc(tday_inout_a) - 1.0
  end
end

标签:end,内部,tday,mid,limit,变化率,计算,收益率,inout
来源: https://www.cnblogs.com/fitmap/p/16588864.html