
来自专业程序员的提示(第二部分):在智能交易系统、脚本和外部程序之间存储和交换参数
内容
概述
在本文里,我们将讨论终端重启(关闭)后如何恢复参数。 所有示例都是来自我的 Cayman 项目的真实工作代码片段。
参数存储位置
参数示例
- 零号柱线时间 例如,在检测烛条形态时,在给定时间帧内出现新柱线后对其进行一次评估是合乎逻辑的。
- 交易级别参数。 例如,您也许会选择一个交易级别,并用脚本设置级别突破时进行交易的时间和规模。 该脚本将参数传递给智能交易系统。 智能交易系统创建一个级别分析器。 分析器仅在指定时间帧内出现新柱线之后才会“开机”。
- 用户偏好。 这些包括颜色、交易规则、绘图方法和其他参数。 显然,此类参数应该一次性安装,例如,在设置文件中。
- 终端全局变量
- 图形对象
- 订单注释
- 文本文件
贮存 | 类型 | 范围 | 生存期 |
---|---|---|---|
终端全局变量 | 双精度 | 任意图表 | 上次调用之后 4 周 |
图形对象 | 任何 字符串 <= 63 个字符 | 当前图表 | 图表生存期 |
订单注释 | 字符串 <= 23 个字符 | 任意图表 | 终端生存期 |
文本文件 | 任何 无限制 | 任意图表 | 文件生存期 |
终端全局变量
终端全局变量可从任何图表获得。 它们的范围可以通过在变量名称中包含其他组件来限制,例如 ChartId、Symbol 或 Period。 不能改变的是变量类型。 您也无法保存文本。
有一个小窍门:打包/解包整数值。 如您所知,双精度数值占用 8 个字节(64 位)。 请查看以下示例:它显示了如何在一个变量中存储多个整数值。 最重要的是判定它们的最大值的位长。
// ----------------------------------------------------------------------------- // 将存至/取自全局变量的整数值打包/解包的示例 | // 运用按位运算 | // ----------------------------------------------------------------------------- void OnStart() { int value10 = 10; // 最大 = 255 (8 位) int value20 = 300; // 最大 = 65535 (16 位) bool value30 = true; // 最大 = 1 (1 位) // 将值打包成 25 位 (8+16+1) // 39 位 (64-25) 保持空闲 ulong packedValue = (value10 << 17) + // 保留空间 (16+1) 对于 value20, value30 (value20 << 1) + // 保留空间 (1) 对于 value30 value30; // 保存全局变量 string nameGVar = "temp"; GlobalVariableSet(nameGVar, packedValue); // 读取全局变量 packedValue = (ulong)GlobalVariableGet(nameGVar); // 数值解包 // 0xFF, 0xFFFF, 0x1 - 最大值的位掩码 int value11 = (int)((packedValue >> 17) & 0xFF); int value21 = (int)((packedValue >> 1) & 0xFFFF); bool value31 = (bool)(packedValue & 0x1); // 比较值 if (value11 == value10 && value21 == value20 && value31 == value30) Print("OK"); else PrintFormat("0x%X / 0x%X /0x%X / 0x%X", packedValue, value11, value21, value31); }
图形对象
您可以将脚本参数存储在图形对象中吗? 有何不可。 设置对象属性 OBJPROP_PRICE = 0 — 在这种情况下,对象在视觉上是“隐藏的”,但可以在程序中访问。 为了可靠性,这样的对象可以保存在图表模板之中。 参数访问逻辑如下:如果有对象,则提取参数;如果没有对象,则设置默认值。
订单注释
最大订单注释长度限制为 23 个字符。 什么可以存储在注释里? 例如,SOP/H1/SS/C2/Br/Br/Br。 其中 (从左至右)
- SOP — 订单发送者 (SOP – SendOrderByPlan 脚本)
- H1 — 订单生成时间帧 (H1)
- SS — 订单类型 (SS – Sell Stop)
- C2 — 订单平仓算法
- Br — D1 趋势 (Br – 看跌)
- Br — H4 趋势 (Br – 看跌)
- Br — 订单生成时间帧内的趋势 (Br – 看跌)
我们为什么需要这样做? 例如,此数据可用于分析交易。 此处是我如何运用它:当一笔挂单被触发,我提取平仓算法值,并创建一个虚拟止损分析器 AnalyserVirtSL,然后它将在指定条件下平仓。
文本文件
这可能是存储恢复参数的最可靠和最通用的方式。 您可一次性设置访问类,然后随时随地使用它们。
应用程序设置
AppSettings.txt 设置文件的一部分:
# ------------------------------------------------------------------- # 智能交易系统和脚本设置 # 文件编码 = UCS-2 LE with BOM (需要!!!) // 此为 Unicode # ------------------------------------------------------------------- TimeEurWinter = 10:00 # 欧洲时段开始冬季时间 (服务器时间) TimeEurSummer = 09:00 # 欧洲时段开始夏季时间 (服务器时间) ColorSessionEur = 224,255,255 # 欧洲时段颜色 ColorSessionUsd = 255,240,245 # 美洲时段颜色 NumberColorDays = 10 # 高亮天数 (时段)
AppSettings.mqh 类
#property copyright "Copyright 2020, Malik Arykov" #property link "malik.arykov@gmail.com" #property strict #include <Cayman/Params.mqh> // 应用程序参数名 #define APP_TIME_EUR_SUMMER "TimeEurSummer" #define APP_TIME_EUR_WINTER "TimeEurWinter" #define APP_TIME_TRADE_ASIA "TimeTradeAsia" #define APP_COLOR_SESSION_EUR "ColorSessionEur" #define APP_COLOR_SESSION_USD "ColorSessionUsd" #define APP_NUMBER_COLOR_DAYS "NumberColorDays" // ----------------------------------------------------------------------------- // 智能交易系统和脚本的常用设置 | // ----------------------------------------------------------------------------- class AppSettings { private: Params *m_params; public: // 设置位于 AppSettings.txt 文件 string TimeEurSummer; // 欧洲时段开始夏季时间 string TimeEurWinter; // 欧洲时段开始冬季时间 string TimeTradeAsia; // 亚洲走廊交易结束时间 color ColorSessionEur; // 欧洲时段颜色 color ColorSessionUsd; // 美洲时段颜色 int NumberColorDays; // 高亮天数 // 由程序设置 string PeriodTrends; // 趋势计算周期 (D1,H4) string TradePlan; // 交易方向(简要计划) bool IsValid; // 参数有效性 // 方法 AppSettings(); ~AppSettings() { delete m_params; }; void Dump(string sender); }; // ----------------------------------------------------------------------------- // 构造函数 | // ----------------------------------------------------------------------------- AppSettings::AppSettings() { IsValid = true; m_params = new Params(); m_params.Load(PATH_APP_SETTINGS); if (m_params.Total() == 0) { PrintFormat("%s / ERROR: Invalid file / %s", __FUNCTION__, PATH_APP_SETTINGS); IsValid = false; 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)); ColorSessionUsd = StringToColor(m_params.GetValue(APP_COLOR_SESSION_USD)); NumberColorDays = (int)StringToInteger(m_params.GetValue(APP_NUMBER_COLOR_DAYS)); } // ----------------------------------------------------------------------------- // 打印设定参数 | // ----------------------------------------------------------------------------- void AppSettings::Dump(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); }
特征
AppSettings 类的声明位于 Uterminal.mqh 文件当中,该文件通过 #include 与智能交易系统和所有脚本连接。
extern AppSettings *gAppSettings; // 应用程序设置
以这个解决方案,您可以:
- 在任何位置一次性初始化 gAppSettings
- 可在任何类的实例里使用 gAppSettings(替代将其作为参数传递)
分析器参数
Cayman 智能交易系统可管理各种分析器,诸如 AnalyzerTrend、AnalyserLevel、AnalyserVirtSL。 每个分析器都与特定的时间帧相关联。 这意味着只有在指定时间帧内出现新柱线时才会启动分析器。 分析器示例存储在文本文件中,带有 Key = Value 字符串。 例如,H4 交易级别分析器将其参数存储在 Files\Cayman\Params\128968168864101576\exp_05_Lev607A160E_H4.txt 文件当中
- Cayman — 项目名称
- Params — 含有分析器参数的子目录
- 128968168864101576 — 图表 ID // IntergerToString(ChartID())
- exp_05_Lev607A160E_H4.txt — 包含分析器参数的文件名 —
- exp — 前缀
- 05 — 分析器类型
- Lev607A160E — 分析器的名称(交易级别)
- H4 — 所跟踪的时间帧。
以下是带注释的文件内容(实际文件不含注释)
// 交易级别参数 nameObj=Lev607A160E // 交易级别名称 kindLevel=1 // 级别类型 (1 - 阻力) riskValue=1.00 // 级别突破时的成交量 (1) riskUnit=1 // 交易量变化单位(保证金的 1 - %) (1 - 保证金的百分比 %) algClose=2 // 成交平仓算法(2 – 两根修正柱线) ticketNew=0 // 在级别突破时的成交单据 ticketOld=0 // 在级别突破时的平仓单据 profits=0 // 以点数为单位的计划内利润 losses=0 // 以点数为单位的计划内亏损 // 分析器参数 symbol=EURUSD // 品种名称 period=16388 // 分析器周期 (H4) time0Bar=1618603200 // 零号柱线时间 (秒数) typeAnalyser=5 // 分析器类型 colorAnalyser=16711935 // 分析器结果的颜色 resultAnalyser=Lev607A160E, H4, 20:00, RS // 分析器结果
有一个基类 Analyser,其可保存和恢复任何分析器的参数。 当智能交易系统重启时(例如,切换时间帧之后),分析器从相关文本文件中恢复参数。 如果新柱线的时间尚未到来,则分析不会重新开始。 分析器针对前一根柱线的计算结果(resultAnalyser、colorAnalyser)显示在智能交易系统注释当中。
将脚本参数传递给智能交易系统
SetTradeLevel 脚本允许设置交易级别的参数。 在图表上选择了一个对象(直线、趋势线或矩形)。 SetTradeLevel 脚本查找所选对象(交易级别),并为其设置参数。
接下来,脚本将参数保存到 Files\Cayman\Params\128968168864101576\exp_05_Lev607A160E_H4.txt,并通过 SendCommand 函数发送命令和文件路径至文件。
// ----------------------------------------------------------------------------- // 将级别参数发送到智能交易系统 | // ----------------------------------------------------------------------------- NCommand SendCommand() { // 加载级别参数(如果有) Params *params = new Params(); string speriod = UConvert::PeriodToStr(_Period); params.Load(PREFIX_EXPERT, anaLevel, gNameLev, speriod); // 定义命令 NCommand cmd = (gKindLevel == levUnknown) ? cmdDelete : (params.Total() > 0) ? cmdUpdate : cmdCreate; // 保存参数 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, 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(); // 向智能交易系统发送命令 params.SendCommand(cmd); delete params; return cmd; }
params.SendCommand(cmd) 函数如下:
// ----------------------------------------------------------------------------- // 向智能交易系统发送命令 | // ----------------------------------------------------------------------------- void Params::SendCommand(NCommand cmd) { string nameObj = NAME_OBJECT_CMD; ObjectCreate(0, nameObj, OBJ_LABEL, 0, 0, 0); ObjectSetString(0, nameObj, OBJPROP_TEXT, m_path); ObjectSetInteger(0, nameObj, OBJPROP_ZORDER, cmd); ObjectSetInteger(0, nameObj, OBJPROP_TIMEFRAMES, 0); }
每次价格跳变 (OnTick),智能交易系统都会调用 CheckExpernalCommand() 函数检查名为 NAME_OBJECT_CMD 的对象是否存在。 如果存在,则读取含有分析器参数的命令和文件路径,并立即删除该对象。 接下来,智能交易系统按文件名搜索正在运行的分析器。 如果 cmd == cmdDelete,则分析器被删除。 如果 cmd == cmdUpdate,则分析器参数从文件里更新。 如果 cmd == cmdNew,则采用来自文件的参数创建新的分析器。
此处 Params 类的代码全本,它封装了操控参数(关键字=参数值字符串)文件的逻辑。
#property copyright "Copyright 2020, Malik Arykov" #property link "malik.arykov@gmail.com" #include <Arrays/ArrayString.mqh> #include <Cayman/UConvert.mqh> #include <Cayman/UFile.mqh> // ----------------------------------------------------------------------------- // 参数类(关键字=参数值字符串 #注释) | // ----------------------------------------------------------------------------- class Params { private: string m_path; // 参数文件路径 NCommand m_cmd; // 智能交易系统命令 CArrayString *m_items; // 配对数组 {关键字=参数值} 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++) { if (StringFind(m_items.At(j), key) == 0) { index = j; break; } } return index; } // ----------------------------------------------------------------------------- // 加载参数 | // ----------------------------------------------------------------------------- void Params::Load(string prefix, int typeAnalyser, string nameObj, string speriod) { 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); } // ----------------------------------------------------------------------------- // 加载参数 | // ----------------------------------------------------------------------------- void Params::Load(string path) { m_path = path; if (!FileIsExist(m_path)) return; //PrintFormat("%s / %s", __FUNCTION__, m_path); string text = UFile::LoadText(m_path); if (text == NULL) return; // 将文本切分成数行 string line, lines[]; int numLines = StringSplit(text, DLM_LINE, lines); for (int j = 0; j < numLines; j++) { line = lines[j]; // 删除注释 int k = StringFind(line, "#"); if (k == 0) continue; // 整个字符串是一条注释 if (k > 0) line = StringSubstr(line, 0, k); // 添加一个非空字符串 if (line != "") m_items.Add(line); } } // ----------------------------------------------------------------------------- // 保存参数 | // ----------------------------------------------------------------------------- void Params::Save() { string text = ""; for (int j = 0; j < m_items.Total(); j++) { text += m_items.At(j) + "\n"; } // 重写现有文件 UFile::SaveText(text, m_path, true); } // ----------------------------------------------------------------------------- // 向智能交易系统发送命令 | // ----------------------------------------------------------------------------- void Params::SendCommand(NCommand cmd) { string nameObj = NAME_OBJECT_CMD; ObjectCreate(0, nameObj, OBJ_LABEL, 0, 0, 0); ObjectSetString(0, nameObj, OBJPROP_TEXT, m_path); ObjectSetInteger(0, nameObj, OBJPROP_ZORDER, cmd); ObjectSetInteger(0, nameObj, OBJPROP_TIMEFRAMES, 0); } // ----------------------------------------------------------------------------- // 从脚本接收命令 | // ----------------------------------------------------------------------------- NCommand Params::TakeCommand() { string nameObj = NAME_OBJECT_CMD; if (ObjectFind(0, nameObj) < 0) return cmdUnknown; m_path = ObjectGetString(0, nameObj, OBJPROP_TEXT); m_cmd = (NCommand)ObjectGetInteger(0, nameObj, OBJPROP_ZORDER); ObjectDelete(0, nameObj); Load(m_path); return m_cmd; } // ----------------------------------------------------------------------------- // | 转储参数 | // ----------------------------------------------------------------------------- void Params::Dump(string sender) { for (int j = 0; j < m_items.Total(); j++) { PrintFormat("%s / %s", sender, m_items.At(j)); } }
对于 MQL5 的粉丝:当将 m_items 类型更改为 CHashMap 时,Add、GetValue、Find 函数的代码将显著减少。 但 Params 类在 MQL4 中也用到了 。 甚至,在这种情况下,参数访问速度并不重要,因为参数只会被读取一次,来初始化局部变量。 为什么没有在 MQL5 里针对 CHashMap 重新编制类? 可能是因为我在银行工作了很长时间。 金融软件开发者有一个很重要的原则:如果操作正常,就不要碰它! ;-)
将参数传递给外部程序
不同系统之间的数据交换单元实际上是一个 json 文件。 以前它曾是一个 xml 文件。 json 文件的主要优点是:
- 易于创建(生成/格式化)
- 所有高级语言均完美支持
- 可读性
例如,有一个包含以下字段的 Bar 类:m_time、m_open、m_high、m_low、m_close、m_body。 其中 m_body 是烛条颜色:阳线、阴线或十字星。 Bar 类有一个 ToJson() 方法,它生成一个 json 字符串
string Bar::ToJson() { return "{" + "\n\t\"symbol\":\"" + _Symbol + "\"," + "\n\t\"period\":" + IntegerToString(_Period) + "," + "\n\t\"digits\":" + IntegerToString(_Digits) + "," + "\n\t\"timeBar\":\"" + TimeToStr(m_time) + "\"," + "\n\t\"open\":" + DoubleToString(m_open, _Digits) + "," + "\n\t\"high\":" + DoubleToString(m_high, _Digits) + "," + "\n\t\"low\":" + DoubleToString(m_low, _Digits) + "," + "\n\t\"close\":" + DoubleToString(m_close, _Digits) + "," + "\n\t\"body\":" + IntegerToString(m_body) + "," + "\n}"; }
我们可以改用 StringFormat,但这会在重新排列或删除数值时导致出问题。 格式化 “\n\t” 可以删除,因为网上有很多 json 格式化服务。 其中之一是 JSON 解析器。 一次设置有效 json 的接收,并在您需要时调用 bar.ToJson() 函数。
一个外部程序,例如 C# 应用程序,可以将任何复杂度的 json 文件转换为对象。 如何从 MQL 传输一个 json 文件? 这很简单。 例如,将 json 文件加载(保存)到 Files/Json 终端目录。 一个外部程序监视此目录中是否有新文件。 若发现文件,程序读取它,将其转换为对象,并立即删除文件,或将其移动到存档(用于统计)。
从外部程序接收参数
将一个 json 库(或重新发明轮子)连接到 MQL 程序会导致额外的麻烦。 一个更好的解决方案是传递含有“关键字=参数值”字符串的文本文件。 可以利用 Params 类(见上文)处理文件。 智能交易系统和指标是接收来自外部程序或脚本参数的候选对象。 例如,您需要在 OnTick 处理程序中调用 CheckExternalCommand() 函数,其会检查 Files/ExtCmd 目录中的文件是否存在。 当发现一个文件时,它应该读取、处理(接受参数)、并删除该文件。
如此,我们已研究了在 MQL 和外部程序之间接收和传递参数的方法。 现在思考如下问题:为什么 MQL 程序需要 DLL?MQL 市场不接受此类程序。 原因只有一个 — 安全,因为您可以从 DLL 访问任何内容。
将参数传递给智能手机
为了进一步的操作,我将使用安卓 app 程序 WirePusher。 这是一项很棒的服务(免费且无插件)。 我不知道 iPhone 是否有类似的东西。 如果有任何 iPhone 粉丝正在阅读本文,请在评论中分享。
开始使用服务:
- 在您的智能手机上安装 WirePusher
- 启动应用程序。 您将在主屏幕上看到您的 ID
- 在终端的服务/设置/智能系统/允许 WebRequest 访问的 URL 里加入 https://wirepusher.com
然后启动脚本(不要忘记在 id = “********” 中写上你的 id,替换星号)
void OnStart() { string id = "**********"; // 在 WirePusher 里填写您的智能手机 id WirePusher("Profit $1000", "Deal", "Closed", id); } // ------------------------------------------------------------------------------------------------ // 通过 WirePusher 网页服务向智能手机发送通知 // 在终端的服务/设置/智能系统/允许 WebRequest 访问的 URL 里加入 https://wirepusher.com // message - 通知文本 // title - 通知标题(例如,Attention / Alert / Deal) // type - 通知类型(例如,触发挂单/级别突破/平仓) // id - 来自 WirePusher 安卓应用程序的唯一智能手机 id // ------------------------------------------------------------------------------------------------ bool WirePusher(string message, string title, string type, string id) { char data[]; // HTTP 消息主体数据数组 char result[]; // Web 服务响应数据数组 string answer; // Web 服务响应头 string url = "https://wirepusher.com/send?id={id}&title={title}&message={message}&type={type}"; StringReplace(url, "{id}", id); StringReplace(url, "{type}", type); StringReplace(url, "{title}", title); StringReplace(url, "{message}", message); ResetLastError(); int rcode = WebRequest("GET", url, NULL, 3000, data, result, answer); if (rcode != 200) { PrintFormat("%s / error=%i / url=%s / answer=%s / %s", __FUNCTION__, GetLastError(), url, answer, CharArrayToString(result)); return false; } PrintFormat("%s / %s / %s", __FUNCTION__, title, message); return true; }
在 Cayman EA 中,在 AnalyserTrade 中调用 WirePusher 函数的时刻:
- 挂单触发
- 价格突破交易级别
- 一笔成交平仓
可以为 WirePusher 上的每种通知类型分配一个独立的声音。 以前,我曾为以盈利了结的成交发出 “ta-da” 声音,并为亏损了结的成交发出“炸弹”声音。 但随后我就被炸弹搞得疲惫不堪。
结束语
存储参数最可靠、最方便的方法是运用文本文件。 甚而,在任何操作系统(应用程序)里都完全支持/缓存文件操作。
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/9327
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
This article was written by a user of the site and reflects their personal views. MetaQuotes Ltd is not responsible for the accuracy of the information presented, nor for any consequences resulting from the use of the solutions, strategies or recommendations described.



我的帖子并不是说什么不起作用,而是说没有确保数据的安全性和完整性。在使用上述所有数据传输方法时,都无法确保机制的可靠性。此外,这与第三方系统可能存在的错误(终端、操作系统、硬件错误)无关,而是与方法本身有关:全局终端变量 的名称或用户操作碰撞、图形对象的相同+删除功能、不同线程对文件的读写操作缺乏同步。
是的,这种概率可以降到很低,但不可能降到 0。这正是您应该牢记的)。
将参数存储在文件中可以提供 100% 的可靠性。所有的 "碰撞 "都可以通过对象名称(包括全局变量)的前缀来解决。
例如,级别参数存储在 Files\Cayman\Params\128968168864101576\exp_05_Lev607A160E_H4.txt 中。其中 128968168864101576 是图表 ID。除了这个特定级别的分析器,没有人知道这个文件。从文章中提取任意一段代码并尝试 "破解 "它;-)如果你破解了,我会告诉你哪里出错了;-)
好奇的用户在任何 EA/指示器/脚本/服务中运行类似这样的脚本,就可以花数周时间寻找一个不存在的、无法证实的错误,其乐无穷)))))。
好奇的用户在任何 EA/指示器/脚本/服务中运行类似这样的脚本,就可以花数周时间寻找一个不存在的、无法证实的错误,其乐无穷)))))。
天哪,幼儿园。如果你这样写代码,我真为你的用户感到遗憾。不要毁了你的名声。如果你是那种总是应该说最后一句话的人,那就别说了。你对我文章的所有批评都是胡说八道。
天哪,这简直就是幼儿园。如果你写了这样的代码,我为你的用户感到遗憾。不要毁了你的名声。如果你是那种总是应该说最后一句话的人,那就别说了。你对我文章的所有批评都是胡说八道。
很明显,您的意思是不小心用另一个程序(任何程序,甚至是市场上的演示程序)改变了您的变量。
好吧,如果版主表扬了,那好吧...... )))