見出し画像

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

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

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

ChatGPTを使えば、様々な不具合を修正できるので、今回はその一例をご紹介いたします。

以下のMQL4プログラムは、ゴールデンクロスでロング、デッドクロスでショートのポジションを持つEAのプログラムソースコードです。
ゴールデンクロスとデッドクロスが頻繁に入れ替わる時に、エントリーと決済を短時間で行う不具合があります。
修正するにはどうすればよいですか?とChatGPTに質問してみます。


エントリーとエグジットが連発する不具合がある
//+------------------------------------------------------------------+
//|                                               Moving Average.mq4 |
//|                   Copyright 2005-2014, MetaQuotes Software Corp. |
//|                                              http://www.mql4.com |
//+------------------------------------------------------------------+ 
 #define   MAGICMA  20131111
//--- Inputs
input double Lots          =0.1;
input double MaximumRisk   =0.02;
input double DecreaseFactor=3;
input int    MovingPeriod  =12;
input int    MovingShift   =6;
int ShortMovingPeriod = 90;
int LongMovingPeriod = 300;
//+------------------------------------------------------------------+
//| 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, longMA;
   int res;
   
   // Get Moving Averages
   shortMA = iMA(NULL, 0, ShortMovingPeriod, 0, MODE_SMA, PRICE_CLOSE, 0);
   longMA = iMA(NULL, 0, LongMovingPeriod, 0, MODE_SMA, PRICE_CLOSE, 0);
   
   // Buy conditions (Golden Cross)
   if (shortMA > longMA)
   {
      res = OrderSend(Symbol(), OP_BUY, LotsOptimized(), Ask, 3, 0, 0, "", MAGICMA, 0, Blue);
      return;
   }
   
   // Sell conditions (Dead Cross)
   if (shortMA < longMA)
   {
      res = OrderSend(Symbol(), OP_SELL, LotsOptimized(), Bid, 3, 0, 0, "", MAGICMA, 0, Red);
      return;
   }
}

//+------------------------------------------------------------------+
//| Check for close order conditions                                 |
//+------------------------------------------------------------------+
void CheckForClose()
{
   double shortMA, longMA;
   // Get Moving Averages
   shortMA = iMA(NULL, 0, ShortMovingPeriod, 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 < longMA)
         {
            if (!OrderClose(OrderTicket(), OrderLots(), Bid, 3, White))
               Print("OrderClose error ", GetLastError());
         }
         break;
      }
      
      if (OrderType() == OP_SELL)
      {
         if (shortMA > 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();
//---
  }
//+------------------------------------------------------------------+

すると、改善されたプログラムソースコードを書き出してくれました!
素晴らしい!

以下の点を導入しました:

TradeDelayという変数を導入し、最後のトレードから一定期間(バー数)内では新しいエントリーを行わないようにしました。
ゴールデンクロスとデッドクロスの確認に、前のバーのクロスをチェックする関数PreviousGoldenCrossおよびPreviousDeadCrossを追加しました。
これにより、頻繁なエントリーと決済を防ぎ、より安定したトレードが可能になります。

//+------------------------------------------------------------------+
//|                                               Moving Average.mq4 |
//|                   Copyright 2005-2014, MetaQuotes Software Corp. |
//|                                              http://www.mql4.com |
//+------------------------------------------------------------------+ 
 #define   MAGICMA  20131111
//--- Inputs
input double Lots          =0.1;
input double MaximumRisk   =0.02;
input double DecreaseFactor=3;
input int    MovingPeriod  =12;
input int    MovingShift   =6;
input int    ShortMovingPeriod = 90;
input int    LongMovingPeriod = 300;
input int    TradeDelay = 10; // リトレード防止期間(バー数)

datetime LastTradeTime = 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++;
        }
     }
//--- 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);
//--- calculate 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, longMA;
   int res;
   
   // Get Moving Averages
   shortMA = iMA(NULL, 0, ShortMovingPeriod, 0, MODE_SMA, PRICE_CLOSE, 0);
   longMA = iMA(NULL, 0, LongMovingPeriod, 0, MODE_SMA, PRICE_CLOSE, 0);

   // リトレード防止期間のチェック
   if (TimeCurrent() - LastTradeTime < TradeDelay * PeriodSeconds())
      return;
   
   // Buy conditions (Golden Cross)
   if (shortMA > longMA && PreviousGoldenCross())
   {
      res = OrderSend(Symbol(), OP_BUY, LotsOptimized(), Ask, 3, 0, 0, "", MAGICMA, 0, Blue);
      if(res > 0) LastTradeTime = TimeCurrent(); // 最後のトレード時間を更新
      return;
   }
   
   // Sell conditions (Dead Cross)
   if (shortMA < longMA && PreviousDeadCross())
   {
      res = OrderSend(Symbol(), OP_SELL, LotsOptimized(), Bid, 3, 0, 0, "", MAGICMA, 0, Red);
      if(res > 0) LastTradeTime = TimeCurrent(); // 最後のトレード時間を更新
      return;
   }
}
/*

return (shortMA1 <= longMA1) は、shortMA1 が longMA1 
以下の場合に true を返し、それ以外の場合は false を返します。MQL4 では、true は 1 に、false は 0 にそれぞれ相当します。
この条件式の結果は 1 または 0 として解釈されますが、実際には論理値 (true または false) が返されます。
*/
// ゴールデンクロスが確認されたかどうかをチェック
bool PreviousGoldenCross()
{
   double shortMA1 = iMA(NULL, 0, ShortMovingPeriod, 0, MODE_SMA, PRICE_CLOSE, 1);
   double longMA1 = iMA(NULL, 0, LongMovingPeriod, 0, MODE_SMA, PRICE_CLOSE, 1);
   return (shortMA1 <= longMA1);
}

// デッドクロスが確認されたかどうかをチェック
bool PreviousDeadCross()
{
   double shortMA1 = iMA(NULL, 0, ShortMovingPeriod, 0, MODE_SMA, PRICE_CLOSE, 1);
   double longMA1 = iMA(NULL, 0, LongMovingPeriod, 0, MODE_SMA, PRICE_CLOSE, 1);
   return (shortMA1 >= longMA1);
}

//+------------------------------------------------------------------+
//| Check for close order conditions                                 |
//+------------------------------------------------------------------+
void CheckForClose()
{
   double shortMA, longMA;
   // Get Moving Averages
   shortMA = iMA(NULL, 0, ShortMovingPeriod, 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 < longMA)
         {
            if (!OrderClose(OrderTicket(), OrderLots(), Bid, 3, White))
               Print("OrderClose error ", GetLastError());
         }
         break;
      }
      
      if (OrderType() == OP_SELL)
      {
         if (shortMA > 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();
//---
  }
//+------------------------------------------------------------------+

分からない部分があれば、続けて質問してみましょう。

return (shortMA1 <= longMA1)は1か0が入りますか?と質問してみます。

return (shortMA1 <= longMA1)
この条件式の結果は 1 または 0 として解釈されますが、実際には論理値 (true または false) が返されます。

との答えが返ってきました。

なるほど、疑問点があればきちんと教えてくれるので、プログラミングスクールの先生みたいなこともできるわけですね。

上記のソースコードでMT4のバックテストをまわしたところ、理想のトレードになったのですが、成績が悪化しました。

プロフィットファクターは1.10⇒1.03に低下

おそらく改善案として導入されたフィルターが悪さをしているようです。

ちなみに私が開発したEA「GoldenCross_USDJPY」では成績が悪化することはありませんでした。

GoldenCross_USDJPY
https://www.gogojungle.co.jp/systemtrade/fx/43098

トレンドフォロー型のEAの場合、ゴールデンクロス・デッドクロスよりもパーフェクトオーダーの方が使い勝手が良いので、次回は3本の移動平均線を使ったロジックをChatGPTに作ってもらいたいと思います。


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