Off quotes / 리크오트(MT5/MQL5) 완전 해결 — 10021
목차
- 이 두 가지(+MT4의 136/138)는 무엇이 다른가
- ① TRADE_RETCODE_PRICE_OFF = 10021 (처리할 호가가 없음)
- ② TRADE_RETCODE_REQUOTE = 10004 (리크오트 = 새 가격 재제시)
- ③ MT4 시절의 에러 136 / 138
- 우선 30초 만에 할 수 있는 구분
- 원인과 대처(5가지 패턴)
- ① 급변 시세·지표 스파이크 (전송한 가격이 도착하기 전에 낡아짐)
- ② deviation(허용 슬리피지)이 지나치게 좁게 설정됨
- ③ 체결 방식의 차이 (리크오트는 instant 체결의 현상)
- ④ 호가가 얇거나 멈춰 있는 시간대·종목
- ⑤ 회선 지연(VPS가 브로커 서버에서 멀다)
- MQL5로 이 에러를 줄이는 코드(EA 개발자용)
- deviation을 포인트 단위로 적절히 설정한다
- 가격 계열 retcode만 재시도한다(최신 틱으로 재전송)
- 리크오트(10004)의 재제시 가격을 사용한다
- 애초에 주문을 넣지 않는 시간대를 만든다
- 우선순위 체크리스트
- 정리
- FAQ
- Q: 10004와 10021은 어느 쪽이 더 심각한가요?
- Q: 리크오트가 한 번도 발생하지 않는 브로커는 우수한 건가요?
- Q: MT4 EA에서 error 136 / 138이 발생합니다. 같은 대처로 괜찮나요?
- Q: 백테스트에서는 발생하지 않는데 실전에서는 발생합니다.
Off quotes / 리크오트(MT5/MQL5) 완전 해결
EA를 운용하다가 저널이나 Expert 탭에 off quotes(10021)나 requote(10004)가 뜨면 "브로커에게 체결을 거부당했다"는 느낌이 들어 불안해지지만, 이 두 가지는 자금이나 로트 문제가 아니라 "가격" 문제입니다. 당신이 보낸 가격과 서버가 지금 가지고 있는 가격이 맞지 않았다—그것뿐이며, 원인 대부분은 급변 시세·허용 슬리피지·체결 방식·회선 지연 중 하나로 귀결됩니다.
이 글은 MT5에서 EA를 사용하는 사람과 MQL5로 EA를 작성하는 사람 모두를 위해, 10021(TRADE_RETCODE_PRICE_OFF)과 10004(TRADE_RETCODE_REQUOTE), 그리고 MT4 시절의 에러 136/138의 정체·원인·즉시 가능한 대처·코드 차원의 근본 대책까지를 하나로 정리한 결정판입니다. 에러 코드 전반에 대한 목록은 MQL5 / MT5 에러 코드 대처법 종합 가이드를 참조하세요.
이 글은 2026년 7월 시점의 MT5(build 4xxx 계열)를 기준으로 합니다. 세부 동작(리크오트를 반환하는지, 그대로 체결시키는지 등)은 브로커의 체결 방식에 따라 다릅니다.
이 두 가지(+MT4의 136/138)는 무엇이 다른가
"가격이 맞지 않는" 계열의 거부는 서버의 응답 방식에 따라 2가지로 나뉩니다.
① TRADE_RETCODE_PRICE_OFF = 10021 (처리할 호가가 없음)
OrderSend()의 결과 MqlTradeResult.retcode에 들어가는 값으로, **"요청을 처리할 호가가 없다(There are no quotes to process the request)"**는 거부입니다. 서버 측에 유효한 가격이 없거나, 전송된 가격이 현재 호가에서 너무 벗어나 처리할 수 없는 상태를 가리킵니다.
의미: 요청을 처리할 호가가 없음
상수: TRADE_RETCODE_PRICE_OFF
값 : 10021
② TRADE_RETCODE_REQUOTE = 10004 (리크오트 = 새 가격 재제시)
역시 OrderSend()의 retcode이지만, 이쪽은 단순한 거부가 아니라 **"그 가격으로는 안 되지만, 이 새로운 가격이라면 어떤가"라는 재제시(리크오트)**입니다. MqlTradeResult의 bid / ask에 서버가 다시 제시한 가격이 담겨 반환됩니다.
의미: 리크오트(Requote) — 새 가격 재제시
상수: TRADE_RETCODE_REQUOTE
값 : 10004
// 로그에서 확인할 수 있는 전형적인 출력 예시
2026.07.07 21:30:02.118 EA_NAME EURUSD,M5: OrderSend error 10004 (requote)
2026.07.07 21:30:02.310 EA_NAME EURUSD,M5: OrderSend error 10021
③ MT4 시절의 에러 136 / 138
MQL4(MT4)에서는 같은 현상이 GetLastError()의 에러 코드로 반환되었습니다.
| MT4 상수 | 값 | 대응하는 MT5 retcode |
|---|---|---|
ERR_OFF_QUOTES | 136 | 10021 (TRADE_RETCODE_PRICE_OFF) |
ERR_REQUOTE | 138 | 10004 (TRADE_RETCODE_REQUOTE) |
오래된 EA 해설 글이나 MT4판 EA의 로그에서 "error 136", "error 138"을 보았다면, 이 글의 내용이 그대로 적용됩니다(MT4에서는 RefreshRates()로 가격을 다시 가져온 뒤 재전송하는 것이 정석이었습니다. MT5에서의 작성법은 뒤에서 다룹니다).
실무상의 구분:
| retcode | 서버의 입장 | EA가 취해야 할 행동 |
|---|---|---|
| 10004 (requote) | "가격이 움직였다. 새 가격을 제시한다" | 최신 가격으로 재전송(또는 보류) |
| 10021 (price off) | "처리할 호가가 없다" | 잠시 대기 후 최신 틱으로 재전송 |
둘 다 일시적(재시도 가능)인 에러이며, 코드 수정이나 설정 변경으로 "절대 발생하지 않게" 만들 수는 없지만, 빈도는 크게 줄일 수 있습니다.
우선 30초 만에 할 수 있는 구분
- 언제 발생했는지를 저널의 시각으로 확인한다
- 고용지표·FOMC 등 지표 발표 순간 → 정상적인 급변 시세. EA에 지표 필터가 있다면 활성화한다
- 서버 시각 0시 전후(롤오버)나 주초 갭 → 호가가 얇은 시간대. 정상 범위
- 시간대와 무관하게 무작위로 빈발 → 회선·VPS·deviation 설정을 의심한다
- 어느 종목에서 발생했는지를 확인한다
- 마이너 통화쌍·이색 종목·CFD 등 유동성이 낮은 종목에 편중되어 있다면, 종목 측 호가가 얇은 것이 원인
- 수동 주문에서는 발생하는지를 시험한다
- 수동 퀵 주문은 정상적으로 체결되는데 EA만 거부된다 → EA의
deviation(허용 슬리피지)이 지나치게 좁게 설정되어 있을 가능성이 높다
- 수동 퀵 주문은 정상적으로 체결되는데 EA만 거부된다 → EA의
이 3가지로 "시세 탓", "종목 탓", "설정·환경 탓" 중 어느 쪽인지 감을 잡은 뒤, 다음의 원인별 대처로 넘어갑니다.
원인과 대처(5가지 패턴)
① 급변 시세·지표 스파이크 (전송한 가격이 도착하기 전에 낡아짐)
증상: 경제지표 발표 시각, 요인 발언, 월요일 이른 아침 등에 집중적으로 10004/10021이 발생한다.
원인: EA가 틱을 받아 가격을 계산하고, 주문이 서버에 도착할 때까지의 수십~수백ms 사이에 가격이 몇 pips 움직였다. 전송한 가격은 이미 존재하지 않으므로, 서버는 리크오트(10004)나 호가 없음(10021)을 반환합니다. 에러라기보다, 빠른 시세에서는 당연히 일어나는 현상입니다.
대처:
- 지표 전후의 신규 진입을 멈춘다(뉴스 필터). 본 사이트 배포 EA는
EconomicFilter를 표준 탑재 deviation(허용 슬리피지)을 현실적인 값으로 넓힌다(② 참조)- 재시도 로직을 넣는다(후술하는 코드)
② deviation(허용 슬리피지)이 지나치게 좁게 설정됨
증상: 잔잔한 시세에서도 간간이 발생한다. 수동 주문은 통과되는데 EA만 거부된다.
원인: MqlTradeRequest.deviation은 "전송한 가격에서 몇 포인트까지의 차이를 받아들일 것인가"의 선언입니다. 이를 0~수 포인트로 좁히면, 통상적인 틱 갱신 정도의 차이도 거부 대상이 됩니다. **pips가 아니라 points(5자리 브로커라면 1 pip = 10 points)**라는 점의 착각도 흔한 패턴입니다.
대처:
deviation을 1030 points(= 13 pips) 정도부터 시작해 상태를 본다. 스캘핑이 아니라면 20 points가 무난한 출발점- "deviation=5로 설정했는데 실제로는 0.5 pips였다" 같은 단위 착오가 없는지 확인한다
- 슬리피지를 일절 허용하고 싶지 않은 전략이라면, 거부는 사양으로 받아들이고 재시도 횟수만 제어한다
③ 체결 방식의 차이 (리크오트는 instant 체결의 현상)
증상: 브로커 A에서는 빈발하는데, 브로커 B에서는 한 번도 보이지 않는다.
원인: 리크오트(10004)는 instant execution(인스턴트 체결) 특유의 현상입니다. instant 체결은 "이 가격으로 체결시켜라"라는 주문이므로, 가격이 움직이면 서버는 새 가격을 재제시(리크오트)합니다. 반면 **market execution(마켓 체결)**은 "지금의 시장 가격으로 체결시켜라"라는 주문이므로, 리크오트는 원리적으로 발생하지 않으며, **대신 벌어진 가격으로 그대로 체결(슬리피지)**됩니다.
즉 "리크오트가 발생하지 않는다 = 우수하다"가 아니라, 거부되느냐 밀려서 체결되느냐의 트레이드오프입니다. 심볼의 체결 방식은 MT5의 "종목 사양(Specification)"의 Execution 항목, 또는 코드에서는 SYMBOL_TRADE_EXEMODE로 확인할 수 있습니다.
대처:
- 자신의 계좌 체결 방식을 확인한다(많은 해외 브로커의 표준 계좌는 market 체결이며, 리크오트 자체가 발생하지 않는다)
- 리크오트 빈발이 전략에 방해가 된다면, market 체결 계좌 유형·브로커를 검토한다
- market 체결이라도
deviation을 존중하는 브로커와 그렇지 않은 브로커가 있으므로, 극단적인 밀림을 허용할 수 없다면 체결 이력에서 실제 슬리피지를 검증한다
④ 호가가 얇거나 멈춰 있는 시간대·종목
증상: 서버 시각 0시 전후(롤오버), 주초 오픈 직후, 크리스마스 등 한산기, 마이너 종목에서 10021이 발생한다.
원인: 롤오버 시에는 스왑 처리를 위해 일시적으로 호가 배포가 멈추거나 스프레드가 극단적으로 벌어지기도 합니다. 주초 오픈 직후나 유동성이 낮은 종목에서는 애초에 처리할 수 있는 호가 자체가 적습니다. 이 상태에서 주문을 넣으면 10021(호가 없음)이 됩니다.
대처:
- 서버 시각 23:55~0:05 전후의 신규 진입을 피한다(시간 필터)
- 주초 오픈 직후 몇 분을 피한다(
AvoidMondayOpen계열 설정) - 스프레드 필터(
MaxSpread)를 넣는다. 호가가 얇은 시간에는 스프레드가 벌어지므로, 실질적으로 이 시간대를 자동으로 회피할 수 있다
⑤ 회선 지연(VPS가 브로커 서버에서 멀다)
증상: 시간대·종목과 무관하게, 다른 환경보다 명백히 빈도가 높다. ping 값이 크다.
원인: 주문이 서버에 도착할 때까지의 왕복 시간(레이턴시)이 길수록, 그 사이에 가격이 움직일 확률이 올라갑니다. 자택 PC나, 브로커 서버 소재지(런던·뉴욕 등이 많음)에서 먼 리전의 VPS에서 운용하면, 10004/10021의 빈도가 구조적으로 올라갑니다. MT5 우측 하단에 표시되는 ping이 하나의 기준입니다(수백ms는 명확히 불리하며, 수십ms 이하가 바람직합니다).
대처:
- MT5 우측 하단의 ping 값을 확인하고, 항상 크다면 EA 실행 환경을 재검토한다
- 브로커 서버에 가까운 리전의 VPS로 옮긴다. 솔직히 말해, 레이턴시에서 비롯된 10004/10021은 코드로는 줄일 수 없으며, 물리적으로 가까워지는 것이 유일한 대책입니다. 선택 방법은 EA용 VPS 선택 가이드에 정리되어 있습니다
- 스캘핑 계열 EA일수록 레이턴시의 영향이 큽니다. 일봉·H4 계열 EA라면 이 원인의 우선순위는 낮습니다
MQL5로 이 에러를 줄이는 코드(EA 개발자용)
방침은 3가지입니다. (1) deviation을 현실적으로 설정한다, (2) 거부되면 최신 틱을 다시 가져와 재전송한다, (3) 재시도는 가격 계열 retcode만으로 한정한다.
deviation을 포인트 단위로 적절히 설정한다
MqlTradeRequest req; MqlTradeResult res;
ZeroMemory(req); ZeroMemory(res);
req.action = TRADE_ACTION_DEAL;
req.symbol = _Symbol;
req.type = ORDER_TYPE_BUY;
req.volume = lots;
req.deviation = 20; // 허용 슬리피지 20 points(5자리라면 2.0pips)
// req.price / req.sl / req.tp / req.magic 등도 설정한다
deviation의 단위는 points입니다. 5자리(3자리) 표시 브로커에서는 10 points = 1 pip. 0이나 극단적으로 작은 값은 거부율을 불필요하게 높입니다.
가격 계열 retcode만 재시도한다(최신 틱으로 재전송)
핵심은 재시도할 때마다 SymbolInfoTick()으로 가격을 다시 가져오는 것(낡은 가격 그대로 재전송하면 같은 거부가 반환될 뿐입니다)과, 재시도 대상을 10004/10021로 한정하는 것입니다. 자금 부족(10019)이나 잘못된 요청(10013)을 기계적으로 재전송해도 의미가 없고, 로그만 지저분해집니다.
bool IsRetryableRetcode(uint rc)
{
return (rc == TRADE_RETCODE_REQUOTE // 10004
|| rc == TRADE_RETCODE_PRICE_OFF); // 10021
}
// 최신 틱으로 가격을 갱신하면서 최대 3회까지 재전송한다
bool SendWithRetry(MqlTradeRequest &req, MqlTradeResult &res, int maxTries = 3)
{
for(int attempt = 0; attempt < maxTries; attempt++)
{
MqlTick tick;
if(!SymbolInfoTick(req.symbol, tick))
{
Print("SymbolInfoTick failed: ", GetLastError());
return false;
}
req.price = (req.type == ORDER_TYPE_BUY) ? tick.ask : tick.bid;
if(OrderSend(req, res) && res.retcode == TRADE_RETCODE_DONE)
return true; // 체결 성공
if(!IsRetryableRetcode(res.retcode))
{
PrintFormat("OrderSend failed (no retry): retcode=%d", res.retcode);
return false; // 가격 계열 이외는 재시도하지 않는다
}
PrintFormat("Retry %d/%d after retcode=%d", attempt + 1, maxTries, res.retcode);
Sleep(200 + 150 * attempt); // 200ms→350ms→500ms로 조금씩 대기
}
Print("Order abandoned after retries (price kept moving).");
return false;
}
Sleep()의 대기 시간을 단계적으로 늘리는 이유는, 급변 순간에 0ms로 연타해도 같은 거부를 계속 당하기 때문입니다. 반대로 너무 오래 기다리면 진입 가격이 전략의 의도에서 멀어지므로, 재시도는 2~3회로 포기하는 것이 건전합니다. 지정가 주문(TRADE_ACTION_PENDING)의 경우, 성공 시 retcode는 TRADE_RETCODE_PLACED(10008)가 된다는 점에도 주의하세요.
리크오트(10004)의 재제시 가격을 사용한다
10004일 때, MqlTradeResult의 bid / ask에 서버의 재제시 가격이 담겨 있습니다. 위와 같이 최신 틱으로 재전송하면 실용상 충분하지만, instant 체결에서 "재제시 가격이 허용 범위 내라면 받아들인다"는 로직을 짜고 싶다면 res.ask / res.bid와 원래 예상 가격의 차이를 points로 비교한 뒤 재전송하는 형태가 됩니다.
애초에 주문을 넣지 않는 시간대를 만든다
코드에서의 재시도는 대증요법입니다. 롤오버 전후·지표 전후·스프레드 확대 시 신규를 멈추는 필터 쪽이 더 근본적으로 효과가 있습니다.
// 스프레드 필터 예시: 벌어져 있는 시간대는 신규를 보류한다
long spreadPts = SymbolInfoInteger(_Symbol, SYMBOL_SPREAD);
if(spreadPts > MaxSpreadPoints)
{
// 신규 진입을 보류한다(10021이 발생하기 쉬운 얇은 시간대를 자동 회피할 수 있다)
return;
}
FXEA365가 배포하는 EA는 이 스프레드 필터·지표 필터·가격 계열 retcode의 자동 재시도를 표준으로 구현하고 있습니다.
우선순위 체크리스트
| 우선 | 확인 | 대처 |
|---|---|---|
| 🚨 먼저 | 지표 발표·급변 순간에 집중되어 있는가 | 뉴스 필터 활성화·해당 시간대는 사양으로 받아들인다 |
| 🚨 먼저 | deviation이 너무 작지 않은가(단위는 points) | 10~30 points로·pips/points 착오 확인 |
| ⚠️ 다음 | 롤오버·주초·한산 종목에 편중되는가 | 시간 필터 + 스프레드 필터 |
| ⚠️ 다음 | 체결 방식이 instant인가 | market 체결 계좌도 검토(밀림과의 트레이드오프) |
| ⚠️ 다음 | ping이 큰가(수백ms) | 서버에 가까운 VPS로 이전 |
| 🛠 개발 | 재시도가 가격 계열 retcode로 한정되어 있는가 | SymbolInfoTick으로 재취득 + 2~3회로 중단 |
정리
- 10021(TRADE_RETCODE_PRICE_OFF)은 "처리할 호가가 없음", 10004(TRADE_RETCODE_REQUOTE)는 "새 가격의 재제시". MT4 시절의 136/138과 동일한 가격 거부 계열이며, 자금이나 로트의 문제가 아니다.
- 원인은 급변 시세·deviation 과소·instant 체결·얇은 호가 시간대/종목·회선 지연의 5가지로 귀결된다.
- 리크오트는 instant 체결 특유의 현상이며, market 체결에서는 대신 슬리피지로 나타난다. "발생하지 않는다 = 좋다"가 아니라 트레이드오프다.
- EA 개발자는 최신 틱으로 다시 가져오는 2~3회 재시도(10004/10021 한정) + deviation의 적정화 + 시간/스프레드 필터로 근본 대책을 마련한다. 레이턴시에서 비롯된 부분만은 물리적 대책(브로커에 가까운 VPS)으로밖에 줄일 수 없다.
에러 코드 전반은 MQL5 / MT5 에러 코드 대처법 종합 가이드를, 이러한 대책을 표준으로 구현한 무료 EA는 EA 목록을 참조하세요.
FAQ
Q: 10004와 10021은 어느 쪽이 더 심각한가요?
둘 다 일시적인 가격 계열 에러이며, 심각도에 큰 차이는 없습니다. 10004는 "새 가격이 재제시되었다", 10021은 "처리할 호가가 없었다"는 응답의 차이입니다. 단발성이라면 방치해도 문제없으며, 특정 시간대나 종목에서 빈발하는 경우에만 원인(지표·롤오버·deviation·회선)을 해결하세요.
Q: 리크오트가 한 번도 발생하지 않는 브로커는 우수한 건가요?
체결 방식의 차이일 가능성이 높습니다. market 체결 계좌에서는 리크오트가 원리적으로 발생하지 않으며, 가격이 움직인 경우 **벌어진 가격으로 그대로 체결(슬리피지)**됩니다. 거부되느냐 밀리느냐의 트레이드오프이므로, 체결 이력에서 실제 슬리피지를 확인하고 판단하세요.
Q: MT4 EA에서 error 136 / 138이 발생합니다. 같은 대처로 괜찮나요?
네. 136(ERR_OFF_QUOTES)은 10021, 138(ERR_REQUOTE)은 10004에 해당하며, 원인도 대책도 공통입니다. MQL4에서는 재전송 전에 RefreshRates()를 호출해 Bid / Ask를 최신화하는 것이 정석이었으며, MQL5의 SymbolInfoTick()으로 다시 가져오는 것과 같은 발상입니다.
Q: 백테스트에서는 발생하지 않는데 실전에서는 발생합니다.
정상입니다. Strategy Tester에는 "주문이 서버에 도착할 때까지의 지연"과 "그 사이의 가격 변동"이 존재하지 않거나(혹은 단순화되어 있어) 10004/10021은 포워드에서만 나타납니다. 백테스트가 좋아도, 실전에서는 deviation 설정·재시도·실행 환경(VPS) 정비가 별도로 필요합니다.
관련 기사
📧 가격 인상 사전 알림 + 무료 5일 이메일 강좌
모든 EA는 현재 출시 가격이며 판매 수량에 따라 단계적으로 인상됩니다. 인상 전 사전 알림과 함께 자동매매의 본질, 백테스트 해석법, 브로커 선택 요령을 매일 한 통씩 보내드립니다.
※ 개인정보는 엄격히 보호. 언제든 구독 해지 가능.