istox 0.1.158.1 → 0.2.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/istox.rb +0 -5
- data/lib/istox/helpers/common_helper.rb +6 -0
- data/lib/istox/helpers/formatter.rb +12 -2
- data/lib/istox/helpers/order_book_price_time.rb +2 -2
- data/lib/istox/helpers/sns_publisher.rb +10 -3
- data/lib/istox/helpers/xray/grpc_client_xray_interceptor.rb +1 -0
- data/lib/istox/helpers/xray/grpc_server_xray_interceptor.rb +1 -0
- data/lib/istox/helpers/xray/rabbitmq_consumer_interceptor.rb +1 -0
- data/lib/istox/helpers/xray/rabbitmq_publisher_interceptor.rb +1 -0
- data/lib/istox/quant/bond.rb +188 -99
- data/lib/istox/version.rb +1 -1
- metadata +2 -15
- data/.idea/.rakeTasks +0 -7
- data/.idea/encodings.xml +0 -4
- data/.idea/inspectionProfiles/Project_Default.xml +0 -6
- data/.idea/istox-gem.iml +0 -12
- data/.idea/misc.xml +0 -7
- data/.idea/modules.xml +0 -8
- data/.idea/vcs.xml +0 -6
- data/lib/istox/helpers/integration_test/grpc_client_interceptor.rb +0 -22
- data/lib/istox/helpers/integration_test/grpc_server_interceptor.rb +0 -31
- data/lib/istox/helpers/integration_test/rabbitmq_consumer_interceptor.rb +0 -32
- data/lib/istox/helpers/integration_test/rabbitmq_publisher_interceptor.rb +0 -14
- data/lib/istox/helpers/integration_test/settings.rb +0 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3aa5c0d11cb873cfd398db44b8f4a139baf6668bf04f578db3091cb049b7d89f
|
4
|
+
data.tar.gz: '02109c5b9c3cb01c288d757cef841357f79cc36186d4245f624a619dbb984489'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bdfe781debfdb6c49ab24140562b55bff2e919090fb6e46c71b6e6dfecbb9607843379e0ac97e58edc26a0426527839494294bcb94703d80fc7a0102b244333a
|
7
|
+
data.tar.gz: 88b913cbd976f4d85e2e7a140e7fd3747dd42a5c8da83b2135796b20d3121619ed54b8a1f2040179aafd13ee03cae5646fd4c6634258854e2bfb522681dc649f
|
data/lib/istox.rb
CHANGED
@@ -45,11 +45,6 @@ module Istox
|
|
45
45
|
require 'istox/helpers/xray/rabbitmq_consumer_interceptor'
|
46
46
|
require 'istox/helpers/xray/xray_initializer'
|
47
47
|
|
48
|
-
require 'istox/helpers/integration_test/grpc_client_interceptor'
|
49
|
-
require 'istox/helpers/integration_test/grpc_server_interceptor'
|
50
|
-
require 'istox/helpers/integration_test/rabbitmq_publisher_interceptor'
|
51
|
-
require 'istox/helpers/integration_test/rabbitmq_consumer_interceptor'
|
52
|
-
|
53
48
|
require 'istox/models/blockchain_receipt'
|
54
49
|
require 'istox/models/concerns/blockchain_receipt_query'
|
55
50
|
require 'istox/consumers/blockchain_status_handler'
|
@@ -17,8 +17,18 @@ module Istox
|
|
17
17
|
end
|
18
18
|
|
19
19
|
# format a money, eg. 20000.134 > SGD 20,000.14, position can be :front or :behind, abs_num: whether to absolute the number
|
20
|
-
def money(input, round_mode: :half_up, precision:
|
21
|
-
|
20
|
+
def money(input, round_mode: :half_up, precision: nil, currency:, position: :front, abs_num: false, hide_currency: false)
|
21
|
+
# precision = ::Istox::CommonHelper.get_currency_decimal(currency, precision)
|
22
|
+
|
23
|
+
decimal_place = if precision.present?
|
24
|
+
precision
|
25
|
+
else
|
26
|
+
::Istox::CommonHelper.get_currency_decimal(currency)
|
27
|
+
end
|
28
|
+
|
29
|
+
result = number(input, round_mode: round_mode, precision: decimal_place, abs_num: abs_num)
|
30
|
+
|
31
|
+
return result if hide_currency
|
22
32
|
|
23
33
|
return currency + ' ' + result if position == :front
|
24
34
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module Istox
|
2
2
|
class OrderBookPriceTime
|
3
3
|
class << self
|
4
|
-
def allocation(soft_cap, total_supply, investments)
|
4
|
+
def allocation(soft_cap, total_supply, investments, decimal_place: 2)
|
5
5
|
# sort by token price desc, and id asc
|
6
6
|
investments = investments.sort do |a, b|
|
7
7
|
[b[:token_price], a[:id]] <=> [a[:token_price], b[:id]]
|
@@ -15,7 +15,7 @@ module Istox
|
|
15
15
|
total_allocated = 0.0
|
16
16
|
total_unallocated = 0.0
|
17
17
|
total_investment = 0.0
|
18
|
-
cutoff_price = ::Istox::FMath.round_up(::Istox::FMath.div(soft_cap, total_supply),
|
18
|
+
cutoff_price = ::Istox::FMath.round_up(::Istox::FMath.div(soft_cap, total_supply), decimal_place)
|
19
19
|
is_cutoff = false
|
20
20
|
|
21
21
|
# return result immediately if no interest
|
@@ -14,7 +14,7 @@ module Istox
|
|
14
14
|
|
15
15
|
message = JSON.generate(message) unless message.is_a? String
|
16
16
|
|
17
|
-
sns_client.publish(
|
17
|
+
sns_client.publish(topic_arn: topic_arn || ENV.fetch('SNS_CLIENT_TOPIC', ''), message: message)
|
18
18
|
|
19
19
|
log.info('Publish to SNS successfully.')
|
20
20
|
rescue StandardError => e
|
@@ -28,8 +28,15 @@ module Istox
|
|
28
28
|
def sns_client
|
29
29
|
return @sns_client if @sns_client.present?
|
30
30
|
|
31
|
-
|
32
|
-
|
31
|
+
access_key = ENV.fetch('AWS_STS_ACCESS_KEY_ID', nil)
|
32
|
+
|
33
|
+
if access_key.present?
|
34
|
+
credentials = Aws::Credentials.new(ENV.fetch('AWS_STS_ACCESS_KEY_ID', nil), ENV.fetch('AWS_STS_SECRET_ACCESS_KEY', nil))
|
35
|
+
Aws.config.update(region: ENV.fetch('AWS_REGION', 'ap-southeast-1'),
|
36
|
+
credentials: credentials)
|
37
|
+
else
|
38
|
+
Aws.config.update(region: ENV.fetch('AWS_REGION', 'ap-southeast-1'))
|
39
|
+
end
|
33
40
|
|
34
41
|
@sns_client = Aws::SNS::Client.new(region: ENV.fetch('AWS_REGION', 'ap-southeast-1'))
|
35
42
|
end
|
@@ -22,6 +22,7 @@ module Istox
|
|
22
22
|
|
23
23
|
request_context.metadata[:xray_trace_id] = XRay.recorder.current_segment.trace_id
|
24
24
|
request_context.metadata[:xray_parent_id] = XRay.recorder.current_segment.id
|
25
|
+
Thread.current[:tracer_id] = XRay.recorder.current_segment.trace_id
|
25
26
|
|
26
27
|
result = yield
|
27
28
|
|
@@ -7,6 +7,7 @@ module Istox
|
|
7
7
|
meta = request.active_call.metadata
|
8
8
|
trace_id = (meta['xray_trace_id'] if meta.present? && meta.key?('xray_trace_id'))
|
9
9
|
parent_id = (meta['xray_parent_id'] if meta.present? && meta.key?('xray_parent_id'))
|
10
|
+
Thread.current[:tracer_id] = trace_id
|
10
11
|
|
11
12
|
XRay.recorder.begin_segment("#{::Istox::Xray::XrayInitializer.service_name}.grpc.#{request.method_key}",
|
12
13
|
trace_id: trace_id, parent_id: parent_id)
|
@@ -8,6 +8,7 @@ module Istox
|
|
8
8
|
|
9
9
|
trace_id = (payload_hash[:xray_trace_id] if payload_hash.key?(:xray_trace_id))
|
10
10
|
parent_id = (payload_hash[:xray_parent_id] if payload_hash.key?(:xray_parent_id))
|
11
|
+
Thread.current[:tracer_id] = trace_id
|
11
12
|
|
12
13
|
arr = [delivery_info[:exchange]&.downcase, delivery_info[:routing_key]&.downcase, payload_hash[:type]&.downcase].compact
|
13
14
|
|
data/lib/istox/quant/bond.rb
CHANGED
@@ -12,45 +12,82 @@ module Istox
|
|
12
12
|
|
13
13
|
DEFAULT_APPROXIMATION_ERROR = 0.00001
|
14
14
|
|
15
|
-
def initialize(coupon: nil, maturity_date: nil,
|
16
|
-
raise "Invalid coupon #{coupon}" if (coupon.nil? || !is_number?(coupon))
|
15
|
+
def initialize(coupon: nil, maturity_date: nil, start_date: nil, coupon_frequency: nil, coupon_payment_dates: nil, face_value: 100, days_of_year: 365)
|
16
|
+
raise "Invalid coupon #{coupon}" if (coupon.nil? || !is_number?(coupon)) || coupon < 0
|
17
17
|
raise "Invalid maturity_date #{maturity_date}" if (maturity_date.nil? || maturity_date.methods.include?("strftime"))
|
18
|
-
raise "Invalid
|
19
|
-
raise "Invalid coupon_frequency #{
|
20
|
-
raise "Invalid coupon_payment_dates #{coupon_payment_dates}" if (coupon_payment_dates.nil? || coupon_payment_dates.count == 0 || coupon_payment_dates.any? { |date| date > maturity_date.to_date })
|
18
|
+
raise "Invalid start_date #{start_date}" if (start_date.nil? || start_date.methods.include?("strftime"))
|
19
|
+
raise "Invalid coupon_frequency #{coupon_frequency}" if (coupon_frequency.nil? || !coupon_frequency.is_a?(Integer) || coupon_frequency < 0)
|
20
|
+
raise "Invalid coupon_payment_dates #{coupon_payment_dates}" if (coupon_payment_dates.nil? || (coupon_payment_dates.count == 0 && coupon_frequency != 0) || coupon_payment_dates.any? { |date| date > maturity_date.to_date })
|
21
21
|
raise "Invalid days_of_year #{days_of_year}" if (days_of_year != 365 && days_of_year != 360)
|
22
|
+
raise "start_date is not before maturity_date" if start_date>=maturity_date
|
22
23
|
|
23
24
|
@coupon = coupon.to_d
|
24
25
|
@maturity_date = maturity_date.to_date
|
25
|
-
@
|
26
|
-
@coupon_frequency = coupon_frequency.to_d
|
26
|
+
@coupon_frequency = coupon_frequency.to_i # if this is 0, it means zero coupon
|
27
27
|
@days_of_year = days_of_year.to_d
|
28
|
-
@face_value = face_value
|
29
|
-
@coupon_payment_dates = coupon_payment_dates.map(&:to_date).sort
|
30
|
-
|
28
|
+
@face_value = face_value.to_d
|
29
|
+
@coupon_payment_dates = coupon_payment_dates.map(&:to_date).uniq.sort
|
30
|
+
# note here we work out the start date based on maturity date and nunber of years
|
31
|
+
@start_date = start_date.to_date
|
32
|
+
|
33
|
+
@pay_accrued_interest = false
|
34
|
+
@coupon_payment_dates_include_accrued_interest = false
|
35
|
+
if !is_zero_coupon?
|
36
|
+
if @coupon_payment_dates.include?(@maturity_date)
|
37
|
+
# maturity date is a coupon payment date, check if this should
|
38
|
+
# be accrued interest or last normal coupon
|
39
|
+
if @coupon_payment_dates.count > 1
|
40
|
+
previous_coupon_date = @coupon_payment_dates[@coupon_payment_dates.count-2]
|
41
|
+
next_coupon_date = add_month(previous_coupon_date, -(12/@coupon_frequency).to_i)
|
42
|
+
# If maturity date is a normal coupon payment, the theorecical next_coupon_date
|
43
|
+
# calculated from previous coupon payment should be the maturity date, to be safe,
|
44
|
+
# we allow 3 days difference
|
45
|
+
if (next_coupon_date - @maturity_date).abs <= 3
|
46
|
+
@pay_accrued_interest = false
|
47
|
+
@coupon_payment_dates_include_accrued_interest = false
|
48
|
+
else
|
49
|
+
@pay_accrued_interest = true
|
50
|
+
@coupon_payment_dates_include_accrued_interest = true
|
51
|
+
end
|
52
|
+
else
|
53
|
+
# maturity date is only coupon payment date, shouldn't be accrued interest!
|
54
|
+
@pay_accrued_interest = false
|
55
|
+
@coupon_payment_dates_include_accrued_interest = false
|
56
|
+
end
|
57
|
+
else
|
58
|
+
# maturity date is not included in coupon payment date, consider
|
59
|
+
# this needs to pay accrued interest
|
60
|
+
@pay_accrued_interest = true
|
61
|
+
@coupon_payment_dates_include_accrued_interest = false
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
log.info "Bond info: start_date=#{@start_date} maturity_date=#{@maturity_date} days_of_years=#{days_of_year} coupon=#{@coupon} coupon_frequency=#{coupon_frequency} face_value=#{@face_value} coupon_payment_dates=#{@coupon_payment_dates} pay_accrued_interest=#{@pay_accrued_interest} coupon_payment_dates_include_accrued_interest=#{@coupon_payment_dates_include_accrued_interest}"
|
31
66
|
end
|
32
67
|
|
33
68
|
def price(ytm, date, ex_coupon_date: nil, fees: 0)
|
34
|
-
|
35
|
-
|
69
|
+
irr = ytm
|
70
|
+
irr = ytm/@coupon_frequency if !is_zero_coupon?
|
71
|
+
price = price_for_irr(irr, date, ex_coupon_date: ex_coupon_date, fees: fees)
|
36
72
|
price
|
37
73
|
end
|
38
74
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
# payments.sort
|
47
|
-
# end
|
75
|
+
def ytm(date, ex_coupon_date: nil, price: 100, fees: 0, approximation_error: DEFAULT_APPROXIMATION_ERROR)
|
76
|
+
ytm_down, ytm_up = ytm_limits(price, date, ex_coupon_date: ex_coupon_date, fees: fees)
|
77
|
+
approximate_ytm(ytm_down, ytm_up, price, date, ex_coupon_date: ex_coupon_date, fees: fees, approximation_error: approximation_error)
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
private
|
48
82
|
|
49
83
|
def coupon_payments(from_date)
|
50
84
|
payments = []
|
51
85
|
|
52
86
|
@coupon_payment_dates.each do |payment_date|
|
53
|
-
if
|
87
|
+
# if from_date falls on coupon payment date or maturity date, we
|
88
|
+
# still add them to make it easy for calculation later
|
89
|
+
# just that the first coupon will not pay actually (i.e. 0)
|
90
|
+
if payment_date >= from_date && payment_date <= @maturity_date
|
54
91
|
payments << payment_date
|
55
92
|
end
|
56
93
|
end
|
@@ -58,29 +95,25 @@ module Istox
|
|
58
95
|
payments.sort
|
59
96
|
end
|
60
97
|
|
61
|
-
def ytm(date, ex_coupon_date: nil, price: 100, fees: 0, approximation_error: DEFAULT_APPROXIMATION_ERROR)
|
62
|
-
ytm_down, ytm_up = ytm_limits(price, date, ex_coupon_date: ex_coupon_date, fees: fees)
|
63
|
-
approximate_ytm(ytm_down, ytm_up, price, date, ex_coupon_date: ex_coupon_date, fees: fees, approximation_error: approximation_error)
|
64
|
-
end
|
65
|
-
|
66
|
-
|
67
|
-
private
|
68
|
-
|
69
98
|
def is_365?
|
70
99
|
@days_of_year == 365.to_d
|
71
100
|
end
|
72
101
|
|
73
|
-
def
|
74
|
-
|
102
|
+
def is_zero_coupon?
|
103
|
+
@coupon == 0
|
104
|
+
end
|
105
|
+
|
106
|
+
def add_month(my_date, n)
|
107
|
+
if is_month_end?(my_date)
|
75
108
|
# month end
|
76
|
-
(
|
109
|
+
(my_date.next_day + n.month).prev_day
|
77
110
|
else
|
78
|
-
|
111
|
+
my_date.next_day + n.month
|
79
112
|
end
|
80
113
|
end
|
81
114
|
|
82
|
-
def is_month_end?(
|
83
|
-
|
115
|
+
def is_month_end?(my_date)
|
116
|
+
my_date.month != my_date.next_day.month
|
84
117
|
end
|
85
118
|
|
86
119
|
def is_number?(val)
|
@@ -107,111 +140,158 @@ module Istox
|
|
107
140
|
[ytm_down, ytm_up]
|
108
141
|
end
|
109
142
|
|
110
|
-
# def price_for_irr(irr, date, fees: 0)
|
111
|
-
# raise "Date is after maturity_date!" if date > @maturity_date
|
112
|
-
# last = date
|
113
|
-
# interest_payments(date).map do |payday|
|
114
|
-
# interest = @coupon * @face_value * ((payday-last)/@days_of_year)
|
115
|
-
# interest += @face_value if payday == @maturity_date
|
116
|
-
# last = payday
|
117
|
-
# interest / ((1+irr) ** ((payday-date)/@days_of_year))
|
118
|
-
# end.inject(:+) / (1+fees)
|
119
|
-
# end
|
120
|
-
|
121
143
|
def price_for_irr(irr, date, ex_coupon_date: nil, fees: 0)
|
122
144
|
date = date.to_date
|
123
145
|
raise "Date is after maturity_date!" if date > @maturity_date
|
146
|
+
# if today is maturity, price is face value (without including any coupon/accrued interest)
|
147
|
+
# BizOps said we will always have an ex_coupon_date when it's close
|
148
|
+
# to coupon payment date but not too early, so the fomula needs to
|
149
|
+
# accept nil value. Even if we have ex_coupon_date as maturity date,
|
150
|
+
# buyer is not eligible to receive the coupon, so no need to check ex_coupon_date here
|
151
|
+
if date == @maturity_date
|
152
|
+
return @face_value
|
153
|
+
end
|
154
|
+
|
124
155
|
if date <= @start_date
|
125
156
|
date = @start_date
|
126
157
|
end
|
127
|
-
last_coupon_payday = @coupon_payment_dates.last
|
128
|
-
payment_dates = coupon_payments(date)
|
129
158
|
|
130
|
-
|
131
|
-
|
159
|
+
# it's supporting 365 actual days only at the moment
|
160
|
+
if is_zero_coupon?
|
161
|
+
total_days = @maturity_date - date
|
162
|
+
no_of_years = total_days / 365
|
163
|
+
remainder_days = total_days % 365
|
164
|
+
|
165
|
+
return @face_value/(1+irr)**no_of_years/(1+irr*remainder_days/365)
|
166
|
+
end
|
167
|
+
|
168
|
+
payment_dates = coupon_payments(date)
|
169
|
+
|
170
|
+
no_regular_coupon_before_maturity = false
|
171
|
+
if payment_dates.count == 0 || (payment_dates.count == 1 && payment_dates.first == @maturity_date)
|
172
|
+
no_regular_coupon_before_maturity = true
|
173
|
+
end
|
174
|
+
|
175
|
+
if no_regular_coupon_before_maturity
|
176
|
+
discount_factor = nil
|
132
177
|
if is_365?
|
133
|
-
|
178
|
+
previous_coupon_date = previous_coupon_date_before_maturity
|
179
|
+
next_coupon_date = add_month(previous_coupon_date, (12/@coupon_frequency).to_i)
|
180
|
+
discount_factor = 1.0/(1+irr*(@maturity_date-date)/(next_coupon_date-previous_coupon_date))
|
134
181
|
else
|
135
182
|
discount_factor = 1.0/(1+irr*@coupon_frequency*day_count_factor(date, @maturity_date, nil))
|
136
183
|
end
|
137
|
-
|
138
|
-
|
139
|
-
|
184
|
+
|
185
|
+
if @pay_accrued_interest
|
186
|
+
# accrued interest + face value discounted to the current date
|
187
|
+
discounted_accrued_interest = 0
|
188
|
+
if ex_coupon_date.nil? || ex_coupon_date == @maturity_date
|
189
|
+
discounted_accrued_interest = @coupon*@face_value*accrued_interest_factor*discount_factor
|
190
|
+
end
|
191
|
+
discounted_face_value = @face_value*discount_factor
|
192
|
+
price = discounted_accrued_interest + discounted_face_value
|
193
|
+
return price
|
194
|
+
else
|
195
|
+
discounted_last_coupon = 0
|
196
|
+
if ex_coupon_date.nil? || ex_coupon_date == @maturity_date
|
197
|
+
discounted_last_coupon = @coupon*@face_value/@coupon_frequency*discount_factor
|
198
|
+
end
|
199
|
+
discounted_face_value = @face_value*discount_factor
|
200
|
+
price = discounted_last_coupon + discounted_face_value
|
201
|
+
return price
|
202
|
+
end
|
140
203
|
else
|
141
|
-
#
|
142
|
-
#
|
204
|
+
# there are at least 1 more regular coupon payment before maturity
|
205
|
+
# we discount face value and coupons or accrued interest if any
|
206
|
+
# to the first coupon payment date left, and then discount the total to current date
|
143
207
|
value_at_first_coupon = 0.to_d
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
208
|
+
# this is the discount period excluding the one at maturity date
|
209
|
+
# e.g. payment_dates has date1, date2, maturity, we need to discount
|
210
|
+
# to date1, excluding the period from date2 to maturity, period = 1 (i.e. from date2 to date1)
|
211
|
+
no_of_discount_period = 0
|
212
|
+
if @pay_accrued_interest
|
213
|
+
if @coupon_payment_dates_include_accrued_interest
|
214
|
+
no_of_discount_period = payment_dates.count-2
|
215
|
+
else
|
216
|
+
no_of_discount_period = payment_dates.count-1
|
217
|
+
end
|
218
|
+
|
219
|
+
discount_factor = 1.0/((1+irr)**no_of_discount_period)/(1+irr*@coupon_frequency*accrued_interest_factor)
|
152
220
|
discounted_accrued_interest = @coupon*@face_value*accrued_interest_factor*discount_factor
|
153
221
|
discounted_face_value = @face_value*discount_factor
|
154
222
|
value_at_first_coupon = discounted_accrued_interest + discounted_face_value
|
155
|
-
|
223
|
+
else
|
224
|
+
# last coupon payment is at maturity
|
225
|
+
no_of_discount_period = payment_dates.count-1
|
226
|
+
discount_factor = 1.0/(1+irr)**(no_of_discount_period+1)
|
227
|
+
discounted_last_coupon = @coupon*@face_value/@coupon_frequency*discount_factor
|
228
|
+
discounted_face_value = @face_value*discount_factor
|
229
|
+
value_at_first_coupon = discounted_last_coupon + discounted_face_value
|
230
|
+
end
|
156
231
|
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
# discount all coupon payments to first coupon date
|
163
|
-
for n in 1..payment_dates.count do
|
164
|
-
value_at_first_coupon += @coupon*@face_value/@coupon_frequency/(1+irr)**n
|
165
|
-
end
|
166
|
-
else
|
167
|
-
# discount all coupon payments excluding first one to first coupon date
|
168
|
-
for n in 1..payment_dates.count-1 do
|
169
|
-
value_at_first_coupon += @coupon*@face_value/@coupon_frequency/(1+irr)**n
|
170
|
-
end
|
171
|
-
end
|
232
|
+
if no_of_discount_period >= 1
|
233
|
+
# discount all coupon payments excluding first one to first coupon date
|
234
|
+
for n in 1..no_of_discount_period do
|
235
|
+
value_at_first_coupon += @coupon*@face_value/@coupon_frequency/(1+irr)**n
|
236
|
+
end
|
172
237
|
end
|
173
238
|
|
239
|
+
# first coupon, can be pro-rated, check the logic in first_coupon
|
240
|
+
value_at_first_coupon += first_coupon(date, ex_coupon_date)
|
241
|
+
|
174
242
|
if @coupon_payment_dates.include?(date)
|
175
243
|
# today is one of the coupon payment, no need to discount
|
176
244
|
price = value_at_first_coupon
|
177
245
|
else
|
178
246
|
# discount value at first coupon date to present value
|
179
247
|
if is_365?
|
180
|
-
|
248
|
+
previous_coupon_date = add_month(payment_dates.first, -(12/@coupon_frequency).to_i)
|
249
|
+
price = value_at_first_coupon/(1+irr*(payment_dates.first-date)/(payment_dates.first-previous_coupon_date))/(1+fees)
|
181
250
|
else
|
182
251
|
price = value_at_first_coupon/(1+irr*@coupon_frequency*day_count_factor(date, payment_dates.first, nil))/(1+fees)
|
183
252
|
end
|
184
253
|
end
|
254
|
+
return price
|
185
255
|
end
|
186
|
-
|
187
256
|
end
|
188
257
|
|
189
258
|
def first_coupon(date, ex_coupon_date)
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
259
|
+
# BizOps said we will always have an ex_coupon_date when it's close
|
260
|
+
# to coupon payment date but not too early, so the fomula needs to
|
261
|
+
# accept nil value.
|
262
|
+
if !ex_coupon_date.nil? && date >= ex_coupon_date.to_date
|
263
|
+
# on or after ex coupon date, buyer won't receive next coupon
|
264
|
+
return 0.to_d
|
265
|
+
elsif @coupon_payment_dates.include?(date)
|
266
|
+
# if today is a coupon payment date, for sure it will pass ex_coupon_date
|
267
|
+
# and buyer won't receive this coupon. In case ex_coupon_date is nil,
|
268
|
+
# that's the same assumption, so first_coupon will be 0
|
194
269
|
return 0.to_d
|
195
270
|
end
|
196
271
|
|
197
272
|
coupon = @coupon*@face_value/@coupon_frequency
|
198
|
-
if
|
273
|
+
# For the very first coupon, we need to check if it needs to be pro-rated
|
274
|
+
if date <= @coupon_payment_dates.first && @pay_accrued_interest
|
199
275
|
coupon = @coupon*@face_value*first_coupon_factor
|
200
276
|
end
|
201
277
|
coupon
|
202
278
|
end
|
203
279
|
|
204
|
-
def
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
280
|
+
def previous_coupon_date_before_maturity
|
281
|
+
previous_coupon_date = @start_date
|
282
|
+
if @coupon_payment_dates.include?(@maturity_date)
|
283
|
+
# maturity date is in coupon payment dates, get the previous coupon date
|
284
|
+
# if there is no more date, use the default start date
|
285
|
+
if @coupon_payment_dates.count > 1
|
286
|
+
previous_coupon_date = @coupon_payment_dates[@coupon_payment_dates.count-2]
|
287
|
+
end
|
288
|
+
else
|
289
|
+
# maturity date is not in coupon payment dates, get the last coupon date
|
290
|
+
# if there is no more date, use the default start date
|
291
|
+
if @coupon_payment_dates.count > 0
|
292
|
+
previous_coupon_date = @coupon_payment_dates.last
|
293
|
+
end
|
294
|
+
end
|
215
295
|
end
|
216
296
|
|
217
297
|
def first_coupon_factor
|
@@ -227,6 +307,13 @@ module Istox
|
|
227
307
|
|
228
308
|
def accrued_interest_factor
|
229
309
|
date1 = @coupon_payment_dates.last
|
310
|
+
if @coupon_payment_dates_include_accrued_interest
|
311
|
+
if @coupon_payment_dates.count > 1
|
312
|
+
date1 = @coupon_payment_dates[@coupon_payment_dates.count-2]
|
313
|
+
else
|
314
|
+
raise "coupon_payment_dates count < 2 and we have accrued interest payment date included!!!"
|
315
|
+
end
|
316
|
+
end
|
230
317
|
date2 = @maturity_date
|
231
318
|
date3 = add_month(date1, (12/@coupon_frequency).to_i)
|
232
319
|
day_count_factor(date1, date2, date3)
|
@@ -234,8 +321,10 @@ module Istox
|
|
234
321
|
|
235
322
|
def day_count_factor(date1, date2, date3)
|
236
323
|
if is_365?
|
324
|
+
# Actual/365
|
237
325
|
(date2 - date1)/(date3 - date1)/@coupon_frequency
|
238
326
|
else
|
327
|
+
# 30/360
|
239
328
|
d1 = [date1.day, 30].min
|
240
329
|
d2 = date2.day
|
241
330
|
if d1 == 30
|
data/lib/istox/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: istox
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Siong Leng
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-03-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: amazing_print
|
@@ -480,14 +480,6 @@ extensions: []
|
|
480
480
|
extra_rdoc_files: []
|
481
481
|
files:
|
482
482
|
- ".gitignore"
|
483
|
-
- ".idea/.rakeTasks"
|
484
|
-
- ".idea/encodings.xml"
|
485
|
-
- ".idea/inspectionProfiles/Project_Default.xml"
|
486
|
-
- ".idea/istox-gem.iml"
|
487
|
-
- ".idea/misc.xml"
|
488
|
-
- ".idea/modules.xml"
|
489
|
-
- ".idea/vcs.xml"
|
490
|
-
- ".idea/workspace.xml"
|
491
483
|
- ".rubocop.yml"
|
492
484
|
- ".solargraph.yml"
|
493
485
|
- CODE_OF_CONDUCT.md
|
@@ -511,11 +503,6 @@ files:
|
|
511
503
|
- lib/istox/helpers/graphql_client.rb
|
512
504
|
- lib/istox/helpers/grpc_client.rb
|
513
505
|
- lib/istox/helpers/gruf_listener_hook.rb
|
514
|
-
- lib/istox/helpers/integration_test/grpc_client_interceptor.rb
|
515
|
-
- lib/istox/helpers/integration_test/grpc_server_interceptor.rb
|
516
|
-
- lib/istox/helpers/integration_test/rabbitmq_consumer_interceptor.rb
|
517
|
-
- lib/istox/helpers/integration_test/rabbitmq_publisher_interceptor.rb
|
518
|
-
- lib/istox/helpers/integration_test/settings.rb
|
519
506
|
- lib/istox/helpers/logger.rb
|
520
507
|
- lib/istox/helpers/messaging.rb
|
521
508
|
- lib/istox/helpers/my_open_struct.rb
|
data/.idea/.rakeTasks
DELETED
@@ -1,7 +0,0 @@
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
-
<Settings><!--This file was automatically generated by Ruby plugin.
|
3
|
-
You are allowed to:
|
4
|
-
1. Remove rake task
|
5
|
-
2. Add existing rake tasks
|
6
|
-
To add existing rake tasks automatically delete this file and reload the project.
|
7
|
-
--><RakeGroup description="" fullCmd="" taksId="rake" /></Settings>
|
data/.idea/encodings.xml
DELETED
data/.idea/istox-gem.iml
DELETED
@@ -1,12 +0,0 @@
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
-
<module type="RUBY_MODULE" version="4">
|
3
|
-
<component name="ModuleRunConfigurationManager">
|
4
|
-
<shared />
|
5
|
-
</component>
|
6
|
-
<component name="NewModuleRootManager">
|
7
|
-
<content url="file://$MODULE_DIR$" />
|
8
|
-
<orderEntry type="inheritedJdk" />
|
9
|
-
<orderEntry type="sourceFolder" forTests="false" />
|
10
|
-
<orderEntry type="library" scope="PROVIDED" name="bundler (v1.16.6, RVM: ruby-2.5.3 [global]) [gem]" level="application" />
|
11
|
-
</component>
|
12
|
-
</module>
|
data/.idea/misc.xml
DELETED
@@ -1,7 +0,0 @@
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
-
<project version="4">
|
3
|
-
<component name="JavaScriptSettings">
|
4
|
-
<option name="languageLevel" value="ES6" />
|
5
|
-
</component>
|
6
|
-
<component name="ProjectRootManager" version="2" project-jdk-name="RVM: ruby-2.5.3 [global]" project-jdk-type="RUBY_SDK" />
|
7
|
-
</project>
|
data/.idea/modules.xml
DELETED
@@ -1,8 +0,0 @@
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
-
<project version="4">
|
3
|
-
<component name="ProjectModuleManager">
|
4
|
-
<modules>
|
5
|
-
<module fileurl="file://$PROJECT_DIR$/.idea/istox-gem.iml" filepath="$PROJECT_DIR$/.idea/istox-gem.iml" />
|
6
|
-
</modules>
|
7
|
-
</component>
|
8
|
-
</project>
|
data/.idea/vcs.xml
DELETED
@@ -1,22 +0,0 @@
|
|
1
|
-
require 'istox/helpers/logger'
|
2
|
-
|
3
|
-
module Istox
|
4
|
-
module IntegrationTest
|
5
|
-
class GrpcClientInterceptor < ::Gruf::Interceptors::ClientInterceptor
|
6
|
-
def call(request_context:)
|
7
|
-
request_context.metadata[:integration_test] = if ::Istox::IntegrationTest::Settings.enable
|
8
|
-
'1'
|
9
|
-
else
|
10
|
-
'0'
|
11
|
-
end
|
12
|
-
|
13
|
-
result = yield
|
14
|
-
|
15
|
-
result
|
16
|
-
rescue StandardError => e
|
17
|
-
log.error e
|
18
|
-
raise e
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
@@ -1,31 +0,0 @@
|
|
1
|
-
require 'istox/helpers/logger'
|
2
|
-
|
3
|
-
module Istox
|
4
|
-
module IntegrationTest
|
5
|
-
class GrpcServerInterceptor < ::Gruf::Interceptors::ServerInterceptor
|
6
|
-
def call
|
7
|
-
meta = request.active_call.metadata
|
8
|
-
enable_integration = (meta['integration_test'] if meta.present? && meta.key?('integration_test'))
|
9
|
-
|
10
|
-
if enable_integration == '1'
|
11
|
-
::Istox::IntegrationTest::Settings.enable = true
|
12
|
-
if ActiveRecord::Base.connection.current_database != Rails.configuration.database_configuration['integration']['database']
|
13
|
-
ActiveRecord::Base.establish_connection(:integration)
|
14
|
-
end
|
15
|
-
else
|
16
|
-
::Istox::IntegrationTest::Settings.enable = false
|
17
|
-
if ActiveRecord::Base.connection.current_database != Rails.configuration.database_configuration['development']['database']
|
18
|
-
ActiveRecord::Base.establish_connection(:development)
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
result = yield # this returns the protobuf message
|
23
|
-
|
24
|
-
result
|
25
|
-
rescue StandardError => e
|
26
|
-
log.error e
|
27
|
-
raise e
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
@@ -1,32 +0,0 @@
|
|
1
|
-
require 'istox/helpers/logger'
|
2
|
-
|
3
|
-
module Istox
|
4
|
-
module IntegrationTest
|
5
|
-
class RabbitmqConsumerInterceptor
|
6
|
-
def call(payload, _metadata, _delivery_info)
|
7
|
-
payload_hash = payload.to_h
|
8
|
-
|
9
|
-
enable_integration_test = (payload_hash[:istox_integration_test] if payload_hash.key?(:istox_integration_test))
|
10
|
-
|
11
|
-
if enable_integration_test == '1'
|
12
|
-
::Istox::IntegrationTest::Settings.enable = true
|
13
|
-
if ActiveRecord::Base.connection.current_database != Rails.configuration.database_configuration['integration']['database']
|
14
|
-
ActiveRecord::Base.establish_connection(:integration)
|
15
|
-
end
|
16
|
-
else
|
17
|
-
::Istox::IntegrationTest::Settings.enable = false
|
18
|
-
if ActiveRecord::Base.connection.current_database != Rails.configuration.database_configuration['development']['database']
|
19
|
-
ActiveRecord::Base.establish_connection(:development)
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
result = yield # this returns consumer handler message
|
24
|
-
|
25
|
-
result
|
26
|
-
rescue StandardError => e
|
27
|
-
log.error e
|
28
|
-
raise e
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
@@ -1,14 +0,0 @@
|
|
1
|
-
require 'istox/helpers/logger'
|
2
|
-
|
3
|
-
module Istox
|
4
|
-
module IntegrationTest
|
5
|
-
class RabbitmqPublisherInterceptor
|
6
|
-
def call(message, _options)
|
7
|
-
message[:istox_integration_test] = '1'
|
8
|
-
rescue StandardError => e
|
9
|
-
log.error e
|
10
|
-
raise e
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|