Skip to content

趋势反转

一、策略概述

源于经典的双均线交叉策略与RSI反转策略,用于捕捉趋势反转点。

核心逻辑:

  1. RSI指标:判断市场超买超卖状态,识别潜在顶部/底部。
  2. 双均线(快/慢线)金死叉:捕捉趋势变化。
  3. 追踪止损:保护利润,本文采用“拉回固定点数”的移动止损方式。

二、策略细节:捕捉趋势反转点

目标:

  • 在上升趋势末期做空,下降趋势末尾做多。
  • 例:RSI > 50表明多头状态,若快线(短期MA)与慢线(长期MA)形成金叉,且前一根K线开盘价在快线之下、收盘价在慢线之上,可能趋势反转,尝试做多(空单逻辑相反)。

三、进出场条件(以做多为例)

进场条件:

  1. 当前RSI > 50;
  2. 快线(短期MA) < 慢线(长期MA);
  3. 前一根K线开盘价 < 快线;
  4. 前一根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;
}