2025年12月31日 星期三

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

核心策略概述 (商品 NAS100)
這是一個基於CDP指標和技術分析的日內交易策略,結合多空條件判斷、動態風險管理和嚴格的出場機制。

基礎概念
動態手數計算(基於風險百分比、帳戶餘額與SL),手數限制在0.01~0.3
商品平均點差限制: 進場前檢查點差 < 商品平均點差 * Point()
資金風控門檻: 低於門檻時停止交易並退出EA

交易限制:
每日限制1次進場
點差控制: 進場前檢查點差 < 商品平均點差 × Point()
交易時段限制(交易時段編號33): 00:00-04:00 或 08:00-12:00
BarSinceExit > 1 (平倉後至少間隔1根K棒才能再進場)

進場策略
多單進場條件
LE_Cond = (ArrayMaximum(High) + 1 <= 8)
條件分析:
近期最高價位置條件(具體邏輯需搭配ArrayMaximum函數定義)
必須同時滿足: 空手狀態、點差符合、近期有空單平倉記錄(最近6筆)

進場方式: 市價單買入

空單進場條件
SE_Cond = (CDPrange > 0 && Close[1] < (CDP - CDPrange×0.3) && Close[1] < Open[1])
條件分析:
CDP區間有效(CDPrange > 0)
收盤價跌破CDP下方30%位置
前一根為黑K棒(Close[1] < Open[1])

進場方式: 市價單賣出

出場策略
多單出場
停損停利設定:
停利: H6時間框架3根K棒內的最高價(向前取2根)
停損: 進場價 - SL × Point()

移動停損(啟用時):
當進場後最高價 > 進場價 + TP×0.3 時啟動
停損調整為: 最高價 - SL × Point()

保本機制(啟用時):
觸發條件: 最高價 > 進場價 + TP×0.5
保本價格: 進場價 + TP×0.168
出場條件: 前收在保本價之上,當前價跌破保本價

主要出場條件:
價格突破H6最高價(上穿)
或價格跌破停損價(下穿)

空單出場
停損停利設定:

停利: 進場價 - TP × Point()
停損: 進場價 + SL × Point()
最小停利: 進場價 - 點差×3

移動停損(啟用時):
當進場後最低價 < 進場價 - TP×0.3 時啟動
停損調整為: 最低價 + SL × Point()

保本機制(啟用時):
觸發條件: 最低價 < 進場價 - TP×0.5
保本價格: 進場價 - TP×0.168
出場條件: 前收在保本價之下,當前價突破保本價

主要出場條件:
達到最小停利且RSI低點背離(RSI_LDiv_02)
或價格突破停損價(上穿)

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

ENUM_TIMEFRAMES  時間週期 = PERIOD_H2; // EA 主要運行的時間週期。
ENUM_TIMEFRAMES  時間框架 = PERIOD_D1; // 相對大週期,可能用於趨勢判斷或過濾

//+------------------------------------------------------------------+
//| EA 初始化函數,當 EA 載入圖表時執行一次                             |
//+------------------------------------------------------------------+
int OnInit()
  {
   LoadEA = TimeCurrent(); // 記錄 EA 啟動時的當前伺服器時間
   return(INIT_SUCCEEDED); // 初始化成功,返回成功代碼
  }

//+------------------------------------------------------------------+
//| EA 核心循環函數,每一跳報價 (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) ; // 空單平倉
      ExpertRemove(); // 將 EA 從圖表中移除
      */
      
      return; // 結束本次 OnTick
     }

   // --- 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) ; // 立即平倉
      CloseOrderNo =  iBarShift(Symbol(),時間週期,LoadEA); // 記錄平倉 K 棒位置
      FastSma = MathMin(LenA1, LenB1); // 設定快線參數
      SlowSma = MathMax(LenA1, LenB1); // 設定慢線參數
     }

   // 如果進入了新的 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); // Tick 價值
   SP = NormalizeDouble(MathAbs(Ask-Bid),Digits()) ; // 計算當前點差 (Spread)

   // --- 手數計算邏輯 ---
   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(); // 呼叫多單條件判斷模組
      // 如果符合多單條件、點差小於平均值且最近 6 根 K 棒內有空單平倉紀錄
      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)
              {

        多單進場單號 = 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 棒編號

              }
           }
        }
     } // 結束允許交易時段

   // 無論是否在交易時段內,都要處理持倉單的停損停利監控
   交易時段外也可停損停利();
   JudgeNo = iBarShift(Symbol(),時間週期,LoadEA); // 更新 JudgeNo 為當前 K 棒編號

  } // 結束 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 棒產生時,刪除所有未成交的掛單
   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_CDP();  // 計算 CDP 指標(逆勢操作指標)
   FixBarHL(); // 修正 K 棒高低價數據

   // 計算過去 5 天的平均震盪區間 (Average Range)
   Day5Range = ((HighD[1] - LowD[1])+(HighD[2] - LowD[2])+(HighD[3] - LowD[3])+(HighD[4] - LowD[4])+(HighD[5] - LowD[5]))/5;
  }

//+------------------------------------------------------------------+
//| 定義允許交易的時間段                                               |
//+------------------------------------------------------------------+
void 交易時段賦值()
  {
   // 僅允許 00:00-03:59 與 08:00-11:59 進行交易
      允許交易時段 = (getTM_hour(TimeCurrent()) >= 0 && getTM_hour(TimeCurrent()) < 4) || (getTM_hour(TimeCurrent()) >= 8 && getTM_hour(TimeCurrent()) < 12);
  }

//+------------------------------------------------------------------+
//| 管理持倉部位的停損、停利、移動停損及保本邏輯                           |
//+------------------------------------------------------------------+
void 交易時段外也可停損停利()
  {
   // --- 多單管理 ---
   if(多單部位() > 0)
     {
      double 多單最小停利 = 0.0, 多單保本價格 = 0.0 ;
      bool LX_MinPF = false, LongPull = false ;
      
      多單進場價格 = LE_EntryPrice(MagicNumber,多單進場單號); // 獲取多單實際成交價
      多單最小停利 = NormalizeDouble(多單進場價格 + SP*3,Digits()) ; // 設定最小利潤點(3倍點差)
      LX_MinPF = Bid > 多單最小停利 ; // 檢查是否已達到最小利潤
      
      多單停利價格 = NormalizeDouble(多單進場價格 + TP * Point(),Digits()) ;
      多單停損價格 = NormalizeDouble(多單進場價格 - SL * Point(),Digits()) ;

      // 多單移動停損:若價格創高且利潤超過 TP 的 30%,則將停損上移
      if(移動停損利 == true)
        {
         多單最高價 = iHigh(Symbol(),時間週期, iHighest(Symbol(),時間週期,MODE_HIGH,LE_BarsSinceEntry(MagicNumber,多單進場單號,時間週期),1));
         多單最高價 = NormalizeDouble(多單最高價,Digits()) ;

         if(多單最高價 > (多單進場價格+TP * Point()*0.3))
            多單停損價格 = NormalizeDouble(多單最高價 - SL * Point(),Digits()) ;
        }
        
      // 多單保本 (保藍):若利潤超過 TP 的 50%,設定保本價為利潤的 16.8%,破位平倉
      if(保藍設定 == true)
        {
         if(多單最高價 > 多單進場價格+TP * Point()*0.5)
           {
            LongPull = true ;
            多單保本價格 = NormalizeDouble(多單進場價格+TP * Point()*0.168,Digits()) ;
            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) ;
           }
        }

      // 使用 6 小時高點作為停利參考

         bool LXcond291 ;
         多單停利價格 = NormalizeDouble(get_HRangeOHLC(Symbol(),PERIOD_H6,MODE_HIGH,3,2),Digits()) ;
         LXcond291 = (多單進場價格 < 多單停利價格 && Close[1] <= 多單停利價格 && Bid > 多單停利價格) ;
         LX_Cond = (LXcond291 == true || (Close[1] >= 多單停損價格 && Bid < 多單停損價格)) ;

      // 最終多單平倉執行
      if(LX_Cond == true && BarNumber != CloseOrderNo && LE_BarsSinceEntry(MagicNumber,多單進場單號,時間週期) > 0)
        {
         LX_CloseByTicket(多單進場單號,Lots) ;
         if(多單部位() == 0) CloseOrderNo = iBarShift(Symbol(),時間週期,LoadEA) ;
        }
     } // 結束多單部位管理

   // --- 空單管理 ---
   if(空單部位() > 0)
     {
      double 空單最小停利 = 0.0, 空單保本價格 = 0.0 ;
      bool SX_MinPF = false, ShortPull = false ;
      
      空單進場價格 = SE_EntryPrice(MagicNumber,空單進場單號);
      空單最小停利 = NormalizeDouble(空單進場價格 - SP*3,Digits()) ;
      SX_MinPF = Ask < 空單最小停利 ;
      
      空單停利價格 = NormalizeDouble(空單進場價格 - TP * Point(),Digits()) ;
      空單停損價格 = NormalizeDouble(空單進場價格 + SL * Point(),Digits()) ;

      // 空單移動停損:若價格創低且利潤超過 TP 的 30%,則將停損下移
      if(移動停損利 == true)
        {
         空單最低價 = iLow(Symbol(),時間週期, iLowest(Symbol(),時間週期,MODE_LOW,SE_BarsSinceEntry(MagicNumber,空單進場單號,時間週期),1));
         空單最低價 = NormalizeDouble(空單最低價,Digits()) ;
         if(空單最低價 < (空單進場價格-TP * Point()*0.3))
            空單停損價格 = NormalizeDouble(空單最低價 + SL * Point(),Digits()) ;
        }

      // 空單保本 (保藍):邏輯同多單,保護已獲得的利潤
      if(保藍設定 == true)
        {
         if(空單最低價 < 空單進場價格-TP * Point()*0.5)
           {
            ShortPull = true ;
            空單保本價格 = NormalizeDouble(空單進場價格-TP * Point()*0.168,Digits()) ;
            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) ;
           }
        }

      // 結合 RSI 背離進行平倉

         SX_Cond = ((SX_MinPF == true && (RSI_LDiv_02() == true)) || (Close[1] <= 空單停損價格 && Ask > 空單停損價格)) ;
        
      // 最終空單平倉執行
      if(SX_Cond == true && BarNumber != CloseOrderNo && SE_BarsSinceEntry(MagicNumber,空單進場單號,時間週期) > 0)
        {
         SX_CloseByTicket(空單進場單號,Lots) ;
         if(空單部位() == 0) CloseOrderNo = iBarShift(Symbol(),時間週期,LoadEA) ;
        }
     } // 結束空單部位管理
  } // 結束 交易時段外也可停損停利()

//+------------------------------------------------------------------+
//| 多單進場條件模組:定義不同編號的進場策略                            |
//+------------------------------------------------------------------+
void set_BuyCondition()
  {
   // 判斷最高價陣列的索引位置,屬於特定的技術形態過濾
      LE_Cond = (ArrayMaximum(High) + 1 <= 8) ;
  }

//+------------------------------------------------------------------+
//| 空單進場條件模組:定義不同編號的進場策略                            |
//+------------------------------------------------------------------+
void set_ShortCondition()
  {
   // 跌破 CDP 指標下方區間 30% 且前一根為黑 K,視為跌勢確認
      SE_Cond = (CDPrange > 0 && Close[1] < (CDP - CDPrange*0.3) && Close[1] < Open[1]) ; 
  }

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


沒有留言:

張貼留言