Советник, который торгует по пересечению скользящих средних

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

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

//Внешние переменные.
extern double LotSize = 0.1;
extern double StopLoss = 50;
extern double TakeProfit = 100;
extern int Slippage = 5;
extern int MagicNumber = 123;
extern int FastMAPeriod = 10;
extern int SlowMAPeriod = 20;

//Глобальные переменные.
int BuyTicket;
int SellTicket;
double UsePoint;
int UseSlippage;

int OnInit()
{
UsePoint = PipPoint(Symbol());
UseSlippage = GetSlippage(Symbol(),Slippage);
}

void OnTick()
{

//Скользящие средние.
double FastMA = iMA(NULL,0,FastMAPeriod,0,0,0,0);
double SlowMA = iMA(NULL,0,SlowMAPeriod,0,0,0,0);

//Ордер на покупку.
if(FastMA > SlowMA && BuyTicket == 0)
{
OrderSelect(SellTicket,SELECT_BY_TICKET);

//Закрываем ордер.
if(OrderCloseTime() == 0 && SellTicket > 0)
{
double CloseLots = OrderLots();
double ClosePrice = Ask;
bool Closed = OrderClose(SellTicket,CloseLots,ClosePrice,UseSlippage,Red);
}

//Рассчитываем стоп-лосс и тейк-профит.
double OpenPrice = Ask;
if(StopLoss > 0) double BuyStopLoss = OpenPrice - (StopLoss * UsePoint);
if(TakeProfit > 0) double BuyTakeProfit = OpenPrice + (TakeProfit * UsePoint);

//Открываем ордер на покупку.
BuyTicket = OrderSend(Symbol(),OP_BUY,LotSize,OpenPrice,UseSlippage,BuyStopLoss,BuyTakeProfit,"Ордер на покупку",MagicNumber,0,Green);
SellTicket = 0;
}

//Ордер на продажу.
if(FastMA < SlowMA && SellTicket == 0)
{
OrderSelect(BuyTicket,SELECT_BY_TICKET);

//Закрываем ордер.
if(OrderCloseTime() == 0 && BuyTicket > 0)
{
CloseLots = OrderLots();
ClosePrice = Bid;
Closed = OrderClose(BuyTicket,CloseLots,ClosePrice,UseSlippage,Red);
}

//Рассчитываем стоп-лосс и тейк-профит.
OpenPrice = Bid;
if(StopLoss > 0) double SellStopLoss = OpenPrice + (StopLoss * UsePoint);
if(TakeProfit > 0) double SellTakeProfit = OpenPrice - (TakeProfit * UsePoint);

//Открываем ордер на продажу.
SellTicket = OrderSend(Symbol(),OP_SELL,LotSize,OpenPrice,UseSlippage,SellStopLoss,SellTakeProfit,"Ордер на продажу",MagicNumber,0,Red);
BuyTicket = 0;
}

}

//Функция PipPoint.
double PipPoint(string Currency)
{
int CalcDigits = MarketInfo(Currency,MODE_DIGITS);
if(CalcDigits == 2 || CalcDigits == 3) double CalcPoint = 0.01;
else if(CalcDigits == 4 || CalcDigits == 5) CalcPoint = 0.0001; return(CalcPoint);
}

//Функция GetSlippage.
int GetSlippage(string Currency, int SlippagePips)
{
int CalcDigits = MarketInfo(Currency,MODE_DIGITS);
if(CalcDigits == 2 || CalcDigits == 4) double CalcSlippage = SlippagePips;
else if(CalcDigits == 3 || CalcDigits == 5) CalcSlippage = SlippagePips * 10; return(CalcSlippage);
}

Мы объявляем BuyTicket и SellTicket как глобальные переменные – таким образом тикет ордеров будет всегда будет сохраняться. Далее мы добавляем UsePoint и UseSlippage в качестве глобальных переменных.

Функция OnInit() запускается первой. Мы вызываем функции PipPoint() и GetSlippage() (которые объявляются в самом внизу) и присваиваем глобальным переменным возвращаемые ими значения.

Далее идет функция OnTick(), в которой выполняется основная часть нашей программы.

Функция iMA() вычисляет значения скользящих средних. Переменная FastMA вычисляет 10-периодное скользящее среднее, которое устанавливается с помощью переменной FastMAPeriod. Переменная SlowMA – скользящее среднее за 20 периодов, установленное с помощью SlowMAPeriod. Все остальные параметры скользящих средних мы оставляем по умолчанию.

Мы используем оператор if для определения условий открытия ордеров. Если текущая скользящая средняя за 10 периодов (FastMA) больше, чем скользящая средняя за 20 периодов (SlowMA), и если BuyTicket равен 0, мы открываем ордер на покупку.

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

Далее мы получаем размер лота ордера на продажу и текущую цену Ask, которая будет ценой закрытия ордера на продажу. Затем мы закрываем ордер на продажу, используя функцию OrderClose().

Назначаем текущую цену Ask переменной OpenPrice – это будет цена открытия нашего ордера на покупку. Мы рассчитываем наш стоп-лосс и тейк-профит относительно цены открытия, сначала проверяя значения StopLoss или TakeProfit. Затем мы размещаем заказ с помощью функции OrderSend() и сохраняем тикет ордера в переменную BuyTicket.

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

Блок ордера на продажу следует той же логике, что и блок ордера на покупку.

Функции PipPoint() и GetSlippage() определяются в самом конце.

Код советника для отложенных ордеров

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

Объявим внешнюю переменную для настройки этого параметра, которая называется PendingPips.

extern int PendingPips = 10;

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

Функция OrderDelete() служит для закрытия отложенных ордеров. Ее синтаксис:

bool  OrderDelete(
   int        ticket,      // номер ордера
   color      arrow_color  // цвет
   );
  • ticket – номер тикета ордера.
  • arrow_color – цвет стрелки.

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

Cтоп-ордер на покупку:

OrderSelect(SellTicket,SELECT_BY_TICKET);

//Закрываем ордер.
if(OrderCloseTime() == 0 && SellTicket > 0 && OrderType() == OP_SELL)
{
double CloseLots = OrderLots();
double ClosePrice = Ask;
bool Closed = OrderClose(SellTicket,CloseLots,ClosePrice,UseSlippage,Red);
if(Closed == true) SellTicket = 0;
}

//Удаляем ордер.
else if(OrderCloseTime() == 0 && SellTicket > 0 && OrderType() == OP_SELLSTOP)
{
bool Deleted = OrderDelete(SellTicket,Red);
if(Deleted == true) SellTicket = 0;
}

//Размещаем новый ордер.
double PendingPrice = Close[0] + (PendingPips * UsePoint);
if(StopLoss > 0) double BuyStopLoss = PendingPrice - (StopLoss * UsePoint);
if(TakeProfit > 0) double BuyTakeProfit = PendingPrice + (TakeProfit * UsePoint);
BuyTicket = OrderSend(Symbol(),OP_BUYSTOP,LotSize,PendingPrice,UseSlippage, BuyStopLoss,BuyTakeProfit,"Buy Stop ордер",MagicNumber,0,Green);
40
SellTicket = 0;

Мы используем OrderType(), чтобы проверить, является ли выбранный ордер на продажу рыночным или стоп-ордером. Если это рыночный ордер, мы закрываем его с помощью OrderClose(). Если это отложенный ордер, мы закрываем его с помощью OrderDelete().

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

Cтоп-ордер на продажу:

OrderSelect(BuyTicket,SELECT_BY_TICKET);

//Закрываем ордер.
if(OrderCloseTime() == 0 && BuyTicket > 0 && OrderType() == OP_BUY)
{
CloseLots = OrderLots();
ClosePrice = Bid;
Closed = OrderClose(BuyTicket,CloseLots,ClosePrice,UseSlippage,Red);
if(Closed == true) BuyTicket = 0;
}

//Удаляем ордер.
else if(OrderCloseTime() == 0 && BuyTicket > 0 && OrderType() == OP_BUYSTOP)
{
Closed = OrderDelete(BuyTicket,Red); if(Closed == true) BuyTicket = 0;
}

//Размещаем новый ордер.
PendingPrice = Close[0] - (PendingPips * UsePoint);
double SellStopLoss = PendingPrice + (StopLoss * UsePoint);
double SellTakeProfit = PendingPrice - (TakeProfit * UsePoint);
SellTicket = OrderSend(Symbol(),OP_SELLSTOP,LotSize,PendingPrice,UseSlippage, SellStopLoss,SellTakeProfit,"Sell Stop ордер",MagicNumber,0,Red);
BuyTicket = 0;

Альтернативный код советника

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

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

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

#property copyright     "Александр Паркер"
#property link          "https://traderblog.net/"
#property version       "1.00"
#property strict
#property description   "Советник торгует по пересечению скользящих средних"

//Внешние переменные.
extern double LotSize=0.1; //Размер позиции
extern bool UseEntryToExit=true; //Использовать следующий сигнал для закрытия позиции.
extern double StopLoss=20; //Стоп-лосс в пунктах.
extern double TakeProfit=50; //Тейк-профит в пунктах.
extern int Slippage=2; //Размер проскальзывания в пунктах.
extern bool TradeEnabled=true; //Разрешена ли торговля.
extern int MAFastPeriod=10; //Период быстрой скользящей средней.
extern int MASlowPeriod=20; //Период медленной скользящей средней.

//Глобальные переменные.
double ePoint; //Нормализованные пункты.
bool CanOrder; //Проверка, можно ли торговать.
bool CanOpenBuy; //Проверка, есть ли открытые ордера на покупку.
bool CanOpenSell; //Проверка, есть ли открытые ордера на продажу.
int OrderOpRetry=10; //Количество попыток совершить торговую операцию.
int SleepSecs=3; //Пауза, если не получается открыть ордер.
int MinBars=60; //Минимальное количество баров для торговли.

//Глобальные переменные для цены.
double MinSL, MaxSL, TP, SL, Spread;
int Slip;

//Функция инициализации.
void Initialize(){
RefreshRates();
ePoint=Point;
Slip=Slippage;
if (MathMod(Digits,2)==1){
ePoint*=10;
Slip*=10;
}
TP=TakeProfit*ePoint;
SL=StopLoss*ePoint;
CanOrder=TradeEnabled;
CanOpenBuy=true;
CanOpenSell=true;
}

//Проверка, можно ли открывать ордера.
void CheckCanOrder(){
if(Bars<MinBars){
Print("Недостаточно баров для торговли.");
CanOrder=false;
}
OrdersOpen();
return;
}

//Проверка есть ли открыте ордера и какого типа.
void OrdersOpen(){
for(int i=0; i<OrdersTotal(); i++) {
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES) == false) {
Print("Нельзя выбрать ордер - ",GetLastError());
break;
}
if(OrderSymbol()==Symbol() && OrderType() == OP_BUY) CanOpenBuy=false;
if(OrderSymbol()==Symbol() && OrderType() == OP_SELL) CanOpenSell=false;
}
return;
}

//Закрываем ордера определенного символа и типа.
void CloseAll(int Command){
double ClosePrice=0;
for(int i=0; i<OrdersTotal(); i++) {
if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES) == false) {
Print("Нельзя выбрать ордер - ",GetLastError());
break;
}
if(OrderSymbol()==Symbol() && OrderType()==Command) {
if(Command==OP_BUY) ClosePrice=Bid;
if(Command==OP_SELL) ClosePrice=Ask;
double Lots=OrderLots();
int Ticket=OrderTicket();
for(int j=1; j<OrderOpRetry; j++){
bool res=OrderClose(Ticket,Lots,ClosePrice,Slip,Red);
}
}
}
return;
}

//Открываем новый ордер определенного типа.
void OpenNew(int Command){
RefreshRates();
double OpenPrice=0;
double SLPrice=0;
double TPPrice=0;

if(Command==OP_BUY){
OpenPrice=Ask;
if(!UseEntryToExit){
SLPrice=OpenPrice-SL;
TPPrice=OpenPrice+TP;
}
}

if(Command==OP_SELL){
OpenPrice=Bid;
if(!UseEntryToExit){
SLPrice=OpenPrice+SL;
TPPrice=OpenPrice-TP;
}
}

for(int i=1; i<OrderOpRetry; i++){
int res=OrderSend(Symbol(),Command,LotSize,OpenPrice,Slip,NormalizeDouble(SLPrice,Digits),NormalizeDouble(TPPrice,Digits),"",0,0,Green);
}
return;
}


//Технический анализ показаний индикаторов скользящих средних.
bool CrossToBuy=false;
bool CrossToSell=false;

void CheckMACross(){
CrossToBuy=false;
CrossToSell=false;
double MASlowCurr=iMA(Symbol(),0,MASlowPeriod,0,MODE_SMA,PRICE_CLOSE,1);
double MASlowPrev=iMA(Symbol(),0,MASlowPeriod,0,MODE_SMA,PRICE_CLOSE,2);
double MAFastCurr=iMA(Symbol(),0,MAFastPeriod,0,MODE_SMA,PRICE_CLOSE,1);
double MAFastPrev=iMA(Symbol(),0,MAFastPeriod,0,MODE_SMA,PRICE_CLOSE,2);
if(MASlowPrev>MAFastPrev && MAFastCurr>MASlowCurr){
CrossToBuy=true;
}
if(MASlowPrev<MAFastPrev && MAFastCurr<MASlowCurr){
CrossToSell=true;
}
}

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
//Вызываем функции инициализации, проверки и торговли.
Initialize();
CheckCanOrder();
CheckMACross();
//Проверяем сигналы на вход или выход.
if(CrossToBuy){
if(UseEntryToExit) CloseAll(OP_SELL);
if(CanOpenBuy && CanOpenSell && CanOrder) OpenNew(OP_BUY);
}
if(CrossToSell){
if(UseEntryToExit) CloseAll(OP_BUY);
if(CanOpenSell && CanOpenBuy && CanOrder) OpenNew(OP_SELL);
}
}
//+------------------------------------------------------------------+