2025年9月3日 星期三

MT5 EA交易策略開發教學[14]

策略概述
這是一個基於多重技術指標的日內交易策略,使用H1(小時)時間週期進行交易決策。策略結合唐奇安通道突破、RSI超買超賣、MACD背離等多種技術分析工具來識別進出場時機,並採用嚴格的風險控制和當沖平倉機制。

基礎概念
時間週期: H1(1小時)
風險控制: 動態手數計算,風險百分比為1%
資金風控門檻: 5000
商品平均點差限制: 50點
交易限制: 每日限制1次進場
交易時段: 06:00-22:00
當沖設定: 23:00或01:00強制平倉
停利停損: TP=3360點,SL=1000點

進出場策略
多單進場條件
LE_Cond = (High[1] >= a_DC_HIGH_A[3] && a_RSIA[1] > 70);

條件分析:
唐奇安通道突破: 前一根K棒最高價突破第3根K棒的唐奇安通道上軌
RSI超買確認: RSI指標大於70,確認強勢上漲動能
進場方式: 市價單買入

空單進場條件
SE_Cond = (Black3Bar_LClose());

條件分析:
三黑創低收: 連續3根陰線且收盤價創新低
高點下降趨勢: 確認空頭趨勢形成
跌破支撐: 跌破第四根K棒低點
進場方式: 市價單賣出

進場價格計算
多單進場價格
BuyPrice = (FVG_多頭上緣價格 + FVG_多頭下緣價格) / 2;
計算方式: FVG多頭缺口的中間價位

空單進場價格
ShortPrice = MathMin(High[1], MathMin(High[2], High[3]));
計算方式: 取前3根K棒中的最低高點

//+------------------------------------------------------------------+
//| MT5 自動交易程式 (Expert Advisor)                                                 
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>                                             // 引入 MT5 交易函數庫
#include <MagicMT5_函數庫V2.mqh>                                      // 引入自定義函數庫

ENUM_TIMEFRAMES 時間週期 = PERIOD_H1;              // 主交易時間框架(小時圖)
ENUM_TIMEFRAMES 時間框架 = PERIOD_D1;              // 相對大週期(日線圖)


//+------------------------------------------------------------------+
//| 初始化函數                                                  
//+------------------------------------------------------------------+
int OnInit()
{
   LoadEA = TimeCurrent();                         // 記錄 EA 載入時間
   return(INIT_SUCCEEDED);                         // 初始化成功
}

//+------------------------------------------------------------------+
//| 每tick執行函數                                                
//+------------------------------------------------------------------+
void OnTick()
{
   // 檢查帳戶餘額是否低於風控門檻
   if(AccountBalance < 資金風控)
   {
      Alert("**********  資金不足 *************"); // 提示資金不足
      return;
   }

   // 計算當前 K 棒編號及自上次平倉後的 K 棒數
   BarNumber = iBarShift(Symbol(), 時間週期, LoadEA);
   BarSinceExit = BarNumber - CloseOrderNo;

   // 模擬測試:第一根 K 棒下單並立即平倉
   if((BarNumber == 1 && BarNumber != JudgeNo))
   {
      多單進場單號 = Buy_at_MARKET(Symbol(), Lots, 0, 0, "1st_K", MagicNumber); // 市價買單
      LX_CloseByTicket(多單進場單號, Lots);                                     // 立即平倉
      CloseOrderNo = iBarShift(Symbol(), 時間週期, LoadEA);           // 更新平倉 K 棒編號
   }

   // 新 K 棒時執行相關計算
   if(BarNumber != JudgeNo)
   {
      換K棒();                      // 執行新 K 棒相關計算
      交易時段賦值();              // 設定交易時段狀態
   }

   // 獲取市場報價與帳戶資訊
   Ask = SymbolInfoDouble(Symbol(), SYMBOL_ASK);                     // 買價
   Bid = SymbolInfoDouble(Symbol(), SYMBOL_BID);                     // 賣價
   AccountBalance = AccountInfoDouble(ACCOUNT_BALANCE);              // 帳戶餘額
   Tickvalue = SymbolInfoDouble(Symbol(), SYMBOL_TRADE_TICK_VALUE);  // 每點價值
   SP = NormalizeDouble(MathAbs(Ask - Bid), Digits());               // 計算點差

   //+------------------------------------------------------------------+
   //| 動態計算交易手數                                            
   //+------------------------------------------------------------------+
   if(動態計算手數 == true)
   {
      Lots = get_dynamic_lot_size(是否偶數單, Symbol(), 風險百分比, AccountBalance, SL); // 計算動態手數
      Lots = MathMin(0.3, MathMax(0.01, Lots));                                         // 限制手數範圍
   }

   // 當沖模式:限制進場時間
   if(當沖 == true)
   {
      接近收盤 = (getTM_hour(TimeCurrent()) >= 18 || getTM_hour(TimeCurrent()) <= 1); // 18:00 至次日 1:00 為接近收盤
      允許交易時段 = (允許交易時段 && !接近收盤);                                    // 更新交易時段狀態
   }

   // 在允許交易時段內執行進場邏輯
   if(允許交易時段 == true)
   {
      //+------------------------------------------------------------------+
      //| 多單進場                                                      
      //+------------------------------------------------------------------+
      set_BuyCondition(); // 設定多單進場條件
      if(LE_Cond == true && SP < NormalizeDouble(商品平均點差 * Point(), Digits()))
      {
         if(多單部位() == 0 && 空單部位() == 0 && BarNumber != OrderBarNo)
         {
            if(BarSinceExit > 1 && EntriesToday(MagicNumber, Symbol()) < 1)
            {

                  多單進場單號 = Buy_at_MARKET(Symbol(), Lots, TP, SL, "BUY MARKET", MagicNumber); // 下市價買單
                  OrderBarNo = iBarShift(Symbol(), 時間週期, LoadEA);            // 更新下單 K 棒編號
            }
         }
      }

      //+------------------------------------------------------------------+
      //| 空單進場                                                  
      //+------------------------------------------------------------------+
      set_ShortCondition(); // 設定空單進場條件
      if(SE_Cond == true && SP < NormalizeDouble(商品平均點差 * Point(), Digits()))
      {
         if(多單部位() == 0 && 空單部位() == 0 && BarNumber != OrderBarNo)
         {

            if(BarSinceExit > 1 && EntriesToday(MagicNumber, Symbol()) < 1)
            {

                  空單進場單號 = Short_at_MARKET(Symbol(), Lots, TP, SL, "Short Market", MagicNumber); // 下市價賣單
                  OrderBarNo = iBarShift(Symbol(), 時間週期, LoadEA);          // 更新下單 K 棒編號           

         }
      }
   }

   // 當沖模式:到達指定時間自動平倉
   if(當沖 == true && (getTM_hour(TimeCurrent()) == 當沖出場時間 || getTM_hour(TimeCurrent()) == 1))
   {
      if(多單部位() > 0 || 空單部位() > 0)
      {
         當沖平倉(); // 執行當沖平倉
      }
   }

   交易時段外也可停損停利(); // 檢查停損停利條件
   JudgeNo = iBarShift(Symbol(), 時間週期, LoadEA); // 更新判斷 K 棒編號
}

//+------------------------------------------------------------------+
//| 自訂函數:計算多單部位數                                       
//+------------------------------------------------------------------+
int 多單部位()
{
   int count;
   count = get_TradeCounts(Symbol(), MagicNumber, POSITION_TYPE_BUY); // 獲取多單部位數
   return count;
}

//+------------------------------------------------------------------+
//| 自訂函數:計算空單部位數                                        
//+------------------------------------------------------------------+
int 空單部位()
{
   int count;
   count = get_TradeCounts(Symbol(), MagicNumber, POSITION_TYPE_SELL); // 獲取空單部位數
   return count;
}

//+------------------------------------------------------------------+
//| 自訂函數:新 K 棒處理                                        
//+------------------------------------------------------------------+
void 換K棒()
{
   // 新 K 棒時刪除所有掛單
   if(total_pending_order_count(Symbol(), MagicNumber, -1) != 0)
   {
      delete_pending_orders_all(Symbol(), MagicNumber, -1, 0x0000ff); // 刪除所有掛單
   }
   
   Set_OHLC_Bar_Series(); // 設定 K 棒開高低收序列
   Set_OHLC_Day_Series(); // 設定日線開高低收序列
   Get_OHLC_Bar(30);      // 獲取最近 30 根 K 棒數據
   Get_OHLC_Day(15);      // 獲取最近 15 根日線數據
   set_BarInfo();         // 計算 K 棒資訊(範圍、實體、上影線、下影線)
   set_ATR();             // 計算 ATR 指標
   set_MACD();            // 計算 MACD 指標
   set_RSI();             // 計算 RSI 指標
   set_DONCHIAN_CHANNEL(); // 計算 Donchian Channel
   計算FVG價格();         // 計算公平價值缺口(FVG)價格
   Day5Range = ((HighD[1] - LowD[1]) + (HighD[2] - LowD[2]) + (HighD[3] - LowD[3]) + 
                (HighD[4] - LowD[4]) + (HighD[5] - LowD[5])) / 5; // 計算五日平均震幅
}

//+------------------------------------------------------------------+
//| 自訂函數:設定交易時段                                        
//+------------------------------------------------------------------+
void 交易時段賦值()
{
      允許交易時段 = (getTM_hour(TimeCurrent()) >= 6 && getTM_hour(TimeCurrent()) < 22); // 交易時段:6:00-22:00
}

//+------------------------------------------------------------------+
//| 自訂函數:當沖平倉                                           
//+------------------------------------------------------------------+
void 當沖平倉()
{
   if((getTM_hour(TimeCurrent()) == 當沖出場時間 || getTM_hour(TimeCurrent()) == 1))
   {
      if(多單部位() > 0 && BarNumber != CloseOrderNo)
         LX_CloseByTicket(多單進場單號, Lots); // 平倉多單
      if(空單部位() > 0 && BarNumber != CloseOrderNo)
         SX_CloseByTicket(空單進場單號, Lots); // 平倉空單
      CloseOrderNo = iBarShift(Symbol(), 時間週期, LoadEA); // 更新平倉 K 棒編號
   }
}

//+------------------------------------------------------------------+
//| 自訂函數:交易時段外檢查停損停利                        
//+------------------------------------------------------------------+
void 交易時段外也可停損停利()
{
   // 多單停損停利
   if(多單部位() > 0)
   {
      多單進場價格 = LE_EntryPrice(MagicNumber, 多單進場單號); // 獲取多單進場價格
      double 多單最小停利 = NormalizeDouble(多單進場價格 + SP * 3, Digits()); // 最小停利:進場價 + 3 倍點差
      bool LX_MinPF = Bid > 多單最小停利; // 檢查是否達到最小停利
      多單停利價格 = NormalizeDouble(多單進場價格 + TP * Point(), Digits()); // 止盈價格
      多單停損價格 = NormalizeDouble(多單進場價格 - SL * Point(), Digits()); // 止損價格


         LX_Cond = ((LX_MinPF == true && MACD_HDiv_01() == true) || 
                    (Close[1] >= 多單停損價格 && Bid < 多單停損價格)); // 出場條件:MACD 背離或觸及止損
    
      if(LX_Cond == true && BarNumber != CloseOrderNo && LE_BarsSinceEntry(MagicNumber, 多單進場單號, 時間週期) > 0)
      {
         LX_CloseByTicket(多單進場單號, Lots); // 平倉多單
         if(多單部位() == 0)
            CloseOrderNo = iBarShift(Symbol(), 時間週期, LoadEA); // 更新平倉 K 棒編號
      }
   }

   // 空單停損停利
   if(空單部位() > 0)
   {
      空單進場價格 = SE_EntryPrice(MagicNumber, 空單進場單號); // 獲取空單進場價格
      double 空單最小停利 = NormalizeDouble(空單進場價格 - SP * 3, Digits()); // 最小停利:進場價 - 3 倍點差
      bool SX_MinPF = Ask < 空單最小停利; // 檢查是否達到最小停利
      空單停利價格 = NormalizeDouble(空單進場價格 - TP * Point(), Digits()); // 止盈價格
      空單停損價格 = NormalizeDouble(空單進場價格 + SL * Point(), Digits()); // 止損價格

         SX_Cond = ((SX_MinPF == true && MACD_LDiv_01() == true) || 
                    (Close[1] <= 空單停損價格 && Ask > 空單停損價格)); // 出場條件:MACD 背離或觸及止損
    
      if(SX_Cond == true && BarNumber != CloseOrderNo && SE_BarsSinceEntry(MagicNumber, 空單進場單號, 時間週期) > 0)
      {
         SX_CloseByTicket(空單進場單號, Lots); // 平倉空單
         if(空單部位() == 0)
            CloseOrderNo = iBarShift(Symbol(), 時間週期, LoadEA); // 更新平倉 K 棒編號
      }
   }
}

//+------------------------------------------------------------------+
//| 自訂函數:設定多單進場條件                                      
//+------------------------------------------------------------------+
void set_BuyCondition()
{
      LE_Cond = (High[1] >= a_DC_HIGH_A[3] && a_RSIA[1] > 70); // 條件:價格突破 Donchian Channel 上軌且 RSI > 70
}

//+------------------------------------------------------------------+
//| 自訂函數:設定空單進場條件                                    
//+------------------------------------------------------------------+
void set_ShortCondition()
{
      SE_Cond = (Black3Bar_LClose()); // 條件:三黑創低收形態
}

//+------------------------------------------------------------------+
//| 自訂函數:設定 K 棒開高低收序列                            
//+------------------------------------------------------------------+
double Open[], High[], Low[], Close[], Range[], Body[], UPshadow[], DNshadow[];
double OpenD[], HighD[], LowD[], CloseD[];
long Volume[], BigVolume[];
void Set_OHLC_Bar_Series()
{
   ArraySetAsSeries(Open, true);   // 設定開盤價陣列為時間序列
   ArraySetAsSeries(High, true);   // 設定最高價陣列為時間序列
   ArraySetAsSeries(Low, true);    // 設定最低價陣列為時間序列
   ArraySetAsSeries(Close, true);  // 設定收盤價陣列為時間序列
   ArraySetAsSeries(Volume, true); // 設定成交量陣列為時間序列
}

//+------------------------------------------------------------------+
//| 自訂函數:獲取 K 棒開高低收數據                            
//+------------------------------------------------------------------+
void Get_OHLC_Bar(int argCount)
{
   get_OpenData(Symbol(), 時間週期, argCount, Open);   // 獲取開盤價
   get_HighData(Symbol(), 時間週期, argCount, High);   // 獲取最高價
   get_LowData(Symbol(), 時間週期, argCount, Low);     // 獲取最低價
   get_CloseData(Symbol(), 時間週期, argCount, Close); // 獲取收盤價
   get_VolumeData(Symbol(), 時間週期, argCount, Volume); // 獲取成交量
}

//+------------------------------------------------------------------+
//| 自訂函數:設定日線開高低收序列                            
//+------------------------------------------------------------------+
void Set_OHLC_Day_Series()
{
   ArraySetAsSeries(OpenD, true);  // 設定日線開盤價陣列
   ArraySetAsSeries(HighD, true);  // 設定日線最高價陣列
   ArraySetAsSeries(LowD, true);   // 設定日線最低價陣列
   ArraySetAsSeries(CloseD, true); // 設定日線收盤價陣列
}

//+------------------------------------------------------------------+
//| 自訂函數:獲取日線開高低收數據                             
//+------------------------------------------------------------------+
void Get_OHLC_Day(int argCount)
{
   get_OpenData(Symbol(), PERIOD_D1, argCount, OpenD);   // 獲取日線開盤價
   get_HighData(Symbol(), PERIOD_D1, argCount, HighD);   // 獲取日線最高價
   get_LowData(Symbol(), PERIOD_D1, argCount, LowD);     // 獲取日線最低價
   get_CloseData(Symbol(), PERIOD_D1, argCount, CloseD); // 獲取日線收盤價
}

//+------------------------------------------------------------------+
//| 自訂函數:計算 K 棒資訊(範圍、實體、影線)      
//+------------------------------------------------------------------+
void set_BarInfo()
{
   ArrayResize(Range, ArraySize(Open));   // 調整範圍陣列大小
   ArraySetAsSeries(Range, true);
   ArrayResize(Body, ArraySize(Open));    // 調整實體陣列大小
   ArraySetAsSeries(Body, true);
   ArrayResize(UPshadow, ArraySize(Open)); // 調整上影線陣列大小
   ArraySetAsSeries(UPshadow, true);
   ArrayResize(DNshadow, ArraySize(Open)); // 調整下影線陣列大小
   ArraySetAsSeries(DNshadow, true);

   for(int i = 0; i < ArraySize(Open) - 1; i++)
   {
      Range[i] = High[i] - Low[i];                     // 計算 K 棒範圍
      Body[i] = MathAbs(Close[i] - Open[i]);           // 計算 K 棒實體
      UPshadow[i] = High[i] - MathMax(Close[i], Open[i]); // 計算上影線
      DNshadow[i] = MathMin(Close[i], Open[i]) - Low[i];  // 計算下影線
   }
}

//+------------------------------------------------------------------+
//| 自訂函數:計算 ATR 指標                                      
//+------------------------------------------------------------------+
double a_ATR[];
void set_ATR()
{
   int h_ATR;
   h_ATR = iATR(Symbol(), 時間週期, LenB2); // 創建 ATR 指標句柄(週期 LenB2 = 24)
   ArraySetAsSeries(a_ATR, true);
   get_IndexData(h_ATR, 0, 0, 5, a_ATR);    // 獲取最近 5 筆 ATR 數據
}

//+------------------------------------------------------------------+
//| 自訂函數:計算 MACD 指標                                      
//+------------------------------------------------------------------+
double a_DIF[], a_MACD[], a_OSC[];
void set_MACD()
{
   int MacdLen, FastLen, SlowLen;
   MacdLen = MathMax(LenA1, LenB1);         // 取 LenA1 和 LenB1 的最大值
   if(MacdLen > 15) MacdLen = 15;           // 限制最大週期為 15
   FastLen = (int)MathRound(MacdLen * 1.33); // 快速線週期
   SlowLen = (int)MathRound(MacdLen * 2.66); // 慢速線週期

   int h_DIF = iMACD(Symbol(), 時間週期, FastLen, SlowLen, MacdLen, PRICE_WEIGHTED); // 創建 MACD 指標句柄
   ArraySetAsSeries(a_DIF, true);
   int h_MACD = iMACD(Symbol(), 時間週期, FastLen, SlowLen, MacdLen, PRICE_WEIGHTED);
   ArraySetAsSeries(a_MACD, true);

   get_IndexData(h_DIF, 0, 0, 10, a_DIF);   // 獲取 DIF 數據
   get_IndexData(h_MACD, 1, 0, 10, a_MACD); // 獲取 MACD 數據

   ArraySetAsSeries(a_OSC, true);
   ArrayResize(a_OSC, ArraySize(a_DIF));
   for(int i = 0; i < ArraySize(a_OSC); i++)
   {
      a_OSC[i] = a_DIF[i] - a_MACD[i];      // 計算 OSC(柱狀圖)
   }
}

//+------------------------------------------------------------------+
//| 自訂函數:計算 RSI 指標                                     
//+------------------------------------------------------------------+
double a_RSIA[], a_RSIB[];
void set_RSI()
{
   int h_RSIA = iRSI(Symbol(), 時間週期, LenA1, PRICE_CLOSE); // 創建 RSI 指標句柄(週期 LenA1)
   ArraySetAsSeries(a_RSIA, true);
   get_IndexData(h_RSIA, 0, 0, 5, a_RSIA);                    // 獲取 RSI A 數據

   int h_RSIB = iRSI(Symbol(), 時間週期, LenB1, PRICE_CLOSE); // 創建 RSI 指標句柄(週期 LenB1)
   ArraySetAsSeries(a_RSIB, true);
   get_IndexData(h_RSIB, 0, 0, 5, a_RSIB);                    // 獲取 RSI B 數據
}

//+------------------------------------------------------------------+
//| 自訂函數:MACD 高點背離                                    
//+------------------------------------------------------------------+
bool MACD_HDiv_01()
{
   if((ArrayMaximum(High) != 0 && ArrayMaximum(High) <= 2) && 
      (ArrayMaximum(a_DIF) > 2) && (a_RSIA[1] < 70))
   {
      return true; // 價格新高但 MACD 未創新高,且 RSI < 70
   }
   return false;
}

//+------------------------------------------------------------------+
//| 自訂函數:MACD 低點背離                                     
//+------------------------------------------------------------------+
bool MACD_LDiv_01()
{
   if((ArrayMinimum(Low) != 0 && ArrayMinimum(Low) <= 2) && 
      (ArrayMinimum(a_DIF) > 2) && (a_RSIA[1] > 30))
   {
      return true; // 價格新低但 MACD 未創新低,且 RSI > 30
   }
   return false;
}

//+------------------------------------------------------------------+
//| 自訂函數:三黑創低收形態                                    
//+------------------------------------------------------------------+
bool Black3Bar_LClose()
{
   int j = 0;
   for(int i = 1; i <= 3; i++)
   {
      if(Close[i] < Open[i]) // 檢查是否為陰線
      {
         j++;
      }
   }
   if((j == 3 && Close[1] <= MathMin(Close[2], Close[3]))) // 三根陰線且最新收盤價最低
   {
      return true;
   }
   return false;
}

//+------------------------------------------------------------------+
//| 自訂函數:計算 Donchian Channel                            
//+------------------------------------------------------------------+
double a_DC_HIGH_A[], a_DC_LOW_A[], a_DC_MIDDLE_A[]; // Donchian Channel A 組

void set_DONCHIAN_CHANNEL()
{
   // 設定陣列為時間序列並調整大小
   ArraySetAsSeries(a_DC_HIGH_A, true);
   ArraySetAsSeries(a_DC_LOW_A, true);
   ArraySetAsSeries(a_DC_MIDDLE_A, true);

   ArrayResize(a_DC_HIGH_A, 25); // 調整陣列大小
   ArrayResize(a_DC_LOW_A, 25);
   ArrayResize(a_DC_MIDDLE_A, 25);

   int Idx_AH, Idx_BH, Idx_CH, Idx_AL, Idx_BL, Idx_CL;
   for(int i = 0; i < ArraySize(a_DC_HIGH_A) - 1; i++)
   {
      Idx_AH = iHighest(Symbol(), 時間週期, MODE_HIGH, LenA1, i); // LenA1 週期最高價索引
      Idx_AL = iLowest(Symbol(), 時間週期, MODE_LOW, LenA1, i);   // LenA1 週期最低價索引

      a_DC_HIGH_A[i] = iHigh(Symbol(), 時間週期, Idx_AH);         // A 組上軌
      a_DC_LOW_A[i] = iLow(Symbol(), 時間週期, Idx_AL);           // A 組下軌
      a_DC_MIDDLE_A[i] = a_DC_HIGH_A[i] - a_DC_LOW_A[i];          // A 組中線

   }
}

回測結果
測試參數交易商品:XAUUSD(黃金)
樣本內區間:2019/1/1 ~ 2023/7/31
交易手數:固定0.1手
時間框架:H1 小時圖表
交易模式:當沖交易


沒有留言:

張貼留言