아카이브 / 개발
autotrade — 시장 흐름 기반 가치·모멘텀 적립식 자동매매 도구
목차 — 프로젝트 요약 · 담당 범위 · 문제와 구현 접근 · 이 작업에서 한 일 · 결과물과 원문 · 구현 상세
프로젝트 요약
시장 레짐(200일 이동평균)과 추세·12-1 모멘텀·가치 점수로 국내외 개별종목과 ETF를 골라 소액을 적립하고 리밸런싱하는 개인용 자동매매 도구. 가격을 예측하지 않고 시장 흐름을 따르는 규칙으로 움직인다.
담당 범위
기획·구현을 맡은 개인 프로젝트다. 공개 저장소에 코드와 사용 방법을 남겼다.
문제와 구현 접근
벤치마크 지수가 200일선 아래면 그 시장 몫을 설정한 방어자산으로 옮기고, 개별 종목은 자기 200일선 위에 있을 때만 매수 후보로 둔다. 12-1 모멘텀과 저PER·저PBR·고배당 z-score를 합친 복합점수 상위 N개에 동일가중으로 목표비중을 준다.
적립금은 비중이 낮은 종목부터 채우는 현금흐름 리밸런싱으로 넣고, 추세 이탈·모멘텀 소멸·순위 탈락·위험회피 레짐 중 하나라도 해당하면 목표비중을 0으로 두어 청산한다. 모의(paper) 브로커와 한국투자증권 KIS Open API 어댑터를 같은 인터페이스로 두었고, 기본 설정은 모의 브로커다.
무료 데이터는 과거 시점의 PER·PBR을 제공하지 않아, 룩어헤드 편향을 피하려고 백테스트에서는 가치점수를 빼고 레짐·추세·모멘텀만 쓴다. 예시 백테스트의 벤치마크 비교는 환율 반영 방식과 생존·사후선택 편향 때문에 공정하지 않다. 교육·연구 목적의 도구이며 수익을 보장하지 않는다는 투자 책임 고지를 첫머리에 두었다.
이 작업에서 한 일
아이디어를 실제로 작동하는 코드로 만들고, 다른 사람이 설치하고 사용할 수 있도록 설명서를 함께 작성했다.
결과물과 원문
관련 등록공보·논문·저장소는 아래 링크에서 볼 수 있다.
구현 상세
README공개 저장소의 구조·기능·실행 문서
autotrade
시장흐름 기반 가치/모멘텀 적립식(DCA) 자동매매 도구
국내·해외 개별종목과 ETF를 대상으로, 매일 시장 데이터를 수집해 시장 상황(레짐)을 판단하고 널리 통용되는 평가 기법으로 종목을 선별·분산투자한다. 종목 가격을 예측하지 않고 시장흐름(추세·모멘텀·레짐)을 따른다. 소액(월 10~30만원)을 매월/매주/매일 적립하며, 기대성이 사라진 종목은 매도하고 더 나은 종목으로 분산·적립한다.
⚠️ 투자 책임 고지 — 이 도구는 교육·연구 목적의 자동화 프레임워크입니다.
어떤 수익도 보장하지 않으며 손실 위험이 있습니다. 실거래 전 반드시
모의투자/백테스트로 충분히 검증하고, 본인 책임 하에 사용하세요.
전략 개요 (모두 통용되는 기법의 조합)
| 단계 | 기법 | 근거 |
|---|---|---|
| ① 시장 레짐 판단 | 벤치마크 지수의 200일 이동평균 타이밍 | Faber, Quantitative Approach to Tactical Asset Allocation |
| ② 추세 필터 | 개별 종목이 자기 200일선 위일 때만 매수 후보 | 추세추종 |
| ③ 모멘텀 | 12-1 모멘텀(최근 1개월 제외) — 절대/상대 모멘텀 | Antonacci, Dual Momentum |
| ④ 가치 | 저PER·저PBR·고배당 z-score (라이브 시점 펀더멘털) | 밸류 팩터 / 어닝일드 |
| ⑤ 선택·분산 | 복합점수 상위 N개 동일가중(종목당 상한 적용) | 분산투자 |
| ⑥ 적립·청산 | 현금흐름 리밸런싱(저비중부터 적립), 목표 0 종목 청산 | 소액 DCA 표준 |
- 위험회피: 지수가 200일선 아래면(약세장) 그 시장 몫을 설정의 방어자산(
markets.<시장>.defensive)으로
옮기고, 방어자산 가격이 없으면 현금으로 둔다. 예시 설정의 방어자산은 US=SHY(미국 단기국채 ETF), KR=360750(TIGER 미국S&P500)이다. KR 쪽 예시는 채권이 아니라 주식형 ETF라서 KOSPI 약세 때 국내 몫이 미국 주식 ETF로 옮겨 간다. 채권으로 피하려면 국내 단기채 ETF 코드로 바꿔 쓴다.
- 종목 매도(기대성 소멸): 추세 이탈(200일선 하향), 12-1 모멘텀 0 이하, 복합점수 상위 N 탈락, 위험회피 레짐
중 하나라도 해당하면 목표비중 0 → 보유분을 전량 청산하고 나머지 목표 종목으로 재배분한다. 이 청산 규칙은 항상 켜져 있다(설정의 sell_on_trend_break / sell_on_rank_dropout 은 현재 코드에서 읽지 않는다).
백테스트 예시 (2020-01 ~ 2024-12, 월 20만원 주별 적립)
개발 당시 예시 설정(월 20만원·주별 적립, 예시 유니버스)으로 한 번 실행해 기록한 값이다. 백테스터는 시작일 이후 382일(200일선·12개월 모멘텀 계산용, max(200, 12×31) + 10)을 워밍업으로 쓰고 그 뒤부터 적립한다. 그래서 2020-01 부터 적립하려면 1년 앞선 시작일을 준다: backtest --start 2019-01-01 --end 2024-12-31 → 2020-01-20 ~ 2024-12-30, 주 259회 적립 (259 × 200,000/4.333 원 = 아래 누적 적립원금). --start 2020-01-01 로 돌리면 적립은 2021-01 부터 시작한다. yfinance 데이터 갱신, 유니버스·설정 변경에 따라 다시 돌리면 수치가 달라질 수 있다.
| 항목 | 값 |
|---|---|
| 누적 적립원금 | 11,954,766 원 |
| 최종 평가금액 | 18,318,480 원 |
| 총수익 | 53.23 % |
| 연환산수익(IRR) | 17.56 % |
| 최대낙폭(MDD) | -12.79 % |
| 벤치마크 수익 (같은 금액을 KOSPI·S&P500 지수에 시장가중으로 단순 적립) | 18.53 % |
이 표의 전략 대 벤치마크 비교는 공정하지 않다. 전략은 해외 종목을 KRW로 환산해 평가하므로 환율
변동분이 들어가지만, 벤치마크는 지수 종가를 그대로 써서 S&P500 쪽에 환율이 반영되지 않는다. 또 예시
유니버스는 지금 시점에서 고른 대형 우량주라 생존편향·사후선택 편향이 있다. 두 수익률의 차이를 전략의
우위로 그대로 읽으면 안 된다.
MDD 는 적립금이 계속 더해지는 평가금액 곡선(리밸런싱 일자 기준)에서 계산하므로
일시금 투자 기준 MDD 보다 작게 나올 수 있다. 벤치마크는 수익률만 계산한다.
과거 성과가 미래를 보장하지 않습니다.
활용한 오픈소스
- yfinance — 가격/펀더멘털 (국내
.KS/.KQ, 미국 모두) - FinanceDataReader — 지수(KOSPI/S&P500) 등 보조 데이터
- python-kis — 한국투자증권 KIS Open API (실거래/모의투자 주문, 선택 설치)
- pandas / numpy / pydantic / PyYAML
구조
autotrade/
├── config.py # 설정/유니버스 로딩 (pydantic)
├── data/provider.py # 데이터 수집 (yfinance/FDR: 가격·지수·환율·펀더멘털)
├── strategy/ # 지표·레짐·가치/모멘텀 전략
│ ├── indicators.py # SMA, 12-1 모멘텀, z-score, 가치점수
│ ├── regime.py # 시장 레짐(200일선) 판단
│ └── value_momentum.py# 메인 전략 → 목표비중 산출
├── portfolio/ # 포트폴리오 상태 + 현금흐름 리밸런싱 주문 생성
├── broker/ # paper(모의) / kis(한국투자증권) 동일 인터페이스
├── backtest/engine.py # 적립식 백테스터(성과지표·벤치마크 비교)
├── engine.py # 실행 오케스트레이터(1회 적립+리밸런싱)
└── cli.py # CLI
config/ # *.example.yaml → 복사해서 사용
state/ # 모의 상태/실행로그 (gitignore)
tests/ # 네트워크 없는 단위 테스트
설치 & 사용
pip install -r requirements.txt
# 설정 준비 (예시 복사 후 수정)
cp config/config.example.yaml config/config.yaml
cp config/universe.example.yaml config/universe.yaml
# 1) 백테스트로 전략 검증 (시작일 이후 약 1년은 워밍업이라 적립 없음)
python -m autotrade.cli backtest --start 2019-01-01 --end 2024-12-31 --out state/eq.csv
# 2) 현재 시점 진단(레짐/목표비중) — 주문 없음
python -m autotrade.cli screen
# 3) 적립+리밸런싱 미리보기(실주문 X)
python -m autotrade.cli run --dry-run
# 4) 실제 실행 (broker.type 설정대로: paper 또는 kis)
python -m autotrade.cli run
# 5) 현재 포트폴리오 상태
python -m autotrade.cli status
정기 실행은 cron + scripts/run_daily.sh 사용 (예: 매주 월요일):
10 9 * * 1 bash /path/to/autotrade/scripts/run_daily.sh >> /path/to/autotrade/state/cron.log 2>&1
RUN_DRY=1 을 주면 주문 없이 run --dry-run 으로 계획만 출력한다. 모의(paper) 브로커 상태는 state/paper_state.json, 실행 기록은 state/run_log.jsonl 에 쌓인다.
테스트
python tests/test_core.py # 또는 (pytest 설치 시): python -m pytest -q
실거래(한국투자증권 KIS) 연동
pip install python-kis- KIS 개발자센터에서 앱키 발급, 모의투자 계좌 신청
- 인증값은 환경변수 권장:
export KIS_APP_KEY=... KIS_APP_SECRET=... KIS_ACCOUNT_NO=12345678-01
config.yaml:
broker:
type: kis
kis:
paper_trading: true # 먼저 모의투자로! 실거래는 false (주의)
broker/kis.py 는 python-kis 호출을 한 곳에 모은 얇은 어댑터다. 라이브러리 버전에 따라 메서드명이 다를 수 있으므로 python-kis 를 부르는 네 곳, _client()(클라이언트 생성), get_cash() / get_positions()(잔고 조회), submit()(시장가 주문)을 설치한 버전에 맞춰 확인한다. get_cash() / get_positions() 는 호출 오류를 삼키고 0 / 빈 dict 를 돌려주므로, 잔고가 비어 보이면 이 두 곳부터 확인한다.
- 적립금 입금은 직접 한다. 코드가 적립금을 넣어 주는 것은 모의(paper) 브로커뿐이다. KIS 브로커는
계좌 예수금(get_cash())을 기준으로 주문을 계획하므로 적립금은 계좌에 따로 이체해 둔다.
- 전용 계좌를 권장한다. 유니버스나 방어자산 목록에 있는 종목을 이미 들고 있으면, 그 종목의 목표비중이
0 일 때 시장가 전량 매도 주문이 나간다(목록 밖 종목은 가격이 없어 건드리지 않는다). 이 도구만 쓰는 계좌에서 돌리고, 먼저 run --dry-run 으로 주문 계획을 확인한다.
상태와 알려진 한계
- 개인 프로젝트. 기본 설정은
broker.type: paper(모의)이며, 네트워크 없이 도는
단위 테스트 7건(지표·모의 브로커·리밸런서·전략)이 있다.
- KIS 어댑터는 얇은 래퍼다. python-kis 공개 API 형태를 가정해 작성했고,
잔고 조회 시 보유 종목을 모두 국내(KR)로 기록하며, 체결가는 주문 시 기준가로 근사 기록한다. 실거래 전 모의투자 계좌로 호출부(_client() / get_cash() / get_positions() / submit())를 확인해야 한다.
- 레짐을 판단하지 못하면 레짐 필터가 꺼진다. 벤치마크 지수(FinanceDataReader) 조회가 실패하거나
200일치가 안 되면 레짐이 unknown 이 되고, 현재 전략은 이를 risk_on 과 똑같이 처리한다. screen / run 출력의 "시장 레짐" 값으로 확인할 수 있다.
- 백테스트는 가치점수를 쓰지 않는다. 무료 데이터는 과거 시점의 PER/PBR
스냅샷을 제공하지 않아 룩어헤드 편향을 피하려 백테스트에서는 레짐+추세+모멘텀만 사용한다(전략이 자동 폴백). 가치 스코어링은 라이브 실행 시 현재 펀더멘털로만 적용. 과거 펀더멘털 백테스트가 필요하면 유료 데이터 소스를 data/ 에 연결하면 된다.
- 소액·정수주: 국내 실거래는 정수주만 가능. 주당 가격이 높은 종목은 소액
적립분으로 1주를 못 사 현금이 쌓일 수 있다. ETF 중심 유니버스 또는 적립 주기를 늘리는 것을 권장. (모의/백테스트는 소수점 매수로 동작)
- 환율: 해외 종목은 USD/KRW(
KRW=X)로 KRW 환산해 단일 통화로 운용한다.
단, 백테스트의 벤치마크 비교는 지수 종가를 그대로 써서 S&P500 쪽은 환율 환산을 하지 않는다.
- 데이터 소스(yfinance) 가용성에 따라 일부 종목이 누락될 수 있다.