在 MQL4 和 MQL5 框架下开发 OpenAI 的 ChatGPT 功能·进阶篇
(2/3)· 从数学建模到 EA 落地,避开幻觉陷阱的正确调用姿势
◍ 让 ChatGPT 把数学式翻成可跑的代码
在 MT5 写交易逻辑时,数学式子经常卡在「想得清楚、写得别扭」。把方程用 LaTeX 贴给 ChatGPT,它能做符号变换并给出数值化写法,再丢进免费 LaTeX 转换器就能看到熟悉的表达式排版,方便核对自己没写错符号优先级。 要求模型「用 LaTeX 输出」是关键一步——只要答案里含数学表达式,就明说格式,否则它常给纯文本近似,复制进 MQL5 容易丢括号。部分 Telegram 机器人(作者友人开发、亲测可用)把图像转 LaTeX 也接了 ChatGPT,懒得手敲公式时能用。 实测它解显式/数值不等式、方程组、简单积分都行,但作为数学背景的人得提醒:它并非始终严谨,偶尔留半成品。外汇与贵金属杠杆高、滑点凶,直接信未校验的数学结论可能把仓位算爆,所有推导务必人工复算一遍。 下面这段是原文给出的 Lorentz 因子示例,MQL5 里若要做相对论修正(虽极少用)可参照其数值结构: E = mc^2 m = m0 / sqrt(1 - v^2/c^2) 代码层通常只用数值法,没有微分数学,复杂符号变换交给模型、落地计算自己写。
E &= mc^class="num">2\\ m &= \frac{m_0}{\sqrt{class="num">1-\frac{v^class="num">2}{c^class="num">2}}}
把大块 EA 代码拆成可独立求解的子任务
高完成度的交易系统代码量都不小,而单次对话能吐出的字符数有上限。反复试过生成大型复杂代码后,结论很直接:每段输出必须够短,代码要分部分给出来。 做法是为 EA、指标或脚本先定一份开发计划,每个子项是一个能单独求解的子任务,按顺序逐个实现再拼回整体。子任务比整坨代码简单,执行更快也更不容易出错。 我更习惯自己先想清楚 EA 主架构,不让 AI 参与顶层设计,只让它填独立过程。具体落地时先用空函数搭出近似模板,再要求它逐个实现函数体,原型或结构体也可以一并交给它。 若你连策略都没谱,可以先和模型聊,让它给几种可选交易方式。从零起步通常落到四种情形:不知架构也不知交易方式;知大致全貌但方式未定;有舒适架构但缺交易逻辑;架构与逻辑都清楚。 不知道架构时,先实现主框架——类、输入变量、字段、方法原型、接口及主要交易功能,其余过程后补。在 ChatGPT 合理拆解下,框架代码可能只占总量 5–10%,过程实现占约 90%,因被拆得很碎而更易执行。有现成模板当然更省事,但那需要你自己的经验打底。外汇与贵金属波动剧烈、杠杆风险高,任何生成代码都先在 MT5 策略测试器跑回测再上实盘。
「让 AI 搭出可复用的指标骨架」
用 ChatGPT 辅助写 EA 时,最划算的做法不是让它吐一整套黑箱系统,而是把交易逻辑拆成四个预案——开多、平多、开空、平空,让模型只给可读的布尔条件。例如模型曾给出:价在 EMA 上、价差小于 ATR*比率且 RSI<30 做多;价收 SMA 下或破布林上轨平多。这类条件成功返 true、失败返 false,市价单够用。 把 RSI 阈值参数化(K1 为低区、K2=100-K1 为高区)能直接提升后续优化空间。原示例里 K1 取 30、K2 取 70,换成变量后 EA 在调参阶段可能更灵活,这是实打实可验证的改动点。 指标实现上,我不依赖 MQL5 内置函数,而是自建定长数组存最近 N 根柱线值,新柱平移、断线清数组、收盘计算。下面这段初始化与平移的骨架代码就是该思路的核心,复制到 MT5 里改 LastBars 就能跑。 自建指标避开了内置指标的冗余计算,也方便以后移植到 MQL4 或其它语言项目。外汇与贵金属波动大、滑点高,多币种并行回测前务必小资金验证,杠杆品种风险极高。
class="type">class="kw">double SMA1Values[]; class=class="str">"cmt">// Array for storing SMA values class="type">class="kw">double EMAValues[]; class=class="str">"cmt">// Array for storing EMA values(exponential) class="type">class="kw">double RSIValues[]; class=class="str">"cmt">// Array for storing RSI values class="type">class="kw">double BollingerBandsUpperValues[]; class=class="str">"cmt">// Array for storing BollingerBands values, upper class="type">class="kw">double BollingerBandsMiddleValues[]; class=class="str">"cmt">// Array for storing BollingerBands values, middle class="type">class="kw">double BollingerBandsLowerValues[]; class=class="str">"cmt">// Array for storing BollingerBands values, lower class="type">class="kw">double ATRValues[];class=class="str">"cmt">// array for storing Average True Range values class=class="str">"cmt">//Prepare indicator arrays class="type">void PrepareArrays() { ArrayResize(SMA1Values, LastBars); ArrayResize(EMAValues, LastBars); ArrayResize(RSIValues, LastBars); ArrayResize(BollingerBandsUpperValues, LastBars); ArrayResize(BollingerBandsMiddleValues, LastBars); ArrayResize(BollingerBandsLowerValues, LastBars); ArrayResize(ATRValues, LastBars); } class=class="str">"cmt">//shift of indicator values class="type">void ShiftValues() { class="type">int shift = class="num">1; for (class="type">int i = LastBars - class="num">1; i >= shift; i--) {
◍ 指标数组的移位、清空与手动均线计算
在 MT5 的 EA 或指标工程里,若用自定义数组缓存多品种指标值,断连或重初始化时要先清场。下面这段 EraseValues 把 SMA、EMA、RSI、布林带三轨和 ATR 的数组统一写成 -1.0,相当于打上“无效”标记,避免在行情中断后读到旧数据产生误信号。外汇与贵金属杠杆高,用脏数据下单可能瞬间放大亏损。 移位逻辑也很直接:ShiftHistory 里把每个指标的 [i] 赋值为 [i-shift],相当于把历史窗口整体后挪,给新 bar 腾出位置。注意 LastBars 决定了数组边界,shift 超过它就会越界。 calculateMA 是手搓均线的核心。它以 Shift+1 为计算 bar,向前取 PeriodMA 根收盘价求和再除周期;若起始索引超出 LastBars 直接返回 -1.0。LastUpdateDateTime 用 TimeCurrent() 打时间戳,方便上层判断数据新鲜度。 calculateEMA 则先以同区间 SMA 作种子,再用 multiplier = Flatness/(PeriodEMA+1) 递推。Flatness 默认 2.0 即标准 EMA 权重;调大它平滑性增加、滞后也增加。开 MT5 把这两段塞进你的指标类,改 PeriodMA 和 Flatness 就能比对与系统 iMA 的偏差。
SMA1Values[i] = SMA1Values[i - shift];
EMAValues[i] = EMAValues[i - shift];
RSIValues[i] = RSIValues[i - shift];
BollingerBandsUpperValues[i] = BollingerBandsUpperValues[i - shift];
BollingerBandsMiddleValues[i] = BollingerBandsMiddleValues[i - shift];
BollingerBandsLowerValues[i] = BollingerBandsLowerValues[i - shift];
ATRValues[i] = ATRValues[i - shift];
}
}
class=class="str">"cmt">//reset all indicator arrays if connection fails [can also be used when initializing an EA]
class="type">void EraseValues()
{
for (class="type">int i = class="num">0; i < LastBars; i++)
{
SMA1Values[i] = -class="num">1.0;
EMAValues[i] = -class="num">1.0;
RSIValues[i] = -class="num">1.0;
BollingerBandsUpperValues[i] = -class="num">1.0;
BollingerBandsMiddleValues[i] = -class="num">1.0;
BollingerBandsLowerValues[i] = -class="num">1.0;
ATRValues[i] = -class="num">1.0;
}
}
class=class="str">"cmt">//class="num">1 Function that calculates the indicator value to bar "class="num">1"
class="type">class="kw">double calculateMA(class="type">int PeriodMA,class="type">int Shift=class="num">0)
{
class="type">int barIndex=Shift+class="num">1;class=class="str">"cmt">//bar index SMA is calculated for (with a shift)
class="type">int StartIndex=barIndex + PeriodMA-class="num">1;class=class="str">"cmt">//starting bar index for calculating SMA
if (StartIndex >= LastBars) class="kw">return -class="num">1.0; class=class="str">"cmt">// Check for the availability of the bars for calculating SMA(if not valid, then the value is -class="num">1)
class="type">class="kw">double sum = class="num">0.0;
for (class="type">int i = StartIndex; i >= barIndex; i--)
{
sum += Charts[chartindex].CloseI[i];
}
LastUpdateDateTime=TimeCurrent();
class="kw">return sum / PeriodMA;
}
class=class="str">"cmt">//class="num">2 Function that calculates the value of the exponential moving average to bar "class="num">1"
class="type">class="kw">double calculateEMA(class="type">int PeriodEMA,class="type">class="kw">double Flatness=class="num">2.0,class="type">int Shift=class="num">0)
{
class="type">int barIndex = Shift+class="num">1; class=class="str">"cmt">// bar index EMA is calculated for (with a shift)
class="type">int StartIndex=barIndex + PeriodEMA-class="num">1;class=class="str">"cmt">//index of the starting bar for calculating the first SMA, for starting the recurrent calculation of EMA
if (StartIndex >= LastBars) class="kw">return -class="num">1.0; class=class="str">"cmt">// Check for the availability of the bars for calculating EMA(if not valid, then the value is -class="num">1)
class="type">class="kw">double sum = class="num">0.0;
class="type">class="kw">double multiplier = Flatness / (PeriodEMA + class="num">1); class=class="str">"cmt">// Weight multiplier
class="type">class="kw">double prevEMA;
class=class="str">"cmt">// Calculate the initial value for the EMA(the first value is considered as a normal SMA)
for (class="type">int i = StartIndex; i >= barIndex; i--)
{
sum += Charts[chartindex].CloseI[i];
}
prevEMA = sum / PeriodEMA;class=class="str">"cmt">//this is the starting value for the bar(StartIndex-class="num">1)
class=class="str">"cmt">// Apply the EMA formula for the remaining values
for (class="type">int i = StartIndex; i >= barIndex; i--)
{RSI 与布林带的自写计算落点
这段逻辑把 RSI 和布林带的计算从内置指标里抽出来,改成可按偏移量 Shift 取任意根 K 线值的函数。RSI 部分先按 PeriodRSI 窗口累加涨跌实体差,再除以周期得平均增益/损耗,最终用 100 - 100/(1+RS) 出值;若起始索引超出 LastBars 可用范围,直接返回 -1.0 表示无效。 calculateRSI 里 barIndex = Shift+1 意味着函数默认算的是偏移根数之后那一根(即 bar 1 而非当前 bar 0),回测时若想对齐信号发生柱,要把 Shift 传对,否则数值会整体错位一根。外汇与贵金属波动剧烈,RSI 在跳空行情中可能瞬间击穿 20/80 边界,仅作概率参考。 布林带用结构体 BollingerBands 一次性回传上中下三轨,calculateBollingerBands 同样以 StartIndex 做 bars 可用性的闸门,不足就三轨全填 -1.0。DeviationBB 是标准差倍数,实盘里黄金 H1 常用 2.0,但欧美盘流动性切换时轨宽可能突然放大 30% 以上,需要动态观察。 把这三段函数接进自己的 EA,开 MT5 用不同 Shift 打印前后两根 RSI 差值,能立刻看出偏移逻辑是否如预期。
prevEMA = (Charts[chartindex].CloseI[i] - prevEMA) * multiplier + prevEMA; } LastUpdateDateTime = TimeCurrent(); class="kw">return prevEMA; } class=class="str">"cmt">//class="num">3 Function for calculating RSI to bar "class="num">1" class="type">class="kw">double calculateRSI(class="type">int PeriodRSI,class="type">int Shift=class="num">0) { class="type">int barIndex = Shift+class="num">1; class=class="str">"cmt">// bar index RSI is calculated for (with a shift) class="type">int StartIndex = barIndex + PeriodRSI - class="num">1; class=class="str">"cmt">// starting bar index for calculating RSI if (StartIndex >= LastBars) class="kw">return -class="num">1.0; class=class="str">"cmt">// Check for the availability of the bars for calculating RSI(if not valid, then the value is -class="num">1) class="type">class="kw">double avgGain = class="num">0.0; class="type">class="kw">double avgLoss = class="num">0.0; class="type">class="kw">double change; class=class="str">"cmt">// Calculate initial values for avgGain and avgLoss for (class="type">int i = StartIndex; i > barIndex; i--) { change = Charts[chartindex].CloseI[i]-Charts[chartindex].OpenI[i]; if (change > class="num">0) { avgGain += change; } else if (change < class="num">0) { avgLoss -= change; } } avgGain /= PeriodRSI; avgLoss /= PeriodRSI; class=class="str">"cmt">// RSI calculation class="type">class="kw">double RS = class="num">0.0; if (avgLoss != class="num">0) { RS = avgGain / avgLoss; } class="type">class="kw">double RSI = class="num">100 - (class="num">100 / (class="num">1 + RS)); LastUpdateDateTime = TimeCurrent(); class="kw">return RSI; } class=class="str">"cmt">//structure to class="kw">return all three bollinger band lines class="kw">struct BollingerBands { class="type">class="kw">double upper; class="type">class="kw">double middle; class="type">class="kw">double lower; }; class=class="str">"cmt">//class="num">4 Function for calculating the Bollinger Bands of the indicator BollingerBands calculateBollingerBands(class="type">int PeriodBB, class="type">class="kw">double DeviationBB,class="type">int Shift=class="num">0) { class="type">int barIndex = Shift+class="num">1; class=class="str">"cmt">// bar index Bollinger Bands is calculated for (with a shift) class="type">int StartIndex = barIndex + PeriodBB - class="num">1; class=class="str">"cmt">// index of the starting bar for calculating the first SMA, for starting the recurrent calculation of EMA BollingerBands rez; rez.lower=-class="num">1.0; rez.middle=-class="num">1.0; rez.upper=-class="num">1.0; if (StartIndex >= LastBars) class="kw">return rez; class=class="str">"cmt">// Check for the availability of the bars for calculating BB(if not valid, then the value is -class="num">1) class="type">class="kw">double sum = class="num">0.0; class="type">class="kw">double prevBB;