EldeTripleScreen
艾尔德三重滤网(Elder Triple Screen)。它由亚历山大·埃尔德(Alexander Elder)在其著作《走进我的交易室》中提出,核心原理是通过两层滤网过滤信号、一层进场点确认,以提高交易胜率。
一、原始策略简介
艾尔德三重滤网交易系统分为三个步骤(原始逻辑针对股票市场):
- 第一重滤网:大盘趋势判断
- 先分析大盘股指方向(如上涨趋势只做多,下跌趋势只做空),确定交易是顺势还是逆势。
- 第二重滤网:个股趋势确认
- 在大盘趋势方向内,筛选强于大盘的个股,通过MACD、均线、RSI等指标验证个股趋势。
- 第三重:进场点信号
- 利用指标(如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. 挂单动态调整
- 触发删单条件:
- 中周期指标(WPR)出现反向信号(如做多信号转为做空)。
- 最近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;
}