2026年8月25日 星期二

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

核心策略概述(商品:NAS100)

多單模組:POC支撐型態
Low[1] < POC_價格 && Close[1] > POC_價格 && 多頭OrderBlock(),以 Buy_at_MARKET 市價買入

空單模組:Swing低點跌破 + 失守POC
SwingLow(3,3,1) > 0 && Close[1] < SwingLow值 && Close[1] < POC_價格,以 Short_at_STOP 掛單突破賣出(非市價單)

交易限制

  • 時段控制:交易時段編號
  • 換倉避讓:若非當沖模式,呼叫 CheckAndCloseForSwap(23,30) 於23:30前避讓出場
  • 每日限2次進場:EntriesToday(MagicNumber, Symbol()) < 2
  • 同根K棒不重複下單:目前K棒編號 != OrderBarNo && != CloseOrderNo,並加入「本棒有平倉紀錄()」防護
  • 點差過濾:SP < 商品平均點差 * Point()
  • 資金風控:帳戶餘額 < 資金風控 → 僅 Alert("資金不足")並 return倉/ExpertRemove()

進場條件

多單模組

  • Low[1] 跌破 POC,Close[1] 站回 POC 之上(假跌破反彈型態)
  • 且符合 多頭OrderBlock() 型態確認

空單模組

  • 收盤價跌破近期 SwingLow(3根K棒擺盪低點)
  • 且收盤價低於 POC_價格(失守成交量控制點)

進場方式

多單下單方式:空手狀態下市價買入(Buy_at_MARKET)
空單下單方式:計算「賣出價格」後掛突破停損賣單(Short_at_STOP)

掛單價格計算(Get_ShortPrice):
賣出價格 = CDP的NL − 0.382 × Range[1]
下單前需同時滿足:

  • 賣出價需低於 Bid 且超過最小止損距離的2倍
  • 賣出價距市價不超過收盤價的「突破距離百分比」上限
  • 前一根收盤價須高於賣出價
  • 不可在商品收盤時間下單
  • 且尚無其他掛單(total_pending_order_count == 0)

出場條件

多單出場方式(創高追蹤停利)

  • 停利/停損每Tick持續判斷,出場條件:Bid <= 停損價 或 Bid >= 停利價
  • 追蹤規則:當 Close[1] > High[2] 且 High[1] 等於區間內最高價(創新高)時:
    • 新停利 = 現有停利 + (TP×0.2) × Point()
    • 新停損 = Bid − SL × Point()
    • 僅在新值優於現值時更新(停利只上移、停損只上移)

空單出場方式(VWAP追蹤停損 + 布林極寬止利)

  • 每Tick計算VWAP,若 VWAP_價格 優於(低於)現有停損價,即更新停損為 VWAP 值(僅更新SL,TP參數傳0不變)
  • 出場條件(SX_Cond):
    (Ask < 空單最小停利 && 布林上一根帶寬>近期最高帶寬(a_BBwidth[1]>High_BBW) && Close[1]>Open[1] && Close[1]>Close[2])
    Ask >= 停損價Ask <= 停利價
  • 空單最小停利 = 進場價 − SP×3

停損停利設計

  • 進場首根K棒(BarsSinceEntry==0)立即呼叫 TPSLchange() 推送初始停損停利至券商端
  • 每Tick透過 PositionGetDouble(POSITION_TP/SL) 讀取當前實際停損停利
  • 多單:初始停利 = 進場價 + TP×Point();初始停損 = 進場價 − SL×Point()
  • 空單:初始停利 = 進場價 − TP×Point();初始停損 = 進場價 + SL×Point()

換K棒更新項目(換K棒())

  • 商品收盤整點前(CloseHour)自動刪除所有掛單
  • 更新:K棒/日線 OHLC 序列
  • 計算:ATR(→AvgATR)、慢均線(→a_SlowSma)
  • 計算波動率(Get_Volatility → Volatility_HLRange,供布林通道使用)
  • 更新布林通道(set_BBAND → a_BBwidth / High_BBW,供空單出場方式46使用)
  • 計算 CDP(set_CDP → NL,供空單掛單價格32使用)
  • 計算 VWAP(計算VWAP,供空單出場方式46使用)
  • 計算 POC 成交量控制點(計算POC,供多空模組132/300共用)
//====================================================================
// OnTick
//====================================================================
void OnTick()
  {
   int 當前K棒 = Bars(Symbol(), 時間週期) - 1;
   目前K棒編號 = 當前K棒;

   if(!IsMarketOpen(10))
      return;

   if(AccountInfoDouble(ACCOUNT_BALANCE) < 資金風控)
     {
      Alert("**********  資金不足 *************");
      return;
     }

   BarNumber = iBarShift(Symbol(), 時間週期, LoadEA);
   BarSinceExit = 目前K棒編號 - CloseOrderNo;

   if(BarNumber == 1 && BarNumber != JudgeNo)
      CloseOrderNo = -1;

   if(BarNumber != JudgeNo)
     {
      換K棒();
      允許交易時段 = true;   // 交易時段編號:全時段允許
     }

   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);
      Lots = MathMin(0.3, MathMax(0.01, Lots));
     }

   交易時段外也可停損停利();

   if(允許交易時段 == true)
     {
      int TempTP = (int)(Close[1]*0.005/Point());
      int TempSL = (int)(Close[1]*0.005/Point());
      double 價格遠距上限 = Close[1] * 突破距離百分比;

      //---------------------------------------------------------
      // 多單進場(模組132 + 價格15 + 下單方式1:市價)
      //---------------------------------------------------------
      set_BuyCondition();
      if(LE_Cond == true && SP < NormalizeDouble(商品平均點差*Point(), Digits()))
        {
         if(多單部位() == 0 && 空單部位() == 0
            && 目前K棒編號 != OrderBarNo
            && 目前K棒編號 != CloseOrderNo
            && !本棒有平倉紀錄())
           {
            買入價格 = Get_BuyPrice();

            if(EntriesToday(MagicNumber, Symbol()) < 2)
              {
               // 多單下單方式:空手+市價單買入
               Print("✅ 準備開市價多單 | K棒:", BarNumber, " 舊單號", OrderBarNo, " 上次平倉單號", CloseOrderNo);
               多單進場單號 = Buy_at_MARKET(Symbol(), Lots, TempTP, TempSL, "BUY MARKET", MagicNumber);
               if(多單進場單號 > 0)
                 {
                  OrderBarNo = 目前K棒編號;
                  Print("✅ 多單開倉成功 | Ticket:", 多單進場單號, " | OrderBarNo:", OrderBarNo);
                 }
              }
           }
        }

      //---------------------------------------------------------
      // 空單進場(模組300 + 價格32 + 下單方式2:突破掛單)
      //---------------------------------------------------------
      set_ShortCondition();
      if(SE_Cond == true && SP < NormalizeDouble(商品平均點差*Point(), Digits()))
        {
         if(多單部位() == 0 && 空單部位() == 0
            && 目前K棒編號 != OrderBarNo
            && 目前K棒編號 != CloseOrderNo
            && !本棒有平倉紀錄())
           {
            賣出價格 = Get_ShortPrice();

            if(EntriesToday(MagicNumber, Symbol()) < 2)
              {
               // 空單下單方式:空手+掛單突破賣出
               if(賣出價格 < Bid - (最小止損*Point())*2
                  && 賣出價格 > Bid - 價格遠距上限
                  && Close[1] > 賣出價格 && getTM_hour(TimeCurrent()) != 商品收盤時間)
                 {
                  if(total_pending_order_count(Symbol(), MagicNumber, -1) == 0)
                    {
                     Print("✅ 準備開突破空單 | K棒:", BarNumber, " | 掛單價:", 賣出價格,
                           " | 距市價%:", DoubleToString((Bid - 賣出價格)/Close[1]*100, 3));
                     空單進場單號 = Short_at_STOP(Symbol(), MagicNumber, 賣出價格, Lots, TempTP, TempSL, "Short STOP", 3600);
                     if(空單進場單號 > 0)
                        OrderBarNo = 目前K棒編號;
                    }
                 }
              }
           }
        }
     }

   JudgeNo = iBarShift(Symbol(), 時間週期, LoadEA);
  }

//====================================================================
// 部位查詢
//====================================================================
int 多單部位() { return get_TradeCounts(Symbol(), MagicNumber, POSITION_TYPE_BUY); }
int 空單部位() { return get_TradeCounts(Symbol(), MagicNumber, POSITION_TYPE_SELL); }

//====================================================================
// 換K棒:
//====================================================================
void 換K棒()
  {
   if((getTM_hour(TimeCurrent()) == CloseHour) && (total_pending_order_count(Symbol(), MagicNumber, -1) != 0))
      delete_pending_orders_all(Symbol(), MagicNumber, -1, 0x0000ff);

   Set_OHLC_Bar_Series();
   Set_OHLC_Day_Series();
   Get_OHLC_Bar(30);
   Get_OHLC_Day(15);
   set_BarInfo();
   set_ATR();         
   set_SlowSma();     
   Get_Volatility();    
   set_BBAND();         
   set_CDP();          
   計算VWAP();          
   計算POC();           
  }

//====================================================================
// 交易時段外也可停損停利:
//====================================================================
void 交易時段外也可停損停利()
  {
   if(多單部位() > 0)
     {
      double 多單最小停利 = 0.0;
      多單進場價格 = LE_EntryPrice(MagicNumber, 多單進場單號);
      多單最小停利 = NormalizeDouble(多單進場價格 + SP*3, Digits());

      多單最高價 = iHigh(Symbol(), 時間週期,
                    iHighest(Symbol(), 時間週期, MODE_HIGH, LE_BarsSinceEntry(MagicNumber, 多單進場單號, 時間週期), 1));
      多單最高價 = NormalizeDouble(多單最高價, Digits());

      if(LE_BarsSinceEntry(MagicNumber, 多單進場單號, 時間週期) == 0)
        {
         double 初始停利 = NormalizeDouble(多單進場價格 + TP*Point(), Digits());
         double 初始停損 = NormalizeDouble(多單進場價格 - SL*Point(), Digits());
         TPSLchange(Symbol(), 多單進場單號, 初始停利, 初始停損, 時間週期);
        }

      if(PositionSelectByTicket(多單進場單號))
        {
         多單停利價格 = PositionGetDouble(POSITION_TP);
         多單停損價格 = PositionGetDouble(POSITION_SL);
        }

      // --- 多單出場方式:創新高時 TP 向上移動 TP*0.2 ---
      if(Close[1] > High[2] && High[1] == 多單最高價)
        {
         double 新停利 = NormalizeDouble(多單停利價格 + (TP*0.2)*Point(), Digits());
         double 新停損 = NormalizeDouble(Bid - SL*Point(), Digits());
         if(新停利 > 多單停利價格) 多單停利價格 = 新停利;
         if(新停損 > 多單停損價格) 多單停損價格 = 新停損;
         TPSLchange(Symbol(), 多單進場單號, 多單停利價格, 多單停損價格, 時間週期);
        }
      LX_Cond = (Bid <= 多單停損價格) || (Bid >= 多單停利價格);

      if(LX_Cond == true && 目前K棒編號 != OrderBarNo && 目前K棒編號 != CloseOrderNo &&
         LE_BarsSinceEntry(MagicNumber, 多單進場單號, 時間週期) > 0)
        {
         LX_CloseByTicket(多單進場單號, Lots);
         CloseOrderNo = 目前K棒編號;
        }
     }

   if(空單部位() > 0)
     {
      double 空單最小停利 = 0.0;
      空單進場價格 = SE_EntryPrice(MagicNumber, 空單進場單號);
      空單最小停利 = NormalizeDouble(空單進場價格 - SP*3, Digits());

      空單最低價 = iLow(Symbol(), 時間週期,
                    iLowest(Symbol(), 時間週期, MODE_LOW, SE_BarsSinceEntry(MagicNumber, 空單進場單號, 時間週期), 1));
      空單最低價 = NormalizeDouble(空單最低價, Digits());

      if(SE_BarsSinceEntry(MagicNumber, 空單進場單號, 時間週期) == 0)
        {
         double 初始停利 = NormalizeDouble(空單進場價格 - TP*Point(), Digits());
         double 初始停損 = NormalizeDouble(空單進場價格 + SL*Point(), Digits());
         TPSLchange(Symbol(), 空單進場單號, 初始停利, 初始停損, 時間週期);
        }

      if(PositionSelectByTicket(空單進場單號))
        {
         空單停利價格 = PositionGetDouble(POSITION_TP);
         空單停損價格 = PositionGetDouble(POSITION_SL);
        }

      // --- 空單出場方式:布林極寬收紅K止利 + VWAP追蹤停損 ---
      計算VWAP();
      double 新停損 = NormalizeDouble(VWAP_價格, Digits());
      if(新停損 < 空單停損價格)
        {
         空單停損價格 = 新停損;
         TPSLchange(Symbol(), 空單進場單號, 0, 空單停損價格, 時間週期);
        }
      SX_Cond = (Ask < 空單最小停利 && a_BBwidth[1] > High_BBW && Close[1] > Open[1] && Close[1] > Close[2])
                || (Ask >= 空單停損價格) || (Ask <= 空單停利價格);

      if(SX_Cond == true && 目前K棒編號 != OrderBarNo && 目前K棒編號 != CloseOrderNo &&
         SE_BarsSinceEntry(MagicNumber, 空單進場單號, 時間週期) > 0)
        {
         SX_CloseByTicket(空單進場單號, Lots);
         CloseOrderNo = 目前K棒編號;
        }
     }
  }

//====================================================================
// 多單價格編號:慢均線 - 0.5×平均ATR
//====================================================================
double Get_BuyPrice()
  {
   double BuyPrice = a_SlowSma[1] - AvgATR*0.5;
   return NormalizeDouble(BuyPrice, Digits());
  }

//====================================================================
// 空單價格編號:CDP的NL - 0.382×Range
//====================================================================
double Get_ShortPrice()
  {
   double ShortPrice = NL - Range[1]*0.382;
   return NormalizeDouble(ShortPrice, Digits());
  }

//====================================================================
// 多單模組:POC支撐 + 多頭OrderBlock
//====================================================================
void set_BuyCondition()
  {
   LE_Cond = (Low[1] < POC_價格 && Close[1] > POC_價格 && 多頭OrderBlock());
  }

//====================================================================
// 空單模組:Swing低點跌破 + 失守POC
//====================================================================
void set_ShortCondition()
  {
   SE_Cond = (SwingLow(Low, ArraySize(Low), 3, 3, 1) > 0
              && Close[1] < SwingLow(Low, ArraySize(Low), 3, 3, 1)
              && Close[1] < POC_價格);
  }


回測結果
測試參數交易商品:NAS100(那指)
樣本內區間:2021/01/01 ~ 2025/06/30
交易手數:固定 1 手
時間框架: M15圖表
交易模式:波段交易




沒有留言:

張貼留言