이건 신호가 아니다

거래소 앱을 켜면 종목 이름 옆에 “24시간 거래대금"이 붙어 있다. 한동안 나는 이걸 신호처럼 읽었다. 지금은 안 그런다. 이 숫자가 대답해주는 질문은 딱 하나다. 이 종목을 내가 사고팔 수 있느냐.

봇에서 하는 일도 딱 그 수준이다. 스캔이 시작되기도 전에 후보 목록부터 잘라내는데, 조건이 아무리 예쁘게 맞아떨어져도 유동성이 없는 종목은 애초에 명단에 안 올린다. 게이트 열 개보다 앞선다.

봇에서의 처리 순서
  ① 전 종목 조회         거래소 상장 목록 전부
  ② 유니버스 필터  ◀ 여기가 24시간 거래대금
     거래대금 하한 미달 → 제외
     가격 하한 미달 → 제외 (호가 단위 문제)
  ③ 지표 계산           살아남은 종목만
  ④ 게이트 판정         10개 조건
  ⑤ 랭킹 → 진입
②에서 자르면 ③~⑤ 의 계산량도 함께 줄어든다 — API 호출 절약이라는 부수 효과
②를 건너뛰면 ④에서 통과한 종목이 ⑤에서 체결이 안 되는 사고가 난다

거래량과 거래대금은 다른 말이다

용어부터. 차트 아래 막대는 보통 거래량, 그러니까 수량이다. 종목을 가로질러 비교할 때 의미가 있는 쪽은 거래대금이다.

수량 vs 금액
거래량(수량)   몇 개가 거래됐나
  BTC 1개와 200원짜리 코인 1개가 같은 '1' 로 계산된다
  종목 간 비교에 쓸 수 없다
거래대금(금액) 얼마어치가 거래됐나 = 수량 × 가격
  종목 간 비교가 가능하다 — 유니버스 필터에 쓰는 건 이쪽
거래소 API 에서
  volume        기초자산 수량 (base volume)
  quote_volume  원화 환산 금액 (quote volume) ← 대개 이걸 쓴다
직접 계산할 때 주의: 수량 × 종가 는 근사치다
  봉 안에서 가격이 움직였다면 실제 체결 금액과 다르다
  거래소가 quote_volume 을 주면 그걸 쓰는 게 정확하다

이 구분을 빼먹으면 유니버스 필터가 엉뚱하게 굴러간다. 수량으로 하한을 걸어보면 안다. 가격이 낮은 코인만 우수수 통과한다. 필터가 아닌 저가주 뽑기 기계가 하나 생긴다.

같은 실수를 나도 다른 자리에서 했다. 신호 게이트에 절대 거래대금 기준을 걸었다가 “게이트가 종목을 거르는 게 아니라 체급을 차별한다"는 지적을 받았다. 유니버스 필터에는 절대 금액이 맞고, 신호 게이트에는 자기 평균 대비 배수가 맞다. 같은 재료라도 놓이는 자리에 따라 기준이 달라진다.

왜 하필 24시간인가

1시간도 7일도 아니고 왜 24시간일까. 답은 코인 시장의 구조에 있다.

주식은 개장과 폐장이 있다. 그래서 “하루치 거래량"이 자연스러운 단위가 된다. 코인은 24시간 내내 돈다. 그런 경계가 없다. 그래서 롤링 24시간, 지금 이 순간부터 정확히 하루 전까지를 쓴다. 언제 조회하든 같은 길이의 창을 본다.

창 길이를 바꾸면 무엇이 달라지나
1시간   너무 예민하다. 펌핑 한 번에 순위가 통째로 뒤집힌다
        "방금 누가 크게 샀다" 는 알 수 있지만 "평소 거래가 되는 종목인가" 는 모른다
24시간  하루 안의 시간대 편차를 한 바퀴 흡수한다
        한국 저녁의 활발함과 새벽의 한산함이 같은 창 안에 들어간다
        → 조회 시각에 따라 값이 크게 안 흔들린다
7일     너무 둔하다. 사흘 전에 죽은 종목이 아직 살아 있는 것처럼 보인다
        상장 폐지 예정·거래 중단 직전 종목을 못 거른다
24시간이 표준이 된 건 관습이 아니라 시간대 편차를 한 주기 담기 때문이다

시간대 편차는 내 봇에서도 실측해봤다. 스캔 기록 1,116건을 시간대별로 갈라 성과를 봤더니 저녁이 나빴다. 그것도 뚜렷하게. 한국 시각 1923시 구간에서 19시 -0.752%, 20시 -0.806%, 21시 -0.716%였고 승률은 1020%대에 머물렀다. 거래가 제일 활발한 시간이 성적은 제일 나빴다.

거래량이 많다는 건 참여자가 많다는 뜻이다. 내 전략이 잘 통한다는 뜻은 아니다. 이걸 놓치면 “붐빌 때 공격적으로” 같은, 직관적이면서 완전히 틀린 설계를 하게 되는데, 나는 처음에 정확히 그렇게 만들어놨다가 데이터한테 한 대 맞고 되돌렸다.

이 숫자가 거짓말하는 세 가지 경우

24시간 거래대금은 유용하다. 만능은 아니다. 실제 유동성을 부풀려 보여주는 경우가 있다.

거래대금이 있어도 못 사는 경우
① 몰린 거래량
   하루 총액은 큰데 대부분이 특정 몇 분에 몰려 있다
   그 시간을 벗어나면 호가창이 텅 빈다 → 지금 이 순간엔 못 산다
② 넓은 호가
   거래대금은 기준을 넘는데 호가 한 칸이 가격의 0.2% 를 넘는다
   사는 순간 이미 마이너스로 시작한다 — 거래대금만으론 안 잡힌다
③ 세탁 거래
   같은 주체가 자기 물량을 주고받아 만든 숫자
   호가창의 실제 두께와 무관하다
대응: 거래대금은 1차 관문일 뿐. 호가 기반 지표를 2차로 세운다

②번은 내 봇이 실제로 물린 함정이다. 앞에서 언급한 본전 청산 27건 실측이 그 결과였는데, 저가 코인의 호가 한 칸이 가격의 0.2%를 넘어서면서 청산선이 호가와 호가 사이 허공에 놓여버린 사고였다. 겪고 나서 값을 한 번 보정했고(본전 바닥 0.4%로 상향), 이틀 뒤엔 아예 구조적으로 막았다. 호가 한 칸이 가격의 0.15%를 넘는 종목은 진입 금지. 게이트를 하나 새로 세웠다.

순서가 중요했다고 본다. 사고 → 실측 → 원인 규명 → 임시 보정 → 구조적 예방. 나흘 걸렸다. 문제를 겪는 시스템에서 문제를 만나지 않는 시스템으로 옮겨가는 과정이 이런 모양이었다.

거래대금 순위표를 믿으면 안 되는 이유

거래소 앱에는 거래대금 순위가 있다. 위에서부터 몇 개 골라 매매하는 사람도 많다. 그런데 이 목록에는 성격이 전혀 다른 종목들이 뒤섞여 있다.

거래대금 상위에 오르는 세 가지 경로
① 원래 큰 종목
   시가총액이 크고 평소에도 거래가 많다. 순위가 잘 안 바뀐다
   → 안정적. 다만 변동성이 작아 눌림목 전략의 먹잇감은 적다
② 오늘 터진 종목
   호재나 펌핑으로 하루 만에 거래가 폭증. 어제는 순위 밖이었다
   → 유동성은 지금만 있다. 내일이면 사라질 수 있다
③ 신규 상장
   상장 직후 며칠은 거래가 몰린다. 과거 데이터가 아예 없다
   → 지표 계산 자체가 불가능하다 (워밍업 봉이 안 쌓임)
셋을 구분하려면 24시간 값 하나로는 부족하다
  거래대금의 최근 7일 대비 배수를 같이 보면 ②가 분리된다
  상장 경과일 필터를 두면 ③이 분리된다

③번이 봇에서 특히 조용하게 사고를 낸다. 상장한 지 얼마 안 된 종목은 봉이 부족해서 지표가 이상한 값을 뱉거나 아예 결측이 나고, 그걸 안 걸러내면 판정 로직이 예상 못 한 경로로 흘러 들어간다. 조건문이 결측을 만나면? 코드를 짠 사람도 모를 때가 많다.

무결성 검사 항목에 “미정의 참조 0건"을 넣어둔 이유도 이 계열이다. 크래시는 차라리 낫다. 조용히 잘못된 값으로 계속 굴러가는 쪽이 훨씬 무섭다.

하한선은 얼마가 적당한가

거래대금 하한은 몇 번 움직였다. 처음엔 50억으로 잡았다. 평가 가능한 종목이 10개 남짓 남았다. 후보가 그 정도면 게이트 열 개를 다 통과하는 종목은 사실상 안 나온다. 그래서 10억으로 내렸고, 평가 종목이 40~50개로 늘었다.

하한선의 트레이드오프
하한 높음   후보 적음 · 유동성 좋음 · 체결 확실 · 기회 부족
   50억 기준일 때 평가 대상 ~10 종목 → 게이트 통과 사실상 0
하한 낮음   후보 많음 · 유동성 나쁨 · 슬리피지 · 미체결 위험
   현행은 거래대금과 가격 하한을 함께 걸어 균형을 잡는다
핵심: 하한선은 독립 변수가 아니다
  게이트가 몇 개인지, 슬롯이 몇 개인지, 계좌 규모가 얼마인지에 따라 최적점이 움직인다
  슬롯 4개짜리 봇은 후보가 20개만 있어도 돌아간다
  슬롯 10개짜리 봇이었다면 같은 하한선에서 굶었을 것이다

슬롯을 10개에서 4개로 줄인 결정과 유니버스 하한 조정은 같은 시기에 맞물려 있었다. 슬롯이 적으면 후보도 적어도 되고, 슬롯당 금액이 커지니 호가가 넓은 종목의 부담은 커진다. 파라미터 하나만 떼어놓고 “이 값이 적당한가"를 따지는 게 왜 공허한지 보여주는 사례다.

유니버스가 작다는 것의 의미

봇이 실제로 다루는 종목 수를 적어두면 이 글의 나머지가 더 잘 읽힐 것이다. 필터를 통과하고 나면 스캔 대상은 수십 개 수준이다. 업비트 원화 마켓에서 상당수가 걸러진다.

유니버스 크기가 만드는 제약들
유니버스가 20~50 종목일 때
① 격리 규칙이 치명적이 된다
   손절한 종목을 하루 격리하면 후보의 상당 비율이 하루 사라진다
   실제로 격리 기간을 하루 → 0 으로 푼 적이 있다
   포트 익절이 4슬롯을 한꺼번에 털면 후보 4개가 동시에 빠지기 때문
② 게이트를 겹칠수록 통과가 0 에 수렴한다
   50 종목에 10 게이트면 전부 통과하는 종목이 없는 날도 생긴다
③ 시간대 차단 같은 규칙의 비용이 커진다
   하루 5시간을 막으면 기회의 20% 가 사라진다
그래서 이 봇의 규칙들은 전부 '유니버스가 작다' 는 전제 위에 서 있다
종목이 수천 개인 시장이었다면 완전히 다른 설계가 나왔을 것이다

①번은 겪은 일이다. 손절한 종목을 하루 격리하는 규칙을 넣었는데, 포트폴리오 익절로 네 슬롯을 한꺼번에 수확하고 나니 그 네 종목이 동시에 격리 목록으로 들어가버렸다. 유니버스가 작으니 다음 스캔에서 살 게 없었다. 격리를 아예 0으로 풀었다가, 나중에 “손절한 종목만 그날 자정까지"라는 절충안으로 다시 정리했다.

규칙 하나가 독립적으로 존재하지 않는다는 게 이 사례의 요지다. 격리 기간은 유니버스 크기의 함수, 유니버스 크기는 거래대금 하한의 함수, 하한은 슬롯 수와 계좌 규모의 함수. 하나를 건드리면 나머지가 다 흔들린다.

계좌 규모라는 숨은 변수

하나 더. 24시간 거래대금 기준은 내 주문 크기와의 상대적 관계로 읽어야 한다.

하루 10억이 거래되는 종목에 10만 원을 넣는 것과 1억을 넣는 것은 완전히 다른 일이다. 앞쪽은 호가창에 흔적도 안 남는다. 뒤쪽은 자기가 자기 가격을 밀어 올린다. 흔히 하루 거래대금의 일정 비율 이하로 주문을 제한하는 원칙을 쓰는데, 소액 계좌에서는 이게 거의 제약이 안 된다.

규모별로 이 지표의 의미가 달라진다
소액 계좌   내 주문이 시장에 영향 없음
   → 거래대금 기준의 목적은 '체결 가능성' 과 '호가 폭' 확보
   → 하한선을 비교적 낮게 잡아도 된다
중대형 계좌  내 주문이 호가를 움직임
   → 목적이 '시장 충격 회피' 로 바뀐다
   → 하한선이 훨씬 높아지고, 분할 주문 설계가 필요해진다
같은 지표, 같은 종목, 다른 판정 — 계좌 규모가 기준을 바꾼다
내 봇은 전자다. 그래서 이 글의 숫자들을 그대로 가져다 쓰면 안 된다

스프레드와 함께 봐야 완성된다

거래대금이 1차 관문이면 2차 관문은 스프레드다. 매수 최우선 호가와 매도 최우선 호가 사이의 간격.

두 숫자를 같이 보면 사분면이 나온다
                   스프레드 좁음        스프레드 넓음
거래대금 큼      ✓ 최적                △ 이상 신호
                   정상 종목            거래는 많은데 호가가 비었다?
                                        세탁 거래 의심 구간
거래대금 작음    △ 조용한 종목        ✗ 최악
                   지금은 한산할 뿐      사는 순간 손해로 시작
                                        절대 건드리면 안 되는 칸
내 봇의 대응
  유니버스 단계 — 거래대금 하한 + 가격 하한
  게이트 단계   — 스프레드 상한 (9번째 게이트로 승격)
  게이트 단계   — 호가 한 칸 / 가격 비율 0.15% 상한
세 겹을 두는 이유: 각각이 못 잡는 경우가 다르기 때문이다

오른쪽 위 칸이 재밌다. 거래대금은 큰데 스프레드가 넓은 조합. 논리적으로 잘 안 어울린다. 그래서 실제로 나타나면 거래대금 숫자 쪽을 의심해볼 만하다. 하루 종일 활발히 거래된 종목의 호가창이 비어 있을 이유가 별로 없으니까.

스프레드를 게이트로 올린 건 계획에 없던 일이었다. 터미널 로그에 “조건은 다 통과했는데 왜 안 샀지?” 싶은 종목이 자꾸 보여서 추적해봤더니, 게이트 바깥에서 스프레드 조건에 조용히 막히고 있었다. 판정은 하는데 화면에 안 보인다. 그래서 정식 게이트로 승격시켜 통과율 통계에 포함시켰다.

있는데 안 보이는 조건은 없는 조건보다 나쁘다. 디버깅이 불가능해지니까.

거래대금과 변동성의 관계

자주 보이는 오해가 있다. 거래대금이 큰 종목은 안전하다는 생각.

거래대금이 크다는 건 체결이 쉽다는 뜻이다. 가격이 안정적이라는 뜻은 아니다. 오히려 하루 만에 거래가 폭증한 종목은 대체로 그날 크게 움직인 종목인데, 애초에 거래가 몰리는 이유 자체가 가격이 움직였기 때문이니 당연한 일이다.

그러니 거래대금 필터로 변동성 필터를 대신할 수 없다. 둘은 다른 축이다. 내 봇에도 변동성 조건이 따로 있는데 하한과 상한이 함께 걸려 있고, 너무 조용한 종목은 움직임이 없어서 못 먹고 너무 요동치는 종목은 손절선이 의미를 잃기 때문이다.

이 상하한 구조를 처음부터 알고 만든 건 아니다. 박스장에서 진입이 막혔을 때 변동성 하한을 계속 내렸는데, 그러다 보니 이번엔 정상 범위를 한참 벗어난 종목까지 들어왔다. 한쪽을 풀면 다른 쪽이 샜다. 조건을 완화할 때는 그 조건이 막고 있던 게 무엇이었는지 같이 봐야 한다는 걸 그때 배웠다.

하한선을 정할 때 실제로 해본 것

값을 정하는 절차를 구체적으로 남기는 게 이 글에서 제일 쓸모 있는 대목일 것 같다. 거래대금 하한을 정할 때 나는 성과를 먼저 보지 않았다. 후보 개수부터 셌다.

하한선 결정 순서
① 후보 개수 곡선을 먼저 그린다
   하한값을 바꿔가며 통과 종목 수를 센다 (성과는 아직 안 본다)
   50억 → 약 10종목 · 10억 → 40~50종목
② 최소 필요 후보 수를 정한다
   슬롯 4개 · 게이트 10개 → 후보가 최소 20~30 은 돼야 통과가 나온다
   이건 성과 문제가 아니라 봇이 굶느냐 마느냐의 문제
③ 그 범위 안에서만 성과를 비교한다
   후보가 10개인 구간은 성과를 볼 것도 없이 탈락
④ 호가·가격 조건을 별도로 세운다
   거래대금 하한을 낮춘 대가는 다른 축에서 막는다
핵심: 성과 최적화 이전에 '봇이 돌아가는 범위' 를 먼저 확정한다

①번을 먼저 하는 이유가 있다. 후보가 10개인 상태에서 성과를 재면 표본이 너무 적어 아무 결론도 안 나온다. 성과 비교는 봇이 정상 작동하는 구간 안에서만 의미가 있다.

이 순서를 몰라서 시간을 버렸다. 하한을 높게 잡아둔 채로 “왜 성과가 안 나오지” 하면서 애먼 파라미터를 며칠 만졌다. 진입 자체가 거의 없어서 통계가 무의미했던 건데, 나는 엉뚱한 데를 파고 있었다.

표본이 없으면 최적화도 없다. 이건 이 봇 개발 전체를 관통하는 제약이다. 조건을 조일수록 성과는 좋아 보이는데 표본이 마르고, 표본이 마르면 그 판단이 맞는지 확인할 길이 사라진다. 시간대 차단 규칙을 해제한 것도 결국 같은 이유였다.

실전 체크리스트

여기서 다룬 것들을 봇에 넣을 때 확인할 항목으로 정리해둔다.

유니버스 필터 점검표
□ 거래대금 하한이 내 슬롯 크기·게이트 개수와 함께 정해졌는가
   후보가 몇 개 남는지 실제로 세어봤는가 (10개 남으면 봇이 굶는다)
□ 가격 하한이 있는가
   저가 코인의 호가 단위 문제를 거래대금만으로는 못 잡는다
□ 호가 기반 조건이 별도로 있는가
   스프레드 상한 · 호가 한 칸/가격 비율 — 거래대금과 다른 것을 잰다
□ 상장 경과일 조건이 있는가
   지표 워밍업이 안 된 종목은 판정 자체가 무의미하다
□ 필터에 걸린 종목이 로그에 남는가
   왜 안 샀는지 나중에 추적 못 하면 튜닝이 감으로 돌아간다
□ 유니버스 크기를 매 스캔마다 기록하는가
   어느 날 갑자기 후보가 줄었을 때 원인을 시장 탓/코드 탓으로 가를 수 있다

마지막 두 항목이 실전에서 제일 자주 빠진다. 필터는 조용히 일한다. 잘 작동하는지 아닌지가 안 보이고, 그러다 “이틀째 매수가 없다” 같은 상황이 오면 어디서부터 봐야 할지 감이 안 잡힌다.

내 봇도 그걸 겪고 나서 게이트별 통과율을 매 스캔마다 출력하게 바꿨다. 돌파 3/15, 거래량 8/15 이런 식으로 찍히니 어느 관문에서 막히는지 바로 보였다. 측정하지 않는 필터는 튜닝할 수 없다.

호가 한 칸이라는 진짜 관문

거래대금이 유동성의 대리 지표라면, 호가 단위는 그 유동성의 해상도다. 이 개념이 내 봇에서 어떻게 게이트가 됐는지 자세히 적어둘 가치가 있다.

업비트는 가격대별로 호가 단위가 정해져 있다. 가격이 낮으면 단위도 작아진다. 다만 비율로 보면 저가 코인이 훨씬 불리하다.

호가 한 칸의 비율 — 가격대별
호가 단위 / 현재가 × 100 = 한 칸이 차지하는 비율
  10,000원대 종목   한 칸이 가격의 0.1% 미만 — 여유롭다
   1,000원대 종목   0.1% 안팎
     443원 종목     한 칸이 0.226% (실측 사례)
     100원대 종목   더 나빠진다
이게 왜 문제인가
  퍼센트로 설계한 청산선이 호가와 호가 사이 허공에 놓인다
  +0.2% 에 바닥을 뒀는데 한 칸이 0.226% 면 그 가격은 존재하지 않는다
  → 한 칸 아래에서 체결되거나 아예 체결이 안 된다
현행 대응: 호가 한 칸 / 가격 비율이 0.15% 를 넘으면 진입 금지

이 게이트가 만들어진 과정은 개인적으로 가장 만족스러운 사례 중 하나다. 순서가 이랬다.

먼저 이상 징후. 본전 사수 장치는 이익을 지키라고 만든 건데 실제로는 손실을 내고 있었다. 그다음 실측. 본전 청산 27건을 뽑아보니 바닥을 +0.2%에 뒀는데 평균 실현이 +0.017%, 합계로는 마이너스였다. 원인 규명 단계에서 슬리피지를 의심했다가 아니라는 걸 확인했고, 범인은 호가 관통이었다. 관통 손실 0.183%p에 왕복 수수료 0.10%를 더하면 손익분기가 0.283%였다.

그래서 1차로 값을 보정했다. 바닥을 0.4%로 올리고 무장선도 0.8%로 함께 올렸다. 간격이 좁으면 무장하자마자 청산돼서 승자를 못 키운다. 60건을 재현해보니 본전 손익이 −33,203원에서 +44,906원으로 뒤집혔다.

여기까지가 보정이다. 이틀 뒤에 구조적 예방을 넣었다. 애초에 호가가 넓은 종목을 사지 않는 게이트를 만든 것이다. 문제를 겪고 대응하는 시스템에서 문제를 안 만나는 시스템으로 옮겨갔다.

사고에서 게이트까지 — 나흘
1일차   이상 징후: 본전 사수가 적자를 내고 있다
2일차   실측 27건 — 바닥 +0.2% 설계, 평균 실현 +0.017%, 합계 −18,144원
2일차   원인 규명 — 슬리피지 아님. 호가 관통 0.183%p
2일차   보정 — 바닥 0.4% / 무장 0.8%, 60건 재현으로 확인
4일차   구조화 — 호가단위 게이트 신설 (0.15% 상한)
이 파이프라인의 속도가 개발 체계의 성적표라고 생각한다
감으로 고치면 하루면 되지만 같은 문제가 다시 온다
측정하고 고치면 나흘 걸리는 대신 다시 안 온다

이 필터가 막아준 것들

거래대금 필터의 성과는 눈에 잘 안 띈다. 막아낸 사고는 일어나지 않았으니 기록에도 안 남는다. 그래도 정황으로 짐작되는 건 있다.

봇 이력에서 유동성 관련 사고는 대부분 필터가 약했던 시기에 몰려 있다. 저유동성 코인에서 매도 주문이 체결되지 않았는데 봇은 팔렸다고 판단하고 포지션 목록에서 지워버린 일이 있었다. 거래소에는 물량이 남아 있고 봇은 그걸 모른다. 이른바 유령 포지션. 겪고 나서 체결 확인 폴링을 넣었고, 거래소 보유분과 봇 상태를 대조해 자동 복구하는 로직도 붙였다.

사고 자체는 집행 로직의 문제였다. 다만 애초에 그 종목이 후보 목록에 없었다면 일어나지 않았을 일이다. 필터는 사고를 막아준다기보다 사고가 일어날 수 있는 표면적을 줄인다. 그래서 성과 측정이 어렵고, 그래서 자주 과소평가된다.

체결 확인, 유동성 문제의 마지막 방어선

필터를 아무리 잘 세워도 뚫린다. 그래서 방어선이 하나 더 필요하다. 주문을 냈으면 체결됐는지 확인하는 것.

당연한 얘기 같지만 초기 코드는 이걸 안 했다. 주문 API가 성공 응답을 주면 체결된 걸로 간주했다. 주문 접수와 체결은 다른 사건인데도 그랬다.

유령 포지션이 만들어지는 경로
잘못된 흐름
  ① 매도 주문 전송
  ② 응답에 주문번호가 있음 → "팔렸다" 고 판단
  ③ 봇의 보유 목록에서 해당 종목 제거
  ④ 그런데 실제로는 체결 안 됨 (호가에 물량이 없어서)
  ⑤ 거래소엔 물량이 남아 있고 봇은 모른다 = 유령 포지션
고친 흐름
  ① 매도 주문 전송
  ② 주문 상태를 반복 조회 (체결 폴링)
  ③ 체결 확인되면 보유 목록에서 제거
  ④ 일정 시간 내 미체결이면 목록 유지 + 로그
추가 방어: 계좌 보유분과 봇 상태를 주기적으로 대조
  거래소엔 있는데 봇 목록에 없는 종목 → 자동으로 다시 편입

이 사고는 저유동성 코인에서 났다. 거래대금 하한을 겨우 넘긴 종목이었고, 매도 시점에 호가창이 비어 있었다. 필터가 약했던 게 1차 원인, 체결 확인을 안 한 게 2차 원인이다.

여기서 배운 건 방어를 한 겹만 두면 안 된다는 것이었다. 유니버스 필터가 뚫려도 게이트가 막고, 게이트가 뚫려도 체결 확인이 잡고, 그것도 놓치면 원장 대조가 잡는다. 각 층이 못 잡는 경우가 서로 다르니 겹쳐야 의미가 있다.

원칙도 하나 생겼다. 봇의 기억보다 거래소의 기록을 믿는다. 봇이 “팔았다"고 생각하는 것과 거래소가 “체결됐다"고 말하는 것이 다르면 거래소 쪽이 정답이다. 손익 계산의 근거를 봇 메모리에서 거래소 주문내역으로 옮긴 것도 같은 맥락이다.

시간대별 거래량, 언제 사야 하나

거래대금의 세 번째 얼굴은 시장의 리듬이다. 코인 시장은 24시간 돌지만 균질하지 않다.

처음 설계할 때 나는 직관적인 가정을 했다. 거래가 활발한 시간에 기회도 많을 테니 그때 공격적으로 가자. 그래서 시간대별로 평가 종목 수와 진입 문턱을 다르게 두는 구조를 만들었고, 한국 저녁 시간대는 평가 종목을 늘리고 문턱을 낮춘 반면 낮 시간대는 보수적으로 잡았다.

실측이 이 설계를 뒤집었다.

시간대별 성과 — 스캔 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 보다 표본 고갈의 비용이 컸다

두 가지를 배웠다.

하나. 거래가 많다는 건 사람이 많다는 얘기지 내 자리가 남아 있다는 얘기가 아니다. 붐비는 시간에는 나 같은 눌림목 사냥꾼도 많다.

둘. 통계적으로 옳은 규칙이 운용상 틀린 규칙일 수 있다. 19~23시 차단은 데이터가 지지하는 결정이었다. 부트스트랩 우세 78.8%면 우연으로 보기 어렵다. 그런데 실제로 돌려보니 진입이 하루 1.6건으로 줄었고, 그러면 통계를 쌓을 표본 자체가 안 만들어진다. 검증 가능한 상태를 유지하는 것도 운용의 일부라는 걸 그때 알았다.

백테스트와 실전의 차이이기도 하다. 백테스트에서는 표본이 이미 존재한다. 실전에서는 매매를 해야 표본이 생긴다. 진입을 줄이는 규칙은 성과를 개선하면서 동시에 학습 속도를 떨어뜨린다. 그 교환을 계산에 넣어야 한다.

마무리

24시간 거래대금은 화려한 지표가 아니다. RSI처럼 매수 시점을 알려주지도 않고 볼린저 밴드처럼 그림이 예쁘지도 않다. 하는 일은 하나다. 문 앞에서 못 들어올 종목을 돌려보내는 것.

그런데 봇을 오래 굴리다 보면 이 종류의 규칙이 수익률에 미치는 영향이 생각보다 크다는 걸 알게 된다. 좋은 종목을 찾는 일과 나쁜 종목을 안 만나는 일 중에서는 후자가 훨씬 쉽다. 그리고 쉬운 쪽이 계좌를 더 확실하게 지킨다. 신호를 잘 잡는 문제와 계좌를 지키는 문제는 서로 다른 문제이고, 뒤쪽은 대체로 이런 지루한 필터들이 맡는다.

거래소 앱에서 무심코 지나치던 그 숫자가, 사실은 내 봇에서 가장 먼저 실행되는 조건이다.

덧붙임, 세탁 거래를 의심하는 방법

신뢰성 얘기를 조금만 더 하고 끝내자. 거래량 지표는 시장이 정직하다는 전제 위에 서 있는데, 그 전제가 항상 성립하지는 않는다.

세탁 거래는 같은 주체가 자기 물량을 주고받아 거래량을 부풀리는 행위다. 목적은 대개 순위표 노출이나 상장 유지 조건 충족이고, 개인이 이걸 정확히 판별하기란 사실상 어렵다. 다만 의심할 만한 정황은 몇 가지 볼 수 있다.

정황 점검 항목
① 거래대금 대비 호가 두께가 얇다
   하루 종일 활발히 거래된 종목의 호가창이 비어 있을 이유가 없다
② 체결 크기 분포가 이상하다
   비슷한 크기의 체결이 규칙적으로 반복되면 사람의 매매 같지 않다
③ 가격 변동 없이 거래량만 크다
   진짜 수급이라면 가격이 움직인다. 안 움직이는데 거래만 많으면 이상하다
④ 특정 시간대에만 거래가 몰린다
   자동화된 반복 패턴일 수 있다
봇 관점의 실용적 결론
  판별하려 애쓰기보다 호가 기반 조건으로 우회하는 게 낫다
  세탁 거래는 거래대금을 부풀릴 수 있지만 호가창을 채우진 못한다
  스프레드 상한과 호가단위 게이트가 이 문제의 실질적 방어선이다

마지막 줄이 실무적 답이다. 세탁 거래인지 판정하려 애쓰지 말고, 세탁 거래가 만들어낼 수 없는 조건을 걸 것. 거래대금 숫자는 조작할 수 있어도 실제로 살 수 있는 호가는 조작하기 어렵다.

이 접근은 다른 문제에도 쓰인다. 원인을 정확히 규명하기 어려울 때 원인 대신 증상이 나타나는 경로를 막는 것. 유령 포지션을 체결 확인으로 막은 것도, 호가 관통을 게이트로 막은 것도 같은 발상이다.

다만 이 방식이 어디까지 통하는지는 아직 잘 모르겠다. 증상만 막아두면 원인은 그 자리에 그대로 남아 있고, 언젠가 다른 얼굴로 다시 나올 것이다. 호가 게이트를 세운 뒤로 아직 그런 일은 없었다. 없었다는 것만으로 안심해도 되는지는, 다음에 뭔가 터져봐야 알 것 같다.


약어 풀이

* 이 글에 나온 영어 약어를 풀어 둔다.

  • RSI (Relative Strength Index): 상대강도지수. 일정 기간 평균 상승폭과 평균 하락폭의 비율을 0~100 으로 나타내 과매수·과매도를 재는 모멘텀 오실레이터.
  • BTC (Bitcoin): 비트코인. 이 블로그에서는 코인 시장 전체의 방향(레짐)을 재는 기준 자산.
  • API (Application Programming Interface): 프로그램끼리 데이터와 기능을 주고받는 규약. 거래소 API 로 시세를 받고 주문을 낸다.