Исходный код
Представленный ниже код включает в себя два основных компонента: класс CKMeans, реализующий алгоритм кластеризации K-Means, и пример экспертного советника (EA) на MQL5, который использует этот класс для фильтрации торговых сигналов индикатора MACD. Класс CKMeans способен работать с многомерными данными, что позволяет нам использовать значения линии MACD, сигнальной линии и гистограммы как отдельные измерения для каждого сигнала.
Экспертный советник на этапе инициализации (OnInit()) собирает исторические данные MACD, обучает модель K-Means, а затем анализирует полученные центроиды для определения, какие кластеры соответствуют сильным бычьим, медвежьим или нейтральным сигналам. В дальнейшем, на каждом новом баре (OnTick()), текущие значения MACD классифицируются, и торговые действия предпринимаются только в случае принадлежности сигнала к ‘сильным’ кластерам, отфильтровывая тем самым ложные или слабые сигналы.
//+------------------------------------------------------------------+
//| K-Means Clustering Class for MQL5 |
//+------------------------------------------------------------------+
class CKMeans
{
private:
int m_num_clusters; // Количество кластеров (K)
int m_num_dimensions; // Размерность данных (например, 3 для MACD, Signal, Histogram)
double m_centroids[][]; // Центроиды кластеров: [cluster_idx][dimension_idx]
double m_data_points[][]; // Точки данных: [point_idx][dimension_idx]
int m_assignments[]; // Принадлежность точек к кластерам: [point_idx] = cluster_idx
int m_data_count; // Текущее количество добавленных точек данных
int m_data_capacity; // Текущая емкость массива m_data_points
public:
// Конструктор класса K-Means
CKMeans(int num_clusters, int num_dimensions)
{
m_num_clusters = num_clusters;
m_num_dimensions = num_dimensions;
m_data_count = 0;
m_data_capacity = 1000; // Начальная емкость, будет увеличиваться при необходимости
// Инициализация массивов для центроидов
ArrayResize(m_centroids, m_num_clusters);
for (int i = 0; i < m_num_clusters; i++)
{
ArrayResize(m_centroids[i], m_num_dimensions);
}
// Инициализация массивов для точек данных и их принадлежности
ArrayResize(m_data_points, m_data_capacity);
for (int i = 0; i < m_data_capacity; i++)
{
ArrayResize(m_data_points[i], m_num_dimensions);
}
ArrayResize(m_assignments, m_data_capacity);
}
// Деструктор (MQL5 автоматически управляет памятью для динамических массивов-членов)
~CKMeans()
{
}
// Добавление новой точки данных для обучения
void AddDataPoint(const double& data[])
{
if (ArraySize(data) != m_num_dimensions)
{
Print("Error: Data point dimensions mismatch.");
return;
}
// Увеличение емкости массива, если необходимо
if (m_data_count >= m_data_capacity)
{
m_data_capacity *= 2; // Удваиваем емкость
ArrayResize(m_data_points, m_data_capacity);
for (int i = m_data_count; i < m_data_capacity; i++)
{
ArrayResize(m_data_points[i], m_num_dimensions);
}
ArrayResize(m_assignments, m_data_capacity);
}
// Копирование данных
for (int i = 0; i < m_num_dimensions; i++)
{
m_data_points[m_data_count][i] = data[i];
}
m_data_count++;
}
// Обучение алгоритма K-Means
bool Train(int max_iterations)
{
if (m_data_count < m_num_clusters)
{
Print("Error: Not enough data points to form clusters.");
return false;
}
InitializeCentroids(); // Инициализация центроидов
bool changed = true;
for (int iter = 0; iter < max_iterations && changed; iter++)
{
changed = AssignToClusters(); // Назначение точек кластерам
UpdateCentroids(); // Обновление центроидов
}
return true;
}
// Классификация новой точки данных
int Classify(const double& data[]) const
{
if (ArraySize(data) != m_num_dimensions)
{
Print("Error: Data point dimensions mismatch for classification.");
return -1; // Индикация ошибки
}
int closest_cluster = -1;
double min_distance = DBL_MAX;
// Поиск ближайшего центроида
for (int i = 0; i < m_num_clusters; i++)
{
double dist = CalculateDistance(data, m_centroids[i]);
if (dist < min_distance)
{
min_distance = dist;
closest_cluster = i;
}
}
return closest_cluster;
}
// Получение значения центроида по индексу кластера и размерности
double GetCentroidValue(int cluster_idx, int dim_idx) const
{
if (cluster_idx < 0 || cluster_idx >= m_num_clusters ||
dim_idx < 0 || dim_idx >= m_num_dimensions)
{
Print("Error: Invalid centroid index.");
return 0.0;
}
return m_centroids[cluster_idx][dim_idx];
}
// Получение количества точек данных
int GetDataCount() const { return m_data_count; }
private:
// Инициализация центроидов случайным выбором из существующих точек данных
void InitializeCentroids()
{
MathSrand(GetTickCount()); // Инициализация генератора случайных чисел
int picked_indices[];
ArrayResize(picked_indices, m_num_clusters);
ArrayInitialize(picked_indices, -1);
for (int i = 0; i < m_num_clusters; i++)
{
int rand_idx;
bool unique = false;
while (!unique)
{
rand_idx = MathRand() % m_data_count;
unique = true;
for (int j = 0; j < i; j++)
{
if (picked_indices[j] == rand_idx)
{
unique = false;
break;
}
}
}
picked_indices[i] = rand_idx;
for (int d = 0; d < m_num_dimensions; d++)
{
m_centroids[i][d] = m_data_points[rand_idx][d];
}
}
}
// Вычисление Евклидова расстояния между двумя точками
double CalculateDistance(const double& p1[], const double& p2[]) const
{
double sum_sq_diff = 0.0;
for (int i = 0; i < m_num_dimensions; i++)
{
sum_sq_diff += MathPow(p1[i] - p2[i], 2);
}
return MathSqrt(sum_sq_diff);
}
// Назначение каждой точки данных ближайшему центроиду
bool AssignToClusters()
{
bool changed = false;
for (int i = 0; i < m_data_count; i++)
{
int closest_cluster = -1;
double min_distance = DBL_MAX;
for (int j = 0; j < m_num_clusters; j++)
{
double dist = CalculateDistance(m_data_points[i], m_centroids[j]);
if (dist < min_distance)
{
min_distance = dist;
closest_cluster = j;
}
}
if (m_assignments[i] != closest_cluster)
{
m_assignments[i] = closest_cluster;
changed = true;
}
}
return changed;
}
// Обновление позиций центроидов на основе текущих назначений
void UpdateCentroids()
{
double new_centroids[][];
int cluster_counts[];
ArrayResize(new_centroids, m_num_clusters);
for (int i = 0; i < m_num_clusters; i++)
{
ArrayResize(new_centroids[i], m_num_dimensions);
ArrayInitialize(new_centroids[i], 0.0);
}
ArrayResize(cluster_counts, m_num_clusters);
ArrayInitialize(cluster_counts, 0);
for (int i = 0; i < m_data_count; i++)
{
int cluster_idx = m_assignments[i];
if (cluster_idx != -1)
{
for (int d = 0; d < m_num_dimensions; d++)
{
new_centroids[cluster_idx][d] += m_data_points[i][d];
}
cluster_counts[cluster_idx]++;
}
}
for (int i = 0; i < m_num_clusters; i++)
{
if (cluster_counts[i] > 0)
{
for (int d = 0; d < m_num_dimensions; d++)
{
m_centroids[i][d] = new_centroids[i][d] / cluster_counts[i];
}
}
// Если кластер становится пустым, его центроид остается на месте.
// В более сложных реализациях можно было бы переинициализировать пустые кластеры.
}
}
};
//+------------------------------------------------------------------+
//| Expert Advisor (EA) using K-Means for MACD signal filtering |
//+------------------------------------------------------------------+
#property version "1.00"
#property copyright "FinFluct"
#property link "https://finfluct.com"
#property description "K-Means clustering for MACD signal filtering"
#property strict
// --- Входные параметры K-Means ---
input int K_Clusters = 4; // Количество кластеров для K-Means
input int MaxIterations = 100; // Максимальное количество итераций для обучения K-Means
input int HistoryBarsForTraining = 500; // Количество исторических баров для обучения K-Means
// --- Входные параметры MACD ---
input int MACD_FastPeriod = 12;
input int MACD_SlowPeriod = 26;
input int MACD_SignalPeriod = 9;
input double MinSignalStrengthThreshold = 0.01; // Порог для определения "силы" сигнала кластера
// Глобальные переменные
CKMeans *kmeans = NULL; // Указатель на объект K-Means
int macd_handle; // Хэндл индикатора MACD
// Размерыность данных для MACD: (MACD_Line, Signal_Line, Histogram_Value)
const int MACD_DIMENSIONS = 3;
// Глобальный массив для хранения типа сигнала, ассоциированного с каждым кластером.
// 1 = Bullish (покупка), -1 = Bearish (продажа), 0 = Neutral/Weak (бездействовать)
int cluster_signal_type[];
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
// Инициализация индикатора MACD
macd_handle = iMACD(_Symbol, _Period, MACD_FastPeriod, MACD_SlowPeriod, MACD_SignalPeriod);
if (macd_handle == INVALID_HANDLE)
{
Print("Failed to create MACD indicator handle. Error: ", GetLastError());
return INIT_FAILED;
}
// Создание объекта K-Means
kmeans = new CKMeans(K_Clusters, MACD_DIMENSIONS);
if (kmeans == NULL)
{
Print("Failed to create CKMeans object.");
return INIT_FAILED;
}
// --- Сбор исторических данных для обучения K-Means ---
double macd_buffer[], signal_buffer[], hist_buffer[];
ArraySetAsSeries(macd_buffer, true);
ArraySetAsSeries(signal_buffer, true);
ArraySetAsSeries(hist_buffer, true);
// Копирование данных MACD из буферов индикатора
int copied = CopyBuffer(macd_handle, INDICATOR_DATA_MACD, 0, HistoryBarsForTraining, macd_buffer);
if (copied != HistoryBarsForTraining)
{
Print("Failed to copy MACD buffer. Copied: ", copied, ", Expected: ", HistoryBarsForTraining);
delete kmeans;
return INIT_FAILED;
}
copied = CopyBuffer(macd_handle, INDICATOR_DATA_SIGNAL, 0, HistoryBarsForTraining, signal_buffer);
if (copied != HistoryBarsForTraining)
{
Print("Failed to copy Signal buffer. Copied: ", copied, ", Expected: ", HistoryBarsForTraining);
delete kmeans;
return INIT_FAILED;
}
copied = CopyBuffer(macd_handle, INDICATOR_DATA_HISTOGRAM, 0, HistoryBarsForTraining, hist_buffer);
if (copied != HistoryBarsForTraining)
{
Print("Failed to copy Histogram buffer. Copied: ", copied, ", Expected: ", HistoryBarsForTraining);
delete kmeans;
return INIT_FAILED;
}
// Добавление исторических данных в K-Means для обучения
double data_point[MACD_DIMENSIONS];
for (int i = HistoryBarsForTraining - 1; i >= 0; i--) // Итерация от старых баров к новым
{
data_point[0] = macd_buffer[i];
data_point[1] = signal_buffer[i];
data_point[2] = hist_buffer[i];
kmeans.AddDataPoint(data_point);
}
// --- Обучение K-Means ---
Print("Training K-Means with ", kmeans.GetDataCount(), " data points...");
if (!kmeans.Train(MaxIterations))
{
Print("K-Means training failed.");
delete kmeans;
return INIT_FAILED;
}
Print("K-Means training completed.");
// --- Анализ центроидов и присвоение типов сигналов ---
// Этот этап требует доменных знаний. Здесь используется простая эвристика:
// Если MACD_Line > 0, Signal_Line > 0, Histogram > MinSignalStrengthThreshold -> Bullish
// Если MACD_Line < 0, Signal_Line < 0, Histogram < -MinSignalStrengthThreshold -> Bearish
// В противном случае -> Neutral/Weak
ArrayResize(cluster_signal_type, K_Clusters);
for (int i = 0; i < K_Clusters; i++)
{
double macd_val = kmeans.GetCentroidValue(i, 0);
double signal_val = kmeans.GetCentroidValue(i, 1);
double hist_val = kmeans.GetCentroidValue(i, 2);
PrintFormat("Centroid %d: MACD=%.4f, Signal=%.4f, Hist=%.4f", i, macd_val, signal_val, hist_val);
if (macd_val > 0 && signal_val > 0 && hist_val > MinSignalStrengthThreshold)
{
cluster_signal_type[i] = 1; // Bullish
PrintFormat(" -> Cluster %d identified as BULLISH", i);
}
else if (macd_val < 0 && signal_val < 0 && hist_val < -MinSignalStrengthThreshold)
{
cluster_signal_type[i] = -1; // Bearish
PrintFormat(" -> Cluster %d identified as BEARISH", i);
}
else
{
cluster_signal_type[i] = 0; // Neutral/Weak
PrintFormat(" -> Cluster %d identified as NEUTRAL/WEAK", i);
}
}
return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
// Освобождение памяти, занятой объектом K-Means
if (kmeans != NULL)
{
delete kmeans;
kmeans = NULL;
}
// Освобождение хэндла индикатора
if (macd_handle != INVALID_HANDLE)
{
IndicatorRelease(macd_handle);
}
}
//+------------------------------------------------------------------+
//| New bar event handler |
//+------------------------------------------------------------------+
void OnTick()
{
static datetime prev_time = 0;
datetime current_time = iTime(_Symbol, _Period, 0);
// Обработка только на новом баре
if (current_time == prev_time)
{
return;
}
prev_time = current_time;
// --- Получение текущих значений MACD ---
double macd_current[], signal_current[], hist_current[];
ArraySetAsSeries(macd_current, true);
ArraySetAsSeries(signal_current, true);
ArraySetAsSeries(hist_current, true);
if (CopyBuffer(macd_handle, INDICATOR_DATA_MACD, 0, 1, macd_current) != 1 ||
CopyBuffer(macd_handle, INDICATOR_DATA_SIGNAL, 0, 1, signal_current) != 1 ||
CopyBuffer(macd_handle, INDICATOR_DATA_HISTOGRAM, 0, 1, hist_current) != 1)
{
Print("Failed to get current MACD values.");
return;
}
double current_data_point[MACD_DIMENSIONS];
current_data_point[0] = macd_current[0];
current_data_point[1] = signal_current[0];
current_data_point[2] = hist_current[0];
// --- Классификация текущего сигнала с помощью K-Means ---
int assigned_cluster = kmeans.Classify(current_data_point);
if (assigned_cluster == -1)
{
Print("Error classifying current data point.");
return;
}
int signal_type = cluster_signal_type[assigned_cluster];
// --- Действие на основе отфильтрованного сигнала ---
if (signal_type == 1) // Сильный бычий сигнал
{
Print("K-Means identified a strong BULLISH signal (Cluster ", assigned_cluster, ")");
// Здесь реализуйте вашу логику покупки.
// Например, проверьте наличие открытых позиций, откройте ордер на покупку.
// Для более продвинутого управления ордерами и уведомлениями, рассмотрите интеграцию с внешними системами,
// как описано в статье MQL5 и Telegram API: Отправка скриншотов при активации отложенных ордеров.
// Также, для учета фундаментального контекста, можно использовать MQL5: Парсинг Экономического Календаря Встроенными Средствами.
}
else if (signal_type == -1) // Сильный медвежий сигнал
{
Print("K-Means identified a strong BEARISH signal (Cluster ", assigned_cluster, ")");
// Здесь реализуйте вашу логику продажи.
// Не забудьте управлять стоп-лоссами и тейк-профитами, возможно, используя динамические методы,
// такие как MQL5: Трейлинг-стоп по Parabolic SAR с динамическим шагом.
}
else // Нейтральный/слабый сигнал
{
Print("K-Means identified a NEUTRAL/WEAK signal (Cluster ", assigned_cluster, "). No action.");
// В этом случае торговые действия не предпринимаются, или можно рассмотреть закрытие слабых позиций.
}
}
Разбор параметров
K_Clusters: Количество кластеров, на которые алгоритм K-Means будет разделять данные. Оптимальное значение зависит от характера данных и может быть найдено путем экспериментов. Слишком малое или слишком большое количество кластеров может снизить эффективность фильтрации.MaxIterations: Максимальное количество итераций, которое алгоритм K-Means будет выполнять во время обучения. Обычно 100-300 итераций достаточно для сходимости.HistoryBarsForTraining: Количество исторических баров, данные с которых будут использоваться для обучения модели K-Means. Чем больше данных, тем потенциально точнее модель, но и дольше время инициализации. Рекомендуется использовать достаточно большой объем данных, чтобы охватить различные рыночные условия.MACD_FastPeriod: Период быстрой скользящей средней для расчета MACD. Стандартное значение — 12.MACD_SlowPeriod: Период медленной скользящей средней для расчета MACD. Стандартное значение — 26.MACD_SignalPeriod: Период сигнальной линии MACD. Стандартное значение — 9.MinSignalStrengthThreshold: Порог, используемый для эвристической классификации центроидов на ‘сильные’ бычьи/медвежьи и ‘нейтральные/слабые’ сигналы. Центроиды с гистограммой MACD выше этого порога (для бычьих) или ниже отрицательного значения этого порога (для медвежьих) будут считаться сильными.
Как запустить
Для запуска алгоритма кластеризации K-Means в MQL5 и его использования для фильтрации сигналов MACD, выполните следующие шаги:
- Создайте новый экспертный советник: Откройте MetaEditor (IDE для MQL5), перейдите в ‘Файл’ -> ‘Создать’ -> ‘Экспертный советник (шаблон)’ и дайте ему имя, например,
KMeansMACDFilter.mq5. - Вставьте код: Скопируйте весь предоставленный выше исходный код и вставьте его в файл
KMeansMACDFilter.mq5, заменив содержимое по умолчанию. - Скомпилируйте код: Нажмите кнопку ‘Компилировать’ в MetaEditor (или F7). Убедитесь, что нет ошибок компиляции. Если есть ошибки, проверьте правильность копирования кода и синтаксиса.
- Прикрепите к графику: Откройте терминал MetaTrader 5, выберите нужную валютную пару и таймфрейм. Перетащите скомпилированный экспертный советник
KMeansMACDFilterиз окна ‘Навигатор’ на график. - Настройте параметры: Во вкладке ‘Входные параметры’ (Inputs) в окне настроек эксперта вы можете изменить значения
K_Clusters,MaxIterations,HistoryBarsForTraining, а также параметры MACD иMinSignalStrengthThreshold. Экспериментируйте с этими значениями для достижения наилучших результатов на выбранном инструменте и таймфрейме. - Анализ и оптимизация: После запуска эксперта, в журнале (вкладка ‘Эксперты’ в терминале) вы увидите информацию об обучении K-Means и классификации кластеров. Важно понимать, что эвристика для определения ‘сильных’ сигналов (
cluster_signal_type) является базовой и может быть доработана. Вы можете вручную проанализировать значения центроидов после обучения и скорректировать логику вOnInit()для более точной классификации кластеров. Рекомендуется использовать тестер стратегий для бэктестинга и оптимизации параметров.
Этот подход позволяет динамически адаптироваться к рыночным условиям, кластеризуя паттерны MACD и отфильтровывая те, которые исторически не приводили к сильным движениям. Это значительно повышает качество торговых сигналов и снижает количество ложных входов. Для дальнейшего улучшения вашей торговой системы, вы можете рассмотреть интеграцию с внешними источниками данных, например, используя MQL5: Парсинг Экономического Календаря Встроенными Средствами для учета фундаментальных факторов, или настроить уведомления о сделках через MQL5 и Telegram API: Отправка скриншотов при активации отложенных ордеров. Не забывайте также о продвинутых методах управления позициями, таких как MQL5: Трейлинг-стоп по Parabolic SAR с динамическим шагом.




