Adwizard/Strategies/SimpleVolumesStrategy.mqh
2025-04-11 13:28:40 +03:00

385 lines
32 KiB
MQL5

//+------------------------------------------------------------------+
//| SimpleVolumesStrategy.mqh |
//| Copyright 2024, Yuriy Bykov |
//| https://www.mql5.com/ru/users/antekov |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, Yuriy Bykov"
#property link "https://www.mql5.com/ru/articles/16452"
#property version "1.11"
#include "../Utils/NewBarEvent.mqh"
#include "../Virtual/VirtualStrategy.mqh"
/** Описание стратегии
Входные параметры стратегии:
- Символ
- Период
- Количество свечей для усреднения объемов (K)
- Относительное отклонение от среднего для открытия первого ордера (D)
- Относительное отклонение от среднего для открытия второго и последующих ордеров (D_add)
- Расстояние от цены до отложенного ордера
- Stop Loss (в пунктах)
- Take Profit (в пунктах)
- Время истечения отложенных ордеров (в минутах)
- Максимальное количество одновременно отрытых ордеров (N_max)
- Объем одного ордера
Находим количество открытых ордеров и позиций (N).
Если оно меньше N_max, то:
- вычисляем средний тиковый объём за последние K закрытых свечей, получаем величину V_avr.
- Если выполнено условие V > V_avr * (1 + D + N * D_add), то:
- определяем направление изменения цены на текущей свече: если цена увеличилась,
то будем выставлять BUY, BUY_STOP или BUY_LIMIT, а иначе - SELL, SELL_STOP или SELL_LIMIT
- выставляем рыночный или отложенный ордер исходя из заданного в параметрах расстояния
(0 - рыночный, >0 - отложенный стоповый, <0 - отложенный лимитный),
времени истечения и уровнями StopLoss и TakeProfit.
*/
//+------------------------------------------------------------------+
//| Торговая стратегия с использованием тиковых объемов |
//+------------------------------------------------------------------+
class CSimpleVolumesStrategy : public CVirtualStrategy {
protected:
string m_symbol; // Символ (торговый инструмент)
ENUM_TIMEFRAMES m_timeframe; // Период графика (таймфрейм)
//--- Параметры сигнала к открытию
int m_signalPeriod; // Количество свечей для усреднения объемов
double m_signalDeviation; // Относ. откл. от среднего для открытия первого ордера
double m_signaAddlDeviation; // Относ. откл. от среднего для открытия второго и последующих ордеров
//--- Параметры отложенных ордеров
int m_openDistance; // Расстояние от цены до отлож. ордера
double m_stopLevel; // Stop Loss (в пунктах)
double m_takeLevel; // Take Profit (в пунктах)
int m_ordersExpiration; // Время истечения отложенных ордеров (в минутах)
//--- Параметры управление капиталом
int m_maxCountOfOrders; // Макс. количество одновременно отрытых ордеров
CSymbolInfo *m_symbolInfo; // Объект для получения информации о свойствах символа
int m_iVolumesHandle; // Хэндл индикатора тиковых объемов
double m_volumes[]; // Массив-приемник значений индикатора (самих объемов)
//--- Методы
int SignalForOpen(); // Сигнал для открытия отложенных ордеров
void OpenBuyOrder(); // Открытие ордера BUY_STOP
void OpenSellOrder(); // Открытие ордера SELL_STOP
double ArrayAverage(
const double &array[]); // Среднее значение массива чисел
public:
//--- Публичные методы
CSimpleVolumesStrategy(string p_params); // Конструктор
virtual string operator~() override; // Преобразование объекта в строку
virtual void Tick() override; // Обработчик события OnTick
virtual void Save() override; // Сохранение состояния
virtual bool Load() override; // Загрузка состояния
// Замена названий символов
virtual bool SymbolsReplace(CHashMap<string, string> &p_symbolsMap);
};
//+------------------------------------------------------------------+
//| Конструктор |
//+------------------------------------------------------------------+
CSimpleVolumesStrategy::CSimpleVolumesStrategy(string p_params) {
m_params = p_params;
m_symbol = ReadString(p_params);
m_timeframe = (ENUM_TIMEFRAMES) ReadLong(p_params);
m_signalPeriod = (int) ReadLong(p_params);
m_signalDeviation = ReadDouble(p_params);
m_signaAddlDeviation = ReadDouble(p_params);
m_openDistance = (int) ReadLong(p_params);
m_stopLevel = ReadDouble(p_params);
m_takeLevel = ReadDouble(p_params);
m_ordersExpiration = (int) ReadLong(p_params);
m_maxCountOfOrders = (int) ReadLong(p_params);
if(IsValid()) {
CVirtualReceiver::Get(GetPointer(this), m_orders, m_maxCountOfOrders);
// Загружаем индикатор для получения тиковых объемов
m_iVolumesHandle = iVolumes(m_symbol, m_timeframe, VOLUME_TICK);
// Если индикатор загружен успешно
if(m_iVolumesHandle != INVALID_HANDLE) {
// Устанавливаем размер массива-приемника тиковых объемов и нужную адресацию
ArrayResize(m_volumes, m_signalPeriod);
ArraySetAsSeries(m_volumes, true);
// Регистрируем обработчик события нового бара на минимальном таймфрейме
//IsNewBar(m_symbol, PERIOD_M1);
m_symbolInfo = CSymbolsMonitor::Instance()[m_symbol];
} else {
// Иначе устанавливаем ошибочное состояние объекта
SetInvalid(__FUNCTION__, "Can't load iVolumes()");
}
}
}
//+------------------------------------------------------------------+
//| Преобразование объекта в строку |
//+------------------------------------------------------------------+
string CSimpleVolumesStrategy::operator~() {
return StringFormat("%s(%s)", typename(this), m_params);
}
//+------------------------------------------------------------------+
//| "Tick" event handler function |
//+------------------------------------------------------------------+
void CSimpleVolumesStrategy::Tick() override {
if(IsNewBar(m_symbol, PERIOD_M1)) {
// Если их количество меньше допустимого
if(m_ordersTotal < m_maxCountOfOrders) {
// Получаем сигнал на открытие
int signal = SignalForOpen();
if(signal == 1 /* || m_ordersTotal < 1 */) { // Если сигнал на покупку, то
OpenBuyOrder(); // открываем ордер BUY_STOP
} else if(signal == -1) { // Если сигнал на продажу, то
OpenSellOrder(); // открываем ордер SELL_STOP
}
}
}
}
//+------------------------------------------------------------------+
//| Замена названий символов |
//+------------------------------------------------------------------+
bool CSimpleVolumesStrategy::SymbolsReplace(CHashMap<string, string> &p_symbolsMap) {
// Если в словаре есть ключ, совпадающий с текущим символом
if(p_symbolsMap.ContainsKey(m_symbol)) {
string targetSymbol; // Целевой символ
// Если целевой символ для текущего успешно получен из словаря
if(p_symbolsMap.TryGetValue(m_symbol, targetSymbol)) {
// Обновляем текущий символ
m_symbol = targetSymbol;
}
}
return true;
}
//+------------------------------------------------------------------+
//| Сигнал для открытия отложенных ордеров |
//+------------------------------------------------------------------+
int CSimpleVolumesStrategy::SignalForOpen() {
// По-умолчанию сигнала на открытие нет
int signal = 0;
// Копируем значения объемов из индикаторного буфера в массив-приёмник
int res = CopyBuffer(m_iVolumesHandle, 0, 0, m_signalPeriod, m_volumes);
// Если скопировалось нужное количество чисел
if(res == m_signalPeriod) {
// Вычисляем их среднее значение
double avrVolume = ArrayAverage(m_volumes);
// Если текущий объем превысил заданный уровень, то
if(m_volumes[0] > avrVolume * (1 + m_signalDeviation + m_ordersTotal * m_signaAddlDeviation)) {
// если цена открытия свечи меньше текущей цены (закрытия), то
if(iOpen(m_symbol, m_timeframe, 0) < iClose(m_symbol, m_timeframe, 0)) {
signal = 1; // сигнал на покупку
} else {
signal = -1; // иначе - сигнал на продажу
}
}
}
return signal;
}
//+------------------------------------------------------------------+
//| Открытие ордера BUY |
//+------------------------------------------------------------------+
void CSimpleVolumesStrategy::OpenBuyOrder() {
// Обновляем информацию о текущих ценах для символа
//m_symbolInfo.Name(m_symbol);
//m_symbolInfo.RefreshRates();
// Берем необходимую нам информацию о символе и ценах
double point = m_symbolInfo.Point();
int digits = m_symbolInfo.Digits();
double bid = m_symbolInfo.Bid();
double ask = m_symbolInfo.Ask();
int spread = (int) ((ask - bid) / point); //m_symbolInfo.Spread();
// Сделаем, чтобы расстояние открытия было не меньше спреда
int distance = MathMax(MathAbs(m_openDistance), spread) * (m_openDistance < 0 ? -1 : 1);
// Цена открытия
double price = ask + distance * point;
// Уровни StopLoss и TakeProfit
double sl = NormalizeDouble(price - m_stopLevel * point, digits);
double tp = NormalizeDouble(price + (m_takeLevel + spread) * point, digits);
// Время истечения
datetime expiration = TimeCurrent() + m_ordersExpiration * 60;
bool res = false;
for(int i = 0; i < m_maxCountOfOrders; i++) { // Перебираем все виртуальные позиции
if(!m_orders[i].IsOpen()) { // Если нашли не открытую, то открываем
if(m_openDistance > 0) {
// Устанавливаем отложенный ордер SELL STOP
res = m_orders[i].Open(m_symbol, ORDER_TYPE_BUY_STOP, m_fixedLot,
NormalizeDouble(price, digits),
NormalizeDouble(sl, digits),
NormalizeDouble(tp, digits),
"", expiration);
} else if(m_openDistance < 0) {
// Устанавливаем отложенный ордер SELL LIMIT
res = m_orders[i].Open(m_symbol, ORDER_TYPE_BUY_LIMIT, m_fixedLot,
NormalizeDouble(price, digits),
NormalizeDouble(sl, digits),
NormalizeDouble(tp, digits),
"", expiration);
} else {
// Открытие виртуальной позиции 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 CSimpleVolumesStrategy::OpenSellOrder() {
// Обновляем информацию о текущих ценах для символа
//m_symbolInfo.Name(m_symbol);
//m_symbolInfo.RefreshRates();
// Берем необходимую нам информацию о символе и ценах
double point = m_symbolInfo.Point();
int digits = m_symbolInfo.Digits();
double bid = m_symbolInfo.Bid();
double ask = m_symbolInfo.Ask();
int spread = (int) ((ask - bid) / point); //m_symbolInfo.Spread();
// Сделаем, чтобы расстояние открытия было не меньше спреда
int distance = MathMax(MathAbs(m_openDistance), spread) * (m_openDistance < 0 ? -1 : 1);
// Цена открытия
double price = bid - distance * point;
// Уровни StopLoss и TakeProfit
double sl = NormalizeDouble(price + m_stopLevel * point, digits);
double tp = NormalizeDouble(price - (m_takeLevel + spread) * point, digits);
// Время истечения
datetime expiration = TimeCurrent() + m_ordersExpiration * 60;
bool res = false;
for(int i = 0; i < m_maxCountOfOrders; i++) { // Перебираем все виртуальные позиции
if(!m_orders[i].IsOpen()) { // Если нашли не открытую, то открываем
if(m_openDistance > 0) {
// Устанавливаем отложенный ордер SELL STOP
res = m_orders[i].Open(m_symbol, ORDER_TYPE_SELL_STOP, m_fixedLot,
NormalizeDouble(price, digits),
NormalizeDouble(sl, digits),
NormalizeDouble(tp, digits),
"", expiration);
} else if(m_openDistance < 0) {
// Устанавливаем отложенный ордер SELL LIMIT
res = m_orders[i].Open(m_symbol, ORDER_TYPE_SELL_LIMIT, m_fixedLot,
NormalizeDouble(price, digits),
NormalizeDouble(sl, digits),
NormalizeDouble(tp, digits),
"", expiration);
} else {
// Открытие виртуальной позиции 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);
}
}
//+------------------------------------------------------------------+
//| Среднее значение массива чисел со второго элемента |
//+------------------------------------------------------------------+
double CSimpleVolumesStrategy::ArrayAverage(const double &array[]) {
double s = 0;
int total = ArraySize(array) - 1;
for(int i = 1; i <= total; i++) {
s += array[i];
}
return s / MathMax(1, total);
}
//+------------------------------------------------------------------+
//| Сохранение состояния |
//+------------------------------------------------------------------+
void CSimpleVolumesStrategy::Save() {
double avrVolume = ArrayAverage(m_volumes);
// Сформируем общую часть ключа с типом и хешем стратегии
string key = "CSimpleVolumesStrategy[" + this.Hash() + "]";
// Сохраняем средний тиковый объём
CStorage::Set(key + ".avrVolume", avrVolume);
// Сохраняем массив тиковых объёмов
CStorage::Set(key + ".m_volumes", m_volumes);
// Вызываем метод базового класса (для сохранения виртуальных позиций)
CVirtualStrategy::Save();
}
//+------------------------------------------------------------------+
//| Загрузка состояния |
//+------------------------------------------------------------------+
bool CSimpleVolumesStrategy::Load() {
bool res = true;
double avrVolume = 0;
// Сформируем общую часть ключа с типом и хешем стратегии
string key = "CSimpleVolumesStrategy[" + this.Hash() + "]";
// Загружаем массив тиковых объёмов
res &= CStorage::Get(key + ".avrVolume", avrVolume);
// Загружаем массив тиковых объёмов
res &= CStorage::Get(key + ".m_volumes", m_volumes);
// Вызываем метод базового класса (для загрузки виртуальных позиций)
res &= CVirtualStrategy::Load();
return res;
}
//+------------------------------------------------------------------+