Skip to content

EldeTripleScreen

艾尔德三重滤网(Elder Triple Screen)。它由亚历山大·埃尔德(Alexander Elder)在其著作《走进我的交易室》中提出,核心原理是通过两层滤网过滤信号、一层进场点确认,以提高交易胜率。

一、原始策略简介

艾尔德三重滤网交易系统分为三个步骤(原始逻辑针对股票市场):

  1. 第一重滤网:大盘趋势判断
    • 先分析大盘股指方向(如上涨趋势只做多,下跌趋势只做空),确定交易是顺势还是逆势。
  2. 第二重滤网:个股趋势确认
    • 在大盘趋势方向内,筛选强于大盘的个股,通过MACD、均线、RSI等指标验证个股趋势。
  3. 第三重:进场点信号
    • 利用指标(如MACD背离、突破信号)确定具体入场时机,进一步过滤无效信号。

策略价值:为交易员提供结构化决策流程,避免冲动交易,以科学方式管理风险。

二、策略改良与跨市场应用

1. 适配外汇/期货/加密货币的调整

  • 滤网指标替换
    • 大周期趋势(第一重):保留MACD(日线,参数12、26、9),也可替换为Vegas通道等。
    • 中周期趋势(第二重):采用WPR(威廉指标)替代RSI,周期和参数可外部输入(示例:H6周期,参数10)。
  • 进场规则
    • 挂突破单而非市价单,基于最近N根K线的高低点确定挂单价与止损价。

2. 核心交易逻辑(以H6周期WPR为例)

  • 做多信号
    • 日线MACD处于上升趋势(第一重滤网),且H6周期WPR处于下降趋势(第二重滤网)。
    • 挂多单:以最近N根K线的最高点为进场价,最低点为止损价,止盈设为止损点数的倍数(如5倍盈亏比)。
  • 做空信号
    • 日线MACD下降,H6周期WPR上升。
    • 挂空单:以最近N根K线的最低点为进场价,最高点为止损价。

三、挂单管理与出场规则

1. 挂单动态调整

  • 触发删单条件
    1. 中周期指标(WPR)出现反向信号(如做多信号转为做空)。
    2. 最近N根K线的高低点变化,导致进场价或止损价变动,需按最新价格重新挂单。
  • 跟踪止损(移动止盈)
    • 多单持仓:以最近N根K线的最低点作为动态止损,随行情上涨逐步上移。
    • 空单持仓:以最近N根K线的最高点作为动态止损,随行情下跌逐步下移。

2. 代码实现

cpp
#property strict

#include <Util/Util.mqh>
Util util;
// 不可以做参数优化的参数
input group "设置参数"
sinput int MagicNum = 12345; // 魔术编号
sinput int SlType = 1;                              // 止损方式(1:固定金额{100}美金 2:账户{1}% 3:固定{0.1}手 4:子账户总账户{1}%)
sinput int SlParam = 100;                           // 止损依据(根据上方【止损方式】的不同,所代表的含义也不同)

input group "参数优化" 
input ENUM_TIMEFRAMES TimeFrame = PERIOD_H1;        // 挂单时间周期
input int EtBarCount = 20;                                           // 突破范围bar
input int SlBarCount = 5;                                            // 跟踪止损bar
input int TpFactor = 5;                                       //盈亏比
input ENUM_TIMEFRAMES WprTimeFrame = PERIOD_H4; // WPR时间周期
input int WprParam =10;  // WPR参数

input group "持仓管理"

input bool UseBE = false;       // 是否使用平保
input double BETriggerPoints = 300; // 平保触发点数
input double BEMovePoints = 100;    // 平保点数 0就是移动到开仓价

input bool UseTS = false;           // 是否跟踪止损
input double TSTriggerPoints = 300; // 跟踪止损触发点数
input double TSInitialPoints = 150; // 跟踪止损初始移动点
input double TSMovePoints = 100;    // 跟踪止损触发后移动点

input group "时间过滤器"
 input bool UseTimeFilter = false; // 是否使用时间过滤器
input int TimeStart = 30;                                  // 开始时间(0~1440)
input int TimeEnd = 1410;                                  // 结束时间(0~1440)

int barsTotal;

int wprHandle;
double wprBuffer[];
int macdHandle;
double macdBuffer[];

int initBalance;
double point;
MqlTick currentTick;
int OnInit()
{
    if(TimeFrame > WprTimeFrame){
        Print("中期趋势时间周期不能低于挂单时间周期");
        return INIT_PARAMETERS_INCORRECT;
    }
    wprHandle = iWPR(_Symbol, WprTimeFrame,WprParam);
    if (wprHandle == INVALID_HANDLE)
    {
        Alert((string)MagicNum + " ", _Symbol, " wprHandle 创建失败");
        return INIT_FAILED;
    }
    macdHandle = iMACD(_Symbol, PERIOD_D1,12,26,9,PRICE_CLOSE);

    if (macdHandle == INVALID_HANDLE)
    {
        Alert((string)MagicNum + " ", _Symbol, " macdHandle 创建失败");
        return INIT_FAILED;
    }


    ArraySetAsSeries(wprBuffer, true);
    ArraySetAsSeries(macdBuffer, true);

    // 当用户没有打开参数对应的图表周期,自动帮他打开对应周期
    if (!MQLInfoInteger(MQL_TESTER))
    {
        ChartSetSymbolPeriod(0, _Symbol, TimeFrame);
    }
    initBalance = SlParam;
    Comment("MagicNum:", MagicNum, " SlType:", SlType, " SlParam:", SlParam);
    return INIT_SUCCEEDED;
}
// 节省内存后 买VPS就可以买小一点
void OnDeinit(const int reason)
{
    if (wprHandle != INVALID_HANDLE)
    {
        IndicatorRelease(wprHandle);
    }
    if (macdHandle != INVALID_HANDLE)
    {
        IndicatorRelease(macdHandle);
    }
}
void OnTick()
{

    if (!util.isNewBar(_Symbol, TimeFrame, barsTotal))
        return;
    point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);

    if (!SymbolInfoTick(_Symbol, currentTick))
    {
        Print("tick数据获取异常");
    }
    CopyBuffer(wprHandle, 0, 0, 3, wprBuffer);
    CopyBuffer(macdHandle, 0, 0, 3, macdBuffer);

    //仓位管理
    trailingStopByBar(_Symbol,TimeFrame,MagicNum,SlBarCount);
    int cntBuy, cntSell;
    if (!util.countOpenPositions(MagicNum, cntBuy, cntSell))
        return;

    if (cntBuy > 0 || cntSell > 0)
        return;

    bool longMacd = checkSignalMACD()==1;
    bool longWpr = checkSignalWPR()==1;

    bool shorMacd =  checkSignalMACD()==-1;
    bool shortWpr = checkSignalWPR()==-1;

    bool signal = longMacd && longWpr ? 1: shorMacd && shortWpr?-1:0;

    // 挂单
    int countBuyOrders, countSellOrders;
    if(!util.countPendingOrders(MagicNum, countBuyOrders, countSellOrders)) return ;

    if(countBuyOrders>0){
        double et = util.highPrice(_Symbol, TimeFrame, EtBarCount);
        double sl = util.lowPrice(_Symbol, TimeFrame, SlBarCount);
        util.deleteOrdersByPriceOpenAndStopLoss(MagicNum, ORDER_TYPE_BUY_STOP,et, sl);
        if(!longWpr){
            util.deleteOrders(MagicNum,ORDER_TYPE_BUY_STOP);
        }

    }else if(countSellOrders>0){
        double et = util.lowPrice(_Symbol, TimeFrame, EtBarCount);
        double sl = util.highPrice(_Symbol, TimeFrame, SlBarCount);
        util.deleteOrdersByPriceOpenAndStopLoss(MagicNum, ORDER_TYPE_SELL_STOP,et, sl);
        if(!shortWpr){
            util.deleteOrders(MagicNum,ORDER_TYPE_SELL_STOP);
        }
    }

    // 刷新挂单
    if(!util.countPendingOrders(MagicNum, countBuyOrders, countSellOrders)) return; 

    // 下单
    bool bullSignal = cntBuy == 0 && countBuyOrders==0  && signal == 1;
    if(bullSignal){
        double et = util.highPrice(_Symbol, TimeFrame, EtBarCount);
        double sl = util.lowPrice(_Symbol, TimeFrame, SlBarCount);
        double tp = TpFactor==0 ?0:et + TpFactor * (et - sl);

        double slp = SlParam;
        if (SlType == 4)
        {
            slp = util.calculateEachEaBalance(MagicNum, initBalance); // 子账户百分之一
        }
        double lotSize = util.calcLots(_Symbol, et, sl, SlType, slp);
        trade.SetExpertMagicNumber(MagicNum);
        trade.BuyStop(lotSize,et,_Symbol, sl, tp,ORDER_TIME_GTC,NULL, "ETS-"+string(MagicNum));
    }
    bool bearSignal = cntSell == 0 && countSellOrders==0  && signal == -1;
    if(bearSignal){
        double et = util.lowPrice(_Symbol, TimeFrame, EtBarCount);
        double sl = util.highPrice(_Symbol, TimeFrame, SlBarCount);
        double tp = TpFactor==0 ?0:et - TpFactor * (sl - et);
        double slp = SlParam;
        if (SlType == 4)
        {
            slp = util.calculateEachEaBalance(MagicNum, initBalance); // 子账户百分之一
        }
        double lotSize = util.calcLots(_Symbol, et, sl, SlType, slp);
        trade.SetExpertMagicNumber(MagicNum);
        trade.SellStop(lotSize,et,_Symbol, sl, tp,ORDER_TIME_GTC,NULL, "ETS-"+string(MagicNum));
    }
    if (UseBE)
    {
        for (int i = 0; i < PositionsTotal(); i++)
        {
            ulong ticket = PositionGetTicket(i);
            int type = (int)PositionGetInteger(POSITION_TYPE);
            long magic = PositionGetInteger(POSITION_MAGIC);
            double positionOpen = PositionGetDouble(POSITION_PRICE_OPEN);
            double positionCurrent = PositionGetDouble(POSITION_PRICE_CURRENT);

            double positionStopLoss = PositionGetDouble(POSITION_SL);
            double positionTakeProfit = PositionGetDouble(POSITION_TP);
            double distance = 0;
            if (type == POSITION_TYPE_BUY)
            {
                distance = (positionCurrent - positionOpen) / _Point;

                if (distance >= BETriggerPoints && positionStopLoss < positionOpen)
                {
                    trade.PositionModify(ticket, positionOpen + BEMovePoints * _Point, positionTakeProfit);
                }
            }
            else if (type == POSITION_TYPE_SELL)
            {
                distance = (positionOpen - positionCurrent) / _Point;
                if (distance >= BETriggerPoints && positionStopLoss > positionOpen)
                {
                    trade.PositionModify(ticket, positionOpen - BEMovePoints * _Point, positionTakeProfit);
                }
            }
        }
    }

    if (UseTS)
    {
        for (int i = 0; i < PositionsTotal(); i++)
        {
            ulong ticket = PositionGetTicket(i);
            int type =(int) PositionGetInteger(POSITION_TYPE);
            long magic = PositionGetInteger(POSITION_MAGIC);
            double positionOpen = PositionGetDouble(POSITION_PRICE_OPEN);
            double positionCurrent = PositionGetDouble(POSITION_PRICE_CURRENT);
            double positionStopLoss = PositionGetDouble(POSITION_SL);
            double positionTakeProfit = PositionGetDouble(POSITION_TP);
            double distance = 0;
            if (type == POSITION_TYPE_BUY)
            {
                distance = (positionCurrent - positionOpen) / _Point;
                if (distance >= TSTriggerPoints && positionStopLoss < positionOpen)
                {

                    trade.PositionModify(ticket, positionOpen + TSInitialPoints * _Point, positionTakeProfit);
                }
                else if (positionStopLoss >= positionOpen && (positionCurrent - positionStopLoss) / _Point > (TSTriggerPoints - TSInitialPoints + TSMovePoints))
                {
                    trade.PositionModify(ticket, positionStopLoss + TSMovePoints * _Point, positionTakeProfit);
                }
            }
            else if (type == POSITION_TYPE_SELL)
            {
                distance = (positionOpen - positionCurrent) / _Point;
                if (distance >= TSTriggerPoints && positionStopLoss > positionOpen)
                {
                    trade.PositionModify(ticket, positionOpen - TSInitialPoints * _Point, positionTakeProfit);
                }
                else if (positionStopLoss <= positionOpen && (positionStopLoss - positionCurrent) / _Point > (TSTriggerPoints - TSInitialPoints + TSMovePoints))
                {
                    trade.PositionModify(ticket, positionStopLoss - TSMovePoints * _Point, positionTakeProfit);
                }
            }
        }
    }
}
// 根据bar的高低价做移动止损
void trailingStopByBar(string symbol,ENUM_TIMEFRAMES tf,long magicNum,int slBarCount){
    int total = PositionsTotal();
    for (int i = total - 1; i >= 0; i--)
    {
        ulong ticket = PositionGetTicket(i);
        positionInfo.SelectByTicket(ticket);
        if (positionInfo.Magic() == magicNum && positionInfo.Symbol() == symbol){
            double slPrice ;
            if(positionInfo.PositionType() == POSITION_TYPE_BUY){
                // 获取最新的最低价作为止损价
                slPrice = util.lowPrice(symbol, tf, slBarCount);
                // 如果计算出来的最新的止损价,比现有持仓的止损价还要高,则更新止损价
                if(slPrice > positionInfo.StopLoss()){
                    trade.PositionModify(positionInfo.Ticket(), slPrice, positionInfo.TakeProfit());
                }
            }
            else if(positionInfo.PositionType() == POSITION_TYPE_SELL){
                // 获取最新的最高价作为止损价
                slPrice = util.highPrice(symbol, tf, slBarCount);
                // 如果计算出来的最新的止损价,比现有持仓的止损价还要低,则更新止损价
                if(slPrice < positionInfo.StopLoss()){
                    trade.PositionModify(positionInfo.Ticket(), slPrice, positionInfo.TakeProfit());
                }
            }
        }
    }
}





// 中周期震荡指标向下,看多
int checkSignalWPR(){
    double wpr_4H1 = wprBuffer[1];
    double wpr_4H2 = wprBuffer[2];

    if(wpr_4H1<wpr_4H2){
        return 1;
    }else if(wpr_4H1>wpr_4H2){
        return -1;
    }else{
        return 0;
    }
}
// 大周期macd向上 看多
int checkSignalMACD(){
    double macd_D1 = macdBuffer[1];
    double macd_D2 = macdBuffer[2];

    if(macd_D1>macd_D2){
        return 1;
    }else if(macd_D1<macd_D2){
        return -1;
    }else{
        return 0;
    }
}   


bool TimeFilter()
{
    if (!UseTimeFilter)
        return true;
    int timeCycle = 86400;
    datetime currentTime = TimeCurrent();
    datetime startTimeDay = (currentTime - (currentTime % timeCycle));
    datetime timeStart = startTimeDay + TimeStart * 60;
    datetime timeEnd = startTimeDay + TimeEnd * 60;
    if (TimeCurrent() < timeStart || TimeCurrent() >= timeEnd)
        return false;
    return true;
}