![특정 매직 넘버에 의한 총 포지션 볼륨 계산을 위한 최적 방법](https://c.mql5.com/i/articles/overlay.png)
특정 매직 넘버에 의한 총 포지션 볼륨 계산을 위한 최적 방법
소개
MetaTrader 5 클라이언트 터미널을 사용하면 하나의 기호로 여러 Expert Advisor의 병렬 작업을 수행할 수 있습니다. 간단합니다. 여러 차트를 열고 Expert Advisors를 첨부하면 됩니다. 각 Expert Advisor가 동일한 기호로 작업하는 다른 Expert Advisor와 독립적으로 작업하면 좋을 것입니다 (다른 기호로 작업하는 Expert Advisor에게는 그러한 문제가 없습니다).
우선 Expert Advisor가 Strategy Tester의 테스트 성능 및 최적화를 완전히 준수하여 거래 할 수 있습니다. 포지션 오픈 조건은 이미 오픈 포지션의 크기 또는 부재에 따라 달라질 수 있습니다. 여러 Expert Advisor가 동일한 기호로 작업하면 서로 영향을 미칩니다.
두 번째이자 아마도 더 중요한 것은 Expert Advisors에서 구현된 거래 전략에 따라 Expert Advisors가 다른 자금 관리 시스템을 사용할 수 있도록 하는 것입니다. 마지막으로 - 각 Expert Advisor의 결과를 모니터링하고 필요한 경우 끌 수 있습니다.
1. 위치 체적 계산의 일반 원리
주문을 열 때 OrderSend() 함수에 전달 된 MqlTradeRequest 구조에 매직 변수 값을 지정하여 매직 넘버로 표시 할 수 있습니다. 주문이 실행되면 거래에도 주문 매직 넘버가 표시됩니다. 또한 이력의 거래를 분석하면 여러 Expert Advisor가 개설한 거래를 볼 수 있습니다.
총 포지션을 계산하는 방법은 매우 간단합니다. 예를 들어 볼륨 0.1로 매수 거래를 실행 한 다음 0.1을 매수하고 0.1을 매도하면 총 포지션의 볼륨은 0.1+0.1-0.1이 됩니다.=+0.1. 매수 거래량을 더하고 매도 거래량을 빼면 총 포지션의 양을 얻습니다.
전체 포지션의 부피가 0 일 때 계산을 시작하는 것이 중요합니다. 첫 번째이자 가장 분명한 점은 계좌 개설의 순간입니다. 즉, 첫 번째 매개 변수가 0 (가능한 최소 시간)이고 두 번째 매개 변수의 값 TimeCurrent() (서버의 최근 알려진 시간)와 함께 HistorySelect() 함수를 사용하여 계정의 모든 거래 내역을 요청할 수 있습니다.
HistorySelect(0,TimeCurrent()); // load all history
그런 다음 처음부터 끝까지 전체 내역을 살펴보고 매수 거래량을 추가하고 지정된 매직 넘버로 각 거래의 매도 거래량을 뺍니다. 또한 솔루션이지만 실제로 거래 내역은 상당히 클 수 있습니다. 이는 특히 테스트 및 최적화 중에 Expert Advisor의 실제 사용이 불가능할 때까지 Expert Advisor의 속도에 상당한 영향을 미칠 수 있습니다. 총 순 포지션이 0이었던 거래 내역의 마지막 순간을 찾아야 합니다.
이를 위해서는 먼저 전체 히스토리를 살펴보고 총 순 포지션의 볼륨이 0 인 마지막 지점을 찾아야 합니다. 이 지점을 찾아서 특정 변수 (고정 포지션 시간)에 저장합니다. 나중에 Expert Advisor는 저장된 시점부터 거래 내역을 살펴 봅니다. 더 나은 해결책은 이 지점을 Expert Advisor의 변수 대신 클라이언트 터미널의 글로벌 변수에 저장하는 것입니다. 이 경우 Expert Advisor를 분리할 때 소멸되기 때문입니다.
이 경우 Expert Advisor가 시작된 경우에도 전체 거래 내역 대신 필요한 최소 내역을 로드해야 합니다. 동일한 심볼로 거래 할 수 있는 Expert Advisor가 많으므로 모든 Expert Advisor와 이 글로벌 변수 (최근 제로 볼륨의 저장 시간 포함)를 공유합니다.
주요 주제에서 벗어나 클라이언트 터미널의 전역 변수 사용을 고려하여 여러 Expert Advisor가 동일한 기호 (다른 매개 변수를 사용할 수 있음)로 작업 할 수 있으며 Expert Advisor의 다른 인스턴스에 의해 생성된 이름의 일치를 방지합니다.
2. 클라이언트 터미널의 전역 변수 사용
MQL5 언어에는 mql5- 프로그램에 대한 다양한 정보를 얻을 수있는 MQLInfoString() 함수가 있습니다.
파일 이름에 대한 정보를 얻으려면 MQL_PROGRAM_NAME 식별자를 사용하여 이 함수를 호출하세요.
MQL5InfoString(MQL_PROGRAM_NAME); // Expert Advisor name
따라서 Expert Advisor의 이름으로 전역 변수 이름을 시작합니다. Expert Advisor는 여러 기호에 대해 작업할 수 있습니다. 이는 심볼 (Symbol)의 이름을 추가해야 함을 의미합니다. Expert Advisors는 동일한 기호로 작업할 수 있지만 다른 시간 프레임 (다른 설정)으로 작업할 수 있습니다. 이러한 경우 매직 넘버를 사용해야 합니다. 따라서 우리는 또한 매직 넘버를 추가합니다.
예를 들어 Expert Advisor에 Magic_N 변수에 저장된 매직 넘버가 있는 경우 글로벌 변수 이름에 추가합니다.
모든 전역 변수의 이름은 다음과 같습니다.
gvp=MQLInfoString(MQL_PROGRAM_NAME)+"_"+_Symbol+"_"+IntegerToString(Magic_N)+"_"; // name of an Expert Advisor and symbol name // and its magic number
여기서 gvp (Global Variable Prefix) - 공통 변수 섹션에 선언된 문자열 변수입니다.
프로그래밍에 사용되는 전역 변수의 혼동을 피하기 위해 용어를 명확히 하고 싶습니다 (전역 변수는 모든 함수 내에서 볼 수 있고 함수의 지역 변수는 함수 내에서만 볼 수 있음).
그러나 여기서는 다른 경우가 있습니다. - "전역 변수" 용어는 클라이언트 터미널의 전역 변수를 의미합니다 (특수 변수, 파일에 저장되며 GlobalVariable...() 함수에서 사용할 수 있음). 전역 변수 (프로그래밍에 사용됨)에 대해 말할 때 "공통 변수"용어를 사용합니다. 지역 변수라는 용어는 지역 변수를 의미합니다.
전역 변수는 Expert Advisor의 초기화를 해제 한 후 (Expert Advisor, 클라이언트 터미널, 컴퓨터 다시 시작) 값을 저장하기 때문에 유용하지만 테스트 모드에서는 모든 변수 (또는 최적화시 이전 패스)를 지워야 합니다. 실제 작업에 사용되는 전역 변수는 테스트시 생성된 전역 변수와 분리되어야 하며, 테스트 후 삭제해야 합니다. 그러나 Expert Advisor에서 생성한 전역 변수를 수정하거나 삭제해서는 안됩니다.
AccountInfoInteger() 함수를 사용하고 ACCOUNT_TRADE_MODE 식별자로 호출하면 현재 모드 (테스터, 데모 또는 실제 계정)를 구분할 수 있습니다.
전역 변수에 접두사를 추가해 보겠습니다. "d" - 데모 계정 작업시 "r" - 실제 계정 작업 시 "t" - Strategy Tester 작업시:
gvp=MQLInfoString(MQL_PROGRAM_NAME)+"_"+_Symbol+"_"+IntegerToString(Magic_N)+"_"; // name of an Expert Advisor, symbol name // and the Expert Advisor magic number if(AccountInfoInteger(ACCOUNT_TRADE_MODE)==ACCOUNT_TRADE_MODE_DEMO)) { gvp=gvp+"d_"; // demo account } if(AccountInfoInteger(ACCOUNT_TRADE_MODE)==ACCOUNT_TRADE_MODE_REAL) { gvp=gvp+"r_"; // real } if(MQL5InfoInteger(MQL_TESTER)) { gvp=gvp+"t_"; // testing }
이 함수는 Expert Advisor의 OnInit() 함수에서 호출해야 합니다.
위에서 언급했듯이 테스트시 전역 변수를 삭제해야 합니다. 즉, Expert Advisor의 OnDeinit() 함수에 전역 변수를 삭제하는 함수를 추가해야 합니다.
void fDeleteGV() { if(MQL5InfoInteger(MQL_TESTER)) // Testing mode { for(int i=GlobalVariablesTotal()-1;i>=0;i--) // Check all global variables (from the end, not from the begin) { if(StringFind(GlobalVariableName(i),gvp,0)==0) // search for the specified prefix { GlobalVariableDel(GlobalVariableName(i)); // Delete variable } } } }
현재 MetaTrader 5에서 테스트를 중단하는 것은 불가능합니다. 즉, OnDeinit() 함수의 실행이 보장되지는 않지만 향후 나타날 수 있습니다. Strategy Tester 중단 후 OnDeinit() 함수가 실행되는지 여부를 알 수 없으므로 Expert Advisor 실행 시작시 전역 변수를 삭제합니다. - OnInit() 함수.
OnInit() 및 OnDeinit() 함수의 다음 코드를 얻습니다.
int OnInit() { fCreateGVP(); // Creating a prefix for the names of global variables of the client terminal fDeleteGV(); // Delete global variables when working in Tester return(0); } void OnDeinit(const int reason) { fDeleteGV(); // Delete global variables when working in tester }
또한 GlobalVariableSet(gvp+...) 대신 전역 변수 생성을위한 짧은 이름으로 함수를 생성하여 전역 변수의 사용을 단순화 할 수 있습니다.
전역 변수의 값을 설정하는 함수:
void fGVS(string aName,double aValue) { GlobalVariableSet(gvp+aName,aValue); }
전역 변수의 값을 가져 오는 함수:
double fGVG(string aName) { return(GlobalVariableGet(gvp+aName)); }
전역 변수를 삭제하는 함수:
void fGVD(string aName) { GlobalVariableDel(gvp+aName); }
전역 변수에 대해 논의했지만 그게 다가 아닙니다.
심볼에 대한 전역 변수를 생성하고 계정과 전략 테스터에서 서로 다른 작업을 제공 할 수 있는 가능성을 제공해야 합니다. 이러한 전역 변수의 이름은 Expert Advisor의 이름과 매직 넘버에 의존하지 않아야 합니다.
"Commom_gvp"라는 이름의 전역 변수 접두사에 대해 다른 변수를 선언 해 보겠습니다. 그런 다음 계정으로 작업하면 "COMMON"값을 가지며 Strategy Tester로 작업할 때 변수 gvp와 동일한 값을 갖게 됩니다 (전략 백 테스팅 프로세스 전후에 변수 삭제).
마지막으로 전역 변수 접두사를 준비하는 함수의 형식은 다음과 같습니다.
void fCreateGVP() { gvp=MQL5InfoString(MQL_PROGRAM_NAME)+"_"+_Symbol+"_"+IntegerToString(Magic_N)+"_"; Commom_gvp="COMMOM_"; // Prefix for common variables for all Expert Advisors if(AccountInfoInteger(ACCOUNT_TRADE_MODE)==ACCOUNT_TRADE_MODE_DEMO) { gvp=gvp+"d_"; } if(AccountInfoInteger(ACCOUNT_TRADE_MODE)==ACCOUNT_TRADE_MODE_REAL) { gvp=gvp+"r_"; } if(MQLInfoInteger(MQL_TESTER)) { gvp=gvp+"t_"; Commom_gvp=gvp; // To be used in tester, the variables with such a prefix // will be deleted after the testing } }
누군가는 글로벌 변수의 접두사에 추가 정보가 포함되어 있다고 생각할 수 있습니다. 데모 및 실제 계정의 분리, 테스트시 "t" 접두사를 테스트 할 때 문자 "t"를 추가하면 되고, Expert Advisor가 Strategy Tester에서 작업을 하는 걸 지시합니다. 하지만 저는 이렇게 했습니다. 우리는 Expert Advisors의 작업을 분석하는 데 필요한 미래와 일을 모릅니다.
그들은 스토어가 문제가 아니라 하죠.
위에 제시된 함수는 클라이언트 터미널이 하나의 계정으로 작동하고 작업 중에 계정이 변경되지 않음을 의미합니다. Expert Advisor 작업 중 계정 변경은 금지됩니다. 물론 필요하다면 글로벌 변수 이름에 계좌 번호를 추가하면 이 문제를 해결할 수 있습니다.
또 한가지 중요한 점! 전역 변수 이름의 길이는 63 개의 기호로 제한됩니다. 이 사실 때문에 Expert Advisors에게 긴 이름을 부여하지 마세요.
우리는 전역 변수에 대한 작업을 마쳤습니다. 이제 글의 주요 주제인 특정 매직 넘버에 의한 포지션 볼륨 계산을 고려할 때입니다.
3. 포지션 볼륨 계산
먼저 GlobalVariableCheck() 함수를 사용하여 제로 볼륨 포지션의 마지막 시간에 대한 정보가 포함된 전역 변수가 있는지 확인합니다(간단하게 하기 위해 열린 포지션이 없으면 호출합니다. '제로 위치'사례).
이러한 변수가 있으면 시간부터 시작하여 변수에 저장된 거래 내역을 로드합시다. 그렇지 않으면 전체 내역을 로드합니다.
if(GlobalVariableCheck(Commom_gvp+sSymbol+"_HistStTm")) // Saved time of a "zero" total position { pLoadHistoryFrom=(datetime)GlobalVariableGet(Commom_gvp+pSymbol+"_HistStTm"); // initial date setting // select only the history needed } else { GlobalVariableSet(Commom_gvp+sSymbol+"_HistStTm",0); } if(!HistorySelect(sLoadHistoryFrom,TimeCurrent())) // Load the necessary part of the deal history { return(false); }
다음으로, 심볼에 대한 총 순 포지션 볼륨을 정의합니다.
double CurrentVolume=fSymbolLots(pSymbol);
위치의 부피는 fSymbolLots() 함수를 사용하여 결정됩니다.
위치의 볼륨을 얻는 방법에는 여러 가지가 있습니다. 예를 들어 PositionSelect() 함수를 사용하여 수행할 수 있습니다. 함수가 false를 반환하면 포지션이 없음을 의미합니다 (볼륨이 0과 같음). 함수가 true를 반환하면 POSITION_VOLUME 식별자와 함께 PositionGetDouble() 함수를 사용하여 볼륨을 가져올 수 있습니다. 포지션 유형(매수 또는 매도)은 POSITION_TYPE 식별자와 함께 PositionGetInteger() 함수를 사용하여 결정됩니다. 이 함수는 긴 포지션에 대해 양수 값을 반환하고 짧은 포지션에 대해 음수를 반환합니다.
완전한 함수는 다음과 같습니다.
double fSymbolLots(string aSymbol) { if(PositionSelect(aSymbol,1000)) // the position has been selected successfully, so it exists { switch(PositionGetInteger(POSITION_TYPE)) // It returns the positive or negative value dependent on the direction { case POSITION_TYPE_BUY: return(NormalizeDouble(PositionGetDouble(POSITION_VOLUME),2)); break; case POSITION_TYPE_SELL: return(NormalizeDouble(-PositionGetDouble(POSITION_VOLUME),2)); break; } } else { return(0); } }
또는 모든 포지션을 통해 루프로 심볼의 전체 포지션의 부피를 결정할 수 있으며, 포지션의 수는 PositionsTotal() 함수로 결정됩니다. 그런 다음 PositionGetSymbol() 함수를 사용하여 필요한 기호를 찾고 포지션의 볼륨과 방향을 결정합니다 (POSITION_VOLUME 식별자가 있는 PositionGetDouble() 및 POSITION_TYPE 식별자가 있는 PositionGetInteger() 함수).
이 경우 준비 함수는 다음과 같은 형식을 갖습니다.
double fSymbolLots(string aSymbol) { double TmpLots=0; for(int i=0;i<PositionsTotal();i++) // Go through all positions { if(PositionGetSymbol(i)==aSymbol) // we have found a position with specified symbol { TmpLots=PositionGetDouble(POSITION_VOLUME); if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL) { TmpLots*=-1; // the sign is dependent on the position type } break; } } TmpLots=NormalizeDouble(TmpLots,2); return(TmpLots); }
현재 거래량을 결정한 후 거래량의 합계가 거래량과 같아 질 때까지 거래 내역을 끝에서 시작까지 살펴봅니다.
선택한 내역 및 거래의 길이는 HistoryDealsTotal() 함수를 사용하여 결정되며, 티켓은 HistoryDealGetTicket () 함수를 사용하여 각 거래에 대해 결정되며 거래 데이터는 HistoryDealGetInteger() 함수 (거래 유형에 대한 DEAL_TYPE 식별자) 및 HistoryDealGetDouble() (거래량에 대한 DEAL_VOLUME 식별자)을 사용하여 추출됩니다.
double Sum=0; int FromI=0; int FromTicket=0; for(int i=HistoryDealsTotal()-1;i>=0;i--) // go through all the deals from the end to the beginning { ulong ticket=HistoryDealGetTicket(i); // Get ticket of the deal if(ticket!=0) { switch(HistoryDealGetInteger(ticket,DEAL_TYPE)) // We add or subtract the volume depending on deal direction { case DEAL_TYPE_BUY: Sum+=HistoryDealGetDouble(ticket,DEAL_VOLUME); Sum=NormalizeDouble(Sum,2); break; case DEAL_TYPE_SELL: Sum-=HistoryDealGetDouble(ticket,DEAL_VOLUME); Sum=NormalizeDouble(Sum,2); break; } if(CurrentVolume==Sum) // all the deals has scanned { sLoadHistoryFrom=HistoryDealGetInteger(ticket,DEAL_TIME); // Save the time of a "zero" position GlobalVariableSet(Commom_gvp+aSymbol+"_HistStTm",sLoadHistoryFrom); FromI=i; // Save the index break; } } }
이 지점을 찾으면 시간을 전역 변수에 저장합니다. 이 변수는 거래 내역을 로드 할 때 더 많이 사용됩니다 (기록의 거래 색인은 FromI 변수에 저장됨).
인덱스 FromI를 처리하기 전에 기호의 총 위치는 0이었습니다.
이제 FromI에서 내역의 끝까지 이동하여 지정된 매직 넘버로 거래의 양을 계산합니다.
static double sVolume=0; static ulong sLastTicket=0; for(int i=FromI;i<HistoryDealsTotal();i++) // from the first deal until the end { ulong ticket=HistoryDealGetTicket(i); // Get deal ticket if(ticket!=0) { if(HistoryDealGetString(ticket,DEAL_SYMBOL)==aSymbol) // Specified symbol { long PosMagic=HistoryDealGetInteger(ticket,DEAL_MAGIC); if(PosMagic==aMagic || aMagic==-1) // Specified magic { switch(HistoryDealGetInteger(ticket,DEAL_TYPE)) // add or subtract the deal volumes // depending on the deal type { case DEAL_TYPE_BUY: sVolume+=HistoryDealGetDouble(ticket,DEAL_VOLUME); sLastTicket=ticket; break; case DEAL_TYPE_SELL: sVolume-=HistoryDealGetDouble(ticket,DEAL_VOLUME); sLastTicket=ticket; break; } } } } }
루프가 끝나면 지정된 매직 넘버에 의해 현재 포지션의 볼륨을 갖게되고, 지정된 매직 넘버를 가진 마지막 딜의 티켓은 딜 실행 후 총 볼륨이 sLastTicket 변수에 저장됩니다. 지정된 매직 넘버가 있는 위치는 sVolume과 같습니다. 함수의 예비 작업이 끝났습니다.
sLoadHistoryFrom, sLastTicket 및 sVolume 변수는 정적 (함수 완료 후 값 저장)으로 선언되며, 이 값은 각 함수 호출에 대해 추가로 사용됩니다.
시간 (거래 내역의 시작점), 거래 티켓, 실행 후 총 포지션 (지정된 기호 포함)의 볼륨이 현재 값을 갖게 됩니다.
제로 볼륨 포지션의 시간이기 때문에 현재 시간부터 저장 시간까지의 이력을 살펴보고 딜 볼륨 합산을 수행하고 마지막 딜의 볼륨과 티켓을 저장하는 것으로 충분합니다.
따라서 Expert Advisor의 총 포지션 계산은 몇 가지 마지막 거래를 처리하는 것입니다.
if(!HistorySelect(sLoadHistoryFrom,TimeCurrent())) // Request for the deals history up to the current time { return(false); } for(int i=HistoryDealsTotal()-1;i>=0;i--) // Loop from the end { ulong ticket=HistoryDealGetTicket(i); // Get ticke if(ticket!=0) { if(ticket==sLastTicket) // We have found the already calculated deal, save the ticket and break { sLastTicket=HistoryDealGetTicket(HistoryDealsTotal()-1); break; } switch(HistoryDealGetInteger(ticket,DEAL_TYPE)) // Add or subtract deal volume depending on deal type { case DEAL_TYPE_BUY: sVolume+=HistoryDealGetDouble(ticket,DEAL_VOLUME); break; case DEAL_TYPE_SELL: sVolume-=HistoryDealGetDouble(ticket,DEAL_VOLUME); break; } } }
함수 알고리즘은 다음과 같이 나타낼 수 있습니다.
완전한 함수:
bool fGetPositionVolume(string aSymbol,int aMagic,double aVolume) { static bool FirstStart=false; static double sVolume=0; static ulong sLastTicket=0; static datetime sLoadHistoryFrom=0; // First execution of function when Expert Advisor has started if(!FirstStart) { if(GlobalVariableCheck(Commom_gvp+aSymbol+"_HistStTm")) { sLoadHistoryFrom=(datetime)GlobalVariableGet(Commom_gvp+aSymbol+"_HistStTm"); } else { GlobalVariableSet(Commom_gvp+aSymbol+"_HistStTm",0); } if(!HistorySelect(sLoadHistoryFrom,TimeCurrent())) // Return if unsuccessful, // we will repeat on the next tick { return(false); } double CurrentVolume=fSymbolLots(aSymbol); // Total volume double Sum=0; int FromI=0; int FromTicket=0; // Search the last time when position volume was equal to zero for(int i=HistoryDealsTotal()-1;i>=0;i--) { ulong ticket=HistoryDealGetTicket(i); if(ticket!=0) { switch(HistoryDealGetInteger(ticket,DEAL_TYPE)) { case DEAL_TYPE_BUY: Sum+=HistoryDealGetDouble(ticket,DEAL_VOLUME); Sum=NormalizeDouble(Sum,2); break; case DEAL_TYPE_SELL: Sum-=HistoryDealGetDouble(ticket,DEAL_VOLUME); Sum=NormalizeDouble(Sum,2); break; } if(CurrentVolume==Sum) { sLoadHistoryFrom=HistoryDealGetInteger(ticket,DEAL_TIME); GlobalVariableSet(Commom_gvp+aSymbol+"_HistStTm",sLoadHistoryFrom); FromI=i; break; } } } // Calculate the volume of position with specified magic number and symbol for(int i=FromI;i<HistoryDealsTotal();i++) { ulong ticket=HistoryDealGetTicket(i); if(ticket!=0) { if(HistoryDealGetString(ticket,DEAL_SYMBOL)==aSymbol) { long PosMagic=HistoryDealGetInteger(ticket,DEAL_MAGIC); if(PosMagic==aMagic || aMagic==-1) { switch(HistoryDealGetInteger(ticket,DEAL_TYPE)) { case DEAL_TYPE_BUY: sVolume+=HistoryDealGetDouble(ticket,DEAL_VOLUME); sLastTicket=ticket; break; case DEAL_TYPE_SELL: sVolume-=HistoryDealGetDouble(ticket,DEAL_VOLUME); sLastTicket=ticket; break; } } } } } FirstStart=true; } // Recalculate the volume of a position (with specified symbol and magic) // for the deals, after the zero position time if(!HistorySelect(sLoadHistoryFrom,TimeCurrent())) { return(false); } for(int i=HistoryDealsTotal()-1;i>=0;i--) { ulong ticket=HistoryDealGetTicket(i); if(ticket!=0) { if(ticket==sLastTicket) { sLastTicket=HistoryDealGetTicket(HistoryDealsTotal()-1); break; } switch(HistoryDealGetInteger(ticket,DEAL_TYPE)) { case DEAL_TYPE_BUY: sVolume+=HistoryDealGetDouble(ticket,DEAL_VOLUME); break; case DEAL_TYPE_SELL: sVolume-=HistoryDealGetDouble(ticket,DEAL_VOLUME); break; } } } aVolume=NormalizeDouble(sVolume,2);; return(true); }
기호와 매직 넘버는 포지션 볼륨을 반환하는 함수에 전달됩니다. 성공하면 true를 반환하고 그렇지 않으면 false를 반환합니다.
성공하면 요청된 볼륨을 변수 aVolume에 반환하고 참조로 함수에 전달합니다. 함수에서 선언된 정적 변수는 다른 매개 변수 (기호 및 매직 넘버)와 함께이 함수를 사용할 수 없습니다.
MQL4의 경우이 문제는 다른 이름으로 이 함수의 복사본을 만들고 다른 쌍 "symbol-magic"에 대해 호출하거나 변수 FirstStart, sVolume, sLastTicket, sLoadHistoryFrom을 공통 변수로 선언하여 해결할 수 있습니다. 각 쌍은 "symbol-magic"이고 함수에 전달합니다.
MQL5에서도 동일한 방식으로 구현할 수 있지만 MQL5에는 훨씬 더 편리한 함수가 있습니다. 클래스 사용이 합리적인 경우입니다. 클래스를 사용할 때 각 심볼-매직 넘버 쌍에 대한 클래스 인스턴스를 만들어야 하며 데이터는 각 클래스 인스턴스에 저장됩니다.
PositionVolume 클래스를 선언합시다. 함수 내에서 정적으로 선언 된 모든 변수는 비공개로 선언되며 Volume 변수를 제외하고 Expert Advisor에서 직접 사용하지 않습니다. 하지만 볼륨 계산 함수를 실행 한 후에만 필요합니다. 또한 Symbol 및 Magic 변수를 선언합니다. 클래스 인스턴스를 초기화 할 때 한 번만 수행하여 함수에 전달하는 것은 비현실적입니다.
클래스에는 초기화 함수와 포지션 볼륨 계산 함수, 포지션의 총 볼륨을 결정하는 프라이빗 함수의 두 가지 공용 함수가 있습니다.
class PositionVolume { private: string pSymbol; int pMagic; bool pFirstStart; ulong pLastTicket; double pVolume; datetime pLoadHistoryFrom; double SymbolLots(); public: void Init(string aSymbol,int aMagic) { pSymbol=aSymbol; pMagic=aMagic; pFirstStart=false; pLastTicket=0; pVolume=0; } bool GetVolume(double &aVolume); };
bool PositionVolume::GetVolume(double &aVolume) { if(!pFirstStart) { if(GlobalVariableCheck(Commom_gvp+pSymbol+"_HistStTm")) { pLoadHistoryFrom=(datetime)GlobalVariableGet(Commom_gvp+pSymbol+"_HistStTm"); } else { GlobalVariableSet(Commom_gvp+pSymbol+"_HistStTm",0); } if(!HistorySelect(pLoadHistoryFrom,TimeCurrent())) { return(false); } double CurrentVolume=fSymbolLots(pSymbol); double Sum=0; int FromI=0; int FromTicket=0; for(int i=HistoryDealsTotal()-1;i>=0;i--) { ulong ticket=HistoryDealGetTicket(i); if(ticket!=0) { switch(HistoryDealGetInteger(ticket,DEAL_TYPE)) { case DEAL_TYPE_BUY: Sum+=HistoryDealGetDouble(ticket,DEAL_VOLUME); Sum=NormalizeDouble(Sum,2); break; case DEAL_TYPE_SELL: Sum-=HistoryDealGetDouble(ticket,DEAL_VOLUME); Sum=NormalizeDouble(Sum,2); break; } if(CurrentVolume==Sum) { pLoadHistoryFrom=HistoryDealGetInteger(ticket,DEAL_TIME); GlobalVariableSet(Commom_gvp+pSymbol+"_HistStTm",pLoadHistoryFrom); FromI=i; break; } } } for(int i=FromI;i<HistoryDealsTotal();i++) { ulong ticket=HistoryDealGetTicket(i); if(ticket!=0) { if(HistoryDealGetString(ticket,DEAL_SYMBOL)==pSymbol) { long PosMagic=HistoryDealGetInteger(ticket,DEAL_MAGIC); if(PosMagic==pMagic || pMagic==-1) { switch(HistoryDealGetInteger(ticket,DEAL_TYPE)) { case DEAL_TYPE_BUY: pVolume+=HistoryDealGetDouble(ticket,DEAL_VOLUME); pLastTicket=ticket; break; case DEAL_TYPE_SELL: pVolume-=HistoryDealGetDouble(ticket,DEAL_VOLUME); pLastTicket=ticket; break; } } } } } pFirstStart=true; } if(!HistorySelect(pLoadHistoryFrom,TimeCurrent())) { return(false); } for(int i=HistoryDealsTotal()-1;i>=0;i--) { ulong ticket=HistoryDealGetTicket(i); if(ticket!=0) { if(ticket==pLastTicket) { break; } if(HistoryDealGetString(ticket,DEAL_SYMBOL)==pSymbol) { long PosMagic=HistoryDealGetInteger(ticket,DEAL_MAGIC); if(PosMagic==pMagic || pMagic==-1) { switch(HistoryDealGetInteger(ticket,DEAL_TYPE)) { case DEAL_TYPE_BUY: pVolume+=HistoryDealGetDouble(ticket,DEAL_VOLUME); break; case DEAL_TYPE_SELL: pVolume-=HistoryDealGetDouble(ticket,DEAL_VOLUME); break; } } } } } if(HistoryDealsTotal()>0) { pLastTicket=HistoryDealGetTicket(HistoryDealsTotal()-1); } pVolume=NormalizeDouble(pVolume,2); aVolume=pVolume; return(true); }
double PositionVolume::SymbolLots() { double TmpLots=0; for(int i=0;i<PositionsTotal();i++) { if(PositionGetSymbol(i)==pSymbol) { TmpLots=PositionGetDouble(POSITION_VOLUME); if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL) { TmpLots*=-1; } break; } } TmpLots=NormalizeDouble(TmpLots,2); return(TmpLots); }
각 심볼-매직 넘버 쌍에이 클래스를 사용할 때는 클래스 인스턴스를 만들어야 합니다.
PositionVolume PosVol11; PositionVolume PosVol12; PositionVolume PosVol21; PositionVolume PosVol22;
Expert Advisor의 OnInit() 함수에서 초기화해야 합니다. 예를 들면 다음과 같습니다.
PosVol11.Init(Symbol_1,Magic_1); PosVol12.Init(Symbol_1,Magic_2); PosVol21.Init(Symbol_2,Magic_1); PosVol22.Init(Symbol_2,Magic_2);
그 후 지정된 기호와 매직 넘버로 포지션의 볼륨을 얻을 수 있습니다. 해당 클래스 인스턴스의 GetVolume 함수를 호출해 보겠습니다.
성공하면 true를 반환하고 함수의 매개 변수로 참조로 전달된 값을 변수에 넣습니다.
double Vol11; double Vol12; double Vol21; double Vol22; PosVol11.GetVolume(Vol11); PosVol12.GetVolume(Vol12); PosVol21.GetVolume(Vol21); PosVol22.GetVolume(Vol22);
여기에서 당신은 끝났다고 말할 수 있지만 통제 테스트는 남아 있습니다.
4. 제어 테스트
함수의 작업을 테스트하기 위해 우리는 4 개의 포지션에서 동시에 작동하는 Expert Advisor를 사용했습니다.
- 매직 넘버 1의 EURUSD에 기간 14의 RSI 인디케이터 사용;
- 매직 넘버 2로 EURUSD에서 기간 21의 RSI 인디케이터 사용;
- 매직 넘버 1의 GBPUSD에서 기간 14의 RSI 인디케이터 사용;
- 매직 넘버 2의 GBPUSD에서 기간 21의 RSI 인디케이터 사용;
매직 넘버 1의 Expert Advisor는 0.1 랏의 거래량을, 매직 넘버 2의 Expert Advisor는 0.2 랏의 거래량을 거래했습니다.
거래량은 거래 실행시 Expert Advisor의 변수에 추가되며, 거래 전후에 위에 제시된 함수를 사용하여 각 포지션의 거래량을 결정했습니다.
이 함수는 부피 계산에 오류가 있는 경우 메시지를 생성합니다.
Expert Advisor의 코드는 글 첨부 파일에서 찾을 수 있습니다 (파일 이름: ePosVolTest.mq5).
결론
Expert Advisor에게는 많은 함수가 필요하며 모든 단계에서 사용하기 편리한 방식으로 구현되어야 합니다. 이러한 함수는 컴퓨팅 리소스를 최대한 활용한다는 관점에서 작성되어야 합니다.
이 글에서 제안한 포지션 볼륨 계산 방법은 이러한 조건을 충족합니다. 시작시 필요한 최소 거래 내역만 로드합니다. 이 글에서 제안한 포지션 볼륨 계산 방법은 이러한 조건을 충족합니다. 시작시 필요한 최소 거래 내역만 로드합니다.
MetaQuotes 소프트웨어 사를 통해 러시아어가 번역됨.
원본 기고글: https://www.mql5.com/ru/articles/125
![TesterWithdrawal() 함수를 이용한 수익 인출 모델링](https://c.mql5.com/2/0/TesterWithdrawal_MQL5.png)
![MQL5 객체 지향 프로그래밍 접근 방식을 사용한 Expert Advisor 작성하기](https://c.mql5.com/2/0/Expert_Advisor_classes_MQL5.png)
![Expert Advisor 작성시 MQL5 Standard Trade Class 라이브러리 사용](https://c.mql5.com/2/0/Trade_Classes_Stdlib_MQL5.png)
![Named Pipes를 사용하여 MetaTrader 5 터미널 간 통신을 위한 DLL없는 솔루션](https://c.mql5.com/2/0/Named_Pipes_MetaTrader5.png)