English Русский Deutsch
preview
プログラミングパラダイムについて(第1部):プライスアクションエキスパートアドバイザー開発の手続き型アプローチ

プログラミングパラダイムについて(第1部):プライスアクションエキスパートアドバイザー開発の手続き型アプローチ

MetaTrader 5 | 20 3月 2024, 11:21
136 0
Kelvin Muturi Muigua
Kelvin Muturi Muigua

はじめに

ソフトウェア開発の世界では、プログラミングパラダイムはコードを書き、整理するための指針となる青写真です。目的地に到達するためにさまざまなルートを選択するように、MQL5を使用してタスクを達成するためのさまざまなプログラミングアプローチやパラダイムが存在します。

2回にわたって、MQL5で取引ツールを構築するために必要な基本的なプログラミングパラダイムを探っていきます。 私のゴールは、短くて効率的なコードで素晴らしい結果を生み出す、効果的でベストプラクティスのメソッドを共有することです。それぞれのプログラミングスタイルを説明し、完全に機能するEAを作成することで実演します。

プログラミングパラダイムの種類

MQL5の開発者やプログラマーが知っておくべき主要なプログラミングパラダイムは3つあります。

  1. 手続き型プログラミング:本稿では、このパラダイムに焦点を当てます。
  2. 関数型プログラミング:このパラダイムは手続き型プログラミングとよく似ているので、この記事でも取り上げます。
  3. オブジェクト指向プログラミング(OOP):このパラダイムについては、次回の記事で説明します。
各パラダイムには独自のルールと特性があり、特定の問題を解決し、MQL5でさまざまな取引ツールを効果的に開発する方法を形作るように設計されています。


手続き型プログラミングについて

手続き型プログラミングは、コードを書くための体系的で段階的なアプローチです。レシピに従うように、あらゆる問題を一連の正確な指示に分解します。プログラマーは、コンピュータに明確な道筋を示し、望ましい結果を得るために一行一行を導いていきます。

プログラミングが初めてでも、コード構成に興味があるだけでも、手続き型プログラミングは、コーディングの世界へのわかりやすく直感的な入口を提供します。

手続き型プログラミングの主な性質


手続き型プログラミングを特徴づける主な性質は以下の通りです。

  1. 関数
    手続き型プログラミングの中核は関数です。これらは、特定のタスクを実行するために集団化された命令のセットです。関数は機能をカプセル化し、モジュール化とコードの再利用を促進します。
  2. トップダウン設計
    手続き型プログラミングは多くの場合、トップダウンの設計アプローチを採用しています。開発者は問題をより小さく、管理しやすいサブタスクに分解します。各サブタスクは個別に解決され、全体的な解決に貢献します。
  3. 命令型スタイル
    手続き型プログラミングの命令型あるいはコマンド型の性質は、プログラムの状態を変更する明示的な文を重視します。開発者は、一連の手続き的コマンドを通して、プログラムがどのようにタスクを達成すべきかを指定します。
  4. 変数とデータ
    手続き型プログラミングにおける手続きや関数は、変数やデータを操作します。これらの変数は、プログラムの実行中に変化する値を保持することができます。状態の変化は手続き型プログラミングの基本です。
  5. 順次実行
    プログラムの実行は逐次的な流れに従います。文は次々に実行され、ループや条件文のような制御構造がプログラムの流れを導きます。
  6. モジュール性
    手続き型プログラミングは、コードを手続きや関数にまとめることでモジュール化を促進します。各モジュールは、プログラムの機能の特定の側面を扱い、コードの構成と保守性を向上させます。
  7. 再利用性
    コードの再利用性は、手続き型プログラミングの重要な利点です。一旦関数が書かれ、テストされれば、その特定の関数がプログラム内で必要とされるところならどこでも使用することができます。冗長性が減り、効率が促進されます。
  8. 読みやすさ
    手続き型コードは、特にステップバイステップのアプローチに慣れている人にとっては、より読みやすい傾向があります。実行の流れが直線的なので、プログラムのロジックを追うのが簡単です。

関数型プログラミングについて

関数型プログラミングは、第一級市民としての関数と不変性という概念を中心に展開されます。データをどのように扱い、タスクを実行するかという大原則を除けば、手続き型プログラミングに似ています。

プログラムの実行中にデータの姿や役割を変えることができる手続き型プログラミングとは異なり、関数型プログラミングはより安定した環境を好みます。一度作成されたデータはそのまま残ります。不変性へのコミットメントは、予測可能性のレベルを保証し、コードにおける予期せぬ副作用を防ぐのに役立ちます。

関数型プログラミングの主な性質


次に、関数型プログラミングを定義する主な特徴をいくつか紹介します。

  1. 不変性
    関数型プログラミングでは、不変性が基本原則です。一度作成されたデータは変更されません。既存のデータを修正するのではなく、必要な変更を加えた新しいデータを作成します。これにより予測可能性が確保され、意図しない副作用を避けることができます。
  2. 第一級市民としての関数
    関数は第一級市民として扱われ、変数に代入したり、他の関数に引数として渡したり、他の関数の結果として返したりすることができます。この柔軟性により、高階関数の作成が可能になり、よりモジュール的で表現力豊かなコーディングスタイルが促進されます。
  3. 宣言的なスタイル
    関数型プログラミングでは、宣言的なプログラミングスタイルが好まれ、そこでは、プログラムをどのように実現するかよりも、プログラムが何を達成すべきかに焦点が置かれます。これは、より簡潔で読みやすいコードに貢献します。
  4. 変更可能な状態の回避
    関数型プログラミングでは、変更可能な状態は最小限に抑えられるか、排除されます。データは不変なものとして扱われ、関数は外部の状態を変更しません。この特性は、関数の動作に関する推論を単純化します。
  5. 再帰と高階関数
    関数型プログラミングでは、関数が他の関数を引数に取ったり、結果として返したりする再帰や高階関数がよく使われます。これは、よりモジュール化された再利用可能なコードにつながります。


プライスアクションEAを開発するための手続き型アプローチ

手続き型プログラミングと関数型プログラミングのパラダイムの本質を掘り下げたところで、実践的な例で理論を実践してみましょう。まず、自動化する取引戦略について説明します。後ほど、コードのさまざまなコンポーネントをナビゲートし、それぞれの機能とシームレスに連動する方法を解き明かしていきます。


EMA指標によるプライスアクション戦略


私たちの取引戦略は、指数移動平均(EMA)として知られる単一の指標に依存しています。この指標は、テクニカル分析で広く使用され、選択した取引設定に基づいて市場の方向性を決定するのに役立ちます。移動平均はMQL5の標準指標として簡単に見つけることができるので、コードに組み込むのは簡単です。


買いエントリ
直近で閉じたローソク足が買いローソク足で、安値と高値の両方が指数移動平均(EMA)を上回ったら買いポジションを建てます。

プライスアクションEMA戦略買いシグナル


売りエントリ
直近で閉じたローソク足が売りローソク足で、安値と高値の両方が指数移動平均(EMA)を下回ったら売りポジションを建てます。

プライスアクションEMA戦略売りシグナル


エグジット
自動的にすべてのポジションを決済し、ユーザーが指定した口座の利益または損失のパーセンテージが達成されたときに関連する利益または損失を実現するか、または従来のストップロスまたはテイクプロフィット注文を使用します。


設定 条件
買いエントリ 直近に閉じたローソク足が買いローソク足(終値>始値)であり、安値と高値の両方が指数移動平均(EMA)を上回っている場合。
売りエントリ 直近に閉じたローソク足が売りローソク足(終値<始値)であり、安値と高値の両方が指数移動平均(EMA)を下回っている場合。
エグジット  ユーザーが指定したパーセンテージに達したとき、またはストップロス注文または利食い注文がトリガーされたときに、すべてのポジションをクローズし、利益または損失を実現


取引戦略のコーディングと実装

さて、取引ルールと取引計画を立てたので、MetaEditor IDEでMQL5コードを書いて、取引戦略を実現させましょう。以下の手順に従って、必要な必須機能のみを含むクリーンなEAテンプレートから始めるようにします。


ステップ1: MetaEditor IDEを開き、'New'メニューアイテムボタンを使用して'MQLウィザード'を起動します。

MQL5ウィザードの新しEA


ステップ2: [Expert Advisor (template)]オプションを選択し、[Next]をクリックします。

MQL5ウィザードの新しEA


ステップ3:  General Propertiesセクションで、EA名を入力し、[Next]をクリックして進みます。

MQL5ウィザードの新しEA


ステップ4:  Event Handlersセクションで、オプションが選択されていないことを確認します。オプションが選択されている場合はチェックを外し、[Next]をクリックします。

MQL5ウィザードの新しEA


ステップ5: Tester Event Handlersセクションで、オプションが選択されていないことを確認します。オプションが選択されている場合はチェックを外し、[Finish]をクリックしてMQL5 EAテンプレートを生成します。

MQL5ウィザードの新しEA


これで、必須関数(OnInit、OnDeinit、OnTick)のみを持つ、クリーンなMQL5 EAテンプレートができました。続行する前に、新しいファイルを保存することを忘れないでください。

新しく生成されたEAのコードは次のようになります。

//+------------------------------------------------------------------+
//|                                               PriceActionEMA.mq5 |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---

  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---

  }
//+------------------------------------------------------------------+


まず、EAの簡単な説明を書き、ユーザー入力変数を宣言して初期化し、すべてのグローバル変数を宣言しましょう。このコードをOnInit()関数の直前に配置します。

#property description "A price action EA to demonstrate how to "
#property description "implement the procedural programming paradigm."

//--User input variables
input long magicNumber = 101;//Magic Number (Set 0 [Zero] to disable

input group ""
input ENUM_TIMEFRAMES tradingTimeframe = PERIOD_H1;//Trading Timeframe
input int emaPeriod = 20;//Moving Average Period
input int emaShift = 0;//Moving Average Shift

input group ""
input bool enableTrading = true;//Enable Trading
input bool enableAlerts = false;//Enable Alerts

input group ""
input double accountPercentageProfitTarget = 10.0;//Account Percentage (%) Profit Target
input double accountPercentageLossTarget = 10.0;//Account Percentage (%) Loss Target

input group ""
input int maxPositions = 3;//Max Positions (Max open positions in one direction)
input int tp = 5000;//TP (Take Profit Points/Pips [Zero (0) to diasable])
input int sl = 10000;//SL (Stop Loss Points/Pips [Zero (0) to diasable])


すでに学んだように、「手続き型プログラミングの核心は関数です。これらは、特定のタスクを実行するために集団化された命令のセットです。関数は機能をカプセル化し、モジュール化とコードの再利用を促進します」。独自のカスタム関数を作成することで、これを実装していきます。

GetInit関数

この関数は、EAが読み込みまたは初期化されたときに、すべてのグローバル変数を初期化し、その他のタスクを実行します。

int GetInit() //Function to initialize the robot and all the variables
  {
   int returnVal = 1;
//create the iMA indicator
   emaHandle = iMA(Symbol(), tradingTimeframe, emaPeriod, emaShift, MODE_EMA, PRICE_CLOSE);
   if(emaHandle < 0)
     {
      Print("Error creating emaHandle = ", INVALID_HANDLE);
      Print("Handle creation: Runtime error = ", GetLastError());
      //force program termination if the handle is not properly set
      return(-1);
     }
   ArraySetAsSeries(movingAverage, true);

//reset the count for positions
   totalOpenBuyPositions = 0;
   totalOpenSellPositions = 0;
   buyPositionsProfit = 0.0;
   sellPositionsProfit = 0.0;
   buyPositionsVol = 0.0;
   sellPositionsVol = 0.0;

   closedCandleTime = iTime(_Symbol, tradingTimeframe, 1);

   startingCapital = AccountInfoDouble(ACCOUNT_EQUITY);//used to calculate the account percentage profit

   if(enableAlerts)
     {
      Alert(MQLInfoString(MQL_PROGRAM_NAME), " has just been LOADED in the ", Symbol(), " ", EnumToString(Period()), " period chart.");
     }

//structure our comment string
   commentString = "\n\n" +
                   "Account No: " + IntegerToString(AccountInfoInteger(ACCOUNT_LOGIN)) +
                   "\nAccount Type: " + EnumToString((ENUM_ACCOUNT_TRADE_MODE)AccountInfoInteger(ACCOUNT_TRADE_MODE)) +
                   "\nAccount Leverage: " + IntegerToString(AccountInfoInteger(ACCOUNT_LEVERAGE)) +
                   "\n-----------------------------------------------------------------------------------------------------";
   return(returnVal);
  }


GetDeinit関数

この関数は、EAがシャットダウンされる前に、使用済みメモリを解放し、チャートコメントを消去し、その他の初期化タスクを処理します。

void GetDeinit()  //De-initialize the robot on shutdown and clean everything up
  {
   IndicatorRelease(emaHandle); //delete the moving average handle and deallocate the memory spaces occupied
   ArrayFree(movingAverage); //free the dynamic arrays containing the moving average buffer data

   if(enableAlerts)
     {
      Alert(MQLInfoString(MQL_PROGRAM_NAME), " has just been REMOVED from the ", Symbol(), " ", EnumToString(Period()), " period chart.");
     }
//delete and clear all chart displayed messages
   Comment("");
  }

GetEma関数

この関数は、指数移動平均の値を取得し、EMAの値と最近閉じたローソク足の始値、終値、高値、安値を比較することで、相場の方向性を判断します。他の関数でさらに処理できるように、市場の方向値をグローバル変数に保存します。

void GetEma()
  {
//Get moving average direction
   if(CopyBuffer(emaHandle, 0, 0, 100, movingAverage) <= 0)
     {
      return;
     }
   movingAverageTrend = "FLAT";
   buyOk = false;
   sellOk = false;
   if(movingAverage[1] > iHigh(_Symbol, tradingTimeframe, 1) && movingAverage[1] > iLow(_Symbol, tradingTimeframe, 1))
     {
      movingAverageTrend = "SELL/SHORT";
      if(iClose(_Symbol, tradingTimeframe, 1) < iOpen(_Symbol, tradingTimeframe, 1))
        {
         sellOk = true;
         buyOk = false;
        }
     }
   if(movingAverage[1] < iHigh(_Symbol, tradingTimeframe, 1) && movingAverage[1] < iLow(_Symbol, tradingTimeframe, 1))
     {
      movingAverageTrend = "BUY/LONG";
      if(iClose(_Symbol, tradingTimeframe, 1) > iOpen(_Symbol, tradingTimeframe, 1))
        {
         buyOk = true;
         sellOk = false;
        }
     }
  }


GetPositionsData関数

この関数は、すべてのポジションをスキャンし、利益額、オープンしたポジションの合計数、オープンした買いポジションと売りポジションの合計、各ポジションタイプの合計数量/ロットなどのプロパティを保存します。EAが開いていないポジションやEAのマジックナンバーと一致しないポジションのデータは除外されます。

void GetPositionsData()
  {
//get the total number of all open positions and their status
   if(PositionsTotal() > 0)
     {
      //variables for storing position properties values
      ulong positionTicket;
      long positionMagic, positionType;
      string positionSymbol;
      int totalPositions = PositionsTotal();

      //reset the count
      totalOpenBuyPositions = 0;
      totalOpenSellPositions = 0;
      buyPositionsProfit = 0.0;
      sellPositionsProfit = 0.0;
      buyPositionsVol = 0.0;
      sellPositionsVol = 0.0;

      //scan all the open positions
      for(int x = totalPositions - 1; x >= 0; x--)
        {
         positionTicket = PositionGetTicket(x);//gain access to other position properties by selecting the ticket
         positionMagic = PositionGetInteger(POSITION_MAGIC);
         positionSymbol = PositionGetString(POSITION_SYMBOL);
         positionType = PositionGetInteger(POSITION_TYPE);

         if(positionMagic == magicNumber && positionSymbol == _Symbol)
           {
            if(positionType == POSITION_TYPE_BUY)
              {
               ++totalOpenBuyPositions;
               buyPositionsProfit += PositionGetDouble(POSITION_PROFIT);
               buyPositionsVol += PositionGetDouble(POSITION_VOLUME);
              }
            if(positionType == POSITION_TYPE_SELL)
              {
               ++totalOpenSellPositions;
               sellPositionsProfit += PositionGetDouble(POSITION_PROFIT);
               sellPositionsVol += PositionGetDouble(POSITION_VOLUME);
              }
           }
        }
      //Get and save the account percentage profit
      accountPercentageProfit = ((buyPositionsProfit + sellPositionsProfit) * 100) / startingCapital;
     }
   else  //if no positions are open then the account percentage profit should be zero
     {
      startingCapital = AccountInfoDouble(ACCOUNT_EQUITY);
      accountPercentageProfit = 0.0;

      //reset position counters too
      totalOpenBuyPositions = 0;
      totalOpenSellPositions = 0;
     }
  }

TradingIsAllowed関数

この関数は、ユーザー、ターミナル、証券会社がEAに取引許可を与えているかどうかを確認します。

bool TradingIsAllowed()
  {
//check if trading is enabled
   if(enableTrading &&
      MQLInfoInteger(MQL_TRADE_ALLOWED) && TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) &&
      AccountInfoInteger(ACCOUNT_TRADE_ALLOWED) && AccountInfoInteger(ACCOUNT_TRADE_EXPERT)
     )
     {
      tradingStatus = "\n-----------------------------------------------------------------------------------------" +
                      "\nTRADING IS FULLY ENABLED! *** SCANNING FOR ENTRY ***";
      return(true);
     }
   else  //trading is disabled
     {
      tradingStatus = "\n-----------------------------------------------------------------------------------------" +
                      "\nTRADING IS NOT FULLY ENABLED! *** GIVE EA PERMISSION TO TRADE ***";
      return(false);
     }
  }

TradeNow関数

すべての必要なチェックとシグナルが、新しい取引の初期化を進めても問題ないことを示した場合、新しいポジションを開く責任を負います。

void TradeNow()
  {
//Detect new candle formation and open a new position
   if(closedCandleTime != iTime(_Symbol, tradingTimeframe, 1))  //-- New candle found
     {
      //use the candle time as the position comment to prevent opening dublicate trades on one candle
      string positionComment = IntegerToString(iTime(_Symbol, tradingTimeframe, 1));

      //open a buy position
      if(buyOk && totalOpenBuyPositions < maxPositions)
        {
         //Use the positionComment string to check if we had already have a position open on this candle
         if(!PositionFound(_Symbol, POSITION_TYPE_BUY, positionComment)) //no position has been openend on this candle, open a buy position now
           {
            BuySellPosition(POSITION_TYPE_BUY, positionComment);
           }
        }

      //open a sell position
      if(sellOk && totalOpenSellPositions < maxPositions)
        {
         //Use the positionComment string to check if we had already have a position open on this candle
         if(!PositionFound(_Symbol, POSITION_TYPE_SELL, positionComment)) //no position has been openend on this candle, open a sell position now
           {
            BuySellPosition(POSITION_TYPE_SELL, positionComment);
           }
        }

      //reset closedCandleTime value to prevent new entry orders from opening before a new candle is formed
      closedCandleTime = iTime(_Symbol, tradingTimeframe, 1);
     }
  }

ManageProfitAndLoss関数

この関数は、ユーザーが入力した利益と損失のしきい値に達したかどうかを確認します。条件が満たされた場合、すべてのポジションを決済することで、すべての利益または損失を清算します。

void ManageProfitAndLoss()
  {
//if the account percentage profit or loss target is hit, delete all positions
   double lossLevel = -accountPercentageLossTarget;
   if(
      (accountPercentageProfit >= accountPercentageProfitTarget || accountPercentageProfit <= lossLevel) ||
      ((totalOpenBuyPositions >= maxPositions || totalOpenSellPositions >= maxPositions) && accountPercentageProfit > 0)
   )
     {
      //delete all open positions
      if(PositionsTotal() > 0)
        {
         //variables for storing position properties values
         ulong positionTicket;
         long positionMagic, positionType;
         string positionSymbol;
         int totalPositions = PositionsTotal();

         //scan all the open positions
         for(int x = totalPositions - 1; x >= 0; x--)
           {
            positionTicket = PositionGetTicket(x);//gain access to other position properties by selecting the ticket
            positionMagic = PositionGetInteger(POSITION_MAGIC);
            positionSymbol = PositionGetString(POSITION_SYMBOL);
            positionType = PositionGetInteger(POSITION_TYPE);
            int positionDigits= (int)SymbolInfoInteger(positionSymbol, SYMBOL_DIGITS);
            double positionVolume = PositionGetDouble(POSITION_VOLUME);
            ENUM_POSITION_TYPE positionType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);

            if(positionMagic == magicNumber && positionSymbol == _Symbol)
              {
               //print the position details
               Print("*********************************************************************");
               PrintFormat(
                  "#%I64u %s  %s  %.2f  %s [%I64d]",
                  positionTicket, positionSymbol, EnumToString(positionType), positionVolume,
                  DoubleToString(PositionGetDouble(POSITION_PRICE_OPEN), positionDigits), positionMagic
               );

               //reset the the tradeRequest and tradeResult values by zeroing them
               ZeroMemory(tradeRequest);
               ZeroMemory(tradeResult);
               //set the operation parameters
               tradeRequest.action = TRADE_ACTION_DEAL;//type of trade operation
               tradeRequest.position = positionTicket;//ticket of the position
               tradeRequest.symbol = positionSymbol;//symbol
               tradeRequest.volume = positionVolume;//volume of the position
               tradeRequest.deviation = SymbolInfoInteger(_Symbol, SYMBOL_SPREAD);//allowed deviation from the price
               tradeRequest.magic = magicNumber;//MagicNumber of the position

               //set the price and order type depending on the position type
               if(positionType == POSITION_TYPE_BUY)
                 {
                  tradeRequest.price = SymbolInfoDouble(positionSymbol, SYMBOL_BID);
                  tradeRequest.type = ORDER_TYPE_SELL;
                 }
               else
                 {
                  tradeRequest.price = SymbolInfoDouble(positionSymbol, SYMBOL_ASK);
                  tradeRequest.type = ORDER_TYPE_BUY;
                 }

               //print the position close details
               PrintFormat("Close #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType));
               //send the tradeRequest
               if(OrderSend(tradeRequest, tradeResult)) //trade tradeRequest success, position has been closed
                 {
                  if(enableAlerts)
                    {
                     Alert(
                        _Symbol + " PROFIT LIQUIDATION: Just successfully closed POSITION (#" +
                        IntegerToString(positionTicket) + "). Check the EA journal for more details."
                     );
                    }
                  PrintFormat("Just successfully closed position: #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType));
                  PrintFormat("retcode=%u  deal=%I64u  order=%I64u", tradeResult.retcode, tradeResult.deal, tradeResult.order);
                 }
               else  //trade tradeRequest failed
                 {
                  //print the information about the operation
                  if(enableAlerts)
                    {
                     Alert(
                        _Symbol + " ERROR ** PROFIT LIQUIDATION: closing POSITION (#" +
                        IntegerToString(positionTicket) + "). Check the EA journal for more details."
                     );
                    }
                  PrintFormat("Position clossing failed: #%I64d %s %s", positionTicket, positionSymbol, EnumToString(positionType));
                  PrintFormat("OrderSend error %d", GetLastError());//print the error code
                 }
              }
           }
        }
     }
  }

PrintOnChart関数

EAのステータスを書式設定してチャート上に表示し、口座とEAのステータスを視覚的にテキスト表示します。

void PrintOnChart()
  {
//update account status strings and display them on the chart
   accountStatus = "\nAccount Balance: " + DoubleToString(AccountInfoDouble(ACCOUNT_BALANCE), 2) + accountCurrency +
                   "\nAccount Equity: " + DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY), 2) + accountCurrency +
                   "\nAccount Profit: " + DoubleToString(AccountInfoDouble(ACCOUNT_PROFIT), 2) + accountCurrency +
                   "\nAccount Percentage Profit: " + DoubleToString(accountPercentageProfit, 2) + "%" +
                   "\n-----------------------------------------------------------------------------------------" +
                   "\nTotal Buy Positions Open: " + IntegerToString(totalOpenBuyPositions) +
                   "        Total Vol/Lots: " + DoubleToString(buyPositionsVol, 2) +
                   "        Profit: " + DoubleToString(buyPositionsProfit, 2) + accountCurrency +
                   "\nTotal Sell Positions Open: " + IntegerToString(totalOpenSellPositions) +
                   "        Total Vol/Lots: " + DoubleToString(sellPositionsVol, 2) +
                   "        Profit: " + DoubleToString(sellPositionsProfit, 2) + accountCurrency +
                   "\nPositionsTotal(): " + IntegerToString(PositionsTotal()) +
                   "\n-----------------------------------------------------------------------------------------" +
                   "\nJust Closed Candle:     Open: " + DoubleToString(iOpen(_Symbol, tradingTimeframe, 1), _Digits) +
                   "     Close: " + DoubleToString(iClose(_Symbol, tradingTimeframe, 1), _Digits) +
                   "     High: " + DoubleToString(iHigh(_Symbol, tradingTimeframe, 1), _Digits) +
                   "     Low: " + DoubleToString(iLow(_Symbol, tradingTimeframe, 1), _Digits) +
                   "\n-----------------------------------------------------------------------------------------" +
                   "\nMovingAverage (EMA): " + DoubleToString(movingAverage[1], _Digits) +
                   "     movingAverageTrend = " + movingAverageTrend +
                   "\nsellOk: " + IntegerToString(sellOk) +
                   "\nbuyOk: " + IntegerToString(buyOk);

//show comments on the chart
   Comment(commentString + accountStatus + tradingStatus);
  }


BuySellPosition関数

この関数は、新しい買いポジションと売りポジションを開きます。

bool BuySellPosition(int positionType, string positionComment)
  {
//reset the the tradeRequest and tradeResult values by zeroing them
   ZeroMemory(tradeRequest);
   ZeroMemory(tradeResult);
//initialize the parameters to open a position
   tradeRequest.action = TRADE_ACTION_DEAL;
   tradeRequest.symbol = Symbol();
   tradeRequest.deviation = SymbolInfoInteger(_Symbol, SYMBOL_SPREAD);
   tradeRequest.magic = magicNumber;
   tradeRequest.comment = positionComment;
   double volumeLot = NormalizeDouble(((SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN) * AccountInfoDouble(ACCOUNT_EQUITY)) / 10000), 2);

   if(positionType == POSITION_TYPE_BUY)
     {
      if(sellPositionsVol > volumeLot && AccountInfoDouble(ACCOUNT_MARGIN_LEVEL) > 200)
        {
         volumeLot = NormalizeDouble((sellPositionsVol + volumeLot), 2);
        }
      if(volumeLot < 0.01)
        {
         volumeLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
        }
      if(volumeLot > SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX))
        {
         volumeLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
        }
      tradeRequest.volume = NormalizeDouble(volumeLot, 2);
      tradeRequest.type = ORDER_TYPE_BUY;
      tradeRequest.price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
      if(tp > 0)
        {
         tradeRequest.tp = NormalizeDouble(tradeRequest.price + (tp * _Point), _Digits);
        }
      if(sl > 0)
        {
         tradeRequest.sl = NormalizeDouble(tradeRequest.price - (sl * _Point), _Digits);
        }
      if(OrderSend(tradeRequest, tradeResult)) //successfully openend the position
        {
         if(enableAlerts)
           {
            Alert(_Symbol, " Successfully openend BUY POSITION #", tradeResult.order, ", Price: ", tradeResult.price);
           }
         PrintFormat("retcode=%u  deal=%I64u  order=%I64u", tradeResult.retcode, tradeResult.deal, tradeResult.order);
         return(true);
        }
      else
        {
         if(enableAlerts)
           {
            Alert(_Symbol, " ERROR opening a BUY POSITION at: ", SymbolInfoDouble(_Symbol, SYMBOL_ASK));
           }
         PrintFormat("ERROR: Opening a BUY POSITION: ErrorCode = %d",GetLastError());//OrderSend failed, output the error code
         return(false);
        }
     }

   if(positionType == POSITION_TYPE_SELL)
     {
      if(buyPositionsVol > volumeLot && AccountInfoDouble(ACCOUNT_MARGIN_LEVEL) > 200)
        {
         volumeLot = NormalizeDouble((buyPositionsVol + volumeLot), 2);
        }
      if(volumeLot < 0.01)
        {
         volumeLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
        }
      if(volumeLot > SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX))
        {
         volumeLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
        }
      tradeRequest.volume = NormalizeDouble(volumeLot, 2);
      tradeRequest.type = ORDER_TYPE_SELL;
      tradeRequest.price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
      if(tp > 0)
        {
         tradeRequest.tp = NormalizeDouble(tradeRequest.price - (tp * _Point), _Digits);
        }
      if(sl > 0)
        {
         tradeRequest.sl = NormalizeDouble(tradeRequest.price + (sl * _Point), _Digits);
        }
      if(OrderSend(tradeRequest, tradeResult)) //successfully openend the position
        {
         if(enableAlerts)
           {
            Alert(_Symbol, " Successfully openend SELL POSITION #", tradeResult.order, ", Price: ", tradeResult.price);
           }
         PrintFormat("retcode=%u  deal=%I64u  order=%I64u", tradeResult.retcode, tradeResult.deal, tradeResult.order);
         return(true);
        }
      else
        {
         if(enableAlerts)
           {
            Alert(_Symbol, " ERROR opening a SELL POSITION at: ", SymbolInfoDouble(_Symbol, SYMBOL_ASK));
           }
         PrintFormat("ERROR: Opening a SELL POSITION: ErrorCode = %d",GetLastError());//OrderSend failed, output the error code
         return(false);
        }
     }
   return(false);
  }

PositionFound関数

この関数は、EAが1つのローソク足で複数の重複したポジションを開かないように、指定されたポジションが存在するかどうかを確認します。

bool PositionFound(string symbol, int positionType, string positionComment)
  {
   if(PositionsTotal() > 0)
     {
      ulong positionTicket;
      int totalPositions = PositionsTotal();
      //scan all the open positions
      for(int x = totalPositions - 1; x >= 0; x--)
        {
         positionTicket = PositionGetTicket(x);//gain access to other position properties by selecting the ticket
         if(
            PositionGetInteger(POSITION_MAGIC) == magicNumber && PositionGetString(POSITION_SYMBOL) == symbol &&
            PositionGetInteger(POSITION_TYPE) == positionType && PositionGetString(POSITION_COMMENT) == positionComment
         )
           {
            return(true);//a similar position exists, don't open another position on this candle
            break;
           }
        }
     }
   return(false);
  }


カスタム関数を定義したので、それらを呼び出して本来のタスクを実行させましょう。

  • OnInit関数内にGetInit関数を配置し、呼び出します。 

int OnInit()
  {
//---
   if(GetInit() <= 0)
     {
      return(INIT_FAILED);
     }
//---
   return(INIT_SUCCEEDED);
  }

  • OnDeinit関数内にGetDeinit関数を配置し、呼び出します。 

void OnDeinit(const int reason)
  {
//---
   GetDeinit();
  }

  • OnTick関数内に以下の関数を適切な順序で配置し、呼び出します。関数の中には、他の関数が重要な決定を下す際に参照するグローバル変数を変更するものがあるため、他の関数がアクセスする前にデータが処理され更新されるように、最初に呼び出すようにします。 

void OnTick()
  {
//---
   GetEma();
   GetPositionsData();
   if(TradingIsAllowed())
     {
      TradeNow();
      ManageProfitAndLoss();
     }
   PrintOnChart();
  }

EAコードが完成したら、保存してコンパイルします。これにより、取引ターミナルから直接EAにアクセスすることができます。全コードは記事の下に添付されています。


ストラテジーテスターでEAをテストする

EAを計画通りに運用することは極めて重要です。そのためには、アクティブな銘柄チャートにストラテジーをロードしてデモ口座で取引するか、ストラテジーテスターを利用して総合的に評価します。デモ口座でテストすることもできますが、今はストラテジーテスターを使ってそのパフォーマンスを評価することにしましょう。

以下は、ストラテジーテスターで適用する設定です。

  • 証券会社:MT5 Metaquotesデモ口座(MT5のインストール時に自動的に作成)

  • 銘柄:EURUSD

  • テスト期間(日付):1年間(2022年11月~2023年11月)

  • モデリング:実際のティックに基づいたすべてのティック

  • 保証金:10,000米ドル

  • レバレッジ:1:100

PriceActionEAストラテジーテスター設定


PriceActionEAストラテジーテスター設定


バックテスト結果を見直すと、私たちのEAは利益を生み出しただけでなく、ドローダウンも驚くほど低く維持していいます。この戦略は有望であり、特に複数の銘柄に同時に適用した場合、より良い結果を得るためにさらに修正最適化することができます。

PriceActionEMAテスター結果

PriceActionEMAテスター結果

PriceActionEMAテスター結果


結論

初心者のMQL5プログラマーでも、上記で作成したEAの手続きコードを理解するのは簡単です。この単純さは、手続き型プログラミングの明確で直接的な性質から生じるもので、特に、特定のタスクに基づいてコードを整理するために関数を利用したり、変更されたデータを異なる関数に渡すためにグローバル変数を利用したりする場合に顕著です。

しかし、手続き型コードの欠点は、EAが複雑になるにつれて大幅に拡張される傾向があることで、主に複雑でないプロジェクトに適していることに気づかれるかもしれません。プロジェクトが非常に複雑な場合は、手続き型プログラミングよりもオブジェクト指向プログラミングの方が有利です。

次回の記事では、オブジェクト指向プログラミングを紹介し、最近作成した手続き型プライスアクションEAコードをオブジェクト指向コードに変換します。これにより、これらのパラダイムを明確に比較することができ、違いをより明確に理解することができます。

この記事を読むために時間を費やしていただきありがとうございます。読者のMQL5開発の旅と取引の試みが最高のものになることを祈っています。

MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/13771

添付されたファイル |
PriceActionEMA.mq5 (23.33 KB)
MQL5における修正グリッドヘッジEA(第1部):シンプルなヘッジEAを作る MQL5における修正グリッドヘッジEA(第1部):シンプルなヘッジEAを作る
古典的なグリッド戦略と古典的なヘッジ戦略を混合した、より高度なグリッドヘッジEAのベースとして、シンプルなヘッジEAを作成する予定です。この記事が終わるころには、簡単なヘッジ戦略の作り方がわかり、この戦略が本当に100%儲かるかどうかについての人々の意見も知ることができるでしょう。
ソフトウェア開発とMQL5におけるデザインパターン(第3回):振る舞いパターン1 ソフトウェア開発とMQL5におけるデザインパターン(第3回):振る舞いパターン1
デザインパターンの新しい記事として、その1タイプである振る舞いパターンを取り上げ、作成されたオブジェクト間の通信を効果的に構築する方法について説明します。これらの振る舞いパターンを完成させることで、再利用可能かつ拡張可能で、テストされたソフトウェアをどのように作成し、構築できるかを理解できるようになります。
MQL5を使ったシンプルな多通貨エキスパートアドバイザーの作り方(第5回): ケルトナーチャネルのボリンジャーバンド—指標シグナル MQL5を使ったシンプルな多通貨エキスパートアドバイザーの作り方(第5回): ケルトナーチャネルのボリンジャーバンド—指標シグナル
この記事の多通貨エキスパートアドバイザー(EA)は、1つの銘柄チャートからのみ複数の銘柄ペアの取引(注文を出す、注文を決済する、トレーリングストップロスとトレーリングプロフィットなどで注文を管理するなど)ができるEAまたは自動売買ロボットです。この記事では、2つの指標、この場合はケルトナーチャネルのボリンジャーバンド®からのシグナルを使用します。
知っておくべきMQL5ウィザードのテクニック(第08回):パーセプトロン 知っておくべきMQL5ウィザードのテクニック(第08回):パーセプトロン
パー​​セプトロン(単一隠れ層ネットワーク)は、基本的な自動取引に精通していて、ニューラルネットワークを試してみようとしている人にとって、優れた入門編となります。エキスパートアドバイザー(EA)用のMQL5ウィザードクラスの一部であるシグナルクラスアセンブリでこれをどのように実現できるかを段階的に見ていきます。