Русский

Учебник по MQL4  Простые программы на MQL4  Простой эксперт

Простой эксперт


В этом параграфе рассматриваются принципы построения простого торгующего эксперта.

Задача 29. Создать торгующий эксперт.

Предварительные рассуждения


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

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

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

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

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

Структура простого эксперта


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



Рис. 109. Структурная схема простого эксперта.


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

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

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

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

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

После того, как необходимые ордера закрыты, управление передаётся в блок вычисления размера новых ордеров. Существует множество алгоритмов для вычисления объема ордера. Самый простой из них - постоянный, фиксированный лот. Этот алгоритм удобно включать в программу для тестирования стратегии. Более распространённый способ определения размера ордера состоит в том, что количество лотов ставится в зависимость от суммы свободных средств, например, может составлять 30-40%. Если средств на счёте недостаточно, то программа заканчивает работу, предварительно уведомив пользователя о причине.

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

В эксперте предусмотрен также анализ ошибок. Если какая-либо торговая операция закончилась неудачей, то (только в этом случае) управление передаётся в блок обработки ошибок. Если ошибка, возвращённая сервером или клиентским терминалом, не является непреодолимой, то производится повторная попытка осуществить торговую операцию. Если же возвращена критическая ошибка (например, заблокирован счёт), то эксперт должен прекратить работу. Здесь необходимо напомнить, что в MQL4 не предусмотрено возможности программного завершения работы экспертов в окне финансового инструмента (в отличие от скриптов, см. Специальные функции). Всё, что программно можно сделать в эксперте - это завершить исполнение специальной функции start(). При новом запуске функции start() на новом тике можно проанализировать значение некоторой переменной - флага, запрещающего торговлю (в данном случае поднятого в результате появления критической ошибки) и передать управление на завершение исполнение специальной функции, не допуская, таким образом, формирование новых торговых приказов. В представленной схеме значение флага анализируется в блоке предварительной обработки.

Торговая стратегия


Рыночные цены находятся в постоянном движении. Состояние рынка в любой момент может быть условно охарактеризовано либо как тренд - сильное однонаправленное изменение (повышение или понижение) цены, либо как флэт - боковое движение цены со слабыми отклонениями от некоторой средней. Эти характеристики рынка являются условными, поскольку не существует чётких критериев, в соответствии с которыми тренд или флэт можно было бы идентифицировать. Например, бывают продолжительные боковые движения цен с сильными отклонениями, которые нельзя отнести ни к флэту ни к тренду. В целом принято считать, что в основном рынок находится в состоянии бокового движения, а тренды на рынке занимают около 15-20% времени.


Рис. 110. Флэт и тренд на рынке.

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

Торговые критерии


В данном примере мы попробуем построить трендовый эксперт, т.е. такой, который будет открывать ордера в сторону движения цены. Для этого среди множества показателей различных технических индикаторов необходимо найти такие, которые свидетельствуют о начале тренда. Один из наиболее простых методов поиска торговых критериев основан на анализе сочетания средних МА с различным периодом усреднения. На Рис. 111 и Рис. 112 показано расположение двух различных МА (с периодами усреднения 11 и 31) на различных участках рынка. Средние с небольшим периодом усреднения (красного цвета) располагаются ближе ценовому графику, они более извилисты и подвижны. В то же время средние с большим периодом усреднения (синего цвета) более инертны, имеют большее запаздывание и располагаются дальше от рыночных цен. Обратим внимание на те места, в которых МА с разным периодом усреднения пересекаются, и попробуем решить: можно ли факт пересечения МА использовать в качестве торгового критерия.



Рис. 111. Пересечения МА(11) и МА(31) при изменении направления движения цены.

На Рис. 111 показан участок рынка, на котором открытие ордеров в сторону движения цены по факту пересечения МА оправдано. В точке А красная линия пересекает синюю линию снизу вверх, и вслед за этим событием рыночная цена некоторое время продолжает расти. Последующее обратное пересечение МА свидетельствует об изменении направления движения цены. Если в точке А открыть ордер Buy, а в точке В закрыть его, то в результате будет получена прибыль, пропорциональная разнице цен А и В.



Рис. 112. Пересечения МА(11) и МА(31) при изменении направления движения цены.

В то же время на рынке попадаются и другие участки, на которых МА также пересекаются, однако это не приводит к существенному последующему повышению или снижению цены (Рис. 112). Ордера, открытые по факту пересечения МА на таких участках оказываются убыточными. Если в точке А открыть Sell, а в точке В его закрыть, то результатом такой торговой операции окажется убыток. То же можно сказать и об ордере Buy, открытом в точке В закрытом в точке С.

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

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


Рис. 113. Сильное движение цены может привести к развитию тенденции.

На Рис. 113 показан участок рынка, на котором сильное движение цены привело к продолжению изменения цены в том же направлении. В качестве показателя "сильного движения" можно использовать разницу значений МА с различным периодом усреднения. Чем сильнее движение, тем большее отставание МА с большим периодом усреднения от МА с малым периодом усреднения. Показательно, что даже сильные скачкообразные изменения цен с последующим возвратом не приводят к большой разнице значений между различными МА, т.е. не возникает много нежелательных ложных сигналов. Например, скачкообразное изменение цены на 50 пунктов, сопровождаемое последующим откатом (в центре Рис. 113), привело к увеличению разницы между МА всего на 20 пунктов. В то же время, действительно сильное движение (которое обычно не сопровождается значительной коррекцией) в точке А повлекло за собой увеличение разницы между МА до 25 - 30 пунктов.

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

Количество ордеров


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

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

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

Отношение торговых критериев


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


Рис. 114. Соотношение критериев открытия и закрытия ордеров (а и b - правильные критерии, с - неправильные).

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

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

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

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

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

Размер открываемых ордеров


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

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

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

Подробности программирования


Простой трендовый эксперт tradingexpert.mq4, построенный на основе предыдущих рассуждений, может выглядеть так:

//--------------------------------------------------------------------
// tradingexpert.mq4
// Предназначен для использования в качестве примера в учебнике MQL4.
//--------------------------------------------------------------------
#property copyright "Copyright © Book, 2007"
#property link "http://AutoGraf.dp.ua"
//--------------------------------------------------------------- 1 --
// Численные значения для М15
extern double StopLoss =200; // SL для открываемого ордера
extern double TakeProfit =39; // ТР для открываемого ордера
extern int Period_MA_1=11; // Период МА 1
extern int Period_MA_2=31; // Период МА 2
extern double Rastvor =28.0; // Расстояние между МА
extern double Lots =0.1; // Жестко заданное колич. лотов
extern double Prots =0.07; // Процент свободных средств

bool Work=true; // Эксперт будет работать.
string Symb; // Название финанс. инструмента
//--------------------------------------------------------------- 2 --
int start()
{
int
Total, // Количество ордеров в окне
Tip=-1, // Тип выбран. ордера (B=0,S=1)
Ticket; // Номер ордера
double
MA_1_t, // Значен. МА_1 текущее
MA_2_t, // Значен. МА_2 текущее
Lot, // Колич. лотов в выбран.ордере
Lts, // Колич. лотов в открыв.ордере
Min_Lot, // Минимальное количество лотов
Step, // Шаг изменения размера лота
Free, // Текущие свободные средства
One_Lot, // Стоимость одного лота
Price, // Цена выбранного ордера
SL, // SL выбранного ордера
TP; // TP выбранного ордера
bool
Ans =false, // Ответ сервера после закрытия
Cls_B=false, // Критерий для закрытия Buy
Cls_S=false, // Критерий для закрытия Sell
Opn_B=false, // Критерий для открытия Buy
Opn_S=false; // Критерий для открытия Sell
//--------------------------------------------------------------- 3 --
// Предварит.обработка
if(Bars < Period_MA_2) // Недостаточно баров
{
Alert("Недостаточно баров в окне. Эксперт не работает.");
return; // Выход из start()
}
if(Work==false) // Критическая ошибка
{
Alert("Критическая ошибка. Эксперт не работает.");
return; // Выход из start()
}
//--------------------------------------------------------------- 4 --
// Учёт ордеров
Symb=Symbol(); // Название фин.инстр.
Total=0; // Количество ордеров
for(int i=1; i<=OrdersTotal(); i++) // Цикл перебора ордер
{
if (OrderSelect(i-1,SELECT_BY_POS)==true) // Если есть следующий
{ // Анализ ордеров:
if (OrderSymbol()!=Symb)continue; // Не наш фин. инструм
if (OrderType()>1) // Попался отложенный
{
Alert("Обнаружен отложенный ордер. Эксперт не работает.");
return; // Выход из start()
}
Total++; // Счётчик рыночн. орд
if (Total>1) // Не более одного орд
{
Alert("Несколько рыночных ордеров. Эксперт не работает.");
return; // Выход из start()
}
Ticket=OrderTicket(); // Номер выбранн. орд.
Tip =OrderType(); // Тип выбранного орд.
Price =OrderOpenPrice(); // Цена выбранн. орд.
SL =OrderStopLoss(); // SL выбранного орд.
TP =OrderTakeProfit(); // TP выбранного орд.
Lot =OrderLots(); // Количество лотов
}
}
//--------------------------------------------------------------- 5 --
// Торговые критерии
MA_1_t=iMA(NULL,0,Period_MA_1,0,MODE_LWMA,PRICE_TYPICAL,0); // МА_1
MA_2_t=iMA(NULL,0,Period_MA_2,0,MODE_LWMA,PRICE_TYPICAL,0); // МА_2

if (MA_1_t > MA_2_t + Rastvor*Point) // Если разница между
{ // ..МА 1 и 2 большая
Opn_B=true; // Критерий откр. Buy
Cls_S=true; // Критерий закр. Sell
}
if (MA_1_t < MA_2_t - Rastvor*Point) // Если разница между
{ // ..МА 1 и 2 большая
Opn_S=true; // Критерий откр. Sell
Cls_B=true; // Критерий закр. Buy
}
//--------------------------------------------------------------- 6 --
// Закрытие ордеров
while(true) // Цикл закрытия орд.
{
if (Tip==0 && Cls_B==true) // Открыт ордер Buy..
{ //и есть критерий закр
Alert("Попытка закрыть Buy ",Ticket,". Ожидание ответа..");
RefreshRates(); // Обновление данных
Ans=OrderClose(Ticket,Lot,Bid,2); // Закрытие Buy
if (Ans==true) // Получилось :)
{
Alert ("Закрыт ордер Buy ",Ticket);
break; // Выход из цикла закр
}
if (Fun_Error(GetLastError())==1) // Обработка ошибок
continue; // Повторная попытка
return; // Выход из start()
}

if (Tip==1 && Cls_S==true) // Открыт ордер Sell..
{ // и есть критерий закр
Alert("Попытка закрыть Sell ",Ticket,". Ожидание ответа..");
RefreshRates(); // Обновление данных
Ans=OrderClose(Ticket,Lot,Ask,2); // Закрытие Sell
if (Ans==true) // Получилось :)
{
Alert ("Закрыт ордер Sell ",Ticket);
break; // Выход из цикла закр
}
if (Fun_Error(GetLastError())==1) // Обработка ошибок
continue; // Повторная попытка
return; // Выход из start()
}
break; // Выход из while
}
//--------------------------------------------------------------- 7 --
// Стоимость ордеров
RefreshRates(); // Обновление данных
Min_Lot=MarketInfo(Symb,MODE_MINLOT); // Миним. колич. лотов
Free =AccountFreeMargin(); // Свободн средства
One_Lot=MarketInfo(Symb,MODE_MARGINREQUIRED);// Стоимость 1 лота
Step =MarketInfo(Symb,MODE_LOTSTEP); // Шаг изменен размера

if (Lots > 0) // Если заданы лоты,то
Lts =Lots; // с ними и работаем
else // % свободных средств
Lts=MathFloor(Free*Prots/One_Lot/Step)*Step;// Для открытия

if(Lts < Min_Lot) Lts=Min_Lot; // Не меньше минимальн
if (Lts*One_Lot > Free) // Лот дороже свободн.
{
Alert(" Не хватает денег на ", Lts," лотов");
return; // Выход из start()
}
//--------------------------------------------------------------- 8 --
// Открытие ордеров
while(true) // Цикл закрытия орд.
{
if (Total==0 && Opn_B==true) // Открытых орд. нет +
{ // критерий откр. Buy
RefreshRates(); // Обновление данных
SL=Bid - New_Stop(StopLoss)*Point; // Вычисление SL откр.
TP=Bid + New_Stop(TakeProfit)*Point; // Вычисление TP откр.
Alert("Попытка открыть Buy. Ожидание ответа..");
Ticket=OrderSend(Symb,OP_BUY,Lts,Ask,2,SL,TP);//Открытие Buy
if (Ticket > 0) // Получилось :)
{
Alert ("Открыт ордер Buy ",Ticket);
return; // Выход из start()
}
if (Fun_Error(GetLastError())==1) // Обработка ошибок
continue; // Повторная попытка
return; // Выход из start()
}
if (Total==0 && Opn_S==true) // Открытых орд. нет +
{ // критерий откр. Sell
RefreshRates(); // Обновление данных
SL=Ask + New_Stop(StopLoss)*Point; // Вычисление SL откр.
TP=Ask - New_Stop(TakeProfit)*Point; // Вычисление TP откр.
Alert("Попытка открыть Sell. Ожидание ответа..");
Ticket=OrderSend(Symb,OP_SELL,Lts,Bid,2,SL,TP);//Открытие Sel
if (Ticket > 0) // Получилось :)
{
Alert ("Открыт ордер Sell ",Ticket);
return; // Выход из start()
}
if (Fun_Error(GetLastError())==1) // Обработка ошибок
continue; // Повторная попытка
return; // Выход из start()
}
break; // Выход из while
}
//--------------------------------------------------------------- 9 --
return; // Выход из start()
}
//-------------------------------------------------------------- 10 --
int Fun_Error(int Error) // Ф-ия обработ ошибок
{
switch(Error)
{ // Преодолимые ошибки
case 4: Alert("Торговый сервер занят. Пробуем ещё раз..");
Sleep(3000); // Простое решение
return(1); // Выход из функции
case 135:Alert("Цена изменилась. Пробуем ещё раз..");
RefreshRates(); // Обновим данные
return(1); // Выход из функции
case 136:Alert("Нет цен. Ждём новый тик..");
while(RefreshRates()==false) // До нового тика
Sleep(1); // Задержка в цикле
return(1); // Выход из функции
case 137:Alert("Брокер занят. Пробуем ещё раз..");
Sleep(3000); // Простое решение
return(1); // Выход из функции
case 146:Alert("Подсистема торговли занята. Пробуем ещё..");
Sleep(500); // Простое решение
return(1); // Выход из функции
// Критические ошибки
case 2: Alert("Общая ошибка.");
return(0); // Выход из функции
case 5: Alert("Старая версия терминала.");
Work=false; // Больше не работать
return(0); // Выход из функции
case 64: Alert("Счет заблокирован.");
Work=false; // Больше не работать
return(0); // Выход из функции
case 133:Alert("Торговля запрещена.");
return(0); // Выход из функции
case 134:Alert("Недостаточно денег для совершения операции.");
return(0); // Выход из функции
default: Alert("Возникла ошибка ",Error); // Другие варианты
return(0); // Выход из функции
}
}
//-------------------------------------------------------------- 11 --
int New_Stop(int Parametr) // Проверка стоп-прик.
{
int Min_Dist=MarketInfo(Symb,MODE_STOPLEVEL);// Миним. дистанция
if (Parametr < Min_Dist) // Если меньше допуст.
{
Parametr=Min_Dist; // Установим допуст.
Alert("Увеличена дистанция стоп-приказа.");
}
return(Parametr); // Возврат значения
}
//-------------------------------------------------------------- 12 --

Описание переменных


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

В блоке 1-2 описаны внешние и глобальные переменные.

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

Блок предварительной обработки


В данном примере предварительная обработка ситуации состоит из двух частей (блок 3-4). Программа заканчивает работу в случае, если в окне финансового инструмента мало баров; в этом случае не представляется возможным правильно определить (в блоке 5-6) значения скользящих средних, необходимых для вычисления торговых критериев. Кроме того, здесь анализируется значение глобальной переменной Work. При нормальной работе эксперта значение этой переменной всегда равно true (устанавливается один раз при инициализации). Если же при выполнении программы возникла критическая (непреодолимая) ошибка, то эта переменная получает значение false, в результате чего специальная функция start() заканчивает работу. В дальнейшем это значение никогда не изменяется, поэтому и последующий код программы не исполняется. В этом случае необходимо остановить исполнение программы и установить причину возникновения критической ошибки (при необходимости связаться с дилинговым центром). После того, как ситуация будет разрешена, можно снова запустить программу в работу, присоединив эксперт к окну финансового инструмента.

Учёт ордеров


Рассматриваемый эксперт предполагает возможность работы только с одним рыночным ордером. Задачей блока учёта ордеров (блок 4-5) является определение характеристик открытого ордера, если такой ордер есть. В цикле перебора ордеров for опрашиваются все имеющиеся рыночные и отложенные ордера, а именно от первого (int i=1) до последнего (i<=OrdersTotal()). На каждой итерации этого цикла с помощью функции OrderSelect() выбирается очередной ордер. Выбор производится из источника открытых и отложенных ордеров (SELECT_BY_POS).

   if (OrderSelect(i-1,SELECT_BY_POS)==true) // Если есть следующий

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

      if (OrderSymbol()!=Symb)continue;      // Не наш фин. инструм

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

Если функция OrderType() возвращает значение больше 1 (см. Типы торговых операций), то выбранный ордер - отложенный. Но в данном эксперте не предусмотрено управление отложенными ордерами. Это значит, что необходимо закончить исполнение специальной функции start(), т.к. сложилась конфликтная ситуация. В этом случае после сообщения об окончании работы исполнение функции start() заканчивается с помощью оператора return.

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

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

Вычисление торговых критериев


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

В начале блока вычисляются значения средних с периодами усреднения Period_MA_1 и Period_MA_2. Факт значимости какого-либо торгового критерия выражается через значение соответствующей переменной. Переменные Opn_B и Opn_S означают срабатывание критерия для открытия ордеров Buy и Sell, а переменные Cls_В и Cls_S - для закрытия. Например, если критерий для открытия ордера Buy не сработал, то значение переменной Opn_B остаётся равным false (установленным при инициализации переменной), а если критерий сработал, то переменная Opn_B получает значение true. В данном случае критерий закрытия ордера Sell совпадает с критерием открытия ордера Buy и критерий открытия Sell совпадает с критерием закрытия Buy.

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

Закрытие ордеров


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

Блок закрытия построен на основе бесконечного цикла while, тело которого состоит из двух аналогичных частей: первая часть предназначена для закрытия ордера Buy, а вторая - для закрытия Sell. Цикл while используется здесь для того, чтобы в случае неудачного завершения торговой операции её можно было повторить.

В заголовке первого оператора if вычисляется условие для закрытия ордера Buy ( закрытие рыночных ордеров Sell происходит по аналогичному алгоритму). Если тип ранее открытого ордера соответствует типу Buy (см. Типы торговых операций) и признак закрытия ордера Buy является значимым, то управление передаётся в тело оператора if, где формируется торговый приказ на закрытие. В качестве цены закрытия ордера в функции OrderClose() указывается значение двухсторонней котировки, соответствующее типу ордера (см. Требования и ограничения торговых операций). Если торговая операция выполнена успешно, то после сообщения о закрытии ордера текущая итерация цикла while прерывается, и, таким образом, исполнение блока закрытия ордеров заканчивается. Если же операция закрытия закончилась неудачей, то вызывается для исполнения пользовательская функция обработки ошибок Fun_Error() (блок 10-11).

Обработка ошибок


В качестве передаваемого параметра в функции Fun_Error() используется код последней ошибки, вычисленной с помощью GetLastError(). В зависимости от кода ошибки функция Fun_Error() возвращает 1 в случае, если ошибка преодолимая и торговую операцию можно повторить, и 0, если возникла критическая ошибка. Критические ошибки в свою очередь тоже делятся на два вида - те, после которых ещё можно продолжать исполнение программы (например, общая ошибка) и те, при появлении которых необходимо прекратить исполнение любых торговых операций (например, счёт заблокирован).

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

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

Вычисление количества лотов для новых ордеров


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

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

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

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

Открытие ордеров


Блок открытия ордеров (блок 8-9) также, как и блок закрытия, представляет бесконечный цикл while. В заголовке первого оператора if вычисляются условия для открытия ордера Buy: если по финансовому инструменту нет ордеров (переменная Total равна 0) и признак для открытия ордера Buy является значимым ( Opn_B равно true ), то управление передаётся в тело оператора if для открытия ордера. В этом случае после обновления данных вычисляются заявленные цены для стоп-приказов.

Значения стоп-приказов изначально задаются пользователем во внешних переменных StopLoss и TakeProfit. В общем случае пользователь может установить значение для этих параметров меньшее, чем позволяет брокер. Кроме того, брокер в любой момент по своему усмотрению может изменить минимально допустимую дистанцию (это нередко случается при сильных движениях рынка, например, перед выходом важных новостей). Поэтому перед каждым открытием ордера значения стоп-приказов должны быть вычислены с учётом значений, установленных пользователем, а также минимально допустимого значения, установленного брокером.

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

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

Некоторые особенности кода


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

Например, в соответствии с принятой стратегией, эксперт ориентирован на работу только с одним ордером. Это позволило использовать переменную Ticket как для идентификации номера закрываемого ордера (в блоке закрытия 6-7), так и для идентификации успешности исполнения торговой операции при открытии ордера (в блоке открытия 8-9). В данном случае такое решение вполне приемлемо. Вместе с тем, если рассматриваемый код взять за основу для реализации другой стратегии (например, позволять встречные ордера), то придётся дополнительно ввести ещё одну или несколько переменных, чтобы иметь возможность различать номера открытых ордеров и идентифицировать успешность торговых операций.

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

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