Skip to content

Instantly share code, notes, and snippets.

@stuartbain
Last active May 21, 2019 09:25
Show Gist options
  • Save stuartbain/7097169 to your computer and use it in GitHub Desktop.
Save stuartbain/7097169 to your computer and use it in GitHub Desktop.
Calculate check digits for SEDOL, CUSIP, ISIN and BBGID (Bloomberg Global Id) codes
module CheckDigit
class SEDOL
SEDOL_WEIGHTS = [1,3,1,7,3,9]
SEDOL_PATTERN = /[B-Db-dF-Hf-hJ-Nj-nP-Tp-tV-Xv-xYyZz\d]{6}/ # http://regexlib.com/REDetails.aspx?regexp_id=1044
def self.calculate(sedol)
raise 'Invalid pattern' unless sedol =~ SEDOL_PATTERN
# Create an array of each of characters in the SEDOL
#chars = sedol.upcase.split('')
# Pair each character with a weighting
# .zip(SEDOL_WEIGHT)
# Calculate the weighted value of each converted character
# c.to_i(36) used Base 36 to convert A-Z to numeric equivalents - http://en.wikipedia.org/wiki/Base_36
# .map { |c, weight| c.to_i(36) * weight}
# Sum all the weighted values
# .inject(:+)
sum = sedol.upcase.chars.zip(SEDOL_WEIGHTS).map{|c,weight| c.to_i(36) * weight}.inject(:+)
((10 - (sum % 10)) % 10)
end
end
class CUSIP
CUSIP_WEIGHTS = [1,2,1,2,1,2,1,2]
CUSIP_PATTERN = /[0-9]{3}[A-Z0-9]{5}/
def self.calculate(cusip)
raise 'Invalid pattern' unless cusip =~ CUSIP_PATTERN
# Create an array of each upper case character in the CUSIP
# cusip.upcase.chars
# Pair each character with a weighting (effectively double every even character)
# .zip(CUSIP_WEIGHT)
# Calculate the weighted value of each converted character
# c.to_i(36) used Base 36 to convert A-Z to numeric equivalents - http://en.wikipedia.org/wiki/Base_36
# .map { |c, weight| c.to_i(36) * weight}
# Sum up the individual digits
# .inject(0) { |sum, v| sum + (v/10) + (v%10) }
sum = cusip.upcase.chars.zip(CUSIP_WEIGHTS).map{|c,weight| c.to_i(36) * weight}.inject(0){|sum,v| sum + v/10 + v%10}
((10 - (sum%10))%10)
end
end
class ISIN
ISIN_PATTERN = /[A-Z]{2}([A-Z0-9]){9}/
def self.calculate(isin)
raise 'Invalid pattern' unless isin =~ ISIN_PATTERN
# Replace Non-Digits (Regex: \D) with numeric equivalent
# c.to_i(36) used Base 36 to convert A-Z to numeric equivalents - http://en.wikipedia.org/wiki/Base_36
cs = isin.upcase.gsub(/\D/){ |c| c.to_i(36) }
# Create a reversed array of the digits
# cs.reverse.chars
# Double the value of each digit in an even position in the array
# .each_with_index.map {|v,i| i % 2 == 0 ? v.to_i * 2 : v.to_i }
# Sum up the individual digits
# .inject(0) { |sum, v| sum + (v/10) + (v%10) }
sum = cs.reverse.chars.each_with_index.map {|v,i| i % 2 == 0 ? v.to_i * 2 : v.to_i }.inject(0){|sum, v| sum + (v/10 + v%10) }
((10 - (sum%10))%10)
end
end
class BBGID
# Bloomberg Global ID - can't find any official documentation on check digit calculation but it appears to
# use the same method as CUSIP
BBGID_WEIGHTS = [1,2,1,2,1,2,1,2,1,2,1]
def self.calculate(id)
raise 'Invalid pattern' unless id =~ /BBG[A-Z0-9]{8}/
# Create an array of each upper case character in the BBGID
# bbgid.upcase.chars
# Pair each character with a weighting (effectively double every even character)
# .zip(BBGID_WEIGHT)
# Calculate the weighted value of each converted character
# c.to_i(36) used Base 36 to convert A-Z to numeric equivalents - http://en.wikipedia.org/wiki/Base_36
# .map { |c, weight| c.to_i(36) * weight}
# Sum up the individual digits
# .inject(0) { |sum, v| sum + (v/10) + (v%10) }
sum = id.upcase.chars.zip(BBGID_WEIGHTS).map{|c,weight| c.to_i(36) * weight}.inject(0){|sum,v| sum + v/10 + v%10}
((10 - (sum%10))%10)
end
end
end
require 'spec_helper'
describe CheckDigit do
describe 'SEDOL' do
it 'should calculate the correct SEDOL checksums' do
sedols = %w(710889 B0YBKJ 406566 B0YBLH 228276 B0YBKL
557910 B0YBKR 585284 B0YBKT B00030 228276
232977 406566 557910 585284 710889 B00030
B01841 B0YBKJ B0YBKL B0YBKR B0YBKT B0YBLH)
checksums = [9,7,3,2,5,9,7,5,2,7,0,5,0,3,7,2,9,0,1,7,9,5,7,2]
results = sedols.map { |sedol| CheckDigit::SEDOL.calculate(sedol) }
results.should == checksums
end
it 'should be the correct length' do
expect {CheckDigit::SEDOL.calculate '12345'}.to raise_error
expect {CheckDigit::SEDOL.calculate '1234567'}.to raise_error
end
it 'should have valid characters' do
expect {CheckDigit::SEDOL.calculate 'O12345'}.to raise_error
expect {CheckDigit::SEDOL.calculate 'I12345'}.to raise_error
end
end
describe 'CUSIP' do
it 'should calculate the correct CUSIP checksums' do
cusips = %w(83764912 392690QT 126650BG 254709AC 437076AQ
441060AG 50075NAN 574599BE 617446B9 637640AC
713291AL 852061AE 887317AA 925524BF 125509BG
125896AV)
checksums = [8,3,4,2,5,5,4,5,9,7,6,0,3,6,3,2]
results = cusips.map { |cusip| CheckDigit::CUSIP.calculate(cusip) }
results.should == checksums
end
it 'should be the correct length' do
expect {CheckDigit::CUSIP.calculate '1234567'}.to raise_error
expect {CheckDigit::CUSIP.calculate '123456789'}.to raise_error
end
it 'should have valid characters' do
expect {CheckDigit::CUSIP.calculate '@1234567'}.to raise_error
expect {CheckDigit::CUSIP.calculate '#1234567'}.to raise_error
end
end
describe 'ISIN' do
it 'should calculate the correct ISIN checksums' do
isins = %w(US037833100 AU0000XVGZA GB000263494 XS074239528
FR001145346 US013817AT8)
checksums = [5,3,6,7,3,6]
results = isins.map { |isin| CheckDigit::ISIN.calculate(isin) }
results.should == checksums
end
it 'should be the correct length' do
expect {CheckDigit::ISIN.calculate '1234567890'}.to raise_error
expect {CheckDigit::ISIN.calculate '123456789012'}.to raise_error
end
it 'should have valid characters' do
expect {CheckDigit::ISIN.calculate '@1234567890'}.to raise_error
expect {CheckDigit::ISIN.calculate '#1234567890'}.to raise_error
end
end
describe 'BBGID' do
it 'should calculate the correct BBGID checksums' do
bbgids = %w(BBG00000MVR BBG0059FR8W BBG00062B4P BBG0001S081
BBG0009FY1P)
checksums = [6,3,0,7,9]
results = bbgids.map { |bbgid| CheckDigit::BBGID.calculate(bbgid) }
results.should == checksums
end
it 'should be the correct length' do
expect {CheckDigit::BBGID.calculate '1234567890'}.to raise_error
expect {CheckDigit::BBGID.calculate '123456789012'}.to raise_error
end
it 'should have valid characters' do
expect {CheckDigit::BBGID.calculate '@1234567890'}.to raise_error
expect {CheckDigit::BBGID.calculate '#1234567890'}.to raise_error
end
end
end
@stuartbain
Copy link
Author

LEI verification (MOD97-10)
'213800IARXDCEFRYHB48'.gsub!(/\D/){|c| c.to_int(36)}.to_i % 97 == 1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment