
创建动态多货币对EA(第1部分):货币正相关性与负相关性
引言
当您拥有一个胜率或盈利率可观的稳健交易策略或系统时,通过在相关和反向相关的货币对之间分散交易,可以提升整体表现。我将展示如何开发一个能够识别多个货币对之间正、负相关性的系统,使交易者能够利用这些关系来改善交易机会。
在重大交易事件期间,例如非农就业报告(NFP)公布时,市场通常会朝着预定方向快速波动。在这种情况下,可以通过指定一个主要货币对来简化多个货币对的交易。利用它们之间的正、负相关性,在该主要货币对上发起的交易将决定其他货币对上的相应交易。这种方法可以在重大市场事件期间显著提高效率和一致性。
文章将包括如下内容:
- 能够更改和修改货币对的能力。
- 作为信号提供者,为其他货币对提供信号的货币对的索引。
- 确定要交易的基础货币和报价货币。
在交易中,相关性指的是不同货币对价格走势之间的关系。当两个货币对呈正相关时,它们倾向于朝同一方向波动。例如,GBPUSD和EURUSD通常呈正相关,这意味着当GBPUSD上涨时,EURUSD也倾向于上涨。这是因为这两对货币都以美元作为报价货币,美元的广泛走弱或走强很可能会以相同的方式影响这两对货币。
另一方面,当两个货币对朝相反方向波动时,就存在反向相关性。一个经典的例子是GBPUSD和USDCAD之间的关系。当GBPUSD上涨(看涨)时,USDCAD通常会下跌(看跌)。这是因为第一对货币(GBPUSD)中美元是报价货币,而在第二对货币(USDCAD)中美元是基础货币。当美元走弱时,GBPUSD上涨,而USDCAD则倾向于下跌。
我们将制定一个能够同时处理多个货币对的动态多货币对EA。该系统具备一定的灵活性,允许您自定义输入、变更和修改货币对。该系统的一个关键特性是能够定义一个主要的或“基准”的货币对,它作为其他货币对的信号提供者。
系统的核心功能是能够动态调整正在交易的货币对列表。交易者可以轻松指定在交易策略中包含哪些货币对,使其能够适应各种市场条件或交易计划。该EA接受不同货币对的输入,允许用户根据需要添加、移除或切换货币对。
该系统最具创新性的方面之一是主要或基准货币对的指定。这个货币对本身不仅被频繁用于交易对象,还作为生成其他货币对信号的参考。通过监控这个主要货币对,EA识别交易信号——无论是买入还是卖出——并将这些信号应用于选定的正相关或负相关货币对。
该系统还支持根据货币对之间的相关性强弱进行动态调整。例如,如果在主要货币对中检测到强烈的看涨信号,EA可以自动在历史上朝同一方向波动的货币对中进行相应的交易。相反,对于通常与主要货币对反向波动的货币对,EA可以开立相反的头寸,从而有效对冲潜在的市场波动。
公式
#include <Trade\Trade.mqh>
CTrade trade;
导入MetaTrader 5交易库,并创建了一个CTrade类的实例,允许您管理交易操作,例如开仓、平仓和修改订单。
int handles[];
这个数组用于存储各种指标或对象的句柄,这些句柄对于跟踪多个货币对的技术分析指标是必要的。
MqlTick previousTick, currentTick;
这些变量用于存储符号价格的tick数据。previousTick用于保存上一个tick数据,而currentTick存储当前的tick数据。
inputstring Symbols = "XAUUSD, GBPUSD, USDCAD, USDJPY"; inputstring Base_Quote = "USD"; inputint Currecy_Main = 0;
这些输入允许用户自定义EA:
- Symbols:以逗号分隔的,EA将监控和交易的货币对列表。
- Base_Quote:用于确定相关性的货币。
- Currency_Main:用于指定生成信号的主要货币对的索引。
string symb_List[]; string Formatted_Symbs[]; int Num_symbs = 0;
- symb_List:一个用于保存待处理货币对的原始列表的数组。
- Formatted_Symbs:一个用于存储处理后的货币对的数组。
- Num_symbs:保存解析Symbols输入字段后,待使用的货币对总数。
intOnInit(){ string sprtr = ","; ushort usprtr; usprtr = StringGetCharacter(sprtr, 0); StringSplit(Symbols, usprtr, symb_List); Num_symbs = ArraySize(symb_List); ArrayResize(Formatted_Symbs, Num_symbs);
OnInit函数在EA加载时被调用一次,用于设置初始值和配置。然后我们定义逗号为分隔符(sprtr),它将用于拆分输入字符串。StringGetCharacter()函数将分隔符转换为ushort(无符号短整型),这是StringSplit()函数所需的。StringSplit()函数将Symbols(一个逗号分隔的字符串)拆分为单个货币对的数组。symb_List[]数组保存解析后的货币对。Formatted_Symbs[]数组的大小被调整为与解析后的符号数量相匹配。我们将使用这个数组进行进一步处理,例如添加用于交易逻辑的数据格式化或调整功能。
for(int i = 0; i < Num_symbs; i++){ Formatted_Symbs[i] = symb_List[i]; }
我们遍历所有货币对,并将其从symb_List[]数组转移到Formatted_Symbs[]数组中。在这个阶段,没有进行额外的格式调整。
ArrayResize(handles, ArraySize(Formatted_Symbs));
在这里,我们将handles[]数组的大小调整为与Formatted_Symbs[]数组的大小相匹配。handles[]中的每个元素将保存相应符号的RSI句柄。
for(int i = 0; i < ArraySize(Formatted_Symbs); i++){ handles[i] = iRSI(Formatted_Symbs[i], PERIOD_CURRENT, 14, PRICE_CLOSE); }
这个循环为每个货币对初始化RSI指标句柄。
void OnTick(){ if(isNewBar()){ for(int i = 0; i < ArraySize(Formatted_Symbs); i++){ Sig_trade(Formatted_Symbs[Currecy_Main], handles[Currecy_Main]); } } }
这里我们首先检查是否有新的K线,然后我们有一个for循环,用于检测将生成信号的主要货币对的索引。然后我们简单地调用Sig_trade()函数,该函数承载交易逻辑,函数接受一个代表货币对的字符串参数和一个代表RSI句柄的整数参数。
void Sig_trade(string symb, int handler){ double rsi[]; CopyBuffer(handler, MAIN_LINE, 1, 1, rsi); bool RSIBuy = rsi[0] < 30; bool RSISell = rsi[0] > 70; // Check if the current symbol is a base USD pair bool isBaseUSD = StringSubstr(symb, 0, 3) == Base_Quote; bool isQuoteUSD = StringSubstr(symb, 3, 3) == Base_Quote; string Bcurr = SymbolInfoString(symb, SYMBOL_CURRENCY_BASE); string Qcurr = SymbolInfoString(symb, SYMBOL_CURRENCY_PROFIT);
我们将使用一种相对简单的策略,即当RSI指标跌破30水平时买入,当它突破70水平时卖出。
- StringSubstr(symb, 0, 3) == Base-Quote:提取货币对符号(symb)的前三个字符,并检查它们是否等于“USD”。这用于判断该符号是否为以美元为基础货币的货币对。
- StringSubstr(symb, 3, 3) == Base-Quote:提取货币对符号(symb)从第四个字符开始的三个字符,并检查它们是否等于“USD”。这用于判断该符号是否为以美元为报价货币的货币对。
- Bcurr = SymbolInfoString(symb, SYMBOL_CURRENCY_BASE):检索货币对符号(货币对中的第一种货币)的基础货币,并将其存储在变量Bcurr中。
- Qcurr = SymbolInfoString(symb, SYMBOL_CURRENCY_PROFIT):检索货币对符号(货币对中的第二种货币)的报价货币,并将其存储在变量Qcurr中。
for(int i = PositionsTotal() - 1; i >= 0; i--){ ulong posTicket = PositionGetTicket(i); if(PositionGetString(POSITION_SYMBOL) == symb){ if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY){ if(RSISell){ trade.PositionClose(posTicket); } RSIBuy = false; }elseif(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL){ if(RSIBuy){ trade.PositionClose(posTicket); } RSISell = false; } } }
为了管理所有头寸,我们需要遍历所有未平仓的头寸。PositionsTotal()函数返回当前未平仓头寸的总数。循环从最后一个头寸(PositionsTotal() - 1)开始,并向后迭代。我们使用向后迭代是为了避免在循环中修改未平仓头寸列表时出现问题。我们使用PositionGetTicket()函数来获取索引为i的头寸的单号。单号是用于平仓或修改特定头寸的唯一标识符。
然后,我们使用PositionGetString()函数来获取当前头寸的货币对符号。接下来,我们将这个符号与symb(正在分析的符号)进行比较。如果它们匹配,那么这个头寸是相关的。我们再通过PositionGetInteger()函数检查当前头寸是否是买单。如果头寸是买单,并且RSI指标显示应该卖出(RSISell为true),那么就使用trade.PositionClose(posTicket)关闭这个头寸。之后,我们将RSIBuy设置为false,以确保不会进一步开启新的买单,因为当前信号表明应该卖出。
同样的逻辑也适用于卖单。我们检查当前头寸是否是卖单。如果头寸是卖单,并且RSI指标显示应该买入(RSIBuy为true),那么就使用trade.PositionClose(posTicket)关闭这个头寸。我们还将RSISell设置为false,以防止开启新的卖单,因为信号表明应该开启买单。由于我们不使用止损和止盈,因此使用上述代码来管理所有未平仓头寸。因此,代码将完全根据RSI指标来开仓和平仓。
for(int i = 0; i < ArraySize(Formatted_Symbs); i++){ string currSymb = Formatted_Symbs[i]; // Get base and quote currencies for the looped symbol string currBaseCurr = SymbolInfoString(currSymb, SYMBOL_CURRENCY_BASE); string currQuoteCurr = SymbolInfoString(currSymb, SYMBOL_CURRENCY_PROFIT);
为了在所有货币对中开仓交易,我们需要遍历Formatted-Symbs[]数组的大小(元素数量)。这个循环会依次处理Formatted-Symbs数组中的每个元素(货币对)。我们将当前图表上的货币对符号存储在变量currSymb中,其来自Formatted-Symbs数组。这个货币对符号将被用来获取有关货币对的相关信息。
if(RSIBuy){ if(currQuoteCurr == Base_Quote){ trade.PositionOpen(currSymb, ORDER_TYPE_BUY, volume, currentTick.ask, NULL, NULL, "Correlation"); } if(currBaseCurr == Base_Quote){ trade.PositionOpen(currSymb, ORDER_TYPE_SELL, volume, currentTick.bid, NULL, NULL, "Correlation"); } }
在这里,我们仅在RSI指标生成买入信号时(通常当RSI值低于某个特定阈值时,表示超卖状态)进行检查和执行。然后,我们检查当前货币对的报价货币是否与指定的Base-Quote货币匹配。如果匹配,则为该货币对开启买单。其背后的逻辑是,当你买入一个货币对时,你实际上是在买入基础货币并卖出报价货币。因此,如果你的交易策略对报价货币为你的Base-Quote的货币对持看涨态度,那么你就会买入该货币对。
接下来,我们检查当前货币对的基础货币是否与指定的Base-Quote货币匹配。如果匹配,则为该货币对开启卖单。其背后的逻辑是,如果你的策略对基础货币为指定的Base-Quote的货币对持看涨态度,那么你会卖出该货币对。卖出该货币对实际上意味着卖出基础货币并买入报价货币。
if(RSISell){ if(currBaseCurr == Base_Quote){ trade.PositionOpen(currSymb, ORDER_TYPE_SELL, volume, currentTick.bid, NULL, NULL, "Correlation"); } if(currQuoteCurr == Base_Quote){ trade.PositionOpen(currSymb, ORDER_TYPE_BUY, volume, currentTick.ask, NULL, NULL, "Correlation"); } }
这里我们处理检测到RSI卖出信号时的逻辑。如果RSI指标生成卖出信号(通常当RSI值高于某个特定阈值时,表示超买状态),则会执行这个代码块。我们通过currBaseCurr == Base-Quote检查当前符号的基础货币是否与指定的Base-Quote匹配。如果匹配,则对该货币对执行卖出操作。其逻辑仍然是,如果你对基础货币为指定Base-Quote的货币对持看跌态度,那么你就会卖出该货币对。卖出该货币对意味着你卖出基础货币并买入报价货币。
接下来,我们检查当前货币对的报价货币是否与指定的Base-Quote货币匹配。如果匹配,则为该货币对开启买单。其背后的逻辑是,如果你的策略对报价货币为指定Base-Quote的货币对持看跌态度,那么你就会在该货币对上开启买单。这是因为买入该货币对将涉及买入基础货币并卖出报价货币。
bool isNewBar() { //--- memorize the time of opening of the last bar in the static variable staticdatetime last_time=0; //--- current time datetime lastbar_time= (datetime) SeriesInfoInteger(Symbol(),Period(),SERIES_LASTBAR_DATE); //--- if it is the first call of the function if(last_time==0) { //--- set the time and exit last_time=lastbar_time; return(false); } //--- if the time differs if(last_time!=lastbar_time) { //--- memorize the time and return true last_time=lastbar_time; return(true); } //--- if we passed to this line, then the bar is not new; return false return(false); }
上面是isNewBar函数,目的是防止EA运行或执行多个重复订单。
策略测试器上的结果
根据上述测试结果,我们可以确认该系统成功地根据指定的货币正、负相关性进行交易。正如之前讨论的,当生成买入信号且主要货币对是以美元为报价货币的货币对时,Formatted-Symbs数组中所有以美元为报价货币的货币对都会执行买入订单,而以美元为基础货币的货币对则执行卖出订单。这种行为与预期功能一致,证明该系统有效地实现了我们想要的相关性逻辑。
为了有效地且正确地使用该系统,考虑使用基于相同货币对的分组至关重要。例如,在交易以欧元为基础货币的货币对(如EURUSD、EURGBP和EURJPY)时,将“EUR”设置为Base-Quote输入项,将允许基础货币在检测到信号时引导交易逻辑。这种方法可以应用于其他货币对组,例如USDEUR,或者任何其他你经纪商允许的自定义货币对,以确保系统的逻辑与你的交易策略正确匹配。
该系统具有动态性,允许你通过简单地对所需货币对进行索引,轻松选择将提供或生成信号的主要货币对。这种灵活性尤其有用,因为RSI指标用于生成信号,且RSI缓冲区和句柄应用于所有货币对。这确保了所选的主要货币对能够有效地为系统中其他相关货币对驱动交易决策。
结论
综上所述,我们探索并开发了一个动态多货币对EA,该EA基于RSI指标处理交易信号,并将其应用于各种具有正相关或负相关特性的货币对。该EA允许用户输入自定义的货币对,并指定一个主要货币对来驱动其他货币对的交易决策。通过对基础货币和报价货币的分析,EA能够根据整体市场趋势开启与之相符的交易,利用相关性逻辑进行操作。
总之,动态多货币对EA可以通过系统性地应用正、负相关性策略,实现更一致的交易结果。这种方法不仅通过自动化信号传播优化了交易效率,还通过将正、负相关性整合到交易系统中,使交易者能够利用相关货币对之间的关系。
本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/15378


