Skip to content

市场噪声

市场噪声概念

它是指在金融市场上由不确定性、偶然因素或非有用信息引起的价格和交易量的随机波动。这些无意义的波动可能会让投资者对市场走势的真实情况产生误解,从而影响投资决策。

举个通俗的例子:市场噪声很像一个喝酒的人走路回家。设想一个醉汉从酒吧出来,家在酒吧不远处的正北方。由于喝醉了,他走路左摇右晃,甚至可能朝南走,每次步伐方向随机(左、右、前、后),但最终会到家,因为他知道需要向北走。

  • 醉汉的步伐:代表市场噪声(短期随机波动)。
  • 最终回家的方向:代表市场潜在趋势(长期方向)。

尽管市场短期可能上下波动(如醉汉左摇右晃),但长期会呈现明显趋势(如醉汉最终回家)。投资者需从噪声中筛选有用信息,看清市场真正趋势。

当噪声微小,市场趋势容易识别;噪声增加时,有意义的价格行为模糊,甚至变成随机运动,此时很多交易系统会失效。

噪声的成因与影响

为什么存在噪声?量化交易大师佩里·考夫曼(Perry Kaufman)指出,噪声是市场无处不在的现象,无法消除,交易者必须学会应对。

  • 价格行为分类
    1. 有意义的价格行为:长期、有方向、有目的的运动(如从A点到B点)。
    2. 随机价格行为:短暂、无方向、无目的的波动(由大量交易者的短期操作引发)。

市场参与者在多个时间周期执行不同策略,短期操作的随机性导致噪声,而市场主导情绪推动长期趋势。

趋势跟踪策略为例,噪声会导致策略失效:

  • 噪声低时,策略能捕捉趋势并盈利;
  • 噪声高时,价格频繁来回波动,吞噬利润,甚至导致亏损(如频繁假突破、来回止损)。

噪声的量化与应用

衡量噪声的方法之一是效率比(由考夫曼开发),用于量化市场噪声高低:

  • 效率比高位:噪声低,适合趋势策略;
  • 效率比低位:噪声高,适合震荡或区间策略。

通过计算价格密度(价格在区间内的填充程度),可判断噪声水平:

  • 价格平稳地从区间一角到另一角:低噪声;
  • 价格频繁填充整个区间:高噪声。

策略细节(以多单为例,空单条件反向)

进场条件

  1. 无多单持仓;
  2. 收盘价跌破布林带下沿;
  3. 噪声过滤器触发:效率比<0.3(高噪声环境)。

出场条件

  1. 有多单持仓;
  2. 收盘价涨破布林带上沿,于下一根K线开盘价平仓。

止损设置

基于布林带上下轨距离的百分比:

  • 例:上下轨距离30点,止损比例50%,则止损15点(从开仓价向下计算)。

资金管理

固定手数或按风险比例计算,代码中包含持仓判断、价格规范等函数。

开仓图示

代码实现要点

主程序代码

cpp
#property strict
#include <Util/Util.mqh>
Util util;
// 不可以做参数优化的参数
input group "固定和输入通用部分"
sinput int MagicNum = 12345; // EA唯一编号
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 BbPeriod = 20;                 // BB 周期
input int SlBBandsRange = 70;            //SLBBandsRange(%)
input int efficiencyRatio = 10;          // 效率比率(%)                   
int barsTotal;
int handle;
double upperBuffer[];
double baseBuffer[];
double lowerBuffer[];
int initBalance;
double point;
MqlTick currentTick;

int efficiencyRatioHandle;
double efficiencyRatioBuffer[];

int OnInit()
{
    if (MagicNum < 0)
    {
        Alert("MagicNum<0");
        return INIT_PARAMETERS_INCORRECT;
    }
    if (BbPeriod < 1)
    {
        Alert("RSIPeriod<1");
        return INIT_PARAMETERS_INCORRECT;
    }
   
    handle = iBands(_Symbol,TimeFrame,BbPeriod,0,2,PRICE_CLOSE);
    efficiencyRatioHandle = iCustom(_Symbol,  TimeFrame,"Efficiency Ratio",efficiencyRatio,PRICE_CLOSE, 0.2);

    if (handle == INVALID_HANDLE)
    {
        Alert((string)MagicNum + " ", _Symbol, " handle 创建失败");
        return INIT_FAILED;
    }
    ArraySetAsSeries(upperBuffer, true);
    ArraySetAsSeries(baseBuffer, true);
    ArraySetAsSeries(lowerBuffer, true);

    ArraySetAsSeries(efficiencyRatioBuffer, true);

    Print(MagicNum, " ", _Symbol, "实例化成功");
    // 当用户没有打开参数对应的图表周期,自动帮他打开对应周期
    if (MQLInfoInteger(MQL_TESTER))
    {
        ChartSetInteger(0,CHART_SHOW_GRID,false);     
    }else{
        ChartSetSymbolPeriod(0,_Symbol, TimeFrame);
    }
    initBalance = SlParam;
    Comment("MagicNum:", MagicNum, " SlType: ", SlType, " SlParam: ", SlParam);

    return INIT_SUCCEEDED;
}
// 节省内存后 买VPS就可以买小一点
void OnDeinit(const int reason)
{
    if (handle != INVALID_HANDLE)
    {
        IndicatorRelease(handle);
    }
}
void OnTick()
{

    if (!util.isNewBar(_Symbol, TimeFrame, barsTotal))
        return;
      
    if(!util.isEnableTimezone()) return;  // 点差过大0 不交易
    datetime a = StringToTime("2012.01.6 5:00:00");
      if(iTime(_Symbol,PERIOD_CURRENT,0) == StringToTime("2012.01.6 5:00:00")){
    Print("debug");
  }
    point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
    int digits = (int) SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
    if (!SymbolInfoTick(_Symbol, currentTick))
    {
        Print("tick数据获取异常");
    }
    CopyBuffer(handle, 1, 0, 2, upperBuffer);
    CopyBuffer(handle, 2, 0, 2, lowerBuffer);
    CopyBuffer(efficiencyRatioHandle, 0, 0, 2, efficiencyRatioBuffer);


    int cntBuy, cntSell;
    if (!util.countOpenPositions(MagicNum, cntBuy, cntSell))
        return;

    double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
    double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
    double bullSignal = cntBuy==0 && iClose(_Symbol,TimeFrame,1) <= lowerBuffer[1] && MathAbs(efficiencyRatioBuffer[1])<0.2;
    if (bullSignal)
    {
        double bbRange = upperBuffer[1] - lowerBuffer[1];
        double slPoint = NormalizeDouble(bbRange*0.01 * SlBBandsRange,digits);
        double sl = currentTick.ask -slPoint;
        double tp = 0;
        if (!util.NormalizePrice(_Symbol, sl))
            return;
        if (!util.NormalizePrice(_Symbol, tp))
            return;
        double slp = SlParam;
        if (SlType == 4)
        {
            slp = util.calculateEachEaBalance(MagicNum, initBalance); // 子账户百分之一
        }
        double lotSize = util.calcLots(_Symbol, ask, sl, SlType, slp);
        trade.SetExpertMagicNumber(MagicNum);
        trade.PositionOpen(_Symbol, ORDER_TYPE_BUY, lotSize, currentTick.ask, sl, tp, "BBR BUY");
    }

    bool bearSignal = cntSell ==0 && iClose(_Symbol,TimeFrame,1) >= upperBuffer[1] && MathAbs(efficiencyRatioBuffer[1])<0.2;


    if (bearSignal)
    {
        double bbRange = upperBuffer[1] - lowerBuffer[1];
        double slPoint = NormalizeDouble(bbRange*0.01 * SlBBandsRange,digits);
        double sl = currentTick.bid + slPoint;
        double tp = 0;

        if (!util.NormalizePrice(_Symbol, sl))
            return;
        if (!util.NormalizePrice(_Symbol, tp))
            return;
        double slp = SlParam;
        if (SlType == 4)
        {
            slp = util.calculateEachEaBalance(MagicNum, initBalance); // 子账户百分之一
        }
        double lotSize = util.calcLots(_Symbol, bid, sl, SlType, slp);
        trade.SetExpertMagicNumber(MagicNum);
        trade.PositionOpen(_Symbol, ORDER_TYPE_SELL, lotSize, currentTick.bid, sl, tp, "BBR SELL");
    }
    // 出场
    if(!util.countOpenPositions(MagicNum, cntBuy, cntSell)) return; 
    if(cntBuy >0 && iClose(_Symbol,TimeFrame,1)>= upperBuffer[1] ){
        util.closePositions(MagicNum, POSITION_TYPE_BUY);
    } 
    if(cntSell >0 && iClose(_Symbol,TimeFrame,1)<= lowerBuffer[1] ){
        util.closePositions(MagicNum, POSITION_TYPE_SELL);
    }
}

副图效率指标源码

cpp
//+------------------------------------------------------------------
#property copyright   "mladen"
#property link        "mladenfx@gmail.com"
#property link        "https://www.mql5.com"
#property description "Directional efficiency ratio"
//+------------------------------------------------------------------
#property indicator_separate_window
#property indicator_buffers 4
#property indicator_plots   1
#property indicator_label1  "Efficiency ratio"
#property indicator_type1   DRAW_COLOR_LINE
#property indicator_color1  clrDarkGray,clrDeepSkyBlue,clrOrangeRed
#property indicator_width1  2
//--- input parameters
enum enMaTypes
  {
   ma_sma,    // Simple moving average
   ma_ema,    // Exponential moving average
   ma_smma,   // Smoothed MA
   ma_lwma    // Linear weighted MA
  };
input int                inpPeriod          = 14;          // ER period
input ENUM_APPLIED_PRICE inpPrice           = PRICE_CLOSE; // Price 
input double             inpLevels          = 0.25;        // Levels for trending/ranging 
input int                inpSmoothingPeriod = 5;           // Smoothing period
input enMaTypes          inpMaMethod        = ma_ema;      // Smoothing method
//--- buffers and global variables declarations
double val[],valc[],diff[],prices[];
string _avgNames[]={"SMA","EMA","SMMA","LWMA"};
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
   SetIndexBuffer(0,val,INDICATOR_DATA);
   SetIndexBuffer(1,valc,INDICATOR_COLOR_INDEX);
   SetIndexBuffer(2,diff,INDICATOR_CALCULATIONS);
   SetIndexBuffer(3,prices,INDICATOR_CALCULATIONS);
   IndicatorSetInteger(INDICATOR_LEVELS,2);
   IndicatorSetDouble(INDICATOR_LEVELVALUE,0,inpLevels);
   IndicatorSetDouble(INDICATOR_LEVELVALUE,1,-inpLevels);
//---
   IndicatorSetString(INDICATOR_SHORTNAME,"Efficiency ratio ("+_avgNames[inpMaMethod]+") ("+(string)inpPeriod+")");
   return (INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Custom indicator de-initialization function                      |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
   if(Bars(_Symbol,_Period)<rates_total) return(prev_calculated);

   int i=(int)MathMax(prev_calculated-1,1); for(; i<rates_total && !_StopFlag; i++)
     {
      prices[i] = getPrice(inpPrice,open,close,high,low,i,rates_total);
      diff[i]   = (i>0) ? prices[i]-prices[i-1] : 0;
      //-------------
      double _noise = MathAbs(diff[i]); int k=1; for(; k<inpPeriod && (i-k)>=0; k++) _noise+=MathAbs(diff[i-k]);
      double _efr   = (_noise!=0 && i>inpPeriod) ? (prices[i]-prices[i-inpPeriod+1])/_noise : 0;
      val[i] = iCustomMa(inpMaMethod,_efr,inpSmoothingPeriod,i,rates_total,0);
      valc[i]= (val[i]>inpLevels) ? 1 :(val[i]<-inpLevels) ? 2 : 0;
     }
   return (i);
  }
//+------------------------------------------------------------------+
//| Custom functions                                                 |
//+------------------------------------------------------------------+
#define _maInstances 1
#define _maWorkBufferx1 1*_maInstances
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
double iCustomMa(int mode,double price,double length,int r,int bars,int instanceNo=0)
  {
   switch(mode)
     {
      case ma_sma   : return(iSma(price,(int)length,r,bars,instanceNo));
      case ma_ema   : return(iEma(price,length,r,bars,instanceNo));
      case ma_smma  : return(iSmma(price,(int)length,r,bars,instanceNo));
      case ma_lwma  : return(iLwma(price,(int)length,r,bars,instanceNo));
      default       : return(price);
     }
  }

//
//
//
//
//
double workSma[][_maWorkBufferx1];
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
double iSma(double price,int period,int r,int _bars,int instanceNo=0)
  {
   if(ArrayRange(workSma,0)!=_bars) ArrayResize(workSma,_bars);

   workSma[r][instanceNo]=price;
   double avg=price; int k=1; for(; k<period && (r-k)>=0; k++) avg+=workSma[r-k][instanceNo];
   return(avg/(double)k);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
double workEma[][_maWorkBufferx1];
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
double iEma(double price,double period,int r,int _bars,int instanceNo=0)
  {
   if(ArrayRange(workEma,0)!=_bars) ArrayResize(workEma,_bars);

   workEma[r][instanceNo]=price;
   if(r>0 && period>1)
      workEma[r][instanceNo]=workEma[r-1][instanceNo]+(2.0/(1.0+period))*(price-workEma[r-1][instanceNo]);
   return(workEma[r][instanceNo]);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
double workSmma[][_maWorkBufferx1];
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
double iSmma(double price,double period,int r,int _bars,int instanceNo=0)
  {
   if(ArrayRange(workSmma,0)!=_bars) ArrayResize(workSmma,_bars);

   workSmma[r][instanceNo]=price;
   if(r>1 && period>1)
      workSmma[r][instanceNo]=workSmma[r-1][instanceNo]+(price-workSmma[r-1][instanceNo])/period;
   return(workSmma[r][instanceNo]);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
double workLwma[][_maWorkBufferx1];
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
double iLwma(double price,double period,int r,int _bars,int instanceNo=0)
  {
   if(ArrayRange(workLwma,0)!=_bars) ArrayResize(workLwma,_bars);

   workLwma[r][instanceNo] = price; if(period<1) return(price);
   double sumw = period;
   double sum  = period*price;

   for(int k=1; k<period && (r-k)>=0; k++)
     {
      double weight=period-k;
      sumw  += weight;
      sum   += weight*workLwma[r-k][instanceNo];
     }
   return(sum/sumw);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
double getPrice(ENUM_APPLIED_PRICE tprice,const double &open[],const double &close[],const double &high[],const double &low[],int i,int _bars)
  {
   switch(tprice)
     {
      case PRICE_CLOSE:     return(close[i]);
      case PRICE_OPEN:      return(open[i]);
      case PRICE_HIGH:      return(high[i]);
      case PRICE_LOW:       return(low[i]);
      case PRICE_MEDIAN:    return((high[i]+low[i])/2.0);
      case PRICE_TYPICAL:   return((high[i]+low[i]+close[i])/3.0);
      case PRICE_WEIGHTED:  return((high[i]+low[i]+close[i]+close[i])/4.0);
     }
   return(0);
  }
//+------------------------------------------------------------------+