見出し画像

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


こんにちは、ChatGPT担当のナナミです。

前回の記事では、ChatGPTを使いゴールデンクロスでロング、デッドクロスでショートのポジションを持つEAのバグの修正方法をご紹介しました。

ChatGPTを使えば様々な不具合を修正できるので、プログラミング初心者さんはChatGPTと対話する形でどんどん改良をしていってくださいね。

今回は、パーフェクトオーダーのEAソースコードを使用します。
前回使用したゴールデンクロスとデッドクロスのEAに移動平均線を1本追加して3本の移動平均線を用いたパーフェクトオーダーのロジックを使用します。

移動平均線の期間は以下の通りです。

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

移動平均線の期間は2021年に販売開始してから累計9000pips勝ちのフォワード成績を出しているEA「PerfectOrderGBPJPY」を参考にしました。

PerfectOrder GBPJPY
https://www.gogojungle.co.jp/systemtrade/fx/30753

例えプログラマーであってもMT4のEA開発で直面する最初の問題に、MQL言語特有の関数や処理が多いことがあります。

どこで何の処理をしているのか分からないと、そもそもプログラム自体をカスタマイズできないので挫折します。

まずはカスタマイズするソースコードに解説文を追加するようChatGPTに依頼してみましょう。

ChatGPTに以下のプロンプトを送信します。

以下のプログラムソースコードはMT4のEAで、3本の移動平均線を用いたパーフェクトオーダーのロジックを使用したプログラムです。
どこで何の処理が行われているか分かるようにプログラムソースコードにコメントを追加してください。
なるべく初心者にも分かるように丁寧にたくさんのコメントを書いてください。とChatGPTに質問してみます。

//+------------------------------------------------------------------+
//|                                               Moving Average.mq4 |
//|                                         https://note.com/aimjey/ |
//+------------------------------------------------------------------+
extern int MAGICMA = 20240529;// EA識別用マジックナンバー
input double Lots          =0.1;
input double MaximumRisk   =0.02;
input double DecreaseFactor=3;
extern int ShortMovingPeriod = 20;// 短期移動平均線の期間
extern int MiddleMovingPeriod = 75;// 中期移動平均線の期間
extern int LongMovingPeriod = 100;// 長期移動平均線の期間
//+------------------------------------------------------------------+
//| 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++;
        }
     }
//--- return orders volume
   if(buys>0) return(buys);
   else       return(-sells);
  }
//+------------------------------------------------------------------+
//| Calculate optimal lot size                                       |
//+------------------------------------------------------------------+
double LotsOptimized()
  {
   double lot=Lots;
   int    orders=HistoryTotal();     // history orders total
   int    losses=0;                  // number of losses orders without a break
//--- select lot size
   lot=NormalizeDouble(AccountFreeMargin()*MaximumRisk/1000.0,1);
//--- calcuulate number of losses orders without a break
   if(DecreaseFactor>0)
     {
      for(int i=orders-1;i>=0;i--)
        {
         if(OrderSelect(i,SELECT_BY_POS,MODE_HISTORY)==false)
           {
            Print("Error in history!");
            break;
           }
         if(OrderSymbol()!=Symbol() || OrderType()>OP_SELL)
            continue;
         //---
         if(OrderProfit()>0) break;
         if(OrderProfit()<0) losses++;
        }
      if(losses>1)
         lot=NormalizeDouble(lot-lot*losses/DecreaseFactor,1);
     }
//--- return lot size
   if(lot<0.1) lot=0.1;
   return(lot);
  }
//+------------------------------------------------------------------+
//| Check for open order conditions                                  |
//+------------------------------------------------------------------+
void CheckForOpen()
{
   double shortMA, middleMA, longMA;
   int res;
   
   // Get Moving Averages
   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);
   
   // Buy conditions (Triple Moving Average Crossover)
   if (shortMA > middleMA && middleMA > longMA)
   {
      res = OrderSend(Symbol(), OP_BUY, LotsOptimized(), Ask, 3, 0, 0, "", MAGICMA, 0, Blue);
      return;
   }
   
   // Sell conditions (Triple Moving Average Crossover)
   if (shortMA < middleMA && middleMA < longMA)
   {
      res = OrderSend(Symbol(), OP_SELL, LotsOptimized(), Bid, 3, 0, 0, "", MAGICMA, 0, Red);
      return;
   }
}

//+------------------------------------------------------------------+
//| Check for close order conditions                                 |
//+------------------------------------------------------------------+
void CheckForClose()
{
   double shortMA, middleMA, longMA;
   // Get Moving Averages
   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;
      
      // Check order type
      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()
  {
//--- check for history and trading
   if(Bars<100 || IsTradeAllowed()==false)
      return;
//--- calculate open orders by current symbol
   if(CalculateCurrentOrders(Symbol())==0) CheckForOpen();
   else                                    CheckForClose();
//---
  }
//+------------------------------------------------------------------+

すると、どうでしょう。プログラムソースコードに説明文を日本語で書いてくれました。

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

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

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

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

//+------------------------------------------------------------------+
//| 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()
{
    double lot = Lots; // 初期ロットサイズ
    int orders = HistoryTotal(); // 全ての履歴オーダーの総数を取得
    int losses = 0; // 連続損失オーダーの数を初期化

    // ロットサイズを計算
    lot = NormalizeDouble(AccountFreeMargin() * MaximumRisk / 1000.0, 1);

    // 連続損失オーダーの数をカウント
    if (DecreaseFactor > 0)
    {
        for (int i = orders - 1; i >= 0; i--)
        {
            if (OrderSelect(i, SELECT_BY_POS, MODE_HISTORY) == false)
            {
                Print("Error in history!"); // 履歴オーダーの選択エラーを表示
                break;
            }
            if (OrderSymbol() != Symbol() || OrderType() > OP_SELL) continue;

            // 利益が出た場合、ループを抜ける
            if (OrderProfit() > 0) break;
            // 損失が出た場合、損失カウンタを増やす
            if (OrderProfit() < 0) losses++;
        }
        // 損失オーダー数に応じてロットサイズを調整
        if (losses > 1)
            lot = NormalizeDouble(lot - lot * losses / DecreaseFactor, 1);
    }

    // 最小ロットサイズを0.1に設定
    if (lot < 0.1) lot = 0.1;
    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)
    {
        // 買いオーダーを送信
        res = OrderSend(Symbol(), OP_BUY, LotsOptimized(), Ask, 3, 0, 0, "", MAGICMA, 0, Blue);
        return;
    }

    // 売りの条件(移動平均線のパーフェクトオーダー)
    if (shortMA < middleMA && middleMA < longMA)
    {
        // 売りオーダーを送信
        res = OrderSend(Symbol(), OP_SELL, LotsOptimized(), Bid, 3, 0, 0, "", MAGICMA, 0, Red);
        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 || IsTradeAllowed() == false)
        return;

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

どうですか?

すごく見やすくなりましたよね。

このEAはポンド円で最大の威力を発揮します。
ドル円で動かす場合、短期移動平均線の期間を20⇒25に、中期移動平均線の期間75⇒50に、長期移動平均線の期間100⇒80にそれぞれ変更して下さい。

ただし、そのままでは決済のタイミングが悪く、勝てないEAになります。
パーフェクトオーダーが崩れたら決済でなくて、ドテンで決済するロジックに変更すると成績が改善します。
やり方は、次回の記事で書きます。

最後に、このEAには致命的なバグがあります。

1つ目のバグは、証拠金不足やサーバーエラー等で注文が通らず、オーダーの無限ループに陥ることです。
2つ目のバグは、スプレッドフィルターが無いことです。スプレッドが開いている時でもトレードを行うことから、トレードを見送るフィルターが必要です。

次回の記事では、上の2つのバグを改善できるかChatGPTに聞いてみます。


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