튜토리얼약 7분 읽기

파이썬으로 주식 자동매매 기초 개념 정리

자동매매 시스템, 뭘로 구성되는가

"파이썬으로 자동매매 만들기"라고 검색하면 특정 증권사 API 연동 코드부터 나오는 경우가 많다. 그런데 코드를 따라 치기 전에, 자동매매 시스템이 어떤 부품들로 이루어지는지 개념부터 잡아두는 게 훨씬 오래간다. 이 글은 특정 API 사용법이 아니라, 어떤 구성요소가 왜 필요한지를 정리한다.

일반적으로 자동매매 시스템은 아래 네 가지 축으로 나뉜다.

  1. 시세/데이터 수집: 현재가, 호가, 과거 데이터를 가져오는 부분
  2. 전략(신호 생성): 데이터를 보고 "사라/팔아라/대기"를 판단하는 로직
  3. 주문 실행: 전략의 판단을 실제 주문으로 바꿔 보내는 부분
  4. 리스크 관리: 손절, 포지션 한도, 이상 상황 감지 등 안전장치

이 네 개를 하나의 거대한 함수에 다 몰아넣지 않고 분리해두는 것이 유지보수와 디버깅 측면에서 훨씬 유리하다.

1. 시세/데이터 수집

가장 먼저 필요한 건 "지금 가격이 얼마인가"를 알아오는 부분이다. 실전에서는 증권사나 데이터 제공업체의 API를 쓰지만, 개념 설명을 위해 인터페이스만 추상화해보면 이렇다.

class MarketDataClient:
    def get_current_price(self, symbol: str) -> float:
        """현재가 조회 (실제 구현은 증권사 API 문서에 따라 다름)"""
        raise NotImplementedError

    def get_price_history(self, symbol: str, days: int) -> list[float]:
        """과거 종가 리스트 조회"""
        raise NotImplementedError

핵심은 이 부분을 인터페이스로 분리해두는 것이다. 나중에 증권사를 바꾸거나 모의투자용 가짜 데이터로 테스트하려면, 이 클래스만 구현체를 바꿔 끼우면 나머지 코드는 손댈 필요가 없다.

2. 전략(신호 생성)

전략은 "데이터를 넣으면 매수/매도/대기 신호가 나오는" 순수 함수로 짜는 게 이상적이다. 가장 단순한 예로 이동평균 기반 신호를 개념적으로 보면 이렇다.

def moving_average(prices: list[float], window: int) -> float:
    return sum(prices[-window:]) / window

def generate_signal(prices: list[float]) -> str:
    short_ma = moving_average(prices, window=5)
    long_ma = moving_average(prices, window=20)
    if short_ma > long_ma:
        return "BUY"
    elif short_ma < long_ma:
        return "SELL"
    return "HOLD"

여기서 중요한 설계 원칙은 전략 함수가 주문을 직접 실행하지 않는다는 점이다. 전략은 "신호"만 반환하고, 그 신호를 실제 주문으로 옮기는 건 다음 단계인 실행 레이어의 몫이다. 이렇게 분리해야 전략 로직만 따로 백테스트하기 쉬워진다.

3. 주문 실행

신호를 받아 실제로 주문을 넣는 레이어다. 이 부분이 왜 별도 레이어여야 하냐면, 주문 실행 전에 검증 로직을 끼워 넣을 수 있는 지점이 필요하기 때문이다.

def execute_signal(signal: str, symbol: str, order_client, risk_checker):
    if signal == "HOLD":
        return
    order = build_order(signal, symbol)
    if not risk_checker.is_allowed(order):
        log_rejected(order)
        return
    order_client.submit(order)

risk_checker.is_allowed(order) 부분이 바로 다음에 다룰 리스크 관리다. 신호가 나왔다고 무조건 주문을 내보내는 게 아니라, 반드시 이 검증을 거치게 만드는 구조가 핵심이다.

4. 리스크 관리 — 가장 자주 생략되고, 가장 비싸게 대가를 치르는 부분

튜토리얼이나 예제 코드에서 가장 흔히 생략되는 부분이 리스크 관리다. 하지만 실전에서는 이 부분이 없으면 전략 로직의 사소한 버그가 그대로 손실로 이어진다. 최소한 아래 항목은 개념적으로 넣어두는 걸 권한다.

이 항목들은 "당장 급한 건 아니고 나중에 붙이면 되지"라고 미루기 쉬운데, 실제로는 전략 로직보다 먼저 갖춰두는 게 안전하다. 전략은 틀려도 손실이 제한적이지만, 리스크 관리 부재는 손실의 상한 자체가 없다는 뜻이기 때문이다.

처음 시작할 때 권장하는 순서

  1. 시세 데이터 클라이언트를 인터페이스로 먼저 설계(실제 구현은 나중)
  2. 전략 함수를 과거 데이터로 백테스트할 수 있는 형태로 분리
  3. 리스크 관리 규칙을 먼저 문서화하고 코드로 구현
  4. 마지막으로 주문 실행 레이어를 붙이되, 처음엔 반드시 모의투자(페이퍼 트레이딩) 환경에서만 실행

이 순서를 거꾸로(주문 실행부터 짜고 리스크 관리를 나중에 붙이는 방식)로 진행하면, 리스크 관리가 "이미 돌아가는 시스템에 억지로 끼워 맞추는" 형태가 되어 빠지는 구멍이 생기기 쉽다.

오늘의 정리

  1. 자동매매 시스템은 데이터 수집·전략·주문 실행·리스크 관리 네 축으로 분리해서 설계하는 게 유지보수에 유리하다.
  2. 전략 함수는 신호만 반환하고, 주문은 별도 레이어에서 검증을 거쳐 실행하는 구조가 안전하다.
  3. 리스크 관리는 가장 생략되기 쉽지만, 손실 상한을 결정하는 가장 중요한 부분이다.

다음 글에서는 이 네 축 중 "시세/데이터 수집과 주문 실행"에 해당하는, 증권사 REST API 연동의 일반적인 흐름(인증부터 주문까지)을 개념 위주로 다뤄본다.


이 글의 코드는 개념 설명을 위한 예시이며, 특정 증권사 API나 실제 운영 중인 시스템의 코드가 아닙니다. 실전 적용 전 반드시 모의투자 환경에서 충분히 검증하시기 바랍니다. 투자 손실에 대한 책임은 투자자 본인에게 있습니다.

← 전체 글 목록