
봇을 돌리기 전에 먼저 해야 할 일이 있습니다. 이 규칙대로 과거에 사고팔았다면 어땠을지 확인하는 백테스트입니다. 첫 전략으로는 많이들 쓰는 모멘텀(추세추종)을 골랐고, 솔직히 당연히 잘 나올 줄 알았습니다.
아래 숫자는 모두 과거 데이터로 계산한 결과입니다. 같은 수익이 앞으로도 난다는 뜻이 아닙니다.
한눈에 보기
- 과거 주가는 네이버 차트 데이터로 무료로 받았습니다. 한 번에 일봉 3,000개(약 12년)까지 됩니다.
- 백테스트는 내 PC 대신 GitHub Actions에서 돌렸습니다. 300종목 10년치가 몇 분이면 끝납니다.
- 결과는 연 +14.9%로, KODEX 200을 그냥 들고 있는 것(+15.5%)보다 못했습니다. 중간에 -63.9%까지 빠지기도 했습니다.
데이터는 네이버에서
과거 주가는 네이버 차트 데이터로 받았습니다. 한 번에 일봉 3,000개, 대략 12년치까지 받을 수 있고 무료입니다. 받은 건 CSV로 저장해 두고 다시 받지 않습니다.
백테스트는 GitHub Actions에서 돌렸습니다. 코드를 올리면 GitHub 서버가 데이터를 받아 백테스트를 돌리고, 결과 리포트를 별도 브랜치(backtest-results)에 올려 줍니다. 약 300종목 10년치가 몇 분이면 끝나고, 결과가 저장소에 쌓이니 나중에 비교하기도 편합니다.
전략: 오르는 종목을 따라 산다
규칙은 이렇습니다.
- 최근 6개월(마지막 1개월은 빼고) 가장 많이 오른 종목 10개를 삽니다.
- 종목이 100일선 아래면 사지 않습니다.
- KODEX 200이 200일선 아래, 즉 하락장이면 새로 사지 않습니다.
- 매주 순위를 다시 보고, 20위 밖으로 밀리면 팝니다.
- -10%면 손절합니다.
비용도 현실적으로 넣었습니다. 수수료 0.015%, 팔 때 증권거래세 0.20%, 원하는 값에 바로 체결되지 않아 생기는 손해(슬리피지) 0.1%입니다.
결과: 지수를 못 이겼습니다
| 2016.1 ~ 2026.10 | 모멘텀 전략 | KODEX 200 보유 |
|---|---|---|
| 연평균 수익률 | +14.9% | +15.5% |
| 최대 낙폭 | -63.9% | -41.2% |
| 샤프 지수 | 0.59 | 0.69 |
| 매매 횟수 | 748 | 0 |
| 승률 | 34% | - |
수익은 비슷한데, 중간에 반 토막 넘게 빠지는 구간이 있었습니다. 748번이나 사고판 결과가 KODEX 200 하나 사 두고 잊어버린 것보다 못했다는 얘기입니다. 보유 종목 수, 모멘텀 기간, 리밸런싱 주기를 바꿔 12가지 조합을 돌려 봐도 지수를 확실히 이기는 건 없었습니다.
연도별로 보면 이 전략의 성격이 확실히 보입니다.
| 연도 | 전략 | KODEX 200 |
|---|---|---|
| 2017 | +54.7% | +27.1% |
| 2018 | +1.8% | -17.3% |
| 2022 | 0.0% | -24.2% |
| 2023 | +62.0% | +24.9% |
| 2025 | +56.5% | +94.2% |
| 2026 | -5.6% | +44.0% |
하락장(2018, 2022)은 잘 피했는데, 지수가 크게 오른 해(2025, 2026)에는 오히려 한참 뒤처졌습니다.
여기서 배운 것
- "모멘텀은 당연히 되겠지"는 돌려 보기 전의 착각이었습니다.
- 한 전략의 숫자를 계속 만지면 과거 데이터에만 딱 맞는 결과가 나오기 쉽습니다. 그래서 설정을 더 만지는 대신 성격이 다른 전략들을 같은 조건으로 붙여 보기로 했습니다.
코드로 보면
네이버 차트 데이터는 주소 하나로 받습니다. 응답이 XML이라 item 태그만 정규식으로 뽑았습니다.
CHART_URL = "https://fchart.stock.naver.com/sise.nhn"
_ITEM = re.compile(r'<item data="([^"]+)"')
def fetch_daily_bars(symbol, count=3000):
res = requests.get(
CHART_URL,
params={"symbol": symbol, "timeframe": "day", "count": count, "requestType": 0},
headers={"User-Agent": "Mozilla/5.0"},
timeout=20,
)
bars = []
for raw in _ITEM.findall(res.content.decode("euc-kr", errors="replace")):
day, o, h, low, c, v = raw.split("|")
if int(v) == 0: # 거래정지일은 0 으로 와서 뺀다
continue
bars.append(Bar(day, float(o), float(h), float(low), float(c), int(v)))
return bars
모멘텀 점수는 "1개월 전 가격 / 6개월 전 가격 - 1"입니다. 100일선 아래거나 거래대금이 부족한 종목은 점수를 매기기 전에 거릅니다.
def ranking(self, panel, i, universe):
"""i 일 종가까지의 정보로 매수 후보를 점수 순으로 돌려준다."""
p = self.p # lookback=126(6개월), skip=21(1개월), stock_ma=100
scored = []
for s in universe:
c = panel.close[s]
now, past, base = c[i], c[i - p.skip], c[i - p.lookback]
ma = sma(c, i, p.stock_ma)
if ma is None or now <= ma: # 100일선 아래면 제외
continue
mom = past / base - 1 # 최근 1개월을 뺀 6개월 수익률
if mom > 0:
scored.append((mom, s))
scored.sort(reverse=True)
return [s for _, s in scored]
백테스트 엔진에서 제일 신경 쓴 건 신호는 오늘 종가로 계산하고, 주문은 다음 날 시가에 체결하는 규칙입니다. 오늘 종가를 보고 오늘 종가에 산 것처럼 계산하면, 실제로는 할 수 없는 매매가 섞여서 결과가 좋아 보입니다.
다음 편에서는 8가지 전략을 비교합니다. 그중 하나는 연 +32%가 나왔는데, 기뻐할 일이 아니었습니다.
주식 자동매매 봇 만들기
'개발 > 주식 자동매매' 카테고리의 다른 글
| 주식 자동매매 봇 만들기 6편: 텔레그램 알림 봇 만들기와 한국투자증권 모의투자 연결 (0) | 2026.10.15 |
|---|---|
| 주식 자동매매 봇 만들기 5편: 공시와 뉴스로 악재 종목 거르기, 네이버 뉴스 검색 API 발급 (0) | 2026.10.15 |
| 주식 자동매매 봇 만들기 4편: 서버 없이 GitHub Actions로 매일 아침 9시 10분에 도는 봇 (0) | 2026.10.15 |
| 주식 자동매매 봇 만들기 3편: 8가지 전략 비교, 그리고 연 +32%가 가짜였던 이유 (0) | 2026.10.15 |
| 주식 자동매매 봇 만들기 1편: 국내주식·한국투자증권·Python으로 정한 이유 (0) | 2026.10.15 |