趋势反转
一、策略概述
源于经典的双均线交叉策略与RSI反转策略,用于捕捉趋势反转点。
核心逻辑:
- RSI指标:判断市场超买超卖状态,识别潜在顶部/底部。
- 双均线(快/慢线)金死叉:捕捉趋势变化。
- 追踪止损:保护利润,本文采用“拉回固定点数”的移动止损方式。
二、策略细节:捕捉趋势反转点
目标:
- 在上升趋势末期做空,下降趋势末尾做多。
- 例:RSI > 50表明多头状态,若快线(短期MA)与慢线(长期MA)形成金叉,且前一根K线开盘价在快线之下、收盘价在慢线之上,可能趋势反转,尝试做多(空单逻辑相反)。
三、进出场条件(以做多为例)
进场条件:
- 当前RSI > 50;
- 快线(短期MA) < 慢线(长期MA);
- 前一根K线开盘价 < 快线;
- 前一根K线收盘价 > 慢线。
空单条件:上述条件反向(如RSI < 50,快线 > 慢线,开盘价 > 快线,收盘价 < 慢线)。
止损与追踪止损:
- 初始止损:固定点数(或基于ATR,更科学)。
- 追踪止损:
- 做多:持仓后记录价格最高点,拉回N点(如200点)作为止损价,随行情上涨不断上移止损。
- 做空:持仓后记录价格最低点,拉回N点作为止损价,随行情下跌不断下移止损。
四、代码逻辑
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 FastMaParam = 10; // 快速均线参数
input int SlowMaParam = 20; // 慢速均线参数
input int RsiParam = 14; // RSI参数
input int SlPoint = 2000; // 止损点位
input double TrailingPoint = 500; // 移动止盈点数
int barsTotal;
int fastMa_handle;
int slowMa_handle;
double fastMaBuffer[];
double slowMaBuffer[];
int rsi_handle;
double rsiBuffer[];
int initBalance;
double point;
MqlTick currentTick;
int OnInit()
{
rsi_handle = iRSI(_Symbol, TimeFrame,RsiParam,PRICE_CLOSE);
fastMa_handle = iMA(_Symbol, TimeFrame, FastMaParam, 0,MODE_EMA,PRICE_CLOSE);
slowMa_handle = iMA(_Symbol, TimeFrame, SlowMaParam, 0,MODE_SMA,PRICE_CLOSE);
if (rsi_handle == INVALID_HANDLE)
{
Alert((string)MagicNum + " ", _Symbol, " rsi_handle 创建失败");
return INIT_FAILED;
}
if (fastMa_handle == INVALID_HANDLE)
{
Alert((string)MagicNum + " ", _Symbol, " fastMa_handle 创建失败");
return INIT_FAILED;
}
if( slowMa_handle == INVALID_HANDLE)
{
Alert((string)MagicNum + " ", _Symbol, " slowMa_handle 创建失败");
return INIT_FAILED;
}
ArraySetAsSeries(rsiBuffer, true);
ArraySetAsSeries(fastMaBuffer, true);
ArraySetAsSeries(slowMaBuffer, true);
// 当用户没有打开参数对应的图表周期,自动帮他打开对应周期
if (MQLInfoInteger(MQL_TESTER))
{
ChartSetInteger(0,CHART_SHOW_GRID,false);
}else{
ChartSetSymbolPeriod(0,_Symbol, TimeFrame);
}
initBalance = SlParam;
Comment("MagicNum:", MagicNum, " SlType: ", SlType, " SlParam: ", SlParam);
// trade.SetExpertMagicNumber(MagicNum);
return INIT_SUCCEEDED;
}
// 节省内存后 买VPS就可以买小一点
void OnDeinit(const int reason)
{
if (fastMa_handle != INVALID_HANDLE)
{
IndicatorRelease(fastMa_handle);
}
if (slowMa_handle != INVALID_HANDLE)
{
IndicatorRelease(slowMa_handle);
}
if (rsi_handle != INVALID_HANDLE)
{
IndicatorRelease(rsi_handle);
}
}
void OnTick()
{
if (!util.isNewBar(_Symbol, TimeFrame, barsTotal))
return;
point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
int digits = (int) SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
if (!SymbolInfoTick(_Symbol, currentTick))
{
Print("tick数据获取异常");
}
CopyBuffer(fastMa_handle, 0, 0, 2, fastMaBuffer);
CopyBuffer(slowMa_handle, 0, 0, 2, slowMaBuffer);
CopyBuffer(rsi_handle, 0, 0, 2, rsiBuffer);
trailingStopByPointPullback(_Symbol,MagicNum,TrailingPoint*_Point);
int cntBuy, cntSell;
if (!util.countOpenPositions(MagicNum, cntBuy, cntSell))
return;
if (cntBuy > 0 || cntSell > 0){
return;
}
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
int signal = checkSignal();
if (cntBuy ==0 && signal == 1)
{
double et = iClose(_Symbol,TimeFrame,1);
double sl = et - SlPoint * point;
double tp = 0;
double slp = SlParam;
if (SlType == 4)
{
slp = util.calculateEachEaBalance(MagicNum, initBalance); // 子账户百分之一
}
util.orderSend(_Symbol,MagicNum,et,sl,SlType,SlParam,0,"MARB-"+(string)MagicNum);
}else if (cntSell == 0 && signal == -1)
{
double et = iClose(_Symbol,TimeFrame,1);
double sl = et+ SlPoint * point;
double tp = 0;
double slp = SlParam;
if (SlType == 4)
{
slp = util.calculateEachEaBalance(MagicNum, initBalance); // 子账户百分之一
}
util.orderSend(_Symbol,MagicNum,et,sl,SlType,SlParam,0,"MARS-"+(string)MagicNum);
}
}
// 针对所有仓位做从最高的价格拉回多少点数移动止损
void trailingStopByPointPullback(string symbol,long magicNum,double trPoint){
int digits = (int) SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
for(int i=0;i<PositionsTotal();i++){
positionInfo.SelectByIndex(i);
if(positionInfo.Magic() == MagicNum){
double slPrice ;
if(positionInfo.PositionType() == POSITION_TYPE_BUY){
double maxPrice = afterEntryExtremPrice(symbol,positionInfo.Time(),positionInfo.PositionType());
// 如果最高点与进场价之差小于slpoint 则忽略这个价位
if((maxPrice- positionInfo.PriceOpen())< trPoint) continue;
slPrice = NormalizeDouble(maxPrice - trPoint , digits);
// 如果新的止损价大于原来的止损价, 则以新的止损价修改该仓位,止盈价不变
if(slPrice > positionInfo.StopLoss()){
trade.PositionModify(positionInfo.Ticket(),slPrice,positionInfo.TakeProfit());
}
}else if(positionInfo.PositionType() == POSITION_TYPE_SELL){
double minPrice = afterEntryExtremPrice(symbol,positionInfo.Time(),positionInfo.PositionType());
// 如果最低点与进场价之差小于slpoint 则忽略这个价位
if((positionInfo.PriceOpen()- minPrice)< trPoint) continue;
slPrice = NormalizeDouble(minPrice + trPoint , digits);
// 如果新的止损价小于原来的止损价, 则以新的止损价修改该仓位,止盈价不变
if(slPrice < positionInfo.StopLoss()){
trade.PositionModify(positionInfo.Ticket(),slPrice,positionInfo.TakeProfit());
}
}
}
}
}
double afterEntryExtremPrice(string symbol,datetime positionTime, ENUM_POSITION_TYPE positionType){
if(positionTime <=0) return -1;
double price[];
if(positionType == POSITION_TYPE_BUY){
// 尝试复制从进场时间到当前时刻的M1时间周期内的最高价到数组"price" 如何没有数据被复制,那么函数直接返回-1
if(CopyHigh(symbol,PERIOD_M1,positionTime,TimeCurrent(),price)<=0) return -1;
// 返回"price"数组中的最大值作为进场后的最高点
return price[ArrayMaximum(price,0,ArraySize(price))];
}
if(positionType == POSITION_TYPE_SELL){
if(CopyLow(symbol,PERIOD_M1,positionTime,TimeCurrent(),price)<=0) return -1;
return price[ArrayMinimum(price,0,ArraySize(price))];
}
return -1;
}
// 多: RSI>50 快线<慢线 开盘价<快线 收盘价>慢线
int checkSignal()
{
double open1 = iOpen(_Symbol, TimeFrame, 1);
double close1 = iClose(_Symbol, TimeFrame, 1);
if(rsiBuffer[1] > 50 && fastMaBuffer[1] < slowMaBuffer[1] && open1< fastMaBuffer[1] && close1 >slowMaBuffer[1]){
return 1;
}
if(rsiBuffer[1] < 50 || fastMaBuffer[1] > slowMaBuffer[1] || open1> fastMaBuffer[1] || close1 <slowMaBuffer[1]){
return -1;
}
return 0;
}