2025年10月8日 星期三

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

核心策略概述 (交易商品 NAS100 )
這是一個基於FVG(Fair Value Gap,公允價值缺口)突破與布林帶收縮的趨勢跟隨策略。策略使用MACD背離和布林帶突破來識別進出場機會,並採用當沖模式在特定時間強制平倉。

多單策略
多單進場條件
MACD OSC上升: a_OSC[1] > a_OSC[2](動能增強)
FVG突破: Close[1] > FVG_多頭上緣價格(價格突破公允價值缺口上緣)
連續創高: High[1] > High[2] 且 High[1] > High[3](確認上漲趨勢)

進場方式:市價單買入(Buy_at_MARKET)
僅在無部位、點差合理、今日未進場、BarSinceExit > 1 時執行

多單出場條件
正常出場: 達到最小停利(3倍點差)且發生MACD高點背離
MACD高點背離判斷:
價格高點在最近2根K棒內(ArrayMaximum(High) <= 2)
MACD DIF最大值在更早前(ArrayMaximum(a_DIF) > 2)
RSI < 70(避免超買區)

停損出場: K棒收盤突破停損價,且當前價格已跌破停損價
空單策略
空單進場條件
條件分析:
布林帶收縮: Shrink == true(波動率降低,蓄勢待發)
向下突破下軌: Low[1] < a_BBDN[1](價格突破布林帶下軌)
黑K確認: Close[1] < Open[1](收盤低於開盤,空方力量確認)

進場方式市價單賣出(Short_at_MARKET)
僅在無部位、點差合理、今日未進場、BarSinceExit > 1 時執行

空單出場條件
條件分析:
正常出場: 達到最小停利(3倍點差)且出現多單進場訊號(LE_Cond)
停損出場: K棒收盤突破停損價,且當前價格已漲破停損價


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

ENUM_TIMEFRAMES 時間週期 = PERIOD_H1; // 主要分析時間框架(1小時) ENUM_TIMEFRAMES 時間框架 = PERIOD_D1; // 相對大週期(日線)

//+------------------------------------------------------------------+
//| EA初始化函數 - 當EA載入到圖表時執行一次                              |
//+------------------------------------------------------------------+
int OnInit()
{
   // 記錄EA載入的時間戳記,用於計算K棒編號
   LoadEA = TimeCurrent();
   
   // 顯示交易商品的基本資訊
   // SYMBOL_VOLUME_MIN: 最小交易手數(例如0.01)
   // Point(): 最小價格變動單位(例如0.00001)
   Alert(" 最小手數=", string(SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MIN)), 
         " point=", string(Point()));
   
   // SYMBOL_TRADE_CONTRACT_SIZE: 合約規格(1手代表多少單位,例如100000)
   // SYMBOL_TRADE_TICK_VALUE: 1跳點的價值(美元)
   Alert(" 合約規格=", string(SymbolInfoDouble(Symbol(), SYMBOL_TRADE_CONTRACT_SIZE)), 
         " 1 跳點可換美元 = ", string(SymbolInfoDouble(Symbol(), SYMBOL_TRADE_TICK_VALUE)));

   return(INIT_SUCCEEDED); // 初始化成功
}

//+------------------------------------------------------------------+
//| Tick事件處理函數 - 每次價格跳動時執行                               |
//+------------------------------------------------------------------+
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棒時更新指標與交易時段
   //=================================================================
   // 當K棒編號改變時(新K棒產生),執行更新
   if(BarNumber != JudgeNo)
   {
      換K棒();          // 更新OHLC資料、計算指標、計算FVG價格
      交易時段賦值();    // 判斷當前是否在允許交易的時段
   }

   //=================================================================
   // 取得當前市場資訊
   //=================================================================
   Ask = SymbolInfoDouble(Symbol(), SYMBOL_ASK);              // 當前賣出價(Ask價)
   Bid = SymbolInfoDouble(Symbol(), SYMBOL_BID);              // 當前買入價(Bid價)
   AccountBalance = AccountInfoDouble(ACCOUNT_BALANCE);       // 帳戶餘額
   Tickvalue = SymbolInfoDouble(Symbol(), SYMBOL_TRADE_TICK_VALUE); // 跳點價值
   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)
   {
      // 當沖模式:18:00 ~ 01:00 視為接近收盤,不允許新倉進場
      接近收盤 = (getTM_hour(TimeCurrent()) >= 18 || getTM_hour(TimeCurrent()) <= 1);
      允許交易時段 = (允許交易時段 && !接近收盤);
   }

   //=================================================================
   // 進場邏輯(僅在允許交易時段執行)
   //=================================================================
   if(允許交易時段 == true)
   {
      //===============================================================
      // 8.1 多單進場邏輯
      //===============================================================
      set_BuyCondition(); // 計算多單進場條件(LE_Cond)
      
      // 檢查多單進場條件:
      // 1. LE_Cond == true(符合多單進場策略)
      // 2. 當前點差 < 設定的商品平均點差(避免點差過大時進場)
      if(LE_Cond == true && SP < NormalizeDouble(商品平均點差 * Point(), Digits()))
      {
         // 檢查部位狀態:
         // 1. 多單部位() == 0(目前沒有多單)
         // 2. 空單部位() == 0(目前沒有空單)
         // 3. BarNumber != OrderBarNo(不是在同一根K棒重複進場)
         if(多單部位() == 0 && 空單部位() == 0 && BarNumber != OrderBarNo)
         {
            // 檢查進場限制:
            // 1. BarSinceExit > 1(距離上次出場至少1根K棒)
            // 2. EntriesToday < 1(今天進場次數少於1次)
            if(BarSinceExit > 1 && EntriesToday(MagicNumber, Symbol()) < 1)
            {

                  // 執行市價單買入
                  // 參數:商品、手數、停利點數、停損點數、註解、魔術編號
                  多單進場單號 = Buy_at_MARKET(Symbol(), Lots, TP, SL, "BUY MARKET", MagicNumber);
                  
                  // 記錄下單時的K棒編號,避免同一根K棒重複進場
                  OrderBarNo = iBarShift(Symbol(), 時間週期, LoadEA);
             
            }
         }
      }
      
      //===============================================================
      // 8.2 空單進場邏輯
      //===============================================================
      set_ShortCondition(); // 計算空單進場條件(SE_Cond)
      
      // 檢查空單進場條件:
      // 1. SE_Cond == true(符合空單進場策略)
      // 2. 當前點差 < 設定的商品平均點差
      if(SE_Cond == true && SP < NormalizeDouble(商品平均點差 * Point(), Digits()))
      {
         // 檢查部位狀態:無任何部位且不在同一根K棒重複進場
         if(多單部位() == 0 && 空單部位() == 0 && BarNumber != OrderBarNo)
         {
            // 檢查進場限制:距離上次出場至少1根K棒且今天進場次數少於1次
            if(BarSinceExit > 1 && EntriesToday(MagicNumber, Symbol()) < 1)
            {

                  // 執行市價單賣出
                  空單進場單號 = Short_at_MARKET(Symbol(), Lots, TP, SL, "Short Market", MagicNumber);
                  
                  // 記錄下單時的K棒編號
                  OrderBarNo = iBarShift(Symbol(), 時間週期, LoadEA);
           
            }
         }
      }
   }

   //=================================================================
   // 當沖強制平倉
   //=================================================================
   // 當沖模式下,在指定的出場時間或凌晨1點強制平倉所有部位
   if(當沖 == true && (getTM_hour(TimeCurrent()) == 當沖出場時間 || getTM_hour(TimeCurrent()) == 1))
   {
      // 如果有任何部位(多單或空單),執行當沖平倉
      if(多單部位() > 0 || 空單部位() > 0)
      {
         當沖平倉();
      }
   }

   //=================================================================
   // 停損停利檢查(交易時段外也執行)
   //=================================================================
   // 無論是否在交易時段,都要檢查並執行停損停利
   交易時段外也可停損停利();
   
   //=================================================================
   // 更新K棒判斷編號
   //=================================================================
   // 記錄當前K棒編號,用於下次Tick判斷是否換K棒
   JudgeNo = iBarShift(Symbol(), 時間週期, LoadEA);

} // end of OnTick


//+------------------------------------------------------------------+
//| 自訂函數庫                                                        
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| 函數:多單部位()                                                    
//| 功能:計算當前多單(買單)的數量                                    
//| 回傳:多單部位數量                                                 
//+------------------------------------------------------------------+
int 多單部位()
{
   int count;
   // 使用get_TradeCounts函數計算指定商品、魔術編號、買單類型的部位數
   count = get_TradeCounts(Symbol(), MagicNumber, POSITION_TYPE_BUY);
   return count;
}

//+------------------------------------------------------------------+
//| 函數:空單部位()                                                    |
//| 功能:計算當前空單(賣單)的數量                                      |
//| 回傳:空單部位數量                                                   |
//+------------------------------------------------------------------+
int 空單部位()
{
   int count;
   // 使用get_TradeCounts函數計算指定商品、魔術編號、賣單類型的部位數
   count = get_TradeCounts(Symbol(), MagicNumber, POSITION_TYPE_SELL);
   return count;
}

//+------------------------------------------------------------------+
//| 函數:換K棒()                                                      |
//| 功能:當新K棒產生時,更新所有資料與指標                                |
//+------------------------------------------------------------------+
void 換K棒()
{
   //=================================================================
   // 1. 刪除所有掛單(限價單、停損單等)
   //=================================================================
   // 檢查是否有未成交的掛單
   if(total_pending_order_count(Symbol(), MagicNumber, -1) != 0)
   {
      // 刪除所有掛單(新K棒時清空,避免過期訂單)
      delete_pending_orders_all(Symbol(), MagicNumber, -1, 0x0000ff);
   }
   
   //=================================================================
   // 2. 設定OHLC陣列序列並取得資料
   //=================================================================
   Set_OHLC_Bar_Series();    // 設定K棒OHLC陣列為時間序列(索引0為最新)
   Set_OHLC_Day_Series();    // 設定日線OHLC陣列為時間序列
   Get_OHLC_Bar(30);         // 取得最近30根K棒的OHLC資料
   Get_OHLC_Day(15);         // 取得最近15天的日線OHLC資料

   //=================================================================
   // 3. 計算技術指標
   //=================================================================
   set_BBAND();  // 計算布林帶(Bollinger Bands)
   set_MACD();   // 計算MACD指標
   set_RSI();    // 計算RSI指標

   //=================================================================
   // 4. 計算FVG(Fair Value Gap)價格
   //=================================================================
   計算FVG價格(); // 計算多頭與空頭FVG的上下緣價格

   //=================================================================
   // 5. 計算近5日平均波動
   //=================================================================
   // Day5Range:過去5天的平均日內波動(High-Low平均值)
   Day5Range = ((HighD[1] - LowD[1]) + 
                (HighD[2] - LowD[2]) + 
                (HighD[3] - LowD[3]) + 
                (HighD[4] - LowD[4]) + 
                (HighD[5] - LowD[5])) / 5;
}

//+------------------------------------------------------------------+
//| 函數:交易時段賦值()                                                |
//| 功能:根據交易時段編號判斷是否允許交易                                 |
//+------------------------------------------------------------------+
void 交易時段賦值()
{
   // 交易時段:只允許8點之後交易

      允許交易時段 = (getTM_hour(TimeCurrent()) >= 8);
   
   // 可擴充其他交易時段編號的判斷邏輯
}

//+------------------------------------------------------------------+
//| 函數:當沖平倉()                                                    
//| 功能:當沖模式下的強制平倉邏輯                           
//+------------------------------------------------------------------+
void 當沖平倉()
{
   // 檢查是否到達當沖出場時間或凌晨1點
   if((getTM_hour(TimeCurrent()) == 當沖出場時間 || getTM_hour(TimeCurrent()) == 1))
   {
      //===============================================================
      // 平倉多單部位
      //===============================================================
      // 條件:1. 有多單部位 2. 不在同一根K棒重複平倉
      if(多單部位() > 0 && BarNumber != CloseOrderNo)
         LX_CloseByTicket(多單進場單號, Lots); // 使用票號平倉多單
      
      //===============================================================
      // 平倉空單部位
      //===============================================================
      // 條件:1. 有空單部位 2. 不在同一根K棒重複平倉
      if(空單部位() > 0 && BarNumber != CloseOrderNo)
         SX_CloseByTicket(空單進場單號, Lots); // 使用票號平倉空單
      
      //===============================================================
      // 記錄平倉時的K棒編號
      //===============================================================
      CloseOrderNo = iBarShift(Symbol(), 時間週期, LoadEA);
   }
}

//+------------------------------------------------------------------+
//| 函數:交易時段外也可停損停利()                                        |
//| 功能:無論是否在交易時段,都執行停損停利檢查                            |
//+------------------------------------------------------------------+
void 交易時段外也可停損停利()
{
   //=================================================================
   // 多單出場邏輯
   //=================================================================
   if(多單部位() > 0)
   {
      //===============================================================
      // 計算多單相關價格
      //===============================================================
      double 多單最小停利 = 0.0;   // 最小停利價格
      bool LX_MinPF = false;       // 是否達到最小停利
      
      // 取得多單進場價格
      多單進場價格 = LE_EntryPrice(MagicNumber, 多單進場單號);
      
      // 計算最小停利價格 = 進場價 + 3倍點差
      // 目的:避免價格剛微幅上漲就出場,確保至少賺到點差成本
      多單最小停利 = NormalizeDouble(多單進場價格 + SP * 3, Digits());
      LX_MinPF = Bid > 多單最小停利; // 當前Bid價是否高於最小停利
      
      // 計算停利價格 = 進場價 + TP點數
      多單停利價格 = NormalizeDouble(多單進場價格 + TP * Point(), Digits());
      
      // 計算停損價格 = 進場價 - SL點數
      多單停損價格 = NormalizeDouble(多單進場價格 - SL * Point(), Digits());

      //===============================================================
      // 多單出場方式 :MACD背離 + 停損
      //===============================================================
  
         // 出場條件(兩種情況任一成立):
         // 1. 達到最小停利 且 發生MACD高點背離
         // 2. K棒收盤價突破停損價 且 當前Bid價已跌破停損價
         LX_Cond = ((LX_MinPF == true && (MACD_HDiv_01() == true)) || 
                    (Close[1] >= 多單停損價格 && Bid < 多單停損價格));
 

      //===============================================================
      // 執行多單出場
      //===============================================================
      // 出場條件:
      // 1. LX_Cond == true(符合出場條件)
      // 2. BarNumber != CloseOrderNo(不在同一根K棒重複平倉)
      // 3. 進場時間 > 0根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 < 空單最小停利; // 當前Ask價是否低於最小停利
      
      // 計算停利價格 = 進場價 - TP點數
      空單停利價格 = NormalizeDouble(空單進場價格 - TP * Point(), Digits());
      
      // 計算停損價格 = 進場價 + SL點數
      空單停損價格 = NormalizeDouble(空單進場價格 + SL * Point(), Digits());

      //===============================================================
      // 空單出場方式 :多單訊號出現 + 停損
      //===============================================================

         // 出場條件(兩種情況任一成立):
         // 1. 達到最小停利 且 出現多單進場訊號(LE_Cond)
         // 2. K棒收盤價突破停損價 且 當前Ask價已漲破停損價
         SX_Cond = ((SX_MinPF == true && LE_Cond) || 
                    (Close[1] <= 空單停損價格 && Ask > 空單停損價格));


      //===============================================================
      // 執行空單出場
      //===============================================================
      // 出場條件:
      // 1. SX_Cond == true(符合出場條件)
      // 2. BarNumber != CloseOrderNo(不在同一根K棒重複平倉)
      // 3. 進場時間 > 0根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()                                          |
//| 功能:根據多單模組編號設定多單進場條件(LE_Cond)                      |
//+------------------------------------------------------------------+
void set_BuyCondition()
{

      // 進場條件(所有條件必須同時成立):
      // 1. a_OSC[1] > a_OSC[2]:MACD柱狀圖上升(動能增強)
      // 2. Close[1] > FVG_多頭上緣價格:收盤價突破多頭FVG上緣
      // 3. High[1] > High[2]:最高價高於前一根K棒
      // 4. High[1] > High[3]:最高價高於前三根K棒(連續創高確認)
      LE_Cond = (a_OSC[1] > a_OSC[2] && 
                 Close[1] > FVG_多頭上緣價格 && 
                 High[1] > High[2] && 
                 High[1] > High[3]);
   
   // 可擴充其他多單模組編號的進場條件
}

//+------------------------------------------------------------------+
//| 空單進場條件模組                                                    |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| 函數:set_ShortCondition()                                        |
//| 功能:根據空單模組編號設定空單進場條件(SE_Cond)                      |
//+------------------------------------------------------------------+
void set_ShortCondition()
{

      // 進場條件(所有條件必須同時成立):
      // 1. Shrink:布林帶處於收縮狀態(波動率降低)
      // 2. Low[1] < a_BBDN[1]:最低價突破布林帶下軌
      // 3. Close[1] < Open[1]:收盤價低於開盤價(黑K確認)
      SE_Cond = (Shrink && 
                 Low[1] < a_BBDN[1] && 
                 Close[1] < Open[1]);

   // 可擴充其他空單模組編號的進場條件
}

//+------------------------------------------------------------------+
//| OHLC資料處理區塊                                                   |
//+------------------------------------------------------------------+

// 宣告K棒價格陣列變數
double Open[];       // 開盤價陣列
double High[];       // 最高價陣列
double Low[];        // 最低價陣列
double Close[];      // 收盤價陣列
double Range[];      // 波動範圍陣列(High - Low)
double Body[];       // K棒實體陣列(|Close - Open|)
double UPshadow[];   // 上影線陣列
double DNshadow[];   // 下影線陣列

// 宣告日線價格陣列變數
double OpenD[];      // 日線開盤價陣列
double HighD[];      // 日線最高價陣列
double LowD[];       // 日線最低價陣列
double CloseD[];     // 日線收盤價陣列

// 宣告成交量陣列變數
long Volume[];       // 成交量陣列
long BigVolume[];    // 大成交量陣列

//+------------------------------------------------------------------+
//| 函數:Set_OHLC_Bar_Series()                                       |
//| 功能:設定K棒OHLC陣列為時間序列(索引0為最新資料)                     |
//+------------------------------------------------------------------+
void Set_OHLC_Bar_Series()
{
   // ArraySetAsSeries:將陣列設為時間序列
   // true:索引0代表最新資料,索引越大代表越舊的資料
   ArraySetAsSeries(Open, true);
   ArraySetAsSeries(High, true);
   ArraySetAsSeries(Low, true);
   ArraySetAsSeries(Close, true);
   ArraySetAsSeries(Volume, true);
}

//+------------------------------------------------------------------+
//| 函數:Get_OHLC_Bar()                                              |
//| 功能:取得指定數量的K棒OHLC資料                                      |
//| 參數:argCount - 要取得的K棒數量                                    |
//+------------------------------------------------------------------+
void Get_OHLC_Bar(int argCount)
{
   // 使用自訂函數取得OHLC資料並存入對應陣列
   get_OpenData(Symbol(), 時間週期, argCount, Open);      // 取得開盤價
   get_HighData(Symbol(), 時間週期, argCount, High);      // 取得最高價
   get_LowData(Symbol(), 時間週期, argCount, Low);        // 取得最低價
   get_CloseData(Symbol(), 時間週期, argCount, Close);    // 取得收盤價
   get_VolumeData(Symbol(), 時間週期, argCount, Volume);  // 取得成交量
}

//+------------------------------------------------------------------+
//| 函數:Set_OHLC_Day_Series()                                       |
//| 功能:設定日線OHLC陣列為時間序列                                     |
//+------------------------------------------------------------------+
void Set_OHLC_Day_Series()
{
   // 將日線資料陣列設為時間序列格式
   ArraySetAsSeries(OpenD, true);
   ArraySetAsSeries(HighD, true);
   ArraySetAsSeries(LowD, true);
   ArraySetAsSeries(CloseD, true);
}

//+------------------------------------------------------------------+
//| 函數:Get_OHLC_Day()                                              |
//| 功能:取得指定天數的日線OHLC資料                                     |
//| 參數:argCount - 要取得的天數                                       |
//+------------------------------------------------------------------+
void Get_OHLC_Day(int argCount)
{
   // 使用PERIOD_D1(日線週期)取得日線OHLC資料
   get_OpenData(Symbol(), PERIOD_D1, argCount, OpenD);    // 取得日線開盤價
   get_HighData(Symbol(), PERIOD_D1, argCount, HighD);    // 取得日線最高價
   get_LowData(Symbol(), PERIOD_D1, argCount, LowD);      // 取得日線最低價
   get_CloseData(Symbol(), PERIOD_D1, argCount, CloseD);  // 取得日線收盤價
}

//+------------------------------------------------------------------+
//| 技術指標計算區塊                                                    |
//+------------------------------------------------------------------+

//=================================================================
// 布林帶(Bollinger Bands)相關變數與函數
//=================================================================
bool UpBreak;     // 向上突破布林帶上軌標記
bool DnBreak;     // 向下突破布林帶下軌標記
bool Shrink;      // 布林帶收縮標記
double a_BBUP[];  // 布林帶上軌陣列
double a_BBDN[];  // 布林帶下軌陣列

//+------------------------------------------------------------------+
//| 函數:set_BBAND()                                                 |
//| 功能:計算布林帶指標並判斷突破與收縮狀態                               |
//+------------------------------------------------------------------+
void set_BBAND()
{
   int BBLen;        // 布林帶週期
   int h_BBUP;       // 布林帶上軌指標句柄
   int h_BBDN;       // 布林帶下軌指標句柄
   bool ShrinkUp;    // 上軌收縮標記
   bool ShrinkDn;    // 下軌收縮標記

   //===============================================================
   // 1. 計算布林帶週期(取LenA2和LenB2的較大值)
   //===============================================================
   BBLen = MathMax(LenA2, LenB2);
   
   //===============================================================
   // 2. 建立布林帶指標句柄並取得資料
   //===============================================================
   // 布林帶上軌:iBands(商品, 週期, BB週期, 偏移, 標準差倍數, 價格類型)
   h_BBUP = iBands(Symbol(), 時間週期, BBLen, 0, 2, PRICE_CLOSE);
   ArraySetAsSeries(a_BBUP, true);  // 設為時間序列
   
   // 布林帶下軌
   h_BBDN = iBands(Symbol(), 時間週期, BBLen, 0, 2, PRICE_CLOSE);
   ArraySetAsSeries(a_BBDN, true);
   
   // get_IndexData(句柄, 緩衝區索引, 起始位置, 數量, 目標陣列)
   get_IndexData(h_BBUP, 1, 0, 10, a_BBUP);  // 索引1:上軌資料
   get_IndexData(h_BBDN, 2, 0, 10, a_BBDN);  // 索引2:下軌資料

   //===============================================================
   // 3. 判斷布林帶突破狀態
   //===============================================================
   // 向上突破:最近兩根K棒的最高價都突破上軌
   UpBreak = (High[1] > a_BBUP[1]) && (High[2] > a_BBUP[2]);
   
   // 向下突破:最近兩根K棒的最低價都突破下軌
   DnBreak = Low[1] < a_BBDN[1] && Low[2] < a_BBDN[2];
   
   //===============================================================
   // 4. 判斷布林帶收縮狀態
   //===============================================================
   // 上軌收縮判斷:
   // 1. a_BBUP[1] > a_BBUP[2] > a_BBUP[3](上軌持續下降)
   // 2. a_BBUP[5] > a_BBUP[4] > a_BBUP[3](前期也在下降)
   ShrinkUp = a_BBUP[1] > a_BBUP[2] && a_BBUP[2] > a_BBUP[3] && 
              a_BBUP[5] > a_BBUP[4] && a_BBUP[4] > a_BBUP[3];
   
   // 下軌收縮判斷:
   // 1. a_BBDN[1] < a_BBDN[2] < a_BBDN[3](下軌持續上升)
   // 2. a_BBDN[5] < a_BBDN[4] < a_BBDN[3](前期也在上升)
   ShrinkDn = a_BBDN[1] < a_BBDN[2] && a_BBDN[2] < a_BBDN[3] && 
              a_BBDN[5] < a_BBDN[4] && a_BBDN[4] < a_BBDN[3];
   
   // 布林帶收縮 = 上軌收縮 或 下軌收縮(波動率降低)
   Shrink = ShrinkUp || ShrinkDn;
}

//=================================================================
// MACD指標相關變數與函數
//=================================================================
double a_DIF[];   // MACD DIF線陣列(快線 - 慢線)
double a_MACD[];  // MACD訊號線陣列(DIF的移動平均)
double a_OSC[];   // MACD柱狀圖陣列(DIF - MACD)

//+------------------------------------------------------------------+
//| 函數:set_MACD()                                                  |
//| 功能:計算MACD指標(DIF、訊號線、柱狀圖)                             |
//+------------------------------------------------------------------+
void set_MACD()
{
   int MacdLen;   // MACD訊號線週期
   int FastLen;   // MACD快線週期
   int SlowLen;   // MACD慢線週期
   int h_DIF;     // DIF指標句柄
   int h_MACD;    // 訊號線指標句柄
   
   //===============================================================
   // 1. 計算MACD各週期參數
   //===============================================================
   MacdLen = MathMax(LenA1, LenB1);  // 取LenA1和LenB1的較大值
   
   // 限制MacdLen最大為15(避免週期過長)
   if(MacdLen > 15)
      MacdLen = 15;
   
   // 根據MacdLen計算快慢線週期
   FastLen = (int)MathRound(MacdLen * 1.33);  // 快線 = 基準 × 1.33
   SlowLen = (int)MathRound(MacdLen * 2.66);  // 慢線 = 基準 × 2.66

   //===============================================================
   // 2. 建立MACD指標句柄並取得資料
   //===============================================================
   // iMACD(商品, 週期, 快線週期, 慢線週期, 訊號線週期, 價格類型)
   h_DIF = iMACD(Symbol(), 時間週期, FastLen, SlowLen, MacdLen, PRICE_WEIGHTED);
   ArraySetAsSeries(a_DIF, true);
   
   h_MACD = iMACD(Symbol(), 時間週期, FastLen, SlowLen, MacdLen, PRICE_WEIGHTED);
   ArraySetAsSeries(a_MACD, true);

   // 取得MACD資料
   get_IndexData(h_DIF, 0, 0, 10, a_DIF);     // 索引0:DIF線
   get_IndexData(h_MACD, 1, 0, 10, a_MACD);   // 索引1:訊號線

   //===============================================================
   // 3. 計算MACD柱狀圖(OSC = DIF - MACD)
   //===============================================================
   ArraySetAsSeries(a_OSC, true);               // 設為時間序列
   ArrayResize(a_OSC, ArraySize(a_DIF));        // 調整陣列大小
   
   // 逐一計算每根K棒的OSC值
   for(int i = 0; i < ArraySize(a_OSC); i++)
   {
      a_OSC[i] = a_DIF[i] - a_MACD[i];  // OSC = DIF - 訊號線
   }
}

//=================================================================
// RSI指標相關變數與函數
//=================================================================
double a_RSIA[];  // RSI指標A陣列(週期LenA1)
double a_RSIB[];  // RSI指標B陣列(週期LenB1)

//+------------------------------------------------------------------+
//| 函數:set_RSI()                                                   |
//| 功能:計算RSI指標(兩條不同週期的RSI)                               |
//+------------------------------------------------------------------+
void set_RSI()
{
   int h_RSIA;  // RSI A指標句柄
   int h_RSIB;  // RSI B指標句柄
   
   //===============================================================
   // 計算RSI A(週期LenA1)
   //===============================================================
   // iRSI(商品, 週期, RSI週期, 價格類型)
   h_RSIA = iRSI(Symbol(), 時間週期, LenA1, PRICE_CLOSE);
   ArraySetAsSeries(a_RSIA, true);  // 設為時間序列
   get_IndexData(h_RSIA, 0, 0, 5, a_RSIA);  // 取得5根K棒的RSI資料
   
   //===============================================================
   // 計算RSI B(週期LenB1)
   //===============================================================
   h_RSIB = iRSI(Symbol(), 時間週期, LenB1, PRICE_CLOSE);
   ArraySetAsSeries(a_RSIB, true);
   get_IndexData(h_RSIB, 0, 0, 5, a_RSIB);
}

//+------------------------------------------------------------------+
//| 進出場條件判斷函數                                                  |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| 函數:MACD_HDiv_01()                                              |
//| 功能:判斷MACD高點背離(價格創高但MACD未創高)                        |
//| 回傳:true - 發生高點背離;false - 未發生背離                        |
//+------------------------------------------------------------------+
bool MACD_HDiv_01()
{
   //=================================================================
   // 背離條件(所有條件必須同時成立)
   //=================================================================
   // 1. ArrayMaximum(High) != 0 && ArrayMaximum(High) <= 2
   //    價格最高點在最近2根K棒內(索引0或1或2)
   // 2. ArrayMaximum(a_DIF) > 2
   //    MACD DIF最大值在更早前(索引大於2)
   // 3. a_RSIA[1] < 70
   //    RSI未超買(避免在極端超買區進場)
   
   // 解釋:價格創新高,但MACD動能反而在減弱 = 背離訊號
   if((ArrayMaximum(High) != 0 && ArrayMaximum(High) <= 2) && 
      (ArrayMaximum(a_DIF) > 2) && 
      (a_RSIA[1] < 70))
   {
      return true;  // 發生高點背離
   }
   return false;  // 未發生背離
}

//+------------------------------------------------------------------+
//| FVG(Fair Value Gap)公允價值缺口相關變數與函數                      |
//+------------------------------------------------------------------+

// FVG上下緣價格變數
double FVG_多頭上緣價格;  // 多頭FVG上緣價格(Low[1])
double FVG_多頭下緣價格;  // 多頭FVG下緣價格(High[3])
double FVG_空頭上緣價格;  // 空頭FVG上緣價格(Low[3])
double FVG_空頭下緣價格;  // 空頭FVG下緣價格(High[1])

//+------------------------------------------------------------------+
//| 函數:計算FVG價格()                                                |
//| 功能:計算多頭與空頭FVG的上下緣價格                                   |
//| 說明:FVG是指價格快速移動留下的"空白區域",市場可能回補此缺口           |
//+------------------------------------------------------------------+
void 計算FVG價格()
{
   //=================================================================
   // 多頭FVG檢測(價格向上跳空)
   //=================================================================
   // 條件1:Low[1] > High[3](當前K棒最低價 > 前3根K棒最高價)
   // 條件2:缺口大小 > 前3根K棒平均波動的1/4(過濾小缺口)
   if(Low[1] > High[3] && (Low[1] - High[3]) > (Range[3] + Range[4] + Range[5]) / 4)
   {
      FVG_多頭上緣價格 = Low[1];   // 多頭FVG上緣 = 當前K棒最低價
      FVG_多頭下緣價格 = High[3];  // 多頭FVG下緣 = 前3根K棒最高價
      
      // 結果:在Low[1]與High[3]之間形成向上跳空缺口
   }
   //=================================================================
   // 空頭FVG檢測(價格向下跳空)
   //=================================================================
   // 條件1:High[1] < Low[3](當前K棒最高價 < 前3根K棒最低價)
   // 條件2:缺口大小 > 前3根K棒平均波動的1/4
   else if(High[1] < Low[3] && (Low[3] - High[1]) > (Range[3] + Range[4] + Range[5]) / 4)
   {
      FVG_空頭上緣價格 = Low[3];   // 空頭FVG上緣 = 前3根K棒最低價
      FVG_空頭下緣價格 = High[1];  // 空頭FVG下緣 = 當前K棒最高價
      
      // 結果:在Low[3]與High[1]之間形成向下跳空缺口
   }
}

回測結果
測試參數交易商品:NAS100(那斯達克指數)
樣本內區間:2019/1/1 ~ 2023/8/31
交易手數:固定 1 手
時間框架:H1 小時圖表
交易模式:當沖交易






沒有留言:

張貼留言