English
preview
Market Sentimentインジケーターの自動化

Market Sentimentインジケーターの自動化

MetaTrader 5トレーディングシステム |
19 0
Hlomohang John Borotho
Hlomohang John Borotho

はじめに

前回の記事では、複数の時間足からのシグナルを1つの見やすいパネルに統合したMarket Sentimentカスタムインジケーターを開発しました。上位時間足のトレンドと下位時間足の構造を併せて分析し、プライスアクション、スイングハイ/スイングロー、移動平均、ボラティリティを検証することで、市場を強気、弱気、リスクオン、リスクオフ、中立の5つの明確なセンチメントに分類しました。

このカスタムインジケーターを自動化すると、取引にいくつかの重要な利点があります。感情的な偏りのない継続的かつ客観的なセンチメント監視を可能にし、市場変化へのより迅速な対応を実現します。時間足をまたいだ分析の一貫性を維持し、ブレイクアウトやトレンド確認シグナルを体系的に検出することで初期シグナルを捉えやすくなり、リスク配分もより効果的におこなうことができます。最終的に、自動化は時間と労力を要しミスの起こりやすいプロセスを効率的なEAへと変換し、データに基づいた意思決定を支援します。



実行ロジック

強気/リスクオンセンチメント

強気のセンチメントは強い上昇モメンタムを示し、高い時間足のトレンドと価格構造が買い手優位を示している状態を指します。一方、リスクオンセンチメントは投資家の自信とリスク選好の高まりを意味し、しばしば価格を押し上げます。いずれの場合も、市場環境は上方向の動きを支持しており、インジケーターが強気またはリスクオンのいずれかのセンチメントを示した時点で買いエントリーをおこなうことが戦略となります。これは、価格が上昇を継続する可能性が高いためです。

弱気/リスクオフセンチメント

弱気のセンチメントは強い下落モメンタムを示し、高い時間足のトレンドと価格構造が売り手の優勢を確認している状態を指します。一方、リスクオフセンチメントは安全志向へのシフトを意味し、投資家がリスク資産を回避することで価格が下落しやすい状態になります。いずれの場合も、市場環境は下方向の動きを支持しており、インジケーターが弱気またはリスクオフのいずれかのセンチメントを示した時点で売りエントリーをおこなうことが戦略となります。これは、価格が下落を継続する可能性が高いためです。

中立センチメント

中立または方向感のないセンチメントは、市場に明確な方向性がなく、価格がレンジ内で推移したり、レベル間で不規則に上下したりする状況を指します。こうした不確実な局面では、だましシグナルやノイズによる急変動(whipsaw)が発生しやすくなるため、中立の読み取り値はエントリー回避のためのフィルターとして使用します。このような状態で取引を控えることで、資金を保護し、明確な強気、弱気、リスクオン、リスクオフのシグナルが再び現れるまで待機することができます。



導入手順

//+------------------------------------------------------------------+
//|                                               Mark_Sent_Auto.mq5 |
//|                        GIT under Copyright 2025, MetaQuotes Ltd. |
//|                     https://www.mql5.com/ja/users/johnhlomohang/ |
//+------------------------------------------------------------------+
#property copyright "GIT under Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com/ja/users/johnhlomohang/"
#property version   "1.00"
#include <Trade/Trade.mqh>

CTrade trade;

このコードでは、#include <Trade/Trade.mqh>という行によってMetaTrader 5の取引ライブラリが読み込まれます。このライブラリには、組み込みのCTradeクラスが含まれており、ポジションや注文のエントリー、決済、変更、管理といった、取引自動化に必要な主要機能がすべて提供されています。ライブラリをインクルードした後に、「CTrade trade;」としてクラスのインスタンスを作成します。このオブジェクトが取引システムへのインターフェースとして機能し、trade.Buy()、trade.Sell()、trade.PositionClose()といったメソッドを通して売買操作を実行できるようになります。今回の目的は、センチメントインジケーターの結果に基づいて取引判断を自動化することです。そのため、取引ライブラリを読み込み、CTradeオブジェクトを定義することは、実際にプログラムから取引を送信し管理するために必要不可欠な要素になります。

//+------------------------------------------------------------------+
//| Input parameters                                                 |
//+------------------------------------------------------------------+
input group "Timeframe Settings"
input ENUM_TIMEFRAMES HigherTF = PERIOD_H4;     // Higher Timeframe
input ENUM_TIMEFRAMES LowerTF1 = PERIOD_H1;     // Lower Timeframe 1
input ENUM_TIMEFRAMES LowerTF2 = PERIOD_M30;    // Lower Timeframe 2

input group "Indicator Settings"
input int MAPeriod = 200;                       // EMA Period
input int SwingLookback = 5;                    // Swing Lookback Bars
input double ATRThreshold = 0.002;              // ATR Threshold for Neutral

input group "Trading Settings"
input double LotSize = 0.1;                     // Trade Lot Size
input int StopLossPips = 50;                    // Stop Loss in Pips
input int TakeProfitPips = 100;                 // Take Profit in Pips
input int MagicNumber = 12345;                  // Magic Number for trades
input int Slippage = 3;                         // Slippage in points

input group "Trailing Stop Parameters"
input bool UseTrailingStop = true;              // Enable trailing stops
input int BreakEvenAtPips = 500;                // Move to breakeven at this profit (pips) 
input int TrailStartAtPips = 600;               // Start trailing at this profit (pips)
input int TrailStepPips = 100;                  // Trail by this many pips 

input group "Risk Management"
input bool UseRiskManagement = true;            // Enable Risk Management
input double RiskPercent = 2.0;                 // Risk Percentage per Trade
input int MinBarsBetweenTrades = 3;             // Minimum bars between trades

input group "Visual Settings"
input bool ShowPanel = true;                    // Show Information Panel
input int PanelCorner = 1;                      // Panel Corner: 0=TopLeft,1=TopRight,2=BottomLeft,3=BottomRight
input int PanelX = 10;                          // Panel X Position
input int PanelY = 10;                          // Panel Y Position
input string FontFace = "Arial";                // Font Face
input int FontSize = 10;                        // Font Size
input color BullishColor = clrLimeGreen;        // Bullish Color
input color BearishColor = clrRed;              // Bearish Color
input color RiskOnColor = clrDodgerBlue;        // Risk-On Color
input color RiskOffColor = clrDarkGray;         // Risk-Off Color
input color NeutralColor = clrGold;             // Neutral Color

ここでは、最初のセクションで時間足設定およびインジケーター設定に関連する入力がまとめられています。HigherTF、LowerTF1、LowerTF2を定義することで、エキスパートアドバイザー(EA)は相場センチメントを複数のプライスアクション層で分析できるようになります。たとえば、上位時間足でトレンド方向を確認し、下位時間足で構造的な動きを確認するといった分析が可能になります。インジケーター設定では、MAPeriod(指数平滑移動平均の期間)、SwingLookback(スイングハイとスイングローを確認するバー数)、ATRThreshold(中立またはレンジ相場を検出するための基準値)といったパラメータによって、センチメントの検出方法を柔軟に調整することができます。

次のセクションは、取引動作とリスク管理を制御します。LotSize、StopLossPips、TakeProfitPipsなどの入力は取引実行の基本パラメータを定義し、MagicNumberはEAが自身の注文を識別するために使われ、Slippageは注文実行時の許容スリッページを管理します。トレーリングストップ関連のパラメータ(UseTrailingStop、BreakEvenAtPips、TrailStartAtPips、TrailStepPips)によって、ポジションが有利に進んだ際に利益を動的に保護できます。さらに、リスク管理ブロック(UseRiskManagement、RiskPercent、MinBarsBetweenTrades)により、ポジションサイズが口座残高に合うよう調整され、過剰なリスクを避け、取引間の最低間隔を設けることで規律ある運用を実現します。

最後のセクションは、センチメントインジケーターの視覚的な表示に焦点を当てています。ShowPanel、PanelCorner、PanelX、PanelYといったオプションにより、センチメントパネルをチャート上のどこに表示するかを自由に設定できます。フォントのカスタマイズ(FontFace、FontSize)によって視認性を確保し、BullishColor、BearishColor、RiskOnColor、RiskOffColor、NeutralColorといった色設定によって、各センチメントタイプが一目で判別できるようになります。こうしたカスタマイズは明瞭性と使いやすさを高め、トレーダーが相場状況を瞬時に把握できる環境を提供します。

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
int higherTFHandle, lowerTF1Handle, lowerTF2Handle;
double higherTFMA[], lowerTF1MA[], lowerTF2MA[];
datetime lastUpdateTime = 0;
string indicatorName = "MarketSentEA";
int currentSentiment = 0;
int previousSentiment = 0;
datetime lastTradeTime = 0;
double global_PipValueInPoints; // Stores pip value in points
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
    indicatorName = "MarketSentEA_" + IntegerToString(ChartID());
    
    // Create handles for MA indicators on different timeframes
    higherTFHandle = iMA(_Symbol, HigherTF, MAPeriod, 0, MODE_EMA, PRICE_CLOSE);
    lowerTF1Handle = iMA(_Symbol, LowerTF1, MAPeriod, 0, MODE_EMA, PRICE_CLOSE);
    lowerTF2Handle = iMA(_Symbol, LowerTF2, MAPeriod, 0, MODE_EMA, PRICE_CLOSE);
    
    // Set array as series
    ArraySetAsSeries(higherTFMA, true);
    ArraySetAsSeries(lowerTF1MA, true);
    ArraySetAsSeries(lowerTF2MA, true);
    
     //global_PipValueInPoints = GetPipValueInPoints();
    
    // Create panel if enabled
    if(ShowPanel)
        CreatePanel();
    
    // Check for trading permission
    if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
        Print("Error: Trading is not allowed! Check AutoTrading permission.");
    
    return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
    // Delete all objects
    ObjectsDeleteAll(0, indicatorName);
    IndicatorRelease(higherTFHandle);
    IndicatorRelease(lowerTF1Handle);
    IndicatorRelease(lowerTF2Handle);
}

ここでは、EAが実行全体で使用するグローバル変数を定義します。higherTFHandle、lowerTF1Handle、lowerTF2Handleといったハンドルは、異なる時間足における移動平均(MA)インジケーターの参照を保持するために作成されます。higherTFMA、lowerTF1MA、lowerTF2MAといった配列は、後の分析で使用するために、これらのインジケーターの計算値を格納します。lastUpdateTime、lastTradeTime、currentSentiment、previousSentimentといった変数は、計算や取引が最後におこなわれたタイミングを追跡し、センチメントの変化を管理するために使用されます。indicatorNameという文字列は、チャート上に描画されるオブジェクトを一意に識別するために使用され、global_PipValueInPointsはリスクやロット計算で用いるピップとポイントの換算値を保持します。

OnInit()関数はEAの初期化を担当します。一意のインジケーター名(チャートIDに紐付く)を設定し、選択された時間足に対するMAハンドルを作成し、時系列データ用の配列を設定します。また、パネルを生成するための処理も含まれています。OnDeinit()関数は、EAがチャートから削除される際のクリーンアップを担当し、インジケーター関連のチャートオブジェクトを削除し、MAハンドルを解放することでリソースを確保します。これらの関数により、EAは不要なチャート上の残骸や無駄なメモリ消費を残さず、適切に開始し、適切に終了できるようになります。

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
    // Update only on new bar or every 10 seconds
    if(TimeCurrent() - lastUpdateTime < 10)
        return;
    
    lastUpdateTime = TimeCurrent();
    
    // Get MA values
    if(CopyBuffer(higherTFHandle, 0, 0, 3, higherTFMA) < 3 ||
       CopyBuffer(lowerTF1Handle, 0, 0, 3, lowerTF1MA) < 3 ||
       CopyBuffer(lowerTF2Handle, 0, 0, 3, lowerTF2MA) < 3)
    {
        Print("Error copying indicator buffers");
        return;
    }
    
    // Get current prices
    double higherTFPrice = iClose(_Symbol, HigherTF, 0);
    double lowerTF1Price = iClose(_Symbol, LowerTF1, 0);
    double lowerTF2Price = iClose(_Symbol, LowerTF2, 0);
    
    // Determine higher timeframe bias
    int higherTFBias = GetHigherTFBias(higherTFPrice, higherTFMA[0]);
    
    // Determine lower timeframe structures
    bool lowerTF1Bullish = IsBullishStructure(LowerTF1, SwingLookback);
    bool lowerTF1Bearish = IsBearishStructure(LowerTF1, SwingLookback);
    bool lowerTF2Bullish = IsBullishStructure(LowerTF2, SwingLookback);
    bool lowerTF2Bearish = IsBearishStructure(LowerTF2, SwingLookback);
    
    // Check for breakouts (BOS)
    bool lowerTF1Breakout = HasBreakout(LowerTF1, SwingLookback, higherTFBias);
    bool lowerTF2Breakout = HasBreakout(LowerTF2, SwingLookback, higherTFBias);
    
    // Determine final sentiment
    previousSentiment = currentSentiment;
    currentSentiment = DetermineSentiment(
        higherTFBias, 
        lowerTF1Bullish, lowerTF1Bearish, lowerTF1Breakout,
        lowerTF2Bullish, lowerTF2Bearish, lowerTF2Breakout
    );
    
    // Update panel if enabled
    if(ShowPanel)
        UpdatePanel(higherTFBias, currentSentiment);
    
    // Check if we should trade
    CheckForTradingOpportunity();
    
    if(UseTrailingStop) ManageOpenTrades();
}

OnTick()関数は、EAの中核となる実行ループであり、市場から新しいティックが発生するたびに実行されます。パフォーマンスを最適化するために、この関数では10秒ごと、または新しいバーが形成されたタイミングでのみ更新をおこなうようにし、不要な計算を避けています。まず、事前に設定された時間足からCopyBufferを用いて移動平均(MA)の値を取得し、iClose()で取得した現在価格と比較します。このデータに基づいて上位時間足の方向性を判定し、下位時間足では強気や弱気の構造や潜在的なブレイクアウトを検出します。

続いて、これらの条件を組み合わせて総合的な市場センチメントを決定し、パネル表示が有効な場合はチャート上の情報を更新します。その後、取引のチャンスがあるかどうかをチェックします。最後に、トレーリングストップが有効な場合は、保有中のポジションに対して動的な利益保護をおこないます。これによりEAは、センチメント分析、ビジュアル更新、取引実行を常に体系的かつ自動的に処理し続けることができます。

//+------------------------------------------------------------------+
//| Determine higher timeframe bias                                  |
//+------------------------------------------------------------------+
int GetHigherTFBias(double price, double maValue)
{
    double deviation = MathAbs(price - maValue) / maValue;
    
    if(price > maValue && deviation > ATRThreshold)
        return 1; // Bullish
    else if(price < maValue && deviation > ATRThreshold)
        return -1; // Bearish
    else
        return 0; // Neutral
}

//+------------------------------------------------------------------+
//| Check for bullish structure (HH, HL)                             |
//+------------------------------------------------------------------+
bool IsBullishStructure(ENUM_TIMEFRAMES tf, int lookback)
{
    // Get swing highs and lows
    int swingHighIndex = iHighest(_Symbol, tf, MODE_HIGH, lookback * 2, 1);
    int swingLowIndex = iLowest(_Symbol, tf, MODE_LOW, lookback * 2, 1);
    
    // Get previous swings for comparison
    int prevSwingHighIndex = iHighest(_Symbol, tf, MODE_HIGH, lookback, lookback + 1);
    int prevSwingLowIndex = iLowest(_Symbol, tf, MODE_LOW, lookback, lookback + 1);
    
    if(swingHighIndex == -1 || swingLowIndex == -1 || 
       prevSwingHighIndex == -1 || prevSwingLowIndex == -1)
        return false;
    
    double swingHigh = iHigh(_Symbol, tf, swingHighIndex);
    double swingLow = iLow(_Symbol, tf, swingLowIndex);
    double prevSwingHigh = iHigh(_Symbol, tf, prevSwingHighIndex);
    double prevSwingLow = iLow(_Symbol, tf, prevSwingLowIndex);
    
    // Check for higher high and higher low
    return (swingHigh > prevSwingHigh && swingLow > prevSwingLow);
}

//+------------------------------------------------------------------+
//| Check for bearish structure (LH, LL)                             |
//+------------------------------------------------------------------+
bool IsBearishStructure(ENUM_TIMEFRAMES tf, int lookback)
{
    // Get swing highs and lows
    int swingHighIndex = iHighest(_Symbol, tf, MODE_HIGH, lookback * 2, 1);
    int swingLowIndex = iLowest(_Symbol, tf, MODE_LOW, lookback * 2, 1);
    
    // Get previous swings for comparison
    int prevSwingHighIndex = iHighest(_Symbol, tf, MODE_HIGH, lookback, lookback + 1);
    int prevSwingLowIndex = iLowest(_Symbol, tf, MODE_LOW, lookback, lookback + 1);
    
    if(swingHighIndex == -1 || swingLowIndex == -1 || 
       prevSwingHighIndex == -1 || prevSwingLowIndex == -1)
        return false;
    
    double swingHigh = iHigh(_Symbol, tf, swingHighIndex);
    double swingLow = iLow(_Symbol, tf, swingLowIndex);
    double prevSwingHigh = iHigh(_Symbol, tf, prevSwingHighIndex);
    double prevSwingLow = iLow(_Symbol, tf, prevSwingLowIndex);
    
    // Check for lower high and lower low
    return (swingHigh < prevSwingHigh && swingLow < prevSwingLow);
}

//+------------------------------------------------------------------+
//| Check for breakout of structure                                  |
//+------------------------------------------------------------------+
bool HasBreakout(ENUM_TIMEFRAMES tf, int lookback, int higherTFBias)
{
    // Get recent swing points
    int swingHighIndex = iHighest(_Symbol, tf, MODE_HIGH, lookback, 1);
    int swingLowIndex = iLowest(_Symbol, tf, MODE_LOW, lookback, 1);
    
    if(swingHighIndex == -1 || swingLowIndex == -1)
        return false;
    
    double swingHigh = iHigh(_Symbol, tf, swingHighIndex);
    double swingLow = iLow(_Symbol, tf, swingLowIndex);
    double currentPrice = iClose(_Symbol, tf, 0);
    
    // Check for breakout based on higher timeframe bias
    if(higherTFBias == 1) // Bullish bias - look for breakout above swing high
        return (currentPrice > swingHigh);
    else if(higherTFBias == -1) // Bearish bias - look for breakout below swing low
        return (currentPrice < swingLow);
    
    return false;
}

GetHigherTFBias()関数は、上位時間足における移動平均値と現在価格を比較して、支配的なトレンドを判定します。両者の相対的な乖離を計算することで、価格が移動平均付近でわずかに上下する程度の小さな変動を中立として扱うように設計されています。価格が移動平均を明確に上回っている場合は強気バイアスとして1を返し、反対に大きく下回っている場合は弱気バイアスとして-1を返します。どちらにも該当しない場合は0を返し、中立状態を示します。これにより、下位時間足を分析する前に、EAは明確な方向性の文脈を得ることができます。

IsBullishStructure()およびIsBearishStructure()関数は、指定された時間足におけるスイングハイとスイングローを調べ、市場の構造を確認します。強気構造の判定では、直近のスイングハイが前のスイングハイより高いこと、そして直近のスイングローが前のスイングローより高いことを確認し、いわゆる高値更新(HH)と安値切り上げ(HL)のパターンを検出します。逆に弱気構造の判定では、安値更新(LL)と高値切り下げ(LH)のパターンが確認されます。これらの構造的な検証により、移動平均の方向性だけでなく、実際の市場構造にも合致した取引をおこなえるようになります。

HasBreakout()関数は、上位時間足の方向性に沿って、価格が直近のスイングレベルをブレイクしたかどうかをチェックします。たとえば強気バイアスの場合、現在価格が直近のスイングハイを上抜いたかどうかを確認し、継続のシグナルと見なします。弱気バイアスの場合は、直近のスイングローを下抜いたかどうかを確認します。明確な方向性が存在しない場合は、どのブレイクアウトも有効とは判断されません。この関数は、価格構造と上位時間足のモメンタムが一致したときのみエントリーするようにすることで、無駄な早期エントリーを防ぎ、シグナルの信頼性を高めています。

//+------------------------------------------------------------------+
//| Determine final sentiment                                        |
//+------------------------------------------------------------------+
int DetermineSentiment(int higherTFBias, 
                      bool tf1Bullish, bool tf1Bearish, bool tf1Breakout,
                      bool tf2Bullish, bool tf2Bearish, bool tf2Breakout)
{
    // Bullish sentiment
    if(higherTFBias == 1 && tf1Bullish && tf2Bullish)
        return 1; // Bullish
    
    // Bearish sentiment
    if(higherTFBias == -1 && tf1Bearish && tf2Bearish)
        return -1; // Bearish
    
    // Risk-on sentiment
    if(higherTFBias == 1 && (tf1Breakout || tf2Breakout))
        return 2; // Risk-On
    
    // Risk-off sentiment
    if(higherTFBias == -1 && (tf1Breakout || tf2Breakout))
        return -2; // Risk-Off
    
    // Neutral/choppy sentiment
    return 0; // Neutral
}

//+------------------------------------------------------------------+
//| Check for trading opportunity                                    |
//+------------------------------------------------------------------+
void CheckForTradingOpportunity()
{
    // Check if enough time has passed since last trade
    if(TimeCurrent() - lastTradeTime < PeriodSeconds(_Period) * MinBarsBetweenTrades)
        return;
    
    // Check if sentiment has changed
    if(currentSentiment == previousSentiment)
        return;
    
    // Close existing positions if sentiment changed significantly
    if((previousSentiment > 0 && currentSentiment < 0) || 
       (previousSentiment < 0 && currentSentiment > 0))
    {
        CloseAllPositions();
    }
    
    // Execute new trades based on sentiment
    switch(currentSentiment)
    {
        case 1: // Bullish - Buy
        case 2: // Risk-On - Buy
            ExecuteTrade(ORDER_TYPE_BUY, _Symbol);
            lastTradeTime = TimeCurrent();
            break;
            
        case -1: // Bearish - Sell
        case -2: // Risk-Off - Sell
            ExecuteTrade(ORDER_TYPE_SELL, _Symbol);
            lastTradeTime = TimeCurrent();
            break;
            
        case 0: // Neutral - Close all positions
            CloseAllPositions();
            break;
    }
}

//+------------------------------------------------------------------+
//| Execute trade with risk parameters                               |
//+------------------------------------------------------------------+
void ExecuteTrade(ENUM_ORDER_TYPE tradeType, string symbol)
{
   double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
   double price = (tradeType == ORDER_TYPE_BUY) ? SymbolInfoDouble(symbol, SYMBOL_ASK) :
                                                SymbolInfoDouble(symbol, SYMBOL_BID);

   // Convert StopLoss and TakeProfit from pips to actual price distances
   double sl_distance = StopLossPips * point;
   double tp_distance = TakeProfitPips * point;
   
   double sl = (tradeType == ORDER_TYPE_BUY) ? price - sl_distance :
                                             price + sl_distance;
   
   double tp = (tradeType == ORDER_TYPE_BUY) ? price + tp_distance :
                                             price - tp_distance;

   trade.PositionOpen(symbol, tradeType, LotSize, price, sl, tp, NULL);
}

DetermineSentiment()関数は、上位時間足と下位時間足から得られるシグナルを統合し、ひとつのセンチメント分類へとまとめます。上位時間足が強気バイアスで、かつ両方の下位時間足が強気構造を確認している場合、この関数は強気センチメントを返します。逆に、すべての条件が弱気側で一致している場合には弱気センチメントとなります。上位時間足が強気バイアスで、かつ下位時間足ではブレイクアウトのみが発生している場合はリスクオン、同様に弱気バイアスで下位時間足がブレイクアウトを示す場合はリスクオフとして扱います。これらのいずれにも該当しない場合、センチメントはデフォルトで中立となり、方向感のないレンジまたは不安定な相場を表します。この階層構造により、センチメントは一貫性をもち、文脈に沿ってトレンドおよびブレイクアウトの状況を反映できるようになります。

CheckForTradingOpportunity()関数は、センチメントシグナルを基に取引を自動化します。取引間隔を空ける(MinBarsBetweenTrades)といったルールや、直前のセンチメントから変化があった場合にのみ反応するといった条件を適用します。センチメントが反転した場合は、新しい取引をおこなう前に既存ポジションをクローズし、最新の相場見通しに整合させます。センチメント内容に応じて、強気またはリスクオンの際には買い注文、弱気またはリスクオフの際には売り注文を実行し、中立時には全ポジションをクローズします。ExecuteTrade()関数は、リスク制御を備えた発注処理をおこない、ストップロスおよびテイクプロフィットを価格ベースで算出します。また、PipsToPrice()ヘルパー関数により、3桁/5桁など異なるポイント制度のシンボルにも対応します。これらの機能が連携することで、センチメント検出から自動売買、そしてリスク管理までを一貫して処理する完全なループが構築されています。

//+------------------------------------------------------------------+
//| Trailing stop function                                           |
//+------------------------------------------------------------------+
void ManageOpenTrades()
{
   if(!UseTrailingStop) return;

   int total = PositionsTotal();
   for(int i = total - 1; i >= 0; i--)
   {
      // get ticket (PositionGetTicket returns ulong; it also selects the position)
      ulong ticket = PositionGetTicket(i);                  // correct usage. :contentReference[oaicite:3]{index=3}
      if(ticket == 0) continue;

      // ensure the position is selected (recommended)
      if(!PositionSelectByTicket(ticket)) continue;

      // Optional: only operate on same symbol or your EA's magic number
      if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
      // if(PositionGetInteger(POSITION_MAGIC) != MyMagicNumber) continue;

      // read position properties AFTER selecting
      double open_price   = PositionGetDouble(POSITION_PRICE_OPEN);
      double current_price= PositionGetDouble(POSITION_PRICE_CURRENT); // current price for the position. :contentReference[oaicite:4]{index=4}
      double current_sl   = PositionGetDouble(POSITION_SL);
      double current_tp   = PositionGetDouble(POSITION_TP);
      ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);

      // pip size
      double pip_price = PipsToPrice(1);

      // profit in pips (use current_price returned above)
      double profit_price = (pos_type == POSITION_TYPE_BUY) ? (current_price - open_price)
                                                             : (open_price - current_price);
      double profit_pips = profit_price / pip_price;
      if(profit_pips <= 0) continue;

      // get broker min stop distance (in price units)
      double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
      double stop_level_points = (double)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL); // integer property. :contentReference[oaicite:5]{index=5}
      double stopLevelPrice = stop_level_points * point;

      // get market Bid/Ask for stop-level checks
      double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
      double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);

      // -------------------------
      // 1) Move to breakeven
      // -------------------------
      if(profit_pips >= BreakEvenAtPips)
      {
         double breakeven = open_price;
         // small adjustment to help account for spread/commissions (optional)
         if(pos_type == POSITION_TYPE_BUY)  breakeven += point; 
         else                                breakeven -= point;

         // Check stop-level rules: for BUY SL must be >= (bid - stopLevelPrice) distance below bid
         if(pos_type == POSITION_TYPE_BUY)
         {
            if((bid - breakeven) >= stopLevelPrice) // allowed by server
            {
               if(breakeven > current_sl) // only move SL up
               {
                  if(!trade.PositionModify(ticket, NormalizeDouble(breakeven, _Digits), current_tp))
                     PrintFormat("PositionModify failed (BE) ticket %I64u error %d", ticket, GetLastError());
               }
            }
         }
         else // SELL
         {
            if((breakeven - ask) >= stopLevelPrice)
            {
               if(current_sl == 0.0 || breakeven < current_sl) // move SL down
               {
                  if(!trade.PositionModify(ticket, NormalizeDouble(breakeven, _Digits), current_tp))
                     PrintFormat("PositionModify failed (BE) ticket %I64u error %d", ticket, GetLastError());
               }
            }
         }
      } // end breakeven

      // -------------------------
      // 2) Trailing in steps after TrailStartAtPips
      // -------------------------
      if(profit_pips >= TrailStartAtPips)
      {
         double extra_pips = profit_pips - TrailStartAtPips;
         int step_count = (int)(extra_pips / TrailStepPips);

         // compute desired SL relative to open_price (as per your original request)
         double desiredOffsetPips = (double)(TrailStartAtPips + step_count * TrailStepPips);
         double new_sl_price;

         if(pos_type == POSITION_TYPE_BUY)
         {
            new_sl_price = open_price + PipsToPrice((int)desiredOffsetPips);
            // ensure new SL respects server min distance from current Bid
            if((bid - new_sl_price) < stopLevelPrice)
               new_sl_price = bid - stopLevelPrice;

            if(new_sl_price > current_sl) // only move SL up
            {
               if(!trade.PositionModify(ticket, NormalizeDouble(new_sl_price, _Digits), current_tp))
                  PrintFormat("PositionModify failed (Trail Buy) ticket %I64u error %d", ticket, GetLastError());
            }
         }
         else // SELL
         {
            new_sl_price = open_price - PipsToPrice((int)desiredOffsetPips);
            // ensure new SL respects server min distance from current Ask
            if((new_sl_price - ask) < stopLevelPrice)
               new_sl_price = ask + stopLevelPrice;

            if(current_sl == 0.0 || new_sl_price < current_sl) // only move SL down (more profitable)
            {
               if(!trade.PositionModify(ticket, NormalizeDouble(new_sl_price, _Digits), current_tp))
                  PrintFormat("PositionModify failed (Trail Sell) ticket %I64u error %d", ticket, GetLastError());
            }
         }
      }
   } 
}

//+------------------------------------------------------------------+
//| Close all positions                                              |
//+------------------------------------------------------------------+
void CloseAllPositions()
{
    for(int i = PositionsTotal() - 1; i >= 0; i--)
    {
        ulong ticket = PositionGetTicket(i);
        if(PositionSelectByTicket(ticket))
        {
            if(PositionGetInteger(POSITION_MAGIC) == MagicNumber)
            {
                MqlTradeRequest request = {};
                MqlTradeResult result = {};
                
                request.action = TRADE_ACTION_DEAL;
                request.symbol = _Symbol;
                request.volume = PositionGetDouble(POSITION_VOLUME);
                request.type = (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) ? 
                              ORDER_TYPE_SELL : ORDER_TYPE_BUY;
                request.price = SymbolInfoDouble(_Symbol, 
                    (request.type == ORDER_TYPE_BUY) ? SYMBOL_ASK : SYMBOL_BID);
                request.deviation = Slippage;
                request.magic = MagicNumber;
                request.comment = "MarketSentEA Close";
                
                if(OrderSend(request, result))
                {
                    if(result.retcode == TRADE_RETCODE_DONE)
                        Print("Position closed successfully. Ticket: ", ticket);
                    else
                        Print("Error closing position. Code: ", result.retcode);
                }
            }
        }
    }
}

ここでは、EAのこのセクションが主にトレーリングストップ管理と強制クローズ機能の2つのリスク管理手法に焦点を当てていることを説明しています。ManageOpenTrades()関数は、まずトレーリングストップが有効化されている場合にのみ処理をおこない、その後すべてのポジションをループして確認します。各ポジションについて、現在の利益をpips単位で計測し、一定以上の利益に達した時点でブレークイーブン(建値ストップ)を設定し、さらに利益が伸びるにつれてステップ方式のトレーリングを適用します。また、ブローカーのストップレベル制限やスプレッドの状況も考慮し、すべてのストップ変更が有効で安全な範囲内でおこなわれるよう設計されています。

これらの機能を組み合わせることで、EAは堅牢な決済および保護システムを構築しており、リスクを抑えつつ利益を最大化しやすくなります。ブレークイーブンロジックとステップベースのトレーリング、そしてセンチメントや戦略ルールに応じて全ポジションを確実にクローズする仕組みを統合することで、相場が不利な状況では資金を保護し、有利に動いた場面では利益を確実に確保できます。結果として、戦略の一貫性、耐久性、長期的な収益性が向上します。

//+------------------------------------------------------------------+
//| Calculate lot size with risk management                          |
//+------------------------------------------------------------------+
double CalculateLotSize(double stopLossPrice)
{
    if(!UseRiskManagement)
        return LotSize;
    
    double accountBalance = AccountInfoDouble(ACCOUNT_BALANCE);
    double riskAmount = accountBalance * RiskPercent / 100.0;
    
    double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
    double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
    
    // Calculate stop loss in points
    double currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
    double slPoints = MathAbs(currentPrice - stopLossPrice) / point;
    
    // Calculate lot size
    double lotSize = riskAmount / (slPoints * tickValue);
    
    // Normalize lot size
    double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
    double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
    double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
    
    lotSize = MathMax(minLot, MathMin(maxLot, lotSize));
    lotSize = MathRound(lotSize / lotStep) * lotStep;
    
    return lotSize;
}

//+------------------------------------------------------------------+
//| Helper: convert timeframe to string                              |
//+------------------------------------------------------------------+
string TFtoString(int tf)
{
   switch(tf)
   {
      case PERIOD_M1:  return "M1";
      case PERIOD_M5:  return "M5";
      case PERIOD_M15: return "M15";
      case PERIOD_M30: return "M30";
      case PERIOD_H1:  return "H1";
      case PERIOD_H4:  return "H4";
      case PERIOD_D1:  return "D1";
      case PERIOD_W1:  return "W1";
      case PERIOD_MN1: return "MN";
      default: return "TF?";
   }
}

//+------------------------------------------------------------------+
//| Create information panel                                         |
//+------------------------------------------------------------------+
void CreatePanel()
{
   //--- background panel
   string bg = indicatorName + "_BG";
   ObjectCreate(0, bg, OBJ_RECTANGLE_LABEL, 0, 0, 0);
   ObjectSetInteger(0, bg, OBJPROP_XDISTANCE, PanelX);
   ObjectSetInteger(0, bg, OBJPROP_YDISTANCE, PanelY);
   ObjectSetInteger(0, bg, OBJPROP_XSIZE, 200);
   ObjectSetInteger(0, bg, OBJPROP_YSIZE, 120);
   ObjectSetInteger(0, bg, OBJPROP_CORNER, PanelCorner);
   ObjectSetInteger(0, bg, OBJPROP_BGCOLOR, clrBlack);
   ObjectSetInteger(0, bg, OBJPROP_BORDER_TYPE, BORDER_FLAT);
   ObjectSetInteger(0, bg, OBJPROP_BORDER_COLOR, clrWhite);
   ObjectSetInteger(0, bg, OBJPROP_BACK, true);
   ObjectSetInteger(0, bg, OBJPROP_SELECTABLE, false);
   ObjectSetInteger(0, bg, OBJPROP_HIDDEN, true);
   ObjectSetInteger(0, bg, OBJPROP_ZORDER, 0);
   
   //--- title
   string title = indicatorName + "_Title";
   ObjectCreate(0, title, OBJ_LABEL, 0, 0, 0);
   ObjectSetInteger(0, title, OBJPROP_XDISTANCE, PanelX + 10);
   ObjectSetInteger(0, title, OBJPROP_YDISTANCE, PanelY + 10);
   ObjectSetInteger(0, title, OBJPROP_CORNER, PanelCorner);
   ObjectSetString (0, title, OBJPROP_TEXT, "Market Sentiment EA");
   ObjectSetInteger(0, title, OBJPROP_COLOR, clrWhite);
   ObjectSetString (0, title, OBJPROP_FONT, FontFace);
   ObjectSetInteger(0, title, OBJPROP_FONTSIZE, FontSize);
   ObjectSetInteger(0, title, OBJPROP_BACK, false);
   ObjectSetInteger(0, title, OBJPROP_SELECTABLE, false);
   ObjectSetInteger(0, title, OBJPROP_HIDDEN, true);
   ObjectSetInteger(0, title, OBJPROP_ZORDER, 1);
   
   //--- timeframe labels + sentiment placeholders
   string timeframes[3] = { TFtoString(HigherTF), TFtoString(LowerTF1), TFtoString(LowerTF2) };
   for(int i=0; i<3; i++)
   {
      string tfLabel = indicatorName + "_TF" + IntegerToString(i);
      ObjectCreate(0, tfLabel, OBJ_LABEL, 0, 0, 0);
      ObjectSetInteger(0, tfLabel, OBJPROP_XDISTANCE, PanelX + 10);
      ObjectSetInteger(0, tfLabel, OBJPROP_YDISTANCE, PanelY + 30 + i * 20);
      ObjectSetInteger(0, tfLabel, OBJPROP_CORNER, PanelCorner);
      ObjectSetString (0, tfLabel, OBJPROP_TEXT, timeframes[i] + ":");
      ObjectSetInteger(0, tfLabel, OBJPROP_COLOR, clrLightGray);
      ObjectSetString (0, tfLabel, OBJPROP_FONT, FontFace);
      ObjectSetInteger(0, tfLabel, OBJPROP_FONTSIZE, FontSize);
      ObjectSetInteger(0, tfLabel, OBJPROP_BACK, false);
      ObjectSetInteger(0, tfLabel, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, tfLabel, OBJPROP_HIDDEN, true);
      ObjectSetInteger(0, tfLabel, OBJPROP_ZORDER, 1);
      
      string sentLabel = indicatorName + "_Sentiment" + IntegerToString(i);
      ObjectCreate(0, sentLabel, OBJ_LABEL, 0, 0, 0);
      ObjectSetInteger(0, sentLabel, OBJPROP_XDISTANCE, PanelX + 100);
      ObjectSetInteger(0, sentLabel, OBJPROP_YDISTANCE, PanelY + 30 + i * 20);
      ObjectSetInteger(0, sentLabel, OBJPROP_CORNER, PanelCorner);
      ObjectSetString (0, sentLabel, OBJPROP_TEXT, "N/A");
      ObjectSetInteger(0, sentLabel, OBJPROP_COLOR, NeutralColor);
      ObjectSetString (0, sentLabel, OBJPROP_FONT, FontFace);
      ObjectSetInteger(0, sentLabel, OBJPROP_FONTSIZE, FontSize);
      ObjectSetInteger(0, sentLabel, OBJPROP_BACK, false);
      ObjectSetInteger(0, sentLabel, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, sentLabel, OBJPROP_HIDDEN, true);
      ObjectSetInteger(0, sentLabel, OBJPROP_ZORDER, 1);
   }
   
   //--- final sentiment label
   string fnl = indicatorName + "_Final";
   ObjectCreate(0, fnl, OBJ_LABEL, 0, 0, 0);
   ObjectSetInteger(0, fnl, OBJPROP_XDISTANCE, PanelX + 10);
   ObjectSetInteger(0, fnl, OBJPROP_YDISTANCE, PanelY + 100);
   ObjectSetInteger(0, fnl, OBJPROP_CORNER, PanelCorner);
   ObjectSetString (0, fnl, OBJPROP_TEXT, "Final: Neutral");
   ObjectSetInteger(0, fnl, OBJPROP_COLOR, NeutralColor);
   ObjectSetString (0, fnl, OBJPROP_FONT, FontFace);
   ObjectSetInteger(0, fnl, OBJPROP_FONTSIZE, FontSize + 2);
   ObjectSetInteger(0, fnl, OBJPROP_BACK, false);
   ObjectSetInteger(0, fnl, OBJPROP_SELECTABLE, false);
   ObjectSetInteger(0, fnl, OBJPROP_HIDDEN, true);
   ObjectSetInteger(0, fnl, OBJPROP_ZORDER, 1);
}

//+------------------------------------------------------------------+
//| Update information panel                                         |
//+------------------------------------------------------------------+
void UpdatePanel(int higherTFBias, int sentiment)
{
    // Update higher timeframe sentiment
    string higherTFSentiment = "Neutral";
    color higherTFColor = NeutralColor;
    
    if(higherTFBias == 1)
    {
        higherTFSentiment = "Bullish";
        higherTFColor = BullishColor;
    }
    else if(higherTFBias == -1)
    {
        higherTFSentiment = "Bearish";
        higherTFColor = BearishColor;
    }
    
    ObjectSetString(0, indicatorName + "_Sentiment0", OBJPROP_TEXT, higherTFSentiment);
    ObjectSetInteger(0, indicatorName + "_Sentiment0", OBJPROP_COLOR, higherTFColor);
    
    // Update final sentiment
    string finalSentiment = "Neutral";
    color finalColor = NeutralColor;
    
    switch(sentiment)
    {
        case 1:
            finalSentiment = "Bullish";
            finalColor = BullishColor;
            break;
        case -1:
            finalSentiment = "Bearish";
            finalColor = BearishColor;
            break;
        case 2:
            finalSentiment = "Risk-On";
            finalColor = RiskOnColor;
            break;
        case -2:
            finalSentiment = "Risk-Off";
            finalColor = RiskOffColor;
            break;
        default:
            finalSentiment = "Neutral";
            finalColor = NeutralColor;
    }
    
    ObjectSetString(0, indicatorName + "_Final", OBJPROP_TEXT, "Final: " + finalSentiment);
    ObjectSetInteger(0, indicatorName + "_Final", OBJPROP_COLOR, finalColor);
}

CalculateLotSize関数は、口座残高とリスク割合に基づいて動的なポジションサイズを算出する役割を果たします。固定ロットではなく、エントリーからストップロスまでの距離をポイント換算し、1pipあたりの損失額から投入リスク資金を割り出すことで、適切なロット数を計算します。これにより、ボラティリティや銘柄価格に左右されず、常に口座資金の一定割合をリスクに設定できる仕組みとなっています。また、最終的なロットサイズはブローカーの制限(最小ロット、最大ロット、ステップサイズ)に合わせて調整されるため、実運用に耐える安全なロット計算が可能です。

ヘルパー関数であるTFtoStringは、数値で定義された時間足定数を人間が読める文字列に変換します。これは、パネル、ダッシュボード、ログなどに時間足を分かりやすく表示するために不可欠です。整数コードを「M5」「H1」などの文字列に変換することで、どの時間足の方向性やシグナルなのかを一目で把握でき、複数の時間足を扱う際の混乱を防ぐ役割を果たします。

CreatePanelとUpdatePanel関数は、EAの視覚インターフェースを担当します。CreatePanelは背景、タイトル、各監視時間足のセンチメント表示フィールド、最終センチメントフィールドを含むクリーンなダッシュボードを初期生成します。一方、UpdatePanelは、現在の上位時間足バイアスや最終センチメントをリアルタイムに更新し、文字と色を切り替えることで、強気、弱気、リスクオン、リスクオフ、中立の状態を視覚的に表現します。これらを組み合わせることで、トレーダーはデータを細かく読む必要がなく、チャート上のパネルを見るだけで市場状況を瞬時に把握できるようになります。



バックテスト結果

バックテストは1時間足で実施し、約2か月間の検証期間(2025年2月17日から2025年5月6日)を対象としました。使用した設定は以下のとおりです。



結論

本記事では Market Sentimentインジケーターを自動化するための完全なフレームワークを設計および実装しました。これには、複数の時間足を横断したセンチメント検出ロジックの構築、強気、弱気、リスクオン、リスクオフ、中立といった明確なカテゴリーの定義、そしてこれらのシグナルに基づく取引ルールのコーディングが含まれます。また、動的ロットサイズによるリスク管理、利益を確保するトレーリングストップ機能、リアルタイムでセンチメント状況を視覚的に表示する情報パネルといった必須機能も統合しました。これらのコンポーネントを組み合わせることで、単なるインジケーターを分析と取引実行の両方が可能な完全自動取引システムに進化させることができました。

結論として、この自動化はトレーダーにとって、感情的な判断を排除し、取引の一貫性を確保する点で大きな助けとなります。センチメント分析、構造化された取引ルール、組み込みのリスク管理を組み合わせることで、より規律あるアプローチで市場に対応できるようになります。システムは明確な視覚インターフェースを提供しつつ、センチメントに沿った自動取引をおこなうため、トレーダーは時間を節約し、ミスを最小化し、変化する市場環境に適応できるプロ仕様のツールとして運用できます。

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

添付されたファイル |
Mark_Sent_Auto.mq5 (62.08 KB)
EAのサンプル EAのサンプル
一般的なMACDを使ったEAを例として、MQL4開発の原則を紹介します。
MQL5でのAI搭載取引システムの構築(第2回):ChatGPT統合型アプリケーションのUI開発 MQL5でのAI搭載取引システムの構築(第2回):ChatGPT統合型アプリケーションのUI開発
本記事では、MQL5でChatGPTを統合したプログラムを開発します。このプログラムでは、第1回で作成したJSON解析フレームワークを活用してOpenAIのAPIにプロンプトを送信し、MetaTrader 5のチャート上に応答を表示します。入力フィールド、送信ボタン、応答表示を備えたダッシュボードを実装し、API通信やテキストの折り返し処理をおこなうことで、ユーザーとのインタラクションを実現します。
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法 エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
この記事では、MT4において複数のEAの衝突をさける方法を扱います。ターミナルの操作、MQL4の基本的な使い方がわかる人にとって、役に立つでしょう。
ParafracおよびParafrac V2オシレーターを使用した取引戦略の開発:シングルエントリーパフォーマンスインサイト ParafracおよびParafrac V2オシレーターを使用した取引戦略の開発:シングルエントリーパフォーマンスインサイト
本記事では、ParaFracオシレーターとその後継であるV2モデルを取引ツールとして紹介し、これらを用いて構築した3種類の取引戦略を解説します。各戦略をテストおよび最適化し、それぞれの強みと弱みを明らかにします。比較分析によって両モデルの性能差を明確にしました。