VWAP 스캘핑 2편 — 손익분기 승률의 정체
VWAP 표준편차 밴드 전략에 ADX 필터를 추가해 추세장을 회피하는 스캘핑 봇을 만들었습니다. 수수료를 이기는 손익분기 승률의 구조와 지정가 주문의 한계를 정리했습니다.
유튜브 영상 '시스템으로 초단타 치면 생기는 일' 편에서 VWAP 밴드 스캘핑 봇을 ADX 필터로 개조해 돌려봤습니다. 이 글은 그 정리본에 영상에는 안 넣은 핵심 실행 코드를 붙인 것입니다. 결론부터 말하면, 이 전략의 진짜 핵심은 기술적 지표가 아니라 '손익분기 승률'이라는 산수였습니다. 그리고 그 산수를 지키려다 다른 함정에 빠지는 과정을 기록했습니다. 실제 수익률은 여기에 옮겨 적으면 제가 유리한 구간만 고르게 되니까요, 영상으로만 확인하시는 게 가장 정직합니다.
VWAP 밴드: 고무줄은 제자리로 돌아온다
전략의 기본 아이디어는 1편과 같습니다. VWAP은 '거래량을 곱해서 평균 낸 현재 시장의 공정 가격'으로 볼 수 있습니다. 가격은 늘 이 평균값을 중심으로 위아래로 움직이죠. 여기서 표준편차로 밴드를 그리면 '가격이 평균에서 얼마나 멀리 떨어졌는지'를 잴 수 있습니다. 고무줄처럼요. 가격이 밴드 하단까지 늘어나면(싸지면) 평균으로 돌아올 거라 보고 롱, 상단까지 늘어나면(비싸지면) 숏을 잡는 게 기본 원리입니다.
문제는 추세가 터졌을 때입니다. 강한 상승 추세에서는 가격이 상단 밴드에 붙어서 계속 올라가 버립니다. 고무줄이 돌아오지 않고 그냥 끊어지는 거죠. 1편에서 깨졌던 이유 중 하나가 바로 이것이었습니다.
ADX, 고무줄이 끊어질 장을 피하는 필터
그래서 이번엔 ADX(Average Directional Index)라는 필터를 추가했습니다. ADX는 시장의 방향이 아니라 '추세의 세기'만 측정하는 지표입니다. 황소와 곰의 줄다리기가 얼마나 치열한지만 보는 거예요. ADX가 20 아래로 낮으면 '줄이 느슨하다', 즉 뚜렷한 방향 없이 횡보하는 장세로 봅니다. 반대로 40 이상으로 높으면 '한쪽이 압도적으로 당긴다', 즉 강한 추세장으로 해석합니다.
이걸 역으로 이용하는 겁니다. ADX가 20을 넘으면 '고무줄이 끊어질 수 있는 위험한 장'으로 보고 아예 진입을 안 하는 거죠. 오직 ADX가 낮은 횡보장에서만 VWAP 밴드 전략을 실행해서 승률을 높이려는 시도입니다. ADX는 방향 예측 도구가 아니라, 특정 전략이 잘 먹히는 운동장을 골라주는 필터인 셈입니다.
// ADX 계산 핵심 (C#)
// 방향성 이동(+DM/-DM)과 변동폭(TR)을 구해서
// DI를 만들고, DI의 차이(DX)를 평활해서 ADX를 얻는다.
public static double Adx(double[] high, double[] low, double[] close, int p)
{
int n = close.Length;
if (n < p * 2 + 2) return 0; // 계산할 봉 개수가 충분해야 한다.
double[] tr = new double[n]; // True Range
double[] pdm = new double[n]; // +Directional Movement
double[] ndm = new double[n]; // -Directional Movement
for (int i = 1; i < n; i++)
{
// ... +DM, -DM, TR 계산 로직 ...
}
// 와일더 평활이동평균(Wilder's Smoothing)으로 ATR, APDM, ANDM을 구한다.
// 이 값들로 +DI와 -DI를 계산하고, 둘의 차이를 이용해 DX를 만든다.
List<double> dxs = new List<double>();
for (int i = p + 1; i < n; i++)
{
// ... 평활 로직 ...
double dx = denom > 0 ? 100.0 * Math.Abs(pdi - ndi) / denom : 0;
dxs.Add(dx);
}
// 마지막으로 DX 시리즈를 다시 평활하면 최종 ADX가 나온다.
double adx = 0;
// ... DX 평활 로직 ...
return adx;
// ADX는 방향이 아니라 힘의 '세기'다. 잊으면 물린다.
}수수료를 이기는 손익비 산수
초단타 스캘핑은 수수료와의 전쟁입니다. 아무리 전략이 좋아도 수수료를 못 넘으면 계좌는 녹을 수밖에 없거든요. 그래서 이 봇은 수수료 구조를 극도로 타이트하게 설계했습니다. 바로 지정가(Maker) 주문을 적극적으로 활용하는 겁니다.
- 진입: VWAP 밴드에 지정가 주문을 미리 깔아둬서 메이커(Maker)로 체결. (수수료 0.02%)
- 익절: +0.6% 지점에 지정가 주문을 걸어 메이커로 체결. (수수료 0.02%)
- 손절: -0.3% 지점을 뚫리면 시장가(Taker)로 즉시 청산. (수수료 0.05%)
이렇게 하면 손익 구조가 비대칭이 됩니다. 익절 시 순수익은 0.6% - 0.02% - 0.02% = 0.56%가 되고, 손절 시 순손실은 0.3% + 0.02% + 0.05% = 0.37%가 됩니다. 이 숫자를 가지고 계산하면 손익분기 승률이 약 39.8%가 나옵니다. 즉, 열 번 매매해서 네 번만 이기면 수수료를 모두 내고도 계좌가 우상향한다는 계산이 나옵니다. 이것이 이 전략의 진짜 엔진입니다. ADX 필터는 이 39.8% 승률을 넘기기 위한 보조 장치일 뿐이고요.
// 포지션 진입과 동시에 익절 주문을 까는 로직 (C#)
void OpenPos(int dir, double entryP, double q, long tMs, Config cfg, BinanceFutures ex)
{
pos = dir;
entryPrice = entryP;
posQty = q;
CancelQuotes(cfg, ex); // 진입했으니 반대쪽 밴드 지정가는 취소한다.
roundPnl = -(cfg.FeeMaker * entryPrice * posQty); // 진입 메이커 수수료부터 까고 시작
// 익절 지정가 0.6%
double tp = dir > 0 ? entryPrice * (1 + cfg.TpPct / 100.0)
: entryPrice * (1 - cfg.TpPct / 100.0);
string tpSide = dir > 0 ? "SELL" : "BUY";
// PlaceLimit 함수는 내부적으로 reduceOnly 옵션을 켠다.
// 즉, 이 주문은 포지션을 늘리는 데 쓰일 수 없고 오직 청산에만 쓰인다.
// 이게 실수로 포지션이 반대로 뒤집히는 걸 막아주는 중요한 안전장치다.
PlaceLimit(tpOrd, tpSide, posQty, tp, true, cfg, ex);
state = IN_POS; // 이제 포지션 보유 상태로 전환
Log("진입체결 -> 익절지정가 " + FmtPrice(RoundTick(tp)));
// 가장 위험한 건 코드가 아니라 내 손이다.
}왜 이게 실전에서 깨지는가
- 지정가는 체결을 보장하지 않습니다. 가격이 밴드를 스치고 지나가도 유동성이 부족하면 제 주문은 아무도 안 사가고 기회비용만 날립니다.
- 손절 시장가는 항상 나에게 불리하게 체결됩니다. 0.3% 손절을 걸어도 실제로는 슬리피지 때문에 0.35%, 0.4% 손실이 나는 게 현실입니다.
- ADX는 후행성 지표입니다. ADX 값이 낮아서 진입했는데, 그 직후 추세가 터져버리는 경우가 비일비재합니다. ADX는 미래를 예측하지 못해요.
- 파라미터가 너무 많아 과최적화의 덫에 빠지기 쉽습니다. VWAP 기간, 밴드 배수, ADX 임계값, 익절/손절폭 등 수많은 변수를 특정 기간에 맞춰 조절하면 과거 데이터에는 완벽한 전략이 탄생합니다. 하지만 그건 미래가 아니라 과거를 암기한 것에 불과합니다.
- 급추세 '감지'는 급추세 '발생 이후'에 일어나는 일입니다. 방어 로직이 작동했을 땐 이미 손실이 발생한 뒤일 수 있습니다. 예측이 아니라 대응이니까요.
- 체결 데이터 스트림의 지연 문제입니다. 특히 변동성이 큰 시장에서는 내가 받는 데이터가 이미 몇백 밀리초 전의 과거일 수 있고, 이 작은 차이가 스캘핑에서는 승패를 가릅니다.
이 글과 첨부 코드는 전략 구조를 설명하려고 만든 교육·연구 자료입니다. 특정 종목이나 매매를 권하지 않습니다.
첨부파일
영상에 사용된 소스코드와 설명서입니다. 로그인 없이 받을 수 있습니다.
VWAP 밴드 + ADX 스캘핑 봇 소스코드