Ваши недокументированные возможности при написании советников на MQL4

В журнале публикуется множество обзоров программ-советников и их результативности: одни более удачны, другие сразу расцениваются как убыточные, что тоже само по себе результат. Но результативность советника, при его тщательном рассмотрении, это комплексный итог:
1) применяемой торговой стратегии (алгоритм выработки сигналов открытия-закрытия);
2) применяемой техники управления капиталом (алгоритм управления размером лотов);
3) точности соответствия программы, записанной на MQL, описаниям двух первых пунктов (наличие логических ошибок) и отсутствие в ней непредусмотренных побочных эффектов.

По двум первым позициям написано много, а сейчас я хотел бы остановиться на некоторых тонких вопросах использование языка MQL, которые не нашли никакого отображения и упоминания в описаниях языка (например, в изложенной здесь: _http://docs.mql4.com/ru/ документации).

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

Передача параметров в функцию по ссылке (по адресу)

Эта вещь прекрасно известна программистам на любом из универсальных языков программирования, например, на C, а ещё более на C++. Напишем самый простейший советник (файл t2.mq4 — здесь и далее имена тестовых примеров в прилагаемом архиве), и поместим его на график любой валютной пары:

int start() {
static int nTick = 0;
Inc( nTick );
Alert( “новый тик № ” + nTick );
}
//————————————–
void Inc( int n ) { n++; }

Результатом выполнения будет непрерывное повторение одного и того же результата: «новый тик №0»… Переданное в функцию значение n изменяется там, но это не производит никакого влияния на внешнее окружение функции (вызвавшую функцию start()) — в функцию передаётся копия переменной nTick, так обычно работает вызов функций в MQL4. Добавим один символ (&) в описание функции Inc() :

void Inc( int& n ) { n++; }

Выполнение советника радикально изменится:
новый тик № 1
новый тик № 2
новый тик № 3

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

Выводы: Какие последствия несёт в себе этот маленький курьёз? Очень далекоидущие. Функции в MQL весьма ограничены, они могут возвращать только одно значение простейшего типа:  int, string, datetime, … За счёт побочного эффекта, функция со многими параметрами может внести изменения во все из них, фактически производя возврат (после своего выполнения) более одного результата (или это можно расценивать как возврат структурированных данных). Например, такая функция:

double Func( int& n, string& s, datetime& t ) { … }

Передача в функцию параметром массива по ссылке

Следующий пример (файл t3.mq4) близок к предыдущему, но здесь побочные эффекты и  сопутствующие возможности существенно больше:

extern int N = 7;
int A[];
//——————————————————————
int init() {
ArrayResize( A, N );
Alert( “размер массива ” + ArraySize( A ) );
for( int i = 0; i < N; i++ ) A[ i ] = i + 1;

}
//——————————————————————
int start() {

static nBars = 0;     // чтобы не частить, теперь на барах, а не на тиках
if( nBars != Bars ) { // чтобы не ждать, запускайте на M1
nBars = Bars;
IncArray( A );
ShowArray( A );

}
}
//——————————————————————
void IncArray( int& Array[] ) {

int n = ArraySize( Array );
for( int i = 0; i < n; i++ ) Array[ i ]++;
}
void ShowArray( int Array[] ) {
int n = ArraySize( Array );
string Msg = “”;
for( int i = 0; i < n; i++ ) {
Msg = Msg + Array[ i ];
if( i < n – 1 ) Msg = Msg + ” , “;

}
Alert( Msg );
}

Перенесите советник на график (лучше тайм-фрейма M1) и вы будете иметь удовольствие наблюдать вывод подобных сообщений:
2, 3, 4, 5, 6, 7, 8
3, 4, 5, 6, 7, 8, 9
4, 5, 6, 7, 8, 9, 10

Выводы: Теперь изменениям подвергается отдельно каждый элемент переданного массива.

Примечание: Любопытно, как изменится поведение программы, если массив в функцию IncArray() будет передаваться не по ссылке? Будет ли создаваться копия массива для работы с ним внутри функции (подобно предыдущему примеру для единичного значения)? Достигаем этого, переписав заголовок этой функции вот так (всего-то):

void IncArray( int Array[] ) {

Результат неожиданный даже для видавших виды программистов на C или C++! На этапе компиляции, не доходя до выполнения, мы получим сообщение об ошибке вида:

‘Array’ – array item cannot be assigned     C:\Program Files\MetaTrader – Alpari\experts\t3.mq4 (22, 33)

Это значит, что контроль на L-value (доступность на присваивание) для элементов массива делается ещё компилятором! А для единичных (скалярных) параметров — это не так.

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

Следующий трюк может повергнуть в изумление даже матёрого программиста на классических языках программирования. Сама по себе возможность в любое время изменить размер массива — это уже сама по себе значительная особенность языка MQL4:

int A[ 5 ];
for( i = 0; i < ArraySize( A ); i++ ) A[ i ] = 333;
ShowArray( A );
ArrayResize( A, 7 );
for( i = 0; i < ArraySize( A ); i++ ) A[ i ] = 333;
ShowArray( A );

После выполнения чего получим, как ни странно: 333, 333, 333, 333, 333, 0, 0

Получается, что массивы не переразмещаются полностью, а «дополняются» до нужной размерности — все ранее присвоенные значения элементов сохраняются! Но без такого трюкачества было бы, пожалуй, невозможно разработчикам MQL4 реализовать представление тайм-серии в MT4.

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

void IncArray( int& Array[] ) {
int n = ArraySize( Array );
ArrayResize( A, n + 1 );
for( int i = 0; i <= n; i++ ) Array[ i ] = i + 1;
}

Результат выполнения (скопировано с журнала терминала MetaTrader 4 (https://mr-trader.com/forex-terminals/torgovyj-terminal-metatrader-4-skachat), который разворачивает результаты снизу-вверх):


2011.02.10 19:54:00 @t3 EURUSD,M1: alert: 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 , 13 , 14
2011.02.10 19:53:02 @t3 EURUSD,M1: alert: 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 , 13
2011.02.10 19:52:08 @t3 EURUSD,M1: alert: 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12

Примечание: Ещё раз вернёмся к вопросу, рассмотренному выше: как вообще (копией или оригиналом) передаётся в функцию массив в MQL4? Для этого ещё раз перепишем:

void IncArray( int Array[] ) {
int n = ArraySize( Array );
ArrayResize( A, n + 1 );
}

И получим достаточно неожиданный результат:

2011.03.07 20:17:02        t4 EURUSD,M1: Alert: 1 , 2 , 3 , 4 , 5 , 6 , 7 , 0 , 0 , 0
2011.03.07 20:16:01        t4 EURUSD,M1: Alert: 1 , 2 , 3 , 4 , 5 , 6 , 7 , 0 , 0
2011.03.07 20:15:44        t4 EURUSD,M1: Alert: 1 , 2 , 3 , 4 , 5 , 6 , 7 , 0

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

Комментарии ( 3 )

  1. Не понятное. Если Вы программист, так сказать – заядлый?! Зачем нарушаете и учите этому маневру других? Где-то там в коде есть функция, где внутри создана другая функция – это(если я не ошибаюсь), ошибка и очень плохая. Вот Ваш код нарушения прописных истин:
    void IncArray( int& Array[] ) {
    int n = ArraySize( Array );
    for( int i = 0; i

  2. Я учусь программированию. И вот, мне сильно помогло написанное в понимании написания кода.
    Сейчас как-раз пытаюсь научиться формировать массивы и заполнять их значения с помощью циклов, правда цикл while у меня на компе почему-то(написанный лично мной) – виснет, и я так думаю, значит я что-то делаю не так?! И руки пока не доходят до разбора этого цикла по порядку его понимания. Это все еще впереди?! И вот теперь статические переменные немного не понятны?! То есть, если она инициализирована, то по сути ее значение должно быть константой и Ask, и Bid, как константы разнятся с моим пониманием – констант?! Тут нужен аргумент, который поставит все на свои места. А пока – это все находится в хаотичном состоянии. Так бывает. Всему свое время и место.

Добавить комментарий

Кнопка возврата к началу