파이썬으로 토스증권 API 자동매매 봇 만들기 — 포트폴리오 대시보드부터 3중 안전장치까지 완전 구현

📌 이 글에서 배울 수 있는 것

  • 토스증권 Open API 인증부터 실시간 시세 조회까지 전 과정
  • 중복 코드 없는 공통 모듈(toss_common.py) 설계 원칙
  • 한글·달러 혼합 포트폴리오를 터미널에 정확히 정렬 출력하는 방법
  • 실제 돈을 지키는 3중 안전장치 설계 패턴
  • API 키를 안전하게 관리하는 .env 보안 처리

1. 왜 토스증권 API인가?

국내 증권사 중 개인 개발자가 접근하기 가장 쉬운 Open API를 제공하는 곳이 토스증권입니다. OAuth 2.0 Client Credentials 방식으로 토큰을 발급받고, REST API로 실시간 시세·잔고·주문까지 모두 처리할 수 있습니다.

특히 해외주식 소수점 투자까지 API로 지원하기 때문에, 원화와 달러 혼합 포트폴리오를 하나의 코드로 관리할 수 있다는 점이 큰 장점입니다.

항목내용
API 방식REST API + OAuth 2.0
인증 방식Client Credentials (서버 to 서버)
지원 기능토큰 발급 / 계좌 조회 / 잔고 조회 / 실시간 시세 / 주문
해외주식소수점 투자 포함 API 지원
개발자 콘솔별도 신청 필요 (토스증권 앱 → 설정 → Open API)

2. 사전 준비물

2-1. 필수 조건

  • ✅ 토스증권 계좌 개설 완료
  • ✅ 토스증권 앱 → 개발자 콘솔에서 Open API 신청 및 CLIENT_ID / CLIENT_SECRET 발급
  • ✅ Python 3.10 이상 설치

2-2. 패키지 설치

pip install requests python-dotenv

또는 requirements.txt를 프로젝트 루트에 만들고 한 번에 설치합니다.

# requirements.txt
requests>=2.31.0
python-dotenv>=1.0.0
pip install -r requirements.txt

3. 완성된 프로젝트 구조

📁 toss_auto_trader/
├── toss_common.py       ← 공통 모듈 (인증·시세·계좌 함수 집결)
├── toss_portfolio.py    ← 실시간 포트폴리오 대시보드
├── auto_trader.py       ← 자동매매 감시 엔진 (3중 안전장치)
├── get_price.py         ← 다종목 실시간 시세 조회
├── toss_test.py         ← API 연결 진단 도구
├── .env                 ← API 자격증명 (Git 추적 제외 필수)
├── .env.example         ← .env 템플릿
├── .gitignore           ← .env 등 민감 파일 추적 방지
└── requirements.txt     ← 의존 패키지 목록

처음에는 단일 파일로 시작하기 쉽지만, 파일이 늘어나면 get_access_token() 같은 함수가 파일마다 복붙되는 상황이 발생합니다. 이를 방지하기 위해 공통 모듈을 중심에 두는 구조가 핵심입니다.


4. 🔒 API 키 보안 처리 — 절대 코드에 하드코딩하지 마세요

가장 먼저 해결해야 할 것이 보안입니다. API 키를 코드에 직접 입력하면 GitHub 등에 실수로 노출되는 순간 계좌가 위험에 처합니다.

❌ 절대 하면 안 되는 방식

# 위험! 키값이 코드에 노출됨
CLIENT_ID = "tsck_live_여기에직접입력"
CLIENT_SECRET = "tssk_live_여기에직접입력"

✅ 올바른 방식: .env 파일 분리

프로젝트 루트에 .env 파일을 만들고 키를 넣습니다.

# .env (절대 Git에 커밋하지 말 것!)
TOSSINVEST_CLIENT_ID=tsck_live_여기에_발급받은_키_입력
TOSSINVEST_CLIENT_SECRET=tssk_live_여기에_발급받은_키_입력

그리고 .gitignore에 반드시 추가합니다.

# .gitignore
.env

코드에서는 python-dotenv로 읽어옵니다.

import os
from dotenv import load_dotenv

load_dotenv()

CLIENT_ID     = os.environ.get("TOSSINVEST_CLIENT_ID")
CLIENT_SECRET = os.environ.get("TOSSINVEST_CLIENT_SECRET")

# 키가 없으면 즉시 실행 차단
if not CLIENT_ID or not CLIENT_SECRET:
    raise EnvironmentError("🚨 .env 파일의 API 키를 확인하세요.")

5. 공통 모듈 toss_common.py — 중복의 뿌리를 뽑아라

토스 API를 다루다 보면 모든 파일에서 반드시 필요한 두 가지 작업이 있습니다. 바로 Access Token 발급계좌 일련번호 조회입니다. 이 둘을 공통 모듈에 한 번만 작성하고, 나머지 파일은 import만 하면 됩니다.

인증 전체 흐름을 한 줄로

import toss_common

# 토큰 발급 → 계좌번호 조회를 한 번에
token, account_seq = toss_common.authenticate()
if not token or not account_seq:
    exit(1)

내부적으로는 아래 두 단계가 자동으로 진행됩니다.

def get_access_token() -> str | None:
    """OAuth 2.0 토큰 발급"""
    url = "https://openapi.tossinvest.com/oauth2/token"
    payload = {
        "grant_type"   : "client_credentials",
        "client_id"    : CLIENT_ID,
        "client_secret": CLIENT_SECRET,
    }
    response = requests.post(url, data=payload,
                             headers={"Content-Type": "application/x-www-form-urlencoded"},
                             timeout=5)
    if response.status_code == 200:
        return response.json().get("access_token")
    return None

공통 모듈에는 이 밖에도 get_current_price()(단일 종목 시세)와 get_current_prices()(다종목 일괄 시세)가 포함되어 있어, 각 파일은 비즈니스 로직에만 집중할 수 있습니다.


6. 실시간 포트폴리오 대시보드 구축

python toss_portfolio.py를 실행하면 다음과 같은 대시보드가 출력됩니다.

========================================================================
                     📊 토스증권 실시간 포트폴리오
========================================================================
💰 총 매수금액 :       1,360,070 원
📈 총 평가금액 :       1,369,296 원
✨ 총 평가손익 :          +9,226 원  (+0.67%)
📅 전일 대비   :         +32,391 원  (+1.99%)
========================================================================
종목명 (코드)            | 수량   | 평균단가     | 현재가       | 수익률
------------------------------------------------------------------------
삼성전자 (005930)        | 10     | ₩   75,000.0 | ₩   76,200.0 | +1.60%
SK하이닉스 (000660)      | 5      | ₩  185,000.0 | ₩  188,500.0 | +1.89%
[가상 예시 종목 C] (XX…  | 8      | ₩   25,000.0 | ₩   24,500.0 | -2.00%
[해외주식 예시] (ABCD)   | 0.42   |  $    150.00 |  $    155.00 | +3.33%
========================================================================

한글·이모지 정렬 문제 해결법

Python의 기본 ljust()바이트 수 기준으로 정렬해서, 한글이 포함되면 열이 어긋납니다. unicodedata.east_asian_width()를 이용해 문자별 실제 터미널 표시 너비를 정확히 계산하는 함수를 만들었습니다.

import unicodedata

def get_display_width(text: str) -> int:
    """
    'W'(Wide) / 'F'(Fullwidth) → 2칸 : 한글, 한자 등 전각 문자
    그 외 ('Na','N','H','A')   → 1칸 : ASCII, ₩, … 등 반각 문자
    """
    return sum(2 if unicodedata.east_asian_width(c) in ('W', 'F') else 1
               for c in text)

def fit_to_width(text: str, width: int) -> str:
    """길면 '…'으로 잘라내고, 짧으면 공백 패딩 — 항상 width 칸 고정"""
    dw = get_display_width(text)
    if dw <= width:
        return text + " " * (width - dw)
    target = width - get_display_width("…")
    result, cur_w = "", 0
    for ch in text:
        ch_w = 2 if unicodedata.east_asian_width(ch) in ('W', 'F') else 1
        if cur_w + ch_w > target:
            break
        result += ch
        cur_w += ch_w
    result += "…"
    return result + " " * (width - get_display_width(result))

💡 함정 주의: (U+20A9)는 ord > 127이지만 유니코드 규격상 H(Halfwidth, 반각) = 1칸입니다. ord(c) > 127 → 2칸 단순 공식을 쓰면 가격 열이 1칸씩 어긋나는 버그가 발생합니다. 반드시 unicodedata를 사용하세요.


7. 🤖 자동매매 엔진 — 3중 안전장치 설계

자동매매에서 가장 중요한 것은 실수로 실제 주문이 나가는 것을 막는 것입니다. 아무리 코드가 완성되어도, 충분한 테스트 없이 실전 모드를 켜는 것은 매우 위험합니다.

이 프로젝트에서는 3단계 안전장치를 기본 탑재합니다.

🔒 안전 1단계 — DRY_RUN 플래그

DRY_RUN = True  # ✅ True = 시뮬레이션 모드 (실제 주문 절대 불가)

True인 상태에서는 조건이 충족되어도 실제 토스 서버에 주문 요청을 보내지 않고, 로그와 텔레그램 알림만 발송합니다.

🔒 안전 2단계 — 1원 목표가

TARGET_BUY_PRICE = 1  # ✅ 시장에서 절대 체결될 수 없는 안전 목표가

삼성전자 1원 매수 지정가 주문은 실제 시장에서 절대 체결되지 않습니다. DRY_RUN을 끄더라도 이 가격 덕분에 이중으로 보호됩니다.

🔒 안전 3단계 — 주문 API 물리적 주석 처리

def execute_order(token, account_seq, symbol, ...):
    # ✅ 시뮬레이션 모드: 주문 없이 알림만 발송
    if DRY_RUN:
        send_notification(f"[시뮬레이션] {symbol} 매수 조건 충족!")
        return True

    # 🔒 실제 주문 API 호출부 — 물리적 주석 처리 (해제 금지)
    # url = "https://openapi.tossinvest.com/api/v1/orders"
    # response = requests.post(url, json=payload, headers=headers)
    # return response.status_code in [200, 201]

실제 HTTP 요청을 보내는 requests.post() 라인이 주석 처리되어 있어, DRY_RUN을 실수로 False로 바꾸더라도 주문이 나가지 않는 물리적 차단 장치입니다.

감시 루프의 네트워크 장애 대응

# 연속 오류가 MAX_CONSECUTIVE_ERRORS(10회)를 초과하면 봇 강제 종료
MAX_CONSECUTIVE_ERRORS = 10

while True:
    current_price = toss_common.get_current_price(token, account_seq, TARGET_SYMBOL)

    if isinstance(current_price, int):
        # 정상 시세 처리
        ...
    elif current_price == "TOKEN_EXPIRED":
        # 토큰 만료 → 토큰 + 계좌번호 동시 재발급
        token, account_seq = toss_common.authenticate()
    else:
        # 네트워크 오류 → 카운터 증가 후 재시도
        consecutive_errors += 1
        if consecutive_errors >= MAX_CONSECUTIVE_ERRORS:
            send_notification("🚨 연속 오류 한계 초과! 봇 종료.")
            break

8. 📱 텔레그램 알림 연동

자동매매 봇이 24시간 돌아가는 동안, 중요한 상태 변화를 스마트폰으로 바로 받을 수 있도록 텔레그램 알림을 연동합니다. .env에 아래 두 값을 추가하면 자동 활성화됩니다.

# .env
TELEGRAM_TOKEN=your_bot_token_from_BotFather
TELEGRAM_CHAT_ID=your_chat_id

봇 토큰은 텔레그램에서 @BotFather에게 /newbot 명령으로, 채팅 ID는 @userinfobot으로 간단히 발급받을 수 있습니다.

알림이 발송되는 시점은 다음과 같습니다.

  • 🤖 봇 최초 가동 시
  • ⚠️ 네트워크 오류 감지 시
  • 🔄 토큰 만료 재발급 시
  • ✅ 매수 조건 충족 시 (시뮬레이션 or 실전)
  • 🚨 봇 강제 종료 시

9. 📋 로그 파일 자동 저장

모든 print()logging 모듈로 교체하여, 콘솔 출력과 동시에 toss_trader.log 파일에 타임스탬프와 함께 자동 저장됩니다.

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S",
    handlers=[
        logging.FileHandler("toss_trader.log", encoding="utf-8"),
        logging.StreamHandler()   # 콘솔 동시 출력
    ]
)

로그 파일이 쌓이면 과거의 매매 이력과 오류 내역을 언제든지 검색하고 분석할 수 있습니다.


10. 실행 순서 — 처음 시작하는 분들을 위한 가이드

# 1. 패키지 설치
pip install -r requirements.txt

# 2. .env 파일 생성 후 API 키 입력
cp .env.example .env
# 메모장 등으로 .env 열어 키 값 입력

# 3. 연결 진단 (API 정상 작동 확인)
python toss_test.py

# 4. 포트폴리오 대시보드 확인
python toss_portfolio.py

# 5. 자동매매 봇 시뮬레이션 가동 (DRY_RUN=True 상태)
python auto_trader.py

⚠️ 주의: auto_trader.py는 반드시 DRY_RUN = True, TARGET_BUY_PRICE = 1 상태에서 충분히 테스트한 후, 신중하게 실전 전환 여부를 결정하세요. 자동매매는 예상치 못한 손실이 발생할 수 있으며, 이 글의 코드는 교육 목적으로 제공됩니다.


11. 마무리 및 다음 단계

이번 글에서는 토스증권 Open API를 연동하여 포트폴리오 대시보드3중 안전장치가 탑재된 자동매매 엔진을 파이썬으로 구축하는 전 과정을 다뤘습니다.

다음 편에서는 아래 내용을 다룰 예정입니다.

  • 📈 이동평균(MA) / RSI 기반 매수 조건 고도화
  • 📊 과거 데이터 백테스팅으로 전략 검증
  • 🕐 장 시작·종료 시간 자동 감지 로직
  • ☁️ 클라우드(GCP / AWS)에 봇 24시간 배포

전체 소스코드는 블로그 댓글 또는 문의를 통해 공유 가능합니다. 궁금한 점은 댓글로 남겨주세요! 🙌


※ 이 글의 코드는 교육 목적으로 작성되었습니다. 실제 자동매매에는 충분한 검증과 리스크 관리가 필요하며, 투자 손실에 대한 책임은 본인에게 있습니다.

댓글 남기기