2026年3月9日 星期一

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

核心策略概述(商品 XAUUSD)

核心策略:
多單:布林通道上軌突破(Buy_at_MARKET 市價單)
空單:三黑K型態 + ADX 趨勢增強(Short_at_MARKET 市價單)

交易限制:
時段:08:00 - 次日 04:00
每日限 2 次進場
同根 K 棒不重複下單(BarNumber != OrderBarNo)
同根 K 棒不於平倉後立即進場(BarNumber != CloseOrderNo)
點差 < 商品平均點差 × Point()

進場條件:
多單:UpBreak==true && Close[1]>a_BBUP[1] && Close[1]>Open[1]
空單:Black3Bar_LClose() && a_ADX[1]>a_ADX[2] && Volume[1]>Volume[2]

進場方式: 兩者皆為市價單直接成交

出場條件:
多單:出現空單信號(SE_Cond) 或 觸及停利 或 觸及停損
空單:出現多單信號(LE_Cond) 或 觸及停利 或 觸及停損
停利擴大為 3 倍 TP,讓利潤有更大成長空間

初始停損停利: 進場當根 K 棒立即推送至券商端
換K棒更新指標: ATR、BBAND、ADX、Volatility、OHLC 各週期數據、Day5Range

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

ENUM_TIMEFRAMES  時間週期 = PERIOD_H2; // EA 主要運行的時間週期。
ENUM_TIMEFRAMES  時間框架 = PERIOD_D1; // 相對大週期,可能用於趨勢判斷或過濾
//+------------------------------------------------------------------+
//| EA 初始化函數,當 EA 載入圖表時執行一次                             |
//+------------------------------------------------------------------+
int OnInit()
  {
   LoadEA = TimeCurrent(); // 記錄 EA 啟動時的當前伺服器時間
   return(INIT_SUCCEEDED); // 初始化成功,返回成功代碼
  }

//+------------------------------------------------------------------+
//| 報價驅動函數:每當價格跳動(Tick)時即執行一次                         |
//+------------------------------------------------------------------+
void OnTick()
  {
   // 資金風控檢查:若帳戶餘額低於設定的「資金風控」值,則停止執行並報警
   if(AccountInfoDouble(ACCOUNT_BALANCE) < 資金風控)
     {
      Alert("**********  資金不足 *************");

      // 執行完整清倉程序:刪除掛單 → 平倉部位 → 移除 EA

      // 第一步:刪除所有未成交的掛單
      if(total_pending_order_count(Symbol(), MagicNumber,-1) != 0)
        {
         delete_pending_orders_all(Symbol(), MagicNumber, -1, 0x0000ff);
        }
      // 第二步:平倉所有持倉部位
      if(多單部位() > 0 && BarNumber != CloseOrderNo)
         LX_CloseByTicket(多單進場單號,Lots) ;
      if(空單部位() > 0 && BarNumber != CloseOrderNo)
         SX_CloseByTicket(空單進場單號,Lots) ;
      // 第三步:強制移除 EA,停止自動交易
      ExpertRemove();

      return;
     }

   // 計算當前 K 棒相對於 EA 啟動時間 (LoadEA) 的位置編號
   BarNumber = iBarShift(Symbol(),時間週期,LoadEA);
   // 計算自上次平倉後經過了多少根 K 棒
   BarSinceExit = BarNumber - CloseOrderNo;

   // 特殊邏輯:啟動後第一根 K 棒時,直接將 CloseOrderNo 設為 -1
   // 不執行測試性開倉,避免不必要的交易紀錄干擾初始狀態
   if((BarNumber == 1 && BarNumber != JudgeNo))
     {
      CloseOrderNo = -1;  // 直接設定,不開倉
     }

   // 換 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()) ;             // 計算當前點差(Ask-Bid 的絕對值)

//+------------------------------------------------------------------+
//| 計算交易手數:根據風險比例動態調整或使用固定手數                       |
//+------------------------------------------------------------------+
   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)
     {
      //+------------------------------------------------------------------+
      //| 多單進場邏輯區塊                                                  |
      //+------------------------------------------------------------------+

      // 確保止損點數不低於最小止損要求,避免滑價或點差過大導致無效止損
      SL = MathMax(SL,最小止損)  ;

      set_BuyCondition(); // 載入多單過濾條件,計算 LE_Cond
      // 判斷條件:符合進場信號、且點差小於商品平均點差
      if(LE_Cond == true && SP < NormalizeDouble(商品平均點差*Point(),Digits()))
        {
         // 確保當前無多空部位、且同一根 K 棒內不重複下單、且非剛平倉的同一根 K 棒
         if(多單部位() == 0 && 空單部位() == 0 && BarNumber != OrderBarNo && BarNumber != CloseOrderNo)
           {
            // 每日進場次數限制:今日下單次數需小於 2 次
            if(EntriesToday(MagicNumber,Symbol()) < 2)
              {
               if(多單下單方式 == 1) // 使用市價單直接買入
                 {
                  // 空手狀態下執行市價買入,立即成交
                  多單進場單號 = Buy_at_MARKET(Symbol(),Lots,TP,SL,"BUY MARKET",MagicNumber) ;
                 }
              } // end of EntriesToday
           } // end of 空手且不同根K棒() == true
        } // end of LE_Cond == true

      //+------------------------------------------------------------------+
      //| 空單進場邏輯區塊                                                  |
      //+------------------------------------------------------------------+
      set_ShortCondition() ; // 載入空單過濾條件,計算 SE_Cond
      // 判斷條件:符合進場信號、且點差在合理範圍內
      if(SE_Cond == true && SP < NormalizeDouble(商品平均點差*Point(),Digits()))
        {
         // 確保當前無多空部位、且同一根 K 棒內不重複下單、且非剛平倉的同一根 K 棒
         if(多單部位() == 0 && 空單部位() == 0 && BarNumber != OrderBarNo && BarNumber != CloseOrderNo)
           {
            // 每日進場次數限制:今日下單次數需小於 2 次
            if(EntriesToday(MagicNumber,Symbol()) < 2)
              {
               if(空單下單方式 == 1) // 使用市價單直接賣出
                 {
                  // 空手狀態下執行市價賣出,立即成交
                  空單進場單號 = Short_at_MARKET(Symbol(),Lots,TP,SL,"Short Market",MagicNumber) ;
                 }
              } //end of EntriesToday
           } // end of 空手且不同根K棒() == true
        } // end of SE_Cond == true
     } //end of 允許交易時段 == true

   // 更新判斷編號,供下一個 Tick 判斷是否已換 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 棒時的處理程序                                         |
//+------------------------------------------------------------------+
void 換K棒()
  {
   // 於商品收盤時間前刪除所有掛單,避免隔夜掛單殘留造成非預期成交
   if((getTM_hour(TimeCurrent()) == 商品收盤時間) && (total_pending_order_count(Symbol(), MagicNumber,-1) != 0))
     {
      delete_pending_orders_all(Symbol(), MagicNumber, -1, 0x0000ff);
     }

   // 更新 K 線數據數組(Bar 級別與 Day 級別)
   Set_OHLC_Bar_Series(); // 保留:設定 Bar 層級 OHLC 陣列
   Set_OHLC_Day_Series(); // 保留:設定 Day 層級 OHLC 陣列
   Get_OHLC_Bar(30) ;     // 保留:取得最近 30 根 K 棒數據
   Get_OHLC_BigBar(15) ;  // 保留:取得最近 15 根大週期 K 棒數據
   Get_OHLC_Day(15) ;     // 保留:取得最近 15 天數據
   set_BarInfo();          // 保留:設定 K 棒相關資訊
   set_ATR();              // 保留:計算 ATR 指標(平均真實波幅)
   Get_Volatility() ;      // 保留:取得波動率數據
   
   // 計算技術指標
   set_BBAND(); // 計算布林通道(Bollinger Bands)
   set_ADX();   // 計算 ADX 趨勢強度指標(Average Directional Index)

   // 計算最近 5 天的平均波動範圍 (Daily Range),用於動態停利計算
   Day5Range = ((HighD[1] - LowD[1])+(HighD[2] - LowD[2])+(HighD[3] - LowD[3])+(HighD[4] - LowD[4])+(HighD[5] - LowD[5]))/5;
  }

//+------------------------------------------------------------------+
//| 自訂函數:設定允許交易的時段                                         |
//+------------------------------------------------------------------+
void 交易時段賦值()
  {
   // 允許交易時段為上午 8:00 至隔日凌晨 4:00(跨日時段)
      允許交易時段 = (getTM_hour(TimeCurrent()) >= 8 || (getTM_hour(TimeCurrent()) >= 0  && getTM_hour(TimeCurrent()) < 4));
  }

//+------------------------------------------------------------------+
//| 自訂函數:核心監控 - 處理停損、停利(交易時段外亦持續執行)              |
//+------------------------------------------------------------------+
void 交易時段外也可停損停利()
  {
   // --- 多單監控區 ---
   // 無論是否在交易時段,只要持有多單就持續監控
   if(多單部位() > 0)
     {
      //+------------------------------------------------------------------+
      //| 多單出場邏輯                                                      |
      //+------------------------------------------------------------------+

      // 取得多單進場價格,並計算最小利潤門檻(需覆蓋 3 倍點差)
      多單進場價格 = LE_EntryPrice(MagicNumber, 多單進場單號);

      // 計算進場後至今出現的最高價(用於移動停損基準)
      多單最高價 = iHigh(Symbol(), 時間週期, iHighest(Symbol(), 時間週期, MODE_HIGH, LE_BarsSinceEntry(MagicNumber, 多單進場單號, 時間週期), 1));
      多單最高價 = NormalizeDouble(多單最高價, Digits());

      //--- 進場當根 K 棒:設定初始停損停利至券商端,確保保護機制立即生效
      if(LE_BarsSinceEntry(MagicNumber, 多單進場單號, 時間週期) == 0)
        {
         double 初始停利 = NormalizeDouble(多單進場價格 + TP * Point(), Digits());
         double 初始停損 = NormalizeDouble(多單進場價格 - SL * Point(), Digits());
         // 將停損停利推送至券商端(伺服器端保護)
         TPSLchange(Symbol(), 多單進場單號, 初始停利, 初始停損, 時間週期);
        }

      // 從券商端讀取當前實際的 TP/SL,確保與伺服器數據同步
      if(PositionSelectByTicket(多單進場單號))
        {
         多單停利價格 = PositionGetDouble(POSITION_TP);
         多單停損價格 = PositionGetDouble(POSITION_SL);
        }

      //+------------------------------------------------------------------+
      //| 出場方式計算                                                      |
      //+------------------------------------------------------------------+
      //配合反向訊號出場(持倉至對立信號出現時才離場)

         // 擴大停利至 3 倍 TP,讓利潤有更大的成長空間
         double 新停利 = NormalizeDouble(多單進場價格 + (TP * 3) * Point(), Digits());
         // 只在新停利高於當前停利時才更新,保持停利只升不降
         if(新停利 > 多單停利價格)
            TPSLchange(Symbol(), 多單進場單號, 多單停利價格, 0, 時間週期);

         // 出場條件:出現空單信號(反向進場) 或 觸及停利 或 觸及停損
         LX_Cond = (SE_Cond || Bid >= 多單停利價格 || (Bid <= 多單停損價格));


      //+------------------------------------------------------------------+
      //| 多單出場執行                                                      |
      //+------------------------------------------------------------------+
      // 確認出場條件成立、不在下單同根 K 棒、不在平倉同根 K 棒、且已持倉超過 0 根 K 棒
      if(LX_Cond == true && BarNumber != OrderBarNo && BarNumber != CloseOrderNo &&
         LE_BarsSinceEntry(MagicNumber, 多單進場單號, 時間週期) > 0)
        {
         LX_CloseByTicket(多單進場單號, Lots); // 依票號平倉多單
        }

     } // end of 多單部位() > 0

   // --- 空單監控區 ---
   // 無論是否在交易時段,只要持有空單就持續監控
   if(空單部位() > 0)
     {
      //+------------------------------------------------------------------+
      //| 空單出場邏輯                                                      |
      //+------------------------------------------------------------------+

      // 取得空單進場價格,並計算最小利潤門檻(需覆蓋 3 倍點差)
      空單進場價格 = SE_EntryPrice(MagicNumber, 空單進場單號);

      // 計算進場後至今出現的最低價(用於移動停損基準)
      空單最低價 = iLow(Symbol(), 時間週期, iLowest(Symbol(), 時間週期, MODE_LOW, SE_BarsSinceEntry(MagicNumber, 空單進場單號, 時間週期), 1));
      空單最低價 = NormalizeDouble(空單最低價, Digits());

      //--- 進場當根 K 棒:設定初始停損停利至券商端,確保保護機制立即生效
      if(SE_BarsSinceEntry(MagicNumber, 空單進場單號, 時間週期) == 0)
        {
         double 初始停利 = NormalizeDouble(空單進場價格 - TP * Point(), Digits());
         double 初始停損 = NormalizeDouble(空單進場價格 + SL * Point(), Digits());
         // 將停損停利推送至券商端(伺服器端保護)
         TPSLchange(Symbol(), 空單進場單號, 初始停利, 初始停損, 時間週期);
        }

      // 從券商端讀取當前實際的 TP/SL,確保與伺服器數據同步
      if(PositionSelectByTicket(空單進場單號))
        {
         空單停利價格 = PositionGetDouble(POSITION_TP);
         空單停損價格 = PositionGetDouble(POSITION_SL);
        }

      //+------------------------------------------------------------------+
      //| 出場方式計算                                                      |
      //+------------------------------------------------------------------+
      //配合反向訊號出場(持倉至對立信號出現時才離場)

         // 擴大停利至 3 倍 TP,讓利潤有更大的成長空間
         double 新停利 = NormalizeDouble(空單進場價格 - (TP * 3) * Point(), Digits());
         // 只在新停利低於當前停利時才更新,保持停利只降不升
         if(新停利 < 空單停利價格)
            TPSLchange(Symbol(), 空單進場單號, 空單停利價格, 0, 時間週期);

         // 出場條件:出現多單信號(反向進場) 或 觸及停利 或 觸及停損
         SX_Cond = (LE_Cond || Ask <= 空單停利價格 || (Ask >= 空單停損價格));

      //+------------------------------------------------------------------+
      //| 空單出場執行                                                   
      //+------------------------------------------------------------------+
      // 確認出場條件成立、不在下單同根 K 棒、不在平倉同根 K 棒、且已持倉超過 0 根 K 棒
      if(SX_Cond == true && BarNumber != OrderBarNo && BarNumber != CloseOrderNo &&
         SE_BarsSinceEntry(MagicNumber, 空單進場單號, 時間週期) > 0)
        {
         SX_CloseByTicket(空單進場單號, Lots); // 依票號平倉空單
        }

     } // end of 空單部位() > 0
  } // end of 交易時段外也可停損停利()


//+------------------------------------------------------------------+
//| 多單策略模組:定義買入信號            
//+------------------------------------------------------------------+
void set_BuyCondition()
  {
   // 布林通道突破 + K 棒型態確認
   // 條件:
   // (1) UpBreak 為真(價格向上突破關鍵區間)
   // (2) 收盤價高於布林通道上軌(確認上方動能強勁)
   // (3) 收盤價高於開盤價(紅 K 棒,多頭型態確認)
   // 適用於強勢突破策略,三重條件過濾確保進場品質

      LE_Cond = (UpBreak == true) && Close[1] > a_BBUP[1] && Close[1] > Open[1] ;
  }

//+------------------------------------------------------------------+
//| 空單策略模組:定義賣出信號                  
//+------------------------------------------------------------------+
void set_ShortCondition()
  {
   // 三黑K型態 + ADX 趨勢增強 + 成交量放大確認
   // 條件:
   // (1) Black3Bar_LClose():出現三根連續收低的黑 K 棒(下跌動能持續)
   // (2) a_ADX[1] > a_ADX[2]:ADX 趨勢強度持續上升(趨勢加速中)
   // (3) Volume[1] > Volume[2]:成交量放大(下跌有量支撐,賣壓增強)
   // 適用於趨勢延續放空策略,確認下跌趨勢有量能配合才進場

      SE_Cond = Black3Bar_LClose() && a_ADX[1] > a_ADX[2] && Volume[1] > Volume[2] ;
  }
回測結果
測試參數交易商品:XAUUSD(黃金)
樣本內區間:2019/1/1 ~ 2024/06/30
交易手數:固定 0.1 手
時間框架: H2圖表
交易模式:波段交易



沒有留言:

張貼留言