© 本贴为 xxtz 原创/首发,严禁抄袭!
这里记录学习MT5中级知识的通过MT5 EA向导建立EA过程,文中也提到通过改写系统自带的signal代码部分算法来实现自己创建signal。
非计算机本科,爱好编程而已,所以解释不很周全、错误之处,恳请善意指出。
MT5基于C++的,其中使用了大量的类,文中解析代码会对类层级间的继承派生关系及其中的成员函数做简要介绍,会大概有个朦胧印象,通过解析代码,可以分清哪些是为了程序更加健壮的,哪些是核心代码需要仔细斟酌的,具体细节请参看系统自带源码。
下面是有关EA向导生成代码涉及到类的继承派生关系等级图
EA使用MQL5向导产生基于如下基类:
CExpertTrade:执行交易操作
CIndicators:管理在EA使用的指标和时间序列数据(主要是开高低收价,成交量等等)
CSymbolInfo: 获得有关交易品种的信息
CAccountInfo: 获得交易账户状态和信息
CPositionInfo:获得已开仓订单信息
COrderInfo: 获得挂单信息
CExpertTrade类是EA向导的主要引擎,其产生的实例包括3个类的实例,分别是:
CExpertSignal: 主要参数交易信号提供给EA关于进出市场的可能性,价格和设置保护性订单。
CExpertMoney:主要是管理资金和风险:计算开仓量。
CExpertTrailing:开仓以后的管理,告诉EA是否要对订单修改保护机制。
内置交易信号标准库存放在.../MQL5/Include/Expert/Signal文件夹下,自己编写的信号不要放在这个文件夹下,可在.../MQL5/Include/Expert/文件下新建文件夹专门用于自己定制的signal程序。
以下以系统自带的SignalMACD.mqh为例解析源码,顺便说说如何编写自己的signal文件。
1. 程序开始让预处理器包括CExperSignal基类,定制signal照抄吧。
#include <Expert\ExpertSignal.mqh>
复制代码
2. 接下来类描述,定制signal这段要改写以确保MQL5向导在”下一步”到选择signal时可见(如Title,Name等),改变成自己的类描述。定制signal参数描述块(parameter)要匹配即将使用的参数。// wizard description start
//+-----------------------------------------------------------------------------+
//| Description of the class |
//| Title=Signals of oscillator 'MACD' |
//| Type=SignalAdvanced |
//| Name=MACD |
//| ShortName=MACD |
//| Class=CSignalMACD |
//| Page=signal_macd |
//| Parameter=PeriodFast,int,12,Period of fast EMA |
//| Parameter=PeriodSlow,int,24,Period of slow EMA |
//| Parameter=PeriodSignal,int,9,Period of averaging of difference |
//| Parameter=Applied,ENUM_APPLIED_PRICE,PRICE_CLOSE,Prices series |
//+------------------------------------------------------------------------------+
// wizard description end
复制代码
3. 好的编程习惯一般都提供一些解释便于更好的理解代码。
//+------------------------------------------------------------------+
//| Class CSignalMACD. |
//| Purpose: Class of generator of trade signals based on |
//| the 'Moving Average Convergence/Divergence' oscillator. |
//| Is derived from the CExpertSignal class. |
//+------------------------------------------------------------------+
复制代码
4. 以下正式进入类的定义。
class CSignalMACD : public CExpertSignal
{
protected:
CiMACD m_MACD; // object-oscillator
//--- adjusted parameters
int m_period_fast; // the "period of fast EMA" parameter of the oscillator
int m_period_slow; // the "period of slow EMA" parameter of the oscillator
int m_period_signal; // the "period of averaging of difference" parameter of the oscillator
ENUM_APPLIED_PRICE m_applied; // the "price series" parameter of the oscillator
//--- "weights" of market models (0-100)
int m_pattern_0; // model 0 "the oscillator has required direction"
int m_pattern_1; // model 1 "reverse of the oscillator to required direction"
int m_pattern_2; // model 2 "crossing of main and signal line"
int m_pattern_3; // model 3 "crossing of main line an the zero level"
int m_pattern_4; // model 4 "divergence of the oscillator and price"
int m_pattern_5; // model 5 "double divergence of the oscillator and price"
//--- variables
double m_extr_osc[10]; // array of values of extremums of the oscillator
double m_extr_pr[10]; // array of values of the corresponding extremums of price
int m_extr_pos[10]; // array of shifts of extremums (in bars)
uint m_extr_map; // resulting bit-map of ratio of extremums of the oscillator and the price
public:
CSignalMACD(void);
~CSignalMACD(void);
//--- methods of setting adjustable parameters
void PeriodFast(int value) { m_period_fast=value; }
void PeriodSlow(int value) { m_period_slow=value; }
void PeriodSignal(int value) { m_period_signal=value; }
void Applied(ENUM_APPLIED_PRICE value) { m_applied=value;}
//--- methods of adjusting "weights" of market models
void Pattern_0(int value) { m_pattern_0=value; }
void Pattern_1(int value) { m_pattern_1=value; }
void Pattern_2(int value) { m_pattern_2=value; }
void Pattern_3(int value) { m_pattern_3=value; }
void Pattern_4(int value) { m_pattern_4=value; }
void Pattern_5(int value) { m_pattern_5=value; }
//--- method of verification of settings
virtual bool ValidationSettings(void);
//--- method of creating the indicator and timeseries
virtual bool InitIndicators(CIndicators *indicators);
//--- methods of checking if the market models are formed
virtual int LongCondition(void);
virtual int ShortCondition(void);
protected:
//--- method of initialization of the oscillator
bool InitMACD(CIndicators *indicators);
//--- methods of getting data
double Main(int ind) { return(m_MACD.Main(ind)); }
double Signal(int ind) { return(m_MACD.Signal(ind)); }
double DiffMain(int ind) { return(Main(ind)-Main(ind+1)); }
int StateMain(int ind);
double State(int ind) { return(Main(ind)-Signal(ind)); }
bool ExtState(int ind);
bool CompareMaps(int map,int count,bool minimax=false,int start=0);
};
复制代码
4.1 CSignalMACD继承于CExpertSignal
首先定义CiMACD m_MACD; // object-oscillator 这句中CiMACD是标准库的技术指标类
但如果自定义signal 可能根据自定义的指标编写定义自己的交易指标类:如CSignalMyCustInd:
class CSignalMyCustInd : public CExpertSignal
{
protected:
CiCustom m_mci; // 自己定义的指标对象m_mci
其中CiCustom类(继承于CIndicator)它提供创建、设置和取得定制指标的数据作用。
Cindicator类让技术指标类的实例的创建、存储和管理技术指标变得方便。Cindicator类中有定义
public:
//--- method for creating
bool Create(const string symbol,const ENUM_TIMEFRAMES period,
const ENUM_INDICATOR type,const int num_params,const MqlParam ¶ms[]);
protected:
//--- methods of tuning
bool CreateBuffers(const string symbol,const ENUM_TIMEFRAMES period,const int buffers);
virtual bool Initialize(const string symbol,const ENUM_TIMEFRAMES period,
const int num_params,const MqlParam ¶ms[]) {return(false);}
//+------------------------------------------------------------------+
//| Creation of the indicator with universal parameters |
//+------------------------------------------------------------------+
bool CIndicator::Create(const string symbol,const ENUM_TIMEFRAMES period,
const ENUM_INDICATOR type,const int num_params,const MqlParam ¶ms[])
{
//--- check history
if(!SetSymbolPeriod(symbol,period))
return(false);
//--- create
m_handle=IndicatorCreate(symbol,period,type,num_params,params);
//--- check result
if(m_handle==INVALID_HANDLE)
return(false);
//--- idicator successfully created
if(!Initialize(symbol,period,num_params,params))
{
//--- initialization failed
IndicatorRelease(m_handle);
m_handle=INVALID_HANDLE;
return(false);
}
//--- ok
return(true);
}
*************************************
SetSymbolPeriod函数,为了获取symbol,period,及PeriodToTimframeFlag(就是获取现在得到的period,比如15min,在ENUM_TIMEFRAMES这个枚举时间周期从小到大排列(静态数组_p_int[])的位置,15min位置是8,前面有1,2,3,4,5,6,10,12分钟,算法是:
//--- cycle for all timeframes
for(int i=0;i<ArraySize(_p_int);i++)
if(period==_p_int)
{
//--- at the same time generate the flag of the working timeframe
m_timeframe_flags=((int)1)<<i;// 位左移运算,位移运算符优先级大于比较运算符,小于+-运算符
return;
}
*************************************
Cindicators类中有定义*Create方法
//--- method for creation
CIndicator *Create(const string symbol,const ENUM_TIMEFRAMES period,
const ENUM_INDICATOR type,const int count,const MqlParam ¶ms[]);
//+------------------------------------------------------------------+
//| Indicator creation |
//+------------------------------------------------------------------+
CIndicator *CIndicators::Create(const string symbol,const ENUM_TIMEFRAMES period,
const ENUM_INDICATOR type,const int count,const MqlParam ¶ms[])
{
CIndicator *result=NULL;
//---
switch(type)
{
case IND_AC:
//--- Identifier of "Accelerator Oscillator"
if(count==0)
result=new CiAC;
break;
case IND_AD:
//--- Identifier of "Accumulation/Distribution"
if(count==1)
result=new CiAD;
break;
......
case IND_VOLUMES:
//--- Identifier of "Volumes"
if(count==1)
result=new CiVolumes;
break;
//--- Identifier of "Custom"
case IND_CUSTOM:
if(count>0)
result=new CiCustom;
break;
}
if(result!=NULL)
{
if(result.Create(symbol,period,type,count,params))
Add(result);
else
{
delete result;
result=NULL;
}
}
//---
return(result);
}
类指针可以指向多个不同的对象,实现多态.
-----------------------------------------------------
如果是自编的定制指标,CiCustom类来实例化对象;而真正锁定定制函数在成员函数“Init指标名称(CIndicators *indicators)”方法中调用indicator中的Create方法(标准库指标有自己定义的类的Create方法,它也是从CIndicator继承来),设置parameters[0].string_value="Examples\\自定义指标名称.ex5"指定其引用的具体位置来锁定。
CIndicator::Create方法
//+------------------------------------------------------------------+
//| Creation of the indicator with universal parameters |
//+------------------------------------------------------------------+
bool CIndicator::Create(const string symbol,const ENUM_TIMEFRAMES period,
const ENUM_INDICATOR type,const int num_params,const MqlParam ¶ms[])
{
//--- check history
if(!SetSymbolPeriod(symbol,period))//后面有详细介绍
return(false);
//--- create
m_handle=IndicatorCreate(symbol,period,type,num_params,params);
//--- check result
if(m_handle==INVALID_HANDLE)
return(false);
//--- idicator successfully created
if(!Initialize(symbol,period,num_params,params))
{
//--- initialization failed
IndicatorRelease(m_handle);
m_handle=INVALID_HANDLE;
return(false);
}
//--- ok
return(true);
}4.2 CSignalMACD< 继承CExpertSignal 继承于<CExpertBase,
这3个类中都有InitIndicators方法,CSignalMACD子类的该方法会调用父类的该方法,然后在调用子类自己的Init***方法,
4.2.1 CExpertBase::InitIndicators(indicators)主要是
1.检查初始化阶段[1.First(初始化赋值symbo,period,m_adjusted_point);2.Tuning(检查symbol和period是否正2.确);3.Validation;3.Complete]是否在INIT_PHASE_VALIDATION,
symbol和period是否正确,
3.时间序列数据(价格和其他)是否就位了,
4.最后m_init_phase=INIT_PHASE_COMPLETE];
4.2.2 CExpertSignal::InitIndicators(CIndicators *indicators) 获得进出场条件需要的时间序列数据SetPriceSeries
4.2.3 CSignalMACD::InitIndicators(CIndicators *indicators) 调用父类同名方法,再调用具体指标的Init***方法
4.2.4 Init***,这里是InitMACD,它完成:
4.2.4.1.indicators.Add(GetPointer(m_MACD))指标加入集合中,
4.2.4.2.m_MACD.Create 由于m_MACD是CiMACD实例,在CiMACD::Create会创建句柄iMACD,
4.2.4.3.再Initialize(symbol,period,fast_ema_period,slow_ema_period,signal_period,applied),该方法会
4.2.4.3.1createbuffers(symbol,period,2) 该方法其中核心代码[result&=Add(new CIndicatorBuffer) m_buffers_total=buffers,其中m_buffers_total是CSeries类定义的成员变量]然后设name,status(指标描述),周期和价格设定
4.2.4.4.//--- create buffers
((CIndicatorBuffer*)At(0)).Name("MAIN_LINE");
((CIndicatorBuffer*)At(1)).Name("SIGNAL_LINE");
**注
class CArrayObj继承于public CArray类中定义了,它在共有方法定义了 CObject *At(const int index) const;//--- method of access to thre array
以及保护变量 CObject *m_data[];
CObject类中定义了CObject类型的指针变量 *m_prev和*m_next,
//| Access to data in the specified position
At方法代码: |
CObject *CArrayObj::At(const int index) const
{
//--- check
if(index<0 || index>=m_data_total)
return(NULL);
//--- result
return(m_data[index]);
}
4.3. //--- adjusted parameters 在生成类的保护变量区中的可调变量(如 int m_period_fast)应与参数描述块一致。
定制signal时更加自己信号相应指标参数
4.4. //--- "weights" of market models (0-100) 部分是给交易信号的各种交易模型以相应的权重。
也可以不给各种pattern信号相应权重。
这里可以看到有6个model,从注释中可以大概了解各个模型意思
model 0 "the oscillator has required direction" 震荡指标有了所需要的开仓方向
model 1 "reverse of the oscillator to required direction" 震荡指标反转到所需要的开仓方向
model 2 "crossing of main and signal line" MACD线与macd的signal线交叉
model 3 "crossing of main line an the zero level" MACD线与0轴交叉
model 4 "divergence of the oscillator and price" 震荡指标与价格发生背离
model 5 "double divergence of the oscillator and price" 震荡指标与价格双背离
4.4 //--- variables 给出了震荡指标的极值、该极值对应的价格、该极值偏移K线根数、震荡指标与其价格的位掩码结果(后面会阐述意思)
5.公有区成员函数 public:
构建和析构函数、可调参数的设定方法(与可调变量定义个数一致);交易模型可调权重方法;设置验证方法;创建指标和时间序列数据方法,检查生成的模型的方法。
构建CSignalMyCustInd(void);和析构函数~CSignalMyCustInd(void);、可调参数的设定方法(与可调变量定义个数一致);交易模型可调权重方法在定制交易信号时做相应修改其他基本相同照抄。
6. 保护区成员函数:一般取得数据的短小成员函数用内联函数,有的用CIndicator类中的GetData方法直接获取。
6.1 GetData方法有3个同名函数的重载,分别是1.指定开始位置和元素的个数CopyBuffer;2.指定开始时间和结束时间CopyBuffer,3.直接指定元素位置通过buffer.At(index)获取(其中CIndicatorBuffer *buffer=At(buffer_num);)(At方法从父类Series调用它的父类CArrayDouble的方法,形如m_data[index]据数列下标取得数据,
6.2 CIndicatorBuffers类(里面成员变量name,offset,主要成员函数offset,Name,At,Refresh)继承于CDoublebuffer(里面成员变量symbol,period,size,成员函数Size,At,Refresh,SetSymbolPeriod),CIndicatorBuffers类为获得技术指标数据而生)
定制信号这块都需要根据自己的信号修改。
7.类声明以外的函数定义:
1. 构建函数://--- initialization of protected data//+------------------------------------------------------------------+
//| Constructor |
//+------------------------------------------------------------------+
CSignalMACD::CSignalMACD(void) : m_period_fast(12),
m_period_slow(24),
m_period_signal(9),
m_applied(PRICE_CLOSE),
m_pattern_0(10),
m_pattern_1(30),
m_pattern_2(80),
m_pattern_3(50),
m_pattern_4(60),
m_pattern_5(100)
{
//--- initialization of protected data
m_used_series=USE_SERIES_HIGH+USE_SERIES_LOW;
}
复制代码
主要是参数的初始化,一般都包括一句使用时间序列数据,例如m_used_series=USE_SERIES_OPEN+USE_SERIES_HIGH+USE_SERIES_LOW+USE_SERIES_CLOSE;
m_used_series是有在class CExpertBase定义的
if(m_other_symbol || m_other_period) return(0);
return(m_used_series);
USE_SERIES_OPEN是在class CExpertBase定义的宏,例如:
#define IS_OPEN_SERIES_USAGE ((m_used_series&USE_SERIES_OPEN)!=0)
自定义的信号可写成:CSignalMyCustInd::CSignalMyCustInd(void) , m_used_series根据策略需要使用的价格数据填入。
8.析构函数:一般为空
//+------------------------------------------------------------------+
//| Destructor |
//+------------------------------------------------------------------+
CSignalMACD::~CSignalMACD(void)
{
}
复制代码
自定义的信号可写成:CSignalMyCustInd::~CSignalMyCustInd(void)(){ }
9.ValidationSettings(void)
//+------------------------------------------------------------------+
//| Validation settings protected data. |
//+------------------------------------------------------------------+
bool CSignalMACD::ValidationSettings(void)
{
//--- validation settings of additional filters
if(!CExpertSignal::ValidationSettings())
return(false);
//--- initial data checks
if(m_period_fast>=m_period_slow)
{
printf(__FUNCTION__+": slow period must be greater than fast period");
return(false);
}
//--- ok
return(true);
}
复制代码
[先通过CExpertBase::Init(CSymbolInfo *symbol,ENUM_TIMEFRAMES period,double point) 对symbol和period赋值后,确保不是othersymbol和otherperiod(非MT5指定的时间周期),判断指标参数错误有时也放在这里,比如这里的 if(m_period_fast>=m_period_slow)
定制信号,函数名改下bool CSignalMyCustInd::ValidationSettings(void),没其他特别的事情照抄。
10. InitIndicators(CIndicators *indicators):判断validationSeting通过否,时间序列数据是否就位了;//+------------------------------------------------------------------+
//| Create indicators. |
//+------------------------------------------------------------------+
bool CSignalMACD::InitIndicators(CIndicators *indicators)
{
//--- check of pointer is performed in the method of the parent class
//---
//--- initialization of indicators and timeseries of additional filters
if(!CExpertSignal::InitIndicators(indicators))
return(false);
//--- create and initialize MACD oscilator
if(!InitMACD(indicators))
return(false);
//--- ok
return(true);
}
复制代码
定制信号,查找替换,函数名MACD替换为MyCustInd,InitMACD替换成InitMyCustomIndicator。
这两行代码干了什么事情,在第4项有较多讲述。
11. Init指标名称(CIndicators *indicators):把MACD加入指标集合中,检查symbol和period设置正确否,创建指标句柄。//+------------------------------------------------------------------+
//| Initialize MACD oscillators. |
//+------------------------------------------------------------------+
bool CSignalMACD::InitMACD(CIndicators *indicators)
{
//--- add object to collection
if(!indicators.Add(GetPointer(m_MACD)))
{
printf(__FUNCTION__+": error adding object");
return(false);
}
//--- initialize object
if(!m_MACD.Create(m_symbol.Name(),m_period,m_period_fast,m_period_slow,m_period_signal,m_applied))
{
printf(__FUNCTION__+": error initializing object");
return(false);
}
//--- ok
return(true);
}
复制代码
这里直接调用了CiMACD类的Create方法(系统帮咱编好了,不再需要设置参数啥的啦)。但定制信号这里要先设置参数parameter的type和值string_value或integer.value等,调用Indicator类中的Create方法,之前有详述,这里要加快,后面有关键的信号如何代码实现,那里烧脑要啰嗦啰嗦,是核心内容,写了这么多还没到核心内容 。
12. 从这开始进入关键的进出场条件的判断阶段了,前面全部是铺垫的检查准备数据阶段。
检查指标的状态,这个不是所有策略都需要的。
//+------------------------------------------------------------------+
//| Check of the oscillator state. |
//+------------------------------------------------------------------+
int CSignalMACD::StateMain(int ind)
{
int res=0;
double var;
//---
for(int i=ind;;i++)
{
if(Main(i+1)==EMPTY_VALUE)
break;
var=DiffMain(i); // 第i根K线与前一根K线MACD差值
if(res>0) //之前的MACD值是延续第i根K线MACD差值方向
{
if(var<0)
break;
res++; // MACD值不减少,res++
continue;
}
if(res<0)
{
if(var>0)
break;
res--; // MACD值不增加,res--
continue;
}
if(var>0) // MACD值增加,res++
res++;
if(var<0)
res--; // MACD值减少,res++
}
//---
return(res);
}
复制代码
返回连续的(与上根K线MACD差值为0也算同趋势)保持第i根K线的MACD走势的之前(偏移)的第几根K线
13. 英文注释段大概解释了这个函数的目的。
//+------------------------------------------------------------------+
//| Extended check of the oscillator state consists |
//| in forming a bit-map according to certain rules, |
//| which shows ratios of extremums of the oscillator and price. |
//+------------------------------------------------------------------+
bool CSignalMACD::ExtState(int ind)
{
//--- operation of this method results in a bit-map of extremums
//--- practically, the bit-map of extremums is an "array" of 4-bit fields
//--- each "element of the array" definitely describes the ratio
//--- of current extremums of the oscillator and the price with previous ones
//--- purpose of bits of an element of the analyzed bit-map
//--- bit 3 - not used (always 0)
//--- bit 2 - is equal to 1 if the current extremum of the oscillator is "more extreme" than the previous one
//--- (a higher peak or a deeper valley), otherwise - 0
//--- bit 1 - not used (always 0)
//--- bit 0 - is equal to 1 if the current extremum of price is "more extreme" than the previous one
//--- (a higher peak or a deeper valley), otherwise - 0
//--- in addition to them, the following is formed:
//--- array of values of extremums of the oscillator,
//--- array of values of price extremums and
//--- array of "distances" between extremums of the oscillator (in bars)
//--- it should be noted that when using the results of the extended check of state,
//--- you should consider, which extremum of the oscillator (peak or valley)
//--- is the "reference point" (i.e. was detected first during the analysis)
//--- if a peak is detected first then even elements of all arrays
//--- will contain information about peaks, and odd elements will contain information about valleys
//--- if a valley is detected first, then respectively in reverse
int pos=ind,off,index;
uint map; // intermediate bit-map for one extremum
//---
m_extr_map=0;
for(int i=0;i<10;i++)
{
off=StateMain(pos); // 距离pos(初值是ind)的MACD极值找到了,赋给off
if(off>0)
{
//--- minimum of the oscillator is detected
pos+=off; //pos=pos+off,距离当前是pos根K线
m_extr_pos[i]=pos; // 记录MACD极值的位置
m_extr_osc[i]=Main(pos); // 记录MACD极值的价格
if(i>1)//如果i的前2根k线或以前
{
m_extr_pr[i]=m_low.MinValue(pos-2,5,index);//第pos-2根K线往前5根K线最低价,如果第//2根K线是极值,比较范围是包括当前价的(即使tick工作模式是FALSE)
//--- form the intermediate bit-map
map=0;
if(m_extr_pr[i-2]<m_extr_pr[i])
map+=1; // set bit 0 ,二进制 0001, 第i个极值价格小于之前2位的极值价格
if(m_extr_osc[i-2]<m_extr_osc[i])
map+=4; // set bit 2 二进制 0100, 第i个极值MACD值小于之前2位的MACD极值
//--- add the result
m_extr_map+=map<<(4*(i-2)); //map值左移4*(i-2)位
}
else
m_extr_pr[i]=m_low.MinValue(pos-1,4,index);
//当前根或上根K线是最低价,从上根或上上根K线起往前4根K线范围的最低价赋给 //m_extr_pr[i]
}
else
{
//--- maximum of the oscillator is detected
pos-=off;
m_extr_pos[i]=pos;
m_extr_osc[i]=Main(pos);
if(i>1)
{
m_extr_pr[i]=m_high.MaxValue(pos-2,5,index);
//--- form the intermediate bit-map
map=0;
if(m_extr_pr[i-2]>m_extr_pr[i])
map+=1; // set bit 0
if(m_extr_osc[i-2]>m_extr_osc[i])
map+=4; // set bit 2
//--- add the result
m_extr_map+=map<<(4*(i-2));
}
else
m_extr_pr[i]=m_high.MaxValue(pos-1,4,index);
}
}
//---
return(true);
}
复制代码
通过4位二进制位图每个位的0或1的取值来描述MACD极值及其对应的价格
第0位等于1--当下价格比前面更加极端(更高或更低),0--不是更极端。
第1位等于0--没使用,总是0
第2位等于1--当下的MACD比前面更加极端(更高或更低),0--不是更极端。
第3位-- 0-- 没使用,总是0
该方法会产生3个数组,MACD极端值数组,价格极端值数组,相邻极端值K线间距数组。
有趣的是,需要考虑哪种MACD的极值作为参考点。 如第一次发现了最高值那么数组中的偶数下标元素都包含了该最高值的信息,如果第一次发现的是最低值,奇数下标元素都包含了该最低值的信息。
因为它在最近的10根K线里寻找和记录波峰和波谷,她们总是间隔产生的。
看什么时候有时间再接着写......