Как подтвердить номер международного идентификационного номера ценных бумаг (ISIN)

Если я не ошибаюсь, последняя позиция номера ISIN является контрольной цифрой. Какая математическая функция определяет его значение в зависимости от первых 11 цифр?


person Pablo Francisco Pérez Hidalgo    schedule 22.04.2013    source источник


Ответы (7)


http://en.wikipedia.org/wiki/International_Securities_Identification_Number

Процедура вычисления контрольных цифр ISIN аналогична методу "Modulus 10 Double Add Double", используемому в CUSIP. Чтобы вычислить контрольную цифру, сначала преобразуйте любые буквы в числа, добавив их порядковый номер в алфавите к 9, так что A = 10 и M = 22. Начиная с самой правой цифры, каждая вторая цифра умножается на два. (Для контрольных цифр CUSIP эти два шага меняются местами.) Результирующая цепочка цифр (числа больше 9 становятся двумя отдельными цифрами) складываются. Вычтите эту сумму из наименьшего числа, оканчивающегося на ноль, которое больше или равно ему: это дает контрольную цифру, которая также известна как десятичное дополнение суммы по модулю 10. То есть полученная сумма, включая контрольную- цифра, кратная 10.

У них тоже есть хороший пример.

person rmmh    schedule 22.04.2013
comment
В этом описании есть некоторые ошибки: ... каждая вторая цифра умножается на два. Как я мог найти в других ссылках и проверяя примеры: не все остальные числа, а альтернативные цифры умножаются на два. - person Pablo Francisco Pérez Hidalgo; 22.04.2013
comment
@ PabloFranciscoPérezHidalgo: вполне разумно истолковать every other digit как означающее alternative digits. Непонятно, в чем, по вашему мнению, заключаются ошибки в статье Википедии. - person High Performance Mark; 22.04.2013

Основываясь на примерах других, вот реализация C #, которая будет проверять как ISIN, так и CUSIP (и, возможно, некоторые другие варианты Luhn).

Использование:

foreach (var isin in ValidIsins)
{
    var calculatedChecksum = SecuritiesValidation.CalculateChecksum(isin.Substring(0, 11));
    var actualChecksum = (isin.Last() - '0');
    Assert.AreEqual(calculatedChecksum, actualChecksum);
}
foreach (var cusip in ValidCusips)
{
    var calculatedChecksum = SecuritiesValidation.CalculateChecksum(cusip.Substring(0, 8), true, true);
    var actualChecksum = (cusip.Last() - '0');
    Assert.AreEqual(calculatedChecksum, actualChecksum);
}

Реализация:

public static class SecuritiesValidation
{
    public static int CalculateChecksum(IEnumerable<char> codeWithoutChecksum, bool reverseLuhn = false, bool allowSymbols = false)
    {
        return reverseLuhn
            ? codeWithoutChecksum
                .Select((c, i) => c.OrdinalPosition(allowSymbols).ConditionalMultiplyByTwo(i.IsOdd()).SumDigits())
                .Sum()
                .TensComplement()
            : codeWithoutChecksum
                .ToArray()
                .ToDigits(allowSymbols)
                .Select((d, i) => d.ConditionalMultiplyByTwo(i.IsEven()).SumDigits())
                .Sum()
                .TensComplement();
    }

    public static bool IsChecksumCorrect(string code, bool reverseLuhn = false, bool allowSymbols = false)
    {
        try
        {
            var checksum = code.Last().ToInt();
            return checksum == CalculateChecksum(code.Take(code.Length - 1), reverseLuhn, allowSymbols);
        }
        catch
        {
            return false;
        }
    }

    /* Be careful here. This method is probably inapropriate for anything other than its designed purpose of Luhn-algorithm based validation.
     * Specifically:
     * - numbers are assigned a value equal to the number ('0' == 0, '1' == 1).
     * - letters are assigned a value indicating the number 9 plus the letters ordinal position in the English alphabet ('A' == 10, 'B' == 11).
     * - if symbols are allowed (eg: for CUSIP validation), they are assigned values beginning from 36 ('*' == 36, '@' == 37).
     */
    private static int OrdinalPosition(this char c, bool allowSymbols = false)
    {
        if (char.IsLower(c))
            return char.ToUpper(c) - 'A' + 10;

        if (char.IsUpper(c))
            return c - 'A' + 10;

        if (char.IsDigit(c))
            return c.ToInt();

        if (allowSymbols)
            switch (c)
            {
                case '*':
                    return 36;
                case '@':
                    return 37;
                case '#':
                    return 38;
            }
        throw new ArgumentOutOfRangeException("Specified character is not a letter, digit or allowed symbol.");
    }

    private static bool IsEven(this int x)
    {
        return (x % 2 == 0);
    }

    private static bool IsOdd(this int x)
    {
        return !IsEven(x);
    }

    private static int ToInt(this char digit)
    {
        if (char.IsDigit(digit))
            return digit - '0';
        throw new ArgumentOutOfRangeException("Specified character is not a digit.");
    }

    private static IEnumerable<int> ToDigits(this char[] s, bool allowSymbols = false)
    {
        var digits = new List<int>();
        for (var i = s.Length - 1; i >= 0; i--)
        {
            var ordinalPosition = s[i].OrdinalPosition(allowSymbols);
            digits.Add(ordinalPosition % 10);
            if (ordinalPosition > 9)
                digits.Add(ordinalPosition / 10);
        }
        return digits;
    }

    private static int SumDigits(this int value)
    {
        //return value > 9 ? ((value / 10) + (value % 10)) : value;
        return ((value / 10) + (value % 10));
    }

    private static int ConditionalMultiplyByTwo(this int value, bool condition)
    {
        return condition ? value * 2 : value;
    }

    private static int TensComplement(this int value)
    {
        return (10 - (value % 10)) % 10;
    }
}

Вероятно, имеет смысл использовать проверку контрольной суммы в сочетании с сопоставлением с шаблоном регулярного выражения. Я использую регулярное выражение:

ISIN: ^(XS|AD|AE|AF|AG|AI|AL|AM|AO|AQ|AR|AS|AT|AU|AW|AX|AZ|BA|BB|BD|BE|BF|BG|BH|BI|BJ|BL|BM|BN|BO|BQ|BR|BS|BT|BV|BW|BY|BZ|CA|CC|CD|CF|CG|CH|CI|CK|CL|CM|CN|CO|CR|CU|CV|CW|CX|CY|CZ|DE|DJ|DK|DM|DO|DZ|EC|EE|EG|EH|ER|ES|ET|FI|FJ|FK|FM|FO|FR|GA|GB|GD|GE|GF|GG|GH|GI|GL|GM|GN|GP|GQ|GR|GS|GT|GU|GW|GY|HK|HM|HN|HR|HT|HU|ID|IE|IL|IM|IN|IO|IQ|IR|IS|IT|JE|JM|JO|JP|KE|KG|KH|KI|KM|KN|KP|KR|KW|KY|KZ|LA|LB|LC|LI|LK|LR|LS|LT|LU|LV|LY|MA|MC|MD|ME|MF|MG|MH|MK|ML|MM|MN|MO|MP|MQ|MR|MS|MT|MU|MV|MW|MX|MY|MZ|NA|NC|NE|NF|NG|NI|NL|NO|NP|NR|NU|NZ|OM|PA|PE|PF|PG|PH|PK|PL|PM|PN|PR|PS|PT|PW|PY|QA|RE|RO|RS|RU|RW|SA|SB|SC|SD|SE|SG|SH|SI|SJ|SK|SL|SM|SN|SO|SR|SS|ST|SV|SX|SY|SZ|TC|TD|TF|TG|TH|TJ|TK|TL|TM|TN|TO|TR|TT|TV|TW|TZ|UA|UG|UM|US|UY|UZ|VA|VC|VE|VG|VI|VN|VU|WF|WS|YE|YT|ZA|ZM|ZW)([0-9A-Z]{9})([0-9]{1})$

CUSIP: ^[A-Z0-9]{8}[0-9]$

person grenade    schedule 20.03.2015
comment
Также хорошо работает для FIGI. - person John Zabroski; 20.12.2019
comment
Это очень полезно. Спасибо! Обратите внимание, что пространство имен ISIN больше не актуально. Например, префикс ЕС сейчас существует. - person Codure; 13.05.2021

Основываясь на примерах, опубликованных в Википедии, метод следующий:

  1. Замените каждую букву ее порядковым номером (A = 1, B = 2 и т. Д.) Плюс 9 -> введите описание изображения здесь
  2. Для каждой цифры в четной позиции, начиная с крайней правой позиции (введите здесь описание изображения), замените его цифрами его двойника (две цифры в двух векторных записях) -> введите здесь описание изображения;
  3. Код верификации:

введите описание изображения здесь

Возможная реализация на JavaScript:

function getVerificationCode(isin)
{
 if(isin.length != 12) return null;
 var v = []; 
 for(var i = isin.length-2; i >= 0; i--)
 {
    var c = isin.charAt(i);
    if(isNaN(c)) //Not a digit
    {
        var letterCode = isin.charCodeAt(i)-55; //Char ordinal + 9
        v.push(letterCode % 10);
        if(letterCode > 9)
          v.push(Math.floor(letterCode/10));
    }
    else
      v.push(Number(c));
 }
 var sum = 0;
 var l = v.length;
 for(var i = 0; i < l; i++)
     if(i % 2 == 0)
 {
    var d = v[i]*2;
    sum += Math.floor(d/10);
    sum += d % 10;
 }
 else
    sum += v[i];
 return 10 - (sum  % 10);
}

РЕДАКТИРОВАТЬ: Чтобы включить обновления @queso:

function getVerificationCode(isin) {
    if (isin.length != 12) return false;
    var v = [];
    for (var i = isin.length - 2; i >= 0; i--) {
        var c = isin.charAt(i);
        if (isNaN(c)) { //not a digit
            var letterCode = isin.charCodeAt(i) - 55; //Char ordinal + 9
            v.push(letterCode % 10);
            if (letterCode > 9) {
                v.push(Math.floor(letterCode / 10));
            }
        } else {
            v.push(Number(c));
        }
    }
    var sum = 0;
    var l = v.length;
    for (var i = 0; i < l; i++) {
        if (i % 2 == 0) {
            var d = v[i] * 2;
            sum += Math.floor(d / 10);
            sum += d % 10;
        } else {
            sum += v[i];
        }
    }
    return (10 - (sum % 10)) % 10
}
person Pablo Francisco Pérez Hidalgo    schedule 22.04.2013
comment
Для приведенного выше кода ISIN XS0977502110 не удается найти здесь: en.wikipedia.org/wiki/ Мне удалось обновить ваш код здесь, jsfiddle.net/markbenda/nh2w1Lbh/16. Спасибо за трудную часть работы. - person Queso; 03.02.2015
comment
@Queso Я отредактировал свой ответ, включив в него ваши предложения по коду. Спасибо за улучшения! - person Pablo Francisco Pérez Hidalgo; 20.03.2015
comment
Я различал эти две версии, и исправления без пробелов / скобок выглядят так: 1) вернуть false вместо null во второй строке и 2) вернуть (10 - (sum % 10)) % 10 вместо 10 - (sum % 10) во второй предпоследней строке, добавив дополнительный мод 10 в качестве последнего шага. diffchecker.com/jEeML1lU - person Martin Burch; 30.07.2020

Я делюсь с вами функцией в Matlab благодаря @pablo и @queso.

function isISIN = checkISINCode(Isin)
%
%
%
% see:
%   - source:https://en.wikipedia.org/wiki/International_Securities_Identification_Number
%   - source: https://stackoverflow.com/questions/16140753/how-to-validate-a-international-securities-identification-number-isin-number
%
%
    isISIN = 0; 

    if length(Isin) ~= 12
        return;
    end

    v = [];
    for i = (length(Isin)-1):-1:1
        c = Isin(i);
        if isnan(str2double(Isin(i)))
            % from ASCII 
            letterCode = double(upper(Isin(i))) - 64 + 9; 
            v = [mod(letterCode, 10), v];
            if letterCode > 9
                v = [floor(letterCode/10),v];
            end
        else
            v = [int8(str2double(Isin(i))), v];
        end
    end

    sum_ = 0;
    l = length(v);
    for i=1:l
        if(mod(i-1,2) == 0)
            d = v(i) * 2.;
            sum_ = sum_ + floor( double(d) / 10.0);
            sum_ = sum_ + mod(d, 10);
        else
            sum_ = sum_ + v(i);
        end
    end
    checkValue = mod((10 - mod(sum_, 10)),10);

    % Check Computed value with last digit
    isISIN = int8(str2double(Isin(end))) == checkValue;
end
person jimban    schedule 09.10.2018

Хочу поделиться своей реализацией на R. Она не требует какого-то конкретного пакета.

Mgsub - это вспомогательная функция, которая позволяет заменять все символы в коде ISIN в одной команде. Он скопирован из Замените несколько букв акцентами на gsub

iso3166alpha2$Code содержит список стран, перечисленных в Grenade

Алгоритм реализован в функции isIsin(x), которая возвращает TRUE в случае правильного ISIN-кода.

mgsub <- function(pattern, replacement, x, ...) {
  if (length(pattern)!=length(replacement)) {
    stop("pattern and replacement do not have the same length.")
  }
  result <- x
  for (i in 1:length(pattern)) {
    result <- gsub(pattern[i], replacement[i], result, ...)
  }
  result
}

isIsin <- function (identifier) {

  correctPrefix <- substr(identifier, 1, 2) %in% c(iso3166alpha2$Code, "XS")

  correctLength <- nchar(identifier) == 12  

  correctCharset <- !grepl('[[:punct:]]', identifier)

  if(!correctPrefix | !correctLength | !correctCharset) {
    return(FALSE)
  }

  # replace all character with its equivalent number  
  identifierOnlyNumbers <- mgsub(LETTERS, seq(10, 35), substr(identifier, 1, 11))

  # split the identifier in single digits and reverse its order
  characterVector <- rev(unlist(strsplit(identifierOnlyNumbers, "")))

  # Double every second digit of the group of digits with the rightmost character
  characterVector[seq(1, nchar(identifierOnlyNumbers), 2)] <- 
    as.character(as.numeric(characterVector[seq(1, nchar(identifierOnlyNumbers), 2)]) * 2)

  # Subtract 9 if > 9 (can apply to all since no digit can be greater than 9 before doubling)
  # Add up the digits
  summation <- sum(ifelse(as.numeric(characterVector) > 9, as.numeric(characterVector) - 9, as.numeric(characterVector)))

  # Take the 10s modulus of the sum, subtract it from 10 and 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
  correctCheckDigit <- (10 - (summation %% 10)) %% 10 == as.numeric(substr(identifier, 12, 12))

  correctCheckDigit 

}
person Simone Raba    schedule 02.02.2018

Это подход в Swift.

Сначала он проверяет требование 2 буквы + 10 буквенно-цифровых символов с регулярным выражением.

func validateISIN(_ isin : String) -> Bool {
    guard isin.range(of: "^[A-Z]{2}[A-Z0-9]{10}$", options: .regularExpression) != nil,
        let checksum = Int(isin.suffix(1)) else { return false }
    let digits = isin.dropLast().map{Int(String($0), radix: 36)!}.map(String.init).joined()
    var sum = 0
    var evenFlag = true
    digits.reversed().forEach { character in
        var integer = Int(String(character))!
        if evenFlag { integer *= 2 }
        sum += integer / 10
        sum += integer % 10
        evenFlag.toggle()
    }
    return (10 - (sum % 10)) % 10 == checksum
}
person vadian    schedule 23.08.2019

person    schedule
comment
Я создал функцию PHP на основе [en.wikipedia.org/wiki/. Вы можете укажите 7-значный номер CUSIP с 2-значным кодом страны - person KiraKim; 17.04.2018