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? После подтверждения оплаты файл сохранится в кабинете на этот email. Если аккаунта ещё нет, мы создадим его и пришлём ссылку для задания пароля. Если аккаунт уже есть, покупка добавится в него. Укажите адрес, к которому у вас есть доступ.
Чтобы оформить заказ, введите email и перейдите к оплате.