随机优化和最优控制示例(基础篇)
「把随机扰动塞进 MT5 优化回路」
MQL5 的遗传优化器默认把参数当定值扫,遇到点差跳变、滑点漂移就容易过拟合。想要更接近实盘,得在目标函数里主动注入随机项,让每一代测试都在噪声环境下跑。 一个直接的做法是用 MathRandom() 在每根 K 线生成扰动,叠加到开仓阈值上。下面这段在 OnTester() 里给盈利加噪声,回测时种群会更偏好鲁棒参数,而非尖峰曲线。 注意:外汇与贵金属属高杠杆品种,随机优化降低过拟合概率,但不保证实盘收益,请先在策略测试器用 2023—2024 年 XAUUSD 的 M15 数据验证。
class="type">class="kw">double OnTester() { class="type">class="kw">double noise = (MathRandom()/class="num">32767.0 - class="num">0.5) * class="num">2.0; class=class="str">"cmt">// 生成 [-class="num">1,class="num">1] 均匀随机扰动 class="type">class="kw">double result = TesterStatistics(STAT_PROFIT) + noise * class="num">100; class=class="str">"cmt">// 把噪声折算成金额叠加到利润 class="kw">return result; class=class="str">"cmt">// 返回带噪目标值供优化器排序 }
用随机模型驯服价格噪声
随机建模不是玄学,它把市场里那些带随机性的部分用随机变量、概率分布和随机过程显式写出来。外汇与贵金属的分钟级报价就符合这类系统:你永远只能拿到过去样本,却要对下一根 K 线方向做概率判断,这类资产高杠杆下风险被放大数倍。 蒙特卡洛模拟和马尔可夫链是两条常用路径。前者靠大量抽样逼出价格路径的分布形态,后者假设下一状态只取决于当前状态而非全部历史——拿 EURUSD 回看,其短周期波动常表现出弱马尔可夫性,但长期会被宏观事件打断。 控制优化则是另一半:线性二次控制器、模型预测控制、强化学习,本质都是在约束里找更优动作序列。把两者拼起来就是随机控制优化,典型场景是仓位决策——你只有不完全的未来信息,却要决定这单开多少、在哪减仓。 对交易者来说,这套框架最实用的落点是:别把单次盈亏当能力,用概率分布和抽样去验证你的入场逻辑在噪声里是否还站得住。
◍ SMOC 智能资金最优控制 EA 的运作逻辑与边界
SMOC(智能资金最优控制)EA 把技术指标、最优控制数学模型和仓位风控拧成一套决策链,本质是用预测管理模型去估计未来价格走向并下单,属于展示该类系统能力的简易范本。它同时挂了 SMA、SAR、RSI、ATR 四类指标,分别管趋势识别与波动度量,再由系统按市场波动和账户回撤去滑动预测窗口与手数。 动态 SL/TP 不是写死点位,而是随 ATR 类的波动读数重算;仓位规模绑定账户余额与回撤阈值,这对外汇、贵金属这类高杠杆高风险品种尤其关键,极端行情下模型自我保护可能失效,须人工盯紧。 适用的场景偏中长线、趋势明确的盘面,或需要统一风控的多标的组合。优点在自适应与复杂分析,但代价是算法难读难调,最优控制计算偏重,弱机器可能卡顿;参数和指标一多,历史过拟合概率上升,且整套逻辑默认‘过去行为可外推’,而金融市场常不买这个账。
「MPC 怎么在 EA 里落地成买卖信号」
SMOC EA 的核心不是传统指标堆叠,而是把模型预测控制(MPC)塞进 OptimalControl() 函数里:喂进当前价格,吐出 1(买)/ -1(卖)/ 0(持有)三个离散决策。函数内部先用自适应步长算预测周期,再估漂移率 mu 与波动率 sigma,把控制问题转成有限网格搜索。 代码里 baseThreshold 写死 0.001,再乘上 (1 + 1 - successRate) 动态放大,说明触发交易的门槛会随历史胜率走低而抬高——这是典型的回撤自我保护。dt 取 1/1440,即按分钟级步长建模,适合外汇和贵金属这种 24 小时连续报价品种,但高频重算也吃 CPU。 网格搜索 u1、u2 都从 0.01 扫到 0.99、步长 0.01,合计约 9801 次期望收益计算;若当前价高于 20 根收盘均值,就把期望收益取反,偏向逢高卖。最后 bestExpectedReturn 过不了 decisionThreshold 就返回 0 观望。 实盘前建议把 lookbackPeriod 从 20 调到符合你品种波周期的数,并在 MT5 策略测试器里打印 LogMessage 那行,看 bestU1/bestU2 是否长期贴边 0.01 或 0.99——若贴边说明网格分辨率不够,得把步长改细。外汇与贵金属杠杆高,MPC 信号只代表概率倾向,不代表胜率保证。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Function for optimal control using Model Predictive Control | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OptimalControl( class="type">class="kw">double currentPrice) { class="type">int predictionHorizon = CalculateAdaptiveHorizon(); class="type">class="kw">double mu = EstimateDrift(); class="type">class="kw">double sigma = EstimateVolatility(); class="type">class="kw">double baseThreshold = class="num">0.001 ; class="type">class="kw">double decisionThreshold = baseThreshold * ( class="num">1 + ( class="num">1 - successRate)); class="type">class="kw">double dt = class="num">1.0 / class="num">1440.0 ; class="type">class="kw">double bestExpectedReturn = - DBL_MAX ; class="type">int bestDecision = class="num">0 ; class="type">class="kw">double bestU1 = class="num">0 , bestU2 = class="num">0 ; class=class="str">"cmt">// Optimize the search space class="type">class="kw">double u1Start = class="num">0.01 , u1End = class="num">0.99 , u1Step = class="num">0.01 ; class="type">class="kw">double u2Start = class="num">0.01 , u2End = class="num">0.99 , u2Step = class="num">0.01 ; class=class="str">"cmt">// Calculate historical average price class="type">int lookbackPeriod = class="num">20 ; class=class="str">"cmt">// You can adjust this class="type">class="kw">double historicalPrices[]; ArraySetAsSeries(historicalPrices, true ); CopyClose( Symbol() , PERIOD_CURRENT , class="num">0 , lookbackPeriod, historicalPrices); class="type">class="kw">double avgHistoricalPrice = ArraySum(historicalPrices) / lookbackPeriod; for ( class="type">class="kw">double u1 = u1Start; u1 <= u1End; u1 += u1Step) { for ( class="type">class="kw">double u2 = u2Start; u2 <= u2End; u2 += u2Step) { class="type">class="kw">double expectedReturn = CalculateExpectedReturn(currentPrice, mu, sigma, dt, predictionHorizon, u1, u2); class=class="str">"cmt">// Compare with historical average if (currentPrice > avgHistoricalPrice) { expectedReturn *= - class="num">1 ; class=class="str">"cmt">// Invert expected class="kw">return to favor selling when price is high } if (expectedReturn > bestExpectedReturn) { bestExpectedReturn = expectedReturn; bestU1 = u1; bestU2 = u2; } } } LogMessage( StringFormat( "OptimalControl - Best u1: %f, Best u2: %f, Best Expected Return: %f, Decision Threshold %f" , bestU1, bestU2, bestExpectedReturn, decisionThreshold)); if (bestExpectedReturn > decisionThreshold) class="kw">return class="num">1 ; class=class="str">"cmt">// Buy if (bestExpectedReturn < decisionThreshold) class="kw">return - class="num">1 ; class=class="str">"cmt">// Sell class="kw">return class="num">0 ; class=class="str">"cmt">// Hold }
用 GBM 跑预期回报的底层函数
这套算法真正落地的核心,是一个用几何布朗运动(GBM)模拟资产价格路径、再算平均回报的函数。它把漂移 μ、波动 σ、时间步长 dt 和模拟步数 horizon 全部吃进去,对外输出「在给定控制参数下,这段窗口的平均收益倾向」——这是后面网格搜索选 u1/u2 的打分依据。 外汇与贵金属市场杠杆高、跳空频繁,σ 若直接套用股指参数,模拟路径会严重偏离真实跳动,实盘前必须在 MT5 里用品种历史波动率重算。 函数开头把 tempPrice 钉死为当前价,totalReturn 清零;随后跑 horizon 次循环。每一次先用 Box-Muller 变换由 u1、u2 造出一个标准正态变量 Z,再套 GBM 离散式:nextPrice = tempPrice * exp((μ - 0.5σ²)dt + σ√dt·Z),其中前一项是漂移修正、后一项是随机冲击。 每步把「nextPrice - tempPrice」累进 totalReturn,并把 tempPrice 推到下一刻。循环结束返回 totalReturn / horizon,即模拟范围内的平均单步回报。下面这段是可直接拷进 MT5 里验证的原码与逐行拆解。 别把正态当圣经:u1、u2 必须是 (0,1) 均匀随机数,若传进 0 或 1,MathLog(u1) 会出域或得 0,Z 直接崩成 NaN,整个回报估计失效。
class="type">class="kw">double CalculateExpectedReturn(class="type">class="kw">double currentPrice, class="type">class="kw">double mu, class="type">class="kw">double sigma, class="type">class="kw">double dt, class="type">int horizon, class="type">class="kw">double u1, class="type">class="kw">double u2) { class="type">class="kw">double tempPrice = currentPrice; class=class="str">"cmt">// 锚定起始价,循环内逐步滚动 class="type">class="kw">double totalReturn = class="num">0; class=class="str">"cmt">// 累计回报初值清零 for (class="type">int i = class="num">0; i < horizon; i++) class=class="str">"cmt">// 按模拟步数迭代 { class="type">class="kw">double Z = MathSqrt(-class="num">2.0 * MathLog(u1)) * MathCos(class="num">2.0 * M_PI * u2); class=class="str">"cmt">// Box-Muller:由均匀随机数造标准正态 Z class="type">class="kw">double nextPrice = tempPrice * MathExp((mu - class="num">0.5 * sigma * sigma) * dt + sigma * MathSqrt(dt) * Z); class=class="str">"cmt">// GBM 一步演化:漂移修正+随机冲击 totalReturn += nextPrice - tempPrice; class=class="str">"cmt">// 本步绝对收益累进 tempPrice = nextPrice; class=class="str">"cmt">// 价格更新供下次使用 } class="kw">return totalReturn / horizon; class=class="str">"cmt">// 回传平均单步回报 }
◍ 别把正态当圣经,模拟次数和参数都得较真
几何布朗运动那套思路默认回报服从正态分布且前后独立,实盘里这前提经常破功——黑天鹅和波动率聚集随时打脸。函数里的 mu 和 sigma 若估偏了,跑出来的未来回报路径可信度直接掉档,外汇和贵金属这种高杠杆品种尤其容易放大误差。 单次模拟只是一次抽样,真实用途里至少该跑几百次取分布,否则结论大概率带偶然性。horizon 和 dt 两个参数也别随手填,前者定预测跨度、后者定步长,选错会让结果要么过度平滑要么噪声爆炸。 自适应那部分倒是实用:系统按波动率和账户回撤动态改预测范围与手数。下面这段 MT5 代码把逻辑落到了函数级,开终端就能抄去测。 [CODE] //+------------------------------------------------------------------+
| // | Calculate adaptive horizon |
|---|
//+------------------------------------------------------------------+ int CalculateAdaptiveHorizon() { double currentVolatility = EstimateVolatility(); int baseHorizon = 5; return MathMax(baseHorizon, MathMin(20, (int)(baseHorizon * (1 + currentVolatility)))); } // Función para ajustar el tamaño del lote basado en drawdown double AdjustLotSizeForDrawdown() { static int consecutiveLosses = 0; static double maxBalance = 0; double currentBalance = AccountInfoDouble(ACCOUNT_BALANCE); double currentEquity = AccountInfoDouble(ACCOUNT_EQUITY); if(currentBalance > maxBalance) maxBalance = currentBalance; double drawdown = (maxBalance - currentEquity) / maxBalance; double baseLotSize = CalculateDynamicLotSize(); if(drawdown > 0.1) // 10% drawdown { return baseLotSize * 0.5; // Reducir el tamaño del lote a la mitad } else if(consecutiveLosses > 3) { return baseLotSize * 0.75; // Reducir el tamaño del lote en un 25% } return baseLotSize; } [/CODE] 逐行拆一下:CalculateAdaptiveHorizon 先抓当前波动率,基底 horizon 锁 5 根 K,最终返回值是 5 和「5×(1+波动率)」夹在 5~20 之间的整数——波动越高预测窗越长,但封顶 20。AdjustLotSizeForDrawdown 用静态变量记连亏次数和历史最大余额,回撤超 10% 直接砍半仓,连亏超 3 次则只减 25%,其余情况走基础手数。 回测时把这两个函数挂上 EA,观察净值曲线在剧烈波动周是否少吐血,可能比单纯调入场信号更有性价比。外汇贵金属高风险,自适应减仓只是降低暴露,不保证不爆。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Calculate adaptive horizon | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int CalculateAdaptiveHorizon() { class="type">class="kw">double currentVolatility = EstimateVolatility(); class="type">int baseHorizon = class="num">5; class="kw">return MathMax(baseHorizon, MathMin(class="num">20, (class="type">int)(baseHorizon * (class="num">1 + currentVolatility)))); } class=class="str">"cmt">// Función para ajustar el tamaño del lote basado en drawdown class="type">class="kw">double AdjustLotSizeForDrawdown() { class="kw">static class="type">int consecutiveLosses = class="num">0; class="kw">static class="type">class="kw">double maxBalance = class="num">0; class="type">class="kw">double currentBalance = AccountInfoDouble(ACCOUNT_BALANCE); class="type">class="kw">double currentEquity = AccountInfoDouble(ACCOUNT_EQUITY); if(currentBalance > maxBalance) maxBalance = currentBalance; class="type">class="kw">double drawdown = (maxBalance - currentEquity) / maxBalance; class="type">class="kw">double baseLotSize = CalculateDynamicLotSize(); if(drawdown > class="num">0.1) class=class="str">"cmt">// class="num">10% drawdown { class="kw">return baseLotSize * class="num">0.5; class=class="str">"cmt">// Reducir el tamaño del lote a la mitad } else if(consecutiveLosses > class="num">3) { class="kw">return baseLotSize * class="num">0.75; class=class="str">"cmt">// Reducir el tamaño del lote en un class="num">25% } class="kw">return baseLotSize; }