Культура написания кода

Культура написания кода

26 ноября 2015, 16:48
eevviill7
[Удален]
3
1 549

Введение

Здравствуйте уважаемые читатели.

В этой статье я попытаюсь изложить своё виденеи культурного написания кода. Как говорил один знакомый программист "Код должен быть написан так, чтобы писавший программист открыл код через 6 лет и мог за 20 минут разобратся в нём". В часности если учится акуратно писать код который впринцыпе может никто кроме программиста и не увидет, то в жизни легче будет воспитывать в себе культуру. 



1.Примеры и объяснения

1.1. enum

Развёрнуто  про enum можно прочитать здесь Что такое enum?

Словами программиста enum это созданый вручную список переменных типа int. В основном применяется для указания диапазона возможных вариантов в внешних переменных или для того чтобы при выборе внешней переменной появлялся выпадающий список возможных вариантов, а не просто вписывалась нужная константа. Нужно обязательно учитывать то что enum нужно применять только в тех случаях, когда ваш список не может выходить за диапазон теоретическо возможного значения. Например месяцы года могут быть только от 1 до 12. А период индикатора MovingAverage может быть от 1 до бесконечности.  Нумерация элементов с списке enumначинается с 0. 

 

Пример 1.1.1. Минуты для старта работы советника.

enum minutes
{
M_0=0,M_15=15,M_30=30,M_45=45,M_60=60
};

extern minutes Start_m = 0;

Итак. Вместо привычного типа данных после extern, пишем имя списка enumStart_m = 1 это равносильно тому значению которое присвоенно 1 элементу в списке. В данном случае M_15. Если M_15=90, то Start_m=90 тоже.

А теперь сравним скриншотами разницу с использованием enum и без.

Рисунок 1.1.1. 

 

 Рисунок 1.1.2. 

 

Пример 1.1.2. Использование альтернативы true/false

Так можно поменять превычное название true/false на то, какое захотите.

enum bools
{
fal=0, //Не использовать
tru=1  //Использовать
};


extern bools work_time = 0;

 Выходит так.

 Рисунок 1.1.3. 

 

 

Также есть встроенный enum.

Например метод  Moving Average и Таймфреймы.

extern ENUM_MA_METHOD  MA_method = MODE_SMA;
extern ENUM_TIMEFRAMES MA_TF = PERIOD_D1;

 

 

1.2. типы данных для целых чисел

Как мы знаем после обновления терминала до 600 билда, появились новые типы данных. 

В часности целые типы. Про них можете почитать здесь Типы данных.  

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

Если размер кода большой, то соотвественно больше можем сэкономить.

 

Пример 1.2.1. функция подающая сигнал для открытия ордера

int Sig_f()
{
//сигнал для бай
if(Close[1]>Open[1]) return(1);

//сигнал для сел
if(Close[1]<Open[1]) return(-1);


return(0);
}

Как мы видим функция типа int. Она может возвратить всего 3 значения (0,1,-1).

Вполне логично чтобы  у функции был тип char. 

char Sig_f()
{
//сигнал для бай
if(Close[1]>Open[1]) return(1);

//сигнал для сел
if(Close[1]<Open[1]) return(-1);


return(0);
}

 

 Пример 1.2.2. шаг между ордерами

extern int step = 40;

Как мы понимаем шаг между ордерами не может иметь отрицательное значение.

Вполне логично написать тип данных uint.

extern uint step = 40;

 

 

1.3. вызов индикатора

Есть ситуации когда в советнике есть основной сигнал который будет учавствувать в коде по-любому, а есть отключаемые фильтры.

Пример 1.3.1

char Sig_f()
{
double Stoch=iStochastic(Symbol(),0,Kperiod,Dperiod,slowing,method,price_field,0,1);

//buy
if(Close[1]>Open[1] && (!use_stoch_filter || Stoch<stoch_overSold)) return(1);
//sell
if(Close[1]<Open[1] && (!use_stoch_filter || Stoch>stoch_overBought)) return(-1);


return(0);
}

В данном случае фильтром у нас выступает индикатор Stochastic.

Как мы видим он вызывается независимо от того используем мы его или нет.

Вполне логично чтобы если он не использовался, то и не вызывался.

Пример 1.3.2.

char Sig_f()
{
double Stoch=EMPTY_VALUE;
if(use_stoch_filter) Stoch=iStochastic(Symbol(),0,Kperiod,Dperiod,slowing,method,price_field,0,1);

//buy
if(Close[1]>Open[1] && (!use_stoch_filter || Stoch<stoch_overSold)) return(1);
//sell
if(Close[1]<Open[1] && (!use_stoch_filter || Stoch>stoch_overBought)) return(-1);


return(0);
}

 

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

При вызове индикатора с советника эти функции нам не нужны и мы их отключаем.   

Пример 1.3.3. 

extern string ind1_name = "Indicator Fox";
extern int PeriodIndikator = 9;
extern double Factor = 2;
extern bool ind_alert = false;
extern bool ind_sound = false;
extern bool ind_email = false;


///////////////////////////////////
void OnTick()
{
double fox=iCustom(Symbol(),0,ind1_name,PeriodIndikator,Factor,ind_alert,ind_sound,ind_email,0,1);

}

 Настройки советника будут выглядеть так.

 Рисунок 1.3.1. 

 

Но вполне логично чтобы настроек алерта, звука и почты небыло в внешних перменных настроек индикатора в советнике.

Мы их в выключеном режиме сразу запишем в настройки при вызове индикатора. 

Пример 1.3.4.

extern string ind1_name = "Indicator Fox";
extern int PeriodIndikator = 9;
extern double Factor = 2;


///////////////////////////////////
void OnTick()
{
double fox=iCustom(Symbol(),0,ind1_name,PeriodIndikator,Factor,false,false,false,0,1);

}

 Теперь лишних настроек индикатора в внешних переменных советника не будет.

 Рисунок 1.3.2. 

 

 

 

1.4. использование пользовательских буферов (step)

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

Итак. Например нам надо мартингейл с максимальным количеством шагов 4(максимум 5 ордеров в рынке).

После всех расчётов нам надо шаг. Не заморачиваясь, проще всего написать так.

Пример 1.4.1.

extern uint step1=30;
extern uint step2=35;
extern uint step3=25;
extern uint step4=35;

///////////////////////////////////////////
int OnInit()
{  

return(INIT_SUCCEEDED);
}

////////////////////////////////////////////
int start()
{
...
uint step_to_chek=0;
if(OrdersTotal()==0) step_to_chek=step1; 
if(OrdersTotal()==1) step_to_chek=step2;
if(OrdersTotal()==2) step_to_chek=step3;
if(OrdersTotal()==3) step_to_chek=step4;
...
return(0);
}

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

 Пример 1.4.2.

extern uint step1=30;
extern uint step2=35;
extern uint step3=25;
extern uint step4=35;

uint steps[4];

///////////////////////////////////////////
int OnInit()
{  
steps[0]=step1; steps[1]=step2; steps[2]=step3; steps[3]=step4;

return(INIT_SUCCEEDED);
}

////////////////////////////////////////////
int start()
{
...
uint step_to_chek=0;
if(OrdersTotal()>0) step_to_chek=steps[OrdersTotal()-1];
...
return(0);
}

 

 

1.5. использование пользовательских функций или код в OnTick()

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

Не имеет смысла например в советнике нагружать кодом OnTick(). Когда всё в куче сложно потом разобратся если есть ошибка или надо изменить алгоритм советника.

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

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

Пример 1.5.1. 

//////////////////////////////////////////////
void OnTick()
{
 double Free    =AccountFreeMargin();
 double One_Lot =MarketInfo(Symbol(),MODE_MARGINREQUIRED);
 double Step    =MarketInfo(Symbol(),MODE_LOTSTEP);
 double Loto     =MathFloor(Free*Risk/100/One_Lot/Step)*Step;
 
 double Min_Lot =MarketInfo(Symbol(),MODE_MINLOT);
 double Max_Lot =MarketInfo(Symbol(),MODE_MAXLOT);
 if(Loto<Min_Lot) Loto=Min_Lot;
 if(Loto>Max_Lot) Loto=Max_Lot;
 

  for (int i=OrdersTotal()-1; i>=0; i--) 
  {
  if(OrderSelect(i, SELECT_BY_POS)) 
  {
  if(OrderSymbol() == Symbol())
  { 
  if (OrderMagicNumber()==Magic)
  {
  while(IsTradeContextBusy()) Sleep(int(pause_if_busy*1000));
  RefreshRates();
  
      if (OrderType()==OP_BUY) 
      {
      if(OrderStopLoss()!=OrderOpenPrice()+NormalizeDouble(Bez_Ub_Size*point,Digits))
      {
      if (Bid>OrderOpenPrice()+Bez_Ub_Level*point && (OrderStopLoss()<OrderOpenPrice() || OrderStopLoss()==0)) 
      {
      if(OrderModify(OrderTicket(),OrderOpenPrice(),OrderOpenPrice()+NormalizeDouble(Bez_Ub_Size*point,Digits),OrderTakeProfit(),0,clrNONE))
      continue;       
      }
      }
      }
      

      if (OrderType()==OP_SELL) 
      {
      if(OrderStopLoss()!=OrderOpenPrice()-NormalizeDouble(Bez_Ub_Size*point,Digits))
      {
      if (Ask<OrderOpenPrice()-Bez_Ub_Level*point && (OrderStopLoss()>OrderOpenPrice() || OrderStopLoss()==0)) 
      {
      if(OrderModify(OrderTicket(),OrderOpenPrice(),OrderOpenPrice()-NormalizeDouble(Bez_Ub_Size*point,Digits),OrderTakeProfit(),0,clrNONE))
      continue;
      }
      }  
      }   
  }
  }
  }
  }


  for(int i=0; i<OrdersTotal(); i++) 
  { 
    if(OrderSelect(i, SELECT_BY_POS))
    {
    if(OrderSymbol() == Symbol())
    {        
    if(OrderMagicNumber()==Magic)
    {

    if(OrderType()==OP_BUY) 
      { 
      if(Bid>=NormalizeDouble(OrderOpenPrice()+TrailingStart*point+TrailingStop*point,Digits)) 
      { 
        if(NormalizeDouble(OrderStopLoss(),Digits)<NormalizeDouble(Bid-(TrailingStop+TrailingStep)*point,Digits))
        {
        if(NormalizeDouble(OrderStopLoss(),Digits)!=NormalizeDouble(Bid-TrailingStop*point,Digits))
        {
        while(IsTradeContextBusy()) Sleep(int(pause_if_busy*1000));
        RefreshRates();
          if(OrderModify(OrderTicket(), OrderOpenPrice(), NormalizeDouble(Bid-TrailingStop*point,Digits), OrderTakeProfit(), 0, clrNONE))
          continue;
        } 
        }
      }
      } 

    if(OrderType()==OP_SELL) 
      { 
      if(Ask<=NormalizeDouble(OrderOpenPrice()-TrailingStart*point-TrailingStop*point,Digits)) 
      { 
        if((NormalizeDouble(OrderStopLoss(),Digits)>NormalizeDouble(Ask+(TrailingStop+TrailingStep)*point,Digits)) || OrderStopLoss()==0) 
        {
        if(NormalizeDouble(OrderStopLoss(),Digits)!=NormalizeDouble(Ask+TrailingStop*point,Digits))
        {
        while(IsTradeContextBusy()) Sleep(int(pause_if_busy*1000));
        RefreshRates();
          if(OrderModify(OrderTicket(), OrderOpenPrice(), NormalizeDouble(Ask+TrailingStop*point,Digits), OrderTakeProfit(), 0, clrNONE))
          continue;
        }
        }
      }
      } 
    }
    }
    } 
  }
  

}//end on tick

Тяжело потом что то изменять или найти, не правда ли?

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

Пример 1.5.2. 

void OnTick()
{
double lot=GetLot();
Traling_Stop_f();
BezUb();

}//enf on tick


//functions
//////////////////////////////////////////////////////////////////
double GetLot()
{
...
}

////////////////////////////////////////////////////////////////
void BezUb()
{
...
}

///////////////////////////////////////////////////////////////////////////
void Traling_Stop_f()
{
...  
}

Мы в OnTick() разместили только вызовы функций. Это очень удобно.

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


 

1.6. пересчёт баров в индикаторе

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

Пример 1.6.1.

for(int i=Bars-IndicatorCounted()-1;i>=0;i--)
{
...
}

 А что делать если надо изначально посчитать не все бары и потом пересчитывать не только текущий бар?

Предлагаю такую модель.


Ой)))) 

Пример 1.6.2.

extern int Bars_To_Count = 400;
extern int Bars_To_Recount = 4;

int BarsCount;
bool count_chek;

//////////////////////////////////////////////////////////////////
int OnCalculate(const int rates_total,const int prev_calculated,const datetime &time[],const double &open[],const double &high[],
                const double &low[],const double &close[],const long &tick_volume[],const long &volume[],const int &spread[])
  {
   for(int i=BarsCount;i>=0;i--)
{
if(i>int(Bars-1)) i=Bars-1;
...
}


//recount chek
if(!count_chek)
{
count_chek=true;
BarsCount=Bars_To_Recount;
}

   return(rates_total);
  }

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

 

 

1.7. создание и удаление объектов с идентификатором

В индикаторах рисующие объекты на графике и часто в советниках есть необходимость создавать объекты.

Например нам нужно создать 2 текстовых объекта.

Пример 1.7.1. 

 

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
int OnCalculate(const int rates_total,const int prev_calculated,const datetime &time[],const double &open[],const double &high[],
                const double &low[],const double &close[],const long &tick_volume[],const long &volume[],const int &spread[])
  {
string name="Leverage";
if(ObjectFind(name)==-1)
{
ObjectCreate(0,name,OBJ_LABEL,0,0,0);
ObjectSetInteger(0,name,OBJPROP_CORNER,0);
ObjectSetInteger(0,name, OBJPROP_XDISTANCE,20);
ObjectSetInteger(0,name, OBJPROP_YDISTANCE, 20);
ObjectSetInteger(0,name, OBJPROP_FONTSIZE, 14);
ObjectSetInteger(0,name, OBJPROP_COLOR, clrBlue);
ObjectSetString(0,name,OBJPROP_TEXT,"Leverage"+": "+string(AccountLeverage()));
}
name="AccFreeMarg";

if(ObjectFind(name)==-1)
{
ObjectCreate(0,name,OBJ_LABEL,0,0,0);
ObjectSetInteger(0,name,OBJPROP_CORNER,0);
ObjectSetInteger(0,name, OBJPROP_XDISTANCE,20);
ObjectSetInteger(0,name, OBJPROP_YDISTANCE, 45);
ObjectSetInteger(0,name, OBJPROP_FONTSIZE, 14);
ObjectSetInteger(0,name, OBJPROP_COLOR, clrGreen);
}
ObjectSetString(0,"AccFreeMarg",OBJPROP_TEXT,"AccountFreeMargin"+": "+string(AccountFreeMargin()));


 return(rates_total);
  }

.А в OnDeinit() надо записать так.

Пример 1.7.2. 

//////////////////////////////////////////////////

void OnDeinit(const int reason)

{

 if(ObjectFind("Leverage")!=-1)         ObjectDelete("Leverage");

 if(ObjectFind("AccFreeMarg")!=-1)  ObjectDelete("AccFreeMarg");

 }

Вроди бы ничего страшного. А если нам надо создать 900 объектов с разными названиями?

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

Во-вторых. В OnDeinit() прийдётся писать именно таким способом как в примере 1.7.2. Итого 900! строк.

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

Пример 1.7.3. 

string identif="mql5 статья";
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
int OnCalculate(const int rates_total,const int prev_calculated,const datetime &time[],const double &open[],const double &high[],
                const double &low[],const double &close[],const long &tick_volume[],const long &volume[],const int &spread[])
  {
string name="Leverage"+identif;
if(ObjectFind(name)==-1)
{
ObjectCreate(0,name,OBJ_LABEL,0,0,0);
ObjectSetInteger(0,name,OBJPROP_CORNER,0);
ObjectSetInteger(0,name, OBJPROP_XDISTANCE,20);
ObjectSetInteger(0,name, OBJPROP_YDISTANCE, 20);
ObjectSetInteger(0,name, OBJPROP_FONTSIZE, 14);
ObjectSetInteger(0,name, OBJPROP_COLOR, clrBlue);
ObjectSetString(0,name,OBJPROP_TEXT,"Leverage"+": "+string(AccountLeverage()));
}
name="AccFreeMarg"+identif;

if(ObjectFind(name)==-1)
{
ObjectCreate(0,name,OBJ_LABEL,0,0,0);
ObjectSetInteger(0,name,OBJPROP_CORNER,0);
ObjectSetInteger(0,name, OBJPROP_XDISTANCE,20);
ObjectSetInteger(0,name, OBJPROP_YDISTANCE, 45);
ObjectSetInteger(0,name, OBJPROP_FONTSIZE, 14);
ObjectSetInteger(0,name, OBJPROP_COLOR, clrGreen);
}
ObjectSetString(0,"AccFreeMarg",OBJPROP_TEXT,"AccountFreeMargin"+": "+string(AccountFreeMargin()));



 return(rates_total);
  }
  
  
//////////////////////////////////////////////////////////////
void OnDeinit(const int reason)
{
string name_delete;
for(int i=ObjectsTotal()-1;i>=0;i--)
{
name_delete=ObjectName(i);
if(StringFind(name_delete,identif)!=-1) ObjectDelete(name_delete);
}

}

 Как видим очень удобно и лаконично.

 

1.8. проверка закрылся ли ордер или модифицировался

Как мы знаем после 600-го билда МетаТрейдер при компиляции стали появлятся предупреждения о том что надо проверять открылся или закрылся или модифицировался ордер.

Рисунок 1.8.1.

 

Итак. Зачем это нужно? Правильно! Для культурного написаня кода! 

Теоретически проверка открытия-закрытия ордера надо для повторной попытки открыть-закрыть ордер или прекратить открытие ордера.

Рисунок 1.8.2. 


Как мы видим предупреждение исчезло после проверки открылся ли ордер. 

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

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

Например функция безубытка.

Пример 1.8.1.

////////////////////////////////////////////////////////////////
void BezUb()
{
  for (int i=OrdersTotal()-1; i>=0; i--) 
  {
  if(OrderSelect(i, SELECT_BY_POS)) 
  {
  if(OrderSymbol() == Symbol())
  { 
  if (OrderMagicNumber()==Magic)
  {
  while(IsTradeContextBusy()) Sleep(int(pause_if_busy*1000));
  RefreshRates();
  
      if (OrderType()==OP_BUY) 
      {
      if(OrderStopLoss()!=OrderOpenPrice()+NormalizeDouble(Bez_Ub_Size*point,Digits))
      {
      if (Bid>OrderOpenPrice()+Bez_Ub_Level*point && (OrderStopLoss()<OrderOpenPrice() || OrderStopLoss()==0)) 
      {
      if(OrderModify(OrderTicket(),OrderOpenPrice(),OrderOpenPrice()+NormalizeDouble(Bez_Ub_Size*point,Digits),OrderTakeProfit(),0,clrNONE))
      continue;       
      }
      }
      }
      else
      if (OrderType()==OP_SELL) 
      {
      if(OrderStopLoss()!=OrderOpenPrice()-NormalizeDouble(Bez_Ub_Size*point,Digits))
      {
      if (Ask<OrderOpenPrice()-Bez_Ub_Level*point && (OrderStopLoss()>OrderOpenPrice() || OrderStopLoss()==0)) 
      {
      if(OrderModify(OrderTicket(),OrderOpenPrice(),OrderOpenPrice()-NormalizeDouble(Bez_Ub_Size*point,Digits),OrderTakeProfit(),0,clrNONE))
      continue;
      }
      }  
      }   
  }
  }
  }
  }
}

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

 

 

Заключение

Итак мы разобрали примеры культурного написания кода.

Если вы будете акуратно писать код, то это сэкономит ресурсы компъютера и разборчивость кода.

Поделитесь с друзьями: