//+------------------------------------------------------------------+
//| OnTester_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 "OnTester() 핸들러 포함 샘플 EA"
#property description "커스텀 최적화 기준으로, "
#property description "밸런스 그래프 선형 회귀의 비율"
#property description "평균 제곱근 편차로 나눈 값이 반환됩니다."
//--- 거래 작업을 위한 등급을 포함
#include <Trade\Trade.mqh>
//--- EA 입력 파라미터
input double Lots = 0.1; // 볼륨
input int Slippage = 10; // 허용가능 저하
input int MovingPeriod = 80; // 이동 평균 기간
input int MovingShift = 6; // 이동 평균 시프트
//--- 글로벌 변수
int IndicatorHandle=0; // 지표 처리
bool IsHedging=false; // 계정의 플래그
CTrade trade; // 거래 작업 수행용
//---
#define EA_MAGIC 18052018
//+------------------------------------------------------------------+
//| 포지션 오프닝 조건 점검 |
//+------------------------------------------------------------------+
void CheckForOpen(void)
{
MqlRates rt[2];
//--- 새 막대의 시작점에서만 거래
if(CopyRates(_Symbol,_Period,0,2,rt)!=2)
{
Print("CopyRates of ",_Symbol," failed, no history");
return;
}
//--- 틱 볼륨
if(rt[1].tick_volume>1)
return;
//--- 이동 평균 값 수신
double ma[1];
if(CopyBuffer(IndicatorHandle,0,1,1,ma)!=1)
{
Print("CopyBuffer from iMA failed, no data");
return;
}
//--- 신호 유무 점검
ENUM_ORDER_TYPE signal=WRONG_VALUE;
//--- 캔들이 더 고점에서 열렸지만, 이동평균보다 다 저점에서 닫혔음
if(rt[0].open>ma[0] && rt[0].close<ma[0])
signal=ORDER_TYPE_BUY; // 매수 시그널
else // 캔들이 더 저점에서 열렸지만, 이동평균보다 더 고점에서 닫혔음
{
if(rt[0].open<ma[0] && rt[0].close>ma[0])
signal=ORDER_TYPE_SELL;// 매도 시그널
}
//--- 추가 점검
if(signal!=WRONG_VALUE)
{
if(TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) && Bars(_Symbol,_Period)>100)
{
double price=SymbolInfoDouble(_Symbol,signal==ORDER_TYPE_SELL ? SYMBOL_BID:SYMBOL_ASK);
trade.PositionOpen(_Symbol,signal,Lots,price,0,0);
}
}
//---
}
//+------------------------------------------------------------------+
//| 포지션 클로징 조건 점검 |
//+------------------------------------------------------------------+
void CheckForClose(void)
{
MqlRates rt[2];
//--- 새 막대의 시작점에서만 거래
if(CopyRates(_Symbol,_Period,0,2,rt)!=2)
{
Print("CopyRates of ",_Symbol," failed, no history");
return;
}
if(rt[1].tick_volume>1)
return;
//--- 이동 평균 값 수신
double ma[1];
if(CopyBuffer(IndicatorHandle,0,1,1,ma)!=1)
{
Print("CopyBuffer from iMA failed, no data");
return;
}
//--- PositionSelect()를 사용하여 이전에 이미 포지션이 선택되었음
bool signal=false;
long type=PositionGetInteger(POSITION_TYPE);
//--- 캔들이 더 고점에서 열렸지만, 이동평균보다 더 저점에서 닫혔음 - 숏 포지션 마감
if(type==(long)POSITION_TYPE_SELL && rt[0].open>ma[0] && rt[0].close<ma[0])
signal=true;
//--- 캔들이 더 저점에서 열렸지만, 이동평균보다 더 고점에서 닫혔음 - 롱 포지션 마감
if(type==(long)POSITION_TYPE_BUY && rt[0].open<ma[0] && rt[0].close>ma[0])
signal=true;
//--- 추가 점검
if(signal)
{
if(TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) && Bars(_Symbol,_Period)>100)
trade.PositionClose(_Symbol,Slippage);
}
//---
}
//+-------------------------------------------------------------------+
//| 계정 유형을 고려하여 포지션 선택: Netting 또는 Hedging |
//+-------------------------------------------------------------------+
bool SelectPosition()
{
bool res=false;
//--- Hedging 계정용 포지션 선택
if(IsHedging)
{
uint total=PositionsTotal();
for(uint i=0; i<total; i++)
{
string position_symbol=PositionGetSymbol(i);
if(_Symbol==position_symbol && EA_MAGIC==PositionGetInteger(POSITION_MAGIC))
{
res=true;
break;
}
}
}
//--- Netting 계정용 포지션 선택
else
{
if(!PositionSelect(_Symbol))
return(false);
else
return(PositionGetInteger(POSITION_MAGIC)==EA_MAGIC); //---check Magic number
}
//--- 실행 결과
return(res);
}
//+------------------------------------------------------------------+
//| Expert 초기화 함수 |
//+------------------------------------------------------------------+
int OnInit(void)
{
//--- 거래 유형 설정: Netting 또는 Hedging
IsHedging=((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING);
//--- 올바른 포지션 제어를 위해 개체 초기화
trade.SetExpertMagicNumber(EA_MAGIC);
trade.SetMarginMode();
trade.SetTypeFillingBySymbol(Symbol());
trade.SetDeviationInPoints(Slippage);
//--- 이동 평균 지표 생성
IndicatorHandle=iMA(_Symbol,_Period,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE);
if(IndicatorHandle==INVALID_HANDLE)
{
printf("iMA 지표 생성 에러");
return(INIT_FAILED);
}
//--- ok
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert 틱 함수 |
//+------------------------------------------------------------------+
void OnTick(void)
{
//--- 이미 포지션이 오픈된 경우 마감 조건을 점검하십시오
if(SelectPosition())
CheckForClose();
// 포지션 오프닝 조건 점검
CheckForOpen();
//---
}
//+------------------------------------------------------------------+
//| 테스터 함수 |
//+------------------------------------------------------------------+
double OnTester()
{
//--- 사용자 정의 기준 최적화 값(높을수록 좋음)
double ret=0.0;
//--- 거래 결과를 배열로 얻기
double array[];
double trades_volume;
GetTradeResultsToArray(array,trades_volume);
int trades=ArraySize(array);
//--- 거래가 10번 미만인 경우, 양성 결과가 나오지 않습니다
if(trades<10)
return (0);
//--- 거래당 평균 실적
double average_pl=0;
for(int i=0;i<ArraySize(array);i++)
average_pl+=array[i];
average_pl/=trades;
//--- 단일 테스트 모드에 대한 메시지를 표시합니다
if(MQLInfoInteger(MQL_TESTER) && !MQLInfoInteger(MQL_OPTIMIZATION))
PrintFormat("%s: Trades=%d, Average profit=%.2f",__FUNCTION__,trades,average_pl);
//--- 이익 그래프에 대한 선형 회귀 비율을 계산
double a,b,std_error;
double chart[];
if(!CalculateLinearRegression(array,chart,a,b))
return (0);
//--- 회귀선에서 차트 편차의 오차를 계산
if(!CalculateStdError(chart,a,b,std_error))
return (0);
//--- 표준 편차에 대한 추세 이익 비율을 계산
ret=(std_error == 0.0) ? a*trades : a*trades/std_error;
//--- 사용자 정의 기준 최적화 값 반환
return(ret);
}
//+------------------------------------------------------------------+
//| 거래에서의 수익/손실 배열 얻기 |
//+------------------------------------------------------------------+
bool GetTradeResultsToArray(double &pl_results[],double &volume)
{
//--- 전체 거래 내역 요청
if(!HistorySelect(0,TimeCurrent()))
return (false);
uint total_deals=HistoryDealsTotal();
volume=0;
//--- 내역에서 거래 수에 따라 마진으로 배열의 초기 크기를 설정
ArrayResize(pl_results,total_deals);
//--- 거래 결과를 확정하는 거래 카운터 - 수익 또는 손실
int counter=0;
ulong ticket_history_deal=0;
//--- 모든 거래 살펴보기
for(uint i=0;i<total_deals;i++)
{
//--- 딜 선택
if((ticket_history_deal=HistoryDealGetTicket(i))>0)
{
ENUM_DEAL_ENTRY deal_entry =(ENUM_DEAL_ENTRY)HistoryDealGetInteger(ticket_history_deal,DEAL_ENTRY);
long deal_type =HistoryDealGetInteger(ticket_history_deal,DEAL_TYPE);
double deal_profit =HistoryDealGetDouble(ticket_history_deal,DEAL_PROFIT);
double deal_volume =HistoryDealGetDouble(ticket_history_deal,DEAL_VOLUME);
//--- 저희는 오직 거래 작업에만 관심이 있습니다
if((deal_type!=DEAL_TYPE_BUY) && (deal_type!=DEAL_TYPE_SELL))
continue;
//--- 수익/손실을 확정하는 거래만
if(deal_entry!=DEAL_ENTRY_IN)
{
//--- 거래 결과를 배열에 기록하고 거래량을 늘림
pl_results[counter]=deal_profit;
volume+=deal_volume;
counter++;
}
}
}
//--- 배열의 최종 규모 설정
ArrayResize(pl_results,counter);
return (true);
}
//+------------------------------------------------------------------+
//| 선형 회귀 계산 y=a*x+b |
//+------------------------------------------------------------------+
bool CalculateLinearRegression(double &change[],double &chartline[],
double &a_coef,double &b_coef)
{
//--- 자료가 충분한지 확인
if(ArraySize(change)<3)
return (false);
//--- 누적 차트 배열 생성
int N=ArraySize(change);
ArrayResize(chartline,N);
chartline[0]=change[0];
for(int i=1;i<N;i++)
chartline[i]=chartline[i-1]+change[i];
//--- 이제 회귀 비율을 계산
double x=0,y=0,x2=0,xy=0;
for(int i=0;i<N;i++)
{
x=x+i;
y=y+chartline[i];
xy=xy+i*chartline[i];
x2=x2+i*i;
}
a_coef=(N*xy-x*y)/(N*x2-x*x);
b_coef=(y-a_coef*x)/N;
//---
return (true);
}
//+------------------------------------------------------------------+
//| 지정된 a 및 b에 대한 평균 제곱근 편차 계산 오류 |
//+------------------------------------------------------------------+
bool CalculateStdError(double &data[],double a_coef,double b_coef,double &std_err)
{
//--- 오차 제곱합
double error=0;
int N=ArraySize(data);
if(N<=2)
return (false);
for(int i=0;i<N;i++)
error+=MathPow(a_coef*i+b_coef-data[i],2);
std_err=MathSqrt(error/(N-2));
//---
return (true);
}
|