Содержание
Доброго времени суток. На прошлом уроке мы познакомились с циклом for — последовательным перебором вводных параметров, без которого программирование на языке MQL просто невозможно представить. Настало время пройтись по другим вариантам операторов цикла.
Оператор цикла while
Основное предназначение цикла while — повторять одинаковые или похожие вычисления чаще, чем один раз. В отличие от цикла for, у которого можно указывать три выражения тут используется только одно. Цикл будет работать, пока заданное выражение не станет ложным.
0 1 2 |
while(выражение) { оператор; } |
При объявлении оператора пишется слово while (пока что), в скобках указывается выражение, условие и далее в фигурных скобках происходит действие, которое должно повторяться пока выражение истинно.
0 1 2 3 4 |
bool check = true; while(check) { Print("Проверка"); check = false; } |
Пример выполнения цикла: мы объявили логическую переменную check, который присвоили истинное значение. Далее запускаем цикл, в котором проверяется, чтобы данная переменная была true (как мы помним из этого урока, для того, чтобы указать, что bool переменная является истинной не обязательно приписывать == true, достаточно просто написать ее наименование). В блоке операторов был выведен принт и значение check изменено на false. Это означает, что цикл while имеет всего одну итерацию, потому что при следующей проверки выражение уже будет ложью.
Если в теле цикла используется всего один оператор, то можно обойтись без фигурных скобок:
0 1 |
bool check = true; while(check) check = false; |
Попробуем математический пример:
0 1 2 3 4 |
int i = 0; while(i < 5) { Print("i: ",i); i++; } |
Это выражение содержит условие, при котором цикл может продолжать пока i меньше пяти. Начальное значение переменной i задается до самого цикла, а шаг при подсчете регулируется внутри тела цикла. Этот алгоритм работает аналогично в представленном цикле for:
0 1 2 |
for(int i=0;i<5;i++) { Print("i: ",i); } |
Стоит заметить, что в выражении можно использовать сразу несколько условий и он будет продолжаться, пока одно из условий не станет ложью.
0 1 2 3 4 5 |
int a = 4, b = 10; while(a < 12 && b > 0) { Print("a: ",a,", b: ",b); a += 2; b -= 2; } |
Цикл while можно выполнять бесконечно, если указать условие, которое никогда не сможет быть выполнено:
0 1 2 3 |
int a2 = 1; while(a2 > 0) { a++; } |
В примере выше a2 всегда будет больше нуля, соответственно у данного цикла нет причин останавливаться. Проблема с зацикливанием частенько случается из-за ошибки написания кода, это может стать серьезной проблемой, цикл будет крутиться бесконечно. Такую ошибку можно найти только вручную. Чтобы избежать в этом случае зависание терминала MetaTrader 4, необходимо в выражение дописать функцию !IsStopped() — проверку на принудительное завершение работы программы.
0 1 2 3 |
int a2 = 1; while(a2 > 0 && !IsStopped()) { a++; } |
Примером использования зацикливания можно считать стандартный скрипт терминала «PeriodConverter«, суть которого создавать нестандартные таймфреймы и обновлять котировки каждый тик. Как известно, скрипт совершает свое действие только один раз, после чего удаляется с графика. С помощью зацикливания скрипт будет находиться на графика до момента удаления программы или закрытия терминала. Нам не совсем понравилась такая логика работы и было решено разработать свой алгоритм для работы с пользовательскими графиками, поэтому был создан индикатор Chart Period Converter.
Цикл while полезно использовать, когда вы не знаете, сколько итераций должно совершить тело цикла и можете завершить работу цикла при соблюдении какого либо условия. Я использую его для перебора валютных пар в окне обзора рынка, для работы с открытыми графиками, когда нужно пробежаться по каждому. Также при загрузке данных из интернета невозможно заранее знать, сколько строк информации нужно анализировать. При модификации или открытии ордера можно делать необходимое повторение, пока тело цикла не выдаст истину.
Оператор цикла do while
Цикл do while выполняет абсолютно то же действие, что цикл while с единственной разницей — проверка условия, заданного в выражении осуществляется после итерации тела цикла. То есть вначале свою работу выполняет оператор, потом проверяется условие:
0 1 2 3 4 5 6 |
int c = 1; do { c += 2; Print("c: ",c); } while(c < 7); |
Тут оператор do вначале прибавляет к c число два, потом выводит принт. Далее тело цикла завершает работу и идет проверка, чтобы целая переменная c была меньше семи. В нашем случае будет три итерации, где переменная будет равна 3, 5 и 7. После ложного результата проверки этот цикл остановится.
Цикл do while выполняет минимум одну итерацию, в отличие от цикла while.
0 1 2 3 4 5 |
int d = 1; do { Print("d: ",d); } while(d < 0); |
Я не так часто использую данную конструкции, редко есть нужда. Как пример, для проверки, что график подгрузился, можно узнать текущую цену Бид, и, если котировки еще не загружены, то терминал вернет значение 0.
0 1 2 3 4 5 6 7 8 9 |
double bid = 0; do { Sleep(500); RefreshRates(); ChartRedraw(); bid = MarketInfo(Symbol(),MODE_BID); Print("bid: ",bid); } while(bid <= 0); |
В теле цикла do мы переводим код в сон на 0.5 секунд, далее обновляем данные в предопределенных переменных и массивах-таймсериях, а также обновляем график и задаем переменной цену Бид. Уже после проходит проверка, и если цена все еще равна нулю, то цикл запускается по новой и так до момента, когда цена не определится.
Давайте попробуем выполнить задачу посложнее. К примеру нам нужно узнать на текущем графике номер свечи и ее цену закрытия, по условию, чтобы расстояние между [1] свечей и искомой было больше 30 старых пунктов. После этого выдать в принт номер свечи, следующей за найденной свечей.
0 1 2 3 4 5 6 7 8 9 |
int k = 2; double candle = 0; do { candle = Close[k]; Print("k: ",k,", Close Price: ",DoubleToString(candle,Digits)); k++; } while(MathAbs(Close[1]-candle) < 300*Point); Print("Номер следующей свечи: ",k, ", Close Price: ",DoubleToString(Close[k],Digits)); |
В цикле тела свечи мы последовательно узнаем цену закрытия свечи и к номеру k прибавляем единицу после каждой итерации. В выражении while идет проверка по модулю на разницу расстояния между [1] и проверяемой свечей. Чтобы задать 30 старых пунктов на торговом счете с 3 или 5 знаками, нужно добавить ноль к числу, а также перевести числовое значение в размер пункта символа в валюте котировки Point.
Надеюсь пример не оказался слишком сумбурным. Он приведен только для практики работы с оператором, смысловой нагрузки особо не несет.
Оператор-переключатель switch
Данный оператор является альтернативной заменой условному оператору if, но представлен немного в другом виде. В круглых скобках оператора switch задается выражение, в фигурных же скобках ищется константа case, которая бы соответствовала ему. Константа выражения при этом не может быть текстом, либо вызывать функцию, она должна быть целым числом.
0 1 2 3 4 5 |
int x = 2; switch(x){ case 1: Print("Равно одному"); break; case 2: Print("Равно двум"); break; case 3: Print("Равно трем"); break; } |
В примере выше мы объявили переменную целого типа x, равную трем. Предположим, мы не знаем ее точного значения, как в примере. Для определения записываем переменную в круглые скобки оператора switch. В фигурных скобках мы перечисляем три возможных варианта значения этой переменной: 1, 2 или 3. Перечисление это указывается с помощью слова case (в случае), после константы должно обязательно стоять двоеточие, а уже после идти необходимое действие. Проверка по телу оператора идет сверху вниз, слева направо. Соответственно первым проверялся случай, если бы переменная была равна единице. Так как программный код знает, что это не так, то проверка переходит на второй случай. Наша переменная равна константе 2, соответственно в журнал выдается принт. Чтобы остановить дальнейшую проверку, которая нам больше не нужна, в каждом случае case рекомендуется указывать оператор break после выполнения нужного действия. Если же его не задать, то после нахождения нужного значения в работу будут включены все нижеследующие случаи case, что в большинстве случаев лучше избегать.
Попробуем другой пример. Тут все ясно без комментариев:
0 1 2 3 4 5 6 |
int x1 = 3; switch(x1){ case 0: Print("Foreign exchange"); break; case 1: Print("Meta Quotes Language"); break; case 3: Print("DaVinci FX Group"); break; case 5: Print("Meta Trader 4"); break; } |
Если же пользователь не уверен, что какой-либо из случаев совпадет, то вводится дополнительный вариант default (по умолчанию). Он может располагаться в любом месте тела оператора, но большинство программистов используют его в самом конце. Ниже представлен вариант, где оба варианта case не подходят под определение.
0 1 2 3 4 5 |
int x2 = 100; switch(x1){ case 0: Alert("Принято значение 0"); break; case 50: Alert("Принято значение 50"); break; default: Alert("Принято значение по умолчанию"); } |
Вариантов case может быть сколько угодно, это зависит только от вашего алгоритма. Очень часто оператор switch примеряется, когда текстом нужно указать период текущего графика, потому что внутренней структурой программы это не заложено:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 |
string s1 = ""; switch(Period()){ case PERIOD_M1: s1 = "M1"; break; case PERIOD_M5: s1 = "M5"; break; case PERIOD_M15: s1 = "M15"; break; case PERIOD_M30: s1 = "M30"; break; case PERIOD_H1: s1 = "H1"; break; case PERIOD_H4: s1 = "H4"; break; case PERIOD_D1: s1 = "D1"; break; case PERIOD_W1: s1 = "W1"; break; case PERIOD_MN1: s1 = "MN1"; break; default: s1 = "Unknown TF"; } Alert("Период графика: ",s1); |
В качестве выражения была применена функция определения периода текущего графика. Период имеет тип данных int, поэтому его можно использовать в операторе switch. Далее проходим по всем возможным периодам, сохраняем нужное значение и прерываем работу оператора. Уже после него выводим Алерт о значении таймфрейма.
Следующий пример у нас не будет корректно работать, потому что мы еще не начали изучать работу с ордерами, но логика должна быть понятна. Задается выражение в виде торговой функции, цель которой определить тип заданного ордера. Как мы знаем, существует 6 типов ордеров: Buy в числовом формате равняется 0, Sell Stop равняется пяти и т.д. В этом примере мы пробегаем по разным типам, пока не найдем нужный и после этого сохраняем его текстовое наименование в переменную:
0 1 2 3 4 5 6 7 8 9 |
string iOrderType = ""; switch(OrderType()) { case 0: iOrderType = "Buy"; break; case 1: iOrderType = "Sell"; break; case 2: iOrderType = "Buy Limit"; break; case 3: iOrderType = "Sell Limit"; break; case 4: iOrderType = "Buy Stop"; break; case 5: iOrderType = "Sell Stop"; break; } Print("Тип ордера: ",iOrderType); |
Приведем последний пример на сегодня. Как известно, при выполнении программного кода у новичков очень часто встречаются ошибки. Некоторые из них критические, но другие могут быть мелкие и сразу о них узнать бывает сложно. Чтобы быть уверенным, что ошибок не было допущено, рекомендуется в конце каждой рабочей функции выполнять проверку на ошибки и выдавать принт, если они обнаружены.
Ошибку можно опознать, вызвав функцию проверки состояния GetLastError(). В ней нет параметров, имеет тип int, так как все ошибки хранятся именно в целых значениях. После вызова функции она обнуляет свое значение, поэтому ее нужно записывать в заранее созданную переменную.
0 1 |
int err = GetLastError(); Print("Ошибка номер: ",err); |
При разных ошибках нужны разные действия и сообщения в журнал, поэтому для этого вполне подойдет оператор switch:
0 1 2 3 4 5 6 |
int err = GetLastError(); switch(err){ case 0: Print("Нет ошибки"); break; case ERR_INVALID_STOPS: Print("Неверный СЛ или ТП"); break; case ERR_REQUOTE: Print("Нужно обновить данные массива таймсессии"); break; case ERR_MARKET_CLOSED: Print("Рынок закрыт"); break; } |
В нашем случае сработает самый первый случай, где константа равна нулю, потому что 0 означает, что ошибки в коде нет. В других же случаях будут свои принты. Очень полезно после открытия или модификации ордера делать проверку на ошибку, и если она имеется — применять необходимые действия по ее ликвидации. Тему ошибок и методов их решения я планирую сделать отдельным постом. Возможно рассмотрю самые известные и крупные, потому что это слишком громоздкая тема и в большинстве случаев каждая отдельная ошибка зависит от кода в целом.
На этом все. Надеюсь эта статья была для вас полезной.
[download url=»http://www.davinci-fx.com/wp-content/uploads/2021/01/1.7-while-do-while-switch.rar» title=»Скачать примеры урока»]