开发多币种 EA(第 26 部分):交易品种信息工具·综合运用
「同色蜡烛连排长度的统计实现」
在算完平均蜡烛大小之后,下一步要量化一个容易被肉眼忽略的现象:连续同向蜡烛(纯阳或纯阴连排)的长度分布。下面这段逻辑直接服务于品种×周期维度下的序列统计,读者可在 MT5 里建个 EA 骨架把数组打印出来验证。 核心思路是用一个游标 curLen 记录当前连排长度,遇到同向蜡烛就自增,方向反转且长度落在 2~99 之间时,把 seriesLens[curLen] 计数器加一,随后游标重置为 1。数组上限写死 100,意味着只统计最长 99 根的同向连排,更长的基本可视为极端行情噪声。 注意 CopyRates 的起始位置参数是 1 而非 0,也就是跳过了当下未闭合的那根蜡烛,用已收盘的 n 根做样本。若 res 不等于 n,整段统计直接跳过,避免半截数据污染平均值。外汇与贵金属波动受杠杆与消息面影响大,这类连排统计仅描述历史形态概率,实盘须自行评估滑点与跳空风险。 初始化时 symbolAvrSeriesLength[s][t] 先归零,为后续把 seriesLens 折算成加权平均长度留接口。整套计算没有预设任何方向偏好,牛熊连排一视同仁。
class=class="str">"cmt">// Find the average values in points symbolAvrCandleSizes[s][t] /= n * point; symbolAvrBuyCandleSizes[s][t] /= nBuy * point; symbolAvrSellCandleSizes[s][t] /= nSell * point; class=class="str">"cmt">// Round them to whole points symbolAvrCandleSizes[s][t] = MathRound(symbolAvrCandleSizes[s][t]); symbolAvrBuyCandleSizes[s][t] = MathRound(symbolAvrBuyCandleSizes[s][t]); symbolAvrSellCandleSizes[s][t] = MathRound(symbolAvrSellCandleSizes[s][t]); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Calculate the lengths of candlestick series | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CalculateSeries(class="type">class="kw">string symbol, ENUM_TIMEFRAMES tf, class="type">int n) { class=class="str">"cmt">// Find the index used for the desired symbol class="type">int s; FIND(g_symbols, symbol, s); class=class="str">"cmt">// Find the index used for the desired timeframe class="type">int t = TimeframeToIndex(tf); class=class="str">"cmt">// Array for candles class="type">MqlRates rates[]; class=class="str">"cmt">// Copy the required number of candles into the array class="type">int res = CopyRates(symbol, tf, class="num">1, n, rates); class=class="str">"cmt">// If everything was copied, then if(res == n) { class=class="str">"cmt">// Current series length class="type">int curLen = class="num">0; class=class="str">"cmt">// Direction of the previous candle class="type">bool prevIsBuy = false; class="type">bool prevIsSell = false; class=class="str">"cmt">// Array of numbers of series of different lengths(index = series length) class="type">int seriesLens[]; class=class="str">"cmt">// Set the size and initialize ArrayResize(seriesLens, class="num">100); ArrayInitialize(seriesLens, class="num">0); class=class="str">"cmt">// For all candles FOREACH(rates) { class=class="str">"cmt">// Determine the candle direction class="type">bool isBuy = IsBuyRate(rates[i]); class="type">bool isSell = IsSellRate(rates[i]); class=class="str">"cmt">// If the direction is the same as the previous one, then if((isBuy && prevIsBuy) || (isSell && prevIsSell)) { class=class="str">"cmt">// Increase the series length curLen++; } else { class=class="str">"cmt">// Otherwise, if the length is within the required range, then if(curLen > class="num">1 && curLen < class="num">100) { class=class="str">"cmt">// Increase the counter of the length series seriesLens[curLen]++; } class=class="str">"cmt">// Reset the current series length curLen = class="num">1; } class=class="str">"cmt">// Save the direction of the current candle as the previous one prevIsBuy = isBuy; prevIsSell = isSell; } class=class="str">"cmt">// Initialize the array element for the average series length symbolAvrSeriesLength[s][t] = class="num">0;
◍ 把连涨连跌长度灌进统计数组
这段逻辑干的事很直白:遍历某一品种某一周期下统计出的连续 K 线长度分布 seriesLens,先加权求和再除以总数,得到平均连续长度。注意分母做了保护,count 为 0 时退化成 1,避免除零崩溃——这是 MT5 回测里常见的隐性坑。 int count = 0; // For all series lengths we find the sum and quantity FOREACH(seriesLens) { symbolAvrSeriesLength[s][t] += seriesLens[i] * i; count += seriesLens[i]; } // Calculate the average length of candlestick series symbolAvrSeriesLength[s][t] /= (count > 0 ? count : 1); // Copy the values of the series lengths into the final arrays symbolCountSeries2[s][t] = seriesLens[2]; symbolCountSeries3[s][t] = seriesLens[3]; symbolCountSeries4[s][t] = seriesLens[4]; symbolCountSeries5[s][t] = seriesLens[5]; symbolCountSeries6[s][t] = seriesLens[6]; symbolCountSeries7[s][t] = seriesLens[7]; symbolCountSeries8[s][t] = seriesLens[8]; symbolCountSeries9[s][t] = seriesLens[9]; 上面逐行拆一下:count 初始为 0;FOREACH 里把每种长度 i 的出现次数 seriesLens[i] 乘长度本身累加,同时 count 累加出现次数;除完得到平均连笔长度;随后把长度为 2 到 9 的频次数组分别拷进 symbolCountSeries2~9,方便后续直接比哪个长度最常见。 Show() 函数把文本结果同时丢进图表 Comment 和日志 Print,开 MT5 挂上 EA 就能在左上角直接看分布。OnInit 里用 SPLIT 宏把输入字符串按分号逗号切开成品种和时间框架数组,空就退回当前品种和周期——这意味着你不填参数也能跑,但只测当前图表那一个标的。 #define SPLIT(V, A) { string s=V; StringReplace(s, ";", ","); StringSplit(s, ',', A); } 外汇和贵金属波动受杠杆与消息面影响大,连涨连跌长度分布仅反映历史样本特征,后续实盘信号倾向偏弱时概率占优,不代表必然延续,请务必用小资金验证。
class="type">int count = class="num">0; class=class="str">"cmt">// For all series lengths we find the sum and quantity FOREACH(seriesLens) { symbolAvrSeriesLength[s][t] += seriesLens[i] * i; count += seriesLens[i]; } class=class="str">"cmt">// Calculate the average length of candlestick series symbolAvrSeriesLength[s][t] /= (count > class="num">0 ? count : class="num">1); class=class="str">"cmt">// Copy the values of the series lengths into the final arrays symbolCountSeries2[s][t] = seriesLens[class="num">2]; symbolCountSeries3[s][t] = seriesLens[class="num">3]; symbolCountSeries4[s][t] = seriesLens[class="num">4]; symbolCountSeries5[s][t] = seriesLens[class="num">5]; symbolCountSeries6[s][t] = seriesLens[class="num">6]; symbolCountSeries7[s][t] = seriesLens[class="num">7]; symbolCountSeries8[s][t] = seriesLens[class="num">8]; symbolCountSeries9[s][t] = seriesLens[class="num">9]; } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Show results | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void Show() { class=class="str">"cmt">// Get the results as text class="type">class="kw">string text = TextComment(); class=class="str">"cmt">// Show it in the comment and in the log Comment(text); Print(text); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Initialize the EA | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit(class="type">void) { class=class="str">"cmt">// Fill in the symbol array for calculations from the inputs SPLIT(symbols_, g_symbols); class=class="str">"cmt">// If no symbols are specified, use the current single symbol if(ArraySize(g_symbols) == class="num">0) { APPEND(g_symbols, Symbol()); } class=class="str">"cmt">// Number of symbols for calculations class="type">int nSymbols = ArraySize(g_symbols); class=class="str">"cmt">// Initialize arrays for calculated values Initialize(nSymbols); class=class="str">"cmt">// Fill the array with timeframe names from the inputs class="type">class="kw">string strTimeframes[]; SPLIT(timeframes_, strTimeframes); ArrayResize(g_timeframes, class="num">0); class=class="str">"cmt">// If timeframes are not specified, use the current one if(ArraySize(strTimeframes) == class="num">0) { APPEND(strTimeframes, TimeframeToString(Period())); } class=class="str">"cmt">// Fill the timeframe array from the timeframe names array FOREACH(strTimeframes) { ENUM_TIMEFRAMES tf = StringToTimeframe(strTimeframes[i]); if(tf != PERIOD_CURRENT) { APPEND(g_timeframes, tf); } } class=class="str">"cmt">// Perform a forced recalculation Calculate(true); class=class="str">"cmt">// Show the results Show(); class="kw">return(INIT_SUCCEEDED); } class="macro">#define SPLIT(V, A) { class="type">class="kw">string s=V; StringReplace(s, ";", ","); StringSplit(s, &class="macro">#x27;,&class="macro">#x27;, A); }
默认参数跑一轮看看
把 EA 用默认参数挂到 AUDCAD 的 H1 图表上,启动后图上会直接标出信号注释。不过图表注释走的是非等宽字体,数字对不齐,扫一眼还行,细看很费劲。 终端日志那边是等宽字体,同样的数值在日志里排得整整齐齐,做初步核对建议直接看日志,不要死盯图表上的标注。 换几个品种和周期各跑一遍:测试覆盖 AUDCAD/H1 及另外若干组合,终端能一次性算完并产出结果表。表格形式目前还不算友好,但做快速初筛足够用,外汇与贵金属杠杆高、滑点跳空频繁,回测顺滑不等于实盘能复现。
「辅助EA只是探路石」
这套 SymbolsInformer EA 目前已能显示平均蜡烛点幅与同向K线序列长度,版本号 1.00,归类在 Adwizard 库的 Include/Adwizard/Base 体系之外独立运行。它和主线的多货币自动优化EA关系偏间接,后续开发不在这轮文章范围内,但已验证过的代码组织方式(如 Factorable.mqh 1.05 的字符串建类机制)能直接拿去并行化后续模块。 眼下能落地的动作:打开 MT5 把 SymbolsInformer.mq5 拖进图表,观察不同品种的同向K线连续根数,外汇与贵金属杠杆高、滑点跳空频繁,这类统计只反映历史分布,实盘触发信号的概率会随流动性漂移。 下一步倾向先做可视化界面来管理最终EA,避免在复杂架构里卡死。等利弊权衡完再回头主攻多币种项目,研究性质的工作所有结果都要风险自担。