316 lines
24 KiB
MQL5
316 lines
24 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| SimpleCandlesStrategy.mqh |
|
|
//| Copyright 2025, Yuriy Bykov |
|
|
//| https://www.mql5.com/ru/users/antekov |
|
|
//+------------------------------------------------------------------+
|
|
#property copyright "Copyright 2025, Yuriy Bykov"
|
|
#property link "https://www.mql5.com/ru/articles/17277"
|
|
#property version "1.02"
|
|
|
|
#include "../Include/Adwizard/Virtual/VirtualStrategy.mqh"
|
|
|
|
/** Описание стратегии
|
|
|
|
Входные параметры:
|
|
|
|
- Символ
|
|
- Таймфрейм для подсчёта однонаправленных свечей
|
|
- Количество однонаправленных свечей (signalSeqLen)
|
|
- Период ATR (periodATR)
|
|
- Stop Loss (в пунктах или % ATR) (stopLevel)
|
|
- Take Profit (в пунктах или % ATR) (takeLevel)
|
|
- Максимальное количество одновременно отрытых позиций (maxCountOfOrders)
|
|
- Максимальный размер спреда (maxSpread)
|
|
- Размер позиций
|
|
|
|
При наступлении нового бара проверяем направления последних закрытых signalSeqLen свечей.
|
|
|
|
Если направления одинаковые и количество открытых позиций меньше maxCountOfOrders
|
|
и текущий спред меньше maxSpread, то:
|
|
- Вычисляем StopLoss и TakeProfit. Если periodATR = 0, то просто отступаем от текущей
|
|
цены на количество пунктов, взятых из параметров stopLevel и takeLevel.
|
|
Если periodATR > 0, то рассчитываем величину ATR, используя параметр periodATR
|
|
для дневного таймфрейма.
|
|
От текущей цены отступаем на величины ATR * stopLevel и ATR * takeLevel.
|
|
|
|
- Открываем позицию SELL, если направления свечей были вверх и
|
|
позицию BUY, если направления свечей были вниз.
|
|
При открытии устанавливаем рассчитанные ранее уровни StopLoss и TakeProfit.
|
|
*/
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Торговая стратегия c использованием однонаправленных свечей |
|
|
//+------------------------------------------------------------------+
|
|
class CSimpleCandlesStrategy : public CVirtualStrategy {
|
|
protected:
|
|
string m_symbol; // Символ (торговый инструмент)
|
|
ENUM_TIMEFRAMES m_timeframe; // Период графика (таймфрейм)
|
|
|
|
//--- Параметры сигнала к открытию
|
|
int m_signalSeqLen; // Количество однонаправленных свечей
|
|
int m_periodATR; // Период ATR
|
|
|
|
//--- Параметры позиций
|
|
double m_stopLevel; // Stop Loss (в пунктах или % ATR)
|
|
double m_takeLevel; // Take Profit (в пунктах или % ATR)
|
|
|
|
//--- Параметры управление капиталом
|
|
int m_maxCountOfOrders; // Макс. количество одновременно отрытых позиций
|
|
int m_maxSpread; // Макс. допустимый спред (в пунктах)
|
|
|
|
CSymbolInfo *m_symbolInfo; // Объект для получения информации о свойствах символа
|
|
|
|
double m_tp; // Stop Loss в пунктах
|
|
double m_sl; // Take Profit в пунктах
|
|
|
|
//--- Методы
|
|
int SignalForOpen(); // Сигнал для открытия позиции
|
|
void OpenBuy(); // Открытие позиции BUY
|
|
void OpenSell(); // Открытие позиции SELL
|
|
|
|
double ChannelWidth(ENUM_TIMEFRAMES p_tf = PERIOD_D1); // Расчёт величины ATR
|
|
void UpdateLevels(); // Обновление уровней SL и TP
|
|
|
|
// Закрытый конструктор
|
|
CSimpleCandlesStrategy(string p_params);
|
|
|
|
public:
|
|
// Статический конструктор
|
|
STATIC_CONSTRUCTOR(CSimpleCandlesStrategy);
|
|
|
|
virtual string operator~() override; // Преобразование объекта в строку
|
|
virtual void Tick() override; // Обработчик события OnTick
|
|
};
|
|
|
|
// Регистрация класса-наследника CFactorable
|
|
REGISTER_FACTORABLE_CLASS(CSimpleCandlesStrategy);
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Конструктор |
|
|
//+------------------------------------------------------------------+
|
|
CSimpleCandlesStrategy::CSimpleCandlesStrategy(string p_params) {
|
|
// Читаем параметры из строки инициализации
|
|
m_params = p_params;
|
|
m_symbol = ReadString(p_params);
|
|
m_timeframe = (ENUM_TIMEFRAMES) ReadLong(p_params);
|
|
m_signalSeqLen = (int) ReadLong(p_params);
|
|
m_periodATR = (int) ReadLong(p_params);
|
|
m_stopLevel = ReadDouble(p_params);
|
|
m_takeLevel = ReadDouble(p_params);
|
|
m_maxCountOfOrders = (int) ReadLong(p_params);
|
|
m_maxSpread = (int) ReadLong(p_params);
|
|
|
|
if(IsValid()) {
|
|
// Запрашиваем нужное количество объектов для виртуальных позиций
|
|
CVirtualReceiver::Get(&this, m_orders, m_maxCountOfOrders);
|
|
|
|
// Добавляем отслеживание нового бара на нужном таймфрейме
|
|
IsNewBar(m_symbol, m_timeframe);
|
|
|
|
// Создаём информационный объект для нужного символа
|
|
m_symbolInfo = CSymbolsMonitor::Instance()[m_symbol];
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Преобразование объекта в строку |
|
|
//+------------------------------------------------------------------+
|
|
string CSimpleCandlesStrategy::operator~() {
|
|
return StringFormat("%s(%s)", typename(this), m_params);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| "Tick" event handler function |
|
|
//+------------------------------------------------------------------+
|
|
void CSimpleCandlesStrategy::Tick() override {
|
|
// Если наступил новый бар по заданному символу и таймфрейму
|
|
if(IsNewBar(m_symbol, m_timeframe)) {
|
|
// Если количество открытых позиций меньше допустимого
|
|
if(m_ordersTotal < m_maxCountOfOrders) {
|
|
// Получаем сигнал на открытие
|
|
int signal = SignalForOpen();
|
|
|
|
if(signal == 1) { // Если сигнал на покупку, то
|
|
OpenBuy(); // открываем позицию BUY
|
|
} else if(signal == -1) { // Если сигнал на продажу, то
|
|
OpenSell(); // открываем позицию SELL_STOP
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Сигнал для открытия отложенных ордеров |
|
|
//+------------------------------------------------------------------+
|
|
int CSimpleCandlesStrategy::SignalForOpen() {
|
|
// По-умолчанию сигнала на открытие нет
|
|
int signal = 0;
|
|
|
|
MqlRates rates[];
|
|
// Копируем значения котировок (свечей) в массив-приёмник.
|
|
// Для проверки сигнала нам нужно m_signalSeqLen закрытых свечей и текущая свеча,
|
|
// поэтому всего m_signalSeqLen + 1
|
|
int res = CopyRates(m_symbol, m_timeframe, 0, m_signalSeqLen + 1, rates);
|
|
|
|
// Если скопировалось нужное количество свечей
|
|
if(res == m_signalSeqLen + 1) {
|
|
signal = 1; // сигнал на покупку
|
|
|
|
// Перебираем все закрытые свечи
|
|
for(int i = 1; i <= m_signalSeqLen; i++) {
|
|
// Если встречается хоть одна свеча вверх, то отменяем сигнал
|
|
if(rates[i].open < rates[i].close ) {
|
|
signal = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(signal == 0) {
|
|
signal = -1; // иначе - сигнал на продажу
|
|
|
|
// Перебираем все закрытые свечи
|
|
for(int i = 1; i <= m_signalSeqLen; i++) {
|
|
// Если встречается хоть одна свеча вниз, то отменяем сигнал
|
|
if(rates[i].open > rates[i].close ) {
|
|
signal = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Если сигнал есть, то
|
|
if(signal != 0) {
|
|
// Если текущий спред больше максимально разрешённого, то
|
|
if(rates[0].spread > m_maxSpread) {
|
|
PrintFormat(__FUNCTION__" | IGNORE %s Signal, spread is too big (%d > %d)",
|
|
(signal > 0 ? "BUY" : "SELL"),
|
|
rates[0].spread, m_maxSpread);
|
|
signal = 0; // Отменяем сигнал
|
|
}
|
|
}
|
|
|
|
return signal;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Открытие ордера BUY |
|
|
//+------------------------------------------------------------------+
|
|
void CSimpleCandlesStrategy::OpenBuy() {
|
|
// Берем необходимую нам информацию о символе и ценах
|
|
double point = m_symbolInfo.Point();
|
|
int digits = m_symbolInfo.Digits();
|
|
|
|
// Цена открытия
|
|
double price = m_symbolInfo.Ask();
|
|
|
|
// Обновим уровни SL и TP, рассчитав ATR
|
|
UpdateLevels();
|
|
|
|
// Уровни StopLoss и TakeProfit
|
|
double sl = NormalizeDouble(price - m_sl * point, digits);
|
|
double tp = NormalizeDouble(price + m_tp * point, digits);
|
|
|
|
bool res = false;
|
|
for(int i = 0; i < m_maxCountOfOrders; i++) { // Перебираем все виртуальные позиции
|
|
if(!m_orders[i].IsOpen()) { // Если нашли не открытую, то открываем
|
|
// Открытие виртуальной позиции SELL
|
|
res = m_orders[i].Open(m_symbol, ORDER_TYPE_BUY, m_fixedLot,
|
|
0,
|
|
NormalizeDouble(sl, digits),
|
|
NormalizeDouble(tp, digits));
|
|
|
|
break; // и выходим
|
|
}
|
|
}
|
|
|
|
if(!res) {
|
|
PrintFormat(__FUNCTION__" | ERROR opening BUY virtual order", 0);
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Открытие ордера SELL |
|
|
//+------------------------------------------------------------------+
|
|
void CSimpleCandlesStrategy::OpenSell() {
|
|
// Берем необходимую нам информацию о символе и ценах
|
|
double point = m_symbolInfo.Point();
|
|
int digits = m_symbolInfo.Digits();
|
|
|
|
// Цена открытия
|
|
double price = m_symbolInfo.Bid();
|
|
|
|
// Обновим уровни SL и TP, рассчитав ATR
|
|
UpdateLevels();
|
|
|
|
// Уровни StopLoss и TakeProfit
|
|
double sl = NormalizeDouble(price + m_sl * point, digits);
|
|
double tp = NormalizeDouble(price - m_tp * point, digits);
|
|
|
|
bool res = false;
|
|
for(int i = 0; i < m_maxCountOfOrders; i++) { // Перебираем все виртуальные позиции
|
|
if(!m_orders[i].IsOpen()) { // Если нашли не открытую, то открываем
|
|
// Открытие виртуальной позиции SELL
|
|
|
|
res = m_orders[i].Open(m_symbol, ORDER_TYPE_SELL, m_fixedLot,
|
|
0,
|
|
NormalizeDouble(sl, digits),
|
|
NormalizeDouble(tp, digits));
|
|
|
|
break; // и выходим
|
|
}
|
|
}
|
|
|
|
if(!res) {
|
|
PrintFormat(__FUNCTION__" | ERROR opening SELL virtual order", 0);
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Обновление уровней SL и TP по рассчитанному ATR |
|
|
//+------------------------------------------------------------------+
|
|
void CSimpleCandlesStrategy::UpdateLevels() {
|
|
// Рассчитываем ATR
|
|
double channelWidth = (m_periodATR > 0 ? ChannelWidth() : 1);
|
|
|
|
// Обновляем уровни SL и TP
|
|
m_sl = m_stopLevel * channelWidth;
|
|
m_tp = m_takeLevel * channelWidth;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Расчёт величины ATR (нестандартная реализация) |
|
|
//+------------------------------------------------------------------+
|
|
double CSimpleCandlesStrategy::ChannelWidth(ENUM_TIMEFRAMES p_tf = PERIOD_D1) {
|
|
int n = m_periodATR; // Количество баров для расчёта
|
|
MqlRates rates[]; // Массив для котировок
|
|
|
|
// Копируем котировки дневного (по умолчанию) таймфрейма
|
|
int res = CopyRates(m_symbol, p_tf, 1, n, rates);
|
|
|
|
// Если скопировалось нужное количество
|
|
if(res == n) {
|
|
double tr[]; // Массив для диапазонов цены
|
|
ArrayResize(tr, n); // Изменяем его размер
|
|
|
|
double s = 0; // Сумма для подсчёта среднего
|
|
FOREACH(rates) {
|
|
tr[i] = rates[i].high - rates[i].low; // Запоминаем размер бара
|
|
}
|
|
|
|
ArraySort(tr); // Сортируем размеры
|
|
|
|
// Суммируем внутренние две четверти размеров баров
|
|
for(int i = n / 4; i < n * 3 / 4; i++) {
|
|
s += tr[i];
|
|
}
|
|
|
|
// Возвращаем средний размер в пунктах
|
|
return 2 * s / n / m_symbolInfo.Point();
|
|
}
|
|
|
|
return 0.0;
|
|
}
|
|
//+------------------------------------------------------------------+
|