diff options
author | Arthur de Jong <arthur@arthurdejong.org> | 2018-03-25 21:44:50 +0200 |
---|---|---|
committer | Arthur de Jong <arthur@arthurdejong.org> | 2018-03-25 21:44:50 +0200 |
commit | 918d4832748ad6203cfbc4d0f58d8a9755e2c344 (patch) | |
tree | 2f04f764223c3e8da84451f75aac6c872bb4aea7 | |
parent | ceb3c628531f17b5a91596295f27fc8ebe0e3d39 (diff) |
Add Financial Instrument Global Identifier
-rw-r--r-- | stdnum/figi.py | 84 | ||||
-rw-r--r-- | tests/test_figi.doctest | 161 |
2 files changed, 245 insertions, 0 deletions
diff --git a/stdnum/figi.py b/stdnum/figi.py new file mode 100644 index 0000000..e8f1434 --- /dev/null +++ b/stdnum/figi.py @@ -0,0 +1,84 @@ +# figi.py - functions for handling FIGI numbers +# +# Copyright (C) 2018 Arthur de Jong +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + +"""FIGI (Financial Instrument Global Identifier). + +The Financial Instrument Global Identifier (FIGI) is a 12-character +alpha-numerical unique identifier of financial instruments such as common +stock, options, derivatives, futures, corporate and government bonds, +municipals, currencies, and mortgage products. + +More information: + +* https://openfigi.com/ +* https://en.wikipedia.org/wiki/Financial_Instrument_Global_Identifier + +>>> validate('BBG000BLNQ16') +'BBG000BLNQ16' +>>> validate('BBG000BLNQ14') +Traceback (most recent call last): + ... +InvalidChecksum: ... +""" + +from stdnum.exceptions import * +from stdnum.util import clean + + +def compact(number): + """Convert the number to the minimal representation. This strips the + number of any valid separators and removes surrounding whitespace.""" + return clean(number, ' ').strip().upper() + + +def calc_check_digit(number): + """Calculate the check digits for the number.""" + # we use the full alphabet for the check digit calculation + alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' + # convert to numeric first, then double some, then sum individual digits + number = ''.join( + str(alphabet.index(n) * (1, 2)[i % 2]) + for i, n in enumerate(number[:11])) + return str((10 - sum(int(n) for n in number)) % 10) + + +def validate(number): + """Check if the number provided is a valid FIGI.""" + number = compact(number) + if not all(x in '0123456789BCDFGHJKLMNPQRSTVWXYZ' for x in number): + raise InvalidFormat() + if len(number) != 12: + raise InvalidLength() + if number[0].isdigit() or number[1].isdigit(): + raise InvalidFormat() + if number[:2] in ('BS', 'BM', 'GG', 'GB', 'VG'): + raise InvalidComponent() + if number[2] != 'G': + raise InvalidComponent() + if calc_check_digit(number[:-1]) != number[-1]: + raise InvalidChecksum() + return number + + +def is_valid(number): + """Check if the number provided is a valid FIGI.""" + try: + return bool(validate(number)) + except ValidationError: + return False diff --git a/tests/test_figi.doctest b/tests/test_figi.doctest new file mode 100644 index 0000000..f63728c --- /dev/null +++ b/tests/test_figi.doctest @@ -0,0 +1,161 @@ +test_figi.doctest - more detailed doctests for the stdnum.figi module + +Copyright (C) 2018 Arthur de Jong + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA + + +This file contains more detailed doctests for the stdnum.figi module. It +tries to validate a number of numbers that have been found online. + +>>> from stdnum import figi +>>> from stdnum.exceptions import * + + +The number should start with two letters (no numbers) and should not contain +one of the letter combinations on the blacklist to avoid clashes with the +ISIN. + +>>> figi.validate('BBG000BLNR78') +'BBG000BLNR78' +>>> figi.validate('B2G000BLNR78') +Traceback (most recent call last): + ... +InvalidFormat: ... +>>> figi.validate('BSG000BLNR71') +Traceback (most recent call last): + ... +InvalidComponent: ... + + +The third position should always be a G: + +>>> figi.validate('BBG000BLNR78') +'BBG000BLNR78' +>>> figi.validate('BBP000BLNR78') +Traceback (most recent call last): + ... +InvalidComponent: ... + + +These have been found online and should all be valid numbers. + +>>> numbers = ''' +... +... BBG000002KG4 +... BBG00002MG40 +... BBG00008PH06 +... BBG0000BYL37 +... BBG0000CKTZ8 +... BBG0000HSV39 +... BBG0000PTG33 +... BBG0000Z40Y9 +... BBG00010C6H2 +... BBG00011PKQ5 +... BBG00011QNR7 +... BBG00015DJD6 +... BBG00016PLG3 +... BBG00017MGS2 +... BBG00017S1T8 +... BBG00018BDZ1 +... BBG00018TBL1 +... BBG00019TTZ5 +... BBG00019XXF3 +... BBG0001BRPC5 +... BBG0001BTYQ9 +... BBG0001DDLR9 +... BBG0001DJNW3 +... BBG000435D86 +... BBG0005FQS91 +... BBG0005GN2W3 +... BBG000702GF7 +... BBG0007CBFS9 +... BBG0007T7L86 +... BBG0007VC3H5 +... BBG0007VS3D2 +... BBG0008C9YV6 +... BBG0008QB509 +... BBG00094V471 +... BBG0009BLLK3 +... BBG0009HJVG6 +... BBG0009KW1N2 +... BBG0009QBMN6 +... BBG0009X4W48 +... BBG0009XGDL8 +... BBG000B1LV75 +... BBG000BVRLT1 +... BBG000GL6ZK9 +... BBG000H3TGD3 +... BBG000PSJRK8 +... BBG0018YXKG6 +... BBG001F8NZN5 +... BBG001NND649 +... BBG001QLGGK7 +... BBG002G4T7H4 +... BBG00337N482 +... BBG003LQ9342 +... BBG0042V1BP7 +... BBG004D1ZCV5 +... BBG004FFN844 +... BBG004QGWGV2 +... BBG0058191D1 +... BBG0058YBK71 +... BBG005THWTJ0 +... BBG0068HLCV6 +... BBG006T9T7D4 +... BBG0077H27R2 +... BBG007N2K8V0 +... BBG007TDLWP3 +... BBG0086KJ938 +... BBG008D76KY4 +... BBG008H2RPR5 +... BBG008KW8457 +... BBG008NX9YV6 +... BBG009S58T33 +... BBG00B3H3RC8 +... BBG00B9D0KL9 +... BBG00BDRD8J9 +... BBG00CNPD178 +... BBG00CTTCVG9 +... BBG00CY386Z9 +... BBG00CY38778 +... BBG00D6PL498 +... BBG00D6RQY20 +... BBG00FBYLY56 +... BBG00FZRBX30 +... BBG00GBVG247 +... BBG00GKR5DB6 +... BBG00GKR5G29 +... BBG00GQ99RN2 +... BBG00GVQVFN9 +... BBG00GW3Q897 +... BBG00H450NQ7 +... BBG00H8ZVML0 +... BBG00H9SZ431 +... BBG00HKBYTG7 +... BBG00HLDPLJ6 +... BBG00HTVPPR0 +... BBG00HTVPSZ5 +... BBG00JGBJZT7 +... BBG00JJC69W0 +... BBG00JQ6F6S6 +... BBG00JXQRJ76 +... BBG00K87P6B8 +... BBG00K87P6K8 +... +... ''' +>>> [x for x in numbers.splitlines() if x and not figi.is_valid(x)] +[] |