Странный мир Python, используемого крупными инвестиционными банками

в 7:25, , рубрики: python, банковские технологии, банковское по, инвестиционный банк, Системы управления версиями, Управление продуктом, финансы в IT
Странный мир Python, используемого крупными инвестиционными банками - 1

Мир больших финансов — это чужая страна; всё в ней происходит иначе

Сегодня мы сквозь замочную скважину взглянем на группу программных систем, о которой общество знает очень мало. Я называю её «банковским Python». Реализации банковского Python, по сути, являются проприетарными форками всей экосистемы Python, которые используются во многих (но не во всех) крупнейших инвестиционных банках. Банковский Python сильно отличается от обычной разновидности Python, которую любят (или ненавидят) большинство людей.

Тысячи людей работают над этими системами (или, скорее, внутри них), но в открытом вебе о них есть не так много информации. Когда я пытался объяснять в разговорах, что такое банковский Python, люди часто высмеивали мои рассказы, как бред лунатика. Всё это кажется слишком эксцентричным.

Я расскажу о вымышленной, объединившей в себе черты многих, воображаемой системе банковского Python под названием «Минерва». Названия подсистем будут изменены, и хотя я попытаюсь быть точным, некоторые подробности придётся стилизовать; кроме того, мне неизвестны все детали. Возможно, я даже допущу случайную ошибку. Но, надеюсь, общая картина будет правдивой.

Barbara, великая хранительница ключей и значений

Первое, что нужно знать о «Минерве» — она построена на основе глобальной базе данных объектов Python.

import barbara

# open a connection to the default database "ring"
db = barbara.open()

# pull out some bond
my_gilt = db["/Instruments/UKGILT201510yZXhhbXBsZQ=="]

# calculate the current value of the bond (according to
# the bank's modellers)
current_value: float = my_gilt.value()

Barbara — это простое хранилище ключей и значений с пространством иерархических ключей. Оно ужасно простое и создано всего лишь из pickle и zip.

Barbara имеет множество «колец», или пространств имён, однако стандартное кольцо — это более-менее одиночная, глобальная база данных объектов для целого банка. Из стандартного кольца можно получать данные о сделках, данные об инструментах (как в коде выше), данные о рынках и так далее. Огромная доля, даже большинство данных, используемых повседневно, поступает из Barbara.

Приложения тоже обычно хранят своё внутреннее состояние в Barbara, записывая классы данных только очень простой блокировкой и транзакциями (при их наличии). Скриптам «Минервы» недоступна файловая система, и небольшие элементы данных, получаемых скриптом, должны помещаться в Barbara.

Внутри Barbara её узлы воссоздают операции записи в её кольцах, это немного напоминает принцип работы Dynamo и BigTable. При вызове barbara.open() он подключается к ближайшему рабочему инстансу стандартного кольца. В пределах этого одного инстанса операции считывания и записи полностью согласованы. Операции считывания и записи от других других инстансов разрешаются быстро, но не мгновенно. Если важна согласованность, нужно просто сделать так, чтобы подключение всегда выполнялось к конкретному инстансу, но при отсутствии необходимости эта практика не поощряется. Barbara на удивление надёжна, вероятно, вследствие своей простоты. Явные сбои чрезвычайно редки, а деградировавшие состояния тоже возникают ненамного чаще.

Примеры путей из стандартного кольца:

Путь Описание
/Instruments Каталог для финансовых инструментов (облигации, акции и т. д.)
/Deals Каталог для сделок (совершившихся торгов)
/FX Общая область отделов иностранной валюты
/Equities/XLON/VODA/ Каталог для элементов, связанных с акциями Vodaphone
/MIFID2/TR/20180103/01 Промежуточный объект для некого бизнес-процесса

Также Barbara имеет функции «перекрытия»:

# connect to multiple rings: keys are 'overlaid' in order of
# the provided ring names
db = barbara.open("middleoffice;ficc;default")

# get /Etc/Something from the 'middleoffice' ring if it exists there,
# otherwise try 'ficc' and finally the default ring
some_obj = db["/Etc/Something"]

Список колец можно поместить в стек, после чего каждая операция считывания будет пробовать получить доступ к первому кольцу, и если ключ в нём отсутствует, она попробует второе кольцо, затем третье, и так далее. Операции записи могут всегда выполняться или в первое кольцо, или в самое верхнее кольцо, где уже существует ключ (это определяется конфигурацией, которую я здесь не привожу).

Существуют веские причины иногда не использовать Barbara. Если набор данных велик, то имеет смысл выбрать что-то другое, например, традиционную базу данных SQL или kdb+. «Мягкое» ограничение на размер объекта Barbara (сжатого) составляет примерно 16 МБ. Сжатые Zip'ом pickle и так достаточно малы, поэтому на самом деле это довольно большой размер. Barbara обеспечивает вторичные индексы для атрибутов объектов, но если вторичные индексы являются важной частью вашей программы, то в этом случае тоже лучше рассмотреть другие варианты.

Dagger — направленный ациклический граф финансовых инструментов

Важной задачей инвестиционных банков является оценка стоимости финансовых инструментов — «оценка активов». Например, облигация оценивается как все деньги, которые мы получим при владении ею, с небольшим снижением цены, учитывающим риск разорения эмитента облигаций. Вероятно, облигации являются простейшим инструментом, поэтому гораздо больший интерес заключается в оценке других, «производных», финансовых инструментов (деривативов), например, кредитных дефолтных свопов, процентных свопов и синтетических версий реальных инструментов. Все они основаны на некоем «базисном» инструменте, но выплата по ним каким-то образом отличается.

Специфика оценки деривативов не важна, достаточно только сказать, что и специфики, и деривативов много. Зависимости между инструментами образуют направленный ациклический граф. Пример иерархии для неких производных финансовых инструментов может выглядеть так:

Странный мир Python, используемого крупными инвестиционными банками - 2

Ценность некоторых финансовых инструментов является производной от ценности других. Благодаря этому они становятся производными. Можно создавать производные производных, а некоторые деривативы производят свою ценность из нескольких базисных активов.

Dagger — это подсистема «Минервы», обеспечивающая правильность этих зависимостей между данными. Класс пишется следующим образом:

class CreditDefaultSwap(Instrument):
    """A credit default swap pays some money when a bond goes into
    default"""

    def __init__(self, bond: Bond):
        super().__init__(underliers=[bond])
        self.bond = bond

    def value(self) -> float:
        # return the (cached) valuation, according to some
        # asset pricing model
        return ...

Dagger отслеживает рёбра графа базисных инструментов и автоматически изменяет оценку деривативов в Barbara при изменении ценности базисных инструментов. Если о компании опубликованы какие-то плохие новости и кредитное агентство снижает её кредитный рейтинг, то кто-то в отделе облигаций изменяет соответствующий объект Bond при помощи Dagger, а сам Dagger автоматически изменяет ценность всех затронутых инструментов. При этом могут быть затронуты сотни других производных инструментов. Снижение кредитного рейтинга может быть довольно увлекательной операцией.

Отдельные инструменты объединены в позиции. Класс Position выглядит примерно так:

class Position:
    """A position is an instrument and how many of it"""
    def __init__(self, inst: Instrument, quantity: float):
        self.inst = inst
        self.quantity = quantity

    def value(self) -> float:
        # return the (cached) valuation, which basically is
        # self.inst.value() * self.quantity
        return ...

Стоит также заметить, что позиция — это нечто, что тоже можно оценить. И её ценность тоже меняется, когда меняется ценность содержащихся в ней инструментов. Всё это автоматически пересчитывает Dagger.

Набор позиций называется «книгой» («book»): в мире финансов это очень перегруженное значениями слово, но в данном контексте оно означает просто набор позиций:

class Book:
    """A book is a set of positions"""
    def __init__(self, contents: Set[Valuable]):
        # the type Valuable is a "protocol" in python terms,
        # or an "interface" in java terms - anything
        # with value()
        self.contents = contents

    def value(self) -> float:
        # again, return the (cached) valuation, which is more
        # or less: sum(p.value() for p in self.contents)
        return ...

Книги могут состоять из других книг. В банке существует иерархия вложенных книг от самого мелкого отдела облигаций до единой книги для всего банка. Для оценки банка нужно выполнить следующее:

# this is the top level book for the whole bank which
# recursively contains everything else in the whole bank
bank = db["/Books/BigBankPlc"]

# this prints the valuation of the whole bank
print(bank.value())

Но это лишь мечта. В реальности финансовый директор, скорее всего, использует для генерации счетов другую систему. Однако оценка вспомогательных книг всё равно используется.

Если вы знаете Excel, то уже должны были замечать схожие черты. В Excel ячейки электронной таблицы тоже обновляются в соответствии с её зависимостями и тоже как направленный ацикличный граф. Dagger позволяет людям перенести их вычисления моделирования из Excel в Python, писать для них тесты, контролировать их версии, не создавая кучу файлов вида CDS-OF-CDS EURO DESK 20180103 Final (final) (2).xlsx. Dagger — важнейшая технология для переноса финансовых моделей из Excel в язык программирования с тестами и контролем версий.

Но Dagger не просто занимается оценками. Также он вычисляет различные «метрики рисков», которые банки используют, чтобы попытаться вычислить, насколько они подвержены различным возможным неприятным событиям. Например, Dagger позволяет относительно легко найти все позиции, допустим, по Compu-Global-Hyper-Mega-Net Plc, которая, по слухам, скоро обанкротится. Он подсчитывает все опционы, фьючерсы, кредитные инструменты, и для всего этого вычисляется «баланс», чтобы найти полную позицию по этой компании для всего банка.

Walpole — исполнитель заданий для всего банка

Выше я говорил, что многие данные хранятся в Barbara. Но вот вам настоящая бомба: исходный код тоже хранится в Barbara, а не на диске. Он находится в специальном кольце Barbara под названием sourcecode.

Хранение исходного кода не в файловой системе противоречит многим допущениям. Как работает такая программа? Этим занимается Walpole — исполнитель заданий для всего банка. Walpole — исполнитель заданий общего назначения, что-то вроде огромного Jenkins в сочетании с огромной systemd.

Как и многое в «Минерве», Walpole не разворачивается для каждого отдела: существует единый инстанс для всего банка. Walpole подходит и для долгоживущих сервисов, и для периодических заданий. Он даже применяется для сборок. Периодические задания возникают в банках часто: существует множество ежедневных или еженедельных заданий по обновлению данных, проверке разных аспектов, отправке дайджестов по электронной почте и т. п.

Walpole выполняет все обычные задачи, необходимые для запуска ПО. Он может перезапускать ПО при его сбоях и отправлять уведомления, если оно продолжает вылетать. Он хранит логи. Он понимает зависимости между заданиями (почти как systemd), поэтому если задание, генерирующее данные для другого задания, вылетает, то другое задание даже не пытается запуститься, а вместо этого отправляет дополнительные уведомления.

Преимущество Walpole заключается в том, что он значительно упрощает развёртывание заданий. Поместить задание в Walpole может кто угодно, вам достаточно создать небольшой файл конфигурации в стиле ini, в котором указано время запуска скрипта и местонахождение основной функции, после чего всё приложение будет развёрнуто без лишних согласований.

И это очень важно, потому что согласование чего бы то ни было в крупном банке — это очень раздражающее занятие: период внедрения на оборудовании может измеряться месяцами. А чтобы согласовать всё с людьми нужно, разумеется, ещё больше времени.

Один из серьёзнейших недостатков «Cloud Native Computing» в его сегодняшнем виде заключается в его очень высокой сложности. Организовать облачные вычислительные системы часто более сложно, чем старомодные, необлачные. Чтобы развернуть своё приложение за пределами «Минервы», вам нужно что-то знать об k8s, или Cloud Formation, или Terraform. Это настолько уникальный набор знаний, что знания обычного программиста (не говоря уж о разработчике финансовых моделей) с ним никак не пересекаются. А с файлом ini может разобраться кто угодно.

MnTable — вездесущая табличная библиотека

Мне всегда казалось упущением, что в языках программирования редко есть встроенные табличные структуры данных. Программисты имеют неприятную склонность тяготеть к хэш-таблицам, особенно в Python и Javascript, где они используются настолько часто, что сложно найти хоть что-нибудь не созданное из хэш-таблиц.

Хэш-таблицы имеют серьёзные недостатки. Во-первых, большинство реализаций хранится только в памяти и находится в ней разреженно, из-за чего становится мучительно работать с наборами данных даже среднего размера; с этой проблемой на практике часто сталкиваются программы на Python. Ещё более важно то, что они заставляют заранее знать паттерны доступа, и уж лучше бы он осуществлялся по одному первичному ключу.

С таблицами всё наоборот: они плотно располагаются в памяти и их легко загружать на диск и считывать с него. Они могут использовать индексы B-дерева, чтобы обеспечить эффективный доступ по любому пути; поэтому вам никогда не придётся инвертировать словарь в процессе выполнения программы только для того, чтобы можно было получить доступ по чему-то кроме ключа. Они могут поддерживать масштабные операции и использовать отложенные вычисления.

В мире open source популярной библиотекой для этого является pandas, но pandas имеет серьёзные недостатки:

  1. Её ещё не существовало, когда реализовали «Минерву»
  2. Она менее эффективна, чем можно было надеяться, особенно при работе с памятью
  3. Она неидеально проявляет себя при работе с наборами данных больше, чем объём памяти
  4. Её API довольно причудлив (хотя это спорный момент).

Вместо pandas в «Минерве» есть проприетарная табличная библиотека: MnTable.

# make a new table with three columns of the types provided
t1 = mntable.Table([('counterparty', str),
                    ('instrument', str),
                    ('quantity', float)])

# put some stuff in the table (in place, tables are
# immutable by default)
t1.extend(
    [
        ['Cleon Partners', 'xlon:voda', 1200.0],
        ['Cleon Partners', 'xlon:spd', 1200.0],
        ['Blackpebble', 'xlon:voda', 1200.0],
    ],
    in_place=True)

# return a new table (without changing the original)
# that only includes vodafone.  this is lazy and
# won't get evaluated until you look at it
t1.restrict(instrument='xlon:voda')

В банковском Python библиотека MnTable используется повсюду. Некоторые реализации представляют собой большие куски кода на C++ (довольно привычно для финансового ПО), другие являются тонким слоем поверх sqlite3. Существует множество программ, которые начинают с MnTable, применяют к ней некий список операций, а затем передают получившуюся таблицу куда-то ещё.

Это удобно, ведь данные в банках повсюду и большая их часть имеет «средний» размер: в пределах гигабайтов. Сейчас много говорят о высокочастотных трейдерах, но большинство финансистов не следит за данными на уровне тиков, а если честно, то и посуточно. «Средний размер» — это достаточно много, поэтому нельзя создавать объект для каждой строки, но не так много, что нужно было бы переносить обработку в какой-то кластер распределённых вычислений.

Мерило мучений

Было бы ошибочно предполагать, что работа с любым финансовым ПО является чистым удовольствием. И «Минерва» в этом — не исключение.

Новичкам требуется чрезвычайно много времени на то, чтобы достичь нужного темпа работы, а для этого им нужно выдержать и не уволиться в припадке ярости после знакомства со специальной собственной IDE банка, работа в которой обязательна (лично я был близок к увольнению). Даже спустя месяцы новички по-прежнему изучают достаточно фундаментальные новые вещи: в этой сфере многое отличается.

Со временем расхождения между банковским Python и опенсорсным Python растут. Технологии развиваются в обоих и разумеется, этот рост больше у опенсорсного, чем у банковского Python, но они всё равно не сближаются. Остальной мир не будет использовать идеи, применяемые в «Минерве», в немалой степени потому, что он никогда о них не слышал. «Минерва» тоже не будет применять многие «внешние» идеи. Существует осуждающее мнение (иногда высказываемое и внутри банковской сферы) о том, что «Минерва» в целом является масштабным примером синдрома неприятия чужой разработки.

По своей природе «Минерва» целостна и приемлет в себя всё. Это замечательно, если ты находишься внутри, но если снаружи, то взаимодействие с «Минервой» превращается в боль. Время от времени разработчики, не занимающиеся «Минервой», спрашивают меня, как можно считать какой-то конкретный элемент данных из Barbara. Я отвечаю, что лучше всего использовать для этого исходный код «Минервы». Они говорят «ну ладно» и спрашивают, можно ли обойтись добавлением скрипта на Python в cronjob, чтобы сделать это? И могу ли я помочь им с кодом? Я отвечаю ему, что это просто: достаточно считать его из Barbara.

Я примерно могу понять, почему у «Минервы» есть собственная IDE — никакая другая IDE не будет работать, если ты хранишь файлы исходного кода в огромной глобальной базе данных. Но я не могу понять, зачем она содержит собственный фреймворк для веба. Инвестиционные банки имеют односторонний подход к ПО в open source: оно может входить, но не может выбраться обратно. Профили на github крупных инвестиционных банков едва живы по сравнению с компаниями схожего размера в других отраслях. Это очень проприетарное отношение, названное правилом Волкера, вытолкнуло из инвестиционных банков почти весь проприетарный трейдинг. Это настоящее проклятие.

Возможно, самый серьёзный недостаток такой системы связан с профессионализмом. С каждым годом, проведённым в монокультуре «Минервы», навыки, необходимые для взаимодействия с обычным ПО, атрофируются. Ко времени своего увольнения я почти забыл, как настраивать pip и virtualenv (необходимые для обычного Python навыки). Когда всё находится в одном репозитории и весь код доступен через import, от пакетирования ПО отвыкаешь.

В чём его отличие

Я рассказал не обо всём, что есть в типичной реализации банковского Python. Например, я пропустил следующие особенности:

  • проприетарная структура данных временных последовательностей
  • система «подтверждения» («vouch») для переноса твоих изменений в продакшен
  • путешествия во времени в Dagger
  • частично уникальная (не git) система контроля версий
  • система разрешений на основе Prolog
  • шина передачи финансовых сообщений, ориентированная на воспроизведение
  • экзистенциальная тоска от длительной работы с Windows 7 и MS Outlook 2010

Тем не менее, надеюсь, я дал представление о самых важных центральных элементах: Barbara, Dagger, Walpole и MnTable. Три из этих четырёх подсистем касаются данных. (Оставшуюся можно рассматривать как базу данных заданий.)

Одна из небольших странностей «Минервы» заключается в том, что многое в ней использует принцип «главное — данные», а не «главное — код». Это странно, потому что по большей мере в разработке ПО всё наоборот. Например, в object oriented design цель заключается в упорядочивании программы на основе «классов», которые являются согласованными группами поведений (т. е. кода), а данные часто просто используются за компанию. Написание программ с MnTable отличается от этого подхода: мы группируем данные в таблицы, после чего код живёт отдельно от них. Эти две концепции упорядочивания вычислений — основная причина несоответствия объектной и реляционной моделей, вызывающего такие страдания. В силе нет равновесия: гораздо больше программистов могут проектировать качественные объектно-ориентированные классы, чем преобразовать набор таблиц в третью нормальную форму. По большей мере это и является причиной постоянного возникновения раздражающего несоответствия.

Ещё одна необычная особенность «Минервы» в том, что во многих случаях её разработчики выбирают использовать что-то одно большое вместо нескольких маленьких. Одна большая кодовая база. Одна большая база данных. Один большой исполнитель заданий. Объединение всего этого устраняет большую долю случайной сложности: у тебя уже есть среда выполнения языка программирования (и версия в продакшене такая же, как на твоём компьютере), простая база данных и место, где может запускаться твой код ещё до того, как ты начал. Это значит, что можно просто сесть, написать скрипт и в течение часа запустить его в продакшене, что очень важно.

Очевидно, что на «Минерву» сильно повлияла зависимость от ранее выбранного технологического пути финансового сектора; иными словами, там много MS Excel. Любое новое программное решение будет сравниваться с MS Excel и если результат неудовлетворителен, люди просто продолжат пользоваться Excel. Очень многие инженеры смотрели на уже имеющийся рабочий процесс, состоящий из электронных таблиц, содрогались и предлагали реализовать триаду из микросервисов, Kubernetes и чего-то под названием «service mesh».

Однако подобные технологии Большого Энтерпрайза отнимают у пользователей Excel возможность влияния, они больше не понимают тех бизнес-процессов, которые реализуют, и при каждом изменении в ПО им приходится договариваться с техногиками. Когда-то доступная им пластичность электронных таблиц теперь совершенно утеряна. Использование простых функций Python в системе с управлением исходным кодом — это более предпочтительный компромисс, чем современный аналог J2EE. Финансисты способны научиться Python, и хотя они никогда не будут от него в восторге, им можно вносить свой вклад на гораздо более высоком уровне, даже создавать собственные изменения и внедрять их.

Плагиат идей из существующих систем

Я жалею о том, что сфера разработки ПО в целом тратит очень мало времени на изучение опыта существующих систем и на определение того, что у них получилось, а что нет. Есть очень мало книг с подробным рассмотрением реально существующих систем.

Даже когда известны подробности устройства систем, их почему-то очень мало изучают. Электронная почта существует уже очень долго: она на десяток лет старше Интернета. И всё это время она изменялась не очень быстро, в основном по-прежнему оставаясь такой же, какой была в 80-х. Несмотря на это, многие программисты всё равно смутно представляют себе, что происходит при нажатии кнопки «Отправить». Однако некоторые из них, я в этом уверен, тем не менее, будут пытаться «уничтожить» электронную почту.

И это печально, ведь чужие системы, как и чужие страны, могут расширять наше сознание, когда мы изучаем их самостоятельно. Их обычаи столь отличаются от наших, что это может заставить нас переосмыслить собственный подход. Но когда ты слышишь о них от кого-то, это может показаться бессмыслицей.

Однажды я вкратце описывал систему «подтверждения» «Минервы» другому программисту, который никогда о ней не слышал. Я рассказал, что когда ты вносишь изменение в код, тебе просто нужно убедить одного из владельцев кода одобрить соответствующий файл. Если изменение очень срочное, они могут одобрить твоё изменение не глядя, полагаясь только на твою репутацию. Как только они нажмут на кнопку «vouch» — бум! — твой код отправляется в продакшен: в конце концов, нет никакого этапа развёртывания, если твой код хранится в базе данных. Не поверив мне, он спросил, кто вообще доверится такому банку. Я ответил, что многие люди. Это очень большой банк. И что программист наверняка о нём слышал.

Примечания

Если вам любопытно попробовать табличную библиотеку в стиле MnTable, то мой друг Сэл написал на чистом Python её совместимую по API версию под названием eztable.

Я уже говорил, что программисты слишком презрительно относятся к MS Excel. При помощи Excel можно достичь ужасно многого: даже большего, чем некоторые программисты достигают без него. В инвестиционных банках «высшего дивизиона» существуют трейдинговые системы, где торги выполняются нажатием на специальные ячейки в специальных файлах xlsx.

Даже я готов признать, что это перебор, но если вы ещё не знаете Excel, то это одна из ценных вещей, которые стоит изучить. Программистам лучше всего узнать, что они теряют, из обзорного доклада Джоэла Спольски, предназначенного специально для программистов. Если после этого вы решите нырнуть в кроличью нору, то говорят, что курс Excel Skills for Business Specialisation на Coursera великолепен.

Одна из самых запутывающих программистов особенностей заключается в том, что несмотря на то, что большинство работающего с деньгами ПО использует арифметику произвольной точности, чтобы точно считался каждый пенни, в финансовом моделировании применяются floats, ведь чаще всего клиентов не волнуют пенни.

Я говорил о перекрытиях в Barbara. Тот же принцип работает и для исходного кода. Можно приказать Walpole смонтировать ваше собственное кольцо перед sourcecode, когда он импортирует код для задания, а затем вы сможете запушить файлы исходников в это кольцо, вместо того, чтобы ждать их подтверждения в sourcecode. На этом тёмном пути возможны различные безумные, причудливые и разнообразные хаки. Если понемногу, то их можно применять.

Автор:
PatientZero

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js