istox 0.1.158.1 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|