2025年11月7日 星期五

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

核心策略概述 (商品 NAS100)
這是一個基於布林通道(Bollinger Bands)與價格型態的反轉交易策略,整合多日高低點分析與三均線系統來識別進場時機,採用當沖模式並設有保本機制。

風險控制:
低於資金風控門檻時停止交易並退出EA
商品平均點差限制:130點(進場前檢查點差)

交易限制:
每日限制1次進場
進場間隔:BarSinceExit > 1(需距離上次平倉至少2根K棒)

進出場策略
多單進場條件
LE_Cond = (布林下軌反轉型態 || 多日低點突破後反彈)
條件A - 布林下軌反轉型態:

收盤價 < 布林下軌
紅K(收>開)且實體小於總區間33%
下影線 > 上影線的3倍(長下影線)

條件B - 多日低點突破:
Low[1] < Low[2]、Low[3]、Low4
High[1] > High2
紅K收盤

進場方式: 市價單買入,僅在無部位、點差合格、今日未進場且BarSinceExit>1時執行

空單進場條件
SE_Cond = Black3Bar_LClose()
三黑創低收型態:

連續3根黑K(收<開)
第3根收盤價 ≤ Min(第2根收盤,第1根收盤)
形成下降趨勢的連續陰線

進場方式: 市價單賣出,僅在無部位、點差合格、今日未進場且BarSinceExit>1時執行

多單出場策略(出場方式9)
停利: 進場價 + 61200點
停損: 進場價 - 15000點
主要出場條件:
LX_Cond = (有最小獲利 && 收盤下穿7MA) || 觸及停損價
保本機制(保藍設定):

當最高價 > 進場價 + TP*0.5 時啟動
保本價格 = 進場價 + TP*0.168 (約10,281點)
若價格回落跌破保本價則平倉

最小獲利要求:

Bid > 進場價 + 點差*3 才允許出場
配合7期均線(a_Avg7)下穿訊號


空單出場策略(出場方式30)
停利: 進場價 - 61200點 或 3日最低價(DayL)
停損: 進場價 + 15000點
主要出場條件:
SX_Cond = (達到3日低點停利 || 觸及停損價)
保本機制(保藍設定):

當最低價 < 進場價 - TP*0.5 時啟動
保本價格 = 進場價 - TP*0.168 (約10,281點)
若價格反彈突破保本價則平倉

動態停利:

優先使用3日最低點(DayL)作為停利目標
若價格到達後反彈則平倉

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

ENUM_TIMEFRAMES  時間週期 = PERIOD_H4; // EA 主要運行的時間週期 (H4: 4 小時)。
ENUM_TIMEFRAMES  時間框架 = PERIOD_D1; // 相對大週期,可能用於趨勢判斷或過濾 (D1: 日線)。

//+------------------------------------------------------------------+
//| 初始化函式 (EA 載入時執行一次)
//+------------------------------------------------------------------+
int OnInit()
  {
   LoadEA = TimeCurrent(); // 記錄 EA 載入的當前時間。
   return(INIT_SUCCEEDED); // 初始化成功。
  }

//+------------------------------------------------------------------+
//| OnTick 函式 (每次報價變動時執行)
//+------------------------------------------------------------------+
void OnTick()
  {
   // 資金風控檢查:如果帳戶淨值低於設定的資金風控值,則執行緊急處理。
   if(AccountInfoDouble(ACCOUNT_BALANCE) < 資金風控)
     {
      Alert("**********  資金不足 *************"); // 彈出警報。
      
      
      // 刪除所有掛單
      if(total_pending_order_count(Symbol(), MagicNumber,-1) != 0) // 檢查是否有掛單
        {
         delete_pending_orders_all(Symbol(), MagicNumber, -1, 0x0000ff); // 刪除所有由本 EA 下的掛單
        }
      // 持倉部位平倉 (確保不在同一根 K 棒重複平倉)
      if(多單部位() > 0 && BarNumber != CloseOrderNo)
         LX_CloseByTicket(多單進場單號,Lots) ; // 平倉多單
      if(空單部位() > 0 && BarNumber != CloseOrderNo)
         SX_CloseByTicket(空單進場單號,Lots) ; // 平倉空單
      ExpertRemove(); // 退出 EA (從圖表中移除)
                     
      return; // 結束 OnTick 函式
     }

   // 計算 K 棒序號,LoadEA 時間在新 K 棒出現時會移動
   BarNumber = iBarShift(Symbol(),時間週期,LoadEA);
   // 計算距離上次平倉過了多少根 K 棒
   BarSinceExit = BarNumber-CloseOrderNo ;
   // 檢查是否為新 K 棒 (BarNumber == 1) 且 JudgeNo 還未更新
   if((BarNumber == 1 && BarNumber != JudgeNo))
     {
      // 在新 K 棒開始時,進行一次測試性的下單與平倉(通常用於初始化交易相關的函式庫或獲取初始部位資訊)
      多單進場單號 = Buy_at_MARKET(Symbol(),Lots,0,0,"1st_K",MagicNumber) ;
      LX_CloseByTicket(多單進場單號,Lots) ;
      空單進場單號 = Short_at_MARKET(Symbol(),Lots,0,0,"1st_K",MagicNumber) ;
      SX_CloseByTicket(空單進場單號,Lots) ;
      // 更新平倉 K 棒序號為當前 K 棒
      CloseOrderNo =  iBarShift(Symbol(),時間週期,LoadEA);
      // 根據 LenA1 和 LenB1 設置快慢均線週期
      FastSma = MathMin(LenA1, LenB1);
      SlowSma = MathMax(LenA1, LenB1);
     }

   // 檢查是否為新 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()) ; // 計算當前點差 (SPread)

//+------------------------------------------------------------------+
//| 計算交易手數
//+------------------------------------------------------------------+
   if(動態計算手數 == true)
     {
      // 根據風險百分比和停損點數 (SL) 計算動態手數
      Lots = get_dynamic_lot_size(是否偶數單,Symbol(),風險百分比,AccountBalance,SL) ;
      // 將計算出的手數限制在 0.01 到 0.3 之間
      Lots = MathMin(0.3,MathMax(0.01,Lots)) ;
     }

   // 當沖模式下的交易時段調整
   if(當沖 == true)
     {
      // 判斷是否接近收盤時段 (例如 18點到次日 1點)
      接近收盤 = (getTM_hour(TimeCurrent()) >= 18 || getTM_hour(TimeCurrent()) <= 1) ;
      // 在當沖模式下,如果接近收盤,則不允許交易
      允許交易時段 = (允許交易時段 && !接近收盤) ;
     }

   // 檢查是否在允許的交易時段內
   if(允許交易時段 == true)
     {
      //+------------------------------------------------------------------+
      //|多單進場邏輯
      //+------------------------------------------------------------------+
      set_BuyCondition(); // 呼叫函式檢查多單進場條件 (LE_Cond)
      // 檢查多單進場條件成立、點差小於平均點差、且最近幾筆內有空單平倉(防止立即反向開倉或特殊邏輯)
      if(LE_Cond == true && SP < NormalizeDouble(商品平均點差*Point(),Digits()) && 最近幾筆內有空單平倉(Symbol(),6) == true)
        {
         // 確保當前空手且不在同一根 K 棒重複下單
         if(多單部位() == 0 && 空單部位() == 0 && BarNumber != OrderBarNo)
           {
            // 確保距離上次平倉已超過 1 根 K 棒,且當日進場次數小於 1 (限制每日一次)
            if(BarSinceExit > 1 && EntriesToday(MagicNumber,Symbol()) < 1)
              {

                  //  執行市價單買入(Long Entry Market)
                  多單進場單號 = Buy_at_MARKET(Symbol(),Lots,TP,SL,"BUY MARKET",MagicNumber) ;
                  OrderBarNo = iBarShift(Symbol(),時間週期,LoadEA); // 記錄下單 K 棒序號

              } // end of BarSinceExit > 1
           } // end of 空手且不同根K棒() == true
        } // end of LE_Cond == true
      //+------------------------------------------------------------------+
      //|空單進場邏輯
      //+------------------------------------------------------------------+
      set_ShortCondition() ; // 呼叫函式檢查空單進場條件 (SE_Cond)
      // 檢查空單進場條件成立、點差小於平均點差
      if(SE_Cond == true && SP < NormalizeDouble(商品平均點差*Point(),Digits()))
        {
         // 確保當前空手且不在同一根 K 棒重複下單
         if(多單部位() == 0 && 空單部位() == 0 && BarNumber != OrderBarNo)
           {
            // 確保距離上次平倉已超過 1 根 K 棒,且當日進場次數小於 1 (限制每日一次)
            if(BarSinceExit > 1 && EntriesToday(MagicNumber,Symbol()) < 1)
              {

                  // 執行市價單賣出(Short Entry Market)
                 空單進場單號 = Short_at_MARKET(Symbol(),Lots,TP,SL,"Short Market",MagicNumber) ;
                  OrderBarNo = iBarShift(Symbol(),時間週期,LoadEA); // 記錄下單 K 棒序號

              } //end of BarSinceExit > 1
           } // end of 空手且不同根K棒() == true
        } // end of SE_Cond == true
     } //end of 允許交易時段 == true

//+------------------------------------------------------------------+
//| 當沖強制平倉邏輯
//+------------------------------------------------------------------+
   // 檢查是否為當沖模式,且當前時間達到設定的出場時間 (當沖出場時間 或 1點)
   if(當沖 == true && (getTM_hour(TimeCurrent()) == 當沖出場時間 || getTM_hour(TimeCurrent()) == 1))
     {

      if(多單部位() > 0 || 空單部位() > 0) // 如果有持倉
        {
         當沖平倉(); // 執行當沖平倉函式
        }
     }

   交易時段外也可停損停利(); // 執行停損停利及保本邏輯(在非交易時段也檢查)
   JudgeNo = iBarShift(Symbol(),時間週期,LoadEA); // 更新 K 棒判斷變數
  } //end of onTick


//+------------------------------------------------------------------+
//|        自訂函數庫                                                          |
//+------------------------------------------------------------------+
// 函式:多單部位數量
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 棒開始時,刪除所有掛單(避免在換 K 棒後誤觸發)
   if(total_pending_order_count(Symbol(), MagicNumber,-1) != 0)
     {
      delete_pending_orders_all(Symbol(), MagicNumber, -1, 0x0000ff); // 刪除本 EA 的所有掛單

     }
   // 呼叫函式庫設定/獲取 OHLC K 棒序列資料(保留:表示該函式未在當前程式碼中定義,來自 MagicMT5_函數庫V2)
   Set_OHLC_Bar_Series(); //保留
   Set_OHLC_Day_Series(); //保留
   Get_OHLC_Bar(30) ; //保留 (獲取最近 30 根 K 棒資料)
   Get_OHLC_Day(15) ; //保留 (獲取最近 15 天 K 棒資料)
   set_BarInfo(); //保留 (設定 K 棒相關資訊)
   set_BBAND(); // 呼叫函式庫計算布林通道 (BBAND)
   set_ThreeLine() ; // 呼叫函式庫計算三線指標 (ThreeLine)
   ThreeDayHL(); // 呼叫函式庫計算三日高低點

   FixBarHL(); // 呼叫函式庫修正/計算 K 棒高低點

   // 計算近五日平均振幅 (使用 DayD[i] 陣列的高點和低點)
   Day5Range = ((HighD[1] - LowD[1])+(HighD[2] - LowD[2])+(HighD[3] - LowD[3])+(HighD[4] - LowD[4])+(HighD[5] - LowD[5]))/5;
  }

//+------------------------------------------------------------------+
//| 函式:根據交易時段編號設定是否允許交易
//+------------------------------------------------------------------+
void 交易時段賦值()
  {

      // 設置允許交易的時段:(4 點到 8 點) 或 (12 點到 16 點)
      允許交易時段 = (getTM_hour(TimeCurrent()) >= 4 && getTM_hour(TimeCurrent()) < 8) || (getTM_hour(TimeCurrent()) >= 12 && getTM_hour(TimeCurrent()) < 16);

  }

//+------------------------------------------------------------------+
//| 函式:當沖強制平倉邏輯
//+------------------------------------------------------------------+
void 當沖平倉()
  {
   // 檢查是否達到當沖出場時間 (當沖出場時間 或 1點)
   if((getTM_hour(TimeCurrent()) == 當沖出場時間 || getTM_hour(TimeCurrent()) == 1))
     {
      // 平倉多單 (確保不在同一根 K 棒重複平倉)
      if(多單部位() > 0 && BarNumber != CloseOrderNo)
         LX_CloseByTicket(多單進場單號,Lots) ;
      // 平倉空單 (確保不在同一根 K 棒重複平倉)
      if(空單部位() > 0 && BarNumber != CloseOrderNo)
         SX_CloseByTicket(空單進場單號,Lots) ;
      CloseOrderNo = iBarShift(Symbol(),時間週期,LoadEA) ; // 記錄平倉 K 棒序號
     }
  }

//+------------------------------------------------------------------+
//| 函式:停損停利及保本邏輯(在交易時段外也應檢查)
//+------------------------------------------------------------------+
void 交易時段外也可停損停利()
  {
// 交易時段外也可停損停利
   if(多單部位() > 0) // 如果有多單部位
     {
      //     多單進場價格 = get_position_info_double((ulong)多單進場單號, POSITION_PRICE_OPEN); // 獲取開倉價格 (備註掉的寫法)
      //+------------------------------------------------------------------+
      //|    Buy Exit Method (多單出場邏輯)
      //+------------------------------------------------------------------+
      double 多單最小停利 = 0.0, 多單保本價格 = 0.0 ; // 最小停利和保本價格變數
      bool LX_MinPF = false, LongPull = false ; // 最小獲利旗標和拉回旗標
      多單進場價格 = LE_EntryPrice(MagicNumber,多單進場單號); // 獲取多單進場價格
      多單最小停利 = NormalizeDouble(多單進場價格 + SP*3,Digits()) ; // 計算最小停利價格 (進場價 + 3 倍點差)
      LX_MinPF = Bid > 多單最小停利 ; // 檢查當前買價是否高於最小停利價格
      // LX_MinPF = true ; // 最小停利旗標直接設為 true (備註掉的寫法)
      多單停利價格 = NormalizeDouble(多單進場價格 + TP * Point(),Digits()) ; // 計算實際停利價格
      多單停損價格 = NormalizeDouble(多單進場價格 - SL * Point(),Digits()) ; // 計算實際停損價格

      //多單保本出場邏輯
      if(保藍設定 == true)
        {
         // 獲取多單進場後達到的最高價
         多單最高價 = iHigh(Symbol(),時間週期, iHighest(Symbol(),時間週期,MODE_HIGH,LE_BarsSinceEntry(MagicNumber,多單進場單號,時間週期),1));
         多單最高價 = NormalizeDouble(多單最高價,Digits()) ;

         // 如果最高價達到停利點數 TP 的 50%
         if(多單最高價 > 多單進場價格+TP * Point()*0.5)
           {
            LongPull = true ; // 設定拉回旗標為 true
            多單保本價格 = NormalizeDouble(多單進場價格+TP * Point()*0.168,Digits()) ; // 計算保本價格 (進場價 + TP 的 16.8%)
            // 保本出場條件:已達一定獲利 (LongPull=true),且上一根收盤價高於保本價,但當前買價 Bid 跌破保本價
            LX_Cond = (LongPull == true && Close[1] >= 多單保本價格 && Bid < 多單保本價格);
           }
         else
            LX_Cond = false ; // 否則保本出場條件不成立

         // 執行保本平倉
         if(LX_Cond == true && BarNumber != CloseOrderNo && LE_BarsSinceEntry(MagicNumber,多單進場單號,時間週期) > 0)
           {
            LX_CloseByTicket(多單進場單號,Lots) ; // 平倉

            if(多單部位() == 0) // 如果平倉成功
              {
               CloseOrderNo = iBarShift(Symbol(),時間週期,LoadEA) ; // 記錄平倉 K 棒序號
              }
           }
        }


         // 出場條件:(最小獲利達標且快線向下穿越慢線) 或 (K 棒收盤價高於停損價但當前買價 Bid 跌破停損價)
         LX_Cond = ((LX_MinPF == true && CrossUnder(Close[2],a_Avg7[2],Close[1],a_Avg7[1])) || (Close[1] >= 多單停損價格 && Bid < 多單停損價格)) ;

      //---------------------------------------------------------多單出場

      // 執行主要出場條件平倉
      if(LX_Cond == true && BarNumber != CloseOrderNo && LE_BarsSinceEntry(MagicNumber,多單進場單號,時間週期) > 0)
        {
         LX_CloseByTicket(多單進場單號,Lots) ; // 平倉

         if(多單部位() == 0) // 如果平倉成功
           {
            CloseOrderNo = iBarShift(Symbol(),時間週期,LoadEA) ; // 記錄平倉 K 棒序號
           }
        }
     } // end of 多單部位() > 0

   if(空單部位() > 0) // 如果有空單部位
     {
      //+------------------------------------------------------------------+
      //|    Short Exit Method (空單出場邏輯)
      //+------------------------------------------------------------------+
      double 空單最小停利 = 0.0, 空單保本價格 = 0.0 ; // 最小停利和保本價格變數
      bool SX_MinPF = false, ShortPull = false ; // 最小獲利旗標和拉回旗標
      空單進場價格 = SE_EntryPrice(MagicNumber,空單進場單號); // 獲取空單進場價格
      空單最小停利 = NormalizeDouble(空單進場價格 - SP*3,Digits()) ; // 計算最小停利價格 (進場價 - 3 倍點差)
      SX_MinPF = Ask < 空單最小停利 ; // 檢查當前賣價是否低於最小停利價格
      //    SX_MinPF = true ; // 最小停利旗標直接設為 true (備註掉的寫法)
      空單停利價格 = NormalizeDouble(空單進場價格 - TP * Point(),Digits()) ; // 計算實際停利價格
      空單停損價格 = NormalizeDouble(空單進場價格 + SL * Point(),Digits()) ; // 計算實際停損價格

      //空單保本出場邏輯
      if(保藍設定 == true)
        {
         // 獲取空單進場後達到的最低價
         空單最低價 = iLow(Symbol(),時間週期, iLowest(Symbol(),時間週期,MODE_LOW,SE_BarsSinceEntry(MagicNumber,空單進場單號,時間週期),1));
         空單最低價 = NormalizeDouble(空單最低價,Digits()) ;

         // 如果最低價達到停利點數 TP 的 50%
         if(空單最低價 < 空單進場價格-TP * Point()*0.5)
           {
            ShortPull = true ; // 設定拉回旗標為 true
            空單保本價格 = NormalizeDouble(空單進場價格-TP * Point()*0.168,Digits()) ; // 計算保本價格 (進場價 - TP 的 16.8%)
            // 保本出場條件:已達一定獲利 (ShortPull=true),且上一根收盤價低於保本價,但當前賣價 Ask 升破保本價
            SX_Cond = (ShortPull == true && Close[1] <= 空單保本價格 && Ask > 空單保本價格);
           }
         else
            SX_Cond = false ; // 否則保本出場條件不成立

         // 執行保本平倉
         if(SX_Cond == true && BarNumber != CloseOrderNo && SE_BarsSinceEntry(MagicNumber,空單進場單號,時間週期) > 0)
           {
            SX_CloseByTicket(空單進場單號,Lots) ; // 平倉

            if(空單部位() == 0) // 如果平倉成功
              {
               CloseOrderNo = iBarShift(Symbol(),時間週期,LoadEA) ; // 記錄平倉 K 棒序號
              }
           }
        }

         bool SXcond301 ;
         空單停利價格 = NormalizeDouble(DayL,Digits()) ; // 將停利價格設定為當日最低價 (DayL 應為自訂函式庫中的變數)
         // 條件 30-1:開倉價高於停利價 (當日低點),且上一根收盤價低於停利價,但當前賣價 Ask 升破停利價
         SXcond301 = (空單進場價格 > 空單停利價格 && Close[1] >= 空單停利價格 && Ask < 空單停利價格) ;
         // 總出場條件:條件 30-1 成立 或 (K 棒收盤價低於停損價但當前賣價 Ask 升破停損價)
         SX_Cond = (SXcond301 == true || (Close[1] <= 空單停損價格 && Ask > 空單停損價格)) ;

      //---------------------------------------------------------空單出場

      // 執行主要出場條件平倉
      if(SX_Cond == true && BarNumber != CloseOrderNo && SE_BarsSinceEntry(MagicNumber,空單進場單號,時間週期) > 0)
        {
         SX_CloseByTicket(空單進場單號,Lots) ; // 平倉

         if(空單部位() == 0) // 如果平倉成功
           {
            CloseOrderNo = iBarShift(Symbol(),時間週期,LoadEA) ; // 記錄平倉 K 棒序號
           }
        }
     } // end of 空單部位() > 0
  } // end of 交易時段外也可停損停利()


//+------------------------------------------------------------------+
//| 函式:多單進場條件設定
//+------------------------------------------------------------------+
void set_BuyCondition()
  {

      // 多單進場條件 :
      // (1) 上一根收盤價低於布林通道下軌 (a_BBDN[1])
      // (2) K 棒有實體 (Body[1] != 0) 且收陽線 (Close[1] > Open[1])
      // (3) 實體長度小於總振幅的 33% (Body[1] < Range[1] * 0.33)
      // (4) 下影線長度大於上影線的 3 倍 (DNshadow[1] > UPshadow[1] * 3) (可能是錘子線或長下影線)
      // --- 或 ---
      // (5) 上一根低點低於前 2、3、4 根低點,且高點高於前 2 根高點,且收陽線 (Close[1] > Open[1]) (可能是吞噬或 V 型反轉)
      LE_Cond = ((Close[1] < a_BBDN[1] && (Body[1] != 0 && Close[1] > Open[1]) && (Body[1] < Range[1] * 0.33) && (DNshadow[1] > UPshadow[1] * 3))
                 || (Low[1] < Low[4] && Low[1] < Low[3] && Low[1] < Low[2] && High[1] > High[2] && Close[1] > Open[1])) ;

  }

//+------------------------------------------------------------------+
//| 函式:空單進場條件設定
//+------------------------------------------------------------------+
void set_ShortCondition()
  {
      // 空單進場條件 :呼叫 Black3Bar_LClose() 函式,通常表示 "三黑創低收+高點下降+跌破第四根低點"
      SE_Cond = (Black3Bar_LClose()) ; // 三黑創低收+高點下降+跌破第四根低點 (條件名稱)

  }

回測結果
測試參數交易商品:NAS100(那斯達克指數)
樣本內區間:2019/1/1 ~ 2023/9/30
交易手數:固定 1 手
時間框架:H4 小時圖表
交易模式:當沖交易

現在就加入《外匯實戰 MT5 EA基礎班》,用量化思維,開啟你的交易新紀元!


★ ~~~ 早鳥優惠 ( 2025/11/13 前報名) ~~~ ★ 
🎁限量加碼 贈送 實戰EA *2
(XAUUSD *1 + NAS100 * 1)
可攜伴2位參加文創手作聖誕樹 DIY才藝班




沒有留言:

張貼留言