[{"content":"앞 글에서 이어서 삼성전자편에서 아홉 개 패턴을 실제 일봉에 대봤다. 성립한 건 셋, 나머지는 없었다. 특히 이중 천장은 봉우리가 하나뿐이라 불성립이었다.\n같은 시장, 같은 기간의 SK하이닉스는 여기가 완전히 다르다. 교과서에 실려도 될 만큼 정확한 이중 천장이 있다. 그리고 그 패턴이 뽑아낸 목표치가 정확히 맞았다가, 거기서 다시 35% 무너진다.\n이 한 종목에 패턴 분석의 가장 좋은 면과 가장 위험한 면이 같이 들어 있다.\n이 글의 데이터 범위 종목 SK하이닉스 (000660) 구간 2026-06-02 ~ 2026-08-14 · 52 거래일 출처 stockanalysis.com 일봉 (2026-08-14 조회) -------------------------------------------------- 기간 최고 2,987,000원 (6/25 고가) 기간 최저 1,246,000원 (7/29 저가) 최종 종가 1,645,000원 (8/14) -------------------------------------------------- 고점 → 저점 −58.29% 저점 → 현재 +32.02% 고점 대비 현재 −44.93% 삼성전자가 −49.5%였는데 하이닉스는 −58.3%다. 그리고 반등은 삼성이 +45.1%, 하이닉스가 +32.0%다. 더 깊이 빠지고 덜 올라왔다.\n이 글은 차트 패턴을 코드로 옮기는 방법을 다루는 학습 기록이다. 특정 종목의 매수·매도 권유가 아니다.\n1. 전체 그림부터 6월 초부터 6월 25일까지 거의 수직으로 올라갔다가, 그 뒤 한 달 넘게 무너진다. 7월 29일 저가 1,246,000이 바닥이고 지금은 1,645,000이다.\nSK하이닉스 · 이 구간의 극단값 최대 하락일 7/13 −15.37% 종가 1,845,000 최저가 7/29 1,246,000 거래량 12,183,680주 (기간 최대) 최대 상승일 7/31 +29.95% 종가 1,718,000 -------------------------------------------------- 삼성전자와 같은 날짜에 같은 사건이 찍혀 있다 최저가 7/29 · 최대 반등 7/31 — 두 종목 완전히 일치 개별 종목 이슈가 아니라 시장 전체 이벤트였다는 증거 앞 글에서 다룬 7월 말 폭락 — 코스피가 이틀 만에 약 16.2% 빠지고 서킷브레이커가 걸린 그 사건이 여기 그대로 찍혀 있다. 흥미로운 건 그 원인 목록에 SK하이닉스 실적 발표가 들어 있었다는 점이다. 사상 최대 분기 실적을 냈는데도 눈높이가 그보다 높아서 실망 매물이 나왔다는 진단이었다.\n차트를 읽는 사람 입장에서 이건 중요한 교훈이다. 좋은 실적이 좋은 주가를 만들지 않는다. 기대와의 차이가 만든다. 그리고 그 기대는 차트에 안 나온다.\n2. 이중 천장 — 교과서에 실어도 될 만큼 정확하다 본론이다. 6월 하순의 두 봉우리를 보자.\n이중 천장 구성 요소 (실측) 첫 번째 봉우리 6/22 고가 2,945,000 골(계곡) 6/24 저가 2,453,000 ← 넥라인 두 번째 봉우리 6/25 고가 2,987,000 -------------------------------------------------- 두 봉우리 차이 = (2,987,000 − 2,945,000) ÷ 2,945,000 = +1.43% 통설은 3% 이내 — 여유롭게 충족한다 -------------------------------------------------- 골의 깊이 = (2,987,000 − 2,453,000) ÷ 2,987,000 = 17.9% 봉우리 사이가 충분히 깊게 파였다 — M자 형태 성립 두 봉우리가 1.43% 차이다. 이 정도면 교과서 그림과 구별이 안 된다. 골도 17.9%나 파여서 M자가 선명하다.\n넥라인 이탈 이중 천장은 골(넥라인)을 아래로 뚫어야 완성된다. 봉우리가 두 개 있는 것만으로는 패턴이 아니다.\n넥라인 이탈 시점 (실측) 넥라인 2,453,000 (6/24 저가) -------------------------------------------------- 6/26 종가 2,673,000 넥라인 위 6/29 종가 2,628,000 넥라인 위 6/30 종가 2,650,000 넥라인 위 7/01 종가 2,560,000 넥라인 위 · 조금씩 접근 7/02 종가 2,187,000 넥라인 아래 마감 → 패턴 완성 -------------------------------------------------- 7/02 하루 낙폭 −14.57% · 저가 2,187,000 = 종가 종가가 저가와 같다 = 장 마감까지 매도 우위. 강한 이탈 신호 7월 2일 종가가 그날 저가와 같다. 장중 내내 밀리다가 마감까지 반등을 못 했다는 뜻이다. 넥라인 이탈로서는 더할 나위 없이 선명한 신호였다.\n이날 삼성전자도 −9.1%(종가 314,500 → 286,000)로 밀렸다. 검색해보니 이 시점은 나스닥발 반도체 매도세가 번진 국면이었다.\n3. 계측 목표가 정확히 맞았다 이중 천장에도 목표가 공식이 있다. 앞 글의 역 헤드 앤 숄더와 방향만 반대다.\n계측 목표 계산과 실제 결과 패턴 높이 = 두 번째 봉우리 − 넥라인 = 2,987,000 − 2,453,000 = 534,000 목표 = 넥라인 − 패턴 높이 = 2,453,000 − 534,000 = 1,919,000 \u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026ndash; 실제: 7/23 종가 = 1,919,000 \u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026ndash; 오차 0원. 계산값과 종가가 정확히 일치했다\n7월 23일 종가가 정확히 1,919,000이었다. 계측 목표와 원 단위로 일치한다.\n이런 걸 보면 소름이 돋는데, 그래서 더 조심해야 한다. 짚어둘 게 몇 개 있다.\n이 일치를 어떻게 읽어야 하나 1. 호가 단위 때문에 값이 이산적이다 하이닉스 이 가격대의 호가 단위는 1,000원 → 우연히 맞을 확률이 연속값보다 훨씬 높다 2. 종가만 봤다. 그날 저가는 1,845,000, 고가는 1,948,000 → 목표치를 장중에 이미 통과했다가 되돌아온 것 3. 표본 1건이다 → 같은 공식이 다음에도 맞는다는 근거가 되지 않는다 -------------------------------------------------- \"맞았다\" 는 사실이고, \"맞는다\" 는 결론은 성립하지 않는다 내 봇에서 파라미터를 정할 때 쓰는 기준을 여기 그대로 적용하면 이렇다. 손절 −2.5%를 채택할 때 나는 실측 58건을 손절선별로 다시 계산해 기대값 +0.209%를 확인했다. 계측 목표 공식은 표본이 1건이다. 채택할 자격이 없다.\n4. 그리고 목표치는 배신했다 여기가 이 글의 핵심이다. 목표치에 닿은 뒤 무슨 일이 있었나.\n계측 목표 도달 이후의 실제 경로 7/23 종가 1,919,000 ← 계측 목표 정확히 도달 7/24 종가 1,759,000 −8.3% 7/27 종가 1,816,000 반등 시도 7/28 종가 1,550,000 −14.7% 7/29 종가 1,401,000 저가 1,246,000 -------------------------------------------------- 목표치 대비 추가 하락 = (1,246,000 − 1,919,000) ÷ 1,919,000 = −35.1% -------------------------------------------------- \"목표 도달했으니 여기서 멈춘다\" 고 봤다면 −35% 를 더 맞았다 계측 목표를 지지선으로 믿고 들어갔다면 나흘 만에 −35%다. 신용을 썼다면 반대매매 구간이다.\n이게 패턴 목표치의 본질이다. 목표는 도착지가 아니라 통과 지점일 수 있다. 그리고 어느 쪽인지는 지나봐야 안다.\n목표치를 대하는 두 가지 태도 [ 지지선으로 본다 ] \"여기서 멈춘다\" → 진입 근거로 사용 틀리면 계좌가 직접 맞는다 [ 참고 좌표로 본다 ] \u0026ldquo;여기 근처에서 무슨 일이 있는지 본다\u0026rdquo; 틀려도 아무것도 안 한 것과 같다 \u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026ndash; 내 봇의 레짐 차단과 같은 논리다 BTC 4시간봉 하락이면 전략을 바꾸는 게 아니라 안 산다 틀렸을 때 대가가 작은 쪽을 고른다\n5. 약세 플래그 — 삼성과 같은 자리, 더 큰 진폭 하락 과정에도 플래그가 있었다. 삼성전자와 거의 같은 날짜다.\n약세 플래그 · 두 종목 비교 삼성전자 SK하이닉스 깃대 시작 7/15 고가 284,500 7/15 고가 2,171,000 깃대 끝 7/20 저가 240,000 7/21 저가 1,732,000 깃대 낙폭 −15.6% −20.2% -------------------------------------------------- 깃발 구간 7/21~7/23 7/22~7/23 깃발 상단 270,000 (종가) 1,919,000 (종가) -------------------------------------------------- 이탈 후 저점 7/29 189,200 7/29 1,246,000 깃발→저점 −29.9% −35.1% -------------------------------------------------- 같은 구조 · 하이닉스의 진폭이 일관되게 더 크다 두 종목이 같은 날 같은 모양을 만들었다. 이건 패턴이 종목 고유의 성질이 아니라 시장 전체 수급의 그림자라는 뜻이다.\n그리고 하이닉스의 깃발 상단 종가 1,919,000이 앞서 본 계측 목표와 같은 숫자다. 플래그의 되돌림 고점과 이중 천장의 계측 목표가 같은 자리였다. 이런 겹침을 보고 \u0026ldquo;중요한 가격대\u0026quot;라고 부르고 싶어지는데, 표본 1건에서는 그냥 우연과 구별되지 않는다.\n6. 역 헤드 앤 숄더 — 같은 모양, 다른 결말 삼성전자에서는 역 헤드 앤 숄더가 넥라인 돌파까지 완성됐다. 하이닉스도 같은 모양을 만들었는데, 아직 완성되지 않았다.\n역 H\u0026S · 두 종목 진행 상황 비교 삼성전자 SK하이닉스 왼쪽 어깨 7/20 240,000 7/21 1,732,000 머리 7/29 189,200 7/29 1,246,000 오른쪽 어깨 8/11 227,500 8/11 1,373,000 넥라인 7/31 267,000 7/31 1,718,000 -------------------------------------------------- 8/14 종가 274,500 1,645,000 넥라인 대비 +2.8% (돌파) −4.2% (미달) -------------------------------------------------- 삼성: 8/13 종가 268,000 으로 넥라인 위 마감 → 완성 하이닉스: 아직 넥라인 아래 → 패턴 미완성 어깨와 머리의 날짜가 완전히 같다. 그런데 하나는 넥라인을 넘었고 하나는 못 넘었다.\n이 대비가 알려주는 게 있다. 패턴이 형성 중일 때는 완성될지 알 수 없다. 지금 하이닉스 차트를 보면 예쁜 역 헤드 앤 숄더처럼 보이지만, 여기서 다시 무너지면 그건 역 헤드 앤 숄더가 아니라 그냥 하락 중 반등이 된다. 이름은 나중에 붙는다.\n미완성 패턴의 두 갈래 (관측 사실만) 현재 8/14 종가 1,645,000 · 넥라인 1,718,000 -------------------------------------------------- 넥라인을 넘으면 → 역 H\u0026S 완성이라 부르게 된다 오른쪽 어깨를 깨면 → 패턴이 아니었던 게 된다 (8/11 저가 1,373,000) -------------------------------------------------- 둘 중 어느 쪽인지 지금은 알 수 없다 \"가능성이 높다\" 는 말을 하려면 표본이 필요한데 없다 여기서 확률을 말하고 싶은 유혹이 크다. 그런데 나는 이 패턴의 성공률 통계를 갖고 있지 않다. 모르는 걸 모른다고 적는 게 이 아카이브의 규칙이라 그냥 두 갈래만 적는다.\n7. 같은 사이클, 다른 진폭 두 종목을 같은 기준으로 겹쳐보면 성격 차이가 선명하다.\n왜 하이닉스가 더 크게 움직였나 — 관측된 사실 고점→저점 삼성 −49.5% 하이닉스 −58.3% 저점→현재 삼성 +45.1% 하이닉스 +32.0% 최대 하락일 삼성 −13.4% 하이닉스 −15.4% 최대 상승일 삼성 +26.8% 하이닉스 +30.0% -------------------------------------------------- 하이닉스 베타 2.41 (출처: stockanalysis.com) 베타는 시장 대비 민감도 — 1보다 크면 시장보다 크게 움직인다 -------------------------------------------------- 방향은 같고 폭이 다르다 = 전형적인 고베타 종목의 행동 베타 2.41이라는 공개 수치가 이 관찰과 맞아떨어진다. 같은 시장 이벤트에 두 배 이상 반응한다는 뜻이다.\n이건 봇 운영에도 그대로 적용되는 얘기다. 변동성이 큰 종목은 같은 손절 폭을 쓰면 안 된다. 내 봇이 손절 −5%를 쓰는데, 하루에 15% 움직이는 자산에서 −5% 손절은 노이즈에 걸리는 값이다. 1시대에 손절을 −3.00%에서 −4.50%로 넓히면서 배운 것과 같다 — 진입가와 손절 폭은 따로 정하는 값이 아니고, 자산의 변동성과도 따로 놀 수 없다.\n8. 패턴이 무의미해지는 구간 마지막 차트가 이 글에서 제일 중요할 수도 있다.\n일간 등락 ±10% 초과 일수 (52거래일 기준) SK하이닉스 +10% 초과 7/03(+10.9%) · 7/31(+30.0%) 2일 −10% 초과 7/02(−14.6%) · 7/13(−15.4%) · 7/28(−15.4%) 3일 -------------------------------------------------- 52일 중 5일이 ±10% 초과. 열흘에 한 번꼴 -------------------------------------------------- 이런 장에서 \"넥라인 돌파 시 진입\" 이 성립하나? 넥라인 위 마감을 확인하고 다음날 시가에 사면 이미 10% 위일 수도, 10% 아래일 수도 있다 패턴 매매에는 진입 시점이 필요하다. \u0026ldquo;넥라인 돌파 확인 후 진입\u0026quot;이 표준인데, 확인은 종가로 하고 진입은 다음날에 한다. 그 사이에 갭이 열린다.\n실제 갭 — 종가 대비 다음날 시가 7/30 종가 1,322,000 → 7/31 시가 1,697,000 +28.4% 7/13 종가 1,845,000 → 7/14 시가 1,825,000 −1.1% 7/28 종가 1,550,000 → 7/29 시가 1,567,000 +1.1% 7/02 종가 2,187,000 → 7/03 시가 2,197,000 +0.5% -------------------------------------------------- 7/31 시가 갭 +28.4% \"7/30 종가 보고 다음날 산다\" 는 계획은 이날 무의미했다 반대로 하락 갭이었다면 손절 자체가 건너뛰어진다 7월 31일 시가는 전날 종가보다 28.4% 높았다. 전날 밤에 무슨 판단을 했든 그 판단은 다음날 아침에 이미 낡은 것이 됐다.\n이게 내가 15분봉 눌림목 전략으로 정착한 이유와 닿아 있다. 판단 주기가 행동 주기보다 느려야 하는데, 갭이 크면 그 전제가 깨진다. 24시간 거래되는 크립토에는 이런 갭이 훨씬 적다. 같은 패턴 규칙이라도 자산에 따라 성립 여부가 달라지는 것이다.\n9. 두 글을 합쳐서 얻은 것 삼성전자와 SK하이닉스, 두 종목 9개 패턴을 실제 데이터에 대본 결과를 한 장으로 정리한다.\n패턴 판정 종합 (2026-06 ~ 08 · 실측 기준) 삼성전자 SK하이닉스 약세 플래그 ✔ 성립 ✔ 성립 역 헤드 앤 숄더 ✔ 완성 △ 미완성 강세 플래그 △ 되돌림 깊음 ✘ 이중 천장 ✘ 봉우리 1개 ✔ 교과서적 이중 바닥 ✘ 20% 차이 ✘ 10% 차이 헤드 앤 숄더(정방향) ✘ ✘ 컵 앤 핸들 ✘ V자 바닥 ✘ V자 바닥 인버티드 컵 앤 핸들 ✘ ✘ 엘리어트 파동 △ 사후 해석 △ 사후 해석 -------------------------------------------------- 18칸 중 확실히 성립: 4칸 (22%) 같은 시장·같은 기간인데 종목별로 성립 패턴이 다르다 이 표가 이번 스터디의 결론이다. 18칸 중 넷만 성립했다. 그리고 삼성에 있는 게 하이닉스에 없고, 하이닉스에 있는 게 삼성에 없다.\n같은 사건을 겪은 같은 업종 두 종목인데 패턴이 다르게 새겨졌다. 이건 패턴이 시장의 법칙이 아니라 가격 경로의 우연한 형상에 가깝다는 뜻으로 읽는 게 안전하다.\n10. 봇 관점의 결론 이번 스터디에서 봇으로 가져갈 것 / 안 가져갈 것 ✘ 안 가져간다 패턴 자동 탐지 (어깨·넥라인 인식) → 탐지 파라미터가 결과를 결정한다 계측 목표를 지지/저항으로 사용 → 표본 1건 · 35% 오버슈팅 사례를 직접 봤다 엘리어트 파동 카운팅 → 끝나야 셀 수 있다 ✔ 가져간다 거래량 급증일의 성격 관찰 → 이미 게이트로 있음 (통행 자격 심사용) 자산 변동성에 손절 폭을 맞추는 원칙 → 베타 2.41 종목과 1.0 종목은 다른 값이어야 갭 리스크 = 판단 주기 설계 근거 → 종가 확인 · 다음날 진입 구조의 한계\n패턴을 봇에 넣지 않기로 한 건 패턴이 틀렸기 때문이 아니다. 검증할 수 없기 때문이다.\nRSI 45라는 값은 8.9일치 매매기록을 전수로 훑어서 뽑았다. 틀렸으면 데이터가 반박해준다. 역 헤드 앤 숄더의 넥라인 돌파는 이 두 종목에서 각각 1건씩 있었고, 하나는 완성됐고 하나는 아직이다. 이걸로는 아무것도 결정할 수 없다.\n3시대에 배운 문장을 다시 적는다. 검증할 수 없는 정교함보다 검증 가능한 단순함. 차트 패턴은 정교하고 아름답지만, 내 손에 있는 도구로는 검증이 안 된다. 그래서 읽는 데만 쓴다.\n11. 남기고 싶은 세 문장 첫째, 맞은 것보다 배신한 것이 많이 가르쳐준다. 계측 목표 1,919,000이 원 단위로 적중한 사실보다, 거기서 35% 더 빠졌다는 사실이 훨씬 유용한 정보다. 전자는 우연일 수 있고 후자는 리스크의 크기다.\n둘째, 패턴 이름은 데이터가 아니라 사람이 고른다. 삼성전자의 8/11 저점을 \u0026ldquo;이중 바닥의 두 번째 저점\u0026quot;이라 부르면 틀리고 \u0026ldquo;역 H\u0026amp;S의 오른쪽 어깨\u0026quot;라 부르면 맞다. 같은 점인데 이름에 따라 결론이 달라진다면, 그건 관찰이 아니라 해석이다.\n셋째, 같은 시장의 두 종목도 다른 패턴을 만든다. 18칸 중 4칸만 성립했고 그 4칸도 종목별로 엇갈렸다. \u0026ldquo;차트에는 패턴이 반복된다\u0026quot;는 말은 맞지만, 어떤 패턴이 언제 나올지는 반복되지 않는다.\n트레이딩의 대부분이 실행의 문제라던 마크 더글러스의 말에 한 줄만 덧붙이고 싶다 — 패턴을 찾는 건 실행이 아니다. 찾은 다음에 무엇을 할지를 미리 정해둔 것만 실행이다. 나는 이번 스터디에서 \u0026ldquo;아무것도 안 한다\u0026quot;를 골랐다.\n이 글은 개인의 학습 기록이며 투자 권유가 아닙니다. 모든 가격은 stockanalysis.com 일봉(2026-08-14 조회) 기준이며, 지수 수치와 베타 값의 출처는 본문에 표기했습니다.\n","permalink":"https://botlab.co.kr/study/hynix-chart-patterns/","summary":"교과서 목표치가 정확히 맞았다. 그리고 거기서 35% 더 빠졌다. 패턴을 믿으면 안 되는 이유를 실측으로 남긴다.","title":"Chart Patterns — SK하이닉스 이중 천장과 계측 목표의 배신 | Crypto Trading Bot"},{"content":"이름의 뜻부터 샹들리에는 천장에 매달린 조명이다. 이 지표의 이름이 거기서 왔다. 손절선을 바닥에서 쌓아 올리는 게 아니라 천장에서 내려 매단다는 뜻이다.\n보통 손절은 진입가 기준으로 잡는다. 10,000원에 샀으면 -5%인 9,500원. 이 방식의 문제는 가격이 12,000원까지 올라가도 손절선이 여전히 9,500원에 있다는 것이다. 이익이 2,000원 쌓였는데 방어선은 진입 시점에 묶여 있다.\n샹들리에 엑시트는 기준점을 옮긴다. 진입가가 아니라 진입 이후의 최고가에서 재는 것이다.\n공식 — 기준점이 다르다 기본값: 기간 22, 배수 3.0 (원저자 척 르보 권장) 롱 청산선 = 최근 n봉 최고가 − ATR(n) × 배수 숏 청산선 = 최근 n봉 최저가 + ATR(n) × 배수 핵심 성질: 청산선은 절대 내려가지 않는다 (롱 기준) 고점이 갱신되면 → 청산선도 따라 올라감 고점이 그대로면 → 청산선도 그대로 (ATR 변화분만 반영) 비교 고정 손절 진입가 − 5% 가격이 올라도 그대로 샹들리에 최고가 − 3×ATR 이익을 따라 올라간다 단, ATR 이 커지면 청산선이 내려갈 수 있다 — 구현 시 max() 로 잠가야 한다 마지막 줄이 실전 구현의 함정이다. 공식만 그대로 쓰면 변동성이 급증했을 때 ATR이 커지면서 청산선이 아래로 내려간다. 이익을 지키려고 만든 장치가 방어선을 후퇴시키는 셈이다. 그래서 대부분의 구현은 \u0026ldquo;이전 청산선보다 낮아지지 않는다\u0026quot;는 잠금을 추가로 건다.\n손절선의 기준점이라는 문제 이 지표의 핵심은 수식이 아니라 기준점을 어디에 두느냐다. 이 질문 자체가 잘 안 다뤄지는 것 같아서 좀 더 파보려 한다.\n손절선을 정할 때 쓸 수 있는 기준점은 여러 개다.\n손절선의 기준점 후보들 ① 진입가 \"내가 산 가격에서 −5%\" 장점: 손실 계산이 직관적. 리스크 관리에 바로 쓰인다 단점: 시장은 내 진입가를 모른다. 아무 의미 없는 가격이다 ② 진입 이후 최고가 \"고점에서 3×ATR 아래\" — 샹들리에 방식 장점: 이익을 따라 올라간다. 시장이 아는 가격(고점)이 기준 단점: 보유 기간이 짧으면 작동할 시간이 없다 ③ 구조적 지점 \"직전 스윙 저점 아래\" — 이 자리가 깨지면 시나리오가 틀린 것 장점: 논리적. 엘리엇의 무효화 라인과 같은 발상 단점: 스윙 확정에 지연. 거리가 종목마다 제각각 ④ 변동성 절대치 \"진입가에서 2×ATR 아래\" — ①과 ②의 절충 내 봇은 ① 을 쓴다. 관성이기도 하고, 계산이 단순해서이기도 하다 ①번의 단점을 다시 읽어보자. 시장은 내 진입가를 모른다. 이건 당연한 사실인데도 손절을 설계할 때 자주 잊힌다.\n내가 10,000원에 샀다는 사실은 나에게만 의미가 있다. 시장이 9,500원까지 밀릴지 말지는 내 진입가와 무관하게 정해진다. 그런데도 대부분의 사람이 진입가 기준으로 손절을 잡는 건, 그게 자기 손익을 계산하기 편해서다.\n계산의 편의가 방어선의 위치를 정하고 있는 셈이다. 이 지적이 샹들리에 엑시트를 공부하면서 얻은 가장 큰 소득이다.\n그럼 왜 아직 안 바꿨나. 두 가지 이유다. 하나는 현행 손절값이 실측으로 확정돼 있어서다. 매매 58건을 손절선별로 재현했을 때 −2.5%에서 기대값이 −0.072%에서 +0.209%로 뒤집혔고, 이후 다층 방어가 완성되면서 −5%까지 넓어졌다. 근거가 있는 값을 근거 없이 바꿀 수는 없다.\n다른 하나는 이익 구간 체류시간 중앙값이 0분이라는 실측 때문이다. 고점 기준 트레일링은 고점이 갱신될 시간이 있어야 의미가 있는데, 그 시간이 없다.\nATR을 쓰는 이유 왜 하필 ATR(평균 실제 범위)일까. 퍼센트로 하면 안 되나.\n퍼센트 손절 vs ATR 손절 종목 A — 하루 평균 진폭 2% | 종목 B — 하루 평균 진폭 12% 퍼센트 방식: 둘 다 −5% A 에서는 하루 진폭의 2.5배 → 거의 안 걸린다 (너무 느슨) B 에서는 하루 진폭의 0.4배 → 정상 출렁임에 계속 베인다 (너무 타이트) ATR 방식: 둘 다 3×ATR A 에서는 좁게, B 에서는 넓게 — 자동으로 종목에 맞춰진다 ATR = 아래 셋 중 최댓값의 평균 (와일더 평활) 고가 − 저가 |고가 − 전일종가| ← 갭 상승을 포착 |저가 − 전일종가| ← 갭 하락을 포착 단순 고저폭이 아니라 갭까지 넣는 게 '실제(True)' 범위라는 이름의 이유다 이 논리는 내 봇의 손절 이력과도 맞닿아 있다. 손절폭이 -2.5% → -2% → -5%로 움직였는데, 마지막 확장의 근거가 \u0026ldquo;노이즈 밴드 탈출\u0026quot;이었다. 정상적인 출렁임 범위 안에 손절선을 두면 방향이 맞아도 계속 베인다는 것. ATR 방식은 그 노이즈 밴드를 종목마다 자동으로 재주는 도구다.\n배수 3은 어디서 왔나 원저자 권장값은 3.0인데, 이게 어디서 나온 숫자인지 따져볼 필요가 있다.\n배수를 바꾸면 무엇이 달라지나 배수 작음 (1.5~2) 청산선이 가깝다 이익 반납이 적다 · 손실이 작다 정상 출렁임에 자주 털린다 · 큰 추세를 못 탄다 배수 큼 (4~5) 청산선이 멀다 추세를 끝까지 탄다 · 휩쏘에 안 털린다 꺾인 뒤 청산까지 반납분이 크다 · 손실 한 건이 커진다 3.0 은 일봉·주식·중장기 추세추종에서 나온 값이다 15분봉 코인에 그대로 가져오면 맞을 이유가 없다 확인해야 할 것: 내 보유 기간의 분포 며칠씩 들고 가는 전략이면 3.0 이 말이 된다 몇 시간 안에 끝나는 전략이면 청산선이 한 번도 안 닿는다 마지막 지적이 내 봇에는 치명적이다. 실측해보니 이익 구간에 머무는 시간의 중앙값이 0분이었다. 오르는 순간 바로 되돌아오는 패턴이 절반이라는 뜻이다. 이런 분포에서 3×ATR짜리 청산선은 사실상 발동하지 않는다. 발동하기 전에 이익이 사라지니까.\n그래서 내 봇의 수확 방식이 지금 형태가 됐다. 개별 종목의 트레일링이 아니라 계좌 합산 기준으로 목표 금액에 닿으면 전량 수확하고, 스테이 시간도 60분에서 30분으로 줄였다. 스윕 결과가 30분 8회 +209,203원, 60분 6회 +159,530원이었다. 긴 대기는 회당 크기를 못 키우고 사이클 수만 깎았다.\nATR이라는 지표 자체에 대하여 샹들리에의 재료인 ATR도 한 번 짚고 가자. 이 코너에서 여러 번 언급했는데 정면으로 다룬 적은 없다.\nATR(Average True Range)은 와일더가 만든 변동성 지표다. RSI를 만든 그 사람이다. 이름의 \u0026ldquo;True\u0026quot;가 붙은 이유가 계산식에 있다.\n왜 '진짜' 범위인가 단순한 방법: 고가 − 저가 문제 — 갭이 있으면 실제 움직임을 과소평가한다 전일 종가 1,000 → 오늘 950 에서 시작해 940~960 사이 움직임 고저폭은 20 이지만 실제로는 60 만큼 움직였다 True Range = 다음 셋 중 최댓값 ① 고가 − 저가 ② |고가 − 전봉 종가| ← 갭 상승 포착 ③ |저가 − 전봉 종가| ← 갭 하락 포착 ATR = True Range 를 와일더 평활한 값 코인 시장은 24시간이라 일봉 갭이 거의 없다 그래도 ②③ 을 넣는 이유: 거래소 점검·급락 시 캔들이 튈 때 잡아준다 그리고 무엇보다 원본과 값을 맞추기 위해서다 ATR이 유용한 진짜 이유는 종목별로 다른 자를 자동으로 대준다는 것이다. 퍼센트 기반 규칙은 하루 평균 2% 움직이는 종목과 12% 움직이는 종목에 같은 잣대를 댄다. ATR 기반이면 각 종목의 평소 진폭을 기준으로 재게 된다.\n내 봇에도 변동성 조건이 있는데 하한과 상한이 함께 있다. 너무 조용한 종목은 움직임이 없어 못 먹고, 너무 요동치는 종목은 손절선이 의미를 잃기 때문이다. 이 상하한 구조도 처음부터 설계한 게 아니라, 박스장에서 하한을 계속 내리다가 이번엔 정상 범위를 한참 벗어난 종목이 들어오면서 만들어졌다.\n한 가지 주의할 점은 ATR이 방향을 모른다는 것이다. 크게 오르든 크게 내리든 ATR은 똑같이 커진다. 그래서 ATR만으로는 아무 판단도 못 한다. 항상 방향 정보와 짝지어 써야 하는 보조 축이다.\n다른 트레일링 방식들과 나란히 고점을 따라가는 청산선을 만드는 방법은 여럿이다. 셋을 나란히 놓으면 각각이 무엇을 가정하는지 보인다.\n트레일링 방식 비교 퍼센트 트레일 청산선 = 최고가 × (1 − p) 가정: 되돌림의 크기가 가격에 비례한다 장점: 계산이 가장 단순. 파라미터 하나 단점: 종목의 변동성 차이를 무시한다 샹들리에 청산선 = 최고가 − ATR × k 가정: 되돌림의 크기가 그 종목의 변동성에 비례한다 장점: 종목마다 자동 조정. 대부분의 경우 퍼센트보다 합리적 단점: ATR 이 급변하면 청산선이 흔들린다 (잠금 필요) 파라볼릭 SAR 시간이 갈수록 가속해서 따라붙는다 가정: 추세가 오래될수록 끝날 확률이 높다 장점: 추세 말미에 빠르게 잠근다 단점: 가속 계수가 두 개라 과적합하기 쉽다 · 횡보장에서 최악 셋 다 '고점 대비 얼마' 라는 뼈대는 같다. 다른 건 그 '얼마' 를 무엇으로 재느냐 퍼센트=가격 · 샹들리에=변동성 · SAR=시간 마지막 줄이 이 코너에서 반복되는 이야기와 이어진다. 아룬 편에서 시간을 재는 지표의 특이함을 다뤘는데, SAR도 같은 계열이다. 무엇을 자로 삼느냐가 지표의 성격을 결정하고, 자가 다르면 못 보는 사각도 다르다.\nATR을 직접 구현할 때의 함정 샹들리에를 이식하려면 ATR부터 정확해야 한다. 여기서 값이 틀리면 청산선 전체가 틀어진다.\npython — ATR 과 청산선 def atr(df, n=22): pc = df[\"close\"].shift(1) tr = pd.concat([df[\"high\"]-df[\"low\"], (df[\"high\"]-pc).abs(), (df[\"low\"] -pc).abs()], axis=1).max(axis=1) return tr.ewm(alpha=1/n, adjust=False).mean() # 와일더 평활 ce_long = df[\"high\"].rolling(22).max() - atr(df,22) * 3.0 ce_long = ce_long.cummax() # 내려가지 않도록 잠금 # 함정 1: ATR 을 rolling().mean() 으로 구하면 원본과 다르다 — 와일더 평활이 맞다 # 함정 2: cummax() 는 포지션 단위로 리셋해야 한다 # 전체 시계열에 한 번 걸면 예전 포지션의 청산선이 계속 따라온다 # 함정 3: 최고가 창(22)과 ATR 창(22)을 꼭 같게 둘 이유는 없다 — 분리 가능한 축이다 함정 2가 실제로 잘 나는 버그다. 백테스트 코드를 벡터 연산으로 짜다 보면 포지션 경계를 잊기 쉽다. 그러면 지난달에 세워둔 청산선이 이번 달 포지션에 그대로 적용돼서, 현실에는 없는 방어선이 백테스트를 지켜준다. 결과가 실전보다 좋게 나오는 전형적인 경로다.\n함정 3은 튜닝 여지다. 최고가를 재는 창과 변동성을 재는 창을 분리하면 축이 하나 늘어난다. 축이 늘면 과적합 위험도 늘지만, 두 창이 서로 다른 역할을 한다는 점에서 분리할 이유는 있다.\n내 봇의 본전 사수와 비교하면 내 봇에도 트레일링 성격의 장치가 있다. 본전 사수라고 부르는 것인데, 구조가 샹들리에와 다르다.\n두 방식의 구조 비교 샹들리에 엑시트 기준점: 최고가 폭: ATR × 배수 (변동성에 따라 자동 조정) 성격: 연속적 — 고점이 오르면 계속 따라 오른다 내 봇의 본전 사수 (SAFE) 기준점: 진입가 구조: 무장선 +0.8% 도달 → 바닥 +0.4% 로 잠금 성격: 이단 — 한 번 무장되면 그 자리에 고정, 계속 따라가지 않는다 왜 이단 구조가 됐나 — 실측이 시켰다 본전 청산 27건 실측: 바닥을 +0.2% 에 뒀는데 평균 실현 +0.017% 원인은 슬리피지가 아니라 호가 관통 443원짜리 코인에서 호가 한 칸이 0.226% — 바닥선이 호가 사이 허공에 떴다 관통 0.183%p + 왕복 수수료 0.10% = 손익분기 0.283% → 바닥 0.4% 무장선도 함께 0.8% 로 올림 (간격이 좁으면 무장 즉시 청산돼 승자를 못 키운다) 60건 재현: 본전 손익 −33,203원 → +44,906원 이 비교에서 배울 게 있다. 샹들리에는 가격 공간이 연속적이라고 가정한다. 최고가에서 3×ATR 내려간 자리에 청산선을 두면 그 가격에 팔린다는 전제다. 그런데 실제 시장은 호가 단위로 끊겨 있다. 저가 코인에서는 그 간격이 0.2%를 넘기도 한다.\n퍼센트로 설계한 규칙이 호가라는 이산적인 세계와 충돌하는 지점이다. 이걸 겪고 나서 나는 아예 호가 한 칸이 가격의 0.15%를 넘는 종목은 진입을 금지하는 게이트를 만들었다. 겪고 보정하는 대신 안 겪는 쪽으로 구조를 바꾼 것이다.\n이익을 지키는 다른 방법 — 내 봇이 고른 길 트레일링이 안 맞는다면 이익은 어떻게 지키나. 내 봇이 도달한 답은 개별 종목이 아니라 계좌 단위로 수확하는 것이었다.\n포트폴리오 익절이라는 축 발상 전환 개별 종목 익절 → 계좌 합산 평가손익이 목표에 닿으면 전량 청산 왜 이 축으로 갔나 — 눌림목 진입 18건 실측 최대 평가이익 중앙값 +3.09% +3% 이상 도달 9/18 · +8% 이상 6/18 · +13% 이상 5/18 그 5건 중 2건은 −2% 를 먼저 맞고 죽었다 → 실효 3건 어떤 익절 수준을 골라도 결과 중앙값이 −2.100% 로 고정 즉 개별 익절 지점 선택이 결과를 못 바꾼다 손절 11건(61%) 이 전부였다 그래서 축을 바꿨다 현행: 계좌 합산 예상실현 2만원 도달 → 전량 수확, 스테이 30분 '예상실현' — 수수료·슬리피지를 뺀 뒤에도 2만원이 남는 시점 마지막 줄의 \u0026lsquo;예상실현\u0026rsquo;이 중요한 디테일이다. 처음엔 평가손익 기준으로 만들었는데, 평가 3만원에 팔면 실현은 3만원이 안 된다. 당연한 산수인데 임계값을 정할 때는 다들 평가 기준으로 생각한다.\n목표는 언제나 비용 후 기준으로 정의해야 한다. 이 원칙으로 시스템의 모든 임계값을 다시 감사해보면, 생각보다 많은 곳에서 비용이 무시되고 있다. 손익분기라고 믿었던 지점이 실제로는 적자인 채로.\n스테이 시간도 실측으로 정했다. 60분에서 30분으로 줄인 근거는 재현 결과였다 — 30분 8회 +209,203원, 60분 6회 +159,530원. 긴 대기는 회당 크기를 못 키우고 사이클 수만 깎았다. 이익 구간 체류시간 중앙값이 0분이라는 관찰과도 일치한다.\n이 설계가 샹들리에보다 우월하다는 뜻은 아니다. 이 전략의 이익 분포에 맞는 설계라는 뜻이다. 분포가 다르면 답도 달라진다.\n그래도 도입한다면 샹들리에 엑시트를 내 봇에 넣는다면 어디일까. 지금 전략에는 잘 안 맞지만, 조건이 바뀌면 얘기가 달라진다.\n도입 조건과 검증 설계 현행 전략에는 부적합 이익 구간 체류 중앙값 0분 · 계좌 합산 수확 · 슬롯 회전 빠름 → 트레일링이 작동할 시간 자체가 없다 맞는 조건 ① 보유 기간이 길어질 때 (스윙 전략으로 전환한다면) ② 큰 추세를 노리는 별도 슬롯을 둘 때 ③ 유동성 좋은 대형 종목만 다룰 때 (호가 관통 문제가 작아짐) 검증 설계 축: 기간 n {14, 22, 30} × 배수 {1.5, 2.0, 3.0} × 잠금 유무 대조군: 현행 고정 손절 −5% + 본전 사수 이단 구조 목적함수: 평균수익 · 승률 · 손절권 진입률 — 셋 동시 개선일 때만 채택 예상: 현행이 이길 가능성이 높다. 체류 시간 분포가 트레일링에 불리하다 그래도 돌려볼 가치는 있다 — 지면 '이 전략에 트레일링은 안 맞는다' 가 느낌이 아니라 측정으로 확정되기 때문이다 마지막 줄이 이 실험의 진짜 목적이다. 나는 이미 트레일링을 한 번 붙였다 뗐다. 포지션을 절반으로 나눠 한쪽은 규칙대로 확정하고 다른 쪽은 큰 상승을 기다리게 하는 설계였는데, 하루 만에 철회했다. 이 전략의 이익 분포에서 러너가 기다릴 큰 꼬리가 통계적으로 드물었기 때문이다.\n그때는 판단으로 접었다. 샹들리에 방식으로 다시 재보면, 같은 결론을 숫자로 확정하거나 아니면 내가 틀렸다는 걸 알게 된다. 어느 쪽이든 남는 장사다.\n트레일링이 정말 필요한가라는 질문 마지막으로 한 걸음 물러나서 생각해볼 게 있다. 트레일링 스톱이 항상 좋은 것인가.\n트레일링이 유리한 조건 · 불리한 조건 유리한 조건 · 이익 분포에 긴 꼬리가 있다 (크게 가는 건이 실제로 존재) · 보유 기간이 길다 (따라 올릴 시간이 있다) · 호가가 촘촘하다 (설계한 가격에 실제로 팔린다) 불리한 조건 · 이익이 짧게 왔다 사라진다 (체류 시간 중앙값이 0에 가깝다) · 회전이 빠르다 (트레일이 무장되기 전에 청산 사이클이 끝난다) · 저가·저유동성 종목을 다룬다 (호가 관통으로 설계가 무너진다) 내 봇은 오른쪽에 가깝다 — 그래서 트레일링 대신 다른 답을 골랐다 개별 트레일링 ✗ → 계좌 합산 목표 금액 도달 시 전량 수확 긴 대기 ✗ → 스테이 30분 (60분 대비 실측 우위 확인) 이건 트레일링이 나쁜 방법이라는 뜻이 아니라, 이 전략의 이익 분포와 안 맞는다는 뜻이다 트레일링 스톱은 트레이딩 교육에서 거의 무조건 좋은 것처럼 가르쳐진다. 이익을 지키면서 상승 여지를 남긴다는 서사가 매력적이기 때문이다. 그런데 그 서사가 성립하려면 상승 여지가 실제로 존재해야 한다. 이익이 오자마자 사라지는 분포에서는 지킬 이익도 남길 여지도 없다.\n그래서 트레일링을 붙일지 말지는 기법의 우열이 아니라 분포의 문제다. 내 이익 분포를 먼저 그려보고, 꼬리가 있으면 붙이고 없으면 안 붙이는 것. 순서가 이렇게 되어야 한다.\n배수를 정하는 다른 방법 배수를 3으로 할지 2로 할지 스윕으로 정하는 게 정석이지만, 표본이 부족할 때 쓸 수 있는 방법이 하나 더 있다. 되돌림 분포에서 역산하는 것이다.\n과거 데이터에서 상승 구간마다 \u0026ldquo;고점 찍고 얼마나 되돌렸다가 다시 갔는지\u0026quot;를 모은다. 그 되돌림 폭을 ATR로 나눠 분포를 만들면, 예컨대 중앙값이 1.2×ATR이고 상위 80퍼센타일이 2.5×ATR이라는 식의 값이 나온다. 그러면 배수를 2.5 근처로 두면 정상적인 되돌림의 80%는 견디고 나머지에서만 털린다는 계산이 선다.\n이 방법의 장점은 손익 결과가 아니라 가격의 구조에서 값을 뽑는다는 것이다. 손익 기반 스윕은 표본이 적으면 쉽게 과적합되는데, 되돌림 분포는 매매를 안 해도 캔들만 있으면 만들 수 있어서 표본이 훨씬 많다.\n물론 이렇게 정한 값도 결국 실제 매매로 검증해야 한다. 다만 스윕의 출발점을 어림짐작이 아니라 분포에서 가져온다는 것만으로도 탐색 범위가 크게 줄어든다.\n트레일링을 붙였다 뗀 실제 기록 이 얘기를 좀 더 구체적으로 적어두는 게 이 글에 도움이 될 것 같다. 나는 트레일링 성격의 설계를 실제로 넣었다가 하루 만에 뺐다.\n포지션 이분할 — 하루짜리 실험 설계 의도 포지션을 core / runner 로 이분할 core → 규칙대로 확정 (짧게) runner → 큰 상승을 기다린다 (트레일링) '확정이냐 꼬리냐' 의 긴장을 절반씩 갖는 절충안 함께 한 일 매수 체결액 기준으로 레그를 재분할 (전 경로 무결성 감사) 청산선 표시가 core 만 설명하던 문제도 같이 고침 다음 날 철회 이 전략의 이익 분포에서 러너가 기다릴 큰 꼬리가 통계적으로 드물었다 기다림의 비용(변동 노출)이 기대 이익을 넘었다 트레일을 TP 직전으로 당기고 손실 최소화 우선으로 정리 실험 비용: 이틀 · 얻은 것: '이 전략에 러너는 없다' 는 확인 이 실험을 실패라고 부르지 않는 이유가 있다. 붙였다 떼는 데 이틀이 걸렸다는 건 되돌리기 쉬운 구조를 갖고 있었다는 뜻이다.\n모듈이 분리돼 있고, 청산 로직이 한 곳에 모여 있고, git으로 이력이 남고, 기능이 스위치로 관리되기 때문에 가능했다. 단일 파일 시절이었다면 같은 실험에 훨씬 오래 걸렸을 것이고, 그러면 애초에 시도도 안 했을 것이다.\n되돌리기 쉬운 구조는 과감한 실험을 싸게 만든다. 실험 비용이 싸지면 아이디어를 아낄 필요가 없어진다. 아키텍처의 품질은 결국 실험의 가격으로 측정된다고 생각한다.\n샹들리에 엑시트를 지금 붙여본다면 아마 비슷한 결과가 나올 것이다. 그래도 재볼 가치가 있다고 앞에서 적은 이유는, 그때는 판단으로 접었지만 이번엔 숫자로 확정할 수 있기 때문이다. 판단과 측정은 결론이 같아도 무게가 다르다.\n숏 방향은 왜 안 다루나 공식에 숏 청산선도 있는데 이 글은 롱만 다뤘다. 이유는 단순하다. 업비트 현물만 거래하니 숏 포지션 자체가 없다.\n그런데 이 제약이 전략 설계에 생각보다 큰 영향을 준다.\n현물만 다룰 때의 구조적 제약 할 수 있는 선택 산다 · 안 산다 · 판다 할 수 없는 선택 하락에 베팅한다 여기서 나오는 결론 하락장에서 할 수 있는 최선은 '아무것도 안 하기' 다 그래서 레짐 차단이 그토록 중요해진다 BTC 4시간봉 하락장이면 신규 진입 자체를 안 하는 규칙 양방향 거래가 가능했다면 같은 상황에서 다른 답이 나왔을 것이다 하락장 = 숏 기회 → 차단이 아니라 방향 전환 즉 '안 사는 것도 포지션이다' 라는 말은 현물 계좌의 논리다 이 제약을 인정하고 나면 전략의 설계 공간이 좁아지는데, 나쁘기만 한 건 아니다. 선택지가 적으면 결정이 단순해지고, 단순한 시스템은 검증하기 쉽다.\n7주 연속 마이너스를 겪고 나서 내린 결론이 \u0026ldquo;하락장에는 안 산다\u0026quot;였는데, 이건 사실 굉장히 소극적인 대응이다. 양방향이 가능했다면 그 구간을 기회로 바꿀 수도 있었을 것이다. 다만 그러려면 숏의 리스크 관리를 새로 설계해야 하고, 그건 지금 봇을 통째로 다시 만드는 수준의 일이다.\n지금은 할 수 있는 범위에서 최선을 찾는 쪽을 택했다. 확장은 지금 것이 안정된 다음의 문제다.\n청산 규칙이 여럿일 때의 관리 방어선이 여섯 겹이라는 얘기를 했는데, 겹칠수록 생기는 문제가 있다. 어느 규칙이 실제로 발동했는지 추적해야 한다는 것이다.\n청산 사유를 기록해야 하는 이유 기록 안 하면 생기는 일 손익만 남고 '왜 팔았는지' 가 안 남는다 손절을 −5% 로 넓혔는데 성과가 나빠졌을 때 그게 손절 탓인지 본전 사수 탓인지 구분이 안 된다 기록하면 청산 사유별 손익 집계가 가능해진다 \"본전 사수로 나간 27건의 합계가 마이너스\" 같은 발견이 여기서 나온다 실제로 그 27건 실측이 호가 관통 문제를 찾아낸 출발점이었다 필요한 필드 청산 사유 · 진입가 · 청산가 · 보유 시간 · 그때의 지표값 그리고 진입 시점의 시장 레짐 본전 사수 27건 이야기가 좋은 예다. 전체 손익만 봤다면 그 장치가 적자를 내고 있다는 걸 몰랐을 것이다. 청산 사유별로 갈라 보니 \u0026ldquo;이익을 지키라고 만든 장치가 손실을 내고 있다\u0026quot;는 게 드러났고, 거기서 호가 관통이라는 원인까지 갔다.\n만약 샹들리에 엑시트를 추가한다면 청산 경로가 하나 더 늘어난다. 그러면 사유 코드도 하나 늘고, 집계 항목도 늘고, 서로 간섭하는 경우의 수도 는다. 이게 앞에서 \u0026ldquo;들어갈 자리가 없다\u0026quot;고 한 이유의 실무적 측면이다.\n새 장치를 넣는 비용은 그 장치의 구현 난이도가 아니라, 그 장치가 기존 체계와 만드는 상호작용의 수로 계산해야 한다.\n마무리 샹들리에 엑시트에서 가져갈 것은 공식이 아니라 관점의 전환이다. 손절선의 기준점을 진입가에서 최고가로 옮긴다는 발상 하나.\n진입가는 내가 산 자리일 뿐, 시장에는 아무 의미가 없는 숫자다. 시장이 아는 건 고점과 저점이다. 그런데도 대부분의 사람이 손절을 진입가 기준으로 잡는 건, 그게 자기 손익을 계산하기 편해서다. 계산의 편의가 방어선의 위치를 정하고 있는 셈이다.\n내 봇의 본전 사수도 여전히 진입가 기준이다. 실측으로 값을 정하긴 했지만 기준점 자체는 관성으로 남아 있다. 이 글을 쓰면서 그걸 알아챘고, 그게 오늘의 수확이다.\n덧붙임 — 방어 장치의 계보 이 봇의 청산·방어 장치가 어떤 순서로 쌓였는지 정리해두면, 왜 지금 트레일링이 안 맞는지가 더 분명해진다.\n방어선이 쌓여온 순서 개별 손절 가장 먼저. 한 번 제거했다가 복원한 이력 있음 58거래 실측으로 −2.5% 채택 → 이후 다층 방어 완성 후 −5% 로 확장 본전 사수 이익이 손실로 뒤집히는 것을 막는다 +1.5% → +0.5% → 무장 +0.8% / 바닥 +0.4% (호가 관통 실측 반영) 일일 손실 한도 하루에 얼마까지 잃으면 멈추나 주간 손실 한도 한 주 단위의 상한 자산 서킷브레이커 총자산이 특정 선 아래로 내려가면 정지 레짐 차단 BTC 4시간봉 하락장이면 신규 진입 자체를 안 함 여섯 겹. 각각 못 막는 상황이 다르다 손절폭을 −5% 까지 넓힐 수 있었던 건 나머지 다섯 겹이 있어서다 홑겹일 때의 −2% 와 여섯 겹일 때의 −5% 는 같은 말의 다른 문장이다 마지막 줄이 이 글 전체와 연결된다. 손절폭이든 트레일링이든, 하나만 떼어놓고 \u0026ldquo;이 값이 적당한가\u0026quot;를 논하는 건 공허하다. 방어 체계 전체의 함수로 봐야 한다.\n샹들리에 엑시트가 이 목록에 들어온다면 어느 자리일까. 본전 사수와 개별 손절 사이 어딘가일 것이다. 그런데 그 자리는 이미 두 장치가 나눠 맡고 있고, 셋이 겹치면 서로 간섭한다. 청산 경로가 늘어나면 손익이 어느 경로로 확정됐는지 귀속시키기도 어려워진다.\n그래서 결론은 같다. 좋은 지표라도 들어갈 자리가 없으면 안 넣는 게 맞다. 자리를 만들려면 기존 장치 하나를 빼야 하는데, 그건 별개의 실험이 필요한 일이다.\n","permalink":"https://botlab.co.kr/study/chandelier-exit/","summary":"진입가가 아니라 고점에서 손절선을 내려 긋는다. 내 봇의 본전 사수와 무엇이 다른지 붙여봤다.","title":"Chandelier Exit — 천장에 매달아 놓고 따라 올리는 손절선 | Crypto Trading Bot"},{"content":"왜 코인 봇 블로그에서 주식 차트를 보나 이 랩은 업비트 크립토 트레이딩 봇 기록이다. 그런데 차트 패턴은 자산을 안 가린다. 사람이 모여서 값을 매기는 곳이면 비슷한 모양이 반복된다 — 문제는 그 모양을 코드로 옮길 수 있느냐다.\n그동안 RSI, 볼린저, 아룬 같은 수식으로 계산되는 지표를 다뤘다. 이번엔 성격이 다른 걸 해본다. 헤드 앤 숄더, 플래그, 이중 천장 같은 눈으로 보는 패턴이다. 이런 건 사람이 차트를 보면 \u0026ldquo;아 저거네\u0026rdquo; 하는데, 봇에게 시키려면 어깨가 뭔지 넥라인이 어딘지를 숫자로 정의해야 한다.\n그 간극을 확인하기에 2026년 6~8월 삼성전자만 한 표본이 없다. 50거래일 만에 고점 대비 반토막이 났다가 45% 되돌아왔다. 교과서 패턴이 성립하기도 하고 처참하게 깨지기도 한 구간이다.\n이 글의 데이터 범위 종목 삼성전자 (005930) 구간 2026-06-05 ~ 2026-08-14 · 50 거래일 출처 stockanalysis.com 일봉 (2026-08-14 조회) -------------------------------------------------- 기간 최고 374,500원 (6/19 고가) 기간 최저 189,200원 (7/29 저가) 최종 종가 274,500원 (8/14) -------------------------------------------------- 고점 → 저점 −49.48% 저점 → 현재 +45.08% 고점 대비 현재 −26.70% 숫자부터 밝혀둔다. 이 글의 모든 가격은 위 출처의 일봉이고, 내가 만들어낸 값은 하나도 없다. 계산식이 들어간 곳은 식을 같이 적었다.\n이 글은 차트 패턴을 코드로 옮기는 방법을 다루는 학습 기록이다. 특정 종목의 매수·매도 권유가 아니다.\n1. 먼저 무슨 일이 있었나 패턴 얘기를 하기 전에 배경을 짚어야 한다. 이 구간의 모양은 개별 종목 사정이 아니라 시장 전체 사건에서 나왔다.\n7월 말에 국내 증시가 이틀 만에 무너졌다. 공개된 지수 수치는 이렇다.\n2026년 7월 말 코스피 (출처: 리치 플랫폼) 7/27 6,755.75 7/28 6,023.66 −10.84% 7/29 5,663.24 −5.98% -------------------------------------------------- 이틀 누적 약 −16.2% · 서킷브레이커 발동 -------------------------------------------------- 8/14 6,923.87 +1.62% (출처: stockinfo7) 외국인 순매수 10,992억 · 개인 순매도 6,816억 원인으로 지목된 건 하나가 아니라 다섯 가지가 얽힌 구조였다 — 지수 내 반도체 대형주 쏠림, AI 투자의 순환 출자 구조에 대한 의심, SK하이닉스가 사상 최대 실적을 냈는데도 눈높이에 못 미친 것, ADR 전환 일정에 따른 유동성 불확실성, 그리고 반대매매와 레버리지 ETF 리밸런싱이 만든 기계적 매도.\n마지막 항목이 차트 관점에서 중요하다. 반대매매는 가격을 안 본다. 담보 비율이 깨지면 시장가로 던진다. 이때 만들어지는 캔들은 수급이 아니라 강제 청산의 흔적이고, 그래서 교과서 패턴이 잘 안 먹는다.\n삼성전자 · 이 구간의 극단값 최대 하락일 7/28 −13.39% 종가 220,000 최저가 7/29 189,200 거래량 61,720,190주 (기간 최대) 최대 상승일 7/31 +26.81% 종가 262,500 -------------------------------------------------- 이틀 만에 −13% → 다음날 저가 −9% → 그 이틀 뒤 +27% 이런 구간에서 \"종가 기준 진입\" 은 사실상 무의미하다 7월 29일 거래량 6,172만 주는 기간 평균의 두 배가 넘는다. 그리고 그날이 저점이었다. 이걸 뭐라고 부르느냐가 이 글의 출발점이다.\n2. 캡처한 아홉 개 패턴, 하나씩 대본다 받은 패턴 목록은 이렇다. 각각을 삼성전자 실제 데이터에 대보고, 성립하면 근거를, 안 되면 왜 안 되는지를 적는다. 억지로 끼워맞추면 그 순간 이 글은 쓸모가 없어진다.\n9개 패턴 판정 (삼성전자 · 2026-06-05 ~ 08-14) ✔ 약세 플래그 7/20~7/23 교과서적으로 성립 ✔ 역 헤드 앤 숄더 7/20~8/13 넥라인 돌파까지 완성 △ 강세 플래그 7/31~8/12 성립하나 되돌림이 깊다 ✘ 이중 바닥 두 저점이 20% 차이 — 아님 ✘ 이중 천장 봉우리가 하나뿐 ✘ 헤드 앤 숄더(정방향) 왼쪽 어깨에 해당하는 고점 없음 ✘ 컵 앤 핸들 바닥이 U자가 아니라 V자 ✘ 인버티드 컵 앤 핸들 해당 구간 없음 △ 엘리어트 파동 셀 수는 있으나 사후 해석 — 뒤에서 따로 아홉 개 중 확실히 성립하는 건 둘, 조건부가 둘, 나머지 다섯은 없다. 이 비율이 정상이다. 패턴 책을 펴놓고 차트를 보면 다 보이는 것 같지만, 기준을 숫자로 고정하는 순간 대부분 탈락한다.\n3. 약세 플래그 — 이 구간에서 가장 깨끗했던 패턴 먼저 성립한 것부터. 7월 중순 하락 구간에 약세 플래그가 하나 있다.\n약세 플래그 구성 요소 (실측) 깃대(flagpole) 7/15 고가 284,500 → 7/20 저가 240,000 3 거래일 만에 −15.6% 깃발(flag) 7/21 종가 259,000 → 7/22 260,500 → 7/23 270,000 완만한 우상향 · 낙폭의 약 2/3 되돌림\n이탈 7/24 종가 249,500 → 7/28 220,000 → 7/29 저가 189,200 \u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026ndash; 급락 → 좁은 되돌림 → 같은 방향 재이탈. 정의 그대로다\n약세 플래그의 논리는 단순하다. 급락 뒤 반등은 매수세가 아니라 숏 커버링과 저가 매수의 일시적 균형이고, 그 균형이 풀리면 원래 방향으로 간다는 것. 되돌림 구간의 거래량이 깃대보다 적으면 신뢰도가 올라간다고들 한다.\n여기서 실제로 확인해보자.\n깃대 vs 깃발 거래량 (실측) 깃대 구간 7/15 24,247,740 7/16 26,605,500 7/20 24,304,800 평균 25,052,680 깃발 구간 7/21 20,353,870 7/22 21,248,990 7/23 15,996,110 평균 19,199,657 \u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026ndash; 깃발 거래량이 깃대의 76.6% — 교과서가 말하는 \u0026ldquo;감소\u0026rdquo; 조건 충족 특히 7/23은 기간 최저 거래량. 되돌림의 힘이 빠진 자리였다\n7월 23일 거래량 1,599만 주는 이 50거래일 중 가장 적다. 가격은 3일째 오르는데 거래량은 바닥이었다. 그 다음날부터 무너졌다.\n이걸 코드로 옮기면 문제는 여기서 시작된다. 사람 눈에는 명확한데, 봇에게 시키려면 이렇게 정의해야 한다.\n약세 플래그를 규칙으로 쓰면 1. 깃대: 최근 N봉 안에 X% 이상 하락 N? X? 2. 깃발: 이후 M봉이 좁은 상승 채널 M? 채널 폭 허용치? 3. 거래량: 깃발 평균 \u0026lt; 깃대 평균 이건 명확 4. 진입: 깃발 하단 이탈 시 숏 \"이탈\" 을 종가로? 장중으로? -------------------------------------------------- 자유 파라미터 5개. 각각을 정하는 순간 과최적화가 시작된다 RSI 는 파라미터가 기간 1개, 문턱 1개였다 내 봇의 RSI 게이트는 조절할 값이 둘이다. 기간과 문턱. 그래서 8.9일치 매매기록을 전수로 훑어 45라는 숫자를 뽑을 수 있었다. 플래그는 자유도가 다섯이다. 같은 데이터로 다섯 개를 동시에 맞추면, 그건 최적화가 아니라 과거에 곡선을 그려 넣는 일이다.\n3시대 모놀리스 시절에 이걸 몸으로 배웠다. 지표를 늘리면 판단이 좋아지는 게 아니라 설정의 복잡도가 먼저 는다.\n4. 역 헤드 앤 숄더 — 이 구간의 주인공 이 글에서 가장 흥미로운 발견이다. 7월 말 폭락과 8월 반등이 완성된 역 헤드 앤 숄더를 만들었다.\n역 헤드 앤 숄더 구성 요소 (실측) 왼쪽 어깨 7/20 저가 240,000 머리 7/29 저가 189,200 ← 기간 최저 · 최대 거래량 오른쪽 어깨 8/11 저가 227,500 넥라인 7/31 고가 267,000 -------------------------------------------------- 돌파 8/13 종가 268,000 넥라인 위 마감 8/14 종가 274,500 추가 상승 확인 -------------------------------------------------- 어깨 비대칭 왼쪽 240,000 vs 오른쪽 227,500 (5.2% 차이) 시간 간격 어깨→머리 6봉 · 머리→어깨 8봉 (비교적 대칭) 교과서적으로 꽤 잘 맞는다. 어깨 높이가 5.2% 차이 나는 건 허용 범위로 보는 게 일반적이고, 시간 간격도 6봉 대 8봉이면 균형이 나쁘지 않다. 머리 자리에서 거래량이 기간 최대였다는 것도 정석이다 — 항복 매물이 나온 자리가 머리라는 게 이 패턴의 논리니까.\n계측 목표라는 것 이 패턴에는 목표가를 뽑는 공식이 붙어 있다.\n계측 목표(measured move) 계산 머리 깊이 = 넥라인 − 머리 = 267,000 − 189,200 = 77,800 목표 = 넥라인 + 머리 깊이 = 267,000 + 77,800 = 344,800 \u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026ndash; 8/14 종가 274,500 기준 +25.6% 지점 기간 최고 374,500 대비로는 아직 −7.9% 자리 \u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026ndash; ※ 이건 예측이 아니라 패턴의 산술이다. 맞을 이유가 없다\n이 숫자를 어떻게 대해야 하는지가 중요하다. 344,800은 전망이 아니다. 그냥 패턴 정의에 딸린 산술일 뿐이고, 도달할 근거는 어디에도 없다. 다음 글에서 SK하이닉스 사례를 보면 이 목표치가 정확히 맞았다가 곧바로 크게 깨지는 장면이 나온다.\n내가 이런 숫자를 다룰 때 쓰는 기준은 하나다. 검증할 수 있느냐. 손절 −2.5%는 실측 58건을 다시 계산해서 기대값 +0.209%를 확인하고 채택했다. 344,800은 그런 검증이 불가능하다. 표본이 1건이니까.\n코드로 옮길 때의 진짜 난관 역 헤드 앤 숄더를 봇에 넣으려면 어깨를 찾아야 한다. 어깨는 스윙 저점이고, 스윙 저점은 이렇게 정의한다.\n스윙 저점 탐지 — k값에 따라 결과가 바뀐다 정의: 좌우 k봉보다 저가가 낮으면 스윙 저점 -------------------------------------------------- k=3 6/11 · 6/23 · 7/02 · 7/20 · 7/29 · 8/11 6개 → 7/20, 7/29, 8/11 이 잡힌다 (어깨·머리·어깨) k를 키우면 어깨가 사라진다 k를 줄이면 노이즈 저점이 어깨로 잡힌다 \u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026ndash; 즉 \u0026ldquo;패턴이 있다\u0026rdquo; 는 결론 자체가 k에 종속된다 k=3 을 고른 근거? 없다. 그게 예쁘게 나와서다\n이게 시각적 패턴의 근본 문제다. 패턴을 찾는 파라미터를 내가 고르고, 그 파라미터가 패턴의 존재 여부를 결정한다. RSI는 안 그렇다. 기간 14로 계산한 RSI 값은 누가 계산해도 같다. 문턱을 어디 두느냐는 다툴 수 있지만 값 자체는 객관적이다.\n5시대에 앵커드 VWAP을 봇에서 뺀 이유와 정확히 같은 문제다. 사람이 앵커를 찍으면 강력한데, 앵커 선택 규칙을 만드는 순간 그 규칙이 또 하나의 튜닝 대상이 된다. 좋은 지표와 자동화하기 좋은 지표는 다르다.\n5. 강세 플래그 — 성립하지만 교과서보다 깊다 8월 반등 구간에도 플래그가 있다. 다만 정직하게 말하면 교과서보다 헐겁다.\n강세 플래그 · 되돌림 비율 점검 깃대 7/30 종가 207,000 → 7/31 종가 262,500 (+26.81%) 깃대 폭 = 55,500 깃발 8/03 종가 239,500 → 8/11 종가 230,000 8/11 저가 227,500 \u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026ndash; 되돌림 = (262,500 − 227,500) ÷ 55,500 = 63.1% \u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026ndash; 교과서는 보통 38~50% 되돌림을 플래그로 본다 63%면 \u0026ldquo;깃발\u0026rdquo; 보다 \u0026ldquo;조정\u0026rdquo; 에 가깝다 — 나는 △로 판정했다\n돌파 8/12 종가 255,500 → 8/13 268,000 → 8/14 274,500\n결과만 보면 돌파했으니 맞은 패턴이다. 그런데 결과로 패턴을 판정하면 아무것도 못 배운다. 되돌림이 63%까지 갔다는 건, 그 시점에 실시간으로 보고 있었다면 \u0026ldquo;플래그가 깨졌다\u0026quot;고 판단했을 가능성이 높다는 뜻이다.\n이게 사후 판정의 함정이다. 차트를 다 그려놓고 보면 8월 3~11일이 예쁜 눌림으로 보이는데, 8월 11일 장중에는 그냥 계속 흘러내리는 차트였다.\n8/3 ~ 8/11 을 실시간으로 봤다면 8/03 239,500 전일 262,500 대비 −8.8% 8/04 240,000 8/05 246,000 반등하나? 8/06 230,500 −6.3% 8/07 231,000 8/10 230,000 7/31 급등분의 절반 반납 8/11 239,500 장중 저가 227,500 찍고 마감은 +4.1% -------------------------------------------------- 6거래일 연속 하락 압박. \"깃발\" 이라고 부를 배짱이 있었을까 8/11 의 아래꼬리(227,500 → 239,500)가 사실상 유일한 신호였다 6. 성립하지 않는 것들 — 여기가 더 중요하다 패턴 글은 대개 맞은 것만 보여준다. 안 맞은 걸 적어야 기준이 생긴다.\n이중 바닥이 아니다 7월 29일 저점과 8월 11일 저점을 보고 이중 바닥이라 부르고 싶어진다. 숫자를 보면 아니다.\n이중 바닥 판정 첫 저점 7/29 189,200 둘째 저점 8/11 227,500 -------------------------------------------------- 차이 = (227,500 − 189,200) ÷ 189,200 = +20.2% -------------------------------------------------- 이중 바닥은 두 저점이 대체로 3% 이내여야 한다는 게 통설 20%면 같은 바닥이 아니라 그냥 \"더 높은 저점\" 이다 → 이건 이중 바닥이 아니라 역 H\u0026S 의 오른쪽 어깨다 같은 두 점을 놓고 \u0026ldquo;이중 바닥\u0026quot;이라 부르면 틀리고 \u0026ldquo;오른쪽 어깨\u0026quot;라 부르면 맞다. 패턴 이름은 데이터가 정하는 게 아니라 내가 고르는 것이라는 게 여기서 드러난다. 위험한 성질이다.\n컵 앤 핸들도 아니다 컵 앤 핸들은 완만한 U자 바닥 뒤에 작은 손잡이가 붙는 모양이다. 이 구간의 바닥은 U가 아니다.\n바닥 모양 판정 7/27 254,000 7/28 220,000 −13.39% 7/29 208,500 저가 189,200 7/30 207,000 7/31 262,500 +26.81% -------------------------------------------------- 바닥 체류 3거래일. 낙하 2일 · 반등 1일 U자가 아니라 완벽한 V자다 → 컵 앤 핸들 불성립 V자 바닥은 \"축적\" 이 없다는 뜻 — 컵의 논리와 정반대 컵 앤 핸들의 논리는 긴 바닥에서 매물이 소화된다는 것이다. 3일 만에 튀어 오른 바닥에는 소화할 시간이 없었다. 모양이 안 맞으면 논리도 안 맞는다.\n정방향 헤드 앤 숄더와 이중 천장도 없다 고점 쪽을 보면 6월 19일 374,500이 유일한 봉우리다. 왼쪽 어깨에 해당할 만한 직전 고점이 이 구간 안에 없고, 6월 25일 362,500은 봉우리라기보다 하락 과정의 반등이다. 두 번째 봉우리가 없으니 이중 천장도 성립하지 않는다.\n참고로 다음 글의 SK하이닉스는 여기가 완전히 다르다. 같은 시장, 같은 기간인데 하이닉스에는 교과서적인 이중 천장이 있다. 종목마다 모양이 다르다는 게 패턴 분석의 재미이자 함정이다.\n7. 엘리어트 파동 — 셀 수는 있는데 받은 목록에 엘리어트가 있으니 짚고 넘어간다. 이 랩에는 이미 엘리어트를 다룬 글이 있고, 결론은 \u0026ldquo;봇에 넣을 수 없다\u0026quot;였다. 이번 데이터로 다시 확인해보자.\n하락 5파로 세어보면 ① 6/19 374,500 → 7/02 종가 286,000 ② 7/02 → 7/06 종가 318,000 되돌림 ③ 7/06 → 7/20 종가 244,000 가장 긴 파동 ④ 7/20 → 7/23 종가 270,000 되돌림 ⑤ 7/23 → 7/29 저가 189,200 최종 하락 -------------------------------------------------- 그럴듯하다. ③이 가장 길다는 규칙도 지켜진다 문제: 이 파동 구분을 7/29 이전에 할 수 있었나? ⑤가 끝났는지 ⑤ 안의 소파동인지는 끝나야 안다 파동 카운팅의 문제는 끝나야 셀 수 있다는 것이다. ⑤파가 189,200에서 끝났다는 건 지금 뒤돌아보니까 아는 것이고, 7월 30일 시점에는 ⑤파가 더 이어질지 ⑥파는 없으니 반등할지 알 수 없었다.\n그래서 이 랩의 판정은 여전히 같다. 엘리어트는 해석 도구지 신호 도구가 아니다. 봇에게 시킬 수 있는 건 \u0026ldquo;지금이 몇 파인가\u0026quot;가 아니라 \u0026ldquo;지금 RSI가 45 이하인가\u0026rdquo; 같은 질문뿐이다.\n8. 낙폭·회복 곡선과 항복의 흔적 패턴에서 잠깐 떨어져서, 이 구간의 성격을 두 장으로 보자.\n기간 최고가를 0%로 놓고 종가를 환산한 그림이다. 삼성전자는 −47.9%까지 갔다가 −26.7%로 올라왔고, SK하이닉스는 더 깊이 갔다가 덜 올라왔다. 같은 사이클인데 진폭이 다르다.\n이 산점도가 개인적으로는 가장 실용적이다. 가로축이 거래량, 세로축이 등락률인데 평균 거래량 오른쪽에 큰 하락과 큰 반등이 몰려 있다.\n거래량 상위 4일의 등락률 7/29 61,720,190주 −5.23% 저가 189,200 (기간 최저) 7/31 57,878,850주 +26.81% 최대 상승일 7/30 46,149,140주 −0.72% 6/24 47,809,959주 +9.84% -------------------------------------------------- 기간 평균 거래량 30,236,517주 -------------------------------------------------- 바닥과 최대 반등이 거래량 1·2위 날에 함께 있었다 다만 이건 사후 관찰이다 — 7/29 당시엔 그냥 폭락일이었다 \u0026ldquo;거래량 터진 날이 바닥\u0026quot;이라는 말은 맞기도 하고 틀리기도 하다. 여기서는 맞았다. 그런데 7월 28일에도 4,035만 주가 터졌고 그날은 바닥이 아니었다. 거래량 급증은 전환의 필요조건일 수는 있어도 충분조건이 아니다.\n내 봇이 거래량을 게이트로 쓰는 방식도 이래서 소극적이다. \u0026ldquo;거래량이 터지면 산다\u0026quot;가 아니라 **\u0026ldquo;거래량이 너무 없으면 안 산다\u0026rdquo;**로 쓴다. 사도 못 파는 종목을 거르는 용도지 신호가 아니다.\n9. 그래서 봇에 넣을 것인가 정리하면 이렇다.\n시각적 패턴 vs 계산 지표 — 자동화 관점 차트 패턴 계산 지표(RSI 등) 값의 객관성 탐지 파라미터 종속 누가 계산해도 동일 자유 파라미터 4~6개 1~2개 실시간 판정 완성돼야 안다 매 봉 계산 가능 검증 가능성 표본이 극히 적다 전 구간 재계산 가능 사후 편향 매우 큼 작음 -------------------------------------------------- 결론: 패턴은 읽는 데 쓰고, 규칙은 계산 지표로 만든다 안 넣는다. 다만 버리지도 않는다.\n차트 패턴이 쓸모없다는 게 아니다. 이 구간을 패턴 언어로 읽으니 \u0026ldquo;7월 말에 항복이 나왔고 8월에 어깨를 만들며 넥라인을 넘었다\u0026quot;는 서술이 가능해졌다. 이건 \u0026ldquo;RSI가 30 아래로 갔다가 50을 회복했다\u0026quot;보다 훨씬 많은 걸 담는다.\n문제는 그 서술을 사전에 할 수 있느냐다. 못 한다. 그래서 패턴은 사후 복기와 시장 이해에 쓰고, 진입 규칙은 계산 가능한 지표로 만든다. 역할을 나누는 것이다.\n4시대 병렬 실험기에서 배운 게 이거였다. 좋은 아이디어 두 개를 한 계좌에 넣으면 둘 다 나빠질 수 있다. 한 봇은 한 성격만. 패턴 인식과 게이트 판정은 성격이 다른 일이고, 섞으면 어느 쪽이 틀렸는지 알 수 없게 된다.\n10. 남기고 싶은 세 문장 첫째, 안 맞는 패턴을 세는 게 맞는 패턴을 찾는 것보다 유용하다. 아홉 개 중 다섯은 없었다. 이 비율을 알고 나면 \u0026ldquo;차트에 패턴이 보인다\u0026quot;는 말이 얼마나 헐거운지 감이 온다.\n둘째, 패턴의 존재 여부가 탐지 파라미터에 종속된다면 그건 관찰이 아니라 선택이다. k=3으로 스윙을 잡으니 어깨가 셋 나왔다. k를 바꾸면 결론이 바뀐다. 값 자체가 객관적인 지표와는 다른 종류의 물건이다.\n셋째, 계측 목표는 예측이 아니라 산술이다. 344,800이라는 숫자에 근거가 있다고 믿는 순간 위험해진다. 다음 글에서 이 숫자가 정확히 맞았다가 곧바로 크게 깨지는 실제 사례를 보게 된다.\n시장이 틀린 게 아니라 사람이 틀린 거라는 제시 리버모어의 말을 차트 패턴에 옮기면 이렇게 바꿔 말하고 싶다 — 패턴이 틀린 게 아니라, 패턴을 찾는 기준을 내가 정했다는 걸 잊은 게 틀린 거다.\n이 글은 개인의 학습 기록이며 투자 권유가 아닙니다. 모든 가격은 stockanalysis.com 일봉(2026-08-14 조회) 기준이며, 지수 수치의 출처는 본문에 표기했습니다.\n","permalink":"https://botlab.co.kr/study/samsung-chart-patterns/","summary":"교과서 패턴 아홉 개를 삼성전자 실제 일봉에 대봤다. 성립한 건 셋, 안 되는 건 안 된다고 적었다.","title":"Chart Patterns — 삼성전자 반토막과 역 헤드 앤 숄더 | Crypto Trading Bot"},{"content":"손으로 긋는 선의 문제 차트에 수평선을 긋는 건 기술적 분석의 기본기다. 여기가 지지, 저기가 저항. 그런데 이걸 봇에 넣으려고 하면 곧바로 막힌다. 어디에 그을지를 정하는 규칙이 사람 머릿속에만 있기 때문이다.\n피보나치 편에서 같은 문제를 봤다. 열 명에게 같은 차트를 주면 열 개의 다른 선이 나오고, 맞은 사람은 지표가 통했다고 하고 틀린 사람은 선을 잘못 그었다고 한다. 이론은 영원히 무죄다.\n오토 키 레벨은 이 도피처를 없애는 도구다. 선을 긋는 규칙을 코드로 못 박는 것. 정확도가 사람보다 높아서가 아니라, 재현이 되기 때문에 가치가 있다.\n지지·저항이 작동한다는 말의 의미 레벨을 자동으로 뽑는 방법을 보기 전에, 애초에 이게 왜 작동하는지를 짚고 가자. 근거를 알아야 어떤 방식으로 뽑을지 정할 수 있다.\n지지선이 지지선인 이유는 그 가격에 주문이 실제로 쌓여 있기 때문이다. 신비로운 힘이 아니라 호가창의 물량이다. 그럼 왜 특정 가격에 주문이 쌓이나.\n주문이 특정 가격에 뭉치는 이유 ① 본전 심리 그 가격에 산 사람들이 본전에 팔려고 매도 주문을 걸어둔다 → 과거에 거래가 많았던 가격대가 저항이 된다 ② 재진입 심리 그 가격에 판 사람들이 다시 사려고 매수 주문을 걸어둔다 → 같은 가격대가 지지로도 작동한다 ③ 관측의 집중 많은 사람이 같은 차트 도구로 같은 선을 본다 → 근거가 약해도 주문이 모이면 실재가 된다 (피보나치·라운드 넘버) ④ 알고리즘 자동매매 프로그램들이 비슷한 조건에 주문을 낸다 ①②는 실제 거래 이력에서 나온다 → 거래량 밀집 방식이 이걸 잡는다 ③④는 관측 도구에서 나온다 → 피벗·라운드 넘버가 이걸 잡는다 둘은 성격이 다르므로 검증도 따로 해야 한다 이 구분이 중요한 이유는 시장 상황에 따라 어느 쪽이 유효한지가 달라질 수 있기 때문이다. 참여자가 적은 종목에서는 ③번이 약하고, 신규 상장 종목에서는 ①②가 아예 없다. 거래 이력이 없으니 물린 사람도 없다.\n내 봇이 신규 상장 종목을 유니버스에서 거르는 이유 중 하나가 이것이다. 지표 워밍업이 안 된다는 기술적 문제가 표면적 이유인데, 더 근본적으로는 가격 구조가 아직 형성되지 않았기 때문이다. 지지도 저항도 없는 가격대에서 눌림목을 논하는 건 의미가 없다.\n후보를 만드는 네 가지 방법 레벨을 자동으로 뽑는 방식은 크게 넷이다. 각각 다른 종류의 \u0026ldquo;중요함\u0026quot;을 잡는다.\n레벨 후보 생성 방식 ① 스윙 극값 좌우 n봉보다 높은(낮은) 봉 = 프랙탈 고점·저점 장점: 직관적. 사람이 눈으로 찍는 자리와 가장 비슷 단점: n봉 뒤에야 확정된다 — 실시간에는 최근 구간이 늘 비어 있다 ② 기간 극값 전일·전주·전월의 고가·저가 장점: 확정 시점이 명확하고 계산이 단순. 참여자 다수가 같은 선을 본다 단점: 기간 경계가 임의적 (코인 시장에 '하루' 의 경계가 어디인가) ③ 피벗 포인트 (고+저+종)/3 에서 산술적으로 파생 장점: 정확히 재현된다. 계산에 판단이 안 들어감 단점: 근거가 계산일 뿐 — 시장에 그 자리가 있어야 할 이유가 없다 ④ 거래량 밀집 가격대별 거래량이 몰린 구간 장점: 실제로 손이 바뀐 자리 — 넷 중 근거가 가장 물질적 단점: 데이터가 무겁다. 체결 단위 데이터가 필요 내가 제일 신뢰하는 건 ④다. 앞의 셋은 가격의 모양에서 뽑은 것이고, ④만이 실제로 거래가 일어난 사실에서 뽑은 것이기 때문이다. 지지선이 작동하는 메커니즘을 생각해보면 이유가 분명하다. 그 가격에 물린 사람이 본전에 팔려 하고, 그 가격에 판 사람이 되사려 한다. 둘 다 \u0026ldquo;거기서 실제로 거래가 있었다\u0026quot;를 전제로 한다.\n뭉치기 — 진짜 기술은 여기 있다 후보를 뽑고 나면 문제가 생긴다. 너무 많다. 스윙 극값만 해도 몇십 개가 나오고, 그중 상당수는 서로 몇 원 차이로 붙어 있다.\n클러스터링 — 붙은 선을 하나로 원본 후보 (예시) 1,247 · 1,251 · 1,253 · 1,289 · 1,340 · 1,344 · 1,410 … 문제: 1,247/1,251/1,253 은 사실상 같은 자리다 뭉치는 기준을 무엇으로 잡을 것인가 ✗ 절대 금액 (예: 5원 이내) 4억짜리 BTC 와 200원짜리 코인에 같은 자를 댈 수 없다 △ 가격 비율 (예: 0.3% 이내) 종목 간 비교는 되지만 변동성 차이를 무시한다 ✓ ATR 배수 (예: 0.5 × ATR 이내) 변동성이 큰 종목은 넓게, 작은 종목은 좁게 — 자동으로 조정된다 뭉친 뒤 대표값: 단순 평균보다 '후보 개수로 가중한 평균' 이 낫다 여러 번 반복해서 찍힌 자리가 클러스터 중심에 가까워진다 ATR 기준을 쓰는 건 내 봇의 다른 곳에서도 반복되는 패턴이다. 변동성으로 정규화하면 종목마다 다른 자를 대는 문제가 사라진다. 반대로 절대 금액이나 고정 퍼센트로 만든 기준은 언젠가 반드시 특정 가격대의 종목을 차별한다.\n이건 경험에서 나온 감각이다. 거래량 게이트를 절대 거래대금으로 판정하다가 \u0026ldquo;게이트가 종목을 거르는 게 아니라 체급을 차별한다\u0026quot;는 지적을 받고 상대 배수 기준으로 다시 만든 적이 있다. 필터의 편향은 그대로 포트폴리오의 편향이 된다.\n거래량 프로파일 — 가장 근거가 튼튼한 방식 앞에서 네 가지 방식 중 ④번(거래량 밀집)을 가장 신뢰한다고 했다. 이건 좀 더 자세히 볼 가치가 있다.\n거래량 프로파일은 시간이 아니라 가격을 축으로 거래량을 쌓는 것이다. 일반 거래량 막대는 가로축이 시간인데, 이건 세로축이 가격이다.\n거래량 프로파일의 모양 가격대별로 거래량을 누적하면 1,300원 ██ 거래 적음 1,250원 ████ 1,200원 ████████████████████ ← 최대 거래 가격대 (POC) 1,150원 ██████████████ 1,100원 █████ 1,050원 ██ POC(Point of Control) = 가장 많이 거래된 가격 해석: 여기서 가장 많은 손이 바뀌었다 → 물린 사람도 가장 많다 가격이 돌아오면 매물이 나올 가능성이 크다 밸류 영역 = 전체 거래량의 약 70% 가 몰린 가격 구간 이 구간 밖은 '거래가 드물었던 가격' — 빠르게 통과하는 경향 구현할 때 필요한 데이터가 문제다. 정확한 거래량 프로파일을 만들려면 체결 단위 데이터가 필요한데, 그건 양이 많고 보관도 부담스럽다.\n실무적 타협안은 봉 데이터로 근사하는 것이다. 각 봉의 거래량을 그 봉의 고가-저가 구간에 균등 분배하거나, 종가 기준으로 한 칸에 몰아넣는 방식. 정확도는 떨어지지만 대략의 밀집 구간은 잡힌다.\n봉 데이터로 근사하기 가격 구간(bin)을 정한다 — 예: ATR 의 0.2배 단위 각 봉에 대해 해당 봉의 고가~저가에 걸친 bin 들에 거래량을 분배 단순 균등 분배 또는 종가에 가중 누적한 뒤 상위 N개 bin 을 레벨 후보로 한계 1: 봉 안에서 실제로 어디에 거래가 몰렸는지는 모른다 한계 2: 오래된 거래에 어떤 가중을 줄지 정해야 한다 최근 거래가 더 중요하다면 시간 감쇠를 넣는다 완벽하진 않지만 스윙 극값보다 근거가 튼튼하다는 점은 유지된다 한계 2가 설계 판단이 필요한 지점이다. 한 달 전에 크게 거래된 가격대와 어제 크게 거래된 가격대 중 어느 쪽이 더 강한 레벨일까. 직관적으로는 최근이지만, 오래된 매물대가 더 무겁다는 관점도 있다. 오래 물려 있을수록 본전 심리가 강해진다는 논리다.\n답은 재봐야 안다. 그리고 이 질문이 재볼 수 있는 형태라는 게 자동화의 이점이다. 손으로 선을 그으면 \u0026ldquo;이 선이 왜 중요한가\u0026quot;에 대한 답이 감이지만, 코드로 만들면 감쇠 계수를 바꿔가며 비교할 수 있다.\n그다음 — 점수 매기기 뭉치고 나서도 레벨이 열 개쯤 남는다. 전부 똑같이 중요할 리는 없다. 그래서 점수를 매긴다.\n레벨 강도 점수 — 무엇으로 매길까 터치 횟수 그 자리에서 몇 번이나 반응했나 → 많을수록 강함 최근성 마지막 터치가 얼마나 최근인가 → 오래된 선은 흐려진다 시간축 등급 일봉 레벨 \u003e 15분봉 레벨 → 상위 TF 가 무겁다 반응 크기 그 자리에서 되돌린 폭 → 클수록 실제 저항 거래량 그 가격대에서 체결된 양 → 물린 사람의 수 주의: 터치 횟수는 양날이다 많이 부딪힌 선은 강하기도 하지만, 많이 부딪힌 선은 곧 뚫리기도 한다 ('여러 번 두드리면 문은 결국 열린다' 는 관점) 어느 해석이 맞는지는 이 종목·이 시간축에서 재봐야 아는 것이지 일반 법칙으로 정할 수 있는 게 아니다 터치 횟수의 양면성은 지지·저항 이론에서 오래된 논쟁이다. 나는 어느 쪽 편을 들 근거가 없다. 다만 봇에 넣는다면 이걸 상수로 박지 않고 측정 가능한 형태로 두겠다는 건 말할 수 있다. \u0026ldquo;터치 3회 이상이면 강한 지지\u0026quot;라고 코드에 쓰는 대신, 터치 횟수별로 이후 돌파율이 어떻게 다른지 재는 쪽이다.\n스윙 극값 뽑기 — 가장 흔한 방식의 실제 코드 네 방식 중 가장 널리 쓰이는 스윙 극값 방식을 코드로 보자. 프랙탈이라고도 부르는 방법이다.\npython — 프랙탈 극값 탐지 # 좌우 n봉보다 높은 봉 = 스윙 고점 def swing_highs(high, n=5): win = high.rolling(2*n+1, center=True).max() return high[(high == win)] def swing_lows(low, n=5): win = low.rolling(2*n+1, center=True).min() return low[(low == win)] # 함정 1: center=True 는 미래를 본다 # 실시간에서는 오른쪽 n봉이 아직 없다 → n봉 뒤에야 확정 # 백테스트에서 이걸 무시하면 미래 참조가 된다 # 함정 2: 동일한 고가가 연속되면 여러 봉이 동시에 스윙으로 잡힌다 # 함정 3: n 을 바꾸면 스윙 개수가 통째로 달라진다 (n=3 이면 수십 개, n=20 이면 몇 개) 확정 지연을 코드에 명시하는 법 각 스윙에 '발생 봉' 과 '확정 봉' 을 따로 기록 판정에는 확정 봉 이후의 데이터만 사용 함정 1이 이 계열 전체의 근본 문제다. center=True로 계산하면 코드는 아무 오류 없이 돌아가고 결과도 예쁘게 나온다. 그런데 그 결과는 미래를 본 것이다.\n백테스트에서 이 실수를 하면 성적이 아주 좋게 나온다. 스윙 저점을 정확히 찍고 거기서 산 것처럼 계산되니까. 그리고 실전에 올리면 전혀 다른 결과가 나온다. 실시간에는 그 저점이 저점인지 알 방법이 없기 때문이다.\n이 문제를 피하는 방법은 하나뿐이다. 모든 레벨에 확정 시점을 함께 기록하고, 그 시점 이후의 판정에만 쓰는 것. 번거롭지만 이걸 안 하면 백테스트 결과 전체가 의미를 잃는다.\n라운드 넘버라는 공짜 레벨 계산이 전혀 필요 없는 레벨도 있다. 1,000원, 10,000원, 100,000원 같은 딱 떨어지는 숫자다.\n사람이 만드는 레벨 왜 작동하나 — 근거는 시장 구조가 아니라 인간의 습관이다 주문을 걸 때 987원보다 1,000원에 거는 사람이 많다 목표가를 정할 때도 \"10,000원 가면 판다\" 같은 식으로 잡는다 → 그 가격에 주문이 실제로 쌓인다 → 지지·저항으로 기능한다 자동 추출은 아주 단순하다 가격대에 따라 단위를 정하고, 현재가 위아래 가장 가까운 값을 뽑는다 100~1,000원 → 100원 단위 · 1,000~10,000원 → 1,000원 단위 … 한계: 근거가 얇다. 검증 없이 믿을 만한 물건은 아니다 다만 검증 설계는 쉽다 — 라운드 넘버 ±0.2×ATR 진입 시 반등률 vs 무작위 가격 ±0.2×ATR 진입 시 반등률. 대조군 만들기가 간단하다 피보나치 편에서 다뤘던 자기실현 논리와 정확히 같은 구조다. 근거가 수학적이냐 심리적이냐만 다르고, \u0026ldquo;많은 사람이 같은 자리를 본다\u0026quot;는 메커니즘은 동일하다. 그리고 검증 방법도 동일하다 — 무작위 레벨을 대조군으로 세우는 것.\n라운드 넘버가 흥미로운 건 오히려 이쪽이 근거가 더 정직하다는 점이다. 피보나치는 자연법칙인 척하지만 라운드 넘버는 처음부터 \u0026ldquo;사람들이 이 숫자를 좋아해서\u0026quot;라고 말한다. 겸손한 가설이 검증하기도 쉽다.\n내 봇에 이미 있는 것 사실 내 봇에도 자동 레벨의 원시적인 형태가 이미 들어 있다. 24시간 저점이다.\n저점근접 게이트는 현재가가 24시간 저점에서 얼마나 가까운지를 보고, 이 기준값은 8.9일치 매매기록을 전수 스윕해서 3.0으로 확정했다. 열한 개 게이트 축을 전부 쓸어보고, 수익·승률·손절권 진입률이 동시에 개선되는 값만 채택한 결과다.\n현행 레벨과 오토 키 레벨의 관계 현행: 24시간 저점 하나 장점 — 계산이 가볍고, 애매함이 없고, 실측으로 값이 확정돼 있다 한계 — 하루라는 창 밖의 구조를 못 본다 일주일 전에 세 번 지지받은 자리가 코앞이어도 모른다 확장안: 다중 시간축 레벨 + 강도 점수 기대 — '왜 하필 여기서 반등했나' 에 대한 답이 하나 늘어난다 비용 — 상태 관리가 복잡해진다. 레벨은 값이 아니라 목록이고, 목록은 생성·갱신·폐기 규칙을 다 설계해야 한다 폐기 규칙이 특히 중요하다 — 뚫린 레벨을 언제 목록에서 지울 것인가 사람이 손으로 그은 선의 최대 문제가 바로 '지우지 않는다' 는 것이다 마지막 줄을 강조하고 싶다. 차트에 선을 그어두면 가격이 그 선을 뚫고 한참 지나가도 선은 그대로 남아 있다. 그리고 사람은 남아 있는 선을 계속 쳐다본다. 봇에는 이 관성이 없어야 한다. 레벨을 만드는 규칙만큼 없애는 규칙을 명시해야 한다.\n여러 시간축을 어떻게 합칠 것인가 일봉에서 뽑은 레벨과 15분봉에서 뽑은 레벨을 같이 쓰려면 규칙이 필요하다. 그냥 다 합치면 목록이 감당 안 되게 길어진다.\n시간축 통합 규칙 원칙 1 — 무게를 다르게 일봉 레벨 3점 · 4시간 2점 · 15분 1점 같은 식으로 등급을 매긴다 상위 시간축일수록 더 많은 참여자가 본다는 전제 원칙 2 — 겹치면 가산 일봉 레벨과 15분 레벨이 ATR 반경 안에서 겹치면 점수를 합산 이게 흔히 말하는 '합류(confluence)' 의 자동화 버전이다 원칙 3 — 조합을 미리 고정할 것 합류를 사후에 찾으면 아무 데서나 찾아진다 (피보나치 편에서 다룬 문제) '일봉 + 4시간' 처럼 어떤 조합을 유효로 칠지 코드에 박아두고 시작한다 원칙 4 — 상위 시간축은 확정이 더 늦다 일봉 스윙은 n일이 지나야 확정된다 — 며칠짜리 지연 '최근 일봉 레벨' 은 실시간에 존재하지 않는다는 걸 받아들여야 한다 원칙 4가 실무에서 제일 자주 무시된다. 차트에는 며칠 전 스윙 고점이 선명하게 찍혀 있으니 그게 그때부터 존재했던 것 같지만, 실제로는 확정에 시간이 걸린다. 백테스트에서 이걸 무시하면 미래 정보를 쓰게 되고, 그렇게 만든 전략은 실전에서 반드시 다르게 움직인다.\n내 봇의 상위 시간축 판정도 이 문제를 겪었다. 시장 상황을 BTC 4시간봉으로 판정하는데, 진행 중인 봉으로 하면 몇 분마다 하락장 판정이 뒤집힌다. 그래서 완성봉 기준으로 못 박았다. 판정이 4시간에 한 번만 바뀌는 게 답답해 보이지만, 그게 정직한 정보량이다.\n뚫린 레벨은 어떻게 되나 지지선이 뚫리면 저항선이 된다는 얘기를 자주 듣는다. 역할 전환이라고 부르는 현상이다.\n논리는 이렇다. 지지선에서 산 사람들이 그 선이 뚫리면서 손실 구간에 들어가고, 가격이 다시 그 선까지 올라오면 본전에 팔려고 한다. 그래서 이번엔 저항으로 작동한다는 것.\n역할 전환을 코드로 다루려면 필요한 상태 level.price 가격 level.role SUPPORT | RESISTANCE level.broken_at 뚫린 시점 (없으면 미돌파) level.touches 터치 횟수 전환 규칙 (설계 판단이 필요한 것들) Q1. 얼마나 뚫려야 '뚫렸다' 인가 종가 기준? 저가 기준? 관통 폭이 ATR 의 몇 배 이상? Q2. 뚫린 뒤 얼마나 지나면 레벨을 폐기하나 영원히 저항으로 남기면 목록이 무한히 늘어난다 Q3. 전환된 레벨의 강도는 원래와 같은가 약해진다고 보는 게 일반적이지만 근거는 약하다 세 질문 다 정답이 없다 — 그래서 코드에 박고 재보는 수밖에 없다 손으로 그은 선은 이 질문들을 통째로 회피한다. 그게 편하지만 그래서 검증도 못 한다 Q1이 특히 까다롭다. 가격이 지지선을 살짝 스치고 올라오는 일이 흔한데, 그걸 돌파로 볼지 말지에 따라 레벨 목록이 완전히 달라진다.\n내 봇에서 비슷한 판정 문제를 겪은 적이 있다. 진입한 종목의 저점이 깨지면 조기 청산하는 로직을 검토했는데, 실측해보니 60건 중 27건이 저점을 스치고 회복했다. 절반 가까이가 일시적 관통이었던 것이다. 그래서 그 로직은 채택하지 않았다. 기준선 −213,394원 대비 모든 변형이 나빴다.\n이 경험이 알려준 게 관통을 어떻게 정의하느냐가 결과를 좌우한다는 것이다. 종가 기준으로 하면 관대해지고 저가 기준으로 하면 엄격해진다. 어느 쪽이 맞는지는 전략마다 다르고, 결국 재봐야 안다.\n이 지표류의 근본 한계 정직하게 짚고 끝내자. 오토 키 레벨은 재현 가능성을 얻는 대신 두 가지를 포기한다.\n자동화가 못 하는 것 ① 맥락 사람은 \"이 자리는 상장 직후 첫 급락이 멈춘 곳\" 같은 서사를 안다 코드는 그냥 좌우보다 낮은 봉으로만 본다 ② 확정 지연 스윙 극값은 n봉이 지나야 확정된다 — 지금 만들어지는 중인 레벨은 못 쓴다 차트에 예쁘게 그려진 과거 레벨은 전부 사후 확정된 것이다 이 지연을 무시하고 백테스트하면 미래를 참조하게 된다 얻는 것: 같은 입력에 항상 같은 선. 그래서 검증이 가능하다 검증 가능성은 정확도보다 앞선 조건이다 — 틀린 걸 알 수 있어야 고친다 ②번 함정은 이 코너에서 계속 반복되는 주제다. 시장 구조를 다루는 지표는 거의 전부 이 문제를 안고 있다. 차트를 열면 과거의 구조가 선명하게 보이는데, 그건 그 구조가 이미 완성됐기 때문이다. 실시간의 봇에게 주어지는 정보는 언제나 더 늦고 더 성기다.\n레벨을 만들었으면 무엇에 쓸 것인가 레벨 목록을 만들고 나면 그다음이 문제다. 이걸 어디에 붙일지 정하지 않으면 그냥 예쁜 선으로 끝난다. 쓸 자리는 크게 셋이다.\n레벨의 세 가지 용도 ① 진입 조건 \"지지 레벨 ±0.3×ATR 안에 있을 때만 산다\" 위험: 레벨이 촘촘하면 사실상 아무 조건도 아니게 된다 레벨 개수를 강도 상위 N개로 제한하는 게 먼저 ② 진입 가격 개선 진입은 다른 조건으로 결정하되, 지정가를 가장 가까운 지지 레벨에 건다 위험이 작다 — 진입 여부를 안 바꾸고 체결 가격만 다듬는다 실험 순서로는 여기가 1순위 ③ 청산 기준 가장 가까운 저항 레벨을 목표가로, 지지 이탈을 손절로 위험: 현행 청산 로직 전체와 충돌한다. 손대려면 통째로 재설계해야 한다 ②→①→③ 순서로 시도하는 게 안전하다. 뒤로 갈수록 시스템에 미치는 범위가 넓다 ②번을 1순위로 꼽는 이유가 있다. 진입 여부를 바꾸지 않는 변경은 되돌리기 쉽고, 효과 측정도 깔끔하다. 같은 신호에 대해 체결가만 비교하면 되니까 대조군을 만들기도 간단하다.\n반면 ①번처럼 진입 조건을 건드리면 신호 개수 자체가 변해서, 성과가 달라졌을 때 그게 조건 덕인지 표본이 달라진 탓인지 구분하기 어려워진다. 시스템을 고칠 때 변경 범위가 작은 것부터 손대는 게 규율인 이유다.\n레벨이 얼마나 자주 지켜지는지부터 이 글 전체가 \u0026ldquo;레벨을 어떻게 만들 것인가\u0026quot;였는데, 사실 그 앞에 물어야 할 질문이 있다. 레벨이라는 게 실제로 작동하기는 하는가.\n이건 측정 가능한 질문이다. 만들어둔 레벨에 가격이 닿았을 때 반등한 비율을 세면 된다. 그리고 반드시 대조군이 필요하다 — 레벨이 아닌 무작위 가격에 닿았을 때의 반등 비율과 비교해야 의미가 나온다. 피보나치 편에서 세운 절차 그대로다.\n여기에 한 가지를 더 봐야 한다. 레벨이 뚫렸을 때의 손실 크기다. 지지선이 70% 확률로 지켜져도, 뚫리는 30%에서 크게 잃으면 기대값은 마이너스가 될 수 있다. 승률과 손익 크기를 함께 봐야 한다는 원칙이 여기서도 적용된다.\n내 봇의 파라미터를 정할 때 목적함수를 수익·승률·손절권 진입률 셋으로 두는 이유가 이것이다. 하나만 보면 반드시 어딘가에서 대가를 치른다. 레벨 검증도 같은 방식으로 설계해야 한다.\nSMC의 오더블록과 무엇이 다른가 지난 SMC 편에서 오더블록을 다뤘는데, 그것도 결국 자동으로 뽑은 레벨이다. 둘을 비교하면 접근의 차이가 보인다.\n오더블록 vs 일반 키 레벨 오더블록 생성 조건: 구조 돌파가 일어났을 때, 그 직전 구간의 극값 봉 형태: 점이 아니라 구간 (봉의 고가~저가) 서사: '큰돈이 급히 떠난 자리' 일반 키 레벨 생성 조건: 스윙 극값 · 기간 극값 · 거래량 밀집 등 형태: 대개 점 (가격 하나) 또는 좁은 밴드 서사: '주문이 쌓인 자리' 공통점 둘 다 생성·소멸 규칙을 가진 자료구조다 (값 하나가 아니다) 둘 다 확정에 지연이 있다 둘 다 목록이 길어지는 문제를 관리해야 한다 차이는 '어떤 사건을 계기로 레벨을 만드느냐' 뿐이다 이렇게 놓고 보면 SMC의 오더블록이 특별한 물건이 아니라는 게 분명해진다. 레벨을 뽑는 여러 방식 중 하나이고, 다만 생성 계기를 구조 돌파로 잡았다는 점이 특징이다.\n그리고 이 관점에서 보면 검증 방법도 같아진다. 오더블록이든 스윙 레벨이든 거래량 밀집이든, \u0026ldquo;그 레벨 근처에서 반등 확률이 무작위보다 높은가\u0026quot;를 대조군과 비교해서 재면 된다. 서사가 다르다고 검증 방법이 달라지지 않는다.\n여러 방식을 나란히 재보면 어느 생성 규칙이 가장 유효한지 순위가 나올 것이다. 그게 이 주제에서 제일 해보고 싶은 실험이다. 지금은 각 진영이 자기 방식을 옹호하는 서사만 있고, 같은 자로 잰 비교는 잘 안 보인다.\n레벨을 화면에 어떻게 보여줄 것인가 봇에 넣는 얘기만 했는데, 운영 관점도 하나 짚고 싶다. 레벨을 계산했으면 그걸 눈에 보이게 해야 한다.\n내 봇은 화면이 터미널이다. 차트가 없으니 선을 그릴 수도 없다. 그래서 지표값이든 레벨이든 텍스트로 찍는 수밖에 없는데, 이게 은근히 중요한 문제였다.\n보이지 않는 조건의 위험 겪은 일 \"조건은 다 통과했는데 왜 안 샀지?\" 하는 종목이 자꾸 보였다 추적해보니 게이트 밖에서 스프레드 조건에 막히고 있었다 판정은 하는데 화면에 안 보이니 원인을 몰랐던 것 대응: 스프레드를 정식 게이트로 승격시켜 통과율 통계에 포함 교훈 — 보이지 않는 조건은 없는 조건보다 나쁘다 없으면 없는 줄 알지만, 있는데 안 보이면 디버깅이 불가능해진다 레벨 시스템에 적용하면 현재가 위아래 가장 가까운 레벨과 거리를 상시 표시 진입 판정 때 어느 레벨을 참조했는지 로그에 남기기 레벨 개수와 폐기 건수를 스캔마다 기록 (목록 폭주 감지) 세 번째 항목이 상태를 가진 부품의 공통 요구사항이다. 레벨 목록은 계속 늘어나고 줄어드는데, 그 개수를 안 보면 어느 날 목록이 수백 개로 불어나 있어도 모른다. 오더블록을 다룰 때도 같은 문제를 봤다 — 목록에 상한을 두고, 그 상한에 걸린 사실도 기록해야 한다.\n터미널 폭도 실제 제약이었다. 한글은 두 칸을 차지해서 계산을 잘못하면 줄바꿈이 일어나고 화면이 무너진다. 표시 폭을 맞추는 작업에 생각보다 많은 버전을 썼다. 사소해 보이지만, 화면이 깨지면 그 순간 관측 능력을 잃는다.\n이 주제에서 제일 하고 싶은 실험 글을 정리하면서 하고 싶은 실험이 하나 분명해졌다. 레벨 생성 방식들을 같은 자로 재보는 것이다.\n생성 방식 대결 설계 참가자 A 스윙 극값 (n = 5 / 10 / 20) B 전일·전주 고저 C 거래량 밀집 상위 3개 D 라운드 넘버 E 무작위 가격 ← 대조군 측정 각 방식의 레벨 ±0.3×ATR 에 가격이 닿은 뒤 k봉 내 반등률 그리고 뚫렸을 때의 평균 손실 폭 판정 E(무작위) 를 못 이기는 방식은 탈락 이긴 방식들 사이에서는 반등률과 손실 폭을 함께 본다 예상: 상당수가 E 를 못 이길 것 같다. 그래도 재봐야 안다 E를 넣는 게 핵심이다. 대조군 없이 \u0026ldquo;레벨에서 60% 반등했다\u0026quot;는 숫자만 보면 대단해 보이지만, 무작위 가격에서도 55%가 나온다면 그 레벨은 아무 일도 안 한 것이다.\n이 절차는 피보나치 편에서 세운 것과 같다. 근거가 서사에서 오는 도구들 — 피보나치, 오더블록, 지지·저항 — 은 전부 이 방식으로 검증해야 한다고 생각한다. 서사가 그럴듯할수록 대조군이 더 필요하다.\n마무리 지지선을 손으로 긋는 일에는 즐거움이 있다. 차트를 들여다보다가 \u0026ldquo;아, 여기구나\u0026rdquo; 하는 순간이 오면 시장을 이해한 것 같은 기분이 든다.\n그 기분이 문제다. 이해했다는 감각과 실제로 맞히는 능력은 다른 것이고, 손으로 그은 선으로는 그 둘을 구분할 방법이 없다. 규칙을 코드에 못 박는 순간 즐거움은 사라지지만, 대신 틀렸을 때 틀렸다는 걸 알 수 있게 된다.\n나는 그 교환을 했고, 지금은 잘한 거래였다고 생각한다.\n덧붙임 — 레벨 없이도 되는 이유 마지막으로 균형을 위해 반대편 얘기를 하겠다. 내 봇은 지금 정교한 레벨 시스템 없이도 돌아간다. 24시간 저점 하나뿐이다.\n단순한 것의 장점 현행: 24시간 저점 대비 거리 하나 · 계산이 가볍다 (매 스캔 수십 종목 × 실시간) · 상태가 없다 (목록 관리·폐기 규칙 불필요) · 애매함이 없다 (어느 저점인지 논쟁의 여지 없음) · 값이 실측으로 확정돼 있다 (전수 스윕 결과 3.0) 잃는 것 · 하루 창 밖의 구조를 못 본다 · 일주일 전 세 번 지지받은 자리가 코앞이어도 모른다 이 교환이 나쁜지는 재봐야 안다 복잡한 레벨 시스템이 24시간 저점보다 나은지는 아직 증명된 바 없다 이 점을 강조하고 싶다. 이 글에서 클러스터링이니 강도 점수니 시간축 통합이니 한참 설명했지만, 그게 실제로 더 나은지는 아직 모른다. 복잡한 시스템이 단순한 규칙을 이긴다는 보장은 없다.\n내 봇 이력에도 그런 사례가 있다. 포지션을 절반으로 나눠 한쪽은 확정하고 한쪽은 큰 상승을 기다리게 하는 설계를 넣었다가 하루 만에 철회했다. 정교한 쪽이 단순한 쪽보다 나빴던 것이다. 복잡성은 그 자체로 비용이고, 그 비용을 넘는 이득이 있는지는 매번 확인해야 한다.\n그래서 이 글의 결론은 \u0026ldquo;레벨 시스템을 만들어라\u0026quot;가 아니다. 만들 거면 검증 가능한 형태로 만들고, 만들기 전에 지금 쓰는 단순한 규칙과 비교하라는 쪽에 가깝다. 손으로 긋는 선을 코드로 옮기는 게 진보인 이유는 정교해져서가 아니라 비교할 수 있게 되기 때문이다.\n","permalink":"https://botlab.co.kr/study/auto-key-levels/","summary":"사람이 긋는 선은 재현이 안 된다. 그걸 코드로 옮기면 무엇이 남고 무엇이 사라지는지.","title":"Auto Key Levels — 지지선을 손으로 긋지 않기로 했다 | Crypto Trading Bot"},{"content":"질문의 각도가 다르다 대부분의 지표는 가격의 크기를 잰다. RSI는 오른 폭과 내린 폭의 비율을, 볼린저는 평균에서 떨어진 거리를, 이동평균은 평균 자체를 본다. 전부 얼마나를 묻는다.\n아룬은 다르다. 이건 언제를 묻는다. 정확히는 \u0026ldquo;최근 n봉 중에서 최고가를 찍은 게 몇 봉 전인가\u0026quot;를 센다. 가격이 얼마나 올랐는지는 전혀 보지 않는다. 이 각도가 이 지표를 특별하게 만든다.\n이름은 산스크리트어로 새벽빛을 뜻하는 단어에서 왔다고 한다. 추세가 시작되는 순간을 잡겠다는 뜻이겠다.\n아룬 — 공식 기본 기간 n = 25 (원저자 권장), 실무에선 14~25 를 많이 쓴다 Aroon Up = (n − 최고가 이후 경과 봉수) / n × 100 Aroon Down = (n − 최저가 이후 경과 봉수) / n × 100 Aroon Oscillator = Up − Down 범위 −100 ~ +100 읽는 법 Up = 100 바로 이 봉이 n봉 신고가 → 방금 고점을 갱신했다 Up = 0 n봉 전이 최고가 → 그 뒤로 한 번도 못 넘었다 오실레이터 \u003e 0 고점 갱신이 저점 갱신보다 최근 → 상승 우세 오실레이터 \u003c 0 그 반대 → 하락 우세 주의: 가격이 얼마나 올랐는지는 계산에 안 들어간다. 오직 시점만 본다 값이 움직이는 방식부터 공식을 봤으니 값이 실제로 어떻게 움직이는지 따라가 보자. 아룬은 다른 지표와 움직이는 방식이 눈에 띄게 다르다.\nAroon Up 의 시간 경과 (n=25) 신고가를 찍은 직후부터 아무 일도 없을 때 0봉 경과 Up = 100 방금 최고가 1봉 경과 Up = 96 5봉 경과 Up = 80 12봉 경과 Up = 52 20봉 경과 Up = 20 25봉 경과 Up = 0 창 밖으로 밀려남 특징 — 가격이 전혀 안 움직여도 값은 계속 내려간다 다른 지표들은 가격이 정지하면 값도 정지하는데 아룬은 아니다 시간이 흐른다는 사실 자체가 정보로 취급된다 그리고 새 신고가가 나오는 순간 100 으로 튄다 — 계단식 복귀 \u0026ldquo;가격이 안 움직여도 값이 변한다\u0026quot;는 성질이 이 지표의 정체성이다. RSI는 가격이 멈추면 서서히 50으로 수렴하고 볼린저는 밴드가 좁아지지만, 아룬은 시간이 지났다는 사실만으로 값을 바꾼다.\n이게 유용한 상황이 있다. 예컨대 \u0026ldquo;고점을 찍은 지 오래됐다\u0026quot;는 정보는 가격만 봐서는 알기 어렵다. 지금 가격이 고점 대비 -3%라는 건 알 수 있어도, 그 고점이 한 시간 전인지 하루 전인지는 별도로 세야 한다. 아룬은 그 세는 일을 대신해준다.\n시간만 보면 무엇이 좋은가 가격 크기를 무시하는 게 왜 장점이 될 수 있을까. 한 가지 성질 때문이다. 크기 기반 지표는 이상치에 흔들리는데, 시간 기반 지표는 안 흔들린다.\n급등 한 방이 들어왔을 때 시나리오: 조용하던 종목에 1봉짜리 +30% 스파이크 RSI 평균 상승폭이 폭발 → 순식간에 과매수 구간, 며칠간 왜곡 지속 볼린저 표준편차 급증 → 밴드가 벌어져 이후 신호가 다 무뎌짐 이동평균 평균이 들려 올라감 → n봉 동안 영향 아룬 \"최고가가 방금이다\" → Up = 100. 그게 전부. +30% 든 +3% 든 값이 같다 장점: 이상치에 강건하다 (robust) 단점: 강한 움직임과 약한 움직임을 구분 못 한다 — 바로 이 지표의 최대 약점 장점과 단점이 같은 뿌리에서 나온다는 게 흥미롭다. 크기를 안 보니까 이상치에 안 흔들리고, 크기를 안 보니까 강도를 모른다. 그래서 아룬은 단독으로 쓰기보다 크기 기반 지표와 짝지을 때 의미가 생긴다. 서로 못 보는 것이 정확히 반대이기 때문이다.\n이름의 유래와 원저자 지표의 배경을 아는 게 쓰임을 정하는 데 도움이 될 때가 있다. 아룬은 1995년 인도의 기술적 분석가 투샤르 찬데가 만들었다. 산스크리트어로 새벽빛을 뜻하는 단어에서 이름을 따왔다고 한다.\n만든 사람의 의도와 실제 쓰임 원저자의 의도 \"추세가 시작되는 순간을 잡는다\" — 새벽빛이라는 이름이 그 뜻 기본 기간 25 · 일봉 기준으로 설계 실제로 잘하는 일 추세의 시작보다 '추세가 없는 상태' 의 감지 Up 과 Down 이 둘 다 낮은 구간 = 정체 의도와 쓰임이 어긋나는 건 흔한 일이다 RSI 도 원래 모멘텀 측정용인데 지금은 과매수/과매도 판정에 더 쓰인다 볼린저 밴드도 변동성 도구였는데 역추세 매매에 쓰이는 경우가 많다 그래서 \"만든 사람이 이렇게 쓰라고 했다\" 는 근거가 약하다 쓰임은 내 데이터가 정한다 — 이 코너에서 반복하는 얘기 같은 지표를 만든 찬데는 CMO(찬데 모멘텀 오실레이터)라는 지표도 만들었는데, 그건 아룬과 정반대로 크기를 재는 물건이다. 한 사람이 시간 축과 크기 축을 각각 만든 셈이다. 두 지표를 같이 쓰라는 뜻이었는지도 모르겠다.\n박스권 감지라는 진짜 특기 아룬이 다른 지표보다 확실히 잘하는 게 하나 있다. 옆으로 기는 장을 알아채는 것이다.\n박스권에서 아룬의 모양 추세장 (상승) Up ▔▔▔▔▔▔▔▔ 90~100 근처에 붙어 있음 (계속 신고가) Down ▁▁▁▁▁▁▁▁ 0 근처 오실레이터 +80 ~ +100 유지 박스권 Up ╱╲╱╲╱╲╱╲ 중간대에서 진동 Down ╲╱╲╱╲╱╲╱ 역시 중간대 오실레이터 0 근처에서 왔다 갔다 — 둘 다 낮으면 '아무 일도 안 일어나는 중' 유용한 조합 판정 Up \u003c 50 AND Down \u003c 50 → 최근 절반 구간에 고점도 저점도 갱신 없음 = 명확한 정체 구간 이 판정은 오실레이터(차이) 만 봐서는 안 나온다 — Up·Down 을 각각 봐야 한다 마지막 줄이 실무 포인트다. 오실레이터는 Up과 Down의 차이라서 정보가 한 겹 줄어든다. Up 20 / Down 20도 0이고 Up 60 / Down 60도 0인데, 앞은 완전 정체이고 뒤는 양쪽 다 활발한 상태다. 봇에 넣을 거라면 오실레이터 하나만 받지 말고 세 값을 다 들고 있어야 한다.\n박스권 판정이 왜 중요한가 정체 구간을 알아채는 게 왜 쓸모 있는지 좀 더 설명할 필요가 있겠다. \u0026ldquo;아무 일도 안 일어나는 중\u0026quot;이라는 정보가 매매에 무슨 도움이 되나 싶을 수 있으니까.\n정체 구간에서 벌어지는 일 박스권에서 각 전략이 겪는 문제 돌파 전략 돌파 신호가 대부분 가짜 — 뚫자마자 되돌아온다 교차 전략 두 평균선이 얽혀 휩쏘가 연달아 터진다 눌림목 전략 '빠진 것' 이 없어서 진입 후보 자체가 안 생긴다 즉 대부분의 전략이 박스권에서 손해를 본다 그런데 대응은 전략마다 다르다 돌파·교차 → 아예 쉬는 게 낫다 (비용만 나감) 눌림목 → 자동으로 안 사게 된다 (조건 미달) 내 봇의 경우 박스권 감지의 실익은 '쉬기' 보다 '진단' 에 가깝다 매수가 없는 날, 그게 게이트가 조여서인지 장이 조용해서인지 구분된다 전자면 파라미터 문제, 후자면 정상 — 대응이 완전히 다르다 마지막 항목이 실제로 유용할 것 같은 지점이다. \u0026ldquo;이틀째 매수가 없다\u0026quot;는 상황을 여러 번 겪었는데, 그때마다 원인 판단이 어려웠다. 게이트를 너무 조여서인지, 아니면 시장이 그냥 조용한 건지.\n전자라면 조건을 풀어야 하고, 후자라면 아무것도 안 하는 게 맞다. 그런데 구분할 방법이 없으니 일단 조건을 풀게 된다. 그렇게 임계를 계속 깎다가 나중에 후회하는 패턴이 반복됐다.\n박스권 판정 축이 있으면 이 상황이 정리된다. 시장이 정체 상태라는 게 확인되면 조건을 안 건드리고 기다리면 된다. 진단이 되면 불필요한 처방을 안 하게 된다.\n코드 python — 함정은 창 길이와 동점 처리 def aroon(high, low, n=25): # 최근 n+1 봉 안에서 최고/최저가 몇 봉 전인지 up = high.rolling(n+1).apply(lambda x: x.argmax(), raw=True) / n * 100 down = low.rolling(n+1).apply(lambda x: x.argmin(), raw=True) / n * 100 return up, down, up - down # 함정 1: 창 길이가 n 이 아니라 n+1 이다 # '현재 봉 포함 n봉 전까지' 를 보려면 n+1 개가 필요하다. 여기서 1 틀리면 # Up 이 100 에 절대 안 닿거나 항상 닿는 이상한 지표가 된다 # 함정 2: 동점. 같은 고가가 두 번 나오면 argmax 는 앞의 것을 고른다 # 최근 것을 기준으로 하려면 뒤에서부터 찾아야 한다 (플랫폼마다 관행이 다름) # 함정 3: 고가/저가를 쓴다 — 종가 기준이 아니다. 꼬리가 값에 그대로 반영된다 함정 3은 은근히 크다. 고가·저가 기준이라 긴 꼬리 한 번에 Up이 100으로 튄다. 종가 기준으로 바꾸면 지표 성격이 꽤 달라지는데, 어느 쪽이 맞다기보다 무엇을 신고가로 칠 것인가의 정의 문제다. 봇에서는 이 정의를 명시해두지 않으면 나중에 백테스트와 실전이 갈린다.\n눌림목 전략에서 시간이 왜 중요한가 내 전략은 과매도 눌림목 반등이다. 빠진 것을 사되 돌기 시작한 것을 산다. 여기서 \u0026ldquo;돌기 시작했다\u0026quot;를 어떻게 정의하느냐가 계속 숙제였는데, 시간이라는 축이 이 문제와 직접 연결된다.\n같은 낙폭, 다른 상황 두 종목 모두 24시간 저점 대비 +1.5% 위치, RSI 42 현행 게이트로는 둘 다 통과한다 종목 A — 저점을 10분 전에 찍었다 아직 떨어지는 중일 수 있다. 반등이 아니라 잠깐 튄 것 종목 B — 저점을 6시간 전에 찍고 그 뒤로 안 깼다 바닥을 다진 시간이 있다. 반등의 신뢰도가 다르다 현행 게이트는 이 둘을 구분하지 못한다 저점근접·낙폭·RSI 전부 크기 기반이라 '언제' 를 안 본다 Aroon Down 은 정확히 이 차이를 잡는다 A: Down 이 아직 높다 (저점 갱신이 최근) B: Down 이 낮아졌다 (저점 갱신 후 시간 경과) 이 예시가 내가 아룬을 진지하게 보는 이유다. 지금 게이트 열 개가 전부 크기를 재고 있다. RSI는 에너지 배분의 크기, 낙폭은 하락의 크기, 저점근접은 거리의 크기, 거량은 거래량의 크기. 시간을 재는 축이 하나도 없다.\n지표를 여러 개 쓴다고 정보가 그만큼 늘지는 않는다는 얘기를 여러 번 했는데, 그 반대도 성립한다. 지금까지 없던 종류의 축을 하나 넣으면 정보가 실제로 늘어날 가능성이 있다. 그게 아룬을 검증 후보 상위에 올려둔 이유다.\nADX와 뭐가 다른가 추세의 존재를 판정하는 지표로 훨씬 유명한 게 ADX다. 둘을 비교하면 아룬의 자리가 분명해진다.\n아룬 vs ADX ADX (평균방향성지수) 재료: +DI / −DI — 가격이 위아래로 움직인 '크기' 를 누적 출력: 0~100. 추세의 강도만 나타내고 방향은 안 나타냄 성질: 와일더 평활이 두 번 들어가 매우 느리다 아룬 재료: 극값이 나온 '시점' 출력: Up/Down 각각 0~100, 오실레이터는 −100~+100 (방향 포함) 성질: 평활이 없다. 극값이 갱신되는 순간 즉시 튄다 핵심 차이 ADX 는 크기를 누적 평활 → 늦지만 안정적 아룬 은 시점을 그대로 → 빠르지만 계단식으로 튄다 아룬의 값은 연속적이지 않다. n=25 면 값이 4 단위로만 움직인다 (100/25) 이 계단 성질 때문에 임계값 비교가 생각보다 거칠다 — 미세 조정이 안 먹힌다 마지막 줄이 실무에서 중요하다. 아룬은 n으로 나눈 값이라 이산적이다. n=25면 0, 4, 8, 12… 이렇게만 나온다. 임계를 50으로 잡든 52로 잡든 결과가 같을 수 있다는 뜻이다.\n이건 파라미터 스윕을 할 때 알아둬야 하는 성질이다. 후보값을 촘촘하게 늘어놓아 봐야 실제로는 몇 개의 구간으로 뭉친다. RSI처럼 연속적인 지표와 같은 방식으로 스윕하면 계산만 낭비한다. 지표마다 값의 알갱이 크기가 다르다는 걸 알고 설계해야 한다.\n오실레이터만 보면 잃는 정보 앞에서 잠깐 짚었지만 중요해서 따로 다룬다. Aroon Oscillator는 Up에서 Down을 뺀 값인데, 이 뺄셈에서 정보가 한 겹 사라진다.\n같은 오실레이터 값, 다른 상태 오실레이터 = 0 인 세 가지 경우 Up = 100, Down = 100 방금 신고가와 신저가를 동시에 찍었다 = 변동성 폭발 구간 Up = 50, Down = 50 양쪽 다 절반쯤 전에 찍었다 = 애매한 국면 Up = 4, Down = 4 양쪽 다 창 끝에 있다 = 완전한 정체, 아무 일도 안 일어남 세 상황의 대응이 전부 달라야 하는데 오실레이터는 다 0 이다 봇에 넣을 땐 (Up, Down, Oscillator) 세 값을 다 들고 다닌다 추가 판정 예시 Up \u0026lt; 50 AND Down \u0026lt; 50 → 정체 구간 (진입 보류 후보) Up \u0026gt; 80 AND Down \u0026gt; 80 → 양방향 급변 (위험 구간) 이 문제는 아룬에만 있는 게 아니다. 두 값을 빼서 하나로 만드는 모든 지표가 같은 손실을 겪는다. MACD도 그렇고, 앞서 다룬 BBTrend도 하단 차이에서 상단 차이를 빼는 구조라 마찬가지다.\n압축은 편리하지만 되돌릴 수 없다. 그래서 지표를 봇에 넣을 때는 최종 출력값 하나만 받지 말고, 중간 산출물까지 상태로 들고 있는 게 대개 낫다. 나중에 \u0026ldquo;왜 이 판정이 나왔지\u0026quot;를 추적할 때 중간값이 있어야 원인을 찾을 수 있다.\n내 봇에서 이 원칙을 배운 계기가 있다. 진입 조건을 여러 개 판정하는데 결과만 통과/탈락으로 기록했더니, 나중에 왜 떨어졌는지 알 수가 없었다. 그래서 게이트별 세부값을 함께 남기도록 바꿨다. 지금은 탈락한 종목도 어느 게이트에서 어떤 값으로 떨어졌는지가 로그에 찍힌다.\n기간을 얼마로 둘 것인가 원저자 권장은 25다. 이 숫자가 어디서 왔는지 따져볼 필요가 있다.\nn 을 바꾸면 n 작음 (10~14) 최근 구간만 본다 → 반응이 빠르다 · 값의 알갱이가 굵다 (100/14 ≈ 7.1) 잦은 극값 갱신으로 100 과 0 을 자주 오간다 n 큼 (25~50) 값이 부드럽다 · 알갱이가 곱다 (100/50 = 2) 오래전 극값에 계속 묶여 최근 변화를 늦게 반영 15분봉 기준 환산 n=14 → 3.5시간 창 n=25 → 6.25시간 창 n=96 → 정확히 24시간 창 마지막 줄이 흥미롭다 — n=96 이면 내 봇의 저점근접 게이트와 같은 창이 된다 즉 '24시간 저점 대비 거리(크기)' 와 '24시간 저점 갱신 이후 경과(시간)' 를 같은 창에서 비교할 수 있다. 대조 실험을 짜기에 딱 좋은 조건이다 n=96 아이디어는 이 글을 쓰면서 나온 것이다. 기간을 원저자 권장값이 아니라 내 시스템의 다른 부품과 창 길이를 맞춰서 정하면, 두 지표를 공정하게 비교할 수 있다. 같은 데이터 창을 크기로 재느냐 시간으로 재느냐만 다른 상태가 되니까.\n내 봇과 붙여보면 아룬과 내 전략의 궁합을 따져보면 정직하게 애매하다.\n궁합 점검 내 전략: 과매도 눌림목 반등 — 빠진 것을 사되 돌기 시작한 것을 산다 ✗ 아룬 오실레이터가 크게 음수 = 저점을 계속 갱신 중 눌림목처럼 보이지만 실제로는 '떨어지는 칼' 일 확률이 높다 현행 게이트가 이미 저점근접·낙폭 상한으로 이 구간을 다룬다 — 중복 우려 ✓ Aroon Down 이 최근 급락 후 회복 = 저점 갱신이 멈춘 시점 '돌기 시작했다' 의 시간 기반 정의가 될 수 있다 현행 반등 확인은 저점 대비 반등률(%) — 크기 기반이다 → 두 정의가 같은 것을 다르게 재고 있다. 어느 쪽이 나은지는 측정할 문제 ✓ 박스권 판정 (Up·Down 둘 다 낮음) 을 레짐 보조 축으로 현행 레짐 판정은 BTC 4시간봉 방향 하나만 본다 — 방향은 보지만 정체는 못 본다 두 번째 항목이 제일 해볼 만하다. 지금 내 봇은 반등을 \u0026ldquo;저점 대비 몇 % 올라왔나\u0026quot;로 판정한다. 아룬식으로 하면 \u0026ldquo;저점 갱신이 멈춘 지 몇 봉 됐나\u0026quot;가 된다. 같은 현상의 다른 측정이다.\n이 비교가 의미 있는 이유는 앞에서 말한 강건성 때문이다. 크기 기반 반등 확인은 한 봉짜리 큰 양봉에 쉽게 걸린다. 시간 기반이라면 그런 봉 하나로는 조건이 안 채워진다. 휩쏘를 줄이는 방향일 가능성이 있고, 반대로 진입이 늦어져 이익 앞부분을 버릴 가능성도 있다. 어느 쪽인지는 재봐야 안다.\n검증 설계 가설: \"저점 갱신 정지(시간 기반)가 반등률(크기 기반)보다 나은 진입 확인이다\" ① 기간 n ∈ {14, 20, 25} ② 조건 A: 현행 반등률 게이트 (대조군) ③ 조건 B: Aroon Down 이 특정 값 아래로 내려간 뒤 k봉 경과 ④ 조건 C: A AND B (둘 다) 목적함수는 셋을 함께 본다 — 평균수익 · 승률 · 손절권 진입률 하나만 좋아지고 다른 게 나빠지면 채택하지 않는다 이 표에 결과 숫자가 없는 건, 아직 안 돌려봤기 때문이다. C(둘 다)가 이길 것 같지만, 조건을 겹칠수록 진입 빈도가 급감한다는 전례가 있어서 낙관하지 않는다 — 표본이 마르면 검증 자체가 불가능해진다 마지막 우려는 경험에서 나온 것이다. 예전에 거래량 조건을 하나 더 얹으려다 후보가 46건에서 6건으로 줄어든 적이 있다. 단독으로는 성적이 좋았던 조건인데, 다른 게이트들이 이미 비슷한 걸 걸러내고 있어서 겹쳐 놓으니 통행량만 죽었다. 조건을 더하는 일은 항상 이 위험을 안고 간다.\n구현할 때 실제로 걸리는 것들 앞에서 함정 셋을 짚었는데, 봇에 넣는다면 그것 말고도 신경 쓸 게 있다.\n운영 관점의 체크 항목 ① 계산 비용 rolling().apply() 는 느리다. 종목 수십 개 × 매 스캔이면 부담이 된다 대안: 최근 극값의 인덱스만 상태로 들고 있다가 갱신하는 방식 새 봉이 올 때 최고가 갱신 여부만 확인하면 O(1) 로 유지 가능 ② 상태 복원 봇이 재시작하면 '최고가가 몇 봉 전인지' 를 다시 계산해야 한다 워밍업 봉을 n+1 개 이상 확보하지 않으면 값이 틀린다 ③ 고가/저가 기준의 일관성 봉의 고가를 쓸지 종가를 쓸지 정하고 문서에 남긴다 백테스트와 실전이 다른 기준을 쓰면 결과가 안 맞는다 ④ 값의 이산성을 스윕 설계에 반영 n=25 면 4 단위로만 움직인다 — 임계 후보를 촘촘히 늘어놓아도 무의미 실질 후보는 0, 4, 8, … 이므로 그 격자에 맞춰 설계한다 ①번은 실제로 겪을 만한 문제다. 내 봇은 매 스캔마다 수십 종목의 지표를 계산하는데, 여기에 느린 연산이 하나 붙으면 스캔 주기가 늘어난다. 스캔이 느려지면 진입 타이밍이 밀리고, 그건 지표의 품질과 무관하게 성과를 깎는다.\n이런 종류의 고려가 트레이딩 봇 개발에서 은근히 큰 비중을 차지한다. 지표 하나를 추가할 때 그 지표의 판별력뿐 아니라 계산 비용, 상태 관리, 복원 절차까지 다 따져야 한다. 지표는 수식이지만 봇에 들어가면 상태를 가진 부품이 되기 때문이다.\n이 지표가 잘 안 쓰이는 이유 아룬은 1995년에 나왔는데 RSI나 MACD만큼 퍼지지 않았다. 이유를 생각해보면 몇 가지 짚인다.\n보급이 안 된 이유들 ① 그림이 안 예쁘다 계단식으로 튀어서 부드러운 곡선이 안 나온다 지표의 확산에는 시각적 설득력이 꽤 크게 작용한다 ② 해석이 직관적이지 않다 \"RSI 70 이면 과매수\" 같은 한 줄 요약이 안 만들어진다 Up 과 Down 을 각각 봐야 하고, 오실레이터만으론 정보가 준다 ③ 단독으로는 매매 규칙이 안 나온다 크기를 모르니 손절폭도 목표가도 이 지표로는 못 정한다 그런데 ③ 은 사실 이 지표의 성격이지 결함이 아니다 아룬은 진입 신호가 아니라 '상태 판정' 도구다 — 지금이 추세장인지 박스권인지를 가르는 스위치로 쓸 때 제 몫을 한다 지표가 유명해지는 것과 유용한 것 사이에 큰 상관이 없다는 건, 이 코너를 하면서 계속 확인하는 사실이다. 유명한 지표는 대개 설명이 짧고 그림이 예쁘다. 그 두 조건은 유용함과 별 관계가 없다.\n거꾸로 말하면, 덜 알려진 지표를 볼 때는 \u0026ldquo;왜 안 퍼졌나\u0026quot;를 먼저 물어보는 게 좋다. 성능이 나빠서 안 퍼진 것과, 설명하기 어려워서 안 퍼진 것은 완전히 다른 얘기니까.\n이 지표로 무엇을 못 하는가 새 지표를 검토할 때 할 수 있는 일보다 못 하는 일을 먼저 적어두면 나중에 실망이 적다.\n아룬으로 답할 수 없는 질문들 \"얼마에 사야 하나\" 가격 정보가 없다. 진입가는 다른 데서 가져와야 한다 \"손절은 어디에 둬야 하나\" 폭을 모르니 손절선도 못 정한다 — ATR 같은 크기 지표가 필요 \"이 움직임이 큰가 작은가\" +30% 스파이크와 +3% 상승을 구분 못 한다 \"거래가 실린 움직임인가\" 거래량을 안 본다. 텅 빈 호가에서 찍힌 저점도 저점으로 센다 답할 수 있는 질문 \"지금 추세 국면인가 정체 국면인가\" \"마지막 극값이 얼마나 최근인가\" 두 개뿐이다. 그런데 이 두 개를 다른 지표들이 못 한다 목록을 만들어보면 이 지표의 좁은 쓸모가 분명해진다. 할 수 있는 게 두 가지뿐인데, 그 두 가지를 기존 게이트가 못 한다. 그래서 후보로 남겨두는 것이다.\n반대로 이 목록이 길었다면 — 예컨대 \u0026ldquo;진입가도 정해주고 손절도 정해주는\u0026rdquo; 만능 지표가 있다고 주장한다면 — 그건 의심해야 할 신호다. 하나의 계산식이 여러 질문에 동시에 답한다고 하면 대개 그중 몇 개는 억지다.\n조합해서 쓴다면 아룬을 단독으로 쓰지 않는다는 전제에서, 어떤 지표와 짝지으면 좋을지 정리해둔다. 기준은 하나 — 못 보는 것이 반대인 지표를 고르는 것이다.\n크기를 재는 지표와의 조합이 우선이다. 아룬이 \u0026ldquo;최근에 저점을 갱신했다\u0026quot;고 알려주면, RSI나 낙폭 지표가 \u0026ldquo;얼마나 깊게 빠졌나\u0026quot;를 채워준다. 시점과 크기가 둘 다 있어야 판단이 된다.\n거래량과의 조합도 말이 된다. 아룬은 가격의 극값만 보므로 그 극값이 거래를 동반했는지 모른다. 거래가 거의 없는 상태에서 찍힌 저점은 의미가 약한데, 아룬은 그걸 구분하지 못한다.\n반대로 스토캐스틱이나 CCI와 묶는 건 별로다. 셋 다 결국 최근 구간에서의 상대 위치를 보는 것이라 정보가 크게 겹친다. 지표를 셋 켜놓고 다 같은 방향을 가리킨다고 확신이 커지는 건 착각일 수 있다. 같은 재료를 세 번 본 것뿐이니까.\n시간을 재는 다른 방법들 아룬만 시간을 재는 건 아니다. 봇에서 시간을 다루는 방식은 여러 가지고, 각각 성격이 다르다.\n시간을 쓰는 규칙들 아룬 극값 이후 경과 봉 수 — 시장의 시간 타임 스톱 포지션 보유 시간 — 내 포지션의 시간 내 봇 이력: 6시간 횡보 시 강제 청산을 넣었다가 나중에 제거 쿨다운 청산 후 재진입까지의 대기 — 행동의 시간 현행: 재장전 쿨다운 30분 스테이 수확 조건 충족 후 대기 — 확정의 시간 현행: 30분 (60분 대비 실측 우위) 격리 손절 종목 재매수 차단 — 표본 독립성의 시간 현행: 그날 자정까지 시간대 차단 절대 시각 기준 — 시장 리듬의 시간 실측으로 도입했다가 매수 가뭄으로 해제 여섯 개 다 '시간' 인데 재는 대상이 전부 다르다 이 목록을 만들면서 새삼 느낀 게 있다. 시간 관련 규칙이 이렇게 많은데 진입 판정에 쓰는 시간 축만 없다. 청산, 대기, 차단은 전부 시간으로 관리하면서 \u0026ldquo;이 종목을 살까\u0026quot;라는 판단에는 시간이 안 들어간다.\n왜 그랬는지 생각해보면, 진입 조건은 지표에서 가져왔고 지표는 대부분 크기를 재기 때문이다. 청산이나 쿨다운은 내가 직접 설계한 규칙이라 자연스럽게 시간을 썼다. 남의 도구를 쓰는 영역과 내가 만든 영역에서 사고 방식이 갈렸던 셈이다.\n스테이 시간을 정할 때의 실측이 좋은 예다. 60분과 30분을 재현해봤더니 30분 8회 +209,203원, 60분 6회 +159,530원이었다. 긴 대기는 회당 크기를 못 키우고 사이클 수만 깎았다. 이 결론은 이익 구간 체류시간 중앙값이 0분이라는 관찰과도 일치했다.\n같은 방식으로 진입 쪽 시간 축도 재볼 수 있을 것이다. \u0026ldquo;저점 갱신 후 몇 봉 지났을 때 사는 게 가장 나은가\u0026rdquo; — 이건 명확한 실험 설계가 가능한 질문이다.\n마무리 아룬은 유명한 지표가 아니다. RSI나 MACD처럼 어디서나 보이지도 않는다. 그런데 이 코너에서 다룰 가치가 있다고 생각한 건, 질문의 각도가 다르기 때문이다.\n지표를 여러 개 쓴다고 정보가 그만큼 늘지는 않는다. RSI와 스토캐스틱과 CCI를 동시에 켜봐야 셋 다 \u0026ldquo;얼마나 올랐나\u0026quot;를 조금씩 다르게 볼 뿐이다. 정말로 정보를 늘리려면 다른 종류의 질문을 하는 지표를 섞어야 한다. 크기를 재는 것 하나, 시간을 재는 것 하나, 거래량을 보는 것 하나, 호가를 보는 것 하나.\n그런 의미에서 아룬은 좋은 지표라기보다 좋은 축이다. 내 게이트 목록에 시간 기반 축이 아직 하나도 없다는 걸, 이 글을 쓰면서 알았다.\n덧붙임 — 지표 목록을 점검하는 법 이 글의 결론을 일반화하면 이렇게 된다. 게이트나 조건 목록을 점검할 때 개수를 세지 말고 축의 종류를 세라는 것.\n내 게이트를 축으로 분류하면 가격 크기 축 RSI · 낙폭 상한 · 저점근접 · 반등 확인 · 중기추세 · ROC → 여섯 개가 전부 이 축이다 거래량 축 거량 배수 · (유니버스의 거래대금 하한) 시장 미시구조 축 스프레드 상한 · 호가단위 비율 시장 전체 축 BTC 4시간봉 레짐 차단 시간 축 없음 개수로는 열 개인데 축으로는 네 종류다 새 조건을 검토할 때 물을 것: 기존에 없는 축인가? 이 분류를 해보고 나서 검토 우선순위가 바뀌었다. 가격 크기를 재는 지표를 하나 더 얹는 것보다, 없는 축을 채우는 쪽이 정보를 늘릴 확률이 높다.\n물론 축이 다르다고 반드시 유용한 건 아니다. 시간 축이 없는 게 우연이 아니라 그 축이 이 전략에 안 맞아서일 수도 있다. 그건 재봐야 안다. 다만 검토 대상 목록의 순서를 정할 때는 이 관점이 도움이 된다.\n지표 스터디를 여러 편 쓰면서 얻은 것 중에, 개별 지표 지식보다 이런 분류 감각이 더 오래갈 것 같다. 새 지표를 만나면 이제 자동으로 묻게 된다. 이건 무슨 축인가, 내가 이미 가진 축인가, 아니면 새로운 각도인가.\n","permalink":"https://botlab.co.kr/study/aroon/","summary":"얼마나 올랐나가 아니라 마지막 고점이 며칠 전이었나를 묻는다. 이 각도가 왜 특이한지.","title":"Aroon Oscillator — 가격이 아니라 시간을 재는 지표 | Crypto Trading Bot"},{"content":"지표가 아니라 심사 기준 거래소 앱을 열면 종목마다 \u0026ldquo;24시간 거래대금\u0026quot;이 붙어 있다. 이건 엄밀히 말해 매매 신호를 주는 지표가 아니다. 이 종목을 내가 사고팔 수 있느냐를 묻는 심사 기준에 가깝다.\n내 봇에서 이 숫자가 하는 일도 딱 그거다. 스캔을 시작하기 전에 후보 목록 자체를 잘라낸다. 조건이 아무리 완벽해도 유동성이 없는 종목은 아예 목록에 안 올린다. 게이트 열 개를 통과시키는 것보다 먼저 하는 일이다.\n봇에서의 처리 순서 ① 전 종목 조회 거래소 상장 목록 전부 ② 유니버스 필터 ◀ 여기가 24시간 거래대금 거래대금 하한 미달 → 제외 가격 하한 미달 → 제외 (호가 단위 문제) ③ 지표 계산 살아남은 종목만 ④ 게이트 판정 10개 조건 ⑤ 랭킹 → 진입 ②에서 자르면 ③~⑤ 의 계산량도 함께 줄어든다 — API 호출 절약이라는 부수 효과 ②를 건너뛰면 ④에서 통과한 종목이 ⑤에서 체결이 안 되는 사고가 난다 거래량과 거래대금은 다른 말이다 용어부터 정리하고 가자. 차트 아래 막대는 보통 거래량(수량)인데, 종목을 가로질러 비교할 때 의미가 있는 건 거래대금이다.\n수량 vs 금액 거래량(수량) 몇 개가 거래됐나 BTC 1개와 200원짜리 코인 1개가 같은 '1' 로 계산된다 종목 간 비교에 쓸 수 없다 거래대금(금액) 얼마어치가 거래됐나 = 수량 × 가격 종목 간 비교가 가능하다 — 유니버스 필터에 쓰는 건 이쪽 거래소 API 에서 volume 기초자산 수량 (base volume) quote_volume 원화 환산 금액 (quote volume) ← 대개 이걸 쓴다 직접 계산할 때 주의: 수량 × 종가 는 근사치다 봉 안에서 가격이 움직였다면 실제 체결 금액과 다르다 거래소가 quote_volume 을 주면 그걸 쓰는 게 정확하다 이 구분을 안 하면 유니버스 필터가 이상하게 작동한다. 수량 기준으로 하한을 걸면 가격이 낮은 코인만 통과하게 되는데, 그건 필터가 아니라 저가주 선별기다.\n같은 실수를 나도 다른 형태로 했다. 신호 게이트에 절대 거래대금 기준을 썼다가 \u0026ldquo;게이트가 종목을 거르는 게 아니라 체급을 차별한다\u0026quot;는 지적을 받은 적이 있다. 유니버스 필터에는 절대 금액이 맞고, 신호 게이트에는 자기 평균 대비 배수가 맞다. 같은 재료라도 어느 자리에 쓰느냐에 따라 기준이 달라진다.\n왜 하필 24시간인가 1시간도 아니고 7일도 아니고 왜 24시간일까. 이유는 코인 시장의 구조에 있다.\n주식 시장은 개장과 폐장이 있어서 \u0026ldquo;하루치 거래량\u0026quot;이라는 게 자연스러운 단위다. 코인은 24시간 돌기 때문에 그런 경계가 없다. 그래서 롤링 24시간 — 지금 이 순간부터 정확히 하루 전까지 — 을 쓴다. 언제 조회하든 같은 길이의 창을 보게 된다.\n창 길이를 바꾸면 무엇이 달라지나 1시간 너무 예민하다. 펌핑 한 번에 순위가 통째로 뒤집힌다 \"방금 누가 크게 샀다\" 는 알 수 있지만 \"평소 거래가 되는 종목인가\" 는 모른다 24시간 하루 안의 시간대 편차를 한 바퀴 흡수한다 한국 저녁의 활발함과 새벽의 한산함이 같은 창 안에 들어간다 → 조회 시각에 따라 값이 크게 안 흔들린다 7일 너무 둔하다. 사흘 전에 죽은 종목이 아직 살아 있는 것처럼 보인다 상장 폐지 예정·거래 중단 직전 종목을 못 거른다 24시간이 표준이 된 건 관습이 아니라 시간대 편차를 한 주기 담기 때문이다 시간대 편차 이야기는 내 봇에서도 실측한 적이 있다. 스캔 기록 1,116건을 시간대별로 갈라 성과를 봤더니 저녁 시간대(한국 시각 1923시)가 뚜렷하게 나빴다. 19시 -0.752%, 20시 -0.806%, 21시 -0.716%였고 승률도 1020%대였다. 거래가 제일 활발한 시간이 성과가 제일 나빴던 것이다.\n거래량이 많다는 건 참여자가 많다는 뜻이지 내 전략이 잘 통한다는 뜻이 아니다. 이 구분을 놓치면 \u0026ldquo;붐빌 때 공격적으로\u0026quot;라는 직관적이지만 틀린 설계를 하게 된다. 실제로 처음엔 그렇게 만들었다가 데이터에 뒤집혔다.\n이 숫자가 거짓말하는 세 가지 경우 24시간 거래대금은 유용하지만 만능이 아니다. 이 숫자가 실제 유동성을 과대평가하는 경우가 있다.\n거래대금이 있어도 못 사는 경우 ① 몰린 거래량 하루 총액은 큰데 대부분이 특정 몇 분에 몰려 있다 그 시간을 벗어나면 호가창이 텅 빈다 → 지금 이 순간엔 못 산다 ② 넓은 호가 거래대금은 기준을 넘는데 호가 한 칸이 가격의 0.2% 를 넘는다 사는 순간 이미 마이너스로 시작한다 — 거래대금만으론 안 잡힌다 ③ 세탁 거래 같은 주체가 자기 물량을 주고받아 만든 숫자 호가창의 실제 두께와 무관하다 대응: 거래대금은 1차 관문일 뿐. 호가 기반 지표를 2차로 세운다 ②번은 내 봇이 실제로 물린 함정이다. 앞서 말한 본전 청산 27건 실측이 그 결과인데, 저가 코인의 호가 한 칸이 가격의 0.2%를 넘어 청산선이 호가 사이 허공에 놓이는 일이 있었다. 이걸 겪고 나서 보정을 한 번 하고(본전 바닥 0.4%로 상향), 이틀 뒤엔 아예 구조적으로 막았다. 호가 한 칸이 가격의 0.15%를 넘는 종목은 진입 금지라는 게이트를 신설한 것이다.\n이 순서가 중요하다고 생각한다. 사고 → 실측 → 원인 규명 → 임시 보정 → 구조적 예방. 나흘 걸렸다. 문제를 겪는 시스템에서 문제를 만나지 않는 시스템으로 옮겨가는 과정이 이런 모양이다.\n거래대금 순위표를 믿으면 안 되는 이유 거래소 앱에는 거래대금 순위가 있다. 위에서부터 몇 개를 골라 매매하는 사람도 많다. 그런데 이 순위표에는 성격이 전혀 다른 종목들이 섞여 있다.\n거래대금 상위에 오르는 세 가지 경로 ① 원래 큰 종목 시가총액이 크고 평소에도 거래가 많다. 순위가 잘 안 바뀐다 → 안정적. 다만 변동성이 작아 눌림목 전략의 먹잇감은 적다 ② 오늘 터진 종목 호재나 펌핑으로 하루 만에 거래가 폭증. 어제는 순위 밖이었다 → 유동성은 지금만 있다. 내일이면 사라질 수 있다 ③ 신규 상장 상장 직후 며칠은 거래가 몰린다. 과거 데이터가 아예 없다 → 지표 계산 자체가 불가능하다 (워밍업 봉이 안 쌓임) 셋을 구분하려면 24시간 값 하나로는 부족하다 거래대금의 최근 7일 대비 배수를 같이 보면 ②가 분리된다 상장 경과일 필터를 두면 ③이 분리된다 ③번은 봇에서 특히 조용하게 사고를 낸다. 상장한 지 얼마 안 된 종목은 봉이 부족해서 지표가 이상한 값을 뱉거나 결측이 나오는데, 이걸 걸러내지 않으면 판정 로직이 예상 못 한 경로로 들어간다. 조건문이 결측을 만나면 어느 쪽으로 튈지는 코드를 짠 사람도 모르는 경우가 많다.\n내가 무결성 검사에 \u0026ldquo;미정의 참조 0건\u0026quot;을 항목으로 넣어둔 이유도 이 계열이다. 크래시가 나면 오히려 다행이고, 조용히 잘못된 값으로 진행되는 게 제일 무섭다.\n하한선은 얼마가 적당한가 내 봇의 거래대금 하한은 몇 번 움직였다. 처음엔 50억으로 잡았는데 평가 가능한 종목이 10개 남짓밖에 안 됐다. 후보가 열 개면 게이트 열 개를 통과하는 종목이 사실상 안 나온다. 그래서 10억으로 내렸고, 평가 종목이 40~50개로 늘었다.\n하한선의 트레이드오프 하한 높음 후보 적음 · 유동성 좋음 · 체결 확실 · 기회 부족 50억 기준일 때 평가 대상 ~10 종목 → 게이트 통과 사실상 0 하한 낮음 후보 많음 · 유동성 나쁨 · 슬리피지 · 미체결 위험 현행은 거래대금과 가격 하한을 함께 걸어 균형을 잡는다 핵심: 하한선은 독립 변수가 아니다 게이트가 몇 개인지, 슬롯이 몇 개인지, 계좌 규모가 얼마인지에 따라 최적점이 움직인다 슬롯 4개짜리 봇은 후보가 20개만 있어도 돌아간다 슬롯 10개짜리 봇이었다면 같은 하한선에서 굶었을 것이다 실제로 슬롯을 10개에서 4개로 줄인 결정과 유니버스 하한 조정은 같은 시기에 맞물려 있었다. 슬롯이 적으면 후보도 적어도 되고, 슬롯당 금액이 커지니 호가가 넓은 종목의 부담도 커진다. 파라미터 하나만 떼어놓고 \u0026ldquo;이 값이 적당한가\u0026quot;를 논하는 게 왜 공허한지 보여주는 사례다.\n유니버스가 작다는 것의 의미 내 봇이 실제로 다루는 종목 수를 적어두면 이 글의 다른 얘기들이 더 잘 이해될 것이다. 필터를 통과하고 나면 스캔 대상은 수십 개 수준이다. 업비트 원화 마켓 전체에서 상당수가 걸러진다는 뜻이다.\n유니버스 크기가 만드는 제약들 유니버스가 20~50 종목일 때 ① 격리 규칙이 치명적이 된다 손절한 종목을 하루 격리하면 후보의 상당 비율이 하루 사라진다 실제로 격리 기간을 하루 → 0 으로 푼 적이 있다 포트 익절이 4슬롯을 한꺼번에 털면 후보 4개가 동시에 빠지기 때문 ② 게이트를 겹칠수록 통과가 0 에 수렴한다 50 종목에 10 게이트면 전부 통과하는 종목이 없는 날도 생긴다 ③ 시간대 차단 같은 규칙의 비용이 커진다 하루 5시간을 막으면 기회의 20% 가 사라진다 그래서 이 봇의 규칙들은 전부 '유니버스가 작다' 는 전제 위에 서 있다 종목이 수천 개인 시장이었다면 완전히 다른 설계가 나왔을 것이다 ①번은 실제로 겪은 일이다. 손절한 종목을 하루 격리하는 규칙을 넣었는데, 포트폴리오 익절로 네 슬롯을 한꺼번에 수확하면 그 네 종목이 동시에 격리 목록에 들어갔다. 유니버스가 작으니 그다음 스캔에서 살 게 없었다. 그래서 격리를 아예 0으로 풀었다가, 나중에 \u0026ldquo;손절한 종목만 그날 자정까지\u0026quot;라는 절충안으로 다시 정리했다.\n이 사례가 보여주는 건 규칙 하나가 독립적으로 존재하지 않는다는 것이다. 격리 기간은 유니버스 크기의 함수이고, 유니버스 크기는 거래대금 하한의 함수이고, 하한은 슬롯 수와 계좌 규모의 함수다. 하나를 바꾸면 나머지가 다 흔들린다.\n계좌 규모라는 숨은 변수 한 가지 더. 24시간 거래대금 기준은 내 주문 크기와의 상대적 관계로 봐야 한다.\n하루 10억이 거래되는 종목에 10만 원을 넣는 것과 1억을 넣는 것은 완전히 다른 일이다. 전자는 호가창에 흔적도 안 남지만 후자는 자기가 자기 가격을 밀어 올린다. 흔히 하루 거래대금의 일정 비율 이하로 주문을 제한하는 원칙을 쓰는데, 소액 계좌에서는 이게 거의 제약이 안 된다.\n규모별로 이 지표의 의미가 달라진다 소액 계좌 내 주문이 시장에 영향 없음 → 거래대금 기준의 목적은 '체결 가능성' 과 '호가 폭' 확보 → 하한선을 비교적 낮게 잡아도 된다 중대형 계좌 내 주문이 호가를 움직임 → 목적이 '시장 충격 회피' 로 바뀐다 → 하한선이 훨씬 높아지고, 분할 주문 설계가 필요해진다 같은 지표, 같은 종목, 다른 판정 — 계좌 규모가 기준을 바꾼다 내 봇은 전자다. 그래서 이 글의 숫자들을 그대로 가져다 쓰면 안 된다 스프레드와 함께 봐야 완성된다 거래대금이 1차 관문이라면 2차 관문은 스프레드다. 매수 최우선 호가와 매도 최우선 호가의 간격 말이다.\n두 숫자를 같이 보면 사분면이 나온다 스프레드 좁음 스프레드 넓음 거래대금 큼 ✓ 최적 △ 이상 신호 정상 종목 거래는 많은데 호가가 비었다? 세탁 거래 의심 구간 거래대금 작음 △ 조용한 종목 ✗ 최악 지금은 한산할 뿐 사는 순간 손해로 시작 절대 건드리면 안 되는 칸 내 봇의 대응 유니버스 단계 — 거래대금 하한 + 가격 하한 게이트 단계 — 스프레드 상한 (9번째 게이트로 승격) 게이트 단계 — 호가 한 칸 / 가격 비율 0.15% 상한 세 겹을 두는 이유: 각각이 못 잡는 경우가 다르기 때문이다 오른쪽 위 칸(거래대금은 큰데 스프레드가 넓다)이 흥미롭다. 논리적으로 잘 안 어울리는 조합인데, 실제로 나타나면 거래대금 숫자를 의심해볼 만하다. 하루 종일 활발히 거래된 종목의 호가창이 비어 있을 이유가 별로 없기 때문이다.\n스프레드를 게이트로 올린 건 원래 계획에 없던 일이었다. 터미널 로그에 \u0026ldquo;조건은 다 통과했는데 왜 안 샀지?\u0026rdquo; 하는 종목이 자꾸 보여서 추적해보니, 게이트 밖에서 스프레드 조건에 막히고 있었다. 판정은 하는데 화면에 안 보이니 원인을 모르고 있었던 것이다. 그래서 아예 정식 게이트로 승격시켜 통과율 통계에 포함시켰다.\n보이지 않는 조건은 없는 조건보다 나쁘다. 있는데 안 보이면 디버깅이 불가능해진다.\n거래대금과 변동성의 관계 한 가지 자주 오해되는 게 있다. 거래대금이 큰 종목이 안전하다는 생각이다.\n거래대금이 크다는 건 체결이 쉽다는 뜻이지 가격이 안정적이라는 뜻이 아니다. 오히려 하루 만에 거래가 폭증한 종목은 대체로 그날 크게 움직인 종목이다. 거래가 몰리는 이유가 가격이 움직였기 때문이니 당연하다.\n그래서 거래대금 필터는 변동성 필터를 대신하지 못한다. 둘은 다른 축이고, 각각 따로 세워야 한다. 내 봇에도 변동성 조건이 별도로 있는데, 하한과 상한이 함께 있다. 너무 조용한 종목은 움직임이 없어서 못 먹고, 너무 요동치는 종목은 손절선이 의미를 잃기 때문이다.\n이 상하한 구조를 처음부터 알고 만든 건 아니다. 박스장에서 진입이 막혔을 때 변동성 하한을 계속 내렸는데, 그러다 보니 이번엔 정상 범위를 한참 벗어난 종목까지 들어왔다. 한쪽을 풀면 다른 쪽이 새는 구조였다. 조건을 하나 완화할 때는 그 조건이 막고 있던 게 무엇이었는지 같이 봐야 한다는 걸 그때 배웠다.\n하한선을 정할 때 실제로 해본 것 값을 정하는 절차를 구체적으로 남겨두는 게 이 글에서 제일 쓸모 있는 부분일 것 같다. 거래대금 하한을 정할 때 나는 성과가 아니라 후보 개수부터 봤다.\n하한선 결정 순서 ① 후보 개수 곡선을 먼저 그린다 하한값을 바꿔가며 통과 종목 수를 센다 (성과는 아직 안 본다) 50억 → 약 10종목 · 10억 → 40~50종목 ② 최소 필요 후보 수를 정한다 슬롯 4개 · 게이트 10개 → 후보가 최소 20~30 은 돼야 통과가 나온다 이건 성과 문제가 아니라 봇이 굶느냐 마느냐의 문제 ③ 그 범위 안에서만 성과를 비교한다 후보가 10개인 구간은 성과를 볼 것도 없이 탈락 ④ 호가·가격 조건을 별도로 세운다 거래대금 하한을 낮춘 대가는 다른 축에서 막는다 핵심: 성과 최적화 이전에 '봇이 돌아가는 범위' 를 먼저 확정한다 ①번을 먼저 하는 이유가 있다. 후보가 10개인 상태에서 성과를 재면 표본이 너무 적어 아무 결론도 안 나온다. 성과 비교는 봇이 정상 작동하는 구간 안에서만 의미가 있다.\n이 순서를 몰라서 시간을 버린 적이 있다. 하한을 높게 잡아놓고 \u0026ldquo;왜 성과가 안 나오지\u0026rdquo; 하며 다른 파라미터를 만졌는데, 실은 진입 자체가 거의 없어서 통계가 무의미했던 것이다. 문제를 엉뚱한 데서 찾고 있었다.\n표본이 없으면 최적화도 없다. 이건 이 봇의 개발 전체를 관통하는 제약이다. 조건을 조일수록 성과는 좋아 보이지만 표본이 마르고, 표본이 마르면 그 판단이 맞는지 확인할 수 없게 된다. 시간대 차단 규칙을 해제한 것도 결국 같은 이유였다.\n실전 체크리스트 이 글에서 다룬 것들을 봇에 넣을 때 확인할 항목으로 정리해둔다.\n유니버스 필터 점검표 □ 거래대금 하한이 내 슬롯 크기·게이트 개수와 함께 정해졌는가 후보가 몇 개 남는지 실제로 세어봤는가 (10개 남으면 봇이 굶는다) □ 가격 하한이 있는가 저가 코인의 호가 단위 문제를 거래대금만으로는 못 잡는다 □ 호가 기반 조건이 별도로 있는가 스프레드 상한 · 호가 한 칸/가격 비율 — 거래대금과 다른 것을 잰다 □ 상장 경과일 조건이 있는가 지표 워밍업이 안 된 종목은 판정 자체가 무의미하다 □ 필터에 걸린 종목이 로그에 남는가 왜 안 샀는지 나중에 추적 못 하면 튜닝이 감으로 돌아간다 □ 유니버스 크기를 매 스캔마다 기록하는가 어느 날 갑자기 후보가 줄었을 때 원인을 시장 탓/코드 탓으로 가를 수 있다 마지막 두 항목이 실전에서 제일 자주 빠진다. 필터는 조용히 일하기 때문에 잘 작동하는지 아닌지가 안 보인다. 그러다 \u0026ldquo;이틀째 매수가 없다\u0026rdquo; 같은 상황이 오면 원인을 못 찾는다.\n내 봇도 그 상황을 겪고 나서 게이트별 통과율을 매 스캔마다 출력하게 바꿨다. 돌파 3/15, 거래량 8/15 같은 식으로 찍어주니 어느 관문에서 막히는지 바로 보였다. 측정하지 않는 필터는 튜닝할 수 없다.\n호가 한 칸이라는 진짜 관문 거래대금이 유동성의 대리 지표라면, 호가 단위는 그 유동성의 해상도다. 이 개념이 내 봇에서 어떻게 게이트가 됐는지 자세히 적어둘 가치가 있다.\n업비트는 가격대별로 호가 단위가 정해져 있다. 가격이 낮을수록 단위도 작아지지만, 비율로 보면 저가 코인이 훨씬 불리하다.\n호가 한 칸의 비율 — 가격대별 호가 단위 / 현재가 × 100 = 한 칸이 차지하는 비율 10,000원대 종목 한 칸이 가격의 0.1% 미만 — 여유롭다 1,000원대 종목 0.1% 안팎 443원 종목 한 칸이 0.226% (실측 사례) 100원대 종목 더 나빠진다 이게 왜 문제인가 퍼센트로 설계한 청산선이 호가와 호가 사이 허공에 놓인다 +0.2% 에 바닥을 뒀는데 한 칸이 0.226% 면 그 가격은 존재하지 않는다 → 한 칸 아래에서 체결되거나 아예 체결이 안 된다 현행 대응: 호가 한 칸 / 가격 비율이 0.15% 를 넘으면 진입 금지 이 게이트가 만들어진 과정이 개인적으로 가장 만족스러운 사례 중 하나다. 순서가 이랬다.\n먼저 이상 징후를 봤다. 본전 사수 장치가 이익을 지키라고 만든 건데 실제로는 손실이 나고 있었다. 그다음 실측을 했다. 본전 청산 27건을 뽑아보니 바닥을 +0.2%에 뒀는데 평균 실현이 +0.017%, 합계로는 마이너스였다. 원인 규명 단계에서 슬리피지를 의심했다가 아니라는 걸 확인했고, 호가 관통이 범인이었다. 관통 손실 0.183%p에 왕복 수수료 0.10%를 더하면 손익분기가 0.283%였다.\n그래서 1차로 값을 보정했다. 바닥을 0.4%로 올리고 무장선도 0.8%로 함께 올렸다. 간격이 좁으면 무장하자마자 청산돼서 승자를 못 키우기 때문이다. 60건을 재현해보니 본전 손익이 −33,203원에서 +44,906원으로 뒤집혔다.\n여기까지가 보정이고, 이틀 뒤에 구조적 예방을 넣었다. 애초에 호가가 넓은 종목을 안 사는 게이트를 만든 것이다. 문제를 겪고 대응하는 시스템에서 문제를 안 만나는 시스템으로 옮긴 셈이다.\n사고에서 게이트까지 — 나흘 1일차 이상 징후: 본전 사수가 적자를 내고 있다 2일차 실측 27건 — 바닥 +0.2% 설계, 평균 실현 +0.017%, 합계 −18,144원 2일차 원인 규명 — 슬리피지 아님. 호가 관통 0.183%p 2일차 보정 — 바닥 0.4% / 무장 0.8%, 60건 재현으로 확인 4일차 구조화 — 호가단위 게이트 신설 (0.15% 상한) 이 파이프라인의 속도가 개발 체계의 성적표라고 생각한다 감으로 고치면 하루면 되지만 같은 문제가 다시 온다 측정하고 고치면 나흘 걸리는 대신 다시 안 온다 이 필터가 막아준 것들 거래대금 필터의 성과는 눈에 잘 안 띈다. 막아낸 사고는 일어나지 않았으니 기록에도 안 남기 때문이다. 그래도 정황으로 짐작되는 건 있다.\n내 봇 이력에서 유동성 관련 사고는 대부분 필터가 약했던 시기에 몰려 있다. 저유동성 코인에서 매도 주문이 체결되지 않았는데 봇은 팔렸다고 판단해 포지션 목록에서 지워버린 일이 있었다. 거래소에는 물량이 남아 있고 봇은 모르는 상태 — 이른바 유령 포지션이다. 이걸 겪고 나서 체결 확인 폴링을 넣었고, 거래소 보유분과 봇 상태를 대조해 자동 복구하는 로직도 붙였다.\n사고 자체는 집행 로직의 문제였지만, 애초에 그 종목이 후보 목록에 없었다면 일어나지 않았을 일이다. 필터는 사고를 막는 게 아니라 사고가 일어날 수 있는 표면적을 줄인다. 그래서 성과 측정이 어렵고, 그래서 자주 과소평가된다.\n체결 확인 — 유동성 문제의 마지막 방어선 필터를 아무리 잘 세워도 뚫리는 경우가 있다. 그래서 마지막 방어선이 하나 더 필요하다. 주문을 냈으면 체결됐는지 확인하는 것이다.\n당연한 얘기 같지만 초기 코드에서는 이걸 안 했다. 주문 API가 성공 응답을 주면 체결된 것으로 간주했다. 그런데 주문 접수와 체결은 다른 사건이다.\n유령 포지션이 만들어지는 경로 잘못된 흐름 ① 매도 주문 전송 ② 응답에 주문번호가 있음 → \"팔렸다\" 고 판단 ③ 봇의 보유 목록에서 해당 종목 제거 ④ 그런데 실제로는 체결 안 됨 (호가에 물량이 없어서) ⑤ 거래소엔 물량이 남아 있고 봇은 모른다 = 유령 포지션 고친 흐름 ① 매도 주문 전송 ② 주문 상태를 반복 조회 (체결 폴링) ③ 체결 확인되면 보유 목록에서 제거 ④ 일정 시간 내 미체결이면 목록 유지 + 로그 추가 방어: 계좌 보유분과 봇 상태를 주기적으로 대조 거래소엔 있는데 봇 목록에 없는 종목 → 자동으로 다시 편입 이 사고는 저유동성 코인에서 났다. 거래대금 하한을 겨우 넘긴 종목이었고, 매도 시점에 호가창이 비어 있었다. 필터가 약했던 게 1차 원인이고, 체결 확인을 안 한 게 2차 원인이다.\n여기서 배운 게 방어를 한 겹만 두면 안 된다는 것이었다. 유니버스 필터가 뚫려도 게이트가 막고, 게이트가 뚫려도 체결 확인이 잡고, 그것도 놓치면 원장 대조가 잡는다. 각 층이 못 잡는 경우가 서로 다르기 때문에 겹쳐야 의미가 있다.\n그리고 이 사고 이후 원칙이 하나 생겼다. 봇의 기억보다 거래소의 기록을 믿는다. 봇이 \u0026ldquo;팔았다\u0026quot;고 생각하는 것과 거래소가 \u0026ldquo;체결됐다\u0026quot;고 말하는 것이 다를 때, 거래소 쪽이 정답이다. 손익 계산의 근거를 봇 메모리에서 거래소 주문내역으로 옮긴 것도 같은 맥락이다.\n시간대별 거래량 — 언제 사야 하나 거래대금의 세 번째 얼굴은 시장의 리듬이다. 코인 시장은 24시간 돌지만 균질하지 않다.\n처음 설계할 때 나는 직관적인 가정을 했다. 거래가 활발한 시간에 기회가 많을 테니 그때 공격적으로 가자는 것. 그래서 시간대별로 평가 종목 수와 진입 문턱을 다르게 두는 구조를 만들었다. 한국 저녁 시간대는 평가 종목을 늘리고 문턱을 낮췄고, 낮 시간대는 보수적으로 잡았다.\n실측이 이 설계를 뒤집었다.\n시간대별 성과 — 스캔 1,116건 실측 19시 평균 −0.752% 승률 19.4% 20시 평균 −0.806% 승률 12.1% 21시 평균 −0.716% 승률 14.6% 22시 평균 −0.044% 23시 평균 −0.409% 승률 23.6% 거래가 제일 활발한 시간대가 성과는 제일 나빴다 19~23시 차단 채택 → 잔여 평균 −0.240% → −0.182% 부트스트랩 우세 78.8% 그런데 이 규칙은 나중에 해제된다 이유: 매수 가뭄. 하루 8건 → 1.6건으로 급감 성과 개선 +0.058%p 보다 표본 고갈의 비용이 컸다 이 사례에서 두 가지를 배웠다.\n하나, 거래량이 많다는 건 참여자가 많다는 뜻이지 내 전략이 잘 통한다는 뜻이 아니다. 붐비는 시간에는 나 같은 눌림목 사냥꾼도 많다. 좋은 자리가 남아 있지 않을 수 있는 것이다.\n둘, 통계적으로 옳은 규칙이 운용상 틀린 규칙일 수 있다. 19~23시 차단은 데이터가 지지하는 결정이었다. 부트스트랩 우세 78.8%면 우연이라고 보기 어렵다. 그런데 실제로 돌려보니 진입이 하루 1.6건으로 줄었고, 그러면 통계를 쌓을 표본 자체가 안 만들어진다. 검증 가능한 상태를 유지하는 것도 운용의 일부라는 걸 그때 알았다.\n이건 백테스트와 실전의 차이이기도 하다. 백테스트에서는 표본이 이미 존재하지만, 실전에서는 매매를 해야 표본이 생긴다. 진입을 줄이는 규칙은 성과를 개선하는 동시에 학습 속도를 떨어뜨린다. 그 교환을 계산에 넣어야 한다.\n마무리 24시간 거래대금은 화려한 지표가 아니다. RSI처럼 매수 시점을 알려주지도, 볼린저 밴드처럼 그림이 예쁘지도 않다. 하는 일은 딱 하나 — 문 앞에서 못 들어올 종목을 돌려보내는 것이다.\n그런데 봇을 오래 굴리다 보면 이 종류의 규칙이 수익률에 미치는 영향이 생각보다 크다는 걸 알게 된다. 좋은 종목을 찾는 것보다 나쁜 종목을 안 만나는 게 쉽고, 쉬운 쪽이 더 확실하게 계좌를 지킨다. 신호를 잘 잡는 문제와 계좌를 지키는 문제는 다른 문제이고, 후자는 대체로 이런 지루한 필터들이 담당한다.\n거래소 앱에서 무심코 지나치던 그 숫자가, 사실은 내 봇에서 가장 먼저 실행되는 조건이다.\n덧붙임 — 세탁 거래를 의심하는 방법 마지막으로 이 지표의 신뢰성 문제를 조금 더 다루고 끝내자. 거래량 지표는 시장이 정직하다는 전제 위에 서 있는데, 그 전제가 항상 성립하지는 않는다.\n세탁 거래는 같은 주체가 자기 물량을 주고받아 거래량을 부풀리는 행위다. 목적은 대개 순위표 노출이나 상장 유지 조건 충족이다. 개인이 이걸 정확히 판별하기는 어렵지만, 의심할 만한 정황은 몇 가지 볼 수 있다.\n정황 점검 항목 ① 거래대금 대비 호가 두께가 얇다 하루 종일 활발히 거래된 종목의 호가창이 비어 있을 이유가 없다 ② 체결 크기 분포가 이상하다 비슷한 크기의 체결이 규칙적으로 반복되면 사람의 매매 같지 않다 ③ 가격 변동 없이 거래량만 크다 진짜 수급이라면 가격이 움직인다. 안 움직이는데 거래만 많으면 이상하다 ④ 특정 시간대에만 거래가 몰린다 자동화된 반복 패턴일 수 있다 봇 관점의 실용적 결론 판별하려 애쓰기보다 호가 기반 조건으로 우회하는 게 낫다 세탁 거래는 거래대금을 부풀릴 수 있지만 호가창을 채우진 못한다 스프레드 상한과 호가단위 게이트가 이 문제의 실질적 방어선이다 마지막 줄이 실무적 답이다. 세탁 거래인지 아닌지를 판정하려 하지 말고, 세탁 거래가 만들어낼 수 없는 조건을 거는 것. 거래대금 숫자는 조작할 수 있어도 실제로 살 수 있는 호가는 조작하기 어렵다.\n이 접근은 다른 문제에도 적용된다. 원인을 정확히 규명하기 어려울 때, 원인 대신 증상이 나타나는 경로를 막는 것. 유령 포지션 문제를 체결 확인으로 막은 것도, 호가 관통을 게이트로 막은 것도 같은 발상이다. 완벽한 이해가 없어도 실용적인 방어는 세울 수 있다.\n","permalink":"https://botlab.co.kr/study/volume-24h/","summary":"이건 신호 지표가 아니라 입장권 심사다. 24시간이라는 창을 왜 쓰는지, 그리고 이 숫자가 언제 거짓말하는지.","title":"24h Trading Value — 살 수 있는 종목인지 먼저 묻는다 | Crypto Trading Bot"},{"content":"모든 사람이 처음 배우는 규칙 이 글은 지표를 소개하는 글이라기보다 왜 버렸는지에 대한 기록에 가깝다. 그래서 다른 편들과 결이 조금 다르다. 새로 도입할 지표를 검토하는 게 아니라, 실제로 써봤다가 접은 물건의 부검 보고서다.\n차트를 처음 열면 누구나 만나는 게 이동평균선이다. 그리고 처음 배우는 매매 규칙은 대개 이것이다. 짧은 이동평균이 긴 이동평균을 아래에서 위로 뚫으면 사고(골든크로스), 반대면 판다(데드크로스).\n내 봇도 여기서 시작했다. 빗썸에서 업비트로 옮긴 직후의 시대를 나는 MA봇 시절이라고 부르는데, 진입 판단이 통째로 이동평균 교차였기 때문이다. 지금 봇에는 이 규칙이 한 줄도 안 남아 있다. 왜 버렸는지가 이 글의 주제다.\n평균의 종류부터 이동평균은 하나가 아니다. 실무에서 부딪히는 건 대개 세 가지다.\n같은 이름, 다른 계산 SMA 단순이동평균 최근 n봉을 똑같은 무게로 평균 close.rolling(n).mean() 성질: n봉 전 값이 빠질 때 튄다 (드롭오프 효과) EMA 지수이동평균 최근 값에 더 큰 무게, 과거는 지수적으로 감쇠 close.ewm(span=n, adjust=False).mean() 성질: 반응이 빠르다. 대신 노이즈도 빨리 받는다 RMA 와일더 평활 EMA 의 일종이지만 alpha = 1/n (더 무겁다) close.ewm(alpha=1/n, adjust=False).mean() 성질: RSI·ATR 내부에서 쓰는 그 평균. EMA(n) 보다 느리다 주의: EMA(14) 와 RMA(14) 는 다른 값이다. alpha 가 2/15 대 1/14 로 다르다 교차 전략에서 SMA→EMA 로 바꾸면 신호 시점이 통째로 이동한다. 즉 '어떤 평균이냐' 는 파라미터가 아니라 전략의 일부다 이걸 먼저 정리하는 이유는, 백테스트 결과를 비교할 때 사람들이 기간(20/60)만 적고 종류(SMA/EMA)를 안 적는 일이 흔하기 때문이다. 같은 20/60 교차라도 SMA와 EMA는 전혀 다른 매매를 한다.\n이동평균이 실제로 하는 일 교차 얘기로 넘어가기 전에 평균 자체가 무엇인지 한 번 짚자. 이동평균은 흔히 \u0026ldquo;추세선\u0026quot;이라고 불리지만, 신호처리의 언어로 보면 저역 통과 필터다. 빠른 진동을 걷어내고 느린 흐름만 남기는 장치.\n평균이 걸러내는 것과 남기는 것 원본 가격 = 느린 흐름 + 빠른 진동(노이즈) 이동평균 통과 후 = 느린 흐름만 (거의) 기간 n 이 커질수록 더 많은 진동을 제거한다 → 선이 매끈해진다 동시에 더 느려진다 → 실제 변화를 늦게 반영한다 이건 트레이드오프가 아니라 물리적 제약이다 노이즈를 지우려면 정보를 평균 내야 하고, 평균을 내려면 시간이 필요하다 \"부드러우면서 빠른 이동평균\" 같은 건 존재하지 않는다 HMA·ZLEMA 같은 변형들이 이 제약을 우회하려 시도하지만, 대개 과거를 외삽하는 방식이라 반전 구간에서 오버슈팅한다 마지막 항목을 짚는 이유는, \u0026ldquo;지연 없는 이동평균\u0026quot;을 표방하는 지표를 종종 보기 때문이다. 대부분은 과거 값의 기울기를 이용해 앞을 조금 내다보는 방식인데, 방향이 바뀌는 순간에는 그 외삽이 반대로 작동한다. 공짜로 얻어지는 건 없다는 원칙이 여기서도 성립한다.\n핵심 문제 — 지연 이동평균은 정의상 과거의 평균이다. 그러니 현재 가격보다 늦다. 얼마나 늦느냐를 대충이 아니라 수치로 보면 이렇다.\n지연의 크기 SMA(n) 의 무게중심은 n봉의 한가운데 → 이론적 지연 ≈ (n−1)/2 봉 SMA(20) ≈ 9.5봉 지연 SMA(60) ≈ 29.5봉 지연 15분봉 기준으로 환산하면 SMA(20) ≈ 2시간 22분 전의 평균을 보고 있는 셈 SMA(60) ≈ 7시간 22분 전 교차는 두 지연선이 만나는 시점이다 — 사건이 아니라 사건의 그림자 EMA 는 이보다 빠르지만 (span n 의 지연 ≈ (n−1)/2 를 넘지는 않음) 빨라진 만큼 가짜 교차도 늘어난다. 공짜는 없다 여기서 골든크로스의 정체가 드러난다. 골든크로스는 \u0026ldquo;지금부터 오른다\u0026quot;는 신호가 아니라 \u0026ldquo;이미 한동안 올랐다\u0026quot;는 사후 확인이다. 상승이 시작되고 짧은 평균이 따라 올라오고, 그게 긴 평균을 넘을 때까지 걸리는 시간만큼 가격은 이미 움직인 뒤다.\n추세가 길게 이어지는 시장이라면 그래도 괜찮다. 남은 구간이 충분하니까. 문제는 그렇지 않은 시장이다.\n휩쏘 — 교차 전략의 사망 원인 가격이 옆으로 기면 두 평균선이 서로 얽힌다. 그러면 교차가 연달아 터진다.\n횡보장에서 벌어지는 일 가격 ──╲─╱─╲──╱╲──╱─╲──╱ (방향 없이 진동) 단기 ───╲╱───╲╱───╲╱─── 긴 평균 근처에서 계속 교차 장기 ───────────────── ↑골든 ↓데드 ↑골든 ↓데드 ↑골든 … 매수 손절 매수 손절 매수 비용 계산 (업비트 기준, 왕복 수수료 0.1% 가정) 휩쏘 10회 = 수수료만 1.0% + 스프레드·호가 관통 별도 저가 코인은 호가 한 칸이 0.2% 를 넘기도 한다 — 실측 사례 있음 즉 방향이 맞아도 자주 틀리면 수수료가 먼저 계좌를 비운다 호가 한 칸 이야기는 실제로 겪은 것이다. 본전 보호선을 +0.2%에 뒀는데 27건을 실측해 보니 평균 실현이 +0.017%였고 합계로는 마이너스였다. 원인은 슬리피지가 아니라 호가 관통이었다. 443원짜리 코인에서 호가 한 칸이 0.226%였으니, 0.2%짜리 선은 호가와 호가 사이 허공에 떠 있었던 셈이다. 관통 손실에 왕복 수수료를 더하면 손익분기가 0.283%였고, 그래서 바닥을 0.4%로 올렸다.\n이 계산이 교차 전략에도 그대로 적용된다. 교차 한 번에 왕복 0.1%가 나가고, 호가가 넓은 종목이면 거기에 0.2%가 더 붙는다. 하루에 몇 번씩 교차가 뜨는 종목에서 이 전략을 돌리면 방향 예측의 정확도와 무관하게 진다.\n골든크로스는 정말 통계적으로 유효한가 이 질문에 대해 내가 확답할 수는 없다. 내 매매기록으로 교차 전략을 제대로 스윕해본 적이 없기 때문이다. 다만 어떻게 확인해야 하는지는 말할 수 있다.\n교차의 유효성을 재는 절차 가설: \"골든크로스 이후 k봉 동안의 수익률이 무작위 시점보다 높다\" ① 실험군 교차 발생 시점 이후 k봉 수익률을 전부 수집 ② 대조군 같은 기간에서 무작위로 뽑은 시점의 k봉 수익률 ③ 비교 실험군 평균이 대조군 분포의 바깥에 있는가 k 를 여러 값으로 (4, 16, 96봉 = 1시간·4시간·24시간) 각각 측정 추가로 봐야 할 것 · 교차 직후 되돌림의 크기 (손절에 걸릴 확률) · 신호 빈도 (비용 계산의 분모) 주의: 상승장 구간만 뽑아 재면 무엇을 해도 이긴다 상승·하락·횡보 구간을 각각 나눠서 봐야 한다 마지막 주의사항이 교차 전략 백테스트에서 특히 중요하다. 코인 시장은 큰 상승 구간이 몇 번 있었고, 그 구간을 포함해서 재면 추세추종 전략은 대부분 좋아 보인다. 문제는 그 구간이 아닐 때인데, 실전에서는 그런 시기가 훨씬 길다.\n내가 이걸 안 재본 이유는 솔직하게 말하면 우선순위에서 밀렸기 때문이다. 이미 전략을 눌림목으로 바꿨고, 표본은 한정돼 있고, 재봐야 할 후보는 줄을 서 있다. 이미 버린 전략을 검증하는 데 표본을 쓰는 것보다 지금 쓰는 게이트를 다듬는 게 우선이었다.\n다만 언젠가는 재보고 싶다. \u0026ldquo;교차를 버린 게 옳았나\u0026quot;를 감이 아니라 숫자로 확인하고 싶어서다. 지금은 \u0026ldquo;박스장에서 돌파가 대부분 가짜였다\u0026quot;는 관찰에 근거해 버렸는데, 그건 인상이지 측정이 아니다.\n선을 여러 개 깔면 나아지나 교차의 휩쏘 문제를 겪은 사람들이 흔히 시도하는 게 이동평균을 여러 개 까는 것이다. 5, 10, 20, 60, 120을 동시에 그려놓고 순서가 가지런한지 보는 방식. 정배열이니 역배열이니 하는 말이 여기서 나온다.\n여러 선을 쓰는 방식과 그 정체 정배열 5 \u003e 10 \u003e 20 \u003e 60 \u003e 120 (짧은 게 위) 역배열 5 \u003c 10 \u003c 20 \u003c 60 \u003c 120 (긴 게 위) 판정: 순서가 가지런한 봉 수를 세거나, 선들 사이 간격의 합을 본다 좋아지는 점 교차 하나가 아니라 다섯 선의 합의를 요구 → 휩쏘가 확실히 줄어든다 나빠지는 점 다섯 선이 다 정렬되려면 시간이 더 걸린다 → 지연이 더 커진다 교차 1회의 지연이 SMA(60) 기준 30봉이라면, 정배열 완성은 그보다 늦다 즉 휩쏘와 지연을 맞바꾼 것이지 문제를 푼 게 아니다 이 교환이 이득인지는 그 시장의 추세 지속 길이에 달렸다 — 다시 측정 문제로 돌아온다 여기서 짚고 싶은 건 \u0026ldquo;조건을 더 붙이면 좋아진다\u0026quot;는 직관이 자주 틀린다는 점이다. 조건을 더하면 오탐은 줄지만 미탐은 늘고, 무엇보다 신호 빈도가 줄어 표본이 마른다. 표본이 마르면 그 조건이 실제로 좋은지 검증할 수도 없게 된다.\n내 봇에서 이걸 정면으로 겪은 적이 있다. 단독으로는 성적이 좋았던 거래량 조건을 다른 게이트들 위에 얹었더니 후보가 46건에서 6건으로 줄었다. 87%가 사라진 것이다. 좋은 필터를 얻은 게 아니라 통행량을 죽인 결과였다.\n승률이 낮아도 되는 전략, 그렇지 않은 전략 교차 전략을 옹호하는 흔한 논리가 있다. \u0026ldquo;승률은 낮지만 이길 때 크게 이기니까 괜찮다\u0026quot;는 것. 추세추종의 표준 서사다.\n이 논리 자체는 건전하다. 문제는 그게 성립하려면 조건이 필요하다는 것이다.\n낮은 승률이 감당되는 조건 기대값 = 승률 × 평균이익 − 패률 × 평균손실 − 비용 승률 30% 로 흑자를 내려면 평균이익 / 평균손실 비율이 최소 2.34 이상이어야 한다 (비용 제외) 비용까지 넣으면 3 이상은 필요하다 그 비율이 나오려면 · 이익 분포에 긴 꼬리가 실제로 존재해야 한다 · 그 꼬리를 끝까지 탈 수 있어야 한다 (오래 보유 가능) · 손실은 짧게 자를 수 있어야 한다 (슬리피지 작아야) 내 봇의 실측은 이 조건과 거리가 있었다 눌림목 진입 18건 재현 — 최대 평가이익 중앙값 +3.09% 이익 구간 체류시간 중앙값 0분 긴 꼬리가 없고, 있어도 탈 시간이 없다 여기서 중요한 건 \u0026ldquo;추세추종이 나쁘다\u0026quot;가 아니라 내 시장과 내 시간축에서는 그 전제가 안 맞았다는 것이다. 일봉으로 몇 주씩 들고 가는 전략이라면 얘기가 완전히 달라진다.\n이 판단은 손절 설계에서도 확인됐다. 실제 매매 58건을 손절선별로 재현했을 때, 손절이 아예 없으면 기대값이 −0.072%였고 −2.5%로 자르면 +0.209%로 뒤집혔다. 반대로 익절에 상한을 씌우면 기대값이 다시 음수가 됐다. 손실은 자르고 이익은 풀어주라는 격언이 내 계좌의 숫자로 확인된 셈이다.\n그런데 같은 데이터가 다른 것도 말해줬다. 어떤 익절 수준을 골라도 결과의 중앙값이 −2.100%로 고정됐다는 것. 즉 이익 쪽을 아무리 잘 설계해도 손실 11건(61%)이 결과를 좌우하고 있었다. 이익을 늘리는 것보다 손실을 줄이는 게 훨씬 큰 지렛대였다.\n교차 전략의 문제도 결국 여기로 모인다. 휩쏘가 만드는 잦은 작은 손실이 전체를 갉아먹는데, 그 손실은 방향 예측을 잘한다고 줄어들지 않는다. 신호 빈도 자체가 만드는 구조적 비용이기 때문이다.\n그럼 왜 아직도 쓰이나 그런데 이동평균은 여전히 거의 모든 차트에 깔려 있다. 이유가 있다.\n첫째, 추세 필터로서는 훌륭하다. 진입 신호로 쓰지 않고 \u0026ldquo;지금 이 종목이 장기 평균 위에 있나 아래에 있나\u0026quot;만 보면, 느린 게 오히려 장점이 된다. 느리다는 건 잘 안 바뀐다는 뜻이고, 잘 안 바뀌는 기준선은 필터로 좋다.\n둘째, 모두가 본다. 60일선, 120일선 같은 숫자는 실제로 많은 사람이 주문을 걸어두는 자리다. 피보나치 편에서 다뤘던 자기실현 구조와 같은 논리다. 근거가 약해도 참여자가 많으면 실재가 된다.\n내 봇에서 이동평균의 최후 MA봇 시절 진입 판단 = 이동평균 교차 (전략의 전부) 단일파일 시절 교차 + 게이트 여러 개로 보강 → 교차의 비중이 줄어듦 돌파 전략기 교차 대신 고점 돌파가 트리거 자리를 가져감 눌림목 전환 교차 완전 제거 — 방향이 정반대인 전략으로 갈아탐 현재 진입 판정에 이동평균 교차 없음 대신 중기추세 게이트가 그 역할의 일부를 대신한다 (며칠 고점 대비 낙폭 상한 — '너무 안 빠진 것' 을 거른다) 마지막 줄이 재밌는 대목이다. 중기추세 게이트를 처음 넣었을 때 값을 -12%로 뒀다가 나중에 -30%로 크게 풀었는데, 그 커밋 제목이 \u0026ldquo;게이트가 전략과 반대로 작동\u0026quot;이었다. 눌림목 전략은 빠진 걸 사는 전략인데, 고점 대비 -12% 이내만 통과시키는 게이트는 안 빠진 것만 통과시키고 있었다. 게이트 하나하나는 그럴듯한데 전략과 방향이 어긋나 있던 것이다.\n이동평균 교차도 같은 종류의 문제였다. 교차는 \u0026ldquo;오르기 시작한 것을 따라 타는\u0026rdquo; 논리인데, 눌림목은 \u0026ldquo;빠진 것을 미리 줍는\u0026rdquo; 논리다. 두 문법은 같은 봇 안에서 공존하기 어렵다.\n그래도 살릴 자리 버렸다고 해서 쓸모가 없다는 뜻은 아니다. 지금의 내 봇에 이동평균을 다시 넣는다면 세 곳이 후보다.\n재도입 후보와 검증 설계 ① 시장 레짐 보조 BTC 4시간봉이 장기 평균 아래면 신규 진입 금지 현행은 다른 방식으로 같은 판정을 이미 하고 있다 — 대체가 아니라 대조군 ② 이격도 게이트 가격이 평균에서 얼마나 떨어졌나 (%) 눌림목 전략과 문법이 맞는다. 많이 떨어진 것을 찾는 지표이므로 ③ 청산 기준선 진입 후 단기 평균 하향 이탈 시 청산 현행 손절은 고정 -5%. 평균 기반 동적 손절과 비교해볼 가치 있음 검증: ②를 먼저. 축은 기간(10/20/60) × 이격 임계(-3%/-5%/-8%) × 평균종류(SMA/EMA) 기존 저점근접·낙폭 게이트와 상관이 높을 것으로 예상 — 독립적인 정보를 주는지가 채택의 관건이다 마지막 줄이 핵심이다. 게이트가 이미 열 개 서 있는 시스템에 열한 번째를 넣을 때 물어야 할 건 \u0026ldquo;이게 좋은 지표인가\u0026quot;가 아니라 \u0026ldquo;이게 나머지 열 개가 못 보는 걸 보는가\u0026ldquo;다. 이격도는 낙폭 게이트와 재는 대상이 거의 같아서, 새 정보를 못 줄 가능성이 크다. 그러면 통과 후보만 깎고 성과는 그대로인 중복 검문소가 된다.\nMA봇 시절에 실제로 있었던 일 이 시대를 좀 더 구체적으로 적어두는 게 이 글에 도움이 될 것 같다. 이동평균 교차로 봇을 돌린다는 게 실제로 어떤 모습이었는지.\nMA봇 시절의 구조 진입 판단 이동평균 교차 (그게 전부였다) 종목 선정 초기엔 내가 지정 → 이후 전 종목 스캔으로 전환 슬롯 동시 보유 개수 제한 — 이 개념이 이때 생겼다 상태 저장 봇이 죽었다 살아나도 포지션을 기억 블랙리스트 건드리지 않을 종목 목록 이때 만들어져 461개 버전을 지나 지금도 살아 있는 것들이다 반면 진입 로직(교차)만 통째로 사라졌다 뼈대는 남고 전략은 갈린다 — 아키텍처와 전략의 수명이 다르다는 얘기 마지막 줄이 이 시절을 돌아보며 가장 크게 느끼는 부분이다. 그때 만든 것 중 전략은 하나도 안 남았는데 구조는 거의 다 남았다. 슬롯이라는 개념, 상태를 파일로 저장하는 습관, 종목을 스캔해서 후보를 만드는 파이프라인.\n특히 상태 저장이 중요했다. 봇이 죽는 건 언제든 일어나는 일이고, 죽었다 살아났을 때 자기가 뭘 들고 있는지 모르면 그때부터 사고가 시작된다. 이건 전략과 무관하게 필요한 기반이었다.\n교차 로직 자체는 이후 여러 번 형태를 바꾸다 사라졌다. 처음엔 교차 하나로 샀고, 그다음엔 교차에 다른 조건을 붙였고, 돌파 전략기에는 트리거 자리를 고점 돌파에 내줬고, 눌림목 전환에서는 아예 제거됐다.\n통신 문제 — 지표보다 먼저 겪은 것 MA봇 시절 얘기를 하면 빼놓을 수 없는 게 하나 더 있다. 지표가 아니라 데이터를 받아오는 문제였다.\n종목을 몇백 개씩 스캔하기 시작하니 REST로 하나씩 조회하는 방식이 감당이 안 됐다. API 호출 제한에 걸리고, 응답이 느려지고, 어떤 종목은 데이터가 비어서 돌아왔다. 그래서 웹소켓을 도입했는데 이번엔 연결이 끊기는 문제가 생겼다.\n통신 안정성이 밟아온 계단 REST 폴링 종목 수가 늘자 한계 웹소켓 도입 실시간 수신, 대신 연결 끊김 문제 3회 재시도 끊기면 세 번까지 다시 지수 백오프 재시도 간격을 1초 → 2 → 4 → … 최대 60초까지 연결 상태 추적 끊긴 횟수·마지막 수신 시각을 상태로 관리 요청 ID 부여 어떤 호출이 실패했는지 추적 가능하게 지표를 아무리 잘 만들어도 데이터가 안 들어오면 소용없다 자동매매에서 통신 안정성은 전략만큼 중요한 축이다 지표 스터디 글에 통신 얘기를 넣는 게 이상해 보일 수 있는데, 실제로 봇을 만들면 이쪽이 시간을 훨씬 많이 잡아먹는다. RSI 계산은 다섯 줄이면 되지만, 그 계산에 넣을 캔들을 안정적으로 받아오는 일은 훨씬 어렵다.\n지난 편에서 \u0026ldquo;지표는 봇의 10%\u0026ldquo;라고 썼는데, 나머지 90% 중 상당 부분이 이런 것들이다. 데이터 수집, 완성봉 확정, 체결 확인, 원장 대조. 화려하지 않고 블로그 글감으로도 재미없지만, 이게 안 되면 아무리 좋은 지표도 무의미하다.\n교차 전략을 정직하게 백테스트하는 법 혹시 직접 검증해볼 분을 위해, 이 전략을 재는 데 필요한 최소 절차를 적어둔다. 교차 전략은 백테스트가 특히 잘 속는 종류라서 그렇다.\n교차 백테스트에서 반드시 넣어야 할 것 ① 비용을 넣는다 왕복 수수료 + 스프레드 + 호가 관통. 비용 없는 백테스트는 전부 이긴다 신호 빈도가 높은 전략일수록 이 항목 하나로 결과가 뒤집힌다 ② 완성봉으로 판정한다 진행봉 종가로 교차를 판정하면 봉 안에서 신호가 생겼다 사라진다 실전에서 재현 불가능한 매매를 백테스트가 만들어낸다 ③ 체결 가격을 현실적으로 잡는다 교차 봉의 종가에 체결됐다고 가정하면 안 된다 — 그건 판정 시점이지 주문 시점이 아니다 최소한 다음 봉 시가, 보수적으로는 거기에 슬리피지를 더한다 ④ 종목을 여러 개 돌린다 한 종목에서 잘 나온 20/60 은 그 종목의 그 기간에 맞춘 값일 뿐이다 ⑤ 기간을 반으로 갈라 앞뒤에서 각각 성립하는지 본다 전체 기간에서만 좋은 값은 과적합일 확률이 높다 이 다섯을 다 넣고도 이기면, 그때는 진짜일 가능성이 있다 ⑤번은 내가 파라미터를 확정할 때 쓰는 절차이기도 하다. 게이트 열한 개 축을 전수 스윕하면서 조합 검증과 기간 반분 안정성 체크를 함께 돌렸고, 수익·승률·손절권 진입률이 동시에 개선되는 값만 채택했다. 한 축만 좋아지고 다른 게 나빠지는 값은 아무리 수익률이 높아도 버렸다.\n한 가지 더 — 기간 숫자의 출처 20, 60, 120 같은 숫자가 어디서 왔는지 아는 사람은 의외로 적다. 대부분 주식 시장의 달력에서 왔다. 한 달 거래일이 대략 20일, 분기가 60일, 반년이 120일이다.\n코인 시장에는 이 달력이 없다. 주말도 없고 폐장도 없다. 그러니 20일선이 \u0026ldquo;한 달 평균\u0026quot;이라는 의미가 성립하지 않는다. 그럼에도 다들 20/60/120을 쓴다.\n기간 숫자를 다시 정한다면 주식에서 온 숫자 코인 15분봉으로 환산 20일 = 한 달 20봉 = 5시간 60일 = 분기 60봉 = 15시간 120일 = 반년 120봉 = 30시간 환산해 보면 의미가 전혀 다르다 — 같은 숫자를 쓸 이유가 없다 코인 시장의 자연스러운 주기라면 96봉 = 24시간 (하루) 672봉 = 7일 (일주일) 그런데도 20/60 을 쓰는 게 완전히 틀렸다고 하기도 어렵다 — 많은 사람이 그 선을 보고 있다면, 그 선이 실제로 작동하기 때문이다 이 딜레마는 피보나치 편에서 다룬 자기실현 문제와 정확히 같다. 근거가 없는 기준선도 참여자가 충분하면 실재가 된다. 그래서 답은 \u0026ldquo;어느 쪽이 맞나\u0026quot;가 아니라 \u0026ldquo;둘 다 후보로 놓고 재본다\u0026ldquo;가 된다. 20/60과 96/672를 나란히 스윕해서 어느 쪽이 내 매매기록에서 나은지 보는 것. 이론으로 못 정하는 건 측정으로 정한다.\n교차 대신 쓰는 것들 교차를 버린 자리에 무엇이 들어왔는지도 적어두는 게 공평하겠다. 같은 질문 — \u0026ldquo;지금 이 종목을 살 만한가\u0026rdquo; — 에 다른 방식으로 답하는 조건들이다.\n교차가 있던 자리를 채운 것들 교차가 하던 일: \"방향이 바뀌었나\" 를 판정 현행에서 그 질문을 나눠 맡은 조건들 RSI 45 이하 충분히 빠졌나 (에너지 배분) 저점근접 3.0 지금 바닥 근처인가 (위치) 반등 확인 저점에서 살짝 올라왔나 (돌기 시작했나) 중기추세 게이트 며칠째 흘러내리는 중은 아닌가 거량 0.6x 죽은 종목은 아닌가 BTC 4시간봉 레짐 시장 전체가 무너지는 중은 아닌가 교차 하나가 하던 판정을 여섯 개가 나눠 맡는 구조 각각은 교차보다 단순하다. 대신 겹쳐서 본다 대가: 조건이 늘수록 신호가 줄고, 튜닝할 파라미터도 늘어난다 이 구조의 장점은 어디가 문제인지 알 수 있다는 것이다. 교차 하나로 판정할 때는 신호가 안 나오면 그냥 안 나오는 것이었다. 지금은 게이트별 통과율이 찍히니 어느 관문이 병목인지 바로 보인다.\n단점도 분명하다. 파라미터가 여섯 개면 조합의 수가 폭발한다. 각각을 독립적으로 튜닝하면 조합에서 무너지고, 조합까지 다 훑으면 계산량이 감당이 안 된다. 그래서 축별 스윕 → 상위 조합 재검증이라는 두 단계 절차를 쓴다.\n그리고 조건이 늘수록 과적합 위험이 커진다는 점도 인정해야 한다. 파라미터 여섯 개를 8.9일치 기록에 맞추면, 그 기록에만 잘 맞는 값이 나올 확률이 높다. 기간을 반으로 갈라 앞뒤에서 모두 성립하는지 확인하는 절차를 넣은 게 그래서인데, 그것도 위험을 줄일 뿐 없애지는 못한다.\n한 줄 요약을 굳이 남긴다면 이동평균 교차를 쓰려는 사람에게 딱 세 가지만 확인시키고 싶다.\n첫째, 내가 쓰는 게 SMA인지 EMA인지 알고 있는가. 같은 20/60이라도 다른 전략이다. 둘째, 신호 한 번당 왕복 비용이 얼마인지 계산해봤는가. 하루 몇 번 신호가 나는지와 곱해보면 수익률 얘기를 꺼내기 전에 답이 나오는 경우가 많다. 셋째, 이 전략이 잘 통하려면 추세가 얼마나 길게 이어져야 하는지 아는가. 지연이 30봉이면 최소한 그보다 긴 추세가 필요하다.\n세 질문 다 지표에 관한 게 아니라 내 시장과 내 비용 구조에 관한 것이다. 지표는 어디서 가져와도 되지만 이 답은 내가 직접 재야 한다. 나는 그걸 몰라서 MA봇 시절을 한 달 가까이 보냈고, 그 시간이 아깝지는 않지만 다시 하고 싶지도 않다.\n버린 전략을 기록으로 남기는 이유 블로그에 실패한 전략 이야기를 쓰는 게 이상해 보일 수 있다. 보통은 잘된 것만 쓰니까.\n그런데 나한테는 이쪽이 더 중요하다. 이유가 둘 있다.\n첫째, 같은 실수를 반복하지 않기 위해서다. 몇 달 뒤에 다시 이동평균 교차가 좋아 보이는 순간이 올 수 있다. 그때 이 글이 있으면 \u0026ldquo;아, 이래서 버렸지\u0026quot;를 기억할 수 있다. 없으면 다시 시도하고 다시 같은 곳에서 막힌다.\n둘째, 잘된 것만 남기면 기록이 아니라 광고가 되기 때문이다. 461개 버전 중 성공한 건 소수고, 나머지는 전부 이런 종류의 시행착오였다. 그 비율을 감추면 봇 개발이 실제보다 쉬워 보이고, 그건 읽는 사람에게 도움이 안 된다.\n이 봇에서 붙였다 뗀 것들 (일부) 이동평균 교차 진입 판정 → 완전 제거 돌파 게이트 진입 트리거 → 박스장 무력화로 폐지 거래량 게이트 두 번 제거, 한 번 부활 (설계를 바꿔서) 분할익절 (TP 사다리) 큰 상승을 놓치는 구조로 판정 → 비활성화 포지션 이분할 하루 만에 철회 시간대 진입 차단 실측으로 도입 → 매수 가뭄으로 해제 개별 손절 제거 위험한 실험 → 13버전 뒤 복원 C++ 포팅 완성 전 중단 살아남은 것: 슬롯 · 상태 저장 · 손절 · 본전 사수 · 원장 대조 · 레짐 차단 지운 목록이 남긴 목록보다 길다. 그게 정상이라고 생각한다 이 목록을 보면 공통점이 하나 보인다. 살아남은 건 대부분 방어 장치이고, 사라진 건 대부분 진입 아이디어다.\n우연이 아니라고 생각한다. 진입 조건은 시장 상황에 따라 유효성이 바뀌지만, 잃지 않는 구조는 어떤 장에서도 필요하다. 7주 연속 마이너스를 겪고 나서야 이 순서를 받아들였다 — 잃지 않는 구조를 먼저 만들고, 버는 건 그다음이라는 것.\n마무리 이동평균 교차는 내 봇이 처음 배운 규칙이고, 가장 먼저 버린 규칙이다. 그런데 버리는 과정에서 배운 게 지표 자체보다 값졌다. 신호의 지연을 봉 수로 환산해보는 습관, 왕복 비용을 신호 빈도와 곱해보는 습관, 그리고 새 조건을 넣기 전에 기존 조건과의 중복부터 의심하는 습관.\n교차 전략을 폄하할 생각은 없다. 다만 그게 잘 통하는 시장 — 추세가 길고, 수수료 비중이 작고, 호가가 촘촘한 시장 — 이 따로 있다는 것을 알고 쓰는 것과 모르고 쓰는 것은 완전히 다른 일이다. 나는 모르고 썼고, 그 수업료를 계좌로 냈다.\n덧붙임 — 이동평균을 쓰는 다른 방법 하나 버린 전략을 소개하고 끝내면 아쉬우니, 지금도 유효하다고 생각하는 쓰임 하나를 남긴다. 이격도다.\n이격도는 가격이 이동평균에서 얼마나 떨어져 있는지를 백분율로 재는 값이다. 계산은 간단하다.\n이격도 — 교차보다 눌림목 전략에 맞는 쓰임 이격도 = (현재가 − 이동평균) / 이동평균 × 100 양수 평균 위에 있다 음수 평균 아래로 떨어져 있다 왜 눌림목 전략과 문법이 맞나 교차는 '오르기 시작한 것' 을 찾는다 — 내 전략과 방향이 반대 이격도는 '많이 떨어진 것' 을 찾는다 — 방향이 같다 다만 예상되는 문제: 기존 낙폭 게이트와 재는 게 거의 같다 낙폭 게이트 = 며칠 고점 대비 얼마나 빠졌나 이격도 = 평균 대비 얼마나 빠졌나 기준점만 다를 뿐 '얼마나 빠졌나' 라는 질문은 동일하다 → 상관이 높으면 중복 검문소가 된다. 검증 1순위 확인 항목 이 후보를 아직 안 넣은 이유가 마지막 항목이다. 새 조건을 넣기 전에 기존 조건과의 상관부터 봐야 하는데, 이격도는 낙폭 게이트와 상관이 높을 가능성이 크다. 그러면 통과 후보만 깎고 성과는 그대로인 결과가 나온다.\n그럼에도 재볼 가치는 있다고 본다. 기준점이 다르다는 건 미묘하지만 실제 차이를 만들 수 있다. 고점 대비 낙폭은 한 지점(고점)을 기준으로 하고, 이격도는 여러 봉의 평균을 기준으로 한다. 고점이 이상치였다면 두 값이 크게 갈린다.\n이런 식으로 후보 목록에 올려두고 순서를 기다리는 아이디어가 여러 개 있다. 전부 재보면 좋겠지만 표본이 한정돼 있어서, 기대 효과가 큰 것부터 차례로 가는 수밖에 없다. 검증할 수 있는 양이 개발 속도의 상한을 정한다는 게 이 시기에 배운 또 하나의 사실이다.\n","permalink":"https://botlab.co.kr/study/ma-cross/","summary":"골든크로스는 교차한 순간이 아니라 이미 오른 뒤에 뜬다. 그 지연을 숫자로 따져본다.","title":"MA Cross — 내 봇이 처음 배운 규칙, 그리고 제일 먼저 버린 규칙 | Crypto Trading Bot"},{"content":"밴드를 만든 사람이 밴드로 만든 지표 볼린저 밴드는 1980년대에 존 볼린저가 만들었다. 이동평균 위아래로 표준편차 두 배만큼 선을 긋는 그 지표 말이다. 그리고 40년쯤 지나 같은 사람이 후속작을 내놨다. 이름은 BBTrend. 재밌는 건 이게 밴드를 그리는 지표가 아니라 밴드 두 벌을 겹쳐놓고 그 어긋남을 숫자 하나로 뽑는 지표라는 점이다.\n발상은 단순하다. 짧은 기간(20)으로 그린 밴드와 긴 기간(50)으로 그린 밴드를 동시에 놓는다. 시장이 한쪽으로 밀리기 시작하면 짧은 밴드가 긴 밴드보다 먼저 움직인다. 그 시차와 방향을 하나의 값으로 만든 것이다.\nBBTrend — 공식 준비물: 밴드 두 벌 (짧은 기간 20, 긴 기간 50, 둘 다 2σ) BBTrend = ( |하단20 − 하단50| − |상단20 − 상단50| ) / 중심20 × 100 값 \u003e 0 하단끼리의 벌어짐이 더 크다 → 상승 쪽으로 밀리는 중 값 \u003c 0 상단끼리의 벌어짐이 더 크다 → 하락 쪽으로 밀리는 중 |값| 이 커질수록 그 방향의 힘이 세다 (통상 1 을 강약의 눈금으로 본다) 중심20 으로 나누는 이유 — 가격 수준을 지워서 종목 간 비교가 가능해진다 4억짜리 BTC 와 200원짜리 코인의 BBTrend 를 같은 자로 잴 수 있다 마지막 줄이 이 지표의 실용적 미덕이다. 절대 가격으로 만든 지표는 종목을 가로질러 비교할 수 없는데, BBTrend는 중심선으로 나눠서 백분율로 만들어 놓았다. 내 봇처럼 매 스캔마다 수십 종목을 한 줄에 세워 순위를 매기는 구조에서는 이 성질이 꽤 크다.\n그 전에 — 볼린저 밴드부터 다시 BBTrend를 이해하려면 재료인 볼린저 밴드를 정확히 알아야 한다. 대충 아는 채로 넘어가면 뒤의 이야기가 다 흐려진다.\n볼린저 밴드 — 세 줄이 전부다 중심선 = 종가의 n봉 단순이동평균 상단 = 중심선 + k × 표준편차 하단 = 중심선 − k × 표준편차 기본값 n=20, k=2 여기서 표준편차가 하는 일 최근 n봉의 종가가 평균에서 얼마나 흩어져 있나를 재는 값 흩어짐이 크면 밴드가 벌어지고, 촘촘하면 좁아진다 즉 볼린저 밴드는 '평균 + 변동성' 을 한 그림에 담은 물건이다 흔한 오해: k=2 면 95% 가 밴드 안에 들어온다 정규분포 가정에서만 참이다. 실제 가격 분포는 꼬리가 두꺼워서 밴드 밖으로 나가는 빈도가 이론값보다 훨씬 높다 마지막 항목을 짚고 가는 이유가 있다. \u0026ldquo;밴드 밖은 드물다\u0026quot;는 전제로 역추세 매매를 설계하면 실제보다 자주 틀린다. 코인처럼 급등락이 흔한 시장에서는 특히 그렇다. 밴드는 확률 구간이 아니라 변동성으로 그린 참고선으로 보는 게 안전하다.\n왜 하단과 상단을 각각 비교하나 처음 공식을 봤을 때 걸린 게 이 부분이었다. 왜 그냥 중심선끼리 빼지 않고, 하단끼리 상단끼리 따로 비교한 다음 그 차이를 볼까.\n이유는 밴드가 두 가지 정보를 동시에 들고 있기 때문이다. 위치(중심선이 어디냐)와 폭(변동성이 얼마냐). 중심선끼리만 비교하면 위치 정보만 남고 폭 정보가 사라진다. 그런데 하단과 상단을 각각 비교하면, 위치 이동과 폭 확장이 섞인 채로 잡힌다.\n상승 국면에서 두 밴드가 어긋나는 모양 상단50 ───────────── 상단20 ───────── ← 간격 작다 가격 ────╱╲──╱▔▔▔▔ ╱ 중심20 이 중심50 위로 올라감 ─────────────────── 중심50 하단20 ──────────── ← 간격 크다 하단50 ─────── |하단 간격| 이 |상단 간격| 보다 크다 → BBTrend 양수 해석: 아래쪽이 확실히 들려 올라갔다 = 지지선 자체가 이동 중 상단은 아직 따라오지 못했다 = 위로 갈 여지가 남아 있다는 뜻으로도 읽힌다 즉 BBTrend가 크게 양수라는 건 \u0026ldquo;바닥이 올라왔다\u0026quot;는 말에 가깝다. 고점이 갱신됐다가 아니라 저점이 갱신됐다를 본다는 게 이 지표의 성격이다. 눌림목 전략을 돌리는 입장에서 이건 흥미로운 각도다. 내 봇이 보는 저점근접 게이트는 \u0026ldquo;지금 가격이 24시간 저점에 얼마나 가까운가\u0026quot;인데, BBTrend는 \u0026ldquo;그 저점 자체가 어느 방향으로 움직이고 있는가\u0026quot;를 본다. 정적인 위치 대 동적인 이동. 서로 다른 질문이다.\n코드로 옮기면 python — 공식 그대로 def bb(close, n, k=2.0): mid = close.rolling(n).mean() sd = close.rolling(n).std(ddof=0) # ddof 주의 — 아래 참조 return mid - k*sd, mid, mid + k*sd lo20, mid20, up20 = bb(close, 20) lo50, mid50, up50 = bb(close, 50) bbtrend = ((lo20 - lo50).abs() - (up20 - up50).abs()) / mid20 * 100 # 함정 1: 표준편차의 자유도. 대부분의 차트 플랫폼은 모집단 표준편차(ddof=0)를 # 쓰는데 pandas 기본값은 표본(ddof=1)이다. 밴드 폭이 미세하게 달라진다 # 함정 2: 워밍업 — 긴 밴드가 50봉이므로 최소 50봉, 안전하게는 100봉 이상 채운 뒤 사용 # 함정 3: 완성봉. 진행봉을 넣으면 표준편차가 봉 안에서 계속 움직인다 함정 1은 사소해 보이지만 짚어둘 만하다. 트레이딩뷰에서 본 값과 내 파이썬 값이 미세하게 다를 때, 대부분 이 자유도 차이다. BBTrend는 그 차이를 두 번(짧은 밴드, 긴 밴드) 겪고 뺄셈으로 증폭시키므로 특히 티가 난다. 지표를 이식할 때 원본 플랫폼과 숫자를 맞춰보는 절차를 건너뛰면, 나중에 \u0026ldquo;왜 백테스트랑 실전이 다르지\u0026quot;의 원인이 여기 숨는다.\n값 하나보다 부호가 이어진 길이 BBTrend를 실제로 화면에 띄워놓고 며칠 보면 알게 되는 게 있다. 순간의 값보다 부호가 얼마나 오래 유지됐는가가 정보량이 더 많다는 것이다.\n값 자체는 시끄럽다. 0 근처에서는 봉마다 부호가 뒤집힌다. 반면 진짜 추세가 붙으면 부호가 수십 봉씩 한쪽에 머문다. 그래서 이 지표를 쓸 때는 값을 임계와 비교하는 것보다, 부호가 유지된 봉 수를 세는 쪽이 안정적이다.\n같은 값, 다른 의미 A 종목: −0.2 +0.3 −0.1 +0.4 −0.3 +0.2 현재값 +0.2 부호가 계속 뒤집힘 → 방향 없음. 이 +0.2 는 노이즈다 B 종목: +0.1 +0.2 +0.2 +0.3 +0.2 +0.2 현재값 +0.2 6봉 연속 양수 → 같은 +0.2 라도 의미가 다르다 코드로는 한 줄 streak = bbtrend.gt(0).groupby((bbtrend.gt(0) != bbtrend.gt(0).shift()).cumsum()).cumcount()+1 봇에 넣을 땐 (값, 부호지속봉수) 를 한 쌍으로 들고 다니는 게 맞다 이건 BBTrend 만의 얘기가 아니다 — 0 근처를 오가는 모든 오실레이터의 공통 처방 지난 RSI 다이버전스 편에서 0선 근처의 톱니를 다루며 히스테리시스를 얘기했는데, 부호 지속 봉 수는 같은 문제에 대한 다른 처방이다. 히스테리시스는 진입선과 이탈선을 벌려서 톱니를 죽이고, 지속 봉 수는 톱니를 세어서 무시한다. 어느 쪽이 나은지는 재봐야 알지만, 후자가 파라미터가 하나 적어서 과적합 위험이 낮다는 점은 말할 수 있다.\n사촌 지표들과의 관계 볼린저 계열에는 BBTrend 말고도 파생 지표가 몇 개 있다. 뭘 압축한 건지 나란히 놓고 보면 이 계열의 지형이 보인다.\n볼린저 파생 지표 지도 %B (종가 − 하단) / (상단 − 하단) 잰다: 밴드 안에서의 위치. 0 이면 하단, 1 이면 상단 못 잰다: 밴드 자체가 어디로 가는지 밴드폭 (상단 − 하단) / 중심 잰다: 변동성의 크기. 스퀴즈(수축) 감지에 쓴다 못 잰다: 방향 BBTrend 밴드 두 벌의 어긋남 잰다: 방향 + 방향에 실린 힘 못 잰다: 현재 가격이 밴드 어디쯤인지 셋은 경쟁 관계가 아니라 보완 관계다 — 각각 다른 축을 담당한다 실전 조합 예: 밴드폭으로 스퀴즈 확인 → BBTrend 로 터질 방향 판정 → %B 로 진입 위치 이 표를 만들면서 든 생각인데, 지표를 고를 때 \u0026ldquo;좋은 지표인가\u0026quot;를 묻는 대신 \u0026ldquo;무슨 축을 담당하는가\u0026ldquo;를 물으면 선택이 훨씬 쉬워진다. 내 게이트 목록에 이미 위치를 재는 축(저점근접)과 크기를 재는 축(낙폭, RSI)이 있으니, 새로 넣을 게 있다면 그 둘이 못 보는 축이어야 한다.\n손으로 한 번 계산해보기 수식만 보면 감이 안 오니까 가상의 숫자로 한 번 돌려보자. 값이 어떤 크기로 나오는지 체감하는 게 목적이다.\n계산 예시 — 상승 국면 어떤 종목, 현재가 1,000원 부근 짧은 밴드(20) 중심 1,000 · 상단 1,030 · 하단 970 (폭 60) 긴 밴드(50) 중심 980 · 상단 1,020 · 하단 940 (폭 80) 하단 차이 = |970 − 940| = 30 상단 차이 = |1,030 − 1,020| = 10 BBTrend = (30 − 10) / 1,000 × 100 = +2.0 해석: 하단이 30원 들려 올라간 반면 상단은 10원밖에 안 움직였다 → 바닥이 확실히 올라오는 중, 상승 우세 같은 구조에서 하락 국면이라면 부호만 뒤집힌다 하단 차이 10 · 상단 차이 30 → BBTrend = −2.0 가격이 4억짜리 BTC 였어도 중심선으로 나누므로 값의 크기는 비슷하게 나온다 계산해보면 +2.0 정도가 꽤 뚜렷한 신호라는 걸 알 수 있다. 짧은 밴드의 하단이 긴 밴드보다 3%나 위에 있다는 뜻이니까. 실제 차트에서 이 정도 값은 자주 안 나온다.\n그래서 앞에서 \u0026ldquo;통상 1을 강약의 눈금으로 본다\u0026quot;고 적었는데, 이 계산을 해보면 왜 그런지 이해가 된다. 1이면 밴드 간격 차이가 가격의 1%라는 뜻이고, 15분봉 한두 시간 안에 그만큼 구조가 이동하는 건 흔한 일이 아니다.\n이 지표의 진짜 약점 호의적으로만 쓰면 스터디가 아니다. BBTrend에는 구조적인 약점이 하나 있다.\n변동성이 죽으면 지표도 죽는다 중심20 으로 나누므로 분모는 안정적이다. 문제는 분자다. 변동성 폭발 구간 밴드 폭이 크다 → 간격 차이도 크다 → |BBTrend| 큼 저변동성 횡보 밴드 폭이 얇다 → 간격 차이도 작다 → |BBTrend| ≈ 0 즉 조용한 장에서는 방향이 있어도 값이 안 나온다. \"추세 없음\" 과 \"추세는 있는데 변동성이 낮음\" 이 같은 0 으로 뭉개진다 대응: 값 자체가 아니라 값의 부호 지속 봉 수를 같이 본다 또는 ATR 로 정규화한 보조 축을 하나 더 두고 교차 확인 이건 볼린저 계열 전체가 공유하는 성질이기도 하다. 표준편차 기반 지표는 변동성이 낮을 때 신호가 함께 죽는다. 스퀴즈(밴드 수축)를 별도로 감지하는 지표가 따로 존재하는 이유이기도 하다.\n내 봇 이력에도 비슷한 계열의 흔적이 있다. 박스장에서 진입이 완전히 막혔을 때 볼린저 밴드 상단 돌파를 별도 게이트로 신설한 적이 있는데(BB_PERIOD 20 → 15 → 10, BB_STD 2.0 → 1.5 → 1.0 → 0.8), 임계를 계속 낮춰야 신호가 나왔다. 밴드가 얇아진 장에서 밴드 기준으로 뭔가를 판정하려니 기준을 계속 깎게 된 것이다. 결국 그 게이트는 눌림목 전환과 함께 사라졌다. 지표가 나빠서가 아니라 그 지표가 잘 작동하는 장이 아니었기 때문이다.\n두 기간을 20과 50으로 잡은 이유 BBTrend는 밴드 두 벌을 쓰는데, 그 기간을 20과 50으로 둔다. 이 조합에도 따져볼 게 있다.\n기간 비율이 지표의 성격을 정한다 중요한 건 절대값이 아니라 두 기간의 비율이다 20 / 50 = 1 : 2.5 ← 원저자 기본값 10 / 50 = 1 : 5 ← 짧은 쪽이 훨씬 민감해짐 30 / 50 = 1 : 1.67 ← 두 밴드가 비슷해져 차이가 잘 안 벌어짐 비율이 1 에 가까워지면 두 밴드가 거의 겹친다 → BBTrend 가 항상 0 근처 → 신호 소멸 비율이 너무 커지면 짧은 밴드가 노이즈로 요동 → 값은 커지는데 방향성은 없음 15분봉 환산 20봉 = 5시간 · 50봉 = 12시간 30분 즉 '반나절 흐름 대비 다섯 시간 흐름' 의 어긋남을 재는 셈이다 이 환산이 중요하다. 원저자는 일봉을 염두에 두고 20/50을 권했을 텐데, 일봉에서 20/50은 한 달 대 두 달 반이다. 15분봉에서는 다섯 시간 대 반나절이 된다. 같은 숫자인데 재는 시간 규모가 전혀 다르다.\n이동평균 편에서도 같은 얘기를 했다. 20, 60, 120 같은 숫자는 주식 시장 달력에서 왔고, 코인 15분봉으로 옮기면 근거가 사라진다. 지표를 가져올 때 기간 숫자를 그대로 옮기는 건, 사실은 아무 근거 없는 값을 쓰는 것과 같다.\n그럼 어떻게 정하느냐. 두 가지 방법이 있다. 하나는 스윕 — 후보를 늘어놓고 내 매매기록에서 재보는 것. 다른 하나는 내 시스템의 다른 부품과 시간 규모를 맞추는 것이다. 예컨대 내 봇의 저점근접 게이트는 24시간 창을 쓴다. 15분봉에서 24시간이면 96봉이다. BBTrend의 긴 밴드를 96으로 맞추면 두 지표가 같은 창을 보게 되고, 그러면 둘을 공정하게 비교할 수 있다.\n봇에 넣는다면 어디에 BBTrend를 진입 트리거로 쓰는 건 별로다. 이건 상태를 재는 지표이지 순간을 잡는 지표가 아니다. 대신 두 자리가 어울린다.\n배치 후보 — 트리거가 아니라 문지기와 심판 ① 레짐 판정 보조 BTC 4시간봉 BBTrend 가 음수 지속 → 시장 전체가 아래로 밀리는 중 현행 봇은 하락장이면 신규 진입을 아예 안 한다 — 그 판정의 두 번째 근거로 ② 랭킹 가점 게이트를 통과한 후보가 여럿일 때 BBTrend 큰 쪽에 가점 진입 여부는 안 바꾸고 순서만 바꾸므로 실험 위험이 작다 ✗ 진입 트리거로는 부적합 — 값이 커졌을 땐 이미 움직임이 진행된 뒤다 검증 설계: 축 3개 (짧은기간 10/20/30 · 긴기간 40/50/60 · 임계 0.5/1.0/1.5) 단변량 → 조합 → 기간 반분. 셋 다 통과해야 채택 ②번을 강조하고 싶다. 랭킹 가점은 진입 빈도를 건드리지 않으므로 가장 싸게 실험할 수 있는 자리다. 내 봇에서 랭킹 가중치를 만졌을 때 흥미로운 걸 배운 적이 있다. 과매도 가점과 압축 가점을 각각 3.0과 2.5로 주고 있었는데, 실제 결과와 대조해 보니 두 항목 모두 상관이 음수였다. 점수가 높을수록 오히려 나쁜 종목을 먼저 사고 있었던 것이다. 두 가중치를 0으로 내린 게 그때의 결론이었다.\n그래서 새 지표를 랭킹에 넣을 때는 방향까지 의심해야 한다. \u0026ldquo;이 지표가 유용한가\u0026quot;만 묻지 말고 \u0026ldquo;부호가 내 생각과 같은가\u0026quot;를 물어야 한다. 그럴듯한 논리로 만든 가점이 반대로 작동하는 일은 생각보다 자주 일어난다.\n내 봇의 볼린저 이력 — 임계를 계속 깎았던 시절 이 지표를 평가할 자격이 나에게 조금은 있다고 생각하는 이유가 있다. 볼린저 밴드를 게이트로 써봤고, 그게 어떻게 무너지는지 봤기 때문이다.\n돌파 전략을 돌리던 시절, 박스장에서 진입이 완전히 막힌 적이 있다. 이틀째 매수가 하나도 없었다. 그때 대안으로 넣은 게 볼린저 밴드 상단 돌파 게이트였다. 고점 돌파가 안 나오니 밴드 돌파로라도 신호를 만들어보자는 발상이었다.\n볼린저 게이트의 임계 하락사 신설 BB 기간 20 · 2.0σ \"박스장에서도 진입 신호 생성\" 1차 조정 기간 15 · 1.5σ 더 빠른 반응, 더 좁은 밴드 2차 조정 기간 10 · 1.0σ \"박스장돌파 통과율 18% → 40~50% 목표\" 3차 조정 기간 8 · 0.8σ 세 개 게이트를 동시에 완화한 배포 결말 눌림목 전략 전환과 함께 게이트 자체가 사라짐 돌아보면 신호가 안 나올 때마다 기준을 깎고 있었다 2.0σ 에서 0.8σ 까지 — 통계적 의미가 남아 있었는지 의문이다 교훈: 임계를 반복해서 완화하고 있다면 지표가 아니라 전략을 의심할 때다 마지막 줄이 이 이력에서 얻은 결론이다. 파라미터를 계속 깎아야 신호가 나온다면, 그건 그 지표가 지금 시장에서 작동하지 않는다는 신호일 가능성이 크다. 그때는 값을 더 깎을 게 아니라 전략 자체를 의심해야 한다.\n실제로 그렇게 됐다. 돌파 조건을 조였다 풀었다 몇 주를 반복한 끝에 나온 결론이 \u0026ldquo;파라미터 문제가 아니라 전략 문제\u0026quot;였고, 진입 전략을 눌림목 반등으로 통째로 바꿨다. 그 전환과 함께 볼린저 게이트도 사라졌다.\n그러니 BBTrend에 대한 내 태도는 조심스럽다. 볼린저 계열 지표가 나쁘다는 게 아니라, 그 계열이 잘 작동하는 장이 따로 있다는 걸 몸으로 배웠기 때문이다. 변동성이 살아 있고 방향이 잡히는 장에서는 좋고, 밴드가 얇아진 박스장에서는 무엇을 해도 안 된다.\n지표를 만든 사람이 후속작을 낸다는 것 잠깐 옆길로 새는 이야기인데, 이 지표를 공부하면서 인상 깊었던 게 있다. 존 볼린저는 자기 지표가 세계 표준이 된 뒤에도 그걸 계속 손보고 있었다는 것이다.\n밴드폭(밴드가 얼마나 벌어졌나), %B(가격이 밴드 안 어디쯤인가), 그리고 BBTrend까지. 전부 원본 밴드를 재료로 만든 파생물이다. 밖에서 보면 밴드 하나로 이미 충분해 보이는데, 만든 사람은 계속 부족한 지점을 발견하고 있었던 셈이다.\n이게 내 상황과 겹쳐 보였다. 내 봇도 같은 뼈대 위에서 461번을 고쳤다. 밖에서 보면 \u0026ldquo;그냥 사고파는 프로그램\u0026quot;인데, 안에서는 매번 부족한 게 보였다. 손절을 넣고, 본전 사수를 붙이고, 레짐 차단을 세우고, 호가 게이트를 만들고.\n같은 뼈대에서 자란 것들 — 두 사례 볼린저 밴드 계열 원본 밴드 → 밴드폭(변동성) → %B(위치) → BBTrend(방향) 각 파생물은 원본이 못 답하던 질문 하나씩을 맡는다 내 봇의 방어 계열 손절 → 본전 사수 → 일일 한도 → 주간 한도 → 서킷브레이커 → 레짐 차단 역시 앞의 것이 못 막던 상황 하나씩을 맡는다 공통점: 처음부터 설계된 게 아니라 부족함이 발견될 때마다 붙었다 그래서 계보를 보면 그 사람이 무엇에 계속 데였는지가 보인다 마지막 줄이 이 코너를 하면서 얻은 관점이다. 어떤 지표의 파생 계보를 따라가면, 만든 사람이 어디서 계속 아쉬웠는지가 드러난다. 밴드폭이 나온 건 \u0026ldquo;변동성을 숫자로 못 읽어서\u0026quot;였을 테고, BBTrend가 나온 건 \u0026ldquo;밴드 두 벌을 눈으로 비교하기 번거로워서\u0026quot;였을 것이다.\n지표를 배울 때 수식만 외우면 이 맥락이 안 보인다. 왜 이게 만들어졌는지, 무엇이 부족해서 나왔는지를 알면 그 지표를 어디에 써야 하는지도 자연히 따라온다.\n흔한 오해 셋 이 지표를 소개하는 글들에서 자주 보이는 설명 중 정확하지 않은 게 몇 개 있다.\n\u0026ldquo;BBTrend가 1을 넘으면 강한 추세다.\u0026rdquo; 1이라는 눈금은 절대 기준이 아니다. 앞서 봤듯 종목마다 값의 범위 자체가 다르다. 어떤 종목은 평생 1을 못 넘고 어떤 종목은 하루에도 몇 번 넘는다. 임계값은 그 종목의 과거 분포 위에서 정해야 한다.\n\u0026ldquo;양수면 매수 신호.\u0026rdquo; 이건 지표를 신호로 오해한 것이다. BBTrend는 이미 벌어진 일을 요약하는 값이지 앞을 가리키는 값이 아니다. 밴드가 어긋나려면 가격이 먼저 움직여야 하니 구조적으로 후행한다.\n\u0026ldquo;짧은 기간을 더 짧게 하면 더 빨라진다.\u0026rdquo; 부분적으로만 맞다. 짧게 하면 반응은 빨라지지만 표준편차가 몇 개 안 되는 표본으로 계산돼서 값이 요동친다. 20 아래로 내리면 노이즈가 신호를 덮는 구간이 온다. 내 봇에서 볼린저 기간을 20에서 8까지 계속 줄여봤을 때도 같은 일이 있었다 — 민감해지긴 했는데 쓸 만한 신호가 늘지는 않았다.\n며칠 켜놓고 본 소감 지표를 글로만 다루면 반쪽이라, 며칠 붙여놓고 봤다. 몇 가지 인상이 남는다.\n첫째, 0 근처에 머무는 시간이 생각보다 길다. 명확한 신호가 나오는 구간은 하루 중 일부고 나머지는 계속 애매하다. 이건 결함이 아니라 시장이 원래 그렇다는 뜻에 가깝다. 지표가 항상 뭔가 말해주기를 기대하는 쪽이 잘못이다. 대부분의 시간에 아무 말도 안 하는 지표가 정직한 지표일 수 있다.\n둘째, 부호가 바뀌는 순간보다 크기가 커지는 구간이 눈에 띈다. 0을 통과하는 시점은 이미 늦었고, +0.3에서 +0.9로 커지는 구간에서 가격이 실제로 움직였다. 이건 진입 트리거로 쓰기 어렵다는 앞의 판단과 일치한다.\n셋째, 종목마다 값의 범위가 꽤 다르다. 어떤 종목은 ±0.5 안에서만 놀고 어떤 종목은 ±3까지 간다. 중심선으로 나눠 정규화했는데도 그렇다. 변동성 구조 자체가 다르기 때문인데, 이러면 모든 종목에 같은 임계값을 쓰는 설계가 위험해진다. 종목별 과거 분포의 상위 몇 퍼센타일을 임계로 쓰는 쪽이 나을 수 있다. 이건 스윕 설계에 반영해야 할 항목이다.\n숏이 없는 계좌에서 음수 구간은 무엇에 쓰나 BBTrend는 -100에서 +100까지가 아니라 사실상 무한대 범위의 값인데, 부호가 양쪽으로 다 나온다. 그런데 나는 현물만 거래한다. 하락에 베팅할 수단이 없다는 뜻이다.\n그럼 음수 구간은 쓸모없는가. 그렇지 않다. 오히려 이쪽이 더 중요할 수도 있다.\n현물 계좌에서 음수 신호의 용도 ✗ 못 하는 것 숏 진입 — 수단이 없다 ✓ 할 수 있는 것 ① 신규 진입 차단 BTC 4시간봉 BBTrend 가 음수 지속 → 시장이 아래로 밀리는 중 이럴 땐 아무것도 안 사는 게 최선일 수 있다 ② 보유 포지션의 경계 상향 시장 레짐이 나쁘면 수확 임계를 낮춰 빨리 털거나 새 진입 슬롯을 줄여 노출을 축소한다 ③ 종목 랭킹에서 후순위 게이트는 통과했지만 BBTrend 가 음수인 종목은 순서를 뒤로 \"안 사는 것도 포지션이다\" — 현물 계좌에서 방향 지표의 절반은 여기에 쓰인다 ①번은 내 봇이 이미 다른 방식으로 하고 있는 일이다. 비트코인 4시간봉이 하락 추세면 신규 진입 자체를 막는다. 이 규칙이 생긴 배경이 7주 연속 마이너스였다. 개별 진입 조건을 아무리 다듬어도, 시장 전체가 흘러내리는 구간에서 계속 사고 있으면 답이 없었다.\n그래서 BBTrend를 도입한다면 진입 트리거가 아니라 이 레짐 판정의 두 번째 근거로 넣는 게 자연스럽다. 지금은 판정 축이 하나뿐인데, 성격이 다른 축을 하나 더 두면 판정이 안정된다. 다만 둘이 자주 엇갈리면 그때는 어느 쪽을 따를지 규칙이 또 필요해진다. 조건을 늘리면 그 조건들을 조율하는 메타 규칙이 생기고, 그 메타 규칙도 결국 파라미터가 된다. 복잡성은 이렇게 늘어난다.\n검증 설계를 구체적으로 앞에서 스윕 축을 대충 적었는데, 실제로 돌린다면 이런 순서가 된다. 이 절차는 BBTrend만이 아니라 새 지표를 들일 때 공통으로 쓰는 틀이다.\n새 지표 도입 4단계 0단계 — 기록부터 진입할 때마다 그 시점의 BBTrend 값을 함께 저장한다 최소 2~3주. 이게 없으면 나머지 단계를 할 수 없다 1단계 — 사후 상관 이미 청산된 건들의 진입 시 BBTrend 와 결과(수익률)의 상관을 본다 익절 건과 손절 건의 BBTrend 분포가 실제로 갈리는가? 안 갈리면 여기서 끝. 게이트에 넣을 이유가 없다 2단계 — 단변량 스윕 임계를 후보값별로 걸어보고 평균수익·승률·손절권을 각각 기록 3단계 — 조합 검증 기존 게이트 통과분에만 적용했을 때도 개선이 남는가 여기서 대부분 죽는다 — 다른 게이트가 이미 같은 걸 걸러내고 있어서 4단계 — 기간 반분 표본을 앞뒤로 갈라 양쪽에서 모두 성립하는지 넷을 다 통과해야 배포 후보. 하나라도 실패하면 기각하고 기록만 남긴다 3단계에서 대부분 죽는다는 게 경험적 사실이다. 단독으로는 성적이 좋았던 조건을 기존 게이트 위에 얹었더니 후보가 46건에서 6건으로 줄어든 적이 있다. 87%가 사라졌고 성과도 무너졌다. 다른 게이트들이 이미 비슷한 정보를 다른 각도로 보고 있었던 것이다.\n그래서 나는 이제 새 지표를 볼 때 성능보다 독립성을 먼저 본다. 이 지표가 기존 조건들과 상관이 낮은가. 낮다면 정보를 늘리는 것이고, 높다면 중복 검문소를 하나 더 세우는 것이다. 검문소가 많다고 도시가 안전해지지는 않는다. 통행량만 죽는다.\nBBTrend의 경우 기존 게이트와의 상관이 어떨지는 재봐야 안다. 다만 예상은 해볼 수 있다. 중기추세 게이트가 며칠 고점 대비 낙폭을 보고, 저점근접이 24시간 저점 대비 거리를 보는데, BBTrend는 밴드의 이동 방향을 본다. 재는 대상이 겹치는 부분이 분명 있다. 완전히 독립적이지는 않을 것이다.\n정리 BBTrend는 새로운 정보를 만들어내는 지표가 아니다. 이미 있던 밴드 두 벌에서 비교 가능한 숫자 하나를 뽑아내는 압축 장치에 가깝다. 그 압축이 유용한 이유는 종목을 가로질러 줄 세울 수 있게 해주기 때문이고, 한계가 있는 이유도 같다 — 압축하면서 버린 정보(밴드 폭 자체, 가격의 밴드 내 위치)는 다시 꺼낼 수 없다.\n지표를 고를 때 나는 이제 이 질문을 먼저 한다. 이 지표는 무엇을 압축했고, 그 과정에서 무엇을 버렸는가. 볼린저가 자기 지표 위에 또 지표를 얹은 건, 밴드 두 벌을 눈으로 비교하는 일이 실제로 번거로웠기 때문일 것이다. 좋은 지표는 대개 누군가의 반복 노동을 자동화한 결과물이다.\n덧붙임 — 이 지표를 쓸 때의 체크리스트 혹시 직접 붙여보실 분을 위해 짧게 정리한다.\n표준편차 자유도를 확인한다. pandas의 std() 기본값은 표본 표준편차(ddof=1)인데 대부분의 차트 플랫폼은 모집단(ddof=0)을 쓴다. BBTrend는 이 차이를 두 번 겪고 뺄셈으로 증폭하므로 특히 티가 난다.\n워밍업을 넉넉히 잡는다. 긴 밴드가 50봉이면 최소 50봉이 필요하지만, 안정적인 값을 원하면 100봉 이상을 채운 뒤부터 쓴다. 앞부분 값은 버린다.\n완성봉만 쓴다. 진행봉을 넣으면 표준편차가 봉 안에서 계속 변한다. 같은 시점을 두 번 계산하면 다른 값이 나오는 지표는 검증이 불가능하다.\n종목별 분포를 먼저 본다. 임계값을 정하기 전에 그 종목의 BBTrend가 평소 어느 범위에서 노는지 확인한다. ±0.5 안에서만 노는 종목에 임계 1.0을 걸면 신호가 영영 안 나온다.\n부호 지속 봉 수를 함께 들고 다닌다. 값 하나보다 방향이 유지된 길이가 정보량이 많다. 0 근처를 오가는 오실레이터 전반에 적용되는 처방이다.\n다섯 개 중 앞의 세 개는 지표를 이식할 때 공통으로 해당되는 항목이고, 뒤의 둘은 이 지표에 특화된 것이다. 이 코너를 여러 편 쓰다 보니 앞의 세 항목은 거의 매번 반복된다. 지표마다 다른 건 수식이지 함정이 아니다.\n마지막으로 하나 더. BBTrend를 켜두고 며칠 보다 보면 값이 커지는 순간에 눈이 간다. 그런데 그 순간을 잡아서 사고 싶어지는 충동이 들면, 이 글의 앞부분을 다시 읽는 게 좋다. 값이 커졌다는 건 이미 움직였다는 뜻이다. 지표가 알려주는 건 방금 일어난 일이지 앞으로 일어날 일이 아니다.\n이 구분을 계속 상기해야 하는 이유는, 차트를 오래 보면 후행 지표가 선행 지표처럼 느껴지기 때문이다. 과거 차트에서는 신호와 상승이 나란히 보이니까 마치 신호가 상승을 예고한 것처럼 읽힌다. 실시간에서는 절대 그렇게 안 보인다. 이 착시를 이기는 방법은 하나뿐이다 — 백테스트를 정직하게 짜서, 신호 시점 이후의 결과만으로 판정하는 것.\n","permalink":"https://botlab.co.kr/study/bbtrend/","summary":"볼린저 밴드를 만든 사람이 40년 뒤에 직접 내놓은 후속작. 밴드 두 개의 어긋남을 숫자 하나로 압축한다.","title":"BBTrend — 볼린저 밴드 두 개를 겹쳐서 추세를 재는 법 | Crypto Trading Bot"},{"content":"오늘의 재료 — 지표가 아니라 세계관 이 코너에서 다룬 것들은 지금까지 전부 \u0026lsquo;계산\u0026rsquo;이었다. RSI는 함수고, 거래량은 원자료고, SMC와 피보나치도 결국 비교문 묶음이었다. 오늘의 재료는 급이 다르다. 엘리엇 파동은 계산이 아니라 세계관이다 — 시장의 모든 움직임이 집단 심리의 리듬에 따라 5개의 추진 파동과 3개의 조정 파동으로 진행된다는, 1930년대 랄프 넬슨 엘리엇의 이론. 그리고 미리 결론을 적어두면, 이 이론은 내 봇에 통째로 들어갈 수 없다. 그런데도 한 편을 쓰는 이유는 해부 과정에서 나오는 부산물 — 반증 가능성이라는 칼 — 이 지표 스터디 전체를 관통하는 주제이기 때문이다.\nelliott wave — 5-3 기본 구조 충격파 (Impulse, 추세 방향) 조정파 (Correction) 5 3 ╱╲ ╲ B ╱╲ ╱ ╲ A ╲ ╱╲ ╱ ╲╱ ╲ ╲╱ ╲ 1 ╱ 4 ╲ ╲ C ╱╲ ╱ ╲ ╲ ╱ ╲╱ ─ 이후 한 단계 큰 파동의 부품이 됨 ╱ 2 프랙탈: 파동 1 안에도 다섯 파동이, 그 안에도 다섯 파동이 — 전 시간축 자기유사 피보나치 결합: 파동 목표·되돌림 깊이를 φ 비율로 잰다 (지난 편과 직결) 뼈대: 절대 규칙 3개 — 이론에서 유일하게 단단한 부분 엘리엇 이론의 방대한 서사 속에서 예외 없는 규칙은 딱 3개다. 나머지(연장, 절단, 교대 지침 등)는 전부 \u0026lsquo;경향\u0026rsquo;이고, 이 셋만이 위반되면 카운팅 자체가 무효가 되는 절대 규칙이다. 그리고 흥미롭게도 — 이 3개는 완벽하게 코드로 옮겨진다.\nthree hard rules — 이론에서 코드로 넘어올 수 있는 전부 규칙 1 파동 2는 파동 1의 시작점 아래로 내려갈 수 없다 wave2_low \u003e wave1_start # 위반 시 카운팅 무효 규칙 2 파동 3은 1·3·5 중 가장 짧을 수 없다 len(w3) \u003e min(len(w1), len(w5)) # 대개 3파가 최장 규칙 3 파동 4는 파동 1의 가격 영역을 침범할 수 없다 wave4_low \u003e wave1_high # (현물 기준 고전 규칙) 이 세 줄이 '무효화 라인' 이다 — 가설이 틀렸음을 아는 지점. 나머지 지침들: 교대(2파와 4파는 형태가 다르다), 연장(한 파동이 길어진다), 채널링, 파동 균등 … 전부 '경향' — 판정식이 아니라 서사 여기서 이 이론의 실용적 core가 보인다. 엘리엇 파동의 진짜 기여는 \u0026ldquo;다음에 5파가 온다\u0026quot;는 예언이 아니라, \u0026ldquo;내 시나리오가 틀렸다는 것을 어느 가격에서 인정할 것인가\u0026quot;를 강제하는 구조다. 파동 2 시나리오로 샀다면 파동 1 시작점이 무효화 라인이고, 무효화 라인은 봇의 언어로 번역하면 정확히 손절선이다. 서사는 못 가져와도 이 구조는 가져올 수 있다.\n파동을 세려면 먼저 스윙을 세야 한다 — ZigZag 파동 카운팅의 입력은 캔들이 아니라 스윙이다. 그래서 어떤 엘리엇 자동화 시도든 첫 공정은 ZigZag — 일정 임계 이상 움직였을 때만 방향 전환을 인정해 지그재그 선분으로 압축하는 알고리즘이다.\npython — ZigZag: 파동 카운팅의 전처리기 # 반전 임계 pct 이상 움직일 때만 방향 전환을 인정 def zigzag(price, pct=0.05): piv, direction, last = [], 0, price.iloc[0] for i, p in enumerate(price): chg = (p - last) / last if direction \u003e= 0 and chg \u003c= -pct: piv.append((i,'H',last)); direction, last = -1, p elif direction \u003c= 0 and chg \u003e= pct: piv.append((i,'L',last)); direction, last = 1, p elif (direction \u003e= 0 and p \u003e last) or (direction \u003c= 0 and p \u003c last): last = p return piv # 함정: 마지막 선분은 항상 미확정이다 — 임계를 넘기 전엔 전환인지 알 수 없다 # 차트의 ZigZag 가 '리페인트' 한다는 말이 이것. 봇은 확정분만 써야 한다 # 함정: pct 는 하이퍼파라미터다. 이 값 하나가 파동의 개수를 통째로 바꾼다 두 번째 함정이 다음 절의 주제로 이어진다. 파동 이론은 \u0026ldquo;시장은 5-3 구조로 움직인다\u0026quot;고 말하지만, 정작 그 5와 3을 세기 위한 스윙의 정의는 이론이 제공하지 않는다. 임계값은 분석가가 고른다 — 그리고 고른 순간, 결론의 절반이 정해진다.\n문제: 카운팅의 조합 폭발 — 같은 차트, 열 개의 진실 그럼 왜 통째로는 못 넣는가. 파동을 \u0026lsquo;세는\u0026rsquo; 작업이 알고리즘이 아니기 때문이다. 파동의 최소 단위인 스윙부터가 임계값의 함수다 — ZigZag 지표로 스윙을 뽑을 때 반전 임계를 3%로 주면 파동이 수십 개, 7%로 주면 몇 개가 된다. 임계마다 다른 파동 세계가 열리고, 그 각각 위에서 규칙 3개를 만족하는 카운팅이 또 여러 개 존재한다.\nthe counting problem — 임계값이 세계를 바꾼다 같은 가격 데이터, ZigZag 반전 임계만 변경: 임계 3% ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ 스윙 16개 → 가능한 카운팅 다수 임계 5% ╱╲ ╱╲ ╱╲ ╱╲ 스윙 8개 → 다른 카운팅 다수 임계 7% ╱ ╲╱ ╲ ╱ ╲ 스윙 5개 → 또 다른 카운팅 어느 임계가 '진짜 파동' 인가? — 이론은 답을 주지 않는다 실무 격언: \"엘리엇 분석가 열 명 = 카운팅 열한 개\" 그리고 결정타 — 사후 재라벨링: 가격이 예상을 깨면 카운팅을 소급 수정한다. \"그건 3파가 아니라 C파였다\" 이론은 영원히 옳고, 틀리는 것은 항상 카운팅이다 → 반증 불가능 마지막 두 줄이 핵심이다. 피보나치 편에서 \u0026ldquo;틀리면 스윙을 잘못 그은 것\u0026quot;이라는 도피처를 봤는데, 엘리엇은 그 도피처가 이론의 공식 문법에 내장돼 있다. 예측이 빗나가면 카운팅을 다시 세면 되고, 다시 센 카운팅은 언제나 지나간 가격과 정합한다. 사후적으로는 항상 옳고 사전적으로는 거의 아무것도 확정하지 않는 이론 — 과학철학의 언어로는 반증 불가능이다. 그리고 반증 불가능한 것은 백테스트도 불가능하다. 백테스트란 조직적 반증 시도의 다른 이름이니까.\n봇 번역 실험 — 절반은 담긴다 그래도 번역을 시도해 보면 어디까지 되는지가 보인다. 되는 것과 안 되는 것의 경계가 이 글의 수확이다.\ntranslation — 엘리엇을 봇에 넣으려는 시도의 결산 번역 가능 (판정식이 존재) ① 스윙 추출 ZigZag(임계 고정) — 단, 확정 지연 명시 (SMC 편과 동일) ② 규칙 3개 검사 스윙 5개 조합마다 규칙 1·2·3 통과 여부 → 유효 카운팅 목록 ③ 무효화 라인 시나리오별 '여기 깨지면 포기' 가격 → 손절선으로 직역 ④ 보조 통계 \"3파 최장\" 경향의 실측 — 추세 구간 길이 분포 측정 번역 불가능 (판정식이 없음) ⑤ 유일 카운팅 선택 유효 카운팅 여러 개 중 '진짜' 고르기 — 기준 부재 ⑥ 파동 등급 판정 지금이 3파인지 C파인지 — 사후에만 확정 ⑦ 예측 \"다음은 5파\" — ⑤⑥이 안 되므로 연쇄 붕괴 결산: 서사(⑤⑥⑦)는 버리고 구조(①②③④)만 수입한다 번역 가능 목록을 다시 보면 낯익다. ①은 SMC의 스윙 구조 감지이고, ③은 손절선이고, ④는 추세 지속 길이의 통계다 — 전부 엘리엇이라는 이름 없이도 성립하는 부품들이다. 결국 엘리엇 파동에서 봇으로 수입되는 것은 이론의 고유 주장(시장은 5-3 리듬으로 움직인다)이 아니라, 이론이 강제하는 규율의 형식(시나리오에는 무효화 지점이 있어야 한다)이다. 내 봇은 이 형식을 이미 다른 경로로 갖고 있다. 진입마다 손절선이 계산되고(-5%), 본전 사수 무장선이 있고(+0.8/+0.4), 레짐이 깨지면 신규 진입이 서고(BTC 4시간봉 차단), 자산 서킷브레이커가 최후 방어선(300만원)을 지킨다 — 전부 \u0026ldquo;여기가 깨지면 내 가설이 틀린 것\u0026quot;의 코드화다. 엘리엇 분석가의 무효화 라인과 내 봇의 손절선은 같은 철학의 두 방언이다.\n그래도 측정할 것 — 서사를 뺀 통계 가설 이론을 반납한다고 해서 배울 게 없는 것은 아니다. 엘리엇의 주장들 중 몇 개는 서사를 벗기면 측정 가능한 통계 가설로 번역된다. 파동 등급을 알아맞힐 필요 없이, 분포만 보면 되는 것들이다.\ntestable claims — 파동 서사에서 뽑아낸 통계 가설 가설 A \"3파가 가장 길다\" → 추세 구간 길이 분포의 검정 ZigZag 선분을 방향별로 나열, 연속 상승 선분 중 2번째가 1·3번째보다 긴 빈도가 무작위 기대치를 넘는가 가설 B \"2파와 4파는 형태가 다르다(교대)\" → 되돌림 깊이의 자기상관 직전 조정이 얕았으면 다음 조정은 깊은 경향이 있는가 (상관계수) 가설 C \"5파 완성 후 3파 조정\" → N연속 추세 후의 조건부 수익률 상승 선분 3개 연속 뒤 진입 시 기대값 vs 무조건 진입 기대값 공통 절차: ZigZag 임계 3종(3%·5%·7%) 각각에서 독립 측정 한 임계에서만 성립하면 그것은 발견이 아니라 우연이다 결과 칸은 비어 있다 — 측정 전이므로. 이 세 가설의 공통점은 파동에 이름표를 붙이지 않아도 검정된다는 것이다. \u0026ldquo;지금이 3파인가\u0026quot;를 묻지 않고 \u0026ldquo;연속 상승 선분 중 두 번째가 긴 경향이 있는가\u0026quot;를 묻는다. 질문을 이렇게 바꾸는 순간 반증 가능해지고, 반증 가능해지는 순간 백테스트에 올릴 수 있다. 이론을 통째로 수입하는 대신 이론이 낳은 가설만 골라 수입하는 것 — 이것이 내가 찾은 타협점이다.\n개발자의 눈 — 반증 가능성이라는 단 하나의 심사 기준 지표 스터디 여섯 편째, 심사 기준이 하나로 수렴했다. RSI와 거래량은 함수라서 통과. SMC는 서사를 벗기면 비교문이라서 통과. 피보나치는 스윙 정의를 고정하고 대조군을 세우면 가설로 승격되어 조건부 통과. 엘리엇은 — 규칙 3개와 무효화 구조만 통과, 나머지는 반증 불가능이라 탈락. 심사 기준은 시종일관 하나였다. 이것이 틀렸다면, 틀렸다는 것을 어떻게 알 수 있는가?\n이 기준은 지표만이 아니라 개발 규율 전체에 적용된다. 커밋마다 근거 수치를 요구하는 것(검증 없이 배포 없다), 예상이 빗나가면 왜 빗나갔는지 추적 가능하게 근거를 기록해 두는 것, 파라미터마다 측정 조합의 전제조건을 남기는 것 — 461개 버전의 규율은 전부 \u0026ldquo;내가 틀렸음을 빨리, 정확하게 알기 위한\u0026rdquo; 장치들이다. 시장에서 오래 살아남는 것은 옳은 사람이 아니라 자기가 틀렸음을 빨리 아는 사람이라는 게, 수업료를 내고 배운 내 결론이다.\n프랙탈이라는 진짜 통찰 — 그리고 그 대가 이론에서 서사를 걷어내고도 살아남는 통찰이 하나 더 있다. 프랙탈 구조다. 파동 1 안에 다섯 파동이 있고 그 안에 또 다섯이 있다는 주장 — 즉 시장의 움직임은 시간축을 바꿔도 비슷하게 생겼다는 것. 이건 엘리엇의 독창적 관찰이었고, 훗날 만델브로가 수학적으로 파고든 주제이기도 하다. 15분봉을 확대하면 1분봉처럼 보이고, 축소하면 4시간봉처럼 보이는 경험은 차트를 오래 본 사람이면 누구나 안다.\nfractal — 자기유사성이 봇에 주는 것과 뺏는 것 주는 것 — 다중 시간축 설계의 근거 같은 판정 로직을 여러 TF 에 재사용할 수 있다 내 봇의 사례: 진입 판정은 15분봉, 레짐 판정은 BTC 4시간봉 (VV202) 하위 TF 는 타이밍, 상위 TF 는 허가 — 역할 분리가 프랙탈 위에서 성립 뺏는 것 — 시간축 특권의 부재 \"어느 TF 가 진짜인가\" 에 이론은 답하지 않는다 15분 기준 3파가 4시간 기준으로는 1파의 일부일 수 있다 → 카운팅의 조합 폭발(앞 절)이 시간축 방향으로 한 번 더 일어난다 봇의 타협: TF 를 이론이 아니라 운용 목적으로 고정한다 \"슬롯 회전이 하루 단위이므로 진입은 15분, 레짐은 4시간\" — 근거가 실무에 있다 프랙탈은 봇에 다중 시간축 설계의 정당성을 준다. 실제로 내 봇의 구조가 그렇다 — 종목 진입은 15분봉으로 보고, 시장 전체의 허가 여부는 BTC 4시간봉 레짐으로 판정한다(VV202). 하위 TF는 타이밍을, 상위 TF는 허가를 담당하는 이 역할 분리는 시장이 시간축을 넘나들며 비슷하게 생겼다는 전제 위에서 성립한다. 그런데 같은 프랙탈이 대가도 청구한다 — 어느 시간축이 \u0026lsquo;진짜\u0026rsquo;인지 이론은 알려주지 않는다. 그래서 나는 시간축을 이론이 아니라 운용 목적으로 고른다. 슬롯 회전 주기가 하루 단위라서 15분봉을 쓰는 것이지, 15분봉이 시장의 참된 리듬이라서가 아니다. 근거를 이론이 아니라 내 운용 조건에 두면, 적어도 왜 그 값인지에는 언제나 답할 수 있다.\n소회 엘리엇 파동을 공부하고 나면 이상한 존경심이 남는다. 1930년대에, 컴퓨터도 없이, 시장의 프랙탈 구조와 집단 심리의 리듬을 통찰한 사람의 눈은 진짜였다고 생각한다. 문제는 그 통찰이 판정식이 아니라 해석의 예술로 전승됐다는 것이다. 예술은 대가를 만들지만 봇을 만들지는 못한다.\n한 가지 덧붙이자면, 이 이론이 오래 살아남은 진짜 이유는 적중률이 아니라 설명력이라고 생각한다. 사람은 무작위 속에서도 이야기를 찾도록 만들어져 있고, 엘리엇은 시장에 붙일 수 있는 가장 정교한 이야기 문법을 제공했다. 지나간 차트를 파동으로 설명하면 모든 급등과 급락에 이름이 붙고, 이름이 붙으면 이해했다는 감각이 온다. 그 감각이 진짜 이해인지 아닌지를 가르는 것이 다음 봉에서의 예측력인데 — 바로 그 지점에서 이론은 카운팅 수정이라는 뒷문으로 빠져나간다. 매혹적인 이야기와 검증 가능한 모델 사이의 거리를, 나는 이 이론을 공부하면서 가장 선명하게 배웠다.\n그래서 이 글은 엘리엇 파동의 부고가 아니라 분해 보고서다. 세계관은 정중히 반납하고, 규칙 3개와 무효화의 형식은 수입한다. 시나리오를 세우되 그 시나리오가 죽는 가격을 먼저 적는 것 — 이 습관 하나가 이론 전체에서 살아남은 알맹이고, 그 알맹이는 이미 내 봇의 손절선 안에서 일하고 있다. 파동을 세는 일은 앞으로도 사람의 예술로 남겨두려 한다. 내 봇은 세지 않는다. 대신 측정한다.\n칼 포퍼는 과학과 비과학을 가르는 기준으로 반증 가능성을 제시했다 — 어떤 관찰로도 부정될 수 없는 이론은 모든 것을 설명하는 대신 아무것도 예측하지 못한다고. 트레이딩 시스템의 심사 기준으로 이보다 나은 문장을 나는 아직 찾지 못했다. 전략을 사기 전에 물어라. 이 전략은 어떤 데이터가 나오면 틀린 것으로 판정되는가. 그 답이 없는 전략은 전략이 아니라 신앙이다.\n","permalink":"https://botlab.co.kr/study/elliott/","summary":"5-3 파동, 3대 절대 규칙, 그리고 카운팅의 조합 폭발. 사람의 눈에는 어디에나 보이고 봇의 코드에는 절반만 담기는 이론 — 그 절반이 무엇이고 왜 그 절반만으로도 가치가 있는지.","title":"Elliott Wave — 봇에 넣을 수 없는 이론을 굳이 해부하는 이유 | Crypto Trading Bot"},{"content":"오늘의 재료 — 13세기 수학이 차트에 사는 법 피보나치 수열은 1202년 이탈리아 수학자 레오나르도 피보나치가 토끼 번식 문제로 소개한 수열이다. 1, 1, 2, 3, 5, 8, 13, 21… 앞의 두 항을 더하면 다음 항이 된다. 이 수열이 트레이딩에 들어온 것은 인접한 두 항의 비율이 황금비 φ ≈ 1.618로 수렴하기 때문이다 — 그리고 그 역수 0.618, 제곱 역수 0.382 같은 파생 비율들이 \u0026ldquo;가격이 되돌아오는 깊이\u0026quot;의 기준선으로 쓰인다.\nfibonacci — 수열에서 비율로, 비율에서 레벨로 수열: 1 1 2 3 5 8 13 21 34 55 89 ... 인접비: 1.0 2.0 1.5 1.667 1.6 1.625 1.615 1.619 1.618 → φ 수렴 파생 비율과 되돌림 레벨 0.236 = 한 항 건너 셋 비율의 역 \"얕은 되돌림\" 0.382 = 1/φ² \"표준 눌림\" 0.618 = 1/φ \"황금 되돌림\" 0.786 = √0.618 \"깊은 되돌림\" 0.500 = 피보나치가 아님 — 다우 이론의 반값 되돌림 전통이 얹혀 산다 계산: 상승 스윙(low→high) 기준 레벨 = high - (high - low) × 비율 먼저 신화부터 걷어내자. \u0026ldquo;황금비는 자연의 법칙이므로 시장에도 작동한다\u0026quot;는 서사는 검증된 적이 없다. 해바라기 씨앗 배열과 업비트 15분봉 사이에 인과가 있을 이유는 없다. 그런데도 이 레벨들이 실전에서 무시할 수 없는 물건인 이유는 따로 있다 — 수백만 명이 같은 자리에 같은 선을 긋기 때문이다. 모두가 0.618에 매수 주문을 걸어두면 0.618은 실제로 지지선이 된다. 법칙이라서가 아니라 합의라서 작동하는 것. 이 구분이 오늘 글 전체의 뼈대다.\n공식에서 코드로 — 계산은 두 줄, 문제는 스윙 되돌림 레벨의 계산 자체는 스터디 역사상 가장 쉽다. 뺄셈과 곱셈뿐이다. 그런데 코드로 옮기는 순간, 차트에서는 보이지 않던 문제가 정면에 나타난다 — 어느 스윙에 긋는가.\npython — 레벨 계산은 쉽고, 스윙 선택은 어렵다 # 레벨 계산 — 이게 전부다 def fib_levels(swing_low, swing_high): r = swing_high - swing_low return {k: swing_high - r * k for k in (0.236, 0.382, 0.5, 0.618, 0.786)} # 진짜 문제 — swing_low, swing_high 를 누가 정하는가 # 사람: 차트를 보고 '의미 있어 보이는' 스윙을 고른다 (재현 불가) # 봇 : 스윙 정의를 고정한다 — 예: N봉 좌우에서 최고/최저인 봉 (fractal) def swing_high_confirmed(h, n): return h.shift(n) == h.rolling(2*n+1, center=True).max().shift(n) # 주의: 이 판정은 n봉 뒤에야 확정된다 — SMC 편의 확정 지연과 동일한 문제. # n을 바꾸면 스윙이 바뀌고, 스윙이 바뀌면 모든 레벨이 통째로 이동한다 사람이 손으로 긋는 피보나치가 백테스트 불가능한 이유가 여기 있다. 열 명의 트레이더에게 같은 차트를 주면 열 개의 다른 스윙을 긋고, 열 벌의 다른 레벨이 나온다. 그중 맞은 사람은 \u0026ldquo;피보나치가 작동했다\u0026quot;고 기억하고, 틀린 사람은 \u0026ldquo;스윙을 잘못 그었다\u0026quot;고 기억한다 — 이론은 영원히 무죄다. 봇은 이 편리한 도피처가 없다. 스윙 정의를 코드로 고정해야 하고, 고정하는 순간 피보나치는 처음으로 반증 가능한 가설이 된다. 나는 이것이 퇴보가 아니라 승격이라고 생각한다.\n레벨의 실제 모습 — 그리고 합류라는 이름의 확률 놀이 retracement — 상승 스윙의 되돌림 지도 high ────────────────────── 0.000 ▲ 스윙 고점 ╲ ───────────── ╲ ─────────── 0.236 얕은 눌림 (강한 추세) ──────────────╲──────────── 0.382 표준 눌림 ───────────────╲─────────── 0.500 반값 (비-피보나치) ────────────────╲╱╲──────── 0.618 황금 되돌림 ◀ 반등 시도 ─────────────────────────── 0.786 깊은 되돌림 (추세 의심 구간) low ────────────────────── 1.000 ▼ 스윙 저점 (전량 반납) 주의: 레벨이 5개면 스윙 범위의 상당 부분이 '레벨 근처'다. 아무 데서나 반등해도 사후에는 어떤 레벨과든 가깝다 — 대조군이 필요한 이유 실전 피보나치 사용자들은 여기에 \u0026lsquo;합류(confluence)\u0026lsquo;를 얹는다 — 0.618 되돌림이 이동평균과 겹치고, 전 저항선과 겹치면 신뢰도가 높다는 논리다. 논리 자체는 건전하다. 독립적인 근거의 교차는 통계에서도 신뢰를 올린다. 문제는 실무에서 합류가 사후 선택이 되기 쉽다는 것이다. 레벨 5개, 이동평균 3개, 전고점·전저점 여러 개를 깔아두면 차트 어디를 찍어도 두세 개는 겹친다. 합류를 신호로 쓰려면 어떤 조합을 유효한 합류로 칠지 사전에 고정해야 하고 — 또 봇의 언어로 돌아왔다.\n되돌림 너머 — 확장과 프로젝션 피보나치의 절반은 되돌림(retracement)이지만 나머지 절반은 **확장(extension)**이다. 되돌림이 \u0026ldquo;얼마나 빠질까\u0026quot;를 묻는다면 확장은 \u0026ldquo;얼마나 갈까\u0026quot;를 묻는다 — 즉 익절 목표 설정에 쓰인다. 1.272, 1.618, 2.618 같은 비율을 스윙 범위에 곱해 목표가를 잡는 방식이다.\nextension — 되돌림이 진입이라면 확장은 익절이다 스윙 low → high 를 100 으로 놓고 2.618 ────────────────────── 공격적 목표 (드물게 도달) 1.618 ────────────────────── 표준 확장 목표 ◀ 가장 널리 쓰임 1.272 ────────────────────── 보수적 확장 목표 1.000 ────────────────────── 전고점 0.618 ────────────────────── 되돌림 진입 후보 실무: 0.618 에서 사고 1.618 에서 판다 = 손익비 약 1:1.6 구조 내 봇의 대응물 — 비율이 아니라 실측으로 정한 값들 익절 목표 포트폴리오 예상실현 20,000원 (VV209, 빈도 극대화) 손절 -5% (VV204, 다층 방어 완성 후 확장) 본전 사수 무장 +0.8% / 바닥 +0.4% (VV206, 호가 관통 실측 반영) 근거의 출처가 다르다: 수열의 비율 vs 내 계좌의 27건·60건 재현 여기서 두 접근의 차이가 선명해진다. 피보나치 확장은 \u0026ldquo;스윙 폭의 1.618배\u0026quot;라는 비율의 세계에 산다. 내 봇의 익절은 \u0026ldquo;예상실현 2만원\u0026quot;이라는 절대금액의 세계에 산다. 어느 쪽이 우월한가는 상황에 달렸다 — 계좌 규모가 커지면 절대금액 기준은 언젠가 비율 기준으로 갈아타야 한다. 다만 지금 이 계좌, 이 슬롯 크기에서는 절대금액이 더 정직했다. 이익 구간 체류시간 중앙값이 0분이라는 실측(VV210) 앞에서, 1.618배까지 기다리는 설계는 성립하기 어려웠다.\n검증 설계 — 대조군 없는 피보나치 백테스트는 전부 무효 이 지표의 검증에는 다른 지표에 없던 절차가 하나 추가된다. 대조군이다. \u0026ldquo;0.618 근처에서 반등이 잦더라\u0026quot;만으로는 아무것도 증명되지 않는다 — 레벨이 촘촘하니 반등은 어차피 어딘가의 레벨 근처에서 일어난다. 증명하려면 무작위 레벨과 비교해야 한다.\nvalidation design — 자기실현 가설의 검증 절차 가설: \"피보나치 레벨 근처에서 반등 확률이 유의하게 높다\" ① 스윙 정의 고정 fractal n ∈ {5, 10, 20} — n마다 별도 실험 ② 실험군 가격이 각 레벨 ±0.2×ATR 진입 후 k봉 내 반등률 측정 ③ 대조군 같은 스윙에 무작위 비율 5개(균등분포)로 같은 측정 ④ 판정 실험군 반등률이 대조군 분포의 바깥(상위 5%)일 때만 유효 추가 대조: 0.618 단독 vs 0.5 vs 0.382 — '황금비' 가 정말 특별한지 분리 측정 이 표의 결과 칸이 비어 있는 이유: 아직 측정 전이다. 측정 전의 숫자를 적는 순간 이 스터디는 광고가 된다 — 이 코너의 서명. 예상을 하나 적어두자면(예상은 수치가 아니니까 적어도 된다), 나는 실험군이 대조군을 크게 이기지 못할 가능성도 상당하다고 본다. 그래도 이 실험은 해볼 가치가 있다 — 지는 쪽이어도 \u0026lsquo;피보나치 합류\u0026rsquo;라는 말을 계좌 근처에서 영구 추방할 근거가 생기고, 이기는 쪽이면 게이트 후보가 하나 는다. 검증의 좋은 점은 어느 쪽 결과든 자산이 된다는 것이다.\n내 봇과의 접점 — 이름 없이 이미 쓰고 있던 것 SMC 편에서 했던 고백을 여기서도 하게 된다. 내 봇은 피보나치를 그은 적이 없지만, 되돌림 깊이의 측정은 이미 하고 있다. 진입 게이트의 낙폭 상한(며칠 고점 대비 얼마나 빠졌나)과 저점근접 게이트(24시간 저점 대비 얼마나 가까운가, VV201 스윕 확정값 3.0)는 사실상 \u0026lsquo;되돌림이 충분히 깊은가\u0026rsquo;를 피보나치 비율 대신 실측 스윕값으로 판정하는 장치다. SMC의 디스카운트 존이 스윙 범위 하단이었던 것, 그 존의 중앙(이퀼리브리엄)이 정확히 0.5 되돌림인 것까지 — 세 지표 스터디가 결국 같은 질문의 세 가지 표기법으로 수렴하고 있다: \u0026ldquo;얼마나 빠졌을 때가 사기 좋은 자리인가.\u0026rdquo;\n차이는 기준선을 어디서 가져오느냐다. 피보나치는 13세기 수열에서, SMC는 스윙 범위 등분에서, 내 봇은 내 매매기록 스윕에서 가져온다. 나는 당연히 세 번째가 옳다고 믿는 쪽이다 — RSI 편에서 적었던 원칙 그대로, 공식은 수입해도 파라미터는 국산이어야 한다. 0.618이 내 매매기록에서 유의하게 나온다면 그때 0.618을 쓰면 된다. 황금비라서가 아니라, 측정돼서.\n봇 구현 — 만든다면 이런 모습 가설 검증이 통과한다고 가정하고, 실제로 구현한다면 어떤 모듈이 되는지도 그려두자. 피보나치는 RSI 같은 단일 값이 아니라 SMC의 오더블록처럼 상태를 관리하는 자료구조가 된다.\nimplementation — 피보나치를 봇 모듈로 만들면 state.active_swing = (low_price, low_bar, high_price, high_bar) state.levels = {0.382: p1, 0.5: p2, 0.618: p3, 0.786: p4} state.invalidated = bool # 스윙 저점 이탈 시 True → 레벨 전체 폐기 갱신 규칙 ① 새 스윙 고점 확정 → 활성 스윙 갱신, 레벨 재계산 ② 가격이 스윙 저점 하향 이탈 → 이 스윙의 레벨 전부 무효 (재사용 금지) ③ 상위 TF 스윙과 하위 TF 스윙을 동시에 보유 (합류 판정용, 조합 사전 고정) 질의 인터페이스 nearest_level_below(price) → (레벨값, 거리/ATR, 비율) is_in_zone(price, k=0.2) → 어떤 레벨의 ±k×ATR 안에 있는가 필수 기록: 각 레벨의 '생성 봉' 과 '확정 봉' 을 분리 저장 스윙은 n봉 뒤 확정 — 이 지연을 무시하면 미래 참조 백테스트가 된다 ②번 규칙을 특히 강조하고 싶다. 사람이 손으로 그은 피보나치의 최대 문제는 폐기 규칙이 없다는 것이다. 가격이 스윙 저점을 뚫고 내려가도 그 선들은 차트에 남아 있고, 사람은 남은 선을 계속 쳐다본다. 봇에는 무효화 규칙이 코드로 있어야 한다 — 그리고 이 요구사항은 다음 편(엘리엇 파동)의 \u0026lsquo;무효화 라인\u0026rsquo; 개념과 정확히 같은 것이다.\n왜 하필 이 숫자들인가 — 회의론자의 반론도 들어두자 공정하게, 피보나치 회의론의 논거도 정리해 둔다. 첫째, 0.5는 피보나치 수열과 아무 관계가 없다. 인접 항 비율 어디서도 0.5가 나오지 않는데, 실전에서는 반값 되돌림이 가장 널리 인용된다 — 다우 이론의 전통이 슬쩍 얹혀 산 것이다. \u0026lsquo;피보나치 레벨\u0026rsquo;이라 불리는 묶음에 비-피보나치가 섞여 있고 아무도 문제 삼지 않는다는 사실 자체가, 이 도구가 수학적 엄밀성으로 쓰이는 게 아니라는 방증이다.\n둘째, 레벨의 밀도 문제. 0.236·0.382·0.5·0.618·0.786 다섯 개를 그으면 스윙 범위의 상당 부분이 어떤 레벨의 근처가 된다. 여기에 확장 레벨과 다른 시간축 스윙까지 얹으면 차트는 선으로 뒤덮인다. 사후에 \u0026ldquo;역시 피보나치에서 돌았다\u0026quot;고 말하기는 너무 쉽다. 셋째, 되돌림 자체의 흔함. 오른 것은 조금 되돌리는 게 정상이므로, 어떤 되돌림 이론이든 기본 적중률이 이미 높다. 그래서 대조군 없이는 아무것도 말할 수 없다.\nskeptic's checklist — 피보나치 주장을 만났을 때의 3문 누군가 \"피보나치가 먹혔다\" 고 말할 때 물어야 할 것 ① 스윙을 언제 정했나? 사후에 골랐다면 데이터가 아니라 이야기다 ② 대조군은? 무작위 레벨 대비 우위가 없으면 착시다 ③ 실패 사례의 수는? 맞은 것만 세면 어떤 이론이든 100%다 이 세 질문은 피보나치 전용이 아니다 — 모든 차트 패턴 주장에 그대로 적용된다 그리고 봇 개발자에게는 셋 다 자동으로 해결된다: 스윙 정의가 코드에 고정 → ① 해결 전수 재현 → ③ 해결 대조군만 직접 설계하면 된다 → 위 검증 계획 그럼에도 나는 이 지표를 완전히 버리지는 않는다. 이유는 하나 — 유동성은 사람이 보는 곳에 뭉치기 때문이다. 지지선이 물리 법칙이어서가 아니라, 그 가격에 주문을 걸어둔 사람이 실제로 있어서 체결이 일어난다. SMC 편의 EQH/EQL(유동성 풀)과 정확히 같은 논리 구조다. 그렇다면 검증해야 할 진짜 가설은 \u0026ldquo;황금비가 신비롭다\u0026quot;가 아니라 \u0026ldquo;널리 관측되는 가격대 근처에 주문이 뭉친다\u0026quot;이고, 이건 측정 가능하다.\n소회 피보나치는 이 코너에서 다룬 지표 중 가장 오래됐고 가장 신비화된 물건이다. 해부해 보니 남는 것은 뺄셈과 곱셈, 그리고 두 개의 진짜 문제 — 스윙 선택의 자의성과 대조군 없는 검증의 착시 — 였다. 신비는 수열에 있지 않았다. 오히려 흥미로운 것은 사회적 현상으로서의 피보나치다. 근거가 희박한 기준선도 충분히 많은 사람이 믿으면 실재가 된다는 것. 시장이 물리계가 아니라 참여자들의 상호 예측 게임이라는 사실을 이만큼 선명하게 보여주는 지표가 없다.\n그래서 봇 개발자의 결론은 양가적이다. 황금비의 신화는 버린다 — 그러나 \u0026lsquo;많은 사람이 보는 선 근처에서 유동성이 뭉친다\u0026rsquo;는 가설은 검증 대상으로 남긴다. 신화와 가설의 차이는 딱 하나, 반증 절차의 유무다. 위의 대조군 실험이 그 절차이고, 결과가 나오면 이 코너에서 속편으로 보고하겠다.\n케인즈는 주식 투자를 미인대회에 비유했다 — 내가 예쁘다고 생각하는 얼굴이 아니라, 남들이 예쁘다고 생각할 얼굴을 골라야 이기는 게임. 피보나치 레벨은 그 미인대회의 후보 명단이다. 0.618이 아름다워서가 아니라 모두가 그 번호를 적어내기 때문에 당선되는 것이다. 그러니 물어야 할 것은 황금비의 신비가 아니라, 오늘도 그 번호를 적는 손이 몇이나 남았는가다.\n","permalink":"https://botlab.co.kr/study/fibonacci/","summary":"토끼 번식 문제에서 나온 수열이 어쩌다 차트의 표준이 됐나. 되돌림 레벨의 실제 계산, 스윙 선택이라는 숨은 자의성, 자기실현 예언 논쟁, 그리고 대조군 없는 백테스트의 착시까지.","title":"Fibonacci Retracement — 0.618이라는 신화와 실용 사이 | Crypto Trading Bot"},{"content":"오늘의 재료 — 계산이 필요 없는 유일한 지표 거래량은 지표 중에서 유일하게 계산이 없다. RSI는 가공이고 이동평균도 가공이지만, 거래량은 거래소가 주는 원자료 그 자체다. 그런데도 이 코너에서 한 편을 통째로 쓰는 이유는, 이 원자료가 가장 오해받는 지표이기 때문이다. 그리고 고백하자면 — 내 봇 461개 버전의 역사에서 가장 여러 번 넣었다 뺐다 한 게이트가 바로 거래량이다. 오늘은 그 실측의 역사를 데이터와 함께 전부 꺼내놓는다.\n먼저 개념 정리. 차트 아래 막대는 보통 \u0026lsquo;거래량(수량)\u0026lsquo;인데, 코인 시장에서 의미 있는 것은 대부분 **거래대금(수량×가격)**이다. 개당 4억 원짜리 BTC의 1개와 개당 200원짜리 코인의 1개는 같은 \u0026lsquo;1\u0026rsquo;이 아니기 때문이다. 그리고 거래량 해석에는 최소 세 가지 다른 얼굴이 있다.\nvolume — 같은 막대의 세 가지 얼굴 ① 절대 거래대금 \"이 종목은 하루에 얼마나 거래되나\" 용도: 유니버스 필터 (살 수 있는 종목인가) — 종목 간 비교 ② 상대 배수 \"지금 이 봉의 거래량은 자기 평균의 몇 배인가\" 용도: 신호 게이트 (뭔가 일어나고 있는가) — 자기 자신과 비교 ③ 시간대 프로파일 \"이 시장은 언제 붐비나\" 용도: 운용 정책 (언제 진입을 허용하나) — 시장 리듬과 비교 흔한 실수: ①의 잣대로 ②의 질문에 답하는 것. \"거래대금 50억 이상만\" 은 대형주 필터이지, 신호 감지가 아니다. 이 세 얼굴을 섞으면 게이트가 이상해진다. 내 봇이 정확히 그 실수를 했고, 그 대가를 실측으로 치렀다. 아래에서 연대기로 보인다.\n공식에서 코드로 — 다섯 줄, 함정은 셋 코드는 짧다. 함정은 짧지 않다.\npython — 상대 배수와 거래대금, 그리고 함정들 # 거래대금 (코인 시장의 기본 단위) value = df[\"volume\"] * df[\"close\"] # 또는 거래소가 주는 quote_volume # 상대 배수: 이 봉의 거래량 / 최근 N봉 평균 vol_mult = df[\"volume\"] / df[\"volume\"].rolling(20).mean() # 함정 1: 진행 중인 봉 — 15분봉이 3분 지난 시점의 거래량은 당연히 평균 미달 # 완성봉만 쓰거나, 경과 시간으로 정규화해야 한다 # 함정 2: 자기 포함 — rolling 평균에 현재 봉을 넣으면 급증 봉이 분모를 키워 # 자기 자신의 배수를 깎아 먹는다 → shift(1) 로 직전까지의 평균과 비교 # 함정 3: 세탁 거래 — 거래소·종목에 따라 거래량 자체가 오염될 수 있다. # 거래량 지표는 시장의 정직함을 전제로 한다는 것을 잊지 말 것 함정 1은 RSI 편부터 반복되는 완성봉 원칙의 거래량 버전인데, 거래량에서는 정도가 훨씬 심하다. 가격은 봉 진행 중에도 \u0026lsquo;현재가\u0026rsquo;라는 완결된 값이지만, 거래량은 봉이 닫힐 때까지 누적 중인 미완성 값이기 때문이다. 진행봉의 거래량으로 배수를 계산하면 모든 봉이 하루 종일 \u0026lsquo;거래량 부족\u0026rsquo;으로 보인다.\n내 봇의 거래량 연대기 — 두 번 죽고 한 번 부활한 게이트 이제 실측의 역사다. 아래 수치는 전부 커밋 기록에 남아 있는 실제 값이다.\nhistory — 거래량 게이트 연대기 (커밋 기록 실측값) ── 유니버스 필터 (절대 거래대금) ── VV131-KO MIN_TRADE_VALUE_24H 50억 → 10억 평가 종목 ~10 → ~40-50개 VV191 유니버스 하한 400원 / 7.5억 \"살 수 없는 종목을 스캔에서 제거\" ── 신호 게이트 (상대 배수) : 완화의 내리막 ── VV131→138 VOL_SMA_5_MULT 1.5 → 1.2 → 1.0 → 0.6 (신호 가뭄 해소 목적) VV140 0.6 → 0.5 \"통과율 31% → ~60% 목표\" ── 1차 사망 ── VV167 구조적 편향 지적 — 게이트가 종목을 거르는 게 아니라 체급을 차별 VV182 정밀분석에서 판별력 부족 판정 → 게이트 제거 ── 부활 ── VV197 10번째 게이트로 재설계 부활: 거량 \u003e= 0.5x (자기 평균 대비 배수) VV200 0.5x → 0.6x 한 칸 조임 VV201 8.9일 전수 스윕: \"거량은 현행(0.6)이 정점\" — 생존 확정 이 연대기에서 배울 것은 값이 아니라 죽고 살아난 이유다. 초기의 거래량 게이트는 \u0026lsquo;거래량 급증 = 좋은 신호\u0026rsquo;라는 돌파 전략의 문법으로 설계됐고, 신호가 마를 때마다 임계를 깎다가(1.5→0.5) 결국 판별력이 없다는 판정으로 제거됐다. 부활한 게이트는 문법이 다르다 — 눌림목 전략에서 거래량의 역할은 \u0026lsquo;급증 감지\u0026rsquo;가 아니라 죽은 종목 배제다. 자기 평균의 0.6배도 안 되는 봉에서의 반등은 반등이 아니라 호가 공백일 확률이 높으니까. 같은 데이터, 같은 이름의 게이트라도 전략이 바뀌면 존재 이유가 바뀐다.\n가장 값진 실측 — 단변량의 함정 거래량 게이트 역사에서 내가 최고로 치는 데이터는 VV184의 철회 기록이다. 스캔로그 1,116건을 전수 시뮬레이션하던 중, 거래량 게이트 재도입(≥1.0x)을 검토했고 — 단변량으로는 유일하게 성적이 좋았다.\nVV184 — 거래량 게이트 재도입 검토와 철회 (커밋 기록) 단변량 분석: 거래량 1.0~2.0x 구간 휩쏘 9.0% / 평균 +0.175% — 전 구간에서 유일한 플러스 → 여기까지만 보면 채택이 당연해 보인다 조합 분석: 다른 게이트 통과분에 적용하면 후보 46건 → 6건 (-87%) 로 급감, 성과도 무너짐 → 철회 교훈: 단변량으로 좋은 필터가 조합에서도 좋다는 보장은 없다. 다른 게이트들이 이미 같은 정보를 다른 각도로 거르고 있었다 — 거래량 조건은 '중복 검문소' 였고, 통행량만 죽였다. 지표 스터디 전체를 통틀어 이 한 장이 제일 중요한 도판일지도 모른다. 백테스트 글에서 흔히 보는 \u0026ldquo;이 필터를 넣으니 승률이 올랐다\u0026quot;는 대부분 단변량 분석이다. 그러나 봇은 필터를 하나만 돌리지 않는다 — 게이트 10개가 이미 서 있는 시스템에 11번째를 넣을 때의 질문은 \u0026ldquo;이 필터가 좋은가\u0026quot;가 아니라 \u0026ldquo;이 필터가 나머지 열 개가 못 거르는 것을 거르는가\u0026ldquo;다. 정보의 독립성이 필터의 가치다. VV201 스윕이 조합 검증을 필수 절차로 못박은 것도, VV195의 RSI 50이 거래량 게이트 도입 후 45로 이동한 것도 전부 같은 원리의 재확인이다.\n세 번째 얼굴 — 시간대 프로파일 거래량의 세 번째 얼굴은 종목이 아니라 시장의 리듬을 본다. 코인 시장은 24시간 돌지만 균질하지 않다. 한국 시장은 저녁에 붐비고 낮에 한산하며, 해외 세션이 열리고 닫힐 때 성격이 바뀐다. 이걸 처음 코드로 옮긴 것이 VV131-KO의 시간대 동적 설정이었다 — 시간대별로 평가 종목 수와 허용 스프레드, 진입 점수 문턱을 다르게 두는 구조.\ntime profile — 시간대별 운용 파라미터 (VV131-KO 커밋 기록) UTC 00-06 (KST 08-14, 한산) 보수: 평가 15 · 점수 75 · 스프레드 0.1% UTC 07-11 (KST 16-20, 저녁) 공격: 평가 40 · 점수 70 · 스프레드 0.2% 기본 균형: 평가 30 · 점수 72 · 스프레드 0.15% 이후 실측이 이 설계를 뒤집는다 — VV184, 스캔 1,116건 시간대별 성과 19시 -0.752% (승19.4%) | 20시 -0.806% (승12.1%) | 21시 -0.716% (승14.6%) 22시 -0.044% | 23시 -0.409% (승23.6%) → 19~23시 차단 채택. 잔여 평균 -0.240% → -0.182%, 부트스트랩 우세 78.8% 그러나 VV191 에서 해제 — 매수 가뭄 (하루 8건 → 1.6건). 기회 확보를 택함 이 대목이 재미있는 이유는 거래가 활발한 시간이 수익이 나는 시간은 아니었다는 것이다. 설계 단계의 직관은 \u0026ldquo;붐빌 때 기회가 많으니 공격적으로\u0026quot;였는데, 실측은 정확히 그 시간대(KST 19~23시)의 성과가 최악이라고 답했다. 거래량이 많다는 것은 참여자가 많다는 뜻이지 내 전략이 잘 통한다는 뜻이 아니다. 붐비는 시간에는 나 같은 눌림목 사냥꾼도 많아서, 좋은 자리가 남아 있지 않을 수도 있는 것이다. 그리고 이 차단마저 결국 해제됐다 — 성과 개선(+0.058%p)보다 표본 고갈(하루 8건→1.6건)의 비용이 컸기 때문이다. 통계적으로 옳은 규칙이 운용상 틀린 규칙일 수 있다는, 값비싼 교훈이다.\n파생 지표들 — OBV와 VWAP, 그리고 이름의 기원 거래량 원자료 위에 세워진 파생 지표도 잠깐 짚자. **OBV(On Balance Volume)**는 상승 봉의 거래량은 더하고 하락 봉의 거래량은 빼서 누적하는 지표다 — 매집/분산의 방향성을 보려는 시도. **VWAP(거래량 가중 평균가)**는 거래량으로 가중한 평균 체결가로, 기관의 체결 품질 기준선으로 쓰인다.\nderived — 거래량 위에 세운 두 지표 # OBV — 방향성 누적 obv = (np.sign(close.diff()) * volume).fillna(0).cumsum() 해석: 가격은 횡보하는데 OBV가 오르면 '조용한 매집' 이라는 서사 주의: 봉 하나의 방향에 거래량 전체를 귀속시키는 거친 가정 위에 서 있다 # VWAP — 거래량 가중 평균가 (앵커 시점부터 누적) vwap = (price * volume).cumsum() / volume.cumsum() # price = (H+L+C)/3 이 봇 이름의 기원이 여기 있다 — 앵커드 VWAP 을 쓰던 시기의 코드가 vvwap → VV 로 줄어 지금의 VV 모듈 시대까지 이름으로 남았다 현행 전략은 VWAP 을 진입 판정에 쓰지 않는다. 이름만 유산으로 남은 셈 VWAP은 개인적으로 각별하다. 내 봇의 이름 \u0026lsquo;VV\u0026rsquo;가 바로 이 지표에서 나왔기 때문이다(앵커드 VWAP을 쓰던 시기의 파일명 vvwap이 줄어 굳었다). 지금의 현행 전략은 VWAP을 진입 판정에 쓰지 않는다 — 지표는 떠났는데 이름은 남았다. 461개 버전을 지나온 프로젝트에는 이런 화석이 몇 개씩 있다.\n저유동성의 진짜 청구서 — 거래량 게이트의 숨은 존재 이유 거래량이 적은 종목의 비용은 \u0026lsquo;신호가 부정확하다\u0026rsquo; 수준이 아니다. 실계좌에서는 훨씬 물리적인 형태로 청구된다. 본전 청산 27건을 실측했을 때(VV206), 보호선을 +0.2%에 뒀는데 평균 실현이 +0.017%, 합계 -18,144원이었다 — 원인은 슬리피지가 아니라 호가 관통이었다. 저가·저유동성 코인은 호가 한 칸이 0.2%를 넘어(실측 예: 443원짜리 코인에서 한 칸 0.226%), 내가 그어놓은 청산선이 호가와 호가 사이 허공에 떠 있었던 것이다. 관통 손실 0.183%p에 왕복 수수료 0.10%를 더하면 손익분기만 0.283% — 이 계산이 본전 바닥을 0.4%로 올렸고(VV206), 이틀 뒤 아예 호가 한 칸/가격 비율이 0.15%를 넘는 종목은 진입 금지라는 게이트(VV208, TICK_RATIO_MAX)로 구조화됐다.\nliquidity cost — 거래량 부족이 계좌에 청구되는 경로 거래량 부족 종목의 3단 청구서 ① 스프레드 사고파는 순간마다 매수-매도 호가 간격만큼 상납 ② 호가 관통 청산선이 호가 사이 허공에 → 의도한 가격에 못 팔림 실측: 바닥 +0.2% 설계 → 평균 실현 +0.017% (VV206, 27건) ③ 체결 실패 수량이 없어 지정가 미체결 → 유령 포지션 위험 실측: 저유동성 코인 매도 미체결이 원장 불일치로 (VANA 사건) 방어 계층 (현행): 유니버스: 거래대금 하한 + 가격 하한 게이트: 거량 0.6x + TICK_RATIO 0.15% 집행: 체결 폴링 확인 회계: 원장 대조 (게이트21) 그래서 내 결론은 이렇다 — 거래량 게이트의 진짜 존재 이유는 알파(초과수익)가 아니라 비용 방어다. 좋은 종목을 찾아주는 게 아니라, 계좌를 갉아먹는 종목을 문 앞에서 돌려보내는 것. 수익 기여도로만 평가하면 거래량 게이트는 늘 애매하게 나온다(그래서 두 번 죽었다). 비용 방어까지 계산에 넣어야 이 게이트의 손익계산서가 완성된다.\n검증 계획 — 거래량을 다시 심사대에 올린다면 현행 거량 게이트(0.6x)는 VV201 스윕에서 \u0026lsquo;현행이 정점\u0026rsquo;으로 확인받았지만, 그것은 8.9일치 기록 위에서의 판정이었다. 표본이 더 쌓인 지금 다시 심사한다면 설계는 이렇게 짠다.\nvalidation design — 거래량 게이트 재심사 설계 축 후보값 비고 거량 배수 하한 0.4 / 0.5 / 0.6 / 0.8 현행 0.6 평균 기간 10 / 20 / 60봉 자기 평균의 정의 거래대금 하한 5억 / 7.5억 / 15억 현행 7.5억 (유니버스) TICK_RATIO 상한 0.10% / 0.15% / 0.20% 현행 0.15% 목적함수: 평균수익 · 승률 · 손절권 진입률 — 파레토 우세만 채택 필수 절차: 단변량 → 조합 검증 → 기간 반분 안정성 (VV184의 교훈) 추가 관측: 게이트 통과분의 '체결 품질' 도 같이 측정한다 (지정가 체결률 · 실체결가와 의도가의 괴리 · 본전 청산 실현율) 결과 칸이 비어 있는 이유: 아직 측정 전이다. 마지막 항목이 이 편의 결론과 이어진다. 거래량 게이트를 수익률로만 심사하면 또 애매하게 나올 것이다 — 이미 두 번 그렇게 죽었다. 체결 품질(지정가가 의도한 가격에 실제로 체결되는가, 본전 청산이 설계대로 실현되는가)을 같이 측정해야 이 게이트의 진짜 기여가 장부에 잡힌다. 측정 대상을 잘못 고르면 좋은 부품도 해고당한다.\n소회 계산이 없는 지표를 다루는 데 한 편이 다 갔다. 그럴 만했다고 생각한다. 거래량은 유일하게 시장의 \u0026lsquo;의견\u0026rsquo;이 아니라 \u0026lsquo;행동\u0026rsquo;을 기록한 데이터다. 가격은 단 한 주의 체결로도 움직일 수 있지만, 거래량은 누군가 실제로 지불한 총량이다. 그래서 조작이 아닌 한 거래량은 거짓말을 못 하고, 그래서 세탁 거래가 있는 시장에서는 거래량부터 의심해야 한다 — 동전의 양면이다.\n내 봇에서 거래량 게이트가 걸어온 길(완화 → 사망 → 재설계 부활 → 스윕 생존)은 이 코너가 말하려는 것의 축소판이기도 하다. 지표는 전략의 문법 안에서만 의미를 갖고, 필터의 가치는 단독 성적이 아니라 조합 기여로 측정되며, 어떤 게이트는 수익이 아니라 비용 쪽 장부에서 밥값을 한다. 세 문장 모두 실측 데이터가 가르쳐줬다.\n조셉 그랜빌은 거래량이 가격에 선행한다고 가르쳤다 — OBV라는 지표까지 만들면서. 그 명제가 보편 법칙인지는 지금도 논쟁거리지만, 적어도 이것 하나는 분명하다. 가격은 의견이고 거래량은 지불이다. 의견은 공짜지만 지불은 공짜가 아니므로, 나는 지불 쪽 데이터를 더 믿는 편이다.\n","permalink":"https://botlab.co.kr/study/volume/","summary":"가장 오래된 지표이자 가장 오해받는 지표. 절대 거래대금과 상대 배수의 차이, 내 봇에서 거래량 게이트가 두 번 죽고 한 번 부활한 실측의 역사, 그리고 저유동성의 진짜 비용까지.","title":"Volume — 가격은 의견이고, 거래량은 지불이다 | Crypto Trading Bot"},{"content":"오늘의 재료 — 500줄짜리 개념 사전 오늘 스터디의 재료는 트레이딩뷰에서 가장 유명한 지표 중 하나, LuxAlgo의 Smart Money Concepts다(CC BY-NC-SA 4.0, 원작자 © LuxAlgo). 지난 RSI 다이버전스 편은 열두 줄이었는데 이번엔 500줄이 넘는다. 라이선스를 존중해 코드 전문은 싣지 않고, 대신 그 500줄이 무엇을 계산하는지를 전부 분해한다. 봇 개발자에게 필요한 것은 코드의 복사가 아니라 판정식의 이해다.\nSMC는 단일 지표가 아니라 개념의 묶음이다. 코드의 모듈 구성을 그대로 지도로 그리면 이렇다.\nSmart Money Concepts — 모듈 지도 (코드 구조 기준) SMC ├─ 시장 구조 (Market Structure) │ ├─ 스윙 구조 50봉 스윙 기준 BOS / CHoCH │ ├─ 내부 구조 5봉 스윙 기준 BOS / CHoCH (단기) │ └─ 강/약 고저 Strong·Weak High/Low (추세 편향 라벨) ├─ 오더블록 (Order Blocks) 구조 돌파 직전의 반대 극값 봉 영역 ├─ EQH / EQL 같은 높이의 고점·저점 (유동성 풀 후보) ├─ FVG (Fair Value Gap) 3봉 사이의 미체결 가격 공백 ├─ MTF 레벨 전일/전주/전월 고저 (D/W/M) └─ 프리미엄/디스카운트 존 스윙 범위의 상단 5% / 중앙 / 하단 5% 핵심 파라미터: 스윙 50봉 · 내부 5봉 · EQ 확인 3봉 · EQ 임계 0.1×ATR(200) SMC의 서사는 이렇다 — \u0026ldquo;큰돈(스마트 머니)은 흔적을 남긴다. 구조의 파괴, 급히 떠난 자리(오더블록), 채우지 못한 공백(FVG), 유동성이 고인 곳(EQH/EQL)을 읽으면 그 흔적을 따라갈 수 있다.\u0026rdquo; 서사는 매혹적이다. 하지만 이 스터디의 관심은 서사가 아니라 판정식이다. 코드로 내려가면 SMC의 모든 개념은 고점·저점·종가의 비교문으로 환원된다. 그리고 비교문이 된 순간, 검증 가능한 대상이 된다.\n뼈대 1: 시장 구조 — BOS와 CHoCH의 실제 정의 차트 교과서에서 BOS(Break of Structure)와 CHoCH(Change of Character)는 신비롭게 설명되지만, 코드 속 정의는 간명하다. 먼저 스윙(변곡점)을 잡는다 — N봉(기본 50) 전의 고점이 최근 N봉의 최고가보다 높으면 그 봉이 스윙 고점 후보가 되는 식이다. 그다음 종가가 마지막 스윙 고점을 상향 돌파하면 구조 파괴 이벤트가 발생한다. 이때 직전 추세가 상승이었으면 BOS(추세 지속), 하락이었으면 CHoCH(추세 전환)라는 이름표가 붙는다. 같은 이벤트에 추세 맥락만 다른 이름을 붙이는 것이다.\nmarket structure — BOS 와 CHoCH 는 같은 이벤트의 두 이름이다 하락 추세 중 상승 추세 중 고점A ─────╮ 고점C ─────────── ◀ 종가 돌파 ╲ │ ╱╲ ╱ = BOS (추세 지속) ╲ │╱ ╲ ╱╲ ╱ ╲ ● ╲ ╱ ╲──╮ ╱╲ ╱ ╲╱ ╲ ╱ ╲ ╱ ╲╱ 저점B ╲ ╱ ╲╱ 종가가 고점A 상향 돌파 = CHoCH (하락→상승 전환 선언) 판정식: crossover(close, 마지막 스윙고점) → 직전 편향이 하락이면 CHoCH 스윙 확정 조건: 그 봉이 스윙인지 아는 데 N봉(기본 50봉)의 미래가 필요하다 봇 개발자가 여기서 형광펜을 칠 곳은 마지막 줄이다. 스윙 고점은 50봉이 지나야 확정된다. 차트에서 SMC 지표가 과거에 그려놓은 스윙 라벨은 전부 사후 확정된 것들이다. 15분봉 기준 50봉이면 12시간 반 — 지금 이 순간의 봉이 스윙 고점인지 아닌지는 반나절 뒤에나 알 수 있다는 뜻이다. 사람 눈에는 \u0026ldquo;구조가 깨질 때마다 지표가 정확히 표시해 줬네\u0026quot;로 보이지만, 실시간의 봇에게 주어지는 것은 훨씬 늦고 성긴 정보다. 지표를 백테스트할 때 이 확정 지연을 무시하면 미래 정보를 쓰는 백테스트가 된다 — 내가 완성봉 원칙에 집착하는 이유의 확장판이다.\n뼈대 2: 오더블록 — \u0026lsquo;급히 떠난 자리\u0026rsquo;의 수치적 정의 오더블록의 서사는 \u0026ldquo;기관이 물량을 쌓은 마지막 음봉/양봉\u0026quot;이다. 코드의 정의는 이렇다: 구조 돌파가 발생하면, 직전 스윙과 현재 사이에서 극값을 만든 봉을 찾아 그 봉의 고저 범위를 상자로 저장한다. 상승 돌파면 그 구간의 최저가 봉이 불리시 오더블록이 된다. 가격이 훗날 그 상자로 돌아오면(리테스트) 지지를 기대하고, 상자를 완전히 관통하면(mitigation) 상자를 폐기한다.\n디테일이 두 개 있는데 둘 다 배울 게 있다. 첫째, 변동성 필터. 봉의 범위가 ATR(200)의 2배를 넘는 \u0026lsquo;고변동성 봉\u0026rsquo;은 고저를 뒤집어 기록한다 — 긴 꼬리 봉이 만드는 과대한 상자를 걸러내는 장치다. 둘째, 소멸 기준의 선택지. 상자 관통을 종가로 판정할지(Close), 고저로 판정할지(High/Low)를 사용자에게 맡긴다. 같은 개념이라도 판정 소스가 다르면 상자의 수명이 달라진다.\norder block — 형성과 소멸의 생애주기 ① 형성 ② 리테스트 ③ 소멸(mitigation) ╱╲ 돌파↗ ╲ ╲ ╱ ╲ ╱ ╲ ╱ ╲ ▓▓▓▓▓▓▓▓▓▓ ╱ ◀ 돌파 전 최저가 봉 ▓▓▓▓▓▓ ← 지지 기대 ▓▓▓▓▓▓ ▓ 불리시OB ▓ ▓ ╲╱ ▓ ╲ ╲ ← 하향 관통 = 폐기 필터: 봉 범위 ≥ 2×ATR(200) 인 고변동성 봉은 고저 반전 기록 (꼬리 상자 방지) 소멸: Close 기준(보수적) vs High/Low 기준(민감) — 선택이 상자 수명을 바꾼다 구현: 돌파 시점에 구간 슬라이스 → 극값 인덱스 → 상자 저장 (최대 100개 큐) 봇의 관점에서 오더블록은 동적으로 생성·소멸되는 지지/저항 레벨의 목록이다. RSI처럼 매 봉 하나의 숫자가 나오는 지표가 아니라, 상태(상자 목록)를 관리하는 자료구조다. 이 차이가 구현 난이도를 결정한다 — 숫자 지표는 함수 하나면 되지만, 오더블록은 생성 이벤트, 보유 목록, 소멸 조건, 우선순위(가장 가까운 상자부터)까지 설계해야 한다. 지표가 아니라 작은 시스템이다.\n뼈대 3: FVG, EQH/EQL, 그리고 프리미엄/디스카운트 **FVG(Fair Value Gap)**는 3봉 패턴이다. 불리시 FVG의 판정식: 현재 봉의 저가가 두 봉 전 고가보다 높고, 직전 봉 종가도 그 고가 위에 있으면 — 두 봉 전 고가와 현재 저가 사이에 아무도 거래하지 않은 공백이 남는다. 그 공백은 미래에 메워지는 경향이 있다는 것이 서사이고, 코드는 자동 임계값(봉 변화율 누적 평균)으로 자잘한 갭을 걸러낸다. 가격이 갭을 완전히 되메우면 갭은 목록에서 삭제된다 — 오더블록과 같은 생성·소멸 구조다.\nEQH/EQL은 거의 같은 높이의 고점(저점) 쌍이다. 판정은 두 스윙 레벨의 차이가 0.1×ATR(200) 미만일 때. 서사적으로는 \u0026lsquo;유동성이 고인 곳\u0026rsquo;(그 위에 손절 주문이 쌓여 있어 큰돈이 한 번 건드리고 간다)으로 읽는다. 프리미엄/디스카운트 존은 더 단순하다 — 최근 스윙 범위의 상단 5%가 프리미엄(비싸다), 하단 5%가 디스카운트(싸다), 중앙 ±2.5%가 이퀼리브리엄. \u0026ldquo;디스카운트에서 사고 프리미엄에서 팔라\u0026quot;는 SMC의 기본 문법이 여기서 나온다.\nFVG · EQH · zones — 나머지 세 부품의 판정식 [FVG 불리시] 봉1 봉2 봉3 ▂▄ ▄█▆ ▂█▄ 고가H ──┐ 갭 ┌── 저가L 조건: L \u003e H and 봉2종가 \u003e H └─▒▒▒▒▒─┘ ▒ = 미체결 공백 (되메움 관찰 대상) [EQH] 고점A ══════ 고점B |A-B| \u003c 0.1 × ATR(200) → EQH [존 구분] 스윙 범위(top~bottom) 기준 프리미엄 top ~ 0.95·top+0.05·bottom (상단 5% — 팔 자리) 이퀼리브리엄 중앙 ±2.5% 밴드 (관망) 디스카운트 0.95·bottom+0.05·top ~ bottom (하단 5% — 살 자리) 여기까지 오면 SMC의 정체가 보인다. 신비로운 기관 추적 장치가 아니라, 고전 기술적 분석의 리네이밍에 가깝다. 구조 파괴는 돌파, 오더블록은 수요/공급 존, FVG는 갭, EQH는 이중천장, 디스카운트는 눌림목이다. 이름이 바뀌면서 정밀해진 것은 사실이다 — 판정식이 명시되고 생성·소멸 규칙이 생겼다. 그것이 이 지표의 진짜 기여라고 생각한다. 서사는 마케팅이고, 판정식은 자산이다.\n개발자의 눈: 그림 그리는 지표를 판정하는 봇으로 이 지표를 봇에 넣으려면 세 가지 번역 작업이 필요하다.\n첫째, 그리기와 판정의 분리. 이 코드의 절반 이상은 선·상자·라벨을 그리는 코드다. 봇에는 화면이 없다 — 필요한 것은 \u0026ldquo;지금 이 순간 불리시 CHoCH가 발생했는가\u0026rdquo;, \u0026ldquo;현재가 아래 가장 가까운 미소멸 불리시 오더블록은 어디인가\u0026rdquo; 같은 질의 가능한 상태다. 번역하면 이벤트 스트림과 상태 저장소가 된다. 둘째, 확정 지연의 명시. 스윙 구조는 50봉, 내부 구조는 5봉 뒤에 확정된다. 실시간 봇은 확정 전 정보를 쓸 수 없으므로, 모든 이벤트에 \u0026lsquo;발생 봉\u0026rsquo;과 \u0026lsquo;확정 봉\u0026rsquo;을 따로 기록해야 백테스트가 정직해진다. 셋째, 상위 타임프레임 데이터의 시점 정렬. 원본 코드는 상위 TF 조회에 lookahead 옵션을 쓴다 — 차트 표시용으로는 관행이지만, 봇이 같은 방식으로 과거를 재생하면 미래 참조가 된다. HTF 봉은 그 봉이 닫힌 시점 이후에만 쓸 수 있다.\ntranslation — SMC 를 봇의 상태기계로 옮기면 # 화면의 그림 → 질의 가능한 상태 state.trend = BULLISH | BEARISH (스윙/내부 각각) state.last_event = BOS | CHoCH (+발생가, 발생봉, 확정봉) state.order_blocks = [(고가, 저가, 방향, 생성봉), ...] 미소멸 목록 state.fvgs = [(상단, 하단, 방향), ...] 되메움 전 목록 state.zone = PREMIUM | EQUILIBRIUM | DISCOUNT 진입 후보 (불리시 시나리오 예시): ① 내부 CHoCH 상승 전환 AND ② 현재가가 디스카운트 존 AND ③ 가장 가까운 불리시 OB 상단 리테스트 (거리 ≤ k×ATR) 금지: 스윙 확정 전 값 사용 / HTF lookahead / 그리기 로직의 이식 파라미터 후보: 스윙 N ∈ {20,50} 내부 n ∈ {3,5,8} 존 경계 ∈ {5%,10%} k ∈ {0.5,1.0} 내 봇과의 접점 — 그리고 검증 계획 번역을 끝내고 나면 흥미로운 사실이 드러난다. 내 봇은 SMC라는 단어를 한 번도 쓴 적이 없지만, 이미 SMC의 절반을 다른 이름으로 운용하고 있다. 과매도 눌림목 진입은 \u0026lsquo;디스카운트 존에서 산다\u0026rsquo;와 같은 문장이고, 저점근접 게이트(24시간 저점 대비 3.0, VV201 스윕 확정값)는 디스카운트 판정의 내 버전이다. BTC 4시간봉 하락장 레짐 차단(VV202)은 \u0026lsquo;상위 TF 구조가 베어리시면 롱 금지\u0026rsquo;와 같은 문장이다. 진입 기록에 레짐을 저장하기 시작한 것(VV164)은 SMC식으로 말하면 \u0026lsquo;구조 맥락의 스탬핑\u0026rsquo;이다. 개념은 수렴한다 — 살아남는 전략들이 비슷한 곳에 도달하는 것인지, 아니면 같은 상식의 다른 표기법인지는 열린 질문으로 남긴다.\n그래서 이 지표에서 내 봇으로 가져와 검증할 가치가 있는 후보는 명확하다. 첫째, 내부 CHoCH를 반등 확인 게이트의 대체재로 — 현행 반등확인(저점 대비 반등률)과 5봉 내부 구조 전환 중 어느 쪽이 손절권 진입률을 더 낮추는가. 둘째, 오더블록 리테스트를 진입가 개선에 — 지정가를 현재가가 아니라 가장 가까운 불리시 OB 상단에 거는 변형. 셋째, EQL 근접을 감점 요인으로 — 같은 저점을 두 번 찍은 자리는 유동성 사냥의 표적이라는 가설의 실측. 검증 방법은 늘 그렇듯 하나다: 매매기록 위에서 전수 재현하고, 수익·승률·손절권이 동시에 개선될 때만(파레토 우세) 채택한다. 결과 숫자는 측정 후에만 적는다 — 이 문장은 이제 이 코너의 서명이다.\n소회 500줄을 해부하고 남은 감상은 의외로 담백하다. SMC는 \u0026lsquo;스마트 머니\u0026rsquo;를 추적하지 않는다. 고점과 저점과 종가를 비교한다. 그런데 그게 실망스럽지 않다 — 오히려 그래서 쓸 만하다. 추적 불가능한 서사였다면 검증도 불가능했을 텐데, 비교문의 묶음이라면 내 매매기록 위에서 시험할 수 있다. 지표의 가치는 서사의 화려함이 아니라 판정식의 명료함에 있다.\n그리고 이름에 대하여. \u0026lsquo;기관의 발자국\u0026rsquo;, \u0026lsquo;유동성 사냥\u0026rsquo;, \u0026lsquo;공정 가치\u0026rsquo; — SMC의 용어들은 시장에 의도를 가진 주인공이 있다는 인상을 준다. 나는 461개 버전을 만들며 반대 방향으로 걸어왔다. 시장에 주인공이 있는지 나는 모른다. 내가 아는 것은 내 계좌의 체결 기록과, 그 위에서 측정된 조건부 기대값뿐이다. SMC의 판정식 중 몇 개가 그 기대값을 개선해 준다면 이름이야 뭐라 부르든 게이트에 들어올 것이고, 못 하면 아름다운 서사인 채로 남을 것이다.\n리처드 와이코프는 백 년 전에 시장을 \u0026lsquo;컴포지트 맨\u0026rsquo; — 모든 큰손을 합친 가상의 단일 행위자 — 이 움직이는 것처럼 읽으라고 가르쳤다. SMC는 그 오래된 렌즈의 현대판이다. 렌즈는 세상을 보는 도구이지 세상 그 자체가 아니다. 렌즈를 믿기 전에, 렌즈로 본 것을 측정하라.\n참고: 이 글이 해부한 지표는 LuxAlgo의 Smart Money Concepts (CC BY-NC-SA 4.0)이다. 코드 전문은 라이선스와 지면을 존중해 싣지 않았으며, 본문의 판정식 설명은 공개 소스를 읽고 내 언어로 재서술한 것이다.\n","permalink":"https://botlab.co.kr/study/smc/","summary":"LuxAlgo의 Smart Money Concepts 지표를 해부한다. 차트에 그림을 그리는 지표를, 판정을 내리는 봇으로 번역할 때 무엇이 남고 무엇이 부서지는가 — 시장 구조, 오더블록, FVG, 프리미엄/디스카운트까지.","title":"SMC (Smart Money Concepts) — BOS·CHoCH·오더블록을 봇의 언어로 번역한다 | Crypto Trading Bot"},{"content":"오늘의 재료 — 파인스크립트 한 장 오늘 스터디의 출발점은 트레이딩뷰 지표 하나다. 이름은 RSI Divergence. 코드부터 그대로 올려놓고 시작한다.\nTradingView — RSI Divergence (Pine Script 원문) study(title=\"RSI Divergence\", shorttitle=\"RSI Divergence\") src_fast = close, len_fast = input(5, minval=1, title=\"Length Fast RSI\") src_slow = close, len_slow = input(14, minval=1, title=\"Length Slow RSI\") up_fast = rma(max(change(src_fast), 0), len_fast) down_fast = rma(-min(change(src_fast), 0), len_fast) rsi_fast = down_fast == 0 ? 100 : up_fast == 0 ? 0 : 100 - (100 / (1 + up_fast / down_fast)) up_slow = rma(max(change(src_slow), 0), len_slow) down_slow = rma(-min(change(src_slow), 0), len_slow) rsi_slow = down_slow == 0 ? 100 : up_slow == 0 ? 0 : 100 - (100 / (1 + up_slow / down_slow)) divergence = rsi_fast - rsi_slow plotdiv = plot(divergence, color = divergence \u003e 0 ? lime : red, linewidth = 2) band = hline(0) 구조는 간결하다. 기간 5짜리 빠른 RSI와 기간 14짜리 느린 RSI를 각각 와일더 방식(rma)으로 구하고, 빠른 것에서 느린 것을 뺀 차이를 0선 위아래의 히스토그램으로 그린다. 0 위면 라임색, 아래면 빨강. 이름은 다이버전스지만, 교과서에서 말하는 \u0026lsquo;가격 고점과 지표 고점의 엇갈림\u0026rsquo;을 자동 탐지하는 물건은 아니다 — 두 RSI의 스프레드, 정확히는 모멘텀의 가속도를 그리는 오실레이터다. 이 구분이 중요하다. 지표를 봇에 넣으려면 이름이 아니라 수식이 하는 일을 정확히 알아야 한다.\n지표의 뼈대: 빠른 놈에서 느린 놈을 뺀다 RSI(14)는 최근 14봉의 방향성 요약이고, RSI(5)는 최근 5봉의 방향성 요약이다. 둘의 차이가 양수라는 것은 단기 모멘텀이 중기 모멘텀보다 강하다는 뜻이다. 하락하던 종목이 바닥을 다지고 돌기 시작하면, 느린 RSI가 아직 바닥에 누워 있는 동안 빠른 RSI가 먼저 고개를 든다 — 그 순간 이 오실레이터가 0선을 뚫고 올라온다. 반대로 상승이 지치면 빠른 쪽이 먼저 꺾여 0선 아래로 내려간다.\ndivergence = rsi(5) - rsi(14) — 반등 초입의 형태 값 해석 +6 │ ██ 단기가 중기를 크게 추월 +4 │ ████ (반등 가속 구간) +2 │ ██████ 0 ┼────────▁▁▁▁────────────── ◀ 진입 후보: 0선 상향 돌파 -2 │ ████ -4 │ ██████ 단기가 중기보다 약함 -6 │ ████████ (하락 진행 구간) └──── 과거 ────────────── 현재 ────▶ 핵심: 느린 RSI 가 바닥에 있는 동안 빠른 RSI 가 먼저 돈다 이 형태를 보고 바로 알았다. 이것은 내 봇의 철학과 같은 동네에 사는 지표다. 내 봇의 현행 전략은 과매도 눌림목 반등 — 빠진 것을 사되, 빠지는 중이 아니라 돌기 시작한 것을 사는 전략이다. \u0026lsquo;돌기 시작했다\u0026rsquo;를 어떻게 수치로 정의하느냐가 늘 숙제인데, 이 오실레이터의 0선 상향 돌파가 정확히 그 정의 후보가 된다. 느린 RSI가 과매도의 \u0026lsquo;맥락\u0026rsquo;을 제공하고, 빠른 RSI와의 스프레드가 반등의 \u0026lsquo;타이밍\u0026rsquo;을 제공하는 이중 구조다.\n공식에서 코드로 — 파이썬 이식 봇은 파이썬으로 돈다. 그러니 첫 공정은 이식이다. 여기서 초보 시절의 나를 여러 번 넘어뜨린 함정이 나온다 — rma는 그냥 이동평균이 아니다. 와일더의 rma는 지수평활의 일종으로, pandas로는 ewm(alpha=1/len)이 맞다. rolling(len).mean()으로 이식하면 트레이딩뷰와 값이 몇 포인트씩 어긋나고, 그 차이는 0선 근처에서 신호의 유무를 갈라 버린다.\npython — pine 의 rma 를 그대로 재현하는 이식 # rma(x, n) == Wilder smoothing == EMA(alpha=1/n), TV는 첫 값을 SMA로 시드 def rma(s, n): return s.ewm(alpha=1/n, min_periods=n, adjust=False).mean() # change(close) == close.diff() def rsi_wilder(close, n): d = close.diff() up = rma(d.clip(lower=0), n) down = rma(-d.clip(upper=0), n) return 100 - 100 / (1 + up / down) # down==0 이면 inf→100 으로 수렴 rsi_fast = rsi_wilder(close, 5) rsi_slow = rsi_wilder(close, 14) divergence = rsi_fast - rsi_slow # 주의 1: 마지막 봉이 진행 중이면 계산에서 제외한다 (완성봉 원칙) # 주의 2: 워밍업 — 최소 len_slow*5 봉을 채우기 전의 값은 버린다 주석의 두 가지 주의사항은 장식이 아니라 흉터다. 진행 중인 봉을 계산에 넣으면 오실레이터가 초 단위로 0선을 들락거리고, 봇은 같은 자리에서 사고팔기를 반복한다 — 내 봇이 완성봉만 쓰는 이유는 이 경험 때문이다. 워밍업도 마찬가지다. 지수평활은 초기 구간에서 시드의 영향이 남아 있어서, 데이터 로딩 직후의 값으로 판정하면 재시작할 때마다 신호가 달라지는 봇이 된다. 재현되지 않는 신호는 검증할 수 없고, 검증할 수 없는 신호는 운용할 수 없다.\n신호 설계: 오실레이터를 매매 규칙으로 바꾸기 지표가 이식됐다고 신호가 생긴 것은 아니다. \u0026ldquo;0선을 상향 돌파하면 산다\u0026quot;는 문장은 그럴듯해 보이지만, 그대로 코드에 넣으면 첫날부터 문제를 만난다 — 0선 근처의 톱니다. 스프레드가 0 언저리에서 미세하게 진동하면 돌파가 하루에도 수십 번 찍힌다. 그래서 설계 단계에서 최소한 세 가지를 결정해야 한다.\n첫째, 히스테리시스. 0선 하나가 아니라 진입선과 이탈선을 분리한다. 예컨대 \u0026ldquo;-2 아래를 찍은 뒤 +2를 상향 돌파할 때만 트리거\u0026quot;로 만들면, 한 번 발동한 신호가 다시 무장되려면 오실레이터가 충분히 내려갔다 와야 한다. 둘째, 맥락 조건. 스프레드의 0선 돌파는 상승 중간의 잔출렁임에서도 발생한다. 내가 원하는 것은 \u0026lsquo;과매도에서의\u0026rsquo; 반등이므로, 느린 RSI가 낮은 상태라는 조건을 함께 건다. 셋째, 확인 봉 수. 돌파 봉 즉시 진입할지, 한 봉 유지를 확인하고 진입할지 — 빠르면 휩쏘를 먹고, 늦으면 이익의 앞부분을 버린다.\nsignal design — 문장을 판정식으로 바꾸는 과정 # \"반등 초입을 산다\" 를 3개의 결정으로 분해 [1] 트리거 divergence 가 arm선(-B) 아래 무장 후 fire선(+B) 상향 돌파 [2] 맥락 rsi_slow \u003c= OS (과매도 국면에서만 트리거 유효) [3] 확인 돌파 후 confirm_bars 봉 유지 시 진입 (0 = 즉시) 파라미터 후보: B ∈ {0, 1, 2, 3} OS ∈ {35, 40, 45, 50} confirm ∈ {0, 1} # 값을 지금 정하지 않는 이유 — 아래 '검증 계획' 참조. 참고: 내 봇의 과매도 기준 RSI 45 는 8.9일 매매기록 전수 스윕의 결과값이다 (VV201) 교과서의 30 이 아니라 45 가 된 과정은 지난 RSI 편에서 다뤘다 눈여겨볼 것은 파라미터 후보에 값을 아직 안 정했다는 점이다. B=2가 좋아 보이고 OS=45가 익숙하지만, \u0026lsquo;좋아 보인다\u0026rsquo;는 이 스터디에서 금지어다. 후보군만 설계하고, 채택은 측정에 맡긴다. 지표 설계자의 일은 정답을 찍는 게 아니라 실험이 가능한 형태로 질문을 좁히는 것이다.\n봇의 골격: 지표는 부품이고, 봇은 공정이다 여기까지가 지표 이야기였다면, 지금부터가 봇 이야기다. 461개 버전을 만들며 배운 가장 비싼 교훈을 한 줄로 줄이면 이렇다 — 지표는 봇의 10%다. 나머지 90%는 데이터의 무결성, 집행의 정확성, 그리고 잃지 않는 구조다. RSI 다이버전스가 아무리 좋은 신호를 줘도, 체결 확인을 안 하는 봇은 유령 포지션을 만들고(VANA 사건), 원장 대조를 안 하는 봇은 자기 손익을 모른 채 달린다.\nbot pipeline — 지표에서 계좌까지, 한 신호의 일생 ① 수집 거래소 캔들/호가 수신 (웹소켓 + REST 폴백, 재연결 백오프) ② 확정 완성봉만 확정 큐로 — 진행봉은 지표 계산 금지 ③ 지표 rsi_fast / rsi_slow / divergence 갱신 (워밍업 구간 폐기) ④ 게이트 트리거 + 맥락 + 유동성/레짐 게이트 일괄 판정 ◀ 지표는 여기 한 칸 ⑤ 사이징 슬롯 예산 = 가용 현금의 고정 비중 (몰빵 구조적 차단) ⑥ 집행 지정가 → 체결 폴링으로 확인 → 미체결 처리 명시 ⑦ 청산 손절 / 본전 사수(SAFE) / 포트폴리오 익절 — 진입과 독립된 엔진 ⑧ 대조 거래소 원장과 손익·잔고 대조, 불일치 시 배포/운용 중단 지표 교체 = ③④ 만 갈아끼운다. 나머지 공정은 지표와 무관하게 재사용. 이 구조의 요점은 ③④만 오늘의 지표로 갈아끼우면 나머지가 전부 재사용된다는 것이다. 내 봇이 돌파 전략에서 눌림목 전략으로 통째로 전환(VV168)하는 데 하루면 충분했던 이유가 이 분리에 있다. 지표를 공부할 때 봇 전체를 새로 짜야 한다면 그것은 지표의 문제가 아니라 아키텍처의 문제다.\n그리고 ④의 게이트. 오실레이터 트리거 하나로 진입을 결정하는 봇은 오래 못 간다. 내 봇이 게이트를 10개 두는 이유는 지표마다 못 보는 사각이 다르기 때문이다. RSI 스프레드는 가격의 모멘텀만 본다 — 그 종목이 거래대금이 말라 있는지, 호가 한 칸이 가격의 몇 퍼센트인지, 시장 전체가 하락 레짐인지는 모른다. 실측으로 확인한 예를 들면, 저가 코인은 호가 한 칸이 0.2%를 넘어 청산선이 호가 사이에 끼는 문제가 있었고(본전 청산 27건 실측, VV206), 그래서 호가단위/가격 비율 게이트(VV208)가 생겼다. 오늘의 오실레이터를 실전에 올린다면 최소한 유동성 게이트와 BTC 4시간봉 레짐 차단은 같이 서야 한다 — 과매도 반등 전략의 최대 리스크는 \u0026lsquo;하락 추세의 중간을 바닥으로 오인하는 것\u0026rsquo;이고, 그것은 종목 레벨 지표로는 안 걸러진다.\n검증 계획: 숫자는 측정한 다음에 적는다 마지막 공정이자, 이 블로그가 존재하는 이유다. 위에서 설계한 파라미터(B, OS, confirm, len_fast, len_slow)는 전부 후보 상태다. 이걸 채우는 절차는 내 봇의 현행 규율 그대로 간다.\nvalidation plan — VV201 방법론을 그대로 적용한 스윕 설계 축 후보값 채택 기준 len_fast 3 / 5 / 7 ┐ len_slow 10 / 14 / 21 │ ① 축별 전수 스윕 밴드 B 0 / 1 / 2 / 3 │ ② 상위 조합 재검증 과매도 OS 35 / 40 / 45 / 50 │ ③ 기간 반분 안정성 체크 confirm 0 / 1 ┘ 목적함수: 평균수익 · 승률 · 손절권 진입률 — 셋 다 개선(파레토 우세)일 때만 채택 한 축이 좋아도 손절권이 나빠지면 기각 (예: VV201 에서 낙폭 -2.5 기각 사유) 이 표의 결과 칸이 비어 있는 이유: 아직 측정 전이기 때문이다. 측정 전의 숫자를 적는 순간, 이 스터디는 광고가 된다. 스윕에서 강조하고 싶은 것은 목적함수다. 수익률 한 줄만 보고 고르면 반드시 과적합된 값이 뽑힌다. 내 봇의 VV201 스윕이 파레토 우세 — 수익, 승률, 손절권 진입률이 동시에 개선되는 값만 채택 — 를 조건으로 삼은 이유이고, 기간을 반으로 갈라 앞뒤 양쪽에서 성립하는지 본 이유다. 그리고 스윕으로 뽑은 값에도 유효기간이 있다. 같은 봇에서 RSI 문턱이 50으로 측정됐다가(VV195) 거래량 게이트 하나가 추가된 뒤 45로 재측정된(VV201) 전례가 있다 — 게이트 조합이 바뀌면 개별 지표의 최적점도 움직인다. 오늘의 오실레이터를 어느 봇에 얹느냐에 따라 B와 OS의 정답이 달라진다는 뜻이다. 그래서 남의 백테스트 결과는, 내 것이 아니다.\n소회 파인스크립트 열두 줄짜리 지표를 받아서 여기까지 왔다. 수식 해석, 이식의 함정, 신호 설계의 3분해, 공정 분리, 게이트, 그리고 스윕 계획 — 지표 하나를 봇으로 만드는 데 필요한 공정 전부다. 돌아보면 이 공정표 자체가 461개 버전의 수업료로 산 물건이다. 예전의 나는 ③에서 곧장 ⑥으로 건너뛰었고, 그 지름길의 값을 계좌로 치렀다.\n빠른 RSI에서 느린 RSI를 뺀다는 아이디어 자체는 아름답다. 과매도의 맥락과 반등의 타이밍을 지표 하나가 같이 들고 있다. 하지만 이 지표가 내 봇의 열한 번째 게이트가 될지는 지금 알 수 없고, 알 수 없다고 말하는 것이 이 스터디의 규칙이다. 다음 단계는 정해져 있다 — 매매기록 위에서 전수 스윕을 돌리고, 파레토 우세가 나오면 채택하고, 안 나오면 이 글이 그 지표의 부검 보고서가 될 것이다. 어느 쪽이든 기록은 남는다.\n짐 사이먼스의 르네상스가 지킨 철칙은 \u0026lsquo;모델이 시키는 대로 하라\u0026rsquo;였다. 그 말의 숨은 전제는, 시키는 대로 해도 될 만큼 모델을 검증해 뒀다는 것이다. 지표를 믿는 것이 아니라, 지표를 검증한 절차를 믿는 것 — 열두 줄의 파인스크립트와 한 대의 봇 사이에 놓인 거리가 정확히 그만큼이다.\n","permalink":"https://botlab.co.kr/study/rsi-divergence/","summary":"빠른 RSI(5)에서 느린 RSI(14)를 뺀 오실레이터. 트레이딩뷰 지표 한 장을 받아서, 신호 설계 → 파이썬 이식 → 게이트 → 집행 → 검증까지 — 봇 한 대가 되기까지의 전 공정을 기록한다.","title":"RSI Divergence Oscillator — 파인스크립트 한 장으로 봇 한 대를 조립한다 | Crypto Trading Bot"},{"content":"지표의 뼈대 RSI(상대강도지수)는 1978년 웰레스 와일더가 만든 모멘텀 지표다. 최근 N봉(보통 14봉) 동안 오른 날의 평균 상승폭과 내린 날의 평균 하락폭의 비율을 0~100 사이 숫자 하나로 압축한다. 요지는 단순하다 — 최근 이 가격이 얼마나 일방적으로 움직였나.\nRSI — 공식에서 코드까지 # RS = 평균 상승폭 / 평균 하락폭 → RSI = 100 - 100/(1+RS) delta = close.diff() up = delta.clip(lower=0).rolling(14).mean() down = (-delta.clip(upper=0)).rolling(14).mean() rsi = 100 - 100 / (1 + up / down) -------------------------------------------------- 교과서: 70 이상 과매수 · 30 이하 과매도 내 봇 : RSI 45 이하 진입 (8.9일 매매기록 전수 스윕 최적값) 이 수식이 실제로 무엇을 재고 있나 공식을 외우는 것보다 중요한 건 이 숫자가 무엇의 요약인지 아는 것이다. RSI를 분해하면 결국 최근 N봉의 상승 에너지가 전체 에너지에서 차지하는 비율이다.\nRS = 평균 상승폭 / 평균 하락폭이라는 정의를 RSI 공식에 대입해서 정리하면 이렇게 된다.\nRSI 를 다르게 쓰면 RSI = 100 − 100/(1 + up/down) = 100 × (up/down) / (1 + up/down) = 100 × up / (up + down) 즉 RSI 는 '상승분 / (상승분 + 하락분)' 의 백분율이다 N봉 전부 양봉 → down = 0 → RSI = 100 N봉 전부 음봉 → up = 0 → RSI = 0 상승분과 하락분이 같음 → RSI = 50 여기서 나오는 성질 — RSI 50 은 '중립' 이 아니라 '균형' 이다 최근 구간에서 오른 만큼 내렸다는 뜻일 뿐, 가격이 시작점으로 돌아왔다는 뜻이 아니다 마지막 줄이 흔한 오해를 정리해준다. RSI 50을 \u0026ldquo;가격이 제자리\u0026quot;로 읽는 사람이 많은데, 실제로는 상승 폭의 합과 하락 폭의 합이 비슷하다는 뜻이다. 상승이 크고 드물게, 하락이 작고 자주 일어났다면 RSI가 50이어도 가격은 크게 올라 있을 수 있다.\n이 성질을 알고 나면 RSI를 쓰는 방식이 조금 달라진다. 이 지표는 가격의 위치가 아니라 에너지의 배분을 재는 도구다. 위치를 알고 싶으면 다른 지표를 써야 한다.\n평균 방식이라는 첫 번째 함정 코드 다섯 줄에 함정이 둘 있다고 했는데, 첫 번째가 평균 방식이다. 위에 적은 코드는 rolling(14).mean() — 단순이동평균을 썼다. 그런데 와일더의 원본은 지수평활을 쓴다.\n같은 RSI(14), 다른 값 단순평균 방식 (SMA) up = delta.clip(lower=0).rolling(14).mean() 성질: 14봉 전 값이 창에서 빠지는 순간 값이 툭 튄다 (드롭오프) 와일더 방식 (RMA · 정본) up = delta.clip(lower=0).ewm(alpha=1/14, adjust=False).mean() 성질: 과거가 지수적으로 감쇠. 값이 부드럽다 둘의 차이 같은 차트, 같은 기간인데 값이 몇 포인트씩 어긋난다 문턱 근처(45 언저리)에서는 그 몇 포인트가 통과/탈락을 가른다 거래소 앱 RSI 와 내 봇 RSI 가 다르다면 십중팔구 이것이다 트레이딩뷰·대부분의 차트 플랫폼은 와일더 방식을 쓴다 이게 왜 중요하냐면, 백테스트와 실전이 갈리는 조용한 경로이기 때문이다. 백테스트를 단순평균으로 돌리고 실전 판정은 거래소 앱을 보며 감으로 확인하면, 두 세계가 미세하게 다른 지표를 쓰고 있는 것이다. 어긋남이 크지 않아 눈치채기도 어렵다.\n지표를 이식할 때 원본 플랫폼과 값을 맞춰보는 절차를 반드시 넣어야 하는 이유다. 같은 종목, 같은 시각의 값을 뽑아 소수점까지 대조해보면 5분이면 끝나는 확인인데, 이걸 건너뛰면 나중에 원인을 못 찾는다.\n진행 중인 봉이라는 두 번째 함정 두 번째 함정은 완성봉 문제다. 15분봉을 쓴다고 하면, 지금 만들어지는 중인 봉의 종가는 계속 바뀐다. 그 값으로 RSI를 계산하면 RSI도 초 단위로 움직인다.\n진행봉을 넣으면 벌어지는 일 15분봉이 진행되는 동안의 RSI (진행봉 포함 계산) 09:00:10 RSI 45.8 ← 게이트 통과 (45 이하 조건이라면 아직 미달) 09:03:22 RSI 44.9 ← 통과! 매수 신호 09:07:41 RSI 45.3 ← 다시 미달 09:11:05 RSI 44.7 ← 또 통과 09:14:59 RSI 46.1 ← 봉 마감 시점엔 결국 미달이었다 봇의 입장에서 이건 재앙이다 · 같은 자리에서 사고팔기를 반복한다 · 백테스트로 재현이 불가능하다 (봉 안의 경로는 캔들에 안 남는다) · 봇을 재시작하면 같은 시점에 다른 판단을 한다 해법: 완성된 봉만 확정 큐에 넣고, 지표는 확정 봉으로만 계산한다 대가: 최대 한 봉만큼 늦어진다 — 15분봉이면 최대 15분 그 지연을 받아들이는 대신 재현 가능성을 얻는 거래다 \u0026ldquo;재현되지 않는 신호는 검증할 수 없고, 검증할 수 없는 신호는 운용할 수 없다.\u0026rdquo; 이 원칙은 내 봇에서 여러 번 값을 치르고 얻은 것이다. 지연을 감수하더라도 같은 입력에 같은 판단이 나오는 쪽을 골라야 한다.\n워밍업 — 세 번째 함정 함정이 하나 더 있다. 지수평활을 쓰면 초기 구간의 값이 시드에 오염된다.\nRSI(14)를 계산할 때 처음 14봉으로는 값을 못 만들고, 그 이후에도 한동안은 초기값의 영향이 남는다. 지수평활의 특성상 영향이 완전히 사라지지는 않고 지수적으로 줄어들 뿐이다. 실무적으로는 기간의 5배 정도를 워밍업으로 잡는 게 안전하다. RSI(14)면 최소 70봉, 여유 있게 100봉을 채운 뒤부터 값을 신뢰한다.\n이게 왜 문제가 되냐면, 봇을 재시작할 때마다 캔들을 새로 불러오기 때문이다. 100봉을 불러왔는데 앞의 70봉을 버려야 한다면 실제로 쓸 수 있는 건 30봉뿐이다. 데이터 로딩 개수를 지표 기간과 함께 설계하지 않으면, 재시작 직후의 봇이 미묘하게 다른 판단을 한다.\n워밍업 설계 필요 봉 수 = 지표 최대 기간 × 5 + 실제 판정에 쓸 봉 수 예: RSI(14) · 볼린저(20) · 중기추세(14일) 를 동시에 쓴다면 가장 긴 기간을 기준으로 잡는다 흔한 실수: 지표가 값을 뱉으니까 맞다고 생각하는 것 pandas 는 워밍업이 부족해도 값을 준다. NaN 이 아니라 '틀린 값' 을 준다 점검법: 500봉으로 계산한 RSI 와 100봉으로 계산한 RSI 를 같은 시점에서 비교한다. 차이가 0.1 이내로 수렴하는 지점이 안전선 문턱은 왜 30이 아니라 45가 됐나 이제 이 글의 제목이 된 이야기다. 교과서의 30/70은 와일더가 1970년대 상품 선물 시장에서 잡은 경험값이다. 자연법칙이 아니라 그 시장, 그 시대의 관찰이다.\n코인 시장은 다르다. 24시간 돌고, 변동성이 크고, 참여자 구성도 다르다. 실제로 RSI 30 이하는 생각보다 드물게 온다. 기다리다 장이 끝나는 것이다.\n내 봇은 과매도 눌림목 반등 전략을 쓴다. 빠진 것을 사되, 빠지는 중이 아니라 돌기 시작한 것을 사는 전략이다. 그러려면 \u0026ldquo;충분히 빠졌다\u0026quot;의 기준이 필요한데, 그 기준을 교과서에서 빌려오지 않고 내 매매기록에서 뽑기로 했다.\nRSI 문턱의 이동 — 30 → 40 → 50 → 45 ① 교과서 30 근거: 와일더의 경험값. 내 시장에서 검증된 적 없음 문제: 신호가 거의 안 나온다 (진입 가뭄) ② 40 으로 완화 근거: \"이틀째 매수가 없다\" 는 조급함. 데이터 아님 같이 푼 것: 낙폭 상한 −5% → −3% ③ 50 으로 재조정 근거: 게이트 전수조사. 이때는 거래량 게이트가 없던 시점 ④ 45 로 확정 (현행) 근거: 8.9일 매매기록 전수 스윕 · 11개 게이트 축 전부 결과: RSI 50→45 단독 +0.415% / 승률 55% / 손절권 18% 저점근접 4→3 단독 +0.433% / 54% / 20% 두 값 조합 +0.456% / 56% / 손절권 18% (신호 −10%, 37건/일) ③에서 ④로 가는 대목이 이 이야기의 핵심이다. 두 측정 모두 정직하게 수행됐는데 결론이 달랐다. 왜냐면 측정 시점의 게이트 조합이 달랐기 때문이다.\n50을 채택했을 때는 거래량 게이트가 없었다. 그 뒤 거래량 조건이 들어오면서, RSI 45~50 사이에 남아 있던 후보들의 성격이 바뀌었다. 그 구간의 평균 성과가 마이너스로 돌아선 것이다. 그래서 45가 됐다.\n이 사례가 알려주는 원칙은 이렇다. 파라미터에는 유효기간과 전제조건이 있다. 값만 적어두면 안 되고 \u0026ldquo;어떤 조합에서 측정된 값인지\u0026quot;를 같이 적어야 한다. 실험 노트의 규율이 코드에도 필요하다는 뜻이다.\n채택 기준 — 왜 파레토 우세인가 스윕에서 값을 고를 때 수익률만 보면 안 된다. 이건 여러 번 겪은 뒤에 세운 규칙이다.\n기각된 후보들과 그 이유 채택 기준: 평균수익 · 승률 · 손절권 진입률 — 셋 다 개선일 때만 ✓ RSI 45 세 축 동시 개선 → 채택 ✓ 저점근접 3.0 세 축 동시 개선 → 채택 ✗ 낙폭 −2.5 먼 구간 수익은 최고였으나 손절권 33% → 기각 수익만 보고 골랐다면 채택했을 값이다 ✗ ATR 0.3 대형 종목이 통째로 차단됨 → 기각 ✗ 밴드 0.6~0.7 단독 개선분이 조합 후 소멸 → 기각 현행 유지로 확인된 축: 거량 · 중기추세 · 반등 · ROC 절차: 축별 전수 스윕 → 상위 조합 재검증 → 기간 반분 안정성 셋을 다 통과해야 배포 후보가 된다 낙폭 −2.5의 사례가 특히 교훈적이다. 단일 지표로 보면 성적이 제일 좋았는데 손절권 진입률이 33%였다. 열 번 중 세 번은 손절 구간까지 밀린다는 뜻이다. 평균이 좋아도 그 경로가 험하면 실제 운용에서는 견디기 어렵다.\n목적함수를 하나로 두면 반드시 어딘가에서 대가를 치른다. 수익만 보면 변동성이 커지고, 승률만 보면 작은 이익을 자주 확정하다 큰 손실 한 방에 무너진다. 세 축을 함께 보는 건 그 대가를 눈에 보이게 만드는 장치다.\nRSI를 단독으로 쓰지 않는 이유 문턱을 45로 낮췄다는 건 조건이 느슨해졌다는 뜻이다. 그런데도 성과가 개선된 건, 다른 게이트가 나머지를 걸러주기 때문이다.\nRSI 가 못 보는 것들 RSI 가 보는 것: 최근 N봉의 상승/하락 에너지 배분 RSI 가 못 보는 것 · 이 종목이 거래되는 종목인가 → 거래대금 필터 · 호가 한 칸이 얼마나 넓은가 → 호가단위 게이트 (0.15% 상한) · 지금 저점 근처인가 → 저점근접 게이트 (3.0) · 최근 봉에 거래가 있었나 → 거량 게이트 (0.6x) · 시장 전체가 무너지는 중인가 → BTC 4시간봉 레짐 차단 · 며칠째 흘러내리는 중인가 → 중기추세 게이트 게이트가 10개인 이유가 이것이다 — 지표마다 사각이 다르다 그래서 문턱을 낮출 수 있다. 판정을 혼자 하지 않으니까 이건 반대로도 성립한다. 게이트가 하나뿐이라면 그 하나를 아주 엄격하게 잡아야 하고, 그러면 신호가 마른다. 여러 개를 두면 각각은 느슨해도 된다. 필터를 겹치는 설계의 진짜 이득은 정확도가 아니라 각 조건을 완화할 여유다.\n다만 겹치는 데는 비용도 있다. 조건을 하나 더 얹으면 통과 후보가 줄어드는데, 그 감소가 예상보다 클 때가 있다. 실제로 단독으로는 성적이 좋았던 거래량 조건을 다른 게이트 위에 올렸다가 후보가 46건에서 6건으로 줄어든 적이 있다. 87%가 사라진 것이다. 좋은 필터를 얻은 게 아니라 통행량을 죽인 결과였다.\n그래서 새 조건을 넣을 때 물어야 할 건 \u0026ldquo;이게 좋은 조건인가\u0026quot;가 아니라 \u0026ldquo;이게 나머지가 못 거르는 것을 거르는가\u0026ldquo;다. 정보의 독립성이 필터의 가치다.\n기간 14는 어디서 왔나 문턱값 얘기를 했으니 기간도 짚어야 한다. RSI(14)의 14는 어디서 온 숫자일까.\n와일더가 쓴 시장은 상품 선물이었고, 그가 기준으로 삼은 건 대략 반달 주기였다. 거래일 기준 2주가 10일이고 여기에 주말을 낀 자연일로 보면 14일 — 이런 식의 어림이다. 다시 말해 달력에서 온 숫자이지 최적화의 결과가 아니다.\n기간을 바꾸면 무엇이 달라지나 RSI(7) 짧은 창 반응이 빠르다 · 극단값(0, 100)에 자주 닿는다 봉 하나의 영향이 1/7 → 큰 봉 한 방에 값이 통째로 뒤집힌다 RSI(14) 표준 대부분의 플랫폼 기본값 — '남들이 보는 값' 이라는 것 자체가 의미가 있다 RSI(21~28) 긴 창 값이 부드럽다 · 30/70 에 거의 안 닿는다 문턱을 그대로 두면 신호가 사실상 사라진다 중요한 성질: 기간과 문턱은 한 쌍이다 기간을 늘리면 값의 분포가 50 쪽으로 좁아지므로 문턱도 함께 조여야 한다 기간만 바꾸고 문턱을 그대로 두는 건 두 파라미터를 동시에 바꾼 것과 같다 마지막 줄이 스윕 설계에서 자주 놓치는 부분이다. 기간 축과 문턱 축을 독립적으로 훑으면 조합의 절반은 애초에 말이 안 되는 지점이다. RSI(28)에 문턱 30을 걸면 신호가 거의 0이라 표본 자체가 안 쌓인다. 두 축을 함께 움직이거나, 문턱을 절대값이 아니라 분포의 백분위로 정의하는 쪽이 낫다.\n백분위 방식은 이런 식이다. \u0026ldquo;RSI 45 이하\u0026quot;가 아니라 \u0026ldquo;이 종목 최근 30일 RSI 분포의 하위 25%\u0026rdquo;. 종목마다 RSI가 놀던 범위가 다르니 자동으로 조정된다. 다만 계산이 무거워지고 해석이 어려워져서, 나는 아직 절대값을 쓰고 있다. 언젠가 재볼 후보 목록에 올려둔 항목이다.\n과매수 쪽은 왜 안 쓰나 교과서는 30과 70을 대칭으로 가르치는데, 내 봇은 45만 쓴다. 70은 아예 조건에 없다.\n이유는 단순하다. 숏 포지션이 없기 때문이다. 업비트 현물만 다루니 오를 것 같으면 사고 아니면 안 사는 두 가지 선택뿐이다. 과매수 판정은 \u0026ldquo;팔아라\u0026quot;에 쓰이는데, 안 들고 있는 종목에 대해서는 할 일이 없다.\n그럼 보유 중인 종목의 청산에 RSI 70을 쓰면 되지 않느냐고 할 수 있다. 실제로 시도해볼 만한 아이디어인데, 지금 구조에서는 잘 안 맞는다.\n청산에 RSI 를 쓰기 어려운 이유 현행 청산 구조 손절 −5% (고정) 본전 사수 +0.8% 도달 시 +0.4% 잠금 포트 익절 계좌 합산 예상실현 2만원 도달 시 전량, 스테이 30분 여기에 RSI 70 청산을 얹으면 · 이익 구간 체류시간 중앙값이 0분 — RSI 가 70 까지 오를 시간이 없다 · 계좌 합산 수확과 개별 종목 청산이 서로 간섭한다 · 청산 경로가 하나 늘면 손익 귀속 분석이 그만큼 복잡해진다 가능성이 있는 자리: 스윙 전략으로 전환하거나, 보유 기간이 길어질 때 지금은 안 맞는다는 것이지 나쁜 아이디어라는 뜻이 아니다 이 판단에는 실측 근거가 있다. 눌림목 진입 18건을 재현했을 때 최대 평가이익의 중앙값이 +3.09%였다. 평균은 +12.27%였지만 그건 한 종목이 +98.65%로 튄 탓이고, 중앙값이 실제 감각에 가깝다. +3% 남짓 오르는 동안 RSI가 70을 넘기는 경우는 드물다.\n같은 재현에서 더 뼈아픈 숫자도 나왔다. 어떤 익절 수준을 골라도 결과의 중앙값이 −2.100%로 고정됐다는 것. 익절 지점을 바꿔봐야 결과가 안 달라졌다는 뜻이다. 손절 11건(61%)이 전부를 좌우하고 있었다. 이 발견이 개별 익절을 포기하고 계좌 합산 수확으로 넘어간 계기였다.\n실제 스캔에서 RSI가 하는 일 이론 말고 운용 데이터로 보면 RSI 게이트의 역할이 좀 더 구체적으로 보인다.\n내 봇은 매 스캔마다 게이트별 통과율을 터미널에 찍는다. 어느 관문에서 후보가 얼마나 걸러지는지를 보기 위해서다. 이 로그를 도입한 계기가 \u0026ldquo;이틀째 매수가 없다\u0026quot;였는데, 그때 게이트별로 숫자를 보고 나서야 어디가 병목인지 알았다.\n게이트 통과율 로그 (형식 예시) [게이트통과율] 과:10/15 | 거:8/15 | 모:6/15 | 변:11/15 | 급:12/15 | 박:9/15 읽는 법 — 분모는 유니버스 통과 종목 수, 분자는 그 게이트를 통과한 수 여기서 얻는 것 · 어느 게이트가 병목인지 즉시 보인다 · 통과율이 갑자기 0 이 되면 코드 사고를 의심할 수 있다 · 게이트 완화의 효과를 다음 스캔에서 바로 확인할 수 있다 이 로그가 없던 시절엔 \"왜 안 사지\" 를 감으로 추측했다 측정하지 않는 필터는 튜닝할 수 없다 — 값을 바꿔도 효과를 모르니까 이 로그를 붙이고 나서 게이트 튜닝의 성격이 바뀌었다. 이전에는 \u0026ldquo;신호가 안 나오니 아무거나 풀어보자\u0026quot;였는데, 이후에는 \u0026ldquo;통과율이 제일 낮은 축부터 본다\u0026quot;가 됐다. 같은 완화라도 병목이 아닌 게이트를 풀면 신호는 안 늘고 품질만 떨어진다.\nRSI 게이트는 이 통계에서 대체로 중간쯤에 있다. 압도적 병목도 아니고 무의미한 관문도 아니다. 게이트 하나가 후보를 절반 이하로 깎으면 그건 사실상 그 게이트가 전략을 결정하고 있다는 뜻이고, 반대로 90% 이상을 통과시키면 없는 거나 마찬가지다. 적당히 걸러주는 자리에 있는 게 건강한 상태라고 본다.\nRSI 다이버전스는 다루지 않는다 RSI 얘기를 하면 반드시 나오는 게 다이버전스다. 가격은 신저점인데 RSI는 더 낮아지지 않는 상황을 반등 신호로 읽는 것.\n이 글에서 다루지 않는 이유는 하나다. 자동 판정 규칙을 만들기가 생각보다 까다롭기 때문이다. 어느 고점과 어느 고점을 비교할지, 그 고점은 몇 봉 뒤에 확정되는지, 두 지점 사이 간격은 얼마까지 허용할지 — 규칙을 못 박지 않으면 사람마다 다른 다이버전스를 본다.\n피보나치의 스윙 선택 문제와 똑같은 구조다. 재현 불가능한 판정은 검증도 불가능하다. 이 주제는 별도로 다룰 가치가 있어서 다음 편에서 파인스크립트 지표 하나를 재료 삼아 따로 정리했다.\n다른 시장으로 가져갈 때 혹시 이 글의 45라는 숫자를 그대로 쓰려는 분이 있을까 봐 적어둔다. 그러면 안 된다.\n45 가 성립하는 전제들 이 값은 아래 조건이 전부 맞을 때의 측정치다 거래소 업비트 원화 마켓 시간축 15분봉 전략 과매도 눌림목 반등 동반 게이트 거량 0.6x · 저점근접 3.0 · 호가단위 0.15% 외 계좌 규모 소액 (내 주문이 호가를 안 움직이는 수준) 표본 8.9일치 실매매 기록 하나라도 다르면 최적점이 이동한다 가져갈 것은 45 라는 숫자가 아니라, 45 를 얻은 절차다 축별 스윕 → 조합 검증 → 기간 반분 → 파레토 우세 채택 특히 표본 8.9일은 결코 넉넉한 양이 아니다. 그 위에서 뽑은 값에는 과적합이 섞여 있을 수밖에 없다. 기간을 반으로 갈라 앞뒤에서 모두 성립하는지 확인한 건 그 위험을 조금이라도 줄이려는 절차였지, 위험을 없앤 게 아니다.\n이 한계를 인정하고 적어두는 게 중요하다고 생각한다. 숫자를 자신 있게 제시하면 글은 강해 보이지만, 그 숫자가 어디까지 유효한지 밝히지 않으면 읽는 사람이 잘못 가져다 쓴다.\n시간축을 바꾸면 다른 지표가 된다 같은 RSI(14)라도 15분봉이냐 일봉이냐에 따라 완전히 다른 물건이다. 이건 당연해 보이지만 실무에서 자주 뒤섞인다.\nRSI(14) 가 커버하는 실제 시간 1분봉 14분 5분봉 1시간 10분 15분봉 3시간 30분 ← 내 봇의 진입 판정 시간축 1시간봉 14시간 4시간봉 2일 8시간 ← 시장 레짐 판정에 쓰는 시간축 일봉 2주 같은 \"RSI 45 이하\" 조건도 15분봉에서는 '오늘 오전에 좀 빠졌다' 일봉에서는 '2주째 흘러내리는 중이다' 두 상황은 대응이 정반대여야 한다 — 앞은 살 자리, 뒤는 피할 자리일 수 있다 그래서 시간축은 지표의 파라미터가 아니라 전략의 정의에 속한다 내 봇은 이 둘을 역할로 분리해서 쓴다. 종목 진입은 15분봉으로 보고, 시장 전체의 허가 여부는 BTC 4시간봉으로 판정한다. 하위 시간축은 타이밍을, 상위 시간축은 허가를 담당하는 구조다.\n이 분리는 실측이 시킨 것이기도 하다. 7주 연속 마이너스를 겪고 나서 원인을 뒤졌는데, 개별 진입 조건이 나빴다기보다 시장이 통째로 흘러내리는 구간에서 계속 사고 있었던 것이 문제였다. 종목 레벨 지표로는 그게 안 잡힌다. RSI 45짜리 눌림목이 하락 추세의 중간 지점일 때, 15분봉 RSI는 그 사실을 모른다.\n그래서 하락장이면 신규 진입 자체를 안 하는 규칙이 생겼다. 종목을 더 잘 고르려는 노력이 한계에 달했을 때, 남은 개선은 \u0026ldquo;언제 게임을 쉬는가\u0026quot;에 있었다.\n이 지표를 처음 붙이는 사람에게 정리 삼아, RSI를 봇에 처음 넣는다면 순서를 이렇게 잡으라고 권하고 싶다.\n도입 순서 ① 값을 맞춘다 내 코드의 RSI 와 차트 플랫폼의 RSI 를 같은 시점에서 대조 소수점까지 맞을 때까지 — 대부분 평활 방식이 원인이다 ② 완성봉만 쓰도록 고정한다 진행봉 제외 · 워밍업 구간 폐기. 재현 가능성이 검증의 전제다 ③ 문턱을 정하지 말고 기록부터 쌓는다 진입할 때마다 그 시점의 RSI 를 함께 저장한다 나중에 '익절한 건과 손절한 건의 진입 RSI 분포' 를 비교할 수 있다 ④ 표본이 쌓이면 스윕한다 축별 → 조합 → 기간 반분. 세 축(수익·승률·손절권) 동시 개선만 채택 ③ 을 건너뛰면 ④ 를 할 수 없다. 그런데 대부분 ③ 을 건너뛴다 나도 건너뛰었고, 그래서 초기 백여 개 버전의 절반은 감으로 만든 것이었다 ③번이 이 목록에서 제일 중요하다고 생각한다. 지표를 넣는 것보다 지표값을 기록으로 남기는 것이 먼저다. 진입 시점의 지표값을 저장해두지 않으면, 나중에 어떤 조건에서 잘 됐고 어떤 조건에서 안 됐는지 비교할 방법이 없다.\n내 봇에서 이 습관이 생긴 건 꽤 늦었다. 단일 파일로 개발하던 시절에는 매매 기록을 체계적으로 안 남겼고, 그래서 파라미터를 바꿀 때 근거가 없었다. \u0026ldquo;이게 나을 것 같다\u0026quot;로 바꾸고 며칠 지켜보다 느낌이 안 좋으면 되돌리고. 지금 돌아보면 그 시기 버전의 절반은 만들 필요가 없었다. 기록이 있었다면 데이터가 답을 줬을 것이다.\n나중에 진입 기록에 그 시점의 시장 레짐까지 함께 저장하기 시작했는데, 이게 이후 분석의 기반이 됐다. 시간대별 성과를 갈라 볼 수 있었던 것도, 게이트 조합을 바꿔가며 재현할 수 있었던 것도 전부 그때 심어둔 기록 덕분이다. 오늘 심는 데이터가 내일의 분석을 결정한다. 분석 인프라는 필요해진 다음에 만들면 늦다. 그때는 이미 과거가 비어 있다.\n변형 지표들 — 스토캐스틱 RSI와 그 친척들 RSI가 워낙 널리 쓰이다 보니 파생 지표도 많다. 그중 실무에서 자주 마주치는 게 스토캐스틱 RSI다.\n발상은 이렇다. RSI가 45라는 값만으로는 그게 이 종목 기준으로 낮은 건지 아닌지 모른다. 어떤 종목은 평소 RSI가 4060에서만 놀고, 어떤 종목은 2080을 오간다. 그래서 RSI 자체에 다시 스토캐스틱을 적용해서 최근 구간 안에서의 상대 위치로 바꾼다.\n스토캐스틱 RSI StochRSI = (RSI − 최근 n봉 RSI 최솟값) / (최댓값 − 최솟값) 결과: 0~1 범위. 이 종목 기준으로 지금 RSI 가 어디쯤인가 장점: 종목별 RSI 범위 차이를 자동으로 흡수한다 단점 1: 정규화를 한 번 더 하면서 원래 크기 정보가 사라진다 RSI 20 → 25 도, RSI 55 → 60 도 같은 StochRSI 상승으로 보일 수 있다 단점 2: 극단값에 자주 닿는다 (0 과 1 에 붙어 있는 시간이 길다) 단점 3: 지연이 이중으로 쌓인다 (RSI 의 지연 + 스토캐스틱 창의 지연) 앞서 얘기한 '백분위 문턱' 아이디어와 사실상 같은 계열의 처방이다 내가 아직 이걸 안 쓰는 이유는 단점 1 때문이다. 눌림목 전략에서는 \u0026ldquo;얼마나 깊게 빠졌나\u0026quot;가 실제로 중요한데, 정규화를 두 번 하면 그 정보가 흐려진다. 다만 종목별 차이를 흡수한다는 장점은 분명하니, 랭킹 가점처럼 위험이 작은 자리에서 먼저 시험해볼 후보로 남겨뒀다.\n랭킹 자리를 고르는 데는 이유가 있다. 진입 여부를 안 바꾸고 순서만 바꾸는 변경은 되돌리기 쉽고 효과 측정도 깔끔하다. 반대로 게이트를 건드리면 신호 개수가 변해서, 성과가 달라졌을 때 조건 덕인지 표본이 달라진 탓인지 구분하기 어려워진다.\n다만 랭킹에도 함정이 있다는 걸 배운 적이 있다. 과매도 정도에 가점을 주는 항목과 밴드 압축에 가점을 주는 항목을 각각 3.0과 2.5로 두고 있었는데, 실제 결과와 대조해보니 두 항목 모두 상관이 음수였다. 점수가 높을수록 오히려 나쁜 종목을 먼저 사고 있었던 것이다. 두 가중치를 0으로 내리는 게 그때의 결론이었다.\n그러니 새 지표를 랭킹에 넣을 때도 \u0026ldquo;유용한가\u0026quot;만 묻지 말고 \u0026ldquo;부호가 내 생각과 같은가\u0026ldquo;를 물어야 한다. 그럴듯한 논리로 만든 가점이 반대로 작동하는 일은 생각보다 자주 일어난다. 나중에 RSI 깊이 가점을 다시 살렸는데, 그때는 근거가 달랐다 — 실제 완결 매매 35건에서 RSI 25 미만 구간의 승률이 78%, 25~35 구간이 21%로 갈렸기 때문이다. 같은 아이디어라도 감으로 넣은 것과 실측으로 넣은 것은 다른 물건이다.\n소회 지표 공부의 끝은 언제나 같은 결론이었다 — 숫자는 빌려 오되, 문턱은 내 데이터로 정한다. RSI는 내 봇의 첫 게이트지만, 30이라는 남의 경험값을 45라는 내 검증값으로 바꾸는 데 두 달이 걸렸다.\n두 달 동안 한 일을 돌아보면 대부분은 값을 바꾼 게 아니라 값을 정하는 방법을 만든 일이었다. 매매기록을 분석 가능한 형태로 남기고, 그 기록으로 과거를 재현하는 코드를 짜고, 채택 기준을 세 축으로 정하고, 기간을 갈라 확인하는 절차를 만들고. 45라는 숫자는 그 인프라가 뱉어낸 첫 결과물일 뿐이다.\n그래서 이 코너의 이름을 지표 스터디로 지었지만, 실제로 공부하고 있는 건 지표가 아니라 검증 절차인지도 모르겠다. 지표는 계속 바뀔 것이고 실제로 여러 번 바뀌었다. 바뀌지 않은 건 \u0026ldquo;이 값이 왜 이 값인지 답할 수 있어야 한다\u0026quot;는 규칙 하나다.\n와일더는 지표를 만들었지만, 그 지표의 문턱은 시장마다 다시 태어나야 한다. 공식은 수입품이어도 파라미터는 국산이어야 한다 — 이게 이 스터디의 첫 번째 원칙이다.\n덧붙임 — 자주 받는 질문 몇 가지 \u0026ldquo;RSI가 30 아래로 갔는데 계속 떨어지는 건 왜인가요.\u0026rdquo; RSI는 상한과 하한이 있는 지표라 극단에서 오래 머무를 수 있다. 강한 하락 추세에서는 RSI가 20대에 며칠씩 붙어 있는 게 정상이다. \u0026ldquo;과매도니까 곧 오른다\u0026quot;는 해석이 위험한 이유가 이것이다. 과매도는 상태이지 예측이 아니다. 그래서 나는 RSI를 진입 신호가 아니라 진입 자격으로 쓴다. 45 이하는 후보가 될 자격이 있다는 뜻일 뿐, 사라는 뜻이 아니다. 실제로 사는 건 나머지 아홉 개 게이트를 다 통과했을 때다.\n\u0026ldquo;거래소 앱 RSI와 제 계산이 다릅니다.\u0026rdquo; 평활 방식(단순평균 vs 와일더), 시간축, 봉 마감 기준 시각, 그리고 진행봉 포함 여부 — 이 넷 중 하나다. 순서대로 확인하면 대개 첫 번째에서 잡힌다.\n\u0026ldquo;RSI 하나만 쓰면 안 되나요.\u0026rdquo; 안 될 건 없지만 문턱을 훨씬 보수적으로 잡아야 한다. 조건이 하나뿐이면 그 하나가 모든 걸 판정해야 하니까. 그러면 신호가 마르고, 신호가 마르면 표본이 안 쌓이고, 표본이 없으면 그 문턱이 옳은지 확인할 방법이 없다. 이 악순환에서 나가는 길이 게이트를 늘리는 것이었다.\n\u0026ldquo;8.9일은 표본이 너무 적지 않나요.\u0026rdquo; 맞다. 적다. 그래서 그 스윕 결과를 확정이 아니라 현재까지의 최선으로 취급한다. 표본이 쌓이면 다시 돌릴 예정이고, 그때 45가 아닌 다른 값이 나와도 놀라지 않을 것이다. 중요한 건 값이 아니라 값을 갱신할 수 있는 상태를 유지하는 것이다.\n","permalink":"https://botlab.co.kr/study/rsi/","summary":"상대강도지수를 개발자의 눈으로 해부한다. 그리고 교과서의 30이 왜 내 봇에서는 45가 됐는지.","title":"RSI — 교과서는 30, 내 봇은 45 | Crypto Trading Bot"},{"content":"두 가지가 동시에 바뀌었습니다 7월 말, 봇에 두 가지 변화가 같이 왔습니다.\n첫째, 파일을 쪼갰습니다. 1,500줄짜리 단일 파일을 역할별로 나눴어요. 상수와 상태, 체결과 청산 엔진, 종목 스캔과 게이트, 원장 대조, 화면 출력. 이렇게 나누고 나니 \u0026ldquo;어디를 고치면 어디가 부서지나\u0026quot;라는 질문에 답할 수 있게 됐습니다.\n둘째, git을 쓰기 시작했습니다. 그전까지는 파일을 복사해서 버전을 매겼어요. bot_v1.py, bot_v2.py 이런 식으로요. git으로 옮기면서 변경 이력이 남기 시작했고, 이게 생각보다 큰 변화였습니다.\n현행 구조 vv_state 상수와 전역 상태 — 파라미터는 전부 여기 모여 있다 vv_core 체결·청산 엔진 vv_scan2 종목 스캔과 진입 게이트 vv_ledger 거래소 주문내역 대조 vv_log 터미널 출력과 상태 저장 변경 대부분은 vv_state 의 상수 한두 줄 — 그 한두 줄마다 시뮬레이션 결과를 커밋 메시지에 같이 넣는다 이 경계를 어떻게 정했냐면, 고민을 별로 안 했습니다. 이미 알고 있었거든요. 모놀리스 시절에 서로 다른 이유로 바뀌던 덩어리들이 그대로 모듈이 됐습니다. 파라미터는 파라미터끼리 바뀌고, 청산 로직은 청산 로직끼리 바뀌고, 화면은 화면끼리 바뀌었어요. 그 패턴이 곧 설계도였습니다.\n모듈 경계를 정한 기준 ✘ 코드 줄 수로 나누기 균등하게 나뉘지만 의미가 없다 ✘ 개념적으로 예뻐 보이게 나누기 모듈끼리 서로를 계속 호출하게 된다 ✔ 같이 바뀌는 것끼리 묶기 한 가지 이유로 고칠 때 한 파일만 열면 된다 -------------------------------------------------- 이 기준은 코드를 노려본다고 안 보인다 몇 달 고쳐본 이력에서 나온다 git 도입도 단순히 도구를 바꾼 게 아니었습니다. 변경에 이유를 붙일 자리가 생긴 것이 핵심이에요. 파일 복사 방식에는 그 자리가 없습니다. 파일명에 \u0026ldquo;손절을 -2.5%로 바꿨고 근거는 실측 58건 시뮬레이션\u0026quot;이라고 쓸 수는 없잖아요.\n커밋에 숫자를 넣기 시작한 뒤 이 시기 개발이 결정적으로 달라진 건 구조 때문이 아니라 습관 때문입니다. 파라미터를 바꿀 때마다 왜 그 값인지를 커밋 메시지에 적기 시작했습니다.\n예를 들면 이런 식입니다. 손절선을 정할 때, 그냥 -2.5%로 정한 게 아니라 실제 매매 58건을 손절선별로 다시 돌려봤어요.\n손절선을 정한 방식 (실측 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% 결론: 손실은 자르고 이익은 풀어준다 — 격언이 아니라 내 계좌의 숫자로 이 표에서 제일 중요한 건 첫 줄입니다. 손절이 없으면 기대값이 음수라는 것. 손절을 넣으면 승률은 떨어집니다. 버티면 돌아왔을 자리에서 잘리니까요. 그런데 기대값은 올라갑니다. 승률과 기대값이 반대로 움직이는 구간이 있다는 걸 숫자로 본 게 컸어요.\n익절 상한 실험도 같은 얘기입니다. 익절 폭을 좁히면 승률이 확 올라갑니다. 작은 이익을 자주 확정하니까요. 그런데 기대값은 음수가 됩니다. 큰 이익 몇 건이 전체를 먹여 살리고 있었던 거예요. 승률만 보면 정확히 반대로 최적화하게 됩니다.\n승률이라는 함정 익절을 좁힌다 → 승률 ↑ · 기대값 ↓ 손절을 없앤다 → 승률 ↑ · 기대값 ↓ -------------------------------------------------- 승률만 보고 튜닝하면 두 가지를 다 하게 된다 → 계좌는 조용히 준다 현행 평가 지표: 건당 기대값 + 손익비(R:R) 이렇게 적어두면 두 가지가 좋습니다. 하나, 나중에 그 값이 틀렸을 때 왜 틀렸는지 추적할 수 있습니다. 둘, 과거의 나와 논쟁할 수 있습니다. 석 달 전의 내가 \u0026ldquo;이 값이 좋다\u0026quot;고 했는데 지금 보니 아니면, 그때 근거가 뭐였는지 확인하고 무엇이 달라졌는지 알 수 있어요. 근거 없이 정한 값은 틀려도 배울 게 없습니다.\n시뮬레이션이라고 부르는 것의 정체 여기서 말하는 시뮬레이션은 거창한 백테스트 엔진이 아닙니다. 실제로 체결된 내 매매 기록을 다른 파라미터로 다시 계산해보는 거예요.\n실측 기반 재계산의 장단점 장점 · 실제 체결가 · 실제 수수료 · 실제 슬리피지 반영 · \"그때 정말 살 수 있었나\" 를 가정할 필요가 없다 · 구현이 단순하다 (기록 + 계산) 한계 · 진입 조건을 바꾸는 실험은 못 한다 (안 산 종목의 결과는 기록에 없으니까) · 표본이 내 매매 이력만큼만 쌓인다 · 장세 편향이 있다 (그 기간의 장세) \u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026ndash; 그래서 청산 파라미터 검증에는 강하고 진입 게이트 검증에는 스캔 로그를 따로 본다\n가상 백테스트의 고질적인 문제가 \u0026ldquo;그 가격에 정말 체결됐을까\u0026quot;입니다. 호가가 얇은 종목에서는 화면에 찍힌 가격과 실제 체결가가 다르고, 수수료와 슬리피지를 어떻게 가정하느냐에 따라 결과가 통째로 바뀝니다. 실측 기록을 쓰면 그 가정이 전부 사라져요. 이미 일어난 일이니까요.\n대신 한계도 명확합니다. 안 산 종목의 결과는 기록에 없습니다. 진입 게이트를 느슨하게 하면 어떻게 됐을지는 이 방식으로 알 수 없어요. 그래서 진입 쪽은 스캔 로그를 따로 봅니다. 어느 게이트에서 몇 개가 떨어졌는지, 그 게이트를 조정하면 통행량이 얼마나 바뀌는지요.\n두 도구가 각각 다른 질문에 답합니다. 시뮬레이션은 \u0026ldquo;언제 팔까\u0026quot;에, 스캔 로그는 \u0026ldquo;무엇을 살까\u0026quot;에요.\n전략이 바뀐 날 이 시기 가장 큰 사건은 진입 전략을 통째로 바꾼 겁니다. 돌파를 쫓던 것에서 과매도 눌림목 반등으로요.\n몇 주 동안 돌파 조건을 조였다 풀었다 반복했는데 결론이 \u0026ldquo;파라미터 문제가 아니라 전략 문제\u0026quot;였습니다. 박스권에서 돌파는 대부분 가짜였거든요. 병렬 실험기의 BASE에서 확인했던 그 결론으로 돌아온 셈입니다.\n전략 전환을 결심한 신호들 · 조건을 조여도 풀어도 기대값이 비슷하다 · 조이면 매매가 줄 뿐 건당 성적은 그대로 · 이기는 건은 크게 이기지 않고 지는 건은 반복된다 -------------------------------------------------- → 파라미터 공간 안에 답이 없다는 뜻 → 이럴 때 바꿔야 하는 건 값이 아니라 전제다 설계에서 하나 잘한 건, 이걸 스위치로 만든 겁니다. 돌파 로직을 지우지 않고 모드로 남겨뒀어요. 전략 전환은 도박이고, 도박에는 퇴로가 필요하니까요.\n그리고 이 판단은 앞 시대들의 반복이기도 합니다. 2시대에서 이동평균이 양쪽으로 다 나빠지는 트레이드오프를 만났을 때 저는 파라미터를 더 만졌어요. 같은 신호를 이번에는 읽은 겁니다. 트레이드오프가 양방향으로 나빠지면 그건 전제를 바꾸라는 신호라는 걸 배우는 데 시대가 네 개 걸렸습니다.\n지금 봇이 지키는 규칙들 현재 운용 중인 규칙을 정리하면 이렇습니다.\n현행 운용 규칙 진입 10개 게이트를 전부 통과한 종목만 (RSI·거래량·저점근접·호가단위 등) RSI 45 이하 / 저점근접 3.0 — 8.9일치 매매기록 전수 스윕으로 확정 운용 4슬롯. 슬롯당 예산은 가용 현금의 고정 비중 방어 손절 -5% · 본전 사수(+0.8% 도달 시 +0.4% 잠금) 일일 손실 한도 · 주간 손실 한도 · 자산 서킷브레이커 수확 계좌 합산 예상실현 2만원 도달 시 전량 청산, 스테이 30분 차단 BTC 4시간봉이 하락장이면 신규 진입 자체를 안 한다 배포 무결성 검사 + 거래소 잔고 대조 통과 못 하면 배포 금지 여기서 RSI 45가 눈에 띌 겁니다. 교과서는 30을 과매도 기준으로 씁니다. 그런데 실측 기록을 전수로 훑어보니 45가 나왔어요. 30까지 기다리면 이 시장에서는 진입 자체가 거의 안 일어납니다. 교과서 값은 출발점이지 정답이 아니다라는 걸 숫자로 확인한 사례입니다.\n본전 사수도 설명해두겠습니다. 이익이 +0.8%까지 갔다가 다시 마이너스로 돌아오는 게 심리적으로 제일 아픈 패턴이에요. 그래서 +0.8%에 닿으면 +0.4% 지점에 하한을 겁니다. 최대 이익을 포기하는 대신 이익이 손실로 바뀌는 경로를 막는 장치입니다. 첫 시대 메모에 있던 \u0026ldquo;50% 익절 + 트레일링\u0026quot;의 후손인데, 물량을 쪼개는 대신 가격 하한을 올리는 방식으로 진화했어요.\n포트폴리오 수확은 좀 독특한 규칙입니다. 종목별로 파는 게 아니라 계좌 전체의 예상 실현액이 기준에 닿으면 전량 정리해요. 그리고 30분 쉽니다.\n왜 계좌 단위로 수확하나 [ 종목 단위 청산 ] 각 종목이 목표에 닿을 때까지 기다린다 일부는 목표 도달 · 일부는 계속 물려 있음 → 슬롯이 안 비고 사이클이 멈춘다 [ 계좌 단위 수확 ] 합산 예상실현이 목표에 닿으면 전량 정리 → 슬롯이 한 번에 비고 다음 사이클로 → 스테이 30분: 정리 직후 재진입 방지 \u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026ndash; 개별 최적이 아니라 회전 최적을 택한 것\n개별 종목 기준으로만 보면 손해 보는 결정입니다. 더 갈 수 있었던 종목도 같이 팔리니까요. 대신 슬롯이 한 번에 비고 사이클이 다시 돕니다. 물린 종목 하나가 슬롯을 몇 시간씩 잡고 있는 것보다 낫다고 판단했어요.\n배포 규율 마지막 줄이 이 시대의 정체성입니다. 손익 계산의 근거를 봇의 기억이 아니라 거래소 주문내역으로 옮겼고, 거래소가 알려주는 총자산과 봇이 계산한 총자산이 원 단위로 일치하지 않으면 배포를 안 합니다.\n봇의 기억이 어긋나는 경로 · 부분 체결 주문한 수량과 체결된 수량이 다르다 · 수수료 계산 시점·방식에 따라 잔량이 미세하게 다르다 · 재시작 저장 시점과 실제 상태 사이의 공백 · 수동 개입 사람이 거래소에서 직접 팔면 봇은 모른다 -------------------------------------------------- 대조하지 않으면 이 오차가 계속 누적된다 → 성적 분석 자체가 틀리게 된다 자기 기억을 안 믿기로 한 건 소프트웨어적으로는 굴욕이지만 회계적으로는 성숙이라고 생각해요. 그리고 이건 1시대의 무결성 점검이 자란 형태입니다. 그때는 \u0026ldquo;가끔 확인해보자\u0026quot;였는데 지금은 \u0026ldquo;안 맞으면 배포 금지\u0026quot;예요. 같은 아이디어가 권한을 얻은 거죠.\n배포 전 통과해야 하는 것들 1. 무결성 검사 모듈 간 상태 일관성 2. 거래소 잔고 대조 원 단위 일치 3. 파라미터 근거 시뮬레이션 결과가 커밋에 있는가 -------------------------------------------------- 하나라도 실패하면 배포하지 않는다 3번이 특히 중요 — 근거 없는 값은 코드에 못 들어간다 3번을 규칙으로 만든 게 개인적으로는 제일 큰 변화입니다. 값을 바꾸고 싶으면 먼저 시뮬레이션을 돌려야 해요. 귀찮아서 안 바꾸게 되는 경우도 생기는데, 그것도 나쁘지 않다고 봅니다. 바꾸기 어렵게 만드는 것 자체가 안전장치니까요.\n게이트 열 개가 각각 하는 일 게이트를 열 개까지 늘린 과정도 적어두겠습니다. 처음부터 열 개를 설계한 게 아니라, 사고가 날 때마다 하나씩 붙였어요.\n게이트가 막는 실패의 종류 데이터 충분성 봉이 부족한 종목 → 지표가 의미 없는 값 RSI 아직 안 빠진 종목 → 눌림목이 아님 저점 근접 바닥에서 먼 종목 → 되돌림 여지 부족 거래량 거래가 없는 종목 → 사도 못 판다 호가 단위 주문 가격이 규격 밖 → 조용히 거부됨 최소 주문 슬롯 예산이 최소 미만 → 주문 불가 중복 보유 이미 들고 있는 종목 재진입 쿨다운 방금 손절한 종목 → 반복 손절 방지 -------------------------------------------------- 각각 다른 종류의 실패를 막는다 — 겹치는 게 없다 여기서 중요한 건 게이트들이 서로 다른 종류의 실패를 막는다는 겁니다. 3시대에서 지표를 잔뜩 붙여봤을 때는 여러 지표가 사실상 같은 얘기를 하고 있었어요. 지금은 그렇지 않습니다. 데이터 품질, 종목 상태, 유동성, 주문 가능성, 봇 자신의 이력 — 층위가 다 다릅니다.\n특히 뒤쪽 세 개가 재밌습니다. 호가 단위, 최소 주문, 재진입 쿨다운은 시장 데이터가 아니라 주문 시스템과 봇 자신의 상태에 관한 조건이에요. 지표 이야기가 아닙니다. 그런데 이것들이 없으면 조건은 다 맞는데 주문이 안 나가거나, 같은 종목에서 계속 깎이는 상황이 생깁니다.\n게이트를 설계할 때 지표부터 떠올리게 되는데, 실제로 계좌를 지켜주는 건 이런 사무적인 조건들이더군요.\n스캔 로그를 실제로 읽기 시작하다 3시대에 남기기만 하고 안 보던 스캔 로그를, 이 시대에 드디어 분석하기 시작했습니다.\n게이트별 탈락 분포를 보면 알 수 있는 것 게이트 A 탈락 0건 → 아무 일도 안 하고 있다 게이트 B 탈락 대부분 → 혼자 다 거른다 (나머지가 무의미) 게이트 C 고르게 → 제 역할을 한다 -------------------------------------------------- 조정 후 반드시 같이 확인할 것: · 건당 기대값이 올랐나 · 매매 건수가 얼마나 줄었나 기대값 그대로 + 건수만 감소 = 그 게이트는 문만 좁힌 것 실제로 이 분석에서 나온 게 앞서 말한 RSI 45입니다. 30으로 두면 통과하는 종목이 거의 없어요. 문턱을 45로 올리니 매매가 일어나고, 그 상태에서 기대값도 유지됐습니다. 통행량과 성적을 같이 봐야 알 수 있는 값이었어요.\n반대 사례도 있었습니다. 그럴듯한 논리로 넣은 가점 항목이 실제로는 반대로 작동하고 있던 경우요. 점수가 높을수록 성적이 나빴습니다. 논리는 완벽했는데 데이터가 반박한 거죠. 이런 걸 발견하면 좀 허탈한데, 동시에 이 방식이 작동한다는 증거이기도 합니다. 내 논리를 반박해주는 도구가 있다는 게 혼자 개발할 때 제일 필요한 것이더군요.\n7주 연속 마이너스가 가르쳐준 것 7주 연속 마이너스를 보고 나서야 받아들인 게 하나 있습니다. 잃지 않는 구조를 먼저 만들고, 버는 건 그다음이라는 것.\n그전에는 어떻게 더 벌지만 생각했어요. 진입 조건을 정교하게 만들고, 지표를 추가하고, 타이밍을 다듬고. 전부 \u0026ldquo;더 잘 사는 법\u0026quot;이었습니다.\n현행 규칙의 성격 분포 사는 것에 관한 규칙 진입 게이트 10개 · 랭킹 · 눌림목 대기 안 사거나 멈추는 규칙 슬롯 상한 · 손절 · 본전 사수 일일 손실 한도 · 주간 손실 한도 자산 서킷브레이커 · 레짐 차단 · 수확 후 스테이 \u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026ndash; 아래쪽이 더 많다 그리고 아래쪽은 전부 \u0026ldquo;여기서 멈춘다\u0026rdquo; 를 미리 정한 것\n지금 규칙 목록을 보면 절반 이상이 방어 장치입니다. 손절, 본전 사수, 일일·주간 한도, 서킷브레이커, 레짐 차단. 전부 \u0026ldquo;여기서 멈춘다\u0026quot;를 미리 정해둔 것들이죠.\n방어 규칙의 공통점이 하나 있습니다. 전부 사전에 정해둔 것이라는 점이에요. 손실이 커진 다음에 \u0026ldquo;여기서 멈출까\u0026quot;를 판단하면 절대 못 멈춥니다. 그 순간에는 조금만 더 버티면 돌아올 것 같거든요. 그래서 판단을 미리 해두고 실행만 봇에게 맡깁니다. 처음에 자제력 문제로 시작한 프로젝트가 결국 여기로 왔어요.\n모듈화가 실제로 바꾼 것 파일을 쪼갠 효과를 구체적으로 적어두겠습니다. \u0026ldquo;코드가 깔끔해졌다\u0026rdquo; 말고 실질적인 것들이요.\n모놀리스 ↔ 모듈, 작업별 차이 모놀리스 모듈 파라미터 변경 한 파일 안 어딘가 vv_state 한 곳 청산 로직 수정 화면이 깨질 수 있음 영향 범위 명확 로그 형식 변경 판단 로직 근처 완전히 분리 전략 검증 불가능 기록 재계산 가능 잔고 대조 할 자리가 없음 전담 모듈 -------------------------------------------------- 제일 큰 변화는 마지막 두 줄 — 검증과 대조가 있을 자리가 생겼다 마지막 두 줄이 핵심입니다. 모놀리스에서는 검증 로직을 넣을 자리가 없었어요. 어디에 넣어도 다른 것과 얽힙니다. 모듈로 나누고 나니 원장 대조를 전담하는 자리가 생겼고, 그 자리가 생기니까 배포 규칙을 만들 수 있게 됐습니다.\n구조가 규율을 가능하게 한다는 게 이 시대의 발견입니다. 규율은 의지의 문제 같지만, 실은 그 규율을 실행할 자리가 코드에 있느냐의 문제였어요. 자리가 없으면 아무리 마음먹어도 못 합니다.\n파라미터를 한 모듈에 모은 것도 비슷한 효과가 있었습니다. 값이 흩어져 있으면 \u0026ldquo;지금 이 봇이 어떤 설정으로 돌고 있나\u0026quot;를 한눈에 못 봐요. 한곳에 모으니 파일 하나만 열면 봇의 전체 성격이 보입니다. 그리고 각 값 옆에 근거를 주석으로 붙여두니, 별도 문서 없이 코드가 문서 역할을 합니다.\n여섯 시대를 관통하는 세 문장 461개를 정리하며 반복해서 나온 것들만 뽑으면 셋입니다.\n하나, 늘어난 건 전부 안 사는 이유였습니다. 615줄에서 여섯 모듈까지, 코드가 늘어난 양의 대부분은 진입을 막는 조건입니다. 게이트, 슬롯, 손절, 한도, 레짐 차단. 자동매매를 만들기 시작하면 사는 쪽을 정교하게 만들고 싶어지는데, 실제로 계좌를 지킨 건 반대쪽이었어요.\n둘, 기록이 없으면 아무것도 축적되지 않습니다. 3시대 버전의 절반은 안 만들어도 되는 것들이었습니다. 기록이 있었으면 데이터가 답을 줬을 테니까요. 그리고 기록의 가치는 항상 시차를 두고 나타납니다. 오늘 로그를 남기는 커밋은 오늘 성적을 1원도 안 바꾸지만 몇 주 뒤 답을 줍니다.\n셋, 고통은 설계 정보입니다. 각 시대를 끝낸 건 좋은 아이디어가 아니라 한계에 부딪힌 경험이었어요. 그리고 그 경험이 다음 구조의 설계도가 됐습니다. 모놀리스 시절에 어디가 자주 깨졌는지가 곧 모듈 경계였고, 병렬 실험의 관리 비용이 곧 \u0026ldquo;한 봇은 한 성격\u0026rdquo; 원칙이 됐습니다.\n남은 이야기 지금도 매일 플러스인 건 아닙니다. 다만 지금의 봇은 자기가 왜 그 값으로 돌아가는지 전부 답할 수 있어요. 저한테는 그게 수익률보다 먼저 온 자산입니다.\n461개 버전을 여섯 시대로 정리하면서 확인한 게 하나 있습니다. 각 시대를 끝낸 건 새로운 아이디어가 아니라 한계에 부딪힌 경험이었어요.\n시대를 끝낸 것들 1시대 윈도우 불안정 · 단타 수수료 2시대 이동평균이 횡보장에서 무력 3시대 단일 파일 · 예측 불가능 4시대 갈래 네 개의 관리 비용 5시대 조기 최적화로 낭비한 시간 6시대 진행 중 -------------------------------------------------- 전부 \"안 되는 걸 충분히 겪은 뒤\" 다음이 나왔다 그래서 지금 시대도 언젠가 끝날 겁니다. 무엇 때문에 끝날지는 아직 모르는데, 그때 이유를 설명할 수 있는 상태이기만 하면 된다고 생각해요. 461개를 돌아보며 제일 아쉬웠던 건 성적이 나빴던 시기가 아니라 왜 그랬는지 모르는 시기였습니다.\n이 아카이브를 쓴 이유도 그겁니다. 다음에 무언가 끝날 때, 그 이유가 여기 남아 있게 하려고요.\n","permalink":"https://botlab.co.kr/archive/06-vv-modules/","summary":"커밋마다 근거 수치를 남기기 시작하면서 개발이 달라졌습니다. 과거의 나와 논쟁할 수 있게 됐거든요.","title":"6. 지금의 봇 — 모듈로 쪼개고, 숫자로 정하기 시작한 뒤 | Crypto Trading Bot"},{"content":"이름의 유래 지금 봇의 버전은 전부 VV로 시작합니다. vv141, vv210 같은 식으로요. 이 VV가 어디서 왔냐면, VWAP입니다.\nVWAP은 거래량 가중 평균가입니다. 그냥 평균가는 모든 봉을 똑같이 취급하는데, VWAP은 거래가 많이 일어난 가격에 더 무게를 둡니다. 실제로 돈이 오간 자리가 어디냐를 보는 거죠.\n단순 평균 ↔ 거래량 가중 평균 단순 평균 모든 봉의 가격을 똑같이 취급 거래가 거의 없던 봉도 한 표 VWAP 가격 × 그 가격에서 오간 거래량 누적해서 나눈다 돈이 실제로 오간 자리에 무게가 실린다 \u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026ndash; \u0026ldquo;앵커드\u0026rdquo; = 특정 시점부터 누적해서 계산 예: 어제 저점부터 지금까지 → 그 구간의 평균 매입가\n여기에 \u0026ldquo;앵커드\u0026quot;가 붙으면 특정 시점부터 누적해서 계산한다는 뜻입니다. 예를 들어 어제 저점부터 지금까지의 VWAP을 보면, 그 구간에서 사람들이 평균 얼마에 샀는지가 나옵니다.\n이 지표에 끌린 이유는 명확했습니다. 의미가 해석 가능하다는 것. 이동평균은 \u0026ldquo;최근 평균\u0026quot;이라는 것 말고는 실체가 없는데, 앵커드 VWAP은 \u0026ldquo;이 구간에 들어온 사람들의 평균 단가\u0026quot;라는 실제 의미가 있어요. 그 선 위에 있으면 그 구간 참여자 대부분이 이익 상태고, 아래면 손실 상태입니다. 지표가 시장 참여자의 상태를 말해준다는 게 매력적이었습니다.\n이 지표를 중심에 놓고 만든 봇의 파일 이름이 vvwap이었고, 버전을 매기다 보니 앞의 두 글자만 남아 VV가 됐습니다. 그게 지금까지 이어지고 있어요.\n왜 결국 빠졌나 VWAP 자체는 결국 현행 봇의 진입 판단에서 빠졌습니다. 이유를 정리해두면 이렇습니다.\n앵커드 VWAP 을 실전에 넣을 때의 문제 1. 앵커를 어디에 둘 것인가 어제 저점? 최근 급등 시작점? 특정 시각? 앵커가 바뀌면 선이 통째로 달라진다 → 사람이 눈으로 찍을 땐 되는데 자동화가 어렵다 2. 종목마다 적절한 앵커가 다르다 수백 종목을 스캔하는 봇에서 각각 앵커를 정할 수 없다\n3. 대체 가능했다 \u0026ldquo;많이 빠졌나\u0026rdquo; 는 다른 방식으로도 판정된다 → RSI · 최근 저점 근접도\n1번이 결정적이었습니다. 앵커드 VWAP은 사람이 차트를 보면서 \u0026ldquo;여기부터\u0026quot;를 찍을 때 강력합니다. 그런데 봇은 그 판단을 자동으로 해야 해요. 앵커 선택 규칙을 만들려고 하는 순간, 그 규칙이 또 하나의 튜닝 대상이 됩니다. 지표를 하나 넣었는데 파라미터가 두 개 늘어난 셈이죠.\n이건 앞 시대에서 배운 교훈의 반복입니다. 사람의 해석이 필요한 지표는 봇으로 옮기는 순간 무너집니다. 좋은 지표와 자동화하기 좋은 지표는 다릅니다.\n그래도 이름은 남았습니다. 지금 봇에서 안 쓰는 지표의 약자가 버전 접두어로 계속 붙어 있는 게 좀 웃긴데, 저는 그대로 두고 있어요. 이름을 바꾸면 그 전에 있었던 일들과 연결이 끊깁니다. 461개가 하나의 계보라는 게 이 프로젝트에서 제일 중요한 사실이라서요.\n여기서 만들어진 진짜 자산 VWAP은 빠졌지만, 이 시기에 만들어진 것 중 지금 가장 중요한 게 하나 있습니다.\nv103 — 시장 상황 판단의 시작 BTC_DROP_LIMIT 도입 비트코인이 일정 이상 빠지면 신규 진입을 막는다 개별 종목 조건이 아무리 좋아도, 시장이 무너지면 소용없다는 전제 이것이 훗날 vv202 의 \"하락장 신규 진입 중단\" 으로 이어진다 BB.V008 에서는 4시간봉 완성봉 기준으로 판정하도록 정리 — 진행 중인 봉으로 판단하면 시장 상황이 몇 분 단위로 뒤집히기 때문 MACRO 실험에서 얻은 문제의식 — 시장 전체를 먼저 봐야 한다 — 이 여기서 처음 실제 코드가 됐습니다. 개별 종목 게이트를 아무리 정교하게 깎아도 시장이 통째로 흘러내리는 날엔 다 걸려요. 그럴 땐 안 사는 게 답이고, 그 판단은 종목이 아니라 시장 레벨에서 해야 합니다.\n여기서 중요한 건 구현이 아니라 결정의 성격입니다. 그전까지 봇의 모든 규칙은 \u0026ldquo;어떤 종목을 어떻게 살까\u0026quot;였어요. BTC_DROP_LIMIT은 처음으로 종목과 무관하게 전체를 멈추는 규칙입니다. 판단의 층위가 하나 올라간 거죠.\n규칙의 두 종류 [ 종목 단위 규칙 ] 이 종목을 살까 말까 틀려도 그 종목 하나의 문제 [ 계좌 단위 규칙 ] 지금 아무것도 안 할까 한 번의 판단이 모든 매매를 좌우 → 그래서 더 보수적으로, 더 느린 주기로 판단해야 \u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026ndash; 현행 계좌 단위 규칙: 레짐 차단 · 일일/주간 손실 한도 자산 서킷브레이커 · 포트폴리오 수확\n계좌 단위 규칙은 영향력이 큰 만큼 조심스럽게 다뤄야 합니다. 종목 하나를 잘못 거르면 기회 하나를 놓치는데, 시장 판정을 잘못하면 그날 하루를 통째로 날립니다. 그래서 이런 규칙은 더 느린 주기로, 더 안정적인 기준으로 판단하게 됩니다.\n시장 판정을 무엇으로 할 것인가 BTC를 기준으로 삼은 것도 이때 정해졌습니다. 여러 후보가 있었는데요.\n시장 기준 후보들 BTC 단일 + 데이터가 가장 두껍다 · 대표성이 높다 + 계산이 단순하다 (기준 하나) - BTC 만 따로 움직이는 구간이 있다 시총 상위 N개 평균 + 개별 종목 특이 움직임이 상쇄된다 - 구성 종목을 언제 갱신할지 또 정해야 한다 - 계산량이 늘고 재현이 복잡해진다\n보유 종목 평균 - 보유가 없으면 판정 자체가 불가능 \u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026ndash; 단순함을 골랐다 — 검증 가능성이 우선\n정교한 쪽이 이론적으로는 낫습니다. 그런데 정교해질수록 검증이 어려워져요. 구성 종목 갱신 규칙, 가중치, 갱신 주기 — 전부 새로운 튜닝 대상입니다. 그리고 이 시대에는 그걸 검증할 데이터가 없었습니다.\n검증할 수 없는 정교함보다 검증 가능한 단순함을 고른 겁니다. 이 판단 기준은 이후에도 반복해서 씁니다. 지금 랭킹 산식이 단순한 것도, 게이트가 각각 독립적으로 판정되는 것도 같은 이유예요.\n한 가지 덧붙이면, 단순한 기준의 약점은 알고 쓰는 게 중요합니다. BTC만 보면 개별 종목 장세를 놓치는 구간이 분명히 있어요. 그걸 모르고 쓰면 나중에 성적이 이상할 때 원인을 못 찾습니다. 알고 쓰면 \u0026ldquo;아, 이 구간은 기준의 한계구나\u0026rdquo; 하고 넘어갈 수 있고요. 한계를 아는 단순한 도구가 한계를 모르는 정교한 도구보다 낫습니다.\n완성봉만 쓰기로 한 이유 완성봉만 쓰기로 한 것도 이때 정리됐습니다. 진행 중인 4시간봉으로 판단하면 몇 분마다 \u0026ldquo;하락장이다 / 아니다\u0026quot;가 뒤집힙니다.\n진행 중인 봉으로 판단하면 4시간봉이 만들어지는 동안 -------------------------------------------------- 10:05 현재 시가 대비 -1.2% → 하락장 판정 · 진입 차단 10:20 반등해서 +0.3% → 정상 판정 · 진입 허용 10:45 다시 -0.8% → 하락장 판정 · 진입 차단 -------------------------------------------------- 봇은 진입을 열었다 닫았다만 하고 아무것도 못 한다 완성봉 기준: 4시간에 한 번만 판정이 바뀐다 봇이 그때마다 진입을 열었다 닫았다 하면 아무것도 못 해요. 더 나쁜 건 그 판정 순간에 따라 결과가 완전히 달라진다는 겁니다. 같은 날 같은 종목인데 봇이 몇 분 차이로 다르게 행동하면, 나중에 성적을 분석할 때 원인을 짚을 수가 없습니다.\n판단의 주기는 행동의 주기보다 느려야 합니다. 이 원칙은 시장 판정뿐 아니라 지표 전반에 적용됩니다. 지금 봇이 완성된 봉만 쓰는 것도, 진입 후 일정 시간 재판단을 안 하는 것도 같은 이유예요.\n그리고 이건 검증 가능성 문제이기도 합니다. 진행 중인 봉을 쓰면 같은 데이터로 시뮬레이션을 돌려도 결과가 재현되지 않아요. 몇 시 몇 분에 봤느냐에 따라 값이 다르니까요. 재현되지 않는 로직은 검증할 수 없고, 검증할 수 없는 값은 배포하면 안 됩니다. 지금 배포 규율의 기술적 전제가 이때 마련됐습니다.\n지표를 버리는 일에 대하여 VWAP을 뺀 이야기를 조금 더 하고 싶습니다. 지표를 넣는 것보다 빼는 게 훨씬 어렵거든요.\n지표를 못 버리게 만드는 것들 · 넣는 데 시간을 썼다 매몰 비용 · 논리가 그럴듯하다 설명이 되니까 맞는 것 같다 · 가끔 잘 맞는 사례가 있다 기억에 남는 건 맞은 쪽 · 빼면 뭔가 허전하다 조건이 줄면 불안하다 -------------------------------------------------- 판단 기준은 하나여야 한다: 이 지표를 빼면 결과가 나빠지는가? 이 시대엔 그 질문에 답할 데이터가 없었다 세 번째가 특히 함정입니다. 지표가 극적으로 잘 맞은 사례는 기억에 오래 남아요. 반대로 조용히 방해만 한 수십 건은 기억에 안 남습니다. 그래서 체감상으로는 항상 \u0026ldquo;이 지표 넣길 잘했다\u0026quot;가 됩니다.\n이걸 깨려면 숫자가 필요합니다. 지표를 뺀 버전과 넣은 버전을 같은 데이터로 돌려서 비교하는 것요. 이 시대에는 그게 안 됐고, 그래서 VWAP을 빼는 결정도 \u0026ldquo;자동화가 어렵다\u0026quot;는 실무적 이유로 내렸습니다. 성적 근거가 아니었어요.\n나중에 매매기록 시뮬레이션이 생기고 나서 알게 된 게 있습니다. 조건을 추가했는데 통행량만 죽고 성적은 그대로인 경우가 꽤 많다는 것. 매매가 줄면 손실도 줄어서 좋아 보이는데, 건당 기대값은 안 변했어요. 좋은 필터를 얻은 게 아니라 그냥 문을 좁힌 겁니다.\n그래서 지금은 게이트를 추가할 때 반드시 두 가지를 같이 봅니다. 건당 기대값이 올라갔는지, 그리고 매매 건수가 얼마나 줄었는지. 기대값이 그대로인데 건수만 줄었다면 그 게이트는 아무 일도 안 하는 겁니다.\nC++로 옮기려다 접은 이야기 이 시기 흔적 중에 C++ 프로젝트가 하나 있습니다. 파이썬 봇을 C++로 포팅하려던 시도예요. websocketpp로 통신을 짜고 jwt-cpp로 인증을 붙이다가, 완성 전에 멈췄습니다.\n왜 시작했고 왜 접었나 시작한 이유 파이썬이 느리다는 막연한 불안 — 체결 속도에서 밀리는 것 아닌가 접은 이유 1. 병목이 파이썬이 아니었다 — 대부분의 지연은 네트워크와 거래소 응답 2. 개발 속도가 절반 이하로 떨어졌다 — 하루 열 번 고치던 리듬이 죽음 3. 눌림목 전략은 애초에 밀리초 싸움이 아니다 (15분봉 기준) 교훈: 최적화는 측정 후에. 느낌으로 하는 최적화는 시간만 쓴다 돌이켜보면 이건 전형적인 조기 최적화였습니다. 느릴 것 같다는 느낌으로 언어를 바꾸려 했는데, 실제로 어디가 느린지 측정한 적이 없었어요.\n한 번의 매매에서 시간이 쓰이는 곳 시세 수신 대기 거래소가 보내줄 때까지 지표 계산 파이썬 · 매우 짧음 게이트 판정 파이썬 · 매우 짧음 주문 전송 → 응답 네트워크 왕복 + 거래소 처리 체결 확인 거래소 응답 대기 -------------------------------------------------- 빨간 부분이 대부분 — 언어를 바꿔도 그대로다 초록 부분을 100배 빠르게 해도 총 시간은 거의 안 준다 나중에 재보니 지연의 대부분은 거래소 응답 대기였고, 그건 언어를 바꿔도 그대로입니다. 계산 부분을 아무리 빠르게 만들어도 전체 시간에서 차지하는 비중이 작으니 체감이 안 돼요.\n그리고 더 중요한 손실이 있었습니다. C++로 옮기는 동안 전략 개선이 멈췄어요. 이 봇에서 가장 값진 자원은 실행 속도가 아니라 고치고 확인하는 사이클의 속도였는데, 그걸 스스로 절반으로 떨어뜨린 겁니다.\n포팅이 실제로 바꾼 것 얻으려던 것 실행 속도 (체감 안 됨 · 병목이 아니었음) 잃은 것 개발 사이클 속도 (절반 이하) 전략 개선 기간 (정지) 디버깅 난이도 (상승) -------------------------------------------------- 혼자 만드는 봇에서 최적화할 대상 1순위는 코드 실행 속도가 아니라 사람의 반복 속도다 결국 파이썬으로 돌아왔습니다. 이 판단이 늦었으면 지금 봇이 없었을 겁니다.\n조기 최적화를 알아보는 법 같은 함정에 빠지지 않으려고 기준을 하나 정해뒀습니다.\n최적화 전에 던지는 질문 1. 지금 몇 밀리초가 걸리는지 아나? 모르면 → 측정부터. 최적화는 그다음 2. 그 시간이 전체의 몇 %인가? 10% 미만이면 → 절반으로 줄여도 5% 개선 3. 이 개선이 전략 성적에 영향을 주나? 15분봉 전략에서 0.1초 → 영향 없음 4. 이 작업 동안 멈추는 것은 무엇인가? 전략 개선이 멈춘다면 → 기회비용이 더 크다 -------------------------------------------------- 네 질문 다 답할 수 있으면 해도 된다 4번이 특히 중요합니다. 최적화 작업의 비용은 그 작업에 든 시간만이 아니라 그동안 못 한 일이에요. 혼자 개발하면 동시에 두 가지를 못 하니까 이 비용이 큽니다.\n이 시기에 배운 걸 한 문장으로 줄이면, 성능은 문제가 됐을 때 고치는 것입니다. 문제가 될 것 같아서 미리 고치면 대개 문제가 아닌 걸 고치게 돼요.\n언어를 바꾸려던 진짜 이유 솔직하게 하나 더 적겠습니다. C++ 포팅을 시작한 데는 속도 말고 다른 이유도 있었습니다.\n그때 전략이 잘 안 되고 있었어요. 파라미터를 만져도 안 되고, 조건을 바꿔도 안 되고. 그 상태에서 \u0026ldquo;언어를 바꾸면 뭔가 달라질지도\u0026quot;라는 생각이 들었습니다. 논리적인 판단이 아니라 막힌 상태에서 다른 일로 도망친 것에 가까웠어요.\n막혔을 때 하게 되는 일들 · 언어를 바꾼다 · 프레임워크를 바꾼다 · 코드를 전면 재작성한다 · 새 지표를 붙인다 -------------------------------------------------- 공통점: 진척이 눈에 보인다 (커밋이 쌓인다) 공통점: 원래 막혀 있던 문제는 그대로다 -------------------------------------------------- 막혔을 때 실제로 필요한 것: 왜 막혔는지 측정하기 → 이 시대엔 측정 수단이 없었다. 그래서 도망쳤다 이런 종류의 도망은 알아채기 어렵습니다. 열심히 일하고 있고, 커밋도 쌓이고, 기술적으로도 배우는 게 있으니까요. 그런데 정작 막힌 문제는 그대로예요.\n지금은 막히면 데이터를 봅니다. 어느 게이트에서 몇 개가 떨어지는지, 손절과 익절의 비율이 어떤지, 어떤 시간대에 성적이 나쁜지. 측정할 수 있으면 도망칠 필요가 없어요. 측정 수단이 없는 상태가 조기 최적화를 부릅니다.\n이 시기가 남긴 개인적인 교훈은 이겁니다. 새로운 기술로 넘어가고 싶어질 때, 그게 정말 필요해서인지 아니면 지금 문제가 안 풀려서인지 한 번 물어보기. 대개 후자더군요.\n이 시대의 정체성 VVWAP기는 짧고 어수선한 시대입니다. 주력 지표는 결국 빠졌고, C++ 포팅은 미완으로 끝났어요. 그런데 남긴 게 셋 있습니다.\nVVWAP기가 남긴 것 1. 이름 VV — 461개를 하나의 계보로 묶는 접두어 2. 레짐 판단 시장 전체를 보는 첫 코드 → 현행 BTC 4시간봉 하락장 진입 차단 3. 완성봉 원칙 판단 주기는 행동 주기보다 느려야 → 재현 가능성 · 검증 가능성의 전제 + 조기 최적화라는 실패 사례 한 건 -------------------------------------------------- 실패한 시도가 남긴 규칙이 성공한 코드보다 오래 남는다 2번과 3번이 사실 한 쌍입니다. 시장 판단이라는 강력한 규칙을 만들면서, 그 규칙이 흔들리지 않게 하는 방법도 같이 정한 거예요. 좋은 규칙과 그 규칙을 안정적으로 적용하는 방법은 별개의 문제인데, 이 시대에 둘을 같이 배웠습니다.\n다음으로 이 시대 마지막 즈음, 파일 하나로는 더 못 버티겠다는 게 확실해집니다. 모놀리스의 문제는 3시대에 이미 알고 있었는데, 실제로 손을 대게 된 건 여기서였어요. 아는 것과 하는 것 사이에 시대가 두 개 있었습니다.\n그리고 개발 도구도 바뀝니다. 7월 25일 새벽, 이 저장소의 커밋 목록에 처음으로 AI 협업 흔적이 남아요. 그 얘기는 다음 편에서 하겠습니다.\n이 시대에서 가져갈 것 첫째, 좋은 지표와 자동화하기 좋은 지표는 다릅니다. 앵커드 VWAP은 사람이 쓰기엔 훌륭한데 봇으로 옮기려니 앵커 선택이라는 새 문제를 만들었어요. 지표를 고를 때는 \u0026ldquo;이게 유용한가\u0026quot;뿐 아니라 \u0026ldquo;이걸 사람 판단 없이 계산할 수 있나\u0026quot;를 같이 물어야 합니다.\n둘째, 판단 주기는 행동 주기보다 느려야 합니다. 완성봉 원칙이 그겁니다. 판정이 행동보다 자주 뒤집히면 봇은 아무것도 못 하고, 결과를 재현할 수도 없어요. 재현되지 않으면 검증도 안 되고, 검증 안 된 값은 배포하면 안 됩니다. 이 사슬의 첫 고리가 완성봉이었습니다.\n셋째, 최적화는 측정 다음입니다. 몇 밀리초인지 모르는 상태에서 언어를 바꾸는 건 도박이에요. 그리고 그 작업 동안 멈추는 것의 가치를 같이 계산해야 합니다. 혼자 만드는 봇에서 제일 값진 자원은 실행 속도가 아니라 반복 속도입니다.\n넷째, 막혔을 때 새 기술로 도망치지 마세요. 이건 저에게 하는 말입니다. 진척이 눈에 보이는 일과 문제를 푸는 일은 다릅니다. 막히면 측정할 방법부터 만드는 게 맞더군요.\n이름 하나 남기고 지표는 버린 시대. 그런데 그 이름이 지금까지 붙어 있다는 게, 이 프로젝트가 무엇을 중요하게 여기는지 보여준다고 생각합니다. 연속성이요.\n","permalink":"https://botlab.co.kr/archive/05-vvwap/","summary":"지금 봇 이름 앞에 붙은 VV는 VWAP에서 왔습니다. 정작 그 지표는 지금 안 쓰는데도요.","title":"5. VV라는 이름이 생긴 곳 | Crypto Trading Bot"},{"content":"왜 여러 개를 만들었나 단일 파일 시대의 답답함은 코드 구조 문제만이 아니었습니다. 더 근본적인 의심이 있었어요. 지금 이 전략이 맞긴 한 건가?\n돌파를 노리는 전략 하나를 계속 고치고 있었는데, 파라미터를 아무리 만져도 결과가 크게 안 달라졌습니다. 그러면 문제가 파라미터가 아니라 전략일 수 있잖아요. 그걸 확인하려면 다른 전략을 옆에 놓고 비교해야 했습니다.\n같은 증상, 두 가지 해석 증상: 값을 어느 쪽으로 바꿔도 결과가 비슷하다 -------------------------------------------------- 해석 A 아직 최적값을 못 찾았다 → 더 촘촘히 탐색한다 해석 B 이 전략의 한계가 여기다 → 다른 전략과 비교해야 안다 -------------------------------------------------- A 로만 몇 주를 보냈다. B 를 시험할 방법이 없었기 때문이다 → 그래서 비교 대상을 직접 만들기로 했다 혼자 만드는 봇의 한계가 여기 있습니다. 비교 대상이 없어요. 성적이 나쁠 때 그게 전략 탓인지, 파라미터 탓인지, 그냥 장이 안 좋았던 건지 구분이 안 됩니다. 같은 기간 다른 전략을 돌려본 적이 없으니까요.\n그래서 갈래를 나눴습니다.\n동시에 굴린 갈래들 HY 하이브리드 (36개) 스윙 + 단타 혼합 느린 흐름과 빠른 매매를 한 봇 안에서 같이 돌린다 BASE Bottom Area Support Entry (11개) 바닥권 지지 구간에서 산다 — 지금 전략의 직계 조상 MACRO (56개) 개별 종목이 아니라 시장 전체 흐름을 먼저 본다 빗썸 분기 (3개) 같은 로직을 다른 거래소에서 — 거래소 차이 확인용 버전 개수가 흥미롭습니다. MACRO가 56개로 제일 많고 BASE가 11개로 제일 적어요. 그런데 지금 봇에 남은 건 BASE입니다. 손을 많이 댄 것과 남은 것이 반대였습니다.\n각각에서 배운 것 하이브리드 — 자원을 나눠 쓰면 둘 다 나빠집니다 하이브리드는 욕심의 결과였습니다. 스윙으로 큰 걸 먹으면서 단타로 잔돈도 벌자는 발상이었는데, 실제로 돌려보니 두 전략이 서로 방해했습니다.\n한 계좌에 두 전략을 넣으면 공유 자원: 슬롯 · 현금 · 주의력 -------------------------------------------------- 단타가 슬롯을 채우고 있으면 → 스윙 기회가 와도 못 들어간다 스윙 포지션이 며칠 잡혀 있으면 → 단타 회전이 죽는다 손절 기준이 서로 다르면 → 같은 종목을 다른 규칙으로 다루게 된다 -------------------------------------------------- 결론: 각각 혼자 돌 때보다 나빠질 수 있다 자원(슬롯, 현금)이 유한한데 성격이 다른 두 전략이 그걸 나눠 쓰면, 각각이 혼자 돌 때보다 나빠질 수 있다는 걸 배웠습니다. 이건 전략의 우열 문제가 아니라 자원 배분 구조의 문제예요.\n더 골치 아팠던 건 어느 쪽이 잘못했는지 알 수 없다는 겁니다. 성적이 나쁘면 스윙이 문제인지 단타가 문제인지 구분이 안 돼요. 두 전략이 같은 계좌에서 서로 영향을 주니까 각각을 따로 평가할 수 없습니다. 실험 설계 자체가 잘못된 거였죠.\n여기서 얻은 원칙이 하나 있습니다. 한 봇은 한 성격만. 지금 봇은 눌림목 하나만 봅니다. 다른 기회가 보여도 안 갑니다. 기회를 놓치는 손해보다 규칙이 흐려지는 손해가 크다고 판단해서예요.\nBASE — 이 시대의 진짜 수확 BASE는 이 시대의 진짜 수확입니다. 이름 그대로 바닥권 지지 구간에서 진입하는 전략인데, 지금 봇이 쓰는 과매도 눌림목 전략의 직계 조상이에요.\n돌파 ↔ 바닥권, 정반대의 전제 [ 돌파 ] 저항선을 뚫으면 계속 간다 전제: 추세가 이어진다 추세장에서 강함 박스권에서 → 뚫고 다시 내려온다 (가짜 돌파) [ 바닥권 ] 많이 빠진 건 되돌아온다 전제: 평균으로 회귀한다 박스권에서 강함 하락 추세에서 → 계속 빠진다 (칼날 잡기) \u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026ndash; 둘 다 맞고 둘 다 틀리다 — 어떤 장이냐에 달렸다\n돌파를 쫓는 것과 빠진 걸 줍는 것은 방향이 정반대인데, 박스권에서는 후자가 확실히 나았습니다. 돌파는 박스권에서 대부분 가짜였거든요. 저항선을 살짝 뚫고 바로 되돌아옵니다. 그 살짝 뚫는 순간에 사면 고점에 사는 겁니다.\n다만 바닥권 전략에도 명확한 약점이 있습니다. 하락 추세에서는 바닥이 계속 갱신돼요. \u0026ldquo;많이 빠졌다\u0026quot;고 산 자리가 다음 날 더 빠집니다. 이 약점을 막는 방법이 결국 시장 상황 판단인데, 그게 다음 갈래의 주제였습니다.\nMACRO — 완성 못 했지만 문제의식은 남았습니다 MACRO는 시야를 한 단계 위로 올린 실험입니다. 개별 종목의 조건이 아무리 좋아도 시장 전체가 흘러내리면 소용없다는 문제의식이었어요.\n판단의 층위 3층 시장 전체 지금 살 때인가? → 아니면 아무것도 안 한다 2층 종목 선택 무엇을 살까? → 게이트와 랭킹 1층 진입 타이밍 얼마에 살까? → 눌림목 대기 -------------------------------------------------- 이때까지 봇은 1·2층만 있었다 3층이 없으면 하락장에서 열심히 잘 산다 이 발상은 당시엔 완성 못 했습니다. 시장 전체를 어떤 지표로 볼지, 어느 주기로 볼지, 판단이 뒤집힐 때 어떻게 할지 — 정할 게 너무 많았어요. 56개 버전을 만들고도 결론이 안 났습니다.\n그런데 문제의식 자체는 정확했습니다. 나중에 BTC 4시간봉으로 시장 상황을 판단해서 하락장에는 아예 안 사는 규칙으로 돌아옵니다. 56개 버전이 만든 건 코드가 아니라 \u0026ldquo;3층이 필요하다\u0026quot;는 확신이었어요.\nMACRO에서 정하기 어려웠던 것들을 구체적으로 적어두면 이렇습니다.\n\"지금이 어떤 장인가\" 를 코드로 옮기려면 무엇으로 보나 BTC? 시가총액 상위 평균? 전체 지수? 어느 주기로 15분? 1시간? 4시간? 일봉? 어떤 기준으로 이동평균 대비? 최근 고점 대비? 변동성? 판정이 뒤집히면 보유 중인 포지션은 어떻게 하나? 경계에 걸리면 상승/하락 사이에 중립 구간을 둘 것인가? -------------------------------------------------- 질문 다섯 개가 곱해지면 조합이 수십 가지 검증 수단이 없는 상태에서 56개 버전으로도 못 좁혔다 네 번째 질문이 특히 까다로웠습니다. 하락장으로 판정됐을 때 신규 진입만 막을 건지, 보유분도 정리할 건지요. 정리하면 반등을 놓치고, 안 하면 하락을 그대로 맞습니다.\n지금은 신규 진입만 막습니다. 보유분은 원래 규칙대로 관리해요. 이유는 앞서 말한 것과 같습니다 — 판정이 틀렸을 때 대가가 작은 쪽이니까요. 잘못 막으면 기회를 놓치고, 잘못 정리하면 실제 손실이 확정됩니다.\n다섯 번째 질문도 나중에야 답이 나왔습니다. 완성된 4시간봉만 쓰기로 한 것이요. 진행 중인 봉으로 판단하면 몇 분마다 판정이 뒤집히는데, 그러면 봇이 진입을 열었다 닫았다만 반복합니다. 판단의 주기가 행동의 주기보다 짧으면 아무것도 못 합니다.\n빗썸 분기 — 거래소는 변수입니다 빗썸 분기는 짧게 끝났습니다. 같은 로직도 거래소가 다르면 호가 단위와 유동성 때문에 결과가 꽤 달라진다는 것만 확인하고 접었어요.\n확인한 것 자체는 값집니다. 파라미터는 거래소에 종속된다는 뜻이니까요. 한쪽에서 최적인 손절 폭이 다른 쪽에서는 너무 좁을 수 있습니다. 유동성이 얕으면 같은 뉴스에도 더 크게 흔들리거든요. 값을 거래소와 함께 기록해둬야 한다는 걸 이때 배웠습니다.\n예상과 달랐던 결론 여러 개를 돌리면 승자가 나올 줄 알았습니다. 실제로는 그렇게 안 됐어요.\n병렬 실험이 실제로 준 것 기대했던 것 \"네 개 중 제일 좋은 하나를 고른다\" 실제 결과 어느 것도 압도적이지 않았다 — 장세에 따라 순위가 바뀜 진짜 수확 1 전략마다 잘 되는 장이 다르다는 걸 몸으로 확인 진짜 수확 2 그래서 \"지금이 어떤 장인가\" 를 판단하는 게 먼저다 진짜 수확 3 여러 봇을 동시 관리하는 건 혼자서 감당이 안 된다 순위가 장세에 따라 바뀐다는 게 처음엔 실망스러웠습니다. 답이 없다는 뜻처럼 들렸거든요. 그런데 이게 오히려 답이었어요. 전략을 고르는 문제가 아니라 장을 판단하는 문제였던 겁니다.\n여기서 갈림길이 하나 있었습니다. 장에 따라 전략을 갈아 끼우는 봇을 만들 수도 있었어요. 추세장이면 돌파, 박스권이면 바닥권. 매력적인 설계입니다.\n전략 자동 전환을 안 한 이유 [ 전략 전환형 ] 장세 판정 → 그에 맞는 전략 실행 문제 1 장세 판정이 틀리면 두 배로 틀린다 문제 2 판정이 자주 뒤집히면 전략이 계속 바뀐다 문제 3 검증할 조합이 폭발한다 (전략 × 판정 기준) [ 채택한 방식 ] 전략은 하나 · 장세는 진입 차단에만 사용 틀려도 최악이 \u0026ldquo;안 사는 것\u0026rdquo; \u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026ndash; 틀렸을 때의 대가가 작은 쪽을 골랐다\n지금 봇이 하락장에 전략을 바꾸는 대신 아예 안 사는 이유가 이겁니다. 판정이 틀렸을 때 대가가 다릅니다. 전환형은 틀리면 잘못된 전략으로 매매하고, 차단형은 틀리면 기회를 놓칠 뿐이에요. 놓친 기회는 아프지만 계좌를 깎지는 않습니다.\n실험을 제대로 하려면 필요했던 것들 이 시기에 실험이 결론을 못 낸 이유를 지금 시점에서 정리해보면, 방법론이 없었습니다.\n당시 실험에 없었던 것 ✘ 같은 기간·같은 자금 조건 갈래마다 시작 시점과 배분이 달랐다 ✘ 표본 수 기준 몇 건이 쌓여야 판단할지 정하지 않았다 ✘ 평가 지표 합의 수익률? 승률? 손익비? 그날그날 다른 걸 봤다 ✘ 코드 동기화 갈래마다 다른 수정이 들어감 -------------------------------------------------- → 네 개를 돌렸지만 \"비교\" 는 못 했다. 관찰만 했다 세 번째가 특히 문제였습니다. 평가 지표를 미리 안 정해두면 결과를 보고 나서 유리한 지표를 고르게 됩니다. A가 승률이 높고 B가 수익률이 높으면, 그날 기분에 따라 승자가 바뀌어요. 이건 실험이 아니라 해석입니다.\n지금은 지표를 먼저 정합니다. 건당 기대값과 손익비를 봅니다. 승률만 보면 안 되는 이유는, 승률은 익절 폭을 좁히기만 해도 올라가기 때문이에요. 작은 이익을 자주 확정하고 큰 손실을 가끔 맞으면 승률은 높은데 계좌는 줍니다. 지표를 하나만 보면 그 지표를 속이는 방향으로 최적화됩니다.\n표본 수 기준도 마찬가지입니다. 며칠 성적으로 순위를 매기면 그날 장세에 맞았던 전략이 이깁니다. 그런데 우리가 알고 싶은 건 \u0026ldquo;이 전략이 여러 장세에 걸쳐 어떤가\u0026quot;예요. 이 둘을 구분하려면 최소 몇 건, 최소 몇 주 같은 기준이 필요한데 그때는 없었습니다.\n관리 비용이 실험을 끝냈습니다 세 번째 수확이 이 시대를 끝냈습니다. 봇 네 개를 동시에 돌리면 배포도 네 번, 모니터링도 네 번, 사고도 네 배입니다.\n갈래 N개를 동시에 굴리는 비용 갈래 하나가 늘 때마다 · 배포 절차 × N · 모니터링 대상 × N · 버그 수정을 손으로 복사 × N · 계좌·자금 배분 관리 × N -------------------------------------------------- 게다가 갈래마다 코드가 조금씩 달라진다 → 어느 갈래에 어떤 수정이 들어갔는지 헷갈린다 → 비교 실험의 전제(같은 조건)가 무너진다 마지막 줄이 치명적이었습니다. 실험을 하려면 조건이 같아야 하는데, 갈래마다 손을 대다 보니 어느새 서로 다른 봇이 돼 있었어요. A에서 버그를 고치고 B에는 안 옮기면, 두 갈래의 성적 차이가 전략 차이인지 버그 차이인지 알 수 없습니다. 실험이 실험이 아니게 된 겁니다.\n이걸 제대로 하려면 공통 코드를 라이브러리로 빼고 전략만 갈아 끼우는 구조가 필요합니다. 그런데 그건 이 시대에는 불가능했어요. 아직 모놀리스였으니까요. 병렬 실험이 제대로 되려면 먼저 모듈화가 됐어야 했다는 게 순서상의 아이러니입니다.\n실험의 가치보다 관리 비용이 커지는 지점이 왔습니다.\n무엇을 남기고 무엇을 버렸나 그래서 정리하기로 합니다.\n정리의 결과 BASE 접근법을 본류로 가져온다 → 과매도 눌림목 전략의 뿌리 MACRO 문제의식을 규칙 하나로 압축한다 → 하락장 신규 진입 차단 하이브리드 접는다 → \"한 봇은 한 성격\" 원칙만 남김 빗썸 분기 접는다 → \"값은 거래소에 종속\" 이라는 사실만 남김 -------------------------------------------------- 106개 버전 → 살아남은 것: 접근법 1개 + 규칙 1개 + 원칙 2개 여러 개를 만들어본 덕에 무엇을 버릴지 알게 된 셈입니다. 실험의 목적은 승자를 뽑는 게 아니라 선택지를 줄이는 것이었더군요.\n그리고 버린 것들도 빈손은 아니었습니다. 하이브리드는 \u0026ldquo;한 봇은 한 성격\u0026quot;을 남겼고, 빗썸 분기는 \u0026ldquo;값은 거래소에 속한다\u0026quot;를 남겼습니다. 코드는 안 남았지만 규칙은 남았어요. 아카이브를 정리하면서 제일 자주 확인하는 게 이겁니다. 버려진 시도가 남긴 문장이 살아남은 코드보다 오래갑니다.\nBASE라는 이름에 대해 지금 봇의 전략을 설명할 때 \u0026ldquo;과매도 눌림목 반등\u0026quot;이라고 부르는데, 이 이름의 절반은 첫 시대의 v1.09에서, 나머지 절반은 이 시대의 BASE에서 왔습니다.\n전략 이름의 계보 v1.09 (1시대) \"조건이 서도 -0.5% 더 빠지면 산다\" → 눌림목 = 진입 가격을 낮추는 장치 BASE (4시대) \u0026ldquo;바닥권 지지 구간에서 산다\u0026rdquo; → 과매도 = 어떤 상태의 종목을 볼 것인가\n현행 과매도 상태 종목을 고르고 · 눌림목에서 진입한다 두 개는 다른 층위의 개념이라 합칠 수 있었다\n이 둘이 합쳐질 수 있었던 건 층위가 달랐기 때문입니다. 하나는 종목 선택 기준이고 다른 하나는 진입 가격 기준이에요. 같은 층위의 아이디어였다면 하나를 버려야 했을 겁니다.\n아카이브를 정리하면서 이런 게 자주 보입니다. 서로 다른 시대에 나온 아이디어가 나중에 결합되는 경우요. 당시에는 각각 독립적인 시도였는데, 층위가 다르니 충돌하지 않고 겹쳐진 겁니다. 좋은 아이디어를 버리지 않고 쌓아두면 나중에 조합됩니다. 그래서 접은 갈래의 코드도 안 지웠어요.\n여러 개를 만들어본 것 자체의 값 이 시대를 실패로 분류할 수도 있습니다. 106개 버전을 만들고 세 갈래를 접었으니까요. 그런데 저는 이 시기를 가장 잘 쓴 시간 중 하나로 봅니다.\n병렬 실험이 없었다면 돌파 전략 하나만 계속 고쳤을 경우 -------------------------------------------------- · 파라미터 탐색을 몇 달 더 했을 것 · \"전략 자체가 문제\" 라는 가설을 못 세웠을 것 · 바닥권 접근을 시도할 계기가 없었을 것 · 시장 판단 층의 필요성을 몰랐을 것 -------------------------------------------------- 106개는 비쌌지만, 안 만들었으면 더 비쌌다 특히 \u0026ldquo;전략 자체가 문제일 수 있다\u0026quot;는 가설을 세운 게 컸습니다. 하나만 붙들고 있으면 이 가설이 잘 안 떠올라요. 내가 만든 것이니 애착도 있고, 조금만 더 만지면 될 것 같거든요. 옆에 다른 게 돌아가고 있으면 비교가 되니까 그 애착이 깨집니다.\n혼자 개발할 때 제일 부족한 게 반대 의견입니다. 병렬 실험은 반대 의견을 코드로 만들어 옆에 놓는 일이었어요. 지금은 그 역할을 매매기록 시뮬레이션이 합니다. 내가 좋다고 생각한 값을 데이터가 반박해주니까요.\n이 시대에서 가져갈 것 첫째, 비교 대상이 없으면 판단할 수 없습니다. 성적이 나쁠 때 전략 탓인지 장 탓인지 구분하려면 같은 기간에 돌아간 다른 것이 있어야 합니다. 봇이 하나뿐이면 그 봇의 성적은 절대 평가할 수 없어요. 지금은 매매기록 시뮬레이션이 그 역할을 합니다 — 같은 데이터에 다른 값을 넣어 비교하니까요. 병렬 실험의 목적을 훨씬 싸게 달성하는 방법입니다.\n둘째, 실험에는 인프라가 먼저 필요합니다. 전략을 비교하려면 나머지 조건이 같아야 하는데, 모놀리스에서는 그게 유지되지 않습니다. 이 시대의 실험이 결론에 도달하지 못한 건 아이디어가 나빠서가 아니라 실험 환경이 없어서였어요. 비교하고 싶으면 먼저 공통 부분을 고정할 수 있어야 합니다.\n셋째, 틀렸을 때의 대가가 작은 쪽을 고르세요. 장세 판정으로 전략을 바꾸는 설계와 진입만 차단하는 설계 중에 후자를 골랐는데, 이유는 정확도가 아니라 실패 비용이었습니다. 자동매매에서 설계 선택의 기준은 대개 \u0026ldquo;맞을 확률\u0026quot;보다 \u0026ldquo;틀렸을 때 얼마나 아픈가\u0026quot;입니다.\n넷째, 한 봇은 한 성격만. 하이브리드에서 배운 겁니다. 좋은 아이디어 두 개를 한 계좌에 넣으면 자원을 나눠 쓰면서 둘 다 나빠질 수 있어요. 그리고 성적이 나빠도 어느 쪽 탓인지 알 수 없게 됩니다. 기회를 놓치는 손해보다 판단의 근거가 흐려지는 손해가 큽니다.\n106개 버전, 네 갈래. 답을 하나 고르려고 시작해서 질문을 바꾸게 된 시기였습니다.\n\u0026ldquo;어떤 전략이 제일 좋은가\u0026quot;로 시작해서 \u0026ldquo;지금이 무엇을 할 때인가\u0026quot;로 끝났어요. 그리고 그 질문은 전략보다 상위에 있습니다. 아무리 좋은 전략도 안 맞는 장에서는 지고, 그럭저럭인 전략도 맞는 장에서는 버팁니다. 지금 봇에 레짐 차단이 들어 있는 건 이 시대가 남긴 유일하지만 가장 큰 결론입니다.\n다음 시대에는 이름이 하나 붙습니다. 지금까지 이어지는 VV라는 이름이요. 그리고 시장 판단이 처음으로 실제 코드가 됩니다.\n","permalink":"https://botlab.co.kr/archive/04-parallel/","summary":"하나를 계속 고치는 대신 여러 개를 만들어 경쟁시켰던 시기. 결론은 예상과 달랐습니다.","title":"4. 전략 네 개를 동시에 돌려봤습니다 | Crypto Trading Bot"},{"content":"한 파일이 1,500줄이 되기까지 이 시대 코드는 파일 하나입니다. 통신도, 지표 계산도, 진입 판단도, 주문 실행도, 화면 출력도 전부 한 .py 안에 있었어요. 처음엔 그게 편했습니다. 뭘 고치려면 그 파일만 열면 되니까.\n단일 파일이 나쁘다고 단정할 순 없습니다. 오히려 초기에는 유리해요. 파일을 옮겨 다닐 필요가 없고, import 구조를 고민할 필요도 없고, 어떤 값이 어디 있는지 검색 한 번이면 나옵니다. 하루에 열 번 고치던 리듬에는 이게 맞았습니다.\n문제는 임계점이 있다는 겁니다. 대략 몇백 줄까지는 단일 파일이 빠르고, 그 위로 가면 급격히 느려집니다. 그리고 그 임계점은 서서히 오는 게 아니라 어느 날 갑자기 체감돼요.\n버전 번호가 이 시기에 이상하게 움직입니다. v091에서 시작해 v9xx까지 가는데, 백 자리가 바뀌면 대개 큰 변화가 있었다는 뜻이었습니다.\n버전 번호가 곧 세대였다 v09x 빗썸에서 막 넘어온 직후 v1xx 구조 정리 1차 v2xx 지표 확장 v3xx 진입 조건 재설계 v4xx 청산 로직 재설계 v5xx ~ v7xx 반복 개선 v9xx 마지막 세대 — v901 에서 스캐너 역할 축소 코드 길이: 1,500 ~ 2,000줄 (단일 파일) 버전 번호가 백 단위로 뛴 건 자신감이 아니라 시행착오의 개수였다 목록을 다시 보면 재밌는 게 있습니다. v3xx가 진입 재설계, v4xx가 청산 재설계예요. 진입을 먼저 다 만지고 나서야 청산으로 넘어갔다는 뜻입니다. 대부분 이 순서로 갑니다. 사는 게 재밌고 파는 건 귀찮으니까요. 그런데 실제로 성적을 결정하는 비중은 반대에 가깝습니다. 이 순서를 되짚어보게 되는 게 훨씬 나중이에요.\n무엇이 문제였나 모놀리스의 문제는 코드가 길다는 게 아닙니다. 어디를 고치면 어디가 부서지는지 알 수 없다는 게 문제입니다.\n진입 조건을 하나 바꿨는데 화면 출력이 깨지고, 화면 출력을 고쳤더니 상태 저장이 안 되고. 이유는 단순합니다. 같은 변수를 여기저기서 직접 만지고 있었으니까요. 전역 변수 하나를 열 군데에서 읽고 쓰면, 그 변수의 값이 언제 어떻게 바뀌는지는 아무도 모릅니다.\n공유 상태가 만드는 미로 ┌──▶ 스캔 루프에서 읽고 쓴다 positions ─┼──▶ 청산 판정에서 읽고 쓴다 (전역) ├──▶ 화면 출력에서 읽는다 ├──▶ 상태 저장에서 읽는다 └──▶ 수동 제어에서 직접 수정한다 -------------------------------------------------- 질문: 이 값이 지금 왜 이렇게 돼 있나? 답: 다섯 군데를 다 읽어봐야 안다 현행: 상태는 한 모듈이 소유하고 나머지는 함수로 요청 이런 구조에서는 버그를 고치는 데 걸리는 시간이 코드 길이에 비례하지 않고 연결 개수에 비례해서 늡니다. 줄이 두 배가 되면 시간이 두 배가 아니라 네 배가 돼요. 하루에 열 번 고치던 리듬이 하루에 세 번으로 떨어지고, 결국 하루에 한 번이 됩니다.\n여기에 더 큰 문제가 있었습니다. 매매 기록을 체계적으로 안 남겼습니다. 봇은 돌고 있었고 사고팔고 있었는데, 그 기록이 분석 가능한 형태로 쌓이지 않았어요. 그래서 파라미터를 바꿀 때 근거가 없었습니다. \u0026ldquo;이게 나을 것 같다\u0026quot;로 바꾸고, 며칠 지켜보고, 느낌이 안 좋으면 되돌리고. 지금 돌아보면 이 시기 버전 절반은 안 만들어도 되는 것들이었습니다. 기록이 있었다면 데이터가 답을 줬을 테니까요.\n근거 없는 튜닝이 만드는 순환 기록 없이 파라미터를 만지면 어떤 일이 벌어지는지 정리해둘 만합니다. 이 시기 버전 절반이 여기서 나왔거든요.\n근거 없는 튜닝의 순환 값을 바꾼다 ↓ 며칠 지켜본다 ← 표본이 너무 작다 ↓ 성적이 나쁘다 ↓ 값 때문인지 장세 때문인지 모른다 ↓ 되돌리거나 또 바꾼다 ↓ 처음으로 -------------------------------------------------- 이 순환에서는 아무것도 축적되지 않는다 같은 값을 몇 주 뒤에 또 시도하게 된다 며칠은 표본이 아닙니다. 특히 진입 빈도가 낮은 전략에서는 며칠에 매매가 몇 건밖에 안 나와요. 그 몇 건으로 값의 우열을 판단하는 건 동전 세 번 던져서 앞면이 많이 나왔으니 이 동전은 앞면이 잘 나온다고 결론 내는 것과 같습니다.\n더 나쁜 건 되돌린 기록도 안 남는다는 겁니다. 그러면 몇 주 뒤에 같은 값을 다시 시도하게 돼요. 실제로 이 시기 버전 목록을 보면 비슷한 변경이 여러 번 반복됩니다. 앞에서 이미 해봤다는 걸 몰랐던 거죠.\n지금은 파라미터를 바꿀 때 매매기록 전체를 그 값으로 다시 돌려봅니다. 며칠이 아니라 축적된 전 구간이요. 그리고 결과 수치를 커밋 메시지에 같이 넣습니다. 그러면 몇 주 뒤에 같은 값을 떠올려도 \u0026ldquo;이미 해봤고 결과는 이랬다\u0026quot;가 바로 나옵니다. 기록은 실험을 반복하지 않게 해주는 장치입니다.\n왜 그때 안 쪼갰나 지금 이 글을 읽는 분은 당연히 이렇게 생각할 겁니다. 진작 파일을 쪼개지 그랬냐고. 저도 같은 생각을 여러 번 했습니다. 그래서 왜 안 했는지를 정직하게 적어두겠습니다.\n쪼개지 못한 이유들 1. 쪼개는 동안 개발이 멈춘다 그날 고치고 싶은 게 있는데 리팩터링부터 하라니 2. 어디서 쪼개야 할지 몰랐다 경계를 잘못 그으면 모듈이 서로를 계속 호출한다 = 파일만 늘고 복잡도는 그대로 3. 지금도 돌아가고 있다 돈을 벌든 잃든 봇은 실행 중이었다 멈추고 뜯는 데 심리적 저항이 컸다 -------------------------------------------------- 2번이 진짜 이유였다 — 경계는 겪어봐야 보인다 2번이 핵심입니다. 리팩터링의 어려움은 쪼개는 작업 자체가 아니라 어디서 쪼갤지 정하는 것이에요. 경계를 잘못 그으면 모듈 A가 모듈 B를 부르고 B가 다시 A를 부르는 구조가 됩니다. 파일 수만 늘고 복잡도는 그대로거나 오히려 나빠져요.\n그리고 좋은 경계는 코드를 노려본다고 보이지 않습니다. 어디가 자주 같이 바뀌는지를 봐야 보여요. 늘 함께 바뀌는 코드는 한 모듈에 있어야 하고, 따로 바뀌는 코드는 나뉘어야 합니다. 그 패턴은 여러 달 고쳐본 뒤에야 드러납니다.\n그래서 나중에 실제로 쪼갤 때는 경계를 별로 고민하지 않았습니다. 이미 알고 있었거든요. 상수와 상태, 체결과 청산, 스캔과 게이트, 원장 대조, 화면 출력. 이 다섯 덩어리는 이 시대 내내 서로 다른 이유로 바뀌던 것들입니다. 모놀리스 시절의 고통이 곧 설계도였습니다.\n그래도 남긴 것 이 시대에 확립된 개념 중 지금까지 이어지는 게 몇 개 있습니다.\n여기서 만들어져 현행까지 이어진 것들 게이트 개념 조건을 여러 개 통과해야 진입 — 지금은 10개 랭킹 점수 통과한 종목이 여럿일 때 순서를 정하는 방식 스캔 로그 어떤 종목이 왜 떨어졌는지 남기기 시작 v901 \"스나이퍼 강등\" 스캐너가 진입까지 결정하던 걸 후보 추천으로 축소 마지막 항목이 특히 중요했다 — 종목 선정과 진입 결정의 분리 게이트라는 표현을 쓰기 시작한 것 자체가 사고방식의 변화였습니다. 그전에는 조건들을 점수로 합쳐서 일정 점수 이상이면 사는 식이었어요. 게이트는 다릅니다. 하나라도 통과 못 하면 탈락입니다. 곱셈이 아니라 AND죠.\n점수 합산 ↔ 게이트 통과 [ 점수 합산 ] 조건 A 90점 + 조건 B 10점 = 100점 → 통과 한 항목이 아주 좋으면 다른 결함이 가려진다 → \"거래량이 거의 없지만 RSI 가 완벽한\" 종목이 통과 [ 게이트 ] 조건 A 통과 · 조건 B 탈락 → 진입 안 함 치명적 결함이 다른 장점으로 상쇄되지 않는다 \u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026ndash; 현행: 게이트로 거르고 → 통과한 것들끼리만 점수로 정렬\n이 구분이 실전에서 꽤 중요합니다. 점수 합산은 결함을 상쇄시켜요. 거래량이 거의 없는 종목인데 다른 지표가 완벽해서 통과하는 경우가 생깁니다. 그런 종목은 사면 못 팝니다. 진입 자체가 성립해도 청산이 안 되는 거죠. 게이트로 바꾸면 이런 종목이 처음부터 걸러집니다.\n지금 봇은 둘 다 씁니다. 게이트로 거른 다음, 통과한 것들끼리 점수로 순서를 정해요. 역할을 나눈 겁니다 — 게이트는 자격을 보고 점수는 우선순위를 봅니다.\nv901, 역할을 쪼갠 순간 v901의 변화를 좀 더 설명하면, 그전까지 스캐너는 종목을 고르고 바로 사는 것까지 했습니다. 이걸 \u0026ldquo;후보만 추천하고, 살지 말지는 별도 로직이 판단한다\u0026quot;로 바꿨어요. 역할을 쪼갠 겁니다.\n스캐너의 역할 축소 [ 이전 ] 스캐너 = 발견 + 판단 + 주문 스캐너를 고치면 주문 동작이 바뀐다 진입 규칙을 바꾸려면 스캔 코드를 건드려야 한다 [ v901 ] 스캐너 = 발견만 스캐너 → 후보 목록 → 진입 판단 → 주문 각각 따로 고칠 수 있다 \u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026ndash; 이 분리가 다음 시대 모듈화의 예고편이 된다\n이 분리가 왜 중요했냐면, 바꾸고 싶은 것과 건드려야 하는 것을 일치시켰기 때문입니다. 진입 규칙만 바꾸고 싶은데 스캔 코드를 건드려야 하는 구조에서는 매번 관계없는 부분이 부서질 위험을 감수해야 해요. 역할이 나뉘면 그 위험이 사라집니다.\n그리고 여기서 하나 더 얻는 게 있습니다. 각 단계의 출력을 따로 볼 수 있게 됩니다. 스캐너가 뽑은 후보 목록과 진입 판단이 실제로 산 종목을 비교하면, 어느 단계에서 무엇이 걸러졌는지 보여요. 문제가 발견 단계인지 판단 단계인지 구분할 수 있게 된 겁니다.\n스캔 로그, 반쪽짜리 시작 스캔 로그도 이때 시작합니다. 통과 못 한 종목이 어느 조건에서 떨어졌는지 기록하기 시작한 건데, 이게 나중에 파라미터를 데이터로 정하는 기반이 됩니다. 다만 이때는 남기기만 하고 분석은 안 했어요.\n스캔 로그로 알 수 있는 것들 게이트별 탈락 수를 세어보면 -------------------------------------------------- 탈락 0건인 게이트 아무 일도 안 하고 있다 → 있으나 마나 · 없애거나 조여야 탈락 99%인 게이트 혼자 다 거르고 있다 → 나머지 게이트가 무의미해진다 고르게 분포 각 게이트가 제 역할을 한다 -------------------------------------------------- 이 분포를 안 보면 게이트를 10개 만들어놓고 실제로는 2개만 작동하는 상태를 모른다 나중에 실제로 이 분석을 해보니, 조건을 추가했는데 통행량만 죽고 성적은 안 좋아진 사례가 나옵니다. 좋은 필터를 얻었다고 생각했는데 그냥 매매 자체를 줄인 거였어요. 매매가 줄면 손실도 줄어서 잠깐은 좋아 보입니다. 그런데 기대값이 개선된 게 아니라 표본이 줄어든 것뿐이에요.\n이 시기에 로그를 남기기만 하고 안 본 게 아쉽습니다. 다만 남기기라도 한 게 다행이었어요. 나중에 분석 도구가 생겼을 때 돌아볼 데이터가 있었으니까요. 분석할 여유가 없어도 기록은 남겨두라는 게 이 시대의 교훈입니다.\n지표를 늘리면 좋아질 줄 알았습니다 v2xx 구간이 \u0026ldquo;지표 확장\u0026quot;인데, 여기서 배운 게 있습니다. 지표를 추가한다고 판단이 좋아지지는 않는다는 것.\n처음 생각은 단순했습니다. 지표 하나로 판단하면 틀릴 수 있으니 여러 개를 보자. 그래서 지표를 붙이기 시작했어요. RSI, 이동평균, 거래량, 변동성. 각각은 다 나름의 논리가 있습니다.\n지표를 늘리면 실제로 늘어나는 것 지표 4개 · 각 지표에 파라미터 2개 = 조합 가능한 설정이 폭발적으로 증가 -------------------------------------------------- · 검증해야 할 조합이 는다 (기록도 없는데) · 지표끼리 같은 얘기를 하는 경우가 많다 예: 여러 추세 지표는 대체로 함께 움직인다 · 서로 반대 신호를 낼 때 우선순위 규칙이 또 필요하다 → 판단의 정확도보다 설정의 복잡도가 먼저 는다 특히 두 번째가 함정입니다. 서로 다른 지표를 봤다고 생각하는데 실은 같은 정보를 다른 방식으로 본 것일 때가 많아요. 추세를 보는 지표들은 대체로 함께 움직입니다. 세 개를 다 보고 \u0026ldquo;세 지표가 모두 동의한다\u0026quot;고 판단해도, 실제로는 하나를 세 번 본 것에 가까울 수 있습니다.\n그래서 지금 봇의 게이트는 서로 다른 성격의 조건들로 구성돼 있습니다. 가격 위치, 모멘텀, 거래량, 유동성, 주문 가능성. 각각이 다른 종류의 실패를 막습니다. 지표 개수가 아니라 커버하는 실패 유형의 개수가 중요하다는 걸 이 시기에 지표를 잔뜩 붙여보고 나서야 알았어요.\n청산이 진입보다 늦게 온 이유 v3xx가 진입, v4xx가 청산이라고 했는데 이 순서에 대해 더 적어두겠습니다.\n진입 로직은 만드는 게 재밌습니다. 조건을 설계하고, 신호가 뜨고, 봇이 사는 걸 보면 뭔가 하고 있다는 느낌이 들어요. 반면 청산은 지루합니다. 손절선을 정하고 익절선을 정하는 게 전부처럼 보이거든요.\n청산이 실제로 결정하는 것들 진입은 한 번의 결정 청산은 보유 기간 내내의 결정 -------------------------------------------------- 손절선 얼마나 버틸 것인가 익절 방식 목표가에서 전량? 분할? 트레일링? 시간 청산 오래 안 움직이면 뺄 것인가 본전 사수 이익이 났을 때 어디까지 되돌릴 것인가 전체 수확 계좌 단위로 정리하는 기준 -------------------------------------------------- 진입 조건을 아무리 다듬어도 청산이 엉성하면 결과는 안 바뀐다 이 시대에 청산을 재설계하고 나서 성적이 눈에 띄게 달라졌던 기억이 있습니다. 다만 기록이 없어서 얼마나 달라졌는지는 숫자로 못 남겼어요. 그게 이 시대의 한계입니다 — 뭔가 나아진 건 아는데 얼마나인지 모릅니다.\n지금 봇에서 방어 규칙이 절반을 차지하는 것도 결국 이 순서를 뒤늦게 뒤집은 결과입니다. 손절, 본전 사수, 일일 한도, 주간 한도, 서킷브레이커, 레짐 차단. 전부 사는 것과 무관한 규칙들이에요. 만드는 재미는 없지만 계좌를 지키는 건 이쪽입니다.\n코드가 아니라 사이클이 병목이었습니다 이 시대의 진짜 비용은 코드 품질이 아니라 한 번 고치고 확인하는 데 걸리는 시간이었습니다.\n사이클 시간이 늘어난 경로 초기 고친다(5분) → 돌린다 → 눈으로 본다 = 빠름 중기 고친다(20분) → 다른 데가 깨진다 → 그것도 고친다 → 다시 돌린다 = 느림 말기 고치기 전에 어디가 깨질지 예측이 안 됨 → 고치는 걸 미루게 된다 = 정지 -------------------------------------------------- 개발이 멈추는 건 의욕이 아니라 예측 불가능성 때문이다 마지막 단계가 위험합니다. 고치면 뭐가 깨질지 모르니까 안 고치게 돼요. 명백히 개선인 변경도 미루게 됩니다. 이 상태가 되면 코드가 개발자를 통제하기 시작한 겁니다.\n그리고 이 시기 개발의 상당 부분이 버그를 고치는 게 아니라 버그를 찾는 데 들어갔습니다. 증상은 화면에서 나타나는데 원인은 스캔 로직에 있고, 그 둘을 잇는 경로가 전역 변수라 추적이 안 됩니다. 이런 디버깅은 실력으로 극복이 안 돼요. 구조를 바꿔야 합니다.\n끝 이 시대가 끝난 이유는 단순합니다. 감당이 안 됐습니다. 파일 하나를 열면 스크롤을 한참 내려야 원하는 함수가 나오고, 함수 하나를 고치면 뭐가 깨질지 예측이 안 됐어요.\n그래서 두 가지 방향이 동시에 나옵니다. 하나는 여러 전략을 각각 별도 프로젝트로 떼어내서 실험해보는 것(다음 시대), 다른 하나는 결국 모듈로 쪼개는 것(그다음 시대).\n모놀리스가 남긴 두 갈래 [ 갈래 1 ] 전략을 의심한다 \"지금 이 전략이 맞긴 한 건가?\" → 여러 전략을 별도 프로젝트로 만들어 비교 → 4시대 병렬 실험기 [ 갈래 2 ] 구조를 의심한다 \u0026ldquo;이 파일로는 뭘 해도 못 검증한다\u0026rdquo; → 역할별 모듈 분리 · git 도입 → 6시대 VV 모듈 \u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026ndash; 둘 다 이 시대의 답답함에서 나왔다\n지금 돌아보면 이 시기의 답답함이 없었으면 그 두 결정 다 안 나왔을 겁니다. 불편함이 충분히 쌓여야 구조를 바꾸게 되더군요. 진작 쪼갤 걸 그랬다는 생각도 들지만, 실은 쪼개야 하는 이유를 몸으로 알기 전에 쪼갰으면 잘못된 경계로 쪼갰을 겁니다.\n120개를 다시 세어보며 이 시대 버전 목록을 유형별로 나눠보면 어디에 시간을 썼는지가 보입니다.\n이 시대 변경의 성격 (분류) 파라미터 조정 근거 없이 바꾸고 되돌린 것들 상당수가 반복 시도였다 버그 수정 같은 원인이 다른 증상으로 재발 구조 문제라 근본 해결이 안 됨 구조 변경 v901 역할 분리 등 개수는 적은데 효과는 컸다 관측 추가 스캔 로그 등 당장은 무의미해 보였으나 자산이 됨 -------------------------------------------------- 개수는 위 둘이 많고 · 가치는 아래 둘이 컸다 이 표가 이 시대의 요약입니다. 많이 한 일과 값진 일이 달랐어요. 그리고 그 사실을 그때는 몰랐습니다. 파라미터를 만지는 건 즉각적인 만족감이 있고 구조를 바꾸는 건 티가 안 나니까요.\n한 가지 덧붙이면, \u0026ldquo;관측 추가\u0026quot;의 가치는 항상 시차를 두고 나타납니다. 로그를 남기는 커밋은 그날의 성적을 1원도 안 바꿔요. 그런데 몇 주 뒤 그 로그가 답을 줍니다. 이런 종류의 작업을 계속하려면 당장 효과가 없는 일의 가치를 인정하는 태도가 필요한데, 이건 규율의 문제지 실력의 문제가 아니더군요.\n이 시대에서 가져갈 것 첫째, 단일 파일은 나쁜 게 아니라 유효 기간이 있는 겁니다. 초기에는 오히려 빠릅니다. 문제는 임계점을 지났는데도 계속 쓰는 것이고, 그 임계점은 줄 수가 아니라 \u0026ldquo;고치기 전에 결과를 예측할 수 있는가\u0026quot;로 판단해야 합니다.\n둘째, 게이트와 점수는 역할이 다릅니다. 자격을 보는 조건과 우선순위를 보는 조건을 한 산식에 섞으면 치명적 결함이 장점으로 상쇄됩니다. 통과·탈락으로 판정할 것과 정렬에 쓸 것을 분리하세요.\n셋째, 분석할 여유가 없어도 기록은 남기세요. 이 시기에 스캔 로그를 남기기만 하고 안 본 게 나중에 큰 자산이 됐습니다. 반대로 매매 기록을 아예 안 남긴 구간은 지금도 복원이 불가능합니다. 기록은 나중을 위한 저축이고, 안 남긴 시간은 영원히 안 돌아옵니다.\n넷째, 고통은 설계 정보입니다. 어디가 자주 부서지고 어디를 고칠 때 겁이 나는지가 곧 경계선입니다. 리팩터링을 미룬 몇 주가 낭비만은 아니었어요. 그동안 어디서 쪼개야 할지가 정해졌으니까요. 다만 그걸 알았으면 미루는 대신 기록이라도 해뒀을 겁니다. \u0026ldquo;오늘 또 여기가 깨졌다\u0026quot;를 세어두는 것만으로 설계도가 됩니다.\n120개 버전, 1,500줄. 이 시대의 결론은 \u0026ldquo;더 열심히 고치면 된다\u0026quot;가 틀렸다는 것이었습니다.\n같은 파일을 백스무 번 고치면서 알게 된 건, 문제가 코드의 품질이 아니라 코드에 대해 질문할 수 있는 능력이었다는 겁니다. 이 값이 왜 이런지, 이 조건이 실제로 뭘 거르는지, 어제와 오늘 중 뭐가 나은지. 단일 파일에 기록 없이 돌아가는 봇은 그 어떤 질문에도 답을 못 합니다. 다음 두 시대는 그 답할 수 있는 상태를 만들러 가는 여정입니다.\n","permalink":"https://botlab.co.kr/archive/03-monolith/","summary":"버전 번호가 백 단위로 뛰던 시기. 빨랐고, 그만큼 어디를 고치면 어디가 부서지는지 아무도 몰랐습니다.","title":"3. 파일 하나에 다 넣던 시절 — 120개 버전 | Crypto Trading Bot"},{"content":"거래소를 옮긴 이유 빗썸에서 쓰던 엔진을 업비트로 옮겼습니다. 업비트 봇이 갑자기 툭 튀어나온 것처럼 보이는 이유가 이겁니다 — 처음부터 만든 게 아니라 이식한 거예요. 그래서 초기 업비트 코드에는 빗썸 시절 흔적이 여기저기 남아 있습니다.\n옮긴 이유는 몇 가지가 겹쳤습니다. 종목 수, 유동성, 그리고 API 문서의 친절함. 특히 마지막 게 컸어요. 앞 시대에서 인증 규격 하나 때문에 몇 시간을 태운 경험이 있으니, 문서가 명확한 쪽이 개발 속도에서 유리하다는 걸 알고 있었습니다.\n이 시대 이름을 MA봇이라고 붙인 건 진입 판단을 이동평균 교차에 맡겼기 때문입니다. 짧은 이동평균이 긴 이동평균을 위로 뚫으면 산다는, 교과서에 제일 먼저 나오는 그 방식이요.\n이동평균 교차 — 왜 첫 선택이었나 단기선이 장기선을 위로 교차 → 매수 단기선이 장기선을 아래로 교차 → 매도 -------------------------------------------------- 장점 구현이 단순하다 (몇 줄) 판정이 명확하다 (교차했나 안 했나) → 봇으로 만들기에 최적 단점 후행 지표다 (이미 오른 뒤에 신호) 횡보장에서 신호가 계속 나온다 -------------------------------------------------- 결국 이 단점이 다음 시대의 전략 전환 이유가 된다 이동평균을 고른 건 그게 좋아서가 아니라 코드로 옮기기 쉬워서였습니다. 교차했나 안 했나는 부등호 하나로 판정됩니다. 애매한 구간이 없어요. 자동매매를 처음 만들 때 이 성질이 굉장히 매력적입니다. 사람의 해석이 필요한 지표는 봇으로 옮기는 순간 무너지거든요.\n봇이 종목을 고르기 시작했습니다 이 시대의 진짜 사건은 이동평균이 아니라 스캔입니다. 그전까지는 제가 종목을 정해주면 봇이 그 종목만 감시했어요. 그런데 남아 있는 가장 이른 버전(v032)에는 이미 \u0026ldquo;CROSS SCANNING\u0026quot;이라는 문구와 함께 슬롯 UI가 들어 있습니다. 즉 수동 지정에서 자동 스캔으로 넘어간 순간은 v032 이전인데, 그 파일이 안 남아 있습니다. 이 봇 역사에서 가장 아쉬운 공백입니다.\nv034에서는 업비트 market/all을 호출해 전 종목을 긁어오는 코드가 확인됩니다. 몇백 개 종목을 매번 훑는 구조로 넘어간 거죠.\n수동 지정 → 자동 스캔 이전 내가 종목 지정 → 봇은 그 종목만 감시 문제: 종목 선택이라는 제일 어려운 판단이 여전히 내 몫 이후 market/all 전 종목 조회 → 조건 맞는 것만 추림 → 슬롯에 배치 이때 생긴 개념들이 지금도 그대로 살아 있다: 슬롯 동시에 몇 개까지 들고 갈지 (현재 4개) 블랙리스트 건드리지 않을 종목 목록 상태 저장 봇이 죽었다 살아나도 포지션을 기억 이 전환이 왜 결정적이냐면, 제일 어려운 판단을 봇에게 넘긴 것이기 때문입니다. 매매에서 가장 어려운 건 언제 팔지가 아니라 무엇을 살지예요. 종목을 제가 고르는 한, 봇은 제 판단의 실행 도구일 뿐입니다. 감정을 배제하려고 만든 봇인데 가장 감정적인 결정을 제가 하고 있었던 거죠.\n스캔으로 넘어가면서 문제의 성격도 바뀝니다. 이전에는 \u0026ldquo;이 종목을 언제 살까\u0026quot;였는데, 이후에는 \u0026ldquo;수백 개 중에 어떤 조건을 만족하는 것만 추릴까\u0026quot;가 됩니다. 후자는 규칙으로 쓸 수 있는 문제예요. 지금 봇의 게이트 열 개가 하는 일이 정확히 이 추리기 작업입니다.\n슬롯, 블랙리스트, 상태 저장 슬롯, 블랙리스트, 상태 저장. 이 세 가지는 이때 만들어져서 461개 버전을 지나 지금까지 형태만 바뀐 채 살아 있습니다.\n슬롯은 앞 시대에서 개념이 나왔지만, 스캔과 만나면서 진짜 역할을 하게 됩니다. 전 종목을 훑으면 조건에 맞는 종목이 수십 개 나올 수 있어요. 그걸 다 사면 안 되니까 상한이 필요합니다. 그리고 상한이 있으면 그다음 질문이 자동으로 따라옵니다 — 어떤 순서로 채울 것인가. 랭킹이라는 개념이 여기서 시작됩니다.\n블랙리스트는 더 소박한 장치입니다. 그냥 안 건드릴 종목 목록이에요. 거래량이 지나치게 적거나, 특정 사유로 손대고 싶지 않은 종목을 빼둡니다. 조건으로 거르는 게 정석이지만, 조건으로 표현하기 어려운 판단도 있습니다. 그럴 땐 목록이 답이에요.\n상태 저장은 셋 중 가장 중요했습니다.\n상태 저장이 없으면 벌어지는 일 [ 상태 저장 없음 ] 봇 재시작 → 보유 현황 초기화 → 이미 들고 있는 종목을 또 산다 → 슬롯 계산이 어긋난다 → 손절 기준가를 모른다 (얼마에 샀는지 잊음) [ 상태 저장 있음 ] 보유 종목 · 진입가 · 진입 시각을 파일로 기록 재시작 시 복원 → 이어서 관리 \u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026ndash; 봇이 죽는 건 예외가 아니라 일상이다\n봇이 죽는 건 언제든 일어나는 일이고, 죽었다 살아났을 때 자기가 뭘 들고 있는지 모르면 그때부터 사고가 시작됩니다. 특히 진입가를 잊는 게 치명적이에요. 손절선은 진입가 기준으로 계산되는데, 진입가를 모르면 손절 자체가 불가능합니다. 들고는 있는데 언제 잘라야 할지 모르는 포지션이 되는 거죠.\n이 상태 저장이 나중에 훨씬 엄격한 형태로 진화합니다. 봇이 저장한 상태와 거래소가 알려주는 실제 보유 현황을 대조하고, 어긋나면 배포를 막는 규칙으로요. 자기 기억을 기록으로 남기는 단계에서 자기 기억을 외부와 대조하는 단계로 가는 데 시대가 네 개 더 걸립니다.\n랭킹이라는, 예상 못 한 문제 슬롯이 네 개인데 조건에 맞는 종목이 스무 개 나오면 어떻게 할까요. 이게 스캔으로 넘어가면서 새로 생긴 문제입니다.\n가장 단순한 답은 \u0026ldquo;먼저 발견한 순서대로\u0026quot;입니다. 그런데 이러면 봇이 종목 목록을 훑는 순서가 곧 매매 결정이 돼버려요. 목록이 알파벳 순이면 알파벳 앞쪽 종목만 사게 됩니다. 아무 근거 없는 편향이죠.\n후보가 슬롯보다 많을 때 조건 통과 종목 20개 · 빈 슬롯 4개 -------------------------------------------------- [ 발견 순서대로 ] 목록 정렬 방식이 곧 매매 결정 근거 없는 편향 [ 무작위 ] 재현이 안 된다 같은 상황에 다른 결과 → 검증 불가 [ 점수순 ] 조건들을 점수로 합쳐 정렬 → 재현 가능 · 근거 설명 가능 -------------------------------------------------- 다만 점수 산식 자체가 새로운 튜닝 대상이 된다 점수순이 맞는데, 여기서 함정이 하나 있습니다. 점수 산식이 그럴듯하다고 좋은 게 아니라는 것이에요. 여러 조건을 가중치로 합쳐 점수를 만들면 논리적으로 보입니다. 그런데 실제 결과와 대조해보면 점수가 높은 종목이 오히려 성적이 나쁜 경우가 나옵니다. 이 시대에는 그걸 확인할 데이터가 없었어요.\n나중에 매매기록으로 검증할 수 있게 되고 나서야, 산식 안의 어떤 가점 항목이 반대로 작동하고 있다는 걸 발견합니다. 그럴듯한 논리로 만든 가점이 실제로는 나쁜 종목을 앞으로 끌어올리고 있었던 거죠. 지금 랭킹 산식이 훨씬 단순한 이유입니다. 검증할 수 없는 복잡함은 그냥 위험이더군요.\n이동평균을 코드로 옮기면 생기는 것들 교과서에서는 이동평균이 한 줄입니다. 최근 N봉의 평균. 그런데 봇으로 옮기면 교과서에 안 나오는 문제들이 생깁니다.\n지표를 실전 코드로 옮길 때의 실무 문제 1. 워밍업 20봉 평균을 쓰려면 최소 20봉이 있어야 한다 신규 상장 종목은 데이터가 부족 → 계산 불가 → 봉 수 부족 종목은 후보에서 제외 2. 진행 중인 봉 지금 만들어지는 중인 봉을 포함할 것인가 포함하면 판정이 몇 초마다 뒤집힌다 → 완성된 봉만 사용\n3. 교차의 정의 \u0026ldquo;위로 뚫었다\u0026rdquo; 를 어떻게 판정하나 직전 봉에서 아래, 이번 봉에서 위 — 한 번만 발동 → 상태를 기억해야 한다 (조건문 하나로 안 됨)\n2번이 특히 중요합니다. 진행 중인 봉을 계산에 넣으면 같은 종목이 몇 초 간격으로 \u0026ldquo;교차했다 / 안 했다\u0026quot;를 왔다 갔다 합니다. 봇이 그때마다 반응하면 아무것도 못 해요. 완성된 봉만 쓴다는 원칙은 이때 생겨서 지금까지 유지됩니다. 나중에 시장 상황을 판단하는 로직에서도 같은 원칙이 다시 적용돼요.\n3번도 은근히 걸립니다. 교차는 순간의 사건인데 봇은 주기적으로 검사합니다. 검사 시점에 이미 교차가 끝나 있으면 놓치고, 교차 상태가 유지되는 동안 매번 검사하면 계속 신호가 납니다. 그래서 직전 상태를 기억해뒀다가 상태가 바뀌는 순간만 잡아야 해요. 지표 계산보다 상태 관리가 더 까다롭다는 걸 이때 배웠습니다.\n통신을 다시 만들었습니다 v034 즈음 웹소켓을 도입합니다. 그전까지는 REST로 계속 물어보는 방식이었는데, 종목 수가 늘어나니까 감당이 안 됐어요. 몇백 개 종목을 각각 REST로 조회하면 API 제한에 걸립니다.\nREST 폴링 → 웹소켓 [ REST 폴링 ] 봇 → \"BTC 얼마?\" → 거래소 봇 → \"ETH 얼마?\" → 거래소 … 종목 수만큼 반복 … 종목이 늘면 요청 수가 비례해서 는다 → 호출 제한에 걸린다 [ 웹소켓 ] 봇 → \u0026ldquo;이 종목들 구독할게\u0026rdquo; → 거래소 거래소 → 값이 바뀔 때마다 밀어준다 연결 하나로 다수 종목을 받는다 \u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026ndash; 대신 새 문제가 생긴다: 연결이 끊기면 조용히 멈춘다\n웹소켓은 효율은 좋은데 실패 방식이 고약합니다. REST는 요청이 실패하면 즉시 에러가 돌아오는데, 웹소켓은 연결이 끊긴 채로 조용히 아무것도 안 옵니다. 봇 입장에서는 \u0026ldquo;값이 안 바뀌는 것\u0026quot;과 \u0026ldquo;연결이 죽은 것\u0026quot;이 똑같아 보여요. 시장이 조용한 새벽에 연결이 끊기면 몇 시간을 모른 채 지나갑니다.\n그래서 연결 상태 추적이 필요해집니다. 마지막으로 데이터를 받은 시각을 기록해두고, 일정 시간 이상 아무것도 안 오면 연결이 죽었다고 판단해서 재연결하는 로직이요. 이 문제는 이후에도 계속 형태를 바꿔가며 돌아옵니다. 3회 재시도, 지수 백오프, 하트비트, 연결 상태 추적 같은 것들이 순서대로 붙어요.\n자동매매에서 통신 안정성은 전략만큼 중요한데, 그걸 이 시기에 데이터가 끊길 때마다 배웠습니다. 정확히 말하면 끊긴 걸 몰랐던 경험을 통해 배웠어요. 앞 시대의 \u0026ldquo;조용한 실패\u0026quot;가 통신 계층에서 다시 나타난 겁니다.\n이식이라는 작업의 실체 \u0026ldquo;엔진을 옮겼다\u0026quot;는 한 문장으로 적었지만, 실제로 해보면 이식은 새로 짜는 것과 크게 다르지 않습니다. 다른 점은 무엇을 만들지 이미 안다는 것뿐이에요.\n이식에서 옮겨지는 것 / 안 옮겨지는 것 옮겨지는 것 · 구조 (읽기 → 판단 → 주문) · 상태 관리 방식 · 화면 배치와 흐름 · 겪어본 실패의 목록 ← 제일 값지다 안 옮겨지는 것 · 인증 코드 (전면 재작성) · 심볼·호가·최소금액 처리 · 통신 계층 (REST → 웹소켓으로 아예 교체) · 파라미터 값 (거래소가 다르면 시장도 다르다)\n마지막 항목이 은근히 중요합니다. 빗썸에서 쓰던 손절 폭이나 대기 폭을 그대로 가져오고 싶어지는데, 거래소가 다르면 유동성도 변동성도 다릅니다. 같은 -0.5% 대기가 한쪽에서는 자주 채워지고 다른 쪽에서는 거의 안 채워질 수 있어요. 값은 코드가 아니라 시장에 속한 것이라, 이사할 때 같이 안 따라옵니다.\n그런데 이때는 그냥 가져왔습니다. 검증할 방법이 없었으니까요. 지금이라면 이관 직후 몇 주 동안은 값을 다시 재는 기간으로 잡았을 겁니다. 이식의 진짜 비용은 코드 작업이 아니라 값을 다시 세우는 시간이더군요.\n그리고 옮겨지는 것 중 제일 값진 게 겪어본 실패의 목록이라는 걸 강조하고 싶습니다. 이 시대에 조용한 실패가 훨씬 적었던 건 코드를 잘 짜서가 아니라, 앞 시대에서 어디가 조용히 실패하는지 이미 알고 있었기 때문이에요. 주문 직전에 수량 자릿수를 고정하는 것, 통신 실패를 정상 상황으로 처리하는 것 — 이런 건 처음부터 넣었습니다. 첫 시대에서 배운 것들이니까요.\n이식이 남긴 흔적들 거래소를 옮기면서 배운 실무적인 것들도 적어둡니다. 같은 로직이라도 거래소가 바뀌면 손봐야 하는 곳이 정해져 있더군요.\n거래소를 옮길 때 반드시 바뀌는 것들 1. 인증 규격 서명 방식 · 헤더 이름 · 유효 시간 2. 심볼 표기 BTC / KRW-BTC / BTC-KRW … 전부 다르다 3. 호가 단위 가격대별 최소 변동폭 규칙 4. 최소 주문 금액 이 아래로는 주문 자체가 거부 5. 수량·금액 표기 소수 자릿수 · 시장가 주문의 인자 6. 호출 제한 초당 · 분당 상한 -------------------------------------------------- 이 중 3·4를 놓치면 \"조건은 맞는데 주문이 안 나가는\" 상태가 된다 3번과 4번이 특히 성가셨습니다. 호가 단위는 가격대마다 다릅니다. 어떤 구간에서는 1원 단위로 움직이고 다른 구간에서는 0.1원 단위로 움직여요. 계산한 주문 가격이 그 단위에 안 맞으면 거부됩니다. 최소 주문 금액도 마찬가지예요. 슬롯 예산을 나눴는데 그 금액이 최소 미만이면 주문이 안 나갑니다.\n둘 다 앞 시대에서 배운 그 실패 유형입니다 — 조건은 다 맞았는데 주문이 조용히 거부되는 것. 그래서 이 시기에 주문 직전 검사를 붙였습니다. 호가 단위에 맞게 가격을 반올림하고, 최소 금액을 넘는지 확인하고 나서 보냅니다. 지금 봇의 게이트 열 개 중 하나가 호가 단위 검사인 건 이때의 유산이에요.\n종목이 늘면 무엇이 늘어나는가 수동 지정에서 전 종목 스캔으로 가면 종목 수만 느는 게 아닙니다. 관리해야 할 것들이 같이 늘어납니다.\n스캔 도입 후 새로 생긴 관리 항목 데이터 품질 종목마다 봉 개수가 다르다 신규 상장 · 거래 정지 · 데이터 누락 계산 비용 수백 종목 × 지표 여러 개 = 매 주기 부담 주기가 길어지면 신호가 늦는다 중복 진입 같은 종목이 여러 조건에 걸린다 한 종목을 두 슬롯에 넣으면 안 된다 재진입 방금 손절한 종목이 다시 후보로 올라온다 쿨다운이 없으면 같은 자리에서 반복 손절 -------------------------------------------------- 마지막 항목이 제일 비쌌다 — 이 시대엔 쿨다운이 없었다 재진입 문제가 특히 아팠습니다. 손절한 종목은 대개 가격이 내려간 상태인데, 조건에 따라서는 그게 오히려 매력적으로 보입니다. 그래서 잘라내자마자 다시 사요. 그리고 또 잘립니다. 봇은 규칙대로 하고 있는데 계좌는 같은 종목에서 계속 깎입니다.\n이건 개별 조건으로는 못 막습니다. \u0026ldquo;이 종목을 방금 잘랐다\u0026quot;는 사실은 시장 데이터가 아니라 봇 자신의 이력이니까요. 그래서 진입 판단에 자기 이력을 넣는 로직이 필요해집니다. 지금 봇의 재진입 쿨다운이 그것이고, 이 필요성은 이 시대에 몸으로 확인했습니다.\n데이터 품질 문제도 짚어둘 만합니다. 전 종목을 훑으면 반드시 이상한 종목이 섞여 들어옵니다. 봉이 몇 개 없는 신규 상장 종목, 거래가 거의 없어서 봉이 비어 있는 종목 같은 것들이요. 이런 종목에 지표를 계산하면 값이 나오긴 하는데 의미가 없습니다. 계산에 실패하는 것보다 의미 없는 값이 나오는 게 더 위험해요. 지금 봇이 진입 게이트 앞단에서 데이터 충분성부터 검사하는 이유입니다.\n매뉴얼을 만들었다는 것의 의미 이 시기에 v057 매뉴얼 PDF를 만들었습니다. 나 혼자 쓰는 봇에 매뉴얼을 만든 게 좀 웃기지만, 그때 이미 봇이 제 머릿속보다 복잡해지기 시작했다는 뜻이기도 합니다.\n매뉴얼이 필요해졌다는 신호 파라미터가 늘어난다 → 각 값이 뭘 하는지 기억이 안 난다 → 문서를 만든다 → 문서와 코드가 어긋나기 시작한다 → 코드 자체가 읽히게 만들어야 한다 -------------------------------------------------- 현행: 파라미터를 한 모듈에 모으고 각 값 옆에 근거 수치를 주석으로 붙인다 문서가 아니라 코드가 문서 역할을 한다 문서를 만들어야 할 만큼 커진 코드는 그다음부터 관리 문제가 됩니다. 그리고 별도 문서는 반드시 코드와 어긋나요. 값을 하나 바꾸고 문서를 안 고치면 그 순간부터 문서가 거짓말을 시작합니다. 나중에 그 거짓말을 믿고 판단하면 사고가 납니다.\n지금은 문서를 따로 안 만듭니다. 대신 파라미터를 한 곳에 모아두고, 각 값 옆에 그 값을 정한 근거를 적어둬요. 그리고 커밋 메시지에 시뮬레이션 결과를 같이 넣습니다. 문서가 코드 안에 있으니 어긋날 수가 없습니다. 이 방식에 도달하기까지 매뉴얼 PDF를 만들어보는 경험이 필요했습니다.\n사라진 파일에 대하여 이 시대에서 제일 아쉬운 건 v032 이전이 안 남아 있다는 겁니다. 수동 지정에서 자동 스캔으로 넘어가는 그 순간의 코드가 없어요.\n보존 상태 v001 ~ v031 없음 이 구간 어딘가에서 스캔이 도입됐다 v032 이미 \"CROSS SCANNING\" + 슬롯 UI 존재 v034 market/all 전 종목 조회 확인 ~ v0xx 보존 48개 -------------------------------------------------- 전환의 결과는 남았는데 전환의 과정이 없다 왜 없어졌는지는 모릅니다. 폴더를 정리하다 지웠거나, 이관 과정에서 빠졌거나. git이 없던 시절의 대가입니다. 파일을 지우는 데 확인 절차가 없으니까요.\n이 공백 때문에 이 시대의 서술에는 추정이 많습니다. 스캔이 어떤 순서로 도입됐는지, 처음에 몇 종목을 훑었는지, 슬롯 개수가 처음부터 넷이었는지 — 전부 v032 시점의 결과물로 역산한 겁니다. 확실한 건 v032에 이미 완성된 형태로 들어 있다는 것뿐이에요.\n기록에서 제일 아까운 건 실패한 시도가 사라지는 겁니다. 성공한 결과는 최종 코드에 남지만, 시도했다가 접은 것들은 중간 파일에만 있어요. 그게 사라지면 \u0026ldquo;왜 이 방식이 아니라 저 방식인가\u0026quot;를 설명할 수 없게 됩니다. 지금 git을 쓰면서 실패한 브랜치도 지우지 않는 이유입니다.\n짧지만 결정적이었던 시대 이 시대는 짧습니다. 보존된 게 48개뿐이고 기간도 일주일 남짓이에요. 그런데 하는 일이 명확했습니다 — 빗썸 시절의 \u0026ldquo;내가 정해준 종목을 봇이 감시한다\u0026quot;에서 \u0026ldquo;봇이 스스로 고른다\u0026quot;로 넘어가는 다리 역할.\nMA봇 전환기가 남긴 것 구조 전 종목 스캔 → 조건 필터 → 슬롯 배치 현행 봇의 진입 파이프라인과 동일한 형태 개념 슬롯 · 블랙리스트 · 상태 저장 · 랭킹 넷 다 현재까지 살아 있음 통신 웹소켓 + 연결 상태 추적 이후 재시도·백오프로 계속 보강 숙제 이동평균은 횡보장에서 신호가 너무 많다 → 다음 시대의 전략 실험으로 이어짐 특히 진입 파이프라인의 형태가 여기서 굳었다는 게 큽니다. 지금 봇도 똑같이 동작해요. 전 종목을 훑고, 게이트로 거르고, 남은 후보를 랭킹으로 정렬하고, 빈 슬롯에 배치합니다. 게이트가 하나에서 열 개로 늘고 랭킹 산식이 정교해졌을 뿐, 뼈대는 이 시대의 것입니다.\n스캔이 바꾼 개발의 성격 봇이 종목을 고르기 시작하자 개발 작업의 성격 자체가 바뀌었습니다.\n수동 지정 시대 ↔ 스캔 시대의 작업 목록 [ 수동 지정 ] · 진입 타이밍 다듬기 · 청산 규칙 다듬기 · 화면 개선 종목 한두 개만 보면 되니 눈으로 검증 가능 [ 스캔 ] · 거르는 조건 설계 · 순서 정하는 규칙 설계 · 제외 목록 관리 · 호출량·성능 관리 수백 종목을 눈으로 검증할 수 없다 → 로그가 필수\n수백 개 종목을 훑기 시작하면 사람이 눈으로 따라갈 수 없습니다. 그러면 로그가 유일한 검증 수단이 돼요. 이 시기부터 \u0026ldquo;왜 이 종목을 샀나\u0026quot;를 로그에서 역추적하는 작업이 개발의 일부가 됩니다.\n그리고 여기서 중요한 습관이 하나 생깁니다. 떨어진 종목도 기록하기. 산 종목만 기록하면 조건이 너무 빡빡한지 헐거운지 알 수 없어요. 조건 A에서 몇 개가 떨어지고 조건 B에서 몇 개가 떨어지는지를 봐야 어느 조건이 실질적으로 작동하는지 압니다.\n이 시기에는 이걸 화면에 출력만 하고 파일로 안 남겼습니다. 그래서 나중에 분석은 못 했어요. 그래도 \u0026ldquo;떨어진 것도 봐야 한다\u0026quot;는 감각 자체는 여기서 생겼고, 다음다음 시대에 스캔 로그로 정식화됩니다.\n남은 숙제가 다음 시대를 열었습니다 이동평균의 문제는 이 시기에 이미 보였습니다. 횡보장에서 교차가 계속 일어나거든요. 올랐다 내렸다 하면서 단기선이 장기선을 몇 번씩 넘나듭니다. 그때마다 신호가 나오고, 그때마다 사고, 그때마다 수수료가 나갑니다.\n이건 파라미터로 해결이 안 되는 문제입니다. 이동평균 기간을 늘리면 신호는 줄지만 반응이 더 느려져요. 줄이면 반응은 빨라지는데 신호가 더 늘어납니다. 어느 쪽으로 가도 다른 쪽이 나빠지는 구조예요. 이럴 때가 전략 자체를 의심해야 하는 순간이라는 걸 이때는 몰랐습니다. 한동안 더 파라미터를 만졌어요.\n그 답답함이 쌓여서 다음 시대의 병렬 실험으로 이어집니다. 하나를 계속 고치는 대신 여러 개를 만들어 비교해보자는 발상이요. 그리고 그 실험에서 나온 결론이 지금 전략의 직계 조상이 됩니다.\n이 시대에서 가져갈 세 문장 첫째, 제일 어려운 판단부터 넘겨야 합니다. 자동매매를 만들 때 쉬운 것부터 자동화하고 싶어집니다. 주문 실행, 손절 감시 같은 것들이요. 그런데 정작 감정이 개입하는 자리는 종목 선택입니다. 거기를 사람이 쥐고 있으면 나머지를 아무리 자동화해도 결과는 사람 판단의 복사본이에요.\n둘째, 효율을 올리면 새로운 실패 방식이 생깁니다. REST에서 웹소켓으로 가면 호출량은 줄지만 조용히 끊기는 실패가 생깁니다. 최적화는 문제를 없애는 게 아니라 다른 문제로 바꾸는 일에 가깝더군요. 그래서 효율을 올릴 때는 항상 \u0026ldquo;이제 어떻게 실패하나\u0026quot;를 같이 물어야 합니다.\n셋째, 파라미터로 안 풀리면 전략을 의심해야 합니다. 이동평균 기간을 늘려도 줄여도 다른 쪽이 나빠지는 구조였는데, 저는 한동안 더 파라미터를 만졌습니다. 양쪽으로 다 나빠지는 트레이드오프를 만나면 그건 값의 문제가 아니라 접근의 문제라는 신호예요. 이 신호를 읽는 데 시대가 하나 더 걸렸습니다.\n일주일, 48개 버전. 봇이 처음으로 저 대신 종목을 고른 시기였습니다. 그때부터 이건 도구가 아니라 봇이 됐다고 생각합니다.\n","permalink":"https://botlab.co.kr/archive/02-ma/","summary":"봇이 종목을 스스로 고르기 시작한 시기. 보존된 건 48개뿐이지만, 지금 쓰는 골격 대부분이 이때 만들어졌습니다.","title":"2. 업비트로 넘어가며 — 이동평균 봇 시절 | Crypto Trading Bot"},{"content":"시작은 자제력 문제였습니다 빗썸 계좌는 원래 장기투자용이었습니다. 그런데 계속 들여다보게 되더군요. 오르면 팔고 싶고 내리면 더 사고 싶고. 그럴 거면 차라리 규칙을 정해서 코드에 박아놓고 손을 떼자 — 그게 이 봇의 출발점이었습니다. 봇을 만들면 돈을 벌 것 같아서가 아니라, 제가 저를 못 믿어서 시작한 겁니다.\n이 동기가 왜 중요하냐면, 이후 461개 버전의 방향을 전부 결정했기 때문입니다. 돈을 더 벌려고 시작했다면 아마 신호를 더 빨리 잡는 쪽으로 갔을 겁니다. 그런데 출발점이 자제력이었으니, 개발의 대부분이 하지 말아야 할 것을 정하는 일이 됐어요. 지금 봇의 규칙 목록을 보면 절반이 \u0026ldquo;이럴 땐 안 산다\u0026quot;입니다. 그 성격은 첫날에 이미 정해져 있었습니다.\n남아 있는 가장 오래된 파일은 코드가 아니라 메모입니다. Futuring Request.txt, 2026년 5월 25일. 열어보면 그냥 하고 싶은 것들을 나열한 요구사항 목록이에요. 트레일링 스톱을 넣고 싶다, 창이 두 개 뜨면 안 된다, 같은 것들. 그중 \u0026ldquo;2중 실행 방지\u0026quot;라는 한 줄은 며칠 뒤 v1.04 코드 맨 위에 주석으로 그대로 올라갑니다. 같은 날 만든 RunTrader.bat도 남아 있습니다. 윈도우 배치 파일이죠.\n메모에서 눈에 띄는 건 비율입니다. 열 줄 남짓 중 대부분이 화면 이야기예요. 색을 통일해달라, 버튼을 나눠달라, 글자를 키워달라. 매매에 관한 건 두 줄뿐입니다. 목표 수익률에서 절반을 익절하고 나머지는 추세를 끝까지 따라가겠다는 것. 그때는 화면이 봇의 전부였으니 당연한 비율이었는데, 지금 보면 그 두 줄이 나머지를 다 합친 것보다 오래 살아남았습니다.\n뼈대는 첫 주에 이미 완성돼 있었습니다 기술 구성은 단순했습니다. 빗썸 공개 API에서 분봉을 REST로 긁어오고, 조건을 검사하고, tkinter 창에 상태를 그리는 구조. 시세를 읽고 → 판단하고 → 주문한다는 뼈대는 사실 이때 이미 완성돼 있었고, 그 뒤 두 달은 이 뼈대에 살을 붙이는 과정이었습니다.\n통신에서 한 가지 선택을 했는데, 지금까지 유지되고 있습니다. 시세나 분봉처럼 인증이 필요 없는 데이터는 공개 라이브러리를 그대로 썼고, 잔고 조회와 주문처럼 돈이 움직이는 구간만 직접 짰어요. 서명을 만들고 헤더에 싣는 부분을 손으로 구현한 겁니다. 이유는 단순했습니다. 틀렸을 때 어디서 틀렸는지 알고 싶었거든요. 시세를 잘못 읽으면 안 사면 그만이지만, 주문이 잘못 나가면 돈이 나갑니다.\n문제는 GUI였습니다. 이벤트 루프랑 tkinter 루프가 한 프로세스에서 경합하니까 창이 자주 멈췄어요. 멈출 때마다 스레드를 하나씩 붙여가며 때웠습니다. 지금 보면 아키텍처라고 부르기 민망한 수준인데, 그때는 그게 최선이었습니다.\n이 시대의 구조 [ 빗썸 공개 API ] ──REST 폴링──▶ [ 조건 검사 ] ──▶ [ 주문 ] │ ▼ [ tkinter 창 ] ← 자주 멈춤 파일: 단일 .py · 168줄(v1.0f) → 1,047줄(v1.34) → 이후 계속 증가 없는 것: 재진입 통제 · 일일 손실 한도 · 시장 상황 판단 · 매매 기록 저장 없는 것 목록이 더 중요합니다. 손절은 있었어요. 그런데 같은 종목을 몇 번까지 살지, 하루에 얼마까지 잃으면 멈출지, 시장 전체가 빠지는 날엔 어떻게 할지에 대한 규칙이 하나도 없었습니다. 전략은 있는데 리스크 관리가 없는 구조였던 겁니다. 이후 개발사 전체를 한 줄로 줄이면, 이 빈칸을 하나씩 채워온 과정입니다.\n이 차이를 조금 더 풀어서 설명하면 이렇습니다. 개별 손절만 있는 봇은 시장이 통째로 빠지는 날에 가장 나쁘게 행동합니다. 조건이 서니까 사고, 빠지니까 잘리고, 또 조건이 서니까 또 삽니다. 봇은 규칙대로 완벽하게 동작하는데 계좌만 녹아요. 손절이 잘 작동할수록 손실이 규칙적으로 쌓입니다. 이 장면을 실제로 겪기 전까지는 \u0026ldquo;안 사는 규칙\u0026quot;의 필요성을 머리로만 알았습니다.\n창이 곧 프로세스였습니다 이 시절 운영 방식은 배치 파일 네 줄에 다 들어 있습니다. 폴더로 들어가서 파이썬을 띄우고, 마지막에 pause. 더블클릭으로 봇을 켜고 검은 창을 들여다보다가 아무 키나 눌러 닫는 구조였어요.\n더블클릭으로 켜는 봇의 사망 원인 · 창을 실수로 닫으면 죽는다 · PC가 절전으로 들어가면 죽는다 · 윈도우 자동 업데이트가 재부팅하면 죽는다 · 죽었다는 사실을 아무도 안 알려준다 -------------------------------------------------- 포지션을 들고 있는 상태에서 봇이 죽으면 손절도 익절도 아무도 안 한다 자동매매에서 제일 무서운 건 잘못 사는 게 아니라 들고 있는데 아무도 안 지켜보는 상태입니다. 손절선을 아무리 잘 정해놔도 프로세스가 죽어 있으면 그 값은 없는 것과 같습니다. 이 불안이 이 시대 내내 깔려 있었고, 결국 시대를 끝내는 이유 중 하나가 됩니다.\n그래서 이 시기 중반부터 알림을 붙이기 시작했습니다. 매수했는지, 손절했는지, 죽었는지를 밖에서 알 수 있게 하는 작업이었어요. 성능 개선이 아니라 운영 형태의 변경이었는데, 지나고 보면 이게 GUI를 버리는 결정의 예고편이었습니다. 화면 없이도 봇이 뭘 하는지 알 수 있게 되자, 화면이 없어도 되는 상태가 된 거죠.\n하루에 열 개씩 버전이 나왔습니다 이 시기 버전 번호가 어떻게 움직였는지 보면 좀 웃깁니다.\n열흘간의 버전 (보존 95개) 5/25 v1.04 첫 코드 — 메모의 \"2중 실행 방지\" 가 주석으로 올라감 5/26 v1.05 ~ v1.15 5/29 v1.31 ~ v1.90 하루에만 스무 개 넘게 5/30 v2.x 6/01 v3.39 3회 재시도 통신 로직 6/02 v4.66 무결성 점검의 원형 6/02 v4.73 EC2 세션 풀링 6/03 v83 ~ v90 → 같은 날 upbit_v091 시작 마지막 빗썸 버전 바로 다음 번호로 업비트 봇이 시작된다 — 번호가 거래소를 넘는다 지금 보면 무모했지만, 그 속도가 아니었으면 구조의 문제를 몸으로 배우지 못했을 겁니다. 하루에 열 번 고치다 보면 어디가 약한지 손끝으로 알게 되거든요.\n그렇게 빨리 돌 수 있었던 이유는 검증 절차가 없었기 때문입니다. 고치고, 돌리고, 눈으로 보고, 다음. 시뮬레이션도 백테스트도 없으니 배포까지 몇 분이면 됐어요. 지금은 파라미터 하나 바꾸는 데 매매기록 시뮬레이션을 돌리고 근거 수치를 커밋에 적습니다. 훨씬 느립니다. 어느 쪽이 나으냐고 물으면, 초기엔 속도가 맞았고 지금은 규율이 맞다고 답하겠습니다. 순서를 바꿨으면 둘 다 못 얻었을 것 같아요.\n다만 이 속도에는 대가가 있었습니다. 어떤 변경이 어떤 결과를 냈는지 짝을 지을 수 없다는 것. 30분 사이에 세 군데를 고치면, 그중 무엇이 효과였는지 알 방법이 없습니다. 지금 파라미터를 한 번에 하나씩만 바꾸는 규칙은 정확히 이 답답함의 반작용입니다.\n버전 관리가 파일 복사였습니다 이때는 git이 없었습니다. 버전 관리라는 게 파일을 복사해서 번호를 하나 올리는 것이었어요. 되돌리고 싶으면 예전 파일을 열면 되고, 그게 전부였습니다.\ngit 없이 461개를 남긴 방식 저장 파일 복사 → 번호 +1 되돌리기 예전 파일을 연다 변경 이력 파일 헤더 주석에 한 줄 브랜치 폴더를 하나 더 판다 -------------------------------------------------- 장점 되돌아갈 지점이 촘촘하다 단점 왜 바꿨는지가 대부분 안 남는다 폴더 이름이 사람 손을 타서 어긋난다 실제로 이 시대 파일 중에는 폴더 이름과 파일 헤더의 버전 번호가 어긋난 것들이 있습니다. 나중에 아카이브를 정리하면서 전부 파일 시각 기준으로 재정렬해야 했어요. 폴더 이름은 사람이 붙인 것이고 파일 시각은 시스템이 남긴 것이라, 둘이 다투면 시스템 쪽을 믿는 게 맞더군요.\n재밌는 건 코드가 한 줄도 안 바뀐 버전도 남아 있다는 점입니다. 파일명만 올라가고 내용은 동일한 버전이요. 큰 걸 건드리기 직전에 복사부터 해둔 흔적입니다. git이 없던 시절에도 \u0026ldquo;망하면 돌아갈 자리\u0026quot;를 먼저 만드는 본능은 있었던 셈이에요. 지금의 배포 규율은 그 본능이 도구를 만난 결과라고 생각합니다.\n여기서 태어나 지금까지 살아남은 것들 이 시대에 만들어져 461개 버전을 지나 현행까지 이어진 개념이 몇 개 있습니다.\n빗썸 기원기의 유산 v1.09 눌림목 대기 조건이 서도 바로 안 산다 -0.5% 더 빠지면 산다 → 현행 과매도 눌림목 전략의 원형 v1.31 슬롯 동시에 몇 개까지 들고 갈지 → 현행 4슬롯 운용 v1.34 RSI 도입 과매도를 숫자로 정의 → 문턱값 확정까지 두 달 더 걸림 v3.39 3회 재시도 통신 실패를 정상 상황으로 취급 v4.66 무결성 점검 봇의 기억을 의심하기 시작 -------------------------------------------------- 다섯 개 전부 \"더 잘 사는 법\" 이 아니라 \"덜 틀리는 법\" 이다 특히 v1.09가 이 시대의 분수령입니다. 그전까지 봇이 판단하는 건 \u0026ldquo;살까 말까\u0026rdquo; 하나였는데, 여기서 \u0026ldquo;얼마에 살까\u0026quot;가 추가됐어요. 조건이 서도 가격이 안 맞으면 안 삽니다. 신호와 체결 사이에 조건이 하나 더 끼어든 거죠.\n같은 버전에서 손절선도 함께 넓혔습니다. 더 낮게 사니까 더 넓게 버틸 수 있게 된 겁니다. 여기서 배운 게 하나 있는데, 진입가와 손절 폭은 따로 정하는 값이 아니라는 것입니다. 하나를 바꾸면 다른 하나도 다시 봐야 해요. 지금 파라미터를 바꿀 때마다 전체를 다시 시뮬레이션하는 이유가 이겁니다. 값 하나가 독립적으로 존재하지 않습니다.\nv4.66의 무결성 점검도 짚어둘 만합니다. 봇이 자기가 계산한 보유 현황을 의심하고 거래소에 다시 물어보는 로직인데, 이게 나중에 \u0026ldquo;거래소 잔고와 원 단위로 일치하지 않으면 배포 금지\u0026quot;라는 규칙으로 자랍니다. 자기 기억을 안 믿기로 한 첫 순간이 이 시대에 있었습니다.\n화면이 곧 전략이었습니다 이 시대의 코드를 열어보면 화면 그리는 코드와 매매 판단하는 코드가 한 클래스 안에 섞여 있습니다. 창을 그리는 객체가 시세 폴링도 하고 손절 판정도 해요.\n당시엔 그게 자연스러웠습니다. 봇의 상태를 알 수 있는 유일한 창구가 화면이었으니, 화면을 중심으로 코드를 짠 거죠. 그런데 이 구조에는 치명적인 부작용이 있었습니다. 전략을 검증할 방법이 없어집니다.\n화면과 판단이 붙어 있으면 전략을 시험하려면 → 창을 띄워야 하고 → 창을 띄우면 사람이 앉아 있어야 하고 → 실시간으로만 확인 가능하고 → 과거 데이터로 돌려볼 수 없다 -------------------------------------------------- 결과: 파라미터를 \"느낌\" 으로 정하게 된다 현행: 판단 로직이 화면과 분리 → 매매기록으로 재현 가능 이 구조 때문에 이 시대의 파라미터는 전부 감으로 정해졌습니다. 손절 몇 퍼센트, 익절 몇 퍼센트, RSI 몇 이하 — 근거가 \u0026ldquo;이게 나을 것 같다\u0026quot;였어요. 그리고 며칠 돌려보고 느낌이 안 좋으면 되돌립니다. 며칠은 표본이 아닌데도 그게 유일한 판단 근거였습니다.\n지금 봇이 매매기록 전수 시뮬레이션으로 파라미터를 정할 수 있는 건 판단 로직이 화면에서 완전히 분리돼 있기 때문입니다. 같은 로직에 과거 기록을 넣어 다시 돌릴 수 있으니까요. 그 구조를 얻기까지 시대가 다섯 개 더 걸렸습니다.\n실패 목록을 남겨둡니다 이 시대에 겪은 실패를 성격별로 나눠보면 이렇습니다. 이후 개발의 방향이 여기서 다 나왔어요.\n빗썸 기원기 실패 유형 [1] 시끄러운 실패 실행이 안 된다 · 창이 안 뜬다 즉시 발견 · 제일 싸다 [2] 에러 실패 주문 거부 · 인증 실패 로그를 보면 안다 [3] 조용한 실패 요청이 거부됐는데 그냥 다음 루프로 모른다 · 제일 비싸다 [4] 화면 거짓말 숫자가 잘려 보인다 · 낡은 값이 남아 있다 사람이 잘못된 판단을 한다 [5] 죽은 봇 프로세스가 종료됐는데 포지션은 그대로 손절도 익절도 아무도 안 한다 -------------------------------------------------- 이후 개발의 목표: 3·4·5 를 1로 끌어올리기 3번이 제일 무섭습니다. 봇이 멈추면 알아채고, 에러 창이 뜨면 알아채는데, 평소처럼 돌아가면서 아무것도 안 하는 상태는 구분할 방법이 없어요. 시장이 조용한 건지 봇이 고장 난 건지 모릅니다.\n지금 봇이 로그를 그렇게 많이 찍는 이유가 이겁니다. 게이트를 통과하지 못한 종목에 대해서도 어느 게이트에서 왜 떨어졌는지를 남깁니다. \u0026ldquo;안 샀다\u0026quot;와 \u0026ldquo;못 샀다\u0026quot;를 구분할 수 있어야 하니까요. 조용한 실패를 시끄러운 실패로 바꾸는 게 자동매매 개발의 절반이라고 생각합니다.\n통신이 끊긴다는 걸 배운 시기 6월 1일 v3.39에 \u0026ldquo;3회 재시도\u0026rdquo; 로직이 들어갑니다. 별것 아닌 것처럼 보이지만, 봇을 대하는 태도가 바뀐 지점이에요.\n그전까지 저는 통신 실패를 예외 상황으로 취급했습니다. 요청을 보내면 응답이 오는 게 정상이고, 안 오면 뭔가 잘못된 거라고 생각했죠. 그런데 실제로 며칠 돌려보면 통신은 그냥 끊깁니다. 거래소가 잠깐 느려지고, 와이파이가 몇 초 흔들리고, 응답이 늦게 옵니다. 이건 사고가 아니라 날씨예요.\n통신 실패를 대하는 두 가지 태도 [ 예외로 취급 ] 실패 → 로그 남기고 다음 루프 → 그 틱의 판단이 통째로 사라진다 → 손절 확인도 같이 사라진다 [ 정상 상황으로 취급 ] 실패 → 잠깐 쉬고 재시도 → 재시도 → 그래도 실패면 보고 → 대부분은 2회 안에 복구된다 \u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026ndash; 자동매매에서 \u0026ldquo;가끔 실패한다\u0026rdquo; 는 설계 조건이지 버그가 아니다\n특히 위험한 건 청산 경로의 통신 실패입니다. 매수는 실패해도 안 사면 그만인데, 손절 확인 요청이 실패하면 봇은 그 종목이 손절선을 넘었는지 모르는 채로 넘어갑니다. 사는 쪽의 실패는 기회비용이지만 파는 쪽의 실패는 실제 손실이에요. 지금 봇에서 청산 관련 통신에 더 엄격한 재시도와 검증이 붙어 있는 이유가 여기 있습니다.\n6월 2일 v4.73의 EC2 세션 풀링도 같은 계열입니다. 매번 새로 연결을 여는 대신 연결을 재사용하는 방식이죠. 표면적으로는 속도 개선이지만, 실제 효과는 연결 수립 단계에서 실패할 기회 자체를 줄이는 것이었습니다. 안 하는 일은 실패하지 않는다는 게 안정성의 기본이더군요.\n슬롯과 RSI, 두 개의 씨앗 5월 29일 v1.31에 슬롯 개념이 들어갑니다. 동시에 몇 종목까지 들고 갈지를 숫자로 정한 겁니다.\n지금 보면 당연한 장치인데, 이게 없으면 어떻게 되는지 생각해보면 필요성이 분명해집니다. 조건이 서는 종목이 열 개면 열 개를 다 삽니다. 현금이 허락하는 한 계속 사요. 그러면 시장이 통째로 좋을 때는 좋은데, 통째로 나쁠 때는 전 재산이 한 방향에 걸립니다. 슬롯은 수익을 위한 장치가 아니라 노출을 고정하는 장치입니다.\n슬롯이 하는 일 슬롯 없음 조건 성립 종목 = 매수 종목 시장이 좋은 날 → 전액 노출 시장이 나쁜 날 → 전액 노출 (같음) 슬롯 있음 동시 보유 상한 고정 최악의 날에 잃을 수 있는 금액이 계산 가능해진다 \u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026ndash; 현행 4슬롯 · 슬롯당 예산은 가용 현금의 고정 비중\n5월 29일 v1.34에는 RSI가 처음 들어갑니다. 과매도를 감이 아니라 숫자로 정의하려는 첫 시도였어요. 다만 이때 쓴 문턱값은 교과서에 나오는 값 그대로였고, 그게 실제 계좌에서 맞는 값인지는 확인할 방법이 없었습니다. 매매 기록을 체계적으로 안 남기던 시절이니까요.\n이 문턱값이 실측 데이터로 확정되기까지 두 달이 더 걸립니다. 지표를 넣는 것과 그 지표의 값을 정하는 건 전혀 다른 작업이라는 걸 이때는 몰랐어요. 지표는 하루면 붙이는데, 그 지표를 신뢰할 수 있는 숫자로 만드는 데는 두 달이 걸립니다.\n무엇이 끝을 냈나 두 가지였습니다.\n첫째, 윈도우에서 봇을 24시간 돌리는 게 계속 불안했습니다. 자동 업데이트 한 번, 재부팅 한 번에 포지션이 방치되니까요. 이 불안이 결국 맥북 구매로 이어지고, 맥으로 옮기면서 창 형태 GUI는 사라지고 터미널 모니터링만 남게 됩니다.\n둘째, 단타의 수수료를 계좌로 배웠습니다. 처음에 초단타로 시작한 건 순전히 호기심이었어요. 왕복 0.1%가 숫자로는 별거 아닌데, 하루에 수십 번 돌리면 그게 전부 나갑니다.\n회전율이 만드는 벽 (개념 계산) 왕복 수수료가 a 일 때, 회전 n 회의 누적 비용 = a × n -------------------------------------------------- 단타 회전 많음 · 회당 목표 작음 → 비용이 목표를 잠식한다 → 승률이 높아도 계좌는 준다 눌림목 회전 적음 · 회당 목표 큼 → 같은 비용이 훨씬 덜 아프다 -------------------------------------------------- ※ 실제 요율은 거래소·등급별로 다릅니다 요점은 숫자가 아니라 구조 — 회전율은 비용을 곱한다 이 감각이 이후 모든 전략 선택에 영향을 줬습니다. 회전이 많은 전략은 신호가 아무리 좋아도 비용에서 집니다. 지금 봇이 15분봉 기준으로 판단하고 하루에 몇 번만 진입하는 구조인 것도, 밀리초 싸움에 관심이 없는 것도 전부 이 시기의 학습에서 나왔어요. 속도가 아니라 회전율이 진짜 비용이라는 걸 계좌로 배웠습니다.\n단타를 해봐야 알게 되는 것들 초단타로 시작했다는 얘기를 조금 더 하겠습니다. 수수료 말고도 배운 게 있었거든요.\n첫째, 회전이 빠른 전략은 사람이 못 검증합니다. 하루에 수십 번 매매하면 로그가 화면을 순식간에 지나갑니다. 뭐가 잘 됐고 뭐가 잘못됐는지 눈으로는 못 따라가요. 기록을 남기지 않던 시절이라 그날 성적이 왜 그랬는지 설명할 수가 없었습니다. 전략의 회전율은 검증 난이도와 직결됩니다.\n둘째, 빠른 전략일수록 인프라 문제가 성적으로 직결됩니다. 응답이 0.5초 늦으면 15분봉 전략에서는 아무 일도 안 일어나지만, 초 단위 전략에서는 그게 손실입니다. 그러면 전략을 개선하는 대신 인프라를 개선하는 데 시간을 쓰게 돼요. 혼자 만드는 봇에서 그 방향은 밑 빠진 독입니다.\n전략 주기가 결정하는 것들 초 단위 15분봉 비용 회전 × 수수료 (큼) 회전 적음 검증 사람이 못 따라감 건별로 확인 가능 인프라 지연이 곧 손실 몇 초 늦어도 무관 개발 초점 속도 판단 -------------------------------------------------- 혼자 만드는 봇이 이길 수 있는 쪽은 오른쪽이었다 셋째, 이게 제일 중요한데 — 단타는 제가 원래 고치려던 문제를 오히려 키웠습니다. 시작이 자제력 문제였잖아요. 그런데 초단타 봇은 화면을 계속 보게 만듭니다. 로그가 끊임없이 올라가니까요. 자제력 때문에 만든 봇 앞에 하루 종일 앉아 있게 된 겁니다.\n이 자각이 이후 방향을 완전히 바꿨습니다. 좋은 자동매매는 성적이 좋은 게 아니라 안 봐도 되는 것이라는 기준이 생겼어요. 지금 봇이 하루에 몇 번만 진입하고, 알림이 상태 변화 때만 오고, 터미널 대시보드 한 장으로 끝나는 것도 전부 그 기준에서 나왔습니다.\n번호가 거래소를 넘어갑니다 6월 3일, 빗썸 v90 다음 번호로 업비트 v091이 시작됩니다. 거래소는 바뀌었는데 버전 번호는 이어졌어요.\n그 연속성이 이 봇의 정체성이라고 생각합니다. 새로 만든 게 아니라, 하던 걸 계속한 겁니다. 거래소를 옮기는 건 보통 프로젝트를 새로 시작할 명분이 되는데, 그때 저는 번호를 이어 붙였어요. 지금 돌아보면 그게 좋은 선택이었습니다. 처음부터 다시 시작했다면 빗썸에서 배운 것들 — 슬롯, 재시도, 무결성, 수수료 감각 — 을 절반쯤 흘렸을 겁니다.\n열흘, 95개 버전. 코드는 대부분 버려졌지만 이유는 하나도 안 버렸습니다.\n열흘의 타임라인, 다시 한 번 이 시대를 밀도로 보면 이렇게 됩니다.\n2026-05-25 ~ 06-03 · 무엇이 언제 생겼나 5/25 메모 한 장 코드 없음. 요구사항만 5/26 첫 코드 · 인증 통과 주문이 나가기 시작 5/28 눌림목 · 알림 전략과 운영이 동시에 5/29 슬롯 · RSI 노출 통제와 지표 6/01 재시도 로직 통신을 날씨로 인정 6/02 무결성 · 세션 풀링 자기 기억을 의심 6/03 업비트로 이관 번호는 이어짐 -------------------------------------------------- 9일 · 95개 버전 · 그중 지금까지 살아남은 개념 5개 95개를 만들어 5개가 남았습니다. 비율로 보면 5%예요. 낭비처럼 보이지만 그렇게 생각하지 않습니다. 남은 5개가 무엇인지는 90개를 만들어보기 전엔 알 수 없었으니까요.\n그리고 이 열흘의 진짜 산출물은 코드가 아니라 판단 기준이었습니다. 무엇이 위험한지, 무엇이 조용히 실패하는지, 어떤 전략이 나한테 안 맞는지. 그 기준이 있으니 다음 시대에는 같은 실수를 안 하게 됩니다. 두 번째 시대가 일주일 만에 지나간 것도 그래서예요.\n이 시대를 다시 읽는 방법 마지막으로, 이 아카이브를 어떻게 복원했는지 적어둡니다. 같은 처지에 놓인 분이 있을 수 있으니까요.\n461개 버전을 정리한 순서 1. 폴더명과 버전 번호를 전부 무시한다 2. 파일 시각으로 재정렬한다 ← 유일하게 못 속이는 값 3. 인접 버전끼리 diff 를 돌려 변경량을 뽑는다 4. 파일 헤더 주석에서 그날의 의도를 찾는다 5. 없으면 \"추정\" 이라고 표시하고 근거를 같이 적는다 6. 공개 전에 키·토큰·서버 주소를 먼저 지운다 -------------------------------------------------- 6번이 1번보다 먼저입니다. 읽기 전에 지우세요. 이 글에 \u0026ldquo;추정\u0026quot;이라는 단어가 자주 나오는 이유가 5번입니다. 코드가 어떻게 바뀌었는지는 diff로 확실히 알 수 있지만, 왜 바꿨는지는 대부분 안 남아 있어요. 확실한 것과 추정한 것을 섞어 쓰면 기록으로서 가치가 없어지니 굳이 매번 표시했습니다.\n6번은 실제로 제일 먼저 한 작업입니다. 이 시절 코드에는 인증 정보와 서버 주소가 파일에 그대로 박혀 있었어요. 설정 파일을 따로 만들 시간이 아까워서 그렇게 했던 건데, 그 편의의 대가가 두 달 뒤 아카이브를 공개하려 할 때 돌아왔습니다. 이 블로그의 어떤 화면에도 실제 값은 옮기지 않았습니다.\n그때의 나에게 해주고 싶은 말 아카이브를 정리하다 보면 자연스럽게 이런 생각이 듭니다. 그때 뭘 다르게 했으면 좋았을까.\n솔직히 대부분은 순서를 건너뛸 수 없었다고 생각합니다. 슬롯 제한의 필요성은 슬롯 없이 물려봐야 알고, 수수료의 무게는 수수료를 내봐야 압니다. \u0026ldquo;그때 리스크 관리부터 넣었어야지\u0026quot;라고 쓰는 건 지금이니까 쉬운 말이에요.\n다만 세 가지는 순서와 상관없이 처음부터 할 수 있었습니다.\n첫날부터 했어도 됐던 것들 1. 매매 기록을 파일로 남기기 전략이 뭐든 상관없다. 기록만 있으면 나중에 분석된다 안 남긴 대가: 이 시대 파라미터는 전부 근거가 없다 2. 키와 서버 주소를 설정 파일로 분리하기 10분이면 되는 작업 안 한 대가: 두 달 뒤 공개 정리에 훨씬 오래 걸림 3. 변경 이유를 한 줄이라도 남기기 주석이든 텍스트 파일이든 안 남긴 대가: 461개 중 상당수가 \"추정\" 으로만 복원됨 -------------------------------------------------- 셋 다 실력이 아니라 습관의 문제였다 1번이 특히 뼈아픕니다. 이 시기 봇은 실제로 돌면서 사고팔고 있었어요. 그 기록이 분석 가능한 형태로 쌓였다면 지금 두 달 치 데이터가 더 있었을 겁니다. 전략이 미숙했던 건 어쩔 수 없지만, 미숙한 전략의 기록도 데이터입니다. 그걸 안 남긴 건 실력 부족이 아니라 그냥 안 한 거예요.\n그래서 자동매매를 시작하는 분께 하나만 권한다면 이겁니다. 전략보다 기록을 먼저 만드세요. 전략은 어차피 바뀝니다. 기록은 안 바뀌고 계속 쌓입니다.\n남기고 싶은 문장 열흘을 한 문장으로 줄이면 이겁니다. 뼈대는 첫 주에 완성됐고, 그 뒤로 늘어난 건 전부 안 사는 이유였다.\n읽고, 판단하고, 주문한다 — 이 세 단계는 615줄짜리 첫 코드에도 있었고 지금 여섯 개 모듈에도 있습니다. 461개 버전 동안 늘어난 코드의 대부분은 진입을 막는 조건이에요. 게이트 열 개, 슬롯 상한, 손절, 본전 사수, 일일 한도, 레짐 차단. 전부 \u0026ldquo;여기선 안 한다\u0026quot;를 정해둔 것들입니다.\n자제력이 없어서 시작한 프로젝트가 결국 자제력을 코드로 구현하는 프로젝트가 됐습니다. 시작할 때 의도한 건 아닌데, 지나고 보니 그것 말고는 할 수 있는 게 없었더군요.\n","permalink":"https://botlab.co.kr/archive/01-bithumb/","summary":"장기투자 계좌에 손이 자꾸 가는 게 싫어서 규칙을 코드로 박은 게 시작이었습니다. 2026년 5월 25일부터 6월 3일까지 열흘간의 기록.","title":"1. 빗썸에서 시작했다 — 열흘, 95개 버전 | Crypto Trading Bot"},{"content":"0. 하루에 담긴 세 가지 2026년 5월 28일 저녁은 이 봇의 역사에서 가장 밀도 높은 몇 시간이다. 저녁 7시부터 밤 8시 반까지 버전이 여섯 개 넘게 쌓였다.\n앞 글에서는 그중 \u0026ldquo;숫자를 다루는 자리를 지운\u0026rdquo; 버전들을 봤다. 이 글은 나머지 셋이다. 성격이 완전히 다르다.\n2026-05-28 저녁 · 이 글이 다루는 세 버전 19:07 v1.09 668줄 +216/-190 눌림목 대기 로직 신설 이 시대 최대 규모의 수술 19:24 v1.11 704줄 +0/-0 코드 동일. 번호만 상승 git 없던 시절의 세이브 포인트 20:22 v1.15 775줄 +52/-21 알림 토큰 원클릭 발급 매매 로직은 한 줄도 안 건드림 -------------------------------------------------- 전략이 태어나고 · 겁을 먹고 · 화면에서 벗어나려 한 하루 1. 눌림목이 태어난 날 1-1. 이 시대 최대의 수술 이틀 만에 돌아온 버전이다. 668줄, +216/-190줄. 이 시대에서 손꼽는 대수술이다. 코드의 3분의 1이 바뀌었다. 그리고 이 봇 역사에서 가장 중요한 한 줄이 여기서 들어간다.\n빗썸 스마트 자동 매매 봇 v1.9 — 감시 엔진 로그 🚀 V1.0 엔진 로그인 성공! ⚙️ 24시간 감시 엔진 가동 진입 대기 폭 -0.5% · 익절 +2.0% · 손절 -4.5% ⏳ [눌림목 대기] 현재 4,120원 ➔ 4,099원까지 하락 대기 … 대기 … 대기 … 목표가 미달, 진입 보류 🤖 [알고리즘 진입] 50,000원 매수 시도 💥 [매수] 50,000원 🎯 1차 목표 도달. 50% 익절 🔥 [Trailing] 잔여 청산 1-2. target_entry_price target_entry_price라는 변수가 처음 생겼다. 그리고 사용자가 조절하는 대기 폭, 기본값 -0.5%.\n동작의 차이는 이렇다.\n진입 방식의 전환 [ 이전 ] 조건 성립 → 즉시 시장가 매수 조건 판정 시점의 가격에 그대로 산다 → 신호가 늦으면 고점에 산다 [ v1.09 ] 조건 성립 → 목표가 산출 → 대기 target = 현재가 × (1 - 0.005) 현재가가 target 이하로 내려오면 매수 → 안 내려오면 안 산다 \u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026ndash; \u0026ldquo;진입 조건\u0026quot;과 \u0026ldquo;진입 가격\u0026quot;이 분리된 순간\n이게 왜 큰 변화인지는 조금 뜯어봐야 한다. 이전 봇에서 조건 판정과 주문은 하나의 사건이었다. 조건이 서면 곧바로 산다. 그러니까 봇이 판단하는 건 \u0026ldquo;살까 말까\u0026rdquo; 하나뿐이었다.\nv1.09부터는 두 개다. 살까 말까, 그리고 얼마에 살까. 조건이 서도 가격이 안 맞으면 안 산다. 신호와 체결 사이에 조건이 하나 더 끼어든 것이다.\n1-3. 버튼 문구가 바뀌었다 재밌는 흔적이 하나 있다. UI 버튼 문구까지 같이 바뀌었다.\n버튼 문구의 변화 - [ 감시 엔진 시동 ] + [ 감시 엔진 시동 (눌림목 대기 가능) ] -------------------------------------------------- 이 시점에 이미 스스로 \"눌림목\"이라는 단어를 쓰고 있었다 이름이 붙었다는 건 개념이 정리됐다는 뜻이다 기능에 이름이 붙는 순간이 있다. 그전까지는 \u0026ldquo;조금 기다렸다 사는 로직\u0026quot;이었을 텐데, 이 버전에서 눌림목이라는 단어가 나온다. 이름이 붙으면 그 뒤로는 그 이름으로 사고하게 된다. 지금 봇의 전략을 한 줄로 부르면 \u0026ldquo;과매도 눌림목 반등\u0026quot;이다. 그 단어가 여기서 처음 코드에 들어왔다.\n1-4. 첫날 메모가 여기서 코드가 됐다 이 버전의 로그를 다시 보면 마지막 두 줄이 눈에 걸린다.\n메모 → 코드 · 사흘 만에 05-25 메모 · 목표 수익률 도달 시 50% 물량 즉시 익절 · 나머지 50%는 트레일링 스톱으로 추세 추적 05-28 로그 🎯 1차 목표 도달. 50% 익절 🔥 [Trailing] 잔여 청산 \u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026ndash; 문장으로 적어둔 요구사항이 사흘 만에 로그가 됐다\n첫 글에서 봤던 요구사항 메모의 두 문장이 여기서 실제 동작으로 나타난다. 목표 도달 시 절반 익절, 나머지는 트레일링. 사흘 걸렸다.\n분할 청산의 논리는 단순하다. 전량을 목표가에서 팔면 그 뒤 상승을 놓치고, 전량을 트레일링으로 끌면 목표가에서 확보할 수 있던 이익을 되돌릴 수 있다. 절반씩 나누면 둘 다 조금씩 가져간다. 최적해는 아니지만 후회를 반으로 줄이는 구조다.\n현행 봇은 다른 방식을 쓴다. +0.8%에 도달하면 +0.4%를 잠그는 본전 사수(SAFE), 그리고 포트폴리오 전체의 예상 실현액이 기준에 닿으면 전량 수확하는 방식이다. 물량을 쪼개는 대신 가격 하한을 올리는 쪽으로 진화했다. 하지만 뿌리는 같다 — 이익이 났을 때 그걸 어떻게 지킬 것인가.\n1-5. 손절선을 넓힌 이유 같이 들어간 변화도 방향이 같다. 손절선을 -3.00%에서 -4.50%로 넓혔다.\n왜 손절을 넓히는가 — 진입가와의 관계 [ 즉시 진입 + 좁은 손절 ] 진입 4,120 손절 -3.00% → 3,996 진입 직후 노이즈만으로 닿는다 [ 대기 진입 + 넓은 손절 ] 대기 4,099 손절 -4.50% → 3,914 더 낮게 사서 더 넓게 버틴다 \u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026ndash; 진입가를 낮추면 손절 여유가 생긴다 두 파라미터는 따로 노는 값이 아니다\n추정하자면 -3%는 노이즈에 자꾸 걸렸을 것이다. 진입가가 높으면 손절선이 시세 근처에 붙는다. 조금만 흔들려도 잘린다. 손절이 자주 걸리는 봇은 방향을 맞춰도 돈을 잃는다.\n여기서 배운 게 하나 있다. 진입가와 손절 폭은 따로 정하는 값이 아니다. 하나를 바꾸면 다른 하나도 다시 봐야 한다. 지금 봇에서 파라미터를 바꿀 때마다 매매기록 시뮬레이션을 다시 돌리는 이유가 이거다. 값 하나가 독립적으로 존재하지 않는다.\n감시 종목 기본값도 이때 변동성이 큰 쪽으로 바꿨다. 눌림목 전략은 애초에 움직임이 있어야 성립한다. 하루에 0.3% 움직이는 종목에서 -0.5% 대기는 영원히 안 채워진다.\n1-6. 안 사고 지나가는 것도 결과다 대기 진입에는 대가가 있다. 안 내려오면 못 산다.\n대기 진입의 두 얼굴 [ 잘 되는 경우 ] 4,120 → 4,099 터치 → 매수 → 반등 같은 신호를 0.5% 싸게 잡았다 [ 놓치는 경우 ] 4,120 → 안 내려옴 → 그대로 상승 신호는 맞았는데 못 샀다 \u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026ndash; 놓친 상승은 로그에 안 남는다 그래서 이 손해는 체감이 안 된다\n이게 은근히 까다로운 문제다. 물려서 잃은 돈은 계좌에 찍히지만, 못 사서 놓친 수익은 아무 데도 안 찍힌다. 그러니까 대기 폭을 넓힐수록 성적이 좋아 보이는 착시가 생긴다. 진입 횟수가 줄면서 승률이 올라가기 때문이다.\n이 착시를 깨려면 놓친 신호도 기록해야 한다. 지금 봇이 \u0026ldquo;게이트에서 탈락한 종목\u0026quot;을 로그에 남기는 이유 중 하나가 이거다. 안 산 것도 결정이고, 결정은 기록으로 남아야 나중에 평가할 수 있다.\n이 시점의 봇에는 그 기록이 없었다. [눌림목 대기] … 목표가 미달, 진입 보류 로그가 화면에 뜨고 끝이다. 며칠 뒤 그 종목이 얼마나 올랐는지는 아무도 모른다. 대기 폭을 -0.5%로 정한 근거가 감일 수밖에 없었던 이유이기도 하다. 비교할 데이터가 없으면 튜닝이 아니라 취향이다.\n1-7. 조급함을 코드로 눌러두기 지금 봇은 열 겹 게이트로 과매도 눌림목을 노린다. RSI, 거래량, 저점 근접도, 호가 단위 같은 조건을 전부 통과해야 진입 후보가 된다. 그 물건의 씨앗이 여기 있다.\n\u0026ldquo;지금 사지 말고 조금 더 빠지면 사라.\u0026rdquo; 문장 하나가 두 달을 버텼다.\nv1.09 → 현행 · 같은 문장의 진화 v1.09 조건 성립 → -0.5% 대기 → 진입 게이트 1개 (가격) 현행 게이트 10개 통과 → 진입 후보 RSI · 거래량 · 저점 근접 · 호가 단위 … 4슬롯 · 손절 -5% · SAFE 본전 사수 BTC 4시간봉 하락 시 신규 진입 차단 \u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026ndash; 늘어난 건 전부 \u0026ldquo;안 사는 이유\u0026quot;다\n돌아보면 기술이 아니라 성격의 문제였다. 조급함을 코드로 눌러둔 첫 시도다.\n사람이 직접 매매하면 -0.5%를 못 기다린다. 화면을 보고 있으면 지금 사야 할 것 같다. 놓칠 것 같다. 그 감각을 이길 방법이 나한테는 없었고, 그래서 봇에게 넘겼다. -0.50%는 내 인내심을 처음으로 숫자로 적어본 값이다.\n2. 한 줄도 안 바뀐 버전 2-1. diff가 비어 있다 17분 뒤. 704줄. 직전 버전 대비 +0 / -0줄. diff를 돌리면 아무것도 안 나온다. 파일명만 바뀌었다.\ndiff — bithumb_bot_v1_10.py ↔ v1_11.py $ diff bithumb_bot_v1_10.py bithumb_bot_v1_11.py $ _ -------------------------------------------------- $ ls -l 19:20 704 lines bithumb_bot_v1_10.py 19:24 704 lines bithumb_bot_v1_11.py ← 내용 동일 19:27 697 lines bithumb_bot_v1_14.py ← 3분 뒤 대수술 시작 아무것도 안 바꿨다. 그래서 이 절은 코드가 아니라 당시의 개발 방식에 대한 기록이다.\n2-2. 버전 관리가 파일 복사였던 시절 이때는 git이 없었다. 버전 관리라는 게 파일을 복사해서 번호를 하나 올려 저장하는 것이었다.\ngit 없는 형상 관리 저장 파일 복사 → 번호 +1 되돌리기 예전 파일을 연다 비교 두 파일을 나란히 놓고 눈으로 본다 브랜치 폴더를 하나 더 판다 기록 파일 헤더 주석 -------------------------------------------------- 문제 1 무엇을 왜 바꿨는지 안 남는다 문제 2 폴더 이름이 사람 손을 타서 어긋난다 문제 3 변경 하나를 되돌릴 수 없다 (파일 통째로만) 되돌리고 싶으면 예전 파일을 열면 되고, 그게 전부다. 원시적이지만 작동은 한다. 실제로 이 시대의 461개 중 상당수가 이 방식으로 남았고, 덕분에 지금 이 글을 쓸 수 있다.\n2-3. 왜 똑같은 파일을 하나 더 만들었나 추정은 둘이다.\n+0/-0 버전의 두 가지 해석 [가설 A] 세이브 포인트 \"지금부터 크게 건드린다. 망하면 여기로 돌아온다.\" 근거: 3분 뒤 버전에서 폰트·UI 구조가 크게 흔들린다 [가설 B] 실행 테스트용 사본 원본은 두고 사본으로 돌려본다 근거: 이 시기 파일 간격이 3~7분으로 매우 짧다 \u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026ndash; 공통점: 되돌아갈 자리를 먼저 만들어두는 습관\n가설 A가 유력하다. 실제로 3분 뒤 버전에서 폰트 체계와 UI 구조가 크게 흔들린다. 큰 걸 건드리기 직전에 복사부터 해둔 것이다.\n어느 쪽이든 공통점이 있다. 되돌아갈 자리를 먼저 만들어두는 습관. git을 안 쓰던 시절에도 그 본능은 있었던 셈이다.\n2-4. 이 방식이 남긴 상처 이 시절의 파일 복사 방식은 지금 아카이브를 정리하면서 대가를 치르게 했다.\n461개 버전을 정리하며 만난 문제들 · 폴더명과 파일 헤더 버전이 어긋난다 · 같은 번호가 두 번 나온다 · 무엇을 왜 바꿨는지 대부분 안 남아 있다 · 중간 시대 파일이 초기 폴더에 섞여 들어와 있다 -------------------------------------------------- 해결: 폴더명·번호를 무시하고 파일 시각으로 전부 재정렬 내용은 diff 로 복원, 의도는 추정으로 표시 그래서 이 아카이브의 글에는 \u0026ldquo;추정\u0026quot;이라는 단어가 자주 나온다. 코드 변화는 확실하지만 의도는 남아 있지 않기 때문이다. 확실한 것과 추정한 것을 섞어 쓰면 기록으로서 가치가 없어지니, 굳이 매번 표시한다.\ngit을 쓰기 시작한 뒤로 이 문제가 사라졌다. 커밋 메시지에 이유를 적고, 근거 수치를 동봉하고, 시각은 시스템이 알아서 남긴다. 지금의 규율은 미래의 나를 위한 게 아니라, 과거의 나에게 당해본 사람의 방어에 가깝다.\n2-5. 겁이 데이터가 될 때 지금 봇은 git으로 관리되고, 커밋마다 근거 수치를 같이 적고, 파라미터는 시뮬레이션을 통과해야만 배포된다. 그 규율의 원형이 여기 있다고 하면 과장일까.\n같은 본능, 다른 도구 2026-05-28 파일 복사 → 번호 +1 \"망하면 돌아온다\" 현행 브랜치 → 시뮬레이션 → 커밋 → 배포 \"검증 안 된 값은 안 올린다\" -------------------------------------------------- 방법은 조잡했지만 행동은 같다 실패했을 때 돌아갈 곳을 먼저 확보한다 방법이 조잡했을 뿐, 실패하면 돌아갈 곳을 확보하는 행동은 같다. 그리고 그 행동의 출처는 자신감이 아니라 겁이다. 자동매매에서 겁은 나쁜 감정이 아니다. 겁이 습관이 되면 규율이 된다.\n아무것도 안 바뀐 버전을 아카이브에서 안 지운 것도 같은 이유다. 빈 페이지도 기록이다. 이 파일은 코드를 한 줄도 안 남겼지만, 그날 저녁 내가 무엇을 앞두고 긴장했는지를 남겼다.\n3. 봇이 나에게 말을 걸게 만들기 3-1. 그날의 마지막 버전 밤 8시 22분. 775줄, +52/-21줄. 이날 마지막 버전이다. 매매 로직은 한 줄도 안 건드렸다. 대신 알림 채널을 손봤다.\nbithumb_bot_v1_15.py — 토큰 원클릭 발급 # 이전: 브라우저 → 별도 도구 → 복사 → 붙여넣기 (3단계, 손으로) + def generate_kakao_token(self): + auth_code = self.ent_auth_code.get().strip() + tokens = requests.post(AUTH_URL, data={...}).json() + json.dump(tokens, open(\"kakao_token.json\", \"w\")) -------------------------------------------------- ✅ 카카오 토큰이 성공적으로 발급 및 저장되었습니다! ❌ 인가 코드가 이미 사용되었거나 만료되었습니다. → 인가 코드는 일회용이고 금방 만료된다 ⚠️ client_id / 콜백 서버 주소가 소스에 하드코딩됨 (지금은 안 하는 방식) 3-2. 3단계를 1단계로 토큰 발급 함수가 새로 생겼다. 브라우저에서 받은 인가 코드를 붙여넣고 버튼을 누르면, 봇이 직접 인증 서버에 토큰 교환을 요청하고 받아온 토큰을 파일로 저장한다.\n토큰 발급 절차 — 이전 / 이후 [ 이전 ] 1. 브라우저에서 인가 코드 받기 2. 별도 도구로 토큰 교환 3. 결과를 복사해 봇에 넣기 → 중간에 꼬이면 1번부터 다시 [ v1.15 ]\n인가 코드를 붙여넣고 버튼 → 교환·저장까지 봇이 한다 \u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026ndash; 인가 코드는 일회용 · 유효 시간이 짧다 단계를 줄이는 게 곧 성공률이다 문제는 인가 코드가 일회용이고 금방 만료된다는 점이다. 브라우저에서 받아서 다른 도구를 열고 붙여넣는 사이에 시간이 간다. 한 번 실패하면 그 코드는 죽는다. 처음부터 다시다. 실패 메시지에 그 좌절이 그대로 적혀 있다.\n여기서도 원칙은 앞 글과 같다. 사람이 손으로 옮기는 단계를 지운다. 앞에서는 숫자였고 여기서는 문자열이다.\n3-3. 왜 이 시점이었을까 매매 로직을 한 줄도 안 건드리고 알림만 손봤다는 게 힌트다.\n추정: 화면을 안 보고도 봇을 돌리고 싶어졌기 때문이다.\n보고 있어야 하는 봇 → 알려주는 봇 [ 그때까지 ] 윈도우 PC에 창을 띄운 채 실행 창을 봐야만 상황을 안다 외출하면 봇이 뭘 하는지 모른다 봇이 죽어도 모른다 [ 알림이 붙으면 ] 매수했는지 · 손절했는지 · 죽었는지 밖에서도 안다 \u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026ndash; 전략이 아니라 운영 방식의 변경이다\n당시 봇은 윈도우 PC에 창을 띄운 채 돌았다. 창을 봐야만 상황을 아는 구조다. 알림이 휴대폰으로 오면 매수했는지, 손절했는지, 죽었는지를 밖에서 알 수 있다.\n이건 성능 개선이 아니다. 운영 형태의 변경이다. 그리고 자동매매에서 운영 형태는 전략만큼 중요하다. 하루 종일 화면 앞에 앉아 있어야 하는 봇은 아무리 성적이 좋아도 오래 못 돌린다. 사람이 먼저 지친다.\n3-4. GUI를 버리는 결정의 예고편 나중에 맥북으로 넘어가며 GUI를 버리고 터미널만 남기는 결정을 하게 되는데, 그 예고편이 여기다.\n화면의 역할이 옮겨간 순서 1단계 창이 유일한 정보원 v1.04 ~ 2단계 창 + 알림 (이중화) v1.15 ← 지금 여기 3단계 알림이 주 · 화면은 보조 4단계 터미널 대시보드 + 알림 현행 -------------------------------------------------- GUI를 버릴 수 있었던 건 알림이 먼저 있었기 때문이다 순서가 중요하다. GUI를 먼저 버리고 알림을 붙였다면 중간에 아무것도 모르는 구간이 생겼을 것이다. 알림을 먼저 만들어두고, 그게 믿을 만해진 뒤에 창을 버렸다. 보고 있어야 하는 봇에서, 알려주는 봇으로.\n3-5. 알림이 만드는 새로운 문제 알림을 붙이면 곧바로 다음 문제가 생긴다. 얼마나 자주 보낼 것인가.\n알림 빈도의 함정 너무 많이 틱마다 · 감시 상태마다 알림 → 하루 수십 건 → 읽지 않게 된다 → 정작 손절 알림도 놓친다 너무 적게 하루 요약 한 번 → 사고를 몇 시간 뒤에 안다\n적절히 진입 · 청산 · 손절 · 사망 상태가 바뀔 때만 보낸다\n알림이 많으면 안 읽는다. 안 읽으면 없는 것과 같다. 오히려 \u0026ldquo;알림을 받고 있다\u0026quot;는 착각 때문에 더 위험하다.\n원칙은 하나다. 상태가 바뀔 때만 보낸다. 감시 중은 상태 변화가 아니다. 매수, 청산, 손절, 그리고 봇이 죽는 것 — 이 넷만 상태 변화다. 이 시절엔 그 구분이 아직 없었지만, 알림 채널을 만들자마자 곧 마주하게 되는 질문이었다.\n3-6. 신뢰의 문제 자동매매에서 제일 어려운 건 전략이 아니라 신뢰다.\n켜놓고 잠들 수 있어야 자동매매지, 창을 지키고 있으면 도구 붙은 수동매매다. 그리고 그 신뢰는 성적에서 오지 않는다. 아무 일도 없을 때 아무 알림이 안 오고, 뭔가 일어났을 때 반드시 알림이 오는 것 — 그 예측 가능성에서 온다.\n지금 봇의 알림 설계도 같은 원칙 위에 있다. 진입, 청산, 손절, 그리고 죽었을 때. 그 네 가지만 확실하면 나머지는 화면을 안 봐도 된다.\n참고로 이 시절 코드엔 인증 식별자와 콜백 서버 주소가 파일에 그대로 박혀 있었다. 지금 기준으로는 절대 안 하는 방식이고, 이 글의 화면에도 자리만 표시해두고 값은 비웠다. 이런 값이 코드에 박히는 건 편의 때문이다 — 설정 파일을 따로 만들 시간이 아까워서. 그 편의의 대가가 몇 달 뒤 아카이브를 공개할 때 돌아온다.\n4. 하루가 남긴 것 4-1. 세 버전, 세 개의 축 2026-05-28 저녁 · 무엇이 바뀌었나 v1.09 전략 조건과 가격을 분리했다 진입가와 손절 폭이 한 쌍이 됐다 v1.11 규율 되돌아갈 자리를 먼저 만든다 겁이 습관이 되고 습관이 규율이 된다 v1.15 운영 화면에서 벗어나기 시작했다 알림이 먼저, GUI 폐기는 나중 -------------------------------------------------- 전략 · 규율 · 운영 — 지금 봇을 지탱하는 세 축이 하루 저녁에 전부 씨앗을 뿌렸다 세 버전이 각각 다른 축을 건드렸다는 게 지금 보면 신기하다. 그날은 그냥 손에 잡히는 대로 고쳤을 텐데, 지나고 보니 봇을 지탱하는 세 기둥이 그날 저녁에 다 세워졌다.\n4-2. 가장 중요한 값은 대개 가장 단순하다 -0.50%는 계산해서 나온 값이 아니다. 시뮬레이션도 백테스트도 없던 시절의 감이다. 지금 기준으로는 배포할 자격이 없는 값이다.\n그런데 이 값이 두 달을 버텼다. 정확히는 값이 아니라 구조가 버텼다. \u0026ldquo;조건이 서도 가격이 안 맞으면 안 산다\u0026quot;는 구조 말이다. 값은 나중에 시뮬레이션으로 몇 번이나 바뀌었지만 구조는 그대로다.\n값과 구조 — 무엇이 오래 사는가 값 -0.50% · -4.50% · +2.0% 시뮬레이션으로 계속 바뀐다 현행 봇의 손절은 -5%, 익절 구조도 다르다 구조 \u0026ldquo;조건 성립 ≠ 즉시 매수\u0026rdquo; \u0026ldquo;진입가와 손절 폭은 한 쌍이다\u0026rdquo; 두 달 넘게 안 바뀌었다 \u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026ndash; 파라미터 튜닝으로 얻는 것과 구조 변경으로 얻는 것은 크기가 다르다\n이 아카이브를 다시 읽으며 얻은 교훈 중 제일 실용적인 게 이거다. 성적이 안 나올 때 파라미터부터 만지게 되는데, 대부분의 큰 개선은 값이 아니라 구조에서 나왔다. +216/-190줄짜리 수술이 -0.5%라는 숫자보다 중요했다.\n4-3. 그리고 아직 없던 것 물론 이 시점에도 계좌 전체를 지키는 로직은 없었다. 슬롯 제한도, 일일 한도도, 시장 레짐 판단도 없다. 눌림목 대기는 더 좋은 가격에 사는 장치지 안 사는 장치가 아니다.\nv1.09 시점의 빈칸 ✔ 진입가 통제 (눌림목 대기) ✔ 개별 손절 -4.50% ✔ 분할 익절 + 트레일링 ✘ 동시 보유 슬롯 제한 ✘ 재진입 쿨다운 ✘ 일일 손실 한도 ✘ 시장 레짐 판단 -------------------------------------------------- \"더 잘 사는 것\"과 \"안 사는 것\"은 다른 문제다 후자를 배우는 데 시대가 몇 개 더 걸린다 하락장에서는 눌림목이 계속 나온다. 계속 나오고, 계속 더 빠진다. 대기 폭만 있는 봇은 그 장에서 열심히 잘 산다 — 그리고 계속 물린다. BTC 4시간봉으로 레짐을 판단해 신규 진입 자체를 차단하는 로직이 왜 필요했는지는 그 장을 겪어봐야 안다.\n4-4. 하루의 밀도 마지막으로 하나. 이날 저녁 한 시간 반 동안 여섯 개 넘는 버전이 나왔다. 지금 기준으로는 커밋 두세 개 분량이다.\n그렇게 빨리 돌 수 있었던 건 검증 절차가 없었기 때문이다. 고치고, 돌리고, 눈으로 보고, 다음. 시뮬레이션도 백테스트도 없으니 배포까지 3분이면 된다. 그 속도가 이 시대의 장점이자 전부였다.\n지금은 파라미터 하나 바꾸는 데 시뮬레이션을 돌리고 근거 수치를 커밋에 적는다. 훨씬 느리다. 대신 틀린 값이 실계좌로 나가지 않는다. 어느 쪽이 낫냐고 물으면, 초기엔 속도가 맞았고 지금은 규율이 맞다고 답하겠다. 순서를 바꿨으면 둘 다 못 얻었을 것 같다.\n짐 로저스는 돈이 구석에 놓일 때까지 기다렸다 줍는다고 했다. 나는 그 기다림을 못 하는 사람이라 봇에게 넘겼다. -0.50%는 내 인내심의 첫 수치화였다.\n레이 달리오는 실패보다 실패에서 아무것도 안 남기는 게 문제라고 했다. +0/-0 버전엔 코드가 안 남았지만 겁먹고 백업부터 뜬 흔적이 남았다. 그것도 데이터다.\n에드 세이코타는 좋은 트레이딩은 지루해야 한다고 했다. 지루하려면 안 보고 있어도 괜찮아야 한다. 알림을 붙인 그날 밤은 지루해지기 위한 첫 투자였다.\n","permalink":"https://botlab.co.kr/posts/the-day-the-pullback-was-born/","summary":"지금 사지 말고 0.5% 더 빠지면 사라 — 현행 전략의 첫 조상이 태어난 날. 그리고 한 줄도 안 바뀐 버전, 봇이 알림을 보내기 시작한 밤까지.","title":"눌림목이 태어난 날, 그리고 봇이 말을 걸기 시작한 날 | Crypto Trading Bot"},{"content":"0. 인증을 뚫고 나면 앞 글에서 봇은 로그인에 성공했다. 통신 엔진을 갈아끼우고, 서명을 맞추고, V1.0 엔진 로그인 성공!을 봤다.\n그런데 자동매매는 로그인부터가 아니라 로그인 다음부터가 진짜다. 계좌에 붙었다고 주문이 나가는 게 아니다. 이 글은 주문이 안 나가던 사흘의 기록이다. 원인은 두 종류였다 — 기계가 숫자를 잘못 다룬 것, 그리고 사람이 숫자를 잘못 다룰 여지가 남아 있던 것.\n이 글이 다루는 네 버전 05-26 22:05 v1.07 642줄 +27/-14 지수 표기 방지 05-28 19:20 v1.10 704줄 +49/-13 금액 ↔ 수량 자동 환산 05-28 19:27 v1.14 697줄 +30/-37 폰트 통일 · grid 수정 05-28 19:34 v1.12 744줄 +58/-11 Max 라벨 클릭 입력 -------------------------------------------------- 공통 주제: 사람이 숫자를 직접 치는 자리를 지운다 번호가 뒤엉킨 건 오타가 아니다. 그 얘기는 뒤에서 따로 한다.\n1. 수량이 1.234e-05로 나가서 주문이 죽었다 1-1. 파일 헤더가 전부를 말한다 첫 번째 사건은 인증에 성공한 그날 밤 10시 5분이다. 642줄, +27/-14줄. 파일 헤더에 이렇게 적혀 있다 — \u0026ldquo;지수 에러 방지 완벽 패치.\u0026rdquo;\nbithumb_bot_v1_7.py — 주문 수량 직렬화 \u0026gt;\u0026gt;\u0026gt; units = budget / price \u0026gt;\u0026gt;\u0026gt; str(units) '1.234e-05' # ← 이게 그대로 API 로 나갔다 ⚠️ 에러: {'status':'5500','message':'Invalid Parameter'} -------------------------------------------------- + formatted_units = f\"{units:.4f}\" + res = self.api.buy_market(ticker, formatted_units) \u0026gt;\u0026gt;\u0026gt; f\"{units:.4f}\" '0.0000' → 자릿수 고정. 지수 표기는 더 이상 안 나온다 💥 [수동 매수] 50,000원 완료 (수량: 12.1359) 1-2. 파이썬이 숫자를 문자열로 바꿀 때 파이썬은 아주 작거나 아주 큰 실수를 문자열로 바꿀 때 지수 표기를 쓴다. 기준은 대략 소수점 아래 다섯 자리 근처다.\n어디서부터 지수 표기가 되는가 \u0026gt;\u0026gt;\u0026gt; str(0.001) '0.001' \u0026gt;\u0026gt;\u0026gt; str(0.0001) '0.0001' \u0026gt;\u0026gt;\u0026gt; str(0.00001) '1e-05' ← 여기서 넘어간다 \u0026gt;\u0026gt;\u0026gt; str(0.00001234) '1.234e-05' -------------------------------------------------- 값은 같다. 표기만 다르다. 그런데 API 는 표기를 본다. 값이 바뀐 게 아니다. 표기만 바뀐다. 파이썬 안에서만 놀면 아무 문제가 없다. 문제는 그 문자열이 그대로 HTTP 요청 본문에 실려 나갈 때다.\n거래소 서버 입장에서는 숫자가 아니라 이상한 문자열이다. 1.234e-05를 수량으로 파싱하는 API는 별로 없다. 요청은 거부되고 status 5500이 돌아온다.\n1-3. 제일 나쁜 종류의 버그 여기서 진짜 문제는 에러 코드가 아니다. 봇이 그걸 모르고 다음 루프로 넘어간다는 점이다.\n조용히 실패하는 루프 tick 001 조건 성립 → 매수 요청 → 5500 (무시됨) tick 002 조건 성립 → 매수 요청 → 5500 (무시됨) tick 003 조건 성립 → 매수 요청 → 5500 (무시됨) -------------------------------------------------- 화면: 아무 일도 안 일어남 잔고: 그대로 사람: \"오늘은 조건이 안 서나 보다\" → 봇이 죽은 게 아니라 일하는 척을 하고 있었다 봇이 멈추면 알아챈다. 에러 창이 뜨면 알아챈다. 최악은 평소처럼 돌아가는데 아무것도 안 사는 것이다. 시장이 조용한 건지 봇이 고장 난 건지 구분할 방법이 없다.\n이 경험이 지금 봇의 로그 설계를 만들었다. 현행 봇은 게이트를 통과하지 못한 종목에 대해서도 어느 게이트에서 왜 떨어졌는지를 남긴다. \u0026ldquo;안 샀다\u0026quot;와 \u0026ldquo;못 샀다\u0026quot;를 구분할 수 있어야 하기 때문이다.\n1-4. 패치는 한 줄, 적용은 전 구간 고치는 방법 자체는 단순하다. 주문 직전에 소수점 자릿수를 고정한 문자열로 강제 변환한다. 서식 지정자를 쓰면 지수 표기가 나올 수 없다.\n중요한 건 그다음이다. 수량이 API로 나가는 지점 전부에 같은 처리를 넣었다.\n같은 변환을 넣은 지점들 ✔ 수동 매수 ✔ 수동 매도 ✔ 알고리즘 자동 진입 ✔ 자동 청산 (익절 / 손절) ✔ 잔량 정리 -------------------------------------------------- 그리고 로그에도 변환된 수량을 같이 찍는다 💥 [수동 매수] 50,000원 완료 (수량: 12.1359) → 또 터지면 눈으로 잡겠다는 뜻 버그를 한 군데서만 고치면 나머지 네 군데는 시한폭탄으로 남는다. 그리고 그 폭탄은 하필 자동 청산 경로에서 터진다 — 사람이 안 보고 있을 때.\n1-5. 왜 이제야 터졌나 추정이지만, 이 버그는 저가 코인에서만 터졌을 것이다.\n가격대별 주문 수량 (5만원 기준 · 개념 예시) 가격 5,000만원 → 수량 0.001 정상 표기 가격 4,000원 → 수량 12.5 정상 표기 가격 1원 → 수량 50000.0 정상 표기 가격 매우 낮음 → 수량 0.0000xx 지수 표기 위험 구간 -------------------------------------------------- ※ 수량 = 주문금액 ÷ 현재가. 실제 값은 종목마다 다르다 코인 가격이 높으면 수량이 0.001 근처라 지수 표기가 안 나온다. 특정 종목에서만 재현됐다면, 원인을 찾는 시간의 대부분은 \u0026ldquo;왜 얘만 안 되지\u0026quot;에 쓰였을 것이다. 그리고 이런 버그는 재현 조건을 못 찾으면 영원히 못 잡는다.\n1-6. 여기서 굳어진 원칙 전략을 아무리 잘 짜도 숫자가 문자열로 바뀌는 한 줄에서 다 무너진다. 이후로 나는 API 경계에서 타입과 자릿수를 항상 고정한다.\nAPI 경계 규칙 — 이때 생겨서 지금까지 · 숫자를 문자열로 넘길 땐 자릿수를 명시한다 · 자동 변환에 맡기지 않는다 · 나가는 값을 로그에 그대로 찍는다 · 호가 단위 / 최소 주문 수량은 경계에서 검사한다 -------------------------------------------------- 현행 봇의 게이트 10개 중 하나가 호가 단위 검사인 것도 이 계보에 속한다 자릿수를 명시하는 건 미신이 아니라 방어다.\n2. 급할 때 암산을 시키면 안 된다 2-1. 13분짜리 패치 이틀 뒤인 5월 28일 저녁. 직전 버전으로부터 13분. 704줄, +49/-13줄. 겉보기엔 편의 기능인데 막으려던 건 사고다.\n수동 긴급 제어 — 금액 ↔ 수량 연동 ──[ 수동 긴급 제어 ]────────────────────── ▸ 주문 금액 (KRW) [ 50,000 ] ↓ 실시간 시세로 자동 환산 ▸ 주문 수량 (개) [ 12.1359 ] ────────────────────────────────────────── # current_market_price 를 시세 루프에서 계속 갱신 calc_qty = num / self.current_market_price self.ent_manual_sell.insert(0, f\"{calc_qty:.4f}\") # 한쪽을 비우면 반대쪽도 같이 비운다 실시간 시세를 담아두는 변수를 새로 두고, 수동 주문창 두 개를 서로 묶었다. 금액 칸에 50,000을 치면 현재가로 나눠 수량 칸이 자동으로 채워진다. 반대로 수량을 치면 금액이 채워진다.\n작지만 중요한 디테일이 하나 있다. 한쪽을 비우면 반대쪽도 같이 비운다. 이걸 안 하면 금액을 지우고 다시 쳤을 때 예전 수량이 남아 있게 된다. 화면에 남은 낡은 숫자만큼 위험한 게 없다.\n2-2. 그 순간은 항상 최악의 타이밍이다 왜 13분 만에 급히 넣었을까. 추정은 이렇다.\n이 시절 봇에는 수동 긴급 제어 패널이 있었다. 자동매매가 이상하게 굴 때 사람이 직접 주문을 던지는 용도다. 그런데 그 패널을 쓰는 순간이 언제인지 생각해보면 답이 나온다.\n수동 개입이 필요한 순간의 조건 · 가격이 급하게 움직이는 중 · 봇의 판단을 신뢰할 수 없는 상태 · 손실이 커지고 있거나 커질 것 같음 · 시간이 없음 -------------------------------------------------- 이 상태에서 \"5만원어치면 몇 개지?\"를 암산한다 → 자릿수 하나 틀린 주문이 나간다 → 계산은 화면이 한다 암산은 평온할 때 잘 된다. 그리고 수동 제어 패널은 평온할 때 쓰지 않는다. 이 둘을 겹쳐놓은 게 애초에 설계 실수였다.\n2-3. 사흘의 흐름 위에서 보면 바로 앞 버전에서 지수 표기 때문에 주문이 죽는 걸 겪은 직후라는 점도 겹친다.\n같은 문제의 두 얼굴 v1.07 기계가 숫자를 잘못 다뤘다 0.00001234 → '1.234e-05' → 주문 거부 해결: 자릿수 고정 v1.10 사람이 숫자를 잘못 다룰 수 있었다 50,000원 → \u0026ldquo;몇 개지?\u0026rdquo; → 자릿수 오타 해결: 자동 환산 \u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026ndash; 둘 다 \u0026ldquo;숫자가 손을 거치는 지점\u0026quot;의 문제였다\n기계가 숫자를 잘못 다뤄 한 번 데였고, 이번엔 사람이 잘못 다룰 여지를 지운 것이다. 이틀 사이에 같은 교훈을 양쪽에서 배웠다.\n2-4. 자동화의 완성도는 수동 창구에 달려 있다 역설이 하나 있다. 자동화의 완성도는 수동 개입 창구의 안전성에 달려 있다.\n완전 자동을 목표로 하면 수동 패널은 임시방편처럼 느껴진다. 그래서 대충 만든다. 그런데 결국 사람이 손대는 순간은 오고, 그 순간은 항상 최악의 타이밍이다. 그때 임시방편으로 만든 창구가 사고를 낸다.\n지금 봇에 GUI는 없다. 터미널 대시보드뿐이다. 그래도 원칙은 같다 — 계산은 화면이 하고 사람은 확인만 한다. 사람이 하는 일은 판단이지 산수가 아니다.\n3. Max를 눌러서 넣게 만든 이유 3-1. 7분짜리 응급처치 같은 날 저녁, 7분 뒤. 744줄, +58/-11줄.\n수동 제어 — Max 클릭 입력 ──[ 수동 제어 ]─────────────────────────── ▸ 주문 금액 [ 50,000 ] (Max: 1,234,567원) ▸ 주문 수량 [ 12.1359 ] (Max: 12.345678개) 라벨을 클릭하면 그대로 입력된다 ↑ ────────────────────────────────────────── + self.lbl_max_coin.bind(\"\u0026lt;Button-1\u0026gt;\", lambda e: self.fill_max_coin()) # 라벨 텍스트를 되파싱해서 입력창에 넣는다 coin_str = txt.replace(\"(Max: \",\"\").replace(\"개)\",\"\").replace(\",\",\"\") ────────────────────────────────────────── 손 입력 12.3456 → 잔량 0.000078 → 슬롯 계산 어긋남 Max 클릭 12.345678 → 잔량 0 주문창 옆에 최대 금액·최대 수량 라벨을 붙이고 그 라벨을 클릭하면 값이 입력창에 자동으로 들어가게 했다.\n3-2. 좋은 설계는 아니다 구현은 소박하다. 라벨에 찍힌 텍스트에서 괄호와 콤마를 잘라내고 숫자로 바꿔 입력창에 넣는다. 화면에 그려진 문자열을 다시 파싱하는 방식이다.\n화면 문자열 되파싱 — 왜 위험한가 현재: 상태값 → 문자열 포맷 → 라벨 → 되파싱 → 입력창 포맷을 바꾸면 파싱이 깨진다 \"12.345678개\" → \"12.345678 개\" 만 돼도 실패 정석: 상태값 ──────────────────────→ 입력창 └→ 문자열 포맷 → 라벨 (표시 전용) \u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026ndash; 7분 만에 넣은 걸 감안하면 이건 설계가 아니라 응급처치다\n상태 값을 따로 들고 쓰는 게 맞다. 다만 그때는 정석을 고를 여유가 없었다. 그리고 이런 코드는 나중에 반드시 물린다 — 표시 형식을 한 번만 바꿔도 조용히 깨지기 때문이다. 이 글에서 굳이 짚어두는 이유다.\n3-3. 부스러기가 만드는 문제 목적은 명확하다. 전량 매도를 손으로 타이핑하지 않게 만드는 것.\n잔량 0.000078이 만드는 연쇄 보유 12.345678 손 입력 12.3456 → 매도 체결 잔량 0.000078 남음 ↓ · 최소 주문 수량 미만이라 팔 수도 없다 · 봇은 \"보유 중\"으로 인식한다 · 슬롯이 비지 않는다 → 다음 진입 차단 · 평단·수익률 계산이 어긋난다 -------------------------------------------------- Max 클릭 → 잔량 0 → 슬롯 정상 반환 보유 수량이 12.345678인데 손으로 12.3456을 치면 잔량이 남는다. 남은 부스러기는 최소 주문 수량 아래라 정리도 안 된다. 그런데 봇은 그 종목을 여전히 보유 중으로 본다. 슬롯 하나가 영원히 잠기는 것이다.\n클릭 한 번이면 그 오차가 통째로 사라진다. 7분짜리 패치의 값어치가 여기 있다.\n3-4. 사흘 동안 지운 것들 앞선 두 버전과 방향이 정확히 같다.\n사람이 숫자를 치는 자리 — 하나씩 지우기 v1.07 주문 수량의 표기를 기계가 정한다 ✔ v1.10 금액 ↔ 수량 변환을 기계가 한다 ✔ v1.12 최대값 입력을 기계가 채운다 ✔ -------------------------------------------------- 남은 것: 사람은 \"얼마를\" 정하고 \"확인\" 버튼만 누른다 사람이 숫자를 직접 치는 구간을 하나씩 지워나간 사흘이었다. 지금 봇엔 이런 버튼이 없다. 애초에 사람이 수량을 칠 일이 없으니까. 하지만 거기까지 오는 데, 실수할 수 있는 자리를 손으로 하나하나 막는 과정이 있었다.\n4. 폰트를 바꾼 게 아니라 깨진 창을 고친 것 4-1. 줄 수가 줄어든 드문 버전 같은 날 저녁, 앞선 버전들 사이에 낀 시각이다. 697줄, +30/-37줄. 줄 수가 줄어든 드문 버전이다. 기능을 넣은 게 아니라 정리한 버전이라는 뜻이다.\nbithumb_bot_v1_14.py — 폰트 통일 · grid 에러 - self.std_font = (\"나눔스퀘어\", 10) - self.input_font = (\"나눔스퀘어\", 11, \"bold\") + # [수정] 모든 폰트를 '맑은 고딕'으로 통일하고 bold 속성 제거 + self.std_font = (\"맑은 고딕\", 10) + self.input_font = (\"맑은 고딕\", 11) + # [에러 해결 구역] grid 괄호를 정상적으로 닫았습니다. -------------------------------------------------- 폴더명 bt_v1_14 ↔ 파일 헤더 \"v1.12 (맑은 고딕 UI 최적화)\" → 이관 중 폴더 이름이 뒤바뀐 것으로 추정. 정답은 파일 시각. 4-2. 폰트가 없으면 창이 깨진다 폰트 체계를 통째로 갈았다. 기본 폰트를 맑은 고딕으로 통일하고 입력창의 bold를 뺐다. 주석에도 그대로 적어뒀다.\n추정: 폰트가 없어서 창이 깨졌을 것이다.\n왜 맑은 고딕인가 나눔스퀘어 설치돼 있어야 쓴다 개발 PC엔 있고 다른 PC엔 없을 수 있다 맑은 고딕 윈도우 기본 탑재 어느 윈도우에서도 같은 폭으로 그려진다 -------------------------------------------------- tkinter는 폰트가 없으면 대체 폰트로 그린다. 글자 폭이 달라지고 → 위젯 크기가 달라지고 → 레이아웃이 통째로 밀린다 tkinter는 폰트를 못 찾으면 조용히 다른 폰트로 대체한다. 에러도 안 난다. 다만 글자 폭이 달라지고, 폭이 달라지면 위젯 크기가 달라지고, 위젯 크기가 달라지면 배치가 밀린다.\n4-3. 잘린 숫자는 자동매매에서 위험하다 여기서 끝났으면 그냥 디자인 이슈다. 문제는 이게 자동매매 화면이라는 점이다.\n레이아웃이 밀렸을 때 [ 정상 ] 보유수량 12.345678 평단 4,120원 [ 밀림 ] 보유수량 12.3456… 평단 4,12… ↑ 잘려서 안 보인다 -------------------------------------------------- 잔고를 잘못 읽으면 잘못 누른다 앞 절에서 만든 Max 클릭이 필요했던 이유이기도 하다 숫자가 잘려 보이는 대시보드는 꽤 위험하다. 잔고를 잘못 읽으면 잘못 누른다. 앞 절에서 \u0026ldquo;화면 문자열을 되파싱한다\u0026quot;고 했던 그 구조를 떠올리면 더 그렇다 — 화면이 틀리면 입력도 틀린다.\n4-4. 주석으로 남은 로그 또 하나. 주석에 [에러 해결 구역] grid 괄호를 정상적으로 닫았습니다가 남아 있다. 괄호 하나 때문에 실행이 안 되던 걸 잡은 흔적이다.\n이 시절엔 이런 게 로그 대신 주석으로 남았다. git이 없으니 \u0026ldquo;무엇을 왜 고쳤는지\u0026quot;를 적을 데가 코드 안밖에 없었다. 파일 헤더에 버전 설명을 쓰고, 고친 자리마다 대괄호 표시를 남기고. 지금 기준으로는 지저분한 코드지만, 그 덕에 두 달 뒤의 내가 이 글을 쓸 수 있다.\n4-5. 아카이브의 정답은 파일 시각 이 버전엔 이상한 점이 하나 있다. 폴더명이 bt_v1_14인데 파일 헤더엔 v1.12로 적혀 있다. 그리고 7분 뒤 버전은 그 반대다 — 폴더명이 bt_v1_12인데 헤더는 v1.14.\n폴더명과 헤더가 뒤바뀐 흔적 19:24 704줄 폴더 bt_v1_11 헤더 v1.11 일치 19:27 697줄 폴더 bt_v1_14 헤더 v1.12 불일치 19:34 744줄 폴더 bt_v1_12 헤더 v1.14 불일치 -------------------------------------------------- 파일 시각과 헤더 번호는 서로 앞뒤가 맞는다 → 이관 중 폴더 이름이 뒤바뀐 것으로 추정 → 아카이브의 정답은 파일 시각 파일 시각과 헤더 번호는 서로 앞뒤가 맞으니, 이관 과정에서 폴더 이름이 뒤바뀐 것으로 추정한다. 아카이브의 정답은 파일 시각이라는 원칙을 여기서 배웠다. 폴더 이름은 사람이 붙인 것이고, 파일 시각은 시스템이 남긴 것이다. 둘이 다투면 시스템 쪽을 믿는다.\n이 글에서 버전 번호가 뒤엉켜 보이는 것도 그래서다. 시각 순으로 늘어놓았기 때문이다.\n5. 사흘이 남긴 것 5-1. 실패의 세 가지 등급 이 사흘 동안 만난 실패를 등급으로 나눠보면 이렇다.\n실패의 등급 — 비용 순 [ 1급 ] 시끄럽게 실패 실행이 안 된다 (grid 괄호) 즉시 발견. 제일 싸다. [ 2급 ] 에러로 실패 status 5500 로그를 보면 안다. [ 3급 ] 조용히 실패 주문 거부 후 다음 루프 모른다. 제일 비싸다. [ 3급 ] 화면이 거짓말 숫자가 잘려 보인다 사람이 잘못된 판단을 한다. -------------------------------------------------- 개발의 목표는 3급을 1급으로 끌어올리는 것 지금 봇이 로그를 그렇게 많이 찍는 이유가 이거다. 조용한 실패를 시끄러운 실패로 바꾸는 작업이다. 게이트 열 개를 통과하지 못한 종목마다 어느 게이트에서 왜 떨어졌는지를 남기는 것도, 결국 \u0026ldquo;안 샀다\u0026quot;와 \u0026ldquo;못 샀다\u0026quot;를 구분하기 위해서다.\n5-2. 사람은 판단하고 기계는 계산한다 네 버전을 관통하는 문장 하나를 뽑으면 이거다. 사람이 숫자를 직접 다루는 자리를 지운다.\n지수 표기는 기계가 숫자를 문자열로 바꾸는 자리에서 터졌고, 암산과 자릿수 오타는 사람이 숫자를 입력하는 자리에서 터졌고, 잘린 화면은 사람이 숫자를 읽는 자리에서 터졌다. 세 자리 모두 \u0026ldquo;숫자가 형태를 바꾸는 경계\u0026quot;다.\n숫자가 형태를 바꾸는 경계들 내부 실수값 │ ① 문자열로 변환 ← v1.07 사고 지점 ▼ API 요청 문자열 │ 서버 → 응답 → 잔고 │ ② 화면에 그리기 ← v1.14 사고 지점 ▼ 사람이 읽는 숫자 │ ③ 사람이 다시 입력 ← v1.10 / v1.12 사고 지점 ▼ 다시 내부 실수값 -------------------------------------------------- 경계마다 검증을 넣는다. 이게 이 사흘의 결론. 5-3. 응급처치를 기록해두는 이유 이 사흘의 코드에는 자랑할 게 별로 없다. 화면 문자열을 되파싱하고, 라벨에서 콤마를 잘라내고, 주석에 \u0026ldquo;에러 해결 구역\u0026quot;이라고 적어놓는다. 7분, 13분 간격으로 나온 결과물이 정교할 리 없다.\n그런데 응급처치를 기록으로 남기는 게 정석 코드를 남기는 것만큼 쓸모 있다고 생각한다. 나중에 같은 자리를 다시 지나갈 때, \u0026ldquo;그때 왜 이렇게 급하게 했나\u0026quot;를 알면 지금 무엇을 갚아야 하는지가 보인다. 정석으로 다시 짤 자리와, 그냥 두어도 되는 자리를 구분할 수 있게 된다.\n이 사흘의 부채 — 갚은 것과 남긴 것 갚음 API 경계 자릿수 고정 → 현행에도 그대로 살아 있다 갚음 금액↔수량 자동 환산 → 사람 입력 자체가 사라짐 갚음 화면 문자열 되파싱 → GUI를 버리며 자연 소멸 갚음 환경 의존 폰트 → 터미널로 이주하며 소멸 -------------------------------------------------- 부채의 절반은 갚은 게 아니라 구조가 바뀌며 없어졌다 그래도 원칙은 남았다 재밌는 건, 이 사흘의 기술 부채 대부분을 리팩터링으로 갚은 게 아니라는 점이다. GUI를 버리면서 통째로 사라졌다. 응급처치 코드가 나쁜 코드였다는 사실은 변하지 않지만, 그게 남긴 원칙은 구조가 바뀌어도 살아남았다. 코드는 버려도 이유는 안 버린다.\n5-4. 그리고 청소도 기록이다 기능을 안 늘리고 줄 수를 줄인 버전이 계보에 남아 있는 게 나쁘지 않다. 하루에 열 개씩 버전을 뽑던 시기에도 가끔은 청소를 했다는 뜻이니까.\n지금도 그렇다. 파라미터를 하나도 안 건드리고 로그 형식만 정리한 커밋이 종종 있다. 그런 커밋은 수익률을 1원도 못 올린다. 대신 다음 사고를 더 빨리 찾게 해준다. 매매 성적으로 환산되지 않는 작업의 값어치를 인정하는 게, 자동매매를 오래 돌리는 데 생각보다 중요하다.\n마크 더글러스는 트레이딩의 대부분이 실행의 문제라고 했다. 봇도 같더라. 신호가 맞아도 주문 문자열이 틀렸으면 결과는 0원이다.\n알렉산더 엘더는 계획대로 거래하지 않으면 시장이 대신 계획을 세운다고 했다. 급할 때 하는 암산은 계획이 아니라 즉흥이다. 그 자리를 UI에서 지우는 게 이 사흘의 작업이었다.\n마크 미너비니는 리스크를 먼저 정한 뒤 진입한다고 했다. 나는 아직 오타를 막는 단계였지만 방향은 같았다. 결과를 통제하려면 입력부터 통제해야 한다.\n","permalink":"https://botlab.co.kr/posts/orders-that-never-went-out/","summary":"지수 표기 하나로 주문이 죽고, 암산 한 번으로 자릿수가 틀린다. 사흘 동안 사람이 숫자를 직접 치는 자리를 하나씩 지웠다.","title":"주문이 나가지 않는다 — 숫자와 사람 사이의 버그들 | Crypto Trading Bot"},{"content":"0. 시작하기 전에 461개 버전을 처음부터 다시 열어보기로 했다. 이유는 단순하다. 지금 돌아가는 봇의 파라미터 하나하나에 \u0026ldquo;왜 이 값인가\u0026quot;를 설명할 수 있어야 하는데, 그 근거의 절반쯤은 초기 몇 주에 만들어졌기 때문이다. 그때 뭘 겪었는지 모르면 지금 값이 왜 그 자리에 있는지도 모른다.\n그래서 아카이브를 시각 순으로 정렬했다. 그리고 첫 화면에서 좀 웃었다. 가장 오래된 파일이 코드가 아니었다.\n1. 봇보다 먼저 있었던 건 메모 한 장 1-1. Futuring Request.txt, 2026년 5월 25일 목록의 맨 위에는 Futuring Request.txt가 있다. 봇에게 뭘 고쳐달라고 적어둔 요구사항 메모다. 그 옆에는 윈도우에서 봇을 띄우던 배치파일이 같이 남아 있다.\nC:\\codezero — 아카이브 최초 목록 C:\\Trader\u0026gt; type RunTrader.bat @echo off cd /d \"C:\\codezero\" python \"bithumb_pure_bot.py\" pause C:\\Trader\u0026gt; dir /od Futuring Request.txt ← 최초 요구사항 메모 (보존 최고문서) bithumb.txt / BAPI.txt ← 당시 API 규격 메모 bithum.pdf ← 거래소 문서 v81~v90_slot_states.json 배치파일 네 줄이 이 시절을 다 설명한다. C:\\codezero로 들어가서 파이썬을 띄우고 pause. 마지막 줄이 특히 그렇다. pause가 있다는 건 더블클릭으로 켰다는 뜻이다. 콘솔에서 실행했다면 창이 저절로 닫히지 않으니 pause가 필요 없다. 아이콘을 두 번 눌러 봇을 켜고, 검은 창에 뜬 로그를 눈으로 읽고, 다 보면 아무 키나 눌러 닫던 시절이다.\n지금은 맥북에서 터미널 세션에 붙여놓고 돌린다. 켜는 방식 하나가 그 시절 운영 방식 전부를 요약한다. 더블클릭으로 켜는 봇은 필연적으로 사람이 앞에 앉아 있는 봇이다. 창을 닫으면 죽고, PC가 잠들면 죽고, 윈도우 업데이트가 재부팅을 걸면 죽는다. 그러니까 이 시절의 운영이란 결국 \u0026ldquo;봇을 지키고 앉아 있기\u0026quot;였다.\n목록에 같이 남아 있는 bithumb.txt, BAPI.txt, bithum.pdf도 같은 시절의 증거다. 거래소 API 문서를 텍스트로 뜯어 옆에 펼쳐놓고 코드를 짰다는 뜻이다. 지금 같으면 문서 링크 하나 북마크해두면 끝인데, 그때는 규격 메모를 로컬 파일로 들고 다녔다. 왜 그랬는지는 뒤에서 나온다 — 인증 규격 하나 틀리면 아무것도 안 되는데, 그 \u0026ldquo;하나\u0026quot;가 문서 어디에 적혀 있는지 계속 확인해야 했기 때문이다.\nv81~v90_slot_states.json은 훨씬 나중 파일이 같은 폴더에 섞여 들어온 흔적이다. 이 시절엔 없던 개념 — 슬롯 — 이 파일 이름에 들어 있다. 아카이브를 시각 순으로 정렬해야 하는 이유가 이런 데 있다. 폴더는 거짓말을 한다.\n1-2. 메모 안에 이미 들어 있던 것 메모 쪽이 훨씬 재밌다. 내용의 대부분은 UI 요청이다. 시총 화면 색을 통일해달라, 연결 버튼을 연결/연결해지 둘로 쪼개달라, 입력창 글자를 키워달라. 지금 읽으면 자동매매 요구사항이라기보단 화면 불만 목록에 가깝다.\n그런데 그 사이에 문장 두 개가 섞여 있다.\nFuturing Request.txt — 발췌 [ UI 요청 ] · 시총 화면 색 통일 · 연결 버튼 → 연결 / 연결해지 분리 · 입력창 글자 크게 [ 매매 요청 ] · 목표 수익률 도달 시 50% 물량 즉시 익절 · 나머지 50%는 트레일링 스톱으로 추세를 끝까지 추적\n[ 안정성 요청 ] · 2중으로 창 실행을 막는 로직을 추가해줘\n첫날부터 익절 폭, 분할 청산, 트레일링 스톱이 요구사항에 있었다는 뜻이다. 코드 한 줄 없던 시점에 이미 \u0026ldquo;얼마에 팔고 나머지는 어떻게 끌고 갈지\u0026quot;를 문장으로 정해뒀다.\n그리고 마지막 한 줄. \u0026ldquo;2중으로 창 실행을 막는 로직을 추가해줘.\u0026rdquo; 이건 전략이 아니라 사고 방지다. 같은 봇이 두 번 떠 있으면 잔고를 두 번 읽고 주문을 두 번 낸다. 실제로 이 문장은 바로 다음 버전 코드의 파일 맨 첫 줄에 그대로 박힌다.\n1-3. 이 메모가 남긴 것 기록을 정리하면서 알게 된 건, 코드를 짜기 전에 문장을 먼저 썼다는 사실이다. 그 문장들은 대부분 화면 이야기였지만, 그 안에 이미 손익 처리의 뼈대가 들어 있었다.\n재밌는 건 메모의 비율이다. UI 요청이 대여섯 줄인데 매매 요청은 두 줄이다. 화면 이야기가 훨씬 많다. 그럴 수밖에 없다 — 그때 내가 매일 마주하던 건 전략이 아니라 창이었으니까. 봇의 성능을 판단하는 유일한 창구가 그 창이었고, 창이 불편하면 봇이 잘 돌아가는지도 알 수 없었다.\n지금은 정반대다. 화면은 터미널 대시보드 한 장이고, 요구사항의 100%가 게이트 조건과 파라미터다. 그 사이 두 달 동안 \u0026ldquo;보는 문제\u0026quot;가 \u0026ldquo;판단하는 문제\u0026quot;로 옮겨간 것이다. 다만 순서는 이게 맞았다고 생각한다. 볼 수 없으면 판단도 못 한다.\n461개 버전을 다시 여는 이유도 여기 있다. 뭘 고쳤는지보다 왜 고쳐야 했는지가 안 남으면, 다음에 또 같은 자리에서 넘어진다. 지금 커밋마다 근거 수치를 같이 적는 습관도 결국 이 메모의 후손이다. 형식은 텍스트 파일에서 git 커밋으로 바뀌었지만, 하는 일은 똑같다 — 미래의 나에게 이유를 남기는 것.\n2. 615줄짜리 시조 2-1. 계보의 0번 보존된 코드 중 가장 오래된 파일은 615줄, 2026년 5월 26일 밤 9시 11분이다. 461개 버전의 시조다. 파일 머리에 적힌 메모는 딱 한 줄인데, 전날 메모의 마지막 문장과 정확히 같다.\nbithumb_bot_v1_4.py — 615 lines ================= [ 2중 실행 방지 로직 ] ================= import tkinter as tk from tkinter import ttk, messagebox import pybithumb # --- 시세·차트는 라이브러리로 --- mkt_price = pybithumb.get_current_price(ticker) df = pybithumb.get_ohlcv(ticker) # --- 인증이 필요한 구간만 직접 구현 --- class CustomBithumbAPI: def _req(...) # HMAC-SHA512 서명 def get_balance(...) def buy_market(...) def sell_market(...) 요구사항 메모가 파일 헤더가 되는 흐름이 꽤 정직하다. 문장 → 주석 → 코드. 지금은 그 자리를 커밋 메시지가 대신한다.\n2-2. 한 파일에 전부 구조를 그려보면 이렇다.\nv1.04 구조도 — 단일 파일 615줄 bithumb_bot_v1_4.py ├─ CustomBithumbAPI 인증 통신 엔진 │ ├─ _req() HMAC-SHA512 서명 + POST │ ├─ get_balance() │ ├─ buy_market() │ └─ sell_market() ├─ TraderApp(tk.Tk) GUI + 매매 로직 │ ├─ build_ui() tkinter 위젯 배치 │ ├─ on_tick() 시세 폴링 루프 │ └─ 손절 판정 개별 포지션 단위 └─ pybithumb 공개 데이터 전담 클래스 하나가 API를 감싸고, 다른 하나가 창을 그린다. 그리고 그 창을 그리는 클래스 안에 매매 판단까지 같이 들어 있다. 화면과 전략이 분리돼 있지 않다는 뜻이다.\n이게 왜 문제가 되는지는 나중에 아주 비싸게 배운다. 화면을 그리는 코드와 판단하는 코드가 한 클래스에 있으면, 전략을 검증할 방법이 없다. 백테스트를 돌리려면 창을 띄워야 하고, 창을 띄우면 사람이 앉아 있어야 한다. 검증이 불가능한 구조다. 지금 봇이 매매기록 시뮬레이션으로 모든 파라미터를 검증하고 배포하는 규율을 갖게 된 건, 이 구조를 뜯어내는 데 몇 시대가 걸린 뒤였다.\n지금 구조와 나란히 놓으면 이렇다.\nv1.04 ↔ 현행 — 같은 일, 다른 배치 [ 2026-05-26 · 1 파일 615줄 ] 통신 + 화면 + 판단 ─ 전부 한 덩어리 검증 방법: 없음 (눈으로 본다) 되돌리기: 파일 복사 [ 현행 · 6 모듈 · git ] 통신 / 데이터 / 게이트 / 슬롯 / 청산 / 알림 검증 방법: 매매기록 시뮬레이션 되돌리기: git revert 공통: 읽는다 → 판단한다 → 주문한다\n2-3. 왜 하필 tkinter였나 지금 보면 의아한 선택이다. 웹 대시보드를 만들 수도 있었고, 그냥 콘솔에 로그만 찍어도 됐다. 그런데 창을 그렸다.\n이유는 단순하다. 버튼이 필요했기 때문이다. 자동매매를 처음 돌리는 사람이 제일 무서워하는 건 \u0026ldquo;지금 당장 멈추는 방법이 없는 것\u0026quot;이다. 콘솔 로그만 있으면 이상한 걸 보고도 할 수 있는 게 없다. 창에 빨간 버튼이 하나 있으면 다르다.\ntkinter를 고른 이유 — 당시 기준 ✔ 파이썬 표준 라이브러리 (설치 불필요) ✔ 버튼·입력창을 몇 줄로 만든다 ✔ 윈도우에서 더블클릭으로 바로 뜬다 ✘ 화면 코드와 로직 코드가 섞이기 쉽다 ✘ 창이 떠 있어야만 봇이 산다 ✘ 폰트·해상도 환경을 타면 레이아웃이 밀린다 → 장점 셋은 첫 주에, 단점 셋은 그 뒤 두 달에 걸쳐 나타난다 tkinter는 파이썬에 기본 포함돼 있어 아무것도 설치할 필요가 없다. 진입 장벽이 0이다. 그 대가로 화면과 로직이 섞이고, 창이 곧 프로세스가 된다. 뒤에 나올 폰트 사고도 이 선택의 연장선이다.\n2-4. 왜 인증만 직접 짰나 여기가 이 버전에서 제일 마음에 드는 선택이다. 통신이 두 갈래로 나뉘어 있다.\n시세, 분봉처럼 인증이 필요 없는 공개 데이터 → pybithumb 라이브러리를 그대로 사용 잔고 조회, 주문처럼 돈이 움직이는 구간 → 직접 구현 인증 요청은 공개 요청과 성격이 다르다. 요청 본문을 정해진 규칙대로 문자열로 조립하고, 비밀키로 해시를 만들어 헤더에 같이 실어 보낸다. 서버는 같은 규칙으로 해시를 다시 만들어 비교한다. 하나라도 어긋나면 거절이다 — 문자열 조립 순서, 인코딩, 구분자, 심지어 해시 결과를 어떤 타입으로 헤더에 넣는지까지.\n인증 요청이 실패하는 지점들 payload → 문자열 조립 순서·구분자 틀리면 실패 → 인코딩 UTF-8 아니면 실패 → HMAC-SHA512 키 오타면 실패 → base64 타입 틀리면 실패 → 헤더에 싣기 헤더 이름 틀리면 실패 그리고 전부 같은 메시지로 돌아온다: 인증 실패 라이브러리를 쓰면 저 다섯 단계가 한 줄 뒤로 숨는다. 잘 될 땐 편하다. 문제는 안 될 때다. 어느 단계에서 틀렸는지 알 방법이 없다. 남의 코드가 어디서 실패하는지 모르는 채로 실주문을 내보내는 게 더 무서웠다. 그래서 인증 구간만 손으로 짰다.\n여기서 nonce라는 값도 처음 만난다. 요청마다 달라지는 숫자 — 보통 밀리초 단위 시각 — 를 서명에 섞어 넣는 장치다. 같은 요청을 누가 가로채서 그대로 다시 보내도 서버가 \u0026ldquo;이미 지나간 nonce\u0026quot;라며 거절한다. 재전송 공격을 막는 표준적인 방법이다.\nnonce — 같은 요청이 두 번 통하지 않게 1차 요청 nonce=1748232011234 → 통과 가로채기 동일 요청 그대로 재전송 2차 요청 nonce=1748232011234 → 거절 (지나간 값) -------------------------------------------------- 부작용: PC 시계가 서버와 크게 어긋나면 정상 요청도 거절된다 → 인증 실패의 원인 후보가 하나 더 늘어난다 문제는 이게 인증 실패 원인 목록을 하나 더 늘린다는 점이다. 서명이 틀린 건지, 키가 틀린 건지, 시계가 어긋난 건지 — 서버는 전부 같은 코드로 답한다. 뒤에 나올 34분짜리 사건의 배경이 여기 다 깔려 있다.\n공개 데이터를 라이브러리에 맡긴 것도 게으름은 아니었다. 시세와 분봉은 초 단위로 계속 불러야 하는데, 그 반복 요청을 안정적으로 돌리는 코드는 생각보다 손이 많이 간다. 재시도, 타임아웃, 호출 간격 조절 같은 것들이다. 그리고 그쪽은 틀려도 돈이 안 나간다. 잘못 읽으면 안 사면 그만이다.\n이 감각은 지금도 그대로다. 편한 건 라이브러리에 맡기고, 위험한 건 직접 짠다. 경계선은 하나다 — 틀렸을 때 돈이 움직이는가.\n2-5. 있는 리스크와 없는 리스크 추정이지만, 이 시점에 손절은 있었고 그 위의 통제는 없었다. 재진입 통제도, 일일 손실 한도도, 시장 상황 판단도 없다.\nv1.04 리스크 통제 — 있는 것 / 없는 것 ✔ 개별 포지션 손절 ✘ 동시 보유 슬롯 제한 ✘ 재진입 쿨다운 ✘ 일일 손실 한도 ✘ 시장 레짐 판단 (하락장 진입 차단) → 개별 거래의 리스크는 있고 계좌 전체의 리스크는 없다 한 종목이 손절선에 닿으면 자른다. 그런데 다섯 종목이 동시에 물려도 봇은 그걸 하나의 사건으로 인식하지 못한다. 손절이 다섯 번 따로 일어날 뿐이다.\n차이가 어디서 벌어지는지 보면 이렇다. 개별 손절만 있는 봇은 시장이 통째로 빠지는 날에 가장 나쁘게 행동한다. 다 물리고, 다 잘리고, 그다음에도 조건이 서면 또 산다. 손절이 잘 작동할수록 손실이 규칙적으로 쌓인다.\n하락장에서 두 구조의 차이 (개념도) [ 개별 손절만 있는 구조 ] 시장 하락 → 5종목 동시 진입 → 5회 손절 → 조건 재성립 → 또 진입 → 또 손절 봇은 아무 잘못도 안 했고 계좌만 녹는다 [ 계좌 단위 통제가 있는 구조 ] BTC 4시간봉 하락 → 신규 진입 자체를 차단 동시 보유 4슬롯 → 노출 상한 고정 안 사는 것이 전략이 된다\n지금 봇에 4슬롯 제한이 있고, BTC 4시간봉이 하락일 때 신규 진입 자체를 차단하는 레짐 로직이 있는 이유가 이 빈칸이다. 이후 두 달은 저 ✘ 다섯 줄을 하나씩 지우는 과정이었다. 그리고 순서가 재밌다 — 진입 조건을 정교하게 만드는 작업보다, 안 사는 조건을 만드는 작업이 훨씬 뒤에 왔다. 대부분의 초보가 그 순서로 간다고 생각한다.\n2-6. 615줄에 대한 소회 615줄은 지금 보면 귀엽다. 현행 봇은 모듈이 여섯 개고 git으로 관리되고, 파라미터는 매매기록 시뮬레이션을 통과해야만 배포된다.\n그런데 뼈대는 안 바뀌었다. 읽고, 판단하고, 주문한다. 늘어난 건 오직 하나 — \u0026ldquo;안 사는 이유\u0026quot;의 개수다. 615줄에서 지금까지, 코드가 늘어난 양의 대부분은 매수를 막는 조건이다.\n3. 34분 만에 심장을 갈아끼웠다 3-1. 최신을 버리고 구버전으로 직전 버전으로부터 34분 뒤. 629줄, +40/-26줄. 시간은 짧은데 바꾼 건 봇의 심장이다.\nbithumb_bot_v1_5.py — 인증 엔진 교체 - class CustomBithumbAPI: # V2 규격 - data = {\"endpoint\":…, \"nonce\":…} - query = quote(json.dumps(data)) - \"Api-Sign\": b64encode(sig) # bytes! + class CustomBithumbAPI_V1: # API 1.0 규격 + str_data = urllib.parse.urlencode(data) + query = endpoint + chr(0) + str_data + chr(0) + nonce + \"Content-Type\": \"application/x-www-form-urlencoded\" + \"Api-Sign\": b64encode(sig).decode('utf-8') -------------------------------------------------- ⚠️ 로그인 실패: {'status': '5300'} 🚀 V1.0 엔진 로그인 성공! V2 대응 클래스를 지우고 API 1.0 전용 클래스를 새로 썼다. 최신 규격을 버리고 구버전으로 후퇴한 것이다.\n3-2. 서명 문자열이 다르다 두 규격의 차이는 \u0026ldquo;무엇을 해시하는가\u0026quot;에 있다.\n서명 대상 문자열 조립 — 두 방식 [ 이전 ] JSON 직렬화 후 URL 인코딩 {\"endpoint\":\"/info/balance\",\"nonce\":\"1748...\"} → quote(json.dumps(data)) → %7B%22endpoint%22%3A... [ 이후 ] 폼 인코딩 + NULL 구분자 endpoint=/info/balance\u0026amp;currency=BTC → endpoint + chr(0) + str_data + chr(0) + nonce → /info/balance\\0endpoint=\u0026hellip;\u0026amp;currency=BTC\\0 1748\u0026hellip; ↑ chr(0) = NULL 문자. 눈에 안 보이는 구분자다\nchr(0)은 화면에 아무것도 안 그려진다. 로그로 찍어봐도 그냥 붙어 보인다. 서명 문자열을 눈으로 검증하기가 사실상 불가능하다는 뜻이다. 대신 규격이 단순하다 — 사전 순 정렬도, JSON 공백 처리도 신경 쓸 게 없다. 눈으로 못 보는 대신 틀릴 여지가 적은 방식을 고른 셈이다.\nJSON 방식이 왜 까다로운지도 짚어둘 만하다. 같은 데이터를 JSON으로 만들어도 결과 문자열은 여러 가지가 나온다. 키 순서가 다를 수 있고, 콜론 뒤 공백을 넣을 수도 안 넣을 수도 있고, 한글이 들어가면 이스케이프 방식이 갈린다. 사람 눈에는 다 같은 데이터인데 바이트로는 전부 다른 문자열이다. 해시는 바이트 하나만 달라도 완전히 다른 값을 뱉는다.\n같은 데이터, 다른 바이트 {\"a\":1,\"b\":2} ← 공백 없음 {\"a\": 1, \"b\": 2} ← 공백 있음 {\"b\":2,\"a\":1} ← 키 순서 다름 -------------------------------------------------- 사람 눈: 전부 같은 데이터 해시 : 전부 다른 결과 폼 인코딩은 이 함정이 훨씬 적다 폼 인코딩은 키=값\u0026amp;키=값 한 줄이라 변형의 여지가 작다. 후퇴처럼 보이지만, 실은 변수를 줄이는 선택이었다.\n3-3. .decode('utf-8') 한 줄 더 결정적인 건 마지막 한 줄이다. 서명 결과에 .decode('utf-8')이 붙었다. 이전엔 base64 인코딩 결과가 bytes 객체 그대로 헤더에 실리고 있었다.\nbytes vs str — 헤더에 실릴 때 \u0026gt;\u0026gt;\u0026gt; sig = base64.b64encode(h.digest()) \u0026gt;\u0026gt;\u0026gt; sig b'YWJjZGVm...' ← bytes \u0026gt;\u0026gt;\u0026gt; headers = {\"Api-Sign\": sig} HTTP 라이브러리가 알아서 처리해 줄 때도 있고 b'...' 접두어까지 문자열로 만들어 보낼 때도 있다 -------------------------------------------------- \u0026gt;\u0026gt;\u0026gt; sig.decode('utf-8') 'YWJjZGVm...' ← str. 논쟁의 여지가 없다 이런 실수는 최악의 방식으로 실패한다. 어떤 환경에선 조용히 통과하고 어떤 서버에선 거절된다. 심지어 같은 코드가 라이브러리 버전에 따라 다르게 동작한다. 재현이 안 되는 버그가 되는 것이다.\n3-4. 왜 34분이었나 추정하자면 인증이 계속 실패했을 것이다. 그것도 왜 실패하는지 메시지가 안 나오는 종류로. 로그에 남은 status 5300은 그냥 \u0026ldquo;인증 실패\u0026quot;다. 어느 단계가 틀렸는지는 안 알려준다.\n34분 동안의 선택지 [A] V2 규격을 계속 디버깅한다 → 후보가 다섯 개(조립·인코딩·키·타입·헤더) → 서버 응답은 전부 같은 코드 → 소요 시간 예측 불가 [B] 확실히 동작하는 규격으로 후퇴한다 → 문서가 오래돼 예제가 많다 → 조립 규칙이 단순하다 → 34분\n34분 만에 엔진을 갈아엎었다는 건 원인 추적을 포기하고 확실히 되는 길로 갈아탔다는 뜻이다.\n같은 버전에서 키 입력창에 눈 아이콘 토글을 붙인 것도 같은 흔적이다. 마스킹된 키에서는 오타를 찾을 방법이 없다. 앞에 여덟 자리가 맞는지, 마지막에 공백이 하나 붙었는지 — 별표만 보고는 알 수 없다. 그것마저 의심하고 있었다는 뜻이다. 서명 로직을 의심하고, 규격을 의심하고, 결국 자기가 붙여넣은 문자열까지 의심했다.\n디버깅이 막히면 대개 이 순서로 간다. 코드를 의심하다가 → 문서를 의심하다가 → 입력을 의심한다. 그리고 셋 다 아니면 규격을 바꾼다. 34분은 그 네 단계를 다 밟기에 딱 맞는 시간이다.\n한 가지 더. 이 버전에서 만든 후퇴 판단이 좋았던 이유는 되돌릴 수 있었기 때문이다. 이전 버전 파일이 그대로 남아 있으니, V1 엔진도 안 되면 다시 열면 그만이다. git이 없던 시절인데도 후퇴가 안전했던 건 파일을 복사해두는 습관 덕이었다. 이 습관이 얼마나 뿌리 깊은지는 다음 글에서 아주 극단적인 형태로 다시 나온다.\n3-5. 그날 밤에 정리된 디버깅 순서 이 사건 이후로 내 디버깅 순서가 하나 굳었다. 인증이든 주문이든, 외부 시스템과 통신하다 막히면 아래 순서로 좁힌다.\n외부 API가 막혔을 때 — 좁혀가는 순서 1. 최소 요청 하나만 남긴다 잔고 조회 등 가장 단순한 것 2. 서명 대상 문자열을 찍는다 repr() 로. 눈에 안 보이는 문자까지 3. 타입을 확인한다 bytes 인가 str 인가 4. 입력값을 의심한다 키 앞뒤 공백, 복사 누락 5. 시계를 확인한다 nonce 가 서버 기준에서 유효한가 6. 여기까지 아니면 규격을 바꾼다 -------------------------------------------------- 2번의 repr() 하나가 그날 밤 34분을 아꼈을 수도 있다 특히 2번이 중요하다. print로 찍으면 chr(0) 같은 문자는 보이지 않지만 repr로 찍으면 \\x00으로 나온다. 눈에 안 보이는 것을 눈에 보이게 만드는 게 디버깅의 절반이다. 지금 봇의 로그가 숫자를 찍을 때 자릿수를 명시하고, 게이트 판정 결과를 통과/탈락 이유까지 같이 찍는 것도 같은 원칙이다. 판단의 근거를 화면에 남긴다.\n4. 이틀치 타임라인 세 파일을 시각으로 늘어놓으면 이렇게 된다.\n2026-05-25 ~ 05-26 · 파일 시각 기준 05-25 Futuring Request.txt 요구사항 메모 (코드 없음) · 50% 익절 + 50% 트레일링 · 2중 실행 방지 05-26 21:11 v1.04 615줄 시조. 메모 → 파일 헤더 05-26 21:45 v1.05 629줄 +40/-26 · 인증 엔진 전면 교체 34분 만의 후퇴 -------------------------------------------------- 이틀 동안 늘어난 코드: 615 → 629줄 (+14) 이틀 동안 바뀐 것 : 봇의 심장 줄 수만 보면 14줄 늘었다. 이 시기 기록을 줄 수로 읽으면 아무것도 안 보인다는 걸 잘 보여주는 구간이다. 그래서 이 아카이브는 줄 수와 함께 무엇이 바뀌었는지를 항상 같이 적는다. 지금 커밋 메시지에 근거 수치를 동봉하는 규칙도 같은 이유다. 숫자만 남기면 나중의 내가 못 읽는다.\n한 가지 덧붙이면, 이 이틀은 \u0026ldquo;하루에 열 개씩 버전을 뽑던 시기\u0026quot;의 시작이기도 하다. 34분 간격, 20분 간격으로 파일이 쌓인다. 지금 기준으로는 커밋 하나로 묶였을 변경들이 전부 별도 버전 번호를 받았다. 버전 번호가 곧 저장 버튼이었기 때문이다.\n그게 나쁘기만 한 건 아니었다. 되돌아갈 지점이 촘촘하다는 뜻이니까. 나쁜 쪽은 따로 있다 — 어떤 변경이 어떤 결과를 냈는지 짝을 지을 수 없다. 34분 안에 서명 방식도 바꾸고, 헤더 타입도 바꾸고, 입력창 토글도 붙였다. 그중 무엇이 로그인 성공을 만들었는지 이 기록만으로는 확정할 수 없다. 지금 봇에서 파라미터를 한 번에 하나씩만 바꾸고 시뮬레이션을 돌리는 규율은, 정확히 이 답답함의 반작용이다.\n5. 이틀이 남긴 것 세 개의 파일 — 메모, 615줄, 그리고 34분짜리 후퇴 — 을 나란히 놓고 보면 이후 두 달의 방향이 이미 보인다.\n첫째, 문장을 먼저 쓴다. 요구사항 메모가 파일 헤더가 되고, 파일 헤더가 코드가 됐다. 지금은 커밋 메시지에 근거 수치를 같이 적는다. 형식만 바뀌었지 순서는 같다. 왜 바꾸는지 먼저 쓰고 그다음에 바꾼다.\n둘째, 돈이 오가는 구간은 남에게 안 맡긴다. 시세는 라이브러리로 받고 주문은 직접 짠 그 선택이 지금까지 유지된다. 편의와 통제 중에 통제를 고르는 자리가 어디인지, 그 경계는 첫 코드에서 정해졌다.\n그 경계선은 지금도 매 버전 다시 그어진다. 새 라이브러리를 붙일 때마다 같은 질문을 한다 — 이게 틀리면 주문이 나가나, 아니면 안 사고 넘어가나. 후자면 붙이고, 전자면 직접 짜거나 최소한 결과를 한 번 더 검증한다.\n셋째, 새로 나온 것보다 확실히 동작하는 것. 34분의 후퇴가 알려준 건 두 가지다. 하나는 최신 규격이 항상 정답은 아니라는 것. 다른 하나는 안 되는 걸 붙들고 있는 시간보다 되는 길로 갈아타는 결단이 쌀 때가 있다는 것. 지금 봇이 시뮬레이션으로 검증되지 않은 값을 절대 배포하지 않는 것도 같은 원칙의 다른 얼굴이다. 검증된 것만 실계좌에 올린다.\n넷째, 실패는 조용할수록 비싸다. 인증 실패는 그래도 로그에 뭔가 찍혔다. 앞으로 나올 버그들은 그마저도 없다. 주문이 거부됐는데 봇은 다음 루프로 넘어가고, 화면엔 아무 일도 안 일어난 것처럼 보인다. 이때의 status 5300이 차라리 친절했다는 걸 그다음 날 알게 된다.\n그리고 하나 더. 이 이틀 동안 계좌 전체를 지키는 로직은 한 줄도 없었다. 손절은 있었지만 슬롯도, 쿨다운도, 레짐 판단도 없었다. 그게 없다는 걸 이때는 몰랐다.\n몰랐다는 사실 자체가 기록으로 남아 있는 게 아카이브의 쓸모다. 지금 시점에서 \u0026ldquo;그때 슬롯 제한을 넣었어야지\u0026quot;라고 쓰는 건 쉽다. 하지만 그때의 나는 주문이 나가는 것만으로도 벅찼다. 순서를 건너뛸 수 있었다고 말하는 건 대체로 거짓말이다. 다만 어느 지점에서 다음 단계로 올라갔어야 하는지는 기록해둘 수 있다. 그게 이 아카이브가 하는 일이다.\n이틀 동안 만들어진 건 결국 세 가지다 — 문장, 615줄, 그리고 후퇴할 줄 아는 습관. 뒤의 459개 버전은 여기에 \u0026ldquo;안 사는 이유\u0026quot;를 붙여나간 기록이다.\n마지막으로 하나. 이 시절 코드에는 인증 식별자나 서버 주소 같은 값이 파일에 그대로 박혀 있었다. 지금은 안 하는 방식이라 이 글의 어떤 화면에도 실제 값은 옮기지 않았다. 아카이브를 공개 기록으로 정리할 때 제일 먼저 하는 일이 그 값들을 골라내는 작업이었다. 초기 코드를 다시 여는 사람이 있다면 이 순서를 권한다 — 읽기 전에 지운다.\n다음 글에서는 이 봇이 실제로 주문을 못 내던 이야기가 나온다. 인증은 뚫었는데 숫자가 발목을 잡는다. 그리고 그 뒤엔 숫자가 아니라 사람이 발목을 잡는다.\n폴 튜더 존스는 방어가 공격보다 중요하다고 했다. 이 이틀의 방어는 2중 실행 방지 하나뿐이었지만, 그걸 파일 맨 위에 적어뒀다는 게 방향은 맞았다는 증거다.\n제시 리버모어는 시장이 틀린 게 아니라 사람이 틀린 거라고 했다. 코드도 같다. API가 이상한 게 아니라 내 서명이 틀린 거였다. 그걸 인정하는 데 34분이 걸렸다.\n","permalink":"https://botlab.co.kr/posts/genesis-three-days/","summary":"아카이브에서 가장 오래된 파일은 코드가 아니라 메모였다. 그 메모가 615줄이 되고, 34분 만에 심장을 갈아끼우기까지.","title":"메모 한 장에서 615줄까지 — 봇이 태어난 이틀 | Crypto Trading Bot"},{"content":"시간이 없는 사람의 투자 직장인의 하루엔 남는 시간이 별로 없다. 아침에 출근해서 회의 몇 개 치르고, 밀린 일 쳐내다 보면 해가 진다. 퇴근하고 나면 이미 지쳐서, 뭘 배우거나 공부할 여유조차 안 생긴다. 그런 삶 속에서 소소하게 시작한 주식이나 코인 투자는, 대개 물리는 걸로 끝났다. 사놓고 잊어버리거나, 잊어버리려다 다시 열어보거나. 차트를 들여다볼 시간은 없는데, 계좌를 신경 쓸 이유는 매일 생겼다.\n이 구조가 왜 나쁘냐면, 시간이 없는 사람일수록 판단의 밀도가 낮아지기 때문이다. 하루 종일 차트를 보는 사람은 틀려도 자주 틀리고 자주 고친다. 그런데 하루에 세 번 계좌를 여는 사람은 그 세 번의 순간에 모든 결정이 몰린다. 그리고 그 세 번은 대개 마음이 불편해서 여는 순간이다.\n계좌를 여는 순간의 분포 출근길 지하철 간밤에 뭔 일 있었나 점심시간 아까 빨간불이었는데 자기 전 오늘 왜 이렇게 빠졌지 -------------------------------------------------- 전부 \"불안해서\" 여는 순간이다 그리고 그 자리에서 결정을 내린다 → 판단의 횟수는 적은데 전부 감정 상태에서 나온다 시간이 없다는 건 정보가 부족하다는 뜻만이 아니다. 차분한 상태에서 판단할 기회가 없다는 뜻이다. 이게 내가 처음 인정해야 했던 사실이다.\n감정이 계좌를 움직인다 인간은 원래 그런 존재다. 계좌가 -20%를 찍으면 갑자기 기도가 늘어난다. \u0026ldquo;제발 본전만\u0026hellip;\u0026rdquo; 하면서 평소엔 믿지도 않던 것들에 빌게 된다. 반대로 +5%만 떠도 휴대폰에서 손을 못 뗀다. 더 오를까 봐, 지금 안 팔면 놓칠까 봐.\n냉정하게 보면 둘 다 같은 병이다 — 숫자가 계좌를 움직이는 게 아니라, 감정이 계좌를 움직이고 있는 거다. 손실 앞에서는 근거 없는 희망을 붙잡고, 수익 앞에서는 근거 없는 불안을 붙잡는다. 어느 쪽이든 판단은 이미 감정에 넘어간 뒤였다.\n같은 병의 두 얼굴 [ -20% ] \"본전만 오면 판다\" → 손절선이 사후에 만들어진다 → 실은 손실을 인정하기 싫은 것 [ +5% ] \u0026ldquo;지금 팔면 더 오를 텐데\u0026rdquo; → 익절 기준이 사후에 사라진다 → 실은 놓치는 게 두려운 것 \u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026ndash; 공통점: 진입 전에 정해둔 게 없다 결정을 그 순간에 하니까 감정이 이긴다\n이 표를 그려보고 나서야 문제의 정체를 알았다. 내가 감정적인 게 문제가 아니라, 감정이 개입할 자리에 결정이 놓여 있었던 게 문제였다. 진입 전에 손절선과 익절선을 정해두면 그 순간에 할 일이 없다. 정해둔 대로 하면 된다. 그런데 대부분의 개인 투자는 그 순서가 반대다. 일단 사고, 나중에 어떻게 할지 그때 생각한다.\n결론은 심리 게임이었다 몇 번 겪어보니 결론은 하나였다. 투자는 결국 심리 게임이더라. 차트도, 뉴스도, 재무제표도 아니고 결국은 그 앞에 앉은 사람의 감정이 승패를 갈랐다.\n그렇다면 답은 생각보다 간단했다. 감정과 본성을 아예 매매 과정에서 빼버리면 된다. 철저한 기준, 철저한 로직 — 사람이 개입할 틈을 최소화하는 것. 그게 이 Bot의 출발점이었다.\n여기서 오해를 하나 풀어둬야겠다. 봇을 만든 이유가 더 잘 벌기 위해서가 아니었다. 나보다 똑똑한 알고리즘을 만들 자신이 없었다. 다만 나보다 규율 있는 알고리즘은 만들 수 있을 것 같았다. 규칙을 정해두고 그대로 실행하는 건 코드가 사람보다 압도적으로 잘하는 일이니까.\n봇이 사람보다 잘하는 것 / 못하는 것 잘하는 것 · 정해둔 규칙을 예외 없이 실행한다 · 손실 중에도 손이 안 떨린다 · 24시간 같은 기준으로 본다 · 어제 성적을 오늘 판단에 안 섞는다 못하는 것 · 처음 보는 상황을 해석한다 · 뉴스의 맥락을 읽는다 · 규칙 자체가 틀렸다는 걸 안다 \u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026ndash; 그래서 규칙을 정하는 건 내 일 · 지키는 건 봇 일\n역할 분담이 명확해지니 마음이 편해졌다. 내가 할 일은 차분할 때 규칙을 정하는 것이고, 봇이 할 일은 불편한 순간에 그 규칙을 지키는 것이다. 내가 제일 못하는 게 두 번째였다.\n랩톱 한 대로 시작했다 처음엔 거창하지 않았다. 집 랩톱을 24시간 켜두고, 장기투자용으로 넣어둔 빗썸 계좌를 지키려고 만든 작은 프로그램이었다. 윈도우에서 tkinter로 창을 띄우고, 단타로 시세를 훑는 봇이었다.\n그런데 랩톱을 상시로 돌리다 보니 이슈가 하나둘 터졌다. 재부팅 한 번, 업데이트 한 번이면 포지션이 그대로 방치됐다. 화면은 얼어붙어 있는데 포지션은 살아있는, 그 불안이 매일 쌓였다.\n봇이 죽으면 벌어지는 일 프로세스 종료 ↓ 손절 감시 정지 기준가를 넘어도 아무도 안 판다 익절 감시 정지 목표에 닿아도 그대로 지나간다 알림 정지 죽었다는 사실조차 안 알려준다 -------------------------------------------------- 자동매매에서 제일 무서운 건 잘못 사는 게 아니라 들고 있는데 아무도 안 보고 있는 상태다 손절선을 아무리 잘 정해놔도 프로세스가 죽어 있으면 그 값은 없는 것과 같다. 감정을 지우려고 만든 봇인데, 봇이 죽어 있을까 봐 불안해하는 상태가 된 것이다. 문제를 옮겨놓았을 뿐 없앤 게 아니었다.\n그래서 EC2로 옮기려 했는데, 메모리 스펙이 Bot을 안정적으로 돌리기엔 너무 빠듯했다. 위험을 감수할 이유가 없었다. 결국 맥북을 샀다. 맥으로 옮기면서 창을 띄우던 GUI는 사라지고, 터미널 모니터링만 남았다 — 화면은 단순해졌지만, 대신 더 이상 재부팅에 떨지 않아도 됐다.\n운영 환경이 바뀌며 사라진 것들 윈도우 랩톱 창을 띄운다 → 창이 곧 프로세스 업데이트·재부팅·절전에 취약 창을 봐야만 상황을 안다 맥북 + 터미널 세션에 붙여두고 돌린다 GUI 소멸 → 화면 코드와 판단 코드 분리 알림으로 상태를 받는다 \u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026ndash; 하드웨어를 바꾼 김에 구조도 정리됐다\n돌아보면 이 이사가 기술적으로도 전환점이었다. GUI를 버리면서 화면 그리는 코드와 판단하는 코드가 분리됐고, 그 분리가 나중에 전략을 검증 가능하게 만드는 전제가 됐다. 불안 때문에 산 노트북이 구조를 정리해준 셈이다.\n수수료를 계좌로 배웠다 거기서 끝이 아니었다. 윈도우 버전에서 맥 버전으로, 단타에서 눌림목으로 — Bot도 나도 그렇게 조금씩 바뀌었다.\n처음엔 순전히 호기심으로 시작한 단타 HFT였다. 초 단위로 사고파는 봇을 만들어보고 싶었다. 그런데 왕복 수수료의 무게를 계좌로 직접 배우고 나서야 전략을 다시 짰다.\n회전율은 비용을 곱한다 왕복 비용 a · 회전 n회 → 누적 비용 a × n -------------------------------------------------- 단타 회전 많음 · 회당 목표 작음 비용이 목표를 잠식한다 승률이 높아도 계좌는 준다 스윙 회전 적음 · 회당 목표 큼 같은 비용이 훨씬 덜 아프다 -------------------------------------------------- ※ 실제 요율은 거래소·등급마다 다르다 요점은 숫자가 아니라 구조다 숫자로는 별거 아닌 것 같은데, 하루에 수십 번 돌리면 그게 전부 나간다. 그리고 더 중요한 게 있었다. 단타 봇은 내가 원래 고치려던 문제를 오히려 키웠다. 로그가 끊임없이 올라가니까 화면을 계속 보게 된다. 감정을 지우려고 만든 봇 앞에 하루 종일 앉아 있게 된 것이다.\n이 자각이 방향을 바꿨다. 좋은 자동매매는 성적이 좋은 게 아니라 안 봐도 되는 것이라는 기준이 생겼다.\n봇을 만들면서 알게 된 나 이 프로젝트가 예상 못 한 걸 하나 줬다. 내가 어떻게 판단하는 사람인지 알게 됐다.\n규칙을 코드로 옮기려면 애매하게 둘 수가 없다. \u0026ldquo;많이 빠지면 산다\u0026quot;를 그대로 못 쓴다. 얼마나 빠져야 많이 빠진 건지, 무엇 대비 빠진 건지, 얼마 동안 빠진 건지를 전부 숫자로 적어야 한다.\n말로는 되는데 코드로는 안 되는 것들 \"많이 빠지면 산다\" → 얼마나? 무엇 대비? 며칠 동안? \"거래량이 붙으면 산다\" → 평소 대비 몇 배? 평소는 며칠 평균? \"분위기가 안 좋으면 쉰다\" → 무엇으로 판정? 어느 주기로? -------------------------------------------------- 코드로 옮기려는 순간, 내가 사실 아무것도 정해두지 않았다는 게 드러난다 그 과정에서 알게 됐다. 나는 규칙이 있다고 생각했지 실제로는 없었다. 머릿속에 있던 건 규칙이 아니라 느낌이었고, 느낌은 그날 기분에 따라 기준이 바뀐다. 같은 -5%가 어떤 날은 기회고 어떤 날은 위험 신호였다.\n봇을 만드는 작업의 절반은 코딩이 아니라 내 기준을 문장으로 적어내는 일이었다. 그리고 적어놓고 보면 대부분 근거가 없다. 그때부터 근거를 찾기 시작했고, 그게 지금의 검증 습관이 됐다.\n자동화가 옮겨놓는 문제 한 가지 더 배운 게 있다. 자동화는 문제를 없애는 게 아니라 옮긴다.\n해결한 문제 → 새로 생긴 문제 감정적 매매를 막았다 → 봇이 잘 돌고 있는지 불안해진다 화면을 안 봐도 되게 했다 → 봇이 죽었는지 알 방법이 필요해진다 규칙을 코드로 고정했다 → 규칙이 틀렸을 때 알아챌 방법이 필요해진다 -------------------------------------------------- → 알림 · 무결성 검사 · 매매기록 검증이 여기서 나왔다 세 번째가 제일 어렵다. 봇은 규칙을 완벽하게 지키는데, 그 규칙 자체가 틀렸으면 완벽하게 틀린 방향으로 간다. 사람이 매매하면 뭔가 이상하다는 느낌이 들어서 멈추는데, 봇은 안 멈춘다.\n그래서 결국 기록과 검증이 필요해졌다. 봇이 규칙대로 했는지가 아니라, 그 규칙이 여전히 맞는지를 주기적으로 확인해야 한다. 자동매매를 오래 하려면 봇을 만드는 것보다 이 확인 절차를 만드는 게 더 중요하더라.\n여섯 번의 시대 빗썸에서 시작한 엔진은 곧 업비트로 옮겨 갔고, 이동평균 기반 스캔, 단일파일 모놀리스, 여러 전략이 동시에 경쟁하던 병렬 실험기를 거쳐, 지금은 git으로 관리하는 모듈형 구조에 정착했다. 그 사이에 버전은 수백 개를 넘겼고, 시행착오 하나하나가 지금 로직의 게이트 하나, 조건 하나로 남아 있다.\n각 시대가 남긴 한 가지 빗썸 기원기 수수료의 무게 · 리스크 관리의 부재를 자각 MA봇 전환기 봇이 종목을 스스로 고르기 시작 단일파일 진화기 구조가 없으면 검증이 불가능하다 병렬 실험기 전략보다 \"지금이 어떤 장인가\" 가 먼저 VVWAP기 시장 판단이 처음 코드가 되다 VV 모듈 시대 근거 없는 값은 배포하지 않는다 -------------------------------------------------- 각 시대를 끝낸 건 좋은 아이디어가 아니라 한계에 부딪힌 경험이었다 이 목록을 정리하면서 알게 된 게 하나 있다. 늘어난 코드의 대부분이 **\u0026ldquo;안 사는 이유\u0026rdquo;**였다는 것. 615줄짜리 첫 코드에도 읽고, 판단하고, 주문하는 뼈대는 다 있었다. 그 뒤로 붙은 건 거의 전부 진입을 막는 조건이다. 게이트, 슬롯 상한, 손절, 본전 사수, 손실 한도, 하락장 차단.\n감정을 지우겠다고 시작했는데, 실제로 한 일은 하지 말아야 할 것을 코드로 정해두는 것이었다. 생각해보면 감정을 지운다는 게 그것 말고 무엇이겠나.\n지금의 봇 지금은 과매도 구간에서 눌림목 반등을 노리는, 스윙에 가까운 구조로 자리를 잡았다. RSI와 거래량, 저점 근접도 같은 조건들을 여러 겹의 게이트로 걸러 종목을 고르고, 손절선을 정해두고, 일정 수익에 도달하면 본전을 지키는 안전장치를 걸고, 포트폴리오 전체가 목표한 만큼 벌면 미련 없이 수확한다. 그리고 시장 전체가 무너지는 구간에는 아예 신규 진입 자체를 막아버린다.\n봇이 나 대신 참아주는 순간들 손절선에 닿았을 때 사람: \"조금만 더 버티면 돌아올 것 같은데\" 봇: 정해둔 대로 자른다 이익이 났을 때 사람: \u0026ldquo;지금 팔면 더 오를 텐데\u0026rdquo; 봇: +0.8% 도달 → 하한선을 올려둔다\n하락장에서 좋아 보이는 종목이 있을 때 사람: \u0026ldquo;이번엔 다를 거야\u0026rdquo; 봇: 신규 진입 자체를 안 한다 \u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026mdash;\u0026ndash; 세 순간 모두 내가 실제로 틀렸던 자리다\n마지막 항목이 특히 그렇다. 시장이 통째로 빠지는 날에는 좋아 보이는 종목이 오히려 많아진다. 다 빠졌으니 다 싸 보인다. 사람이었다면 \u0026ldquo;이번엔 다를 거야\u0026rdquo; 하고 들어갔을 자리다. 실제로 그렇게 들어가서 여러 번 물렸다.\n규칙을 정하는 사람과 지키는 코드 봇을 운영하면서 규칙이 하나 더 생겼다. 파라미터를 바꿀 때 근거 수치를 같이 남긴다는 것. 손절선을 몇 퍼센트로 할지, 진입 문턱을 어디에 둘지 — 감으로 정하지 않고 실제 매매 기록을 그 값으로 다시 돌려본다. 그리고 결과를 커밋 메시지에 적는다.\n왜 근거를 적어두나 1. 틀렸을 때 왜 틀렸는지 추적할 수 있다 2. 과거의 나와 논쟁할 수 있다 \"석 달 전엔 이 값이 좋다고 했는데 뭐가 달라졌나\" 3. 같은 실험을 반복하지 않게 된다 4. 바꾸기 어렵게 만든다 ← 이것도 안전장치 -------------------------------------------------- 근거 없이 정한 값은 틀려도 배울 게 없다 4번이 의외로 중요하다. 값을 바꾸려면 시뮬레이션을 먼저 돌려야 하니 귀찮아서 안 바꾸게 되는 경우가 생긴다. 그것도 나쁘지 않다고 본다. 바꾸기 어렵게 만드는 것 자체가 안전장치니까. 성적이 안 좋은 날에 충동적으로 파라미터를 만지는 걸 막아준다.\n결국 이것도 같은 이야기다. 차분할 때 정한 규칙이 불편한 순간의 나를 이기게 하는 것. 봇에 손절선을 넣는 것과 배포 절차를 만드는 건 다른 층위에서 같은 일을 한다.\n자동매매를 시작하려는 분께 이 글을 읽고 봇을 만들어볼까 하는 분이 있다면, 몇 달 먼저 해본 사람으로서 몇 가지만 적어두고 싶다.\n먼저 해봤으면 좋았을 것들 1. 전략보다 기록을 먼저 만든다 전략은 어차피 바뀐다. 기록은 계속 쌓인다 기록 없는 기간은 나중에 복원이 안 된다 2. \"안 사는 규칙\" 을 일찍 넣는다 진입 조건 다듬기가 재밌지만 계좌를 지키는 건 슬롯 상한과 손실 한도다 3. 조용한 실패를 시끄럽게 만든다 주문이 거부됐는데 그냥 넘어가는 게 제일 위험 \"안 샀다\" 와 \"못 샀다\" 를 구분할 수 있어야 4. 키와 설정을 코드에서 분리한다 10분이면 되는 작업. 안 하면 두고두고 발목 1번이 특히 그렇다. 초기 몇 주 동안 봇은 실제로 사고팔고 있었는데 그 기록을 안 남겼다. 전략이 미숙했으니 의미 없다고 생각했는데, 미숙한 전략의 기록도 데이터다. 지금 두 달치가 더 있었으면 훨씬 나은 판단을 했을 것 같다.\n그리고 하나 더. 봇이 돈을 벌어줄 거라는 기대로 시작하면 오래 못 간다. 초기 몇 달은 거의 확실히 마이너스다. 코드가 미숙해서가 아니라 규칙이 아직 없어서 그렇다. 그 구간을 버티려면 다른 동기가 필요하다. 나는 \u0026ldquo;내가 못 하는 걸 코드가 대신하는 게 재밌어서\u0026rdquo; 버텼다.\n가장 솔직한 이유 돌아보면 이 Bot은 나 대신 감정을 참아주는 존재다. 나는 여전히 -20%에서 기도하고 싶고, +5%에서 휴대폰을 놓기 싫어하는 인간이니까. 대신 로직이 참는다.\n그리고 하나 더 있다. 봇은 어제를 오늘에 안 섞는다. 사람은 어제 크게 잃으면 오늘 만회하려 들고, 어제 크게 벌면 오늘 대담해진다. 봇은 매일 같은 기준으로 본다. 이게 성적에 얼마나 기여하는지는 숫자로 모르겠지만, 계좌를 오래 유지하는 데는 확실히 도움이 됐다.\n지금도 매일 플러스인 건 아니다. 다만 지금의 봇은 자기가 왜 그 값으로 돌아가는지 전부 답할 수 있다. 나한테는 그게 수익률보다 먼저 온 자산이다.\n내가 못 하는 일을, 코드로 짜서 대신 시키는 것 — 어쩌면 이게 자동매매 봇을 만드는 가장 솔직한 이유일지도 모른다.\n제시 리버모어는 시장이 인간의 감정을 먹고 산다고 했다. 나는 여기에 한 줄만 더 붙이고 싶다 — 감정을 이길 수 없다면, 감정이 끼어들 자리 자체를 코드로 지워버리면 된다.\nEnglish There isn\u0026rsquo;t much spare time in a working professional\u0026rsquo;s day. You show up, sit through a few meetings, clear the backlog, and the sun is already down. By the time you get home, you\u0026rsquo;re too drained to learn or study anything new. Within that kind of life, the stock and crypto investing I dabbled in on the side almost always ended the same way: bags I was stuck holding. I\u0026rsquo;d buy, forget, and either stay forgotten or check back in out of anxiety. I never had time to actually study a chart, but somehow I always had a reason to worry about the account.\nThat\u0026rsquo;s just how humans are wired. The moment an account drops -20%, the praying starts — \u0026ldquo;please, just let me break even\u0026rdquo; — bargaining with things you don\u0026rsquo;t normally believe in. And the moment it\u0026rsquo;s up +5%, you can\u0026rsquo;t put the phone down, afraid it\u0026rsquo;ll go higher, afraid that if you don\u0026rsquo;t sell right now you\u0026rsquo;ll miss it. Looked at coldly, both are the same disease. It isn\u0026rsquo;t the numbers moving the account — it\u0026rsquo;s emotion. In loss, you grab onto baseless hope. In gain, you grab onto baseless anxiety. Either way, the decision has already been handed over to feeling.\nAfter going through that cycle a few times, I landed on one conclusion: investing is, in the end, a psychology game. Not the chart, not the news, not the fundamentals — it\u0026rsquo;s the emotional state of the person sitting in front of the screen that decides the outcome. And if that\u0026rsquo;s true, the fix turned out to be simpler than expected: strip emotion and human nature out of the trading process entirely. Strict criteria, strict logic — minimize the room for a person to step in. That\u0026rsquo;s where this Bot started.\nIt didn\u0026rsquo;t start big. It was a small program running on a laptop at home, left on 24/7, built just to protect a long-term investment account on Bithumb. A Windows box with a tkinter window, scanning the market for short-term entries. But running a laptop around the clock brought its own problems, one after another. A single reboot or update and open positions sat there unmanaged — the screen frozen while the position stayed live. That anxiety compounded daily. So I tried moving it to EC2, but the memory spec was too tight to run the Bot reliably. There was no reason to take that risk. In the end, I bought a MacBook. Moving to Mac, the GUI window disappeared — the screen got simpler, but I also stopped flinching every time a reboot was due.\nThat wasn\u0026rsquo;t the end of it. From a Windows version to a Mac version, from day trading to pullback entries — the Bot changed, and so did I, little by little. It started as pure curiosity: a day-trading HFT bot. Only after learning the weight of a round-trip fee the hard way, through my own account, did I rebuild the strategy. The engine that started on Bithumb soon moved to Upbit, passed through a moving-average scanner, a single-file monolith, and an era where several strategies competed in parallel, before settling into the git-managed, modular structure it runs on today. Along the way the version count climbed into the hundreds, and every round of trial and error is still there — as a gate, a condition, a line of logic in what runs now.\nToday it\u0026rsquo;s settled into something closer to a swing structure, hunting for pullback rebounds in oversold conditions. It filters candidates through layered gates — RSI, volume, proximity to a recent low — sets a stop loss up front, locks in breakeven once a position clears a certain gain, and harvests the whole portfolio without hesitation once it hits its target. And when the broader market is falling apart, it simply refuses to open new positions at all — exactly the spot where a human would\u0026rsquo;ve told themselves \u0026ldquo;this time is different\u0026rdquo; and jumped in anyway.\nLooking back, this Bot is what holds the emotions I can\u0026rsquo;t. I\u0026rsquo;m still the kind of person who wants to pray at -20% and can\u0026rsquo;t let go of the phone at +5%. The logic holds the line instead. And there\u0026rsquo;s one more thing: the bot doesn\u0026rsquo;t carry yesterday into today. People try to make back a big loss and get bold after a big win. The bot looks at every day through the same lens. Writing code to do the thing I can\u0026rsquo;t do myself — that might be the most honest reason to build a trading bot at all.\nJesse Livermore said the market feeds on human emotion. I\u0026rsquo;d only add one line to that — if you can\u0026rsquo;t beat emotion, delete the space where it gets a say, and write that space into code instead.\n","permalink":"https://botlab.co.kr/posts/why-i-built-this-bot/","summary":"왜 이 Bot을 만들게 됐는가 — 바쁜 삶, 심리 게임, 그리고 EC2에서 맥북까지.","title":"감정을 지워버린 트레이더 | Crypto Trading Bot"},{"content":"이 블로그(Trading Bot Lab, 이하 \u0026ldquo;본 사이트\u0026rdquo;)는 개인이 운영하는 개발 기록 블로그입니다. 방문자의 개인정보를 어떻게 다루는지 아래에 정리합니다.\n최종 수정일: 2026년 8월 14일\n수집하는 정보 본 사이트는 회원가입, 로그인, 댓글 기능을 제공하지 않으며 이름, 이메일, 전화번호 등 개인을 식별할 수 있는 정보를 직접 수집하지 않습니다.\n다만 아래 정보가 자동으로 기록되거나 제3자 서비스를 통해 수집될 수 있습니다.\n접속 기록: IP 주소, 브라우저 종류, 접속 시각, 방문한 페이지 (호스팅 서비스인 GitHub Pages가 처리) 쿠키 및 유사 기술: 방문자의 브라우저에 저장되는 소량의 데이터 쿠키에 대하여 쿠키는 웹사이트가 방문자의 브라우저에 저장하는 작은 텍스트 파일입니다. 본 사이트에서는 다크 모드 설정처럼 화면 표시에 관한 값을 저장하는 데 쓰이며, 이 값은 방문자의 기기에만 저장되고 외부로 전송되지 않습니다.\n방문자는 브라우저 설정에서 언제든 쿠키를 차단하거나 삭제할 수 있습니다. 다만 일부 표시 설정이 저장되지 않을 수 있습니다.\n광고에 대하여 본 사이트는 향후 Google AdSense를 포함한 제3자 광고 서비스를 게재할 수 있습니다. 이 경우 다음 사항이 적용됩니다.\nGoogle을 포함한 제3자 광고 공급업체는 쿠키를 사용하여 방문자의 이전 방문 기록에 기반한 광고를 게재할 수 있습니다. Google이 광고 쿠키를 사용함으로써 방문자에게 이 사이트나 다른 사이트 방문 기록을 기반으로 맞춤 광고를 제공할 수 있습니다. 방문자는 Google 광고 설정에서 맞춤 광고를 원하지 않도록 설정할 수 있습니다. 제3자 광고 공급업체의 쿠키 사용을 원하지 않는 경우 www.aboutads.info에서 선택 해제할 수 있습니다. 정보의 제3자 제공 본 사이트는 방문자의 정보를 판매하거나 제3자에게 제공하지 않습니다. 위에 언급한 호스팅 및 광고 서비스가 각자의 정책에 따라 정보를 처리하는 경우를 제외합니다.\n외부 링크 본 사이트의 글에는 외부 사이트로 연결되는 링크가 포함될 수 있습니다. 연결된 사이트의 개인정보 처리에 대해서는 본 사이트가 책임지지 않으며, 해당 사이트의 방침을 확인하시기 바랍니다.\n투자 관련 고지 본 사이트의 모든 글은 개인의 개발 기록이자 학습 노트이며, 투자 권유나 자문이 아닙니다. 글에 등장하는 전략, 파라미터, 수치는 필자 개인의 계좌와 환경에서 관찰된 것으로 다른 환경에서 같은 결과를 보장하지 않습니다. 투자 판단과 그 결과는 전적으로 방문자 본인의 책임입니다.\n문의 개인정보 처리에 관한 문의는 아래 주소로 보내주시기 바랍니다.\n이메일: jungjuho@gmail.com\n변경 고지 본 방침이 변경되는 경우 이 페이지에 수정일과 함께 게시합니다.\n","permalink":"https://botlab.co.kr/privacy/","summary":"쿠키, 접속 기록, 광고에 관한 안내","title":"개인정보 처리방침"},{"content":"안녕하세요. 이 블로그는 혼자서 트레이딩 봇을 만들고, 부수고, 다시 고치는 과정을 기록하는 공간입니다.\n거창한 투자 리포트나 \u0026ldquo;이렇게 하면 무조건 번다\u0026rdquo; 류의 글은 아니고요, 그냥 새벽에 봇 로그 들여다보다가 깨달은 것들, 코드 한 줄 고쳤다가 계좌가 웃거나 우는 이야기들을 가볍게 씁니다. 완성형보다는 진행형에 가까운 글이 많을 거예요.\n여기에 있는 것들 지표 스터디 — RSI, 거래량, 피보나치처럼 차트에서 흔히 보는 지표들을 공식부터 뜯어봅니다. 교과서 정의에서 출발해서, 그 지표를 실제로 봇의 조건식에 넣으면 무엇이 달라지는지까지 씁니다. 개인적으로 제일 애착이 가는 코너입니다.\n개발일지 — 봇을 고치면서 그때그때 남긴 기록입니다. 사고가 났던 이야기가 많습니다.\n개발 아카이브 — 봇이 461번 바뀌는 동안 있었던 일을 시대별로 묶어 정리했습니다. 빗썸에서 시작해서 지금 형태가 되기까지의 흐름을 한 번에 보고 싶으시면 여기부터 보세요.\n봇에 대해 빗썸에서 장기투자 계좌를 지키려고 만들기 시작해서, 지금은 업비트에서 과매도 눌림목 반등 전략으로 돌고 있습니다. 조건 10개를 통과한 종목만 사고, 슬롯은 4개, 비트코인이 하락 추세면 아예 안 삽니다. 파라미터는 전부 실제 매매 기록을 다시 돌려서 정한 값이고, 근거 없이 바꾸지 않는 걸 규칙으로 삼고 있습니다.\n미리 말씀드릴 것 여기 있는 글은 전부 제 개인 기록이고 투자 권유가 아닙니다. 제 계좌에서 나온 숫자가 다른 분의 계좌에서 같게 나올 이유는 없습니다. 저도 매일 버는 게 아니고, 실패한 버전 이야기가 성공한 버전 이야기보다 많습니다.\n\u0026ldquo;시장에서 살아남는 사람은 가장 똑똑한 사람이 아니라, 가장 오래 버틴 사람이다.\u0026rdquo; — 제시 리버모어\n버티는 쪽에 걸어보려고 합니다. 천천히, 자주 쓸게요.\n연락 궁금한 점이나 잘못된 내용 지적은 메일로 주시면 감사하겠습니다.\n이메일: jungjuho@gmail.com\n","permalink":"https://botlab.co.kr/about/","summary":"혼자 트레이딩 봇을 만들고 부수고 고치는 기록","title":"소개 — About This Crypto Trading Bot Blog"}]