Lizard MT5
Сообщите нам, если считаете, что материал размещён без разрешения.
Сообщить о материале
Обращение по товару «lizard mt5 2.1.mq5» отправится на рассмотрение. Укажите контакт для ответа и опишите причину.
О программе
Lizard MT5 2.1 — торговый советник для MetaTrader 5, предназначенный для работы на инструментах золота (XAUUSD/GOLD). Советник обрабатывает тиковые события, события сделок и таймерные вызовы, использует торговые функции MetaTrader 5 для выставления отложенных ордеров, изменения защитных уровней и закрытия позиций.
Логика построена на нескольких независимых стратегических модулях (зон A1–A3 и B1–B3), каждый из которых имеет собственный магический номер и настройки сопровождения. Предусмотрены фильтры по времени, торговым сессиям, волатильности и новостям, а также ограничение дневной просадки и управление объёмом позиции (фиксированный лот, процент риска или сумма на 0.01 лота).
Советник содержит информационную панель с отображением риска, защитных уровней, результатов по зонам, новостного статуса и кривой прибыли. В тестере доступен пользовательский критерий оценки. Поддерживается виртуальный стоп-лосс и несколько режимов трейлинга/безубытка.
Настройки программы
| Настройка | Описание |
|---|---|
| Notice | Информационное поле с предупреждением о рисках торговли. |
| SimpleForexTeam__ | Информационное поле со ссылкой на канал и бесплатные инструменты. |
| InpNewsSetup1 | Информационное поле с указанием включить WebRequest в настройках терминала для работы новостного фильтра. |
| InpWarn1 | Информационное предупреждение о нежелательности использования MQL VPS. |
| InpWarn2 | Информационное предупреждение о необходимости быстрого и стабильного VPS. |
| InpInfoBacktest | Информационное поле с напоминанием выполнить тестирование перед использованием. |
| InpTimeFilter | Выбор варианта фильтра по часам торговли: старый или новый. |
| InpFakeout | Выбор режима фильтра ложных пробоев: выключен, мягкий, обычный или агрессивный. |
| InpFakeoutMinutes | Таймер в минутах для обычного режима фильтра ложных пробоев, после которого убыточная позиция может быть закрыта. |
| Zone1_ID | Базовый магический номер, от которого отсчитываются магические номера стратегических модулей. |
| InpTrailMode | Выбор режима трейлинга и безубытка: динамический высокий, динамический низкий или фиксированный. |
| InpFixedBE | Уровень активации безубытка в пунктах для фиксированного режима трейлинга. |
| InpFixedTrail | Дистанция трейлинга в пунктах для фиксированного режима. |
| InpSLInfo | Справочное информационное поле с примерами значений стоп-лосса. |
| InpStopLossPts | Базовый размер стоп-лосса в пунктах. |
| InpVSL | Режим виртуального стоп-лосса: срабатывание на уровне заморозки, всегда активен или отключён. |
| InpCloseWindow | Количество минут до закрытия рынка, в течение которых советник удаляет отложенные ордера и может закрывать позиции. |
| InpOpenDelay | Задержка в минутах после открытия рынка, в течение которой новые входы не выполняются. |
| InpCloseWeekend | Включает закрытие позиций советника перед выходными при попадании в окно закрытия рынка. |
| InpNewsSetup3 | Информационное поле с напоминанием, что новостному фильтру требуется разрешение WebRequest. |
| InpNFP_Enable | Включает дополнительную защиту вокруг времени публикации NFP. |
| InpNFPWeek | Режим недели NFP: выключен, блокировка торговли с понедельника по пятницу или уменьшение лота вдвое. |
| InpNews_High | Включает блокировку торговли вокруг новостей высокой важности. |
| InpNews_Holidays | Включает запрет торговли в дни праздников США. |
| Inp_GMT_Winter | Зимнее смещение GMT брокера, используемое в тестере и при ручной настройке. |
| Inp_GMT_Summer | Летнее смещение GMT брокера, используемое в тестере и при ручной настройке. |
| InpGMT_AutoDetect | Включает автоматическое определение смещения GMT брокера в реальной торговле; при выключении используются заданные зимнее и летнее смещения. |
| InpNFP_CloseOpen | Включает закрытие открытых позиций советника при активации новостного фильтра. |
| InpNFP_ClosePending | Включает удаление отложенных ордеров советника при активации новостного фильтра. |
| InpNFP_Before | Количество минут до новости, в течение которых торговля блокируется. |
| InpNFP_After | Количество минут после новости, в течение которых торговля блокируется. |
| InpRiskInfo | Информационное поле, поясняющее, что настройки лота не ограничивают убыток, для этого служит дневная просадка. |
| InpLotMode | Выбор способа расчёта объёма: фиксированный лот, процент риска или сумма на 0.01 лота. |
| InpFixLot | Фиксированный размер лота, используемый в режиме фиксированного объёма. |
| InpRiskPct | Процент риска на сделку, используемый в режиме расчёта по риску. |
| InpDollarPer001 | Сумма в долларах, соответствующая объёму 0.01 лота, для режима расчёта объёма по деньгам. |
| InpUseEquity | Определяет, использовать эквити вместо баланса при расчёте объёма. |
| InpCheckMargin | Включает проверку достаточности маржи перед выставлением новых ордеров. |
| InpMaxDailyDD | Порог максимальной дневной просадки; при нуле ограничение отключено. |
| InpMaxDDUnit | Единица измерения максимальной дневной просадки: процент от пиковой эквити или денежная сумма. |
| InpZoneA1 | Включает стратегический модуль A1. |
| InpZoneA2 | Включает стратегический модуль A2. |
| InpZoneA3 | Включает стратегический модуль A3. |
| InpZoneB1 | Включает стратегический модуль B1. |
| InpZoneB2 | Включает стратегический модуль B2. |
| InpZoneB3 | Включает стратегический модуль B3. |
| InpSess_Asia | Включает торговлю в азиатскую сессию. |
| InpSess_London | Включает торговлю в лондонскую сессию. |
| InpSess_Overlap | Включает торговлю в период пересечения лондонской и нью-йоркской сессий. |
| InpSess_NY | Включает торговлю в нью-йоркскую сессию. |
| InpPanelMode | Выбор размера информационной панели: малый или большой с дополнительными метриками. |
| InpShowPanel | Включает отображение информационной панели в реальной торговле. |
| InpPanelTest | Включает отображение информационной панели в тестере стратегий. |
| InpPanelSize | Масштаб информационной панели. |
| InpFontScale | Масштаб шрифта информационной панели. |
Поиск по коду файла
#property strict
#property version "2.10"
#property description "Lizard MT5 2.1"
enum LotMode
{
FIXED_VOLUME = 0, // Fixlot
RISK_PERCENT = 1, // Risk %
BALANCE_PER_001 = 2 // $ per 0.01 Lot
};
struct VolumeLimits
{
double minimum;
double maximum;
double step;
double strategyMaximum;
double tickValue;
double tickSize;
double point;
double priceUnit;
};
struct SizingSettings
{
LotMode mode;
double fixedVolume;
double riskPercent;
double dollarsPer001;
};
double CalculateStrategyVolume(const SizingSettings &settings,
const VolumeLimits &limits,
const double accountBasis,
const double roundedUsdBasis,
const double stopDistanceUnits,
const double allocationPercent,
const bool reduceForNfpWeek,
const double previousVolume)
{
int initialPrecision = 2;
if(limits.step == 0.1)
initialPrecision = 1;
if(settings.mode == FIXED_VOLUME)
{
double volume = NormalizeDouble(allocationPercent * 0.01 * settings.fixedVolume,
initialPrecision);
if(reduceForNfpWeek)
volume = NormalizeDouble(limits.step * MathFloor(volume * 0.5 / limits.step), 8);
volume = MathMax(volume, limits.minimum);
if(limits.maximum > 0.0)
volume = MathMin(volume, limits.maximum);
return volume;
}
double volume = previousVolume;
if(settings.mode == BALANCE_PER_001)
{
if(settings.dollarsPer001 <= 0.0)
return limits.minimum;
volume = (roundedUsdBasis / settings.dollarsPer001) * 0.01 * allocationPercent * 0.01;
volume = NormalizeDouble(volume, initialPrecision);
}
else if(settings.mode == RISK_PERCENT)
{
double tickSize = limits.tickSize;
if(tickSize <= 0.0)
tickSize = limits.point;
double lossPerLot = limits.tickValue * (stopDistanceUnits * limits.priceUnit) / tickSize;
if(lossPerLot <= 0.0)
lossPerLot = 1.0;
volume = (accountBasis * settings.riskPercent / 100.0) / lossPerLot;
}
volume = MathMax(volume, limits.step);
volume = MathMin(volume, limits.strategyMaximum);
volume = MathMax(volume, limits.minimum);
if(limits.maximum > 0.0)
volume = MathMin(volume, limits.maximum);
if(reduceForNfpWeek)
volume *= 0.5;
volume = NormalizeDouble(limits.step * MathFloor(volume / limits.step), 8);
return MathMax(volume, limits.minimum);
}
enum TimeFilter
{
TIME_OLD = 0, // Old
TIME_NEW = 1 // New
};
enum FakeoutMode
{
FAKEOUT_OFF = 0, // OFF
FAKEOUT_SOFT = 1, // Soft
FAKEOUT_NORMAL = 2, // Normal
FAKEOUT_AGGRESSIVE = 3 // Aggressive
};
enum TrailMode
{
TRAIL_DYNAMIC_HIGH = 0, // Dynamic High (high risk / high profit)
TRAIL_DYNAMIC_LOW = 1, // Dynamic Low (low risk / low profit)
TRAIL_FIXED = 2 // Fixed (custom BE + trail points)
};
enum VirtualStopMode
{
VIRTUAL_ON_FREEZE = 0, // Trigger at Freeze Level (default)
VIRTUAL_ALWAYS = 1, // On (always active)
VIRTUAL_OFF = 2 // Off
};
enum OpeningDelay
{
OPEN_AFTER_5 = 5, // 5 Min after open
OPEN_AFTER_10 = 10, // 10 Min after open
OPEN_AFTER_15 = 15, // 15 Min after open
OPEN_AFTER_30 = 30, // 30 Min after open
OPEN_AFTER_60 = 60 // 1 Hour after open
};
enum PayrollWeekMode
{
PAYROLL_WEEK_OFF = 0, // Off
PAYROLL_WEEK_BLOCK = 1, // Block (no trading Mon-Fri)
PAYROLL_WEEK_HALF = 2 // Reduce Lot 50%
};
enum DailyLossUnit
{
DAILY_LOSS_PERCENT = 0, // Percent %
DAILY_LOSS_MONEY = 1 // Money $
};
enum PanelMode
{
PANEL_SMALL = 0, // Small
PANEL_LARGE = 1 // Large
};
input string Notice = "Trading are risky! Trade only if you can loss your money";
input string SimpleForexTeam__ = "free tools for forex! Join our channel for more free eas";
input string The_our_telegram = "";
input string VIP_group = "";
input group "======= News Filter Setup - READ FIRST ======="
input string InpNewsSetup1 = "Tools > Options > Expert > Allow WebRequest"; // NEWS FILTER: enable this
input string InpNewsSetup2 = ""; // ...and add this URL (copy)
input group "======= Community & Support ======="
input string InpWarn1 = "!! WARNING: Do NOT use MQL VPS !!"; // READ THIS
input string InpWarn2 = "Use a fast and stable VPS! Disconnects lose trade management"; // Risk Notice
input string InpInfoBacktest = ""; // Perform a Backtest before use!
input group "======= Main Settings ======="
input TimeFilter InpTimeFilter = TIME_NEW; // Time Filter
input FakeoutMode InpFakeout = FAKEOUT_NORMAL; // Fakeout Filter
input int InpFakeoutMinutes = 8; // Fakeout Normal: close-in-loss timer (minutes)
input int Zone1_ID = 1337; // Magic Number
input string Zone1_Tag = ""; // Trade Comment
input group "============ Trailing/SL ============"
input TrailMode InpTrailMode = TRAIL_DYNAMIC_HIGH; // Trailing/BE Mode
input int InpFixedBE = 0; // Fixed: BE Trigger (points, 0=must set)
input int InpFixedTrail = 0; // Fixed: Trail Distance (points, 0=must set)
input string InpSLInfo = "Normal=3000 / High=1000 / Armageddon=500"; // SL Presets (reference)
input int InpStopLossPts = 3000; // Stop Loss (points)
input VirtualStopMode InpVSL = VIRTUAL_ON_FREEZE; // Virtual SL (Freeze=automatic, On=always, Off=disabled)
input group "========== Market Window =========="
input int InpCloseWindow = 120; // Close Window (minutes before broker close)
input OpeningDelay InpOpenDelay = OPEN_AFTER_15; // Resume Entries After Open
input bool InpCloseWeekend = false; // Close Before Weekend
input group "======= News Filter ======="
input string InpNewsSetup3 = "Tools > Options > Expert > Allow WebRequest"; // REQUIRED for the news filter
input string InpNewsSetup4 = ""; // Add this URL (copy)
input bool InpNFP_Enable = true; // NFP (extra guard, High Impact already covers NFP)
input PayrollWeekMode InpNFPWeek = PAYROLL_WEEK_OFF; // NFP Week (Block = no trades Mon-Fri, Reduce = half lot)
input bool InpNews_High = true; // High Impact (CPI, FOMC, PPI, NFP etc.)
input bool InpNews_Holidays = true; // USD Holidays (no trading entire day)
input int Inp_GMT_Winter = 2; // GMT Offset Winter (Backtest + Manual Live)
input int Inp_GMT_Summer = 3; // GMT Offset Summer (Backtest + Manual Live)
input bool InpGMT_AutoDetect = true; // GMT Auto-Detect Live (Off = use Winter/Summer above)
input bool InpNFP_CloseOpen = true; // Close Open Positions (News)
input bool InpNFP_ClosePending = true; // Close Pending Orders (News)
input int InpNFP_Before = 30; // Minutes Before News
input int InpNFP_After = 30; // Minutes After News
input group "======= Lot Settings ======="
input string InpRiskInfo = ""; // Lot sizing only! Not a loss cap - use Max Daily DD for that
input LotMode InpLotMode = RISK_PERCENT; // Lot Mode
input double InpFixLot = 0.01; // Fix Lot Size
input double InpRiskPct = 1.0; // Risk %
input double InpDollarPer001 = 2500.0; // $ per 0.01 Lot
input bool InpUseEquity = false; // Use Equity
input bool InpCheckMargin = true; // Check Margin
input double InpMaxDailyDD = 0.0; // Max Daily DD value (0=off; % or $ per Unit)
input DailyLossUnit InpMaxDDUnit = DAILY_LOSS_PERCENT; // Max Daily DD Unit (% of peak equity, or $)
input group "======= Zone Selection (Custom) ======="
input bool InpZoneA1 = true; // A1 H1 Breakout (Base)
input bool InpZoneA2 = true; // A2 H1 Breakout (Base)
input bool InpZoneA3 = true; // A3 H1 Breakout (Base)
input bool InpZoneB1 = true; // B1 H1 Breakout (Mid)
input bool InpZoneB2 = true; // B2 H1 Breakout (Mid)
input bool InpZoneB3 = true; // B3 H1 Breakout (Mid)
input group "======= Session Filter ======="
input bool InpSess_Asia = true; // Asia: Sydney+Tokyo (NY close -> London open)
input bool InpSess_London = true; // London (London open -> NY open)
input bool InpSess_Overlap = true; // London+NY Overlap (NY open -> London close)
input bool InpSess_NY = true; // New York (London close -> NY close)
input group "======= Info Panel ======="
input PanelMode InpPanelMode = PANEL_SMALL; // Panel Mode (Large adds execution, cost, curve)
input bool InpShowPanel = true; // Show Panel (Live)
input bool InpPanelTest = false; // Show Panel (Backtest)
input double InpPanelSize = 1.0; // Panel Scale (0.5-2.0)
input double InpFontScale = 0.9; // Font Scale (0.5-2.0)
enum StrategyId
{
CORE_A1,
CORE_A2,
CORE_A3,
CORE_B1,
CORE_B2,
CORE_B3
};
bool IsStrategyHourBlocked(const StrategyId strategy, const int filterMode,
const datetime serverTime, const int brokerOffsetHours)
{
MqlDateTime clock;
TimeToStruct(serverTime, clock);
int hour = (clock.hour - brokerOffsetHours + 24) % 24;
if(filterMode == TIME_NEW)
{
switch(strategy)
{
case CORE_A1: return hour == 16 || hour == 19;
case CORE_A2: return hour == 19;
case CORE_A3: return hour == 5;
case CORE_B1: return hour == 4 || hour == 7 || hour == 16 || hour == 17;
default: return false;
}
}
switch(strategy)
{
case CORE_A1: return hour == 8 || hour == 13 || hour == 16;
case CORE_A2: return hour == 8 || hour == 16 || hour == 18 || hour == 19;
case CORE_A3: return hour == 5 || hour == 8 || hour == 10 || hour == 14;
case CORE_B1:
return clock.day_of_week == 3 || hour == 1 || hour == 5 || hour == 8 ||
hour == 10 || hour == 11 || hour == 12 || hour == 13 ||
hour == 14 || hour == 19;
default: return false;
}
}
datetime CalendarTime(const int year, const int month, const int day,
const int hour = 0, const int minute = 0)
{
MqlDateTime date = {};
date.year = year;
date.mon = month;
date.day = day;
date.hour = hour;
date.min = minute;
return StructToTime(date);
}
bool UsesSummerBrokerOffset(const datetime serverTime)
{
MqlDateTime now, start, finish;
TimeToStruct(serverTime, now);
if(now.year < 1987)
return false;
if(now.year < 2007)
{
int startDay = 1 + ((6 * now.year - now.year / 4 + 2) % 7);
int endDay = 31 - ((5 * now.year / 4 + 1) % 7);
TimeToStruct(CalendarTime(now.year, 4, startDay, 2), start);
TimeToStruct(CalendarTime(now.year, 10, endDay, 2), finish);
}
else
{
int weekdayOffset = (5 * now.year / 4 + 1) % 7;
TimeToStruct(CalendarTime(now.year, 3, 14 - weekdayOffset, 2), start);
TimeToStruct(CalendarTime(now.year, 11, 7 - weekdayOffset, 2), finish);
}
return now.day_of_year > start.day_of_year && now.day_of_year < finish.day_of_year;
}
int BrokerUtcOffset(const datetime serverTime, const datetime utcTime,
const bool autoDetect, const bool testing,
const int winterHours, const int summerHours)
{
if(autoDetect && !testing)
{
MqlDateTime serverClock, utcClock;
TimeToStruct(serverTime, serverClock);
TimeToStruct(utcTime, utcClock);
int offset = serverClock.hour - utcClock.hour;
if(offset < -12) offset += 24;
if(offset > 12) offset -= 24;
return offset;
}
return UsesSummerBrokerOffset(serverTime) ? summerHours : winterHours;
}
int SundayOfMonth(const int year, const int month, const int occurrence)
{
MqlDateTime first;
TimeToStruct(CalendarTime(year, month, 1), first);
return 1 + (7 - first.day_of_week) % 7 + (occurrence - 1) * 7;
}
int LastSundayOfMonth(const int year, const int month)
{
int nextMonth = month + 1;
int nextYear = year;
if(nextMonth == 13) { nextMonth = 1; ++nextYear; }
MqlDateTime last;
TimeToStruct(CalendarTime(nextYear, nextMonth, 1) - 86400, last);
return last.day - last.day_of_week;
}
bool IsTradingSessionBlocked(const datetime utcTime, const bool asia,
const bool london, const bool overlap, const bool newYork)
{
if(asia && london && overlap && newYork)
return false;
MqlDateTime now;
TimeToStruct(utcTime, now);
datetime londonSummerStart = CalendarTime(now.year, 3, LastSundayOfMonth(now.year, 3), 1);
datetime londonSummerEnd = CalendarTime(now.year, 10, LastSundayOfMonth(now.year, 10), 1);
datetime newYorkSummerStart = CalendarTime(now.year, 3, SundayOfMonth(now.year, 3, 2), 7);
datetime newYorkSummerEnd = CalendarTime(now.year, 11, SundayOfMonth(now.year, 11, 1), 6);
bool londonSummer = utcTime >= londonSummerStart && utcTime < londonSummerEnd;
bool newYorkSummer = utcTime >= newYorkSummerStart && utcTime < newYorkSummerEnd;
int londonStart = londonSummer ? 7 : 8;
int londonEnd = londonSummer ? 16 : 17;
int newYorkStart = newYorkSummer ? 12 : 13;
int newYorkEnd = newYorkSummer ? 21 : 22;
if(now.hour >= newYorkEnd || now.hour < londonStart) return !asia;
if(now.hour < newYorkStart) return !london;
if(now.hour < londonEnd) return !overlap;
return !newYork;
}
const datetime PAYROLL_DATES[] =
{
D'2026.12.04 12:30:00',
D'2026.11.06 12:30:00',
D'2026.10.02 12:30:00',
D'2026.09.04 12:30:00',
D'2026.08.07 12:30:00',
D'2026.07.02 12:30:00',
D'2026.06.05 12:30:00',
D'2026.05.08 12:30:00',
D'2026.04.03 12:30:00',
D'2026.03.06 12:30:00',
D'2026.02.11 12:30:00',
D'2026.01.09 12:30:00',
D'2025.12.16 12:30:00',
D'2025.11.07 12:30:00',
D'2025.10.03 12:30:00',
D'2025.09.05 12:30:00',
D'2025.08.01 12:30:00',
D'2025.07.03 12:30:00',
D'2025.06.06 12:30:00',
D'2025.05.02 12:30:00',
D'2025.04.04 12:30:00',
D'2025.03.07 12:30:00',
D'2025.02.07 12:30:00',
D'2025.01.10 12:30:00',
D'2024.12.06 12:30:00',
D'2024.11.01 12:30:00',
D'2024.10.04 12:30:00',
D'2024.09.06 12:30:00',
D'2024.08.02 12:30:00',
D'2024.07.05 12:30:00',
D'2024.06.07 12:30:00',
D'2024.05.03 12:30:00',
D'2024.04.05 12:30:00',
D'2024.03.08 12:30:00',
D'2024.02.02 12:30:00',
D'2024.01.05 12:30:00',
D'2023.12.08 12:30:00',
D'2023.11.03 12:30:00',
D'2023.10.06 12:30:00',
D'2023.09.01 12:30:00',
D'2023.08.04 12:30:00',
D'2023.07.07 12:30:00',
D'2023.06.02 12:30:00',
D'2023.05.05 12:30:00',
D'2023.04.07 12:30:00',
D'2023.03.10 12:30:00',
D'2023.02.03 12:30:00',
D'2023.01.06 12:30:00'
};
datetime PayrollDateForMonth(const int year, const int month)
{
for(int i = 0; i < ArraySize(PAYROLL_DATES); ++i)
{
MqlDateTime event;
TimeToStruct(PAYROLL_DATES[i], event);
if(event.year == year && event.mon == month)
return PAYROLL_DATES[i];
}
if(year < 2027)
return 0;
MqlDateTime first;
TimeToStruct(CalendarTime(year, month, 1), first);
int firstFriday = 1 + (5 - first.day_of_week + 7) % 7;
return CalendarTime(year, month, firstFriday, 12, 30);
}
bool IsPayrollWindow(const datetime utcLikeTime, const datetime serverTime,
const int minutesBefore, const int minutesAfter)
{
MqlDateTime now;
TimeToStruct(serverTime, now);
datetime event = PayrollDateForMonth(now.year, now.mon);
if(event == 0)
return false;
if(!UsesSummerBrokerOffset(serverTime))
event += 3600;
return utcLikeTime >= event - minutesBefore * 60 &&
utcLikeTime <= event + minutesAfter * 60;
}
enum SwingSide
{
SWING_HIGH,
SWING_LOW
};
struct SwingConfig
{
ENUM_TIMEFRAMES timeframe;
int newerBars;
int olderBars;
int lastCandidateShift;
double minimumQuoteDistance;
double buyEntryOffset;
double sellEntryOffset;
double pendingSpacing;
};
double ReadSwingBarPrice(const string symbol, const ENUM_TIMEFRAMES timeframe,
const SwingSide side, const int shift)
{
if(side == SWING_HIGH)
return iHigh(symbol, timeframe, shift);
return iLow(symbol, timeframe, shift);
}
bool IsMoreExtremePrice(const double price, const double candidate,
const SwingSide side)
{
if(side == SWING_HIGH)
return price > candidate;
return price < candidate;
}
bool HasNearbyStopOrder(const string symbol, const long magic,
const SwingSide side, const double entryPrice,
const double spacing)
{
ENUM_ORDER_TYPE expectedType = ORDER_TYPE_BUY_STOP;
if(side == SWING_LOW)
expectedType = ORDER_TYPE_SELL_STOP;
for(int index = OrdersTotal() - 1; index >= 0; --index)
{
if(OrderGetTicket(index) == 0)
continue;
if(OrderGetInteger(ORDER_MAGIC) != magic || OrderGetString(ORDER_SYMBOL) != symbol)
continue;
if((ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE) != expectedType)
continue;
if(MathAbs(OrderGetDouble(ORDER_PRICE_OPEN) - entryPrice) < spacing)
return true;
}
return false;
}
double FindSwingPrice(const string symbol, const long magic,
const SwingConfig &config, const SwingSide side,
const double priceUnit, const int priceDigits)
{
for(int candidateShift = config.newerBars + 1;
candidateShift <= config.lastCandidateShift; ++candidateShift)
{
bool newerSideValid = true;
for(int shift = candidateShift; shift >= candidateShift - config.newerBars; --shift)
{
double neighbor = ReadSwingBarPrice(symbol, config.timeframe, side, shift);
double candidate = ReadSwingBarPrice(symbol, config.timeframe, side, candidateShift);
if(IsMoreExtremePrice(neighbor, candidate, side))
{
newerSideValid = false;
break;
}
}
bool olderSideValid = true;
for(int shift = candidateShift; shift <= candidateShift + config.olderBars; ++shift)
{
double neighbor = ReadSwingBarPrice(symbol, config.timeframe, side, shift);
double candidate = ReadSwingBarPrice(symbol, config.timeframe, side, candidateShift);
if(IsMoreExtremePrice(neighbor, candidate, side))
{
olderSideValid = false;
break;
}
}
double candidate = ReadSwingBarPrice(symbol, config.timeframe, side, candidateShift);
bool farEnough;
if(side == SWING_HIGH)
{
double ask = SymbolInfoDouble(symbol, SYMBOL_ASK);
farEnough = candidate > ask + config.minimumQuoteDistance * priceUnit;
}
else
{
double bid = SymbolInfoDouble(symbol, SYMBOL_BID);
farEnough = candidate < bid - config.minimumQuoteDistance * priceUnit;
}
if(!newerSideValid || !olderSideValid || !farEnough)
continue;
double recentExtreme = ReadSwingBarPrice(symbol, config.timeframe, side, 0);
for(int shift = 1; shift <= candidateShift; ++shift)
{
double price = ReadSwingBarPrice(symbol, config.timeframe, side, shift);
if(IsMoreExtremePrice(price, recentExtreme, side))
recentExtreme = price;
}
if(IsMoreExtremePrice(recentExtreme, candidate, side))
continue;
candidate = NormalizeDouble(ReadSwingBarPrice(symbol, config.timeframe, side,
candidateShift), priceDigits);
double offset = config.buyEntryOffset;
if(side == SWING_LOW)
offset = -config.sellEntryOffset;
if(HasNearbyStopOrder(symbol, magic, side, candidate + offset * priceUnit,
config.pendingSpacing * priceUnit))
continue;
return candidate;
}
return 0.0;
}
struct StrategyConfig
{
long magicOffset;
string commentSuffix;
double referenceDailyOpen;
double takeProfitExtraFraction;
ENUM_TIMEFRAMES swingTimeframe;
ENUM_TIMEFRAMES entryRefreshTimeframe;
int swingOlderBars;
int swingNewerBars;
int lastSwingShift;
double minimumSwingDistance;
double buyEntryOffset;
double sellEntryOffset;
int pendingSearchPassLimit;
double pendingSpacing;
int pendingLifetimeHours;
bool throttleManagementToM1;
int maxPositionsPerSide;
double stopDistance;
double takeProfitBaseDistance;
double trailingDistance;
double trailingActivation;
double trailingStep;
double takeProfitFollowDistance;
double takeProfitFollowAdverseThreshold;
double breakEvenActivation;
double breakEvenLockedProfit;
bool auxiliarySwingUseH1;
bool auxiliarySwingSearchTo50;
int auxiliarySwingNewerBars;
int auxiliarySwingOlderBars;
};
const StrategyConfig strategyDefinitions[6] =
{
{9, "_A1", 2400.0, 0.03, PERIOD_H1, PERIOD_M5, 26, 24, 140, 120.0, -105.0, -130.0, 5, 55.0, 20, false, 5, 3636.0, 800.0, 145.0, 145.0, 0.1, 350.0, 1950.0, 145.0, 36.0, true, true, 0, 0},
{14, "_A2", 2600.0, 0.05, PERIOD_H1, PERIOD_M15, 25, 23, 145, 10.0, -55.0, -140.0, 5, 90.0, 60, false, 5, 2150.0, 1450.0, 158.0, 158.0, 0.1, 350.0, 2800.0, 158.0, 39.0, true, true, 12, 17},
{15, "_A3", 2800.0, 0.07, PERIOD_H1, PERIOD_M15, 26, 20, 235, 80.0, -140.0, -170.0, 5, 5.0, 55, false, 20, 1800.0, 1200.0, 170.0, 170.0, 0.1, 250.0, 1950.0, 170.0, 42.0, true, true, 15, 3},
{13, "_B1", 1900.0, 0.1, PERIOD_H1, PERIOD_M15, 7, 5, 200, 40.0, -130.0, -125.0, 3, 5.0, 15, false, 99, 2878.0, 1350.0, 115.0, 115.0, 0.1, 250.0, 1850.0, 115.0, 29.0, true, true, 1, 9},
{12, "_B2", 2600.0, 0.03, PERIOD_H1, PERIOD_M15, 30, 19, 110, 160.0, -115.0, -105.0, 3, 55.0, 30, false, 99, 3940.0, 900.0, 158.0, 158.0, 0.1, 250.0, 1900.0, 158.0, 39.0, true, true, 0, 0},
{8, "_B3", 2000.0, 0.1, PERIOD_D1, PERIOD_H1, 7, 2, 20, 250.0, -112.0, -104.0, 1, 980.0, 432, false, 99, 730.0, 3300.0, 121.0, 121.0, 0.1, 2000.0, 1000.0, 121.0, 30.0, true, true, 7, 4}
};
void BuildEffectiveStrategyConfig(const StrategyId id,
const double previousDailyOpen,
const TrailMode trailingMode,
const double fixedBreakEven,
const double fixedTrail,
const double configuredStop,
StrategyConfig &config)
{
config = strategyDefinitions[(int)id];
double scale = 1.0;
if(config.referenceDailyOpen > 0.0)
scale = previousDailyOpen / config.referenceDailyOpen;
if(scale <= 0.0)
return;
config.minimumSwingDistance *= scale;
config.buyEntryOffset = NormalizeDouble(config.buyEntryOffset * scale, 0);
config.sellEntryOffset = NormalizeDouble(config.sellEntryOffset * scale, 0);
config.takeProfitBaseDistance *= scale;
if(trailingMode == TRAIL_DYNAMIC_HIGH || trailingMode == TRAIL_DYNAMIC_LOW)
{
double trailingScale = scale;
if(trailingMode == TRAIL_DYNAMIC_LOW)
trailingScale = MathMin(trailingScale, 1.2);
config.trailingDistance *= trailingScale;
config.trailingActivation *= trailingScale;
config.takeProfitFollowDistance *= trailingScale;
config.takeProfitFollowAdverseThreshold *= trailingScale;
config.breakEvenActivation *= trailingScale;
config.breakEvenLockedProfit *= trailingScale;
}
else
{
config.trailingActivation = fixedBreakEven;
config.trailingDistance = fixedTrail;
config.breakEvenActivation = fixedBreakEven;
config.breakEvenLockedProfit = 0.0;
config.takeProfitFollowDistance *= scale;
config.takeProfitFollowAdverseThreshold *= scale;
}
config.stopDistance = configuredStop;
}
void AdjustActivationForSpread(StrategyConfig &config, const double spread,
const double priceUnit)
{
double spreadUnits = spread / priceUnit;
if(spreadUnits > 0.0)
{
config.breakEvenActivation = MathMax(1.0, config.breakEvenActivation - spreadUnits);
config.trailingActivation = MathMax(1.0, config.trailingActivation - spreadUnits);
}
}
void GetSwingConfig(const StrategyConfig &config, SwingConfig &swing)
{
swing.timeframe = config.swingTimeframe;
swing.newerBars = config.swingNewerBars;
swing.olderBars = config.swingOlderBars;
swing.lastCandidateShift = config.lastSwingShift;
swing.minimumQuoteDistance = config.minimumSwingDistance;
swing.buyEntryOffset = config.buyEntryOffset;
swing.sellEntryOffset = config.sellEntryOffset;
swing.pendingSpacing = config.pendingSpacing;
}
struct EntryReference
{
ulong ticket;
double price;
};
struct VirtualTarget
{
ulong ticket;
double price;
bool armed;
};
struct VirtualStop
{
ulong ticket;
double price;
ENUM_POSITION_TYPE side;
};
EntryReference entryReferences[100];
VirtualTarget virtualTargets[50];
VirtualStop virtualStops[50];
int virtualStopCount = 0;
void RememberEntryPrice(const ulong ticket, const double price)
{
for(int i = 0; i < ArraySize(entryReferences); ++i)
{
if(entryReferences[i].ticket == ticket)
{
entryReferences[i].price = price;
return;
}
}
for(int i = 0; i < ArraySize(entryReferences); ++i)
{
if(entryReferences[i].ticket == 0)
{
entryReferences[i].ticket = ticket;
entryReferences[i].price = price;
return;
}
}
}
double EntryReferencePrice(const ulong ticket, const double actualEntry)
{
for(int i = 0; i < ArraySize(entryReferences); ++i)
if(entryReferences[i].ticket == ticket && entryReferences[i].price > 0.0)
return entryReferences[i].price;
RememberEntryPrice(ticket, actualEntry);
return actualEntry;
}
void ForgetEntryPrice(const ulong ticket)
{
for(int i = 0; i < ArraySize(entryReferences); ++i)
if(entryReferences[i].ticket == ticket)
ZeroMemory(entryReferences[i]);
}
void PruneEntryReferences()
{
for(int i = 0; i < ArraySize(entryReferences); ++i)
{
ulong ticket = entryReferences[i].ticket;
if(ticket != 0 && !PositionSelectByTicket(ticket) && !OrderSelect(ticket))
ZeroMemory(entryReferences[i]);
}
}
int FindVirtualTarget(const ulong ticket)
{
for(int i = 0; i < ArraySize(virtualTargets); ++i)
if(virtualTargets[i].ticket == ticket)
return i;
return -1;
}
int RegisterVirtualTarget(const ulong ticket, const double price)
{
int existing = FindVirtualTarget(ticket);
if(existing >= 0)
return existing;
for(int i = 0; i < ArraySize(virtualTargets); ++i)
{
if(virtualTargets[i].ticket != 0)
continue;
virtualTargets[i].ticket = ticket;
virtualTargets[i].price = price;
virtualTargets[i].armed = false;
string name = "vtp_" + (string)ticket;
ObjectCreate(0, name, OBJ_HLINE, 0, 0, price);
ObjectSetInteger(0, name, OBJPROP_COLOR, clrYellow);
ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_SOLID);
ObjectSetInteger(0, name, OBJPROP_WIDTH, 1);
ObjectSetInteger(0, name, OBJPROP_BACK, true);
return i;
}
return -1;
}
void ClearVirtualTarget(const ulong ticket)
{
int index = FindVirtualTarget(ticket);
if(index >= 0)
ZeroMemory(virtualTargets[index]);
ObjectDelete(0, "vtp_" + (string)ticket);
}
int FindVirtualStop(const ulong ticket)
{
for(int i = 0; i < virtualStopCount; ++i)
if(virtualStops[i].ticket == ticket)
return i;
return -1;
}
void TightenVirtualStop(const ulong ticket, const ENUM_POSITION_TYPE side, const double price)
{
int index = FindVirtualStop(ticket);
if(index < 0)
{
if(virtualStopCount >= ArraySize(virtualStops))
return;
index = virtualStopCount++;
virtualStops[index].ticket = ticket;
virtualStops[index].side = side;
virtualStops[index].price = 0.0;
}
else if(virtualStops[index].price > 0.0)
{
if(side == POSITION_TYPE_BUY && price <= virtualStops[index].price) return;
if(side == POSITION_TYPE_SELL && price >= virtualStops[index].price) return;
}
virtualStops[index].price = price;
}
void RemoveVirtualStop(const ulong ticket)
{
int index = FindVirtualStop(ticket);
if(index < 0)
return;
--virtualStopCount;
virtualStops[index] = virtualStops[virtualStopCount];
ZeroMemory(virtualStops[virtualStopCount]);
}
double CalculateVirtualTarget(const string symbol, const double entry,
const ENUM_POSITION_TYPE side, const double point, const int digits)
{
double prices[];
int copied;
if(side == POSITION_TYPE_BUY)
copied = CopyLow(symbol, PERIOD_H1, 1, 24, prices);
else
copied = CopyHigh(symbol, PERIOD_H1, 1, 24, prices);
double direction = side == POSITION_TYPE_BUY ? 1.0 : -1.0;
double distance = 100.0 * point;
if(copied == 24)
{
double extreme = prices[0];
for(int bar = 1; bar < 24; ++bar)
{
if(side == POSITION_TYPE_BUY) extreme = MathMin(extreme, prices[bar]);
else extreme = MathMax(extreme, prices[bar]);
}
if(extreme > 0.0)
distance = 0.25 * MathMax(distance, direction * (entry - extreme));
}
return NormalizeDouble(entry + direction * distance, digits);
}
#include <Trade/Trade.mqh>
void RecordExecutionDuration(const int action, const ulong started, const bool accepted);
CTrade trade;
struct SymbolSnapshot
{
string symbol;
int digits;
double point;
double priceUnit;
double bid;
double ask;
double spread;
double stopsDistance;
double freezeDistance;
};
void RefreshSymbolSnapshot(const string symbol, SymbolSnapshot &market)
{
market.symbol = symbol;
market.point = SymbolInfoDouble(symbol, SYMBOL_POINT);
market.digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
market.priceUnit = market.point;
if(market.digits == 3 || market.digits == 5)
market.priceUnit *= 10.0;
if((ENUM_SYMBOL_CALC_MODE)SymbolInfoInteger(symbol, SYMBOL_TRADE_CALC_MODE) == SYMBOL_CALC_MODE_FOREX_NO_LEVERAGE)
market.priceUnit = market.point / 10.0;
market.ask = SymbolInfoDouble(symbol, SYMBOL_ASK);
market.bid = SymbolInfoDouble(symbol, SYMBOL_BID);
market.spread = market.ask - market.bid;
market.stopsDistance = SymbolInfoInteger(symbol, SYMBOL_TRADE_STOPS_LEVEL) * market.point;
market.freezeDistance = SymbolInfoInteger(symbol, SYMBOL_TRADE_FREEZE_LEVEL) * market.point;
}
bool IsOwnedMagic(const long magic)
{
return magic > Zone1_ID && magic <= (long)Zone1_ID + 15;
}
bool IsSelectedOwnedPosition(const string symbol)
{
return PositionGetString(POSITION_SYMBOL) == symbol && IsOwnedMagic(PositionGetInteger(POSITION_MAGIC));
}
bool IsSelectedOwnedOrder(const string symbol)
{
return OrderGetString(ORDER_SYMBOL) == symbol && IsOwnedMagic(OrderGetInteger(ORDER_MAGIC));
}
void DeleteOwnedPendingOrders(const string symbol)
{
for(int i = OrdersTotal() - 1; i >= 0; --i)
{
ulong ticket = OrderGetTicket(i);
if(ticket != 0 && IsSelectedOwnedOrder(symbol))
trade.OrderDelete(ticket);
}
}
void CloseOwnedPositions(const string symbol)
{
for(int i = PositionsTotal() - 1; i >= 0; --i)
{
ulong ticket = PositionGetTicket(i);
if(ticket != 0 && IsSelectedOwnedPosition(symbol))
CloseManagedPosition(ticket);
}
}
int CountStrategyPositions(const string symbol, const long magic, const ENUM_POSITION_TYPE side)
{
int count = 0;
for(int i = PositionsTotal() - 1; i >= 0; --i)
{
if(PositionGetTicket(i) == 0) continue;
if(PositionGetString(POSITION_SYMBOL) == symbol && PositionGetInteger(POSITION_MAGIC) == magic &&
(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE) == side)
++count;
}
return count;
}
bool ModifyProtection(const ulong ticket, const double stopLoss, const double takeProfit)
{
ulong started = GetMicrosecondCount();
bool accepted = trade.PositionModify(ticket, stopLoss, takeProfit);
RecordExecutionDuration(1, started, accepted);
return accepted;
}
bool CloseManagedPosition(const ulong ticket)
{
ulong started = GetMicrosecondCount();
bool accepted = trade.PositionClose(ticket);
RecordExecutionDuration(2, started, accepted);
return accepted;
}
struct TrapState
{
datetime lastHour;
double positiveDirection;
double negativeDirection;
double stochastic;
};
void RefreshTrapIndicators(const string symbol, TrapState &state)
{
datetime currentHour = iTime(symbol, PERIOD_H1, 0);
if(currentHour == state.lastHour)
return;
state.lastHour = currentHour;
double highs[], lows[], closes[];
if(CopyHigh(symbol, PERIOD_H1, 0, 43, highs) == 43 &&
CopyLow(symbol, PERIOD_H1, 0, 43, lows) == 43 &&
CopyClose(symbol, PERIOD_H1, 0, 43, closes) == 43)
{
double positive = 0.0, negative = 0.0, range = 0.0;
for(int bar = 1; bar < 43; ++bar)
{
double rise = highs[bar] - highs[bar - 1];
double fall = lows[bar - 1] - lows[bar];
double positiveMove = 0.0, negativeMove = 0.0;
if(rise > fall && rise > 0.0) positiveMove = rise;
if(fall > rise && fall > 0.0) negativeMove = fall;
double trueRange = MathMax(highs[bar] - lows[bar],
MathMax(MathAbs(highs[bar] - closes[bar - 1]),
MathAbs(lows[bar] - closes[bar - 1])));
if(bar <= 14)
{
positive += positiveMove;
negative += negativeMove;
range += trueRange;
}
else
{
positive = positive + positive / -14.0 + positiveMove;
negative = negative - negative / 14.0 + negativeMove;
range = range - range / 14.0 + trueRange;
}
}
if(range > 0.0)
{
state.positiveDirection = positive / range * 100.0;
state.negativeDirection = negative / range * 100.0;
}
}
if(CopyHigh(symbol, PERIOD_H1, 1, 17, highs) != 17 ||
CopyLow(symbol, PERIOD_H1, 1, 17, lows) != 17 ||
CopyClose(symbol, PERIOD_H1, 1, 17, closes) != 17)
return;
double total = 0.0;
for(int bar = 14; bar < 17; ++bar)
{
double highest = highs[bar - 13];
double lowest = lows[bar - 13];
for(int prior = bar - 12; prior <= bar; ++prior)
{
highest = MathMax(highest, highs[prior]);
lowest = MathMin(lowest, lows[prior]);
}
double oscillator = 50.0;
if(highest > lowest)
oscillator = ((closes[bar] - lowest) / (highest - lowest)) * 100.0;
total += oscillator;
}
state.stochastic = total / 3.0;
}
bool IsPendingTrap(const ENUM_ORDER_TYPE type, const double orderPrice,
const double bid, const double ask, const double stopsDistance,
const TrapState &state)
{
if(type == ORDER_TYPE_BUY_STOP)
return ask >= orderPrice - stopsDistance &&
(state.positiveDirection <= state.negativeDirection || state.stochastic <= 65.0);
if(type == ORDER_TYPE_SELL_STOP)
return bid <= orderPrice + stopsDistance &&
(state.negativeDirection <= state.positiveDirection || state.stochastic >= 35.0);
return false;
}
bool CheckFallbackStop(const ulong ticket, const double bid, const double ask)
{
int index = FindVirtualStop(ticket);
if(index < 0 || virtualStops[index].price <= 0.0)
return false;
bool crossed = virtualStops[index].side == POSITION_TYPE_BUY
? bid <= virtualStops[index].price : ask >= virtualStops[index].price;
if(!crossed || !CloseManagedPosition(ticket))
return false;
ClearVirtualTarget(ticket);
RemoveVirtualStop(ticket);
return true;
}
bool CloseFakeoutPosition(const ulong ticket)
{
if(!CloseManagedPosition(ticket))
return false;
ClearVirtualTarget(ticket);
return true;
}
bool IsCandleFakeout(const string symbol, const datetime opened, const double entry,
const ENUM_POSITION_TYPE side)
{
datetime previousBar = iTime(symbol, PERIOD_M1, 1);
datetime currentBar = iTime(symbol, PERIOD_M1, 0);
if(opened < previousBar || opened >= currentBar)
return false;
double open = iOpen(symbol, PERIOD_M1, 1);
double close = iClose(symbol, PERIOD_M1, 1);
if(side == POSITION_TYPE_BUY)
return close < open && close < entry;
return close > open && close > entry;
}
bool CheckFakeoutExits(const ulong ticket, const datetime opened, const double entry,
const ENUM_POSITION_TYPE side, const SymbolSnapshot &market)
{
if(InpFakeout >= FAKEOUT_SOFT && IsCandleFakeout(market.symbol, opened, entry, side) &&
CloseFakeoutPosition(ticket))
return true;
bool losing = side == POSITION_TYPE_BUY ? market.bid < entry : market.ask > entry;
if(InpFakeout >= FAKEOUT_NORMAL && TimeCurrent() >= opened + MathMax(1, InpFakeoutMinutes) * 60 &&
losing && CloseFakeoutPosition(ticket))
return true;
if(InpFakeout >= FAKEOUT_AGGRESSIVE && opened >= iTime(market.symbol, PERIOD_M1, 2) &&
opened < iTime(market.symbol, PERIOD_M1, 0))
{
double close = iClose(market.symbol, PERIOD_M1, 1);
bool adverse = side == POSITION_TYPE_BUY ? close < entry : close > entry;
if(adverse && CloseFakeoutPosition(ticket))
return true;
}
return false;
}
bool CheckVirtualTarget(const ulong ticket, const double entry, const ENUM_POSITION_TYPE side,
const SymbolSnapshot &market)
{
int index = FindVirtualTarget(ticket);
if(index < 0)
{
double initialTarget = CalculateVirtualTarget(market.symbol, entry, side, market.point, market.digits);
if(side == POSITION_TYPE_BUY && initialTarget <= entry) return false;
if(side == POSITION_TYPE_SELL && initialTarget >= entry) return false;
index = RegisterVirtualTarget(ticket, initialTarget);
}
if(index < 0) return false;
double target = virtualTargets[index].price;
if(side == POSITION_TYPE_BUY && target <= entry) return false;
if(side == POSITION_TYPE_SELL && target >= entry) return false;
double quote = side == POSITION_TYPE_BUY ? market.bid : market.ask;
if(!virtualTargets[index].armed)
{
bool reached = side == POSITION_TYPE_BUY ? quote >= target : quote <= target;
if(reached) virtualTargets[index].armed = true;
return false;
}
bool pulledBack = side == POSITION_TYPE_BUY ? quote <= target : quote >= target;
if(!pulledBack) return false;
ClearVirtualTarget(ticket);
return CloseManagedPosition(ticket);
}
bool ApplyBuyProtection(const ulong ticket, const double entry, const double reference,
double &stopLoss, double &takeProfit,
const StrategyConfig &config, const SymbolSnapshot &market)
{
bool changed = false;
double candidate;
if(config.breakEvenActivation > 0.0 &&
market.bid > NormalizeDouble(entry + config.breakEvenActivation * market.priceUnit, market.digits))
{
candidate = NormalizeDouble(entry + config.breakEvenLockedProfit * market.priceUnit, market.digits);
if(candidate > stopLoss + market.point && candidate + market.stopsDistance < market.bid &&
market.bid < takeProfit - market.freezeDistance && candidate < market.bid - market.stopsDistance)
{
stopLoss = candidate;
if(ModifyProtection(ticket, candidate, takeProfit)) changed = true;
}
}
if(config.trailingDistance > 0.0 &&
market.bid > NormalizeDouble(stopLoss + market.point +
(config.trailingStep + config.trailingDistance) * market.priceUnit, market.digits) &&
market.bid > NormalizeDouble(reference + config.trailingActivation * market.priceUnit, market.digits) &&
market.bid < takeProfit - market.freezeDistance)
{
candidate = NormalizeDouble(market.bid - config.trailingDistance * market.priceUnit, market.digits);
if(candidate < market.bid - market.stopsDistance && ModifyProtection(ticket, candidate, takeProfit))
{
stopLoss = candidate;
changed = true;
}
}
bool slipped = reference > 0.0 && entry - reference > 5000.0 * market.priceUnit;
if(slipped && market.bid > stopLoss + 100.0 * market.priceUnit + market.point + market.stopsDistance &&
market.bid > reference + 400.0 * market.priceUnit && market.bid < takeProfit - market.freezeDistance)
{
candidate = NormalizeDouble(market.bid - 100.0 * market.priceUnit, market.digits);
if(candidate < market.bid - market.stopsDistance && ModifyProtection(ticket, candidate, takeProfit))
{
stopLoss = candidate;
changed = true;
}
}
if(config.takeProfitFollowDistance > 0.0 &&
market.ask < NormalizeDouble(takeProfit - market.point - config.takeProfitFollowDistance * market.priceUnit, market.digits) &&
market.ask < NormalizeDouble(reference - config.takeProfitFollowAdverseThreshold * market.priceUnit, market.digits) &&
market.bid < takeProfit - market.freezeDistance)
{
candidate = NormalizeDouble(market.bid + config.takeProfitFollowDistance * market.priceUnit, market.digits);
if(market.ask + market.stopsDistance < candidate && ModifyProtection(ticket, stopLoss, candidate))
{
takeProfit = candidate;
changed = true;
}
}
return changed;
}
bool ApplySellProtection(const ulong ticket, const double entry, const double reference,
double &stopLoss, double &takeProfit,
const StrategyConfig &config, const SymbolSnapshot &market)
{
bool changed = false;
double candidate;
if(config.breakEvenActivation > 0.0 && market.ask < entry - config.breakEvenActivation * market.priceUnit)
{
candidate = entry - config.breakEvenLockedProfit * market.priceUnit;
if(candidate < stopLoss - market.point && market.ask < candidate - market.stopsDistance &&
takeProfit + market.freezeDistance < market.ask)
{
candidate = NormalizeDouble(candidate, market.digits);
if(candidate > market.ask + market.stopsDistance && ModifyProtection(ticket, candidate, takeProfit))
{
stopLoss = candidate;
changed = true;
}
}
}
if(config.trailingDistance > 0.0 &&
market.ask < (stopLoss - market.point) - (config.trailingStep + config.trailingDistance) * market.priceUnit &&
market.ask < reference - config.trailingActivation * market.priceUnit &&
takeProfit + market.freezeDistance < market.ask)
{
candidate = NormalizeDouble(market.ask + config.trailingDistance * market.priceUnit, market.digits);
if(candidate > market.ask + market.stopsDistance && ModifyProtection(ticket, candidate, takeProfit))
{
stopLoss = candidate;
changed = true;
}
}
bool slipped = reference > 0.0 && reference - entry > 5000.0 * market.priceUnit;
if(slipped && market.ask < (stopLoss - market.point) - 100.0 * market.priceUnit &&
market.ask < reference - 400.0 * market.priceUnit && takeProfit + market.freezeDistance < market.ask)
{
candidate = NormalizeDouble(market.ask + 100.0 * market.priceUnit, market.digits);
if(candidate > market.ask + market.stopsDistance && ModifyProtection(ticket, candidate, takeProfit))
{
stopLoss = candidate;
changed = true;
}
}
if(config.takeProfitFollowDistance > 0.0 &&
market.bid > NormalizeDouble(market.point + takeProfit + config.takeProfitFollowDistance * market.priceUnit, market.digits) &&
market.bid > NormalizeDouble(reference + config.takeProfitFollowAdverseThreshold * market.priceUnit, market.digits) &&
market.bid > takeProfit + market.freezeDistance)
{
candidate = NormalizeDouble(market.bid - config.takeProfitFollowDistance * market.priceUnit, market.digits);
if(candidate < market.bid - market.stopsDistance && ModifyProtection(ticket, stopLoss, candidate))
{
takeProfit = candidate;
changed = true;
}
}
return changed;
}
double VirtualProtectionCandidate(const ulong ticket, const ENUM_POSITION_TYPE side,
const double entry, const double reference, const double stopLoss,
const StrategyConfig &config, const SymbolSnapshot &market,
const bool includeExistingVirtual)
{
double previous = stopLoss;
int index = FindVirtualStop(ticket);
if(includeExistingVirtual && index >= 0 && virtualStops[index].price > 0.0)
{
if(side == POSITION_TYPE_BUY) previous = MathMax(previous, virtualStops[index].price);
else if(previous == 0.0 || virtualStops[index].price < previous) previous = virtualStops[index].price;
}
double candidate = 0.0;
if(side == POSITION_TYPE_BUY)
{
if(config.breakEvenActivation > 0.0 && market.bid > NormalizeDouble(entry + config.breakEvenActivation * market.priceUnit, market.digits))
{
double level = NormalizeDouble(entry + config.breakEvenLockedProfit * market.priceUnit, market.digits);
if(level > 0.0 && level > previous + market.point) candidate = level;
}
if(config.trailingDistance > 0.0 &&
market.bid > NormalizeDouble(previous + market.point + (config.trailingStep + config.trailingDistance) * market.priceUnit, market.digits) &&
market.bid > NormalizeDouble(reference + config.trailingActivation * market.priceUnit, market.digits))
{
double level = NormalizeDouble(market.bid - config.trailingDistance * market.priceUnit, market.digits);
if(level > previous + market.point && level > candidate) candidate = level;
}
}
else
{
if(config.breakEvenActivation > 0.0 && market.ask < entry - config.breakEvenActivation * market.priceUnit)
{
double level = NormalizeDouble(entry - config.breakEvenLockedProfit * market.priceUnit, market.digits);
if(level > 0.0 && level < previous - market.point) candidate = level;
}
if(config.trailingDistance > 0.0 &&
market.ask < (previous - market.point) - (config.trailingStep + config.trailingDistance) * market.priceUnit &&
market.ask < reference - config.trailingActivation * market.priceUnit)
{
double level = NormalizeDouble(market.ask + config.trailingDistance * market.priceUnit, market.digits);
if(level < previous - market.point && (candidate == 0.0 || level < candidate)) candidate = level;
}
}
return candidate;
}
double BrokerFallbackCandidate(const ENUM_POSITION_TYPE side, const double entry,
const double reference, const double stopLoss,
const StrategyConfig &config, const SymbolSnapshot &market)
{
double candidate = 0.0;
if(side == POSITION_TYPE_BUY)
{
if(config.breakEvenActivation > 0.0 &&
market.bid > NormalizeDouble(entry + config.breakEvenActivation * market.priceUnit, market.digits))
{
double level = NormalizeDouble(entry + config.breakEvenLockedProfit * market.priceUnit, market.digits);
if(level > stopLoss + market.point) candidate = level;
}
if(config.trailingDistance > 0.0 &&
market.bid > NormalizeDouble(reference + config.trailingActivation * market.priceUnit, market.digits))
{
double level = NormalizeDouble(market.bid - config.trailingDistance * market.priceUnit, market.digits);
if(level > stopLoss + market.point && level > candidate) candidate = level;
}
}
else
{
if(config.breakEvenActivation > 0.0 &&
market.ask < NormalizeDouble(entry - config.breakEvenActivation * market.priceUnit, market.digits))
{
double level = NormalizeDouble(entry - config.breakEvenLockedProfit * market.priceUnit, market.digits);
if(level > 0.0 && level < stopLoss - market.point) candidate = level;
}
if(config.trailingDistance > 0.0 &&
market.ask < NormalizeDouble(reference - config.trailingActivation * market.priceUnit, market.digits))
{
double level = NormalizeDouble(market.ask + config.trailingDistance * market.priceUnit, market.digits);
if(level > 0.0 && level < stopLoss - market.point && (candidate == 0.0 || level < candidate)) candidate = level;
}
}
return candidate;
}
void ApplyFallbackProtection(const ulong ticket, const ENUM_POSITION_TYPE side,
const double entry, const double reference, const double stopLoss,
const double takeProfit, const bool modified,
const StrategyConfig &config, const SymbolSnapshot &market)
{
bool testing = MQLInfoInteger(MQL_TESTER) != 0;
if(InpVSL == VIRTUAL_OFF)
{
if(testing) return;
double buffer = MathMax(market.stopsDistance, market.freezeDistance) + 3.0 * market.point;
if(buffer <= 10.0 * market.point) return;
double candidate = BrokerFallbackCandidate(side, entry, reference, stopLoss, config, market);
if(candidate <= 0.0) return;
if(side == POSITION_TYPE_BUY)
{
candidate = MathMin(candidate, NormalizeDouble(market.bid - buffer, market.digits));
if(candidate > stopLoss + market.point) ModifyProtection(ticket, candidate, takeProfit);
}
else
{
candidate = MathMax(candidate, NormalizeDouble(market.ask + buffer, market.digits));
if(candidate < stopLoss - market.point) ModifyProtection(ticket, candidate, takeProfit);
}
return;
}
if(InpVSL < VIRTUAL_ON_FREEZE || InpVSL > VIRTUAL_OFF) return;
bool forceVirtual = InpVSL == VIRTUAL_ALWAYS && !testing;
bool brokerRestricted = market.stopsDistance > 10.0 * market.point || market.freezeDistance > 10.0 * market.point;
if(!forceVirtual && (modified || !brokerRestricted)) return;
double candidate = VirtualProtectionCandidate(ticket, side, entry, reference, stopLoss, config, market, true);
if(candidate > 0.0) TightenVirtualStop(ticket, side, candidate);
}
datetime lastDeferredCloseMessage = 0;
void CloseCrossedStop(const ulong ticket)
{
if(CloseManagedPosition(ticket))
{
Print("Closing with virtual SL | #", ticket);
ClearVirtualTarget(ticket);
return;
}
uint code = trade.ResultRetcode();
bool retryable = code == TRADE_RETCODE_REQUOTE || code == TRADE_RETCODE_PRICE_CHANGED ||
code == TRADE_RETCODE_PRICE_OFF || code == TRADE_RETCODE_TOO_MANY_REQUESTS ||
code == TRADE_RETCODE_FROZEN;
if(retryable)
{
datetime now = TimeCurrent();
if(now == lastDeferredCloseMessage) return;
lastDeferredCloseMessage = now;
Print("Virtual SL close deferred (", trade.ResultRetcodeDescription(), ") #", ticket,
" - price inside freeze band, will retry");
}
else Print("Virtual SL close failed: ", code, " (", trade.ResultRetcodeDescription(), ") #", ticket);
}
bool ManageStrategyPositions(const long magic, const ENUM_POSITION_TYPE side,
const StrategyConfig &config, const SymbolSnapshot &market)
{
bool activity = false;
for(int index = PositionsTotal() - 1; index >= 0; --index)
{
ulong ticket = PositionGetTicket(index);
if(ticket == 0 || PositionGetString(POSITION_SYMBOL) != market.symbol ||
PositionGetInteger(POSITION_MAGIC) != magic ||
(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE) != side)
continue;
double entry = NormalizeDouble(PositionGetDouble(POSITION_PRICE_OPEN), market.digits);
double stopLoss = NormalizeDouble(PositionGetDouble(POSITION_SL), market.digits);
double takeProfit = NormalizeDouble(PositionGetDouble(POSITION_TP), market.digits);
datetime opened = (datetime)PositionGetInteger(POSITION_TIME);
double direction = side == POSITION_TYPE_BUY ? 1.0 : -1.0;
if(takeProfit == 0.0)
takeProfit = NormalizeDouble(entry + direction * (1.0 + config.takeProfitExtraFraction) *
config.takeProfitBaseDistance * market.priceUnit, market.digits);
if(CheckFallbackStop(ticket, market.bid, market.ask)) return true;
double quickThreshold = InpTrailMode == TRAIL_FIXED ? (double)InpFixedBE : 250.0;
double quickLock = InpTrailMode == TRAIL_FIXED ? 0.0 : 10.0;
double quickStop = NormalizeDouble(entry + direction * quickLock * market.point, market.digits);
bool needsQuickStop;
if(side == POSITION_TYPE_BUY)
needsQuickStop = stopLoss < entry && market.bid >= entry + quickThreshold * market.point &&
quickStop > stopLoss + market.point && MathAbs(market.bid - quickStop) > market.freezeDistance;
else
needsQuickStop = (stopLoss > entry || stopLoss == 0.0) && market.ask <= entry - quickThreshold * market.point &&
(quickStop < stopLoss - market.point || stopLoss == 0.0) && MathAbs(market.ask - quickStop) > market.freezeDistance;
if(needsQuickStop)
{
ModifyProtection(ticket, quickStop, takeProfit);
continue;
}
bool modified = false;
if(stopLoss == 0.0)
{
stopLoss = NormalizeDouble(entry - direction * config.stopDistance * market.priceUnit, market.digits);
modified = ModifyProtection(ticket, stopLoss, takeProfit);
}
if(CheckFakeoutExits(ticket, opened, entry, side, market)) return true;
double reference = EntryReferencePrice(ticket, entry);
double hardStop = entry - config.stopDistance * market.priceUnit - market.spread;
if(side == POSITION_TYPE_SELL)
hardStop = entry + config.stopDistance * market.priceUnit + market.spread;
double roundedHardStop = NormalizeDouble(hardStop, market.digits);
bool improveHardStop = side == POSITION_TYPE_BUY ? stopLoss < roundedHardStop : roundedHardStop < stopLoss;
if(improveHardStop)
{
stopLoss = roundedHardStop;
ModifyProtection(ticket, stopLoss, takeProfit);
}
bool hardStopCrossed = side == POSITION_TYPE_BUY ? market.bid < hardStop : market.ask > hardStop;
if(hardStopCrossed)
{
CloseManagedPosition(ticket);
ClearVirtualTarget(ticket);
return true;
}
if(CheckVirtualTarget(ticket, entry, side, market)) return true;
if(side == POSITION_TYPE_BUY)
{
if(ApplyBuyProtection(ticket, entry, reference, stopLoss, takeProfit, config, market)) modified = true;
}
else
{
if(ApplySellProtection(ticket, entry, reference, stopLoss, takeProfit, config, market)) modified = true;
}
ApplyFallbackProtection(ticket, side, entry, reference, stopLoss, takeProfit, modified, config, market);
bool stopCrossed = side == POSITION_TYPE_BUY ? stopLoss > market.bid : stopLoss < market.ask;
if(stopCrossed)
{
CloseCrossedStop(ticket);
return true;
}
if(modified) activity = true;
}
return activity;
}
struct DailyRiskState
{
int dailyBars;
double openingBalance;
double peakProfit;
bool paused;
};
DailyRiskState dailyRisk;
void CloseDailyRiskPositions(const string symbol)
{
long fillingFlags = 0;
bool fillingKnown = SymbolInfoInteger(symbol, SYMBOL_FILLING_MODE, fillingFlags);
ENUM_ORDER_TYPE_FILLING filling = ORDER_FILLING_FOK;
bool retryWithIoc = true;
if(fillingKnown)
{
if((fillingFlags & SYMBOL_FILLING_IOC) != 0)
{
filling = ORDER_FILLING_IOC;
retryWithIoc = false;
}
else if((fillingFlags & SYMBOL_FILLING_FOK) != 0) filling = ORDER_FILLING_FOK;
else
{
filling = ORDER_FILLING_RETURN;
retryWithIoc = false;
}
}
for(int i = PositionsTotal() - 1; i >= 0; --i)
{
ulong ticket = PositionGetTicket(i);
if(ticket == 0 || !IsSelectedOwnedPosition(symbol)) continue;
ENUM_POSITION_TYPE side = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
MqlTradeRequest request = {};
MqlTradeResult result = {};
request.action = TRADE_ACTION_DEAL;
request.position = ticket;
request.symbol = symbol;
request.volume = PositionGetDouble(POSITION_VOLUME);
request.deviation = 5000;
request.type_filling = filling;
request.type = side == POSITION_TYPE_BUY ? ORDER_TYPE_SELL : ORDER_TYPE_BUY;
request.price = SymbolInfoDouble(symbol, side == POSITION_TYPE_BUY ? SYMBOL_BID : SYMBOL_ASK);
if(OrderSend(request, result))
{
Print("DD KILL | Position closed #", ticket, " | ", EnumToString(side), " ",
DoubleToString(request.volume, 2), " lots");
continue;
}
Print("DD KILL | PositionClose FAILED #", ticket, " | retcode: ", result.retcode,
" | error: ", GetLastError(), " | fill: ", EnumToString(filling));
if(!retryWithIoc) continue;
request.type_filling = ORDER_FILLING_IOC;
request.price = SymbolInfoDouble(symbol, side == POSITION_TYPE_BUY ? SYMBOL_BID : SYMBOL_ASK);
if(OrderSend(request, result)) Print("DD KILL | PositionClose RETRY OK #", ticket);
else Print("DD KILL | PositionClose RETRY FAILED #", ticket, " | retcode: ", result.retcode,
" | error: ", GetLastError());
}
for(int i = OrdersTotal() - 1; i >= 0; --i)
{
ulong ticket = OrderGetTicket(i);
if(ticket == 0 || !IsSelectedOwnedOrder(symbol)) continue;
ENUM_ORDER_TYPE type = (ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE);
if(type != ORDER_TYPE_BUY_LIMIT && type != ORDER_TYPE_SELL_LIMIT &&
type != ORDER_TYPE_BUY_STOP && type != ORDER_TYPE_SELL_STOP) continue;
MqlTradeRequest request = {};
MqlTradeResult result = {};
request.action = TRADE_ACTION_REMOVE;
request.order = ticket;
if(OrderSend(request, result)) Print("DD KILL | Pending deleted #", ticket);
else Print("DD KILL | OrderDelete FAILED #", ticket, " | retcode: ", result.retcode,
" | error: ", GetLastError());
}
}
bool UpdateDailyRisk(const string symbol)
{
int bars = Bars(symbol, PERIOD_D1);
if(dailyRisk.dailyBars != bars)
{
dailyRisk.dailyBars = bars;
dailyRisk.openingBalance = AccountInfoDouble(ACCOUNT_BALANCE);
dailyRisk.peakProfit = 0.0;
dailyRisk.paused = false;
}
if(dailyRisk.openingBalance <= 0.0)
dailyRisk.openingBalance = AccountInfoDouble(ACCOUNT_BALANCE);
if(InpMaxDailyDD <= 0.0)
return dailyRisk.paused;
double profit = 0.0;
if(HistorySelect(iTime(symbol, PERIOD_D1, 0), TimeCurrent()))
{
for(int i = 0; i < HistoryDealsTotal(); ++i)
{
ulong deal = HistoryDealGetTicket(i);
if(deal == 0 || HistoryDealGetString(deal, DEAL_SYMBOL) != symbol ||
!IsOwnedMagic(HistoryDealGetInteger(deal, DEAL_MAGIC)))
continue;
ENUM_DEAL_ENTRY entry = (ENUM_DEAL_ENTRY)HistoryDealGetInteger(deal, DEAL_ENTRY);
if(entry == DEAL_ENTRY_OUT || entry == DEAL_ENTRY_INOUT)
profit += HistoryDealGetDouble(deal, DEAL_PROFIT) +
HistoryDealGetDouble(deal, DEAL_SWAP) + HistoryDealGetDouble(deal, DEAL_COMMISSION);
}
}
for(int i = PositionsTotal() - 1; i >= 0; --i)
{
if(PositionGetTicket(i) != 0 && IsSelectedOwnedPosition(symbol))
profit += PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP);
}
dailyRisk.peakProfit = MathMax(dailyRisk.peakProfit, profit);
double threshold = InpMaxDailyDD;
if(InpMaxDDUnit != DAILY_LOSS_MONEY)
threshold = dailyRisk.openingBalance * InpMaxDailyDD / 100.0;
if(profit < 0.0 && -profit > threshold)
{
if(!dailyRisk.paused)
Print("DD KILL | Max Daily DD reached (", DoubleToString(InpMaxDailyDD, 1),
InpMaxDDUnit == DAILY_LOSS_MONEY ? "$" : "%", ") | Daily PnL: ", DoubleToString(profit, 2),
" | Closing EA positions + pendings only");
CloseDailyRiskPositions(symbol);
dailyRisk.paused = true;
dailyRisk.peakProfit = 0.0;
}
return dailyRisk.paused;
}
struct NewsSettings
{
int brokerUtcOffsetHours;
int minutesAfter;
bool blockHighImpact;
bool blockHolidays;
};
struct NewsEvent
{
datetime time;
string title;
int impact;
bool blocksTrading;
};
class NewsCalendar
{
private:
string m_text;
datetime m_downloadedAt;
datetime m_nextAttempt;
datetime m_lastParse;
datetime m_lastWarning;
NewsEvent m_events[64];
int m_count;
bool m_loaded;
bool m_holiday;
bool m_webRequestForbidden;
int m_successfulFetches;
string Trim(string value)
{
StringTrimLeft(value);
StringTrimRight(value);
return value;
}
string Field(const string xml,const string name)
{
const string opening="<"+name+">";
const string closing="</"+name+">";
int first=StringFind(xml,opening);
if(first<0) return "";
first+=StringLen(opening);
const int last=StringFind(xml,closing,first);
if(last<0) return "";
string value=StringSubstr(xml,first,last-first);
StringReplace(value,"<![CDATA[","");
StringReplace(value,"]]>","");
StringReplace(value,"&","&");
return Trim(value);
}
bool ReadTime(string text,int &hour,int &minute)
{
StringToLower(text);
const bool pm=StringFind(text,"pm")>=0;
const bool am=StringFind(text,"am")>=0;
if(!pm && !am) return false;
StringReplace(text,"am","");
StringReplace(text,"pm","");
text=Trim(text);
const int colon=StringFind(text,":");
if(colon<0) return false;
hour=(int)StringToInteger(StringSubstr(text,0,colon));
minute=(int)StringToInteger(StringSubstr(text,colon+1));
if(pm && hour<12) hour+=12;
if(am && hour==12) hour=0;
return hour>=0 && hour<24 && minute>=0 && minute<60;
}
datetime ReadDateTime(const string date,const int hour,const int minute,
const int offsetHours)
{
if(StringLen(date)<10) return 0;
const int month=(int)StringToInteger(StringSubstr(date,0,2));
const int day=(int)StringToInteger(StringSubstr(date,3,2));
const int year=(int)StringToInteger(StringSubstr(date,6,4));
if(month<1 || month>12 || day<1 || day>31 || year<2000) return 0;
const datetime utc=StringToTime(StringFormat("%04d.%02d.%02d %02d:%02d",
year,month,day,hour,minute));
if(utc<=0) return 0;
return (datetime)((long)utc+(long)offsetHours*3600);
}
bool Fetch(const string url,string &text,int &httpStatus)
{
char request[],response[];
string responseHeaders;
ResetLastError();
httpStatus=WebRequest("GET",url,"User-Agent: Mozilla/5.0\r\n",8000,
request,response,responseHeaders);
if(httpStatus==-1)
{
m_webRequestForbidden=GetLastError()==4014;
return false;
}
m_webRequestForbidden=false;
if(httpStatus!=200) return false;
text=CharArrayToString(response,0,-1,CP_UTF8);
if(StringFind(text,"<event>")<0) return false;
m_successfulFetches++;
return true;
}
void ReadCache()
{
const string filename="LZ_FFCal.xml";
if(!FileIsExist(filename,FILE_COMMON)) return;
const int file=FileOpen(filename,FILE_READ|FILE_BIN|FILE_COMMON);
if(file==INVALID_HANDLE) return;
const int size=(int)FileSize(file);
if(size<1)
{
FileClose(file);
return;
}
uchar bytes[];
ArrayResize(bytes,size);
FileReadArray(file,bytes,0,size);
FileClose(file);
const string text=CharArrayToString(bytes,0,size,CP_UTF8);
if(StringLen(text)<=20 || StringFind(text,"LZFF|")!=0) return;
const int newline=StringFind(text,"\n");
if(newline<0) return;
const datetime timestamp=(datetime)StringToInteger(StringSubstr(text,5,newline-5));
if(timestamp>m_downloadedAt)
{
m_text=text;
m_downloadedAt=timestamp;
}
}
bool WriteCache(const string text)
{
const int file=FileOpen("LZ_FFCal.xml",FILE_WRITE|FILE_BIN|FILE_COMMON);
if(file==INVALID_HANDLE) return false;
uchar bytes[];
int size=StringToCharArray(text,bytes,0,-1,CP_UTF8);
if(size>0) size--;
if(size>0) FileWriteArray(file,bytes,0,size);
FileClose(file);
return size>0;
}
void Refresh(const datetime now,const int offsetHours)
{
MqlDateTime date;
TimeToStruct(now,date);
date.hour=1;
date.min=0;
date.sec=0;
datetime boundary=StructToTime(date);
if(now<boundary) boundary-=86400;
boundary=(datetime)((long)boundary-(long)offsetHours*3600);
if(m_downloadedAt>=boundary && StringLen(m_text)>20) return;
ReadCache();
if(m_downloadedAt>=boundary || now<m_nextAttempt) return;
m_nextAttempt=now+1800;
const string lockName="LZ_FFCAL_LOCK";
const double lockTime=GlobalVariableGet(lockName);
if(lockTime>0.0 && (long)TimeGMT()-(long)lockTime<=119) return;
GlobalVariableSet(lockName,(double)TimeGMT());
string thisWeek="",nextWeek="";
int status=0;
if(!Fetch("",thisWeek,status))
{
GlobalVariableSet(lockName,0.0);
Print("ForexFactory download failed (HTTP ",status,", error ",
GetLastError(),"). Keeping the previous calendar, next attempt in 30 min.");
return;
}
Sleep(1000);
const bool nextWeekAvailable=Fetch(
"",nextWeek,status);
GlobalVariableSet(lockName,0.0);
string combined="LZFF|"+IntegerToString((long)TimeGMT())+"\n"+thisWeek;
if(nextWeekAvailable) combined+=nextWeek;
if(!WriteCache(combined)) return;
m_text=combined;
m_downloadedAt=TimeGMT();
Print("ForexFactory calendar updated (thisweek",
nextWeekAvailable ? " + nextweek" : " only, nextweek failed",").");
}
void InsertEvent(const datetime time,const string title,const int impact,
const bool blocksTrading)
{
int index=m_count;
while(index>0 && m_events[index-1].time>time)
{
m_events[index]=m_events[index-1];
index--;
}
m_events[index].time=time;
m_events[index].title=title;
m_events[index].impact=impact;
m_events[index].blocksTrading=blocksTrading;
m_count++;
}
void Parse(const datetime now,const NewsSettings &settings)
{
m_count=0;
m_holiday=false;
m_loaded=false;
iПоказано первые 50% кода (символов: 104837)
Открыть по тарифу
Купите тариф с пакетом открытий или войдите, если тариф уже есть. Открытый файл останется в вашем кабинете навсегда.
Разовая покупка файла
На этой странице откроется полный исходный код файла вместо ознакомительного фрагмента.
Зачем нужен email? После подтверждения оплаты файл сохранится в кабинете на этот email. Если аккаунта ещё нет, мы создадим его и пришлём ссылку для задания пароля. Если аккаунт уже есть, покупка добавится в него. Укажите адрес, к которому у вас есть доступ.
Чтобы оформить заказ, введите email и перейдите к оплате.