美元和欧元指数图表 MetaTrader 5 服务示例·综合运用
(3/3)·把 USDX 与 EURX 的加权几何均值算进 MT5 服务,前两步搭好函数与任务,这回跑通整条链路
「合成货币对的开高低是怎么算出来的」
做篮子型合成品种(比如自定义美元指数)时,单根 M1 K线的开高低不是简单相加,而是按各成分权重做几何加权。核心公式里先放一个 MAIN_COEFF 常数,再对每个成分品种的对应价格取权次幂连乘,外汇与贵金属合成均属高风险,权重配错图形会整体变形。 下面这段是计算函数的主体逻辑:先取各品种 Open 填入 values[],若总 tick_volume 为 0 说明这一分钟没数据直接返 false;随后 open 以 MAIN_COEFF 打底,循环乘上 MathPow(成分价, 权重)。High、Low 同理换取值类型重算一遍。 代码里 BASKET_SIZE 是篮子品种数,ExtWeights[i].weight 是第 i 个品种的权重,GetRateValue 负责按 time 和 VALUE_OPEN/HIGH/LOW 捞出对应价。你在 MT5 里改权重数组后重跑,合成品种的分时波动斜率会明显变化,可用历史数据比对验证。
class="type">bool CalculateRate(class="type">MqlRates& rate,str_rates& symbols_rates[]) { class="type">class="kw">double values[BASKET_SIZE]={class="num">0}; class="type">long tick_volume=class="num">0; class="type">int i; class=class="str">"cmt">//--- get Open prices of all symbols of the instrument basket into the values[] array for(i=class="num">0; i<BASKET_SIZE; i++) values[i]=GetRateValue(tick_volume,symbols_rates[i],rate.time,VALUE_OPEN); class=class="str">"cmt">//--- if the tick volume is zero, then there is no data for this minute - class="kw">return &class="macro">#x27;class="kw">false&class="macro">#x27; if(tick_volume==class="num">0) class="kw">return(class="kw">false); class=class="str">"cmt">//--- write down the total volume of all timeseries rate.tick_volume=tick_volume; class=class="str">"cmt">//--- calculate the Open price based on the prices and weights of all instruments in the basket rate.open=MAIN_COEFF; for(i=class="num">0; i<BASKET_SIZE; i++) rate.open*=MathPow(values[i],ExtWeights[i].weight); class=class="str">"cmt">//--- calculate the High price based on the prices and weights of all instruments in the basket for(i=class="num">0; i<BASKET_SIZE; i++) values[i]=GetRateValue(tick_volume,symbols_rates[i],rate.time,VALUE_HIGH); rate.high=MAIN_COEFF; for(i=class="num">0; i<BASKET_SIZE; i++) rate.high*=MathPow(values[i],ExtWeights[i].weight); class=class="str">"cmt">//--- calculate the Low price based on the prices and weights of all instruments in the basket for(i=class="num">0; i<BASKET_SIZE; i++) values[i]=GetRateValue(tick_volume,symbols_rates[i],rate.time,VALUE_LOW); rate.low=MAIN_COEFF; for(i=class="num">0; i<BASKET_SIZE; i++) rate.low*=MathPow(values[i],ExtWeights[i].weight);
◍ 篮子加权合成报价的具体算法
合成一篮子货币(类似美元指数)的收盘价,核心是用各成分货币的权重做幂乘。代码里先把 MAIN_COEFF 作为基底,再对每一种货币对的取值做 MathPow(values[i], weight) 连乘,权重为负代表该货币对与篮子反向。 下面这段循环就是合成 close 的关键:先取每个品种在对应时间的收盘价,再逐一对基底做幂乘累积。 for(i=0; i<BASKET_SIZE; i++) values[i]=GetRateValue(tick_volume,symbols_rates[i],rate.time,VALUE_CLOSE); rate.close=MAIN_COEFF; for(i=0; i<BASKET_SIZE; i++) rate.close*=MathPow(values[i],ExtWeights[i].weight); return(CheckRate(rate)); 美元指数经典公式可直接对照验证:基底常数 50.14348112,EURUSD 权重 -0.576、USDJPY 权重 0.136、GBPUSD 权重 -0.119、USDCAD 权重 0.091、USDSEK 权重 0.042、USDCHF 权重 0.036。把 Open/High/Low/Close 分别代入,就能在 MT5 里复刻出 USDX 四个价位。 GetRateValue 负责按时间定位具体 K 线价格;当请求 VALUE_OPEN 且当前 bar 时间匹配时,会把该品种的 tick_volume 累加进引用变量,用来统计篮子总 tick 量。外汇与贵金属合成品种波动受杠杆与流动性影响,实际报价可能偏离理论值,属高风险验证。
for(i=class="num">0; i<BASKET_SIZE; i++) values[i]=GetRateValue(tick_volume,symbols_rates[i],rate.time,VALUE_CLOSE); rate.close=MAIN_COEFF; for(i=class="num">0; i<BASKET_SIZE; i++) rate.close*=MathPow(values[i],ExtWeights[i].weight); class="kw">return(CheckRate(rate)); } Open USDX = class="num">50.14348112 * Open EURUSD^(-class="num">0.576) * Open USDJPY^(class="num">0.136) * Open GBPUSD^(-class="num">0.119) * Open USDCAD^(class="num">0.091) * Open USDSEK^(class="num">0.042) * Open USDCHF^(class="num">0.036); High USDX = class="num">50.14348112 * High EURUSD^(-class="num">0.576) * High USDJPY^(class="num">0.136) * High GBPUSD^(-class="num">0.119) * High USDCAD^(class="num">0.091) * High USDSEK^(class="num">0.042) * High USDCHF^(class="num">0.036); Low USDX = class="num">50.14348112 * Low EURUSD^(-class="num">0.576) * Low USDJPY^(class="num">0.136) * Low GBPUSD^(-class="num">0.119) * Low USDCAD^(class="num">0.091) * Low USDSEK^(class="num">0.042) * Low USDCHF^(class="num">0.036); CloseUSDX = class="num">50.14348112 * CloseEURUSD^(-class="num">0.576) * CloseUSDJPY^(class="num">0.136) * CloseGBPUSD^(-class="num">0.119) * CloseUSDCAD^(class="num">0.091) * CloseUSDSEK^(class="num">0.042) * CloseUSDCHF^(class="num">0.036); class="type">class="kw">double GetRateValue(class="type">long &tick_volume,str_rates &symbol_rates,class="type">class="kw">datetime time,ENUM_RATES_VALUES num_value) { class="type">class="kw">double value=class="num">0; class="type">int index=symbol_rates.index; if(index<ArraySize(symbol_rates.rates)) { class="kw">switch(num_value) { case VALUE_OPEN: if(symbol_rates.rates[index].time<time) value=symbol_rates.rates[index].close; else { if(symbol_rates.rates[index].time==time) { value=symbol_rates.rates[index].open; tick_volume+=symbol_rates.rates[index].tick_volume; } } class="kw">break; case VALUE_HIGH:
最后说句实在话
上面这段取价逻辑里,HIGH 和 LOW 在目标 K 线时间吻合时直接取该根高低点,而早于目标时间的 K 线一律回退到 close,CLOSE 分支则连等于时间也吃 close——这意味着你若用这套函数去回测跨周期拐点,可能把未走完 K 线的瞬时高低误判成定值。 CheckRate 里先把四个价格过一遍 MathIsValidNumber 和零值拦截,再用 ExtDigits 做 NormalizeDouble,随后四条 if 把 high/low 往 open 与 close 之间夹。这套校正能挡掉不少脏数据,但注意它改的是传入引用本身,调用方手里的 MqlRates 会被就地抹平。 外汇和贵金属杠杆高、跳空频繁,这类自行校正若没接在严格的时间边界判断后,可能在极端行情里给出偏离盘口的价格。开 MT5 把 ExtDigits 调成你品种的规范小数位,跑一根非交易时段数据,看 high 是否被 open 强行拉低,就能验证这处细节。
if(symbol_rates.rates[index].time<time) value=symbol_rates.rates[index].close; else { if(symbol_rates.rates[index].time==time) value=symbol_rates.rates[index].high; } class="kw">break; class=class="str">"cmt">//--- Low price case VALUE_LOW: if(symbol_rates.rates[index].time<time) value=symbol_rates.rates[index].close; else { if(symbol_rates.rates[index].time==time) value=symbol_rates.rates[index].low; } class="kw">break; class=class="str">"cmt">//--- Close price case VALUE_CLOSE: if(symbol_rates.rates[index].time<=time) value=symbol_rates.rates[index].close; class="kw">break; } } class=class="str">"cmt">//--- class="kw">return the received value class="kw">return(value); } class=class="str">"cmt">//--- class="kw">return the result of checking prices for validity class="kw">return(CheckRate(rate)); class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Check prices for validity and class="kw">return the check result | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CheckRate(class="type">MqlRates &rate) { class=class="str">"cmt">//--- if prices are not valid real numbers, or are less than or equal to zero - class="kw">return &class="macro">#x27;class="kw">false&class="macro">#x27; if(!MathIsValidNumber(rate.open) || !MathIsValidNumber(rate.high) || !MathIsValidNumber(rate.low) || !MathIsValidNumber(rate.close)) class="kw">return(class="kw">false); if(rate.open<=class="num">0.0 || rate.high<=class="num">0.0 || rate.low<=class="num">0.0 || rate.close<=class="num">0.0) class="kw">return(class="kw">false); class=class="str">"cmt">//--- normalize prices to the required number of digits rate.open=NormalizeDouble(rate.open,ExtDigits); rate.high=NormalizeDouble(rate.high,ExtDigits); rate.low=NormalizeDouble(rate.low,ExtDigits); rate.close=NormalizeDouble(rate.close,ExtDigits); class=class="str">"cmt">//--- adjust prices if necessary if(rate.high<rate.open) rate.high=rate.open; if(rate.low>rate.open) rate.low=rate.open; if(rate.high<rate.close) rate.high=rate.close; if(rate.low>rate.close) rate.low=rate.close; class=class="str">"cmt">//--- all is fine class="kw">return(true); }