A utility class to validate and parse (simply) International Securities Identification Numbers.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 | """ISIN Validation Class
The class ISIN accepts an ISIN code and stores it - if valid - as countrycode,
code and checkdigit.
Issuing agency and country are stored in the agencies dictionary"""
__encoding__ = "utf-16"
import string
#ISO_3166_1 coutnry codes and numbering agencies
agencies = {'BE': (u'Euronext - Brussels', u'Belgium'),
'FR': (u'Euroclear France', u'France'),
'BG': (u'Central Depository of Bulgaria', u'Bulgaria'),
'VE': (u'Bolsa de Valores de Caracas, C.A.', u'Venezuela'),
'DK': (u'VP Securities Services', u'Denmark'),
'HR': (u'SDA - Central Depository Agency of Croatia', u'Croatia'),
'DE': (u'Wertpapier-Mitteilungen', u'Germany'),
'JP': (u'Tokyo Stock Exchange', u'Japan'),
'HU': (u'KELER', u'Hungary'),
'HK': (u'Hong Kong Exchanges and Clearing Ltd', u'Hong Kong'),
'JO': (u'Securities Depository Center of Jordan', u'Jordan'),
'BR': (u'Bolsa de Valores de Sao Paulo - BOVESPA', u'Brazil'),
'XS': (u'Clearstream Banking', u'Clearstream'),
'FI': (u'Finnish Central Securities Depository Ltd', u'Finland'),
'GR': (u'Central Securities Depository S.A.', u'Greece'),
'IS': (u'Icelandic Securities Depository', u'Iceland'),
'RU': (u'The National Depository Center, Russia', u'Russia'),
'LB': (u'Midclear S.A.L.', u'Lebanon'),
'PT': (u'Interbolsa - Sociedade Gestora de Sistemas de Liquida\xc3\xa7\xc3\xa3o e Sistemas Centralizados de Valores', u'Portugal'),
'NO': (u'Verdipapirsentralen (VPS) ASA', u'Norway'),
'TW': (u'Taiwan Stock Exchange Corporation', u'Taiwan, Province of China'),
'UA': (u'National Depository of Ukraine', u'Ukraine'),
'TR': (u'Takasbank', u'Turkey'),
'LK': (u'Colombo Stock Exchange', u'Sri Lanka'),
'LV': (u'OMX - Latvian Central Depository', u'Latvia'),
'LU': (u'Clearstream Banking', u'Luxembourg'),
'TH': (u'Thailand Securities Depository Co., Ltd', u'Thailand'),
'NL': (u'Euronext Netherlands', u'Netherlands'),
'PK': (u'Central Depository Company of Pakistan Ltd', u'Pakistan'),
'PH': (u'Philippine Stock Exchange, Inc.', u'Philippines'),
'RO': (u'The National Securities Clearing Settlement and Depository Corporation', u'Romania'),
'EG': (u'Misr for Central Clearing, Depository and Registry (MCDR)', u'Egypt'),
'PL': (u'National Depository for Securities', u'Poland'),
'AA': (u'ANNA Secretariat', u'ANNAland'),
'CH': (u'Telekurs Financial Ltd.', u'Switzerland'),
'CN': (u'China Securities Regulatory Commission', u'China'),
'CL': (u'Deposito Central de Valores', u'Chile'),
'EE': (u'Estonian Central Depository for Securities', u'Estonia'),
'CA': (u'The Canadian Depository for Securities Ltd', u'Canada'),
'IR': (u'Tehran Stock Exchange Services Company', u'Iran'),
'IT': (u'Ufficio Italiano dei Cambi', u'Italy'),
'ZA': (u'JSE Securities Exchange of South Africa', u'South Africa'),
'CZ': (u'Czech National Bank', u'Czech Republic'),
'CY': (u'Cyprus Stock Exchange', u'Cyprus'),
'AR': (u'Caja de Valores S.A.', u'Argentina'),
'AU': (u'Australian Stock Exchange Limited', u'Australia'),
'AT': (u'Oesterreichische Kontrollbank AG', u'Austria'),
'IN': (u'Securities and Exchange Board of India', u'India'),
'CS': (u'Central Securities Depository A.D. Beograd', u'Serbia & Montenegro'),
'CR': (u'Central de Valores - CEVAL', u'Costa Rica'),
'IE': (u'The Irish Stock Exchange', u'Ireland'),
'ID': (u'PT. Kustodian Sentral Efek Indonesia (Indonesian Central Securities Depository (ICSD))', u'Indonesia'),
'ES': (u'Comision Nacional del Mercado de Valores (CNMV)', u'Spain'),
'PE': (u'Bolsa de Valores de Lima', u'Peru'),
'TN': (u'Sticodevam', u'Tunisia'),
'PA': (u'Bolsa de Valores de Panama S.A.', u'Panama'),
'SG': (u'Singapore Exchange Limited', u'Singapore'),
'IL': (u'The Tel Aviv Stock Exchange', u'Israel'),
'US': (u'Standard & Poor\xb4s - CUSIP Service Bureau', u'USA'),
'MX': (u'S.D. Indeval SA de CV', u'Mexico'),
'SK': (u'Central Securities Depository SR, Inc.', u'Slovakia'),
'KR': (u'Korea Exchange - KRX', u'Korea'),
'SI': (u'KDD Central Securities Clearing Corporation', u'Slovenia'),
'KW': (u'Kuwait Clearing Company', u'Kuwait'),
'MY': (u'Bursa Malaysia', u'Malaysia'),
'MO': (u'MAROCLEAR S.A.', u'Morocco'),
'SE': (u'VPC AB', u'Sweden'),
'GB': (u'London Stock Exchange', u'United Kingdom')}
class ISIN(object):
"""Represents a valid ISIN number"""
def __init__(self, isin):
try:
self.validate(isin)
self.countrycode = isin[:2].upper()
self.code = str(isin[2:-1])
self.checkdigit = int(isin[-1])
self.value = "%s%s%s" % (self.countrycode, self.code, self.checkdigit)
except ISINException:
raise
def __str__(self):
return self.value
def validate(self, isin):
"""Check the length, country code and checkdigit of the isin"""
self.check_length(isin)
self.check_country(isin)
try:
if self.calc_checkdigit(isin) != int(isin[-1]):
raise CheckdigitError("Checkdigit '%s' is not valid" % isin[-1])
except ValueError:
raise CheckdigitError("Checkdigit '%s' is not valid" % isin[-1])
def check_length(self, isin):
"""Raise ValueError is the isin is not 12 characters long"""
if len(isin) != 12:
raise LengthError('ISIN is not 12 characters')
def check_country(self, isin):
"""Raise ValueError is the country code is not present or not
recognised"""
if not isin[:2].isalpha():
raise CountrycodeError('Country code is not present')
if isin[:2] not in agencies.keys():
raise CountrycodeError("Country Code '%s' is not valid" % isin[:2])
def calc_checkdigit(self, isin):
"""Calculate and return the check digit"""
#Convert alpha characters to digits
isin2 = []
for char in isin[:-1]:
if char.isalpha():
isin2.append((string.ascii_uppercase.index(char.upper()) + 9 + 1))
else:
isin2.append(char)
#Convert each int into string and join
isin2 = ''.join([str(i) for i in isin2])
#Gather every second digit (even)
even = isin2[::2]
#Gather the other digits (odd)
odd = isin2[1::2]
#If len(isin2) is odd, multiply evens by 2, else multiply odds by 2
if len(isin2) % 2 > 0:
even = ''.join([str(int(i)*2) for i in list(even)])
else:
odd = ''.join([str(int(i)*2) for i in list(odd)])
even_sum = sum([int(i) for i in even])
#then add each single int in both odd and even
odd_sum = sum([int(i) for i in odd])
mod = (even_sum + odd_sum) % 10
return 10 - mod
def agency(self):
"""Return the issuing agency name"""
try:
return agencies[self.value[0:2].upper()][0]
except KeyError:
return None
def country(self):
"""Return the Country of issue"""
try:
return agencies[self.value[0:2].upper()][1]
except KeyError:
return None
class ISINException(Exception):
pass
class CheckdigitError(ISINException):
pass
class LengthError(ISINException):
pass
class CountrycodeError(ISINException):
pass
|
A simple class that takes an ISIN number, validates it and - if valid - stores it as Country code, code and check digit.
ISIN.country() and ISIN.agency() return the issuing country and agency, listed in the agencies dictionary.
There are likely to be several possible optimisations in the calc_checkdigit function.
Encoding syntax error. __encoding__ = "utf-16"
should read
-- coding: utf-16 --
and it should be on the top line.
UTF-16: Is it really needed? The source is all-ASCII anyway.
Is it really needed? No. You're absolutly right. I had it in my head that without it python 2.5 raised a warning when importing it due to the non-ascii characters on line 28. It doesn't.
Line 144/145:
return 10 - mod
should read:
(see the comment in the "Examples" section on http://en.wikipedia.org/wiki/International_Securities_Identifying_Number)
...Take the 10s modulus of the result (this final step is important in the instance where the modulus of the sum is 0, as the resulting check digit would be 10)...
I checked this case on about 3000 instruments in our database. Else the code seems fine, thanks for the implementation.
cheers, alex
PS Is there a way to attach a patch here for additional countries for example ?