파이썬 틱 데이터 백테스팅이 느린 이유 pandas 병목 줄이는 방법
틱 데이터를 이용한 백테스팅이 느려졌다면 단순히 컴퓨터 사양이나 pandas만의 문제로 보기 어렵습니다. 데이터 읽기부터 전처리, 전략 계산, 주문 시뮬레이션까지 어느 구간에서 시간이 걸리는지 나눠서 봐야 합니다. 아래 글에 제가 성능을 점검할 때 보는 기준을 순서대로 정리해두었습니다.
파이썬으로 일봉이나 분봉 데이터를 백테스트할 때는 별문제가 없었는데, 틱 데이터를 넣는 순간 상황이 완전히 달라지는 경우가 있습니다.
같은 전략인데 실행 시간이 몇 배씩 길어지고, 메모리 사용량은 계속 올라가고, 조금만 데이터를 늘리면 작업이 끝날 기미가 보이지 않습니다.
이때 가장 먼저 나오는 말이 있습니다.
“pandas가 느려서 그런가?”
절반은 맞을 수 있지만, 절반은 아닐 수 있습니다.
틱 데이터 백테스팅에서는 라이브러리 하나보다 데이터가 저장되는 방식, 파일을 읽는 방식, 반복문 구조, 자료형, 불필요한 복사, 전략 계산 방식이 동시에 영향을 줍니다.
따라서 pandas를 Polars로 바꾸기 전에 먼저 해야 할 일은 명확합니다.
내 백테스트에서 시간이 어디에 사용되고 있는지 찾는 것.
이 글에서는 틱 데이터 백테스팅이 느려지는 대표적인 이유와 pandas를 계속 사용할 때 개선할 수 있는 방법, 그리고 Polars·Parquet·DuckDB·Numba를 어느 지점에서 검토하면 좋은지 순서대로 정리해보겠습니다.
참고로 여기서 말하는 ‘지연시간’은 거래소 주문 전송과 체결 과정의 초저지연 latency가 아니라, 과거 틱 데이터를 읽어 백테스트 결과를 만들어내는 데 필요한 처리 시간을 의미합니다.

틱 데이터가 들어오면 왜 갑자기 백테스팅이 느려질까?
일봉 데이터와 틱 데이터의 차이는 단순히 행의 개수가 조금 늘어나는 수준이 아닙니다.
일봉에서는 한 종목이 1년에 수백 개 행이면 충분하지만 틱 데이터에서는 체결이나 호가 변화 하나하나가 레코드가 됩니다.
여기에 여러 종목과 장기간 데이터가 합쳐지면 읽어야 하는 데이터, 비교해야 하는 timestamp, 수행해야 하는 조건 판단 자체가 크게 늘어납니다.
그런데 많은 코드가 처음에는 이런 구조로 만들어집니다.
- CSV 파일 전체를 읽는다.
- 모든 컬럼을 DataFrame에 올린다.
- 시간순으로 정렬한다.
- 각 행을 반복하면서 조건을 확인한다.
- 조건이 맞으면 포지션과 손익을 다시 계산한다.
데이터가 작을 때는 크게 문제되지 않습니다.
하지만 데이터가 커지면 파일 읽기, 메모리 할당, 데이터 복사, Python 레벨 반복문이 한꺼번에 병목으로 나타날 수 있습니다.
그래서 “pandas가 느리다”라고 결론 내리기 전에 최소한 다음 시간을 따로 측정해봐야 합니다.
- 데이터 파일을 읽는 시간
- timestamp 변환과 정렬 시간
- 지표 생성 시간
- 매매 조건 계산 시간
- 주문·포지션 이벤트 처리 시간
- 결과 저장 시간
백테스트 전체가 10분 걸린다고 해서 전략 계산이 10분 걸리는 것은 아닙니다.
실제로는 데이터를 읽고 변환하는 데 상당한 시간이 사용되고 있을 수도 있고, 반대로 데이터 로딩은 빠른데 iterrows(), apply() 또는 Python 반복문이 병목일 수도 있습니다.
따라서 최적화의 첫 번째 원칙은 라이브러리 교체가 아니라 측정입니다.
pandas가 문제라기보다 사용 방식이 문제인 경우가 많다
pandas를 사용한다는 이유만으로 백테스트가 느린 것은 아닙니다.
오히려 먼저 확인해야 하는 것은 pandas를 어떤 방식으로 사용하고 있느냐입니다.
대표적인 예가 행 단위 반복입니다.
틱 하나가 들어올 때마다 DataFrame 한 행을 꺼내 Python 코드에서 조건을 검사하는 구조는 데이터가 커질수록 반복 호출 비용이 쌓일 수 있습니다.
가능한 계산이라면 배열 연산과 벡터화 방식으로 먼저 바꾸는 것이 좋습니다.
pandas 공식 성능 가이드 역시 계산 집약적인 코드를 최적화할 때 Python 반복문을 줄이고 NumPy 벡터화를 먼저 검토한 뒤 Cython이나 Numba 같은 방법을 고려하는 흐름을 설명하고 있습니다.
두 번째는 자료형입니다.
가격, 수량, 매수·매도 구분, 종목 코드, timestamp를 아무 생각 없이 기본 자료형으로 읽으면 필요 이상으로 메모리를 사용할 수 있습니다.
문자열로 들어온 시간을 매번 변환하거나, 모든 숫자를 동일하게 큰 자료형으로 유지하거나, 반복적으로 DataFrame을 복사하는 구조도 확인할 필요가 있습니다.
세 번째는 apply()를 만능 도구처럼 사용하는 경우입니다.
복잡한 사용자 정의 함수를 행마다 실행한다면 데이터가 많아졌을 때 비용이 커질 수 있습니다.
연산이 순수 수치 계산 중심이라면 NumPy 배열로 변환한 후 Numba 같은 JIT 컴파일 방식을 검토할 수 있습니다.
pandas 공식 문서에서도 Numba는 첫 실행 시 컴파일 비용이 발생하기 때문에 작은 데이터에서는 이점이 크지 않을 수 있지만, 큰 데이터의 수치 연산에서는 성능 개선 가능성이 있다고 설명합니다. 공식 문서의 예시 역시 최초 실행과 이후 실행의 시간이 크게 달라질 수 있음을 보여줍니다. 다만 이 수치는 예제 환경의 결과이므로 자신의 백테스트 성능으로 그대로 받아들이면 안 됩니다.
여기서 스스로 질문해볼 필요가 있습니다.
“나는 정말 pandas의 한계에 도달한 것일까, 아니면 pandas를 Python 반복문처럼 사용하고 있는 것일까?”
이 질문 하나만으로 최적화 방향이 상당히 달라집니다.
CSV를 계속 읽고 있다면 연산보다 I/O부터 확인해야 한다
대규모 틱 데이터를 다룰 때 놓치기 쉬운 부분이 파일 형식입니다.
원본 틱 데이터를 매번 CSV에서 처음부터 읽고 있다면 전략 계산을 최적화하기 전에 저장 구조부터 살펴볼 가치가 있습니다.
Parquet은 분석 시스템에서 널리 사용되는 컬럼 기반 저장 형식입니다. Apache Arrow 문서에서도 Parquet을 컬럼형 저장 포맷으로 설명하며, 필요한 컬럼만 선택해서 읽을 수 있기 때문에 전체 컬럼을 읽는 것보다 효율적일 수 있다고 설명합니다.
예를 들어 백테스트에서 필요한 데이터가
timestamp
price
volume
side
뿐이라면 굳이 원본 파일의 다른 컬럼까지 매번 모두 메모리에 올릴 이유가 없습니다.
여기에서 DuckDB도 좋은 선택지가 됩니다.
DuckDB는 Parquet 파일을 직접 조회할 수 있으며 필요한 컬럼만 읽는 projection pushdown과 조건에 맞는 데이터만 앞단에서 걸러내는 filter pushdown을 지원합니다.
즉,
“1년 치 틱 데이터를 전부 pandas에 올리고 나서 특정 날짜만 자른다”
보다
“필요한 날짜와 컬럼만 먼저 읽어온다”
는 방향을 생각할 수 있습니다.
특히 전략 연구를 반복하는 사람이라면 이 차이가 중요합니다.
백테스트는 한 번 돌리고 끝나는 작업이 아니기 때문입니다.
조건 하나를 변경하고 다시 돌리고, 기간을 바꾸고 다시 돌리고, 종목을 바꾸고 또 돌립니다.
한 번의 처리시간 차이보다 반복 연구 과정 전체에서 얼마나 불필요한 작업을 줄이느냐가 더 중요할 수 있습니다.
그렇다면 Polars와 Numba는 언제 사용하는 것이 좋을까?
Polars가 알려지면서 “pandas 대신 Polars를 쓰면 해결되는 것 아닌가?”라고 생각하는 경우가 많습니다.
하지만 무조건 교체하는 것보다는 병목의 위치를 기준으로 선택하는 편이 합리적입니다.
Polars의 강점 중 하나는 Lazy API입니다.
Polars 공식 문서에 따르면 Lazy API에서는 실제 연산을 실행하기 전에 전체 쿼리를 구성하고, query optimizer가 predicate pushdown과 projection pushdown 등을 적용할 수 있습니다.
쉽게 말하면 필요한 행을 가능한 한 빨리 거르고, 실제 사용하는 컬럼만 읽어 불필요한 CPU와 메모리 사용을 줄이는 방식입니다.
따라서 대량 파일을 읽고 필터링하고 집계하는 데이터 전처리가 주된 병목이라면 Polars를 검토할 이유가 있습니다.
반면 전략 자체가 순차적인 이벤트 처리로 구성되어 있다면 상황이 달라집니다.
예를 들어
틱 수신
현재 포지션 확인
주문 발생 여부 판단
체결 상태 변경
손절·익절 확인
다음 틱 처리
처럼 앞선 상태가 다음 계산에 영향을 주는 이벤트 기반 전략은 모든 연산을 단순 벡터화하기 어려울 수 있습니다.
이런 부분은 NumPy 배열과 Numba를 이용해 핵심 루프를 최적화하는 방식이 더 적합할 수도 있습니다.
정리하면 다음과 같이 볼 수 있습니다.
- 데이터가 작고 코드 가독성이 중요하다
→ pandas를 유지하면서 벡터화와 자료형부터 점검 - 대규모 파일의 필터·집계·전처리가 느리다
→ Parquet + Polars 또는 DuckDB 검토 - 순수 Python 반복문의 수치 계산이 병목이다
→ NumPy + Numba 검토 - 매번 CSV 전체를 읽는 시간이 크다
→ Parquet 변환과 partition 구조 검토 - 이벤트 기반 체결 시뮬레이션 자체가 느리다
→ DataFrame 바깥에서 핵심 이벤트 루프를 분리하고 프로파일링
결국 중요한 것은 “어떤 라이브러리가 가장 빠른가?”가 아닙니다.
내 작업에서 어느 단계가 가장 느린가?
이 질문에 먼저 답해야 합니다.
빠른 백테스트보다 먼저 만들어야 할 것은 측정 가능한 백테스트다
틱 데이터 백테스팅을 최적화할 때 흔히 하는 실수는 처음부터 전체 코드를 갈아엎는 것입니다.
pandas를 Polars로 변경하고, CSV를 Parquet으로 바꾸고, Numba까지 적용했는데 결과적으로 어디에서 얼마나 좋아졌는지 알 수 없다면 이후 유지보수는 더 어려워집니다.
그래서 저는 다음 순서를 권합니다.
- 현재 구조의 실행 시간을 기록합니다.
- 데이터 로딩과 전략 연산 시간을 분리합니다.
- 가장 큰 병목 하나를 찾습니다.
- 한 번에 하나만 변경합니다.
- 동일 데이터와 동일 전략으로 다시 측정합니다.
- 결과값이 기존 백테스트와 동일한지도 함께 검증합니다.
특히 마지막 항목이 중요합니다.
백테스트가 빨라졌는데 체결 순서가 달라지거나 timestamp 정렬 방식이 바뀌거나 floating point 처리 차이로 결과가 달라진다면 단순한 성능 향상이라고 보기 어렵습니다.
속도와 정확성을 함께 검증해야 합니다.
틱 데이터에서는 몇 밀리초, 몇 마이크로초 단위의 시간 순서가 전략 로직에 영향을 줄 수도 있기 때문에 timestamp 정밀도와 동일 timestamp 데이터의 정렬 규칙도 명확하게 정해두는 편이 좋습니다.
결국 좋은 백테스팅 시스템은 단순히 빠른 시스템이 아닙니다.
왜 이 결과가 나왔는지 추적할 수 있고, 같은 조건에서 다시 실행했을 때 동일한 결과를 검증할 수 있으며, 데이터가 커져도 어느 부분이 느린지 확인할 수 있는 시스템이어야 합니다.
현재 틱 데이터 백테스트가 느리다면 바로 라이브러리를 교체하기보다 먼저 세 가지만 확인해보세요.
- 데이터를 읽는 데 시간이 얼마나 걸리는가?
- Python 반복문에서 시간이 얼마나 걸리는가?
- 실제 전략 계산 외에 불필요하게 반복되는 작업은 무엇인가?
이 세 가지를 분리하는 것만으로도 “pandas를 없애야 하나?”라는 막연한 고민이 훨씬 구체적인 기술 문제로 바뀝니다.
이미 백테스팅 코드를 만들어 사용하고 있지만 데이터가 커질수록 실행 시간이 감당하기 어려워졌거나, pandas·Polars·DuckDB·Numba 중 무엇을 어디에 적용해야 할지 애매하다면 전체 코드를 바꾸기 전에 병목 구간부터 점검해보는 것이 좋습니다.
라이브러리 선택보다 먼저 현재 데이터 구조와 실행 흐름을 확인하면 불필요한 재개발을 줄일 수 있습니다.
파이썬 틱 데이터 백테스팅이 느린 이유 pandas 병목 줄이는 방법
자주 묻는 질문
Q1. 틱 데이터 백테스팅이라면 pandas를 사용하면 안 되나요?
그렇지 않습니다. 데이터 규모와 연산 방식에 따라 pandas로 충분한 경우도 있습니다. 먼저 행 단위 반복, 불필요한 복사, 자료형, 파일 로딩 구조를 확인한 뒤 다른 도구의 필요성을 판단하는 편이 좋습니다.
Q2. pandas 대신 Polars로 바꾸면 무조건 빨라지나요?
작업의 종류에 따라 다릅니다. 대규모 스캔, 필터, 집계에서는 Polars의 Lazy API와 query optimization이 도움이 될 수 있지만, 순차적인 이벤트 시뮬레이션에서는 다른 부분이 병목일 수 있습니다. 자신의 데이터로 동일 조건의 벤치마크를 해보는 것이 가장 정확합니다.
Q3. 가장 먼저 바꿔야 할 것은 무엇인가요?
라이브러리보다 측정 방식입니다. 데이터 로딩, 전처리, 전략 계산, 주문 시뮬레이션, 결과 저장 시간을 각각 측정해 가장 큰 병목부터 해결하는 것이 좋습니다.
자료 출처
- pandas 공식 문서, Enhancing performance: 벡터화, Cython, Numba를 이용한 성능 개선 가이드
- Polars 공식 문서, Lazy API 및 Query Optimizations: predicate pushdown, projection pushdown 등
- DuckDB 공식 문서, Reading and Writing Parquet Files: Parquet 조회 시 projection/filter pushdown 설명
- Apache Arrow 공식 문서, Apache Parquet Format: 컬럼 기반 저장 구조와 필요한 컬럼 선택 읽기
※ 라이브러리별 성능은 데이터 규모, CPU·메모리, 저장장치, 코드 구조, 파일 구조 및 버전에 따라 달라질 수 있습니다. 특정 라이브러리가 모든 백테스트 환경에서 더 빠르다는 의미는 아닙니다. 실제 적용 전 동일한 데이터와 로직으로 직접 벤치마크하는 것이 좋습니다.
금알남
댓글 0
첫 댓글을 남겨보세요.