sec_id 4.1.0 → 4.2.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: a3391006ed4c0e135a0e94ef0d43da6aae3146a178f13c6237aec8d940931c52
4
- data.tar.gz: a572d9b26de1c8c97476cd954caeefacc9fe44ac8ffff7f25d9b4a72ca058e54
3
+ metadata.gz: 58a56c99692f66ae9ff3be10064ab29e729f34d6877456a6c998ee1a3509478d
4
+ data.tar.gz: ed1ead1827a544b8c48894739916781ea62337aa75e26f7263dd54c08f70b074
5
5
  SHA512:
6
- metadata.gz: 1d380490f4a25d1044734b7724bf7e752e7dbb43097a468d3210e8fd9f4efd185beac207d9b5adfc5cce0cedfd5ea944be269785f444fd7a4bbb449b63ea9962
7
- data.tar.gz: 30434ebe94c7f2c7f3a311ab2a3857af1ae0b66b5993c3b659fd9f6852633171cd7e4ce3c73f7d1e554211cbb53259e1e7679236df243f8dfaf311a0a61cebee
6
+ metadata.gz: 437a5b95d0e14cead4d0392dbaa2da4fbc7c3325a01252f17032ca50e9dc3609a6dfe8e73947535de2e2fe228c460c33847635f90c0d99fddf6ff32c66117fbb
7
+ data.tar.gz: d2e44281f824c033add9074633432291794789b4f1b89e50b5977b1dfd34efd68e5dd2883c7c9076cf5e30316b6a8e487853185824f988827cad61e44e2c38c9
data/CHANGELOG.md CHANGED
@@ -1,13 +1,31 @@
1
1
  # Changelog
2
2
 
3
+ ## [4.2.0] - 2025-01-12
4
+
5
+ ### Added
6
+
7
+ - OCC support ([@wtn](https://github.com/wtn), [#93](https://github.com/svyatov/sec_id/pull/93))
8
+
9
+ ### Fixed
10
+
11
+ - CUSIP#cins? usage example in README ([@wtn](https://github.com/wtn), [#91](https://github.com/svyatov/sec_id/pull/91))
12
+
13
+ ### Updated
14
+
15
+ - Separate CIK from Base for cleaner architecture ([@wtn](https://github.com/wtn), [#92](https://github.com/svyatov/sec_id/pull/92))
16
+ - Use rubocop-rspec plugin ([@wtn](https://github.com/wtn), [#90](https://github.com/svyatov/sec_id/pull/90))
17
+ - Replace CodeClimate with Codecov
18
+ - Add permissions to CI workflow
19
+ - Clean up gemspec: update description and simplify files list
20
+
3
21
  ## [4.1.0] - 2024-09-23
4
22
 
5
23
  ### Added
6
24
 
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)
25
+ - FIGI support ([@wtn](https://github.com/wtn), [#84](https://github.com/svyatov/sec_id/pull/84))
26
+ - CIK support ([@wtn](https://github.com/wtn), [#85](https://github.com/svyatov/sec_id/pull/85))
27
+ - 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
+ - CINS check method for CUSIPs ([@wtn](https://github.com/wtn), [#87](https://github.com/svyatov/sec_id/pull/87))
11
29
 
12
30
  ### Updated
13
31
 
data/README.md CHANGED
@@ -1,8 +1,7 @@
1
1
  # SecId
2
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)
3
4
  ![Build Status](https://github.com/svyatov/sec_id/actions/workflows/main.yml/badge.svg?branch=main)
4
- [![Maintainability](https://api.codeclimate.com/v1/badges/a4759963a5ddc4d55b24/maintainability)](https://codeclimate.com/github/svyatov/sec_id/maintainability)
5
- [![Test Coverage](https://api.codeclimate.com/v1/badges/a4759963a5ddc4d55b24/test_coverage)](https://codeclimate.com/github/svyatov/sec_id/test_coverage)
6
5
 
7
6
  Validate securities identification numbers with ease!
8
7
 
@@ -13,7 +12,8 @@ Currently supported standards:
13
12
  [CUSIP](https://en.wikipedia.org/wiki/CUSIP),
14
13
  [SEDOL](https://en.wikipedia.org/wiki/SEDOL),
15
14
  [FIGI](https://en.wikipedia.org/wiki/Financial_Instrument_Global_Identifier),
16
- [CIK](https://en.wikipedia.org/wiki/Central_Index_Key).
15
+ [CIK](https://en.wikipedia.org/wiki/Central_Index_Key),
16
+ [OCC](https://en.wikipedia.org/wiki/Option_symbol#The_OCC_Option_Symbol).
17
17
 
18
18
  Work in progress:
19
19
  [IBAN](https://en.wikipedia.org/wiki/International_Bank_Account_Number).
@@ -23,7 +23,7 @@ Work in progress:
23
23
  Add this line to your application's Gemfile:
24
24
 
25
25
  ```ruby
26
- gem 'sec_id', '~> 4.1'
26
+ gem 'sec_id', '~> 4.2'
27
27
  ```
28
28
 
29
29
  And then execute:
@@ -140,7 +140,7 @@ cusip.valid_format? # => true
140
140
  cusip.restore! # => '594918104'
141
141
  cusip.calculate_check_digit # => 4
142
142
  cusip.to_isin('US') # => #<SecId::ISIN>
143
- cusip.cins? # => true
143
+ cusip.cins? # => false
144
144
  ```
145
145
 
146
146
  ### SecId::SEDOL full example
@@ -189,19 +189,54 @@ figi.calculate_check_digit # => 2
189
189
  # class level
190
190
  SecId::CIK.valid?('0001094517') # => true
191
191
  SecId::CIK.valid_format?('0001094517') # => true
192
- SecId::CIK.restore!('1094517') # => '0001094517'
193
- SecId::CIK.check_digit('0001094517') # raises NotImplementedError
192
+ SecId::CIK.normalize!('1094517') # => '0001094517'
194
193
 
195
194
  # instance level
196
195
  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
196
+ cik.full_number # => '0001094517'
197
+ cik.padding # => '000'
198
+ cik.identifier # => '1094517'
199
+ cik.valid? # => true
200
+ cik.valid_format? # => true
201
+ cik.normalize! # => '0001094517'
202
+ cik.to_s # => '0001094517'
203
+ ```
204
+
205
+ ### SecId::OCC full example
206
+
207
+ ```ruby
208
+ # class level
209
+ SecId::OCC.valid?('BRKB 100417C00090000') # => true
210
+ SecId::OCC.valid_format?('BRKB 100417C00090000') # => true
211
+ SecId::OCC.normalize!('BRKB100417C00090000') # => 'BRKB 100417C00090000'
212
+ SecId::OCC.build(
213
+ underlying: 'BRKB',
214
+ date: Date.new(2010, 4, 17),
215
+ type: 'C',
216
+ strike: 90,
217
+ ) # => #<SecId::OCC>
218
+
219
+ # instance level
220
+ occ = SecId::OCC.new('BRKB 100417C00090000')
221
+ occ.full_symbol # => 'BRKB 100417C00090000'
222
+ occ.underlying # => 'BRKB'
223
+ occ.date_str # => '100417'
224
+ occ.date_obj # => #<Date: 2010-04-17>
225
+ occ.type # => 'C'
226
+ occ.strike # => 90.0
227
+ occ.valid? # => true
228
+ occ.valid_format? # => true
229
+ occ.normalize! # => 'BRKB 100417C00090000'
230
+
231
+ occ = SecId::OCC.new('BRKB 2010-04-17C00090000')
232
+ occ.valid_format? # => false
233
+ occ.normalize! # raises SecId::InvalidFormatError
234
+
235
+ occ = SecId::OCC.new('X 250620C00050000')
236
+ occ.full_symbol # => 'X 250620C00050000'
237
+ occ.valid? # => true
238
+ occ.normalize! # => 'X 250620C00050000'
239
+ occ.full_symbol # => 'X 250620C00050000'
205
240
  ```
206
241
 
207
242
  ## Development
data/lib/sec_id/cik.rb CHANGED
@@ -2,15 +2,29 @@
2
2
 
3
3
  module SecId
4
4
  # https://en.wikipedia.org/wiki/Central_Index_Key
5
- class CIK < Base
5
+ class CIK
6
6
  ID_REGEX = /\A
7
7
  (?=\d{1,10}\z)(?<padding>0*)(?<identifier>[1-9]\d{0,9})
8
8
  \z/x
9
9
 
10
- attr_reader :padding
10
+ attr_reader :full_number, :identifier, :padding
11
+
12
+ class << self
13
+ def valid?(id)
14
+ new(id).valid?
15
+ end
16
+
17
+ def valid_format?(id)
18
+ new(id).valid_format?
19
+ end
20
+
21
+ def normalize!(id)
22
+ new(id).normalize!
23
+ end
24
+ end
11
25
 
12
26
  def initialize(cik)
13
- cik_parts = parse cik
27
+ cik_parts = parse(cik)
14
28
  @padding = cik_parts[:padding]
15
29
  @identifier = cik_parts[:identifier]
16
30
  end
@@ -23,11 +37,23 @@ module SecId
23
37
  !identifier.nil?
24
38
  end
25
39
 
26
- def restore!
27
- raise InvalidFormatError, "CIK '#{full_number}' is invalid and cannot be restored!" unless valid_format?
40
+ def normalize!
41
+ raise InvalidFormatError, "CIK '#{full_number}' is invalid and cannot be normalized!" unless valid_format?
28
42
 
29
43
  @padding = '0' * (10 - @identifier.length)
30
44
  @full_number = @identifier.rjust(10, '0')
31
45
  end
46
+
47
+ def to_s
48
+ full_number
49
+ end
50
+ alias to_str to_s
51
+
52
+ private
53
+
54
+ def parse(cik_number)
55
+ @full_number = cik_number.to_s.strip
56
+ @full_number.match(ID_REGEX) || {}
57
+ end
32
58
  end
33
59
  end
data/lib/sec_id/occ.rb ADDED
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'date'
4
+
5
+ module SecId
6
+ # https://en.wikipedia.org/wiki/Option_symbol#The_OCC_Option_Symbol
7
+ # https://web.archive.org/web/20120507220143/http://www.theocc.com/components/docs/initiatives/symbology/symbology_initiative_v1_8.pdf
8
+ class OCC
9
+ ID_REGEX = /\A
10
+ (?<initial>
11
+ (?=.{1,6})(?<underlying>\d?[A-Z]{1,5}\d?)(?<padding>[ ]*))
12
+ (?<date>\d{6})
13
+ (?<type>[CP])
14
+ (?<strike_mills>\d{8})
15
+ \z/x
16
+
17
+ attr_reader :full_symbol, :underlying, :date_str, :type
18
+
19
+ def initialize(symbol)
20
+ symbol_parts = parse symbol
21
+ @initial = symbol_parts[:initial]
22
+ @underlying = symbol_parts[:underlying]
23
+ @date_str = symbol_parts[:date]
24
+ @type = symbol_parts[:type]
25
+ @strike_mills = symbol_parts[:strike_mills]
26
+ end
27
+
28
+ def date
29
+ return @date if @date
30
+
31
+ @date = Date.strptime(date_str, '%y%m%d') if date_str
32
+ rescue Date::Error
33
+ nil
34
+ end
35
+ alias date_obj date
36
+
37
+ def strike
38
+ @strike ||= @strike_mills.to_i / 1000.0
39
+ end
40
+
41
+ def valid?
42
+ valid_format? && !date.nil?
43
+ end
44
+
45
+ def valid_format?
46
+ !@initial.nil?
47
+ end
48
+
49
+ def normalize!
50
+ raise InvalidFormatError, "OCC '#{full_symbol}' is invalid and cannot be normalized!" unless valid?
51
+
52
+ @strike_mills.length > 8 && @strike_mills = format('%08d', @strike_mills.to_i)
53
+ @initial.length < 6 && @initial = underlying.ljust(6, "\s")
54
+
55
+ @full_symbol = "#{@initial}#{date_str}#{type}#{@strike_mills}"
56
+ end
57
+
58
+ def to_s
59
+ full_symbol
60
+ end
61
+ alias to_str to_s
62
+
63
+ class << self
64
+ def valid?(id)
65
+ new(id).valid?
66
+ end
67
+
68
+ def valid_format?(id)
69
+ new(id).valid_format?
70
+ end
71
+
72
+ def normalize!(id)
73
+ new(id).normalize!
74
+ end
75
+
76
+ # rubocop:disable Metrics/MethodLength
77
+ def build(underlying:, date:, type:, strike:)
78
+ initial = underlying.to_s.ljust(6, "\s")
79
+ date = Date.parse(date.to_s) unless date.is_a?(Date)
80
+
81
+ case strike
82
+ when Numeric
83
+ strike_mills = format('%08d', (strike * 1000).to_i)
84
+ when /\A\d{8}\z/
85
+ strike_mills = strike
86
+ else
87
+ raise ArgumentError, 'Strike must be numeric or an 8-char string!'
88
+ end
89
+
90
+ symbol = "#{initial}#{date.strftime('%y%m%d')}#{type}#{strike_mills}"
91
+ new(symbol)
92
+ end
93
+ # rubocop:enable Metrics/MethodLength
94
+ end
95
+
96
+ private
97
+
98
+ def parse(symbol)
99
+ @full_symbol = symbol.to_s.strip
100
+ @full_symbol.match(ID_REGEX) || {}
101
+ end
102
+ end
103
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SecId
4
- VERSION = '4.1.0'
4
+ VERSION = '4.2.0'
5
5
  end
data/lib/sec_id.rb CHANGED
@@ -8,6 +8,7 @@ require 'sec_id/cusip'
8
8
  require 'sec_id/sedol'
9
9
  require 'sec_id/figi'
10
10
  require 'sec_id/cik'
11
+ require 'sec_id/occ'
11
12
 
12
13
  module SecId
13
14
  Error = Class.new(StandardError)
data/sec_id.gemspec CHANGED
@@ -11,16 +11,14 @@ Gem::Specification.new do |spec|
11
11
  spec.email = ['leonid@svyatov.ru']
12
12
 
13
13
  spec.summary = 'Validate securities identification numbers with ease!'
14
- spec.description = %(#{spec.summary} Currently supported standards: ISIN, CUSIP, SEDOL.)
14
+ spec.description = %(#{spec.summary} Currently supported standards: ISIN, CUSIP, SEDOL, FIGI, CIK, OCC.)
15
15
  spec.homepage = 'https://github.com/svyatov/sec_id'
16
16
  spec.license = 'MIT'
17
17
 
18
18
  spec.required_ruby_version = '>= 3.1.0'
19
19
 
20
20
  spec.require_paths = ['lib']
21
- spec.files = `git ls-files -z`.split("\x0").reject do |f|
22
- f.match(%r{^(test|spec|features)/})
23
- end
21
+ spec.files = Dir['lib/**/*.rb'] + %w[CHANGELOG.md LICENSE.txt README.md sec_id.gemspec]
24
22
 
25
23
  spec.metadata['rubygems_mfa_required'] = 'true'
26
24
  end
metadata CHANGED
@@ -1,41 +1,32 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sec_id
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.1.0
4
+ version: 4.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Leonid Svyatov
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2024-09-23 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies: []
13
12
  description: 'Validate securities identification numbers with ease! Currently supported
14
- standards: ISIN, CUSIP, SEDOL.'
13
+ standards: ISIN, CUSIP, SEDOL, FIGI, CIK, OCC.'
15
14
  email:
16
15
  - leonid@svyatov.ru
17
16
  executables: []
18
17
  extensions: []
19
18
  extra_rdoc_files: []
20
19
  files:
21
- - ".github/dependabot.yml"
22
- - ".github/workflows/main.yml"
23
- - ".gitignore"
24
- - ".rspec"
25
- - ".rubocop.yml"
26
20
  - CHANGELOG.md
27
- - Gemfile
28
21
  - LICENSE.txt
29
22
  - README.md
30
- - Rakefile
31
- - bin/console
32
- - bin/setup
33
23
  - lib/sec_id.rb
34
24
  - lib/sec_id/base.rb
35
25
  - lib/sec_id/cik.rb
36
26
  - lib/sec_id/cusip.rb
37
27
  - lib/sec_id/figi.rb
38
28
  - lib/sec_id/isin.rb
29
+ - lib/sec_id/occ.rb
39
30
  - lib/sec_id/sedol.rb
40
31
  - lib/sec_id/version.rb
41
32
  - sec_id.gemspec
@@ -44,7 +35,6 @@ licenses:
44
35
  - MIT
45
36
  metadata:
46
37
  rubygems_mfa_required: 'true'
47
- post_install_message:
48
38
  rdoc_options: []
49
39
  require_paths:
50
40
  - lib
@@ -59,8 +49,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
59
49
  - !ruby/object:Gem::Version
60
50
  version: '0'
61
51
  requirements: []
62
- rubygems_version: 3.5.15
63
- signing_key:
52
+ rubygems_version: 4.0.3
64
53
  specification_version: 4
65
54
  summary: Validate securities identification numbers with ease!
66
55
  test_files: []
@@ -1,11 +0,0 @@
1
- # To get started with Dependabot version updates, you'll need to specify which
2
- # package ecosystems to update and where the package manifests are located.
3
- # Please see the documentation for all configuration options:
4
- # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
5
-
6
- version: 2
7
- updates:
8
- - package-ecosystem: "bundler" # See documentation for possible values
9
- directory: "/" # Location of package manifests
10
- schedule:
11
- interval: "weekly"
@@ -1,39 +0,0 @@
1
- name: CI
2
-
3
- concurrency:
4
- group: ${{ github.workflow }}-${{ github.ref }}
5
- cancel-in-progress: true
6
-
7
- on:
8
- push:
9
- branches: ["main"]
10
- pull_request:
11
- branches: ["main"]
12
-
13
- jobs:
14
- build:
15
- runs-on: ubuntu-latest
16
- name: Ruby ${{ matrix.ruby_version }}
17
- strategy:
18
- matrix:
19
- ruby_version: [ruby-head, '3.3', '3.2', '3.1']
20
-
21
- env:
22
- COVERAGE: true
23
- CC_TEST_REPORTER_ID: ${{ vars.CC_TEST_REPORTER_ID }}
24
-
25
- steps:
26
- - uses: actions/checkout@v4
27
-
28
- - uses: ruby/setup-ruby@v1
29
- with:
30
- ruby-version: ${{ matrix.ruby_version }}
31
- bundler-cache: true
32
- continue-on-error: ${{ matrix.ruby_version == 'ruby-head' }}
33
-
34
- - run: bundle exec rake
35
- continue-on-error: ${{ matrix.ruby_version == 'ruby-head' }}
36
-
37
- - uses: paambaati/codeclimate-action@v8.0.0
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/.gitignore DELETED
@@ -1,12 +0,0 @@
1
- /.bundle/
2
- /.yardoc
3
- /Gemfile.lock
4
- /_yardoc/
5
- /coverage/
6
- /doc/
7
- /pkg/
8
- /spec/reports/
9
- /tmp/
10
-
11
- # rspec failure tracking
12
- .rspec_status
data/.rspec DELETED
@@ -1,3 +0,0 @@
1
- --format documentation
2
- --color
3
- --require spec_helper
data/.rubocop.yml DELETED
@@ -1,39 +0,0 @@
1
- require:
2
- - rubocop-rspec
3
-
4
- AllCops:
5
- TargetRubyVersion: 3.1
6
- DisplayCopNames: true
7
- DisplayStyleGuide: true
8
- ExtraDetails: true
9
- SuggestExtensions: false
10
- NewCops: enable
11
-
12
- Layout/LineLength:
13
- Max: 120
14
-
15
- Style/Documentation:
16
- Enabled: false
17
-
18
- Style/HashEachMethods:
19
- Enabled: true
20
-
21
- Style/HashTransformKeys:
22
- Enabled: true
23
-
24
- Style/HashTransformValues:
25
- Enabled: true
26
-
27
- Metrics/BlockLength:
28
- Exclude:
29
- - 'spec/**/*'
30
-
31
- Lint/MissingSuper:
32
- AllowedParentClasses:
33
- - Base
34
-
35
- RSpec/MultipleExpectations:
36
- Max: 5
37
-
38
- RSpec/ExampleLength:
39
- Max: 10
data/Gemfile DELETED
@@ -1,20 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- source 'https://rubygems.org'
4
-
5
- git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
6
-
7
- # Specify your gem's dependencies in sec_id.gemspec
8
- gemspec
9
-
10
- # Specify your gem's development dependencies below
11
- gem 'rake', '>= 13'
12
-
13
- gem 'rspec', '~> 3.9'
14
- gem 'rspec_junit_formatter'
15
-
16
- gem 'rubocop', '~> 1.64'
17
- gem 'rubocop-rspec', '~> 3.0'
18
-
19
- gem 'simplecov', '~> 0.22', require: false
20
- gem 'simplecov_json_formatter', require: false
data/Rakefile DELETED
@@ -1,13 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'bundler/gem_tasks'
4
- require 'rspec/core/rake_task'
5
- require 'rubocop/rake_task'
6
-
7
- RSpec::Core::RakeTask.new(:spec)
8
-
9
- RuboCop::RakeTask.new do |task|
10
- task.requires << 'rubocop-rspec'
11
- end
12
-
13
- task default: %i[rubocop spec]
data/bin/console DELETED
@@ -1,15 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- require 'bundler/setup'
5
- require 'sec_id'
6
-
7
- # You can add fixtures and/or initialization code here to make experimenting
8
- # with your gem easier. You can also use a different console, if you like.
9
-
10
- # (If you use this, don't forget to add pry to your Gemfile!)
11
- # require "pry"
12
- # Pry.start
13
-
14
- require 'irb'
15
- IRB.start(__FILE__)
data/bin/setup DELETED
@@ -1,8 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
- IFS=$'\n\t'
4
- set -vx
5
-
6
- bundle install
7
-
8
- # Do any other automated setup that you need to do here