만들면서 배운 것

백테스트 방법, 제가 코인이랑 미국주식 돌릴 때 순서가 서로 다른 이유를 공개합니다 🔀

생각에 잠긴 초록이 아빠

“백테스트 방법”을 검색하면 보통 전략 하나를 기준으로 설명이 끝납니다. 데이터 받고, 신호 계산하고, 결과 보고.

저는 자산군이 둘(코인·미국주식)이라 코드를 따로 짜야 했는데, 먼저 짜둔 코인 쪽 코드를 그대로 복붙해서 미국주식에 돌리려다 한참 막힌 적이 있습니다. 매일 추세만 확인하면 끝나는 구조라고 믿었거든요. 안 돌아갔습니다. 지난 빌드로그에서 백테스트가 진입·청산·비용을 어떻게 계산하는지 이야기했는데, 오늘은 그 코드를 자산군 두 개에 각각 짜면서 발견한, 구조 자체가 달라야 했던 지점을 공개합니다.

코인 하네스는 매일 같은 질문 하나만 묻는다

코인(BTC·ETH) 쪽 코드는 하루 단위 루프 안에 질문이 하나뿐입니다. “오늘 종가가 이동평균선 위에 있나?” 위면 들고 있고, 아래면 현금입니다.

INIT, COST = 1_000_000, 0.001
def sim(cl, sm, a, b, trend=True):
    eq = INIT; out = []; prev = 0.0
    for i in range(a, b):
        p = (1.0 if (sm[i] and cl[i] > sm[i]) else 0.0) if trend else 1.0
        eq -= eq*abs(p-prev)*COST
        if i < b-1: eq *= 1 + p*(cl[i+1]/cl[i]-1)
        out.append(eq); prev = p
    return out

이 루프엔 “어떤 자산을 들고 있을지 고르는” 단계가 없습니다. 이미 BTC·ETH로 정해져 있으니까요. 매일 하는 일은 그 하나를 들고 있을지 현금으로 도망갈지, 둘 중 하나를 다시 정하는 것뿐입니다. 포지션이 바뀔 때마다(abs(p-prev)) 비용 0.1%(COST=0.001)가 바로 깎입니다. 표에 올라가는 비교도 “그냥 들고 있기”와 이동평균 기간을 50·100·150·200일로 바꿔본 네 가지 추세값뿐, 선택 단계가 없으니 더 늘어날 게 없습니다.

미국주식 하네스는 매일 하는 일과 며칠마다 하는 일이 나뉘어 있다

미국주식 쪽은 후보가 대형주 약 50종목입니다. “어떤 자산”부터 매번 다시 골라야 하는데, 이 단계 자체가 코인 코드엔 없었던 겁니다.

FEE, SLIP = 0.0005, 0.0010   # 수수료+슬리피지
COST = FEE + SLIP
TOP_K, REBAL = 10, 5

def sim_select(use_bo):
    cash=INIT; pos={}; out=[]
    for i in range(start,N):
        for s in list(pos):           # 추세이탈 청산
            if not up(s,i):
                c=C(s,i)
                if c: cash+=pos.pop(s)["q"]*c*(1-COST)
        if (i-start)%REBAL==0:        # 선택·리밸런싱은 5일마다만
            cand=[]
            for s in UNIV:
                if not up(s,i): continue
                if use_bo and not breakout(s,i): continue
                m=mom(s,i)
                if m is None: continue
                cand.append((s,m))
            cand.sort(key=lambda x:x[1],reverse=True)
            tgt=[s for s,_ in cand[:TOP_K]]

매일 하는 일은 추세이탈 청산 체크 하나뿐입니다. 추세를 벗어난 자산이 있으면 그날 바로 정리합니다. 그런데 “새로 뭘 담을지” 고르는 일은 5일(REBAL=5)마다만 합니다. 추세 조건을 통과한 후보 중 최근 변화율(모멘텀) 순위로 상위 10개(TOP_K=10)만 추려 동일 비중으로 채웁니다. 표에 올라가는 비교도 코인 쪽보다 하나 더 늘어서, 그냥 지수 보유·동일비중 보유·모멘텀 선택·돌파선택 네 가지를 나란히 봅니다. “선택”이라는 변수가 하나 더 생겼으니 비교할 방식도 그만큼 늘어난 겁니다.

왜 순서 자체가 다른가 — 고를 게 2개냐 50개냐

코인 코드는 고를 필요가 없습니다. BTC·ETH 둘 중 뭘 들고 있을지 매일 다시 물으면 충분합니다. 미국주식은 후보가 50개라 “뭘 담을지” 자체가 매일 바뀔 수 있는 질문인데, 이걸 매일 다시 풀면 리밸런싱 비용이 매번 쌓여 결과가 비용에 먹힙니다. 그래서 “청산 체크는 매일, 선택은 N일마다”로 쪼갠 겁니다.

비용 모델도 그래서 다릅니다. 코인 쪽은 COST 하나(0.1%)로 묶었습니다. 자산이 둘뿐이고 매일 포지션을 켜고 끄는 정도라 턴오버 비용 하나로 충분합니다. 미국주식 쪽은 수수료(FEE=0.05%)와 슬리피지(SLIP=0.10%)를 따로 갈라 더해서 0.15%로 씁니다. 종목 수가 많아지면 한 번 리밸런싱할 때 동시에 체결되는 주문 수도 늘어서, 둘을 뭉개면 어느 쪽이 더 아픈지 안 보이기 때문입니다.


자산이 몇 개냐가 아니라 “뭘 고를지”를 매번 다시 물어야 하느냐가 코드의 실행 순서를 가른다.

따라 하기

두 하네스를 각각 돌려보면 이 차이가 바로 보입니다.

  1. 코인 하네스를 돌립니다
cd <프로젝트 폴더>
python3 binance_btc.py

BTC·ETH 각각 9년치를 받아와 추세 추종 성과를 계산합니다. 선택 단계가 없어서 코드도 100줄 남짓으로 끝납니다.

  1. 미국주식 하네스를 돌립니다
python3 us_stocks.py

대형주 약 50종목 데이터를 받아오는 데 시간이 더 걸립니다. 후보가 많아서 당연한 일인데, 처음엔 이것도 “왜 이렇게 오래 걸리지”로 한참 들여다봤습니다.

  1. 두 리포트를 나란히 엽니다
open binance_btc_report.html
open us_stocks_report.html

코인 쪽 표는 “들고 있을까 말까”의 변주(이동평균 기간 4가지)만 비교합니다. 미국주식 쪽 표는 거기에 “뭘 담을지” 선택 방식(동일비중·모멘텀 선택·돌파선택) 비교가 하나 더 붙습니다. 표 구성 자체가 “고를 게 있었는지”를 보여줍니다.

코드를 복붙했다가 안 돌아가서 다시 들여다본 그 밤이, 사실은 이 둘이 왜 다른 구조여야 하는지를 제대로 이해한 순간이었습니다.

오늘도 한 걸음. 🐴

이 글은 산업 구조와 만드는 과정을 정리한 개인 기록입니다.
특정 종목의 매수·매도 추천이 아니며, 투자 판단과 그 결과는 독자 본인에게 있습니다.

← 글 목록으로