見出し画像

【第1回】MQL4入門講座:ChatGPTでEAを作ろう!EAのロジックの構築部分について解説


ChatGPT担当のナナミです。

前回の動画では、カギ足ロジックを使用したEAの開発を行いました。

本日から、新連載としてMQL4入門講座:ChatGPTでEAを作ろう!というタイトルで動画を撮っていこうと思います。

この動画を見ることで、EA開発未経験の人でも簡単にEA開発できるようになりますので、よろしくお願いします。

GPTsのリンクはこちら(GPT Store)
https://chatgpt.com/g/g-VYNEupgws-masayan-ea-generator-for-mt4-ver1-01

■ブログURL
https://fx.reform-network.net/2024/10/16/mt4初心者向け!gptsのmasayan-ea-generatorを使った自動売買講座/

■前回記事 カギ足ロジックを使用したEAの開発
https://note.com/aimjey/n/n7264d0c3d130

第一回の動画ということで、ロジックの構築部分について解説します。

EA開発を行ううえで一番大切なことは、ロジックの構築になります。

私が開発したChatGPTのカスタムGPT「Masayan EA Generator for MT4」を使用すれば、ロジック部分のコーディングは簡単に行えます。

つまり、アイデアとロジックを日本語でChatGPTに伝えるだけで、あなたの代わりにChatGPTがプログラミングを行ってくれるわけです。

ですがロジックの核となる部分、どこで何の処理を行っているのか分からないとEAが作れません。

Masayan EA Generator for MT4は、EAの元になるテンプレートを元にエントリーと決済の部分を書き変えるGPTsです。

MT4のインジケーターやEAはティックが動くたびに、コードを上から順番に読み取り実行します。

エントリーやイグジットの部分はサブルーチンとして関数化することで、ロングの時は新規のロングエントリーのサブルーチンを呼び出したり、ショートのポジションを決済したりといった処理をします。

人によっては、ロジック部分を関数にまとめる人がいます。

そうすると、プログラムソースコードの途中でサブルーチンを呼び出す処理が入り、ロジック部分が何個かに分かれてしまいます。

Masayan EA Generator for MT4のテンプレートでは、ロジック部分は1ヵ所にまとめて記載するようにしています。

これをインライン化(関数を使わない)といいます。

インライン化のメリットとして処理の高速化があります。

関数の呼び出しでは、スタックの操作や引数の受け渡しなどで処理が発生します。

インライン化することで、余計な処理がなくなり、EAが高速で動きます。

処理が増えるとバックテストで時間がかかるとか、VPSサーバーの処理が重いなどの原因となります。

インライン化は、MQL4プログラミングにおいて高速化の重要な手法となりますので、覚えておいてください。

さて、前回完成したコードは、カギ足ロジックによるシグナル生成部分がサブルーチンとして作成されています。

前回作成したEAのバックテスト(TDS変動全ティックバックテスト)

//---START--------
#property copyright "Copyright 2024, Masayan."
#property version   "1.01"
#property strict
#property description "https://fx.reform-network.net"

extern int Magic = 20240615;  // Magic number
extern double Lots = 0.1;     // ロットサイズ
extern double StopLossRequest = 4.0;   // ストップロス幅
extern double TakeProfitRequest = 5.0; // テイクプロフィット幅
extern int MaxSpread = 50;     // 最大スプレッド
extern int MaxError = 100;     // 最大エラーカウント
extern double GridSize = 0.05;  // カギ足のサイズ(最小値0.01=1pips 推奨値0.05=5pips)
extern string CommentPositions = "Grid Rash";

//ここから改変可能
double lastBrickHigh = 0;  // 前回のカギ足高値
double lastBrickLow = 0;   // 前回のカギ足安値
//ここまで改変可能

string tmpstr;
string error_msg;
string spread_msg;
string position_msg;
bool LongSign = false;
bool ShortSign = false;
double Pips = 0.01;
int e = 0;
int Adjusted_Slippage = 0;

void OnTick()
{
    if (Bars < 10) return;

    double currentPrice = iClose(NULL, 0, 0);
    double Info_Spread = MarketInfo(Symbol(), MODE_SPREAD);
string value = Symbol();
string target = "JPY";
int pos = StringFind(value, target);
double Symbol_RATE = Close[1];
if(pos > 0){
Pips = 1.00;// ドルストレートは0.010(100pips)、クロス円は1.0(100pips)で判定
}else if(Symbol_RATE > 10 && Symbol_RATE <= 100){
Pips = 0.1;
}else if(Symbol_RATE > 100 && Symbol_RATE <= 1000){
Pips = 1.0;
}else if(Symbol_RATE > 1000 && Symbol_RATE <= 10000){
Pips = 10.0;
}else if(Symbol_RATE > 10000 && Symbol_RATE <= 100000){
Pips = 100.0;
}else if(Symbol_RATE > 100000){
Pips = 1000.0;
}
    // スプレッドのチェック
    if (MaxSpread < Info_Spread) {
        LongSign = false;
        ShortSign = false;
        spread_msg = "Max spread Orber\n";
    } else {
        spread_msg = "";
    }

    // カギ足ロジックによるシグナル生成
    GenerateRenkoSignals(currentPrice);

    if (Hour() == 4 && Minute() == 0 && Seconds() < 6) e = 0;
    if (Hour() == 11 && Minute() == 0 && Seconds() < 6) e = 0;
    if (Hour() == 18 && Minute() == 0 && Seconds() < 6) e = 0;

    if (e > MaxError) {
        LongSign = false;
        ShortSign = false;
        error_msg = "Continuous order count limit\n";
    } else {
        error_msg = "";
    }

    if (LongSign) position_msg = "LongSign = true\n";
    else if (ShortSign) position_msg = "ShortSign = true\n";
    else position_msg = "No Sign\n";

    // 売買指示
    if (LongSign) {
        if (CalculateCurrentOrders() == 0) {
            CheckForOpenLong();
            e++;
        } else {
            CloseShortPosition();
        }
    }

    if (ShortSign) {
        if (CalculateCurrentOrders() == 0) {
            CheckForOpenShort();
            e++;
        } else {
            CloseLongPosition();
        }
    }

    tmpstr = StringConcatenate(error_msg, spread_msg, position_msg);
    Comment(tmpstr);
}

// カギ足ロジックを生成する関数
void GenerateRenkoSignals(double price)
{

    // 初回は基準の高値・安値を設定
    if (lastBrickHigh == 0 && lastBrickLow == 0) {
        lastBrickHigh = price;
        lastBrickLow = price;
        return;
    }

    // カギ足の生成条件チェック
      if (price >= lastBrickHigh + (GridSize * Pips) && Seconds() < 3 ) { 
        ShortSign = true;
        LongSign = false;
        lastBrickHigh = price;
        lastBrickLow = price - (GridSize * Pips);
    } else if (price <= lastBrickLow - (GridSize * Pips) && Seconds() < 3 ) {
        LongSign = true;
        ShortSign = false;
        lastBrickLow = price;
        lastBrickHigh = price + (GridSize * Pips);
    } else {
        LongSign = false;
        ShortSign = false;
    }
}



/*ここに決済ロジックを挿入*/


// 保有中のポジションを計算
int CalculateCurrentOrders(void)
{
    int buys = 0;
    int sells = 0;
    for (int icount = 0; icount < OrdersTotal(); icount++) {
        if (!OrderSelect(icount, SELECT_BY_POS, MODE_TRADES)) break;
        if (OrderSymbol() == Symbol() && OrderMagicNumber() == Magic) {
            if (OrderType() == OP_BUY) buys++;
            if (OrderType() == OP_SELL) sells++;
        }
    }
    return (buys > 0) ? buys : -sells;
}

//+------------------------------------------------------------------+
//| ロングオーダー処理                                               |
//+------------------------------------------------------------------+
void CheckForOpenLong() {
    int    res;      // オーダー送信戻り値用
    double entrylot; // オーダー用ロット
    entrylot = NormalizeDouble(Lots,2); // 入力パラメータのロット数を、小数点第二位で正規化
//■□■□■□■□■ ロングエントリー ■□■□■□■□■
        res = OrderSend(
                        Symbol(),       // 現在の通貨ペア
                        OP_BUY,         // ロングエントリー(成行注文)
                        entrylot,        // ロット設定
                        Ask,            // 現在の買値で発注
                        Adjusted_Slippage,// スリップページ
                        Ask - (Pips * StopLossRequest),              // ストップロス設定
                        Ask + (Pips * TakeProfitRequest),              // リミット設定
                        CommentPositions,       // ロングコメント
                        Magic,        // マジックナンバー
                        0,              // 有効期限:無し
                        clrRed);          // チャート上の注文矢印の色:赤
        return; // 関数処理終了
}

//+------------------------------------------------------------------+
//| ショートオーダー処理                                             |
//+------------------------------------------------------------------+
void CheckForOpenShort() {
    int    res;      // オーダー送信戻り値用
    double entrylot; // オーダー用ロット
    entrylot = NormalizeDouble(Lots,2); // 入力パラメータのロット数を、小数点第二位で正規化
//■□■□■□■□■ ショートエントリー ■□■□■□■□■
        res = OrderSend(
                        Symbol(),        // 現在の通貨ペア
                        OP_SELL,         // ショートエントリー(成行注文)
                        entrylot,        // ロット設定
                        Bid,             // 現在の売値で発注
                        Adjusted_Slippage,// スリップページ
                        Bid + (Pips * StopLossRequest),               // ストップロス設定
                        Bid - (Pips * TakeProfitRequest),               // リミット設定
                        CommentPositions,      // ショートコメント
                        Magic,         // マジックナンバー
                        0,               // 有効期限:無し
                        clrBlue);            // チャート上の注文矢印の色:青
        return; // 関数処理終了
}

// ショートポジションをクローズ
void CloseShortPosition()
{
    for (int icount = 0; icount < OrdersTotal(); icount++) {
        if (!OrderSelect(icount, SELECT_BY_POS, MODE_TRADES)) break;
        if (OrderMagicNumber() != Magic || OrderSymbol() != Symbol()) continue;
        if (OrderType() == OP_SELL) {
            if (!OrderClose(OrderTicket(), OrderLots(), Ask, Adjusted_Slippage, clrBlue)) 
                Print("エラーコード=", GetLastError());
            break;
        }
    }
}

// ロングポジションをクローズ
void CloseLongPosition()
{
    for (int icount = 0; icount < OrdersTotal(); icount++) {
        if (!OrderSelect(icount, SELECT_BY_POS, MODE_TRADES)) break;
        if (OrderMagicNumber() != Magic || OrderSymbol() != Symbol()) continue;
        if (OrderType() == OP_BUY) {
            if (!OrderClose(OrderTicket(), OrderLots(), Bid, Adjusted_Slippage, clrRed)) 
                Print("エラーコード=", GetLastError());
            break;
        }
    }
}
//---END---

GenerateRenkoSignalsという関数を呼び出す感じですね。

GenerateRenkoSignalsは、ソースコードの真ん中より少し下の方のvoid GenerateRenkoSignalsに記述されています。

このまま開発を進めていってもいいのですが、ロジック部分をテンプレートの本来の位置に移動させたいと思います。

修正したEAのソースコードは以下の通りです。

今回作成したEAのバックテスト(TDS変動全ティックバックテスト)

//---START--------
#property copyright "Copyright 2024, Masayan."
#property version   "1.01"
#property strict
#property description "https://fx.reform-network.net"

extern int Magic = 20240615;  // Magic number
extern double Lots = 0.1;     // ロットサイズ
extern double StopLossRequest = 4.0;   // ストップロス幅
extern double TakeProfitRequest = 5.0; // テイクプロフィット幅
extern int MaxSpread = 50;     // 最大スプレッド
extern int MaxError = 100;     // 最大エラーカウント
extern double GridSize = 0.05;  // カギ足のサイズ(最小値0.01=1pips 推奨値0.05=5pips)
extern string CommentPositions = "Grid Rash";

//ここから改変可能
double lastBrickHigh = 0;  // 前回のカギ足高値
double lastBrickLow = 0;   // 前回のカギ足安値
//ここまで改変可能

string tmpstr;
string error_msg;
string spread_msg;
string position_msg;
bool LongSign = false;
bool ShortSign = false;
double Pips = 0.01;
int e = 0;
int Adjusted_Slippage = 0;

void OnTick()
{
    if (Bars < 10) return;

    double currentPrice = iClose(NULL, 0, 0);
    double Info_Spread = MarketInfo(Symbol(), MODE_SPREAD);
string value = Symbol();
string target = "JPY";
int pos = StringFind(value, target);
double Symbol_RATE = Close[1];
if(pos > 0){
Pips = 1.00;// ドルストレートは0.010(100pips)、クロス円は1.0(100pips)で判定
}else if(Symbol_RATE > 10 && Symbol_RATE <= 100){
Pips = 0.1;
}else if(Symbol_RATE > 100 && Symbol_RATE <= 1000){
Pips = 1.0;
}else if(Symbol_RATE > 1000 && Symbol_RATE <= 10000){
Pips = 10.0;
}else if(Symbol_RATE > 10000 && Symbol_RATE <= 100000){
Pips = 100.0;
}else if(Symbol_RATE > 100000){
Pips = 1000.0;
}



// カギ足ロジックを生成する

    // 初回は基準の高値・安値を設定
    if (lastBrickHigh == 0 && lastBrickLow == 0) {
        lastBrickHigh = currentPrice;
        lastBrickLow = currentPrice;
        return;
    }

    // カギ足の生成条件チェック
    if (currentPrice >= lastBrickHigh + (GridSize * Pips) && Seconds() < 3) { 
        ShortSign = true;
        LongSign = false;
        lastBrickHigh = currentPrice;
        lastBrickLow = currentPrice - (GridSize * Pips);
    } else if (currentPrice <= lastBrickLow - (GridSize * Pips) && Seconds() < 3) {
        LongSign = true;
        ShortSign = false;
        lastBrickLow = currentPrice;
        lastBrickHigh = currentPrice + (GridSize * Pips);
    } else {
        LongSign = false;
        ShortSign = false;
    }


/*ここに決済ロジックを挿入*/



    // スプレッドのチェック
    if (MaxSpread < Info_Spread) {
        LongSign = false;
        ShortSign = false;
        spread_msg = "Max spread Orber\n";
    } else {
        spread_msg = "";
    }

    if (Hour() == 4 && Minute() == 0 && Seconds() < 6) e = 0;
    if (Hour() == 11 && Minute() == 0 && Seconds() < 6) e = 0;
    if (Hour() == 18 && Minute() == 0 && Seconds() < 6) e = 0;

    if (e > MaxError) {
        LongSign = false;
        ShortSign = false;
        error_msg = "Continuous order count limit\n";
    } else {
        error_msg = "";
    }

    if (LongSign) position_msg = "LongSign = true\n";
    else if (ShortSign) position_msg = "ShortSign = true\n";
    else position_msg = "No Sign\n";

    // 売買指示
    if (LongSign) {
        if (CalculateCurrentOrders() == 0) {
            CheckForOpenLong();
            e++;
        } else {
            CloseShortPosition();
        }
    }

    if (ShortSign) {
        if (CalculateCurrentOrders() == 0) {
            CheckForOpenShort();
            e++;
        } else {
            CloseLongPosition();
        }
    }

    tmpstr = StringConcatenate(error_msg, spread_msg, position_msg);
    Comment(tmpstr);
}




// 保有中のポジションを計算
int CalculateCurrentOrders(void)
{
    int buys = 0;
    int sells = 0;
    for (int icount = 0; icount < OrdersTotal(); icount++) {
        if (!OrderSelect(icount, SELECT_BY_POS, MODE_TRADES)) break;
        if (OrderSymbol() == Symbol() && OrderMagicNumber() == Magic) {
            if (OrderType() == OP_BUY) buys++;
            if (OrderType() == OP_SELL) sells++;
        }
    }
    return (buys > 0) ? buys : -sells;
}

//+------------------------------------------------------------------+
//| ロングオーダー処理                                               |
//+------------------------------------------------------------------+
void CheckForOpenLong() {
    int    res;      // オーダー送信戻り値用
    double entrylot; // オーダー用ロット
    entrylot = NormalizeDouble(Lots,2); // 入力パラメータのロット数を、小数点第二位で正規化
//■□■□■□■□■ ロングエントリー ■□■□■□■□■
        res = OrderSend(
                        Symbol(),       // 現在の通貨ペア
                        OP_BUY,         // ロングエントリー(成行注文)
                        entrylot,        // ロット設定
                        Ask,            // 現在の買値で発注
                        Adjusted_Slippage,// スリップページ
                        Ask - (Pips * StopLossRequest),              // ストップロス設定
                        Ask + (Pips * TakeProfitRequest),              // リミット設定
                        CommentPositions,       // ロングコメント
                        Magic,        // マジックナンバー
                        0,              // 有効期限:無し
                        clrRed);          // チャート上の注文矢印の色:赤
        return; // 関数処理終了
}

//+------------------------------------------------------------------+
//| ショートオーダー処理                                             |
//+------------------------------------------------------------------+
void CheckForOpenShort() {
    int    res;      // オーダー送信戻り値用
    double entrylot; // オーダー用ロット
    entrylot = NormalizeDouble(Lots,2); // 入力パラメータのロット数を、小数点第二位で正規化
//■□■□■□■□■ ショートエントリー ■□■□■□■□■
        res = OrderSend(
                        Symbol(),        // 現在の通貨ペア
                        OP_SELL,         // ショートエントリー(成行注文)
                        entrylot,        // ロット設定
                        Bid,             // 現在の売値で発注
                        Adjusted_Slippage,// スリップページ
                        Bid + (Pips * StopLossRequest),               // ストップロス設定
                        Bid - (Pips * TakeProfitRequest),               // リミット設定
                        CommentPositions,      // ショートコメント
                        Magic,         // マジックナンバー
                        0,               // 有効期限:無し
                        clrBlue);            // チャート上の注文矢印の色:青
        return; // 関数処理終了
}

// ショートポジションをクローズ
void CloseShortPosition()
{
    for (int icount = 0; icount < OrdersTotal(); icount++) {
        if (!OrderSelect(icount, SELECT_BY_POS, MODE_TRADES)) break;
        if (OrderMagicNumber() != Magic || OrderSymbol() != Symbol()) continue;
        if (OrderType() == OP_SELL) {
            if (!OrderClose(OrderTicket(), OrderLots(), Ask, Adjusted_Slippage, clrBlue)) 
                Print("エラーコード=", GetLastError());
            break;
        }
    }
}

// ロングポジションをクローズ
void CloseLongPosition()
{
    for (int icount = 0; icount < OrdersTotal(); icount++) {
        if (!OrderSelect(icount, SELECT_BY_POS, MODE_TRADES)) break;
        if (OrderMagicNumber() != Magic || OrderSymbol() != Symbol()) continue;
        if (OrderType() == OP_BUY) {
            if (!OrderClose(OrderTicket(), OrderLots(), Bid, Adjusted_Slippage, clrRed)) 
                Print("エラーコード=", GetLastError());
            break;
        }
    }
}
//---END---

始値のみのバックテスト結果では、プロフィットファクターは1.08です。

始値のみのトレード回数は22861回です。


というわけで、次回の動画ではGrid Rashにテクニカル指標を用いた手法を追加して、成績が改善できるかやってみたいと思います。


EA開発に自信のある方は、ロジックのところを変更するなどしてカスタマイズしてみてください。

好成績のEAが完成した際には、コメント頂けると幸いです。


他にも、こんな手法EAにしてほしいみたいなリクエストがあれば、コメントお願いします。


【免責事項】
・本GPTsについて、正当性を保証するものではありません。
・本GPTsを利用して損失を被った場合でも一切の責任を負いません。
・投資の決定は、自己判断 自己責任でお願いします。

この記事が気に入ったらサポートをしてみませんか?