sec_id 4.4.1 → 5.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.
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # SecId [![Gem Version](https://img.shields.io/gem/v/sec_id)](https://rubygems.org/gems/sec_id) [![Codecov](https://img.shields.io/codecov/c/github/svyatov/sec_id)](https://app.codecov.io/gh/svyatov/sec_id) [![CI](https://github.com/svyatov/sec_id/actions/workflows/main.yml/badge.svg?branch=main)](https://github.com/svyatov/sec_id/actions?query=workflow%3ACI)
1
+ # SecID [![Gem Version](https://img.shields.io/gem/v/sec_id)](https://rubygems.org/gems/sec_id) [![Codecov](https://img.shields.io/codecov/c/github/svyatov/sec_id)](https://app.codecov.io/gh/svyatov/sec_id) [![CI](https://github.com/svyatov/sec_id/actions/workflows/main.yml/badge.svg?branch=main)](https://github.com/svyatov/sec_id/actions?query=workflow%3ACI)
2
2
 
3
3
  > Validate securities identification numbers with ease!
4
4
 
@@ -7,6 +7,8 @@
7
7
  - [Supported Ruby Versions](#supported-ruby-versions)
8
8
  - [Installation](#installation)
9
9
  - [Supported Standards and Usage](#supported-standards-and-usage)
10
+ - [Metadata Registry](#metadata-registry) - enumerate, filter, look up, and detect identifier types
11
+ - [Structured Validation](#structured-validation) - detailed error codes and messages
10
12
  - [ISIN](#isin) - International Securities Identification Number
11
13
  - [CUSIP](#cusip) - Committee on Uniform Securities Identification Procedures
12
14
  - [CEI](#cei) - CUSIP Entity Identifier
@@ -28,14 +30,14 @@
28
30
 
29
31
  ## Supported Ruby Versions
30
32
 
31
- Ruby 3.1+ is required.
33
+ Ruby 3.2+ is required.
32
34
 
33
35
  ## Installation
34
36
 
35
37
  Add this line to your application's Gemfile:
36
38
 
37
39
  ```ruby
38
- gem 'sec_id', '~> 4.4'
40
+ gem 'sec_id', '~> 5.0'
39
41
  ```
40
42
 
41
43
  And then execute:
@@ -50,16 +52,124 @@ Or install it yourself:
50
52
  gem install sec_id
51
53
  ```
52
54
 
55
+ **Upgrading from v4?** See [MIGRATION.md](MIGRATION.md) for a step-by-step guide.
56
+
53
57
  ## Supported Standards and Usage
54
58
 
55
- All identifier classes provide `valid?` and `valid_format?` methods at both class and instance levels.
59
+ All identifier classes provide `valid?`, `errors`, `validate`, `validate!` methods at both class and instance levels.
60
+
61
+ **All identifiers** support normalization:
62
+ - `.normalize(id)` - strips separators, upcases, validates, and returns the canonical string
63
+ - `#normalized` / `#normalize` - returns the canonical string for a valid instance
64
+ - `#normalize!` - mutates `full_id` to canonical form, returns `self`
56
65
 
57
66
  **Check-digit based identifiers** (ISIN, CUSIP, CEI, SEDOL, FIGI, LEI, IBAN) also provide:
58
- - `restore!` - restores check-digit and returns the full number
67
+ - `restore` / `.restore` - returns the full identifier string with correct check-digit (no mutation)
68
+ - `restore!` / `.restore!` - restores check-digit in place and returns `self` / instance
59
69
  - `check_digit` / `calculate_check_digit` - calculates and returns the check-digit
60
70
 
61
- **Normalization based identifiers** (CIK, OCC, Valoren) provide instead:
62
- - `normalize!` - pads/formats the identifier to its standard form
71
+ ### Metadata Registry
72
+
73
+ All identifier classes are registered automatically and can be enumerated, filtered, and looked up by symbol key:
74
+
75
+ ```ruby
76
+ # Look up by symbol key
77
+ SecID[:isin] # => SecID::ISIN
78
+ SecID[:cusip] # => SecID::CUSIP
79
+
80
+ # Enumerate all identifier classes
81
+ SecID.identifiers # => [SecID::ISIN, SecID::CUSIP, ...]
82
+ SecID.identifiers.map(&:short_name) # => ["ISIN", "CUSIP", "SEDOL", ...]
83
+
84
+ # Query metadata
85
+ SecID::ISIN.short_name # => "ISIN"
86
+ SecID::ISIN.full_name # => "International Securities Identification Number"
87
+ SecID::ISIN.id_length # => 12
88
+ SecID::ISIN.example # => "US5949181045"
89
+ SecID::ISIN.has_check_digit? # => true
90
+
91
+ # Filter with standard Ruby
92
+ SecID.identifiers.select(&:has_check_digit?).map(&:short_name)
93
+ # => ["ISIN", "CUSIP", "SEDOL", "FIGI", "LEI", "IBAN", "CEI"]
94
+
95
+ # Detect identifier type from an unknown string
96
+ # Results are sorted by specificity: check-digit types first, then by length precision
97
+ SecID.detect('US5949181045') # => [:isin]
98
+ SecID.detect('037833100') # => [:cusip, :valoren, :cik]
99
+ SecID.detect('APPLE INC/SH') # => [:fisn]
100
+ SecID.detect('INVALID') # => []
101
+
102
+ # Quick boolean validation
103
+ SecID.valid?('US5949181045') # => true (any type)
104
+ SecID.valid?('INVALID') # => false
105
+ SecID.valid?('US5949181045', types: [:isin]) # => true
106
+ SecID.valid?('594918104', types: %i[cusip sedol]) # => true
107
+ SecID.valid?('US5949181045', types: [:cusip]) # => false
108
+
109
+ # Parse into a typed instance (returns the most specific match)
110
+ SecID.parse('US5949181045') # => #<SecID::ISIN>
111
+ SecID.parse('594918104') # => #<SecID::CUSIP>
112
+ SecID.parse('unknown') # => nil
113
+ SecID.parse('594918104', types: [:cusip]) # => #<SecID::CUSIP>
114
+
115
+ # Bang version raises on failure
116
+ SecID.parse!('US5949181045') # => #<SecID::ISIN>
117
+ SecID.parse!('unknown') # raises SecID::InvalidFormatError
118
+ ```
119
+
120
+ ### Structured Validation
121
+
122
+ All identifier classes provide a Rails-like `#errors` API for detailed error reporting:
123
+
124
+ ```ruby
125
+ isin = SecID::ISIN.new('US5949181040')
126
+ isin.errors.none? # => false
127
+ isin.errors.messages # => ["Check digit '0' is invalid, expected '5'"]
128
+ isin.errors.details # => [{ error: :invalid_check_digit, message: "Check digit '0' is invalid, expected '5'" }]
129
+ isin.errors.any? # => true
130
+ isin.errors.empty? # => false
131
+ isin.errors.size # => 1
132
+ isin.errors.to_a # => same as messages
133
+
134
+ # Class-level convenience method (returns the instance with errors cached)
135
+ SecID::ISIN.validate('US5949181040') # => #<SecID::ISIN>
136
+ SecID::ISIN.validate('US5949181040').errors # => #<SecID::Errors>
137
+ ```
138
+
139
+ **Common error codes** (all identifier types):
140
+ - `:invalid_length` - wrong number of characters
141
+ - `:invalid_characters` - contains characters not allowed for this type
142
+ - `:invalid_format` - correct length and characters but wrong structure
143
+
144
+ **Type-specific error codes:**
145
+ - `:invalid_check_digit` - check digit mismatch (ISIN, CUSIP, SEDOL, FIGI, LEI, IBAN, CEI)
146
+ - `:invalid_prefix` - restricted FIGI prefix (FIGI)
147
+ - `:invalid_category` - unknown CFI category code (CFI)
148
+ - `:invalid_group` - unknown CFI group code for category (CFI)
149
+ - `:invalid_bban` - BBAN format invalid for country (IBAN)
150
+ - `:invalid_date` - unparseable expiration date (OCC)
151
+
152
+ #### Fail-fast validation with `validate!`
153
+
154
+ Use `validate!` when you want to raise an exception on invalid input instead of inspecting errors:
155
+
156
+ ```ruby
157
+ # Returns self when valid (enables chaining)
158
+ SecID::ISIN.new('US5949181045').validate! # => #<SecID::ISIN>
159
+
160
+ # Raises with a descriptive message when invalid
161
+ SecID::ISIN.new('INVALID').validate!
162
+ # => SecID::InvalidFormatError: Expected 12 characters, got 7
163
+
164
+ SecID::ISIN.new('US5949181040').validate!
165
+ # => SecID::InvalidCheckDigitError: Check digit '0' is invalid, expected '5'
166
+
167
+ SecID::FIGI.new('BSG000BLNNH6').validate!
168
+ # => SecID::InvalidStructureError: Prefix 'BS' is restricted
169
+
170
+ # Class-level convenience method (returns the instance)
171
+ isin = SecID::ISIN.validate!('US5949181045') # => #<SecID::ISIN>
172
+ ```
63
173
 
64
174
  ### ISIN
65
175
 
@@ -67,42 +177,42 @@ All identifier classes provide `valid?` and `valid_format?` methods at both clas
67
177
 
68
178
  ```ruby
69
179
  # class level
70
- SecId::ISIN.valid?('US5949181045') # => true
71
- SecId::ISIN.valid_format?('US594918104') # => true
72
- SecId::ISIN.restore!('US594918104') # => 'US5949181045'
73
- SecId::ISIN.check_digit('US594918104') # => 5
180
+ SecID::ISIN.valid?('US5949181045') # => true
181
+ SecID::ISIN.restore('US594918104') # => 'US5949181045'
182
+ SecID::ISIN.restore!('US594918104') # => #<SecID::ISIN>
183
+ SecID::ISIN.check_digit('US594918104') # => 5
74
184
 
75
185
  # instance level
76
- isin = SecId::ISIN.new('US5949181045')
77
- isin.full_number # => 'US5949181045'
186
+ isin = SecID::ISIN.new('US5949181045')
187
+ isin.full_id # => 'US5949181045'
78
188
  isin.country_code # => 'US'
79
189
  isin.nsin # => '594918104'
80
190
  isin.check_digit # => 5
81
191
  isin.valid? # => true
82
- isin.valid_format? # => true
83
- isin.restore! # => 'US5949181045'
192
+ isin.restore # => 'US5949181045'
193
+ isin.restore! # => #<SecID::ISIN> (mutates instance)
84
194
  isin.calculate_check_digit # => 5
85
- isin.to_cusip # => #<SecId::CUSIP>
195
+ isin.to_cusip # => #<SecID::CUSIP>
86
196
  isin.nsin_type # => :cusip
87
- isin.to_nsin # => #<SecId::CUSIP>
197
+ isin.to_nsin # => #<SecID::CUSIP>
88
198
 
89
199
  # NSIN extraction for different countries
90
- SecId::ISIN.new('GB00B02H2F76').nsin_type # => :sedol
91
- SecId::ISIN.new('GB00B02H2F76').to_nsin # => #<SecId::SEDOL>
92
- SecId::ISIN.new('DE0007164600').nsin_type # => :wkn
93
- SecId::ISIN.new('DE0007164600').to_nsin # => #<SecId::WKN>
94
- SecId::ISIN.new('CH0012221716').nsin_type # => :valoren
95
- SecId::ISIN.new('CH0012221716').to_nsin # => #<SecId::Valoren>
96
- SecId::ISIN.new('FR0000120271').nsin_type # => :generic
97
- SecId::ISIN.new('FR0000120271').to_nsin # => '000012027' (raw NSIN string)
200
+ SecID::ISIN.new('GB00B02H2F76').nsin_type # => :sedol
201
+ SecID::ISIN.new('GB00B02H2F76').to_nsin # => #<SecID::SEDOL>
202
+ SecID::ISIN.new('DE0007164600').nsin_type # => :wkn
203
+ SecID::ISIN.new('DE0007164600').to_nsin # => #<SecID::WKN>
204
+ SecID::ISIN.new('CH0012221716').nsin_type # => :valoren
205
+ SecID::ISIN.new('CH0012221716').to_nsin # => #<SecID::Valoren>
206
+ SecID::ISIN.new('FR0000120271').nsin_type # => :generic
207
+ SecID::ISIN.new('FR0000120271').to_nsin # => '000012027' (raw NSIN string)
98
208
 
99
209
  # Type-specific conversion methods with validation
100
- SecId::ISIN.new('GB00B02H2F76').sedol? # => true
101
- SecId::ISIN.new('GB00B02H2F76').to_sedol # => #<SecId::SEDOL>
102
- SecId::ISIN.new('DE0007164600').wkn? # => true
103
- SecId::ISIN.new('DE0007164600').to_wkn # => #<SecId::WKN>
104
- SecId::ISIN.new('CH0012221716').valoren? # => true
105
- SecId::ISIN.new('CH0012221716').to_valoren # => #<SecId::Valoren>
210
+ SecID::ISIN.new('GB00B02H2F76').sedol? # => true
211
+ SecID::ISIN.new('GB00B02H2F76').to_sedol # => #<SecID::SEDOL>
212
+ SecID::ISIN.new('DE0007164600').wkn? # => true
213
+ SecID::ISIN.new('DE0007164600').to_wkn # => #<SecID::WKN>
214
+ SecID::ISIN.new('CH0012221716').valoren? # => true
215
+ SecID::ISIN.new('CH0012221716').to_valoren # => #<SecID::Valoren>
106
216
  ```
107
217
 
108
218
  ### CUSIP
@@ -111,22 +221,22 @@ SecId::ISIN.new('CH0012221716').to_valoren # => #<SecId::Valoren>
111
221
 
112
222
  ```ruby
113
223
  # class level
114
- SecId::CUSIP.valid?('594918104') # => true
115
- SecId::CUSIP.valid_format?('59491810') # => true
116
- SecId::CUSIP.restore!('59491810') # => '594918104'
117
- SecId::CUSIP.check_digit('59491810') # => 4
224
+ SecID::CUSIP.valid?('594918104') # => true
225
+ SecID::CUSIP.restore('59491810') # => '594918104'
226
+ SecID::CUSIP.restore!('59491810') # => #<SecID::CUSIP>
227
+ SecID::CUSIP.check_digit('59491810') # => 4
118
228
 
119
229
  # instance level
120
- cusip = SecId::CUSIP.new('594918104')
121
- cusip.full_number # => '594918104'
230
+ cusip = SecID::CUSIP.new('594918104')
231
+ cusip.full_id # => '594918104'
122
232
  cusip.cusip6 # => '594918'
123
233
  cusip.issue # => '10'
124
234
  cusip.check_digit # => 4
125
235
  cusip.valid? # => true
126
- cusip.valid_format? # => true
127
- cusip.restore! # => '594918104'
236
+ cusip.restore # => '594918104'
237
+ cusip.restore! # => #<SecID::CUSIP> (mutates instance)
128
238
  cusip.calculate_check_digit # => 4
129
- cusip.to_isin('US') # => #<SecId::ISIN>
239
+ cusip.to_isin('US') # => #<SecID::ISIN>
130
240
  cusip.cins? # => false
131
241
  ```
132
242
 
@@ -136,21 +246,21 @@ cusip.cins? # => false
136
246
 
137
247
  ```ruby
138
248
  # class level
139
- SecId::CEI.valid?('A0BCDEFGH1') # => true
140
- SecId::CEI.valid_format?('A0BCDEFGH') # => true
141
- SecId::CEI.restore!('A0BCDEFGH') # => 'A0BCDEFGH1'
142
- SecId::CEI.check_digit('A0BCDEFGH') # => 1
249
+ SecID::CEI.valid?('A0BCDEFGH1') # => true
250
+ SecID::CEI.restore('A0BCDEFGH') # => 'A0BCDEFGH1'
251
+ SecID::CEI.restore!('A0BCDEFGH') # => #<SecID::CEI>
252
+ SecID::CEI.check_digit('A0BCDEFGH') # => 1
143
253
 
144
254
  # instance level
145
- cei = SecId::CEI.new('A0BCDEFGH1')
146
- cei.full_number # => 'A0BCDEFGH1'
255
+ cei = SecID::CEI.new('A0BCDEFGH1')
256
+ cei.full_id # => 'A0BCDEFGH1'
147
257
  cei.prefix # => 'A'
148
258
  cei.numeric # => '0'
149
259
  cei.entity_id # => 'BCDEFGH'
150
260
  cei.check_digit # => 1
151
261
  cei.valid? # => true
152
- cei.valid_format? # => true
153
- cei.restore! # => 'A0BCDEFGH1'
262
+ cei.restore # => 'A0BCDEFGH1'
263
+ cei.restore! # => #<SecID::CEI> (mutates instance)
154
264
  cei.calculate_check_digit # => 1
155
265
  ```
156
266
 
@@ -160,21 +270,21 @@ cei.calculate_check_digit # => 1
160
270
 
161
271
  ```ruby
162
272
  # class level
163
- SecId::SEDOL.valid?('B0Z52W5') # => true
164
- SecId::SEDOL.valid_format?('B0Z52W') # => true
165
- SecId::SEDOL.restore!('B0Z52W') # => 'B0Z52W5'
166
- SecId::SEDOL.check_digit('B0Z52W') # => 5
273
+ SecID::SEDOL.valid?('B0Z52W5') # => true
274
+ SecID::SEDOL.restore('B0Z52W') # => 'B0Z52W5'
275
+ SecID::SEDOL.restore!('B0Z52W') # => #<SecID::SEDOL>
276
+ SecID::SEDOL.check_digit('B0Z52W') # => 5
167
277
 
168
278
  # instance level
169
- sedol = SecId::SEDOL.new('B0Z52W5')
170
- sedol.full_number # => 'B0Z52W5'
279
+ sedol = SecID::SEDOL.new('B0Z52W5')
280
+ sedol.full_id # => 'B0Z52W5'
171
281
  sedol.check_digit # => 5
172
282
  sedol.valid? # => true
173
- sedol.valid_format? # => true
174
- sedol.restore! # => 'B0Z52W5'
283
+ sedol.restore # => 'B0Z52W5'
284
+ sedol.restore! # => #<SecID::SEDOL> (mutates instance)
175
285
  sedol.calculate_check_digit # => 5
176
- sedol.to_isin # => #<SecId::ISIN> (GB ISIN by default)
177
- sedol.to_isin('IE') # => #<SecId::ISIN> (IE ISIN)
286
+ sedol.to_isin # => #<SecID::ISIN> (GB ISIN by default)
287
+ sedol.to_isin('IE') # => #<SecID::ISIN> (IE ISIN)
178
288
  ```
179
289
 
180
290
  ### FIGI
@@ -183,20 +293,20 @@ sedol.to_isin('IE') # => #<SecId::ISIN> (IE ISIN)
183
293
 
184
294
  ```ruby
185
295
  # class level
186
- SecId::FIGI.valid?('BBG000DMBXR2') # => true
187
- SecId::FIGI.valid_format?('BBG000DMBXR2') # => true
188
- SecId::FIGI.restore!('BBG000DMBXR') # => 'BBG000DMBXR2'
189
- SecId::FIGI.check_digit('BBG000DMBXR') # => 2
296
+ SecID::FIGI.valid?('BBG000DMBXR2') # => true
297
+ SecID::FIGI.restore('BBG000DMBXR') # => 'BBG000DMBXR2'
298
+ SecID::FIGI.restore!('BBG000DMBXR') # => #<SecID::FIGI>
299
+ SecID::FIGI.check_digit('BBG000DMBXR') # => 2
190
300
 
191
301
  # instance level
192
- figi = SecId::FIGI.new('BBG000DMBXR2')
193
- figi.full_number # => 'BBG000DMBXR2'
302
+ figi = SecID::FIGI.new('BBG000DMBXR2')
303
+ figi.full_id # => 'BBG000DMBXR2'
194
304
  figi.prefix # => 'BB'
195
305
  figi.random_part # => '000DMBXR'
196
306
  figi.check_digit # => 2
197
307
  figi.valid? # => true
198
- figi.valid_format? # => true
199
- figi.restore! # => 'BBG000DMBXR2'
308
+ figi.restore # => 'BBG000DMBXR2'
309
+ figi.restore! # => #<SecID::FIGI> (mutates instance)
200
310
  figi.calculate_check_digit # => 2
201
311
  ```
202
312
 
@@ -206,21 +316,21 @@ figi.calculate_check_digit # => 2
206
316
 
207
317
  ```ruby
208
318
  # class level
209
- SecId::LEI.valid?('5493006MHB84DD0ZWV18') # => true
210
- SecId::LEI.valid_format?('5493006MHB84DD0ZWV') # => true
211
- SecId::LEI.restore!('5493006MHB84DD0ZWV') # => '5493006MHB84DD0ZWV18'
212
- SecId::LEI.check_digit('5493006MHB84DD0ZWV') # => 18
319
+ SecID::LEI.valid?('5493006MHB84DD0ZWV18') # => true
320
+ SecID::LEI.restore('5493006MHB84DD0ZWV') # => '5493006MHB84DD0ZWV18'
321
+ SecID::LEI.restore!('5493006MHB84DD0ZWV') # => #<SecID::LEI>
322
+ SecID::LEI.check_digit('5493006MHB84DD0ZWV') # => 18
213
323
 
214
324
  # instance level
215
- lei = SecId::LEI.new('5493006MHB84DD0ZWV18')
216
- lei.full_number # => '5493006MHB84DD0ZWV18'
325
+ lei = SecID::LEI.new('5493006MHB84DD0ZWV18')
326
+ lei.full_id # => '5493006MHB84DD0ZWV18'
217
327
  lei.lou_id # => '5493'
218
328
  lei.reserved # => '00'
219
329
  lei.entity_id # => '6MHB84DD0ZWV'
220
330
  lei.check_digit # => 18
221
331
  lei.valid? # => true
222
- lei.valid_format? # => true
223
- lei.restore! # => '5493006MHB84DD0ZWV18'
332
+ lei.restore # => '5493006MHB84DD0ZWV18'
333
+ lei.restore! # => #<SecID::LEI> (mutates instance)
224
334
  lei.calculate_check_digit # => 18
225
335
  ```
226
336
 
@@ -230,22 +340,22 @@ lei.calculate_check_digit # => 18
230
340
 
231
341
  ```ruby
232
342
  # class level
233
- SecId::IBAN.valid?('DE89370400440532013000') # => true
234
- SecId::IBAN.valid_format?('DE370400440532013000') # => true
235
- SecId::IBAN.restore!('DE370400440532013000') # => 'DE89370400440532013000'
236
- SecId::IBAN.check_digit('DE370400440532013000') # => 89
343
+ SecID::IBAN.valid?('DE89370400440532013000') # => true
344
+ SecID::IBAN.restore('DE370400440532013000') # => 'DE89370400440532013000'
345
+ SecID::IBAN.restore!('DE370400440532013000') # => #<SecID::IBAN>
346
+ SecID::IBAN.check_digit('DE370400440532013000') # => 89
237
347
 
238
348
  # instance level
239
- iban = SecId::IBAN.new('DE89370400440532013000')
240
- iban.full_number # => 'DE89370400440532013000'
349
+ iban = SecID::IBAN.new('DE89370400440532013000')
350
+ iban.full_id # => 'DE89370400440532013000'
241
351
  iban.country_code # => 'DE'
242
352
  iban.bban # => '370400440532013000'
243
353
  iban.bank_code # => '37040044'
244
354
  iban.account_number # => '0532013000'
245
355
  iban.check_digit # => 89
246
356
  iban.valid? # => true
247
- iban.valid_format? # => true
248
- iban.restore! # => 'DE89370400440532013000'
357
+ iban.restore # => 'DE89370400440532013000'
358
+ iban.restore! # => #<SecID::IBAN> (mutates instance)
249
359
  iban.calculate_check_digit # => 89
250
360
  iban.known_country? # => true
251
361
  ```
@@ -258,58 +368,50 @@ Full BBAN structural validation is supported for EU/EEA countries. Other countri
258
368
 
259
369
  ```ruby
260
370
  # class level
261
- SecId::CIK.valid?('0001094517') # => true
262
- SecId::CIK.valid_format?('0001094517') # => true
263
- SecId::CIK.normalize!('1094517') # => '0001094517'
371
+ SecID::CIK.valid?('0001094517') # => true
372
+ SecID::CIK.normalize('1094517') # => '0001094517'
264
373
 
265
374
  # instance level
266
- cik = SecId::CIK.new('0001094517')
267
- cik.full_number # => '0001094517'
375
+ cik = SecID::CIK.new('0001094517')
376
+ cik.full_id # => '0001094517'
268
377
  cik.padding # => '000'
269
378
  cik.identifier # => '1094517'
270
379
  cik.valid? # => true
271
- cik.valid_format? # => true
272
- cik.normalize! # => '0001094517'
273
- cik.to_s # => '0001094517'
380
+ cik.normalized # => '0001094517'
381
+ cik.normalize! # => #<SecID::CIK> (mutates full_id, returns self)
274
382
  ```
275
383
 
276
384
  ### OCC
277
385
 
278
- > [Options Clearing Corporation Symbol](https://en.wikipedia.org/wiki/Option_symbol#The_OCC_Option_Symbol) - a 21-character code used to identify equity options contracts.
386
+ > [Options Clearing Corporation Symbol](https://en.wikipedia.org/wiki/Option_symbol#The_OCC_Option_Symbol) - a 16-to-21-character code used to identify equity options contracts.
279
387
 
280
388
  ```ruby
281
389
  # class level
282
- SecId::OCC.valid?('BRKB 100417C00090000') # => true
283
- SecId::OCC.valid_format?('BRKB 100417C00090000') # => true
284
- SecId::OCC.normalize!('BRKB100417C00090000') # => 'BRKB 100417C00090000'
285
- SecId::OCC.build(
390
+ SecID::OCC.valid?('BRKB 100417C00090000') # => true
391
+ SecID::OCC.normalize('BRKB100417C00090000') # => 'BRKB 100417C00090000'
392
+ SecID::OCC.build(
286
393
  underlying: 'BRKB',
287
394
  date: Date.new(2010, 4, 17),
288
395
  type: 'C',
289
396
  strike: 90,
290
- ) # => #<SecId::OCC>
397
+ ) # => #<SecID::OCC>
291
398
 
292
399
  # instance level
293
- occ = SecId::OCC.new('BRKB 100417C00090000')
294
- occ.full_symbol # => 'BRKB 100417C00090000'
400
+ occ = SecID::OCC.new('BRKB 100417C00090000')
401
+ occ.full_id # => 'BRKB 100417C00090000'
295
402
  occ.underlying # => 'BRKB'
296
403
  occ.date_str # => '100417'
297
404
  occ.date_obj # => #<Date: 2010-04-17>
298
405
  occ.type # => 'C'
299
406
  occ.strike # => 90.0
300
407
  occ.valid? # => true
301
- occ.valid_format? # => true
302
- occ.normalize! # => 'BRKB 100417C00090000'
408
+ occ.normalized # => 'BRKB 100417C00090000'
303
409
 
304
- occ = SecId::OCC.new('BRKB 2010-04-17C00090000')
305
- occ.valid_format? # => false
306
- occ.normalize! # raises SecId::InvalidFormatError
307
-
308
- occ = SecId::OCC.new('X 250620C00050000')
309
- occ.full_symbol # => 'X 250620C00050000'
410
+ occ = SecID::OCC.new('X 250620C00050000')
411
+ occ.full_id # => 'X 250620C00050000'
310
412
  occ.valid? # => true
311
- occ.normalize! # => 'X 250620C00050000'
312
- occ.full_symbol # => 'X 250620C00050000'
413
+ occ.normalize! # => #<SecID::OCC> (mutates full_id, returns self)
414
+ occ.full_id # => 'X 250620C00050000'
313
415
  ```
314
416
 
315
417
  ### WKN
@@ -318,18 +420,16 @@ occ.full_symbol # => 'X 250620C00050000'
318
420
 
319
421
  ```ruby
320
422
  # class level
321
- SecId::WKN.valid?('514000') # => true
322
- SecId::WKN.valid?('CBK100') # => true
323
- SecId::WKN.valid_format?('514000') # => true
423
+ SecID::WKN.valid?('514000') # => true
424
+ SecID::WKN.valid?('CBK100') # => true
324
425
 
325
426
  # instance level
326
- wkn = SecId::WKN.new('514000')
327
- wkn.full_number # => '514000'
427
+ wkn = SecID::WKN.new('514000')
428
+ wkn.full_id # => '514000'
328
429
  wkn.identifier # => '514000'
329
430
  wkn.valid? # => true
330
- wkn.valid_format? # => true
331
431
  wkn.to_s # => '514000'
332
- wkn.to_isin # => #<SecId::ISIN> (DE ISIN)
432
+ wkn.to_isin # => #<SecID::ISIN> (DE ISIN)
333
433
  ```
334
434
 
335
435
  WKN excludes letters I and O to avoid confusion with digits 1 and 0.
@@ -340,24 +440,22 @@ WKN excludes letters I and O to avoid confusion with digits 1 and 0.
340
440
 
341
441
  ```ruby
342
442
  # class level
343
- SecId::Valoren.valid?('3886335') # => true
344
- SecId::Valoren.valid?('24476758') # => true
345
- SecId::Valoren.valid?('35514757') # => true
346
- SecId::Valoren.valid?('97429325') # => true
347
- SecId::Valoren.valid_format?('3886335') # => true
348
- SecId::Valoren.normalize!('3886335') # => '003886335'
443
+ SecID::Valoren.valid?('3886335') # => true
444
+ SecID::Valoren.valid?('24476758') # => true
445
+ SecID::Valoren.valid?('35514757') # => true
446
+ SecID::Valoren.valid?('97429325') # => true
447
+ SecID::Valoren.normalize('3886335') # => '003886335'
349
448
 
350
449
  # instance level
351
- valoren = SecId::Valoren.new('3886335')
352
- valoren.full_number # => '3886335'
450
+ valoren = SecID::Valoren.new('3886335')
451
+ valoren.full_id # => '3886335'
353
452
  valoren.padding # => ''
354
453
  valoren.identifier # => '3886335'
355
454
  valoren.valid? # => true
356
- valoren.valid_format? # => true
357
- valoren.normalize! # => '003886335'
358
- valoren.to_s # => '003886335'
359
- valoren.to_isin # => #<SecId::ISIN> (CH ISIN by default)
360
- valoren.to_isin('LI') # => #<SecId::ISIN> (LI ISIN)
455
+ valoren.normalized # => '003886335'
456
+ valoren.normalize! # => #<SecID::Valoren> (mutates full_id, returns self)
457
+ valoren.to_isin # => #<SecID::ISIN> (CH ISIN by default)
458
+ valoren.to_isin('LI') # => #<SecID::ISIN> (LI ISIN)
361
459
  ```
362
460
 
363
461
  ### CFI
@@ -366,20 +464,17 @@ valoren.to_isin('LI') # => #<SecId::ISIN> (LI ISIN)
366
464
 
367
465
  ```ruby
368
466
  # class level
369
- SecId::CFI.valid?('ESXXXX') # => true
370
- SecId::CFI.valid?('ESVUFR') # => true
371
- SecId::CFI.valid_format?('ESXXXX') # => true
372
-
467
+ SecID::CFI.valid?('ESXXXX') # => true
468
+ SecID::CFI.valid?('ESVUFR') # => true
373
469
  # instance level
374
- cfi = SecId::CFI.new('ESVUFR')
375
- cfi.full_number # => 'ESVUFR'
470
+ cfi = SecID::CFI.new('ESVUFR')
471
+ cfi.full_id # => 'ESVUFR'
376
472
  cfi.identifier # => 'ESVUFR'
377
473
  cfi.category_code # => 'E'
378
474
  cfi.group_code # => 'S'
379
475
  cfi.category # => :equity
380
476
  cfi.group # => :common_shares
381
477
  cfi.valid? # => true
382
- cfi.valid_format? # => true
383
478
 
384
479
  # Equity-specific predicates
385
480
  cfi.equity? # => true
@@ -397,18 +492,15 @@ CFI validates the category code (position 1) against 14 valid values and the gro
397
492
 
398
493
  ```ruby
399
494
  # class level
400
- SecId::FISN.valid?('APPLE INC/SH') # => true
401
- SecId::FISN.valid?('apple inc/sh') # => true (normalized to uppercase)
402
- SecId::FISN.valid_format?('APPLE INC/SH') # => true
403
-
495
+ SecID::FISN.valid?('APPLE INC/SH') # => true
496
+ SecID::FISN.valid?('apple inc/sh') # => true (normalized to uppercase)
404
497
  # instance level
405
- fisn = SecId::FISN.new('APPLE INC/SH')
406
- fisn.full_number # => 'APPLE INC/SH'
498
+ fisn = SecID::FISN.new('APPLE INC/SH')
499
+ fisn.full_id # => 'APPLE INC/SH'
407
500
  fisn.identifier # => 'APPLE INC/SH'
408
501
  fisn.issuer # => 'APPLE INC'
409
502
  fisn.description # => 'SH'
410
503
  fisn.valid? # => true
411
- fisn.valid_format? # => true
412
504
  fisn.to_s # => 'APPLE INC/SH'
413
505
  ```
414
506