sec_id 4.2.0 → 4.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 58a56c99692f66ae9ff3be10064ab29e729f34d6877456a6c998ee1a3509478d
4
- data.tar.gz: ed1ead1827a544b8c48894739916781ea62337aa75e26f7263dd54c08f70b074
3
+ metadata.gz: d1a703602defc67f745ab0994c73f61d964ed1047a029cc01c2cf7edcda82045
4
+ data.tar.gz: cf7a004bb992ceb3a9991946d09c908a76d13852c7a87a0b2527a4633da869be
5
5
  SHA512:
6
- metadata.gz: 437a5b95d0e14cead4d0392dbaa2da4fbc7c3325a01252f17032ca50e9dc3609a6dfe8e73947535de2e2fe228c460c33847635f90c0d99fddf6ff32c66117fbb
7
- data.tar.gz: d2e44281f824c033add9074633432291794789b4f1b89e50b5977b1dfd34efd68e5dd2883c7c9076cf5e30316b6a8e487853185824f988827cad61e44e2c38c9
6
+ metadata.gz: c4e726ce2c1cd7b64dc6d2f8db7e284166ff0229eef52f064975d244e766e28ba239dd8f39dc1730e3b092ae0e57cf982adb683711cde6dd9b15a4bc8c88eb09
7
+ data.tar.gz: '0986116c63812cc37af215b4f564e3db44d4ea154d90dc49a99e82096fe18e099277087a2b399920e2bf03acbdc69d098b6442c2195d434f2af3230cffe2b257'
data/CHANGELOG.md CHANGED
@@ -1,16 +1,66 @@
1
1
  # Changelog
2
2
 
3
- ## [4.2.0] - 2025-01-12
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
+ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html)
7
+ and [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/).
8
+
9
+ ## [Unreleased]
10
+
11
+ ## [4.4.0] - 2026-01-29
4
12
 
5
13
  ### Added
6
14
 
7
- - OCC support ([@wtn](https://github.com/wtn), [#93](https://github.com/svyatov/sec_id/pull/93))
15
+ - Cross-identifier conversions: SEDOL, WKN, and Valoren `to_isin` methods with country code validation; ISIN `to_sedol`, `to_wkn`, `to_valoren` methods with predicate helpers (`sedol?`, `wkn?`, `valoren?`) ([#115](https://github.com/svyatov/sec_id/pull/115))
16
+ - ISIN `nsin_type` and `to_nsin` methods for country-aware NSIN extraction ([#114](https://github.com/svyatov/sec_id/pull/114))
17
+ - CEI (CUSIP Entity Identifier) support for syndicated loan market entity identification ([#113](https://github.com/svyatov/sec_id/pull/113))
18
+ - FISN (Financial Instrument Short Name) support per ISO 18774 ([#112](https://github.com/svyatov/sec_id/pull/112))
19
+ - CFI (Classification of Financial Instruments) support with category/group validation and equity-specific predicates ([#111](https://github.com/svyatov/sec_id/pull/111))
20
+ - Valoren support (Swiss Security Number) ([@wtn](https://github.com/wtn), [#109](https://github.com/svyatov/sec_id/pull/109))
21
+ - WKN support (Wertpapierkennnummer - German securities identifier) ([@wtn](https://github.com/wtn), [#108](https://github.com/svyatov/sec_id/pull/108))
22
+
23
+ ### Changed
24
+
25
+ - Replaced `has_check_digit` DSL with explicit `Checkable` concern that consolidates all check-digit logic (constants, Luhn algorithms, validation, restoration)
26
+ - Simplified `Base` class to core validation and parsing; check-digit classes now `include Checkable`
27
+ - Non-check-digit classes (CIK, OCC, WKN, Valoren, CFI, FISN) no longer need any special declaration
28
+ - Moved `Normalizable` module to `lib/sec_id/concerns/` for consistency with other concerns
29
+ - Optimized hot paths by replacing `&method(:char_to_digit)` with inline blocks to avoid Method object allocation
30
+ - Added frozen Set constants for ISIN country code lookups (`SEDOL_COUNTRY_CODES`, `VALOREN_COUNTRY_CODES`)
8
31
 
9
32
  ### Fixed
10
33
 
11
- - CUSIP#cins? usage example in README ([@wtn](https://github.com/wtn), [#91](https://github.com/svyatov/sec_id/pull/91))
34
+ - Allow Crown Dependencies (GG, IM, JE) and Overseas Territories (FK) in SEDOL/ISIN conversions ([@wtn](https://github.com/wtn), [#117](https://github.com/svyatov/sec_id/pull/117))
35
+ - Removed BR (Brazil) from CGS country codes — Brazil never used CINS numbers and Brazilian ISINs cannot be converted to CUSIP ([@wtn](https://github.com/wtn), [#110](https://github.com/svyatov/sec_id/pull/110))
12
36
 
13
- ### Updated
37
+ ## [4.3.0] - 2025-01-13
38
+
39
+ ### Added
40
+
41
+ - LEI support (Legal Entity Identifier, ISO 17442)
42
+ - IBAN support (International Bank Account Number, ISO 13616) with EU/EEA country validation
43
+
44
+ ### Changed
45
+
46
+ - Improved README: better formatting, navigation, and clear API distinction between check-digit and normalization identifiers
47
+ - Refactored CIK and OCC to inherit from Base class with `has_check_digit?` hook for cleaner architecture
48
+ - Added `Normalizable` module for consistent `normalize!` class method across identifiers
49
+ - Added `validate_format_for_calculation!` helper method to Base class to reduce code duplication
50
+ - Added comprehensive YARD documentation to all classes (public and private methods)
51
+ - Applied Stepdown Rule to method ordering throughout codebase
52
+ - Created shared RSpec examples for edge cases (nil, empty, whitespace inputs)
53
+ - Created shared RSpec examples for check-digit and normalization identifiers
54
+ - Applied shared examples to all identifier specs, removing ~350 lines of duplicate test code
55
+ - Improved test maintainability with 582 tests covering all identifier types
56
+
57
+ ## [4.2.0] - 2025-01-12
58
+
59
+ ### Added
60
+
61
+ - OCC support ([@wtn](https://github.com/wtn), [#93](https://github.com/svyatov/sec_id/pull/93))
62
+
63
+ ### Changed
14
64
 
15
65
  - Separate CIK from Base for cleaner architecture ([@wtn](https://github.com/wtn), [#92](https://github.com/svyatov/sec_id/pull/92))
16
66
  - Use rubocop-rspec plugin ([@wtn](https://github.com/wtn), [#90](https://github.com/svyatov/sec_id/pull/90))
@@ -18,6 +68,10 @@
18
68
  - Add permissions to CI workflow
19
69
  - Clean up gemspec: update description and simplify files list
20
70
 
71
+ ### Fixed
72
+
73
+ - CUSIP#cins? usage example in README ([@wtn](https://github.com/wtn), [#91](https://github.com/svyatov/sec_id/pull/91))
74
+
21
75
  ## [4.1.0] - 2024-09-23
22
76
 
23
77
  ### Added
@@ -27,19 +81,16 @@
27
81
  - Convert between CUSIPs and ISINs ([@wtn](https://github.com/wtn), [#86](https://github.com/svyatov/sec_id/pull/86), [#88](https://github.com/svyatov/sec_id/pull/88))
28
82
  - CINS check method for CUSIPs ([@wtn](https://github.com/wtn), [#87](https://github.com/svyatov/sec_id/pull/87))
29
83
 
30
- ### Updated
84
+ ### Changed
31
85
 
32
86
  - Small internal refactorings
33
87
 
34
88
  ## [4.0.0] - 2024-07-09
35
89
 
36
- ### Breaking changes
37
-
38
- - Minimum required Ruby version is 3.1 now
39
- - Default repository branch renamed to `main`
40
-
41
- ### Updated
90
+ ### Changed
42
91
 
92
+ - **BREAKING:** Minimum required Ruby version is 3.1 now
93
+ - **BREAKING:** Default repository branch renamed to `main`
43
94
  - Small internal refactorings
44
95
  - TravisCI -> GitHub Actions
45
96
  - Dropped tests for Ruby below 3.1
@@ -47,12 +98,9 @@
47
98
 
48
99
  ## [3.0.0] - 2020-03-10
49
100
 
50
- ### Breaking changes
51
-
52
- - Minimum required Ruby version is 2.5 now
53
-
54
- ### Updated
101
+ ### Changed
55
102
 
103
+ - **BREAKING:** Minimum required Ruby version is 2.5 now
56
104
  - Small internal refactorings
57
105
  - TravisCI config updated: dropped Ruby 2.3 and 2.4, added Ruby 2.7
58
106
  - Rubocop's Ruby target version changed to 2.5
@@ -63,11 +111,9 @@
63
111
 
64
112
  - SEDOL numbers support: `SecId::SEDOL`
65
113
 
66
- ### Updated
67
-
68
- - **Breaking change**
114
+ ### Changed
69
115
 
70
- API for accessing full number is unified across all classes:
116
+ - **BREAKING:** API for accessing full number is unified across all classes:
71
117
 
72
118
  ```
73
119
  SecId::ISIN#full_number # previously SecId::ISIN#isin
@@ -86,7 +132,7 @@
86
132
  - CUSIP numbers support: `SecId::CUSIP`
87
133
  - CHANGELOG.md file added
88
134
 
89
- ### Updated
135
+ ### Changed
90
136
 
91
137
  - Char to digit conversion now uses precalculated tables instead of dynamic calculation for speed
92
138
 
data/README.md CHANGED
@@ -1,104 +1,69 @@
1
- # SecId
2
- [![Gem Version](https://badge.fury.io/rb/sec_id.svg)](https://badge.fury.io/rb/sec_id)
3
- [![codecov](https://codecov.io/gh/svyatov/sec_id/graph/badge.svg)](https://codecov.io/gh/svyatov/sec_id)
4
- ![Build Status](https://github.com/svyatov/sec_id/actions/workflows/main.yml/badge.svg?branch=main)
5
-
6
- Validate securities identification numbers with ease!
7
-
8
- Check-digit calculation is also available.
9
-
10
- Currently supported standards:
11
- [ISIN](https://en.wikipedia.org/wiki/International_Securities_Identification_Number),
12
- [CUSIP](https://en.wikipedia.org/wiki/CUSIP),
13
- [SEDOL](https://en.wikipedia.org/wiki/SEDOL),
14
- [FIGI](https://en.wikipedia.org/wiki/Financial_Instrument_Global_Identifier),
15
- [CIK](https://en.wikipedia.org/wiki/Central_Index_Key),
16
- [OCC](https://en.wikipedia.org/wiki/Option_symbol#The_OCC_Option_Symbol).
17
-
18
- Work in progress:
19
- [IBAN](https://en.wikipedia.org/wiki/International_Bank_Account_Number).
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
+
3
+ > Validate securities identification numbers with ease!
4
+
5
+ ## Table of Contents
6
+
7
+ - [Supported Ruby Versions](#supported-ruby-versions)
8
+ - [Installation](#installation)
9
+ - [Supported Standards and Usage](#supported-standards-and-usage)
10
+ - [ISIN](#isin) - International Securities Identification Number
11
+ - [CUSIP](#cusip) - Committee on Uniform Securities Identification Procedures
12
+ - [CEI](#cei) - CUSIP Entity Identifier
13
+ - [SEDOL](#sedol) - Stock Exchange Daily Official List
14
+ - [FIGI](#figi) - Financial Instrument Global Identifier
15
+ - [LEI](#lei) - Legal Entity Identifier
16
+ - [IBAN](#iban) - International Bank Account Number
17
+ - [CIK](#cik) - Central Index Key
18
+ - [OCC](#occ) - Options Clearing Corporation Symbol
19
+ - [WKN](#wkn) - Wertpapierkennnummer
20
+ - [Valoren](#valoren) - Swiss Security Number
21
+ - [CFI](#cfi) - Classification of Financial Instruments
22
+ - [FISN](#fisn) - Financial Instrument Short Name
23
+ - [Development](#development)
24
+ - [Contributing](#contributing)
25
+ - [Changelog](#changelog)
26
+ - [Versioning](#versioning)
27
+ - [License](#license)
28
+
29
+ ## Supported Ruby Versions
30
+
31
+ Ruby 3.1+ is required.
20
32
 
21
33
  ## Installation
22
34
 
23
35
  Add this line to your application's Gemfile:
24
36
 
25
37
  ```ruby
26
- gem 'sec_id', '~> 4.2'
38
+ gem 'sec_id', '~> 4.4'
27
39
  ```
28
40
 
29
41
  And then execute:
30
42
 
31
- $ bundle
32
-
33
- Or install it yourself as:
34
-
35
- $ gem install sec_id
36
-
37
- ## Usage
38
-
39
- ### Base API
40
-
41
- Base API has 4 main methods which can be used both on class level and on instance level:
42
-
43
- * `valid?` - never raises any errors, always returns `true` or `false`,
44
- numbers without the check-digit will return `false`
45
-
46
- ```ruby
47
- # class level
48
- SecId::ISIN.valid?('US5949181045') # => true
49
- SecId::ISIN.valid?('US594918104') # => false
50
-
51
- # instance level
52
- isin = SecId::ISIN.new('US5949181045')
53
- isin.valid? # => true
54
- ```
55
-
56
- * `valid_format?` - never raises any errors, always returns `true` or `false`,
57
- numbers without the check-digit but in valid format will return `true`
58
-
59
- ```ruby
60
- # class level
61
- SecId::ISIN.valid_format?('US5949181045') # => true
62
- SecId::ISIN.valid_format?('US594918104') # => true
63
-
64
- # instance level
65
- isin = SecId::ISIN.new('US594918104')
66
- isin.valid_format? # => true
67
- ```
43
+ ```bash
44
+ bundle install
45
+ ```
68
46
 
69
- * `restore!` - restores check-digit and returns the full number,
70
- raises an error if number's format is invalid and thus check-digit is impossible to calculate
47
+ Or install it yourself:
71
48
 
72
- ```ruby
73
- # class level
74
- SecId::ISIN.restore!('US594918104') # => 'US5949181045'
49
+ ```bash
50
+ gem install sec_id
51
+ ```
75
52
 
76
- # instance level
77
- isin = SecId::ISIN.new('US5949181045')
78
- isin.restore! # => 'US5949181045'
79
- ```
53
+ ## Supported Standards and Usage
80
54
 
81
- * `check_digit` and `calculate_check_digit` - these are the same,
82
- but the former is used at class level for bravity,
83
- and the latter is used at instance level for clarity;
84
- it calculates and returns the check-digit if the number is valid
85
- and raises an error otherwise.
55
+ All identifier classes provide `valid?` and `valid_format?` methods at both class and instance levels.
86
56
 
87
- ```ruby
88
- # class level
89
- SecId::ISIN.check_digit('US594918104') # => 5
57
+ **Check-digit based identifiers** (ISIN, CUSIP, CEI, SEDOL, FIGI, LEI, IBAN) also provide:
58
+ - `restore!` - restores check-digit and returns the full number
59
+ - `check_digit` / `calculate_check_digit` - calculates and returns the check-digit
90
60
 
91
- # instance level
92
- isin = SecId::ISIN.new('US594918104')
93
- isin.calculate_check_digit # => 5
94
- isin.check_digit # => nil
95
- ```
61
+ **Normalization based identifiers** (CIK, OCC, Valoren) provide instead:
62
+ - `normalize!` - pads/formats the identifier to its standard form
96
63
 
97
- :exclamation: Please note that `isin.check_digit` returns `nil` because `#check_digit`
98
- at instance level represents original check-digit of the number passed to `new`,
99
- which in this example is missing and thus it's `nil`.
64
+ ### ISIN
100
65
 
101
- ### SecId::ISIN full example
66
+ > [International Securities Identification Number](https://en.wikipedia.org/wiki/International_Securities_Identification_Number) - a 12-character alphanumeric code that uniquely identifies a security.
102
67
 
103
68
  ```ruby
104
69
  # class level
@@ -118,16 +83,38 @@ isin.valid_format? # => true
118
83
  isin.restore! # => 'US5949181045'
119
84
  isin.calculate_check_digit # => 5
120
85
  isin.to_cusip # => #<SecId::CUSIP>
86
+ isin.nsin_type # => :cusip
87
+ isin.to_nsin # => #<SecId::CUSIP>
88
+
89
+ # 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)
98
+
99
+ # 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>
121
106
  ```
122
107
 
123
- ### SecId::CUSIP full example
108
+ ### CUSIP
109
+
110
+ > [Committee on Uniform Securities Identification Procedures](https://en.wikipedia.org/wiki/CUSIP) - a 9-character alphanumeric code that identifies North American securities.
124
111
 
125
112
  ```ruby
126
113
  # class level
127
114
  SecId::CUSIP.valid?('594918104') # => true
128
115
  SecId::CUSIP.valid_format?('59491810') # => true
129
116
  SecId::CUSIP.restore!('59491810') # => '594918104'
130
- SecId::CUSIP.check_digit('59491810') # => 5
117
+ SecId::CUSIP.check_digit('59491810') # => 4
131
118
 
132
119
  # instance level
133
120
  cusip = SecId::CUSIP.new('594918104')
@@ -143,7 +130,33 @@ cusip.to_isin('US') # => #<SecId::ISIN>
143
130
  cusip.cins? # => false
144
131
  ```
145
132
 
146
- ### SecId::SEDOL full example
133
+ ### CEI
134
+
135
+ > [CUSIP Entity Identifier](https://www.cusip.com/identifiers.html) - a 10-character alphanumeric code that identifies legal entities in the syndicated loan market.
136
+
137
+ ```ruby
138
+ # 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
143
+
144
+ # instance level
145
+ cei = SecId::CEI.new('A0BCDEFGH1')
146
+ cei.full_number # => 'A0BCDEFGH1'
147
+ cei.prefix # => 'A'
148
+ cei.numeric # => '0'
149
+ cei.entity_id # => 'BCDEFGH'
150
+ cei.check_digit # => 1
151
+ cei.valid? # => true
152
+ cei.valid_format? # => true
153
+ cei.restore! # => 'A0BCDEFGH1'
154
+ cei.calculate_check_digit # => 1
155
+ ```
156
+
157
+ ### SEDOL
158
+
159
+ > [Stock Exchange Daily Official List](https://en.wikipedia.org/wiki/SEDOL) - a 7-character alphanumeric code used in the United Kingdom, Ireland, Crown Dependencies (Jersey, Guernsey, Isle of Man), and select British Overseas Territories.
147
160
 
148
161
  ```ruby
149
162
  # class level
@@ -153,16 +166,20 @@ SecId::SEDOL.restore!('B0Z52W') # => 'B0Z52W5'
153
166
  SecId::SEDOL.check_digit('B0Z52W') # => 5
154
167
 
155
168
  # instance level
156
- cusip = SecId::SEDOL.new('B0Z52W5')
157
- cusip.full_number # => 'B0Z52W5'
158
- cusip.check_digit # => 5
159
- cusip.valid? # => true
160
- cusip.valid_format? # => true
161
- cusip.restore! # => 'B0Z52W5'
162
- cusip.calculate_check_digit # => 5
169
+ sedol = SecId::SEDOL.new('B0Z52W5')
170
+ sedol.full_number # => 'B0Z52W5'
171
+ sedol.check_digit # => 5
172
+ sedol.valid? # => true
173
+ sedol.valid_format? # => true
174
+ sedol.restore! # => 'B0Z52W5'
175
+ sedol.calculate_check_digit # => 5
176
+ sedol.to_isin # => #<SecId::ISIN> (GB ISIN by default)
177
+ sedol.to_isin('IE') # => #<SecId::ISIN> (IE ISIN)
163
178
  ```
164
179
 
165
- ### SecId::FIGI full example
180
+ ### FIGI
181
+
182
+ > [Financial Instrument Global Identifier](https://en.wikipedia.org/wiki/Financial_Instrument_Global_Identifier) - a 12-character alphanumeric code that provides unique identification of financial instruments.
166
183
 
167
184
  ```ruby
168
185
  # class level
@@ -183,7 +200,61 @@ figi.restore! # => 'BBG000DMBXR2'
183
200
  figi.calculate_check_digit # => 2
184
201
  ```
185
202
 
186
- ### SecId::CIK full example
203
+ ### LEI
204
+
205
+ > [Legal Entity Identifier](https://en.wikipedia.org/wiki/Legal_Entity_Identifier) - a 20-character alphanumeric code that uniquely identifies legal entities participating in financial transactions.
206
+
207
+ ```ruby
208
+ # 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
213
+
214
+ # instance level
215
+ lei = SecId::LEI.new('5493006MHB84DD0ZWV18')
216
+ lei.full_number # => '5493006MHB84DD0ZWV18'
217
+ lei.lou_id # => '5493'
218
+ lei.reserved # => '00'
219
+ lei.entity_id # => '6MHB84DD0ZWV'
220
+ lei.check_digit # => 18
221
+ lei.valid? # => true
222
+ lei.valid_format? # => true
223
+ lei.restore! # => '5493006MHB84DD0ZWV18'
224
+ lei.calculate_check_digit # => 18
225
+ ```
226
+
227
+ ### IBAN
228
+
229
+ > [International Bank Account Number](https://en.wikipedia.org/wiki/International_Bank_Account_Number) - an internationally standardized system for identifying bank accounts across national borders (ISO 13616).
230
+
231
+ ```ruby
232
+ # 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
237
+
238
+ # instance level
239
+ iban = SecId::IBAN.new('DE89370400440532013000')
240
+ iban.full_number # => 'DE89370400440532013000'
241
+ iban.country_code # => 'DE'
242
+ iban.bban # => '370400440532013000'
243
+ iban.bank_code # => '37040044'
244
+ iban.account_number # => '0532013000'
245
+ iban.check_digit # => 89
246
+ iban.valid? # => true
247
+ iban.valid_format? # => true
248
+ iban.restore! # => 'DE89370400440532013000'
249
+ iban.calculate_check_digit # => 89
250
+ iban.known_country? # => true
251
+ ```
252
+
253
+ Full BBAN structural validation is supported for EU/EEA countries. Other countries have length-only validation.
254
+
255
+ ### CIK
256
+
257
+ > [Central Index Key](https://en.wikipedia.org/wiki/Central_Index_Key) - a 10-digit number used by the SEC to identify corporations and individuals who have filed disclosures.
187
258
 
188
259
  ```ruby
189
260
  # class level
@@ -202,7 +273,9 @@ cik.normalize! # => '0001094517'
202
273
  cik.to_s # => '0001094517'
203
274
  ```
204
275
 
205
- ### SecId::OCC full example
276
+ ### OCC
277
+
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.
206
279
 
207
280
  ```ruby
208
281
  # class level
@@ -239,18 +312,132 @@ occ.normalize! # => 'X 250620C00050000'
239
312
  occ.full_symbol # => 'X 250620C00050000'
240
313
  ```
241
314
 
315
+ ### WKN
316
+
317
+ > [Wertpapierkennnummer](https://en.wikipedia.org/wiki/Wertpapierkennnummer) - a 6-character alphanumeric code used to identify securities in Germany.
318
+
319
+ ```ruby
320
+ # class level
321
+ SecId::WKN.valid?('514000') # => true
322
+ SecId::WKN.valid?('CBK100') # => true
323
+ SecId::WKN.valid_format?('514000') # => true
324
+
325
+ # instance level
326
+ wkn = SecId::WKN.new('514000')
327
+ wkn.full_number # => '514000'
328
+ wkn.identifier # => '514000'
329
+ wkn.valid? # => true
330
+ wkn.valid_format? # => true
331
+ wkn.to_s # => '514000'
332
+ wkn.to_isin # => #<SecId::ISIN> (DE ISIN)
333
+ ```
334
+
335
+ WKN excludes letters I and O to avoid confusion with digits 1 and 0.
336
+
337
+ ### Valoren
338
+
339
+ > [Valoren](https://en.wikipedia.org/wiki/Valoren_number) - a numeric identifier for securities in Switzerland, Liechtenstein, and Belgium.
340
+
341
+ ```ruby
342
+ # 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'
349
+
350
+ # instance level
351
+ valoren = SecId::Valoren.new('3886335')
352
+ valoren.full_number # => '3886335'
353
+ valoren.padding # => ''
354
+ valoren.identifier # => '3886335'
355
+ 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)
361
+ ```
362
+
363
+ ### CFI
364
+
365
+ > [Classification of Financial Instruments](https://en.wikipedia.org/wiki/ISO_10962) - a 6-character alphabetic code that classifies financial instruments per ISO 10962.
366
+
367
+ ```ruby
368
+ # class level
369
+ SecId::CFI.valid?('ESXXXX') # => true
370
+ SecId::CFI.valid?('ESVUFR') # => true
371
+ SecId::CFI.valid_format?('ESXXXX') # => true
372
+
373
+ # instance level
374
+ cfi = SecId::CFI.new('ESVUFR')
375
+ cfi.full_number # => 'ESVUFR'
376
+ cfi.identifier # => 'ESVUFR'
377
+ cfi.category_code # => 'E'
378
+ cfi.group_code # => 'S'
379
+ cfi.category # => :equity
380
+ cfi.group # => :common_shares
381
+ cfi.valid? # => true
382
+ cfi.valid_format? # => true
383
+
384
+ # Equity-specific predicates
385
+ cfi.equity? # => true
386
+ cfi.voting? # => true
387
+ cfi.restrictions? # => false
388
+ cfi.fully_paid? # => true
389
+ cfi.registered? # => true
390
+ ```
391
+
392
+ CFI validates the category code (position 1) against 14 valid values and the group code (position 2) against valid values for that category. Attribute positions 3-6 accept any letter A-Z, with X meaning "not applicable".
393
+
394
+ ### FISN
395
+
396
+ > [Financial Instrument Short Name](https://en.wikipedia.org/wiki/ISO_18774) - a human-readable short name for financial instruments per ISO 18774.
397
+
398
+ ```ruby
399
+ # 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
+
404
+ # instance level
405
+ fisn = SecId::FISN.new('APPLE INC/SH')
406
+ fisn.full_number # => 'APPLE INC/SH'
407
+ fisn.identifier # => 'APPLE INC/SH'
408
+ fisn.issuer # => 'APPLE INC'
409
+ fisn.description # => 'SH'
410
+ fisn.valid? # => true
411
+ fisn.valid_format? # => true
412
+ fisn.to_s # => 'APPLE INC/SH'
413
+ ```
414
+
415
+ FISN format: `Issuer Name/Abbreviated Instrument Description` with issuer (1-15 chars) and description (1-19 chars) separated by a forward slash. Character set: uppercase A-Z, digits 0-9, and space.
416
+
242
417
  ## Development
243
418
 
244
419
  After checking out the repo, run `bin/setup` to install dependencies.
245
- Then, run `rake spec` to run the tests. You can also run `bin/console`
420
+ Then, run `bundle exec rake` to run the tests. You can also run `bin/console`
246
421
  for an interactive prompt that will allow you to experiment.
247
422
 
248
423
  To install this gem onto your local machine, run `bundle exec rake install`.
249
424
 
250
425
  ## Contributing
251
426
 
252
- Bug reports and pull requests are welcome on
253
- GitHub at https://github.com/svyatov/sec_id.
427
+ 1. Fork it
428
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
429
+ 3. Make your changes and run tests (`bundle exec rake`)
430
+ 4. Commit using [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) format (`git commit -m 'feat: add some feature'`)
431
+ 5. Push to the branch (`git push origin my-new-feature`)
432
+ 6. Create a new Pull Request
433
+
434
+ ## Changelog
435
+
436
+ See [CHANGELOG.md](CHANGELOG.md) for a detailed history of changes, following [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) format.
437
+
438
+ ## Versioning
439
+
440
+ This project follows [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html)
254
441
 
255
442
  ## License
256
443