2025年10月20日 星期一

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

策略概述
這是一個基於多重技術指標超賣/超買背離的反轉交易策略。策略整合RVI背離、多重指標鈍化(RSI、KD、Williams %R、CCI)、Keltner通道突破等多項指標來識別進場機會,並使用固定時段交易模式(14:00-22:00)。

交易限制:
資金風控門檻: 低於門檻時停止交易
商品平均點差限制: 進場前檢查點差 < 商品平均點差 * Point()
每日限制1次進場
距離上次平倉需間隔 > 1根K棒
交易時段: 14:00 至 22:00

進出場策略
多單進場條件
LE_Cond = (RVI_HDiv_01() && Close[1] > 流動性池底部價格 && Close[1] > a_KC_UPPER_B[1]);
條件分析:
RVI高點背離 (RVI_HDiv_01())
價格最高點在最近3根K棒內(索引0-2)發生
RVI最高點出現在更早之前(索引>2),顯示動能減弱 目前RVI值 < 0.5(回落狀態)

突破流動性池底部價格
收盤價突破最近20根K棒的最低價(從第5根開始計算)
突破Keltner通道上軌B 確認突破強度

進場方式: 市價單買入,僅在無部位、點差小、最近6筆內有空單平倉、今日未進場、BarSinceExit > 1時執行

空單進場條件
SE_Cond = (多重超賣鈍化() && Close[1] > Close[3] && Close[1] < Open[1]);

條件分析:
多重超賣鈍化 (多重超賣鈍化())
需要至少2個以上指標同時出現低檔鈍化:
RSI低檔鈍化: RSI < 30 且出現 ≥ 4次
KD低檔鈍化: K值 < 30 且出現 ≥ 4次
Williams %R低檔鈍化: WPR < -80 且出現 ≥ 4次
CCI超賣鈍化: CCI < -100 且出現 ≥ 4次

價格回升確認
Close[1] > Close[3] (收盤價高於2根K棒前) 確認超賣後的反彈動作

黑K確認 Close[1] < Open[1] (收盤價低於開盤價) 形成看跌K線型態

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

//+------------------------------------------------------------------+
//| EA 初始化函數                                                     
//| 說明:程式啟動時執行一次,用於初始化變數和顯示商品資訊               
//+------------------------------------------------------------------+
int OnInit()
  {
   // 記錄EA載入時間
   LoadEA = TimeCurrent();
   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//| EA 主要執行函數                                                   
//| 說明:每個Tick(價格變動)都會執行,包含所有交易邏輯                     
//+------------------------------------------------------------------+
void OnTick()
  {
   // 檢查帳戶餘額是否低於風控設定值
   if(AccountInfoDouble(ACCOUNT_BALANCE) < 資金風控)
     {
      Alert("**********  資金不足 *************");
      return; // 資金不足時停止執行
     }

   // 計算目前K棒編號(從EA載入時間開始算起)
   BarNumber = iBarShift(Symbol(),時間週期,LoadEA);
   
   // 計算距離上次平倉後經過的K棒數量
   BarSinceExit = BarNumber-CloseOrderNo ;
   
   // 當出現新K棒時(BarNumber=1)且未重複判斷時,執行測試交易
   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棒編號改變時,更新指標和交易時段設定
   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()) ; // 計算買賣價差(點差)

//+------------------------------------------------------------------+
//| 動態計算交易手數                                                   
//| 說明:根據風險百分比和停損點數自動計算合適的交易手數                  
//+------------------------------------------------------------------+
   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(); // 計算多單進場條件
      
      // 多單進場判斷:
      // 1. 多單進場條件成立
      // 2. 點差小於商品平均點差
      // 3. 最近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)
              {
               if(多單下單方式 == 1) // 使用市價單買入
                 {
                  // 執行市價多單進場
                  多單進場單號 = Buy_at_MARKET(Symbol(),Lots,TP,SL,"BUY MARKET",MagicNumber) ;
                  
                  // 記錄下單時的K棒編號(避免重複下單)
                  OrderBarNo = iBarShift(Symbol(),時間週期,LoadEA);
                 }
              }
           }
        }
        
      //+------------------------------------------------------------------+
      //| 空單進場邏輯                                                   
      //+------------------------------------------------------------------+
      set_ShortCondition() ; // 計算空單進場條件
      
      // 空單進場判斷:
      // 1. 空單進場條件成立
      // 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)
              {
               if(空單下單方式 == 1) // 使用市價單賣出
                 {
                  // 執行市價空單進場
                  空單進場單號 = Short_at_MARKET(Symbol(),Lots,TP,SL,"Short Market",MagicNumber) ;
                  
                  // 記錄下單時的K棒編號(避免重複下單)
                  OrderBarNo = iBarShift(Symbol(),時間週期,LoadEA);
                 }
              }
           }
        }
     }

   // 執行停損停利邏輯(即使不在交易時段也可執行)
   交易時段外也可停損停利();
   
   // 更新判斷編號,避免同一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棒時執行的更新函數                                             
//| 說明:每當新K棒產生時,更新所有技術指標和K棒數據                    
//+------------------------------------------------------------------+
void 換K棒()
  {
   // 新K棒產生時刪除所有未成交的掛單
   if(total_pending_order_count(Symbol(), MagicNumber,-1) != 0)
     {
      delete_pending_orders_all(Symbol(), MagicNumber, -1, 0x0000ff);
     }
     
   // 設定K棒和日線數據的陣列序列(必要步驟)
   Set_OHLC_Bar_Series();
   Set_OHLC_Day_Series();
   
   // 獲取最近30根K棒和15天的OHLC數據
   Get_OHLC_Bar(30) ;
   Get_OHLC_Day(15) ;

   // 更新所有技術指標
   set_MACD();          // 更新MACD指標
   set_RSI();           // 更新RSI指標
   set_KD() ;           // 更新KD隨機指標
   set_CCI();           // 更新CCI指標
   set_RVI();           // 更新RVI指標
   set_WILLIAMS_PR() ;  // 更新威廉指標
   set_KELTNER_CHANNEL() ; // 更新肯特納通道

   // 計算最近5日的平均波動範圍
   Day5Range = ((HighD[1] - LowD[1])+(HighD[2] - LowD[2])+(HighD[3] - LowD[3])+(HighD[4] - LowD[4])+(HighD[5] - LowD[5]))/5;
  }

//+------------------------------------------------------------------+
//| 交易時段判斷                                                     
//| 說明:根據時間設定允許交易的時段                                    
//+------------------------------------------------------------------+
void 交易時段賦值()
  {
   // 允許交易時間為14:00到22:00
      允許交易時段 = (getTM_hour(TimeCurrent()) >= 14 && getTM_hour(TimeCurrent()) < 22);
  }

//+------------------------------------------------------------------+
//| 停損停利執行函數                                                   |
//| 說明:即使不在交易時段也執行停損停利,保護帳戶資金                     |
//+------------------------------------------------------------------+
void 交易時段外也可停損停利()
  {
   // 處理多單部位的停損停利
   if(多單部位() > 0)
     {
      //+------------------------------------------------------------------+
      //| 多單出場方法設定                                                  |
      //+------------------------------------------------------------------+
      double 多單最小停利 = 0.0;
      bool LX_MinPF = false ;
      
      // 取得多單進場價格
      多單進場價格 = LE_EntryPrice(MagicNumber,多單進場單號);
      
      // 計算最小停利價格(進場價格+3倍點差)
      多單最小停利 = NormalizeDouble(多單進場價格 + SP*3,Digits()) ;
      LX_MinPF = Bid > 多單最小停利 ; // 判斷是否達到最小停利
      
      // 計算多單停利價格(進場價格+停利點數)
      多單停利價格 = NormalizeDouble(多單進場價格 + TP * Point(),Digits()) ;
      
      // 計算多單停損價格(進場價格-停損點數)
      多單停損價格 = NormalizeDouble(多單進場價格 - SL * Point(),Digits()) ;

      // 多單出場方式13:動態停利(加上5日平均波動)

         // 停利價格加上5日平均波動範圍
         多單停利價格 = NormalizeDouble(多單進場價格 + (TP+Day5Range) * Point(),Digits()) ;
         
         // 多單出場條件:價格突破停利或停損價格
         LX_Cond = ((Close[1] <= 多單停利價格 && Bid > 多單停利價格) || (Close[1] >= 多單停損價格 && Bid < 多單停損價格)) ;

      // 執行多單平倉
      // 條件:1.出場條件成立 2.非同一K棒重複平倉 3.持倉超過0根K棒
      if(LX_Cond == true && BarNumber != CloseOrderNo && LE_BarsSinceEntry(MagicNumber,多單進場單號,時間週期) > 0)
        {
         // 執行平倉
         LX_CloseByTicket(多單進場單號,Lots) ;

         // 如果多單已全部平倉,記錄平倉K棒編號
         if(多單部位() == 0)
           {
            CloseOrderNo = iBarShift(Symbol(),時間週期,LoadEA) ;
           }
        }
     }

   // 處理空單部位的停損停利
   if(空單部位() > 0)
     {
      //+------------------------------------------------------------------+
      //| 空單出場方法設定                                                  |
      //+------------------------------------------------------------------+
      double 空單最小停利 = 0.0;
      bool SX_MinPF = false ;
      
      // 取得空單進場價格
      空單進場價格 = SE_EntryPrice(MagicNumber,空單進場單號);
      
      // 計算最小停利價格(進場價格-3倍點差)
      空單最小停利 = NormalizeDouble(空單進場價格 - SP*3,Digits()) ;
      SX_MinPF = Ask < 空單最小停利 ; // 判斷是否達到最小停利
      
      // 計算空單停利價格(進場價格-停利點數)
      空單停利價格 = NormalizeDouble(空單進場價格 - TP * Point(),Digits()) ;
      
      // 計算空單停損價格(進場價格+停損點數)
      空單停損價格 = NormalizeDouble(空單進場價格 + SL * Point(),Digits()) ;

      // 空單出場方式11:MACD黃金交叉或觸及停損

         // 空單出場條件:1.達到最小停利且MACD向上交叉0軸 2.價格突破停損
         SX_Cond = ((SX_MinPF == true && CrossOver(a_MACD[2],0,a_MACD[1],0)) || (Close[1] <= 空單停損價格 && Ask > 空單停損價格)) ;
        }

      // 執行空單平倉
      // 條件:1.出場條件成立 2.非同一K棒重複平倉 3.持倉超過0根K棒
      if(SX_Cond == true && BarNumber != CloseOrderNo && SE_BarsSinceEntry(MagicNumber,空單進場單號,時間週期) > 0)

         // 執行平倉
         SX_CloseByTicket(空單進場單號,Lots) ;

         // 如果空單已全部平倉,記錄平倉K棒編號
         if(空單部位() == 0)
           {
            CloseOrderNo = iBarShift(Symbol(),時間週期,LoadEA) ;
           }
        }
     }
  }

//+------------------------------------------------------------------+
//| 多單進場條件設定                                                 
//| 說明:根據多單模組編號設定不同的進場條件                           
//+------------------------------------------------------------------+
void set_BuyCondition()
  {
   // 條件1:RVI高點背離(價格創新高但RVI未創新高)
   // 條件2:收盤價突破流動性池底部價格
   // 條件3:收盤價突破Keltner通道上軌B
      LE_Cond = (RVI_HDiv_01() && Close[1] > 流動性池底部價格 && Close[1] > a_KC_UPPER_B[1]) ;
  }

//+------------------------------------------------------------------+
//| 空單進場條件設定                                                  
//| 說明:根據空單模組編號設定不同的進場條件                     
//+------------------------------------------------------------------+
void set_ShortCondition()
  {
   // 條件1:多重超賣鈍化(多個指標同時顯示超賣且持續)
   // 條件2:價格回升(收盤價高於2根K棒前)
   // 條件3:形成黑K(收盤價低於開盤價)
      SE_Cond = (多重超賣鈍化() && Close[1] > Close[3] && Close[1] < Open[1]) ;
  }

//+------------------------------------------------------------------+
//| K棒價格數據陣列變數宣告                                          
//+------------------------------------------------------------------+
double Open[], High[], Low[], Close[],Range[],Body[],UPshadow[],DNshadow[] ; // K棒OHLC及相關計算
double OpenD[], HighD[], LowD[], CloseD[] ; // 日線OHLC
long Volume[],BigVolume[] ; // 成交量

//+------------------------------------------------------------------+
//| 設定K棒數據陣列為時間序列                                        
//| 說明:陣列索引[0]代表最新K棒,[1]代表前一根,以此類推               
//+------------------------------------------------------------------+
void Set_OHLC_Bar_Series()
  {
   ArraySetAsSeries(Open,true);   // 開盤價陣列
   ArraySetAsSeries(High,true);   // 最高價陣列
   ArraySetAsSeries(Low,true);    // 最低價陣列
   ArraySetAsSeries(Close,true);  // 收盤價陣列
   ArraySetAsSeries(Volume,true); // 成交量陣列
  }

//+------------------------------------------------------------------+
//| 獲取K棒OHLC數據                                             
//| 參數:argCount - 要獲取的K棒數量                               
//+------------------------------------------------------------------+
void Get_OHLC_Bar(int argCount)
  {
   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) ; // 獲取成交量
  }

//+------------------------------------------------------------------+
//| 設定日線數據陣列為時間序列                                         
//+------------------------------------------------------------------+
void Set_OHLC_Day_Series()
  {
   ArraySetAsSeries(OpenD,true);  // 日線開盤價陣列
   ArraySetAsSeries(HighD,true);  // 日線最高價陣列
   ArraySetAsSeries(LowD,true);   // 日線最低價陣列
   ArraySetAsSeries(CloseD,true); // 日線收盤價陣列
  }

//+------------------------------------------------------------------+
//| 獲取日線OHLC數據                                               
//| 參數:argCount - 要獲取的天數                               
//+------------------------------------------------------------------+
void Get_OHLC_Day(int argCount)
  {
   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) ; // 獲取日線收盤價
  }

//+------------------------------------------------------------------+
//| MACD指標計算                                                     
//| 說明:計算MACD的DIF、MACD和OSC(柱狀圖)值                        
//+------------------------------------------------------------------+
double a_DIF[], a_MACD[], a_OSC[] ; // MACD指標三條線的數值陣列

void set_MACD()
  {
   int MacdLen, FastLen, SlowLen ;
   int h_DIF,h_MACD ;
   
   // 根據LenA1和LenB1動態計算MACD參數
   MacdLen = MathMax(LenA1,LenB1) ;
   if(MacdLen > 15)
      MacdLen = 15 ; // 限制MACD週期最大為15
      
   FastLen = (int)MathRound(MacdLen*1.33) ;  // 快線週期
   SlowLen = (int)MathRound(MacdLen*2.66) ;  // 慢線週期

   // 建立MACD指標句柄(使用加權收盤價)
   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) ;   // DIF線(快線-慢線)
   get_IndexData(h_MACD,1,0,10,a_MACD) ; // MACD線(DIF的移動平均)

   // 計算OSC柱狀圖(DIF-MACD)
   ArraySetAsSeries(a_OSC, true);
   ArrayResize(a_OSC,ArraySize(a_DIF)) ;
   for(int i = 0;i < ArraySize(a_OSC); i++)
     {
      a_OSC[i] = a_DIF[i]-a_MACD[i] ;
     }
  }

//+------------------------------------------------------------------+
//| RSI指標計算                                                   
//| 說明:相對強弱指標,用於判斷超買超賣                              
//+------------------------------------------------------------------+
double a_RSIB[] ; // RSI指標數值陣列

void set_RSI()
  {
   int h_RSIB ;
   
   // 建立RSI指標句柄(週期使用LenB1)
   h_RSIB = iRSI(Symbol(),時間週期,LenB1,PRICE_CLOSE) ;
   ArraySetAsSeries(a_RSIB,true) ;
   
   // 獲取RSI數據(最近5個值)
   get_IndexData(h_RSIB,0,0,5,a_RSIB) ;
  }

//+------------------------------------------------------------------+
//| KD隨機指標計算                                                   
//| 說明:隨機震盪指標,用於判斷超買超賣和動能                       
//+------------------------------------------------------------------+
int h_KA2,h_DA2,h_KB2,h_DB2 ; // KD指標句柄
double a_KA2[], a_DA2[], a_JA2[], a_KB2[], a_DB2[], a_JB2[] ; // KD指標數值陣列

void set_KD()
  {
   // 建立KD指標句柄
   // 參數:週期LenA2, K值平滑3, D值平滑3, EMA平滑, 使用最高最低價計算
   h_KA2 = iStochastic(Symbol(),時間週期,LenA2,3,3,MODE_EMA,STO_LOWHIGH) ;
   ArraySetAsSeries(a_KA2,true);
   h_DA2 = iStochastic(Symbol(),時間週期,LenA2,3,3,MODE_EMA,STO_LOWHIGH) ;
   ArraySetAsSeries(a_DA2,true);

   // 獲取K值和D值數據
   get_IndexData(h_KA2,0,0,5,a_KA2) ; // K值
   get_IndexData(h_DA2,1,0,5,a_DA2) ; // D值
  }

//+------------------------------------------------------------------+
//| CCI指標計算                                                     
//| 說明:順勢指標,用於判斷價格偏離程度                              
//+------------------------------------------------------------------+
double a_CCI[] ; // CCI指標數值陣列

void set_CCI()
  {
   int h_CCI ;
   
   // 建立CCI指標句柄(週期使用LenB1)
   h_CCI = iCCI(Symbol(),時間週期,LenB1,PRICE_CLOSE);
   ArraySetAsSeries(a_CCI, true);
   
   // 獲取CCI數據(最近5個值)
   get_IndexData(h_CCI,0,0,5,a_CCI) ;
  }

//+------------------------------------------------------------------+
//| RVI指標計算                                                    
//| 說明:相對活力指標,衡量價格波動的方向和強度                   
//+------------------------------------------------------------------+
double a_RVI_1[], a_RVI_2[] ; // RVI指標主線和訊號線數值陣列

void set_RVI()
  {
   int h_RVI_1,h_RVI_2 ;
   
   // 建立RVI指標句柄(週期使用LenB1)
   h_RVI_1 = iRVI(Symbol(),時間週期,LenB1);
   ArraySetAsSeries(a_RVI_1, true);
   h_RVI_2 = iRVI(Symbol(),時間週期,LenB1);
   ArraySetAsSeries(a_RVI_2, true);

   // 獲取RVI主線和訊號線數據
   get_IndexData(h_RVI_1,0,0,5,a_RVI_1) ; // RVI主線
   get_IndexData(h_RVI_2,1,0,5,a_RVI_2) ; // RVI訊號線
  }

//+------------------------------------------------------------------+
//| Williams %R 威廉指標計算                                        
//| 說明:威廉指標,測量超買超賣狀態,數值範圍-100到0               
//+------------------------------------------------------------------+
double a_WPR_A[] ; // Williams %R 數值陣列

void set_WILLIAMS_PR()
  {
   int h_WPR_A ; // Williams %R 指標句柄

   // 建立 Williams %R 指標句柄(使用 LenA1 週期)
   h_WPR_A = iWPR(Symbol(), 時間週期, LenA1);
   ArraySetAsSeries(a_WPR_A, true); // 設定陣列為時間序列

   // 獲取 Williams %R 數據(最近15個數值)
   get_IndexData(h_WPR_A, 0, 0, 15, a_WPR_A);
  }

//+------------------------------------------------------------------+
//| Keltner Channel 肯特納通道指標計算                             
//| 說明:基於EMA和ATR的通道指標,用於判斷趨勢和突破              
//+------------------------------------------------------------------+
double a_KC_UPPER_B[], a_KC_LOWER_B[], a_KC_MIDDLE_B[] ; // Keltner通道:上軌、下軌、中線
double a_KC_ATR_B[] ; // 對應的ATR數值

void set_KELTNER_CHANNEL()
  {
   int h_EMA_B ; // EMA句柄(用作中線)
   int h_ATR_B ; // ATR句柄(用於計算通道寬度)

   // 建立 Keltner Channel B組指標句柄(使用 LenB1 週期)
   h_EMA_B = iMA(Symbol(), 時間週期, LenB1, 0, MODE_EMA, PRICE_CLOSE);
   h_ATR_B = iATR(Symbol(), 時間週期, LenB1);
   
   // 設定所有陣列為時間序列
   ArraySetAsSeries(a_KC_UPPER_B, true);
   ArraySetAsSeries(a_KC_LOWER_B, true);
   ArraySetAsSeries(a_KC_MIDDLE_B, true);
   ArraySetAsSeries(a_KC_ATR_B, true);

   // 調整陣列大小為20
   ArrayResize(a_KC_UPPER_B, 20);
   ArrayResize(a_KC_LOWER_B, 20);
   ArrayResize(a_KC_MIDDLE_B, 20);
   ArrayResize(a_KC_ATR_B, 20);

   // 獲取EMA和ATR數據
   get_IndexData(h_EMA_B, 0, 0, 20, a_KC_MIDDLE_B); // EMA作為中線
   get_IndexData(h_ATR_B, 0, 0, 20, a_KC_ATR_B);    // ATR數據

   // 計算Keltner通道上下軌
   for(int i = 0; i < ArraySize(a_KC_MIDDLE_B)-1; i++)
     {
      // Keltner Channel 計算公式:
      // 上軌 = EMA + (ATR × 倍數) - 倍數設定為1.5
      // 下軌 = EMA - (ATR × 倍數)
      a_KC_UPPER_B[i] = a_KC_MIDDLE_B[i] + (a_KC_ATR_B[i] * 1.5);
      a_KC_LOWER_B[i] = a_KC_MIDDLE_B[i] - (a_KC_ATR_B[i] * 1.5);
     }
  }

//+------------------------------------------------------------------+
//| RVI 死亡交叉判斷                                           
//| 說明:判斷RVI主線是否向下穿越訊號線(看跌訊號)                   
//| 返回:true=死叉發生, false=無死叉                                
//+------------------------------------------------------------------+
bool RVI_死叉()
  {
   // 前一根K棒RVI主線>=訊號線,且目前K棒主線<訊號線
   if(a_RVI_1[2] >= a_RVI_2[2] && a_RVI_1[1] < a_RVI_2[1])
     {
      return true;
     }
   return false;
  }

//+------------------------------------------------------------------+
//| RVI 高點背離判斷(版本01)                               
//| 說明:價格創新高但RVI未創新高,顯示動能減弱(看漲背離)            
//| 返回:true=出現高點背離, false=無背離                          
//+------------------------------------------------------------------+
bool RVI_HDiv_01()
  {
   // 條件1:價格最高點出現在最近3根K棒內(索引0-2)
   // 條件2:RVI最高點出現在更早之前(索引>2)
   // 條件3:目前RVI值<0.5(回落狀態)
   if((ArrayMaximum(High) !=0 && ArrayMaximum(High) <= 2) && (ArrayMaximum(a_RVI_1) > 2) && a_RVI_1[1] < 0.5)
     {
      return true;
     }
   return false;
  }

//+------------------------------------------------------------------+
//| 多重超賣鈍化綜合判斷                                               |
//| 說明:統計多個指標的超賣鈍化狀態,當2個以上指標鈍化時返回true          |
//| 返回:true=多重鈍化, false=未達標準                                 |
//+------------------------------------------------------------------+
bool 多重超賣鈍化()
  {
   int oversold_count = 0; // 超賣鈍化計數器
   
   // 檢查各指標是否鈍化,每個鈍化指標計數+1
   if(RSI低檔鈍化())
      oversold_count++;
   if(KD低檔鈍化())
      oversold_count++;
   if(WPR低檔鈍化())
      oversold_count++;
   if(CCI超賣鈍化())
      oversold_count++;
      
   // 當2個以上指標同時鈍化時,判定為多重超賣鈍化
   if(oversold_count >= 2)
     {
      return true;
     }
   return false;
  }

//+------------------------------------------------------------------+
//| 流動性池價格計算                                                   |
//| 說明:計算最近20根K棒的頂部和底部價格(從第5根開始算)                 |
//|      用於判斷價格突破的關鍵支撐壓力位                               |
//+------------------------------------------------------------------+
double 流動性池頂部價格 = Highest_OHLC(Symbol(), 時間週期, MODE_HIGH, 20, 5);
double 流動性池底部價格 = Lowest_OHLC(Symbol(), 時間週期, MODE_LOW, 20, 5);

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




沒有留言:

張貼留言