877 lines
34 KiB
MQL5
//+------------------------------------------------------------------+
| |||
//| LW_SMC_CHOCH_OCO_EA.mq5 |
| |||
//| CHoCH -> OCO pending orders + trailing stop |
| |||
//| |
| |||
//| Структура рынка считается так же, как в индикаторе |
| |||
//| LW_MarketStructure_SMC.mq5 (фрактальные swing H/L + BOS + CHoCH). |
| |||
//| |
| |||
//| Логика: |
| |||
//| 1) На новом ЗАКРЫТОМ баре ищем последний CHoCH (Change of |
| |||
//| Character = пробой swing-уровня против текущего тренда). |
| |||
//| 2) На новый CHoCH выставляем два отложенных ордера (OCO): |
| |||
//| - OCO_STOP : BUY STOP выше цены + SELL STOP ниже цены; |
| |||
//| - OCO_LIMIT: BUY LIMIT ниже цены + SELL LIMIT выше цены. |
| |||
//| 3) Как только один ордер исполнился — второй удаляется (OCO). |
| |||
//| 4) Выход — по трейлинг-стопу (опц. безубыток / фикс. TP). |
| |||
//| |
| |||
//| ВАЖНО: MT5 не принимает отложенный ордер с ценой на "неверной" |
| |||
//| стороне рынка (BUY LIMIT выше цены отклоняется сервером). |
| |||
//| Поэтому "BUY сверху + SELL снизу" реализовано стоп-ордерами |
| |||
//| (OCO_STOP), а лимитные ордера возможны только в зеркальной |
| |||
//| геометрии (BUY ниже, SELL выше) — режим OCO_LIMIT. |
| |||
//+------------------------------------------------------------------+
| |||
#property copyright "Lonely Wolf"
| |||
#property version "1.01"
| |||
#property description "CHoCH (LW_MarketStructure_SMC) -> OCO pending orders (BUY above / SELL below) + trailing stop."
| |||
#property description "Detection uses closed bars only (no repaint)."
| |||
| |||
#include <Trade\Trade.mqh>
| |||
| |||
//============================== ENUMS ================================
| |||
enum ENUM_OCO_MODE
| |||
{
| |||
OCO_STOP = 0, // Пробой: BUY STOP выше + SELL STOP ниже
| |||
OCO_LIMIT = 1 // Откат: BUY LIMIT ниже + SELL LIMIT выше
| |||
};
| |||
| |||
enum ENUM_SL_MODE
| |||
{
| |||
SL_STRUCT = 0, // По структуре (swing-экстремум CHoCH)
| |||
SL_FIXED = 1 // Фиксированный в пунктах
| |||
};
| |||
| |||
enum ENUM_SL_ANCHOR
| |||
{
| |||
SL_ANCHOR_NEAR = 0, // Ближний swing (тейтовый SL)
| |||
SL_ANCHOR_FAR = 1 // Дальний swing (широкий SL)
| |||
};
| |||
| |||
//============================== INPUTS ===============================
| |||
input group "=== Идентификация ==="
| |||
input long InpMagic = 20261023; // Магический номер
| |||
input double InpLot = 0.10; // Объём (лоты), если риск-лот выключен
| |||
input bool InpUseRiskPercent = false; // Считать лот от риска (% баланса)
| |||
input double InpRiskPercent = 1.0; // Риск на сделку (% баланса)
| |||
| |||
input group "=== Детекция CHoCH (как в индикаторе) ==="
| |||
input int InpSwingN = 5; // Фрактал: баров с каждой стороны
| |||
input bool InpBreakOnClose = true; // Пробой подтверждается по CLOSE свечи
| |||
input int InpMaxBars = 1500; // Сколько последних закрытых баров анализировать
| |||
input bool InpTradeExistingSignal = false; // Торговать CHoCH, который уже был на старте EA
| |||
| |||
input group "=== OCO: отложенные ордера ==="
| |||
input ENUM_OCO_MODE InpOCOMode = OCO_STOP; // Тип OCO-сетапа
| |||
input double InpOffsetPoints = 100.0; // Отступ ордеров от цены (пункты)
| |||
input int InpExpireBars = 0; // Срок жизни ордеров (баров; 0 = не истекают)
| |||
input bool InpCancelOnNewSignal = true; // Снимать прошлые ордера при новом CHoCH
| |||
input bool InpOneSetupAtATime = true; // Не выставлять новый сетап, пока есть позиция
| |||
input int InpMaxSpreadPoints = 0; // Макс. спред (пункты; 0 = без контроля)
| |||
input int InpDeviationPoints = 30; // Допустимое отклонение цены (пункты)
| |||
| |||
input group "=== Стоп-лосс / тейк-профит ==="
| |||
input ENUM_SL_MODE InpSLMode = SL_STRUCT; // Режим Stop Loss
| |||
input ENUM_SL_ANCHOR InpSLAnchor = SL_ANCHOR_FAR; // Опора структурного SL (где стоит стоп)
| |||
input int InpSLBufferPoints = 30; // Буфер SL за swing-экстремум (пункты)
| |||
input double InpSLFallbackPoints = 500.0; // Резервный SL (пункты), если нет валидной структуры (0 = не входить)
| |||
input double InpMaxSLPoints = 1500.0; // Макс. ширина SL (пункты; 0 = без ограничения)
| |||
input double InpSLPoints = 500.0; // Фикс. SL от входа (пункты) для режима SL_FIXED
| |||
input double InpTPPoints = 0.0; // TP от входа (пункты; 0 = без TP)
| |||
input bool InpAllowNoSL = false; // ВНИМАНИЕ: разрешить вход без SL (риск)
| |||
| |||
input group "=== Трейлинг-стоп ==="
| |||
input bool InpUseTrailing = true; // Трейлинг-стоп
| |||
input int InpTrailStartPoints = 300; // Старт трейлинга при такой прибыли (пункты)
| |||
input int InpTrailDistPoints = 200; // Дистанция трейлинга (пункты)
| |||
input int InpTrailStepPoints = 10; // Мин. шаг перемещения SL (пункты)
| |||
input bool InpUseBreakEven = false; // Перевод в безубыток
| |||
input int InpBETriggerPoints = 200; // Прибыль для перевода в безубыток (пункты)
| |||
input int InpBEBufferPoints = 10; // Смещение SL за цену входа (пункты)
| |||
| |||
input group "=== Прочее ==="
| |||
input bool InpVerboseLog = true; // Подробный лог
| |||
| |||
//============================== TYPES ================================
| |||
struct SChochEvent
| |||
{
| |||
datetime time; // время бара подтверждения CHoCH
| |||
int dir; // +1 = бычий CHoCH (разворот вверх), -1 = медвежий
| |||
double level; // пробитый swing-уровень
| |||
double oppLevel; // противоположный swing (вторая опора для SL)
| |||
bool oppValid; // противоположный swing найден
| |||
};
| |||
| |||
struct SSymbolMetrics
| |||
{
| |||
double point;
| |||
int digits;
| |||
double tickSize;
| |||
double tickValue;
| |||
double volMin;
| |||
double volMax;
| |||
double volStep;
| |||
double stopsLevel; // в цене
| |||
double freezeLevel; // в цене
| |||
bool valid;
| |||
};
| |||
| |||
//============================== GLOBALS ==============================
| |||
CTrade trade;
| |||
SSymbolMetrics g_sym;
| |||
datetime g_lastBarTime = 0;
| |||
datetime g_lastSignalTime = 0;
| |||
ulong g_ticketBuy = 0; // тикеты текущего OCO-сетапа
| |||
ulong g_ticketSell = 0;
| |||
| |||
//============================== INIT =================================
| |||
int OnInit()
| |||
{
| |||
if(InpLot <= 0.0 && !InpUseRiskPercent)
| |||
{
| |||
Print("LW_SMC_CHOCH_OCO_EA: InpLot must be > 0 (или включите риск-лот)");
| |||
return INIT_PARAMETERS_INCORRECT;
| |||
}
| |||
if(InpSwingN < 1 || InpMaxBars < InpSwingN * 2 + 10)
| |||
{
| |||
Print("LW_SMC_CHOCH_OCO_EA: invalid detection inputs");
| |||
return INIT_PARAMETERS_INCORRECT;
| |||
}
| |||
if(InpSLMode == SL_FIXED && InpSLPoints <= 0.0)
| |||
{
| |||
Print("LW_SMC_CHOCH_OCO_EA: InpSLPoints must be > 0 in SL_FIXED mode");
| |||
return INIT_PARAMETERS_INCORRECT;
| |||
}
| |||
| |||
trade.SetExpertMagicNumber(InpMagic);
| |||
trade.SetDeviationInPoints(InpDeviationPoints);
| |||
trade.SetTypeFillingBySymbol(_Symbol);
| |||
trade.SetAsyncMode(false);
| |||
| |||
RefreshSymbolMetrics();
| |||
| |||
if(InpSLMode == SL_STRUCT && InpAllowNoSL)
| |||
Print("LW_SMC_CHOCH_OCO_EA: WARNING - SL по структуре может отсутствовать, вход без SL разрешён (InpAllowNoSL=true).");
| |||
if(!InpUseTrailing && !InpUseBreakEven && InpTPPoints <= 0.0)
| |||
Print("LW_SMC_CHOCH_OCO_EA: WARNING - выход не управляется: трейлинг/безубыток выключены и TP=0.");
| |||
if(InpOCOMode == OCO_LIMIT)
| |||
Print("LW_SMC_CHOCH_OCO_EA: OCO_LIMIT - BUY LIMIT ниже цены, SELL LIMIT выше цены (зеркальная геометрия).");
| |||
| |||
return INIT_SUCCEEDED;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| |
| |||
//+------------------------------------------------------------------+
| |||
void OnDeinit(const int reason)
| |||
{
| |||
// Отложенные ордера намеренно не удаляем: ими управляет OCO и срок жизни.
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| |
| |||
//+------------------------------------------------------------------+
| |||
void OnTick()
| |||
{
| |||
ManageOCO(); // OCO: снятие парного ордера + истечение
| |||
ManageTrailing(); // трейлинг-стоп / безубыток
| |||
| |||
datetime bt = (datetime)SeriesInfoInteger(_Symbol, PERIOD_CURRENT, SERIES_LASTBAR_DATE);
| |||
if(bt == g_lastBarTime)
| |||
return;
| |||
g_lastBarTime = bt;
| |||
| |||
RefreshSymbolMetrics();
| |||
ProcessNewBar();
| |||
}
| |||
| |||
//============================== SYMBOL METRICS ========================
| |||
void RefreshSymbolMetrics()
| |||
{
| |||
g_sym.point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
| |||
g_sym.digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
| |||
g_sym.tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
| |||
if(g_sym.tickSize <= 0.0)
| |||
g_sym.tickSize = g_sym.point;
| |||
g_sym.tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
| |||
g_sym.volMin = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
| |||
g_sym.volMax = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
| |||
g_sym.volStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
| |||
g_sym.stopsLevel = (double)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL) * g_sym.point;
| |||
g_sym.freezeLevel= (double)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_FREEZE_LEVEL) * g_sym.point;
| |||
g_sym.valid = (g_sym.point > 0.0);
| |||
}
| |||
| |||
//============================== MAIN LOGIC ===========================
| |||
void ProcessNewBar()
| |||
{
| |||
if(!g_sym.valid)
| |||
return;
| |||
| |||
SChochEvent ev;
| |||
if(!FindLastCHoCH(ev))
| |||
return; // в окне анализа CHoCH нет
| |||
| |||
if(g_lastSignalTime == 0)
| |||
{
| |||
if(!InpTradeExistingSignal)
| |||
{
| |||
g_lastSignalTime = ev.time; // сигналы до старта EA не торгуем
| |||
if(InpVerboseLog)
| |||
PrintFormat("LW_SMC_CHOCH_OCO_EA: старт, CHoCH %s пропущен (InpTradeExistingSignal=false)",
| |||
TimeToString(ev.time, TIME_DATE | TIME_MINUTES));
| |||
return;
| |||
}
| |||
}
| |||
else
| |||
if(ev.time == g_lastSignalTime)
| |||
return; // этот CHoCH уже обработан
| |||
| |||
g_lastSignalTime = ev.time;
| |||
| |||
if(!IsTradeAllowed())
| |||
return;
| |||
| |||
if(InpOneSetupAtATime && CountOurPositions() > 0)
| |||
{
| |||
if(InpVerboseLog)
| |||
PrintFormat("LW_SMC_CHOCH_OCO_EA: CHoCH %s пропущен - открыта позиция",
| |||
TimeToString(ev.time, TIME_DATE | TIME_MINUTES));
| |||
return;
| |||
}
| |||
| |||
if(InpCancelOnNewSignal)
| |||
CancelAllOurPendings();
| |||
| |||
if(InpMaxSpreadPoints > 0)
| |||
{
| |||
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
| |||
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
| |||
if(ask > 0.0 && bid > 0.0)
| |||
{
| |||
double spreadPts = (ask - bid) / g_sym.point;
| |||
if(spreadPts > InpMaxSpreadPoints)
| |||
{
| |||
PrintFormat("LW_SMC_CHOCH_OCO_EA: CHoCH %s пропущен - спред %.1f > %.1f пт",
| |||
TimeToString(ev.time, TIME_DATE | TIME_MINUTES), spreadPts, (double)InpMaxSpreadPoints);
| |||
return;
| |||
}
| |||
}
| |||
}
| |||
| |||
PlaceOCO(ev);
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Поиск последнего CHoCH на закрытых барах |
| |||
//| (логика BOS/CHoCH повторяет индикатор LW_MarketStructure_SMC) |
| |||
//+------------------------------------------------------------------+
| |||
bool FindLastCHoCH(SChochEvent &ev)
| |||
{
| |||
int need = InpMaxBars + InpSwingN * 2 + 10;
| |||
| |||
MqlRates rates[];
| |||
ArraySetAsSeries(rates, false);
| |||
int n = CopyRates(_Symbol, PERIOD_CURRENT, 0, need, rates);
| |||
if(n < InpSwingN * 2 + 10)
| |||
return false;
| |||
| |||
int last = n - 2; // последний ЗАКРЫТЫЙ бар (без repaint)
| |||
if(last < InpSwingN * 2)
| |||
return false;
| |||
| |||
int start = (int)MathMax((double)InpSwingN, (double)(last - InpMaxBars + 1));
| |||
| |||
//--- Pass 1: фрактальные swing-экстремумы (как в индикаторе)
| |||
char sh[], sl[];
| |||
ArrayResize(sh, n);
| |||
ArrayResize(sl, n);
| |||
ArrayInitialize(sh, 0);
| |||
ArrayInitialize(sl, 0);
| |||
| |||
for(int i = start; i <= last - InpSwingN; i++)
| |||
{
| |||
bool isHigh = true, isLow = true;
| |||
for(int k = 1; k <= InpSwingN; k++)
| |||
{
| |||
if(rates[i].high <= rates[i - k].high || rates[i].high <= rates[i + k].high)
| |||
isHigh = false;
| |||
if(rates[i].low >= rates[i - k].low || rates[i].low >= rates[i + k].low)
| |||
isLow = false;
| |||
}
| |||
if(isHigh)
| |||
sh[i] = 1;
| |||
if(isLow)
| |||
sl[i] = 1;
| |||
}
| |||
| |||
//--- Pass 2: BOS / CHoCH
| |||
bool found = false;
| |||
int trend = 0; // 1 = up, -1 = down, 0 = neutral
| |||
double refHigh = 0.0, refLow = 0.0;
| |||
int refHighBar = -1, refLowBar = -1;
| |||
bool haveHigh = false, haveLow = false;
| |||
| |||
for(int i = start + InpSwingN; i <= last; i++)
| |||
{
| |||
int j = i - InpSwingN; // swing подтверждён на баре j
| |||
| |||
if(sh[j] == 1)
| |||
{
| |||
refHigh = rates[j].high;
| |||
refHighBar = j;
| |||
haveHigh = true;
| |||
}
| |||
if(sl[j] == 1)
| |||
{
| |||
refLow = rates[j].low;
| |||
refLowBar = j;
| |||
haveLow = true;
| |||
}
| |||
| |||
double upPx = InpBreakOnClose ? rates[i].close : rates[i].high;
| |||
double downPx = InpBreakOnClose ? rates[i].close : rates[i].low;
| |||
| |||
if(haveHigh && upPx > refHigh) // бычий пробой
| |||
{
| |||
bool isBOS = (trend != -1); // против нисходящего тренда = CHoCH
| |||
if(!isBOS)
| |||
{
| |||
ev.time = rates[i].time;
| |||
ev.dir = 1;
| |||
ev.level = refHigh;
| |||
ev.oppLevel = refLow;
| |||
ev.oppValid = haveLow;
| |||
found = true;
| |||
}
| |||
trend = 1;
| |||
haveHigh = false; // уровень израсходован
| |||
}
| |||
else
| |||
if(haveLow && downPx < refLow) // медвежий пробой
| |||
{
| |||
bool isBOS = (trend != 1); // против восходящего тренда = CHoCH
| |||
if(!isBOS)
| |||
{
| |||
ev.time = rates[i].time;
| |||
ev.dir = -1;
| |||
ev.level = refLow;
| |||
ev.oppLevel = refHigh;
| |||
ev.oppValid = haveHigh;
| |||
found = true;
| |||
}
| |||
trend = -1;
| |||
haveLow = false;
| |||
}
| |||
}
| |||
| |||
return found;
| |||
}
| |||
| |||
//============================== OCO PLACEMENT ========================
| |||
void PlaceOCO(const SChochEvent &ev)
| |||
{
| |||
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
| |||
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
| |||
if(ask <= 0.0 || bid <= 0.0)
| |||
return;
| |||
| |||
g_ticketBuy = 0; // новая группа: старые танкеты недействительны
| |||
g_ticketSell = 0;
| |||
| |||
double point = g_sym.point;
| |||
double minDist = MathMax(g_sym.stopsLevel, g_sym.point);
| |||
double off = InpOffsetPoints * point;
| |||
if(off < minDist) // соблюдаем Stops Level
| |||
off = minDist;
| |||
| |||
double buyPx, sellPx;
| |||
if(InpOCOMode == OCO_STOP)
| |||
{
| |||
buyPx = ask + off; // BUY STOP выше цены
| |||
sellPx = bid - off; // SELL STOP ниже цены
| |||
}
| |||
else
| |||
{
| |||
buyPx = ask - off; // BUY LIMIT ниже цены
| |||
sellPx = bid + off; // SELL LIMIT выше цены
| |||
}
| |||
buyPx = NormalizeDouble(buyPx, g_sym.digits);
| |||
sellPx = NormalizeDouble(sellPx, g_sym.digits);
| |||
| |||
string cmt = "CHOCH_" + (string)(long)ev.time;
| |||
| |||
//--- BUY сторона
| |||
double buySL = 0.0;
| |||
bool buySLok = BuildSL(1, buyPx, ev, buySL);
| |||
if(buySLok || InpAllowNoSL)
| |||
{
| |||
double buyTP = BuildTP(1, buyPx);
| |||
double lot = CalcLot(buyPx, buySL);
| |||
if(lot > 0.0)
| |||
{
| |||
bool ok = (InpOCOMode == OCO_STOP)
| |||
? trade.BuyStop(lot, buyPx, _Symbol, buySL, buyTP, ORDER_TIME_GTC, 0, cmt)
| |||
: trade.BuyLimit(lot, buyPx, _Symbol, buySL, buyTP, ORDER_TIME_GTC, 0, cmt);
| |||
if(ok && trade.ResultRetcode() == TRADE_RETCODE_DONE)
| |||
{
| |||
g_ticketBuy = trade.ResultOrder();
| |||
if(InpVerboseLog)
| |||
PrintFormat("LW_SMC_CHOCH_OCO_EA: %s BUY %.2f @ %.5f SL %.5f TP %.5f #%I64u",
| |||
(InpOCOMode == OCO_STOP ? "BUY STOP" : "BUY LIMIT"),
| |||
lot, buyPx, buySL, buyTP, g_ticketBuy);
| |||
}
| |||
else
| |||
PrintFormat("LW_SMC_CHOCH_OCO_EA: BUY pending failed: %d %s",
| |||
trade.ResultRetcode(), trade.ResultComment());
| |||
}
| |||
}
| |||
else
| |||
PrintFormat("LW_SMC_CHOCH_OCO_EA: BUY ордер не выставлен - нет валидного SL (структура и резерв = 0)");
| |||
| |||
//--- SELL сторона
| |||
double sellSL = 0.0;
| |||
bool sellSLok = BuildSL(-1, sellPx, ev, sellSL);
| |||
if(sellSLok || InpAllowNoSL)
| |||
{
| |||
double sellTP = BuildTP(-1, sellPx);
| |||
double lot = CalcLot(sellPx, sellSL);
| |||
if(lot > 0.0)
| |||
{
| |||
bool ok = (InpOCOMode == OCO_STOP)
| |||
? trade.SellStop(lot, sellPx, _Symbol, sellSL, sellTP, ORDER_TIME_GTC, 0, cmt)
| |||
: trade.SellLimit(lot, sellPx, _Symbol, sellSL, sellTP, ORDER_TIME_GTC, 0, cmt);
| |||
if(ok && trade.ResultRetcode() == TRADE_RETCODE_DONE)
| |||
{
| |||
g_ticketSell = trade.ResultOrder();
| |||
if(InpVerboseLog)
| |||
PrintFormat("LW_SMC_CHOCH_OCO_EA: %s SELL %.2f @ %.5f SL %.5f TP %.5f #%I64u",
| |||
(InpOCOMode == OCO_STOP ? "SELL STOP" : "SELL LIMIT"),
| |||
lot, sellPx, sellSL, sellTP, g_ticketSell);
| |||
}
| |||
else
| |||
PrintFormat("LW_SMC_CHOCH_OCO_EA: SELL pending failed: %d %s",
| |||
trade.ResultRetcode(), trade.ResultComment());
| |||
}
| |||
}
| |||
else
| |||
PrintFormat("LW_SMC_CHOCH_OCO_EA: SELL ордер не выставлен - нет валидного SL (структура и резерв = 0)");
| |||
| |||
if(InpVerboseLog)
| |||
PrintFormat("LW_SMC_CHOCH_OCO_EA: CHoCH %s (%s) level %.5f -> OCO %s",
| |||
TimeToString(ev.time, TIME_DATE | TIME_MINUTES),
| |||
(ev.dir > 0 ? "bullish" : "bearish"), ev.level,
| |||
(InpOCOMode == OCO_STOP ? "stops" : "limits"));
| |||
}
| |||
| |||
//============================== SL / TP ==============================
| |||
//+------------------------------------------------------------------+
| |||
//| SL: фиксированный или по структуре. |
| |||
//| Для BUY берём опору НИЖЕ входа, для SELL — ВЫШЕ входа; |
| |||
//| из валидных опор (пробитый swing и противоположный swing) |
| |||
//| выбираем ближнюю (тейтовую) или дальнюю (широкую) по InpSLAnchor. |
| |||
//| Если валидной опоры нет — резервный SL в пунктах (если задан). |
| |||
//+------------------------------------------------------------------+
| |||
//| Ограничение ширины SL: если структурная опора слишком далеко, |
| |||
//| SL подрезается до InpMaxSLPoints. |
| |||
//+------------------------------------------------------------------+
| |||
double CapSL(const int dir, const double entry, const double sl)
| |||
{
| |||
if(InpMaxSLPoints <= 0.0 || sl <= 0.0)
| |||
return sl;
| |||
| |||
double maxD = InpMaxSLPoints * g_sym.point;
| |||
if(MathAbs(entry - sl) <= maxD)
| |||
return sl;
| |||
| |||
return (dir > 0) ? (entry - maxD) : (entry + maxD);
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
bool BuildSL(const int dir, const double entry, const SChochEvent &ev, double &sl)
| |||
{
| |||
sl = 0.0;
| |||
double point = g_sym.point;
| |||
double minDist = MathMax(g_sym.stopsLevel, g_sym.point);
| |||
| |||
if(InpSLMode == SL_FIXED)
| |||
{
| |||
double d = InpSLPoints * point;
| |||
if(d < minDist)
| |||
d = minDist;
| |||
sl = (dir > 0) ? entry - d : entry + d;
| |||
sl = NormalizeDouble(sl, g_sym.digits);
| |||
return true;
| |||
}
| |||
| |||
double anchors[2];
| |||
int cnt = 0;
| |||
if(ev.level > 0.0)
| |||
anchors[cnt++] = ev.level;
| |||
if(ev.oppValid && ev.oppLevel > 0.0)
| |||
anchors[cnt++] = ev.oppLevel;
| |||
| |||
double buf = InpSLBufferPoints * point;
| |||
bool found = false;
| |||
| |||
for(int k = 0; k < cnt; k++)
| |||
{
| |||
double cand = (dir > 0) ? (anchors[k] - buf) : (anchors[k] + buf);
| |||
| |||
if(dir > 0)
| |||
{
| |||
if(cand > entry - minDist) // опора выше/слишком близко к входу
| |||
continue;
| |||
if(!found || (InpSLAnchor == SL_ANCHOR_FAR ? (cand < sl) : (cand > sl)))
| |||
{
| |||
sl = cand;
| |||
found = true;
| |||
}
| |||
}
| |||
else
| |||
{
| |||
if(cand < entry + minDist)
| |||
continue;
| |||
if(!found || (InpSLAnchor == SL_ANCHOR_FAR ? (cand > sl) : (cand < sl)))
| |||
{
| |||
sl = cand;
| |||
found = true;
| |||
}
| |||
}
| |||
}
| |||
| |||
if(!found)
| |||
{
| |||
// Структурной опоры нет (цена уже ушла от swing): резервный SL в пунктах.
| |||
if(InpSLFallbackPoints <= 0.0)
| |||
return false;
| |||
double d = InpSLFallbackPoints * point;
| |||
if(d < minDist)
| |||
d = minDist;
| |||
sl = CapSL(dir, entry, (dir > 0) ? entry - d : entry + d);
| |||
sl = NormalizeDouble(sl, g_sym.digits);
| |||
return true;
| |||
}
| |||
| |||
sl = CapSL(dir, entry, sl);
| |||
sl = NormalizeDouble(sl, g_sym.digits);
| |||
return true;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
double BuildTP(const int dir, const double entry)
| |||
{
| |||
if(InpTPPoints <= 0.0)
| |||
return 0.0;
| |||
double d = InpTPPoints * g_sym.point;
| |||
double tp = (dir > 0) ? entry + d : entry - d;
| |||
return NormalizeDouble(tp, g_sym.digits);
| |||
}
| |||
| |||
//============================== SIZING ===============================
| |||
double NormalizeLot(double lot)
| |||
{
| |||
double step = (g_sym.volStep > 0.0) ? g_sym.volStep : 0.01;
| |||
lot = MathRound(lot / step) * step;
| |||
if(lot < g_sym.volMin)
| |||
lot = g_sym.volMin;
| |||
if(lot > g_sym.volMax)
| |||
lot = g_sym.volMax;
| |||
return NormalizeDouble(lot, 3);
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
double CalcLot(const double entry, const double sl)
| |||
{
| |||
if(!InpUseRiskPercent || sl <= 0.0)
| |||
return NormalizeLot(InpLot);
| |||
| |||
double slDist = MathAbs(entry - sl);
| |||
if(slDist <= 0.0 || g_sym.tickSize <= 0.0 || g_sym.tickValue <= 0.0)
| |||
return NormalizeLot(InpLot);
| |||
| |||
double balance = AccountInfoDouble(ACCOUNT_BALANCE);
| |||
double riskMoney = balance * InpRiskPercent / 100.0;
| |||
double lossPerLot = (slDist / g_sym.tickSize) * g_sym.tickValue;
| |||
if(lossPerLot <= 0.0)
| |||
return NormalizeLot(InpLot);
| |||
| |||
return NormalizeLot(riskMoney / lossPerLot);
| |||
}
| |||
| |||
//============================== OCO MANAGEMENT ======================
| |||
int CollectOurPendings(ulong &tickets[])
| |||
{
| |||
ArrayResize(tickets, 0);
| |||
int cnt = 0;
| |||
for(int i = OrdersTotal() - 1; i >= 0; i--)
| |||
{
| |||
ulong ticket = OrderGetTicket(i);
| |||
if(ticket == 0)
| |||
continue;
| |||
if(!IsOurPending(ticket))
| |||
continue;
| |||
cnt++;
| |||
ArrayResize(tickets, cnt);
| |||
tickets[cnt - 1] = ticket;
| |||
}
| |||
return cnt;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
bool IsOurPending(const ulong ticket)
| |||
{
| |||
if(ticket == 0)
| |||
return false;
| |||
if(!OrderSelect(ticket))
| |||
return false;
| |||
if(OrderGetString(ORDER_SYMBOL) != _Symbol)
| |||
return false;
| |||
if(OrderGetInteger(ORDER_MAGIC) != InpMagic)
| |||
return false;
| |||
| |||
ENUM_ORDER_TYPE t = (ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE);
| |||
return (t == ORDER_TYPE_BUY_LIMIT || t == ORDER_TYPE_SELL_LIMIT ||
| |||
t == ORDER_TYPE_BUY_STOP || t == ORDER_TYPE_SELL_STOP ||
| |||
t == ORDER_TYPE_BUY_STOP_LIMIT || t == ORDER_TYPE_SELL_STOP_LIMIT);
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
int CountOurPositions()
| |||
{
| |||
int cnt = 0;
| |||
for(int i = PositionsTotal() - 1; i >= 0; i--)
| |||
{
| |||
ulong ticket = PositionGetTicket(i);
| |||
if(ticket == 0)
| |||
continue;
| |||
if(PositionGetString(POSITION_SYMBOL) != _Symbol)
| |||
continue;
| |||
if(PositionGetInteger(POSITION_MAGIC) != InpMagic)
| |||
continue;
| |||
cnt++;
| |||
}
| |||
return cnt;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
bool GroupHasPosition(const string grp)
| |||
{
| |||
if(grp == "")
| |||
return false;
| |||
for(int i = PositionsTotal() - 1; i >= 0; i--)
| |||
{
| |||
ulong ticket = PositionGetTicket(i);
| |||
if(ticket == 0)
| |||
continue;
| |||
if(PositionGetString(POSITION_SYMBOL) != _Symbol)
| |||
continue;
| |||
if(PositionGetInteger(POSITION_MAGIC) != InpMagic)
| |||
continue;
| |||
if(PositionGetString(POSITION_COMMENT) == grp)
| |||
return true;
| |||
}
| |||
return false;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
void DeletePending(const ulong ticket)
| |||
{
| |||
if(ticket == 0)
| |||
return;
| |||
if(!IsOurPending(ticket))
| |||
return; // ордер уже исполнен/снят — не трогаем
| |||
if(!trade.OrderDelete(ticket))
| |||
PrintFormat("LW_SMC_CHOCH_OCO_EA: OrderDelete #%I64u failed: %d %s",
| |||
ticket, trade.ResultRetcode(), trade.ResultComment());
| |||
else
| |||
if(InpVerboseLog)
| |||
PrintFormat("LW_SMC_CHOCH_OCO_EA: снят отложенный ордер #%I64u (OCO)", ticket);
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
void CancelAllOurPendings()
| |||
{
| |||
ulong tickets[];
| |||
int cnt = CollectOurPendings(tickets);
| |||
for(int k = 0; k < cnt; k++)
| |||
DeletePending(tickets[k]);
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
void ManageOCO()
| |||
{
| |||
//--- (a) точная связка по тикетам текущего сетапа: сработал один -> снять второй
| |||
if(g_ticketBuy != 0 && g_ticketSell != 0)
| |||
{
| |||
bool buyFilled = PositionSelectByTicket(g_ticketBuy);
| |||
bool sellFilled = PositionSelectByTicket(g_ticketSell);
| |||
if(buyFilled && !sellFilled)
| |||
{
| |||
DeletePending(g_ticketSell);
| |||
g_ticketSell = 0;
| |||
}
| |||
else
| |||
if(sellFilled && !buyFilled)
| |||
{
| |||
DeletePending(g_ticketBuy);
| |||
g_ticketBuy = 0;
| |||
}
| |||
}
| |||
| |||
ulong tickets[];
| |||
int cnt = CollectOurPendings(tickets);
| |||
if(cnt == 0)
| |||
return;
| |||
| |||
//--- (b)/(c)/(d) страховки и срок жизни
| |||
for(int k = 0; k < cnt; k++)
| |||
{
| |||
ulong ticket = tickets[k];
| |||
if(!OrderSelect(ticket))
| |||
continue;
| |||
| |||
string grp = OrderGetString(ORDER_COMMENT);
| |||
| |||
// (b) по группе уже открыта позиция -> это исполненный OCO, снимаем остаток
| |||
if(GroupHasPosition(grp))
| |||
{
| |||
DeletePending(ticket);
| |||
continue;
| |||
}
| |||
| |||
// (c) срок жизни ордера истёк
| |||
if(InpExpireBars > 0)
| |||
{
| |||
datetime setup = (datetime)OrderGetInteger(ORDER_TIME_SETUP);
| |||
if(TimeCurrent() - setup >= (long)InpExpireBars * PeriodSeconds())
| |||
{
| |||
DeletePending(ticket);
| |||
continue;
| |||
}
| |||
}
| |||
| |||
// (d) страховка после рестарта EA: позиция есть, а связка неизвестна
| |||
if(InpOneSetupAtATime && CountOurPositions() > 0)
| |||
DeletePending(ticket);
| |||
}
| |||
}
| |||
| |||
//============================== TRAILING ==============================
| |||
void ManageTrailing()
| |||
{
| |||
if(!InpUseTrailing && !InpUseBreakEven)
| |||
return;
| |||
| |||
double point = g_sym.point;
| |||
int digits = g_sym.digits;
| |||
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
| |||
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
| |||
if(bid <= 0.0 || ask <= 0.0)
| |||
return;
| |||
| |||
for(int i = PositionsTotal() - 1; i >= 0; i--)
| |||
{
| |||
ulong ticket = PositionGetTicket(i);
| |||
if(ticket == 0)
| |||
continue;
| |||
if(PositionGetString(POSITION_SYMBOL) != _Symbol)
| |||
continue;
| |||
if(PositionGetInteger(POSITION_MAGIC) != InpMagic)
| |||
continue;
| |||
| |||
ENUM_POSITION_TYPE pt = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
| |||
double entry = PositionGetDouble(POSITION_PRICE_OPEN);
| |||
double sl = PositionGetDouble(POSITION_SL);
| |||
double tp = PositionGetDouble(POSITION_TP);
| |||
| |||
double price = (pt == POSITION_TYPE_BUY) ? bid : ask;
| |||
double profitPts = (pt == POSITION_TYPE_BUY) ? (bid - entry) / point
| |||
: (entry - ask) / point;
| |||
double bestSL = sl;
| |||
| |||
if(pt == POSITION_TYPE_BUY)
| |||
{
| |||
if(InpUseBreakEven && profitPts >= InpBETriggerPoints)
| |||
{
| |||
double be = NormalizeDouble(entry + InpBEBufferPoints * point, digits);
| |||
if(be > bestSL)
| |||
bestSL = be;
| |||
}
| |||
if(InpUseTrailing && profitPts >= InpTrailStartPoints)
| |||
{
| |||
double tr = NormalizeDouble(price - InpTrailDistPoints * point, digits);
| |||
if(tr > bestSL)
| |||
bestSL = tr;
| |||
}
| |||
| |||
double maxSL = NormalizeDouble(price - MathMax(g_sym.stopsLevel, point), digits);
| |||
if(bestSL > maxSL)
| |||
bestSL = maxSL; // Stops Level
| |||
if(tp > 0.0 && bestSL > tp - point)
| |||
bestSL = NormalizeDouble(tp - point, digits); // SL ниже TP
| |||
if(bestSL <= 0.0)
| |||
continue;
| |||
if(sl > 0.0 && bestSL <= sl + InpTrailStepPoints * point)
| |||
continue; // шаг перемещения
| |||
if(sl == 0.0 || bestSL > sl)
| |||
if(!trade.PositionModify(ticket, bestSL, tp))
| |||
PrintFormat("LW_SMC_CHOCH_OCO_EA: BUY SL modify #%I64u failed: %d %s",
| |||
ticket, trade.ResultRetcode(), trade.ResultComment());
| |||
}
| |||
else
| |||
{
| |||
if(InpUseBreakEven && profitPts >= InpBETriggerPoints)
| |||
{
| |||
double be = NormalizeDouble(entry - InpBEBufferPoints * point, digits);
| |||
if(bestSL == 0.0 || be < bestSL)
| |||
bestSL = be;
| |||
}
| |||
if(InpUseTrailing && profitPts >= InpTrailStartPoints)
| |||
{
| |||
double tr = NormalizeDouble(price + InpTrailDistPoints * point, digits);
| |||
if(bestSL == 0.0 || tr < bestSL)
| |||
bestSL = tr;
| |||
}
| |||
| |||
double minSL = NormalizeDouble(price + MathMax(g_sym.stopsLevel, point), digits);
| |||
if(bestSL == 0.0 || bestSL < minSL)
| |||
bestSL = minSL; // Stops Level
| |||
if(tp > 0.0 && bestSL < tp + point)
| |||
bestSL = NormalizeDouble(tp + point, digits); // SL выше TP
| |||
if(bestSL <= 0.0)
| |||
continue;
| |||
if(sl > 0.0 && bestSL >= sl - InpTrailStepPoints * point)
| |||
continue; // шаг перемещения
| |||
if(sl == 0.0 || bestSL < sl)
| |||
if(!trade.PositionModify(ticket, bestSL, tp))
| |||
PrintFormat("LW_SMC_CHOCH_OCO_EA: SELL SL modify #%I64u failed: %d %s",
| |||
ticket, trade.ResultRetcode(), trade.ResultComment());
| |||
}
| |||
}
| |||
}
| |||
| |||
//============================== COMMON ===============================
| |||
bool IsTradeAllowed()
| |||
{
| |||
if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
| |||
{
| |||
Print("LW_SMC_CHOCH_OCO_EA: trading is disabled in the terminal");
| |||
return false;
| |||
}
| |||
if(!MQLInfoInteger(MQL_TRADE_ALLOWED))
| |||
{
| |||
Print("LW_SMC_CHOCH_OCO_EA: trading is disabled for this EA");
| |||
return false;
| |||
}
| |||
if(!AccountInfoInteger(ACCOUNT_TRADE_ALLOWED))
| |||
{
| |||
Print("LW_SMC_CHOCH_OCO_EA: trading is disabled for this account");
| |||
return false;
| |||
}
| |||
if(!AccountInfoInteger(ACCOUNT_TRADE_EXPERT))
| |||
{
| |||
Print("LW_SMC_CHOCH_OCO_EA: automated trading is disabled for this account");
| |||
return false;
| |||
}
| |||
return true;
| |||
}
| |||
//+------------------------------------------------------------------+
|