Bank Python의 구술 역사

ko생성일: 2025. 6. 23.갱신일: 2025. 7. 28.

대형 투자 은행에서 사용되는 특이한 Bank Python 생태계에 대한 심층 리포트. 고유한 데이터베이스, 가격 산정 시스템, 잡 러너, 테이블 라이브러리를 중심으로 투자 은행 내에서 Python이 어떻게 변형되고 있는지 탐구합니다.

Bank Python의 구술 역사

2021년 11월

대형 투자 은행에서 사용되는 신기한 Python 세계

Image 1: 주거 지역에서 바라본 캐너리 워프 모습

고금융(High finance)은 외국과도 같다. 이곳에서는 모든 것이 다르게 돌아간다.

오늘은 대중에게 잘 알려지지 않은 소프트웨어 시스템, 즉 "Bank Python"이라고 부르는 집단을 들여다보는 시간을 가져보려 한다. Bank Python 구현체들은 실질적으로 전체 Python 생태계를 포크(fork)하여 독자적으로 운영하는 폐쇄형 시스템으로, 주요 투자은행 대다수(모두는 아님)에서 사용된다. 이 Bank Python은 우리가 일반적으로 알고 사랑(혹은 싫어)하는 흔한 Python과는 꽤나 다르다.

수천 명 이상의 사람들이 이 시스템에서 또는 시스템 "안"에서 일하고 있지만, 공개 웹에는 이에 대한 정보가 잘 알려져 있지 않다. 내가 이 이야기를 대화 중에 하면 사람들이 돌아버린 것 아니냐는 식으로 치부하기 일쑤였다. 너무도 비현실적인 얘기처럼 들리기 때문이다.

이 글에서는 실제의 여러 Bank Python 시스템을 섞어 만든 가상의 시스템 "Minerva"를 통해 이야기하겠다. 서브시스템(하위 시스템)의 이름은 바꿨고, 최대한 정확히 쓰려 노력하겠지만 디테일이나 스타일은 일부 바뀌었을 수 있으며(그리고 모든 디테일을 다 알지 못하는 것도 있다). 실수도 있을 수 있는 점 참고 바란다. 큰 줄기를 전달하는 게 목표다.

바버라(Barbara), 위대한 키-밸류 저장소

Minerva의 가장 첫번째 특징은 글로벌한 Python 객체 데이터베이스 위에 구축되어 있다는 점이다.

python
import barbara # "ring"이라는 기본 데이터베이스에 연결 db = barbara.open() # 일부 채권 정보 가져오기 my_gilt = db["/Instruments/UKGILT201510yZXhhbXBsZQ=="] # 해당 채권의 현재 가치 계산 (은행의 모델러가 산출) current_value: float = my_gilt.value()

Barbara는 계층적 키 공간(hierarchical key space)을 가진 단순한 키-밸류 저장소다. 구성도 정말 단순해서 picklezip만으로 만들어졌다.

Barbara에는 여러 "ring"(네임스페이스)이 있지만, 기본 ring은 사실상 전체 은행을 위한 전역 객체 데이터베이스다. 여기서 거래 데이터, 금융상품 데이터(위 예시), 시세 데이터 등 은행이 일상적으로 쓰는 거의 대부분의 데이터가 나온다.

응용프로그램의 내부 상태 또한 Barbara에 저장하는 것이 흔하다. dataclass 객체를 특별한 록이나 트랜잭션(있어도 아주 단순함) 없이 그냥 직렬화해서 저장한다. Minerva 스크립트에는 파일시스템 자체가 아예 제공되지 않으므로, 스크립트가 다루는 그 조각의 데이터조차 Barbara에 저장해야 한다.

Barbara 노드는 내부적으로 ring 내에서 쓰기(write)를 복제(replication)하며, 이런 방식은 DynamoBigTable과 비슷하다. barbara.open()을 호출하면 가장 가까운 기본 ring 인스턴스에 연결된다. 해당 인스턴스 내에서 읽기/쓰기 일관성은 강하게 보장된다. 다른 인스턴스에서 들어오는 변화도 곧 반영되긴 하지만 즉시 일치되지는 않는다. 만약 일관성이 정말 중요하면, 특정 인스턴스에만 연결되도록 하면 된다(권장되는 방법은 아님). Barbara는 굉장히 단순해서 그런지 의외로 매우 안정적이며, 치명적 장애는 극히 드물고 성능 저하도 아주 희귀하다.

예시 경로는 아래와 같다:

경로설명
/Instruments금융상품(채권, 주식 등) 디렉토리
/Deals(체결된) 거래 내역 디렉토리
/FX외환(FX) 부문의 데이터 영역
/Equities/XLON/VODA/Vodafone 주식 관련 내용 디렉토리
/MIFID2/TR/20180103/01업무 프로세스 중간 객체

Barbara의 '오버레이' 기능으로 여러 ring을 겹쳐서 쓸 수 있다:

python
# 여러 ring을 스택처럼 연결. 순서대로 키를 찾다가 없으면 다음 ring에서 시도 db = barbara.open("middleoffice;ficc;default") # /Etc/Something이 'middleoffice'에 있으면 그걸, 아니면 'ficc', 아니면 default ring에서 some_obj = db["/Etc/Something"]

이렇게 ring들을 스택처럼 나열하면, 읽기(read)는 첫 번째 ring부터 찾아보고 없으면 두 번째, 그 다음 식으로 내려간다. 쓰기(write)는 첫 ring에 하거나, 해당 키가 존재하는 가장 위쪽 ring에 쓰기도 한다(구성 방법에 따라 다름).

Barbara를 쓰지 말아야 할 이유도 일부 있다. 데이터셋이 너무 크면 전통적인 SQL 데이터베이스나 kdb+ 등 다른 걸 고려해야 한다. Barbara 객체(압축 기준)의 암묵적 최대 크기는 약 16MB다. 하지만 pickle 압축이 작아서 실제로는 꽤 넉넉하다. Barbara에는 객체 속성에 대한 2차 인덱스를 만들 수 있지만, 이 인덱스 사용이 매우 중요한 프로그램이라면 역시 다른 걸 쓰는 게 낫다.

Dagger, 비순환 방향 그래프로서의 금융상품

투자 은행의 중요한 역할 중 하나는 금융상품의 가치를 산정(자산 가격 평가)하는 것이다. 예를 들어 채권은 소유 시 받을 모든 금액을 약간 할인(발행자가 파산 위험이 있으므로)해서 제대로 평가된다. 채권은 개념적으로(!) 최단명한 상품이며, 더 큰 관심사는 "파생금융상품(derivative)" 예컨대 신용부도스왑(CDS), 금리스왑, 실물상품을 합성한 파생상품 등이다. 이들은 전부 '기초자산'에서 가치를 파생하지만, 실제 지급 형태는 다르다.

파생상품 가치 산정의 세부 내용은 여기서는 중요하지 않다. 어쨌든 수많은 종류와 복잡함이 있으며, 이들끼리의 의존성은 비순환 방향 그래프(DAG, Directed Acyclic Graph)로 표현된다. 파생상품 계층 구조의 예시는 다음과 같을 수 있다:

Image 2: 여러 금융상품이 의존하는 구조 다이어그램

어떤 금융상품은 다른 것에 의존해 가치가 정해진다. 이게 파생상품이다. 실제로 파생된 파생상품도 있으며, 여러 개의 '기초자산'에 의존하는 상품도 있다.

Dagger는 Minerva의 하위 시스템으로, 이런 데이터 의존성을 체계적으로 관리하는 역할을 한다. 코드는 대략 이렇게 쓴다:

python
class CreditDefaultSwap(Instrument): """신용부도스왑: 해당 채권이 디폴트 시 일정 금액 지급""" def __init__(self, bond: Bond): super().__init__(underliers=[bond]) self.bond = bond def value(self) -> float: # 자산 가격 모델에 따른 (캐시된) 평가액 리턴 return ...

Dagger는 기초자산(underlier) 간의 그래프 구조를 추적하며, 이 기초자산의 가치가 변하면 Barbara 상의 파생상품 평가액을 자동으로 갱신한다. 예를 들어, 한 회사의 신용등급이 갑작스레 하락하면 누군가가 채권 객체를 Dagger를 통해 갱신하고, Dagger는 그에 따라 영향을 받는 모든 파생상품을 자동으로 재평가한다. 수백 가지 금융상품에 연쇄 영향이 올 수 있다. 신용등급 강등은 꽤 스릴 넘치는 이벤트다.

각 금융상품은 Position(포지션)이라는 클래스로 묶인다. 구조는 블록처럼 단순하다:

python
class Position: """포지션: 해당 상품과 수량""" def __init__(self, inst: Instrument, quantity: float): self.inst = inst self.quantity = quantity def value(self) -> float: # (캐시된) 평가액 가져오기 return ...

포지션 또한 바로 평가가 가능하고, 포함된 상품 가치가 변할 때 자동 재평가된다.

여러 포지션의 집합을 "Book"(장부)이라고 부른다. 금융권에서 이미 의미가 너무 많은 단어지만, 여기서는 포지션들의 집합일 뿐이다:

python
class Book: """Book: 여러 포지션들의 집합""" def __init__(self, contents: Set[Valuable]): # Valuable은 Python 용어론상 "프로토콜", 자바로 치면 "인터페이스" (value() 함수만 있으면 됨) self.contents = contents def value(self) -> float: return ...

Book은 다른 Book을 담기도 한다. 가장 작은 데스크(book)에서 은행 전체까지 계층이 쌓인다. 아래는 은행 전체 가치 산정 예시다:

python
# 은행 전체를 대표하는 최상위 Book, 내부적으로 전체를 포함 bank = db["/Books/BigBankPlc"] # 전체 은행 평가액 출력 print(bank.value())

이게 이상적인 모습이지만, 실상은 CFO는 계정 집계를 위해 전혀 다른 시스템을 쓴다. 그래도 하위 Book 산정액 등은 실제로 많이 활용된다.

엑셀을 아는 사람은 비슷한 점을 느꼈을 것이다. 엑셀 역시 계산식 셀(셀 간 참조)이 비순환 방향 그래프 형태로 갱신된다. Dagger 덕분에 금융 모델러들은 Excel 스타일 모델링을 Python 코드로, 테스트 및 버전 관리를 통합해서 쓸 수 있다. 파일명 난장판에서 벗어나 아래와 같은 일이 없다:

CDS-OF-CDS EURO DESK
20180103 Final (final) (2).xlsx

Dagger는 엑셀을 넘어 프로그래밍 언어와 테스트, 버전 관리로 금융 모델을 끌어올리기 위한 핵심 기술이다.

Dagger는 자산 평가뿐 아니라 은행이 리스크(위험 지표)를 관리할 수 있도록 돕는다. 예를 들어, 파산 루머가 도는 Compu-Global-Hyper-Mega-Net Plc사에 대한 전체 포지션(옵션, 선물, 신용상품 등 다 포함)을 Dagger로 바로 집계할 수 있다. 리스크를 실시간으로 파악하여 문제가 될 수 있는 익스포저를 조기에 대응할 수 있게 해준다.

Walpole, 은행 전역의 잡(job) 실행기

지금까지 데이터가 Barbara에 저장된다고 했지만, 여기서 충격 고백: 소스 코드도 파일이 아닌 Barbara에 굴러간다. Barbara의 특별한 ring, sourcecode에 저장된다.

소스코드가 파일이 아니라면 어떻게 실행될까? 답은 Walpole, 은행 전체를 아우르는 범용 Job Runner(작업 실행기)다. Walpole은 mega Jenkins와 mega systemd가 합쳐진 듯한 범용 잡 러너다.

Minerva의 많은 것들처럼 Walpole도 팀별로 배포되는 게 아니라 은행 전체에 단 하나만 존재한다. Walpole은 장기 실행 서비스와 주기적 Job 모두에 적합하며, 빌드 용도로도 쓰인다. 은행 업무에는 매일, 매주 주기적으로 돌려야 하는 Job이 정말 많다.

Walpole은 소프트웨어 운영에 필요한 거의 모든 일을 담당한다. 소프트웨어가 죽으면 재시작하고, 크래시가 반복되면 경보도 보낸다. 로그 저장도 해주고, 잡 간의 의존성도 관리한다(systemd와 유사). 내 잡이 의존하는 선행 데이터가 생성 실패하면 내 잡은 아예 시작도 안 하고 경보만 더 띄운다.

진짜 장점은 배포의 진입장벽이 대폭 내려간다는 점이다. Walpole에 Job 추가는 매우 쉽다. 실행 시간, main 함수 위치 등만 간단한 ini 파일로 설명하면 그 자체만으로 곧장 배포된다.

이게 엄청난 이유는, 큰 은행에서 협의를 거치는 것이 늘 고통이기 때문이다. 하드웨어 리드타임만 수개월이다. 사람과의 협의는 당연히 더 오래 걸린다.

요즘의 "클라우드 네이티브 컴퓨팅"은 너무 복잡하다. 기존의 비 클라우드 환경보다 오히려 더 복잡한 경우가 많다. Minerva 밖에서 앱을 배포하려면 k8s나 CloudFormation, Terraform 등을 알아야 한다. 이건 일반 프로그래머(금융 모델러라면 더더욱)가 배우기 힘든 완전 별개의 스킬셋이다. 반대로, ini 파일 정도는 누구나 쓸 수 있다.

MnTable, 어디서나 쓰이는 테이블 라이브러리

나에겐 늘 아쉬움이 있는데, 프로그래밍 언어에는 내장 테이블 자료구조가 거의 없다. 프로그래머들은 해시테이블에 끌리는 경향이 있는데, Python, JS처럼 대부분이 해시테이블로 만들어져서, 해시테이블 아닌 구조를 찾기 힘든 지경이다.

해시테이블에는 치명적 단점이 있다. 대다수가 인메모리에서만 동작하고 데이터가 희박(sparse)하게 저장돼, 꽤 보통 크기 데이터도 쉽게 다루기 어렵다. 특히 Python에선 중간 크기 데이터 작업에서 늘 이런 문제를 겪는다. 게다가, 사전에 엑세스 패턴을 알 수 없고 거의 항상 단일 기본키로만 액세스해야 한다.

테이블은 정반대다. 메모리 집약적이고, 디스크에 쉽게 저장-불러오기가 가능하다. b-tree 인덱스 덕분에 어떤 경로로든(어떤 컬럼 기준으로든) 효율적 접근이 가능하다. 즉, 중간에 dictionary를 전치(invert)할 필요가 없다. 대량 연산도 잘 지원하며, 지연(lazy)평가도 쉽다.

오픈소스에서 많이 쓰는 라이브러리는 pandas인데, pandas에는 명확한 한계가 있다:

  1. Minerva 구현 때는 존재하지 않았다
  2. 메모리 측면에서 기대만큼 효율적이지 않다
  3. 메모리보다 큰 데이터셋은 잘 지원하지 못한다
  4. (논쟁의 여지 있지만) 지나치게 복잡한 API를 가졌다

Minerva에는 pandas 대신 독자적인 프로퍼라이어터리 테이블 라이브러리 MnTable이 있다.

python
# 컬럼 3개로 테이블 생성 t1 = mntable.Table([('counterparty', str), ('instrument', str), ('quantity', float)]) # 테이블에 데이터 추가 (in place, 기본은 immutable) t1.extend( [ ['Cleon Partners', 'xlon:voda', 1200.0], ['Cleon Partners', 'xlon:spd', 1200.0], ['Blackpebble', 'xlon:voda', 1200.0], ], in_place=True) # 원본 불변, vodafone만 포함하는 새 테이블 리턴 (lazy 평가) t1.restrict(instrument='xlon:voda')

MnTable은 Bank Python 여기저기에서 항상 쓰인다. C++로 구현된 것도 있고, sqlite3을 래핑한 버전도 있다. 수많은 프로그램이 MnTable 생성 -> 일련의 연산 수행 -> 결과를 어딘가에 전송하는 패턴으로 짜여 있다.

은행 데이터는 "중간 크기"가 정말 많기에 이런 테이블 라이브러리가 특히 유용하다(대략 기가바이트 단위). HFT(초단타) 거래자들이야 티릭 수준 데이터를 쓰지만, 대부분의 금융인은 intra-day(일중)보다 굵은 데이터만 다룬다. "중간 크기"란, 각 row마다 객체를 만들기는 버겁고, 그렇다고 대규모 분산 시스템도 필요없는 크기다.

고통의 척도

모든 금융 소프트웨어가 즐겁거나 순수하지만은 않다. Minerva도 마찬가지다.

신입은 익숙해지는 데 매우 오랜 시간이 걸린다. 입사하자마자 강제되는 사내 전용 IDE를 보고 나처럼 때려칠 뻔 한 사람도 많다. 몇 달이 지나도 배워야 할 근본적 요소가 새롭게 나온다.

시간이 흐를수록 Bank Python과 오픈소스 Python의 격차는 커질 수밖에 없다. 기술 변화 속도도 바깥이 훨씬 더 빠르다. 외부에서 Minerva의 어떤 아이디어를 채택할 리도 없을뿐더러 Minerva 또한 외부의 어떤 걸 따오지도 않는다. 내부에서도 Minerva는 NIH(Not Invented Here) 신드롬의 전형이라고 손가락질하는 시선도 있다.

Minerva는 총체적(holistic)이고 포괄적이다. 내부에서는 편하지만, 외부에서 연동하거나 데이터를 빼오려면 늘 고통이다. 예전에 어떤 비 Minerva 개발자가 Barbara에서 데이터를 뽑으려면 어떻게 하냐고 물어봤다. 나는 Minerva 소스 코드를 활용하는 게 최선이라고 알려줬다. 그럼 파이썬 스크립트를 cron에 넣어서 뽑으면 되냐며 코드만 달라고 한다. 나는 아주 간단하게, "그럼 Barbara에서 코드를 읽어오면 된다"라고 답했다.

Minerva에 자체 IDE가 있는 건 어느 정도 이해된다. 소스가 거대한 글로벌 DB에 있으면 다른 IDE는 동작하지 않는다. 하지만 자체 웹 프레임워크까지 가진 건 이해가 안 간다. 투자은행들은 오픈소스 소프트웨어를 받기만 하고(내부에만), 거의 밖으로 돌려보내진 않는다. 규모비슷한 다른 업계와 비교하면 주요 투자은행의 github 프로필은 정말 빈약하다. Volcker Rule 이후, 대부분의 자체 트레이딩이 사라졌지만 이 독점적 태도는 변하지 않았다. 이건 저주에 가깝다.

가장 큰 단점은 아마도 직업적 관점일 것이다. Minerva 생태계에서만 몇 년 있으면, 일반 프로그래머로서 다른 소프트웨어와 상호작용하는 능력 자체가 점점 녹슨다. pip, virtualenv 등 평범한 Python에서 필수인 스킬을 거의 잊는다. 모두가 같은 repo 안, 코드는 언제나 import 가능하므로 소프트웨어 패키싱 자체가 필요 없어진다.

무엇이 다른가

일반적 Bank Python 구현의 전부를 다 다룬 것은 아니다. 예를 들어:

  • 자체 시계열 데이터 구조
  • 운영 반영을 위한 "vouch" 시스템
  • Dagger의 시점 이동(time travel)
  • Git이 아닌 반(半)자체 버전 관리 시스템
  • Prolog 기반 권한 시스템
  • 재생 지향의 금융 메시지 버스
  • 오랜 Windows 7과 MS Outlook 2010에서 오는 존재론적 허무(ennui)

이 정도는 상상에 맡기겠다.

하지만, 바버라, Dagger, Walpole, MnTable—이 네 가지가 가장 중요한 핵심이고, 이중 세 개는 데이터에, 나머지 하나는 잡(잡=데이터베이스로 볼 수도 있음)에 관한 것이다.

Minerva가 좀 특이한 이유 중 하나는 많은 부분이 "코드 우선"이 아니라 "데이터 우선"이라는 점이다. 보통 소프트웨어 공학은 반대다. 예를 들어, 객체지향 설계에서는 코드(행동)를 기반으로 클래스를 짜고, 데이터는 거기에 따라온다. MnTable 방식에서는 데이터를 테이블로 모으고, 코드는 따로 움직인다. 이 두 방식의 충돌이 바로 객체-관계 불일치(ORM impedance mismatch)를 낳는 원인 중 하나다. 어중간한 프로그래머보다는 오히려 테이블을 3정규형까지 끌어올릴 수 있는 사람이 훨씬 드물다는 점이 애증의 원인이다.

또 다른 특이점은, Minerva는 여러 개의 작은 것보다 하나의 거대한 무엇을 더 선호한다는 것이다. 큰 코드베이스, 큰 데이터베이스, 큰 잡 러너를 한데 묶는다. 이로써 많은 우연적 복잡성이 사라진다. 이미 언어 런타임(운영 환경), DB, 코드 실행 장소가 다 준비되어 있으니 사실상 바로 prod에 스크립트를 던질 수 있다.

Minerva는 금융권의 기술 경로 의존성에 크게 영향 받았다. 즉, MS 엑셀이 너무 널리 퍼져 있다. 어떤 새로운 솔루션도 늘 엑셀과 비교받고, 불리하면 다들 그냥 엑셀만 쓴다. 수도 없이 많은 기술자가 스프레드시트 환경을 보고 경악하면서, "마이크로서비스 + 쿠버네티스 + 서비스 메시" 삼종 세트를 밀어붙였다.

하지만 이런 거대한 엔터프라이즈 솔루션은 엑셀 유저들의 자율성을 뺏어가며, 자신이 하던 비즈니스 로직을 전혀 알 수 없게 만든다. 임시방편 댄서들과 협상해야 하는 현실만 남는다. 이런 상황에서 버전 관리된 Python 함수로 로직을 짜는 것이 엑셀의 유연성과 현대 소프트웨어의 중간지대를 제공할 수 있다. 금융인도 Python 정도는 익힐 수 있고, 기여 내역도 늘리고 실제 배포까지 겨눌 수 있다.

기존 시스템에서 아이디어를 "찔끔 훔치기"

소프트웨어 분야에서, 실제로 존재하는 시스템을 공부하고 좋은 점, 나쁜 점을 배워가는 시간이 너무 적다는 점이 늘 아쉬웠다. 실제 시스템을 상세히 다룬 책은 매우 드물다.

정보가 공개된 시스템도 제대로 연구되는 경우가 드물다. 이메일은 인터넷보다 훨씬 오래된 기술이고 지금도 80년대와 별 차이 없이 동작하지만, 많은 프로그래머는 "보내기" 버튼을 눌렀을 때 실제 어떤 일이 벌어지는지 정확히 모른다. 그래도 계속 이메일 혁신을 해보겠다는 시도는 끝이 없다.

이런 외국 시스템(혹은 외국 그 자체)을 직접 경험하면, 그 차이에서 큰 영감을 받을 수 있다. 직접 몸으로 겪지 않으면 헛소리처럼 들릴 수밖에 없는 것도 맞다.

실제로 한 번 다른 프로그래머에게 Minerva의 "vouch" 시스템을 간단히 설명해 준 적이 있다. 코드 소유자 중 한 명만 승인해주면 바로 운영에 반영된다(심지어 긴급하면 그냥 신뢰로 승인). "vouch" 버튼만 누르면 바로 운영 적용(배포 단계 자체가 없음). 듣던 그는 세상에 그런 은행을 누가 믿냐고 했는데, 정답은: 매우 큰 은행이며, 아마 당신도 이름을 들어봤을 것이다.


Contact/etc


Other notes

만약 MnTable 스타일의 테이블 라이브러리에 관심이 있다면, 내 친구 Sal이 순수 파이썬으로 api 호환 eztable(https://github.com/salimfadhley/eztable)을 만들었다.

나는 프로그래머들이 MS 엑셀을 너무 무시하는 경향이 있다고 생각한다. 엑셀로도 엄청난 것을 할 수 있고, 일부 프로그래머보다 더 많은 것을 해낸다. 1급 투자은행조차도 특별 xlsx 파일에서 셀 클릭으로 실제 거래를 체결할 정도다.

그게 너무 나간 거란 데 동의하지만, 엑셀을 잘 모른다면 배워두는 게 제일 값진 것 중 하나다. Joel Spolsky의 엑셀 개요 영상이 개발자에게 추천할만하고, 더 진지하게 배우고 싶다면 Coursera의 Excel Skills for Business Specialisation도 추천한다.

대부분의 화폐 소프트웨어는 정밀도 무한 숫자로 1원 한 푼까지 맞추지만, 금융 모델링에서는 float(double precision 부동소수점)을 쓴다. 고객은 일전 한 푼에 전화하지 않으니 그렇다.

Barbara의 오버레이 기능은 소스 코드에도 적용된다. Walpole에 내 ring을 sourcecode 앞에 마운트하도록 하면(vouch 없이 개발 ring에만 코드 푸시), 여러 기상천외한 해킹(급조 로직)을 쓸 수 있다. 다만, 적당히만 활용하길 권한다.