Polymarket 거래 봇은 충돌 없이 실행되더라도 심각한 실수를 할 수 있습니다. 중복 주문을 제출하거나, 잘못된 포지션 크기를 계산하거나, 오래된 가격(stale prices)으로 거래하거나, 실제로 체결되지 않은 주문이 체결되었다고 가정할 수 있습니다.
단순히 스크립트가 성공적으로 시작하는지만 테스트하는 것은 충분하지 않습니다.
신뢰할 수 있는 거래 봇은 의사 결정 로직(decision logic), 위험 통제(risk controls), 실행 동작(execution behaviour), 그리고 실패로부터의 복구에 대한 테스트를 필요로 합니다. 이 모든 구성 요소들은 실제 주문을 넣거나 자금을 위험에 빠뜨리지 않고도 테스트할 수 있습니다.
이 튜토리얼은 간단한 거래 결정, 위험 확인, 시뮬레이션된 실행 계층(simulated execution layer), 그리고 자동화된 테스트를 사용하여 기본적인 Python 테스트 워크플로우를 구성하는 방법을 보여줍니다.
- 거래 로직을 주문 실행과 분리하기
가장 유용한 설계 결정 중 하나는 무엇을 할지 결정하는 구성 요소와 거래 API와 통신하는 구성 요소를 분리하는 것입니다.
간단한 아키텍처는 다음과 같습니다:
- 시장 데이터(Market data): 가격, 오더북 정보, 시장 식별자를 제공합니다.
- 전략(Strategy): 거래 신호가 전략의 조건을 충족하는지 평가합니다.
- 위험 관리자(Risk manager): 포지션 한도, 사용 가능한 자본, 허용된 가격을 확인합니다.
- 실행 계층(Execution layer): 주문을 제출하고 추적하며 조정합니다.
- 테스트 스위트(Test suite): 각 구성 요소의 동작을 독립적으로 검증합니다.
이러한 분리는 테스트를 더 쉽게 만듭니다. 왜냐하면 지갑을 연결하거나 주문을 보내지 않고도 전략을 평가할 수 있기 때문입니다.
또한, 테스트가 예상대로 작동하지 않을 때 어떤 구성 요소가 실패했는지 식별하는 데 도움이 됩니다.
- 작고 테스트 가능한 위험 통제 함수 작성하기
제안된 주문이 허용되는지 여부를 결정하는 함수부터 시작합니다.
이 예시의 경우, 봇은 가격, 크기, 총 비용이 미리 정의된 한도를 충족할 때만 주문을 수락합니다. 이것들은 완전한 거래 전략이 아닌 예시적인 규칙입니다.
risk.py
from decimal import Decimal
가격 제한은 이 간소화된 예시에서 사용되는 일반적인 확률-토큰 범위를 반영합니다. 실제 운영 환경 구현에서는 또한 실제 시장이 지원하는 가격 증분, 주문 크기 제약 조건, 적용 가능한 수수료 및 현재 API 요구 사항을 검증해야 합니다.
주의할 점은 이 함수가 실제로 주문을 제출하지 않는다는 것입니다. 단지 제안된 주문이 이러한 특정 검사를 충족하는지 여부만을 평가합니다.
따라서 테스트하기에 결정론적이고 간단합니다.
- pytest를 사용하여 자동화된 단위 테스트 추가
테스트 라이브러리 설치:
pip install pytest
"test_risk.py"라는 파일을 생성합니다:
from decimal import Decimal
from risk import validate_order
def test_accepts_order_within_limits():
assert validate_order(
price=Decimal("0.40"),
shares=Decimal("10"),
available_cash=Decimal("20"),
max_order_cost=Decimal("10"),
)
def test_rejects_order_exceeding_available_cash():
assert not validate_order(
price=Decimal("0.50"),
shares=Decimal("50"),
available_cash=Decimal("20"),
max_order_cost=Decimal("100"),
)
def test_rejects_order_exceeding_order_limit():
assert not validate_order(
price=Decimal("0.50"),
shares=Decimal("30"),
available_cash=Decimal("100"),
max_order_cost=Decimal("10"),
)
def test_rejects_zero_shares():
assert not validate_order(
price=Decimal("0.40"),
shares=Decimal("0"),
available_cash=Decimal("100"),
max_order_cost=Decimal("10"),
)
def test_rejects_invalid_price():
assert not validate_order(
price=Decimal("1.00"),
shares=Decimal("10"),
available_cash=Decimal("100"),
max_order_cost=Decimal("10"),
)
테스트 실행:
python -m pytest -v
이러한 테스트는 정상적인 동작과 여러 유효하지 않은 입력값들을 확인합니다. 이것들이 봇 전체가 안전하다는 것을 증명하는 것은 아니지만, 특정 위험 규칙들이 의도대로 작동하는지 확립해 줍니다.
새로운 규칙이 도입될 때마다 테스트를 추가하세요. 예를 들어 주문 크기 계산 방식을 변경했다면, 예상되는 할당액과 주문이 거부되어야 하는 조건들을 모두 테스트해야 합니다.
- 실제 주문을 전송하는 대신 실행(execution) 시뮬레이션하기
단위 테스트(Unit tests)만으로는 실행 요청이 시간 초과되거나(times out), 주문이 부분적으로 체결되거나(partially filled), 시장 가격이 변동할 때 봇이 어떻게 동작하는지 알 수 없습니다.
이러한 시나리오들을 위해서는 시뮬레이션된 실행 계층(simulated execution layer)을 사용해야 합니다.
최소 인터페이스는 다음과 같을 수 있습니다:
class SimulatedBroker:
def init(self):
self.orders = []
def submit(self, token_id, side, price, shares):
order = {
"token_id": token_id,
...
전략(strategy)은 실제 거래소 클라이언트 대신 이 인터페이스를 테스트에서 호출할 수 있습니다.
이 시뮬레이터는 주문 요청을 기록하지만, 라이브 거래에 서명하거나, 제출하거나, 정산하지 않습니다. 이를 통해 전략이 무엇을 의도했는지 검사할 수 있게 해줍니다.
그러나 시뮬레이션된 브로커(simulated broker) 자체가 현실적인 시장 시뮬레이터는 아닙니다. 주문장 깊이(order-book depth), 큐 위치(queue position), 지연 시간(latency), 수수료(fees), 부분 체결, 또는 불리한 가격 변동을 모델링하지 않습니다.
만약 시뮬레이션이 이러한 것들을 나타내기를 원한다면, 이 동작들을 명시적으로 추가해야 합니다.
- 성공적인 거래뿐 아니라 실패 케이스 테스트하기
많은 중요한 버그들은 예상치 못한 일이 발생할 때 나타납니다.
유용한 테스트 스위트(test suite)에는 다음 시나리오들이 포함되어야 합니다.
- 잔고 부족 (Insufficient balance): 전략이 기회를 감지했지만, 사용 가능한 자본을 초과하는 주문은 제출하지 않는 경우.
- 오래된 시장 데이터 (Stale market data): 봇이 설정된 신선도 한계(freshness limit)보다 오래된 가격 스냅샷을 사용하는 것을 거부하는 경우.
- 중복 신호 (Duplicate signal): 동일한 소스 이벤트(source event)를 두 번 처리한다고 해서 독립적인 주문 두 개가 생성되지 않는 경우.
- 부분 체결 (Partial fill): 시스템이 체결된 수량과 남은 수량을 별도로 기록하는 경우.
거부된 주문(Rejected order): 봇은 거부 사실을 기록하고 해당 포지션을 성공적으로 개설된 것으로 표시하지 않습니다.
네트워크 시간 초과(Network timeout): 봇은 단순히 중복 제출하는 대신, 주문 상태를 조정할 수 있을 때까지 그 결과를 알 수 없는 상태로 처리합니다.
재시작 복구(Restart recovery): 봇은 영속적인 상태(persistent state)를 다시 로드하고 실행을 재개하기 전에 열려 있는 주문과 포지션을 조정합니다.
이러한 테스트는 반환 값뿐만 아니라 상태 전이(state transitions)도 검증해야 합니다. 예를 들어, 시간 초과가 발생한 후에도 주문은 해결되지 않은 상태(unresolved state)로 진입하여 조정을 유발해야 하며, 응답이 누락되었다고 해서 자동으로 실패한 주문이 되어서는 안 됩니다.
- 과거 데이터에 대한 테스트를 신중하게 수행하기
과거 데이터는 특정 전략의 규칙이 과거 시장 상황에서 어떤 시그널을 생성했을지 평가하는 데 도움이 될 수 있습니다.
하지만 과거 시뮬레이션은 그 가정만큼만 현실적입니다.
모든 주문이 과거 데이터셋에 기록된 가격으로 체결된다고 가정한 테스트는 스프레드(spread), 오더북 깊이(order-book depth), 실행 지연(execution delays), 그리고 원하는 수량이 이용 가능하지 않았을 수 있다는 사실을 무시할 수 있습니다.
더 유용한 평가를 위해서는 다음 사항들을 문서화해야 합니다:
- 시뮬레이션이 어떤 가격 또는 오더북 데이터를 사용하는지.
- 주문이 어떻게 체결된다고 가정하는지.
- 수수료 및 실행 비용은 어떻게 처리되는지.
- 데이터가 누락되었을 때 무슨 일이 발생하는지.
- 전략이 의사 결정 시점에 이용 가능했을 정보만을 사용하는지 여부.
과거 거래 결정을 내리는 데 미래 정보를 사용하지 않도록 주의하세요. 이는 전방 주시 편향(look-ahead bias)을 만들고 비효율적인 전략이 성공적으로 보이게 할 수 있습니다.
과거 전략 평가는 소프트웨어 정확성 테스트와 분리하여 유지해야 합니다. 전략은 모든 단위 테스트(unit test)를 통과하더라도 여전히 수익성 있는 거래 우위(profitable trading edge)가 없을 수 있습니다.
- 단계별 테스트 프로세스 사용하기
봇을 실시간 실행에 연결하기 전에, 뚜렷한 단계를 거쳐 진행해야 합니다.
단계 1: 단위 테스트 (Unit testing)
제어된 입력(controlled inputs)을 사용하여 계산, 유효성 검사 규칙, 시그널 처리 및 상태 전이(state transitions)를 테스트합니다.
단계 2: 통합 테스트 (Integration testing)
데이터, 전략, 위험 관리(risk), 지속성(persistence), 실행(execution) 구성 요소가 어떻게 함께 작동하는지 테스트합니다. 적절한 경우 외부 서비스를 목업(mock)합니다.
3단계: 역사적 시뮬레이션 (Historical simulation)
체결(fills), 수수료(fees), 데이터 가용성 등에 대한 명시적인 가정을 사용하여 전략을 과거 데이터에 대해 평가합니다.
4단계: 드라이-런 모니터링 (Dry-run monitoring)
관련 시장 데이터 피드에 연결하고 실제 주문을 제출하지 않고 봇이 무엇을 할지 기록합니다. 의도된 결정과 현재 시장 상황을 비교합니다.
5단계: 통제된 실시간 테스트 (Controlled live testing)
선행 단계들이 검토된 후에만 실시간 실행을 고려해야 합니다. 제한된 자금의 전용 지갑, 보수적인 노출 한도(exposure limits), 보호된 자격 증명(credentials), 그리고 테스트된 비상 정지 기능(emergency stop)을 사용합니다.
한 단계를 통과했다고 해서 다음 단계에서 자동으로 성공을 보장하는 것은 아닙니다. 특히, 드라이-런 실행은 실제 주문 체결이나 결제(settlement)를 완전히 재현할 수 없습니다.
1. '통과'의 의미 정의하기
테스트 수트(test suite)는 승인 기준(acceptance criteria)이 명시적일 때 더 유용합니다.
예를 들어, 팀은 다음과 같은 사항을 요구할 수 있습니다:
- 유효하지 않은 주문은 제출 전에 거부되어야 한다.
- 반복되는 소스 이벤트는 중복 제거(deduplicated)되어야 한다.
- 알려지지 않은 주문 결과는 조정(reconciliation)을 유발해야 한다.
- 오래된 시장 데이터는 새로운 거래 결정을 차단해야 한다.
- 부분 체결은 정확하게 표현되어야 한다.
- 재시작 복구(Restart recovery)가 이전 주문을 맹목적으로 중복해서는 안 된다.
- 비밀 정보(Secrets)가 로그에 절대 나타나지 않아야 한다.
- 위험 한도(Risk limits)는 오류 및 재연결 중에도 계속 적용되어야 한다.
이것들은 테스트 가능한 엔지니어링 요구사항입니다. 봇이 다운되지 않고 한 시간 동안 실행되었다고 단순히 선언하는 것보다 더 의미가 있습니다.
적절한 기준은 봇의 아키텍처와 의도된 사용 사례에 따라 달라질 것입니다.
결론
Polymarket 거래 봇을 테스트하는 것은 단지 그 전략이 신호(signals)를 생성하는지 확인하는 것 이상의 의미입니다. 입력이 유효하지 않거나, 데이터가 오래되거나, 주문이 실패하거나, 애플리케이션이 재시작될 때 전체 시스템이 올바르게 작동하는지 검증하는 것입니다.
먼저 결정론적 단위 테스트(deterministic unit tests)로 시작하고, 외부 실행을 테스트 가능한 인터페이스 뒤에 격리하며, 실패 조건을 시뮬레이션하고, 현실적인 가정을 바탕으로 과거 성과를 평가합니다. 그런 다음 라이브 실행을 고려하기 전에 드라이 런 모니터링(dry-run monitoring)으로 진행합니다.
Dexoryn Labs는 개발자들이 기존 구현을 평가할 때 검토할 수 있는 공개 'Polymarket 복사 거래 봇 저장소'(https://github.com/dexorynlabs/polymarket-copy-trading-bot)를 유지하고 있습니다. 테스트, 모니터링, 실행 또는 위험 제어 워크플로우가 다른 팀은 맞춤 개발이 필요할 수 있습니다.
목표는 봇이 절대 실패하지 않는다는 것을 증명하는 것이 아닙니다. 비용이 많이 들기 전에 예측 가능한 실패를 식별하고, 예상치 못한 실패를 더 쉽게 감지하고 복구할 수 있도록 하는 것입니다.
본 문서는 교육 목적으로만 제공되며 금융 자문이 아닙니다. 자동화 거래에는 위험이 따르며, 소프트웨어 테스트가 수익성을 보장하거나 모든 운영상의 실패를 방지할 수는 없습니다.