Три аспекта ручного автотрейдинга. Часть 1 - Торговля

Sergey Kravchuk | 12 апреля, 2013


Введение

За долгие годы разработки для платформы MetaTrader 4 я перепробовал массу вариантов и подходов к созданию, если так можно выразиться, "автоматизированного рабочего места трейдера". Самый первый и самый очевидный вариант был реализован в наборе торговых скриптов Mouse Only Trader. Опыт в принципе оказался удачным, и, добавив в него расчеты по управлению рисками и функции управления капиталом, я получил достаточно функциональный инструмент Trading Mouse.

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

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

Варианты интерфейсов

Рис. 1. Варианты интерфейсов.

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

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

 

Лучше один раз увидеть

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

Для осознанного восприятия ролика пока достаточно будет понимать, что в терминале работает эксперт, которым управляют с помощью сброса в окно терминала управляющих скриптов. Сам по себе эксперт не торгует, он только и умеет, что подтягивать стоп-уровни (Stop Loss и Take Profit) в трейлинге, но делать это начинает так же по команде трейдера. Названия скриптов очевидно соответствуют выполняемым действиям, а всплывающие подсказки поясняют их использование.

Ну вот, теперь имея общее представление о предмете статьи, давайте ее пристально изучим.

 

Базовые принципы ручного автотрейдера

Прежде всего хочу заметить, что я достаточно нетерпелив, чтобы торговать на днях и тем более на недельных или месячных графиках. Позиция, открытая дольше одного дня, у меня живет только в случае абсолютно стабильного и очевидно сильного тренда. Рабочий период у меня 5 и 15 минут. На минутках все еще слишком много шума, а на часах я уже начинаю терять терпение. Многие тут же вспомнят о двух МАшках, аллигаторе, экранах Элдера и т.п. - как можно обходиться без "старших" периодов?! Ведь по ним мы проверяем текущий краткосрочный тренд и ищем точки входа в рынок! Я их не использую в явном виде. Вместо них у меня отлично работает этакая помесь зигзага и крестиков-ноликов, которые я строю из тех же текущих 5 и 15 минут. Но речь не о них, а о работе внутридневого трейдера. Это практически классический скальпинг. Здесь нужно очень внимательно следить за текущей ценой и мгновенно реагировать на рыночные импульсы!

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

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

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

 

Ноу-хау известное всем

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

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

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

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

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

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

 

Плюсы и фишки скриптоглобального механизма команд

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

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

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

Но хватит теории - перейдем к практической реализации этой идеи.

 

Параметры управляющих команд

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

 

Исходный код управляющих скриптов

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

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

#property link      "http://forextools.com.ua"
#include <stderror.mqh>
#include <WinUser32.mqh>
 
#import "user32.dll"
 int RegisterWindowMessageA(string lpstring);
 int PostMessageA(int hWnd,int Msg,int wParam,int lParam);
#import
 
void start()
{
  if ( !IsExpertEnabled() )   
  { MessageBox("Experts disabled. Action canceled.","SimpleTrader ERROR", MB_ICONERROR); return; }
  if ( !IsTradeAllowed() )    
  { MessageBox("Trade disabled. Action canceled.","SimpleTrader ERROR", MB_ICONERROR); return; }
  if ( IsTradeContextBusy() ) 
  { MessageBox("Trade context is bysy. Action canceled.","SimpleTrader ERROR", MB_ICONERROR); return; }
 
  GlobalVariableDel ( "#Trader-Chart" );
  GlobalVariableDel ( "#Trader-Command" );
  GlobalVariableDel ( "#Trader-Price" );
  GlobalVariableDel ( "#Trader-Time" );
 
  // чтобы несколько экспов отрабатывали только сбросы в СВОИ окна
  GlobalVariableSet ( "#Trader-Chart", WindowHandle( Symbol(), Period()) ); 
  GlobalVariableSet ( "#Trader-Command", COMMAND_ID );
  GlobalVariableSet ( "#Trader-Price", NormalizeDouble ( WindowPriceOnDropped(), Digits ) );
  GlobalVariableSet ( "#Trader-Time", TimeCurrent() );
 
  // эмуляция входящего тика для запуска эксперта
  int hwnd = WindowHandle(Symbol(), Period());
  int MT4InternalMsg = RegisterWindowMessageA("MetaTrader4_Internal_Message");
  PostMessageA(hwnd, MT4InternalMsg, 2, 1);  
}

Сам код скрипта состоит из нескольких строк:

#property copyright "Open buy or sell according to the droped price of stoploss level"
#include  "#Trader-commands.mqh"
int COMMAND_ID = COMMAND_OPEN;
#include  "#Trader-common.mqh"  

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

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

На этом работа с управляющими скриптами заканчивается и начинается работа эксперта.

 

Логика работы эксперта ручного автотрейдера

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

Получил команду –> Проверил и рассчитал параметры –> Выполнил команду –> Отобразил результат

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

#property copyright "Copyright © 2006-2013, Sergey Kravchuk. http://forextools.com.ua"
#property link      "http://forextools.com.ua"

#include <stdlib.mqh>
#include "scripts\#Trader-commands.mqh"

extern int    MaxAllowedRisk = 2// максимально допустимый процент потерь 
extern int    OpenSlippage   = 2// на каком расстоянии от текущей цены выставлять ордер для открытия
extern int    CloseSlippage  = 10; // максимально допустимое проскальзывание при закрытие ордера
extern int    ExecDelaySec   = 10; // максимально допустимая задержка на НАЧАЛО выполнения команды

extern int    ShowClosedCnt  = 5// количество закрытых рыночных ордеров для показа истории

extern int    TralStep       = 5// шаг изменения цены при трале
extern color  TralStartColor = BlueViolet;
extern color  Tral0LossStartColor = LimeGreen;
extern int    Tral0LossGap   = 10; // отступ от цены открытия на безубыток при трале

// цвета стрелок открытия и закрытия ордеров
extern color  MarkBuyColor    = Blue;
extern color  MarkSellColor   = Red;
extern color  MarkModifyColor = Magenta;
extern color  MarkCloseColor  = Gray;

extern int    MessageShowSec  = 30; // сколько секунд держать на экране последнее сообщение

extern bool   ShowComment     = true;
extern bool   WriteGadgetFile = true;

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

// достанем "динамические" параметры
Spread      = MarketInfo ( Symbol(), MODE_SPREAD );
StopLevel   = MarketInfo ( Symbol(), MODE_STOPLEVEL );

// освежим цены (в тестере вместо Ask и Bid используется Close[0])
if ( IsTesting() ) { CurBid = Close[0]; CurAsk = Close[0]+Spread*Point; } 
else { CurAsk = Ask; CurBid = Bid; }

// Выберем ПЕРВЫЙ ПОПАВШИЙСЯ открытый ордер (для возможных модификаций) включая и лимитники 
// чтобы можно было на них тейк например поставить
SelectedOrder = 0; for ( i = 0; i < OrdersTotal() && SelectedOrder <= 0; i++ )
{
  OrderSelect ( i, SELECT_BY_POS, MODE_TRADES );
  if ( OrderSymbol() == Symbol() && OrderMagicNumber() == MAGIC ) SelectedOrder = OrderTicket();
}

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

// цена начала трала живет только вместе с открытым ордером!!!
if ( SelectedOrder <= 0 ) { ZeroLossTral = false; HalfProfitTral = false; PrevPrice = 0; TralStart = 0; } 
// тралим (проверки и исполнение - внутри TrailingStop)
else TrailingStop ( TralStep );

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

// на сброс в другое окно только обновляем инфо.
if ( GlobalVariableGet ( "#Trader-Chart" ) != WindowHandle( Symbol(), Period()) ) { ShowInfo(); return; }
// это оказался сброс в свое окно - отработка команд
// получим из глобальных переменных код и цену команды, и сразу же удалим их
if ( GlobalVariableCheck( "#Trader-Command" ) == false ) { CmdID = 0; CmdPrice = 0; ShowInfo(); return; } 
// команда таки есть - отработает ее
CmdTime  = GlobalVariableGet ( "#Trader-Time" );
CmdID    = GlobalVariableGet ( "#Trader-Command" );
CmdPrice = NormalizeDouble ( GlobalVariableGet ( "#Trader-Price" ), PriceDigits );
GlobalVariableDel ( "#Trader-Command" );
GlobalVariableDel ( "#Trader-Price" );
GlobalVariableDel ( "#Trader-Time" );
// если команда была выставлена раньше чем допустимое время задержки на ее выполнение - ничего не делаем!
if ( CmdTime+ExecDelaySec*60 < TimeCurrent() )
{
  SetError("Igore command from " + TimeToStr(CmdTime,TIME_DATE+TIME_SECONDS) 
         + " delayed > " + ExecDelaySec + "sec");
  ShowInfo();
  return;
}

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

Важные замечания:

  1. Торговли без стопов не бывает.

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

  2. Открываемся только лимитными ордерами.

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

 

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

Проведя анализ текущей ситуации и выбрав направление торговли, решите где должен располагаться стоп открываемого ордера и сбросьте скрипт #OPEN в эту точку установки стоплосса. Ее расположение также используется для определения направления торговли. Стоп в ордере на покупку всегда должен располагаться ниже цены открытия, и если вы сбросом скрипта указали цену для стопа ниже текущей цены - значит вы собираетесь покупать. Аналогично стоп выше текущей цены будет означать открытие ордера на продажу. Единственное место где эта техника не сработает - стоп внутри спреда. Но, как правило, вам там стоп и не дадут разместить правила торговли в ДЦ, по которым нельзя ставить стоп ближе, чем указано в параметре StopLevel.

Не смотря на обилие текста описывающего правила открытия, его код составляет всего три строчки:

// достанем "динамические" параметры
Spread      = MarketInfo ( Symbol(), MODE_SPREAD );
StopLevel   = MarketInfo ( Symbol(), MODE_STOPLEVEL );
// освежим цены (в тестере вместо Ask и Bid используется Close[0])
if ( IsTesting() ) { CurBid = Close[0]; CurAsk = Close[0]+Spread*Point; } 
else { CurAsk = Ask; CurBid = Bid; }
…
// определим направление торговли
if ( CurBid <= CmdPrice && CmdPrice <= CurAsk ) 
{ SetError("Stop inside spread. Undefined trade direction."); ShowInfo(); return; }
if ( CmdPrice < CurBid ) 
{ 
  Operation = OP_BUYSTOP;  
  PriceForOrder = CurAsk+(StopLevel+OpenSlippage)*Point; 
  MarkOpenColor = MarkBuyColor; 
}
if ( CmdPrice > CurAsk ) 
{ 
  Operation = OP_SELLSTOP; 
  PriceForOrder = CurBid-(StopLevel+OpenSlippage)*Point; 
  MarkOpenColor = MarkSellColor; 
}

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

// если это открытие в противоположном направлении - сначала закрываем текущий ордер (если он есть)
if ( SelectedOrder > 0 )
{
  if ( ( Operation == OP_BUYSTOP  && ( OrderType() == OP_BUY  || OrderType() == OP_BUYSTOP  ) ) ||
       ( Operation == OP_SELLSTOP && ( OrderType() == OP_SELL || OrderType() == OP_SELLSTOP ) ) )
  {
    SetError("Only one order per symbol is allowed"); 
    ShowInfo();
    return;
  }
  // ордера разнонаправленные - закрываем предыдущий
  {
    if ( OrderType() == OP_BUY || OrderType() == OP_SELL )
    {
      // обновимся текущими ценами. в тестере вместо Ask и Bid используется Close[0]
      if ( IsTesting() ) { CurBid = Close[0]; CurAsk = Close[0]+Spread*Point; } 
      else { CurAsk = Ask; CurBid = Bid; }
      if ( OrderType() == OP_BUY ) PriceForOrder = CurBid; else PriceForOrder = CurAsk;
      OK = OrderClose ( OrderTicket(), OrderLots(), PriceForOrder, CloseSlippage, MarkCloseColor );
    }
    else 
    {
      OK = OrderDelete ( OrderTicket(), MarkCloseColor );      
      // прочищаем линии и стрелки удаленных лимитников (чтобы не мусорить экран)
      for(i = ObjectsTotal()-1; i >= 0 ; i--) 
      if ( StringFind(ObjectName(i),"#"+SelectedOrder) == 0 ) ObjectDelete(ObjectName(i)); 
    }
    if ( OK == false) { SetLastError("Close on reverse"); return; } // удалить не удалось - дальше ничего не делаем 
  } 
}

 

Money management и допустимый процент риска

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

// рассчитаем нужный лот под заданные потери на указанном стопе
Loss = AccountBalance() * MaxAllowedRisk / 100.0; // размер допустимых потерь в проценте от баланса
PriceInPoint = MathAbs ( PriceForOrder - CmdPrice ) / Point;
SL1lot = PriceInPoint * TickValue;   // размер стопа в деньгах для операции одним лотом.
Lot = Loss / SL1lot;         // допустимый риск / SL1lot
Lot = NormalizeDouble ( MathFloor ( Lot / LotStep ) * LotStep, LotDigits );

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

if ( Lot < MinLot )
{
  OnePipCost = TickValue * MinLot; // пересчитаем стоимость пункта под минимально возможный лот
  SetError("Stoploss "+DoubleToStr(PriceInPoint,0)
          +" > max available "+DoubleToStr(MathAbs (Loss / OnePipCost),0));
  ShowInfo();
  return;
}

Если размер лота превысит максимально допустимый размер лота (если вы разместите стоп очень близко и процент риска зададите достаточно высоким) - будет выдано соответствующее предупреждение:

if ( MaxLot < Lot )
{
  OnePipCost = TickValue * MaxLot; // пересчитаем стоимость пункта под максимально возможный лот
  SetError("Stoploss "+DoubleToStr(PriceInPoint,0)
          +" < min available "+DoubleToStr(MathAbs (Loss / OnePipCost),0));
  ShowInfo();
  return;
}  

 

Модификация ордера

Если вам нужно установить тейкпрофит или переставить стоплосс на новую цену - сбросьте скрипт #MODIFY в нужную точку цены на графике. Эксперт сам разберется что именно модифицируется (стоплосс или тейкпрофит) и исправит ордер. Здесь используется та же техника, что и при открытии ордера: указание цены модификации ниже текущей цены для ордера на покупку, означает модификацию стопа, а выше - тейка.

// определим что правим
if ( ( (OrderType() == OP_BUY  || OrderType() == OP_BUYSTOP)  && CmdPrice >= CurAsk ) ||
     ( (OrderType() == OP_SELL || OrderType() == OP_SELLSTOP) && CmdPrice <= CurBid ) ) TP = CmdPrice;
else // правим стоп
{
  SL = CmdPrice;

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

  // Контроль размера стопа и процента риска! 
  Loss = AccountBalance() * MaxAllowedRisk / 100.0; // потери заданы в проценте от баланса
  OnePipCost = TickValue * OrderLots(); // пересчитаем стоимость пункта под лот ордера
  if ( OrderType() == OP_BUY  ) NewLoss = ( OrderOpenPrice() - SL ) / Point * OnePipCost;
  if ( OrderType() == OP_SELL ) NewLoss = ( SL - OrderOpenPrice() ) / Point * OnePipCost;
  if ( NewLoss > Loss ) 
  { 
    SetError("Stoploss "+DoubleToStr(NewLoss/OnePipCost,0)
            +" > max available "+DoubleToStr(Loss/OnePipCost,0)); 
    ShowInfo(); 
    return; 
  }
}

 

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

Когда вы решите закрыть текущий ордер - сбросьте на график скрипт #CLOSE в любую точку цены. При закрытии это не имеет значения и ордер закроется.

 

Трейлинг-стоп

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

Прежде всего это касается того, что вы сами можете задать цену, с которой хотите начать трейлинг. Сделать это можно сразу же после открытия ордера. Для этого нужно сбросить скрипт #TRAL-START на цену где вы собираетесь начать трейлинг. Эксперт "запомнит" это значение, и, как только цена пробьет этот уровень, начнет работать трейлинг-механизм, и эксперт сам будет подтягивать стоп вслед за идущей ценой. Чтобы не модифицировать ордер слишком часто, в эксперте предусмотрен параметр дискретности шага трейлинг стопа TralStep. Новый стоп будет поставлен только в том случае если цена пройдет в "правильном" направлении не менее TralStep пунктов.

Второе отличие от штатного трала заключается в том, что одновременно с началом трейлинга можно задать его размер. Скрипт #TRAL-START0LOSS укажет начало трейлинга и в момент его начала автоматически переставит стоплосс в точку безубытка на расстоянии Tral0LossGap пунктов от цены открытия ордера. Другая его модификация #TRAL-START50PROFIT переставит стоп в начале трейлинга в середину расстояния между ценой начала трейлинга и ценой открытия ордера, что автоматически будет означать сохранение как минимум 50% накопленной прибыли в момент начала трейлинга.

Команды настройки трейлинг стопа указывают эксперту параметры будущего трейлинга.

//——— TRALSTART

// задаем цену с которой включим трал. 
// до момента начала срабатывания TralStart < 0 как признак что он еще не рабоает
if ( CmdID == COMMAND_TRALSTART && CmdPrice > 0) 
{ 
  ZeroLossTral = false; HalfProfitTral = false; TralStart = CmdPrice; PrevPrice = 0; ShowInfo(); 
  return; 
}
//——— TRALSTART0LOSS
// задаем цену с которой включим трал. с одновременным переносом стопа в безубыток
if ( CmdID == COMMAND_TRALSTART0LOSS && CmdPrice > 0) 
{ 
  ZeroLossTral = true; HalfProfitTral = false; TralStart = CmdPrice; PrevPrice = 0; ShowInfo(); 
  return; 
}
//——— TRALSTART50PROFIT
// задаем цену с которой включим трал. с одновременным переносом стопа на 50% заработанной прибыли
if ( CmdID == COMMAND_TRALSTART50PROFIT && CmdPrice > 0) 
{ 
  ZeroLossTral = false; HalfProfitTral = true; TralStart = CmdPrice; PrevPrice = 0; ShowInfo(); 
  return; 
}
//——— TRALSTOP
// обнуляем и значит - перестаем тралить
if ( CmdID == COMMAND_TRALSTOP ) 
{ 
  ZeroLossTral = false; HalfProfitTral = false; PrevPrice = 0; TralStart = 0; ShowInfo(); 
  return; 
}  

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

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

void TrailingStop(int Step)
{
  double OnePipCost, NewLoss, SL=0;
  
  if ( OrderSelect ( SelectedOrder, SELECT_BY_TICKET ) == false ) return; 
  if ( OrderCloseTime() > 0 ) return; // ордер уже закрыт - тралить нечего
  
  // проверим разумность данных
  if ( TralStart <= 0 || Step < 1 ) return(-1); 

  // Получим данные для контроля размера стопа и процента риска!  
  Loss = AccountBalance() * MaxAllowedRisk / 100.0; // потери заданы в проценте от баланса
  OnePipCost = TickValue * OrderLots(); // пересчитаем стоимость пункта под лот ордера

  if ( OrderType() == OP_BUY && CurBid >= TralStart ) 
  {
    if ( PrevPrice <= 0 ) 
    { 
      if ( ZeroLossTral   ) SL = NormalizeDouble(OrderOpenPrice() + Tral0LossGap*Point, Digits); 
      if ( HalfProfitTral ) SL = NormalizeDouble(OrderOpenPrice() + (CurBid - OrderOpenPrice())/2.0, Digits); 
      else                  SL = NormalizeDouble(OrderStopLoss(), Digits);
    }
    else SL = NormalizeDouble(OrderStopLoss() + (CurBid - PrevPrice), Digits);
    if ( SL < OrderStopLoss() ) return;
    NewLoss = ( OrderOpenPrice() - SL ) / Point * OnePipCost;
  }
  if ( OrderType() == OP_SELL && CurAsk <= TralStart )  
  {
    if ( PrevPrice <= 0 ) 
    { 
      if ( ZeroLossTral   ) SL = NormalizeDouble(OrderOpenPrice() - Tral0LossGap*Point, Digits); 
      if ( HalfProfitTral ) SL = NormalizeDouble(OrderOpenPrice() - (OrderOpenPrice() - CurAsk)/2.0, Digits); 
      else                  SL = NormalizeDouble(OrderStopLoss(), Digits);
    }
    else SL = NormalizeDouble(OrderStopLoss() - (PrevPrice - CurAsk), Digits);
    if ( SL > OrderStopLoss() ) return;
    NewLoss = ( SL - OrderOpenPrice() ) / Point * OnePipCost;
  }
  if ( SL <= 0 ) return; // цена еще не пересекла уровень старта тралинга
  
  if ( NewLoss > Loss ) 
  { 
    SetError("Trailing Stoploss "+DoubleToStr(NewLoss/OnePipCost,0)
            +" > max available "+DoubleToStr(Loss/OnePipCost,0)); 
    ShowInfo(); 

    return; 
  }

  if ( ( OrderType() == OP_BUY && SL - OrderStopLoss() >= Step*Point ) || 
       ( OrderType() == OP_SELL && OrderStopLoss() - SL >= Step*Point ) )
  {
    TXT = "• Tralingstop order. Please wait..."; 
    bool OK = OrderModify(OrderTicket(),OrderOpenPrice(),SL,OrderTakeProfit(),OrderExpiration(),Blue);
    if ( OK ) SetWarning("Traling stop moved to " + DoubleToStr(SL, Digits) 
                        +" at " + TimeToStr(TimeLocal(),TIME_SECONDS)); 
    else SetLastError("Tralingstop");
  }
  if ( PrevPrice <= 0 || OK )
  {  
    // только если мы переставили трал - запоминаем цены для трала на следующем тике
    if ( OrderType() == OP_BUY  ) PrevPrice = CurBid;
    if ( OrderType() == OP_SELL ) PrevPrice = CurAsk;
  }
}

 

Вывод информации о текущем состоянии

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

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

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

 

Торговля по внешним сигналам

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

Берем для модификаций исходный код расчета индикатора и в самом конце, после того как все данные индикаторных буферов рассчитаны, вставляем "аналитический блок" управления экспертом. В этом примере реализованы следующие правила открытий ордеров: если график RSI пересекает уровень 52 сверху вниз или уровень 48 снизу вверх - открывается новый ордер.

Благодаря встроенному в эксперт механизму блокировки повторных открытий и автоматического закрытия ордеров в случае переворота позиции, в этом примере нам не придется заботиться о принудительном закрытии ордеров. При пересечении уровней 45 и 55 включается механизм автотрала 50% прибыли. Это конечно не прибыльная система, а просто демонстрационная, целью которой является показать что и как нужно делать в коде индикатора, чтобы обучить его управлять торговлей эксперта.

// добавка для #Trader ======================================================================
double DefaultStop = 150 * Point; // смещение стопа при открытии ордера

// условие покупки
if( RSIBuffer[3] <= 50 && RSIBuffer[2] <= 52 && RSIBuffer[1] > 52 )
{
   GlobalVariableSet ( "#Trader-Chart", WindowHandle( Symbol(), Period()) ); 
   GlobalVariableSet ( "#Trader-Command", 1 );
   GlobalVariableSet ( "#Trader-Price", NormalizeDouble ( Close[0] - DefaultStop, Digits ) );
   GlobalVariableSet ( "#Trader-Time", TimeCurrent() );
}

// условие продажи
if( RSIBuffer[3] >= 50 && RSIBuffer[2] >= 48 && RSIBuffer[1] < 48 ) 
{
   GlobalVariableSet ( "#Trader-Chart", WindowHandle( Symbol(), Period()) ); 
   GlobalVariableSet ( "#Trader-Command", 1 );
   GlobalVariableSet ( "#Trader-Price", NormalizeDouble ( Close[0] + DefaultStop, Digits ) );
   GlobalVariableSet ( "#Trader-Time", TimeCurrent() );
}

// старт трала 50%
if( ( RSIBuffer[2] >= 45 && RSIBuffer[1] < 45 ) || ( RSIBuffer[2] <= 55 && RSIBuffer[1] > 55 ) ) 
{
   GlobalVariableSet ( "#Trader-Chart", WindowHandle( Symbol(), Period()) );
   GlobalVariableSet ( "#Trader-Command", 7 );
   GlobalVariableSet ( "#Trader-Price", NormalizeDouble ( Close[0], Digits ) );
   GlobalVariableSet ( "#Trader-Time", TimeCurrent() );
}
// добавка для #Trader ======================================================================

Единственное важное замечание: тестировать такие индикаторы можно только в режиме визуального тестирования и конечно никакие оптимизации с ним невозможны.

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

Итоги

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

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

 

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