//+------------------------------------------------------------------+ //| OnTesterInit_Sample.mq5 | //| Copyright 2018, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2000-2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property description "OnTesterInit() 핸들러 포함 샘플 EA," #property description "다음의 값과 제한이 있는 " #property description "최적화 중 입력 설정" input double lots=0. 1; // 롯 단위 볼륨 input double kATR=3; // ATR에서의 시그널 캔들 길이 input int ATRperiod=20; // ATR 지표 기간 input int holdbars=8; // 포지션을 유지할 막대 수 input int slippage=10; // 허용가능 저하 input bool revers=false; // 시그널을 반전시킬까요? input ulong EXPERT_MAGIC=0; // EA's MagicNumber //--- ATR 지표 핸들 보관용 int atr_handle; //--- 여기에 마지막 ATR 값과 캔들 바디를 저장할 것입니다. double last_atr,last_body; datetime lastbar_timeopen; double trade_lot; //--- 최적화 시작 시간 기억 datetime optimization_start; //--- 최적화 종료 후 차트에 지속 시간 표시 string report; //+------------------------------------------------------------------+ //| TesterInit 함수 | //+------------------------------------------------------------------+ void OnTesterInit() { //--- 최적화할 입력 값을 설정 ParameterSetRange("lots",false,0.1,0,0,0); ParameterSetRange("kATR",true,3.0,1.0,0.3,7.0); ParameterSetRange("ATRperiod",true,10,15,1,30); ParameterSetRange("holdbars",true,5,3,1,15); ParameterSetRange("slippage",false,10,0,0,0); ParameterSetRange("revers",true,false,false,1,true); ParameterSetRange("EXPERT_MAGIC",false,123456,0,0,0); Print("초기값 및 최적화 파라미터 제한이 설정됨"); //--- 최적화 시작 기억 optimization_start=TimeLocal(); report=StringFormat("%s: optimization launched at %s", __FUNCTION__,TimeToString(TimeLocal(),TIME_MINUTES|TIME_SECONDS)); //--- 차트 및 터미널 저널에 메시지 표시 Print(report); Comment(report); //--- } //+------------------------------------------------------------------+ //| TesterDeinit 함수 | //+------------------------------------------------------------------+ void OnTesterDeinit() { //--- 최적화 기간 string log_message=StringFormat("%s: optimization took %d seconds", __FUNCTION__,TimeLocal()-optimization_start); PrintFormat(log_message); report=report+"\r\n"+log_message; Comment(report); } //+------------------------------------------------------------------+ //| Expert 초기화 함수 | //+------------------------------------------------------------------+ int OnInit() { //--- 글로벌 변수 최적화 last_atr=0; last_body=0; //--- 알맞은 볼륨 설정 double min_lot=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN); trade_lot=lots>min_lot? lots:min_lot; //--- ATR 지표 핸들 생성 atr_handle=iATR(_Symbol,_Period,ATRperiod); if(atr_handle==INVALID_HANDLE) { PrintFormat("%s: iATR 생성 실패, 에러 코드 %d",__FUNCTION__,GetLastError()); return(INIT_FAILED); } //--- EA 초기화 성공 return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert 틱 함수 | //+------------------------------------------------------------------+ void OnTick() { //--- 거래 시그널 static int signal=0; // +1은 매수 시그널을, -1은 매도 시그널을 의미합니다 //--- 'holdbars' 막대 보다 이전에 오픈된 포지션을 확인 후 마감합니다 ClosePositionsByBars(holdbars,slippage,EXPERT_MAGIC); //--- 새 막대 점검 if(isNewBar()) { //--- 시그널 유무를 확인 signal=CheckSignal(); } //--- netting 포지션이 오픈한 경우 시그널을 스킵합니다 - 시그널 마감까지 대기하십시오 if(signal!=0 && PositionsTotal()>0 && (ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_NETTING) { signal=0; return; // NewTick 이벤트 핸들러를 종료하고 새 막대가 나타나기 전에 마켓에 진입하지 마십시오 } //--- 헷징 계정의 경우, 각 포지션은 별도로 보유 및 마감합니다 if(signal!=0) { //--- 매수 시그널 if(signal>0) { PrintFormat("%s: 매수 시그널! Revers=%s",__FUNCTION__,string(revers)); if(Buy(trade_lot,slippage,EXPERT_MAGIC)) signal=0; } //--- 매도 시그널 if(signal<0) { PrintFormat("%s: 매도 시그널! Revers=%s",__FUNCTION__,string(revers)); if(Sell(trade_lot,slippage,EXPERT_MAGIC)) signal=0; } } //--- OnTick 함수 종료 } //+------------------------------------------------------------------+ //| 새 거래 신호 점검 | //+------------------------------------------------------------------+ int CheckSignal() { //--- 0은 시그널이 없음을 의미합니다 int res=0; //--- 끝에서 두번째의 완성된 막대에서 ATR 값 획득 (막대 인덱스는 2) double atr_value[1]; if(CopyBuffer(atr_handle,0,2,1,atr_value)!=-1) { last_atr=atr_value[0]; //--- 최근에 마감된 막대의 데이터를 MqlRates 타입 배열로 가져옵니다. MqlRates bar[1]; if(CopyRates(_Symbol,_Period,1,1,bar)!=-1) { //--- 최근 완성된 막대에서 바 몸체 크기를 계산 last_body=bar[0].close-bar[0].open; //--- 최근 바 몸체(인덱스 1)이 이전 ATR 값(인덱스 2)을 초과하면 거래 시그널이 수신됩니다 if(MathAbs(last_body)>kATR*last_atr) res=last_body>0?1:-1; // 상향 캔들에 대해선 양의 값 } else PrintFormat("%s: 최근 막대 수령에 실패! Error",__FUNCTION__,GetLastError()); } else PrintFormat("%s: ATR 지표 값 수신 실패! Error",__FUNCTION__,GetLastError()); //--- 역거래 모드가 활성화된 경우 res=revers?-res:res; // 필요한 경우 시그널을 역방향으로(1이 아닌 -1을 반환하고 그 반대로도 반환) //--- 거래 시그널 값을 반환 return (res); } //+------------------------------------------------------------------+ //| 새 막대가 나타나면 'true'를 반환 | //+------------------------------------------------------------------+ bool isNewBar(const bool print_log=true) { static datetime bartime=0; // 현재 막대의 오픈 시간 저장 //--- 0바의 오픈 시간 획득 datetime currbar_time=iTime(_Symbol,_Period,0); //--- 오픈 시간이 변경된 경우, 새 막대가 도착한 것입니다 if(bartime!=currbar_time) { bartime=currbar_time; lastbar_timeopen=bartime; //--- 로그에서의 새 막대의 오픈 시간에 데이터 표시 if(print_log && !(MQLInfoInteger(MQL_OPTIMIZATION)||MQLInfoInteger(MQL_TESTER))) { //--- 새 막대를 열고 메시지를 표시 PrintFormat("%s: new bar on %s %s opened at %s",__FUNCTION__,_Symbol, StringSubstr(EnumToString(_Period),7), TimeToString(TimeCurrent(),TIME_SECONDS)); //--- 최근 틱의 데이터 획득 MqlTick last_tick; if(!SymbolInfoTick(Symbol(),last_tick)) Print("SymbolInfoTick() failed, error = ",GetLastError()); //--- 마지막 틱 시간 표시(밀리초) PrintFormat("Last tick was at %s.%03d", TimeToString(last_tick.time,TIME_SECONDS),last_tick.time_msc%1000); } //--- 새로운 막대가 있습니다 return (true); } //--- 새로운 막대가 없습니다 return (false); } //+------------------------------------------------------------------+ //| 지정된 볼륨으로 시장 가격으로 구매 | //+------------------------------------------------------------------+ bool Buy(double volume,ulong deviation=10,ulong magicnumber=0) { //--- 시장 가격에 매수 return (MarketOrder(ORDER_TYPE_BUY,volume,deviation,magicnumber)); } //+------------------------------------------------------------------+ //| 지정된 수량으로 시장 가격으로 판매 | //+------------------------------------------------------------------+ bool Sell(double volume,ulong deviation=10,ulong magicnumber=0) { //--- 시장 가격에 매도 return (MarketOrder(ORDER_TYPE_SELL,volume,deviation,magicnumber)); } //+------------------------------------------------------------------+ //| 막대에서 유지 시간별로 포지션 마감 | //+------------------------------------------------------------------+ void ClosePositionsByBars(int holdtimebars,ulong deviation=10,ulong magicnumber=0) { int total=PositionsTotal(); // 오픈 포지션의 수 //--- 오픈 포지션 반복 for(int i=total-1; i>=0; i--) { //--- 포지션 매개 변수 ulong position_ticket=PositionGetTicket(i); // 포지션 티켓 string position_symbol=PositionGetString(POSITION_SYMBOL); // 심볼 ulong magic=PositionGetInteger(POSITION_MAGIC); // 포지션 MagicNumber datetime position_open=(datetime)PositionGetInteger(POSITION_TIME); // 포지션 오픈 시간 int bars=iBarShift(_Symbol,PERIOD_CURRENT,position_open)+1; // 몇 개 막대 전에 포지션이 오픈되었는지 //--- MagicNumber와 심볼이 일치하는 동안 포지션의 수명이 이미 큰 경우 if(bars>holdtimebars && magic==magicnumber && position_symbol==_Symbol) { int digits=(int)SymbolInfoInteger(position_symbol,SYMBOL_DIGITS); // 소수 자릿수 double volume=PositionGetDouble(POSITION_VOLUME); // 포지션 볼륨 ENUM_POSITION_TYPE type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); // 포지션 유형 string str_type=StringSubstr(EnumToString(type),14); StringToLower(str_type); // 올바른 메시지 형식을 위해 텍스트 대소문자를 낮춤 PrintFormat("클로즈 포지션 #%I64u %s %s %.2f", position_ticket,position_symbol,str_type,volume); //--- 주문 유형을 설정하고 거래 요청을 전송 if(type==POSITION_TYPE_BUY) MarketOrder(ORDER_TYPE_SELL,volume,deviation,magicnumber,position_ticket); else MarketOrder(ORDER_TYPE_BUY,volume,deviation,magicnumber,position_ticket); } } } //+------------------------------------------------------------------+ //| 거래 요청을 준비 후 전송 | //+------------------------------------------------------------------+ bool MarketOrder(ENUM_ORDER_TYPE type,double volume,ulong slip,ulong magicnumber,ulong pos_ticket=0) { //--- 구조 선언 및 초기화 MqlTradeRequest request={0}; MqlTradeResult result={0}; double price=SymbolInfoDouble(Symbol(),SYMBOL_BID); if(type==ORDER_TYPE_BUY) price=SymbolInfoDouble(Symbol(),SYMBOL_ASK); //--- 요청 파라미터 request.action =TRADE_ACTION_DEAL; // 거래 작업 유형 request.position =pos_ticket; // 마감의 경우 포지션 티켓 request.symbol =Symbol(); // 심볼 request.volume =volume; // 볼륨 request.type =type; // 주문 유형 request.price =price; // 거래 가격 request.deviation=slip; // 가격에서 허용 가능한 편차 request.magic =magicnumber; // 주문 MagicNumber //--- 요청 전송 if(!OrderSend(request,result)) { //--- 실패에 대한 데이터 표시 PrintFormat("OrderSend %s %s %.2f at %.5f error %d", request.symbol,EnumToString(type),volume,request.price,GetLastError()); return (false); } //--- 성공적 작업 통지 PrintFormat("retcode=%u deal=%I64u order=%I64u",result.retcode,result.deal,result.order); return (true); } |