Ошибки при компиляции mql4

100

Ошибка чтения файла

101

Ошибка открытия *.EX4 файла для записи

103

Недостаточно свободной памяти для завершения компиляции

104

Нераспознанная компилятором пустая синтаксическая единица

105

Некорректное имя файла в #include

106

Ошибка доступа к файлу в #include (возможно файл не существует)

108

Неподходящее имя для #define

109

Неизвестная команда препроцессора (допустимы #include,#define,#property,#import)

110

Неизвестный для компилятора символ

111

Функция не реализована (описание есть, тела нет)

112

Пропущена двойная кавычка («)

113

Пропущена открывающая угловая скобка (<) или двойная кавычка («)

114

Пропущена одинарная кавычка (‘)

115

Пропущена закрывающая угловая скобка «>»

116

Не указан тип в объявлении

117

Нет оператора возврата return или имеется не во всех ветках выполнения

118

Ожидалась открывающая скобка параметров вызова

119

Ошибка записи EX4

120

Некорректный доступ к элементу массива

121

Функция не имеет тип void и оператор return должен вернуть значение

122

Некорректное объявление деструктора

123

Отсутствует двоеточие «:»

124

Переменная уже объявлена

125

Переменная с таким идентификатором уже объявлена

126

Имя переменной слишком длинное (>250 символов)

127

Структура с таким идентификатором уже определена

128

Структура не определена

129

Член структуры с таким именем уже определен

130

Нет такого члена структуры

131

Нарушена парность квадратных скобок

132

Ожидается открывающая круглая скобка «(«

133

Несбалансированные фигурные скобки ( отсутствует «}» )

134

Сложно для компиляции (слишком большое ветвление, внутренний стек уровней переполнен)

135

Ошибка открытия файла на чтение

136

Недостаточно памяти для загрузки исходного файла в память

137

Ожидается переменная

138

Ссылка не может быть инициализирована

140

Ожидалось присваивание (возникает при объявлении)

141

Ожидается открывающая фигурная скобка «{«

142

Параметр может быть только динамическим массивом

143

Использование типа «void» недопустимо

144

Нет пары для «)» или «]», т.е. отсутствует «(» или «[«

145

Нет пары для «(» или «[«, т.е. отсутствует «)» или «]»

146

Некорректная  размерность массива

147

Слишком много параметров (>64)

149

Этот токен тут не ожидается

150

Недопустимое использование операции (неправильные операнды)

151

Выражение типа void недопустимо

152

Ожидается оператор

153

Неправильное использование break

154

Ожидается точка с запятой «;»

155

Ожидается запятая «,»

156

Тип должен быть определен как класс, а не как структура

157

Ожидалось выражение

158

В HEX встречается «не HEX символ» или слишком длинное число (количество цифр > 511)

159

Строка-константа имеет более 65534 символов

160

Определение функции здесь недопустимо

161

Неожиданный конец программы

162

Форвардная декларация для структур запрещена

163

Функция с таким именем уже определена и имеет иной тип возвращаемого значения

164

Функция с таким именем уже определена и имеет иной набор параметров

165

Функция с таким именем уже определена и реализована

166

Перегрузка функции для данного вызова не найдена

167

Функция с возвращаемым значением типа void не может возвращать значение

168

Функция не определена

170

Ожидается значение

171

В выражении case допустимы только целочисленные константы

172

Значение для case в этом switch уже использовано

173

Ожидается целочисленное значение

174

В выражении #import ожидается имя файла

175

Выражения на глобальном уровне не допустимы

176

Пропущена круглая скобка «)» перед «;»

177

Слева от знака равенства предполагается переменная

178

Результат выражения не используется

179

Объявление переменных в case недопустимо

180

Неявное преобразование из строки в число

181

Неявное преобразование числа в строку

182

Неоднозначный вызов перегруженной функции (подходят несколько перегрузок)

183

Недопустимый else без соответствующего if

184

Недопустимый case или default без соответствующего switch

185

Недопустимое использование эллипсиса

186

Инициализирующая последовательность имеет большее количество элементов чем инициализируемая переменная

187

Ожидается константа для case

188

Требуется константное выражение

189

Константная переменная не может быть изменена

190

Ожидается закрывающая скобка или запятая (объявление члена массива)

191

Идентификатор перечисления уже используется

192

Перечисление не может иметь модификаторов доступа (const, extern, static)

193

Член перечисления уже объявлен с другим значением

194

Существует переменная, определенная с таким же именем

195

Существует структура, определенная с таким же именем

196

Ожидается имя члена перечисления

197

Ожидается целочисленное выражение

198

Деление на ноль в константном выражении

199

Неверное количество параметров в функции

200

Параметром по ссылке должна быть переменная

201

Ожидается переменная такого же типа для передачи по ссылке

202

Константная переменная не может быть передана по неконстантной ссылке

203

Требуется  целочисленная положительная константа

204

Ошибка доступа к защищенному члену класса

205

Импорт уже определен по другому пути

208

Исполняемый файл не создан

209

Для индикатора не найдена точка входа ‘OnCalculate’

210

Оператор continue может быть использован только внутри цикла

211

Ошибка доступа к private(закрытому) члену класса

213

Метод структуры или класса не объявлен

214

Ошибка доступа к private(закрытому) методу класса

216

Копирование структур с объектами недопустимо

218

Выход индекса за границы массива

219

Недопустима инициализация массивов в объявлении структуры или класса

220

Конструктор класса не может иметь параметров

221

Деструктор класса не может иметь параметров

222

Метод класса или структуры с таким именем и параметрами уже объявлен

223

Ожидается операнд

224

Метод класса или структуры с таким именем есть, но с другими параметрами  (объявление!=реализация)

225

Импортируемая функция не описана

226

Функция ZeroMemory() не применима для классов с защищенными членами или наследованием

227

Неоднозначный вызов перегруженной функции (точное совпадение параметров для нескольких перегрузок)

228

Ожидается имя переменной

229

Ссылку нельзя объявить в этом месте

230

Уже используется в качестве имени перечисления

232

Ожидается класс или структура

235

Нельзя вызывать delete для удаления массива

236

Ожидается оператор ‘ while ‘

237

В delete должен быть указатель

238

default для этого switch уже есть

239

Синтаксическая ошибка

240

Escape-последовательность может встретиться только в строках ( начинается с » )

241

Требуется массив – квадратная скобка ‘[‘ не относится к массиву либо в качестве параметра-массива подают не массив

242

Не может быть инициализировано посредством инициализирующей последовательности

243

Импорт не определен

244

Ошибка оптимизатора на синтаксическом дереве

245

Объявлено слишком много структур (упростите программу)

246

Преобразование параметра недопустимо

247

Некорректное  использование оператора delete

248

Нельзя объявить указатель на ссылку

249

Нельзя объявить ссылку на ссылку

250

Нельзя объявить указатель на указатель

251

Недопустимо объявление структуры в списке параметров

252

Недопустимая операция приведения типов

253

Указатель можно объявить только для класса или структуры

256

Необъявленный идентификатор

257

Ошибка оптимизатора исполняемого кода

258

Ошибка генерации исполняемого кода

260

Недопустимое выражение для оператора switch

261

Переполнение пула строковых констант, упростите программу

262

Невозможно преобразовать к перечислению

263

Нельзя использовать virtual для данных (членов класса или структуры)

264

Нельзя вызвать защищенный метод класса

265

Переопределяемая виртуальная функция возвращает другой тип

266

Класс нельзя наследовать от структуры

267

Структуру нельзя наследовать от класса

268

Конструктор не может быть виртуальным (спецификатор virtual недопустим)

269

Структура не может иметь виртуальных методов

270

Функция должна иметь тело

271

Перегрузка системных функций (функций терминала) запрещена

272

Спецификатор const недопустим для функций, не являющихся членом класса или структуры

274

Нельзя менять члены класса в константном методе

276

Неподходящая инициализирующая последовательность

277

Пропущено значение по умолчанию для параметра (специфика объявления параметров по умолчанию)

278

Переопределение параметра по умолчанию (в объявлении и реализации разные значения)

279

Нельзя вызвать неконстантный метод для константного объекта

280

Для доступа к членам требуется объект (поставлена точка для не класса/структуры)

281

Имя уже объявленной структуры нельзя использовать при объявлении

284

Неразрешенное преобразование (при закрытом наследовании)

285

Структуры и массивы не могут быть использованы в качестве input-переменных

286

Спецификатор const недопустим для конструктора/деструктора

287

Неправильное строковое выражение для типа datetime

288

Неизвестное свойство (#property)

289

Некорректное значение для свойства

290

Некорректный индекс для свойства в #property

291

Пропущен параметр вызова – < func(x,) >

293

Объект должен быть передан по ссылке

294

Массив должен быть передан по ссылке

295

Функция была декларирована как экспортируемая

296

Функция не была декларирована как экспортируемая

297

Экспортировать импортируемую функцию нельзя

298

Импортируемая функция не может иметь такого параметра (нельзя передавать указатель, класс или структуру, содержащую динамический массив, указатель, класс и т.д.)

299

Должен быть класс

300

Секция #import не закрыта

302

Несоответствие типов

303

extern-переменная уже инициализирована

304

Не найдено ни одной экспортируемой функции или стандартной точки входа

305

Явный вызов конструктора запрещен

306

Метод был объявлен константным

307

Метод не был объявлен константным

308

Некорректный размер ресурсного файла

309

Некорректное имя ресурса

310

Ошибка открытия файла ресурса

311

Ошибка чтения файла ресурса

312

Неизвестный тип ресурса

313

Некорректный путь к файлу ресурса

314

Указанное имя ресурса уже используется

315

Ожидались параметры макроса

316

После имени макроса должен быть пробел

317

Ошибка в описании параметров макроса

318

Неверное число параметров при использовании макроса

319

Превышение максимального количества(16) параметров для макроса

320

Макрос слишком сложный, требуется упрощение

321

Параметром EnumToString() может быть только перечисление

322

Имя ресурса слишком длинное

323

Неподдерживаемый формат изображения (допустим только BMP-формат с глубиной цвета 24 или 32 бита)

324

Объявление массива внутри оператора запрещено

325

Функцию можно определить только на глобальном уровне

326

Данное объявление недопустимо для текущей области видимости (области определения)

327

Инициализация статичных переменных значениями локальных недопустима

328

Недопустимое объявление массива объектов, не имеющих конструктора по умолчанию

329

Список инициализации разрешен только для конструкторов

330

Отсутствует определение функции после списка инициализации

331

Список инициализации пуст

332

Инициализация массива в конструкторе запрещена

333

В списке инициализации запрещено  инициализировать члены родительского класса

334

Ожидалось выражение целого типа

335

Требуемый объем памяти для массива превышает максимально допустимое значение

336

Требуемый объем памяти для структуры превышает максимально допустимое значение

337

Требуемый объем памяти для переменных, объявленных на глобальном уровне, превышает максимально допустимое значение

338

Требуемый объем памяти для локальных переменных превышает максимально допустимое значение

339

Конструктор не определен

340

Недопустимое имя для файла иконки

341

Не удалось открыть файла иконки по указанному пути

342

Файл иконки некорректен и не соответствует формату ICO

343

Повторная инициализация члена в конструкторе класса/структуры с помощью списка инициализации

344

Инициализация статических членов в списке инициализации конструктора не допускается

345

Инициализация нестатического члена класса/структуры на глобальном уровне запрещена

346

Имя метода класса/структуры совпадает с ранее объявленным именем члена

347

Имя члена класса/структуры совпадает с ранее объявленным именем метода

348

Виртуальная функция не может быть объявлена как static

349

Модификатор const недопустим для статической функции

350

Конструктор или деструктор не могут быть статическими

351

Нельзя обращаться к нестатическому члену/методу класса или структуры из статической функции

352

После ключевого слова operator ожидается перегружаемая операция (+,-,[],++,— и т.д.)

353

Не все операции можно перегружать в MQL4

354

Определение не соответствует объявлению

355

Указано неверное количество параметров для оператора

356

Не обнаружено ни одной функции-обработчика события

357

Методы не могут быть экспортируемыми

358

Нельзя приводить указатель на константный объект к указателю на неконстантный объект

359

Шаблоны классов пока не поддерживаются

360

Перегрузка шаблонов функций пока не поддерживается

361

Невозможно применить шаблон функции

362

Неоднозначный параметр в шаблоне функции (подходят несколько типов параметра)

363

Невозможно определить к какому типу параметра приводить аргумент шаблона функции

364

Неверное количество параметров в шаблоне функции

365

Шаблон функции не может быть виртуальным

366

Шаблоны функций не могут быть экспортированы

367

Нельзя импортировать шаблоны функций

368

Cтруктуры, содержащие объекты, недопустимы

369

Массивы строк и структуры, содержащие объекты, недопустимы

370

Статический член класса/структуры должен быть явно инициализирован

371

Ограничение компилятора: строка не может содержать более 65 535 символов

372

Несогласованные #ifdef/#endif

373

Результатом выполнения функции не может быть объект класса, так как отсутствует конструктор копирования

374

Нельзя использовать нестатические члены и/или методы при инициализации статической переменной

375

OnTesterInit() нельзя использовать без объявления обработчика OnTesterDeinit()

376

Имя локальной переменной совпадает с именем одного из параметров функции

377

Нельзя использовать макросы __FUNCSIG__ и __FUNCTION__ вне тела функции

378

Недопустимый возвращаемый тип. Например, такая ошибка будет выдана для функций, импортированных из DLL, которые возвращают структуру или указатель в качестве результата

379

Ошибка при использовании шаблона

380

Не используется

381

Недопустимый синтаксис при объявлении чисто виртуальной функции, разрешено «=NULL» или «=0»

382

Только  виртуальные функции могут быть объявлены со спецификатором чисто виртуальной функции  («=NULL» или «=0»)

383

Нельзя создать экземпляр абстрактного класса

384

Для динамического приведения  с помощью оператора dynamic_cast типом назначения должен быть указатель на пользовательский тип

385

Ожидается тип «указатель на функцию»

386

Указатели на методы не поддерживаются

387

Ошибка – невозможно определить тип указателя на функцию

388

Приведение типа недоступно из-за закрытого наследования

389

Переменная с модификатором const должна быть проинициализирована при объявлении

Введение

При использовании новой версии компилятора языка MQL4 некоторые старые программы могут выдавать ошибки.

В старой версии компилятора во избежание критического завершения программ многие ошибки обрабатывались средой исполнения и не приводили к остановке работы. Например, деление на ноль или выход за пределы массива являются критическими ошибками и обычно приводят к аварийному завершению. Проявляются такие ошибки лишь в некоторых состояниях при определенных значениях переменных, однако о таких ситуациях следует знать и корректно их обрабатывать.

Новый компилятор позволяет обнаружить реальные или потенциальные источники ошибок и повысить качество кода.

В этой статье мы рассмотрим возможные ошибки, возникающие при компиляции старых программ, и методы их устранения.

  1. Ошибки компиляции
    • 1.1. Идентификатор совпадает с зарезервированным словом
    • 1.2. Специальные символы в наименованиях переменных и функций
    • 1.3. Ошибки использования оператора switch
    • 1.4. Возвращаемые значения у функций
    • 1.5. Массивы в аргументах функций
  2. Ошибки времени выполнения
    • 2.1. Выход за пределы массива (Array out of range)
    • 2.2. Деление на ноль (Zero divide)
    • 2.3. Использование 0 вместо NULL для текущего символа
    • 2.4. Строки в формате Unicodе и их использование в DLL
    • 2.5. Совместное использование файлов
    • 2.6. Особенность преобразования datetime
  3. Предупреждения компилятора
    • 3.1. Пересечения имен глобальных и локальных переменных
    • 3.2. Несоответствие типов
    • 3.3. Неиспользуемые переменные

1. Ошибки компиляции

При наличии ошибок в коде программа не может быть скомпилирована.

Для полного контроля всех ошибок рекомендуется использовать строгий режим компиляции, который устанавливается директивой:

#property strict

Этот режим значительно упрощает поиск ошибок.

1.1. Идентификатор совпадает с зарезервированным словом

Если наименование переменной или функции совпадает с одним из зарезервированных слов:

int char[];  
int char1[]; 
int char()   
{
 return(0);
}

то компилятор выводит сообщения об ошибках:

Рис.1. Ошибки "unexpected token" и "name expected"

Рис.1. Ошибки «unexpected token» и «name expected»

Для исправления данной ошибки нужно исправить имя переменной или функции.

1.2. Специальные символы в наименованиях переменных и функций

Если наименования переменных или функций содержат специальные символы ($, @, точка):

int $var1; 
int @var2; 
int var.3; 
void f@()  
{
 return;
}

то компилятор выводит сообщения об ошибках:

Рис.2. Ошибки "unknown symbol" и "semicolon expected"

Рис.2. Ошибки «unknown symbol» и «semicolon expected»

Для исправления данной ошибки нужно скорректировать имена переменных или функций.

1.3. Ошибки использования оператора switch

Старая версия компилятора позволяла использовать любые значения в выражениях и константах оператора switch:

void start()
  {
   double n=3.14;
   switch(n)
     {
      case 3.14: Print("Pi");break;
      case 2.7: Print("E");break;
     }
  }

В новом компиляторе выражения и константы оператора switch должны быть целыми числами, поэтому при использовании подобных конструкций возникают ошибки:

Рис.3. Ошибки "illegal switch expression type" и "constant expression is not integral"

Рис.3. Ошибки «illegal switch expression type» и «constant expression is not integral»

В таких случаях можно использовать явные сравнения численных значений, например:

void start()
  {
   double n=3.14;
   if(n==3.14) Print("Pi");
   else
      if(n==2.7) Print("E");
  }

1.4. Возвращаемые значений функций

Все функции, кроме void, должны возвращать значение объявленного типа. Например:

int function()
{
}

При строгом режиме компиляции (strict) возникает ошибка:

Рис.4. Ошибка "not all control paths return a value"

Рис.4. Ошибка «not all control paths return a value»

В режиме компиляции по умолчанию компилятор выводит предупреждение:

Рис.5. Предупреждение "not all control paths return a value"

Рис.5. Предупреждение «not all control paths return a value»

Если возвращаемое значение функции не соответствует объявлению:

int init()                         
  {
   return;                          
  }

то при строгом режиме компиляции возникает ошибка:

Рис.6. Ошибка "function must return a value"

Рис.6. Ошибка «function must return a value»

В режиме компиляции по умолчанию компилятор выводит предупреждение:

Рис.7. Предупреждение 'return - function must return a value"

Рис.7. Предупреждение ‘return — function must return a value»

Для исправления таких ошибок в код функции нужно добавить оператор возврата return c возвращаемым значением соответствующего типа.

1.5. Массивы в аргументах функций

Массивы в аргументах функций теперь передаются только по ссылке.

double ArrayAverage(double a[])
{
 return(0);
}

Данный код при строгом режиме компиляции (strict) приведет к ошибке:

Рис.8. Ошибка компилятора "arrays passed by reference only"

Рис.8. Ошибка компилятора «arrays passed by reference only»

В режиме компиляции по умолчанию компилятор выводит предупреждение:

Рис.9. Предупреждение компилятора "arrays passed by reference only"

Рис.9. Предупреждение компилятора «arrays passed by reference only»

Для исправления таких ошибок нужно явно указать передачу массива по ссылке, добавив префикс & перед именем массива:

double ArrayAverage(double &a[])
{
 return(0);
}

Следует отметить, что теперь константные массивы (Time[], Open[], High[], Low[], Close[], Volume[]) не могут быть переданы по ссылке. Например, вызов:

ArrayAverage(Open);

вне зависимости от режима компиляции приводит к ошибке:

Рис.10. Ошибка 'Open' - constant variable cannot be passed as reference

Рис.10. Ошибка ‘Open’ — constant variable cannot be passed as reference

Для устранения подобных ошибок нужно скопировать необходимые данные из константного массива:

   
   double OpenPrices[];
   
   ArrayCopy(OpenPrices,Open,0,0,WHOLE_ARRAY);
   
   ArrayAverage(OpenPrices);

2. Ошибки времени выполнения

Ошибки, возникающие в процессе исполнения кода программы принято называть ошибками времени выполнения (runtime errors). Такие ошибки обычно зависят от состояния программы и связаны с некорректными значениями переменных.

Например, если переменная используется в качестве индекса элементов массива, то ее отрицательные значения неизбежно приведут к выходу за пределы массива.

2.1. Выход за пределы массива (Array out of range)

Эта ошибка часто возникает в индикаторах при обращении к индикаторным буферам. Функция IndicatorCounted() возвращает количество баров, неизменившихся после последнего вызова индикатора. Значения индикаторов на уже рассчитанных ранее
барах не нуждаются в пересчете, поэтому для ускорения расчетов
достаточно обрабатывать только несколько последних баров.

Большинство индикаторов, в которых используется данный способ оптимизации вычислений, имеют вид:



int start()
  {
   
   if (Bars<100) 
     return(-1); 

   
   int counted_bars=IndicatorCounted();
   
   if(counted_bars<0) return(-1);
      
   
   int limit=Bars-counted_bars;

   
   if(counted_bars==0) 
     {
      limit--;  
      
      limit-=10;
     }
   else 
     {     
      
      limit++;
     } 
   
   for (int i=limit; i>0; i--)
   {
     Buff1[i]=0.5*(Open[i+5]+Close[i+10]) 
   }
}

Часто встречается некорректная обработка случая counted_bars==0 (начальную позицию limit нужно уменьшить на значение, равное 1 + максимальный индекс относительно переменной цикла).

Также следует помнить о том, что в момент исполнения функции start() мы можем обращаться к элементам массивов индикаторных буферов от 0 до Bars()-1. Если есть необходимость работы с массивами, которые не являются индикаторными буферами, то их размер следует увеличить при помощи функции ArrayResize() в соответствии с текущим размером индикаторных буферов. Максимальный индекс элемента для адресации также можно получить вызовом ArraySize() с одним из индикаторных буферов в качестве аргумента.

2.2. Деление на ноль (Zero divide)

Ошибка «Zero divide» возникает в случае, если при выполнении операции деления делитель оказывается равен нулю:

void OnStart()
  {

   int a=0, b=0,c;
   c=a/b;
   Print("c=",c);
  }

При выполнении данного скрипта во вкладке «Эксперты» возникает сообщение об ошибке и завершении работы программы:

Рис.11. Сообщение об ошибке "zero divide"

Рис.11. Сообщение об ошибке «zero divide»

Обычно такая ошибка возникает в случаях, когда значение делителя определяется значениями каких-либо внешних данных. Например, если анализируются параметры торговли, то величина задействованной маржи оказывается равна 0 если нет открытых ордеров. Другой пример: если анализируемые данные считываются из файла, то в случае его отсутствия нельзя гарантировать корректную работу. По этой причине желательно стараться учитывать подобные случаи и корректно их обрабатывать.

Самый простой способ — проверять делитель перед операцией деления и выводить сообщение об некорректном значении параметра:

void OnStart()
  {

   int a=0, b=0,c;
   if(b!=0) {c=a/b; Print(c);}
   else {Print("Error: b=0"); return; };
  }

В результате критической ошибки не возникает, но выводится сообщение о некорректном значении параметра и работа завершается:

Рис. 12. Сообщение о некорректном значении делителя

Рис. 12. Сообщение о некорректном значении делителя

2.3. Использование 0 вместо NULL для текущего символа

В старой версии компилятора допускалось использование 0 (нуля) в качестве аргумента в функциях, требующих указания финансового инструмента.

Например, значение технического индикатора Moving Average для текущего символа можно было запрашивать следующим образом:

AlligatorJawsBuffer[i]=iMA(0,0,13,8,MODE_SMMA,PRICE_MEDIAN,i); 

В новом компиляторе для указания текущего символа нужно явно указывать NULL:

AlligatorJawsBuffer[i]=iMA(NULL,0,13,8,MODE_SMMA,PRICE_MEDIAN,i); 

Кроме того, текущий символ и период графика можно указать при помощи функций Symbol() и Period().

AlligatorJawsBuffer[i]=iMA(Symbol(),Period(),13,8,MODE_SMMA,PRICE_MEDIAN,i); 

2.4. Строки в формате Unicodе и их использование в DLL

Строки теперь представляют собой последовательность символов Unicode.

Следует учитывать этот факт и использовать соответствующие функции Windows. Например, при использовании функций библиотеки wininet.dll вместо InternetOpenA() и InternetOpenUrlA() следует вызывать InternetOpenW() и InternetOpenUrlW().

В MQL4 изменилась внутренняя структура строк (теперь она занимает 12 байт), поэтому при передаче строк в DLL следует использовать структуру MqlString:

#pragma pack(push,1)
struct MqlString
  {
   int      size;       
   LPWSTR   buffer;     
   int      reserved;   
  };
#pragma pack(pop,1)

2.5. Совместное использование файлов

В новом MQL4 при открытии файлов необходимо явно указывать флаги FILE_SHARE_WRITE и FILE_SHARE_READ для совместного использования.

В случае их отсутствия файл будет открыт в монопольном режиме, что не позволит больше никому его открывать, пока он не будет закрыт монополистом.

Например, при работе с оффлайновыми графиками требуется явно указывать флаги совместного доступа:

   
   ExtHandle=FileOpenHistory(c_symbol+i_period+".hst",FILE_BIN|FILE_WRITE|FILE_SHARE_WRITE|FILE_SHARE_READ);

Подробности можно найти в статье в статье «Оффлайновые графики и новый MQL4«.

2.6. Особенность преобразования datetime

Следует иметь ввиду, что преобразование типа datetime в строку теперь зависит от режима компиляции:

  datetime date=D'2014.03.05 15:46:58';
  string str="mydate="+date;

Например, попытка работы с файлами, имя которых содержит двоеточие, приведет к ошибке.

3. Предупреждения компилятора

Предупреждения компилятора носят информационный характер и не являются сообщениями об ошибках, однако они указывают на возможные источники ошибок и лучше их скорректировать.

Чистый код не должен содержать предупреждений.

3.1. Пересечения имен глобальных и локальных переменных

Если на глобальном и локальном уровнях присутствуют переменные с одинаковыми именами:

int i; 
void OnStart()
  {

   int i=0,j=0; 
   for (i=0; i<5; i++) {j+=i;}
   PrintFormat("i=%d, j=%d",i,j);
  }

то компилятор выводит предупреждение и укажет номер строки, на которой объявлена глобальная переменная:

Рис.13. Предупреждение "declaration of '%' hides global declaration at line %"

Рис.13. Предупреждение «declaration of ‘%’ hides global declaration at line %»

Для исправления таких предупреждений нужно скорректировать имена глобальных переменных.

3.2. Несоответствие типов

В новой версии компилятора введена операция приведения типов.

#property strict
void OnStart()
  {
   double a=7;
   float b=a;
   int c=b;
   string str=c;
   Print(c);
  }

В строгом режиме компиляции при несоответствии типов компилятор выводит предупреждения:

Рис.14. Предупреждения "possible loss of data due to type conversion" и "implicit conversion from 'number' to 'string'

Рис.14. Предупреждения «possible loss of data due to type conversion» и «implicit conversion from ‘number’ to ‘string’

В данном примере компилятор предупреждает о возможной потере точности при присвоении различных типов данных и неявном преобразовании типа int в string.

Для исправления нужно использовать явное приведение типов:

#property strict
void OnStart()
  {
   double a=7;
   float b=(float)a;
   int c=(int)b;
   string str=(string)c;
   Print(c);
  }

3.3. Неиспользуемые переменные

Наличие переменных, которые не используются в коде программы (лишние сущности) не является хорошим тоном.

void OnStart()
  {
   int i,j=10,k,l,m,n2=1;
   for(i=0; i<5; i++) {j+=i;}
  }

Сообщения о таких переменных выводятся вне зависимости от режима компиляции:

Рис.15. Предупреждения "variable '%' not used'

Рис.15. Предупреждения «variable ‘%’ not used’

Для исправления нужно убрать неиспользуемые переменные из кода программы.

Выводы

В статье рассмотрены типичные проблемы, с которыми могут столкнуться программисты при компиляции старых программ, содержащих ошибки.

Во всех случаях при отладке программ рекомендуется использовать строгий режим компиляции.

Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.

Разработка торговых экспертов на языке MQL4 является не такой уж простой задачей. Во-первых — алгоритмизация любой сложной торговой системы уже представляет собой проблему, так как нужно учесть очень много деталей, начиная с особенностей ТС и заканчивая спецификой среды MetaTrader 4. Во-вторых, даже наличие детальнейшего алгоритма не избавляет от сложностей, возникающих при переносе разработанного алгоритма на язык программирования MQL4.

Компилятор оказывает некоторую помощь при написании корректных экспертов. После начала компиляции MetaEditor сообщит обо всех синтаксических ошибках в вашем коде. Но, к сожалению, помимо синтаксических ошибок ваш советник может содержать еще и логические ошибки, которые компилятор выловить не может. Поэтому этим нам придется заняться самим. Как это сделать — в нашем сегодняшнем материале.

MQL4 — Ошибки и как их исправить

Самые распространенные ошибки компиляции

При наличии ошибок в коде программа не может быть скомпилирована. Для полного контроля всех ошибок рекомендуется использовать строгий режим компиляции, который устанавливается директивой:

Этот режим значительно упрощает поиск ошибок. Теперь перейдем к самым распространенным ошибкам при компиляции.

Идентификатор совпадает с зарезервированным словом

Если наименование переменной или функции совпадает с одним из зарезервированных слов:

int char[];  // неправильно
int char1[]; // правильно
int char()   // неправильно
{
    return(0);
}

то компилятор выводит сообщения об ошибках:

Для исправления данной ошибки нужно исправить имя переменной или функции. Я рекомендую придерживаться следующей системы для именования:

Все функции должны обозначать действие. То есть это должен быть глагол. Например, OpenLongPosition() или ModifyStopLoss(). Ведь функции всегда именно что-то делают, верно?

Кроме того, функции желательно называть в так называемом CamelCase стиле. А переменные в cebab_case стиле. Это общепринятая практика.

Кстати, об именах переменных. Переменные — это существительные. Например, my_stop_loss, day_of_week, current_month. Не так страшно назвать переменную длинным именем, гораздо страшнее назвать ее непонятно. Что такое dow, индекс Dow Jones? Нет, это, оказывается, день недели. Конечно, сегодня вам и так понятно, что это за переменная. Но когда вы откроете код советника месяц спустя, все будет уже не так явно. А это время, упущенное на расшифровку посланий из прошлого – оно вам надо?

Специальные символы в наименованиях переменных и функций

Идем дальше. Если наименования переменных или функций содержат специальные символы ($, @, точка):

int $var1; // неправильно
int @var2; // неправильно
int var.3; // неправильно
void f@()  // неправильно
{
    return;
}

то компилятор выводит сообщения об ошибках:

Для исправления данной ошибки снова нужно скорректировать имена переменных или функций, ну или сразу называть их по-человечески. В идеале код нужно писать так, чтобы даже человек, не знающий программирование, просто его прочел и понял, что там вообще происходит.

Ошибки использования оператора switch

Старая версия компилятора позволяла использовать любые значения в выражениях и константах оператора switch:

void start()
{
    double n=3.14;
    switch(n)
    {
        case 3.14: 
            Print("Pi");
            break;
        case 2.7: 
            Print("E");
            break;
    }
}

В новом компиляторе выражения и константы оператора switch должны быть целыми числами, поэтому при использовании подобных конструкций возникают ошибки:

Поэтому, когда вы разбираете код классики, такой, как WallStreet, Ilan и прочей нетленки (что очень полезно для саморазвития), можно натолкнуться на эту ошибку. Лечится она очень просто, например, при использовании такой вот строки:

switch(MathMod(day_48, 10))

Вот так можно запросто решить проблему:

switch((int)MathMod(day_48, 10))

Возвращаемые значений функций

Все функции, кроме void, должны возвращать значение объявленного типа. Например:

При строгом режиме компиляции (strict) возникает ошибка:

В режиме компиляции по умолчанию компилятор выводит предупреждение:

Если возвращаемое значение функции не соответствует объявлению:

Тогда при строгом режиме компиляции возникает ошибка:

В режиме компиляции по умолчанию компилятор выводит предупреждение:

Для исправления таких ошибок в код функции всего-навсего нужно добавить оператор возврата return c возвращаемым значением соответствующего типа.

Массивы в аргументах функций

Массивы в аргументах функций передаются только по ссылке. Раньше это было не так, поэтому в старых советниках можно встретить эту ошибку. Вот пример:

double ArrayAverage(double a[])
{
    return(0);
}

Данный код при строгом режиме компиляции (strict) приведет к ошибке:

В режиме компиляции по умолчанию компилятор выводит предупреждение:

Для исправления таких ошибок нужно явно указать передачу массива по ссылке, добавив префикс & перед именем массива:

double ArrayAverage(double &a[])
{
    return(0);
}

Кстати, константные массивы (Time[], Open[], High[], Low[], Close[], Volume[]) не могут быть переданы по ссылке. Например, вызов:

вне зависимости от режима компиляции приводит к ошибке:

Для устранения подобных ошибок нужно скопировать необходимые данные из константного массива:

//--- массив для хранения значений цен открытия
double OpenPrices[];
//--- копируем значения цен открытия в массив OpenPrices[]
ArrayCopy(OpenPrices,Open,0,0,WHOLE_ARRAY);
//--- вызываем функцию
ArrayAverage(OpenPrices);

Одна из самых распространенных ошибок – потеря советником индикатора. В таких случаях обычно пользователи эксперта на форумах гневно пишут: «Советник не работает!» или «Ставлю советник на график и ничего не происходит!». Решение этого вопроса на самом деле очень простое. Как всегда, достаточно просто заглянуть на вкладку «Журнал» терминала и обнаружить там запись вроде:

2018.07.08 09:15:44.957 2016.01.04 00:51 cannot open file 
'C:Users1AppDataRoamingMetaQuotesTerminal
MQL4indicatorsKELTNER_F12.ex4' [2]

Говорит это нам о том, что индикатор в папку положить забыли, или же он назван по-другому. Если индикатор отсутствует, нужно добавить его в папку с индикаторами. Если он есть, стоит проверить его название в коде советника – скорее всего там он называется по-другому.

Предупреждения компилятора

Предупреждения компилятора носят информационный характер и не являются сообщениями об ошибках, однако они указывают на возможные источники ошибок и лучше их скорректировать. Чистый код не должен содержать предупреждений.

Пересечения имен глобальных и локальных переменных

Если на глобальном и локальном уровнях присутствуют переменные с одинаковыми именами:

int i; // глобальная переменная
void OnStart()
{
    int i=0,j=0; // локальные переменные

    for (i=0; i<5; i++) {
        j+=i;
    }
    PrintFormat("i=%d, j=%d",i,j);
}

то компилятор выводит предупреждение и укажет номер строки, на которой объявлена глобальная переменная:

Для исправления таких предупреждений нужно скорректировать имена глобальных переменных.

Несоответствие типов

В следующем примере:

#property strict
void OnStart()
{
    double a=7;
    float b=a;
    int c=b;
    string str=c;
    Print(c);
}

при строгом режиме компиляции при несоответствии типов компилятор выводит предупреждения:

В данном примере компилятор предупреждает о возможной потере точности при присвоении различных типов данных и неявном преобразовании типа int в string.

Для исправления нужно использовать явное приведение типов:

#property strict
void OnStart()
{
    double a=7;
    float b=(float)a;
    int c=(int)b;
    string str=(string)c;
    Print(c);
}

Неиспользуемые переменные

Наличие переменных, которые не используются в коде программы (лишние сущности) не является хорошим тоном.

void OnStart()
{
    int i,j=10,k,l,m,n2=1;
    for(i=0; i<5; i++) {j+=i;}
}

Сообщения о таких переменных выводятся вне зависимости от режима компиляции:

Для исправления нужно просто убрать неиспользуемые переменные из кода программы.

Диагностика ошибок при компиляции

Часто после написания программы возникают проблемы при компиляции, вызванные ошибками в коде. Это могут быть самые различные ошибки, но в любом случае возникает необходимость оперативного обнаружения участка кода, где допущена ошибка.

Нередко у людей уходит немало времени и масса нервов на поиски какой-нибудь лишней скобки. Однако, есть способ быстрого обнаружения ошибок, который основан на использовании комментирования.

Написать достаточно большой код без единой ошибки – очень приятно. Но, к сожалению, так получается не часто. Я не рассматриваю здесь ошибки, которые приводят к неверному исполнению кода. Здесь пойдёт речь об ошибках, из-за которых становится невозможной компиляция.

Весьма распространённые ошибки – вставка лишней скобки в сложном условии, нехватка скобки, не выставление двоеточия, запятой при объявлении переменных, опечатка в названии переменной и так далее. Часто при компиляции можно сразу увидеть, в какой строке допущена подобная ошибка. Но бывают и случаи, когда найти такую ошибку не так просто. Ни компилятор, ни зоркий глаз нам не могут помочь сразу найти ошибку. В таких случаях, как правило, начинающие программисты начинают «обходить» весь код, пытаясь визуально определить ошибку. И снова, и снова, пока выдерживают нервы.

Однако MQL, как и другие языки программирования, предлагает отличный инструмент – комментирование. Используя его, можно отключать какие-то участки кода. Обычно комментирование используют именно для вставки каких-то комментариев, или же отключения неиспользуемых участков кода. Комментирование можно также успешно применять и при поиске ошибок.

Поиск ошибок обычно сводится к определению участка кода, где допущена ошибка, а затем, в этом участке, визуально находится ошибка. Думаю, вряд ли кто-то будет сомневаться в том, что исследовать «на глаз» 5-10 строчек кода проще и быстрей, чем 100-500, а то и несколько тысяч.

При использовании комментирования задача предельно проста. Сначала нужно закомментировать различные участки кода (иногда чуть ли не весь код), тем самым «отключив» его. Затем, по очереди, комментирование снимается с этих участков кода. После очередного снятия комментирования совершается попытка компиляции. Если компиляция прошла успешно – ошибка не в этом участке кода. Затем открывается следующий участок кода и так далее. Когда находится проблемный участок кода, визуально ищется ошибка, затем устраняется. Опять происходит попытка компиляции. Если всё прошло успешно, ошибка устранена.

Важно правильно определять участки кода, которые необходимо комментировать. Если это условие (или иная логическая конструкция), то оно должно комментироваться полностью. Если комментируется участок кода, где объявляются переменные, важно, чтобы не был открыт участок, где происходит обращение к этим переменным. Иначе говоря — комментирование должно применяться по логике программирования. Несоблюдения такого подхода приводит к возникновению новых, вводящих в заблуждение, ошибок при компиляции.

Вот отличный пример ошибки, когда неясно, где ее искать и нас может выручить комментирование кода.

Ошибки времени выполнения

Ошибки, возникающие в процессе исполнения кода программы, принято называть ошибками времени выполнения (runtime errors). Такие ошибки обычно зависят от состояния программы и связаны с некорректными значениями переменных.

Например, если переменная используется в качестве индекса элементов массива, то ее отрицательные значения неизбежно приведут к выходу за пределы массива.

Выход за пределы массива (Array out of range)

Эта ошибка часто возникает в индикаторах при обращении к индикаторным буферам. Функция IndicatorCounted() возвращает количество баров, не изменившихся после последнего вызова индикатора. Значения индикаторов на уже рассчитанных ранее барах не нуждаются в пересчете, поэтому для ускорения расчетов достаточно обрабатывать только несколько последних баров.

Большинство индикаторов, в которых используется данный способ оптимизации вычислений, имеют такой вид:

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+

int start()
{
    //--- иногда для расчета требуется не менее N баров (например, 100)
    // если на графике нет такого количества баров (например, на таймфрейме MN)
    if (Bars<100) {
        return(-1); // прекращаем расчет и выходим досрочно
    }

    //--- количество баров, не изменившихся с момента последнего вызова индикатора
    int counted_bars=IndicatorCounted();
    //--- в случае ошибки выходим
    if (counted_bars<0) {
        return(-1);
    }
    //--- позиция бара, с которого начинается расчет в цикле
    int limit=Bars-counted_bars;

    //--- если counted_bars=0, то начальную позицию в цикле нужно уменьшить на 1,
    if (counted_bars==0) {
        limit--;  // чтобы не выйти за пределы массива при counted_bars==0
        //--- в расчетах используется смещение на 10 баров вглубь 
        //--- истории, поэтому добавим это смещение при первом расчете
        limit-=10;
    } else {
        //--- индикатор уже рассчитывался ранее, counted_bars>0
        //--- при повторных вызовах увеличим limit на 1, чтобы 
        //--- гарантированно обновлять значения индикатора для последнего бара
        limit++;
    }
    //--- основной цикл расчета
    for (int i=limit; i>0; i--) {
        // используются значения баров, уходящих вглубь истории на 5 и 10
        Buff1[i]=0.5*(Open[i+5]+Close[i+10]) 
    }
}

Часто встречается некорректная обработка случая counted_bars==0 (начальную позицию limit нужно уменьшить на значение, равное 1 + максимальный индекс относительно переменной цикла).

Также следует помнить о том, что в момент исполнения функции start() мы можем обращаться к элементам массивов индикаторных буферов от 0 до Bars()-1. Если есть необходимость работы с массивами, которые не являются индикаторными буферами, то их размер следует увеличить при помощи функции ArrayResize() в соответствии с текущим размером индикаторных буферов. Максимальный индекс элемента для адресации также можно получить вызовом ArraySize() с одним из индикаторных буферов в качестве аргумента.

Деление на ноль (Zero divide)

Ошибка «Zero divide» возникает в случае, если при выполнении операции деления делитель оказывается равен нулю:

void OnStart()
{
    int a=0, b=0,c;
    c=a/b;
    Print("c=",c);
}

При выполнении данного скрипта во вкладке «Эксперты» возникает сообщение об ошибке и завершении работы программы:

Обычно такая ошибка возникает в случаях, когда значение делителя определяется значениями каких-либо внешних данных. Например, если анализируются параметры торговли, то величина задействованной маржи оказывается равна 0, если нет открытых ордеров. Другой пример: если анализируемые данные считываются из файла, то в случае его отсутствия нельзя гарантировать корректную работу. По этой причине желательно стараться учитывать подобные случаи и корректно их обрабатывать.

Самый простой способ — проверять делитель перед операцией деления и выводить сообщение о некорректном значении параметра:

void OnStart()
{
    int a=0, b=0,c;
    if (b!=0) {
        c=a/b; Print(c);
    } else {
        Print("Error: b=0"); 
        return; 
    }
}

В результате критической ошибки не возникает, но выводится сообщение о некорректном значении параметра и работа завершается:

Использование 0 вместо NULL для текущего символа

В старой версии компилятора допускалось использование 0 (нуля) в качестве аргумента в функциях, требующих указания финансового инструмента.

Например, значение технического индикатора Moving Average для текущего символа можно было запрашивать следующим образом:

AlligatorJawsBuffer[i]=iMA(0,0,13,8,MODE_SMMA,PRICE_MEDIAN,i);    // неправильно

В новом компиляторе для указания текущего символа нужно явно указывать NULL:

AlligatorJawsBuffer[i]=iMA(NULL,0,13,8,MODE_SMMA,PRICE_MEDIAN,i); // правильно

Кроме того, текущий символ и период графика можно указать при помощи функций Symbol() и Period().

AlligatorJawsBuffer[i]=iMA(Symbol(),Period(),13,8,MODE_SMMA,PRICE_MEDIAN,i); // правильно

Еще лучше, если вы будете использовать предопределенные переменные _Symbol и _Period – они обрабатываются быстрее:

AlligatorJawsBuffer[i]=iMA(_Symbol,_Period,13,8,MODE_SMMA,PRICE_MEDIAN,i); // правильно

Строки в формате Unicodе и их использование в DLL

Строки представляют собой последовательность символов Unicode. Следует учитывать этот факт и использовать соответствующие функции Windows. Например, при использовании функций библиотеки wininet.dll вместо InternetOpenA() и InternetOpenUrlA() следует вызывать InternetOpenW() и InternetOpenUrlW(). При передаче строк в DLL следует использовать структуру MqlString:

#pragma pack(push,1)
struct MqlString
{
    int      size;       // 32-битное целое, содержит размер распределенного для строки буфера
    LPWSTR   buffer;     // 32-разрядный адрес буфера, содержащего строку
    int      reserved;   // 32-битное целое, зарезервировано, не использовать
};
#pragma pack(pop,1)

Совместное использование файлов

При открытии файлов необходимо явно указывать флаги FILE_SHARE_WRITE и FILE_SHARE_READ для совместного использования.

В случае их отсутствия, файл будет открыт в монопольном режиме, что не позволит больше никому его открывать, пока он не будет закрыт монополистом.

Например, при работе с оффлайновыми графиками требуется явно указывать флаги совместного доступа:

// 1-st change - add share flags
ExtHandle=FileOpenHistory(c_symbol+i_period+".hst",
    FILE_BIN|FILE_WRITE|FILE_SHARE_WRITE|FILE_SHARE_READ);

Особенность преобразования datetime

Следует иметь ввиду, что преобразование типа datetime в строку зависит от режима компиляции:

datetime date=D'2014.03.05 15:46:58';
string str="mydate="+date;
//--- str="mydate=1394034418" - без директивы #property strict
//--- str="mydate=2014.03.05 15:46:58" - с директивой #property strict

Например, попытка работы с файлами, имя которых содержит двоеточие, приведет к ошибке.

Обработка ошибок времени выполнения

Так как без использования встроенных пользовательских функций не сможет обойтись ни один торгующий эксперт, то в первую очередь попытаемся упростить себе жизнь при анализе ошибок, возвращаемых этими функциями.

В наборе «из коробки» доступны некоторые библиотеки для упрощения написания советников, в том числе и для работы с ошибками. Хранятся они в папке MQL4/Include:

Нам понадобятся две библиотеки:

  • stderror.mqh — содержит константы для номера каждой ошибки;
  • stdlib.mqh — содержит несколько вспомогательных функций, в том числе и функцию возврата описания ошибки в виде строки:
string ErrorDescription(int error_code)

Поэтому подключим в наш проект обе эти библиотеки:

#include <stderror.mqh>
#include <stdlib.mqh>

Сами описания ошибок находятся в файле MQL4/Library/stdlib.mql4 и они на английском языке. Поэтому, если вы против иностранных языков, всегда можно переписать описания на свой родной.

Еще одна встроенная необходимая нам функция — GetLastError(). Именно она возвращает коды ошибок в виде целого числа (int), который мы потом будем обрабатывать. Сами коды ошибок и их описания на русском можно посмотреть в руководстве по mql4 от MetaQuotes. Оттуда же можно взять информацию для перевода файла stdlib.mql4 на русский.

Теперь, когда мы подключили необходимые библиотеки, рассмотрим результаты работы функций, непосредственно связанных с торговыми операциями, так как игнорирование сбоев в этих функциях может привести к критическим для бота последствиям.

К сожалению, средствами MQL4 нельзя написать обобщенную библиотеку для обработки всех возможных ошибочных ситуаций. В каждом отдельном случае придется обрабатывать ошибки отдельно. Но не все так плохо, – многие ошибки не нужно обрабатывать, их достаточно исключить на этапе разработки и тестирования эксперта, хотя для этого и нужно вовремя узнать об их наличии.

Рассмотрим для примера две типичные для экспертов на MQL4 ошибки:

  1. Ошибка 130 — ERR_INVALID_STOPS
  2. Ошибка 146 — ERR_TRADE_CONTEXT_BUSY

Одним из случаев, когда возникает первая ошибка, является попытка эксперта выставить отложенный ордер слишком близко к рынку. Ее наличие может серьезно ухудшить показатели эксперта в некоторых случаях. Например, допустим эксперт, открыв прибыльную позицию, поджимает прибыль каждые 150 пунктов. Если при очередной такой попытке возникнет ошибка 130, а цена безвозвратно вернется к предыдущему уровню стопа, эксперт может лишить вас законной прибыли. Несмотря на возможность таких последствий, данную ошибку можно исключить в корне, доработав код эксперта так, чтобы он учитывал минимальное допустимое расстояние между ценой и стопами.

Вторую ошибку, связанную с занятостью торгового контекста терминала, полностью исключить не получится. При работе нескольких экспертов в одном терминале всегда возможна ситуация, когда один из экспертов попытается открыть позицию, пока другой все еще делает то же самое. Следовательно, такую ошибку всегда нужно обрабатывать.

Таким образом, мы всегда должны быть в курсе, если какая-то из используемых встроенных функций возвращает ошибку во время работы эксперта. Добиться этого можно, используя следующую нехитрую вспомогательную функцию:

void logError(string functionName, string msg, int errorCode = -1)
{
    Print("ERROR: in " + functionName + "()");
    Print("ERROR: " + msg );

    int err = GetLastError();

    if (errorCode != -1) {
        err = errorCode;
    }

    if (err != ERR_NO_ERROR) {
        Print("ERROR: code=" + err + " - " + ErrorDescription( err ));
    }
}

Использовать ее мы будем следующим образом:

void openLongTrade()
{
    int ticket = OrderSend(Symbol(), OP_BUY, 1.0, Ask + 5, 5, 0, 0);
    if (ticket == -1) {
        logError("openLongTrade", "could not open order");
    }
}

Конечно, это упрощенный пример. Для написания более грамотных функций открытия, закрытия и модификации ордеров смотрите этот урок.

Первым параметром в функцию logError() передается имя функции, в которой была обнаружена ошибка, в нашем примере — в функции openLongTrade(). Если наш эксперт вызывает функцию OrderSend() в нескольких местах, это позволит нам точно установить, в каком из них произошла ошибка. Вторым параметром передается описание ошибки, чтобы можно было понять, где именно внутри функции openLongTrade() была обнаружена ошибка. Это может быть как краткое описание ошибки, так и более развернутое, с перечислением значений всех параметров, переданных во встроенную функцию.

Я предпочитаю последний вариант, так как при возникновении ошибки можно сразу получить всю необходимую для анализа информацию. Для примера допустим, что до вызова OrderSend() текущая цена успела сильно отклониться от последней известной нам цены. В результате при выполнении этого примера произойдет ошибка и в протоколе работы эксперта появятся следующие строки:

ERROR: in openLongTrade()
ERROR: could not open order
ERROR: code=138 - requote

То есть сразу будет видно:

  • в какой функции произошла ошибка;
  • к чему она относится (в данном случае — к попытке открыть позицию);
  • какая именно ошибка возникла (код ошибки и ее описание).

Теперь рассмотрим третий, необязательный, параметр функции logError(). Он необходим в тех случаях, когда мы хотим обработать конкретный вид ошибки, а об остальных будем отчитываться в протоколе работы эксперта, как и прежде:

void updateStopLoss(double newStopLoss)
{
    bool modified = OrderModify(OrderTicket(), OrderOpenPrice(), 
        newStopLoss, OrderTakeProfit(), OrderExpiration());
    if (!modified) {
        int errorCode = GetLastError();
        if (errorCode != ERR_NO_RESULT ) {
            logError("updateStopLoss", "failed to modify order", errorCode);
        }
    }
}

Здесь в функции updateStopLoss() вызывается встроенная функция OrderModify(). Эта функция несколько отличается в плане обработки ошибок от OrderSend(). Если ни один из параметров изменяемого ордера не отличается от его текущих параметров, то функция вернет ошибку ERR_NO_RESULT. Если в нашем эксперте такая ситуация допустима, то мы должны игнорировать конкретно эту ошибку. Для этого мы анализируем значение, возвращаемое GetLastError(). Если произошла ошибка с кодом ERR_NO_RESULT, то мы ничего не выводим в протокол.

Однако если произошла другая ошибка, то необходимо полностью отрапортовать о ней, как мы делали это раньше. Именно для этого мы сохраняем результат функции GetLastError() в промежуточной переменной и передаем его третьим параметром в функцию logError(). Дело в том, что встроенная функция GetLastError() автоматически обнуляет код последней ошибки после своего вызова. Если бы мы не передали код ошибки явно в logError(), то в протоколе была бы отражена ошибка с кодом 0 и описанием «no error».

Похожие действия необходимо совершать и при обработке других ошибок, например, реквотов. Основная идея заключается в том, чтобы обрабатывать только ошибки, требующие обработки, а остальные передавать в функцию logError(). Тогда мы всегда будем в курсе, если во время работы эксперта произошла непредвиденная ошибка. Проанализировав логи, мы сможем решить, требует ли данная ошибка отдельной обработки или же ее можно исключить, доработав код эксперта. Такой подход часто заметно упрощает жизнь и сокращает время, уходящее на борьбу с ошибками.

Диагностика логических ошибок

Логические ошибки в коде эксперта могут доставить много проблем. Отсутствие возможности пошаговой отладки экспертов делают борьбу с такими ошибками не очень приятным занятием. Основным средством для диагностики этого на данный момент является встроенная функция Print(). С ее помощью можно выполнять распечатку текущих значений важных переменных, а также протоколировать ход работы эксперта прямо в терминале во время тестирования. При отладке эксперта во время тестирования с визуализацией также может помочь встроенная функция Comment(), которая выводит сообщения на график. Как правило, убедившись, что эксперт работает не так, как было задумано, приходится добавлять временные вызовы функции Print() и протоколировать внутреннее состояние эксперта в предполагаемых местах возникновения ошибки.

Однако, для обнаружения сложных ошибочных ситуаций порой приходится добавлять десятки таких вызовов функции Print(), а после обнаружения и устранения проблемы их приходится удалять или комментировать, чтобы не загромождался код эксперта и не замедлялось его тестирование. Ситуация ухудшается, если в коде эксперта функция Print() уже используется для периодического протоколирования различных состояний. Тогда удаление временных вызовов Print() не удается выполнить путем простого поиска фразы ‘Print’ в коде эксперта. Приходится задумываться, чтобы не удалить еще и полезные вызовы этой функции.

Например, при протоколировании ошибок функций OrderSend(), OrderModify() и OrderClose() полезным бывает печатать в протокол текущее значение переменных Bid и Ask. Это несколько облегчает распознавание причин таких ошибок, как ERR_INVALID_STOPS и ERR_OFF_QUOTES.

Чтобы выделить такие диагностические выводы в протокол, я рекомендую использовать такую вспомогательную функцию:

void logInfo(string msg)
{
    Print("INFO: " + msg);
}

Это желательно сделать по нескольким причинам. Во-первых, теперь такие вызовы не будут попадаться при поиске ‘Print’ в коде эксперта, ведь искать мы будем logInfo. Во-вторых, у этой функции есть еще одна полезная особенность, о которой мы поговорим чуть позже.

Добавление и удаление временных диагностических вызовов функции Print() отнимает у нас драгоценное время. Поэтому я предлагаю рассмотреть еще один подход, который эффективен при обнаружении логических ошибок в коде и позволяет немного сэкономить наше время. Рассмотрим следующую несложную функцию:

void openLongTrade(double stopLoss)
{
    int ticket = OrderSend(Symbol(), OP_BUY, 1.0, Ask, 5, stopLoss, 0);
    if (ticket == -1) {
        logError("openLongTrade", "could not open order");
    }
}

В данном случае, так как мы открываем длинную позицию, совершенно очевидно, что при нормальной работе эксперта значение параметра stopLoss никогда не будет больше или равно текущей цене Bid. То есть, при вызове функции openLongTrade() всегда выполняется условие stopLoss < Bid. Так как мы знаем об этом еще на этапе написания рассматриваемой функции, то мы сразу же можем этим воспользоваться следующим образом:

void openLongTrade( double stopLoss )
{
    assert("openLongTrade", stopLoss < Bid, "stopLoss < Bid");
 
    int ticket = OrderSend(Symbol(), OP_BUY, 1.0, Ask, 5, stopLoss, 0);
    if (ticket == -1) {
        logError("openLongTrade", "could not open order");
    }
}

То есть мы логируем наше утверждение в коде при помощи новой вспомогательной функции assert(). Сама функция выглядит довольно просто:

void assert(string functionName, bool assertion, string description = "")
{
    if (!assertion) {
        Print("ASSERT: in " + functionName + "() - " + description);
    }
}

Первым параметром функции является ее имя, в котором проверяется наше условие (по аналогии с функцией logError()). Вторым параметром передается результат проверки этого условия. И третьим параметром передается его описание. В результате, если ожидаемое условие не выполняется, в протокол будет выведена следующая информация:

  • название функции, в которой условие было нарушено;
  • описание нарушенного условия.

В качестве описания можно передавать, например, само условие, а можно выводить и более подробное описание, содержащее значения контролируемых переменных на момент проверки условия, если это поможет разобраться в причинах ошибки.

Конечно же, рассмотренный пример максимально упрощен. Но, надеюсь, основную идею отражает достаточно хорошо. В процессе наращивания функциональности эксперта мы отдаем себе отчет в том, как он должен работать и какие состояния и входные параметры функций допустимы, а какие нет. Фиксируя это в коде эксперта при помощи функции assert() мы получаем ценную информацию о месте, в котором нарушается логика работы эксперта. Более того, мы частично избавляем себя от необходимости добавлять и удалять временные вызовы функции Print(), так как функция assert() выдает диагностические сообщения в протокол только в момент обнаружения несоответствий в ожидаемых нами условиях.

Еще одним полезным приемом является использование этой функции перед каждой операцией деления. Дело в том, что иногда в результате той или иной логической ошибки иногда происходит деление на ноль. Работа эксперта в этом случае прекращается, а в протоколе появляется одна лишь строка с печальным диагнозом: ‘zero divide’. Узнать, в каком именно месте произошла эта ошибка, если операция деления используется в коде неоднократно, достаточно сложно. Вот здесь и поможет функция assert(). Вставляем соответствующие проверки перед каждой операцией деления:

assert("buildChannel", distance > 0, "distance > 0");
double slope = delta / distance;

И теперь, в случае деления на ноль, достаточно будет лишь взглянуть в логи, чтобы узнать, в каком именно месте произошла ошибка.

Обработка состояний

Во время работы эксперта на вашем счете могут возникнуть некоторые ситуации, которые не являются ошибками – так называемые состояния эксперта. Такие состояния не являются ошибками, но все же их стоит логировать. В этом помогают специальные функции языка mql4.

Функция IsExpertEnabled() возвращает информацию о возможности запуска экспертов. Функция вернет true, если в клиентском терминале разрешен запуск экспертов, иначе возвращает false. В случае возврата false полезно будет известить об этом пользователя с просьбой включить соответствующую настройку. Пример:

void OnStart()
{
    if (!IsExpertEnabled() {
        //советникам не разрешено торговать
        Alert("Attention! Please press the "Expert Advisors" button in MT4");
    }
    //рабочий алгоритм советника
    return;
}

Если эксперт использует внешние библиотеки, пригодится функция IsLibrariesAllowed(). Она возвращает true, если эксперт может вызвать библиотечную функцию, иначе возвращает false.

Если библиотека в виде dll файла, пригодится функция IsDllsAllowed(). Также нелишним будет проверить, есть ли вообще возможность торговать при помощи экспертов с помощью функции IsTradeAllowed().

Если вы хотите узнать, является ли счет демонстрационным, или же реальным, можно использовать функцию IsDemo().

Все вышеперечисленные проверки стоит сделать в функции OnInit().

Конечно же, стоит проверять периодически связь с сервером. В этом поможет функция IsConnected().

Следующие три функции помогут определить, в каком режиме находится советник. Если IsOptimisation() возвращает true, проводится оптимизация, если IsTesting(), то тестирование, IsVisualMode() – тестирование в режиме визуализации. Под каждый из этих вариантов в советнике может быть предусмотрена своя логика. Например, для режима визуализации можно что-то выводить на график (и не выводить в других режимах ради экономии ресурсов). В режиме тестирования можно выводить отладочную информацию, в режиме оптимизации максимально облегчить код, чтобы сэкономить время.

И последняя функция – IsTradeContextBusy(). Она вернет true, если поток для выполнения торговых операций занят. Это бывает полезно при совершении экспертом торговых операций. Можно применить функцию Sleep для ожидания некоторого момента и новой попытки.

Еще одна полезная функция — UninitializeReason()

int deinit()
{
    switch(UninitializeReason())
    {
        case REASON_CHARTCLOSE:
        case REASON_REMOVE:      
            CleanUp(); 
            break;    // очистка и освобождение ресурсов.
        case REASON_RECOMPILE:
        case REASON_CHARTCHANGE:
        case REASON_PARAMETERS:
        case REASON_ACCOUNT:     
            StoreData(); 
            break;  // подготовка к рестарту.
    }
    //...
}

Можно также логировать причину выхода советника.

Коды самых распространенных ошибок и их вероятное решение

№ ошибки Значение Проблема Решение
4, 146 Торговый сервер занят Советник подал слишком много приказов одновременно или не дождавшись ответа от сервера, при выполнении операции — советник пытается отправить новый приказ Перезагрузка терминала или оптимизация кода советника с помощью функций обработки ошибок
8, 141 Слишком частые запросы Предыдущие причины ошибки, в сильно частом запросе Аналогичное решение
129 Неправильная цена Цена по которой Вы пытаетесь открыть позицию (BUY или SELL) неправильная BUY нужно открывать по Ask а закрывать по BID;
SELL нужно открывать по BID а закрывать по ASK
130, 145 Неправильные стопы Стоп лосс, тейк профит или уровень открытия отложки или лимитника неверные.
Стопы расположены слишком близко к цене.
Ваш счет открыт в группе ECN (ЕЦН) или NDD (НДД), что не дает сразу выставлять стопы
Проверьте значения Ваших стоп лоссов, тейк профитов, уточните минимальный стоп уровень по Вашему инструменту у брокера, при выставлении стопов — соблюдайте уровень минимальной дистанции. В хорошо написанном советнике должны быть функции работы на счетах ECN и NDD – это происходит путем модификации ордера уже после его открытия
131 Неправильный объем Неправильный лот при открытии сделки, или меньше минимального (больше максимального). Разрядность лота тоже может отличаться от разрядности брокера Проверьте правильность открытия лота, изучите спецификацию контракта и прочтите условия торговли в Вашем ДЦ, проверьте минимальный и максимальный лот в Вашем ДЦ и на Вашем счете
132 Рынок закрыт Рынок закрыт на выходные дни Пробуйте связаться с рынком после выходных
133 Торговля запрещена В данный момент торговля запрещена По данной валютной паре запрещено торговать – в конкретный момент времени или вообще. Часто у брокеров есть перерыв в несколько минут в полночь
134 Недостаточно денег для совершения операции Лот, который Вы пытаетесь открыть, слишком большой, на него не хватает маржи Проверьте уровень свободных средств и рассчитайте средства, которые Вам нужны для открытия лота, следите за уровнем Ваших свободных средств
135-138 Цена изменилась Реквот, слишком быстрый рынок (новости), Брокер или ДЦ не дает Вам поставить позицию по заявленной цене Не торгуйте в такие моменты, увеличьте уровень проскальзывания, но помните, что это влечет за собой открытие позиций не по заявленной Вами цене. Предусмотрите в советнике функцию обработки ошибок и количество попыток открытия позиций
147 Использование даты истечения ордера запрещено брокером Ваш советник или Вы пытаетесь установить срок истечения отложенного ордера В советнике, в функции OrderSend в параметре срок истечения поставьте 0 (ноль). Не устанавливайте срок истечения ордера
148 Количество открытых и отложенных ордеров достигло предела, установленного брокером Максимальное количество открытых ордеров и позиций достигло предела, установленного брокером Удалите или закройте часть позиций. Остановите процесс открытия новых позиций
4012, 4013 Остаток от деления на ноль Вы пытаетесь поделить число на 0 (ноль) Проверьте код советника на наличие ошибки, или же проверьте все значения из MarketInfo функций на момент возвращения 0, иногда при MarketInfo(Symbol(),MODE_SPREAD) возвращается не спред, а 0 (у брокеров с плавающим спредом)
4017 Вызовы DLL не разрешены В Вашем терминале запрещен вызов DLL Разрешите вызов DLL через Меню – сервис – Настройки – Советник – Разрешить вызов DLL
4018, 4019 Невозможно загрузить библиотеку Библиотека повреждена или ее вызов завершается с ошибкой, возможно она вообще отсутствует Проверьте библиотеку DLL
4020 Вызовы внешних библиотечных функций не разрешены В Вашем терминале запрещен вызов функций из внешних экспертов Разрешите вызов функций через Меню – сервис – Настройки – Советник – Разрешить вызов внешних экспертов
4103 Невозможно открыть файл Данный файл не существует или заблокирован другим процессом Проверьте наличие указанного файла. Проверьте, не заблокирован ли файл системой антивируса, разрешен ли режим записи-чтения файла
4106 Неизвестный символ Символа нет в обзоре рынка В обзоре рынка – правой кнопкой мыши – показать все символы. Проверить названия символа в советнике и наличие его в обзоре рынка. Некоторые советники используют четкие названия без суффиксов, а брокеры намеренно ставят суффиксы, например EURUSDx где х – суффикс
4108 Неверный номер тикета Тикет ордера, который выбирает эксперт – не существует. Эксперт пытается выбрать тикет, но данный ордер был закрыт другим советником или руками. При попытке осуществления приказа над ордером, тикет был исполнен и закрыт брокером Если данная ошибка появляется очень часто, 100-1000 раз за минуту, проверьте функции Вашего советника. Отключите другие советники или настройте их так, чтобы они не конфликтовали, не закрывайте ордер руками, когда эксперт выполняет операцию. Иногда такое случается, когда несколько советников используют одинаковый MagicNumber
4109 Торговля не разрешена Советнику запрещено торговать, на графике грустный смайл или крестик Включите галочку «Разрешить советнику торговать» во вкладе при установке советника, либо в меню — сервис – настройки – советники
4110, 4111 Длинные/короткие позиции не разрешены В настройках советника, во вкладке Общие не разрешен тип позиций Во вкладке Общие, при установке советника, есть выбор позиций, разрешенных к открытию

Заключение

Рассмотренные вспомогательные функции и несложные приемы позволяют заметно упростить и ускорить процесс обнаружения и исправления ошибок в коде торговых экспертов, написанных на языке программирования MQL4. Грамотное написание кода и функций для логирования и сопровождения работы советника существенно ускоряют процесс его разработки.

Тема на форуме

С уважением, Дмитрий аkа Silentspec
TradeLikeaPro.ru

Компиляция

Компиляция — это процесс перевода исходного кода MQL4/MQL5-программы на машинный язык. Ее результатом является создание исполняемого файла программы (*EX4 или *.EX5), который может быть запущен в торговой платформе.

Компиляция состоит из нескольких этапов:

  • Лексический анализ
  • Синтаксический анализ
  • Семантический анализ
  • Генерация кода
  • Оптимизация кода
  • Скомпилировать можно любой файл (*.MQ4, *.MQ5 или *.MQH), однако исполняемый файл (*.EX4 или *.EX5) может быть получен только в результате компиляции основного MQ4/MQ5-файла программы или проекта.
  • Исполняемый файл создается в собственном закрытом формате, что скрывает исходный алгоритм программы.
  • Скомпилированные исполняемые EX4/EX5-файлы можно распространять без исходных MQ4, MQ5 и MQH-файлов. Без них отладка невозможна. Также не рекомендуется распространять исполняемые EX4/EX5-файлы, полученные в процессе отладки.

Чтобы получить исполняемый файл программы, откройте основной исходный файл или проект через «Навигатор», а затем нажмите  «Компилировать Компилировать» в меню «Файл» или «F7». Протокол процесса компиляции будет отображен на вкладке «Ошибки» в окне «Инструменты». Если компиляция прошла без ошибок, вы можете запустить полученную программу в торговой платформе.

Компиляция программы с выводом результатов во вкладке "Ошибки"

Если в процессе компиляции возникли ошибки или предупреждения, их описание будет показано на вкладке «Ошибки».

Ошибки компиляции обозначаются иконками Ошибка как на вкладке «Ошибки», так и в самом коде при переходе к ним. При их возникновении исполняемый файл программы (*EX4 или *.EX5) не создастся. Чтобы перейти к строке, где возникла ошибка, дважды щелкните мышью на ошибке или нажмите «Перейти к строке Перейти к строке» в контекстном меню. Номер строки и столбца, где была найдена ошибка, показываются в соответствующих колонках.

Предупреждения, обозначаемые иконкой Предупреждение, указывают на места возможного появления ошибок. То есть компилятор обращает внимание на те места исходного кода, которые могли быть неверно интерпретированы (например, неявная смена типа значения). Перейти к такой строке в коде можно точно так же, как и в случае с ошибками.

Иконками Информация помечаются различные информационные сообщения. Например, сообщения о включаемых файлах, к которым обращалась программа при компиляции.

Режим компиляции #

В редакторе предусмотрено два режиме компиляции: с максимальной оптимизацией кода и без оптимизации.

Отсутствие оптимизации значительно сокращает время создания исполняемого файла. Используйте этот режим в активной стадии разработки, когда требуется быстро проверить написанный код. А уже при компиляции программы для реальной работы включайте режим максимальной оптимизации для наилучшей производительности.

Для переключения между режимами используйте меню «Сборка» или меню команды компиляции на панели инструментов:

Управляйте режимом компиляции

Параметр «Максимальная оптимизация» в настройках проекта выполняет ту же функцию.

  • Полное описание ошибок компиляции приведено в справочнике по языку MQL4/MQL5.
  • При наличии предупреждений исполняемый файл создается. Однако их не рекомендуется игнорировать. Предупреждения указывают на потенциальные ошибки в коде.
  • При компиляции исполняемый файл (*.EX4 или *.EX5) создается в том же каталоге, что и основной исходный файл  программы (*.MQ4 или *.MQ5) или проект.
  • Исполняемый файл metaeditor.exe можно использовать как внешний компилятор в сторонних IDE.

Рассмотрим дополнительные функции и возможности MQL4, которые могут быть полезны в программировании советников.

Escape символы

Если вы хотите добавить кавычки или символ обратной черты в строку, вам нужно экранировать символ, используя обратную косую черту (). Например, если вам нужно вставить двойную кавычку, escape-символ будет ». Для одиночной кавычки escape-символ — ’. Для обратной косой черты в качестве escape-символа используйте две обратные косые черты: \

string EscQuotes = "Эта строка содержит "двойные кавычки""; // Эта строка содержит "двойные кавычки"
string EscQuote = "Эта строка содержит 'одинарные кавчки'"; // Эта строка содержит 'одинарные кавычки'
string EscSlash = "Эта строка содержит обратную черту \"; // Эта строка содержит обратную черту 

Если вам нужно, чтобы строка занимала несколько строк, используйте escape-символ n для добавления новой строки:

string NewLine = "Эта строка n новая строка"; // Output: Эта строка
новая строка

Использование комментариев на графике

Вы можете напечатать текст в верхнем левом углу графика, используя функцию Comment(). Это можно использовать для вывода информации о состоянии, настроек индикатора или любой другой информации, которая может оказаться полезной.

Один из способов отображения комментариев к графику — объявить несколько строковых переменных и объединить их вместе с символами новой строки. Одна строка может использоваться для отображения настроек, другая — для отображения информационных сообщений или статуса ордера и т. д. Строка будет передана в функцию Comment().

Поместите функцию Comment() в конец функции start(), чтобы обновить комментарий на графике:

string SettingsComment = "FastMAPeriod: "+FastMAPeriod+" SlowMAPeriod: "+SlowMAPeriod; string StatusComment = "Размещен ордер на покупку";
Comment(SettingsComment+"n"+StatusComment);

Мы объявляем и устанавливаем значения строк SettingsComment и StatusComment внутри функции start(). В конце функции start мы вызываем функцию Comment() и используем ее для вывода наших комментариев на графике. Мы используем символ новой строки (n), чтобы разделить комментарий на две строки.

Проверка настроек

Существует несколько настроек советника, которые должны быть проверены, прежде чем советнику будет разрешено торговать. Эти настройки находятся на вкладке «Общие» в диалоговом окне «Свойства эксперта».

Параметр Allow live trading должен быть включен до начала торговли. Если он не включен, в правом верхнем углу графика рядом с именем советника появится эмоция недовольство. Вы можете проверить это условие в своем советнике, используя функцию IsTradeAllowed(). Если она возвращает false, настройка Allow live trading отключена.

Если вы хотите отобразить пользователю сообщение о том, что этот параметр должен быть активирован, вы можете сделать следующее:

if(IsTradeAllowed() == false) Alert("Активируйте настройку 'Allow live trading' в свойствах советника!");

Если ваш эксперт использует внешнюю библиотеку .ex4, в свойствах эксперта должен быть включен параметр Разрешить импорт внешних экспертов. Вы можете проверить это с помощью функции IsLibrariesAllowed():

if(IsLibrariesAllowed() == false) Alert("Активруйте настройку 'Allow import of external experts' в свойствах советника!");

То же самое можно сделать для DLL, используя функцию IsDllsAllowed():

if(IsDllsAllowed() == false) Alert("Активруйте настройку 'Allow DLL imports' в свойствах советника!");

настройки советника

Ограчения аккаунтов

В какой-то момент вы можете решить продать своего советника другим трейдерам. Вы также можете предоставить демо-версию для потенциальных покупателей. Чтобы ваш советник не мог свободно распространяться посторонними лицами, вы должны включить некоторые ограничения учетной записи, которые ограничивают использование советника покупателям или брокером.

Чтобы ограничить использование советника только для демо-счета, используйте функцию IsDemo(), чтобы проверить, является ли текущий активный аккаунт демо-счетом. Если текущий счет не является демо-счетом, мы выведем предупреждение и остановим выполнение работы советника.

if(IsDemo() == false) {
Alert("Этот советник работает только на демо-аккаунте!");
return(0); }

Вы можете использовать функции учетной записи AccountName(), AccountNumber() и AccountBroker(), чтобы проверить имя учетной записи, номер аккаунта и брокера соответственно. Ограничение использования по номеру счета является распространенным и простым в реализации способом защиты:

int CustomerAccount = 123456;
if(AccountNumber() != CustomerAccount) {
Alert("Номер аккаунта не совпадает!");
return(0); 
}

Вы можете использовать AccountName() или AccountBroker() аналогичным образом. Для AccountBroker() вам сначала нужно использовать Print(), чтобы получить правильное возвращаемое имя брокера. Это значение будет напечатано в журнале экспертов.

Если вы решили продавать советник на коммерческой основе, имейте в виду, что файлы MQL легко декомпилировать. Существуют различные методы, которые могут использовать хакеры для взлома вашего советника, например, размещение функций во внешних библиотеках или DLL.

MessageBox()

До сих пор в этой книге мы использовали встроенную функцию Alert() для отображения сообщений об ошибках. Но что, если вы хотите настроить диалоги оповещений или запросить ввод данных у пользователя? Функция MessageBox() позволит вам создать собственный всплывающий диалог с помощью Windows API.

Чтобы использовать функцию MessageBox(), мы должны сначала выполнить #include файл WinUser32.mqh, который устанавливается вместе с MetaTrader. Этот файл импортирует функции из файла Windows user32.dll и определяет константы, необходимые для работы функции MessageBox(). Вот синтаксис функции MessageBox():

int MessageBox(string Text, string Title, int Flags);

Чтобы использовать функцию MessageBox(), мы должны определить текст, который будет отображаться во всплывающем диалоговом окне, а также заголовок, отображаемый в строке заголовка. Нам также нужно будет указать флаги, которые указывают, какие кнопки и значки должны появляться в нашем всплывающем окне. Если флаги не указаны, кнопка ОК будет использоваться по умолчанию. Флаги должны быть разделены символом (|).

Вот пример окна сообщения с кнопками Да / Нет и значком вопросительного знака:

// #include <WinUser32.mqh>
// start() function
int YesNoBox = MessageBox("Открыть сделку?","Подтверждение торговли",
      MB_YESNO|MB_ICONQUESTION);
if(YesNoBox == IDYES) {
        // Разместить ордер
      }

Флаг MB_YESNO указывает, что мы будем использовать кнопки «Да / Нет» в нашем окне сообщений, в то время как флаг MB_ICONQUESTION помещает значок вопросительного знака в диалоговом окне. Переменная YesNoBox содержит возвращаемое значение функций MessageBox(), которое будет указывать, какая кнопка была нажата.

Если кнопка «Да» была нажата, значением YesNoBox будет IDYES, и будет размещен ордер. Если кнопка «Нет» была нажата, флаг возврата будет IDNO. Вы можете использовать возвращаемое значение MessageBox() в качестве входных данных, чтобы определить порядок действий, таких как размещение ордера.

Ниже приведен неполный список флагов, которые можно использовать в окнах сообщений.

Флаги для кнопок

Эти флаги указывают, какие кнопки появляются в вашем окне сообщения:

  • MB_OKCANCEL — кнопки ОК и Отмена.
  • MB_YESNO — кнопки Да и Нет.
  • MB_YESNOCANCEL — кнопки Да, Нет и Отмена.

Флаги для иконок

Эти флаги указывают на значки, которые появляются рядом с текстом в окне сообщения.

  • MB_ICONSTOP — значок знака остановки.
  • MB_ICONQUESTION — значок знака вопроса.
  • MB_ICONEXCLAMATION — значок с восклицательным знаком.
  • MB_ICONINFORMATION — информационный значок.

Возвращаемые флаги

Эти флаги являются возвращаемым значением функции MessageBox() и указывают, какая кнопка была нажата.

  • IDOK — нажата кнопка «ОК».
  • IDCANCEL — была нажата кнопка «Отмена».
  • IDYES — была нажата кнопка «Да».
  • IDNO — была нажата кнопка «Нет».

Уведомления по E-mail

Ваш эксперт может уведомить вас по электронной почте о размещенных сделках, возможных настройках торговли и многом другом. Функция SendMail() отправит электронное письмо с выбранной темой и текстом на адрес электронной почты, указанный в диалоговом окне «Сервис — Параметры» на вкладке «Электронная почта».

На вкладке «Электронная почта» необходимо сначала указать почтовый сервер SMTP с номером порта, например, mail.yourdomain.com:25, а также имя пользователя и пароль, если это необходимо.

Вы можете использовать любой адрес электронной почты в поле От. Поле «Кому» — это адрес электронной почты, на который отправляются сообщения.

Функция SendMail() имеет два аргумента: первый — это строка темы письма, а второй — содержимое самого письма. Вы можете использовать новые строки, экранированные символы, переменные и константы в теле вашего письма.

настройки e-mail

Вот пример использования функции SendMail():

string EmailSubject = "Ордер на покупку открыт";
string EmailBody = Ордер на покупку "+Ticket+" открыт на "+Symbol()+" по цене "+Ask; // "Ордер на покупку 12584 открыт на EURDUSD по цене 1.4544"
SendMail(EmailSubject,EmailBody);

Повтор после ошибки

Мы пытались проверить параметры ордера, прежде чем пытаться разместить ордер, чтобы избежать распространенных сообщений об ошибках из-за неправильных настроек или цены. Тем не менее, ошибки все еще могут возникать, к примеру, из-за реквот или проблем с сервером. Этих ошибок не всегда можно избежать, но мы можем попробовать разместить ордер снова, когда это произойдет.

Чтобы повторить ордер, мы поместим функцию OrderSend() в цикл while. Если OrderSend() не возвращает номер тикета, мы попробуем открыть ордер еще раз:

int Ticket = 0; while(Ticket <= 0) {
Ticket = OrderSend(Symbol(),OP_BUY,LotSize,OpenPrice,UseSlippage,BuyStopLoss,BuyTakeProfit); 
}

Сначала мы объявляем переменную для номера тикета, в данном случае Ticket. Пока Ticket не больше 0, цикл while с функцией OrderSend() будет выполняться снова и снова.

В случае ошибки или какой-либо другой неисправленной торговой ошибки, цикл будет повторяться бесконечно, и ваш эксперт будет зависать. Мы можем исправить это, добавив максимальное количество повторов:

int Retries = 0;
int MaxRetries = 5;
int Ticket = 0; while(Ticket <= 0) {
Ticket = OrderSend(Symbol(),OP_BUY,LotSize,OpenPrice,UseSlippage,BuyStopLoss,BuyTakeProfit);
        if(Retries <= MaxRetries) Retries++;
else break;
}

Мы объявляем переменную для использования в качестве счетчика (Retries) и максимальной настройки повторов (MaxRetries). Пока мы не превысили MaxRetries, переменная Retries увеличивается, и цикл повторяется снова. Как только MaxRetries достигнут, цикл завершается. После этого вы можете при необходимости предупредить пользователя об ошибке.

Если вы хотите сделать цикл повторных попыток зависимым от определенного состояния ошибки, мы можем проверить код ошибки по списку и вернуть значение true, если оно совпадение. Эта функция содержит некоторые распространенные коды ошибок:

    bool ErrorCheck(int ErrorCode)
      {
switch(ErrorCode)
  {
    case 128: // Trade timeout
    return(true);
 
    case 136: // Off quotes
    return(true);
 
 
  case 138: // Requotes
  return(true);
 
  case 146: // Trade context busy
  return(true);
 
default:
  return(false);
}
}

Эта функция использует оператор switch. Мы ищем метку, значение которой соответствует выражению, назначенному оператору switch (в данном примере ErrorCode).

Когда совпадение наблюдений найдено, блок switch должен быть завершен с оператором прерывания или возврата. В этом примере мы используем оператор return для возврата значения true / false вызывающей функции.

Вот как мы используем ErrorCheck(), чтобы повторить попытку размещения ордера:

int Retries;
int MaxRetries = 5;
int Ticket; while(Ticket <= 0) {
Ticket = OrderSend(Symbol(),OP_BUY,LotSize,OpenPrice,UseSlippage,BuyStopLoss,BuyTakeProfit);
if(Ticket == -1) int ErrCode = GetLastError();
if(Retries <= MaxRetries && ErrorCheck(ErrCode) == true) Retries++; else break;
}

Если Ticket возвращает -1, указывая на то, что произошла ошибка, мы получаем код ошибки с помощью GetLastError(). Мы передаем код ошибки в нашу функцию ErrorCheck(). Если код ошибки соответствует какой-либо из ошибок в функции проверки ошибок, ErrorCheck() вернет true, а функция OrderSend() будет повторена до 5 раз.

Использование комментариев к ордеру в качестве идентификатора

Мы использовали «магическое число» в качестве идентификатора ордера, который однозначно идентифицирует ордера, размещаемые конкретным советником. Если ваш эксперт размещает несколько ордеров одновременно, и вы хотите иметь возможность обрабатывать каждый из этих ордеров, вы можете использовать комментарий к ордеру в качестве необязательного идентификатора.

Допустим, ваш эксперт разместит два типа ордеров. Вы хотите иметь возможность изменить или закрыть эти ордера по отдельности. Вы можете использовать две функции OrderSend() и разместить разные комментарии к каждому из них. Затем, при выборе ордеров с использованием цикла, вы можете использовать OrderComment() в качестве одного из условий для нахождения ордеров для их изменения или закрытия.

string OrderComment1 = "Первый ордер"; 
string OrderComment2 = "Второй ордер";
 
// Размещение ордеров
int Ticket1 = OrderSend(Symbol(),OP_BUY,LotSize,OpenPrice,UseSlippage,BuyStopLoss,BuyTakeProfit,OrderComment1,MagicNumber,0,Green);
int Ticket2 = OrderSend(Symbol(),OP_BUY,LotSize,OpenPrice,UseSlippage,BuyStopLoss,BuyTakeProfit,OrderComment2,MagicNumber,0,Green);
 
// Изменение ордера
for(int Counter = 0; Counter <= OrdersTotal()-1; Counter++) {
OrderSelect(Counter,SELECT_BY_POS);
if(OrderMagicNumber() == MagicNumber && OrderSymbol() == Symbol() && OrderComment() == OrderComment1)
{
             // Изменение первого ордера
          }
else if(OrderMagicNumber() == MagicNumber && OrderSymbol() == Symbol() && OrderComment() == OrderComment2)
{
             // Изменение второго ордера
          }
}

Мы объявляем две строковые переменные для использования в качестве комментариев к ордеру. Функции OrderSend() размещают два ордера, каждый со своим комментарием. В следующем примере цикла модификации ордеров функция OrderComment() используется в качестве условия при выборе ордеров для модификации.

Вы можете использовать проверку OrderComment(), чтобы закрывать ордера независимо от других ордеров, использовать другие настройки трейлинг-стопа или что-либо другое, что требует ваша торговая система.

Проверка маржинальных требований

MetaTrader поставляется с функциями, которые позволяют вам проверить текущую свободную маржу или уровень стопов перед размещением ордера. Уровень Stop Out — это процент или сумма свободной маржи, ниже которой вы не сможете размещать ордера. Однако ручная проверка уровня свободной маржи или уровня стоп-ордера на самом деле не нужна, поскольку при попытке разместить ордер с слишком малым запасом маржипроизойдет ошибка.

Более полезная идея — определить собственный уровень стоп-аута и прекратить торговлю, если текущий капитал опустится ниже этого уровня. Давайте начнем с объявления внешней переменной MinimumEquity, которая является минимальной суммой капитала, необходимой на нашем счете, прежде чем мы сможем разместить ордер.

Мы сравним MinimumEquity с нашим текущим счетом. Если текущий эквити меньше нашего минимального эквити, ордер не будет размещен, и предупреждающее сообщение проинформирует пользователя о состоянии. Предположим, у нас есть баланс на счете 10000 долларов. Если мы потеряем более 20% этого капитала, мы не будет размещать ордера. Вот код для проверки минимального эквити:

// Внешние переменные
extern int MinimumEquity = 8000;
 
    // Размещение ордера
    if(AccountEquity() > MinimumEquity)
      {
        // Открываем ордер
}
else if(AccountEquity() <= MinimumEquity)
{
Alert("Размер эквити слишком низкий!");
}

Внешняя переменная MinimumEquity помещается в начало файла. Если текущий капитал, как указано AccountEquity(), больше MinimumEquity, ордер будет размещен. В противном случае заказ не будет размещен, и будет отображено предупреждающее сообщение.

Проверка спреда

Возможно, вы захотите избегать размещения ордеров в периоды, когда спред значительно расширяется. Мы можем установить максимальный спред и проверить текущий спред перед торговлей. Мы объявим внешнюю переменную MaximumSpread и используем MarketInfo() для проверки текущего спреда.

    // внешние переменные
extern int MaximumSpread = 5;
extern int MinimumEquity = 8000;
 
if(AccountEquity() > MinimumEquity && MarketInfo(Symbol(),MODE_SPREAD) < MaximumSpread) {
        // Place order
      }
else {
}
if(AccountEquity() <= MinimumEquity) Alert("Размер эквити ниже минимального!");
if(MarketInfo(Symbol(),MODE_SPREAD) > MaximumSpread) Alert("Размер спреда слишком большой!");
}

Обратите внимание, что мы выполняем проверку минимального эквити и спреда перед размещением ордера. Если одно из условий ложно, мы переходим в блок else и проверяем, какое из условий привело к тому, что ордер не был размещен. Мы будем отображать одно или несколько предупреждений в зависимости от того, какое условие выполняется.

Размещение нескольких ордеров

Вы можете разместить несколько ордеров с разными уровнями стоп-лосс и тейк-профит, а также размерами лота. Есть несколько способов сделать это. Один из способов — просто использовать разные операторы OrderSend() для каждого ордера. Это предполагает, что вы планируете размещать одинаковое количество ордеров каждый раз.

Другой способ — использовать цикл for для размещения заказов. Таким образом, вы можете настроить несколько ордеров одновременно. Вы можете предварительно загрузить свой стоп-лосс и тейк-профит в массивы, а также увеличивать их в циклах for.

Начнем с определения внешних переменных для трех уровней стоп-лосс и тейк-профит. Любые дополнительные ордера выше трех не будут иметь стоп-лосс или тейк-профит. Мы также добавим внешнюю переменную, чтобы настроить количество размещаемых ордеров.

extern int StopLoss1 = 20; 
extern int StopLoss2 = 40; 
extern int StopLoss3 = 60;
 
extern int TakeProfit1 = 40; 
extern int TakeProfit2 = 80; 
extern int TakeProfit3 = 120;
 
extern int MaxOrders = 3;

Далее мы объявим наши массивы, рассчитаем стоп-лосс и тейк-профит и загрузим рассчитанные цены в массив:

double BuyTakeProfit[3];
double BuyStopLoss[3];
 
BuyTakeProfit[0] = CalcBuyTakeProfit(Symbol(),TakeProfit1,Ask); 
BuyTakeProfit[1] = CalcBuyTakeProfit(Symbol(),TakeProfit2,Ask); 
BuyTakeProfit[2] = CalcBuyTakeProfit(Symbol(),TakeProfit3,Ask);
 
BuyStopLoss[0] = CalcBuyStopLoss(Symbol(),StopLoss1,Ask); 
BuyStopLoss[1] = CalcBuyStopLoss(Symbol(),StopLoss2,Ask); 
BuyStopLoss[2] = CalcBuyStopLoss(Symbol(),StopLoss3,Ask);

Мы начнем с объявления массивов для хранения стоп-лосса и тейк-профита, BuyTakeProfit и BuyStopLoss. Количество элементов массива должно быть указано при объявлении массива. Индексы массива начинаются с нуля, поэтому, объявив размер измерения массива 3, наш начальный индекс равен 0, а наш самый большой индекс равен 2.

Далее мы рассчитываем цены стоп-лосс и тейк-профит, используя функции CalcBuyStopLoss() и CalcBuyTakeProfit(). Мы присваиваем рассчитанный стоп-лосс или тейк-профит соответствующему элементу массива. Обратите внимание, что первый индекс массива равен 0, а третий индекс массива равен 2.

Вот цикл for для размещения ордеров:

for(int Count = 0; Count <= MaxOrders - 1; Count++) {
int OrdInt = Count + 1;
OrderSend(Symbol(),OP_BUY,LotSize,Ask,UseSlippage,BuyStopLoss[Count], BuyTakeProfit[Count],"Buy Order "+OrdInt,MagicNumber,0,Green);
}

Переменная Count начинается с 0 и соответствует первому элементу массива. Количество циклов (т.е. количество размещаемых ордеров) определяется MaxOrders — 1. Для каждой итерации цикла мы увеличиваем стоп-лосс и тейк-профит на единицу.

Мы используем переменную OrdInt для увеличения количества ордеров в комментарии к ордеру. Первым комментарием к ордеру будет «Ордер на покупку 1», следующим будет «Ордер на покупку 2» и так далее. Функция OrderSend() размещает ордер с соответствующим стоп-лоссом и значением тейк-профита, используя переменную Count для выбора соответствующего элемента массива.

Это всего лишь один из способов обработки нескольких ордеров. Основным недостатком этого подхода является то, что мы можем рассчитывать только стоп-лосс и тейк-профит цены для ограниченного числа ордеров. Кроме того, мы можем масштабировать значения тейк-профита и стоп-лосса на указанную сумму и размещать потенциально неограниченное количество ордеров:

extern int StopLossStart = 20; 
extern int StopLossIncr = 20;
extern int TakeProfitStart = 40; 
extern int TakeProfitIncr = 40;
extern int MaxOrders = 5;

В приведенном выше примере стоп-лосс для нашего первого ордера составит 20 пунктов. Мы будем увеличивать стоп-лосс на 20 пунктов за каждый дополнительный ордер. То же самое для тейк-профита, за исключением того, что мы начнем с 40 и увеличиваем на 40. Вместо использования массивов мы рассчитаем стоп-лосс и тейк-профит в цикле for:

for(int Count = 0; Count <= MaxOrders - 1; Count++) {
int OrdInt = Count + 1;
 
int UseStopLoss =  StopLossStart + (StopLossIncr * Count);
 int UseTakeProfit =  TakeProfitStart + (TakeProfitIncr * Count);
double BuyStopLoss = CalcBuyStopLoss(Symbol(),UseStopLoss,Ask); double BuyTakeProfit = CalcBuyTakeProfit(Symbol(),UseTakeProfit,Ask);
OrderSend(Symbol(),OP_BUY,LotSize,Ask,UseSlippage,BuyStopLoss, BuyTakeProfit,"Buy Order "+OrdInt,MagicNumber,0,Green);
}

Мы определяем уровень тейк-профита и стоп-лосса в пунктах, умножая переменную StopLossIncr или TakeProfitIncr на значение Count и добавляя его к значению StopLossStart или TakeProfitStart. Для первого ордера уровень стоп-лосса или тейк-профита будет равен StopLossStart или TakeProfitStart.

Далее мы рассчитываем стоп-лосс и цену тейк-профита для ордера. Наконец, мы размещаем ордер с помощью OrderSend().

Цикл продолжится до открытия количества ордеров, указанных в MaxOrders. Этот метод позволяет нам указать столько ордеров, сколько мы хотим, используя переменную MaxOrders, гарантируя, что каждый размещаемый нами ордер будет иметь стоп-лосс и тейк-профит.

Глобальные переменные

В этой книге мы упоминаем переменные с глобальной областью действия как «глобальные переменные». MetaTrader имеет набор функций для установки переменных на уровне терминала, что означает, что эти переменные доступны каждому работающему в данный момент советнику, при условии, что мы знаем имя переменной.

Текущий список глобальных переменных в терминале можно просмотреть, выбрав «Глобальные переменные» в меню «Инструменты» или нажав клавишу F3 на клавиатуре.

Одним из способов использования этих переменных является сохранение определенных глобальных или статических переменных в терминале, чтобы, если советник был закрыт, мы могли продолжить работу с того места, где остановились. Не все советники требуют этого, но более сложные советники будут поддерживать определенное состояние, которое, в случае прерывания, приведет к отказу в работе советника.

Лучший способ предотвратить это — избегать создания советников, требующих такого уровня сложности. Но если этого нельзя избежать, то использование функций глобальных переменных для сохранения текущего состояния в терминале может быть полезным в случае отключения.

Чтобы объявить глобальную переменную, используйте функцию GlobalVariableSet(). Первый аргумент — это строка, обозначающая имя глобальной переменной, а второй аргумент — это значение типа double, присваиваемое ей.

GlobalVariableSet(GlobalVariableName,DoubleValue);

Чтобы ваши имена переменных были уникальными, вы можете создать префикс глобальной переменной. Объявите глобальную переменную в своем эксперте и установите значение в функции init(), используя текущий символ, точку, имя советника и магический номер, чтобы создать уникальный префикс переменной.

  // Глобальные переменные
    string GlobalVariablePrefix;
int init() {
GlobalVariablePrefix = Symbol()+Period()+"_"+"ProfitBuster"+"_"+MagicNumber+"_"; }

Мы используем текущий символ и период, а также идентификатор советника и внешнюю переменную MagicNumber. Теперь, когда мы устанавливаем глобальную переменную с помощью GlobalVariableSet(), мы используем префикс, который мы определили выше вместе с фактическим именем переменной:

GlobalVariableSet(GlobalVariablePrefix+Counter,Counter);

Поэтому, если мы торгуем на EURUSD на таймфрейме M15 с советником с именем «ProfitBuster», используя 11 в качестве магического числа и Counter в качестве имени переменной, именем нашей глобальной переменной будет EURUSD15_ProfitBuster_11_Counter.

Чтобы получить значение глобальной переменной, используйте функцию GlobalVariableGet() с именем переменной в качестве аргумента:

Counter = GlobalVariableGet(GlobalVariablePrefix+Counter);

Чтобы удалить глобальную переменную, используйте функцию GlobalVariableDel() с именем переменной в качестве аргумента. Чтобы удалить все глобальные переменные, размещенные вашим советником, используйте функцию GlobalVariableDeleteAll() с префиксом в качестве аргумента.

    GlobalVariableDel(GlobalVariablePrefix+Counter);
    GlobalVariableDeleteAll(GlobalVariablePrefix);

Проверка прибыли ордера

Иногда может быть полезно проверить текущую прибыль по ордеру или проверить общую прибыль по ордеру, который уже закрыт. Есть два способа проверить прибыль. Чтобы получить прибыль в валюте депозита, используйте функцию OrderProfit(). Сначала вы должны выбрать ордер, используя OrderSelect().

OrderSelect(Ticket,SELECT_BY_TICKET); 
double GetProfit = OrderProfit(Ticket);

Результат функции OrderProfit() должен совпадать с общей прибылью или убытком, который указан в истории для выбранного ордеров.

Чтобы получить прибыль или убыток в пунктах, вам необходимо рассчитать разницу между ценой открытия ордера и ценой закрытия ордера. Вам также нужно будет использовать функцию OrderSelect() для получения цен открытия и закрытия.

OrderSelect(Ticket,SELECT_BY_TICKET);
if(OrderType() == OP_BUY) double GetProfit = OrderClosePrice() - OrderOpenPrice(); else if(OrderType() == OP_SELL) GetProfit = OrderOpenPrice() - OrderClosePrice();
GetProfit /= PipPoint(Symbol());

Для заявок на покупку мы рассчитываем прибыль, вычитая цену открытия из цены закрытия. Для ордеров на продажу мы делаем наоборот. После того, как мы вычислили разницу, мы можем преобразовать прибыль или убыток в целое число, разделив его на пункт, используя нашу функцию PipPoint().

Например, если цена открытия ордера на покупку составляет 1,4650, а цена закрытия — 1,4700, разница между OrderClosePrice() и OrderOpenPrice() составляет 0,0050. Когда мы делим это на нашу функцию PipPoint(), результат равен 50. Таким образом, для этого ордера мы получаем 50 пунктов прибыли. Если бы цена закрытия ордера была 1.4600, то мы бы потеряли 50 пунктов.

Мартингейл

Мартингейл — это система ставок, обычно используемая в рулетке и блэкджеке, где размер ставки удваивается после каждого последовательного проигрыша. Теория состоит в том, что одна выигрышная ставка вернет наш баланс к безубыточности. Недостатком мартингейла является то, что вам потребуется большой размер капитала, чтобы выдержать просадки.

Например, если размер вашего начального лота составляет 0,1 лота, то после 4 последовательных потерь ваш размер лота составит 1,6 лота — в 16 раз больше исходного размера лота. После 7 последовательных проигрышей размер вашего лота составит 12,8 лота — в 128 раз больше исходного размера лота! Долгая полоса неудач может легко уничтожить ваш аккаунт, прежде чем вы сможете вернуть его в безубыточность.

Тем не менее, вы можете включить систему увеличения размера лота при последовательных выигрышах или проигрышах, и это можно сделать, не уничтожая свой счет. Самый простой способ — установить ограничение на количество раз, чтобы увеличить размер лота. Надежная торговая система не должна иметь более 3 или 4 максимальных последовательных убытков. Вы можете определить это, изучив максимальное количество последовательных потерь на вкладке «Отчет» в окне «Тестер стратегий».

Другой способ — увеличить размер вашего лота на меньший множитель. Классическая стратегия Мартингейла удваивает размер лота после каждого последующего проигрыша. Возможно, вы захотите использовать множитель меньше 2. Существует также стратегия анти-мартингейл, где вы увеличиваете размер лота после каждого последовательного выигрыша.

Стратегия Мартингейла работает лучше всего, когда вы размещаете один ордер за раз, поэтому мы будем предполагать, что каждая позиция состоит из одной сделки.

Пользователь сможет выбирать между стратегией Мартингейл (проигрыш) или Анти-Мартингейл (выигрыш). Будет включена настройка ограничения максимального количества последовательных увеличений лота, и множитель лота будет настраиваться.

Сначала посчитаем количество последовательных побед или проигрышей. Нам нужно будет перебрать пул истории ордеров в обратном порядке, начиная с самого последнего закрытого ордера. Мы будем увеличивать счетчик для каждого выигрыша или проигрыша. Пока сохраняется схема последовательных выигрышей или проигрышей, мы продолжим цикл. Как только паттерн нарушается (выигрыш определяется после одного или нескольких проигрышей или наоборот), цикл завершается.

int WinCount;
int LossCount;
for(int Count = OrdersHistoryTotal()-1; ; Count--) {
OrderSelect(Count,SELECT_BY_POS,MODE_HISTORY);
if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber) {
if(OrderProfit() > 0 && LossCount == 0) WinCount++;
else if(OrderProfit() < 0 && WinCount == 0) LossCount++; else break;
} 
}

Мы начнем с объявления переменных для счетчиков выигрышей и проигрышей. Обратите внимание, что в операторе for мы используем OrdersHistoryTotal() для определения начальной позиции. OrdersHistoryTotal() возвращает количество ордеров в пуле истории. Вычитаем 1, чтобы определить позицию индекса для самого последнего ордера, который хранится в переменной Count.

Обратите внимание, что мы пропустили второе выражение в цикле for — то, которое определяет условие прекращения цикла. Мы будем уменьшать переменную Count на каждой итерации цикла.

Мы используем MODE_HISTORY в качестве третьего аргумента в функции OrderSelect(), чтобы указать, что мы перебираем закрытый пул истории ордеров. По умолчанию OrderSelect() использует пул открытых ордеров, поэтому мы должны указать MODE_HISTORY при проверке пула закрытых ордеров.

Мы проверяем, что текущий выбранный ордер соответствует нашему символу графика и нашему магическому номеру. Затем мы проверяем прибыль по ордеру с помощью функции OrderProfit(). Если возвращаемое значение указывает на прибыль (то есть больше нуля), то мы увеличиваем переменную WinCount. Если это потеря, мы увеличиваем LossCount.

Поскольку мы ищем последовательные выигрыши или проигрыши, нам нужно завершить цикл, как только будет найдено альтернативное условие. Для этого мы проверяем переменную WinCount или LossCount при проверке прибыли ордера.

Например, если у нас есть 2 последовательных убытка — это означает, что LossCount = 2 — и наш следующий ордер будет выигрышным, тогда оба наших оператора if будут ложными, и управление перейдет к оператору break, который завершит цикл.

Преимущество этого метода в том, что он не подведет, если советник случайно отключится. Советник продолжит свою работу прямо там, где остановился. Конечно, это означает, что при первом запуске советника он будет использовать любую предыдущую серию выигрышей / проигрышей при определении размера лота. Но, как вы можете видеть, преимущества перевешивают недостатки.

Переменная WinCount или LossCount будет содержать количество последовательных выигрышей или проигрышей. Если мы хотим реализовать стратегию Мартингейла, мы используем LossCount, чтобы определить фактор, с помощью которого можно увеличить размер лота. Если мы делаем анти-Мартингейл, вместо этого мы используем WinCount.

Мы будем использовать внешнюю целочисленную переменную с именем MartingaleType. Если MartingaleType установлен в 0, мы будем использовать стратегию Martingale. Если установлено значение 1, мы будем использовать стратегию анти-мартингейла.

// Внешние переменные
extern int MartingaleType = 0; // 0: Martingale, 1: Anti-Martingale
extern int LotMultiplier = 2; 
extern int MaxMartingale = 4; 
extern double BaseLotSize = 0.1;
 
// Расчет размера лота
if(MartingaleType == 0) int ConsecutiveCount = LossCount; else if(MartingaleType = 1) ConsecutiveCount = WinCount;
if(ConsecutiveCount > MaxMartingale) ConsecutiveCount = MaxMartingale; double LotSize = BaseLotSize * MathPow(LotMultiplier,ConsecutiveCount);

Мы устанавливаем значение ConsecutiveCount либо в WinCount, либо в LossCount, в зависимости от настройки MartingaleType. Мы сравним это с нашей настройкой MaxMartingale. Если количество наших последовательных ордеров больше, чем MaxMartingale, мы изменим его размер, чтобы он был равен MaxMartingale. (Вы также можете изменить его размер до размера лота по умолчанию, если хотите). Размер лота будет оставаться на этом размере до тех пор, пока выигрыш или проигрыш не нарушат нашу последовательную серию ордеров.

Размер лота определяется умножением нашего BaseLotSize на LotMultiplier, который экспоненциально увеличивается на ConsecutiveCount. Функция MathPow() увеличивает число до указанной степени. Например, если размер нашего начального лота равен 0,1, множитель лота равен 2, и у нас есть четыре последовательных ордера, уравнение равно 0,1 * 24 = 1,6.

Настраивая LotMultiplier и используя стратегии как Мартингейл, так и Анти-Мартингейл, это даст вам достаточно возможностей для экспериментов с использованием экспоненциального размера лотов. Вы можете легко изменить код выше, чтобы использовать другие варианты. Например, вы можете масштабировать размеры лотов в обратном порядке, от самого большого до самого маленького. Или вы можете использовать внешний счетчик вместо ConsecutiveCount.

Отладка работы вашего советника

В отличие от большинства программных средств разработки, MetaEditor не поддерживает современные методы отладки. Поэтому вам придется использовать операторы Print() и журнал для отладки ваших экспертов.

Распечатав содержимое переменных и функций в журнал, вы можете проверить вывод вашего кода и исправить любые ошибки.

Вы можете использовать Тестер стратегий, чтобы запустить симуляцию торговли и проверить вывод логов. Журнал тестера стратегий отображается на вкладке «Журнал» в окне «Тестер стратегий». Существует ограничение на количество информации, которая указана на вкладке Журнал, поэтому вы можете захотеть просмотреть реальный журнал.

Журналы тестера стратегий хранятся в папке testerlogs. Щелкните правой кнопкой мыши в любом месте окна журнала и выберите «Открыть» во всплывающем меню. Откроется окно проводника Windows, отображающее содержимое папки журнала. Имена файлов имеют формат yyyymmdd.log, где yyyy — год из четырех цифр, mm — месяц из двух цифр, а dd — дата из двух цифр. Вы можете просматривать журналы в блокноте или любом текстовом редакторе.

Давайте проиллюстрируем пример того, как вы можете использовать журнал, чтобы найти проблему. В приведенном ниже коде есть ошибка, и она не работает так, как мы ожидали. Чтобы иметь возможность диагностировать проблему, нам нужно проверить ввод или вывод функции. Давайте создадим оператор Print() и напечатаем содержимое всех соответствующих переменных в журнале.

Мы запустим советника в тестере стратегий, используя цены открытия в качестве модели тестирования. Убедитесь, что вы тестируете советника в течение достаточно длительного периода времени, чтобы он разместил достаточно сделок для анализа. Если вам нужно проверить цены на графике, нажмите кнопку «Открыть график», чтобы открыть график, показывающий симулированные сделки.

Далее мы перейдем на вкладку «Журнал» и проверим, какая информация нам нужна. Если нам нужно просмотреть весь журнал или если на вкладке «Журнал» не отображаются сделки, мы можем щелкнуть правой кнопкой мыши и выбрать «Открыть» во всплывающем меню, а затем напрямую открыть файл журнала.

Этот код дает нам ошибку 130: «недействительные стопы» каждый раз, когда мы размещаем заказ на покупку. Мы знаем, что ошибка 130 означает, что либо стоп-лосс, либо тейк-профит неверны.

if(Close[0] > MA && BuyTicket == 0) {
double OpenPrice = Ask;
double BuyStopLoss = OpenPrice + (StopLoss * UsePoint); double BuyTakeProfit = OpenPrice + (TakeProfit * UsePoint);
BuyTicket = OrderSend(Symbol(),OP_BUY,LotSize,OpenPrice,UseSlippage, BuyStopLoss,BuyTakeProfit,"Buy Order",MagicNumber,0,Green);
SellTicket = 0; }

Мы будем использовать функцию Print() для проверки параметров, передаваемых в функцию OrderSend(). Мы сосредоточимся на цене открытия ордера, стоп-лоссе и тейк-профите.

Print("Price:"+OpenPrice+" Stop:"+BuyStopLoss+" Profit:"+BuyTakeProfit);

Вот вывод, когда мы запускаем советник в тестере стратегий. Стоп-лосс и тейк-профит в размере 50 пунктов:

11:52:12 2009.11.02 02:00 Example EURUSD,H1: OrderSend error 130
11:52:12 2009.11.02 02:00 Example EURUSD,H1: Price:1.47340000 Stop:1.47840000
Profit:1.47840000

Мы знаем, что стоп-лосс должен быть ниже цены открытия ордера на покупку. Здесь это значение выше цены. На самом деле это та же цена, что и тейк-профит. Беглый взгляд на наш код, и мы понимаем, что случайно вставили знак плюс в уравнение покупки стоп-лосса. Вот правильный код:

double BuyStopLoss = OpenPrice - (StopLoss * UsePoint);

Если вы получаете сообщение об ошибке при попытке разместить, закрыть или изменить ордер, сфокусируйте свои усилия на проблеме, указанной в сообщении об ошибке. Вот несколько наиболее распространенных сообщений об ошибках, вызванных ошибками программирования:

Ошибка 129: недействительная цена — недействительная цена открытия. Для рыночных ордеров убедитесь, что текущая цена Bid или Ask передается в соответствии с типом ордера. Для отложенных ордеров убедитесь, что цена выше или ниже текущей цены, как того требует тип ордера. Также убедитесь, что цена отложенного ордера не слишком близка к текущей цене.

Ошибка 130: недопустимые стопы — неверный стоп-лосс или тейк-профит. Убедитесь, что цены стоп-лосс и тейк-профит расположены выше или ниже текущей цены, в зависимости от того, является ли тип ордера покупкой или продажей. Также убедитесь, что цена стоп-лосса или тейк-профита не слишком близка к текущей цене.

Ошибка 131: неверный объем сделки — неверный размер лота. Убедитесь, что размер лота не превышает минимального или максимального значения брокера и что размер лота нормализован до правильного значения шага (0,1 или 0,01 для большинства брокеров).

Описания всех сообщений об ошибках можно найти в справочнике по MQL в разделе Стандартные константы — коды ошибок.

Устранение ошибок в случае прерывания торговли

В то время как наиболее серьезные ошибки могут быть обнаружены просто путем бэк-тестирования, другие будут появляться только во время торговли в реальном времени. Ошибки могут привести к тому, что сделки будут размещены неправильно, и эти ошибки могут потребовать определенных усилий для их обнаружения.

Мы добавим дополнительную функцию для регистрации информации о сделках и статусе в режиме реального времени, чтобы у нас была запись об этом при устранении неполадок. Мы будем использовать операторы Print(), как и ранее, но мы будем регистрировать значения индикаторов цены — любую информацию, которая будет полезна при отладке.

// Внешние переменные 
extern bool Debug = true;
 
if(Debug == true) Print(StringConcatenate("Bid:",Bid," Ask:",Ask," MA:",MA," BuyTicket:",BuyTicket," SellTicket:",SellTicket));

Приведенный выше код будет регистрировать информацию о цене и индикаторе, а также содержимое переменных BuyTicket и SellTicket. Если есть какие-либо вопросы о том, как была открыта сделка или почему она не была открыта, журнал в этот конкретный момент покажет состояние всех соответствующих условий сделки. Вы можете включать и выключать вход в систему с помощью внешней переменной Debug.

Оператор отладки Print() должен быть помещен в конце функции start() после всех торговых функций. Если вы используете таймер, поместите оператор debug Print() в блок таймера, чтобы он работал только при необходимости. В противном случае строка отладки будет выводиться в журнал при каждом тике, что может привести к разрастанию файла журнала.

Обработка ошибок компиляции

Когда вы компилируете свой советник, компилятор проверит синтаксис вашего кода и убедится, что все пользовательские функции и переменные были правильно объявлены. Если вы что-то пропустили, компилятор остановится, и любые ошибки компиляции появятся на вкладке Errors в окне Toolbox.

Когда сталкиваешься с длинным списком ошибок компиляции, всегда начинайте с первой. Дважды щелкните ошибку в списке, и редактор перейдет непосредственно к строке с ошибкой. Исправьте ошибку и перекомпилируйте. Иногда простая синтаксическая ошибка приводит к нескольким несвязанным ошибкам.

Вот список распространенных ошибок компиляции и их решения:

Variable not defined — вы забыли объявить переменную с типом данных. Если это глобальная или внешняя переменная, объявите ее в верхней части файла. Если это локальная переменная, найдите первое вхождение и поместите перед ним объявление типа данных. В противном случае проверьте правильность написания или регистр (верхний / нижний) имени переменной.

Variable already defined — вы объявили одну и ту же переменную дважды. Удалите объявление типа данных из всех дублированных объявлений переменных.

Function is not defined — если рассматриваемая функция находится в файле включения или библиотеки, убедитесь, что директива #include или #import находится в верхней части файла и является правильной. В противном случае проверьте правильность написания или регистр имени функции и убедитесь, что оно существует либо в текущем файле, либо в соответствующих файлах включений или библиотек.

Illegal assignment used — обычно это относится к знаку равенства (=). Помните, что один знак равенства предназначен для присваивания переменной, а два знака равенства (==) — оператор сравнения. Исправьте оператор присваивания в соответствующем операторе сравнения.

Assignment expected — обычно это относится к оператору сравнения «равно» (==). Вы использовали два знака равенства вместо одного в присваивании переменной. Исправьте оператор в один знак равенства.

Unbalanced right parenthesis — обычно это происходит в операторе if при использовании вложенных скобок. Перейдите к строке, обозначенной первой ошибкой, и вставьте левую скобку в соответствующее место.

Unbalanced left parenthesis — это сложная задача. Ошибка обычно указывает на конец строки программы. По сути, вы где-то забыли правильную скобку. Дважды проверьте код, который вы недавно редактировали, и найдите пропущенную правую скобку. Возможно, вам придется закомментировать строки кода, чтобы найти проблему.

Wrong parameters count — у вас слишком мало или слишком много аргументов в функции. Дважды проверьте синтаксис функции в MQL Reference и исправьте аргументы.

Semicolon expected — вы, вероятно, забыли поставить точку с запятой в конце строки. Поставьте точку с запятой в конце предыдущей строки. Обратите внимание, что пропущенная точка с запятой может вызвать любую из перечисленных выше ошибок, поэтому обязательно ставьте точки с запятой.

Понравилась статья? Поделить с друзьями:

Не пропустите эти материалы по теме:

  • Яндекс еда ошибка привязки карты
  • Ошибки при компиляции gcc
  • Ошибки при изучении немецкого языка
  • Ошибки при компилировании
  • Ошибки при изучении китайского языка

  • 0 0 голоса
    Рейтинг статьи
    Подписаться
    Уведомить о
    guest

    0 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии