두 가지가 동시에 바뀌었습니다
7월 말, 봇에 두 가지 변화가 같이 왔습니다.
첫째, 파일을 쪼갰습니다. 1,500줄짜리 단일 파일을 역할별로 나눴어요. 상수와 상태, 체결과 청산 엔진, 종목 스캔과 게이트, 원장 대조, 화면 출력. 이렇게 나누고 나니 “어디를 고치면 어디가 부서지나"라는 질문에 답할 수 있게 됐습니다.
둘째, git을 쓰기 시작했습니다. 그전까지는 파일을 복사해서 버전을 매겼어요. bot_v1.py, bot_v2.py 이런 식으로요. git으로 옮기면서 변경 이력이 남기 시작했고, 이게 생각보다 큰 변화였습니다.
vv_state 상수와 전역 상태 — 파라미터는 전부 여기 모여 있다
vv_core 체결·청산 엔진
vv_scan2 종목 스캔과 진입 게이트
vv_ledger 거래소 주문내역 대조
vv_log 터미널 출력과 상태 저장
변경 대부분은 vv_state 의 상수 한두 줄 —
그 한두 줄마다 시뮬레이션 결과를 커밋 메시지에 같이 넣는다이 경계를 어떻게 정했냐면, 고민을 별로 안 했습니다. 이미 알고 있었거든요. 모놀리스 시절에 서로 다른 이유로 바뀌던 덩어리들이 그대로 모듈이 됐습니다. 파라미터는 파라미터끼리 바뀌고, 청산 로직은 청산 로직끼리 바뀌고, 화면은 화면끼리 바뀌었어요. 그 패턴이 곧 설계도였습니다.
✘ 코드 줄 수로 나누기
균등하게 나뉘지만 의미가 없다
✘ 개념적으로 예뻐 보이게 나누기
모듈끼리 서로를 계속 호출하게 된다
✔ 같이 바뀌는 것끼리 묶기
한 가지 이유로 고칠 때 한 파일만 열면 된다
--------------------------------------------------
이 기준은 코드를 노려본다고 안 보인다
몇 달 고쳐본 이력에서 나온다git 도입도 단순히 도구를 바꾼 게 아니었습니다. 변경에 이유를 붙일 자리가 생긴 것이 핵심이에요. 파일 복사 방식에는 그 자리가 없습니다. 파일명에 “손절을 -2.5%로 바꿨고 근거는 실측 58건 시뮬레이션"이라고 쓸 수는 없잖아요.
커밋에 숫자를 넣기 시작한 뒤
이 시기 개발이 결정적으로 달라진 건 구조 때문이 아니라 습관 때문입니다. 파라미터를 바꿀 때마다 왜 그 값인지를 커밋 메시지에 적기 시작했습니다.
예를 들면 이런 식입니다. 손절선을 정할 때, 그냥 -2.5%로 정한 게 아니라 실제 매매 58건을 손절선별로 다시 돌려봤어요.
손절 없음 R:R 1.30 기대값 -0.072% (건당 -250원)
손절 -2.5% R:R 1.92 기대값 +0.209% (건당 +727원) ← 채택
손절 -3.0% R:R 1.73 기대값 +0.146% (건당 +508원)
같이 확인한 것: 익절에 상한을 씌우면 기대값이 다시 음수가 된다
손절-2.5% / 익절 3% → -0.032%
손절-2.5% / 익절 6% → +0.209%
결론: 손실은 자르고 이익은 풀어준다 — 격언이 아니라 내 계좌의 숫자로이 표에서 제일 중요한 건 첫 줄입니다. 손절이 없으면 기대값이 음수라는 것. 손절을 넣으면 승률은 떨어집니다. 버티면 돌아왔을 자리에서 잘리니까요. 그런데 기대값은 올라갑니다. 승률과 기대값이 반대로 움직이는 구간이 있다는 걸 숫자로 본 게 컸어요.
익절 상한 실험도 같은 얘기입니다. 익절 폭을 좁히면 승률이 확 올라갑니다. 작은 이익을 자주 확정하니까요. 그런데 기대값은 음수가 됩니다. 큰 이익 몇 건이 전체를 먹여 살리고 있었던 거예요. 승률만 보면 정확히 반대로 최적화하게 됩니다.
익절을 좁힌다 → 승률 ↑ · 기대값 ↓
손절을 없앤다 → 승률 ↑ · 기대값 ↓
--------------------------------------------------
승률만 보고 튜닝하면 두 가지를 다 하게 된다
→ 계좌는 조용히 준다
현행 평가 지표: 건당 기대값 + 손익비(R:R)이렇게 적어두면 두 가지가 좋습니다. 하나, 나중에 그 값이 틀렸을 때 왜 틀렸는지 추적할 수 있습니다. 둘, 과거의 나와 논쟁할 수 있습니다. 석 달 전의 내가 “이 값이 좋다"고 했는데 지금 보니 아니면, 그때 근거가 뭐였는지 확인하고 무엇이 달라졌는지 알 수 있어요. 근거 없이 정한 값은 틀려도 배울 게 없습니다.
시뮬레이션이라고 부르는 것의 정체
여기서 말하는 시뮬레이션은 거창한 백테스트 엔진이 아닙니다. 실제로 체결된 내 매매 기록을 다른 파라미터로 다시 계산해보는 거예요.
장점
· 실제 체결가 · 실제 수수료 · 실제 슬리피지 반영
· "그때 정말 살 수 있었나" 를 가정할 필요가 없다
· 구현이 단순하다 (기록 + 계산)
한계
· 진입 조건을 바꾸는 실험은 못 한다
(안 산 종목의 결과는 기록에 없으니까)
· 표본이 내 매매 이력만큼만 쌓인다
· 장세 편향이 있다 (그 기간의 장세)
————————————————–
그래서 청산 파라미터 검증에는 강하고
진입 게이트 검증에는 스캔 로그를 따로 본다
가상 백테스트의 고질적인 문제가 “그 가격에 정말 체결됐을까"입니다. 호가가 얇은 종목에서는 화면에 찍힌 가격과 실제 체결가가 다르고, 수수료와 슬리피지를 어떻게 가정하느냐에 따라 결과가 통째로 바뀝니다. 실측 기록을 쓰면 그 가정이 전부 사라져요. 이미 일어난 일이니까요.
대신 한계도 명확합니다. 안 산 종목의 결과는 기록에 없습니다. 진입 게이트를 느슨하게 하면 어떻게 됐을지는 이 방식으로 알 수 없어요. 그래서 진입 쪽은 스캔 로그를 따로 봅니다. 어느 게이트에서 몇 개가 떨어졌는지, 그 게이트를 조정하면 통행량이 얼마나 바뀌는지요.
두 도구가 각각 다른 질문에 답합니다. 시뮬레이션은 “언제 팔까"에, 스캔 로그는 “무엇을 살까"에요.
전략이 바뀐 날
이 시기 가장 큰 사건은 진입 전략을 통째로 바꾼 겁니다. 돌파를 쫓던 것에서 과매도 눌림목 반등으로요.
몇 주 동안 돌파 조건을 조였다 풀었다 반복했는데 결론이 “파라미터 문제가 아니라 전략 문제"였습니다. 박스권에서 돌파는 대부분 가짜였거든요. 병렬 실험기의 BASE에서 확인했던 그 결론으로 돌아온 셈입니다.
· 조건을 조여도 풀어도 기대값이 비슷하다
· 조이면 매매가 줄 뿐 건당 성적은 그대로
· 이기는 건은 크게 이기지 않고 지는 건은 반복된다
--------------------------------------------------
→ 파라미터 공간 안에 답이 없다는 뜻
→ 이럴 때 바꿔야 하는 건 값이 아니라 전제다설계에서 하나 잘한 건, 이걸 스위치로 만든 겁니다. 돌파 로직을 지우지 않고 모드로 남겨뒀어요. 전략 전환은 도박이고, 도박에는 퇴로가 필요하니까요.
그리고 이 판단은 앞 시대들의 반복이기도 합니다. 2시대에서 이동평균이 양쪽으로 다 나빠지는 트레이드오프를 만났을 때 저는 파라미터를 더 만졌어요. 같은 신호를 이번에는 읽은 겁니다. 트레이드오프가 양방향으로 나빠지면 그건 전제를 바꾸라는 신호라는 걸 배우는 데 시대가 네 개 걸렸습니다.
지금 봇이 지키는 규칙들
현재 운용 중인 규칙을 정리하면 이렇습니다.
진입 10개 게이트를 전부 통과한 종목만 (RSI·거래량·저점근접·호가단위 등)
RSI 45 이하 / 저점근접 3.0 — 8.9일치 매매기록 전수 스윕으로 확정
운용 4슬롯. 슬롯당 예산은 가용 현금의 고정 비중
방어 손절 -5% · 본전 사수(+0.8% 도달 시 +0.4% 잠금)
일일 손실 한도 · 주간 손실 한도 · 자산 서킷브레이커
수확 계좌 합산 예상실현 2만원 도달 시 전량 청산, 스테이 30분
차단 BTC 4시간봉이 하락장이면 신규 진입 자체를 안 한다
배포 무결성 검사 + 거래소 잔고 대조 통과 못 하면 배포 금지여기서 RSI 45가 눈에 띌 겁니다. 교과서는 30을 과매도 기준으로 씁니다. 그런데 실측 기록을 전수로 훑어보니 45가 나왔어요. 30까지 기다리면 이 시장에서는 진입 자체가 거의 안 일어납니다. 교과서 값은 출발점이지 정답이 아니다라는 걸 숫자로 확인한 사례입니다.
본전 사수도 설명해두겠습니다. 이익이 +0.8%까지 갔다가 다시 마이너스로 돌아오는 게 심리적으로 제일 아픈 패턴이에요. 그래서 +0.8%에 닿으면 +0.4% 지점에 하한을 겁니다. 최대 이익을 포기하는 대신 이익이 손실로 바뀌는 경로를 막는 장치입니다. 첫 시대 메모에 있던 “50% 익절 + 트레일링"의 후손인데, 물량을 쪼개는 대신 가격 하한을 올리는 방식으로 진화했어요.
포트폴리오 수확은 좀 독특한 규칙입니다. 종목별로 파는 게 아니라 계좌 전체의 예상 실현액이 기준에 닿으면 전량 정리해요. 그리고 30분 쉽니다.
[ 종목 단위 청산 ]
각 종목이 목표에 닿을 때까지 기다린다
일부는 목표 도달 · 일부는 계속 물려 있음
→ 슬롯이 안 비고 사이클이 멈춘다
[ 계좌 단위 수확 ]
합산 예상실현이 목표에 닿으면 전량 정리
→ 슬롯이 한 번에 비고 다음 사이클로
→ 스테이 30분: 정리 직후 재진입 방지
————————————————–
개별 최적이 아니라 회전 최적을 택한 것
개별 종목 기준으로만 보면 손해 보는 결정입니다. 더 갈 수 있었던 종목도 같이 팔리니까요. 대신 슬롯이 한 번에 비고 사이클이 다시 돕니다. 물린 종목 하나가 슬롯을 몇 시간씩 잡고 있는 것보다 낫다고 판단했어요.
배포 규율
마지막 줄이 이 시대의 정체성입니다. 손익 계산의 근거를 봇의 기억이 아니라 거래소 주문내역으로 옮겼고, 거래소가 알려주는 총자산과 봇이 계산한 총자산이 원 단위로 일치하지 않으면 배포를 안 합니다.
· 부분 체결 주문한 수량과 체결된 수량이 다르다
· 수수료 계산 시점·방식에 따라 잔량이 미세하게 다르다
· 재시작 저장 시점과 실제 상태 사이의 공백
· 수동 개입 사람이 거래소에서 직접 팔면 봇은 모른다
--------------------------------------------------
대조하지 않으면 이 오차가 계속 누적된다
→ 성적 분석 자체가 틀리게 된다자기 기억을 안 믿기로 한 건 소프트웨어적으로는 굴욕이지만 회계적으로는 성숙이라고 생각해요. 그리고 이건 1시대의 무결성 점검이 자란 형태입니다. 그때는 “가끔 확인해보자"였는데 지금은 “안 맞으면 배포 금지"예요. 같은 아이디어가 권한을 얻은 거죠.
1. 무결성 검사 모듈 간 상태 일관성
2. 거래소 잔고 대조 원 단위 일치
3. 파라미터 근거 시뮬레이션 결과가 커밋에 있는가
--------------------------------------------------
하나라도 실패하면 배포하지 않는다
3번이 특히 중요 — 근거 없는 값은 코드에 못 들어간다3번을 규칙으로 만든 게 개인적으로는 제일 큰 변화입니다. 값을 바꾸고 싶으면 먼저 시뮬레이션을 돌려야 해요. 귀찮아서 안 바꾸게 되는 경우도 생기는데, 그것도 나쁘지 않다고 봅니다. 바꾸기 어렵게 만드는 것 자체가 안전장치니까요.
게이트 열 개가 각각 하는 일
게이트를 열 개까지 늘린 과정도 적어두겠습니다. 처음부터 열 개를 설계한 게 아니라, 사고가 날 때마다 하나씩 붙였어요.
데이터 충분성 봉이 부족한 종목 → 지표가 의미 없는 값
RSI 아직 안 빠진 종목 → 눌림목이 아님
저점 근접 바닥에서 먼 종목 → 되돌림 여지 부족
거래량 거래가 없는 종목 → 사도 못 판다
호가 단위 주문 가격이 규격 밖 → 조용히 거부됨
최소 주문 슬롯 예산이 최소 미만 → 주문 불가
중복 보유 이미 들고 있는 종목
재진입 쿨다운 방금 손절한 종목 → 반복 손절 방지
--------------------------------------------------
각각 다른 종류의 실패를 막는다 — 겹치는 게 없다여기서 중요한 건 게이트들이 서로 다른 종류의 실패를 막는다는 겁니다. 3시대에서 지표를 잔뜩 붙여봤을 때는 여러 지표가 사실상 같은 얘기를 하고 있었어요. 지금은 그렇지 않습니다. 데이터 품질, 종목 상태, 유동성, 주문 가능성, 봇 자신의 이력 — 층위가 다 다릅니다.
특히 뒤쪽 세 개가 재밌습니다. 호가 단위, 최소 주문, 재진입 쿨다운은 시장 데이터가 아니라 주문 시스템과 봇 자신의 상태에 관한 조건이에요. 지표 이야기가 아닙니다. 그런데 이것들이 없으면 조건은 다 맞는데 주문이 안 나가거나, 같은 종목에서 계속 깎이는 상황이 생깁니다.
게이트를 설계할 때 지표부터 떠올리게 되는데, 실제로 계좌를 지켜주는 건 이런 사무적인 조건들이더군요.
스캔 로그를 실제로 읽기 시작하다
3시대에 남기기만 하고 안 보던 스캔 로그를, 이 시대에 드디어 분석하기 시작했습니다.
게이트 A 탈락 0건 → 아무 일도 안 하고 있다
게이트 B 탈락 대부분 → 혼자 다 거른다 (나머지가 무의미)
게이트 C 고르게 → 제 역할을 한다
--------------------------------------------------
조정 후 반드시 같이 확인할 것:
· 건당 기대값이 올랐나
· 매매 건수가 얼마나 줄었나
기대값 그대로 + 건수만 감소 = 그 게이트는 문만 좁힌 것실제로 이 분석에서 나온 게 앞서 말한 RSI 45입니다. 30으로 두면 통과하는 종목이 거의 없어요. 문턱을 45로 올리니 매매가 일어나고, 그 상태에서 기대값도 유지됐습니다. 통행량과 성적을 같이 봐야 알 수 있는 값이었어요.
반대 사례도 있었습니다. 그럴듯한 논리로 넣은 가점 항목이 실제로는 반대로 작동하고 있던 경우요. 점수가 높을수록 성적이 나빴습니다. 논리는 완벽했는데 데이터가 반박한 거죠. 이런 걸 발견하면 좀 허탈한데, 동시에 이 방식이 작동한다는 증거이기도 합니다. 내 논리를 반박해주는 도구가 있다는 게 혼자 개발할 때 제일 필요한 것이더군요.
7주 연속 마이너스가 가르쳐준 것
7주 연속 마이너스를 보고 나서야 받아들인 게 하나 있습니다. 잃지 않는 구조를 먼저 만들고, 버는 건 그다음이라는 것.
그전에는 어떻게 더 벌지만 생각했어요. 진입 조건을 정교하게 만들고, 지표를 추가하고, 타이밍을 다듬고. 전부 “더 잘 사는 법"이었습니다.
사는 것에 관한 규칙
진입 게이트 10개 · 랭킹 · 눌림목 대기
안 사거나 멈추는 규칙
슬롯 상한 · 손절 · 본전 사수
일일 손실 한도 · 주간 손실 한도
자산 서킷브레이커 · 레짐 차단 · 수확 후 스테이
————————————————–
아래쪽이 더 많다
그리고 아래쪽은 전부 “여기서 멈춘다” 를 미리 정한 것
지금 규칙 목록을 보면 절반 이상이 방어 장치입니다. 손절, 본전 사수, 일일·주간 한도, 서킷브레이커, 레짐 차단. 전부 “여기서 멈춘다"를 미리 정해둔 것들이죠.
방어 규칙의 공통점이 하나 있습니다. 전부 사전에 정해둔 것이라는 점이에요. 손실이 커진 다음에 “여기서 멈출까"를 판단하면 절대 못 멈춥니다. 그 순간에는 조금만 더 버티면 돌아올 것 같거든요. 그래서 판단을 미리 해두고 실행만 봇에게 맡깁니다. 처음에 자제력 문제로 시작한 프로젝트가 결국 여기로 왔어요.
모듈화가 실제로 바꾼 것
파일을 쪼갠 효과를 구체적으로 적어두겠습니다. “코드가 깔끔해졌다” 말고 실질적인 것들이요.
모놀리스 모듈
파라미터 변경 한 파일 안 어딘가 vv_state 한 곳
청산 로직 수정 화면이 깨질 수 있음 영향 범위 명확
로그 형식 변경 판단 로직 근처 완전히 분리
전략 검증 불가능 기록 재계산 가능
잔고 대조 할 자리가 없음 전담 모듈
--------------------------------------------------
제일 큰 변화는 마지막 두 줄 —
검증과 대조가 있을 자리가 생겼다마지막 두 줄이 핵심입니다. 모놀리스에서는 검증 로직을 넣을 자리가 없었어요. 어디에 넣어도 다른 것과 얽힙니다. 모듈로 나누고 나니 원장 대조를 전담하는 자리가 생겼고, 그 자리가 생기니까 배포 규칙을 만들 수 있게 됐습니다.
구조가 규율을 가능하게 한다는 게 이 시대의 발견입니다. 규율은 의지의 문제 같지만, 실은 그 규율을 실행할 자리가 코드에 있느냐의 문제였어요. 자리가 없으면 아무리 마음먹어도 못 합니다.
파라미터를 한 모듈에 모은 것도 비슷한 효과가 있었습니다. 값이 흩어져 있으면 “지금 이 봇이 어떤 설정으로 돌고 있나"를 한눈에 못 봐요. 한곳에 모으니 파일 하나만 열면 봇의 전체 성격이 보입니다. 그리고 각 값 옆에 근거를 주석으로 붙여두니, 별도 문서 없이 코드가 문서 역할을 합니다.
여섯 시대를 관통하는 세 문장
461개를 정리하며 반복해서 나온 것들만 뽑으면 셋입니다.
하나, 늘어난 건 전부 안 사는 이유였습니다. 615줄에서 여섯 모듈까지, 코드가 늘어난 양의 대부분은 진입을 막는 조건입니다. 게이트, 슬롯, 손절, 한도, 레짐 차단. 자동매매를 만들기 시작하면 사는 쪽을 정교하게 만들고 싶어지는데, 실제로 계좌를 지킨 건 반대쪽이었어요.
둘, 기록이 없으면 아무것도 축적되지 않습니다. 3시대 버전의 절반은 안 만들어도 되는 것들이었습니다. 기록이 있었으면 데이터가 답을 줬을 테니까요. 그리고 기록의 가치는 항상 시차를 두고 나타납니다. 오늘 로그를 남기는 커밋은 오늘 성적을 1원도 안 바꾸지만 몇 주 뒤 답을 줍니다.
셋, 고통은 설계 정보입니다. 각 시대를 끝낸 건 좋은 아이디어가 아니라 한계에 부딪힌 경험이었어요. 그리고 그 경험이 다음 구조의 설계도가 됐습니다. 모놀리스 시절에 어디가 자주 깨졌는지가 곧 모듈 경계였고, 병렬 실험의 관리 비용이 곧 “한 봇은 한 성격” 원칙이 됐습니다.
남은 이야기
지금도 매일 플러스인 건 아닙니다. 다만 지금의 봇은 자기가 왜 그 값으로 돌아가는지 전부 답할 수 있어요. 저한테는 그게 수익률보다 먼저 온 자산입니다.
461개 버전을 여섯 시대로 정리하면서 확인한 게 하나 있습니다. 각 시대를 끝낸 건 새로운 아이디어가 아니라 한계에 부딪힌 경험이었어요.
1시대 윈도우 불안정 · 단타 수수료
2시대 이동평균이 횡보장에서 무력
3시대 단일 파일 · 예측 불가능
4시대 갈래 네 개의 관리 비용
5시대 조기 최적화로 낭비한 시간
6시대 진행 중
--------------------------------------------------
전부 "안 되는 걸 충분히 겪은 뒤" 다음이 나왔다그래서 지금 시대도 언젠가 끝날 겁니다. 무엇 때문에 끝날지는 아직 모르는데, 그때 이유를 설명할 수 있는 상태이기만 하면 된다고 생각해요. 461개를 돌아보며 제일 아쉬웠던 건 성적이 나빴던 시기가 아니라 왜 그랬는지 모르는 시기였습니다.
이 아카이브를 쓴 이유도 그겁니다. 다음에 무언가 끝날 때, 그 이유가 여기 남아 있게 하려고요.