2025年10月27日 星期一

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

核心策略概述 (商品 NAS100)
這是一個基於技術指標的趨勢突破與逆勢交易策略。策略整合MACD背離分析、CDP逆勢指標、ADX趨勢強度等多項技術指標來識別進場機會,並採用嚴格的風險控制與交易時段管理。

風險控制
資金風控門檻: 帳戶餘額 < 資金風控時停止所有交易
點差控制: 進場前檢查 SP < 商品平均點差 * Point()

交易限制
每日進場次數: 限制 < 1次(EntriesToday(MagicNumber,Symbol()) < 1)
平倉冷卻期: 距離上次出場至少2根K棒(`BarSinceExit > 1`)
K棒控制: 同一根K棒不重複進場(`BarNumber != OrderBarNo`)

進出場策略
多單進場條件
LE_Cond = (High[1] == Highest_OHLC(10日) && Close[1] > Close[2]);
條件分析:
1. 創新高突破: 前一根K棒最高價 = 最近10日最高價
2. 收盤強勢: 前一根收盤價 > 前二根收盤價(確認突破有效性)

進場執行:
方式: 市價單買入(`Buy_at_MARKET`)
前置條件: 
  - 空手狀態(多單部位() == 0 && 空單部位() == 0)
  - 點差合理(SP < 商品平均點差*Point())
  - 今日未進場(EntriesToday < 1)
  - 平倉冷卻期已過(BarSinceExit > 1)
  - 不同K棒(BarNumber != OrderBarNo)

空單進場條件
SE_Cond = (Close[1] < CDP && Close[1] < NL && a_ADX[1] > a_ADX[2]);
條件分析:
1. 跌破CDP中心: 收盤價 < CDP(逆勢指標的中心軸)
2. 跌破近低值: 收盤價 < NL(CDP系統的近低值)
3. *趨勢增強: ADX上升(a_ADX[1] > a_ADX[2]),確認下跌趨勢強度增加

進場執行:
方式: 市價單賣出(`Short_at_MARKET`)
前置條件: 
  - 空手狀態(多單部位() == 0 && 空單部位() == 0)
  - 點差合理(SP < 商品平均點差*Point())
  - 今日未進場(EntriesToday < 1)
  - 平倉冷卻期已過(BarSinceExit > 1)
  - 不同K棒(BarNumber != OrderBarNo)

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

ENUM_TIMEFRAMES 時間週期 = PERIOD_M6; // 主要分析時間框架(6分鐘) ENUM_TIMEFRAMES 時間框架 = PERIOD_H1; // 相對大週期(1 小時)

//+------------------------------------------------------------------+
//| EA 初始化函數 - 當 EA 載入到圖表時執行一次                         
//+------------------------------------------------------------------+
int OnInit()
  {
   // 記錄 EA 載入的時間
   LoadEA = TimeCurrent();
   
   // 顯示交易品種的最小交易手數和點值資訊
   return(INIT_SUCCEEDED); // 返回初始化成功
  }

//+------------------------------------------------------------------+
//| OnTick 函數 - 每次價格變動時執行                                 
//+------------------------------------------------------------------+
void OnTick()
  {
   //+------------------------------------------------------------------+
   //| 資金風控檢查 - 如果帳戶餘額低於設定值則停止交易                  
   //+------------------------------------------------------------------+
   if(AccountInfoDouble(ACCOUNT_BALANCE) < 資金風控)
     {
      Alert("**********  資金不足 *************");
      return; // 資金不足時直接返回,不執行任何交易邏輯
     }

   //+------------------------------------------------------------------+
   //| K 棒計數器與新 K 棒判斷                                            
   //+------------------------------------------------------------------+
   // 計算從 EA 載入到現在經過了多少根 K 棒
   BarNumber = iBarShift(Symbol(),時間週期,LoadEA);
   
   // 計算從上次平倉到現在經過了多少根 K 棒
   BarSinceExit = BarNumber-CloseOrderNo ;
   
   // 判斷是否為新的第一根 K 棒(用於開盤時的初始化交易)
   if((BarNumber == 1 && BarNumber != JudgeNo))
     {
      // 執行市價多單進場並立即平倉(可能是測試或初始化用途)
      多單進場單號 = 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 棒編號
      CloseOrderNo =  iBarShift(Symbol(),時間週期,LoadEA);
      
      // 計算快慢均線週期(取較小值和較大值)
      FastSma = MathMin(LenA1, LenB1);
      SlowSma = MathMax(LenA1, LenB1);
     }

   //+------------------------------------------------------------------+
   //| 新 K 棒時更新指標與交易時段                                          
   //+------------------------------------------------------------------+
   if(BarNumber != JudgeNo)
     {
      換K棒();           // 更新所有指標、OHLC 數據
      交易時段賦值();    // 判斷當前是否在允許交易的時段
     }

   //+------------------------------------------------------------------+
   //| 更新市場報價與帳戶資訊                                               
   //+------------------------------------------------------------------+
   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) ;
      
      // 限制手數在 0.01 到 0.3 之間
      Lots = MathMin(0.3,MathMax(0.01,Lots)) ;
     }

   //+------------------------------------------------------------------+
   //| 交易時段檢查 - 只在允許的時段內執行進場邏輯                          
   //+------------------------------------------------------------------+
   if(允許交易時段 == true)
     {
      //+------------------------------------------------------------------+
      //| 多單進場邏輯                                                      
      //+------------------------------------------------------------------+
      set_BuyCondition(); // 設定多單進場條件
      
      // 多單進場條件:
      // 1. 滿足多單進場條件(LE_Cond)
      // 2. 點差小於設定的平均點差
      // 3. 最近 6 筆交易內有空單平倉(表示市場可能反轉)
      if(LE_Cond == true && SP < NormalizeDouble(商品平均點差*Point(),Digits()) && 最近幾筆內有空單平倉(Symbol(),6) == true)
        {
         // 確保當前為空手狀態且不在同一根 K 棒重複進場
         if(多單部位() == 0 && 空單部位() == 0 && BarNumber != OrderBarNo)
           {
            // 確保距離上次出場至少 2 根 K 棒,且當日進場次數少於 1 次
            if(BarSinceExit > 1 && EntriesToday(MagicNumber,Symbol()) < 1)
              {

                  // 執行市價多單進場
           多單進場單號 = Buy_at_MARKET(Symbol(),Lots,TP,SL,"BUY MARKET",MagicNumber) ;
                  
                  // 記錄進場的 K 棒編號,避免同一根 K 棒重複進場
                  OrderBarNo = iBarShift(Symbol(),時間週期,LoadEA);

              }
           }
        }
        
      //+------------------------------------------------------------------+
      //| 空單進場邏輯                                                       
      //+------------------------------------------------------------------+
      set_ShortCondition() ; // 設定空單進場條件
      
      // 空單進場條件:
      // 1. 滿足空單進場條件(SE_Cond)
      // 2. 點差小於設定的平均點差
      if(SE_Cond == true && SP < NormalizeDouble(商品平均點差*Point(),Digits()))
        {
         // 確保當前為空手狀態且不在同一根 K 棒重複進場
         if(多單部位() == 0 && 空單部位() == 0 && BarNumber != OrderBarNo)
           {
            // 確保距離上次出場至少 2 根 K 棒,且當日進場次數少於 1 次
            if(BarSinceExit > 1 && EntriesToday(MagicNumber,Symbol()) < 1)
              {

                  // 執行市價空單進場
                 空單進場單號 = Short_at_MARKET(Symbol(),Lots,TP,SL,"Short Market",MagicNumber) ;
                  
                  // 記錄進場的 K 棒編號
                  OrderBarNo = iBarShift(Symbol(),時間週期,LoadEA);
                 }              
           }
        }
     } // end of 允許交易時段 == true

   //+------------------------------------------------------------------+
   //| 出場邏輯 - 不受交易時段限制,任何時候都可以停損停利                    
   //+------------------------------------------------------------------+
   交易時段外也可停損停利();
   
   // 更新判斷用的 K 棒編號,用於下次迴圈比對
   JudgeNo = iBarShift(Symbol(),時間週期,LoadEA);

  } // 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棒                                                      
//| 功能: 當新 K 棒形成時,更新所有指標、刪除掛單、計算技術指標            
//+------------------------------------------------------------------+
void 換K棒()
  {
     
   // 設定 K 棒數據陣列為序列模式(索引 0 為最新 K 棒)
   Set_OHLC_Bar_Series();
   
   // 設定日線數據陣列為序列模式
   Set_OHLC_Day_Series();
   
   // 取得最近 30 根 K 棒的 OHLC 數據
   Get_OHLC_Bar(30) ;
   
   // 取得最近 15 根日線的 OHLC 數據
   Get_OHLC_Day(15) ;
   
   // 計算 MACD 指標
   set_MACD();
   
   // 計算 CDP 指標(逆勢操作指標)
   set_CDP();
   
   // 計算固定週期的高低點
   FixBarHL();
   
   // 計算 ADX 指標(趨勢強度指標)
   set_ADX();

   // 計算最近 5 日的平均波動範圍
   Day5Range = ((HighD[1] - LowD[1])+(HighD[2] - LowD[2])+(HighD[3] - LowD[3])+(HighD[4] - LowD[4])+(HighD[5] - LowD[5]))/5;
  }

//+------------------------------------------------------------------+
//| 函數: 交易時段賦值                                                  |
//| 功能: 根據當前時間判斷是否在允許交易的時段內                           |
//+------------------------------------------------------------------+
void 交易時段賦值()
  {
   // 交易時段編號 11: 允許在 14:00 到次日 06:00 之間交易
      允許交易時段 = (getTM_hour(TimeCurrent()) >= 14 || getTM_hour(TimeCurrent()) < 6) ;
  }

//+------------------------------------------------------------------+
//| 函數: 交易時段外也可停損停利                                         |
//| 功能: 不論是否在交易時段,都執行停損停利邏輯                            |
//+------------------------------------------------------------------+
void 交易時段外也可停損停利()
  {
   //+------------------------------------------------------------------+
   //| 多單出場邏輯                                                       |
   //+------------------------------------------------------------------+
   if(多單部位() > 0)
     {
      double 多單最小停利 = 0.0;
      bool LX_MinPF = false ;
      
      // 取得多單進場價格
      多單進場價格 = LE_EntryPrice(MagicNumber,多單進場單號);
      
      // 計算多單最小停利價格(進場價 + 3 倍點差)
      多單最小停利 = NormalizeDouble(多單進場價格 + SP*3,Digits()) ;
      
      // 判斷是否達到最小停利
      LX_MinPF = Bid > 多單最小停利 ;
      
      // 計算多單停利價格(進場價 + 停利點數)
      多單停利價格 = NormalizeDouble(多單進場價格 + TP * Point(),Digits()) ;
      
      // 計算多單停損價格(進場價 - 停損點數)
      多單停損價格 = NormalizeDouble(多單進場價格 - SL * Point(),Digits()) ;

      // 1. 達到最小停利且空單進場條件成立(反向訊號)
      // 2. 或價格跌破停損價(前一根收盤在停損價之上,現在跌破)

         LX_Cond = ((LX_MinPF == true && SE_Cond) || (Close[1] >= 多單停損價格 && Bid < 多單停損價格)) ;

      // 執行多單平倉
      // 條件: 出場條件成立 && 不在同一根 K 棒重複平倉 && 持倉至少 1 根 K 棒
      if(LX_Cond == true && BarNumber != CloseOrderNo && LE_BarsSinceEntry(MagicNumber,多單進場單號,時間週期) > 0)
        {
         // 平倉指定單號的多單
         LX_CloseByTicket(多單進場單號,Lots) ;

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

   //+------------------------------------------------------------------+
   //| 空單出場邏輯                                                       |
   //+------------------------------------------------------------------+
   if(空單部位() > 0)
     {
      double 空單最小停利 = 0.0;
      bool SX_MinPF = false ;
      
      // 取得空單進場價格
      空單進場價格 = SE_EntryPrice(MagicNumber,空單進場單號);
      
      // 計算空單最小停利價格(進場價 - 3 倍點差)
      空單最小停利 = NormalizeDouble(空單進場價格 - SP*3,Digits()) ;
      
      // 判斷是否達到最小停利
      SX_MinPF = Ask < 空單最小停利 ;
      
      // 計算空單停利價格(進場價 - 停利點數)
      空單停利價格 = NormalizeDouble(空單進場價格 - TP * Point(),Digits()) ;
      
      // 計算空單停損價格(進場價 + 停損點數)
      空單停損價格 = NormalizeDouble(空單進場價格 + SL * Point(),Digits()) ;

      // 1. 達到最小停利且 MACD DIF 由負轉正(反向訊號)
      // 2. 或價格突破停損價(前一根收盤在停損價之下,現在突破)

         SX_Cond = ((SX_MinPF == true && CrossOver(a_DIF[2],0,a_DIF[1],0)) || (Close[1] <= 空單停損價格 && Ask > 空單停損價格)) ;


      // 執行空單平倉
      // 條件: 出場條件成立 && 不在同一根 K 棒重複平倉 && 持倉至少 1 根 K 棒
      if(SX_Cond == true && BarNumber != CloseOrderNo && SE_BarsSinceEntry(MagicNumber,空單進場單號,時間週期) > 0)
        {
         // 平倉指定單號的空單
         SX_CloseByTicket(空單進場單號,Lots) ;

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

//+------------------------------------------------------------------+
//| 函數: set_BuyCondition                                            |
//| 功能: 設定多單進場條件(根據不同的模組編號使用不同策略)                  |
//+------------------------------------------------------------------+
void set_BuyCondition()
  {

   // 條件: 前一根 K 棒創 10 日新高 && 前一根收盤價高於前二根收盤價
      LE_Cond = (High[1] == Highest_OHLC(Symbol(),時間週期,MODE_HIGH,10,1) && Close[1] > Close[2]) ;
  }

//+------------------------------------------------------------------+
//| 函數: set_ShortCondition                                          |
//| 功能: 設定空單進場條件(根據不同的模組編號使用不同策略)                  |
//+------------------------------------------------------------------+
void set_ShortCondition()
  {

   // 條件: 
   // 1. 收盤價跌破 CDP(逆勢指標的中心點)
   // 2. 收盤價跌破 NL(近低點)
   // 3. ADX 指標上升(趨勢增強)
      SE_Cond = (Close[1] < CDP && Close[1] < NL && a_ADX[1] > a_ADX[2]) ;
  }


//+------------------------------------------------------------------+
//| CDP 指標相關變數                                               
//+------------------------------------------------------------------+
double CDP,AH,NH,NL,AL,CDPrange ;  // CDP 各關鍵價位
bool CrossAH,CrossAL;               // 是否突破最高值/最低值區間

//+------------------------------------------------------------------+
//| 函數: set_CDP                                                   
//| 功能: 計算 CDP 逆勢操作指標                                       
//| 說明: CDP 是根據前一日的高低收計算出的 5 個關鍵價位                  
//+------------------------------------------------------------------+
void set_CDP()
  {
   double Highest2,Lowest2 ;
   
   // CDP = (前日高 + 前日低 + 2*前日收) / 4
   CDP = (HighD[1]+LowD[1]+2*CloseD[1])/4;
   
   // AH(最高值) = CDP + 前日波動
   AH = CDP + (HighD[1] - LowD[1]);
   
   // NH(近高值) = CDP*2 - 前日低
   NH = CDP*2 - LowD[1];
   
   // NL(近低值) = CDP*2 - 前日高
   NL = CDP*2 - HighD[1];
   
   // AL(最低值) = CDP - 前日波動
   AL = CDP - (HighD[1] - LowD[1]);
   
   // CDP 波動範圍
   CDPrange = HighD[1] - LowD[1] ;
   
   // 計算最近 2 根 K 棒的高低點
   Highest2 = Highest_OHLC(Symbol(),時間週期,MODE_HIGH,2,1) ;
   Lowest2 = Lowest_OHLC(Symbol(),時間週期,MODE_LOW,2,1) ;
   
   // 判斷是否在最高值區間震盪(突破 AH 但回落到 NH 以下)
   CrossAH = Highest2 > AH && Lowest2 < NH ;
   回測結果
測試參數交易商品:NAS100(那斯達克指數)
樣本內區間:2019/1/1 ~ 2023/8/31
交易手數:固定 1 手
時間框架:M6 分鐘圖表
交易模式:波段交易
   // 判斷是否在最低值區間震盪(跌破 AL 但反彈到 NL 以上)
   CrossAL = Highest2 > NL && Lowest2 < AL ;
  }



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


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






沒有留言:

張貼留言