2025年12月19日 星期五

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

核心策略概述 (商品 NAS100)
這是一個日內型態與背離交易策略。策略透過價格型態識別(多單)與CCI背離確認(空單)來捕捉交易機會,搭配嚴格的風險管理與時段控制,專注於單次高勝率進場。

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

交易限制:
每日限制1次進場(EntriesToday < 1)
出場後須間隔1根K棒以上(BarSinceExit > 1)
交易時段: 僅允許 00:00~16:00 交易

進出場策略
多單進場條件
LE_Cond = (High3Bar_LClose() == false && Low[1] < Low[2])
條件分析:
非高點破頂急跌: High3Bar_LClose() == false —— 排除特定高點型態後的急跌走勢
低點下破: Low[1] < Low[2] —— 前一根K棒低點低於前兩根K棒,確認價格下探進場方式:市價單買入
僅在無部位、點差小於限制、今日未進場、BarSinceExit > 1 時執行
額外條件: 最近6筆內有空單平倉(最近幾筆內有空單平倉() == true)

空單進場條件
SE_Cond = (CCI_LDiv_02() && BigMin3L < a_BigAvg13[1])
條件分析:
CCI低檔背離02: CCI_LDiv_02() —— CCI指標出現低檔背離型態(價格創新低但CCI未創新低)
三線最低值低於13線: BigMin3L < a_BigAvg13[1] —— 大週期三線指標的最低值低於13期均線,確認弱勢環境

進場方式:市價單賣出
僅在無部位、點差小於限制、今日未進場、BarSinceExit > 1 時執行

出場機制
多單出場
LX_Cond = ((LX_MinPF && RSI高檔鈍化()) || (Close[1] >= 多單停損價格 && Bid < 多單停損價格))
出場條件:
最小獲利+RSI鈍化: 獲利達 SP*3 且RSI出現高檔鈍化
停損觸發: 前一根收盤價突破停損價,但當前Bid價回落至停損價下方

停損停利設定:
停利價: 進場價 + TP * Point()
停損價: 進場價 - SL * Point()

移動停損 (若 移動停損利 == true):
追蹤多單最高價(進場後所有K棒的最高點)
當獲利達TP的30%以上時,停損調整為: 多單最高價 - SL * Point()

保本出場 (若 保藍設定 == true):
當獲利達TP的50%時啟動保本機制
保本價: 進場價 + TP * 0.168
若前一根收盤價高於保本價,但當前Bid低於保本價,則平倉

空單出場
SX_Cond = ((Close[1] >= 空單停利價格 && Ask < 空單停利價格) || (Close[1] <= 空單停損價格 && Ask > 空單停損價格))
出場條件:
停利觸發: 前一根收盤價突破停利價,但當前Ask回落至停利價上方
停損觸發: 前一根收盤價跌破停損價,但當前Ask回升至停損價下方

停損停利設定:
動態停利價: 進場價 - (TP + Day5Range) * Point() —— 結合近5日平均日內波動範圍
停損價: 進場價 + SL * Point()

移動停損 (若 移動停損利 == true):
追蹤空單最低價(進場後所有K棒的最低點)
當獲利達TP的30%以上時,停損調整為: 空單最低價 + SL * Point()

保本出場 (若 保藍設定 == true):
當獲利達TP的50%時啟動保本機制
保本價: 進場價 - TP * 0.168
若前一根收盤價低於保本價,但當前Ask高於保本價,則平倉

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

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

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

//+------------------------------------------------------------------+
//| OnTick 函式 (每次報價變動時執行)                                                          
//+------------------------------------------------------------------+
int OnInit()
  {
   // 記錄 EA 加載時的當前時間,用於後續計算 K 棒偏移量
   LoadEA = TimeCurrent();
   return(INIT_SUCCEEDED); // 返回初始化成功
  }

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

   // 計算當前 K 棒相對於 EA 啟動時間 (LoadEA) 的位置編號
   BarNumber = iBarShift(Symbol(),時間週期,LoadEA);
   // 計算自上次平倉後經過了多少根 K 棒
   BarSinceExit = BarNumber-CloseOrderNo ;
   
   // 特殊邏輯:當處於啟動後的第一根 K 棒且尚未標記過 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 棒編號
      CloseOrderNo =  iBarShift(Symbol(),時間週期,LoadEA);
      // 初始化快慢均線參數,確保 LenA1 為較小值,LenB1 為較大值
      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); // 取得跳點價值
   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)
     {
      //+------------------------------------------------------------------+
      //| 多單進場邏輯區塊                                                  |
      //+------------------------------------------------------------------+
      set_BuyCondition(); // 載入多單過濾條件
      // 判斷條件:符合進場信號、點差小於平均值、且最近 6 筆成交內有空單平倉紀錄(特定的策略慣性)
      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 棒
              } // end of BarSinceExit > 1
           } // end of 空手檢查
        } // end of LE_Cond

      //+------------------------------------------------------------------+
      //| 空單進場邏輯區塊                                                  |
      //+------------------------------------------------------------------+
      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);
              } //end of BarSinceExit > 1
           } // end of 空手檢查
        } // end of SE_Cond
     } //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棒()
  {
   // 每逢新 K 棒,清除該 MagicNumber 下的所有未成交掛單
   if(total_pending_order_count(Symbol(), MagicNumber,-1) != 0)
     {
      delete_pending_orders_all(Symbol(), MagicNumber, -1, 0x0000ff);
     }
   
   // 更新 K 線數據數組(Open, High, Low, Close)
   Set_OHLC_Bar_Series(); 
   Set_OHLC_Day_Series(); 
   Get_OHLC_Bar(30) ; // 獲取最近 30 根 K 棒數據
   Get_OHLC_BigBar(15) ; // 獲取較大時區的數據
   Get_OHLC_Day(15) ; // 獲取最近 15 天數據

   // 重新計算技術指標
   set_RSI(); // RSI 指標
   set_BigThreeLine(); // 三線指標
   FixBarHL(); // 修正 K 棒高低點數據

   // 計算最近 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 交易時段賦值()
  {
   // 僅允許在 00:00 到 16:00 之間進場
      允許交易時段 = (getTM_hour(TimeCurrent()) >= 0 && getTM_hour(TimeCurrent()) < 16);
  }

//+------------------------------------------------------------------+
//| 自訂函數:核心監控 - 處理停損、停利、移動止損與保本                     |
//+------------------------------------------------------------------+
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()) ;

      // 移動停損 (Trailing Stop) 邏輯
      if(移動停損利 == true)
        {
         // 找出進場後出現的最高價
         多單最高價 = iHigh(Symbol(),時間週期, iHighest(Symbol(),時間週期,MODE_HIGH,LE_BarsSinceEntry(MagicNumber,多單進場單號,時間週期),1));
         多單最高價 = NormalizeDouble(多單最高價,Digits()) ;

         // 若目前獲利已超過 TP 的 30%,則將停損線向上拉升 (跟隨最高價回撤 SL 點位)
         if(多單最高價 > (多單進場價格+TP * Point()*0.3))
            多單停損價格 = NormalizeDouble(多單最高價 - SL * Point(),Digits()) ;
        }

      // 保本機制 (保藍設定:確保獲利後不再虧損)
      if(保藍設定 == true)
        {
         // 若獲利超過 TP 的 50%,啟動保本
         if(多單最高價 > 多單進場價格+TP * Point()*0.5)
           {
            LongPull = true ;
            // 將保護價格設在獲利 16.8% 的位置 (確保利潤鎖定)
            多單保本價格 = 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) ;
           }
        }

      // 特殊多單出場模式 (RSI 鈍化出場)

         LX_Cond = ((LX_MinPF == true && (RSI高檔鈍化() == true)) || (Close[1] >= 多單停損價格 && Bid < 多單停損價格)) ;

      // 執行最終多單出場判斷
      if(LX_Cond == true && BarNumber != CloseOrderNo && LE_BarsSinceEntry(MagicNumber,多單進場單號,時間週期) > 0)
        {
         LX_CloseByTicket(多單進場單號,Lots) ;
         if(多單部位() == 0) CloseOrderNo = iBarShift(Symbol(),時間週期,LoadEA) ;
        }
     } // end of 多單監控

   // --- 空單監控區 ---
   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()) ;

      // 空單移動停損
      if(移動停損利 == true)
        {
         // 找出進場後出現的最低價
         空單最低價 = iLow(Symbol(),時間週期, iLowest(Symbol(),時間週期,MODE_LOW,SE_BarsSinceEntry(MagicNumber,空單進場單號,時間週期),1));
         空單最低價 = NormalizeDouble(空單最低價,Digits()) ;
         // 若獲利超過 30%,停損線下移
         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) ;
           }
        }

      // 特殊空單出場模式  (結合 5 日平均波幅 TP)

         空單停利價格 = NormalizeDouble(空單進場價格 - (TP+Day5Range) * Point(),Digits()) ;
         SX_Cond = ((Close[1] >= 空單停利價格 && Ask < 空單停利價格) || (Close[1] <= 空單停損價格 && Ask > 空單停損價格)) ;

      // 執行最終空單出場判斷
      if(SX_Cond == true && BarNumber != CloseOrderNo && SE_BarsSinceEntry(MagicNumber,空單進場單號,時間週期) > 0)
        {
         SX_CloseByTicket(空單進場單號,Lots) ;
         if(空單部位() == 0) CloseOrderNo = iBarShift(Symbol(),時間週期,LoadEA) ;
        }
     } // end of 空單監控
  } // end of 停損停利監控函數

//+------------------------------------------------------------------+
//| 多單策略模組:定義買入信號                                           |
//+------------------------------------------------------------------+
void set_BuyCondition()
  {
   // 檢查非特定急跌型態,且前一根 K 棒低點低於再前一根(找尋反轉或突破)
      LE_Cond = (High3Bar_LClose() == false && Low[1] < Low[2]) ;  
  }

//+------------------------------------------------------------------+
//| 空單策略模組:定義賣出信號                                           |
//+------------------------------------------------------------------+
void set_ShortCondition()
  {
   // CCI 指標低檔背離且價格三線最小值低於 13 週期均線
      SE_Cond = (CCI_LDiv_02() && BigMin3L < a_BigAvg13[1]) ; 
  }

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



沒有留言:

張貼留言