-
-
Save hafei/b7ffd6526e9547243bca1056d5d63f5e to your computer and use it in GitHub Desktop.
Calculate check digits for SEDOL, CUSIP, ISIN and BBGID (Bloomberg Global Id) codes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment