トレードやったことがある方ならば、誰しもが一度はやった事がある移動平均線のクロスでのエントリー。MQL4でEAを作ってみました。やはり限界がありますね。めちゃ損します。ソースも公開しているので、いい感じで工夫点あれば、よかったらコメントで教えてください。
MQL4での移動平均線のクロス判定
クロスの判定は簡単です。以下の図のように、移動平均線のクロスは1本前の移動平均線と現在の移動平均線の数値が入れ替わります。この大小を比較すればOKです。
MQLで判定すると、以下のようになります。
//単純移動平均線
double ma_20 = iMA(NULL,PERIOD_CURRENT,20,0,MODE_SMA,PRICE_CLOSE,0); //20SMA
double ma_20_1 = iMA(NULL,PERIOD_CURRENT,20,0,MODE_SMA,PRICE_CLOSE,1); //1本前の20SMA
double ma_40 = iMA(NULL,PERIOD_CURRENT,40,0,MODE_SMA,PRICE_CLOSE,0); //40SMA
double ma_40_1 = iMA(NULL,PERIOD_CURRENT,40,0,MODE_SMA,PRICE_CLOSE,1); //1本前の40SMA
//ゴールデンクロス
if(ma_40_1 > ma_20_1 && ma_40 < ma_20){
//ロング処理
}
//デッドクロス
else if(ma_40_1 < ma_20_1 && ma_40 > ma_20){
//ショート処理
}
ソースコード
このままリアル口座で実施しても損します。あくまでも参考にしてください。
移動平均線Aと移動平均線Bのゴールデンクロスでロング、デッドクロスでショートのソースです。パラメーターで移動平均線値を入れれるようにしました。
#include <stdlib.mqh>
#property copyright "Copyright 2022, Yanoteck"
#property link "https://elite-collections.com/"
#property version "0.1"
#property strict
#define MAGIC 202211292329
extern double Lots = 0.01; //lots
extern int Slippage = 30; //slipage
extern double Profitrate = 50; //Take Profit 値幅(pips)
extern double SLrate = 25; //Stop Loss 損切り(pips)
extern int ma_A = 20; //移動平均線A
extern int ma_B = 40; //移動平均線B
//グローバル変数
//大量エントリー回避用(チケット番号)
int g_ticket_no = 0;
//時間足1本目だけ実施したい
int g_Volume = 0;
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
//---
g_ticket_no = 0;
g_Volume = 0;
//---
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
//---
g_ticket_no = 0;
g_Volume = 0;
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
//単純移動平均線
double sma_A = iMA(NULL,PERIOD_CURRENT,ma_A,0,MODE_SMA,PRICE_CLOSE,0); //SMA
double sma_A_1 = iMA(NULL,PERIOD_CURRENT,ma_A,0,MODE_SMA,PRICE_CLOSE,1); //1本前のSMA
double sma_B = iMA(NULL,PERIOD_CURRENT,ma_B,0,MODE_SMA,PRICE_CLOSE,0); //SMA
double sma_B_1 = iMA(NULL,PERIOD_CURRENT,ma_B,0,MODE_SMA,PRICE_CLOSE,1); //1本前のSMA
//---
if (Volume[0] < g_Volume) {
//バーの始まりで1回だけ処理したい内容
//オーダーの有無とフラグのチェック
if(g_ticket_no != 0){ //エントリー中?
//オーダー情報なし。途中でSL、TPで強制決済した
if(OrdersTotal() == 0){
//グローバル変数の初期化
g_ticket_no = 0;
}
}
//バーカウント初期化
g_Volume = 0;
}else{
//ゴールデンクロス
if(sma_B_1 > sma_A_1 && sma_B < sma_A){
//ロング処理
My_Long(Lots,Slippage,SLrate,Profitrate,MAGIC);
}
//デッドクロス
else if(sma_B_1 < sma_A_1 && sma_B > sma_A){
//ショート処理
My_Short(Lots,Slippage,SLrate,Profitrate,MAGIC);
}
//今のVolume[0]をセット
g_Volume = Volume[0];
}
}
//+------------------------------------------------------------------+
//| ロング
//| OP_BUYのエントリー。リトライあり
//| 引数: my_lot = ロット
//| my_slipage = スリッページ
//| my_sl = 損切り
//| my_tp = 利益
//|
//| 戻値: チケット番号
//+------------------------------------------------------------------+
int My_Long(double my_lot,int my_slipage , double my_sl, double my_tp, int my_magic){
int ticket_no=0; //ticket no
int errorcode=0; // エラーコード
double losscut_normalize=0;
double profit_normalize=0;
double losscut_rate=0;
double profit_rate=0;
if(g_ticket_no != 0){
return -1;
}
// ロスカット価格
losscut_rate = Ask - ( 10 * Point() * my_sl);
// 決済価格
profit_rate = Ask + ( 10 * Point() * my_tp);
//正規化
losscut_normalize = NormalizeDouble(losscut_rate,int(MarketInfo(Symbol(),MODE_DIGITS)));
profit_normalize = NormalizeDouble(profit_rate,int(MarketInfo(Symbol(),MODE_DIGITS)));
//ロングエントリー処理
ticket_no = OrderSend(Symbol(),OP_BUY,my_lot,Ask,my_slipage,losscut_normalize,profit_normalize,"Buy order",my_magic,0,clrGreen);
printf("OrderSend OP_BUY , price=%f , sl=%f , tp=%f",Ask,losscut_normalize,profit_normalize);
if(ticket_no < 0){
errorcode = GetLastError(); // エラーコード取得
printf("Send Error! error_code:%d , detail:%s ",errorcode , ErrorDescription(errorcode));
//再設定
losscut_rate = Ask - ( 10 * Point() * my_sl);
profit_rate = Ask + ( 10 * Point() * my_tp);
losscut_normalize = NormalizeDouble(losscut_rate,int(MarketInfo(Symbol(),MODE_DIGITS)));
profit_normalize = NormalizeDouble(profit_normalize,int(MarketInfo(Symbol(),MODE_DIGITS)));
//オーダー送信と同時に損切り、利益設定エラー対策
ticket_no = OrderSend(Symbol(),OP_BUY,my_lot,Ask,my_slipage,0,0,"Buy order",my_magic,0,clrGreen);
printf("Resend OrderSend OP_BUY , price=%f , sl=%f , tp=%f",Ask,losscut_normalize,profit_normalize);
//オーダー修正
LimitStop_Set(ticket_no,OP_BUY,clrGreen);
}else {
printf("Send Done. Ticket NO = %d",ticket_no);
}
g_ticket_no = ticket_no;
return ticket_no;
}
//+------------------------------------------------------------------+
//| ショート
//| OP_SELLのエントリー。リトライあり
//| 引数: my_lot = ロット
//| my_slipage = スリッページ
//| my_sl = 損切り
//| my_tp = 利益
//|
//| 戻値: チケット番号
//+------------------------------------------------------------------+
int My_Short(double my_lot,int my_slipage , double my_sl, double my_tp, int my_magic){
//ticket no
int ticket_no=0;
int errorcode=0; // エラーコード
double losscut_normalize=0;
double profit_normalize=0;
double losscut_rate=0;
double profit_rate=0;
if(g_ticket_no != 0){
return -1;
}
// ロスカット価格
losscut_rate = Bid + ( 10 * Point() * my_sl);
// 決済価格
profit_rate = Bid - ( 10 * Point() * my_tp);
//正規化
losscut_normalize = NormalizeDouble(losscut_rate,Digits());
profit_normalize = NormalizeDouble(profit_rate,Digits());
//shortエントリー処理
ticket_no = OrderSend(Symbol(),OP_SELL,my_lot,Bid,my_slipage,losscut_normalize,profit_normalize,"Sell order",my_magic,0,clrRed);
printf("OrderSend OP_SELL , price=%f , sl=%f , tp=%f",Bid,losscut_normalize,profit_normalize);
//エラー?
if(ticket_no < 0){
errorcode = GetLastError(); // エラーコード取得
printf("Send Error! error_code:%d , detail:%s ",errorcode , ErrorDescription(errorcode));
// 再設定
losscut_rate = Bid + ( 10 * Point() * my_sl);
profit_rate = Bid - ( 10 * Point() * my_tp);
losscut_normalize = NormalizeDouble(losscut_rate,int(MarketInfo(Symbol(),MODE_DIGITS)));
profit_normalize = NormalizeDouble(profit_normalize,int(MarketInfo(Symbol(),MODE_DIGITS)));
//オーダー送信と同時に損切り、利益設定エラー対策
ticket_no = OrderSend(Symbol(),OP_SELL,my_lot,Bid,my_slipage,0,0,"Sell order",my_magic,0,clrRed);
printf("Resend sOrderSend OP_SELL , price=%f , sl=%f , tp=%f",Bid,losscut_normalize,profit_normalize);
//オーダー修正
LimitStop_Set(ticket_no,OP_SELL,clrRed);
}else {
printf("Send Done. Ticket NO = %d",ticket_no);
}
g_ticket_no = ticket_no;
return ticket_no;
}
//+------------------------------------------------------------------+
//| エントリー中のポジションのリミット・ストップを変更
//|
//| 引数 int in_ticket_no = Ticket no
//| int buysell_type = OP_SELL or OP_BUY
//| color mycolor = 表示色
//+------------------------------------------------------------------+
void LimitStop_Set( int in_ticket_no, int buysell_type , color mycolor) {
int modify_resend_num; //変更試行回数
bool modify_ret; //変更判定
int errorcode; //エラーコード
// bool selbool;
double limit_rate,stop_rate;
int my_ticket_no;
// オーダー中のチケット選択(チケットNo指定)
// selbool = OrderSelect(in_ticket_no, SELECT_BY_TICKET);
if (buysell_type == OP_BUY){
limit_rate = OrderOpenPrice() + ( 10 * Point() * Profitrate);
stop_rate = OrderOpenPrice() - ( 10 * Point() * SLrate);
}else{
limit_rate = OrderOpenPrice() - ( 10 * Point() * Profitrate);
stop_rate = OrderOpenPrice() + ( 10 * Point() * SLrate);
}
//リミット価格のNormalize(正規化)
limit_rate = NormalizeDouble(limit_rate , Digits() );
//ストップロス価格を正規化
stop_rate = NormalizeDouble(stop_rate , Digits() );
//オーダーチケット
my_ticket_no = OrderTicket();
//オーダーの修正
for( modify_resend_num = 0; modify_resend_num < 30; modify_resend_num++ ) {
modify_ret = OrderModify(
my_ticket_no, // チケットNo
OrderOpenPrice(), // 注文価格
stop_rate, // ストップロス価格
limit_rate, // リミット価格
OrderExpiration(), // 有効期限
mycolor // 色
);
printf("OrderModify ticket_no:%d , sl=%f , tp=%f",my_ticket_no , stop_rate,limit_rate);
if ( modify_ret == false ) { // 注文変更拒否
Sleep(300); // 300msec待ち
errorcode = GetLastError(); // エラーコード取得
printf( "[%d]Modify Error! error_code:%d ,detail:%s ",
modify_resend_num+1, errorcode , ErrorDescription(errorcode));
} else { // 決済注文約定
Print("Done. Ticket NO=",in_ticket_no);
break;
}
}
}
//+------------------------------------------------------------------+
//| ポジション強制決済 Long
//| 引数 int my_magic = マジックナンバー
//+------------------------------------------------------------------+
void My_force_close_long(int my_magic){
int i;
int my_ticket_no;
double my_lots;
double my_price = NormalizeDouble(Ask,int(MarketInfo(Symbol(),MODE_DIGITS)));
bool chk;
int errorcode;
for(i=0;i<OrdersTotal();i++){
if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES) == false){
break;
}
if(OrderMagicNumber() != my_magic || OrderSymbol() != Symbol()){
continue;
}
my_ticket_no = OrderTicket();
my_lots = OrderLots();
//買いポジションのチェック
if(OrderType() == OP_BUY){
chk = OrderClose(my_ticket_no,my_lots,my_price,Slippage,White);
printf("OrderClose ret=%d , ticket_no=%d , lots=%f , BUY price=%f",chk,my_ticket_no,my_lots,my_price);
if(chk == False){
errorcode = GetLastError(); // エラーコード取得
printf("OrderClose Error! error_code:%d , detail:%s ",errorcode , ErrorDescription(errorcode));
}else{
printf("OrderClose Done!");
}
break;
}
break;
}
g_ticket_no = 0;
}
//+------------------------------------------------------------------+
//| ポジション強制決済 short
//| 引数 int my_magic = マジックナンバー
//+------------------------------------------------------------------+
void My_force_close_short(int my_magic){
int i;
int my_ticket_no;
double my_lots;
double my_price = NormalizeDouble(Ask,int(MarketInfo(Symbol(),MODE_DIGITS)));
bool chk;
int errorcode;
for(i=0;i<OrdersTotal();i++){
if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES) == false){
break;
}
if(OrderMagicNumber() != my_magic || OrderSymbol() != Symbol()){
continue;
}
my_ticket_no = OrderTicket();
my_lots = OrderLots();
//売りポジションのチェック
if(OrderType() == OP_SELL){
chk = OrderClose(my_ticket_no,my_lots,my_price,Slippage,White);
printf("OrderClose ret=%d , ticket_no=%d , lots=%f , SELL price=%f",chk,my_ticket_no,my_lots,my_price);
if(chk == False){
errorcode = GetLastError(); // エラーコード取得
printf("OrderClose Error! error_code:%d , detail:%s ",errorcode , ErrorDescription(errorcode));
}else{
printf("OrderClose Done!");
}
break;
}
break;
}
g_ticket_no = 0;
}
前提
肝心な部分はOnTick内だけです。うちのブログで公開しているEAのベースソースを基本として組んでます。売買部分をラッピング化して、利確と損切りをpipsで指定出来るようにしてます。以下の記事にまとめました。
バックテストの結果
半年間(2022/6/1〜2022/11/29)でバックテストしてみました。意外にもプラスですが、問題点だらけです。
レポートの結果は以下のようになりました。プロフィットファクタは1.15、勝率は37.5%
問題点
とりあえずの問題点は以下のような感じです。
移動平均線が絡まるときエントリー繰り返す
目視で裁量でやってたら、絶対にエントリーしないタイミングです。どこでクロスしたのかもよくわからない絡まり具合ですが、プログラムは正確に判断して、こんな局面でもエントリーしてしまいます。
【解決済み】1つの足で何度もエントリー利確を繰り返す
OnTickでエントリーする場合の欠点なのですが、1つの足で利確した際には、何度もエントリーしてしまうことです。TP、SLを狭くすると、こんな感じになってしまいます。
2022.11.30 解決済み
ソースは修正しました。具体的には、足が変わった最初だけ処理させるようにしたら無事解決しました。
//バーの最初だけ処理
if (Volume[0] < g_Volume) {
//何か処理
//バーカウント初期化
g_Volume = 0;
}else{
//エントリー条件とか書く
//今のVolume[0]をセット
g_Volume = Volume[0];
}
参考にしたブログは以下の記事です。本当に感謝ですねー。
コメント