見出し画像

【第5回】ChatGPT最新AIモデル GPT-4oを使ったEA開発方法【EA開発】


ChatGPT担当のナナミです。

前回第4回の記事では、ChatGPTを使い有料版のEA「PerfectOrderGBPJPY」に匹敵する成績を叩き出すEAの開発に成功しました。

https://note.com/aimjey/n/n362911a625bf

しかしながら、エラー対策が不十分で、Lot数を10000ロットなど通常では絶対に設定しない数字を入れた時など、オーダーの無限ループに陥ります。
こうしたバグ対策はEA開発者として必須なのですが、バグ対策が不十分であることがChatGPTを使ったEA開発での最大の問題点と言えるでしょう。

さて困りました。
どの様にChatGPTに伝えればよいのでしょうか?

修正する内容が複雑でChatGPTに正確なプロンプトを送信することができません。

したがって、今回は簡易的な修正案として、 Lotsをextern double Lots = 0.1;とし、入力できる最大ロットを10.0までにしてください。
最小ロット数は0.01です。
と送信してみました。

//+------------------------------------------------------------------+
//|                                               Moving Average.mq4 |
//|                                         https://note.com/aimjey/ |
//+------------------------------------------------------------------+

// EA識別用マジックナンバー
extern int MAGICMA = 20240529;

// ロットサイズの入力
input double Lots = 0.1;

// 移動平均線の期間を設定
extern int ShortMovingPeriod = 20;  // 短期移動平均線の期間
extern int MiddleMovingPeriod = 75; // 中期移動平均線の期間
extern int LongMovingPeriod = 100;  // 長期移動平均線の期間

// 1時間あたりの注文回数の上限(プロパティ項目で設定可能)
input int MaxOrder = 20;  // 最小=10 最大=50 デフォルト=20

int OrderCount = 0; // 現在の注文数をカウント
datetime LastOrderTime = 0; // 最後に注文を行った時間

//+------------------------------------------------------------------+
//| Calculate open positions                                         |
//| 現在のオープンポジションを計算する関数                             |
//+------------------------------------------------------------------+
int CalculateCurrentOrders(string symbol)
{
    int buys = 0, sells = 0; // 買いポジションと売りポジションのカウンタを初期化

    // 全てのオープンオーダーをチェック
    for (int i = 0; i < OrdersTotal(); i++)
    {
        if (OrderSelect(i, SELECT_BY_POS, MODE_TRADES) == false) break; // オーダーが選択できない場合、ループを抜ける
        if (OrderSymbol() == Symbol() && OrderMagicNumber() == MAGICMA)
        {
            // 買いオーダーの場合、カウンタを増やす
            if (OrderType() == OP_BUY) buys++;
            // 売りオーダーの場合、カウンタを増やす
            if (OrderType() == OP_SELL) sells++;
        }
    }

    // 買いオーダーの数を返す。売りオーダーの場合は負の値を返す
    if (buys > 0) return (buys);
    else return (-sells);
}

//+------------------------------------------------------------------+
//| Calculate optimal lot size                                       |
//| 最適なロットサイズを計算する関数                                  |
//+------------------------------------------------------------------+
double LotsOptimized()
{
    // 入力パラメータのロット数を小数点第2位で正規化
    double lot = NormalizeDouble(Lots, 2);

    // 最小ロットサイズを取得
    double minLot = MarketInfo(Symbol(), MODE_MINLOT);
    // ロットステップを取得
    double lotStep = MarketInfo(Symbol(), MODE_LOTSTEP);

    // 最小ロットサイズ以上に調整
    if (lot < minLot) lot = minLot;
    // ロットステップに従って丸める
    lot = NormalizeDouble(lot, 2);
    return (lot);
}

//+------------------------------------------------------------------+
//| Check for open order conditions                                  |
//| 新しいオーダーを開く条件をチェックする関数                         |
//+------------------------------------------------------------------+
void CheckForOpen()
{
    double shortMA, middleMA, longMA; // 移動平均線の値を保存する変数
    int res;

    // 各移動平均線の値を取得
    shortMA = iMA(NULL, 0, ShortMovingPeriod, 0, MODE_SMA, PRICE_CLOSE, 0);
    middleMA = iMA(NULL, 0, MiddleMovingPeriod, 0, MODE_SMA, PRICE_CLOSE, 0);
    longMA = iMA(NULL, 0, LongMovingPeriod, 0, MODE_SMA, PRICE_CLOSE, 0);

    // 1時間あたりの注文回数の上限をチェック
    if (OrderCount < MaxOrder)
    {
        // 買いの条件(移動平均線のパーフェクトオーダー)
        if (shortMA > middleMA && middleMA > longMA)
        {
            // 買いオーダーを送信
            res = OrderSend(Symbol(), OP_BUY, LotsOptimized(), Ask, 3, 0, 0, "", MAGICMA, 0, Blue);
            if (res > 0)
            {
                OrderCount++;
                LastOrderTime = TimeCurrent();
            }
            else
            {
                Print("OrderSend error ", GetLastError()); // オーダー送信エラーを表示
            }
            return;
        }

        // 売りの条件(移動平均線のパーフェクトオーダー)
        if (shortMA < middleMA && middleMA < longMA)
        {
            // 売りオーダーを送信
            res = OrderSend(Symbol(), OP_SELL, LotsOptimized(), Bid, 3, 0, 0, "", MAGICMA, 0, Red);
            if (res > 0)
            {
                OrderCount++;
                LastOrderTime = TimeCurrent();
            }
            else
            {
                Print("OrderSend error ", GetLastError()); // オーダー送信エラーを表示
            }
            return;
        }
    }
}

//+------------------------------------------------------------------+
//| Check for close order conditions                                 |
//| オーダーを閉じる条件をチェックする関数                             |
//+------------------------------------------------------------------+
void CheckForClose()
{
    double shortMA, middleMA, longMA; // 移動平均線の値を保存する変数

    // 各移動平均線の値を取得
    shortMA = iMA(NULL, 0, ShortMovingPeriod, 0, MODE_SMA, PRICE_CLOSE, 0);
    middleMA = iMA(NULL, 0, MiddleMovingPeriod, 0, MODE_SMA, PRICE_CLOSE, 0);
    longMA = iMA(NULL, 0, LongMovingPeriod, 0, MODE_SMA, PRICE_CLOSE, 0);

    // 全てのオープンオーダーをチェック
    for (int i = 0; i < OrdersTotal(); i++)
    {
        if (OrderSelect(i, SELECT_BY_POS, MODE_TRADES) == false) break; // オーダーが選択できない場合、ループを抜ける

        if (OrderMagicNumber() != MAGICMA || OrderSymbol() != Symbol()) continue; // EAが管理するオーダーか確認

        // 買いオーダーの場合のクローズ条件
        if (OrderType() == OP_BUY)
        {
            // パーフェクトオーダーが崩れた場合
            if (shortMA < middleMA && middleMA < longMA)
            {
                if (!OrderClose(OrderTicket(), OrderLots(), Bid, 3, White))
                    Print("OrderClose error ", GetLastError()); // オーダークローズエラーを表示
            }
            break;
        }

        // 売りオーダーの場合のクローズ条件
        if (OrderType() == OP_SELL)
        {
            // パーフェクトオーダーが崩れた場合
            if (shortMA > middleMA && middleMA > longMA)
            {
                if (!OrderClose(OrderTicket(), OrderLots(), Ask, 3, White))
                    Print("OrderClose error ", GetLastError()); // オーダークローズエラーを表示
            }
            break;
        }
    }
}

//+------------------------------------------------------------------+
//| OnTick function                                                  |
//| 毎ティック実行される関数                                          |
//+------------------------------------------------------------------+
void OnTick()
{
    // 必要なバー数がない場合、または取引が許可されていない場合は何もしない
    if (Bars < 100 || !TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
        return;

    // 1時間ごとにOrderCountをリセット
    if (TimeCurrent() - LastOrderTime >= 3600)
    {
        OrderCount = 0;
    }

    // 現在のシンボルのオープンオーダーを計算
    if (CalculateCurrentOrders(Symbol()) == 0) 
        CheckForOpen(); // 新しいオーダーを開く条件をチェック
    else 
        CheckForClose(); // 既存のオーダーを閉じる条件をチェック
}

プロパティ項目で入力されたLot数が10.0以上の時はロット数が10.0に修正されます。
したがって1万Lotと入力しても、発注されるロット数は10ロットになります。

リアルマネーでの運用の際には、こうした不具合に注意して開発を行ってください。
ChatGPTが作ったプログラムなので、サポート先、問い合わせ先等はありません。
バグがあっても自力で解決するしかありません。

大きな金額で運用する場合は多少のコストはかかるものの、きちんとサポートが受けられるプラットフォームから有料のEAを購入することをオススメします。

わたしは、ゴゴジャンで有料のEAを販売しています。
また、noteでも無料でEAを配布しています。
わたしが開発したEAはバグチェック済みです。
万が一不具合があれば修正致しますので皆様のご利用を心よりお待ちしております。

続けて開発を行いたいと思います。

注文できる最小ロットサイズを0.01以上、最大のロットサイズを10.0に修正しました。

修正したプログラムはこちらです。

//+------------------------------------------------------------------+
//|                                               Moving Average.mq4 |
//|                                         https://note.com/aimjey/ |
//+------------------------------------------------------------------+

// EA識別用マジックナンバー
extern int MAGICMA = 20240529;

// ロットサイズの入力
extern double Lots = 0.1;

// 移動平均線の期間を設定
extern int ShortMovingPeriod = 20;  // 短期移動平均線の期間
extern int MiddleMovingPeriod = 75; // 中期移動平均線の期間
extern int LongMovingPeriod = 100;  // 長期移動平均線の期間

// 1時間あたりの注文回数の上限(プロパティ項目で設定可能)
input int MaxOrder = 20;  // 最小=10 最大=50 デフォルト=20

int OrderCount = 0; // 現在の注文数をカウント
datetime LastOrderTime = 0; // 最後に注文を行った時間

//+------------------------------------------------------------------+
//| Calculate open positions                                         |
//| 現在のオープンポジションを計算する関数                             |
//+------------------------------------------------------------------+
int CalculateCurrentOrders(string symbol)
{
    int buys = 0, sells = 0; // 買いポジションと売りポジションのカウンタを初期化

    // 全てのオープンオーダーをチェック
    for (int i = 0; i < OrdersTotal(); i++)
    {
        if (OrderSelect(i, SELECT_BY_POS, MODE_TRADES) == false) break; // オーダーが選択できない場合、ループを抜ける
        if (OrderSymbol() == Symbol() && OrderMagicNumber() == MAGICMA)
        {
            // 買いオーダーの場合、カウンタを増やす
            if (OrderType() == OP_BUY) buys++;
            // 売りオーダーの場合、カウンタを増やす
            if (OrderType() == OP_SELL) sells++;
        }
    }

    // 買いオーダーの数を返す。売りオーダーの場合は負の値を返す
    if (buys > 0) return (buys);
    else return (-sells);
}

//+------------------------------------------------------------------+
//| Calculate optimal lot size                                       |
//| 最適なロットサイズを計算する関数                                  |
//+------------------------------------------------------------------+
double LotsOptimized()
{
    // 入力パラメータのロット数を小数点第2位で正規化
    double lot = NormalizeDouble(Lots, 2);

    // 最小ロットサイズを取得
    double minLot = 0.01;
    // 最大ロットサイズを設定
    double maxLot = 10.0;
    
    // 最小ロットサイズ以上に調整
    if (lot < minLot) lot = minLot;
    // 最大ロットサイズ以下に調整
    if (lot > maxLot) lot = maxLot;
    // ロットステップに従って丸める
    lot = NormalizeDouble(lot, 2);
    return (lot);
}

//+------------------------------------------------------------------+
//| Check for open order conditions                                  |
//| 新しいオーダーを開く条件をチェックする関数                         |
//+------------------------------------------------------------------+
void CheckForOpen()
{
    double shortMA, middleMA, longMA; // 移動平均線の値を保存する変数
    int res;

    // 各移動平均線の値を取得
    shortMA = iMA(NULL, 0, ShortMovingPeriod, 0, MODE_SMA, PRICE_CLOSE, 0);
    middleMA = iMA(NULL, 0, MiddleMovingPeriod, 0, MODE_SMA, PRICE_CLOSE, 0);
    longMA = iMA(NULL, 0, LongMovingPeriod, 0, MODE_SMA, PRICE_CLOSE, 0);

    // 買いの条件(移動平均線のパーフェクトオーダー)
    if (shortMA > middleMA && middleMA > longMA && OrderCount < MaxOrder)
    {
        // 買いオーダーを送信
        res = OrderSend(Symbol(), OP_BUY, LotsOptimized(), Ask, 3, 0, 0, "", MAGICMA, 0, Blue);
        if (res > 0)
        {
            OrderCount++;
            LastOrderTime = TimeCurrent();
        }
        else
        {
            Print("OrderSend error ", GetLastError()); // オーダー送信エラーを表示
        }
        return;
    }

    // 売りの条件(移動平均線のパーフェクトオーダー)
    if (shortMA < middleMA && middleMA < longMA && OrderCount < MaxOrder)
    {
        // 売りオーダーを送信
        res = OrderSend(Symbol(), OP_SELL, LotsOptimized(), Bid, 3, 0, 0, "", MAGICMA, 0, Red);
        if (res > 0)
        {
            OrderCount++;
            LastOrderTime = TimeCurrent();
        }
        else
        {
            Print("OrderSend error ", GetLastError()); // オーダー送信エラーを表示
        }
        return;
    }
}

//+------------------------------------------------------------------+
//| Check for close order conditions                                 |
//| オーダーを閉じる条件をチェックする関数                             |
//+------------------------------------------------------------------+
void CheckForClose()
{
    double shortMA, middleMA, longMA; // 移動平均線の値を保存する変数

    // 各移動平均線の値を取得
    shortMA = iMA(NULL, 0, ShortMovingPeriod, 0, MODE_SMA, PRICE_CLOSE, 0);
    middleMA = iMA(NULL, 0, MiddleMovingPeriod, 0, MODE_SMA, PRICE_CLOSE, 0);
    longMA = iMA(NULL, 0, LongMovingPeriod, 0, MODE_SMA, PRICE_CLOSE, 0);

    // 全てのオープンオーダーをチェック
    for (int i = 0; i < OrdersTotal(); i++)
    {
        if (OrderSelect(i, SELECT_BY_POS, MODE_TRADES) == false) break; // オーダーが選択できない場合、ループを抜ける

        if (OrderMagicNumber() != MAGICMA || OrderSymbol() != Symbol()) continue; // EAが管理するオーダーか確認

        // 買いオーダーの場合のクローズ条件
        if (OrderType() == OP_BUY)
        {
            // パーフェクトオーダーが崩れた場合
            if (shortMA < middleMA && middleMA < longMA && OrderCount < MaxOrder)
            {
                if (!OrderClose(OrderTicket(), OrderLots(), Bid, 3, White))
                    Print("OrderClose error ", GetLastError()); // オーダークローズエラーを表示
                OrderCount++;
            }
            break;
        }

        // 売りオーダーの場合のクローズ条件
        if (OrderType() == OP_SELL)
        {
            // パーフェクトオーダーが崩れた場合
            if (shortMA > middleMA && middleMA > longMA && OrderCount < MaxOrder)
            {
                if (!OrderClose(OrderTicket(), OrderLots(), Ask, 3, White))
                    Print("OrderClose error ", GetLastError()); // オーダークローズエラーを表示
                OrderCount++;
            }
            break;
        }
    }
}

//+------------------------------------------------------------------+
//| OnTick function                                                  |
//| 毎ティック実行される関数                                          |
//+------------------------------------------------------------------+
void OnTick()
{
    // 必要なバー数がない場合、または取引が許可されていない場合は何もしない
    if (Bars < 100 || !TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
        return;

    // 1時間ごとにOrderCountをリセット
    if (TimeCurrent() - LastOrderTime >= 3600)
    {
        OrderCount = 0;
    }

    // 現在のシンボルのオープンオーダーを計算
    if (CalculateCurrentOrders(Symbol()) == 0) 
        CheckForOpen(); // 新しいオーダーを開く条件をチェック
    else 
        CheckForClose(); // 既存のオーダーを閉じる条件をチェック
}

次に、このEAプログラムにスプレッドフィルターとストップ逆指値とリミット注文の設定をお願いしてみましょう。

1つ目の修正は、スプレッドフィルターの追加です。
2つ目の修正は、ストップ逆指値とリミット注文の設定です。

ストップ逆指値とリミット注文の入力できる最大値を1000pips、最小値を10pipsとしてください。とお願いしてみます。

完成したEAがこちらです。

//+------------------------------------------------------------------+
//|                                               Moving Average.mq4 |
//|                                         https://note.com/aimjey/ |
//+------------------------------------------------------------------+

// EA識別用マジックナンバー
extern int MAGICMA = 20240529;

// ロットサイズの入力
extern double Lots = 0.1;

// 移動平均線の期間を設定
extern int ShortMovingPeriod = 20;  // 短期移動平均線の期間
extern int MiddleMovingPeriod = 75; // 中期移動平均線の期間
extern int LongMovingPeriod = 100;  // 長期移動平均線の期間

// 1時間あたりの注文回数の上限(プロパティ項目で設定可能)
input int MaxOrder = 20;  // 最小=10 最大=50 デフォルト=20

// スプレッドの最大値(ピップス単位)
input double MaxSpread = 2.0; // スプレッドフィルターを追加

// ストップロスとテイクプロフィット(ピップス単位)
extern double StopLoss = 10; // 最小=10 最大=1000
extern double TakeProfit = 10; // 最小=10 最大=1000

int OrderCount = 0; // 現在の注文数をカウント
datetime LastOrderTime = 0; // 最後に注文を行った時間

//+------------------------------------------------------------------+
//| Calculate open positions                                         |
//| 現在のオープンポジションを計算する関数                             |
//+------------------------------------------------------------------+
int CalculateCurrentOrders(string symbol)
{
    int buys = 0, sells = 0; // 買いポジションと売りポジションのカウンタを初期化

    // 全てのオープンオーダーをチェック
    for (int i = 0; i < OrdersTotal(); i++)
    {
        if (OrderSelect(i, SELECT_BY_POS, MODE_TRADES) == false) break; // オーダーが選択できない場合、ループを抜ける
        if (OrderSymbol() == Symbol() && OrderMagicNumber() == MAGICMA)
        {
            // 買いオーダーの場合、カウンタを増やす
            if (OrderType() == OP_BUY) buys++;
            // 売りオーダーの場合、カウンタを増やす
            if (OrderType() == OP_SELL) sells++;
        }
    }

    // 買いオーダーの数を返す。売りオーダーの場合は負の値を返す
    if (buys > 0) return (buys);
    else return (-sells);
}

//+------------------------------------------------------------------+
//| Calculate optimal lot size                                       |
//| 最適なロットサイズを計算する関数                                  |
//+------------------------------------------------------------------+
double LotsOptimized()
{
    // 入力パラメータのロット数を小数点第2位で正規化
    double lot = NormalizeDouble(Lots, 2);

    // 最小ロットサイズを取得
    double minLot = 0.01;
    // 最大ロットサイズを設定
    double maxLot = 10.0;
    
    // 最小ロットサイズ以上に調整
    if (lot < minLot) lot = minLot;
    // 最大ロットサイズ以下に調整
    if (lot > maxLot) lot = maxLot;
    // ロットステップに従って丸める
    lot = NormalizeDouble(lot, 2);
    return (lot);
}

//+------------------------------------------------------------------+
//| Check for open order conditions                                  |
//| 新しいオーダーを開く条件をチェックする関数                         |
//+------------------------------------------------------------------+
void CheckForOpen()
{
    double shortMA, middleMA, longMA; // 移動平均線の値を保存する変数
    int res;

    // スプレッドを取得
    double spread = (Ask - Bid) / Point;

    // スプレッドが最大値を超える場合、何もしない
    if (spread > MaxSpread) return;

    // 各移動平均線の値を取得
    shortMA = iMA(NULL, 0, ShortMovingPeriod, 0, MODE_SMA, PRICE_CLOSE, 0);
    middleMA = iMA(NULL, 0, MiddleMovingPeriod, 0, MODE_SMA, PRICE_CLOSE, 0);
    longMA = iMA(NULL, 0, LongMovingPeriod, 0, MODE_SMA, PRICE_CLOSE, 0);

    // ストップロスとテイクプロフィットをポイントに変換
    double slPoints = StopLoss * Point;
    double tpPoints = TakeProfit * Point;

    // 買いの条件(移動平均線のパーフェクトオーダー)
    if (shortMA > middleMA && middleMA > longMA && OrderCount < MaxOrder)
    {
        // 買いオーダーを送信
        res = OrderSend(Symbol(), OP_BUY, LotsOptimized(), Ask, 3, Ask - slPoints, Ask + tpPoints, "", MAGICMA, 0, Blue);
        if (res > 0)
        {
            OrderCount++;
            LastOrderTime = TimeCurrent();
        }
        else
        {
            Print("OrderSend error ", GetLastError()); // オーダー送信エラーを表示
        }
        return;
    }

    // 売りの条件(移動平均線のパーフェクトオーダー)
    if (shortMA < middleMA && middleMA < longMA && OrderCount < MaxOrder)
    {
        // 売りオーダーを送信
        res = OrderSend(Symbol(), OP_SELL, LotsOptimized(), Bid, 3, Bid + slPoints, Bid - tpPoints, "", MAGICMA, 0, Red);
        if (res > 0)
        {
            OrderCount++;
            LastOrderTime = TimeCurrent();
        }
        else
        {
            Print("OrderSend error ", GetLastError()); // オーダー送信エラーを表示
        }
        return;
    }
}

//+------------------------------------------------------------------+
//| Check for close order conditions                                 |
//| オーダーを閉じる条件をチェックする関数                             |
//+------------------------------------------------------------------+
void CheckForClose()
{
    double shortMA, middleMA, longMA; // 移動平均線の値を保存する変数

    // 各移動平均線の値を取得
    shortMA = iMA(NULL, 0, ShortMovingPeriod, 0, MODE_SMA, PRICE_CLOSE, 0);
    middleMA = iMA(NULL, 0, MiddleMovingPeriod, 0, MODE_SMA, PRICE_CLOSE, 0);
    longMA = iMA(NULL, 0, LongMovingPeriod, 0, MODE_SMA, PRICE_CLOSE, 0);

    // 全てのオープンオーダーをチェック
    for (int i = 0; i < OrdersTotal(); i++)
    {
        if (OrderSelect(i, SELECT_BY_POS, MODE_TRADES) == false) break; // オーダーが選択できない場合、ループを抜ける

        if (OrderMagicNumber() != MAGICMA || OrderSymbol() != Symbol()) continue; // EAが管理するオーダーか確認

        // 買いオーダーの場合のクローズ条件
        if (OrderType() == OP_BUY)
        {
            // パーフェクトオーダーが崩れた場合
            if (shortMA < middleMA && middleMA < longMA && OrderCount < MaxOrder)
            {
                if (!OrderClose(OrderTicket(), OrderLots(), Bid, 3, White))
                    Print("OrderClose error ", GetLastError()); // オーダークローズエラーを表示
                OrderCount++;
            }
            break;
        }

        // 売りオーダーの場合のクローズ条件
        if (OrderType() == OP_SELL)
        {
            // パーフェクトオーダーが崩れた場合
            if (shortMA > middleMA && middleMA > longMA && OrderCount < MaxOrder)
            {
                if (!OrderClose(OrderTicket(), OrderLots(), Ask, 3, White))
                    Print("OrderClose error ", GetLastError()); // オーダークローズエラーを表示
                OrderCount++;
            }
            break;
        }
    }
}

//+------------------------------------------------------------------+
//| OnTick function                                                  |
//| 毎ティック実行される関数                                          |
//+------------------------------------------------------------------+
void OnTick()
{
    // 必要なバー数がない場合、または取引が許可されていない場合は何もしない
    if (Bars < 100 || !TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
        return;

    // 1時間ごとにOrderCountをリセット
    if (TimeCurrent() - LastOrderTime >= 3600)
    {
        OrderCount = 0;
    }

    // 現在のシンボルのオープンオーダーを計算
    if (CalculateCurrentOrders(Symbol()) == 0) 
        CheckForOpen(); // 新しいオーダーを開く条件をチェック
    else 
        CheckForClose(); // 既存のオーダーを閉じる条件をチェック
}

//+------------------------------------------------------------------+
//| Check input parameters                                           |
//| 入力パラメータのチェック関数                                      |
//+------------------------------------------------------------------+
void CheckInputParameters()
{
    if (StopLoss < 10) StopLoss = 10;
    if (StopLoss > 1000) StopLoss = 1000;
    if (TakeProfit < 10) TakeProfit = 10;
    if (TakeProfit > 1000) TakeProfit = 1000;
}

//+------------------------------------------------------------------+
//| OnInit function                                                  |
//| 初期化時に実行される関数                                          |
//+------------------------------------------------------------------+
int OnInit()
{
    // 入力パラメータをチェック
    CheckInputParameters();
    return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//| 非初期化時に実行される関数                                        |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
}

このEAプログラムを実行してみましたが、ストップとリミットの注文のレートが正しく入力されません。

ストップとリミットの値が正しくない為、エントリーとエグジットが繰り返される


pipsでなければならない部分がなぜかPointになっています。

pipsの計算は少し複雑です。
クロス円通貨だと150.00円とか150.000円とかでレート表示されます。
この場合の100pipsは1円になります。

ドルストレートなどでは1.20000みたいなレートになり、100pipsは0.01ドルになります。

同じ1pipsでも、通貨ペアにより1.0なのか0.01なのか違うわけです。

さらに、ゴールドやJP225やビットコインなどになるとレートが大きく異なることからpipsの計算は個別に計算で求める必要があります。

わたしはpipsを求めるのに独自の計算式を使用しています。

 //■□■□■□■□■ pips判定 by Masayan ■□■□■□■□■
double Pips = 0.01;// ドルストレートは0.010が100pips、クロス円は1.0が100pipsになる
    string value = Symbol();
    string target = "JPY";
    int pos = StringFind(value, target);
    double Symbol_RATE = Close[1];
  if( pos > 0){// posは1以上の整数、else の場合-1
  Pips = 1.00;// クロス円は1.0=100pipsで判定
  }else if(Symbol_RATE > 10 && Symbol_RATE <= 100){
  Pips = 0.1;// 原油などの場合は0.1=100pipsで判定
  }else if(Symbol_RATE > 100 && Symbol_RATE <= 1000){
  Pips = 1.0;
  }else if(Symbol_RATE > 1000 && Symbol_RATE <= 10000){
  Pips = 10.0;// ゴールドはここに当てはまり10.0=100pipsで判定
  }else if(Symbol_RATE > 10000 && Symbol_RATE <= 100000){
  Pips = 100.0;// JP225はここに当てはまり100.0=100pipsで判定
  }else if(Symbol_RATE > 100000 && Symbol_RATE <= 1000000){
  Pips = 1000.0;
  }else if(Symbol_RATE > 1000000 && Symbol_RATE <= 10000000){
  Pips = 10000.0;
  }else if(Symbol_RATE > 10000000 && Symbol_RATE <= 100000000){
  Pips = 100000.0;// ビットコインはここに当てはまり100000.0=100pipsで判定
  }else if(Symbol_RATE > 100000000){
  Pips = 1000000.0;
  }

レートが0.95とかはドルストレートとして判断
例:6月4日現在のAUDUSDのレートは0.668
この場合のpipsは0.010が100pipsになります。

クロス円の場合、シンボルに"JPY"が含まれます。
したがって通貨ペア名に"JPY"が含まれる場合は、クロス円として認識し1.0=100pipsで判定します。

原油などの場合は0.1=100pipsで判定
ゴールドは今のレートだと1000~10000の範囲となり10.0=100pipsで判定
JP225は今のレートだと10000~100000の範囲となり100.0=100pipsで判定
ビットコインは今のレートだと10000000~100000000の範囲となり100000.0=100pipsで判定
仮にビットコインが今の10倍に値上がりして10億円以上になっても、プログラム的には1000000.0=100pipsで判定するのでこの計算式は10億円以上のCFDにも対応できます。

プログラミングのやり方は1つではなく、プログラマーによってクセがあります。
MT4の場合FX会社により動作が異なることがあるので、わたしの場合FX会社の仕様に左右されない方法でコーディングしています。

今回の修正で苦戦しているPipsの計算の個所はChatGPTではPointとPipsを同じレートとして扱っていました。

これは痛いミスです。

    // スプレッドを取得
    double spread = (Ask - Bid) / Point;

    // スプレッドが最大値を超える場合、何もしない
    if (spread > MaxSpread) return;

    // 各移動平均線の値を取得
    shortMA = iMA(NULL, 0, ShortMovingPeriod, 0, MODE_SMA, PRICE_CLOSE, 0);
    middleMA = iMA(NULL, 0, MiddleMovingPeriod, 0, MODE_SMA, PRICE_CLOSE, 0);
    longMA = iMA(NULL, 0, LongMovingPeriod, 0, MODE_SMA, PRICE_CLOSE, 0);

    // ストップロスとテイクプロフィットをポイントに変換
    double slPoints = StopLoss * Point;
    double tpPoints = TakeProfit * Point;

10Pointが1pipsなので、間違っている個所を手動で修正してあげました。
ちなみに個人的に使用したくない定義済変数 Pointが使われていたのでチェック関数Point()に変更します。

    // スプレッドを取得
    double spread = (Ask - Bid) / (Point() * 10);

    // スプレッドが最大値を超える場合、何もしない
    if (spread > MaxSpread) return;

    // 各移動平均線の値を取得
    shortMA = iMA(NULL, 0, ShortMovingPeriod, 0, MODE_SMA, PRICE_CLOSE, 0);
    middleMA = iMA(NULL, 0, MiddleMovingPeriod, 0, MODE_SMA, PRICE_CLOSE, 0);
    longMA = iMA(NULL, 0, LongMovingPeriod, 0, MODE_SMA, PRICE_CLOSE, 0);

    // ストップロスとテイクプロフィットをポイントに変換
    double slPoints = StopLoss * (Point() * 10);
    double tpPoints = TakeProfit * (Point() * 10);

これで、スプレッドフィルターの追加とストップ逆指値とリミット注文の設定が終わりました。

GPT-4o の回数制限に達した為、一部手動での修正を行いました。

完成したEAのソースコードはこちらです。

(追記 2024年6月4日)
スプレッドフィルターが正常に動作しない不具合があります。
今度修正します。

//+------------------------------------------------------------------+
//|                                               Moving Average.mq4 |
//|                                         https://note.com/aimjey/ |
//+------------------------------------------------------------------+

// EA識別用マジックナンバー
extern int MAGICMA = 20240529;

// ロットサイズの入力
extern double Lots = 0.1;

// 移動平均線の期間を設定
extern int ShortMovingPeriod = 20;  // 短期移動平均線の期間
extern int MiddleMovingPeriod = 75; // 中期移動平均線の期間
extern int LongMovingPeriod = 100;  // 長期移動平均線の期間

// 1時間あたりの注文回数の上限(プロパティ項目で設定可能)
input int MaxOrder = 20;  // 最小=10 最大=50 デフォルト=20

// スプレッドの最大値(ピップス単位)
input double MaxSpread = 2.0; // スプレッドフィルターを追加

// ストップロスとテイクプロフィット(ピップス単位)
extern double StopLoss = 400; // 最小=10 最大=1000
extern double TakeProfit = 500; // 最小=10 最大=1000

int OrderCount = 0; // 現在の注文数をカウント
datetime LastOrderTime = 0; // 最後に注文を行った時間

//+------------------------------------------------------------------+
//| Calculate open positions                                         |
//| 現在のオープンポジションを計算する関数                             |
//+------------------------------------------------------------------+
int CalculateCurrentOrders(string symbol)
{
    int buys = 0, sells = 0; // 買いポジションと売りポジションのカウンタを初期化

    // 全てのオープンオーダーをチェック
    for (int i = 0; i < OrdersTotal(); i++)
    {
        if (OrderSelect(i, SELECT_BY_POS, MODE_TRADES) == false) break; // オーダーが選択できない場合、ループを抜ける
        if (OrderSymbol() == Symbol() && OrderMagicNumber() == MAGICMA)
        {
            // 買いオーダーの場合、カウンタを増やす
            if (OrderType() == OP_BUY) buys++;
            // 売りオーダーの場合、カウンタを増やす
            if (OrderType() == OP_SELL) sells++;
        }
    }

    // 買いオーダーの数を返す。売りオーダーの場合は負の値を返す
    if (buys > 0) return (buys);
    else return (-sells);
}

//+------------------------------------------------------------------+
//| Calculate optimal lot size                                       |
//| 最適なロットサイズを計算する関数                                  |
//+------------------------------------------------------------------+
double LotsOptimized()
{
    // 入力パラメータのロット数を小数点第2位で正規化
    double lot = NormalizeDouble(Lots, 2);

    // 最小ロットサイズを取得
    double minLot = 0.01;
    // 最大ロットサイズを設定
    double maxLot = 10.0;
    
    // 最小ロットサイズ以上に調整
    if (lot < minLot) lot = minLot;
    // 最大ロットサイズ以下に調整
    if (lot > maxLot) lot = maxLot;
    // ロットステップに従って丸める
    lot = NormalizeDouble(lot, 2);
    return (lot);
}

//+------------------------------------------------------------------+
//| Check for open order conditions                                  |
//| 新しいオーダーを開く条件をチェックする関数                         |
//+------------------------------------------------------------------+
void CheckForOpen()
{
    double shortMA, middleMA, longMA; // 移動平均線の値を保存する変数
    int res;

    // スプレッドを取得
    double spread = (Ask - Bid) / (Point() * 10);

    // スプレッドが最大値を超える場合、何もしない
    if (spread > MaxSpread) return;

    // 各移動平均線の値を取得
    shortMA = iMA(NULL, 0, ShortMovingPeriod, 0, MODE_SMA, PRICE_CLOSE, 0);
    middleMA = iMA(NULL, 0, MiddleMovingPeriod, 0, MODE_SMA, PRICE_CLOSE, 0);
    longMA = iMA(NULL, 0, LongMovingPeriod, 0, MODE_SMA, PRICE_CLOSE, 0);

    // ストップロスとテイクプロフィットをpipsに変換
    double slPoints = StopLoss * (Point() * 10);
    double tpPoints = TakeProfit * (Point() * 10);

    // 買いの条件(移動平均線のパーフェクトオーダー)
    if (shortMA > middleMA && middleMA > longMA && OrderCount < MaxOrder)
    {
        // 買いオーダーを送信
        res = OrderSend(Symbol(), OP_BUY, LotsOptimized(), Ask, 3, Ask - slPoints, Ask + tpPoints, "", MAGICMA, 0, Blue);
        if (res > 0)
        {
            OrderCount++;
            LastOrderTime = TimeCurrent();
        }
        else
        {
            Print("OrderSend error ", GetLastError()); // オーダー送信エラーを表示
        }
        return;
    }

    // 売りの条件(移動平均線のパーフェクトオーダー)
    if (shortMA < middleMA && middleMA < longMA && OrderCount < MaxOrder)
    {
        // 売りオーダーを送信
        res = OrderSend(Symbol(), OP_SELL, LotsOptimized(), Bid, 3, Bid + slPoints, Bid - tpPoints, "", MAGICMA, 0, Red);
        if (res > 0)
        {
            OrderCount++;
            LastOrderTime = TimeCurrent();
        }
        else
        {
            Print("OrderSend error ", GetLastError()); // オーダー送信エラーを表示
        }
        return;
    }
}

//+------------------------------------------------------------------+
//| Check for close order conditions                                 |
//| オーダーを閉じる条件をチェックする関数                             |
//+------------------------------------------------------------------+
void CheckForClose()
{
    double shortMA, middleMA, longMA; // 移動平均線の値を保存する変数

    // 各移動平均線の値を取得
    shortMA = iMA(NULL, 0, ShortMovingPeriod, 0, MODE_SMA, PRICE_CLOSE, 0);
    middleMA = iMA(NULL, 0, MiddleMovingPeriod, 0, MODE_SMA, PRICE_CLOSE, 0);
    longMA = iMA(NULL, 0, LongMovingPeriod, 0, MODE_SMA, PRICE_CLOSE, 0);

    // 全てのオープンオーダーをチェック
    for (int i = 0; i < OrdersTotal(); i++)
    {
        if (OrderSelect(i, SELECT_BY_POS, MODE_TRADES) == false) break; // オーダーが選択できない場合、ループを抜ける

        if (OrderMagicNumber() != MAGICMA || OrderSymbol() != Symbol()) continue; // EAが管理するオーダーか確認

        // 買いオーダーの場合のクローズ条件
        if (OrderType() == OP_BUY)
        {
            // パーフェクトオーダーが崩れた場合
            if (shortMA < middleMA && middleMA < longMA && OrderCount < MaxOrder)
            {
                if (!OrderClose(OrderTicket(), OrderLots(), Bid, 3, White))
                    Print("OrderClose error ", GetLastError()); // オーダークローズエラーを表示
                OrderCount++;
            }
            break;
        }

        // 売りオーダーの場合のクローズ条件
        if (OrderType() == OP_SELL)
        {
            // パーフェクトオーダーが崩れた場合
            if (shortMA > middleMA && middleMA > longMA && OrderCount < MaxOrder)
            {
                if (!OrderClose(OrderTicket(), OrderLots(), Ask, 3, White))
                    Print("OrderClose error ", GetLastError()); // オーダークローズエラーを表示
                OrderCount++;
            }
            break;
        }
    }
}

//+------------------------------------------------------------------+
//| OnTick function                                                  |
//| 毎ティック実行される関数                                          |
//+------------------------------------------------------------------+
void OnTick()
{
    // 必要なバー数がない場合、または取引が許可されていない場合は何もしない
    if (Bars < 100 || !TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
        return;

    // 1時間ごとにOrderCountをリセット
    if (TimeCurrent() - LastOrderTime >= 3600)
    {
        OrderCount = 0;
    }

    // 現在のシンボルのオープンオーダーを計算
    if (CalculateCurrentOrders(Symbol()) == 0) 
        CheckForOpen(); // 新しいオーダーを開く条件をチェック
    else 
        CheckForClose(); // 既存のオーダーを閉じる条件をチェック
}

//+------------------------------------------------------------------+
//| Check input parameters                                           |
//| 入力パラメータのチェック関数                                      |
//+------------------------------------------------------------------+
void CheckInputParameters()
{
    if (StopLoss < 10) StopLoss = 10;
    if (StopLoss > 1000) StopLoss = 1000;
    if (TakeProfit < 10) TakeProfit = 10;
    if (TakeProfit > 1000) TakeProfit = 1000;
}

//+------------------------------------------------------------------+
//| OnInit function                                                  |
//| 初期化時に実行される関数                                          |
//+------------------------------------------------------------------+
int OnInit()
{
    // 入力パラメータをチェック
    CheckInputParameters();
    return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//| 非初期化時に実行される関数                                        |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
}

TDSによるバックテスト結果も非常に良好な成績となっております。

TDSによるバックテスト

今後、行う作業は以下の通りです。
【第4回動画で部分完了】1つ目の修正は、オーダーの無限ループの防止です。最小=10 最大=50が未実装。
【第5回動画で完了】2つ目の修正は、スプレッドフィルターの追加です。
【第5回動画で完了】3つ目の修正は、ストップ逆指値とリミット注文の設定。
4つ目の修正は、日本時間の夏時間と冬時間の設定。
5つ目の修正は、トレーリングストップの実装。
6つ目の修正は、マルチロジック化にチャレンジです。
【第4回動画で完了】7つ目の修正は、ロット配分の設定。
8つ目の修正は、ナンピン設定。

全部実装できれば、有料版のPerfectOrder GBPJPY以上の性能を得ることができます。


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

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