자동매매를 처음 돌릴 때 저는 시그널이 많을수록 좋다고 착각했습니다. 4시간봉에서 조건이 맞으면 바로 진입하도록 설계했고, 초반 몇 주는 포지션 수가 많다는 것 자체가 시스템이 잘 돌아간다는 착각을 줬습니다. 그런데 막상 결과를 보면 진입 횟수가 많을수록 긁히는 수수료와 손절 횟수도 비례해서 늘었습니다. 알고 보니 문제는 시그널 자체가 아니라, 유효하지 않은 시그널을 걸러내는 필터가 없다는 것이었습니다. 이번 칼럼은 제가 6개월 넘게 운영하면서 직접 부딪혀 정리한 4시간봉 노이즈 필터링 방법을 공유합니다.
노이즈 시그널이란 무엇인가 — 문제를 정확히 정의하기
4시간봉 기준으로 시그널을 설계할 때 가장 흔한 함정은 조건 자체는 맞지만 시장 맥락이 맞지 않는 경우입니다. 예를 들어 RSI가 30 이하이고 EMA 단기선이 장기선을 골든크로스하는 조건을 잡았다고 해봅시다. 백테스트에서는 꽤 그럴듯한 승률이 나옵니다. 그런데 실제로 돌리면 횡보 구간에서 이 조건이 반복적으로 발생합니다. 가격이 좁은 범위 안에서 왔다 갔다 하는 동안 EMA 크로스가 하루에 두 번씩 발생하는 경우도 있었습니다. 저는 이것을 노이즈 시그널이라고 부릅니다. 조건은 충족됐지만 실질적인 추세나 모멘텀이 뒷받침되지 않은 시그널입니다. 노이즈 시그널의 특징은 세 가지입니다. 첫째, 짧은 시간 안에 반복 발생합니다. 4시간봉에서 동일 조건이 2~3캔들 이내에 재발생하면 의심해야 합니다. 둘째, ATR 값이 낮습니다. ATR이 평균보다 40% 이상 낮은 구간에서 발생한 시그널은 변동성이 없는 상태에서의 가짜 움직임인 경우가 많습니다. 셋째, 거래량이 동반되지 않습니다. 시그널 캔들의 거래량이 20봉 평균 거래량의 70% 미만이면 추세 지속 가능성이 떨어집니다. 이 세 가지를 TradingView 조건에 추가하는 것만으로도 시그널 수가 확연히 줄어들고, 남은 시그널의 질이 달라지는 것을 관찰할 수 있었습니다.
TradingView + 웹훅 단계에서 1차 필터 설계하기
저는 TradingView Pine Script 단에서 먼저 1차 필터를 걸고, 그 신호가 Flask 서버를 거쳐 Jesse로 넘어가는 구조를 씁니다. 노이즈를 줄이기 위한 Pine 단의 핵심 조건은 다음과 같습니다. 우선 ADX 필터입니다. ADX 값이 20 미만인 구간에서는 트렌드가 없다고 보고 시그널 자체를 발행하지 않도록 조건에 추가했습니다. 코드로 표현하면 adx_val = ta.rma(…) 계열 계산 후 adx_val > 20 을 and 조건으로 묶는 식입니다. 다음은 캔들 바디 비율 조건입니다. 전체 캔들 범위 대비 바디 비율이 50% 미만인 도지 유형 캔들에서 발생한 시그널은 제외합니다. body_ratio = math.abs(close – open) / (high – low) 로 계산하고 body_ratio > 0.5 를 조건에 포함합니다. 마지막은 직전 시그널 쿨다운입니다. Pine Script에서는 var int last_signal_bar = na 를 선언하고, 시그널 발생 시점의 bar_index 를 저장한 뒤 현재 bar_index – last_signal_bar < 6 이면 발행을 막습니다. 즉 6캔들, 24시간 이내 재진입을 원천 차단합니다. 이렇게 설계하면 웹훅으로 나가는 신호 자체가 줄어들어 Flask 서버 부하도 줄고, 불필요한 거래 시도 자체를 사전에 막을 수 있습니다.
Jesse 프레임워크 진입 로직에서 2차 필터 적용하기
TradingView에서 1차 필터를 통과한 시그널이 Flask를 거쳐 Jesse로 넘어오면, 저는 Jesse 전략 파일 안에서 한 번 더 체크합니다. 이 2차 필터는 시장 상황을 좀 더 정밀하게 보는 역할을 합니다. 첫 번째로 쓰는 것은 상위 타임프레임 정합성 체크입니다. 4시간봉 롱 시그널이 들어왔을 때 일봉 기준 EMA50과 EMA200의 배열이 정배열인지 확인합니다. Jesse에서는 self.get_candles(exchange, symbol, ‘1D’) 로 일봉 데이터를 가져와서 간단히 체크할 수 있습니다. 일봉 방향과 4시간봉 시그널 방향이 맞지 않으면 진입하지 않습니다. 두 번째는 오픈 포지션 수 제한입니다. 동일 코인에서 이미 포지션이 열려 있을 때 추가 시그널이 들어오면 무시합니다. jesse의 self.position.is_open 조건으로 쉽게 처리됩니다. 세 번째는 시간대 필터입니다. 저는 UTC 기준 00시~04시 구간, 즉 주요 시장이 모두 닫혀 있고 유동성이 낮은 구간의 진입을 제한합니다. Python datetime 으로 현재 캔들 시간을 확인해서 해당 구간이면 return 으로 건너뜁니다. 이 2차 필터까지 적용하면 체감상 진입 시도 횟수가 1차 필터 적용 이전 대비 절반 이하로 줄어드는 것을 관찰하고 있습니다. 물론 그게 바로 좋은 결과를 보장하지는 않지만, 적어도 수수료 낭비와 무의미한 손절 반복은 확실히 줄어들었습니다.
💬 운영자 한마디
저도 처음엔 필터를 너무 많이 쌓으면 오히려 좋은 시그널까지 놓치는 게 아닐까 걱정했습니다. 그런데 실제로 운영해보니 필터가 없는 시스템이 더 위험했습니다. 지금도 검증 중이지만, 노이즈를 줄이는 것이 시그널을 더 잡으려는 것보다 훨씬 실용적인 방향이라는 확신이 점점 강해지고 있습니다.
— J_River · autoprofit 운영자
✅ 이번 주 체크리스트
- TradingView 시그널 조건에 ADX > 20 필터를 추가했는가?
- 캔들 바디 비율 0.5 미만 도지 캔들 진입을 차단하는 조건이 있는가?
- 동일 심볼에서 6캔들 이내 재시그널 발행을 막는 쿨다운 로직이 있는가?
- Jesse 전략 파일에서 일봉 EMA 배열로 상위 타임프레임 정합성을 체크하는가?
- 유동성이 낮은 UTC 00~04시 구간 진입을 시간대 필터로 제한하고 있는가?
- 동일 코인 포지션이 이미 열려 있을 때 추가 진입 차단 로직이 설정되어 있는가?
- ATR 값이 평균 대비 40% 이하인 저변동성 구간 시그널을 별도로 로깅해 분석하고 있는가?
필터링은 수익을 만드는 기술이라기보다 손실을 줄이는 기술입니다. 자동매매 시스템이 스스로 나쁜 타이밍을 거를 수 있도록 설계하는 것, 그게 지금 제가 가장 집중하는 부분입니다. 다음 주에도 실전에서 부딪힌 내용을 가지고 돌아오겠습니다.
※ 본 콘텐츠는 운영자 개인의 경험과 연구를 바탕으로 한 정보 제공 목적이며, 특정 종목에 대한 투자 권유나 매매 시그널이 아닙니다. 암호화폐 및 자동매매는 원금 손실 가능성이 있는 고위험 활동이며, 모든 투자 판단과 그 결과에 대한 책임은 투자자 본인에게 있습니다.