sec_id 1.1.0 → 2.0.0
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/CHANGELOG.md +22 -0
- data/README.md +25 -6
- data/lib/sec_id/base.rb +16 -4
- data/lib/sec_id/cusip.rb +4 -28
- data/lib/sec_id/isin.rb +4 -21
- data/lib/sec_id/sedol.rb +46 -0
- data/lib/sec_id/version.rb +1 -1
- data/lib/sec_id.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2306a429fff8833b4ba24039424d20249a1cb6ebb9e9928707c61f7396933835
|
4
|
+
data.tar.gz: 21a9583ecde80ec5a671bbba7ea3492a99c1f128b091a51ea8b3c50d1c3887f5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 42545c4091db82631984837eb0abda9d34eb78447f25280665835c00e395b750a062037602987451aed333c7b7d233df4df042ea89a60dc1635f0d8904844f57
|
7
|
+
data.tar.gz: c1517768cc621f93d92183b4f47e7159e628d02a0f4936ca9658b0b4743498b300afb6d0a7f140e5616776e6b156aa313299dd02a1429c6f3b74897eecf02211
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,27 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [2.0.0] - 2019-02-03
|
4
|
+
|
5
|
+
### Added
|
6
|
+
|
7
|
+
- SEDOL numbers support: `SecId::SEDOL`
|
8
|
+
|
9
|
+
### Updated
|
10
|
+
|
11
|
+
- **Breaking change**
|
12
|
+
|
13
|
+
API for accessing full number is unified across all classes:
|
14
|
+
|
15
|
+
```
|
16
|
+
SecId::ISIN#full_number # previously SecId::ISIN#isin
|
17
|
+
SecId::CUSIP#full_number # previously SecId::CUSIP#cusip
|
18
|
+
SecId::SEDOL#full_number
|
19
|
+
```
|
20
|
+
|
21
|
+
### Fixed
|
22
|
+
|
23
|
+
- CUSIP check-digit algorithm fixed
|
24
|
+
|
3
25
|
## [1.1.0] - 2019-02-03
|
4
26
|
|
5
27
|
### Added
|
data/README.md
CHANGED
@@ -10,10 +10,10 @@ Check-digit calculation is also available.
|
|
10
10
|
|
11
11
|
Currently supported standards:
|
12
12
|
[ISIN](https://en.wikipedia.org/wiki/International_Securities_Identification_Number),
|
13
|
-
[CUSIP](https://en.wikipedia.org/wiki/CUSIP)
|
13
|
+
[CUSIP](https://en.wikipedia.org/wiki/CUSIP),
|
14
|
+
[SEDOL](https://en.wikipedia.org/wiki/SEDOL)
|
14
15
|
|
15
|
-
|
16
|
-
[SEDOL](https://en.wikipedia.org/wiki/SEDOL),
|
16
|
+
Work in progress:
|
17
17
|
[IBAN](https://en.wikipedia.org/wiki/International_Bank_Account_Number).
|
18
18
|
|
19
19
|
## Installation
|
@@ -21,7 +21,7 @@ WIP:
|
|
21
21
|
Add this line to your application's Gemfile:
|
22
22
|
|
23
23
|
```ruby
|
24
|
-
gem 'sec_id', '~>
|
24
|
+
gem 'sec_id', '~> 2.0'
|
25
25
|
```
|
26
26
|
|
27
27
|
And then execute:
|
@@ -107,7 +107,7 @@ SecId::ISIN.check_digit('US594918104') # => 5
|
|
107
107
|
|
108
108
|
# instance level
|
109
109
|
isin = SecId::ISIN.new('US5949181045')
|
110
|
-
isin.
|
110
|
+
isin.full_number # => 'US5949181045'
|
111
111
|
isin.country_code # => 'US'
|
112
112
|
isin.nsin # => '594918104'
|
113
113
|
isin.check_digit # => 5
|
@@ -128,7 +128,7 @@ SecId::CUSIP.check_digit('59491810') # => 5
|
|
128
128
|
|
129
129
|
# instance level
|
130
130
|
cusip = SecId::CUSIP.new('594918104')
|
131
|
-
cusip.
|
131
|
+
cusip.full_number # => '594918104'
|
132
132
|
cusip.cusip6 # => '594918'
|
133
133
|
cusip.issue # => '10'
|
134
134
|
cusip.check_digit # => 4
|
@@ -138,6 +138,25 @@ cusip.restore! # => '594918104'
|
|
138
138
|
cusip.calculate_check_digit # => 4
|
139
139
|
```
|
140
140
|
|
141
|
+
### SecId::SEDOL full example
|
142
|
+
|
143
|
+
```ruby
|
144
|
+
# class level
|
145
|
+
SecId::SEDOL.valid?('B0Z52W5') # => true
|
146
|
+
SecId::SEDOL.valid_format?('B0Z52W') # => true
|
147
|
+
SecId::SEDOL.restore!('B0Z52W') # => 'B0Z52W5'
|
148
|
+
SecId::SEDOL.check_digit('B0Z52W') # => 5
|
149
|
+
|
150
|
+
# instance level
|
151
|
+
cusip = SecId::SEDOL.new('B0Z52W5')
|
152
|
+
cusip.full_number # => 'B0Z52W5'
|
153
|
+
cusip.check_digit # => 5
|
154
|
+
cusip.valid? # => true
|
155
|
+
cusip.valid_format? # => true
|
156
|
+
cusip.restore! # => 'B0Z52W5'
|
157
|
+
cusip.calculate_check_digit # => 5
|
158
|
+
```
|
159
|
+
|
141
160
|
## Development
|
142
161
|
|
143
162
|
After checking out the repo, run `bin/setup` to install dependencies.
|
data/lib/sec_id/base.rb
CHANGED
@@ -24,7 +24,7 @@ module SecId
|
|
24
24
|
'*' => 36, '@' => 37, '#' => 38
|
25
25
|
}.freeze
|
26
26
|
|
27
|
-
attr_reader :identifier, :check_digit
|
27
|
+
attr_reader :full_number, :identifier, :check_digit
|
28
28
|
|
29
29
|
def self.valid?(id)
|
30
30
|
new(id).valid?
|
@@ -47,15 +47,18 @@ module SecId
|
|
47
47
|
end
|
48
48
|
|
49
49
|
def valid?
|
50
|
-
|
50
|
+
return false unless valid_format?
|
51
|
+
|
52
|
+
check_digit == calculate_check_digit
|
51
53
|
end
|
52
54
|
|
53
55
|
def valid_format?
|
54
|
-
|
56
|
+
identifier ? true : false
|
55
57
|
end
|
56
58
|
|
57
59
|
def restore!
|
58
|
-
|
60
|
+
@check_digit = calculate_check_digit
|
61
|
+
@full_number = to_s
|
59
62
|
end
|
60
63
|
|
61
64
|
def calculate_check_digit
|
@@ -73,6 +76,11 @@ module SecId
|
|
73
76
|
raise NotImplementedError
|
74
77
|
end
|
75
78
|
|
79
|
+
def parse(sec_id_number)
|
80
|
+
@full_number = sec_id_number.to_s.strip.upcase
|
81
|
+
@full_number.match(self.class::ID_REGEX) || {}
|
82
|
+
end
|
83
|
+
|
76
84
|
def char_to_digits(char)
|
77
85
|
CHAR_TO_DIGITS.fetch(char)
|
78
86
|
end
|
@@ -84,5 +92,9 @@ module SecId
|
|
84
92
|
def mod_10(sum)
|
85
93
|
(10 - (sum % 10)) % 10
|
86
94
|
end
|
95
|
+
|
96
|
+
def div_10_mod_10(number)
|
97
|
+
(number / 10) + (number % 10)
|
98
|
+
end
|
87
99
|
end
|
88
100
|
end
|
data/lib/sec_id/cusip.rb
CHANGED
@@ -10,39 +10,20 @@ module SecId
|
|
10
10
|
(?<check_digit>\d)?
|
11
11
|
\z/x.freeze
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
attr_reader :cusip, :cusip6, :issue
|
13
|
+
attr_reader :cusip6, :issue
|
16
14
|
|
17
15
|
def initialize(cusip)
|
18
|
-
|
19
|
-
cusip_parts = @cusip.match(ID_REGEX) || {}
|
20
|
-
|
16
|
+
cusip_parts = parse cusip
|
21
17
|
@identifier = cusip_parts[:identifier]
|
22
18
|
@cusip6 = cusip_parts[:cusip6]
|
23
19
|
@issue = cusip_parts[:issue]
|
24
|
-
@check_digit = cusip_parts[:check_digit]
|
25
|
-
end
|
26
|
-
|
27
|
-
def valid?
|
28
|
-
return false unless valid_format?
|
29
|
-
|
30
|
-
check_digit == calculate_check_digit
|
31
|
-
end
|
32
|
-
|
33
|
-
def valid_format?
|
34
|
-
identifier ? true : false
|
35
|
-
end
|
36
|
-
|
37
|
-
def restore!
|
38
|
-
@check_digit = calculate_check_digit
|
39
|
-
@cusip = to_s
|
20
|
+
@check_digit = cusip_parts[:check_digit]&.to_i
|
40
21
|
end
|
41
22
|
|
42
23
|
def calculate_check_digit
|
43
24
|
return mod_10(modified_luhn_sum) if valid_format?
|
44
25
|
|
45
|
-
raise InvalidFormatError, "CUSIP '#{
|
26
|
+
raise InvalidFormatError, "CUSIP '#{full_number}' is invalid and check-digit cannot be calculated!"
|
46
27
|
end
|
47
28
|
|
48
29
|
private
|
@@ -53,7 +34,6 @@ module SecId
|
|
53
34
|
|
54
35
|
digitized_identifier.reverse.each_slice(2) do |even, odd|
|
55
36
|
double_even = (even || 0) * 2
|
56
|
-
double_even -= 9 if double_even > 9
|
57
37
|
sum += div_10_mod_10(double_even) + div_10_mod_10(odd || 0)
|
58
38
|
end
|
59
39
|
|
@@ -63,9 +43,5 @@ module SecId
|
|
63
43
|
def digitized_identifier
|
64
44
|
@digitized_identifier ||= identifier.each_char.map(&method(:char_to_digit))
|
65
45
|
end
|
66
|
-
|
67
|
-
def div_10_mod_10(number)
|
68
|
-
(number / 10) + (number % 10)
|
69
|
-
end
|
70
46
|
end
|
71
47
|
end
|
data/lib/sec_id/isin.rb
CHANGED
@@ -10,37 +10,20 @@ module SecId
|
|
10
10
|
(?<check_digit>\d)?
|
11
11
|
\z/x.freeze
|
12
12
|
|
13
|
-
attr_reader :
|
13
|
+
attr_reader :country_code, :nsin
|
14
14
|
|
15
15
|
def initialize(isin)
|
16
|
-
|
17
|
-
isin_parts = @isin.match(ID_REGEX) || {}
|
18
|
-
|
16
|
+
isin_parts = parse isin
|
19
17
|
@identifier = isin_parts[:identifier]
|
20
18
|
@country_code = isin_parts[:country_code]
|
21
19
|
@nsin = isin_parts[:nsin]
|
22
|
-
@check_digit = isin_parts[:check_digit]
|
23
|
-
end
|
24
|
-
|
25
|
-
def valid?
|
26
|
-
return false unless valid_format?
|
27
|
-
|
28
|
-
check_digit == calculate_check_digit
|
29
|
-
end
|
30
|
-
|
31
|
-
def valid_format?
|
32
|
-
identifier ? true : false
|
33
|
-
end
|
34
|
-
|
35
|
-
def restore!
|
36
|
-
@check_digit = calculate_check_digit
|
37
|
-
@isin = to_s
|
20
|
+
@check_digit = isin_parts[:check_digit]&.to_i
|
38
21
|
end
|
39
22
|
|
40
23
|
def calculate_check_digit
|
41
24
|
return mod_10(luhn_sum) if valid_format?
|
42
25
|
|
43
|
-
raise InvalidFormatError, "ISIN '#{
|
26
|
+
raise InvalidFormatError, "ISIN '#{full_number}' is invalid and check-digit cannot be calculated!"
|
44
27
|
end
|
45
28
|
|
46
29
|
private
|
data/lib/sec_id/sedol.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SecId
|
4
|
+
# https://en.wikipedia.org/wiki/SEDOL
|
5
|
+
class SEDOL < Base
|
6
|
+
ID_REGEX = /\A
|
7
|
+
(?<identifier>[0-9BCDFGHJKLMNPQRSTVWXYZ]{6})
|
8
|
+
(?<check_digit>\d)?
|
9
|
+
\z/x.freeze
|
10
|
+
|
11
|
+
CHARACTER_WEIGHTS = [1, 3, 1, 7, 3, 9].freeze
|
12
|
+
|
13
|
+
attr_reader :full_number
|
14
|
+
|
15
|
+
def initialize(sedol)
|
16
|
+
sedol_parts = parse sedol
|
17
|
+
@identifier = sedol_parts[:identifier]
|
18
|
+
@check_digit = sedol_parts[:check_digit]&.to_i
|
19
|
+
end
|
20
|
+
|
21
|
+
def calculate_check_digit
|
22
|
+
return mod_10(weighted_sum) if valid_format?
|
23
|
+
|
24
|
+
raise InvalidFormatError, "SEDOL '#{full_number}' is invalid and check-digit cannot be calculated!"
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
# NOTE: I know this isn't the most idiomatic Ruby code, but it's the fastest one
|
30
|
+
def weighted_sum
|
31
|
+
index = 0
|
32
|
+
sum = 0
|
33
|
+
|
34
|
+
while index < digitized_identifier.size
|
35
|
+
sum += digitized_identifier[index] * CHARACTER_WEIGHTS[index]
|
36
|
+
index += 1
|
37
|
+
end
|
38
|
+
|
39
|
+
sum
|
40
|
+
end
|
41
|
+
|
42
|
+
def digitized_identifier
|
43
|
+
@digitized_identifier ||= identifier.each_char.map(&method(:char_to_digit))
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/lib/sec_id/version.rb
CHANGED
data/lib/sec_id.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sec_id
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Leonid Svyatov
|
@@ -117,6 +117,7 @@ files:
|
|
117
117
|
- lib/sec_id/base.rb
|
118
118
|
- lib/sec_id/cusip.rb
|
119
119
|
- lib/sec_id/isin.rb
|
120
|
+
- lib/sec_id/sedol.rb
|
120
121
|
- lib/sec_id/version.rb
|
121
122
|
- sec_id.gemspec
|
122
123
|
homepage: https://github.com/svyatov/sec_id
|