← К каталогу
Исходный код · ID 4047

Black Wolf

bw_diag.mq5
Вы автор или правообладатель?

Сообщите нам, если считаете, что материал размещён без разрешения.

Краткий обзор

О программе

Black Wolf — советник для MetaTrader 5, реализующий сеточную торговлю с элементами реверсивного мартингейла. Советник управляет двумя корзинами (BuyBasket и SellBasket), каждая из которых ведёт свои позиции по отдельной стороне. При достижении ценой заданного шага (Grid_Pips) открываются новые ордера с увеличенным лотом, а при активации реверсивного хеджа — противоположные позиции. Внутренняя логика подтверждена обработкой событий OnTick и использованием торгового API CTrade/CTrade.PositionOpen/PositionClose.

Советник поддерживает фиксированное закрытие прибыли (Profit_Close), парное закрытие (старейший + новейший ордер) и трейлинг стоп на основе прибыли с параметрами Trail_Start, Trail_Give_Back и Trail_Lock_Min. Доступен режим Trailing_Only_Exit, при котором фиксированный тейк-профит игнорируется. Лоты наращиваются по схеме, управляемой параметром LotRepeatCount, с возможностью ограничения максимального лота (MaxLot).

Для управления рисками предусмотрены три режима стоп-лосса (корзинный, по проценту от эквити, в валюте счёта), фильтр по дням недели и времени торговли, а также блокировка открытия нескольких ордеров одной стороны на одном ценовом уровне (Block_Same_Price_Level). При инициализации создаётся диагностический CSV-файл bw_diag.csv для записи рыночных данных и состояния позиций.

Параметры UseNewsFilter, NewsMinutesBefore и NewsMinutesAfter объявлены как входные, однако в текущей реализации функция фильтрации новостей (IsNewsBlackout) всегда возвращает false и не выполняет фактическую фильтрацию.

Настройки программы

НастройкаОписание
Lots Начальный лот — Начальный размер лота для первого ордера в корзине.
Times to repeat each lot size before increment (1 = standard martingale) (LotRepeatCount) Повтор лота перед увеличением — Количество повторений каждого размера лота перед увеличением. Значение 1 соответствует стандартному мартингейлу.
Profit_Close Прибыль для фиксированного закрытия — Фиксированный порог прибыли для закрытия корзины или пары. При достижении суммарной прибыли корзины этого значения происходит закрытие.
Pair-closing (oldest+newest) activates once a basket holds this many orders (Pair_Close_Min_Orders) Минимум ордеров для парного закрытия — Минимальное количество ордеров в корзине для активации парного закрытия (старейший + новейший ордер).
arm a money trailing stop before Profit_Close is reached (Use_Profit_Trailing) Использовать трейлинг прибыли — Включает трейлинг-стоп на основе прибыли для корзины и пар. При значении false трейлинг не работает.
arm trailing once basket/pair profit reaches this (money) (Trail_Start) Начало трейлинга — Уровень прибыли (в деньгах), при достижении которого активируется трейлинг-стоп для корзины или пары.
trail this far behind the peak (money) (Trail_Give_Back) Откат трейлинга от пика — Расстояние (в деньгах), на которое трейлинг-стоп откатывается от пика прибыли. При достижении этого отката корзина или пара закрывается.
GUARANTEED minimum profit locked — a trailed close never banks less (keep < Trail_Start) (Trail_Lock_Min) Минимум фиксируемой прибыли при трейлинге — Гарантированный минимум зафиксированной прибыли при трейлинг-закрытии. Закрытие никогда не зафиксирует прибыль ниже этого значения.
true = ignore the fixed Profit_Close ceiling; the moving trailing line is the ONLY profit exit (Trailing_Only_Exit) Только трейлинг как выход — При значении true фиксированный Profit_Close игнорируется, и единственным выходом по прибыли является движущийся трейлинг.
strict 200 pips (Grid_Pips) Шаг сетки (пункты) — Шаг сетки в пунктах. При достижении ценой заданного расстояния от последнего ордера открывается новый ордер или выполняется реверсия.
MaxOrdersPerSide Максимум ордеров на сторону — Максимальное количество ордеров в одной корзине (на одну сторону). При достижении лимита новые ордера не открываются.
Max lot size per order (0 = no cap / unlimited) (MaxLot) Максимальный лот — Максимальный размер лота на один ордер. Значение 0 означает отсутствие ограничения.
Dark_Buy_Signal Сигнал на покупку — Включает или отключает работу корзины покупок (BuyBasket). При значении false корзина покупок не открывает новые позиции.
Dark_Sell_Signal Сигнал на продажу — Включает или отключает работу корзины продаж (SellBasket). При значении false корзина продаж не открывает новые позиции.
MagicStart Начальный магический номер — Начальное значение магического номера. BuyBasket получает MagicStart, SellBasket — MagicStart+1.
Monday Торговля в понедельник — Переключатель торговли в понедельник. true — торговля разрешена, false — нет.
Tuesday Торговля во вторник — Переключатель торговли во вторник. true — торговля разрешена, false — нет.
Wednesday Торговля в среду — Переключатель торговли в среду. true — торговля разрешена, false — нет.
Thursday Торговля в четверг — Переключатель торговли в четверг. true — торговля разрешена, false — нет.
Friday Торговля в пятницу — Переключатель торговли в пятницу. true — торговля в пятницу разрешена, false — нет.
Saturday Торговля в субботу — Переключатель торговли в субботу. true — торговля разрешена, false — нет.
Sunday Торговля в воскресенье — Переключатель торговли в воскресенье. true — торговля разрешена, false — нет.
StartHour Время начала торговли — Начальное время торговли в формате ЧЧ:ММ. Ордера не открываются до этого времени в торговый день.
EndHour Время окончания торговли — Конечное время торговли в формате ЧЧ:ММ. Ордера не открываются после этого времени в торговый день.
UseReversalHedge Реверсивный хедж — При значении true при движении цены против корзины открывается противоположный ордер (реверсивный хедж).
MinDistancePoints Минимальное расстояние между ордерами — Минимальное расстояние в пунктах между ордерами одной стороны. Новые ордера блокируются, если расстояние до существующего меньше этого значения.
one order per side per whole price level (Block_Same_Price_Level) Блокировка на одном ценовом уровне — Блокирует открытие второго ордера на той же стороне внутри одного ценового уровня. При включении каждая ценовая зона принимает не более одного ордера на сторону.
0 = AUTO (1.00 on 2/3-digit symbols e.g. XAUUSD, 10 pips on 4/5-digit FX) (Price_Level_Size) Размер ценового уровня — Размер ценового уровня для блокировки. 0 — автоматический: 1.00 для 2/3-значных символов, 10 пунктов для 4/5-значных FX.
MaxStopLoss Максимальный стоп-лосс — Порог стоп-лосса. Интерпретация зависит от режима MaxStopLoss_Mode: сумма убытка корзины, процент от эквити или убыток в валюте счёта.
MaxStopLoss_Mode Режим стоп-лосса — Режим стоп-лосса: STOPLOSS_BASKET (убыток корзины), STOPLOSS_EQUITY_PERCENT (процент эквити), STOPLOSS_ACCOUNT_CURRENCY (убыток в валюте счёта).
UseNewsFilter Использовать фильтр новостей — Включает фильтр новостей. В текущей реализации функция фильтрации всегда возвращает false, фильтр неактивен.
NewsMinutesBefore Минуты до новостей — Количество минут до экономических новостей, в течение которых торговля ограничена (фильтр объявлен, но в текущей реализации не активен).
NewsMinutesAfter Минуты после новостей — Количество минут после экономических новостей, в течение которых торговля ограничена (фильтр объявлен, но в текущей реализации не активен).
Получить полный код

Поиск по коду файла

#property strict
#property version "2.34"

#include <Trade/Trade.mqh>
#include <Trade/PositionInfo.mqh>

CTrade trade;
CPositionInfo positionInfo;

enum ENUM_STOPLOSS_MODE
{
   STOPLOSS_BASKET = 0,
   STOPLOSS_EQUITY_PERCENT = 1,
   STOPLOSS_ACCOUNT_CURRENCY = 2
};

input double Lots = 0.01;
input int LotRepeatCount = 1;            // Times to repeat each lot size before increment (1 = standard martingale)
input double Profit_Close = 3.0;
input int Pair_Close_Min_Orders = 4;     // Pair-closing (oldest+newest) activates once a basket holds this many orders

input bool   Use_Profit_Trailing = true; // arm a money trailing stop before Profit_Close is reached
input double Trail_Start      = 3.0;     // arm trailing once basket/pair profit reaches this (money)
input double Trail_Give_Back  = 1.0;     // trail this far behind the peak (money)
input double Trail_Lock_Min   = 1.0;     // GUARANTEED minimum profit locked — a trailed close never banks less (keep < Trail_Start)
input bool   Trailing_Only_Exit = false; // true = ignore the fixed Profit_Close ceiling; the moving trailing line is the ONLY profit exit
input int Grid_Pips = 200;               // strict 200 pips
input int MaxOrdersPerSide = 99;
input double MaxLot = 0;                 // Max lot size per order (0 = no cap / unlimited)
input bool Dark_Buy_Signal = true;
input bool Dark_Sell_Signal = true;
input int MagicStart = 11214;

input bool Monday=true;
input bool Tuesday=true;
input bool Wednesday=true;
input bool Thursday=true;
input bool Friday=true;
input bool Saturday=false;
input bool Sunday=false;

input string StartHour="00:50";
input string EndHour="23:55";

input bool UseReversalHedge=true;
input int MinDistancePoints=50;

input bool   Block_Same_Price_Level = true;  // one order per side per whole price level
input double Price_Level_Size       = 0;     // 0 = AUTO (1.00 on 2/3-digit symbols e.g. XAUUSD, 10 pips on 4/5-digit FX)

input double MaxStopLoss=50000;
input ENUM_STOPLOSS_MODE MaxStopLoss_Mode = STOPLOSS_BASKET;

input bool UseNewsFilter=true;
input int NewsMinutesBefore=30;
input int NewsMinutesAfter=30;

struct Basket
{
   int dir;
   int magic;
   bool enabled;
   ulong tickets[];
   double lastLot;
   double lastPrice;

   bool     trailArmed;   // basket trailing switched on
   double   trailPeak;    // highest basket profit seen since arming
   bool     pairArmed;    // oldest+newest pair trailing switched on
   double   pairPeak;     // highest combined pair profit since arming
   ulong    pairOld;      // tickets the pair-trail state belongs to
   ulong    pairNew;
};

Basket BuyBasket;
Basket SellBasket;

datetime LastM1Bar=0;
bool TradingDisabled=false;
datetime DisableDate=0;

int Start_Hour, Start_Min, End_Hour, End_Min;

double BuyLastLot = 0.0;
double SellLastLot = 0.0;

void ParseTime(string value,int &hour,int &minute)
{
   hour=(int)StringToInteger(StringSubstr(value,0,2));
   minute=(int)StringToInteger(StringSubstr(value,3,2));
}

bool IsTradingDay()
{
   MqlDateTime dt;
   TimeToStruct(TimeCurrent(),dt);
   switch(dt.day_of_week)
   {
      case 0: return Sunday;
      case 1: return Monday;
      case 2: return Tuesday;
      case 3: return Wednesday;
      case 4: return Thursday;
      case 5: return Friday;
      case 6: return Saturday;
   }
   return false;
}

bool IsTradingTime()
{
   if(!IsTradingDay()) return false;
   MqlDateTime dt;
   TimeToStruct(TimeCurrent(),dt);
   int now = dt.hour*60 + dt.min;
   int start = Start_Hour*60 + Start_Min;
   int end = End_Hour*60 + End_Min;
   if(now<start || now>end) return false;
   return true;
}

double NormalizeLot(double lot)
{
   double step = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_STEP);
   double minLot = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MIN);
   double brokerMax = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MAX);

   if(MaxLot > 0 && lot > MaxLot)
      lot = MaxLot;

   if(step > 0)
      lot = MathCeil(lot / step) * step;

   if(lot < minLot)
      lot = minLot;

   if(lot > brokerMax)
   {
      Print("LOT LIMIT REACHED. Requested=",
            DoubleToString(lot,2),
            " Broker Max=",
            DoubleToString(brokerMax,2));
      return 0;
   }

   int digits = (step == 0.001) ? 3 : 2;
   return NormalizeDouble(lot, digits);
}

double GetNextLot(Basket &b)
{
   int repeat = (LotRepeatCount < 1) ? 1 : LotRepeatCount;

   double step = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_STEP);
   double tol  = (step > 0) ? step * 0.5 : 0.0005;

   double maxOpenLot = 0;
   int    atMax      = 0;   // open orders holding the largest lot

   for(int i=0;i<ArraySize(b.tickets);i++)
   {
      if(!PositionSelectByTicket(b.tickets[i])) continue;
      double v = PositionGetDouble(POSITION_VOLUME);
      if(v > maxOpenLot + tol) { maxOpenLot = v; atMax = 1; }
      else if(MathAbs(v - maxOpenLot) <= tol) atMax++;
   }

   if(maxOpenLot <= 0)
      return NormalizeLot(Lots);

   double next = (atMax < repeat) ? maxOpenLot : maxOpenLot + Lots;
   return NormalizeLot(next);   // NormalizeLot caps at MaxLot
}

int GetBasketPositions(Basket &b)
{
   ArrayResize(b.tickets,0);
   int total = PositionsTotal();
   for(int i=0;i<total;i++)
   {
      ulong ticket = PositionGetTicket(i);
      if(ticket==0) continue;
      if(!PositionSelectByTicket(ticket)) continue;
      if(PositionGetInteger(POSITION_MAGIC) != b.magic) continue;
      long type = PositionGetInteger(POSITION_TYPE);
      if(b.dir==1 && type!=POSITION_TYPE_BUY) continue;
      if(b.dir==-1 && type!=POSITION_TYPE_SELL) continue;
      int size = ArraySize(b.tickets);
      ArrayResize(b.tickets, size+1);
      b.tickets[size] = ticket;
   }
   return ArraySize(b.tickets);
}

void UpdateBasket(Basket &b)
{
   GetBasketPositions(b);
   if(ArraySize(b.tickets) == 0)   // basket flat -> reset trailing for the next cycle
   {
      b.trailArmed = false; b.trailPeak = 0;
      b.pairArmed = false; b.pairPeak = 0; b.pairOld = 0; b.pairNew = 0;
   }
   b.lastLot = 0;
   b.lastPrice = 0;
   datetime newestTime = 0;
   for(int i=0;i<ArraySize(b.tickets);i++)
   {
      ulong ticket = b.tickets[i];
      if(!PositionSelectByTicket(ticket)) continue;
      datetime openTime = (datetime)PositionGetInteger(POSITION_TIME);
      if(openTime >= newestTime)
      {
         newestTime = openTime;
         b.lastLot = PositionGetDouble(POSITION_VOLUME);
         b.lastPrice = PositionGetDouble(POSITION_PRICE_OPEN);
      }
   }
   if(b.lastLot > 0)
   {
      if(b.dir == 1) BuyLastLot = b.lastLot;
      else SellLastLot = b.lastLot;
   }
}

double BasketProfit(Basket &b)
{
   double result = 0;
   for(int i=0;i<ArraySize(b.tickets);i++)
   {
      if(PositionSelectByTicket(b.tickets[i]))
         result += PositionGetDouble(POSITION_PROFIT);
   }
   return result;
}

double LevelSize()
{
   if(Price_Level_Size > 0) return Price_Level_Size;
   int digits = (int)SymbolInfoInteger(Symbol(), SYMBOL_DIGITS);
   if(digits <= 3) return 1.0;
   double point = SymbolInfoDouble(Symbol(), SYMBOL_POINT);
   return point * ((digits == 5) ? 100 : 10);   // = 10 pips
}

long PriceLevel(double price)
{
   double size = LevelSize();
   if(size <= 0) size = 1.0;
   return (long)MathFloor(price / size);
}

bool IsOurSidePosition(int dir)
{
   if(PositionGetString(POSITION_SYMBOL) != Symbol()) return false;
   long magic = PositionGetInteger(POSITION_MAGIC);
   if(magic != BuyBasket.magic && magic != SellBasket.magic) return false;
   long type = PositionGetInteger(POSITION_TYPE);
   if(dir ==  1 && type != POSITION_TYPE_BUY)  return false;
   if(dir == -1 && type != POSITION_TYPE_SELL) return false;
   return true;
}

bool IsLevelTaken(int dir, double price)
{
   if(!Block_Same_Price_Level) return false;
   long level = PriceLevel(price);
   int total = PositionsTotal();
   for(int i=0;i<total;i++)
   {
      ulong ticket = PositionGetTicket(i);
      if(ticket==0) continue;
      if(!PositionSelectByTicket(ticket)) continue;
      if(!IsOurSidePosition(dir)) continue;
      if(PriceLevel(PositionGetDouble(POSITION_PRICE_OPEN)) == level)
         return true;
   }
   return false;
}

bool IsPriceTooClose(int dir, double price)
{
   if(MinDistancePoints <= 0) return false;
   double point = SymbolInfoDouble(Symbol(), SYMBOL_POINT);
   int total = PositionsTotal();
   for(int i=0;i<total;i++)
   {
      ulong ticket = PositionGetTicket(i);
      if(ticket==0) continue;
      if(!PositionSelectByTicket(ticket)) continue;
      if(!IsOurSidePosition(dir)) continue;
      double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
      if(MathAbs(price-openPrice) <= MinDistancePoints*point)
         return true;
   }
   return false;
}

bool OpenBasketOrder(Basket &b,double lot)
{
   lot = NormalizeLot(lot);
   if(lot <= 0) return false;

   double price;
   ENUM_ORDER_TYPE type;
   if(b.dir == 1)
   {
      price = SymbolInfoDouble(Symbol(), SYMBOL_ASK);
      type = ORDER_TYPE_BUY;
   }
   else
   {
      price = SymbolInfoDouble(Symbol(), SYMBOL_BID);
      type = ORDER_TYPE_SELL;
   }

   if(IsLevelTaken(b.dir, price))
   {
      Print("BLOCKED SAME PRICE LEVEL ", b.dir==1?"BUY ":"SELL ",
            DoubleToString(price,_Digits), " level=", PriceLevel(price));
      return false;
   }

   if(IsPriceTooClose(b.dir, price))
   {
      Print("BLOCKED DUPLICATE PRICE ", b.dir==1?"BUY ":"SELL ",
            DoubleToString(price,_Digits));
      return false;
   }

   trade.SetExpertMagicNumber(b.magic);
   string comment = (b.dir == 1) ? "BLACK WOLF BUY" : "BLACK WOLF SELL";
   bool result = trade.PositionOpen(Symbol(), type, lot, price, 0, 0, comment);

   if(result)
   {
      if(b.dir == 1) BuyLastLot = lot;
      else SellLastLot = lot;
      b.lastPrice = price;
      Print("OPEN ", b.dir==1?"BUY ":"SELL ", "LOT=", DoubleToString(lot,2));
      return true;
   }
   Print("ORDER ERROR ", trade.ResultRetcodeDescription());
   return false;
}

bool ClosePosition(ulong ticket)
{
   if(!PositionSelectByTicket(ticket)) return false;
   return trade.PositionClose(ticket);
}

void CloseBasket(Basket &b)
{
   for(int i=ArraySize(b.tickets)-1; i>=0; i--)
      ClosePosition(b.tickets[i]);
   ArrayResize(b.tickets,0);
   b.lastPrice = 0;
   b.trailArmed = false; b.trailPeak = 0;
   b.pairArmed = false; b.pairPeak = 0; b.pairOld = 0; b.pairNew = 0;
}

bool GetOldNew(Basket &b, ulong &oldest, ulong &newest)
{
   oldest = 0;
   newest = 0;
   datetime oldTime = LONG_MAX;
   datetime newTime = 0;
   for(int i=0;i<ArraySize(b.tickets);i++)
   {
      ulong ticket = b.tickets[i];
      if(!PositionSelectByTicket(ticket)) continue;
      datetime t = (datetime)PositionGetInteger(POSITION_TIME);
      if(t < oldTime) { oldTime = t; oldest = ticket; }
      if(t > newTime) { newTime = t; newest = ticket; }
   }
   return(oldest>0 && newest>0);
}

bool CloseProfitPairs(Basket &b)
{
   if(ArraySize(b.tickets) < 2) return false;
   ulong oldTicket, newTicket;
   if(!GetOldNew(b, oldTicket, newTicket)) return false;
   double profit = 0;
   if(PositionSelectByTicket(oldTicket)) profit += PositionGetDouble(POSITION_PROFIT);
   if(PositionSelectByTicket(newTicket)) profit += PositionGetDouble(POSITION_PROFIT);

   if(!(Trailing_Only_Exit && Use_Profit_Trailing) && profit >= Profit_Close)
   {
      ClosePosition(oldTicket);
      ClosePosition(newTicket);
      b.pairArmed = false; b.pairPeak = 0; b.pairOld = 0; b.p

Показано первые 50% кода (символов: 17550)

Открыть по тарифу

Купите тариф с пакетом открытий или войдите, если тариф уже есть. Открытый файл останется в вашем кабинете навсегда.

Разовая покупка файла

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

Чтобы оформить заказ, введите email и перейдите к оплате.

Стоимость полного кода
1 755₽ ≈ $21.45