sec_id 4.0.0 → 4.1.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/.github/workflows/main.yml +2 -2
- data/CHANGELOG.md +13 -0
- data/README.md +49 -2
- data/lib/sec_id/cik.rb +33 -0
- data/lib/sec_id/cusip.rb +24 -10
- data/lib/sec_id/figi.rb +53 -0
- data/lib/sec_id/isin.rb +27 -10
- data/lib/sec_id/sedol.rb +4 -2
- data/lib/sec_id/version.rb +1 -1
- data/lib/sec_id.rb +2 -0
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a3391006ed4c0e135a0e94ef0d43da6aae3146a178f13c6237aec8d940931c52
|
4
|
+
data.tar.gz: a572d9b26de1c8c97476cd954caeefacc9fe44ac8ffff7f25d9b4a72ca058e54
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1d380490f4a25d1044734b7724bf7e752e7dbb43097a468d3210e8fd9f4efd185beac207d9b5adfc5cce0cedfd5ea944be269785f444fd7a4bbb449b63ea9962
|
7
|
+
data.tar.gz: 30434ebe94c7f2c7f3a311ab2a3857af1ae0b66b5993c3b659fd9f6852633171cd7e4ce3c73f7d1e554211cbb53259e1e7679236df243f8dfaf311a0a61cebee
|
data/.github/workflows/main.yml
CHANGED
@@ -35,5 +35,5 @@ jobs:
|
|
35
35
|
continue-on-error: ${{ matrix.ruby_version == 'ruby-head' }}
|
36
36
|
|
37
37
|
- uses: paambaati/codeclimate-action@v8.0.0
|
38
|
-
# Only upload coverage for the latest Ruby
|
39
|
-
if: ${{ matrix.ruby_version == '3.3' }}
|
38
|
+
# Only upload coverage for the latest Ruby and don't run for PRs from forks
|
39
|
+
if: ${{ matrix.ruby_version == '3.3' && github.event.pull_request.head.repo.fork == false }}
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,18 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [4.1.0] - 2024-09-23
|
4
|
+
|
5
|
+
### Added
|
6
|
+
|
7
|
+
- FIGI support ([@wtn][], #84)
|
8
|
+
- CIK support ([@wtn][], #85)
|
9
|
+
- Convert between CUSIPs and ISINs ([@wtn][], #86, #88)
|
10
|
+
- CINS check method for CUSIPs ([@wtn][], #87)
|
11
|
+
|
12
|
+
### Updated
|
13
|
+
|
14
|
+
- Small internal refactorings
|
15
|
+
|
3
16
|
## [4.0.0] - 2024-07-09
|
4
17
|
|
5
18
|
### Breaking changes
|
data/README.md
CHANGED
@@ -11,7 +11,9 @@ Check-digit calculation is also available.
|
|
11
11
|
Currently supported standards:
|
12
12
|
[ISIN](https://en.wikipedia.org/wiki/International_Securities_Identification_Number),
|
13
13
|
[CUSIP](https://en.wikipedia.org/wiki/CUSIP),
|
14
|
-
[SEDOL](https://en.wikipedia.org/wiki/SEDOL)
|
14
|
+
[SEDOL](https://en.wikipedia.org/wiki/SEDOL),
|
15
|
+
[FIGI](https://en.wikipedia.org/wiki/Financial_Instrument_Global_Identifier),
|
16
|
+
[CIK](https://en.wikipedia.org/wiki/Central_Index_Key).
|
15
17
|
|
16
18
|
Work in progress:
|
17
19
|
[IBAN](https://en.wikipedia.org/wiki/International_Bank_Account_Number).
|
@@ -21,7 +23,7 @@ Work in progress:
|
|
21
23
|
Add this line to your application's Gemfile:
|
22
24
|
|
23
25
|
```ruby
|
24
|
-
gem 'sec_id', '~> 4.
|
26
|
+
gem 'sec_id', '~> 4.1'
|
25
27
|
```
|
26
28
|
|
27
29
|
And then execute:
|
@@ -115,6 +117,7 @@ isin.valid? # => true
|
|
115
117
|
isin.valid_format? # => true
|
116
118
|
isin.restore! # => 'US5949181045'
|
117
119
|
isin.calculate_check_digit # => 5
|
120
|
+
isin.to_cusip # => #<SecId::CUSIP>
|
118
121
|
```
|
119
122
|
|
120
123
|
### SecId::CUSIP full example
|
@@ -136,6 +139,8 @@ cusip.valid? # => true
|
|
136
139
|
cusip.valid_format? # => true
|
137
140
|
cusip.restore! # => '594918104'
|
138
141
|
cusip.calculate_check_digit # => 4
|
142
|
+
cusip.to_isin('US') # => #<SecId::ISIN>
|
143
|
+
cusip.cins? # => true
|
139
144
|
```
|
140
145
|
|
141
146
|
### SecId::SEDOL full example
|
@@ -157,6 +162,48 @@ cusip.restore! # => 'B0Z52W5'
|
|
157
162
|
cusip.calculate_check_digit # => 5
|
158
163
|
```
|
159
164
|
|
165
|
+
### SecId::FIGI full example
|
166
|
+
|
167
|
+
```ruby
|
168
|
+
# class level
|
169
|
+
SecId::FIGI.valid?('BBG000DMBXR2') # => true
|
170
|
+
SecId::FIGI.valid_format?('BBG000DMBXR2') # => true
|
171
|
+
SecId::FIGI.restore!('BBG000DMBXR') # => 'BBG000DMBXR2'
|
172
|
+
SecId::FIGI.check_digit('BBG000DMBXR') # => 2
|
173
|
+
|
174
|
+
# instance level
|
175
|
+
figi = SecId::FIGI.new('BBG000DMBXR2')
|
176
|
+
figi.full_number # => 'BBG000DMBXR2'
|
177
|
+
figi.prefix # => 'BB'
|
178
|
+
figi.random_part # => '000DMBXR'
|
179
|
+
figi.check_digit # => 2
|
180
|
+
figi.valid? # => true
|
181
|
+
figi.valid_format? # => true
|
182
|
+
figi.restore! # => 'BBG000DMBXR2'
|
183
|
+
figi.calculate_check_digit # => 2
|
184
|
+
```
|
185
|
+
|
186
|
+
### SecId::CIK full example
|
187
|
+
|
188
|
+
```ruby
|
189
|
+
# class level
|
190
|
+
SecId::CIK.valid?('0001094517') # => true
|
191
|
+
SecId::CIK.valid_format?('0001094517') # => true
|
192
|
+
SecId::CIK.restore!('1094517') # => '0001094517'
|
193
|
+
SecId::CIK.check_digit('0001094517') # raises NotImplementedError
|
194
|
+
|
195
|
+
# instance level
|
196
|
+
cik = SecId::CIK.new('0001094517')
|
197
|
+
cik.full_number # => '0001094517'
|
198
|
+
cik.padding # => '000'
|
199
|
+
cik.identifier # => '1094517'
|
200
|
+
cik.valid? # => true
|
201
|
+
cik.valid_format? # => true
|
202
|
+
cik.restore! # => '0001094517'
|
203
|
+
cik.calculate_check_digit # raises NotImplementedError
|
204
|
+
cik.check_digit # => nil
|
205
|
+
```
|
206
|
+
|
160
207
|
## Development
|
161
208
|
|
162
209
|
After checking out the repo, run `bin/setup` to install dependencies.
|
data/lib/sec_id/cik.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SecId
|
4
|
+
# https://en.wikipedia.org/wiki/Central_Index_Key
|
5
|
+
class CIK < Base
|
6
|
+
ID_REGEX = /\A
|
7
|
+
(?=\d{1,10}\z)(?<padding>0*)(?<identifier>[1-9]\d{0,9})
|
8
|
+
\z/x
|
9
|
+
|
10
|
+
attr_reader :padding
|
11
|
+
|
12
|
+
def initialize(cik)
|
13
|
+
cik_parts = parse cik
|
14
|
+
@padding = cik_parts[:padding]
|
15
|
+
@identifier = cik_parts[:identifier]
|
16
|
+
end
|
17
|
+
|
18
|
+
def valid?
|
19
|
+
valid_format?
|
20
|
+
end
|
21
|
+
|
22
|
+
def valid_format?
|
23
|
+
!identifier.nil?
|
24
|
+
end
|
25
|
+
|
26
|
+
def restore!
|
27
|
+
raise InvalidFormatError, "CIK '#{full_number}' is invalid and cannot be restored!" unless valid_format?
|
28
|
+
|
29
|
+
@padding = '0' * (10 - @identifier.length)
|
30
|
+
@full_number = @identifier.rjust(10, '0')
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/sec_id/cusip.rb
CHANGED
@@ -21,27 +21,41 @@ module SecId
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def calculate_check_digit
|
24
|
-
|
24
|
+
unless valid_format?
|
25
|
+
raise InvalidFormatError, "CUSIP '#{full_number}' is invalid and check-digit cannot be calculated!"
|
26
|
+
end
|
27
|
+
|
28
|
+
mod10(modified_luhn_sum)
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_isin(country_code)
|
32
|
+
unless ISIN::CGS_COUNTRY_CODES.include?(country_code)
|
33
|
+
raise(InvalidFormatError, "'#{country_code}' is not a CGS country code!")
|
34
|
+
end
|
25
35
|
|
26
|
-
|
36
|
+
restore!
|
37
|
+
isin = ISIN.new(country_code + full_number)
|
38
|
+
isin.restore!
|
39
|
+
isin
|
40
|
+
end
|
41
|
+
|
42
|
+
# CUSIP International Numbering System
|
43
|
+
def cins?
|
44
|
+
cusip6[0] < '0' || cusip6[0] > '9'
|
27
45
|
end
|
28
46
|
|
29
47
|
private
|
30
48
|
|
31
49
|
# https://en.wikipedia.org/wiki/Luhn_algorithm
|
32
50
|
def modified_luhn_sum
|
33
|
-
sum
|
34
|
-
|
35
|
-
id_digits.reverse.each_slice(2) do |even, odd|
|
51
|
+
reversed_id_digits.each_slice(2).reduce(0) do |sum, (even, odd)|
|
36
52
|
double_even = (even || 0) * 2
|
37
|
-
sum
|
53
|
+
sum + div10mod10(double_even) + div10mod10(odd || 0)
|
38
54
|
end
|
39
|
-
|
40
|
-
sum
|
41
55
|
end
|
42
56
|
|
43
|
-
def
|
44
|
-
|
57
|
+
def reversed_id_digits
|
58
|
+
identifier.each_char.map(&method(:char_to_digit)).reverse!
|
45
59
|
end
|
46
60
|
end
|
47
61
|
end
|
data/lib/sec_id/figi.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
module SecId
|
6
|
+
class FIGI < Base
|
7
|
+
ID_REGEX = /\A
|
8
|
+
(?<identifier>
|
9
|
+
(?<prefix>[B-DF-HJ-NP-TV-Z0-9]{2})
|
10
|
+
G
|
11
|
+
(?<random_part>[B-DF-HJ-NP-TV-Z0-9]{8}))
|
12
|
+
(?<check_digit>\d)?
|
13
|
+
\z/x
|
14
|
+
|
15
|
+
RESTRICTED_PREFIXES = Set.new %w[BS BM GG GB GH KY VG]
|
16
|
+
|
17
|
+
attr_reader :prefix, :random_part
|
18
|
+
|
19
|
+
def initialize(figi)
|
20
|
+
figi_parts = parse figi
|
21
|
+
@identifier = figi_parts[:identifier]
|
22
|
+
@prefix = figi_parts[:prefix]
|
23
|
+
@random_part = figi_parts[:random_part]
|
24
|
+
@check_digit = figi_parts[:check_digit]&.to_i
|
25
|
+
end
|
26
|
+
|
27
|
+
def valid_format?
|
28
|
+
!identifier.nil? && !RESTRICTED_PREFIXES.include?(prefix)
|
29
|
+
end
|
30
|
+
|
31
|
+
def calculate_check_digit
|
32
|
+
unless valid_format?
|
33
|
+
raise InvalidFormatError, "FIGI '#{full_number}' is invalid and check-digit cannot be calculated!"
|
34
|
+
end
|
35
|
+
|
36
|
+
mod10(modified_luhn_sum)
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
# https://en.wikipedia.org/wiki/Luhn_algorithm
|
42
|
+
def modified_luhn_sum
|
43
|
+
reversed_id_digits.each_with_index.reduce(0) do |sum, (digit, index)|
|
44
|
+
digit *= 2 if index.odd?
|
45
|
+
sum + digit.divmod(10).sum
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def reversed_id_digits
|
50
|
+
identifier.each_char.map(&method(:char_to_digit)).reverse!
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
data/lib/sec_id/isin.rb
CHANGED
@@ -10,6 +10,14 @@ module SecId
|
|
10
10
|
(?<check_digit>\d)?
|
11
11
|
\z/x
|
12
12
|
|
13
|
+
CGS_COUNTRY_CODES = Set.new(
|
14
|
+
%w[
|
15
|
+
US CA AG AI AN AR AS AW BB BL BM BO BQ BR BS BZ CL CO CR CW DM DO EC FM
|
16
|
+
GD GS GU GY HN HT JM KN KY LC MF MH MP MX NI PA PE PH PR PW PY SR SV SX
|
17
|
+
TT UM UY VC VE VG VI YT
|
18
|
+
]
|
19
|
+
).freeze
|
20
|
+
|
13
21
|
attr_reader :country_code, :nsin
|
14
22
|
|
15
23
|
def initialize(isin)
|
@@ -21,28 +29,37 @@ module SecId
|
|
21
29
|
end
|
22
30
|
|
23
31
|
def calculate_check_digit
|
24
|
-
|
32
|
+
unless valid_format?
|
33
|
+
raise InvalidFormatError, "ISIN '#{full_number}' is invalid and check-digit cannot be calculated!"
|
34
|
+
end
|
25
35
|
|
26
|
-
|
36
|
+
mod10(luhn_sum)
|
37
|
+
end
|
38
|
+
|
39
|
+
# CUSIP Global Services
|
40
|
+
def cgs?
|
41
|
+
CGS_COUNTRY_CODES.include?(country_code)
|
42
|
+
end
|
43
|
+
|
44
|
+
def to_cusip
|
45
|
+
raise InvalidFormatError, "'#{country_code}' is not a CGS country code!" unless cgs?
|
46
|
+
|
47
|
+
CUSIP.new(nsin)
|
27
48
|
end
|
28
49
|
|
29
50
|
private
|
30
51
|
|
31
52
|
# https://en.wikipedia.org/wiki/Luhn_algorithm
|
32
53
|
def luhn_sum
|
33
|
-
sum
|
34
|
-
|
35
|
-
id_digits.reverse.each_slice(2) do |even, odd|
|
54
|
+
reversed_id_digits.each_slice(2).reduce(0) do |sum, (even, odd)|
|
36
55
|
double_even = (even || 0) * 2
|
37
56
|
double_even -= 9 if double_even > 9
|
38
|
-
sum
|
57
|
+
sum + double_even + (odd || 0)
|
39
58
|
end
|
40
|
-
|
41
|
-
sum
|
42
59
|
end
|
43
60
|
|
44
|
-
def
|
45
|
-
|
61
|
+
def reversed_id_digits
|
62
|
+
identifier.each_char.flat_map(&method(:char_to_digits)).reverse!
|
46
63
|
end
|
47
64
|
end
|
48
65
|
end
|
data/lib/sec_id/sedol.rb
CHANGED
@@ -17,9 +17,11 @@ module SecId
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def calculate_check_digit
|
20
|
-
|
20
|
+
unless valid_format?
|
21
|
+
raise InvalidFormatError, "SEDOL '#{full_number}' is invalid and check-digit cannot be calculated!"
|
22
|
+
end
|
21
23
|
|
22
|
-
|
24
|
+
mod10(weighted_sum)
|
23
25
|
end
|
24
26
|
|
25
27
|
private
|
data/lib/sec_id/version.rb
CHANGED
data/lib/sec_id.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sec_id
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.
|
4
|
+
version: 4.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Leonid Svyatov
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-09-23 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: 'Validate securities identification numbers with ease! Currently supported
|
14
14
|
standards: ISIN, CUSIP, SEDOL.'
|
@@ -32,7 +32,9 @@ files:
|
|
32
32
|
- bin/setup
|
33
33
|
- lib/sec_id.rb
|
34
34
|
- lib/sec_id/base.rb
|
35
|
+
- lib/sec_id/cik.rb
|
35
36
|
- lib/sec_id/cusip.rb
|
37
|
+
- lib/sec_id/figi.rb
|
36
38
|
- lib/sec_id/isin.rb
|
37
39
|
- lib/sec_id/sedol.rb
|
38
40
|
- lib/sec_id/version.rb
|
@@ -57,7 +59,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
57
59
|
- !ruby/object:Gem::Version
|
58
60
|
version: '0'
|
59
61
|
requirements: []
|
60
|
-
rubygems_version: 3.5.
|
62
|
+
rubygems_version: 3.5.15
|
61
63
|
signing_key:
|
62
64
|
specification_version: 4
|
63
65
|
summary: Validate securities identification numbers with ease!
|