MetaTrader 5의 주문, 포지션 및 거래
거래 조건
트레이더의 궁극적인 목표는 금융 시장에서 거래를 통해 이익을 얻는 것입니다. 이 문서에서는 MQL5 언어의 거래 기능 작업을 올바르게 이해하는 데 필요한 지식인 MetaTrader 5 거래 플랫폼의 조건과 프로세스에 대해 설명합니다.
-
주문 — MetaTrader 5 플랫폼에 따라 공식화된 거래 서버가 수신한 거래 작업 요청입니다. 요구 사항. 요청이 잘못된 경우 거래 플랫폼에 주문 형태로 표시되지 않습니다. 주문은 특정 금융 상품의 현재 시장 가격으로 특정 수량을 매매하는 것과 같이 즉시 실행될 수 있습니다. 또 다른 유형의 주문은 특정 조건이 있는 경우 거래 작업을 수행하는 주문을 포함하는 보류 중인 주문입니다. 보류 중인 주문에는 작업에 대한 시간 제한(주문 만료 날짜)이 포함될 수도 있습니다.
실행 또는 취소 조건을 기다리고 있는 (보류 중인) 주문은 터미널의 "Trade" 탭에 표시됩니다. 이러한 주문은 수정하거나 취소할 수 있습니다. 주문의 발주, 취소, 수정은 OrderSend() 함수를 이용하여 이루어집니다. 주문이 취소되었거나 주문 만료 시간에 도달했거나 주문이 실행된 경우 주문 내역으로 이동합니다. 실행 및 취소된 주문은 클라이언트 터미널의 "History" 탭에 표시됩니다. 내역의 주문은 수정할 수 없습니다.
-
거래 - 주문 실행의 결과입니다(거래를 커밋하는 명령). 각 거래는 하나의 특정 주문을 기반으로 하지만 단일 주문으로 일련의 거래를 생성할 수 있습니다. 예를 들어, 10랏을 구매하는 주문은 부분적으로 채워진 여러 개의 연속적인 거래로 수행될 수 있습니다. 거래는 항상 거래 내역에 저장되며 수정할 수 없습니다. 터미널에서 거래는 "내역" 탭에 표시됩니다.
-
포지션은 금융 상품에서 매매되는 계약입니다. 롱 포지션(Long)은 가격 상승을 예상하여 매수한 결과, 숏 포지션(Short)은 미래 가격 하락을 예상하여 자산을 매각한 결과입니다. 각 계정에 대해 모든 금융 상품에 대해 하나의 포지션만 있을 수 있습니다. 각 기호에 대해 주어진 시간에 롱 또는 숏 중 하나의 오픈 포지션만 있을 수 있습니다.
포지션 볼륨은 같은 방향의 새로운 거래 작업의 결과로 증가할 수 있습니다. 즉, 매수(매수) 이후에는 매수(매도) 후 롱 포지션의 물량이 증가하고 매도(매도) 후에는 감소합니다. 거래 작업의 결과 약정량이 0이 되면 포지션이 닫힙니다. 이러한 작업을 포지션 청산이라고 합니다.
터미널이 서버로부터 거래 정보를 수신하고 저장하는 방법
터미널는 거래 내역을 특수 기지에 저장하고 거래 서버에 연결할 때마다 거래 계정에 누락된 거래 및 완료된 주문 내역만 수신합니다. 이것은 트래픽을 절약하기 위해 수행됩니다. MetaTrader 5 클라이언트 터미널을 닫거나 현재 활성 계정을 변경할 때 전체 내역이 하드 디스크에 기록되고 다음 터미널 실행 시부터 읽습니다.
모든 데이터베이스는 암호화된 형태로 디스크에 기록되며 암호화 키는 터미널가 설치된 컴퓨터에 따라 다릅니다. 모든 데이터베이스는 암호화된 형태로 디스크에 기록되며 암호화 키는 터미널가 설치된 컴퓨터에 따라 다릅니다.
계정에 연결하는 동안 터미널는 저장된 계정 기반과 계정의 이력을 로드하고 거래 서버에 자신의 이력 데이터베이스를 거래 서버의 계정 이력과 동기화하도록 요청합니다. 또한, 계정에 성공적으로 연결되면 거래 서버는 이 계정과 관련된 진행 중인 거래 이벤트에 대한 보고서를 터미널에 보냅니다.
거래 이벤트는 계정의 다음 변경 사항입니다.
- 인출 및 균형 작업.
- 수수료, 스왑 및 세금 부과.
- 주문, 삭제 및 수정.
- 주문에 따른 거래 실행.
- 포지션의 개설과 폐쇄.
- 포지션의 양과 방향의 변화.
터미널은 거래 서버와의 연결이 해지된 경우 주기적으로 재접속을 시도합니다. 서버와 재접속 후, 터미널은 자신의 이력 데이터베이스에 데이터의 무결성을 유지하기 위해 최근 거래 내역의 모든 변경 사항을 요청합니다.
터미널의 '내역' 탭에 표시되는 거래내역은 터미널 내역을 기준으로 하며, 내역 터미널에 표시되는 기간의 변경은 해당 데이터베이스에 저장되는 내역의 범위만 증가시킬 수 있습니다. 표시된 히스토리의 기간을 줄여도 터미널 베이스에서 히스토리가 물리적으로 제거되지는 않습니다.
이는 표시되는 내역의 간격을 더 짧게 설치한다고 해서 저장된 거래 내역의 깊이가 줄어들지 않음을 의미합니다. 그러나 "기록" 탭의 표시에 대해 더 넓은 간격 범위를 지정하면 이러한 조치는 터미널 자체 기반에 아직 정보가 없는 경우 거래 서버에서 더 깊은 기록에 대한 요청으로 이어질 수 있습니다. 해당 기간 동안 데이터를 요청했습니다.
터미널과 MetaTrader 5 거래 서버 간의 일반적인 상호 작용 방식은 다음 그림에 나와 있습니다.
클라이언트 터미널은 자신의 거래 내역 베이스에 동기화 요청을 전송합니다. 터미널 시작 중, 연결 실패 후 서버와의 다시 연결 중, 한 계정에서 다른 계정으로 전환하는 동안, 그리고 누락된 거래 이력에 대한 직접 요청 중에 말이죠.
차례대로 거래 서버는 터미널의 요청 없이 독립적으로 계정에서 발생하는 거래 이벤트에 대한 클라이언트 메시지를 보냅니다. 주문 및 포지션 상태의 변경, 주문에 따른 거래 수행, 수수료 청구, 잔액 및 출금 등에 대해 말이죠.
MQL5 프로그램에서 거래 내역에 액세스
터미널은 여러 지표, 스크립트 및 EA와 동시에 작동할 수 있으며 이러한 모든 프로그램은 주문, 거래 및 포지션과 같은 거래에 대해 필요한 정보를 요청할 수 있습니다. 전반적인 안정성, 보안 및 성능을 고려하여 터미널의 데이터베이스와 mql5-프로그램의 직접적인 작업은 제외됩니다.
요청에 의한 각 mql5 프로그램은 캐시에서 거래 환경의 "모델"을 작업에 대해 수신합니다. 캐시는 데이터에 빠르게 액세스하기 위한 특별한 메모리 영역입니다. 예를 들어, 주문 처리를 시작하기 전에 mql5 프로그램의 캐시에서 필요한 주문을 얻어야 합니다. 주문을 참조할 때 모든 추가 작업은 해당 주문의 캐시된 사본으로 수행됩니다.
히스토리의 포지션, 딜, 오더에 대한 작업도 비슷한 방식으로 진행됩니다. MQL5 프로그램에서 거래 정보를 얻는 일반적인 방법은 그림에 나와 있습니다.
거래 내역에 대한 데이터가 mql5-program의 처리에 사용 가능하게 되기 전에 터미널 데이터베이스에서 요청해야 합니다. 요청 후 얻은 데이터는 mql5 프로그램의 자체 캐시에 저장됩니다.
캐시를 부적절하게 사용하면 결과가 발생할 가능성이 있습니다.
- 요청한 데이터를 얻을 수 없으면 캐시가 비어 있고 필요한 데이터가 포함되지 않습니다.
- 캐시의 데이터에 업데이트가 필요했지만 업데이트가 요청되지 않은 경우 이러한 데이터로 작업하면 예측할 수 없는 결과가 발생할 수 있습니다. 예를 들어, 현재 포지션에 대한 데이터가 업데이트되지 않았으며 프로그램은 주어진 기호의 열린 포지션와 증가하는 손실에 대해 아무 것도 알지 못합니다.
캐시 작업을 위한 기능
거래 내역에는 mql5 프로그램의 현재 작업에 필요하지 않은 수천 개의 실행된 주문 및 거래가 포함될 수 있습니다. 따라서 캐시 작업은 요청의 원칙에 따라 구축되며 캐시에는 항상 터미널의 데이터베이스에 마지막 연결 시 업로드된 정보가 포함됩니다. 주문 및 거래의 전체 내역을 가져와야 하는 경우 원하는 간격을 지정하여 명시적으로 요청해야 합니다.
각 정보 유형에 대해 독립적인 캐시가 형성됩니다. 주문에 대한 데이터는 주문의 캐시에 저장되고, 포지션에 대한 정보는 포지션의 캐시에 저장되며, 거래 및 주문에 대한 데이터는 캐시 기록의 각 인스턴스에 저장됩니다.
캐시에서 정보를 요청하기 전에 채워야 합니다.
거래 기능은 캐시를 채우는 기능과 캐시에서 정보를 읽는 기능의 두 가지 범주로 나눌 수 있습니다.
캐시를 채우는 기능
거래 내역을 처리하려면 먼저 해당 내역을 가져와 적절한 캐시에 저장해야 합니다. 캐시를 구성하는 기능은 두 개의 하위 그룹으로 나눌 수 있습니다.
거래 캐시를 채우는 기능(활성 주문 및 포지션):
- OrderSelect(티켓) - OrderGetDouble(), OrderGetInteger() 및 OrderGetString() 함수을 사용하여 속성의 추가 요청을 위해 활성 주문을 티켓으로(터미널 베이스에서) 현재 주문의 캐시로 복사합니다.
- OrderGetTicket(index) - 활성 주문의 터미널 기준에서 주문 목록[b1] 터미널 기준의 인덱스를 기준으로 현재 주문의 캐시에 복사하여 OrderGetDouble(), OrderGetInteger() 및 OrderGetString() 함수를 사용하여 속성에 대한 추가 요청을 합니다. 터미널 베이스의 총 주문 수는 OrdersTotal() 함수를 사용하여 얻을 수 있습니다.
- PositionSelect(symbol) - PositionGetDouble( ), PositionGetInteger() 및 PositionGetString() 함수.
- PositionGetSymbol(index) - 추가 요청을 위해 터미널 베이스의 (터미널 베이스에서) 포지션 목록의 인덱스에 따라 열린 포지션을 캐시로 복사합니다. PositionGetDouble(), PositionGetInteger() 및 PositionGetString() 함수를 사용하여 속성에 대한 추가 요청을 합니다. PositionsTotal() 함수를 사용하여 터미널 베이스의 총 포지션 수를 얻을 수 있습니다.
히스토리 캐시를 채우는 기능:
- HistoryOrderSelect(티켓) - HistoryOrderGetDouble(), HistoryOrderGetInteger() 및 HistoryOrderGetString() 함수를 사용하여 속성에 대한 추가 요청을 위해 해당 속성에 대한 추가 호출을 위해 티켓에 의한 기록 주문의 캐시(터미널 베이스에서)로 복사합니다.
- HistoryOrderSelect(티켓) - HistoryOrderGetDouble(), HistoryOrderGetInteger() 및 HistoryOrderGetString() 함수를 사용하여 속성에 대한 추가 요청을 위해 해당 속성에 대한 추가 호출을 위해 티켓에 의한 기록 주문의 캐시(터미널 베이스에서)로 복사합니다..
캐시에서 사용 가능한 일반적인 거래 내역에 영향을 미치는 두 가지 기능을 별도로 고려해야 합니다.
- HistorySelect(start, end) - 서버 시간의 지정된 간격 동안 거래 및 주문으로 기록 캐시를 채웁니다. 이 함수의 실행 결과에 따라 HistoryDealsTotal() 및 HistoryOrdersTotal()에서 반환된 값이 달라집니다.
- HistorySelectByPosition(position_ID) - 지정된 식별자 포지션이 있는 거래 및 주문으로 기록 캐시를 채웁니다. 이 함수의 실행 결과는 HistoryDealsTotal() 및 HistoryOrdersTotal()에도 영향을 미칩니다.
OrderSelect 및 OrderGetTicket
OrderSelect(ticket) 및 OrderGetTicket() 일반 함수는 동일한 방식으로 작동합니다. 즉, 활성 주문 캐시를 단일 주문으로 채웁니다. OrderSelect(ticket)는 티켓 주문을 미리 알고 있는 경우를 위한 것입니다. OrdersTotal()과 함께 OrderGetTicket()을 사용하면 주문의 기본 터미널에서 사용 가능한 모든 주문을 검사할 수 있습니다.
PositionSelect 및 PositionGetSymbol
주문과 마찬가지로 이 두 함수는 포지션에 대해서도 동일한 방식으로 작동합니다. 포지션 캐시를 단일 포지션로 채웁니다. PositionGetSymbol(index)은 포지션 기준 목록의 숫자를 매개변수로 요구하고 PositionSelect(symbol)는 심볼 이름을 기반으로 캐시를 채웁니다. 포지션이 열리는 것입니다. 기호의 이름은 PositionGetSymbol(index) 함수로 얻을 수 있습니다.
HistoryOrderSelect
HistoryOrderSelect(ticket)는 티켓에 의해 터미널 베이스에서 내역적 주문을 캐시로 선택합니다. 이 기능은 필요한 주문의 티켓을 미리 알고 있을 때 사용하기 위한 것입니다.
실행이 성공하면 캐시에 단일 주문이 포함되고 HistoryOrdersTotal() 함수는 단일 단위를 반환합니다. 그렇지 않으면 기록 주문 캐시가 비어 있고 HistoryOrdersTotal() 함수가 0을 반환합니다.
HistoryDealSelect
HistoryDealSelect(ticket)는 티켓을 기준으로 기본 터미널에서 거래를 선택합니다. 이 기능은 거래의 티켓을 미리 알고 있을 때 사용하기 위한 것입니다.
실행이 성공하면 캐시에 단일 거래가 포함되고 HistoryDealsTotal() 함수는 1을 반환합니다. 그렇지 않으면 거래 캐시가 비어 있고 HistoryDealsTotal() 함수가 0을 반환합니다.
캐시에서 정보를 얻는 함수
포지션, 거래 또는 주문의 속성에 대한 정보를 요청하기 전에 mql5 프로그램의 해당 캐시를 업데이트해야 합니다. 이는 요청된 정보가 이미 업데이트되었을 수 있고 이는 캐시에 저장된 사본이 이미 구식임을 의미하기 때문입니다.
-
명령
활성 주문에 대한 정보를 얻으려면 먼저 OrderGetTicket() 또는 OrderSelect()의 두 함수 중 하나의 활성 주문 캐시에 복사해야 합니다. 해당 함수가 호출될 때 속성 값이 제공되는 것은 캐시에 저장된 순서에 대한 것입니다.
- OrderGetDouble(type_property)
- OrderGetInteger(type_property)
- OrderGetString(type_property)
이러한 함수는 캐시에서 모든 데이터를 가져오므로 주문에 대한 정확한 데이터 획득을 보장하기 위해 캐시를 채우는 함수를 호출하는 것이 좋습니다.
-
포지션
포지션에 대한 정보를 얻으려면 PositionGetSymbol 또는 PositionSelect의 두 가지 기능 중 하나를 사용하여 이전에 선택하여 캐시에 복사해야 합니다. 이 캐시에서 해당 함수가 호출될 때 position의 속성 값이 제공됩니다.
- PositionGetDouble(type_property)
- PositionGetInteger(type_property)
- PositionGetString(type_property)
이러한 함수는 캐시에서 모든 데이터를 수신하므로 포지션에 대한 정확한 데이터 획득을 보장하기 위해 포지션 캐시를 채우는 함수를 호출하는 것이 좋습니다.
-
과거 주문
기록에서 주문에 대한 정보를 얻으려면 먼저 HistorySelect(start, end), HistorySelectByPosition() 또는 HistoryOrderSelect(ticket)의 세 가지 함수 중 하나를 사용하여 기록 주문 캐시를 만들어야 합니다. 구현이 성공하면 캐시는 HistoryOrdersTotal() 함수에서 반환된 주문 수를 저장합니다. 이러한 주문의 속성에 대한 액세스는 티켓의 각 요소에서 적절한 기능을 사용하여 수행합니다.
- HistoryOrderGetDouble(ticket_order, type_property)
- HistoryOrderGetInteger(ticket_order, type_property)
- HistoryOrderGetString(ticket_order, type_property)
과거 주문의 티켓은 HistoryOrderGetTicket(index) 함수를 사용하여 과거 주문 캐시에 있는 해당 인덱스로 찾을 수 있습니다. 주문에 대한 정확한 데이터 수신을 보장하기 위해서는 이력 주문의 캐시를 채우는 함수를 호출하는 것이 좋습니다.
-
거래
기록의 특정 거래에 대한 정보를 얻으려면 먼저 HistorySelect(시작, 종료), HistorySelectByPosition() 또는 HistoryDealSelect(티켓)의 세 가지 기능 중 하나를 사용하여 거래 캐시를 생성해야 합니다. 함수 구현이 성공하면 캐시는 HistoryDealsTotal() 함수에서 반환한 금액만큼 거래를 저장합니다. 이러한 거래의 속성에 대한 액세스는 티켓을 기반으로 적절한 기능을 사용하여 수행됩니다.
- HistoryDealGetDouble(ticket_deals, type_property)
- HistoryDealGetInteger(ticket_deals, type_property)
- HistoryDealGetString(ticket_deals, type_property)
딜의 티켓은 HistoryDealGetTicket(index) 함수를 사용하여 딜 캐시에 있는 해당 인덱스로 얻을 수 있습니다. 거래에 대한 정확한 데이터 수신을 보장하려면 거래 캐시를 채우는 함수를 호출하는 것이 좋습니다.
캐시 히스토리에서 티켓을 얻는 기능
HistoryOrderGetTicket(색인)은 과거 주문의 티켓을 과거 주문의 색인 캐시(터미널 기반이 아님)로 반환합니다. 획득한 티켓은 HistoryOrderSelect(티켓) 기능에서 사용할 수 있습니다. 그리고 성공한 경우 이 기능은 캐시를 지우고 캐시를 하나의 주문으로만 다시 채웁니다. HistoryOrdersTotal()에서 반환된 값은 캐시의 주문 수에 따라 다릅니다.
HistoryDealGetTicket(index)은 거래의 캐시에서 인덱스별로 거래 티켓을 반환합니다. 거래 티켓은 HistoryDealSelect(ticket) 기능에서 사용할 수 있습니다. 그리고 성공한 경우 이 기능은 캐시를 지우고 캐시를 하나의 주문으로만 다시 채웁니다. HistoryDealsTotal() 함수에서 반환되는 값은 캐시의 거래 수에 따라 다릅니다.
활성 주문을 사용하여 정보 얻기
현재 활성 주문을 확인하는 것은 표준 절차입니다. 특정 주문에 대한 정보를 얻는 데 필요한 경우 티켓을 알고 OrderSelect(ticket) 기능을 사용하여 이를 수행할 수 있습니다.
bool selected=OrderSelect(ticket); if(selected) { double price_open=OrderGetDouble(ORDER_PRICE_OPEN); datetime time_setup=OrderGetInteger(ORDER_TIME_SETUP); string symbol=OrderGetString(ORDER_SYMBOL); PrintFormat("Ордер #%d for %s was set at %s",ticket,symbol,TimeToString(time_setup)); } else { PrintFormat("Error selecting order with ticket %d. Error %d",ticket, GetLastError()); }
위의 예에서는 주문의 티켓을 미리 알고 있다고 가정합니다. 예를 들어 전역 변수에서 가져옵니다. 그러나 일반적으로 티켓 정보가 없기 때문에 OrderGetTicket(index) 함수의 도움을 받아야 합니다. 이 함수도 하나의 주문을 선택하여 캐시에 저장하지만 현재 주문 목록에서 주문 번호를 매개변수로 지정해야 합니다.
주문 작업을 위한 전체 알고리즘(거래 및 포지션와 유사)은 다음과 같습니다.
- OrdersTotal() 함수를 사용하여 총 주문 수를 구합니다.
- 목록의 인덱스별로 모든 주문을 검색하여 루프를 구성합니다.
- OrderGetTicket() 함수를 사용하여 캐시에 각 주문을 하나씩 복사합니다.
- OrderGetDouble(), OrderGetInteger() 및 OrderGetString() 함수를 사용하여 캐시에서 올바른 주문 데이터를 가져옵니다. 필요한 경우 획득한 데이터를 분석하여 적절한 조치를 취합니다.
다음은 그러한 알고리즘의 간단한 예입니다.
input long my_magic=555; //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- obtain the total number of orders int orders=OrdersTotal(); //--- scan the list of orders for(int i=0;i<orders;i++) { ResetLastError(); //--- copy into the cache, the order by its number in the list ulong ticket=OrderGetTicket(i); if(ticket!=0)// if the order was successfully copied into the cache, work with it { double price_open =OrderGetDouble(ORDER_PRICE_OPEN); datetime time_setup=OrderGetInteger(ORDER_TIME_SETUP); string symbol =OrderGetString(ORDER_SYMBOL); long magic_number =OrderGetInteger(ORDER_MAGIC); if(magic_number==my_magic) { // process the order with the specified ORDER_MAGIC } PrintFormat("Order #%d for %s was set out %s, ORDER_MAGIC=%d",ticket,symbol,TimeToString(time_setup),magic_number); } else // call OrderGetTicket() was completed unsuccessfully { PrintFormat("Error when obtaining an order from the list to the cache. Error code: %d",GetLastError()); } } }
공석에 대한 정보 얻기
공석에 대한 지속적인 모니터링은 단순한 표준 절차가 아니라 각 사례에서 반드시 실행되어야 합니다. 특정 포지션에 대한 정보를 얻으려면 해당 항목을 여는 도구의 이름을 아는 것으로 충분합니다. 이렇게 하려면 PositionSelect(symbol) 함수를 사용합니다. EA가 하나의 기호(첨부된 차트의 기호)에서만 작업하는 경우 기호의 이름은 Symbol() 함수로 얻을 수 있습니다. 또는 사전 정의된 변수 _Symbol에서 가져옵니다.
//--- we will look for the position by the symbol of the chart, on which the EA is working string symbol=Symbol(); //--- attempt to get the position bool selected=PositionSelect(symbol); if(selected) // if the position is selected { long pos_id =PositionGetInteger(POSITION_IDENTIFIER); double price =PositionGetDouble(POSITION_PRICE_OPEN); ENUM_POSITION_TYPE type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); long pos_magic =PositionGetInteger(POSITION_MAGIC); string comment =PositionGetString(POSITION_COMMENT); PrintFormat("Position #%d by %s: POSITION_MAGIC=%d, price=%G, type=%s, commentary=%s", pos_id, symbol, pos_magic, price,EnumToString(type), comment); } else // if selecting the position was unsuccessful { PrintFormat("Unsuccessful selection of the position by the symbol %s. Error",symbol,GetLastError()); } }
일반적으로 심볼에 대한 정보는 PositionGetSymbol(index) 함수를 사용하여 얻을 수 있는데, 이 함수는 하나의 포지션을 선택하여 캐시에 저장합니다. 매개변수로 열린 포지션 목록에서 포지션의 인덱스를 지정해야 합니다. 이것은 루프의 모든 포지션 검색을 통해 가장 잘 수행됩니다.
포지션 작업을 위한 전체 알고리즘:
- PositionsTotal() 함수를 사용하여 총 포지션 수를 구합니다.
- 목록의 인덱스별로 모든 포지션을 검색하여 루프를 구성합니다.
- PositionGetSymbol() 함수를 사용하여 캐시에 각 포지션을 하나씩 복사합니다.
- PositionGetDouble(), PositionGetInteger() 및 PositionGetString() 함수를 사용하여 캐시에서 필요한 포지션 데이터를 가져옵니다. 필요한 경우 획득한 데이터를 분석하여 적절한 조치를 취합니다.
그러한 알고리즘의 예:
#property script_show_inputs input long my_magic=555; //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- obtain the total number of positions int positions=PositionsTotal(); //--- scan the list of orders for(int i=0;i<positions;i++) { ResetLastError(); //--- copy into the cache, the position by its number in the list string symbol=PositionGetSymbol(i); // obtain the name of the symbol by which the position was opened if(symbol!="") // the position was copied into the cache, work with it { long pos_id =PositionGetInteger(POSITION_IDENTIFIER); double price =PositionGetDouble(POSITION_PRICE_OPEN); ENUM_POSITION_TYPE type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); long pos_magic =PositionGetInteger(POSITION_MAGIC); string comment =PositionGetString(POSITION_COMMENT); if(pos_magic==my_magic) { // process the position with a specified POSITION_MAGIC } PrintFormat("Position #%d by %s: POSITION_MAGIC=%d, price=%G, type=%s, commentary=%s", pos_id,symbol,pos_magic,price,EnumToString(type),comment); } else // call to PositionGetSymbol() was unsuccessful { PrintFormat("Error when receiving into the cache the position with index %d."+ " Error code: %d", i, GetLastError()); } } }
기록 캐시 작업 규칙
종종 히스토리 캐시 작업을 위한 코드는 히스토리에 5-10개의 거래 및 주문이 포함된 경우에만 원활하게 작동하도록 프로그래머가 작성합니다. 잘못된 접근 방식의 일반적인 예 - 전체 거래 내역을 캐시에 로드하고 루프에서 처리하고 모든 주문 및 거래를 검색합니다.
//--- datetime start=0; // initial time set to 1970 year datetime end=TimeCurrent(); // the ending time set to the current server time //--- request into the cache of the program the entire trading history HistorySelect(start,end); //--- obtain the number of all of the orders in the history int history_orders=HistoryOrdersTotal(); //--- now scan through all of the orders for(int i=0;i<history_orders;i++) { // processing each order in the history } ... //--- obtain the number of all deals in the history int deals=HistoryDealsTotal(); //--- now scan through all of the deals for(int i=0;i<deals;i++) { // process each deal in the history }
대부분의 경우 모든 거래 내역을 처리하려는 시도는 잘못된 것입니다. 처리된 거래/주문의 수가 수천, 수만 개가 되면 프로그램의 작업이 크게 느려집니다.
이것은 주로 테스트에 중요합니다. 사용자는 테스터가 갑자기 생각에 잠긴 것을 발견하고 클라이언트 터미널에서 그 이유를 찾기 시작합니다. 따라서 먼저 항상 프로그램 MQL5(EA 및 EA에서 호출되는 표시기)의 코드 최적화에 대해 생각하십시오. 컴퓨터가 철로 만들어져 있고 많은 커널이 있다는 사실에 의존하지 마십시오.
EA와 온라인 지표의 적절한 작업을 위해서는 이것만큼 중요합니다. 프로그램의 최적화되지 않은 코드는 가장 강력한 컴퓨터의 작업도 마비시킬 수 있습니다.
거래 내역 작업을 위한 올바른 알고리즘:
- 거래 내역을 캐시에 요청할 필요성을 결정합니다. 이것이 필요하지 않은 경우 다음 작업을 수행하지 마십시오.
- 거래 내역의 최종 날짜를 결정합니다(현재까지의 내역은 필요하지 않을 수 있음).
- 종료 날짜부터 시작하여 거래 내역의 초기 날짜를 계산합니다. 일반적으로 EA는 하루 또는 일주일 이상의 거래 내역이 필요하지 않습니다.
- 알려진 티켓으로 부동산을 얻기 위한 거래 및 과거 주문 티켓을 얻습니다.
- HistoryOrderGetDouble()
- HistoryOrderGetInteger()
- HistoryOrderGetString()
- HistoryDealGetDouble()
- HistoryDealGetInteger()
- HistoryDealGetString()
- 티켓이 알려지지 않았거나 필요한 경우 정렬을 통해 주기를 구성하십시오.
- 루프에서 인덱스(HistoryOrderGetTicket(Index) 및 HistoryDealGetTicket(Index))별로 거래 내역의 캐시에서 각 거래/주문에 대한 티켓을 얻습니다.
- 알려진 티켓으로 주문 및 거래에 필요한 속성을 얻습니다(4번 항목 참조).
이 알고리즘의 코드 예:
//--- the variable, which is set in true only during the change in the trading history bool TradeHistoryChanged=false; //--- here we check for the changes in the history and put out the TradeHistoryChanged=true if needed //... the needed code //--- check if there are changes in the trading history or not if(!TradeHistoryChanged) return; //--- if the history has changed, then it makes sense to load it into the cache //--- the ending time set for the current server time datetime end=TimeCurrent(); //--- the beginning time is set to 3 days ago datetime start=end-3*PeriodSeconds(PERIOD_D1); //--- request in the cache of the program, the trading history for the last 3 days HistorySelect(start,end); //--- obtain the number of orders in the cache of the history int history_orders=HistoryOrdersTotal(); //--- now scan through the orders for(int i=0;i<history_orders;i++) { //--- obtain the ticket of the historical order ulong ticket=HistoryOrderGetTicket(i); //--- work with this order - receive its problems long order_magic=HistoryOrderGetInteger(ticket,ORDER_MAGIC); // obtain the rest of the properties for the order by the ticket // ... }
이 예가 제시하는 기본 아이디어는 먼저 거래 내역에서 변경 사항이 발생하는지 확인해야 한다는 것입니다. 옵션 중 하나는 OnTrade() 함수 내에서 전역 변수 TradeHistoryChanged에 대해 true 값을 설정하는 것입니다. 거래 이벤트는 항상 모든 유형의 무역 이벤트와 함께 돌아오기 때문입니다.
거래 내역이 변경되지 않은 경우 거래 내역을 다시 캐시에 업로드할 필요가 없으며 CPU 리소스를 낭비합니다. 이것은 논리적이며 설명이 필요하지 않습니다. 거래 내역이 변경되면 필요한 부분만 업로드하고 각 거래/주문을 한 번만 진행합니다. 불필요한 반복 주기를 피하십시오.
거래 내역에 대한 올바른 작업 및 잘못된 작업의 예는 WrongWorkWithHistory.mq5 및 RightWorkWithHistory.mq5 파일로 이 문서에 첨부되어 있습니다.
이력에서 명령으로 정보 얻기
과거 주문으로 작업하는 것은 활성 주문으로 작업하는 것과 거의 다르지 않지만 한 가지 예외가 있습니다. mql5 프로그램의 캐시에 있는 활성 주문 수가 1개를 초과할 수 없는 경우 결과 HistoryOrdersTotal()과 캐시의 과거 주문 수는 거래량에 따라 다릅니다. HistorySelect(start, end), HistorySelectByPosition() 또는 HistoryOrderSelection() 함수에 의해 기록이 로드되었습니다.
예를 들어, 우리는 마지막 날의 마지막 주문을 검색하고 그에 대한 정보를 표시하는 스크립트를 제공합니다.
// --- determining the time intervals of the required trading history datetime end=TimeCurrent(); // current server time datetime start=end-PeriodSeconds(PERIOD_D1);// set the beginning for 24 hours ago //--- request in the cache of the program the trading history for a day HistorySelect(start,end); //--- receive the number of orders in the history int history_orders=HistoryOrdersTotal(); //--- obtain the ticket of the order, which has the last index in the list, from the history ulong order_ticket=HistoryOrderGetTicket(history_orders-1); if(order_ticket>0) // obtain in the cache the historical order, work with it { //--- order status ENUM_ORDER_STATE state=(ENUM_ORDER_STATE)HistoryOrderGetInteger(order_ticket,ORDER_STATE); long order_magic =HistoryOrderGetInteger(order_ticket,ORDER_MAGIC); long pos_ID =HistoryOrderGetInteger(order_ticket,ORDER_POSITION_ID); PrintFormat("Order #%d: ORDER_MAGIC=#%d, ORDER_STATE=%d, ORDER_POSITION_ID=%d", order_ticket,order_magic,EnumToString(state),pos_ID); } else // unsuccessful attempt to obtain the order { PrintFormat("In total, in the history of %d orders, we couldn't select the order"+ " with the index %d. Error %d",history_orders,history_orders-1,GetLastError()); }
보다 일반적인 경우에는 루프의 캐시 순서를 정렬하고 분석해야 합니다. 일반적인 알고리즘은 다음과 같습니다.
- 충분한 내역의 시간 범위를 결정합니다. 내역이 HistorySelect() 함수에 의해 로드되는 경우 전체 거래 내역을 캐시에 로드하지 않는 것이 좋습니다.
- 프로그램의 캐시로 로드, 거래 내역 HistorySelect(), HistorySelectByPosition() 또는 HistoryOrderSelect(티켓) 기능.
- HistoryOrdersTotal()을 사용하여 캐시의 총 주문 수를 가져옵니다.
- 목록에 있는 색인별로 모든 주문을 검색하여 주기를 구성합니다.
- HistoryOrderGetTicket() 함수를 사용하여 캐시에서 주문 티켓을 얻습니다.
- HistoryOrderGetDouble(), HistoryOrderGetInteger() 및 HistoryOrderGetString() 함수를 사용하여 캐시에서 주문 데이터를 가져옵니다. 필요한 경우 획득한 데이터를 분석하여 적절한 조치를 취합니다.
그러한 알고리즘의 예:
#property script_show_inputs input long my_magic=999; //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { // --- setting the time intervals of the required trading history datetime end=TimeCurrent(); // the current server time datetime start=end-PeriodSeconds(PERIOD_D1);// set the beginning for 24 hours ago //--- request into the cache of the program the needed interval of the trading history HistorySelect(start,end); //--- obtain the number of orders in history int history_orders=HistoryOrdersTotal(); //--- now scroll through all of the orders for(int i=0;i<history_orders;i++) { //--- obtain the ticket of the order by its number in the list ulong order_ticket=HistoryOrderGetTicket(i); if(order_ticket>0) // obtain in the cache, the historical order, and work with it { //--- time of execution datetime time_done=HistoryOrderGetInteger(order_ticket,ORDER_TIME_DONE); long order_magic =HistoryOrderGetInteger(order_ticket,ORDER_MAGIC); long pos_ID =HistoryOrderGetInteger(order_ticket,ORDER_POSITION_ID); if(order_magic==my_magic) { // process the position with the set ORDER_MAGIC } PrintFormat("Order #%d: ORDER_MAGIC=#%d, time_done %s, ORDER_POSITION_ID=%d", order_ticket,order_magic,TimeToString(time_done),pos_ID); } else // unsuccessful attempt to obtain the order from the history { PrintFormat("we were not able to select the order with the index %d. Error %d", i,GetLastError()); } } }
히스토리에서 거래에 대한 정보 얻기
거래 처리에는 과거 주문 처리와 동일한 기능이 있습니다. 거래 내역의 거래 수 및 역사 거래 실행 결과총합()은 히스토리에 의해 캐시에 로드된 거래 기록에 따라 달라집니다.선택(시작, 끝) 또는 HistorySelectByPosition() 함수입니다.
티켓으로 하나의 거래로만 캐시를 채우려면 HistoryDealSelect(ticket) 함수를 사용하십시오.
// --- determining the time intervals of the required trading history datetime end=TimeCurrent(); // current sever time datetime start=end-PeriodSeconds(PERIOD_D1);// set the beginning for 24 hours ago //--- request in the cache of the program the needed interval of the trading history HistorySelect(start,end); //--- obtain the number of deals in history int deals=HistoryDealsTotal(); //--- obtain the ticket for the deal, which has the last index in the list ulong deal_ticket=HistoryDealGetTicket(deals-1); if(deal_ticket>0) // we obtained in the cache of the deal, and work with it { //--- the ticket order, based on which the deal was made ulong order =HistoryDealGetInteger(deal_ticket,DEAL_ORDER); long order_magic=HistoryDealGetInteger(deal_ticket,DEAL_MAGIC); long pos_ID =HistoryDealGetInteger(deal_ticket,DEAL_POSITION_ID); PrintFormat("Deal #%d for the order #%d with the ORDER_MAGIC=%d that participated in the position", deals-1,order,order_magic,pos_ID); } else // unsuccessful attempt of obtaining a deal { PrintFormat("In total, in the history %d of deals, we couldn't select a deal"+ " with the index %d. Error %d",deals,deals-1,GetLastError()); }
보다 일반적인 경우에는 캐시에서 거래 루프를 검색하고 분석해야 합니다. 일반적인 알고리즘은 다음과 같습니다.
- 충분한 히스토리의 경계를 결정합니다. 히스토리가 HistorySelect(start, end) 함수에 의해 로드되면 전체 거래 히스토리를 캐시에 로드하지 않는 것이 좋습니다.
- 프로그램의 캐시, HistorySelect() 또는 HistorySelectByPosition() 함수의 거래 기록을 로드합니다.
- HistoryDealsTotal() 함수를 사용하여 기록의 총 거래 수를 구합니다.
- 목록에 있는 번호별로 모든 거래를 검색하여 주기를 구성합니다.
- HistoryDealGetTicket()을 사용하여 캐시에서 다음 거래의 티켓을 결정합니다.
- HistoryDealGetDouble(), HistoryDealGetInteger() 및 HistoryDealGetString() 함수를 사용하여 캐시에서 거래에 대한 정보를 가져옵니다. 필요한 경우 획득한 데이터를 분석하여 적절한 조치를 취합니다.
손익 계산을 위한 이러한 알고리즘의 예:
input long my_magic=111; //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { // --- determine the time intervals of the required trading history datetime end=TimeCurrent(); // current server time datetime start=end-PeriodSeconds(PERIOD_D1);// set the beginning time to 24 hours ago //--- request in the cache of the program the needed interval of the trading history HistorySelect(start,end); //--- obtain the number of deals in the history int deals=HistoryDealsTotal(); int returns=0; double profit=0; double loss=0; //--- scan through all of the deals in the history for(int i=0;i<deals;i++) { //--- obtain the ticket of the deals by its index in the list ulong deal_ticket=HistoryDealGetTicket(i); if(deal_ticket>0) // obtain into the cache the deal, and work with it { string symbol =HistoryDealGetString(deal_ticket,DEAL_SYMBOL); datetime time =HistoryDealGetInteger(deal_ticket,DEAL_TIME); ulong order =HistoryDealGetInteger(deal_ticket,DEAL_ORDER); long order_magic =HistoryDealGetInteger(deal_ticket,DEAL_MAGIC); long pos_ID =HistoryDealGetInteger(deal_ticket,DEAL_POSITION_ID); ENUM_DEAL_ENTRY entry_type=(ENUM_DEAL_ENTRY)HistoryDealGetInteger(deal_ticket,DEAL_ENTRY); //--- process the deals with the indicated DEAL_MAGIC if(order_magic==my_magic) { //... necessary actions } //--- calculate the losses and profits with a fixed results if(entry_type==DEAL_ENTRY_OUT) { //--- increase the number of deals returns++; //--- result of fixation double result=HistoryDealGetDouble(deal_ticket,DEAL_PROFIT); //--- input the positive results into the summarized profit if(result>0) profit+=result; //--- input the negative results into the summarized losses if(result<0) loss+=result; } } else // unsuccessful attempt to obtain a deal { PrintFormat("We couldn't select a deal, with the index %d. Error %d", i,GetLastError()); } } //--- output the results of the calculations PrintFormat("The total number of %d deals with a financial result. Profit=%.2f , Loss= %.2f", returns,profit,loss); }
포지션의 식별자(POSITION_IDENTIFIER)로 히스토리의 캐시에서 획득
HistorySelectByPosition(position_ID) 함수는 HistorySelect(start, end) 함수와 마찬가지로 캐시를 기록의 거래 및 주문으로 채우지만, 한 가지 조건에서만 가능합니다. 즉, 지정된 포지션 식별자가 있어야 하죠 (POSITION_IDENTIFIER). 포지션 식별자는 각각의 재오픈 포지션에 자동으로 할당되는 고유 번호이며 평생 변경되지 않습니다. 그동안. 포지션의 변경(POSITION_TYPE_BUY에서 POSITION_TYPE_SELL으로의 포지션 유형 이동)은 포지션의 식별자를 변경하지 않는다는 점에 유의해야 합니다.
각 오픈 포지션은 해당 상품에 대한 하나 이상의 거래의 결과입니다. 따라서 포지션 변경을 분석하기 위해 수명 동안 거래가 수행된 각 거래 및 주문에 이 거래가 참여한 포지션에 대한 식별자가 할당됩니다. 따라서 현재 열려 있는 포지션의 식별자를 알면 전체 기록을 재구성할 수 있습니다. 즉, 변경된 모든 주문과 거래를 찾을 수 있습니다.
HistorySelectByPosition(position_ID) 함수는 프로그래머가 그러한 정보를 찾기 위해 전체 거래 내역을 반복하기 위해 자신의 코드를 작성하지 않아도 되도록 하는 역할을 합니다. 이 기능을 사용하기 위한 일반적인 알고리즘:
- 올바른 포지션 식별자를 얻으십시오.
- HistorySelectByPosition() 함수를 사용하여 거래 내역의 캐시로 모든 주문 및 거래를 가져옵니다. 식별자는 현재 포지션의 식별자와 같습니다.
- 알고리즘에 따라 거래 내역을 처리합니다.
결론
전체 거래 하위 시스템 플랫폼 MetaTrader 5는 잘 생각되고 사용자 친화적입니다. 더욱이 풍부한 거래 기능을 통해 각 특정 문제를 가장 효율적으로 해결할 수 있습니다.
그러나 표준 라이브러리의 특수화된 거래 클래스가 있음에도 불구하고 너무 많은 뉘앙스에 대해 걱정하지 않고 구현에 들어가지 않고 기본 사항을 이해하지 않고도 높은 수준의 프로그램을 작성할 수 있습니다. 그리하여 더 안정적이고 효율적인 거래 EA를 만들 수 있게 됩니다.
주어진 모든 예제는 이 글에 첨부된 파일에서 찾을 수 있습니다.
MetaQuotes 소프트웨어 사를 통해 러시아어가 번역됨.
원본 기고글: https://www.mql5.com/ru/articles/211