sec_id 1.1.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4f48ba902d7677a13e7d53d251473ba5a8b5c0379c06b09ceacda30df79a31d1
4
- data.tar.gz: 1e54b0d518ca576ad853ff0d170026bbf9e0267d419c8a20209e5334a6d15276
3
+ metadata.gz: 2306a429fff8833b4ba24039424d20249a1cb6ebb9e9928707c61f7396933835
4
+ data.tar.gz: 21a9583ecde80ec5a671bbba7ea3492a99c1f128b091a51ea8b3c50d1c3887f5
5
5
  SHA512:
6
- metadata.gz: 10c3e1c32582736216141a539161a6d8aead43bdde27c4ad25ac95fd02ce4009959585248c24a740b0b8752554d36d31dfe2e3d28b1ec1ca92b9b8be2c69b1e7
7
- data.tar.gz: 41a279f6df4045093a9c870c3ddd03845fe508d0d729bc145f59d96351b3580d51285fbae1aa0bef2c176b4c526d4452a5423796b348028ecbe63a9346430f12
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
- WIP:
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', '~> 1.1'
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.isin # => 'US5949181045'
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.cusip # => '594918104'
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
- raise NotImplementedError
50
+ return false unless valid_format?
51
+
52
+ check_digit == calculate_check_digit
51
53
  end
52
54
 
53
55
  def valid_format?
54
- raise NotImplementedError
56
+ identifier ? true : false
55
57
  end
56
58
 
57
59
  def restore!
58
- raise NotImplementedError
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
- VALID_COUNTRY_CODES_FOR_CONVERSION_TO_ISIN = %w[US CA].freeze
14
-
15
- attr_reader :cusip, :cusip6, :issue
13
+ attr_reader :cusip6, :issue
16
14
 
17
15
  def initialize(cusip)
18
- @cusip = cusip.to_s.strip.upcase
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].to_i if 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 '#{cusip}' is invalid and check-digit cannot be calculated!"
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 :isin, :country_code, :nsin
13
+ attr_reader :country_code, :nsin
14
14
 
15
15
  def initialize(isin)
16
- @isin = isin.to_s.strip.upcase
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].to_i if 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 '#{isin}' is invalid and check-digit cannot be calculated!"
26
+ raise InvalidFormatError, "ISIN '#{full_number}' is invalid and check-digit cannot be calculated!"
44
27
  end
45
28
 
46
29
  private
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SecId
4
- VERSION = '1.1.0'
4
+ VERSION = '2.0.0'
5
5
  end
data/lib/sec_id.rb CHANGED
@@ -5,6 +5,7 @@ require 'sec_id/version'
5
5
  require 'sec_id/base'
6
6
  require 'sec_id/isin'
7
7
  require 'sec_id/cusip'
8
+ require 'sec_id/sedol'
8
9
 
9
10
  module SecId
10
11
  Error = Class.new(StandardError)
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: 1.1.0
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