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
|
# Copyright © 2022, 飞麦 <fitmap@qq.com>, All rights reserved.
# frozen_string_literal: true
require 'clipboard'
require 'xirr'
# 根据剪贴板中的日期(可选)、交费与现金价值计算各期的内部收益率
module XirrGo
module_function
# 结构: 日期, 交费, 现金价值
RecEx = Struct.new(:tday, :fee, :curr)
# 从匹配字符串中删除分隔千位数的逗号后获取数值
def positive_value(str)
str && !str.empty? && str != '-' ? str.gsub(',', '').to_f : nil
end
# 从匹配字符串中删除分隔千位数的逗号后获取数值, 然后转换为负值
def negative_value(str)
tmp = positive_value(str)
tmp ? -tmp : nil
end
# 判断行的有效性(包含交费和/或现金价值)
def valid(rec)
rec.fee || rec.curr
end
# 获取计算内部收益率的输入数组
def deal(rec_a, o_idx)
tday = Date.today
inp_a = [] # 内部收益率输入数组
rec_a.each_with_index do |rec, i_idx|
next unless valid(rec)
tday = rec.tday if rec.tday
inp_a << [tday, rec.fee] if rec.fee # 日期、发生金额(交费转换为负数, 现金价值保持为正数)
if i_idx >= o_idx
inp_a << [tday, rec.curr] if rec.curr
break
end
tday >>= 12 # 下一保险年度
end
inp_a
end
# 根据资金发生序列计算各年的内部收益率
def calc(rec_a)
gain_a = [] # 收益率数组
rec_a.each_with_index do |rec, o_idx|
if rec.curr
inp_a = deal(rec_a, o_idx) # 获取计算内部收益率的输入数组
gain = (Xirr.xirc(inp_a)**365.2425) - 1.0 # 计算内部收益率
gain_a << "#{format('%.4f', gain * 100)}%" # 转换为百分数
else
gain_a << '' # 标题行或空行
end
end
gain_a.join("\n") # 所有行合并转换为一段文本
end
# 从字符串行中提取交费、现金价值等数据
def pick2(line)
md = line.match(/^\s*((\d+(,\d+)*(\.\d+)?)|-?)\s*\t\s*((\d+(,\d+)*(\.\d+)?)|-?)\s*$/)
if md
RecEx.new(nil, negative_value(md[1]), positive_value(md[5])) # 交费、现金价值
else
puts "\n#{line}: 本行没有日期[可选]、交费、现金价值等数据"
RecEx.new # 不予处理
end
end
# 分析日期字符串
def date_parse(str)
Date.parse(str)
rescue Date::Error
nil
end
# 从字符串行中提取日期、交费、现金价值等数据
def pick3(line)
md = line.match(/^\s*(\d+-\d+-\d+)\s*\t\s*((\d+(,\d+)*(\.\d+)?)|-?)\s*\t\s*((\d+(,\d+)*(\.\d+)?)|-?)\s*$/)
if md
datey = date_parse(md[1])
if datey
RecEx.new(datey, negative_value(md[2]), positive_value(md[6])) # 日期、交费、现金价值
else
puts "\n#{line}: #{md[1]} 日期非法"
RecEx.new # 不予处理
end
else
pick2(line)
end
end
# 根据剪贴板的日期(可选)、交费与现金价值序列,计算内部收益率
def run
text = Clipboard.paste # 从剪贴板获取日期列(可选)、交费列、现金价值列
rec_a = [] # 日期(可选)、交费、现金价值序列(若无日期,则假设每年一行)
# 对剪贴板内容中的每一行
text.each_line { |line| rec_a << pick3(line) }
Clipboard.copy(calc(rec_a)) # 将计算结果拷贝回剪贴板
puts "\n【内部收益率列已拷贝到剪贴板中】"
end
end
# 用<Ctrl>-<鼠标左键>点选交费列、现金价值列后,拷贝到剪贴板
# 或用<Ctrl>-<鼠标左键>点选日期(YYYY-MM-DD)列、交费列、现金价值列后,拷贝到剪贴板
# 运行后剪贴板包含内部收益率列
$PROGRAM_NAME == __FILE__ && XirrGo.run
|