构建动态多品种EA(第四部分):波动率与风险调节
引言
在多品种交易中,交易者面临一大难题:不同货币对波动率差异巨大,导致策略绩效参差不齐。正如上一篇文章所讲,一套策略在欧美(EURUSD)表现良好,但若直接用于镑日(GBPJPY),很可能收益下滑或是风险急剧升高,根源在于两者波动率特征完全不同。采用固定手数、静态止损会造成两种后果:高波动市场风险敞口过大,平稳市场又容易错失交易机会。这种缺乏自适应能力的设计,往往带来风险敞口不均衡、最大回撤扩大、交易结果难以预判,尤其在高影响力新闻事件或市场突然转向时。
为解决以上问题,我们在 EA 内部引入波动率与风险自适应机制。通过引入平均真实波幅(ATR)、基于风险的动态仓位算法,EA 能够根据实时行情自动调整交易参数。确保每一笔持仓规模与品种波动率相匹配,实现均衡风控,让 EA 在全部交易品种上均拥有稳定表现。
EA 开发整体方案
交易逻辑:

1. 多品种处理模块:
- 品种列表解析
- 独立品种数据跟踪
- 多持仓并发管理

2. 开仓条件模块:

3. 波动率分级风控模块:

4. 仓位计算:
风险金额 = 账户净值 × 风险比例 持仓手数 = 风险金额 ÷(止损距离 × 每点价值)
5. V 型止损目标位识别:
[当前价格]
|
v
[阻力区域] <—— 前一根V-Stop上轨(空单止盈位)
|
v
[当前价格]
|
v
[支撑区域] <—— 前一根V-Stop下轨(多单止盈位) 6. 完整交易生命周期时序:

让我们开始
//+------------------------------------------------------------------+ //| MultiSymbolVolatilityTraderEA.mq5 | //| Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #include <Trade/Trade.mqh>
和往常一样,我们首先引入核心交易库,智能交易程序依靠该库完成下单与持仓管理。
//--- Input settings input string Symbols = "XAUUSD,GBPUSD,USDCAD,USDJPY"; // Symbols to trade input int RSI_Period = 14; // RSI Period input double RSI_Overbought = 70.0; // RSI Overbought Level input double RSI_Oversold = 30.0; // RSI Oversold Level input uint ATR_Period = 14; // ATR Period for Volatility input double ATR_Multiplier = 2.0; // ATR Multiplier for SL input double RiskPercent_High = 0.02; // Risk % High Volatility input double RiskPercent_Mod = 0.01; // Risk % Moderate Volatility input double RiskPercent_Low = 0.005; // Risk % Low Volatility input int Min_Bars = 50; // Minimum Bars Required input double In_Lot = 0.01; // Default lot size input int StopLoss = 100; // SL in points input int TakeProfit = 100; // TP in points
本段定义外部输入参数,用于配置 EA 在多品种环境下的运行规则。Symbols 参数供交易者指定需要交易的品种;RSI 相关参数(RSI 周期、超买、超卖阈值)依托超买超卖信号识别潜在开仓机会。波动率测算采用平均真实波幅 ATR,可自定义周期参数与止损放大倍数。
EA 将根据波动率等级差异化控制风险敞口:高、中、低波动场景分别配置独立风险比例。其余参数包括最低历史 K 线数量、默认基础手数,以及固定点数止损止盈;当动态档位未启用时,将作为兜底方案。
//--- Global variables string symb_List[]; int Num_symbs = 0; int ATR_Handles[]; int RSI_Handles[]; double Prev_ATR[]; double Prev_RSI[]; datetime LastBarTime[]; CTrade trade;
该部分声明 EA 全局变量,贯穿整个程序生命周期。symb_List[] 数组存储全部交易品种,Num_symbs 记录品种总数。ATR_Handles[]、RSI_Handles[] 数组管理指标句柄,用于 ATR、RSI 指标计算,实现多品种并行处理。
Prev_ATR[]、Prev_RSI[] 保存每个品种最新指标数值,供决策逻辑调用。LastBarTime[] 记录每个品种上一根处理完成的 K 线时间,避免同一根 K 线重复执行逻辑。最后创建 CTrade 交易对象,调用 MQL5 内置交易函数,实现下单、持仓管理。
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Split symbol list ushort separator = StringGetCharacter(",", 0); StringSplit(Symbols, separator, symb_List); Num_symbs = ArraySize(symb_List); //--- Resize arrays ArrayResize(ATR_Handles, Num_symbs); ArrayResize(RSI_Handles, Num_symbs); ArrayResize(Prev_ATR, Num_symbs); ArrayResize(Prev_RSI, Num_symbs); ArrayResize(LastBarTime, Num_symbs); ArrayInitialize(Prev_ATR, 0.0); ArrayInitialize(Prev_RSI, 50.0); ArrayInitialize(LastBarTime, 0); //--- Create indicator handles for(int i = 0; i < Num_symbs; i++) { string symbol = symb_List[i]; ATR_Handles[i] = iATR(symbol, PERIOD_CURRENT, ATR_Period); RSI_Handles[i] = iRSI(symbol, PERIOD_CURRENT, RSI_Period, PRICE_CLOSE); if(ATR_Handles[i] == INVALID_HANDLE || RSI_Handles[i] == INVALID_HANDLE) { Print("Error creating indicator handles for ", symbol, " - Error: ", GetLastError()); return INIT_FAILED; } } return INIT_SUCCEEDED; }
OnInit() 在 EA 加载至图表时执行初始化工作。首先以逗号作为分隔符,将输入参数里的品种列表拆分存入数组 symb_List,统计品种总数。随后调整多个全局数组容量,包含 ATR、RSI 指标句柄数组、历史指标值数组、K 线时间数组,为每个品种分配独立存储空间。赋予初始值,避免 EA 首次运行出现未知异常。
接着循环遍历所有品种,调用 iATR、iRSI 创建对应指标句柄。句柄是交易过程中实时读取指标数值的基础。一旦任意句柄创建失败(返回无效句柄),程序打印错误信息并返回 `INIT_FAILED`,终止初始化。所有指标创建成功后返回 INIT_SUCCEEDED,代表 EA 就绪,可以开始运行。
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Release indicator handles for(int i = 0; i < Num_symbs; i++) { if(ATR_Handles[i] != INVALID_HANDLE) IndicatorRelease(ATR_Handles[i]); if(RSI_Handles[i] != INVALID_HANDLE) IndicatorRelease(RSI_Handles[i]); } }
当 EA 从图表移除或重启时,触发 OnDeinit() 函数。核心作用是资源清理,释放各个品种绑定的指标句柄。代码仅在句柄有效时调用 IndicatorRelease() 释放资源,保证内存正常回收,避免内存泄漏、资源占用过高。该机制有利于保障 MT5 平台稳定,尤其适合同时运行多个 EA 与指标的场景。
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { for(int i = 0; i < Num_symbs; i++) { string symbol = symb_List[i]; //--- Check for new bar datetime currentBarTime = iTime(symbol, PERIOD_CURRENT, 0); if(LastBarTime[i] == currentBarTime) continue; LastBarTime[i] = currentBarTime; //--- Get indicator values double atr[2] = {0.0, 0.0}; double rsi[2] = {50.0, 50.0}; if(CopyBuffer(ATR_Handles[i], 0, 1, 2, atr) < 2 || CopyBuffer(RSI_Handles[i], 0, 1, 2, rsi) < 2) continue; Prev_ATR[i] = atr[0]; // Previous bar's ATR Prev_RSI[i] = rsi[0]; // Previous bar's RSI //--- Get current prices MqlTick lastTick; if(!SymbolInfoTick(symbol, lastTick)) continue; double ask = lastTick.ask; double bid = lastTick.bid; //--- Check existing positions bool hasLong = false, hasShort = false; CheckExistingPositions(symbol, hasLong, hasShort); //--- Calculate volatility-based risk double riskPercent = CalculateRiskLevel(symbol); //--- Get V-Stop levels double vStopUpper = CalculateVStop(symbol, 1, true); // Previous bar's upper band double vStopLower = CalculateVStop(symbol, 1, false); // Previous bar's lower band //--- Trade Entry Logic if(!hasLong && !hasShort) { //--- Sell signal: RSI overbought + price below V-Stop upper band if(rsi[0] > RSI_Overbought && bid < vStopUpper) { double tp = GetProfitTarget(symbol, false); // Previous V-Stop level double sl = ask + ATR_Multiplier * atr[0]; double lots = CalculatePositionSize(symbol, riskPercent, sl, ask); if(lots > 0) ExecuteTrade(ORDER_TYPE_SELL, symbol); } //--- Buy signal: RSI oversold + price above V-Stop lower band else if(rsi[0] < RSI_Oversold && ask > vStopLower) { double tp = GetProfitTarget(symbol, true); // Previous V-Stop level double sl = bid - ATR_Multiplier * atr[0]; double lots = CalculatePositionSize(symbol, riskPercent, sl, bid); if(lots > 0) ExecuteTrade(ORDER_TYPE_BUY, symbol); } } //--- Trailing Stop Logic UpdateTrailingStops(symbol, atr[0]); } }
OnTick() 函数每当行情更新时触发执行,遍历交易列表内所有品种,开展实时行情分析与持仓管理。函数首先对比当前 K 线时间戳与上一次记录的时间,判断该品种是否生成新 K 线。如果出现新 K 线,则通过 CopyBuffer() 读取最新 ATR 与 RSI 指标数值,并存入全局数组,供后续决策逻辑调用。同时借助 SymbolInfoTick() 获取实时买价、卖价,保证开仓、平仓价位计算精准。
随后调用 CheckExistingPositions(),检查当前品种是否存在已建立的多单或空单持仓。接着执行 CalculateRiskLevel(),依据品种波动率计算对应的风险等级;同时读取最新 V-Stop 上下轨,作为开仓与移动止损逻辑依据。基于以上信息,EA 执行交易开仓规则:当 RSI 进入超买区间,且价格跌破 V-Stop 上轨时触发空单;当 RSI 进入超卖区间,且价格向上突破 V-Stop 下轨时触发多单。两种场景下,都会结合 ATR 与 V-Stop 计算动态止损、止盈价位,并根据对应的风险等级调整交易手数。
最后,无论是否新开订单,EA 都会调用 UpdateTrailingStops() 管理所有已有持仓。该函数根据最新波动率数据调整止损位置,跟随行情锁定利润、控制亏损。这套动态机制能够让策略在多品种环境下保持实时响应与自适应能力。
//+------------------------------------------------------------------+ //| Execute trade with risk parameters | //+------------------------------------------------------------------+ void ExecuteTrade(ENUM_ORDER_TYPE tradeType, string symbol) { double point = SymbolInfoDouble(symbol, SYMBOL_POINT); double price = (tradeType == ORDER_TYPE_BUY) ? SymbolInfoDouble(symbol, SYMBOL_ASK) : SymbolInfoDouble(symbol, SYMBOL_BID); // Convert StopLoss and TakeProfit from pips to actual price distances double sl_distance = StopLoss * point; double tp_distance = TakeProfit * point; double sl = (tradeType == ORDER_TYPE_BUY) ? price - sl_distance : price + sl_distance; double tp = (tradeType == ORDER_TYPE_BUY) ? price + tp_distance : price - tp_distance; trade.PositionOpen(symbol, tradeType, In_Lot, price, sl, tp, NULL); }
ExecuteTrade() 函数根据指定订单方向(多单 / 空单)与交易品种完成实际下单。根据交易方向选取对应报价:买单使用 Ask,卖单使用 Bid,作为入场价。再利用品种的最小变动单位(point),将用户设定的点数止损、止盈换算成真实价格间距。结合做多、做空方向分别设置止损与止盈价位,最终调用 CTrade 对象,按照预设手数与价格参数开仓。
//+------------------------------------------------------------------+ //| Calculate V-Stop levels | //+------------------------------------------------------------------+ double CalculateVStop(string symbol, int shift, bool isUpper) { double atr[1]; double high[1], low[1], close[1]; if(CopyBuffer(ATR_Handles[GetSymbolIndex(symbol)], 0, shift, 1, atr) < 1 || CopyHigh(symbol, PERIOD_CURRENT, shift, 1, high) < 1 || CopyLow(symbol, PERIOD_CURRENT, shift, 1, low) < 1 || CopyClose(symbol, PERIOD_CURRENT, shift, 1, close) < 1) return 0.0; double price = (high[0] + low[0] + close[0]) / 3.0; // Typical price return isUpper ? price + ATR_Multiplier * atr[0] : price - ATR_Multiplier * atr[0]; }
CalculateVStop() 函数针对指定品种与 K 线偏移量,计算受波动率调节的动态 V-Stop 轨道。函数通过 CopyBuffer() 与 K 线序列读取接口,获取对应 K 线的 ATR、最高价、最低价、收盘价。先计算典型价格((高 + 低 + 收)/3)。根据入参 isUpper 判断需要上轨还是下轨:在典型价格基础上加减 ATR 倍数,生成适配波动率的支撑位 / 阻力位,用于交易过滤与移动止损逻辑。
//+------------------------------------------------------------------+ //| Get profit target from V-Stop history | //+------------------------------------------------------------------+ double GetProfitTarget(string symbol, bool forLong) { int bars = 50; // Look back 50 bars double target = 0.0; for(int i = 1; i <= bars; i++) { double vStop = forLong ? CalculateVStop(symbol, i, false) : // For longs, find support levels CalculateVStop(symbol, i, true); // For shorts, find resistance levels if(vStop != 0.0) { target = vStop; break; } } // Fallback: Use fixed multiplier if no V-Stop found MqlTick lastTick; SymbolInfoTick(symbol, lastTick); return (target == 0.0) ? (forLong ? lastTick.ask + 5*Prev_ATR[GetSymbolIndex(symbol)] : lastTick.bid - 5*Prev_ATR[GetSymbolIndex(symbol)]) : target; }
GetProfitTarget() 函数基于历史 V-Stop 轨道计算动态止盈位置。程序向前回溯最多 50 根 K 线,寻找最近有效的 V-Stop 水平:多单参考下方支撑轨道,空单参考上方阻力轨道。若读取到有效价位,则直接作为止盈目标。如果回溯区间内无法获取有效 V-Stop 数据,则启用兜底方案:以当前买卖价为基准,偏移 5 倍 ATR 距离生成止盈位,保证 EA 在缺少历史 V-Stop 数据时依旧能够设置合理止盈。
//+------------------------------------------------------------------+ //| Calculate risk level based on volatility | //+------------------------------------------------------------------+ double CalculateRiskLevel(string symbol) { double atrValues[20]; if(CopyBuffer(ATR_Handles[GetSymbolIndex(symbol)], 0, 1, 20, atrValues) < 20) return RiskPercent_Mod; double avgATR = 0.0; for(int i = 0; i < 20; i++) avgATR += atrValues[i]; avgATR /= 20.0; double currentATR = atrValues[0]; // Most recent ATR if(currentATR > avgATR * 1.5) return RiskPercent_High; else if(currentATR < avgATR * 0.5) return RiskPercent_Low; return RiskPercent_Mod; }
CalculateRiskLevel() 函数根据实时波动率动态调整 EA 风险敞口。函数读取该品种最近 20 根 ATR 数值并求取平均值,作为波动率基准。将最新 ATR 与均值对比:若当前 ATR 高于均值 1.5 倍,判定为高波动,使用高风险比例;若低于均值 0.5 倍,判定为低波动,采用低风险比例。其余场景使用中等风险档位。这套机制实现交易手数自适应,和实时市场波动环境匹配。
//+------------------------------------------------------------------+ //| Calculate position size based on risk | //+------------------------------------------------------------------+ double CalculatePositionSize(string symbol, double riskPercent, double sl, double entryPrice) { double tickSize = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE); double tickValue = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE); double point = SymbolInfoDouble(symbol, SYMBOL_POINT); if(tickSize == 0 || point == 0) return 0.0; double riskAmount = AccountInfoDouble(ACCOUNT_EQUITY) * riskPercent; double slDistance = MathAbs(entryPrice - sl) / point; double moneyRisk = slDistance * tickValue / (tickSize / point); if(moneyRisk <= 0) return 0.0; double lots = riskAmount / moneyRisk; // Normalize and validate lot size double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN); double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX); double step = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP); lots = MathMax(minLot, MathMin(maxLot, lots)); lots = MathRound(lots / step) * step; return lots; }
CalculatePositionSize() 函数根据设定风险比例与止损距离,计算合理交易手数。函数首先获取该品种关键交易参数:最小变动价位(tick size)、每点价值(tick value)以及报价单位(point)。结合账户净值与选定风险比例,算出交易者单笔交易愿意承担的最大亏损金额。
随后,函数以点数为单位核算止损距离,并结合 tick 参数换算出每手对应的潜在亏损金额。用总风险金额除以单手风险金额,得到理论最优手数。最后,将计算出的手数进行修正,使其符合经纪商规定的最小手数、最大手数与手数变动步长,保证仓位规格合法、数值四舍五入后可正常下单。
//+------------------------------------------------------------------+ //| Update trailing stops | //+------------------------------------------------------------------+ void UpdateTrailingStops(string symbol, double currentATR) { double newSL = 0.0; for(int pos = PositionsTotal()-1; pos >= 0; pos--) { if(PositionGetSymbol(pos) != symbol) continue; ulong ticket = PositionGetInteger(POSITION_TICKET); double currentSL = PositionGetDouble(POSITION_SL); double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); double currentProfit = PositionGetDouble(POSITION_PROFIT); double currentPrice = PositionGetDouble(POSITION_PRICE_CURRENT); if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { newSL = currentPrice - ATR_Multiplier * currentATR; if(newSL > currentSL && newSL > openPrice && currentProfit > 0) ModifySL(ticket, newSL); } else { newSL = currentPrice + ATR_Multiplier * currentATR; if((newSL < currentSL || currentSL == 0) && newSL < openPrice && currentProfit > 0) ModifySL(ticket, newSL); } } }
UpdateTrailingStops() 函数负责动态管理持仓,依据实时波动率调整止损位置。遍历全部持仓,筛选出与目标品种一致的订单,使用最新 ATR 乘以预设倍数计算出新的移动止损位。对于多单:若新止损位高于原有止损、且高于开仓价,同时持仓已有浮盈,则向上移动止损,锁定利润。对于空单,在相似条件下向下调整止损。这套基于波动率的动态移动止损机制,既能保护已实现浮盈,又能给予行情合理波动空间,避免在正常震荡中过早被扫止损。
//+------------------------------------------------------------------+ //| Modify stop loss | //+------------------------------------------------------------------+ bool ModifySL(ulong ticket, double newSL) { MqlTradeRequest request = {}; MqlTradeResult result = {}; request.action = TRADE_ACTION_SLTP; request.position = ticket; request.sl = newSL; request.deviation = 5; if(!OrderSend(request, result)) { Print("Modify SL error: ", GetLastError()); return false; } return true; }
ModifySL() 根据订单编号更新对应持仓的止损价位。构造交易请求,操作类型设置为修改止损(TRADE_ACTION_SLTP),填入目标持仓单号、新止损价格,并设置最大允许滑点。调用 OrderSend() 发送修改指令;若修改失败,打印错误代码。函数执行成功返回 true,失败返回 false,方便 EA 做异常处理。
//+------------------------------------------------------------------+ //| Check existing positions | //+------------------------------------------------------------------+ void CheckExistingPositions(string symbol, bool &hasLong, bool &hasShort) { hasLong = false; hasShort = false; for(int pos = PositionsTotal()-1; pos >= 0; pos--) { if(PositionGetSymbol(pos) == symbol) { if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) hasLong = true; else hasShort = true; } } }
本段代码中,CheckExistingPositions() 遍历所有持仓,判断目标品种是否存在正在持有的多单或空单。函数先将输出标记 hasLong、hasShort 初始化为 false,循环遍历全部持仓。若持仓品种与目标品种匹配,若是多单则置 hasLong=true,若是空单则置 hasShort=true。
//+------------------------------------------------------------------+ //| Get symbol index | //+------------------------------------------------------------------+ int GetSymbolIndex(string symbol) { for(int i = 0; i < Num_symbs; i++) if(symb_List[i] == symbol) return i; return -1; }
最后是工具函数 GetSymbolIndex(),用于从品种列表数组 symb_List 中查找指定品种对应的数组下标。循环遍历所有品种,找到匹配项直接返回下标;若列表内不存在该品种,则返回 -1。该函数至关重要,用来在多个数组之间同步每个品种对应的指标句柄、历史指标数值等数据。
回测结果
本次回测采用 1 小时周期,测试区间大约两个月(2025 年 6 月 11 日 — 2025 年 8 月 1 日),使用如下输入参数:
- RSI 周期 = 60
- RSI 超买水平 = 70
- RSI 超卖水平 = 45
- ATR 波动周期 = 62
- ATR 止损乘数 = 3.2
- 高波动风险比例 = 0.19
- 中等波动风险比例 = 0.064
- 低波动风险比例 = 0.0335
- 所需最小历史 K 线数量 = 50
- 默认基础手数 = 0.01
- 止损点数 = 610
- 止盈点数 = 980


结论
总而言之,我们搭建了一套动态多品种智能交易 EA,通过嵌入波动率风控逻辑,实现对市场行情变化的自适应。该系统可并行处理多个交易品种,依托 ATR、RSI 等技术指标识别开仓机会。程序会计算 V-Stop 动态轨道价位,用于过滤入场信号、管理出场逻辑;同时依据实时波动率动态调整交易手数与止损点位。整套 EA 采用模块化函数设计,统一处理下单、移动止损、波动率分级风控(高 / 中 / 低三档),保证每一笔交易都贴合对应品种当下的行情特征。
综上,这套方案为交易者提供一套稳健、具备自适应能力的交易工具,在各类货币对上均能保持稳定表现。依托波动率动态调节风险敞口与交易参数,EA 在剧烈波动行情中降低过度持仓风险,在平稳行情下也不会出现收益乏力的问题。
本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/18165
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。
基于Mamba选择性状态空间模型的神经网络交易机器人开发
新手在交易中的10个基本错误
交易机器人风险管理器(第一部分):EA风控头文件
你好,
这是一篇非常有趣的文章。我刚刚粗略浏览了一下,决定对其进行详细评估,因为它与我正在开发的一个EA非常相似。
我的第一个问题是:所有交易都是买单而没有卖单,这有什么原因吗?另外,你是否计划就这个主题撰写更多文章?
根据我对您代码的简要审查,我建议将 GetSymbolIndex 以及 point 等其他变量移至符号循环的开头,并将其赋值给变量,通过减少冗余来提高效率。 随着更多符号被添加到对列表中,冗余代码的执行时间将呈指数级 增长。您还可以考虑在数组中添加一个 PairCode 索引,以便直接访问。
CapeCoddah
CapeCoddah
,所有交易都是买入的原因取决于你可能输入的参数设置;如果你在回测中使用了与我相同的设置,那是因为我已经对该EA进行了优化。关于这个主题的后续文章可能会在适当的时候发布。
感谢你的建议,我会记住这一点。
JohnHlomohang
你好……晚上好!!!
首先,我想祝贺你出色地完成了这项工作。
我正在进行测试,对结果非常满意,但必须承认,由于每种资产的设置各不相同,我目前仅使用一种货币进行测试。
我不明白这种同时设置多种货币的逻辑是如何运作的。
如果您能向我解释一下,我将不胜感激。
此处由版主应用了自动翻译。在英语论坛中,请使用英语发帖。