![Expert Advisor 개발 기초부터(18부): 새로운 주문 시스템(I)](https://c.mql5.com/2/49/Developing_a_trading_Expert_Advisor_from_scratch_009_600x314.jpg)
Expert Advisor 개발 기초부터(18부): 새로운 주문 시스템(I)
소개
이 시리즈의 첫 번째 글인 ' Expert Advisor 개발 기초부터'에서 이 EA를 문서화하기 시작한 이후로 우리는 차트 주문 시스템 모델을 그대로 유지하면서 다양한 변경 및 개선을 추가했습니다. 이제 EA는 매우 간단하고 기능적입니다. 그러나 대부분의 상황에서 실제 거래에는 적합하지 않습니다.
우리는 기존 시스템에 몇 가지를 추가하여 진입한 주문이나 펜딩 주문에 대한 정보를 얻을 수 있습니다. 하지만 이렇게 하면 코드가 프랑켄슈타인처럼 변해 결국 개선 과정에서 악몽을 겪게 될 것입니다. 자신만의 방법론이 있더라도 코드가 너무 커지고 복잡해지고 시간이 지나면 그 방법론은 사라지게 될 것입니다.
그렇게 되면 우리가 사용하는 고자 하는 주문의 모델을 수행하는 완전히 다른 시스템을 만들어야 합니다. 동시에 트레이더가 이해하기 쉬우면서도 안전하게 작업하는 데 필요한 모든 정보를 제공해야 합니다.
중요 참고사항:
이 글에서 설명하는 시스템은 네팅 계좌 ACCOUNT_MARGIN_MODE_RETAIL_NETTING용으로 설계되었으며 심볼당 하나의 오픈 포지션만 가질 수 있습니다. 만약 여러분이 헤징 계좌 ACCOUNT_MARGIN_MODE_RETAIL_HEDGING을 사용하는 경우 이 기사의 내용은 관련이 없습니다. 헤징 계좌의 경우 여러분이 원하는 만큼의 포지션을 가질 수 있으며 포지션은 서로 간섭하지 않기 때문입니다. 따라서 여러분은 최종의 코드에서 모든 수정 사항을 간단히 재설정하고 삭제할 수 있습니다.
ACCOUNT_MARGIN_MODE_RETAIL_HEDGING 모드는 EA가 자동으로 실행되고 트레이더의 개입 없이 포지션을 열고 닫고 하며 그 와중에 트레이더는 수동으로 거래를 계속할 때 가장 자주 사용됩니다. 또한 여러분은 EA가 거래하는 자산을 동시에 거래할수 있습니다. 헤징 계좌를 사용하면 EA의 활동이 여러분의 트레이드에 영향을 미치지 않습니다. 그러므로 여러분이 손매매를 할 경우 EA이 포지션과 손매매의 포지션은 별개입니다.
앞으로 추가 또는 수정되는 모든 코드 부분을 강조 표시하겠습니다. 모든 변경 및 추가 사항은 천천히 점진적으로 구현되므로 코드에서 제거해야 하는 부분이 있을 경우 관련된 코드 부분을 쉽게 찾을 수 있을 것입니다.
수정 사항을 제거할 수 있지만 시스템을 확인하는 부분도 있습니다. 여러분이 만약 변경 사항을 저장하더라도 Expert Advisor가 모델을 확인하고 그에 따라 조정하기 때문에 모든 유형의 거래 계좌, 네팅 및 헤징에서 이 EA를 사용할 수 있습니다.
1.0. 계획
실제로 가장 먼저 해야 할 일은 주문이 시스템에 추가되고 적절한 가격에 도달하면 체결되는 과정을 이해하는 것입니다. 많은 트레이더들이 이 부분에 대해 모르거나 전혀 생각조차 하지 않습니다. 아이디어를 이해하고 적절하면서도 신뢰할 수 있는 시스템을 구현하기 위한 테스트를 수행하지 않기도 합니다.
이와 관련하여 실제로 어떤 일이 일어나는지 이해하기 위해 간단한 예를 분석해 보겠습니다. 특정 자산에 대해 초기 거래량이 1 랏인 매수 포지션이 있다고 가정해 보겠습니다. 그런 다음 약간 더 높은 가격으로 2랏의 매수 주문을 새로 넣습니다. 지금까지는 별다른 문제가 없습니다. 하지만 어쨋든 이 두 개의 랏을 매수 하자마자 어떤 일이 일어날 것입니다. 그리고 이것이 바로 문제입니다.
이 두 랏을 구매하면 3개의 랏을 보유하게 되지만 시스템이 처음의 가격을 업데이트하여 평균으로 설정합니다. 모두가 이를 이해합니다. 하지만 새 포지션의 손절매 및 이익실현 레벨은 어떻게 될까요?
많은 트레이더가 정답을 모르지만 생각해 보는 것이 좋습니다. 모든 거래에서 OCO 주문 시스템(One Cancel The Other)을 사용하는 경우 총 볼륨의 일부에 해당하는 포지션에 진입하거나 청산할 때마다 흥미로운 점을 발견할 수 있습니다.
저는 교차 주문에 대한 기사에서 표준 MetaTrader 5 시스템을 사용하지 않고 차트에서 직접 이익실현과 손절매를 설정하는 방법을 소개했습니다. 실제로 이 방법은 MetaTrader 5 시스템과 거의 동일합니다. 왜냐하면 그 기능이 플랫폼의 기능과 매우 유사하기 때문입니다. 그러나 우리의 것은 적절한 비율을 갖습니다. 몇 가지 테스트를 해본 결과 체결된 OCO 오더와 미진입한 펜딩 오더가 있을 때 거래 시스템에서 OCO 주문도 포착되는 것을 발견했습니다. 그 이유는 가격이 주문에서 지정된 값에 도달했기 때문이었습니다. 새로운 평균 가격 외에도 익절과 손절 값도 변경되며 이는 마지막으로 캡처한 OCO 주문에서 지정된 값으로 변경됩니다. 따라서 EA를 어떻게 설정할지에 따라 다르기는 하지만 거래 시스템이 새로운 익절과 손절 값을 보고하면 EA는 즉시 청산합니다.
이는 EA에서 구현된 다음과 같은 검사 기능으로 인해 발생합니다:
inline double CheckPosition(void) { double Res = 0, last, sl; ulong ticket; last = SymbolInfoDouble(Terminal.GetSymbol(), SYMBOL_LAST); for (int i0 = PositionsTotal() - 1; i0 >= 0; i0--) if (PositionGetSymbol(i0) == Terminal.GetSymbol()) { ticket = PositionGetInteger(POSITION_TICKET); Res += PositionGetDouble(POSITION_PROFIT); sl = PositionGetDouble(POSITION_SL); if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { if (last < sl) ClosePosition(ticket); }else { if ((last > sl) && (sl > 0)) ClosePosition(ticket); } } return Res; };
강조 표시된 선이 이와 같은 검사를 수행하고 가격이 익절과 손절 값에 의해 형성된 채널에서 벗어날 경우 주문을 청산합니다.
우리는 EA의 오류나 거래 시스템 문제 또는 결함에 대해 이야기하는 것이 아닙니다. 진짜 문제는 대부분의 경우 우리는 무슨 일이 일어나고 있는지 주의를 기울이지 않고 문제를 알게 되기 전까지 이런 종류의 문제를 무시한다는 것입니다.
가격을 평균화 하지 않는 방식으로 거래하면 이런 일이 발생하지 않습니다. 그러나 평균 가격이 적절한지 여부를 알기 위해 필요한 작업은 매우 광범위합니다. 따라서 이러한 경우 위의 내용을 모른 채 시스템을 실행하는 경우 이전에 오픈 포지션의 OCO 주문을 적절히 변경했더라도 원하는 것보다 일찍 포지션이 청산 될 수 있습니다. 펜딩 중인 OCO 오더가 포착되는 즉시 기준값( 기준값에 대해 이야기할 때마다 저는 이전의 익절과 손절을 날하는 것입니다.)은 최근에 캡처한 지정가 펜딩 OCO 오더에서 지정된 값으로 대체됩니다.
이 문제를 해결하고 피할 수 있는 방법이 있습니다: 적어도 이미 오픈 포지션이 있을 때는 OCO 주문을 사용하지 마세요. 시스템에 전달되는 다른 모든 주문은 익절과 손절을 설정하지 않은 단순 유형이어야 합니다.
기본적으로 그게 다입니다. 하지만 EA가 차트에 있으면 EA는 우리들을 도우며 삶을 더 편하게 만들어 줍니다. EA를 사용하지 않는다면 프로그래밍하는 것은 의미가 없습니다.
2.0. 새로운 시스템 구현
시스템을 구현하고 예상한 대로 작동하도록 하기 위해 코드를 약간 변경해야 합니다. - EA가 이를 도와주고 오류를 방지하는 데 도움을 주어야 합니다.
이는 그리 어렵지 않지만 이러한 변경을 통해 원치 않는 순간에 OCO 주문이 들어와 운영에 큰 혼란을 야기하는 위험을 감수하는 일이 없도록 할 것입니다.
다음과 같은 변경 사항부터 살펴보겠습니다:
2.0.1. C_Router 클래스 수정
C_Router 클래스는 주문을 파싱하고 전송하는 역할을 담당합니다. 여기에 프라이빗 변수를 추가해 보겠습니다. EA에 의해 거래되는 자산에 대해 오픈 포지션이 발견되면 이 변수에 관련 정보가 저장됩니다. EA가 오픈 포지션이 있는지 알고 싶어 할 때마다 이에 대해 알려줍니다.
이 구현은 아래 코드에 나와 있습니다. 추후 이 코드는 이 글에서 변경될 예정입니다. 코드를 수정하는 과정이 실제로 어떠한지 여러분이 이해할 수 있도록 모든 변경 사항을 단계별로 보여 드리고자 합니다.
//+------------------------------------------------------------------+ inline bool ExistPosition(void) const { return m_bContainsPosition; } //+------------------------------------------------------------------+ void UpdatePosition(void) { static int memPositions = 0, memOrder = 0; ulong ul; int p, o; p = PositionsTotal(); o = OrdersTotal(); if ((memPositions != p) || (memOrder != o)) { ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false); RemoveAllsLines(); ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true); memOrder = o; memPositions = p; m_bContainsPosition = false; }; for(int i0 = p; i0 >= 0; i0--) if(PositionGetSymbol(i0) == Terminal.GetSymbol()) { ul = PositionGetInteger(POSITION_TICKET); m_bContainsPosition = true; SetLineOrder(ul, PositionGetDouble(POSITION_PRICE_OPEN), HL_PRICE, false); SetLineOrder(ul, PositionGetDouble(POSITION_TP), HL_TAKE, true); SetLineOrder(ul, PositionGetDouble(POSITION_SL), HL_STOP, true); } for (int i0 = o; i0 >= 0; i0--) if ((ul = OrderGetTicket(i0)) > 0) if (OrderGetString(ORDER_SYMBOL) == Terminal.GetSymbol()) { SetLineOrder(ul, OrderGetDouble(ORDER_PRICE_OPEN), HL_PRICE, true); SetLineOrder(ul, OrderGetDouble(ORDER_TP), HL_TAKE, true); SetLineOrder(ul, OrderGetDouble(ORDER_SL), HL_STOP, true); } }; //+------------------------------------------------------------------+
강조 표시된 선은 나중에 EA 코드의 어딘가에 추가되어 모든 검사를 실행하는 데 필요한 추가적인 내용을 나타냅니다.
한편으론 C_Router 클래스에서 모든 검사와 조정을 구현할 수도 있지만 이것만으로는 충분하지 않습니다. 이 부분은 나중에 설명하겠습니다. 지금은 수정 작업을 계속 진행하겠습니다. 위와 같은 검사를 생성한 후 새로 추가된 변수를 올바르게 초기화하기 위해 생성자를 추가해 보겠습니다.
C_Router() : m_bContainsPosition(false) {}
이제 펜딩 주문을 실행하는 함수를 다음과 같이 편집합니다:
ulong CreateOrderPendent(const bool IsBuy, const double Volume, const double Price, const double Take, const double Stop, const bool DayTrade = true) { double last = SymbolInfoDouble(Terminal.GetSymbol(), SYMBOL_LAST); ZeroMemory(TradeRequest); ZeroMemory(TradeResult); TradeRequest.action = TRADE_ACTION_PENDING; TradeRequest.symbol = Terminal.GetSymbol(); TradeRequest.volume = Volume; TradeRequest.type = (IsBuy ? (last >= Price ? ORDER_TYPE_BUY_LIMIT : ORDER_TYPE_BUY_STOP) : (last < Price ? ORDER_TYPE_SELL_LIMIT : ORDER_TYPE_SELL_STOP)); TradeRequest.price = NormalizeDouble(Price, Terminal.GetDigits()); TradeRequest.sl = NormalizeDouble((m_bContainsPosition ? 0 : Stop), Terminal.GetDigits()); TradeRequest.tp = NormalizeDouble((m_bContainsPosition ? 0 : Take), Terminal.GetDigits()); TradeRequest.type_time = (DayTrade ? ORDER_TIME_DAY : ORDER_TIME_GTC); TradeRequest.stoplimit = 0; TradeRequest.expiration = 0; TradeRequest.type_filling = ORDER_FILLING_RETURN; TradeRequest.deviation = 1000; TradeRequest.comment = "Order Generated by Experts Advisor."; if (!Send()) return 0; return TradeResult.order; };
강조 표시된 부분은 우리가 구현할 변경 사항들입니다.
이제 업데이트된 코드로 돌아가서 새로 수정해 보겠습니다. 이 함수는 OnTrade 함수에 의해 호출되며 주문이 변경될 때마다 호출된다는 점을 기억하세요. 이 부분은 아래 코드에서 볼 수 있습니다:
void UpdatePosition(void) { static int memPositions = 0, memOrder = 0; ulong ul; int p, o; p = PositionsTotal(); o = OrdersTotal(); if ((memPositions != p) || (memOrder != o)) { ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false); RemoveAllsLines(); ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true); memOrder = o; memPositions = p; m_bContainsPosition = false; }; for(int i0 = p; i0 >= 0; i0--) if(PositionGetSymbol(i0) == Terminal.GetSymbol()) { ul = PositionGetInteger(POSITION_TICKET); m_bContainsPosition = true; SetLineOrder(ul, PositionGetDouble(POSITION_PRICE_OPEN), HL_PRICE, false); SetLineOrder(ul, PositionGetDouble(POSITION_TP), HL_TAKE, true); SetLineOrder(ul, PositionGetDouble(POSITION_SL), HL_STOP, true); } for (int i0 = o; i0 >= 0; i0--) if ((ul = OrderGetTicket(i0)) > 0) if (OrderGetString(ORDER_SYMBOL) == Terminal.GetSymbol()) { if (m_bContainsPosition) { ModifyOrderPendent(ul, OrderGetDouble(ORDER_PRICE_OPEN), 0, 0); (OrderSelect(ul) ? 0 : 0); } SetLineOrder(ul, OrderGetDouble(ORDER_PRICE_OPEN), HL_PRICE, true); SetLineOrder(ul, OrderGetDouble(ORDER_TP), HL_TAKE, true); SetLineOrder(ul, OrderGetDouble(ORDER_SL), HL_STOP, true); } };
이제 사용자가 이미 오픈 포지션을 가지고 있을 때, 즉 사용자가 도구 상자를 열고 익절 또는 손절을 편집하려고 할 때 펜딩 오더를 펜딩 OCO 오더로 전환하지 않도록 해야 합니다. 사용자가 이 작업을 시도하면 거래 서버가 온트레이드 기능을 통해 알려주므로 EA는 변경 사항을 즉시 알고 사용자가 변경한 사항을 취소하여 시스템의 안정성을 보장하도록 합니다.
하지만 한 가지 더 수정해야 할 사항이 있습니다; 바로 마켓 오더와 관련된 것입니다. 확인 하는 과정과 관련된 수정이 필요하지 않으므로 매우 간단한 변경입니다. 전체 함수 코드는 다음과 같습니다:
ulong ExecuteOrderInMarket(const bool IsBuy, const double Volume, const double Price, const double Take, const double Stop, const bool DayTrade = true) { ZeroMemory(TradeRequest); ZeroMemory(TradeResult); TradeRequest.action = TRADE_ACTION_DEAL; TradeRequest.symbol = Terminal.GetSymbol(); TradeRequest.volume = Volume; TradeRequest.type = (IsBuy ? ORDER_TYPE_BUY : ORDER_TYPE_SELL); TradeRequest.price = NormalizeDouble(Price, Terminal.GetDigits()); TradeRequest.sl = NormalizeDouble((m_bContainsPosition ? 0 : Stop), Terminal.GetDigits()); TradeRequest.tp = NormalizeDouble((m_bContainsPosition ? 0 : Take), Terminal.GetDigits()); TradeRequest.type_time = (DayTrade ? ORDER_TIME_DAY : ORDER_TIME_GTC); TradeRequest.stoplimit = 0; TradeRequest.expiration = 0; TradeRequest.type_filling = ORDER_FILLING_RETURN; TradeRequest.deviation = 1000; TradeRequest.comment = "[ Order Market ] Generated by Experts Advisor."; if (!Send()) return 0; return TradeResult.order; };
이상하게 보일 수 있지만 이러한 변경 사항은 이미 충분한 수준의 보안 (최소한 허용 가능한 수준)을 제공하므로 EA가 거래하는 동일한 자산에 대해 이미 오픈 포지션이있을 때 OCO, 펜딩 또는 마켓 오더를 놓치지 않을 수 있습니다. 따라서 EA가 실제로 주문 제출 시스템을 관리합니다.
사실이라고 하기에는 너무 아름답고 멋진 일이지 않나요? 이렇게 하면 이미 충분한 안전 마진이 보장될 것이라고 생각할지도 모르겠습니다 그러나 실제로는 그렇지 않습니다. 이러한 수정은 오픈 포지션이 있을 때 OCO 오더가 펜딩 상태로 남아있지 않도록 하거나 시장에 진입하지 않도록 보장합니다. 그러나 이러한 수정에는 치명적인 결함이 있으며 제대로 수정하지 않으면 이 결함으로 인해 큰 골칫거리와 엄청난 손실이 발생하여 계좌가 파산하거나 증거금 부족으로 브로커가 포지션을 청산 할 수 있습니다.
펜딩 오더가 오픈 포지션 한도 내에 있는지 여부는 확인되지 않으며 이는 매우 위험한 상태입니다. 현재 시스템은 오픈 포지션 한도를 벗어나서 펜딩 OCO 어더를 추가하면 EA가 해당 오더를 OCO 유형으로 허용하지 않기 때문입니다. 즉 주문에는 제한이 없으며 이 주문은 익절 또는 손절이 없는 주문이므로 포지션을 청산하고 이 펜딩 오더를 입력할 때 가능한 한 빨리 이 레벨을 조정해야 합니다. 이 작업을 잊어버리면 제한 없이 오픈 포지션을 보유할 위험이 있습니다.
레벨을 설정하려면 메시지 창을 열고 주문을 연 다음 레벨 값을 편집해야 합니다. 이 문제는 곧 수정될 예정입니다. 이제 현재의 문제를 해결해 보겠습니다.
따라서 사용자가 제한 없이 주문을 하려는 경우 EA는 이를 정상적인 주문으로 취급하지만 사용자가 리밋 오더를 하게 되면 EA는 주문이 오픈 포지션 밖에 있으면 한도를 설정하고 오픈 포지션 안에 있으면 펜딩 오더의 한도를 없에는 등 주문을 적절히 조정해야 합니다. 그러므로 펜딩오더의 경우 EA의 작동 방식을 변경해야 합니다.
2.0.2. 한도 내에서 작업
가장 먼저 할 일은 인증 한도를 만드는 것입니다. 매우 간단합니다. 그러나 더 많은 경우로 확장할 수 있는 두 가지 가능한 경우가 있기 때문에 세부 사항에 많은 주의가 필요합니다. 무엇을 해야 하는지 이해하려면 이 두 가지 사례로 충분합니다.
첫 번째 사례는 위에 나와 있습니다. 펜딩 오더가 한도의 범위를 벗어나 있는 경우(이 경우 한도란 그라데이션의 영역, 즉 매수 또는 매도 포지션이 있고 우리는 상방의 한도가 있는 경우를 말합니다. 가격이 이 한도에 도달하거나 초과하면 포지션이 청산됩니다. 이 경우 사용자가 펜딩 오더를 OCO 오더으로 구성할 수 있으며 EA는 펜딩 주문이든 OCO 주문이든 사용자가 구성한 주문 방식을 수락해야 하며 이 경우 EA가 간섭해서는 안 됩니다.
두 번째 사례는 아래와 같습니다. 여기서 펜딩 오더는 오픈 포지션에 의해 제한된 영역 내에 있습니다. 이 경우 EA는 사용자가 구성할 수 있는 한도를 제거해야 합니다.
매수 또는 매도 어떤 경우이던 그리고 한도가 얼마나 멀리 있던 상관 없이 주문이 이 영역에서 진입하면 EA는 펜딩 오더에서 한도 값을 제거해야 한다는 점에 유의하세요. 그러나 이 영역을 벗어나면 EA는 사용자가 구성한 방식대로 주문을 놔둬야 합니다.
이러한 사항을 정의한 후에는 아래와 같이 몇 가지 변수를 만들어야 합니다:
class C_Router : public C_HLineTrade { protected: MqlTradeRequest TradeRequest; MqlTradeResult TradeResult; private : bool m_bContainsPosition; struct st00 { double TakeProfit, StopLoss; bool IsBuy; }m_Limits; // ... Rest of the code
이제 Ontrade 오더 트리거 이벤트가 발생하는 단계에서 한도를 확인할 수 있는 방법이 생겼습니다. 그래서 다시 한 번 C_Router 클래스의 업데이트 함수를 수정합니다.
// Rest of the code.... //+------------------------------------------------------------------+ #define macro_MAX(A, B) (A > B ? A : B) #define macro_MIN(A, B) (A < B ? A : B) void UpdatePosition(void) { static int memPositions = 0, memOrder = 0; ulong ul; int p, o; double price; bool bTest; p = PositionsTotal(); o = OrdersTotal(); if ((memPositions != p) || (memOrder != o)) { ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false); RemoveAllsLines(); ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true); memOrder = o; memPositions = p; m_bContainsPosition = false; m_Limits.StopLoss = -1; m_Limits.TakeProfit = -1; m_Limits.IsBuy = false; }; for(int i0 = p; i0 >= 0; i0--) if(PositionGetSymbol(i0) == Terminal.GetSymbol()) { ul = PositionGetInteger(POSITION_TICKET); m_bContainsPosition = true; SetLineOrder(ul, PositionGetDouble(POSITION_PRICE_OPEN), HL_PRICE, false); SetLineOrder(ul, m_Limits.TakeProfit = PositionGetDouble(POSITION_TP), HL_TAKE, true); SetLineOrder(ul, m_Limits.StopLoss = PositionGetDouble(POSITION_SL), HL_STOP, true); m_Limits.IsBuy = PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY; } for (int i0 = o; i0 >= 0; i0--) if ((ul = OrderGetTicket(i0)) > 0) if (OrderGetString(ORDER_SYMBOL) == Terminal.GetSymbol()) { price = OrderGetDouble(ORDER_PRICE_OPEN); if ((m_Limits.StopLoss == -1) && (m_Limits.TakeProfit == -1)) bTest = false; else { bTest = ((!m_Limits.IsBuy) && (m_Limits.StopLoss > price)); bTest = (bTest ? bTest : (m_Limits.IsBuy) && (m_Limits.StopLoss < price)); bTest = (bTest ? bTest : ((macro_MAX(m_Limits.TakeProfit, m_Limits.StopLoss) > price) && (macro_MIN(m_Limits.TakeProfit, m_Limits.StopLoss) < price))); } if ((m_bContainsPosition) && (bTest)) { ModifyOrderPendent(ul, price, 0, 0); (OrderSelect(ul) ? 0 : 0); } SetLineOrder(ul, price, HL_PRICE, true); SetLineOrder(ul, OrderGetDouble(ORDER_TP), HL_TAKE, true); SetLineOrder(ul, OrderGetDouble(ORDER_SL), HL_STOP, true); } }; #undef macro_MAX #undef macro_MIN //+------------------------------------------------------------------+ // ... The rest of the code...
이제 클래스는 펜딩 중인 포지션을 핸들링 할 것이고 OCO 펜딩 오더를 가질 수 없는 영역 내에 있는지 또는 그 영역 밖에 있는지를 구분합니다. 이 함수는 주문 시스템에서 상태가 변경될 때마다 호출된다는 점에 유의하세요. 가장 먼저 할 일은 변수를 올바르게 초기화하는 것입니다.
m_Limits.StopLoss = -1; m_Limits.TakeProfit = -1; m_Limits.IsBuy = false;
이 작업이 완료되면 오픈 포지션이 있는지 여부를 확인합니다. 이 작업은 언제든지 수행할 수 있습니다. 우리에게 오픈 포지션이 생기게 되면 EA는 OCO 펜딩 오더를 가질 수 없는 영역의 경계를 정하게 됩니다 - 이 시점에서 이렇게 영역이 설정됩니다.
SetLineOrder(ul, m_Limits.TakeProfit = PositionGetDouble(POSITION_TP), HL_TAKE, true); SetLineOrder(ul, m_Limits.StopLoss = PositionGetDouble(POSITION_SL), HL_STOP, true); m_Limits.IsBuy = PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY;
이제 각각의 펜딩 오더를 확인하여 이들 주문들이 한도 내에 있는지 여부를 확인할 수 있습니다. 중요한 참고 사항: 여기서 주문이 매수인지 매도인지 알아야 합니다. 익절은 없지만 손절이 있을 수 있기 때문입니다. 따라서 이 경우 포지션의 유형을 알아야 합니다. 이를 이해하려면 아래 그림을 참조하세요:
매도 포지션인 경우 손절매는 상방의 한도를 표시합니다...
매수 포지션의 경우 손절가는 하방의 한도입니다.
즉 펜딩 오더가 최대로 설정된 경우에는 펜딩 오더들은 OCO 유형일 수 있습니다. 그 외의 경우에는 최소값 이하로 배치해야 합니다. 하지만 아래와 같이 펜딩 오더가 OCO 유형인 경우도 있을 수 있습니다:
한도를 벗어난 펜딩 오더가 대표적인 경우입니다.
이를 확인하기 위해 다음과 같은 조각을 사용합니다.
price = OrderGetDouble(ORDER_PRICE_OPEN); if ((m_Limits.StopLoss == -1) && (m_Limits.TakeProfit == -1)) bTest = false; else { bTest = ((!m_Limits.IsBuy) && (m_Limits.StopLoss > price)); bTest = (bTest ? bTest : (m_Limits.IsBuy) && (m_Limits.StopLoss < price)); bTest = (bTest ? bTest : ((macro_MAX(m_Limits.TakeProfit, m_Limits.StopLoss) > price) && (macro_MIN(m_Limits.TakeProfit, m_Limits.StopLoss) < price))); }
진입한 포지션이 있는지 확인합니다. 그렇지 않은 경우 EA는 사용자가 지정한 주문 세팅을 지켜야 합니다. 포지션이 발견되면 다음과 같은 순서로 점검이 수행됩니다:
- 숏 포지션인 경우 펜딩 오더가 체결되는 가격이 오픈 포지션의 손절가보다 커야 합니다;
- 매수 포지션이라면 펜딩 오더가 설정되는 가격이 오픈 포지션의 손절가보다 낮아야 합니다;
- 시스템이 여전히 주문을 OCO 유형으로 인식하는 경우 주문이 제 위치에 있지 않은지 확인하기 위해 마지막 테스트를 한 번 더 수행합니다.
이 작업이 완료되면 펜딩 주문을 사용자가 구성한 방식대로 남겨 둘지 말지를 확인하고 계속 진행합니다... 하지만 다음 단계로 넘어가기 전에 마지막으로 한 가지 더 추가할 것이 있습니다. 사실 글의 서두에서 언급했던 마지막 점검 사항이며 이는 아래와 같습니다:
void UpdatePosition(void) { // ... Internal code... for(int i0 = p; i0 >= 0; i0--) if(PositionGetSymbol(i0) == Terminal.GetSymbol()) { ul = PositionGetInteger(POSITION_TICKET); m_bContainsPosition = true; SetLineOrder(ul, PositionGetDouble(POSITION_PRICE_OPEN), HL_PRICE, false); SetLineOrder(ul, m_Limits.TakeProfit = PositionGetDouble(POSITION_TP), HL_TAKE, true); SetLineOrder(ul, m_Limits.StopLoss = PositionGetDouble(POSITION_SL), HL_STOP, true); m_Limits.IsBuy = PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY; } if (AccountInfoInteger(ACCOUNT_TRADE_MODE) == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) m_bContainsPosition = false; for (int i0 = o; i0 >= 0; i0--) if ((ul = OrderGetTicket(i0)) > 0) if (OrderGetString(ORDER_SYMBOL) == Terminal.GetSymbol()) { price = OrderGetDouble(ORDER_PRICE_OPEN); if (m_bContainsPosition) { if ((m_Limits.StopLoss == -1) && (m_Limits.TakeProfit == -1)) bTest = false; else { bTest = ((!m_Limits.IsBuy) && (m_Limits.StopLoss > price)); bTest = (bTest ? bTest : (m_Limits.IsBuy) && (m_Limits.StopLoss < price)); bTest = (bTest ? bTest : ((macro_MAX(m_Limits.TakeProfit, m_Limits.StopLoss) > price) && (macro_MIN(m_Limits.TakeProfit, m_Limits.StopLoss) < price))); } if (bTest) { ModifyOrderPendent(ul, price, 0, 0); (OrderSelect(ul) ? 0 : 0); } } SetLineOrder(ul, price, HL_PRICE, true); SetLineOrder(ul, OrderGetDouble(ORDER_TP), HL_TAKE, true); SetLineOrder(ul, OrderGetDouble(ORDER_SL), HL_STOP, true); } };
강조 표시된 부분에서는 계좌 유형이 헤징인지 확인합니다. 만약 그렇다면 변수에 따르면 한도가 적용되어야 한다고 했더라도 한도가 적용될 필요가 없습니다. 따라서 EA는 발생할 수 있는 모든 제한 사항을 무시하고 사용자가 구성한 방식대로 주문을 처리합니다. 이것은 매우 간단한 점검이지만 전체 시스템이 제대로 작동하도록 하기 위해 이 단계에서 수행해야 합니다.
2.1.0. 포지셔닝 시스템 조정
C_Router 객체 클래스를 조정하여 대부분의 문제가 해결되었지만 여전히 부족합니다. 해결해야 할 또 다른 문제는 마우스를 통한 위치 지정 시스템을 수정하는 것인데, 이 역시 마찬가지로 중요한 단계입니다. 여기에는 몇 가지 함의가 있는데 C_OrderView 클래스에 존재하는 시스템이 어떻게 작동해야 하는지를 정의해야 하기 때문입니다.
실제로 어떻게 운영할 것인지에 대한 가장 큰 질문은 오픈 포지션의 한도를 벗어날 때 C_OrderView 클래스가 펜딩 오더에 대한 한도를 생성할지 여부입니다.
이 결정을 내릴 때 고려해야 할 사항이 있습니다. 잭 더 리퍼의 말대로 부분별로 살펴봅시다. 기본적으로 C_OrderView 클래스에서 실제로 변경해야 할 유일한 사항은 아래 코드에 나와 있습니다:
inline void MoveTo(uint Key) { static double local = 0; datetime dt; bool bEClick, bKeyBuy, bKeySell, bCheck; double take = 0, stop = 0, price; bEClick = (Key & 0x01) == 0x01; //Left click bKeyBuy = (Key & 0x04) == 0x04; //SHIFT pressed bKeySell = (Key & 0x08) == 0x08; //CTRL pressed Mouse.GetPositionDP(dt, price); if (bKeyBuy != bKeySell) { Mouse.Hide(); bCheck = CheckLimits(price); } else Mouse.Show(); ObjectMove(Terminal.Get_ID(), m_Infos.szHLinePrice, 0, 0, price = (bKeyBuy != bKeySell ? price : 0)); ObjectMove(Terminal.Get_ID(), m_Infos.szHLineTake, 0, 0, take = (bCheck ? 0 : price + (m_Infos.TakeProfit * (bKeyBuy ? 1 : -1)))); ObjectMove(Terminal.Get_ID(), m_Infos.szHLineStop, 0, 0, stop = (bCheck ? 0 : price + (m_Infos.StopLoss * (bKeyBuy ? -1 : 1)))); if((bEClick) && (bKeyBuy != bKeySell) && (local == 0)) CreateOrderPendent(bKeyBuy, m_Infos.Volume, local = price, take, stop, m_Infos.IsDayTrade); local = (local != price ? 0 : local); ObjectSetInteger(Terminal.Get_ID(), m_Infos.szHLinePrice, OBJPROP_COLOR, (bKeyBuy != bKeySell ? m_Infos.cPrice : clrNONE)); ObjectSetInteger(Terminal.Get_ID(), m_Infos.szHLineTake, OBJPROP_COLOR, (take > 0 ? m_Infos.cTake : clrNONE)); ObjectSetInteger(Terminal.Get_ID(), m_Infos.szHLineStop, OBJPROP_COLOR, (stop > 0 ? m_Infos.cStop : clrNONE)); };
이게 전부인가요? 네, 그렇게만 하면 됩니다. 나머지 모든 로직은 C_Router 클래스 안에 있습니다. 주문 목록(펜딩 또는 포지션)에 변경이 있을 때 OnTrade 루틴이 호출되기 때문에 수정되지 않은 것은 MetaTrader 5의 메시징 시스템에서 실행됩니다. 이런 일이 발생하면 C_Router 클래스 내에서 업데이트 루틴을 트리거하여 필요한 조정을 수행합니다. 하지만 이 루틴에 나타나는 코드가 있어 여러분은 그 코드가 어디에 있는지 찾느라 정신이 없을 수 있습니다. 실제로 아래에서 볼 수 있듯이 C_Router 클래스 내부에 있습니다:
#define macro_MAX(A, B) (A > B ? A : B) #define macro_MIN(A, B) (A < B ? A : B) inline bool CheckLimits(const double price) { bool bTest = false; if ((!m_bContainsPosition) || ((m_Limits.StopLoss == -1) && (m_Limits.TakeProfit == -1))) return bTest; bTest = ((macro_MAX(m_Limits.TakeProfit, m_Limits.StopLoss) > price) && (macro_MIN(m_Limits.TakeProfit, m_Limits.StopLoss) < price)); if (m_Limits.TakeProfit == 0) { bTest = (bTest ? bTest : (!m_Limits.IsBuy) && (m_Limits.StopLoss > price)); bTest = (bTest ? bTest : (m_Limits.IsBuy) && (m_Limits.StopLoss < price)); } return bTest; }; #undef macro_MAX #undef macro_MIN
이 코드는 C_Router 클래스 업데이트 함수 안에 있습니다. 거기에서 제거되고 호출로 대체되었습니다...
2.2.0. 제한할 것인지 아니면 제한하지 않을 것인지가 문제입니다.
우리의 작업은 거의 완료되었지만 현재 가장 어려운 마지막 질문이 남아 있습니다. 지금까지의 내용을 잘 이해하셨다면 시스템이 매우 잘 작동한다는 것을 눈치채셨겠지만, OCO로 설정된 펜딩 오더가 오픈 포지션의 한도에 닿을 때마다 해당 주문은 설정된 한도를 잃게 됩니다. 이런 일은 항상 일어납니다.
그러나 트레이더가 우연히 오픈 포지션 한도를 변경하거나 OCO였던 주문이 이제 일반적인 주문으로 된 경우 해당 한도를 초과하더라도 여전히 일반적인 주문이 됩니다. 따라서 잠재적인 문제가 있게 됩니다.
또 다른 중요한 세부 사항: EA는 어떻게 동작해 나아가야 할까요? 어떤 자산에 대해 단순 주문이 방금 발생했음을 트레이더에게 알려야 하나요? 아니면 주문에 한도를 설정하고 OCO로 전환해야 할까요?
이 질문은 매우 적절한 질문입니다. EA에서 어떤 일이 발생했는지를 알려주는 경고를 알려주는 것이 좋을 것 같습니다. 그러나 변동성이 큰 자산에 투자하는 경우 변동성이 높은 시기에 EA가 주문에 대한 제한을 자동으로 하게 하여 손실이 발생하지 않도록 함으로써 짧은 시간동안 큰 손실을 입는 것을 방지하도록 하는 것도 좋습니다.
그래서 이 문제를 해결하기 위해 시스템을 한 번 더 수정했습니다. 여러분은 위에서 설명했듯이 실제로 이 문제를 어떻게 처리할지 진지하게 고민해봐야 합니다. 아래는 제가 가능한 해결책을 구현한 방법입니다.
먼저 트레이더가 EA에게 어떤 절차를 수행할지를 알려줄 수 있는 새로운 변수를 추가했습니다. 이 코드는 다음과 같이 표시됩니다:
// ... Code ... input group "Chart Trader" input int user20 = 1; //Leverage factor input int user21 = 100; //Take Profit (financial) input int user22 = 75; //Stop Loss (financial) input color user23 = clrBlue; //Price line color input color user24 = clrForestGreen; //Take Profit line color input color user25 = clrFireBrick; //Stop Loss line color input bool user26 = true; //Day Trade? input bool user27 = true; //Always set loose order limits // ... Rest of the code... void OnTrade() { Chart.DispatchMessage(CHARTEVENT_CHART_CHANGE, 0, OrderView.UpdateRoof(), C_Chart_IDE::szMsgIDE[C_Chart_IDE::eROOF_DIARY]); OrderView.UpdatePosition(user27); } // ... Rest of the code...
이제 C_Router 클래스로 돌아가서 새로운 함수 3개를 추가해야 합니다. 이 함수들은 아래에서 나와 있습니다:
//+------------------------------------------------------------------+ void SetFinance(const int Contracts, const int Take, const int Stop) { m_Limits.Contract = Contracts; m_Limits.FinanceTake = Take; m_Limits.FinanceStop = Stop; } //+------------------------------------------------------------------+ inline double GetDisplacementTake(const bool IsBuy, const double Vol) const { return (Terminal.AdjustPrice(m_Limits.FinanceTake * (Vol / m_Limits.Contract) * Terminal.GetAdjustToTrade() / Vol) * (IsBuy ? 1 : -1)); } //+------------------------------------------------------------------+ inline double GetDisplacementStop(const bool IsBuy, const double Vol) const { return (Terminal.AdjustPrice(m_Limits.FinanceStop * (Vol / m_Limits.Contract) * Terminal.GetAdjustToTrade() / Vol) * (IsBuy ? -1 : 1)); } //+------------------------------------------------------------------+
이 코드는 다음 이미지에서 볼 수 있듯이 차트 트레이더에서 알려주는 값을 유지하지만 OCO 펜딩 오더에서 한도로 사용해야 하는 값도 비례적으로 수정합니다.
즉 우리는 펜딩 오더가 트리거될 때 EA가 OCO 주문을 구성할 수 있도록 사용할 값을 어디에서 가져올지를 이미 알고 있습니다. 그러나 우리는 C_Router 클래스 업데이트 코드를 새로 변경해야 합니다. 변경 사항은 아래와 같습니다:
void UpdatePosition(bool bAdjust) { static int memPositions = 0, memOrder = 0; ulong ul; int p, o; long info; double price, stop, take, vol; bool bIsBuy, bTest; p = PositionsTotal(); o = OrdersTotal(); if ((memPositions != p) || (memOrder != o)) { ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false); RemoveAllsLines(); ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true); memOrder = o; memPositions = p; m_bContainsPosition = false; m_Limits.StopLoss = -1; m_Limits.TakeProfit = -1; m_Limits.IsBuy = false; }; for(int i0 = p; i0 >= 0; i0--) if(PositionGetSymbol(i0) == Terminal.GetSymbol()) { ul = PositionGetInteger(POSITION_TICKET); m_bContainsPosition = true; SetLineOrder(ul, PositionGetDouble(POSITION_PRICE_OPEN), HL_PRICE, false); SetLineOrder(ul, take = PositionGetDouble(POSITION_TP), HL_TAKE, true); SetLineOrder(ul, stop = PositionGetDouble(POSITION_SL), HL_STOP, true); m_Limits.IsBuy = PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY; m_Limits.TakeProfit = (m_Limits.TakeProfit < 0 ? take : (m_Limits.IsBuy ? (m_Limits.TakeProfit > take ? m_Limits.TakeProfit : take) : (take > m_Limits.TakeProfit ? m_Limits.TakeProfit : take))); m_Limits.StopLoss = (m_Limits.StopLoss < 0 ? stop : (m_Limits.IsBuy ? (m_Limits.StopLoss < stop ? m_Limits.StopLoss : stop) : (stop < m_Limits.StopLoss ? m_Limits.StopLoss : stop))); } if ((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_TRADE_MODE) == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) m_bContainsPosition = false; for (int i0 = o; i0 >= 0; i0--) if ((ul = OrderGetTicket(i0)) > 0) if (OrderGetString(ORDER_SYMBOL) == Terminal.GetSymbol()) { price = OrderGetDouble(ORDER_PRICE_OPEN); take = OrderGetDouble(ORDER_TP); stop = OrderGetDouble(ORDER_SL); bTest = CheckLimits(price); if ((take == 0) && (stop == 0) && (bAdjust) && (!bTest)) { info = OrderGetInteger(ORDER_TYPE); vol = OrderGetDouble(ORDER_VOLUME_CURRENT); bIsBuy = ((info == ORDER_TYPE_BUY_LIMIT) || (info == ORDER_TYPE_BUY_STOP) || (info == ORDER_TYPE_BUY_STOP_LIMIT) || (info == ORDER_TYPE_BUY)); take = price + GetDisplacementTake(bIsBuy, vol); stop = price + GetDisplacementStop(bIsBuy, vol); ModifyOrderPendent(ul, price, take, stop); } if ((take != 0) && (stop != 0) && (bTest)) ModifyOrderPendent(ul, price, take = 0, stop = 0); SetLineOrder(ul, price, HL_PRICE, true); SetLineOrder(ul, take, HL_TAKE, true); SetLineOrder(ul, stop, HL_STOP, true); } };
강조 표시된 선은 주문이 자유로운지 EA가 주문에 개입해야 하는지 여부를 확인합니다. EA가 개입하면 차트 트레이더에 표시된 잔고의 가치와 펜딩 오더의 양을 기준으로 계산이 이루어집니다. 그러면 단순 펜딩 오더는 수집된 정보를 바탕으로 계산된 한도를 받게 됩니다. EA는 한도가 생성될 것임을 알리는 라인을 생성하여 단순 펜딩 오더를 OCO 펜딩 오더로 전환합니다.
결론
저는 시스템을 테스트하고 계좌를 헤징으로 인식하는지 확인하려고 했지만 이 단계에서는 성공하지 못했습니다. MetaTrader 5 플랫폼에서는 계좌가 헤징이라고 보고했지만 EA는 항상 계좌가 네팅 모드에 있다고 보고했습니다. 따라서 주의해야 합니다. 우리가 원했던 대로 작동하지만 헤징 계좌에서도 마치 네팅 계좌처럼 펜딩 오더가 조정됩니다...
동영상은 위에서 설명한 모든 내용을 명확하게 보여줍니다. 보시다시피 이 시스템은 사용하기에 매우 흥미롭습니다.
MetaQuotes 소프트웨어 사를 통해 포르투갈어가 번역됨
원본 기고글: https://www.mql5.com/pt/articles/10462
![Expert Advisor 개발 기초부터(19부): 새로운 주문 시스템(II)](https://c.mql5.com/2/47/development__2.png)
![모집단 최적화 알고리즘: 파티클 스웜(PSO)](https://c.mql5.com/2/49/avatar_PSO.png)
![포스 인덱스 지표로 트레이딩 시스템을 설계하는 방법 알아보기](https://c.mql5.com/2/48/why-and-how__2.png)
![인구 최적화 알고리즘](https://c.mql5.com/2/48/logo.png)