easytrader788 發表於 26-1-9 08:13

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

//+------------------------------------------------------------------+
//| 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 - LowD)+(HighD - LowD)+(HighD - LowD)+(HighD - LowD)+(HighD - LowD))/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 >= 多單保本價格 && 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 <= 多單停利價格 && Bid > 多單停利價格) ;
         LX_Cond = (LXcond291 == true || (Close >= 多單停損價格 && 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 <= 空單保本價格 && 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 <= 空單停損價格 && 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 < (CDP - CDPrange*0.3) && Close < Open) ;
}

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

easytrader788 發表於 26-1-9 08:15


MT5 EA交易策略開發教學
頁: [1]
查看完整版本: MT5 EA交易策略開發教學[24]