//+------------------------------------------------------------------+
//| TradeByATR.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 "EA 거래 예시 \"폭발적\" 캔들 방향"
#property description "\"폭발적\" 캔들의 몸체 크기가 k*ATR을 초과합니다"
#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;
//+------------------------------------------------------------------+
//| 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 OnDeinit(const int reason)
{
//--- EA 작업 종료 코드 통지
Print(__FILE__,": 초기화해제 근거 코드 = ",reason);
}
//+------------------------------------------------------------------+
//| 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);
}
|