来自专业程序员的提示(第二部分):在智能交易系统、脚本和外部程序之间存储和交换参数·进阶篇
📘

来自专业程序员的提示(第二部分):在智能交易系统、脚本和外部程序之间存储和交换参数·进阶篇

第 2/3 篇

把交易时段配置抽成全局单例

在 MT5 里写 EA 或脚本时,时段颜色、冬夏令起始时间这类参数如果散落在各个类里,后期调黄金和美盘的边界会非常痛苦。下面这套做法把配置收敛到一个 AppSettings 类,通过 Uterminal.mqh 用 #include 挂到 EA 和所有脚本上,全局只用 gAppSettings 一次初始化,任何类的实例直接读它,不必层层传参。 配置文件 AppSettings.txt 必须存成 UCS-2 LE with BOM,否则中文注释和字段解析可能直接失效。里面几个硬参数值得盯一下:欧洲时段冬季起点 10:00 服务器时间、夏季 09:00;欧洲时段色值 224,255,255,美洲 255,240,245;高亮天数默认 10 天。改这些数值不需要动代码,重启 EA 即可生效。 类声明里 private 只有一个 Params* m_params,所有对外字段都是 public——TimeEurWinter、ColorSessionUsd、NumberColorDays 等从 txt 读入,PeriodTrends、TradePlan、IsValid 由程序运行时填。构造函数先 new Params 再 Load,若 Total()==0 就把 IsValid 置 false 并退出,等于文件空了整个配置直接作废。外汇和贵金属波动受时段切换影响明显,这类全局配置出错可能让画线和计划整体偏移,实操前务必在策略测试器里跑一遍空文件场景验证容错。

MQL5 / C++
# -------------------------------------------------------------------
# 智能交易系统和脚本设置
# 文件编码 = UCS-class="num">2 LE with BOM(需要!!!) class=class="str">"cmt">// 此为 Unicode
# -------------------------------------------------------------------
TimeEurWinter = class="num">10:class="num">00 # 欧洲时段开始冬季时间 (服务器时间)
TimeEurSummer = class="num">09:class="num">00 # 欧洲时段开始夏季时间 (服务器时间)
ColorSessionEur = class="num">224,class="num">255,class="num">255 # 欧洲时段颜色
ColorSessionUsd = class="num">255,class="num">240,class="num">245 # 美洲时段颜色
NumberColorDays = class="num">10 # 高亮天数 (时段)
class="macro">#class="kw">property copyright "Copyright class="num">2020, Malik Arykov"
class="macro">#class="kw">property link      "malik.arykov@gmail.com"
class="macro">#class="kw">property strict
class="macro">#include <Cayman/Params.mqh>
class=class="str">"cmt">// 应用程序参数名
class="macro">#define APP_TIME_EUR_SUMMER "TimeEurSummer"
class="macro">#define APP_TIME_EUR_WINTER "TimeEurWinter"
class="macro">#define APP_TIME_TRADE_ASIA "TimeTradeAsia"
class="macro">#define APP_COLOR_SESSION_EUR "ColorSessionEur"
class="macro">#define APP_COLOR_SESSION_USD "ColorSessionUsd"
class="macro">#define APP_NUMBER_COLOR_DAYS "NumberColorDays"
class=class="str">"cmt">// -----------------------------------------------------------------------------
class=class="str">"cmt">// 智能交易系统和脚本的常用设置                                                     |
class=class="str">"cmt">// -----------------------------------------------------------------------------
class AppSettings {
class="kw">private:
    Params  *m_params;
class="kw">public:
    class=class="str">"cmt">// 设置位于 AppSettings.txt 文件
    class="type">class="kw">string  TimeEurSummer; class=class="str">"cmt">// 欧洲时段开始夏季时间
    class="type">class="kw">string  TimeEurWinter; class=class="str">"cmt">// 欧洲时段开始冬季时间
    class="type">class="kw">string  TimeTradeAsia; class=class="str">"cmt">// 亚洲走廊交易结束时间
    class="type">color   ColorSessionEur; class=class="str">"cmt">// 欧洲时段颜色
    class="type">color   ColorSessionUsd; class=class="str">"cmt">// 美洲时段颜色
    class="type">int     NumberColorDays; class=class="str">"cmt">// 高亮天数
    class=class="str">"cmt">// 由程序设置
    class="type">class="kw">string  PeriodTrends; class=class="str">"cmt">// 趋势计算周期 (D1,H4)
    class="type">class="kw">string  TradePlan; class=class="str">"cmt">// 交易方向(简要计划)
    class="type">bool    IsValid; class=class="str">"cmt">// 参数有效性
    
    class=class="str">"cmt">// 方法
    AppSettings();
    ~AppSettings() { class="kw">delete m_params; };
    class="type">void Dump(class="type">class="kw">string sender);
};
class=class="str">"cmt">// -----------------------------------------------------------------------------
class=class="str">"cmt">// 构造函数                                                                      |
class=class="str">"cmt">// -----------------------------------------------------------------------------
AppSettings::AppSettings() {
    IsValid = true;
    m_params = new Params();
    m_params.Load(PATH_APP_SETTINGS);
    if (m_params.Total() == class="num">0) {
        PrintFormat("%s / ERROR: Invalid file / %s", __FUNCTION__, PATH_APP_SETTINGS);
        IsValid = class="kw">false;
        class="kw">return;
    }
    
    TimeEurWinter = m_params.GetValue(APP_TIME_EUR_WINTER);
    TimeEurSummer = m_params.GetValue(APP_TIME_EUR_SUMMER);
    TimeTradeAsia = m_params.GetValue(APP_TIME_TRADE_ASIA);
    ColorSessionEur = StringToColor(m_params.GetValue(APP_COLOR_SESSION_EUR));

◍ 把配置落进日志:Dump 与全局指针

这段逻辑承接上一节的参数读取,把已经转好的颜色与天数变量通过 Dump 方法打印到 MT5 专家日志。注意 USD 会话颜色那两行实际复用了 ColorSessionEur 变量,若直接抄进实盘面板,美元时段会画出和欧元时段一样的颜色——这是个容易漏看的笔误。 Dump 里用了 PrintFormat 的格式化输出,例如 %s = %s / %s 配合 ColorToString 的第二参数 true,能把颜色同时以名称与 ARGB 数值双形式打出,方便你核对主题配置是否生效。 末尾的 extern AppSettings *gAppSettings 把设置对象暴露为全局指针,其他模块直接用 gAppSettings->Dump(__FUNCTION__) 就能在任意函数内吐出当前配置。外汇与贵金属行情受时段切换影响明显,这类会话配色误设可能在高风险波动期干扰你的视觉判断,建议开 MT5 跑一遍下面代码确认输出。

MQL5 / C++
  ColorSessionUsd = StringToColor(m_params.GetValue(APP_COLOR_SESSION_USD));
  NumberColorDays = (class="type">int)StringToInteger(m_params.GetValue(APP_NUMBER_COLOR_DAYS));
}
class=class="str">"cmt">// -----------------------------------------------------------------------------
class=class="str">"cmt">// 打印设定参数                                                                   | 
class=class="str">"cmt">// -----------------------------------------------------------------------------
class="type">void AppSettings::Dump(class="type">class="kw">string sender) {
  PrintFormat("sender=%s / %s", sender, PATH_APP_SETTINGS);
  PrintFormat("%s = %s", APP_TIME_EUR_WINTER, TimeEurWinter);
  PrintFormat("%s = %s", APP_TIME_EUR_SUMMER, TimeEurSummer);
  PrintFormat("%s = %s", APP_TIME_TRADE_ASIA, TimeTradeAsia);
  PrintFormat("%s = %s / %s", APP_COLOR_SESSION_EUR, ColorToString(ColorSessionEur), ColorToString(ColorSessionEur, true));
  PrintFormat("%s = %s / %s", APP_COLOR_SESSION_USD, ColorToString(ColorSessionEur), ColorToString(ColorSessionEur, true));
  PrintFormat("%s = %i", APP_NUMBER_COLOR_DAYS, NumberColorDays);
}

class="kw">extern AppSettings  *gAppSettings; class=class="str">"cmt">// 应用程序设置

「分析器参数怎么落盘与回灌」

Cayman 智能交易系统把 AnalyzerTrend、AnalyserLevel、AnalyserVirtSL 这类分析器各自绑死在一个时间帧上,只有该周期走完一根新柱才会触发计算。参数不写在 EA 内部,而是以 Key=Value 文本落盘,路径形如 Files\Cayman\Params\128968168864101576\exp_05_Lev607A160E_H4.txt,其中 128968168864101576 就是 ChartID() 转出来的图表 ID,H4 代表跟踪周期。 重启 EA(比如切了时间帧再切回来)时,基类 Analyser 会从对应 txt 把参数读回;若新柱时间还没到,就沿用前一根柱的 resultAnalyser 与 colorAnalyser,并把它们打到 EA 注释里,避免重复算。下面这段是实际文件抽出来的内容,原文件无注释,这里补了行内说明方便对照。 别把文件命名当摆设:exp_05_Lev607A160E_H4 里 05 是分析器类型、Lev607A160E 是具体级别名,改了名 EA 就找不回旧参数,相当于分析器被静默重置。

MQL5 / C++
class=class="str">"cmt">// 交易级别参数
nameObj=Lev607A160E class=class="str">"cmt">// 交易级别名称
kindLevel=class="num">1 class=class="str">"cmt">// 级别类型 (class="num">1 - 阻力)
riskValue=class="num">1.00 class=class="str">"cmt">// 级别突破时的成交量 (class="num">1)
riskUnit=class="num">1 class=class="str">"cmt">// 交易量变化单位(保证金的 class="num">1 - %) (class="num">1 - 保证金的百分比 %)
algClose=class="num">2 class=class="str">"cmt">// 成交平仓算法(class="num">2 – 两根修正柱线)
ticketNew=class="num">0 class=class="str">"cmt">// 在级别突破时的成交单据
ticketOld=class="num">0 class=class="str">"cmt">// 在级别突破时的平仓单据
profits=class="num">0 class=class="str">"cmt">// 以点数为单位的计划内利润
losses=class="num">0 class=class="str">"cmt">// 以点数为单位的计划内亏损
class=class="str">"cmt">// 分析器参数
symbol=EURUSD class=class="str">"cmt">// 品种名称
period=class="num">16388 class=class="str">"cmt">// 分析器周期 (H4)
time0Bar=class="num">1618603200 class=class="str">"cmt">// 零号柱线时间 (秒数)
typeAnalyser=class="num">5 class=class="str">"cmt">// 分析器类型
colorAnalyser=class="num">16711935 class=class="str">"cmt">// 分析器结果的颜色
resultAnalyser=Lev607A160E, H4, class="num">20:class="num">00, RS class=class="str">"cmt">// 分析器结果

脚本怎么把级别参数喂给EA

SetTradeLevel 脚本在图表上选中一条直线、趋势线或矩形后,会识别该对象为交易级别,并把参数写到 Files\Cayman\Params\128968168864101576\exp_05_Lev607A160E_H4.txt 这类路径下,随后通过 SendCommand 把命令和文件路径推给 EA。 EA 侧在每次 OnTick 里调用 CheckExpernalCommand(),检查名为 NAME_OBJECT_CMD 的标签对象是否存在。命中后读取命令与文件路径,马上删掉该对象,再按文件名定位正在跑的分析器:cmdDelete 删分析器,cmdUpdate 从文件刷新参数,cmdNew 则按文件建新分析器。 Params 类封装了「关键字=参数值」文本文件的读写逻辑。原作者提过,若在 MQL5 把 m_items 换成 CHashMap,Add/GetValue/Find 代码量会明显缩水,但因该类在 MQL4 也要用、且参数仅初始化时读一次,便沿用旧实现——银行开发圈有句老话:能跑就别动它。 下面这段是 SendCommand 相关核心代码,注意它用 OBJ_LABEL 做命令载体,把文件路径塞进 OBJPROP_TEXT,把命令枚举塞进 OBJPROP_ZORDER,EA 靠读这两个属性就知道要干什么。

MQL5 / C++
class=class="str">"cmt">// -----------------------------------------------------------------------------
class=class="str">"cmt">// 将级别参数发送到智能交易系统                                                     |
class=class="str">"cmt">// -----------------------------------------------------------------------------
NCommand SendCommand() {
    class=class="str">"cmt">// 加载级别参数(如果有)
    Params *params = new Params();
    class="type">class="kw">string speriod = UConvert::PeriodToStr(_Period);
    params.Load(PREFIX_EXPERT, anaLevel, gNameLev, speriod);
    class=class="str">"cmt">// 定义命令
    NCommand cmd =
        (gKindLevel == levUnknown) ? cmdDelete :
        (params.Total() > class="num">0) ? cmdUpdate :
        cmdCreate;
    class=class="str">"cmt">// 保存参数
    params.Clear();
    params.Add(PARAM_NAME_OBJ, gNameLev);
    params.Add(PARAM_TYPE_ANALYSER, IntegerToString(anaLevel));
    params.Add(PARAM_PERIOD, IntegerToString(_Period));
    params.Add(PARAM_KIND_LEVEL, IntegerToString(gKindLevel));
    params.Add(PARAM_RISK_VALUE, DoubleToString(gRiskValue, class="num">2));
    params.Add(PARAM_RISK_UNIT, IntegerToString(gRiskUnit));
    params.Add(PARAM_ALG_CLOSE, IntegerToString(gAlgClose));
    params.Add(PARAM_TICKET_OLD, IntegerToString(gTicketOld));
    params.Add(PARAM_PROFITS, IntegerToString(gProfits));
    params.Add(PARAM_LOSSES, IntegerToString(gLosses));
    params.Save();
    
    class=class="str">"cmt">// 向智能交易系统发送命令
    params.SendCommand(cmd);
    class="kw">delete params;
    
    class="kw">return cmd;
}
class=class="str">"cmt">// -----------------------------------------------------------------------------
class=class="str">"cmt">// 向智能交易系统发送命令                                                          |
class=class="str">"cmt">// -----------------------------------------------------------------------------
class="type">void Params::SendCommand(NCommand cmd) {
    class="type">class="kw">string nameObj = NAME_OBJECT_CMD;
    ObjectCreate(class="num">0, nameObj, OBJ_LABEL, class="num">0, class="num">0, class="num">0);
    ObjectSetString(class="num">0, nameObj, OBJPROP_TEXT, m_path);
    ObjectSetInteger(class="num">0, nameObj, OBJPROP_ZORDER, cmd);
    ObjectSetInteger(class="num">0, nameObj, OBJPROP_TIMEFRAMES, class="num">0);
}
class="macro">#class="kw">property copyright "Copyright class="num">2020, Malik Arykov"
class="macro">#class="kw">property link      "malik.arykov@gmail.com"
class="macro">#include <Arrays/ArrayString.mqh>
class="macro">#include <Cayman/UConvert.mqh>
class="macro">#include <Cayman/UFile.mqh>
class=class="str">"cmt">// -----------------------------------------------------------------------------
class=class="str">"cmt">// 参数类(关键字=参数值字符串 class="macro">#注释)                                               |
class=class="str">"cmt">// -----------------------------------------------------------------------------
class Params {
class="kw">private:
    class="type">class="kw">string  m_path; class=class="str">"cmt">// 参数文件路径
    NCommand m_cmd; class=class="str">"cmt">// 智能交易系统命令
    CArrayString *m_items; class=class="str">"cmt">// 配对数组 {关键字=参数值}

◍ 参数容器的增查与析构细节

在 MT5 的 EA 或指标工程里,把配置项塞进一个 Params 类来管理,比直接写全局变量要干净得多。下面这段实现用的是 CArrayString 存「key=value」文本行,构造时 new、析构时 delete,避免内存泄漏。 [CODE] int Find(string key); public: Params(); ~Params() { delete m_items; }; void Clear() { m_items.Clear(); }; int Total() { return m_items.Total(); }; string Path() { return m_path; }; CArrayString *Items() { return m_items; }; void Add(string line) { m_items.Add(line); }; bool Add(string key, string value); string GetValue(string key); void Load(string prefix, int typeAnalyser, string nameObj, string speriod); void Load(string path); void Save(); void SendCommand(NCommand cmd); NCommand TakeCommand(); void Dump(string sender); }; // ----------------------------------------------------------------------------- // 默认构造函数 | // ----------------------------------------------------------------------------- Params::Params() { m_items = new CArrayString(); } // ----------------------------------------------------------------------------- // 添加一个 key=value 对 | // ----------------------------------------------------------------------------- bool Params::Add(string key, string value) { int j = Find(key); string line = key + "=" + value; if (j >= 0) { // 更新 m_items.Update(j, line); return false; } else { // 添加 m_items.Add(line); return true; } } // ----------------------------------------------------------------------------- // 依据关键字获取参数值 | // ----------------------------------------------------------------------------- string Params::GetValue(string key) { // 搜索关键字 int j = Find(key); if (j < 0) return NULL; // 无关键字 // 检查分隔符 string line = m_items.At(j); j = StringFind(line, "="); if (j < 0) { // 没有 = PrintFormat("%s / ERROR: Invalid string %s", __FUNCTION__, line); return NULL; } // 返回数值 return UConvert::Trim(StringSubstr(line, j + 1)); } // ----------------------------------------------------------------------------- // 依据关键字查找参数值 | // ----------------------------------------------------------------------------- int Params::Find(string key) { int index = -1; for (int j = 0; j < m_items.Total(); j++) { [/CODE] 逐行看:Params() 里 m_items = new CArrayString(); 在堆上开字符串数组;析构 ~Params() 直接 delete m_items 释放。Add(key,value) 先 Find 看 key 在不在,在就 Update 返回 false(算更新),不在就 Add 返回 true(算新增)——调用方能用返回值判断是首次写入还是覆盖。 GetValue 的查找链是 Find→取行→StringFind 找「=」→截子串并 Trim。若 key 不存在或行里没「=」,都返回 NULL 并打印函数级错误。外汇与贵金属交易使用此类自管参数模块时波动剧烈、风险高,参数读写异常可能让 EA 逻辑偏离预期,建议在 MT5 策略测试器里先跑空载校验 Add/GetValue 的往返一致性。

MQL5 / C++
class="type">int Find(class="type">class="kw">string key);
class="kw">public:
    Params();
    ~Params() { class="kw">delete m_items; };
    class="type">void Clear() { m_items.Clear(); };
    class="type">int Total() { class="kw">return m_items.Total(); };
    class="type">class="kw">string Path() { class="kw">return m_path; };
    CArrayString *Items() { class="kw">return m_items; };
    class="type">void Add(class="type">class="kw">string line) { m_items.Add(line); };
    class="type">bool Add(class="type">class="kw">string key, class="type">class="kw">string value);
    class="type">class="kw">string GetValue(class="type">class="kw">string key);
    class="type">void Load(class="type">class="kw">string prefix, class="type">int typeAnalyser, class="type">class="kw">string nameObj, class="type">class="kw">string speriod);
    class="type">void Load(class="type">class="kw">string path);
    class="type">void Save();
    class="type">void SendCommand(NCommand cmd);
    NCommand TakeCommand();
    class="type">void Dump(class="type">class="kw">string sender);
};
class=class="str">"cmt">// -----------------------------------------------------------------------------
class=class="str">"cmt">// 默认构造函数                                                                   |
class=class="str">"cmt">// -----------------------------------------------------------------------------
Params::Params() {
    m_items = new CArrayString();
}
class=class="str">"cmt">// -----------------------------------------------------------------------------
class=class="str">"cmt">// 添加一个 key=value 对                                                         | 
class=class="str">"cmt">// -----------------------------------------------------------------------------
class="type">bool Params::Add(class="type">class="kw">string key, class="type">class="kw">string value) {
    class="type">int j = Find(key);
    class="type">class="kw">string line = key + "=" + value;
    if (j >= class="num">0) { class=class="str">"cmt">// 更新
        m_items.Update(j, line);
        class="kw">return class="kw">false;
    }
    else { class=class="str">"cmt">// 添加
        m_items.Add(line);
        class="kw">return true;
    }
}
class=class="str">"cmt">// -----------------------------------------------------------------------------
class=class="str">"cmt">// 依据关键字获取参数值                                                            |
class=class="str">"cmt">// -----------------------------------------------------------------------------
class="type">class="kw">string Params::GetValue(class="type">class="kw">string key) {
    class=class="str">"cmt">// 搜索关键字
    class="type">int j = Find(key);
    if (j < class="num">0) class="kw">return NULL; class=class="str">"cmt">// 无关键字
    
    class=class="str">"cmt">// 检查分隔符
    class="type">class="kw">string line = m_items.At(j);
    j = StringFind(line, "=");
    if (j < class="num">0) { class=class="str">"cmt">// 没有 =
        PrintFormat("%s / ERROR: Invalid class="type">class="kw">string %s", __FUNCTION__, line);
        class="kw">return NULL;
    }
    
    class=class="str">"cmt">// 返回数值
    class="kw">return UConvert::Trim(StringSubstr(line, j + class="num">1));
}
class=class="str">"cmt">// -----------------------------------------------------------------------------
class=class="str">"cmt">// 依据关键字查找参数值                                                            |
class=class="str">"cmt">// -----------------------------------------------------------------------------
class="type">int Params::Find(class="type">class="kw">string key) {
    class="type">int index = -class="num">1;
    for (class="type">int j = class="num">0; j < m_items.Total(); j++) {

「参数读写与命令下发落到文件对象」

上面这段把分析器的参数持久化逻辑收口了:用 ChartID 拼出独立路径,避免多图表实例互相覆盖参数文件。文件名格式是 %s%02i_%s_%s.txt,其中 typeAnalyser 占两位、nameObj 和周期字符串后缀,实盘里开两个 EURUSD 的 H1 与 M15 窗口会生成不同文件。 Load 的重载先从文本按行切分,遇到 # 开头的整行直接跳过,行内 # 之后的内容也截掉,所以你在参数文件里写注释不会影响解析。m_items 只存非空行,意味着空行和纯注释不占内存。 Save 是覆盖写:把 m_items 逐行拼成文本再调用 UFile::SaveText 重写原路径。如果你在 MT5 里改了参数没生效,先确认 m_path 指向的文件是不是被别的图表实例占用。 SendCommand 借一个隐藏的 OBJ_LABEL 把文件路径写进对象属性,再把命令枚举塞进 ZORDER,EA 端轮询这个对象就能拿到指令。外汇和贵金属波动剧烈,这种文件+对象的跨模块通信要在多图表下自测竞态。

MQL5 / C++
if (StringFind(m_items.At(j), key) == class="num">0) {
      index = j;
      class="kw">break;
    }
  }
  class="kw">return index;
}
class=class="str">"cmt">// -----------------------------------------------------------------------------
class=class="str">"cmt">// 加载参数                                                                      |
class=class="str">"cmt">// -----------------------------------------------------------------------------
class="type">void Params::Load(class="type">class="kw">string prefix, class="type">int typeAnalyser, class="type">class="kw">string nameObj, class="type">class="kw">string speriod) {
  class="type">class="kw">string nameFile = StringFormat("%s%02i_%s_%s.txt", prefix, typeAnalyser, nameObj, speriod);
  m_path = StringFormat("%s%s/%s", PATH_PARAMS, IntegerToString(ChartID()), nameFile);
  if (FileIsExist(m_path)) Load(m_path);
}
class=class="str">"cmt">// -----------------------------------------------------------------------------
class=class="str">"cmt">// 加载参数                                                                      |
class=class="str">"cmt">// -----------------------------------------------------------------------------
class="type">void Params::Load(class="type">class="kw">string path) {

  m_path = path;
  if (!FileIsExist(m_path)) class="kw">return;
  class=class="str">"cmt">//PrintFormat("%s / %s", __FUNCTION__, m_path);
  class="type">class="kw">string text = UFile::LoadText(m_path);
  if (text == NULL) class="kw">return;

  class=class="str">"cmt">// 将文本切分成数行
  class="type">class="kw">string line, lines[];
  class="type">int numLines = StringSplit(text, DLM_LINE, lines);
  for (class="type">int j = class="num">0; j < numLines; j++) {
    line = lines[j];
    class=class="str">"cmt">// 删除注释
    class="type">int k = StringFind(line, "#");
    if (k == class="num">0) class="kw">continue; class=class="str">"cmt">// 整个字符串是一条注释
    if (k > class="num">0) line = StringSubstr(line, class="num">0, k);
    class=class="str">"cmt">// 添加一个非空字符串
    if (line != "") m_items.Add(line);
  }
}
class=class="str">"cmt">// -----------------------------------------------------------------------------
class=class="str">"cmt">// 保存参数                                                                     |
class=class="str">"cmt">// -----------------------------------------------------------------------------
class="type">void Params::Save() {
  class="type">class="kw">string text = "";
  for (class="type">int j = class="num">0; j < m_items.Total(); j++) {
    text += m_items.At(j) + "\n";
  }
  class=class="str">"cmt">// 重写现有文件
  UFile::SaveText(text, m_path, true);
}
class=class="str">"cmt">// -----------------------------------------------------------------------------
class=class="str">"cmt">// 向智能交易系统发送命令                                                          | 
class=class="str">"cmt">// -----------------------------------------------------------------------------
class="type">void Params::SendCommand(NCommand cmd) {
  class="type">class="kw">string nameObj = NAME_OBJECT_CMD;
  ObjectCreate(class="num">0, nameObj, OBJ_LABEL, class="num">0, class="num">0, class="num">0);
  ObjectSetString(class="num">0, nameObj, OBJPROP_TEXT, m_path);
  ObjectSetInteger(class="num">0, nameObj, OBJPROP_ZORDER, cmd);

常见问题

用全局指针存一个配置对象,EA初始化时读它;外汇贵金属波动大,先在小周期回测验证再上实盘。
可用全局终端变量或共享内存对象,脚本写、EA读;注意加读写锁,防止半截参数被加载。
小布盯盘的AIGC可读取品种页诊断并导出参数快照,你点一下就能回灌给下一轮回测,重复劳动交给它。
在Dump日志里打印引用计数和指针地址,崩之前比对增删配对;高风险品种先关实盘只跑模拟。
日志里带时间戳和来源标签,外部改的标Ext,EA自己改的标Self;每天开盘前扫一遍差异。