385 lines
32 KiB
MQL5
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;
|
|
}
|
|
//+------------------------------------------------------------------+
|