您应当知道的 MQL5 向导技术(第 51 部分):配以 SAC 的强化学习(基础篇)
用 MQL5 向导搭一个带 SAC 的强化学习框架
MQL5 向导从 build 2875 起支持通过元数据标签把强化学习智能体直接挂进 EA 模板,省去手写交易循环。本文演示的是把 Soft Actor-Critic(SAC)算法接进向导生成骨架,让策略在仿真环境里自己调仓位权重。 实际跑通时,向导默认生成的 OnTick 只负责信号转发,训练回路要写在独立的 CSacAgent 类里,并用 #property script_show_inputs 暴露超参。下面这段代码是向导产出的最小可编译骨架,删掉了无关绘图。 外汇与贵金属杠杆高,强化学习在样本外容易过拟合,实盘前务必用 MT5 策略测试器做至少 3 个月 Tick 级回测再考虑。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert Advisor generated by Wizard class=class="str">"cmt">//| with SAC reinforcement class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property script_show_inputs class="macro">#include <Trade/Trade.mqh> CTrade trade; class=class="str">"cmt">//--- SAC agent placeholder class CSacAgent { class="kw">public: class="type">class="kw">double Act(class="type">class="kw">double &state[]) { class="kw">return class="num">0.0; } }; class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { class="type">class="kw">double state[class="num">4] = {class="num">0,class="num">0,class="num">0,class="num">0}; CSacAgent agent; class="type">class="kw">double lot = agent.Act(state); if(lot > class="num">0.0) trade.Buy(lot, _Symbol); } class=class="str">"cmt">//+------------------------------------------------------------------+
「双评论者压制 Q 值高估的实现骨架」
柔性参与者评论者(SAC)在 MT5 里落地时,网络结构是三块:两个评论者加一个参与者。两个评论者都吃环境状态和动作,各自吐出一个 Q 值预估,训练参与者时取两者的最小值去调制损失。这种取最小值的做法直接针对 Q 学习里常见的正向偏差——单网络容易把回报估高,双网络并行能把这类误差拉回一点。 参与者网络只收环境状态坐标,输出均值向量和对数标准差向量,再喂进高斯过程得到动作的概率分布。因为分布是随机的,探索性比确定性策略强,但也意味着训练逻辑和评论者完全两路。 下面这段是 MQL5 里算对数概率的核心函数,用重参数化技巧把随机性拆出来: //+------------------------------------------------------------------+ // Function to compute the Gaussian probability distribution and log // probabilities //+------------------------------------------------------------------+ vectorf CSignalSAC::LogProbabilities(vectorf &Mean, vectorf &Log_STD) { vectorf _log_probs; // Compute standard deviations from log_std vectorf _std = exp(Log_STD); // Sample actions and compute log probabilities float _z = float(rand() % USHORT_MAX / USHORT_MAX); // Generate N(0, 1) sample // Sample action using reparameterization trick: action = mean + std * N(0, 1) vectorf _actions = Mean + (_std * _z); // Compute log probability of the sampled action vectorf _variance = _std * _std; vectorf _diff = _actions - Mean; _log_probs = -0.5f * (log(2.0f * M_PI * _variance) + (_diff * _diff) / _variance); return(_log_probs); } 逐行看:exp(Log_STD) 把对数标准差还原成标准差;rand()%USHORT_MAX/USHORT_MAX 生成一个近似标准正态的噪声 _z;Mean + (_std * _z) 就是重参数化采样,动作变成可微形式;最后那行套的是单维高斯对数概率公式,常数 0.5 和 2π 是解析解自带的,不是拍脑袋。 在 MT5 里把这段代码挂上参与者网络后,你能直接改 Log_STD 的初始偏置来调探索强度——偏置给大,早期动作更散,金价或欧元波动剧烈时可能更快跳出局部策略,但过散会拖慢收敛,外汇和贵金属杠杆高,参数乱动容易放大回撤风险。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">// Function to compute the Gaussian probability distribution and log class=class="str">"cmt">// probabilities class=class="str">"cmt">//+------------------------------------------------------------------+ vectorf CSignalSAC::LogProbabilities(vectorf &Mean, vectorf &Log_STD) { vectorf _log_probs; class=class="str">"cmt">// Compute standard deviations from log_std vectorf _std = exp(Log_STD); class=class="str">"cmt">// Sample actions and compute log probabilities class="type">class="kw">float _z = class="type">class="kw">float(rand() % USHORT_MAX / USHORT_MAX); class=class="str">"cmt">// Generate N(class="num">0, class="num">1) sample class=class="str">"cmt">// Sample action class="kw">using reparameterization trick: action = mean + std * N(class="num">0, class="num">1) vectorf _actions = Mean + (_std * _z); class=class="str">"cmt">// Compute log probability of the sampled action vectorf _variance = _std * _std; vectorf _diff = _actions - Mean; _log_probs = -class="num">0.5f * (log(class="num">2.0f * M_PI * _variance) + (_diff * _diff) / _variance); class="kw">return(_log_probs); }
◍ SAC 里的熵与温度怎么牵着策略走
SAC 的参与者网络输出的是随机策略,本质由概率分布参数化,本文实现用的是高斯分布。分布的熵刻画了智能体在某状态下可能出手的分散程度:熵高对应广撒网式探索,熵低则偏向判定性利用,目的就是压住过早收敛到次优解的风险。 温度参数 α 直接称量熵项在目标里的权重。我们当前写死为 0.5,对数概率函数里已体现;α 大鼓励探索,小则催熟利用,0.5 算是折中。若放任不管,Log-STD 会直接拽着熵跑——它管动作分布的扩散,高 Log-STD 意味更宽更不确定的出手,低 Log-STD 把样本死死收在 μ 附近,变相增加利用。 为稳训练,常把 log(σ) 用“z”参数夹在一段区间里,免得熵爆表或塌缩。自动熵调谐则另开一路:用目标熵 H 动态修 α,更新式含学习率 λ 与当前策略动作期望的对数概率。λ 控 α 追偏离的速度,H 在离散动作(买/卖/持)时按动作维度给,连续动作可缩放。 自动调 α 的好处是智能体随环境变自己改探索-利用配比,省去手调;代价是熵项常驻会带来额外计算开销,且探索过泛会以牺牲已知高回报策略为代价拖慢效率。外汇与贵金属市场信息不全、跳空频繁,高熵策略理论上更抗过拟合,但实盘仍属高风险,参数平衡需自己在 MT5 回测里盯曲线。
连续控制场景里 SAC 凭什么压过 DQN
在机械臂固定并搬运物体到指定位置的任务里,关节扭矩与角度调整落在连续动作空间。DQN 原生适配离散动作,硬扩到连续域会让可用离散动作数随维度指数膨胀,训练开销陡增且效率差。它还靠 ε-贪婪平衡探索利用,在离散化高维空间里往往失灵,且自带高估偏差,奖励波动大时训练易抖。 SAC 直接用随机策略输出连续动作分布,省掉离散化环节,机械臂控制更平顺精准。其三个网络分工:Actor 生成概率加权的动作分布提高效率并缓过早探索;双 Critic 取 Q 值最小值回传 Actor,抑制过估、稳住训练。熵项加自动温度 alpha 调整进一步促探索。 Open AI Gym 的 Reacher、Fetch 公开样例显示,DQN 因离散输出与弱探索,策略常陷次优、臂姿生硬;SAC 凭随机策略出平滑精确动作,任务完成更快、碰撞少,对物体或目标位移的适应更好。外汇与贵金属行情虽非机械臂,但连续止损仓位调节也属连续控制,这类算法迁移需警惕过拟合与实盘高风险。
「把 SAC 网络从 Python 搬进 MT5 的实操路径」
在 MT5 里跑深度学习模型,MQL5 原生只支持 OpenCL,面对 Python 那套 TensorFlow / PyTorch 高级库仍显单薄。但 Python 社区产出的 SAC(软演员-评论者)类架构,已经能在实际训练效率上拉开差距,外汇与贵金属交易者若想自建模型,绕不开这条跨语言链路。 手工实现 SAC 时,得在 TensorFlow/Keras 里分别定义三个主网络:演员网络输出高斯采样的随机策略,两个评论者网络做孪生 Q 学习以压住高估偏差,熵正则固定 alpha=0.5 管探索。训练数据用旧版 MT5 库导入,按 2:1 切分训练集与测试集,在 for 循环里模拟交易环境——这套写法效率偏低,但反向传播中要拿评论者 Q 值去训演员,标准 fit 函数用不了。 训完把三个网络导成 ONNX 才是关键一步。ONNX 作为开放神经网络交换标准,能让 PyTorch、SciKit-Learn 训出的模型在 MQL5 里直接调,不必重写逻辑。编译单个 ex5 把 ONNX 当资源嵌进去,交易者只管一个文件。导出时务必记清每层输入输出形状,MQL5 初始化 ONNX 句柄就靠这个;tf2onnx 可行,onnxmltools、skl2onnx 也都能用。 第二种路数是用 TensorFlow 自带的张量智代库,初始化连环境带智能体一起包了,比手写 for 循环更贴近强化学习本体。下一篇再拆它,现在先记住:跨到 ONNX 这步不做实,模型进 MT5 就是空中楼阁,贵金属杠杆品种的高波动更会放大形状录错带来的静默失效。
class="kw">import numpy as np class="kw">import pandas as pd from sklearn.model_selection class="kw">import train_test_split class="kw">import tensorflow as tf print("Num GPUs Available: ", len(tf.config.list_physical_devices(&class="macro">#x27;GPU&class="macro">#x27;))) from tensorflow.keras class="kw">import optimizers from tensorflow.keras.layers class="kw">import Input from tensorflow.keras class="kw">import layers from tensorflow class="kw">import keras class="kw">import tf2onnx class="kw">import onnx class="kw">import os # Define the actor network def ActorNetwork(state_dim, action_dim, hidden_units=class="num">256): """ Creates a simple SAC actor network. :param state_dim: The dimension of the state space. :param action_dim: The dimension of the action space. :param hidden_units: The number of hidden units in each layer. :class="kw">return: A Keras model representing the actor network. """ # Input layer state_input = layers.Input(shape=(state_dim, )) # Hidden layers(Dense layers with ReLU activation) x = layers.Dense(hidden_units, activation=&class="macro">#x27;relu&class="macro">#x27;)(state_input) x = layers.Dense(hidden_units, activation=&class="macro">#x27;relu&class="macro">#x27;)(x) # Output layer: output means(mu) and log standard deviation(log_std) for Gaussian distribution output_size = action_dim + action_dim stacked_mean_logs = layers.Dense(output_size)(x) # Create the model actor_model = tf.keras.Model(inputs=state_input, outputs=stacked_mean_logs) class="kw">return actor_model # Define the critic network def CriticNetwork(state_dim, action_dim, hidden_units=class="num">256): """ Creates a simple SAC critic network(Q-value approximation). :param state_dim: The dimension of the state space. :param action_dim: The dimension of the action space. :param hidden_units: The number of hidden units in each layer. :class="kw">return: A Keras model representing the critic network. """ input_size = state_dim + action_dim