Индикатор скользящего коэффициента Шарпа с доверительными границами в MQL5
Введение
Трейдеры часто рассматривают коэффициент Шарпа, рассчитанный за короткий период (например, 40–60 баров при SR ≈ 1,0–1,5), как свидетельство наличия альфы. Однако с точки зрения статистики такие точечные оценки сильно варьируются: стандартная ошибка выборочного коэффициента Шарпа пропорциональна 1/√n, поэтому при малых n в оценках преобладает шум. Проблема усугубляется в MetaTrader 5, где механизм расчета индикаторов может принудительно запускать полный пересчет с prev_calculated = 0 и предоставлять лишь часть истории; тогда накопители скользящего окна, работающие с сохранением состояния, выдают "правдоподобные" значения, рассчитанные по неполным данным. Нужен диагностический инструмент, разработанный для MetaTrader 5, который показывает не только скользящий коэффициент Шарпа в годовом выражении, но и его неопределенность (±z·SE), позволяя отличать "подтвержденное торговое преимущество" от "коэффициента Шарпа в пределах шума"; при этом его реализация должна быть устойчива к особенностям пересчета и работы с видимой областью графика в MetaTrader 5.
В данной статье представлен пользовательский индикатор MetaTrader 5 — RollingSharpe.mq5, который отображает три линии в отдельном окне под ценовым графиком: скользящий коэффициент Шарпа в годовом выражении, а также верхнюю и нижнюю границы доверительного интервала на расстоянии ±1,96⋅SEann от него. В разделах с 1 по 3 приводится математическое обоснование этих трёх линий; в разделе 4 они объединяются в индикатор; в разделе 5 приводятся точные значения параметров inp_Window и inp_PeriodsPerYear, необходимые для вашего инструмента и таймфрейма, чтобы полосы были правильно откалиброваны с самого первого бара. После построения индикатора интерпретация графика сводится к одному правилу: если границы полосы находятся по разные стороны от нуля, отображаемый коэффициент Шарпа представляет собой статистический шум; в противном случае он статистически значим при выбранном доверительном уровне.
Раздел 1. Математика шума против альфы
Почему выборочные коэффициенты Шарпа вводят в заблуждение
Коэффициент Шарпа — наиболее часто упоминаемый показатель эффективности с поправкой на риск в систематической торговле, однако практикующие трейдеры применяют его наивно: значение 1,2, полученное на окне из 60 баров, считают свидетельством наличия альфы, хотя это не так. Это точечная оценка, полученная из распределения, дисперсия которого напрямую зависит от размера выборки. Без количественной оценки этой дисперсии данное значение не имеет практического смысла.
Для ряда доходностей {rₜ}ⁿₜ₌₁ из стационарного распределения со средним μ и стандартным отклонением σ выборочный коэффициент Шарпа равен:
![]()
Асимптотическая стандартная ошибка по формуле Ло
Эндрю Ло (2002) вывел асимптотическое распределение SR̂ при допущении, что доходности независимы и одинаково распределены (IID). Для выборки из n наблюдений стандартная ошибка равна:
SE(SR̂) = √[ (1 + ½ (SR̂)²) / n ]
Это выражение отражает два важнейших факта:
- Убывание обратно пропорционально квадратному корню: SE ∝ n⁻¹/². Уменьшение размера окна вдвое приводит к удвоению стандартной ошибки. При окне размером 20 баров неопределенность в √5 ≈ 2,24 раза выше, чем при окне размером 100 баров.
- Увеличение ошибки за счет самой оценки: высокие номинальные оценки коэффициента Шарпа сами по себе расширяют доверительный интервал за счет слагаемого ½ (SR̂)², которое отражает вклад коэффициента куртозиса при допущении, что доходности независимы, одинаково распределены (IID) и имеют нормальное распределение.
Приведение к годовому выражению и полосы статистической значимости
Для получения коэффициента Шарпа в годовом выражении исходный коэффициент умножают на √N, где N — число периодов расчета доходности в году:
![]()
Соответствующая стандартная ошибка оценки, приведенной к годовому выражению, равна:

Таким образом, с учетом асимптотической нормальности оценки коэффициента Шарпа 95-процентный доверительный интервал имеет вид:

Любой коэффициент Шарпа в годовом выражении, доверительный интервал которого включает ноль, статистически неотличим от шума на уровне значимости 5 %, независимо от его номинального значения.
Резкий рост дисперсии при малом размере окна: численный пример
Рассмотрим случай, когда истинный коэффициент Шарпа генеральной совокупности равен 1,0. В таблице 1 приведена половина ширины 95-процентного доверительного интервала в зависимости от размера выборки.
Половина ширины доверительного интервала в зависимости от размера выборки (SR_true = 1,0)
| n (баров) | SE(SR̂) | Половина ширины 95%-го доверительного интервала | Минимальное обнаруживаемое значение SR |
|---|---|---|---|
| 20 | 0,2372 | ±0,465 | 1,962 |
| 60 | 0,1374 | ±0,269 | 1,135 |
| 120 | 0,0972 | ±0,190 | 0,803 |
| 252 | 0,0671 | ±0,131 | 0,554 |
| 504 | 0,0475 | ±0,093 | 0,392 |
Скользящее окно длиной 20 баров не позволяет отличить коэффициент Шарпа, равный 1,0, от нуля ни при одном из общепринятых уровней значимости. Это не недостаток модели, а фундаментальное ограничение, обусловленное свойствами информации Фишера. Разработанный здесь индикатор отображает это ограничение в терминале в режиме реального времени.
Рекомендации по выбору размера окна
Числа в приведённой выше таблице не абстрактны — они определяют практическую нижнюю границу параметра inp_Window, используемого далее в статье. Преобразовав формулу стандартной ошибки по Ло так, чтобы нижняя граница доверительного интервала как раз касалась нуля, получим минимальный размер выборки, необходимый для подтверждения того, что заданный истинный коэффициент Шарпа (SR^*) статистически отличим от шума:

Для более высокого целевого значения (SR^*) требуется меньше баров, поскольку больший по величине эффект легче обнаружить на фоне шума оценивания. При применении этой формулы для подтверждения истинного коэффициента Шарпа, равного 0,5, требуется не менее 18 баров, а для подтверждения истинного коэффициента Шарпа, равного 1,0, — не менее 6 баров. На практике трейдерам валютного рынка, работающим на таймфрейме H1, в любом случае следует задавать inp_window не менее 120 баров, поскольку эти теоретические минимумы предполагают независимые и одинаково распределённые (IID) гауссовы доходности и не обеспечивают запаса на случай нарушений этих предпосылок, рассмотренных в разделе 6.
Раздел 2: Архитектура подсистемы обработки данных о доходности
Принципы проектирования
Слой обработки данных должен одновременно решать две задачи: (1) эффективно поддерживать скользящий буфер из n значений доходности без повторного считывания всей истории цен на каждом тике и (2) корректно преобразовывать исходные данные OHLCV в доходности за период, согласующиеся с приведёнными выше статистическими выкладками.
В реализации используется кольцевой буфер фиксированной длины, память под который выделяется один раз в OnInit(). При каждом вызове OnCalculate() добавляется только доходность нового бара; самое старое наблюдение автоматически удаляется за счёт продвижения указателя head по модулю n.
Класс CReturnBuffer — полная реализация
//+------------------------------------------------------------------+ //| CReturnBuffer.mqh | //| Rolling return series buffer using a circular array template. | //+------------------------------------------------------------------+ #ifndef __CRETURNBUFFER_MQH__ #define __CRETURNBUFFER_MQH__ //+------------------------------------------------------------------+ //| Class CReturnBuffer | //| Purpose: Provides a fast, fixed-size rolling circular cache for | //| storing quantitative returns. Tracks running statistical| //| aggregates (mean, variance) using single-pass logic. | //+------------------------------------------------------------------+ class CReturnBuffer { private: double m_data[]; // Circular storage array int m_capacity; // Maximum window index lookback depth int m_head; // Index coordinate pointer of oldest element int m_count; // Total active initialized valid records double m_sum; // Incremental running sum aggregate for mean double m_sumSq; // Incremental sum-of-squares for variance public: CReturnBuffer(void); ~CReturnBuffer(void); bool Init(const int capacity); void Push(const double value); bool IsFull(void) const; int Count(void) const; int Capacity(void) const; double Get(const int offset) const; double Mean(void) const; double Variance(void) const; double StdDev(void) const; void Reset(void); }; //+------------------------------------------------------------------+ //| Default Class Constructor | //+------------------------------------------------------------------+ CReturnBuffer::CReturnBuffer(void) : m_capacity(0), m_head(0), m_count(0), m_sum(0.0), m_sumSq(0.0) { } //+------------------------------------------------------------------+ //| Class Destructor | //+------------------------------------------------------------------+ CReturnBuffer::~CReturnBuffer(void) { //--- Reclaim assigned dynamic array memory structures ArrayFree(m_data); } //+------------------------------------------------------------------+ //| Init | //| Purpose: Allocates initial fixed memory sizing structures. | //+------------------------------------------------------------------+ bool CReturnBuffer::Init(const int capacity) { //--- Enforce minimum size threshold validation for variance tracking equations if(capacity < 2) { Print("CReturnBuffer::Init - capacity must be >= 2, received: ", capacity); return(false); } //--- Establish base ground variables properties m_capacity = capacity; m_head = 0; m_count = 0; m_sum = 0.0; m_sumSq = 0.0; //--- Perform physical dynamic sizing check vector if(ArrayResize(m_data, m_capacity) != m_capacity) { Print("CReturnBuffer::Init - ArrayResize failed for capacity: ", capacity); return(false); } //--- Clean memory pool structures completely ArrayInitialize(m_data, 0.0); return(true); } //+------------------------------------------------------------------+ //| Push | //| Purpose: Writes a value to the current ring buffer position, | //| auto-evicting the oldest element if at capacity. | //+------------------------------------------------------------------+ void CReturnBuffer::Push(const double value) { //--- Check if sliding memory ring context is saturated if(m_count == m_capacity) { //--- Evict historical oldest data entry from statistical tracking metrics double evicted = m_data[m_head]; m_sum -= evicted; m_sumSq -= evicted * evicted; } else { //--- Expand tracked sample size registry directly m_count++; } //--- Overwrite historical ring offset slot with newly arrived data point m_data[m_head] = value; m_sum += value; m_sumSq += value * value; //--- Advance write head pointer wrapping around capacity limits cleanly m_head = (m_head + 1) % m_capacity; } //+------------------------------------------------------------------+ //| IsFull | //+------------------------------------------------------------------+ bool CReturnBuffer::IsFull(void) const { return(m_count == m_capacity); } //+------------------------------------------------------------------+ //| Count | //+------------------------------------------------------------------+ int CReturnBuffer::Count(void) const { return(m_count); } //+------------------------------------------------------------------+ //| Capacity | //+------------------------------------------------------------------+ int CReturnBuffer::Capacity(void) const { return(m_capacity); } //+------------------------------------------------------------------+ //| Get | //| Purpose: Sequential zero-based index query where 0 is newest. | //+------------------------------------------------------------------+ double CReturnBuffer::Get(const int offset) const { //--- Trap invalid or uninitialized out-of-bounds queries. if(offset < 0 || offset >= m_count) return(0.0); //--- Map logical historical array request into the physical circular memory ring index int physIdx = (m_head - 1 - offset + m_capacity * 2) % m_capacity; return(m_data[physIdx]); } //+------------------------------------------------------------------+ //| Mean | //+------------------------------------------------------------------+ double CReturnBuffer::Mean(void) const { if(m_count == 0) return(0.0); return(m_sum / (double)m_count); } //+------------------------------------------------------------------+ //| Variance | //| Purpose: Computes unbiased sample variance (N-1 denominator). | //+------------------------------------------------------------------+ double CReturnBuffer::Variance(void) const { //--- Sample statistical models require at least two active item nodes if(m_count < 2) return(0.0); double n = (double)m_count; double mean = m_sum / n; //--- Apply standard mathematical expansion form equation double var = (m_sumSq - n * mean * mean) / (n - 1.0); //--- Defensively filter out floating point inaccuracies below absolute zero return(MathMax(var, 0.0)); } //+------------------------------------------------------------------+ //| StdDev | //+------------------------------------------------------------------+ double CReturnBuffer::StdDev(void) const { return(MathSqrt(Variance())); } //+------------------------------------------------------------------+ //| Reset | //| Purpose: Resets statistical counters and wipes tracking values. | //+------------------------------------------------------------------+ void CReturnBuffer::Reset(void) { //--- Reset runtime indices tracking metrics counters m_head = 0; m_count = 0; m_sum = 0.0; m_sumSq = 0.0; //--- Return storage elements array fields data to structural baseline definitions ArrayInitialize(m_data, 0.0); } #endif // __CRETURNBUFFER_MQH__ //+------------------------------------------------------------------+
Разбор архитектуры класса CReturnBuffer
Стратегия выделения памяти: функция ArrayResize(m_data, m_capacity) вызывается ровно один раз во время выполнения функции Init(). В ходе дальнейшей работы дополнительное выделение памяти не выполняется. Это исключает фрагментацию динамической памяти при работе с инструментами с высокой частотой тиков. Ошибка явно обрабатывается, и перед возвратом false сообщение о ней выводится через Print(), что позволяет функции OnInit() индикатора корректно вернуть INIT_FAILED.
Механизм циклической индексации: переменная m_head всегда содержит индекс следующего слота для записи, который одновременно является индексом самого старого элемента, когда буфер заполнен. После записи m_data[m_head] = value индекс m_head обновляется по формуле m_head = (m_head + 1) % m_capacity. Эта единственная операция взятия остатка составляет все накладные расходы на управление кольцевым буфером при каждом добавлении элемента. Здесь нет вызовов ArrayCopy(), нет аналогов ArrayShift() и нет перемещений памяти со сложностью O(n).
Инкрементное обновление статистики: вместо пересчета ∑r_t и ∑r_t² по всему окну при каждом добавлении — операции со сложностью O(n) — реализация использует `m_sum` и `m_sumSq` в качестве накопителей текущих сумм. Когда буфер уже заполнен и старый элемент удаляется, его вклад вычитается перед добавлением нового элемента. Это снижает арифметическую сложность каждого добавления элемента до O(1). Выборочная дисперсия вычисляется за время O(1) по формуле:
σ̂² = (∑r_t² - n · r̄²) / (n - 1)
Защита от ошибок вычислений с плавающей запятой: ограничение снизу с помощью MathMax(var, 0.0) в функции Variance() предотвращает получение малого отрицательного значения из-за катастрофической потери значимости в числителе, которая может возникнуть, когда все доходности почти одинаковы. Передача отрицательного аргумента в функцию MathSqrt() в MQL5 приводит к возврату значения NaN, которое незаметно распространяется на все последующие вычисления.
Индексированный доступ: метод Get(offset) позволяет внешнему коду получить доступ к любому элементу в буфере по логическому смещению от самого последнего наблюдения. Физический индекс вычисляется по формуле: (m_head - 1 - offset + 2*m_capacity) % m_capacity. Слагаемое 2*m_capacity гарантирует неотрицательность делимого в операции взятия остатка, что необходимо, поскольку оператор % в MQL5 следует семантике языка C и возвращает отрицательный результат при отрицательном делимом.
Раздел 3: Ядро статистических вычислений
Класс CSharpeCalculator — полная реализация
//+------------------------------------------------------------------+ //| CSharpeCalculator.mqh | //| Computes rolling annualized Sharpe ratio with Lo SE bands. | //+------------------------------------------------------------------+ #ifndef __CSHARPECALCULATOR_MQH__ #define __CSHARPECALCULATOR_MQH__ #include "CReturnBuffer.mqh" //+------------------------------------------------------------------+ //| Struct SSharpeResult | //| Purpose: Structure holding annualized Sharpe evaluation details | //| alongside standard asymptotic error boundary margins. | //+------------------------------------------------------------------+ struct SSharpeResult { double sharpe; // Annualized Sharpe ratio metric double upperBand; // Confidence interval upper limit (Sharpe + zScore * SE) double lowerBand; // Confidence interval lower limit (Sharpe - zScore * SE) double se; // Annualized standard error variance estimator bool valid; // Operational status flag representing dataset maturity }; //+------------------------------------------------------------------+ //| Class CSharpeCalculator | //| Purpose: Processes risk-adjusted metric profiles over sliding | //| lookback window boundaries via single pass iterations. | //+------------------------------------------------------------------+ class CSharpeCalculator { private: CReturnBuffer m_buffer; // Core sliding analytical circular caching array int m_window; // Minimum lookback item count window depth double m_annFactor; // Calculated annualization root factor multiplier double m_zScore; // Standard Normal score confidence coefficient //--- Calculates the standard error envelope over raw performance vectors double ComputeSE(const double sr_raw, const int n) const; public: CSharpeCalculator(void); ~CSharpeCalculator(void); //--- Setup tracking dimensions and statistical scaling coefficients bool Init(const int window, const int periodsPerYear, const double zScore = 1.96); void AddReturn(const double ret); SSharpeResult Calculate(void) const; bool IsReady(void) const; void Reset(void); }; //+------------------------------------------------------------------+ //| Default Class Constructor | //+------------------------------------------------------------------+ CSharpeCalculator::CSharpeCalculator(void) : m_window(0), m_annFactor(1.0), m_zScore(1.96) { } //+------------------------------------------------------------------+ //| Class Destructor | //+------------------------------------------------------------------+ CSharpeCalculator::~CSharpeCalculator(void) { } //+-----------------------------------------------------------------------+ //| Init | //| Purpose: Establishes sizing metrics limits and validation constraints | //+-----------------------------------------------------------------------+ bool CSharpeCalculator::Init(const int window, const int periodsPerYear, const double zScore = 1.96) { //--- Verify fundamental operational sizing ranges if(window < 2) { Print("CSharpeCalculator::Init - window must be >= 2"); return(false); } if(periodsPerYear < 1) { Print("CSharpeCalculator::Init - periodsPerYear must be >= 1"); return(false); } //--- Set up global state property definitions m_window = window; m_annFactor = MathSqrt((double)periodsPerYear); m_zScore = MathAbs(zScore); //--- Configure underlying data array storage limits return(m_buffer.Init(window)); } //+------------------------------------------------------------------+ //| ComputeSE | //| Purpose: Estimates the standard error of the Sharpe Ratio | //| utilizing standard Lo (2002) asymptotic equations. | //+------------------------------------------------------------------+ double CSharpeCalculator::ComputeSE(const double sr_raw, const int n) const { //--- Guard against out of bounds division operations on low sample distributions if(n < 2) return(0.0); //--- Compute standard error base tracking metrics double se_raw = MathSqrt((1.0 + 0.5 * sr_raw * sr_raw) / (double)n); return(m_annFactor * se_raw); } //+------------------------------------------------------------------+ //| AddReturn | //+------------------------------------------------------------------+ void CSharpeCalculator::AddReturn(const double ret) { //--- Write newly arrived data element onto tracking data arrays m_buffer.Push(ret); } //+------------------------------------------------------------------+ //| Calculate | //| Purpose: Transforms runtime rolling sums into an annualized | //| Sharpe profile structure report payload cleanly. | //+------------------------------------------------------------------+ SSharpeResult CSharpeCalculator::Calculate(void) const { SSharpeResult res; res.valid = false; res.sharpe = 0.0; res.upperBand = 0.0; res.lowerBand = 0.0; res.se = 0.0; //--- Extract current item counts stored within the active matrix buffer int n = m_buffer.Count(); if(n < 2) return(res); //--- Prevent divide-by-zero actions under zero-variance market conditions double stdDev = m_buffer.StdDev(); if(stdDev < 1e-12) return(res); //--- Execute mathematical tracking conversions double mean = m_buffer.Mean(); double sr_raw = mean / stdDev; double sr_ann = sr_raw * m_annFactor; double se_ann = ComputeSE(sr_raw, n); //--- Finalize calculation result matrix mapping assignments res.sharpe = sr_ann; res.se = se_ann; res.upperBand = sr_ann + m_zScore * se_ann; res.lowerBand = sr_ann - m_zScore * se_ann; res.valid = true; return(res); } //+------------------------------------------------------------------+ //| IsReady | //+------------------------------------------------------------------+ bool CSharpeCalculator::IsReady(void) const { //--- Asserts whether historical sampling has fully saturated the designated window return(m_buffer.IsFull()); } //+------------------------------------------------------------------+ //| Reset | //+------------------------------------------------------------------+ void CSharpeCalculator::Reset(void) { //--- Flush underlying analytical ring buffers completely m_buffer.Reset(); } #endif // __CSHARPECALCULATOR_MQH__ //+------------------------------------------------------------------+
Разбор архитектуры класса CSharpeCalculator
Разделение исходных величин и величин в годовом выражении: В цепочке вычислений используются две отдельные оценки коэффициента Шарпа: sr_raw (за период) и sr_ann (в годовом выражении). Формула стандартной ошибки по формуле Ло применяется к sr_raw, поскольку при ее выводе предполагается, что исходные наблюдения — это сами доходности за период. Применение формулы непосредственно к значению в годовом выражении привело бы к ошибке в N раз в слагаемом дисперсии. После вычисления стандартной ошибки (SE) приведение к годовому выражению выполняется умножением как sr_raw, так и SE_raw на соответствующий коэффициент, что обеспечивает математическую согласованность.
Защита от вырождения (stdDev < 1e-12): Когда все доходности в окне одинаковы — ситуация, возникающая во время приостановки торгов, при разрывах котировок в выходные дни или при интенсивном исполнении стоп-ордеров, — стандартное отклонение стремится к нулю. Деление на значение, близкое к нулю, дает оценки коэффициента Шарпа порядка 10¹², которые искажают данные в буферах индикатора и приводят к ошибочным сигналам. Порог 1e-12 выбран значительно ниже минимальной значимой доходности, соответствующей изменению цены на один пип по любому ликвидному инструменту, но при этом намного выше машинного эпсилон для типа double (≈ 2,22 × 10⁻¹⁶).
Структура SSharpeResult: объединение всех выходных данных в простую структуру позволяет сохранить константность метода Calculate(), что сигнализирует компилятору об отсутствии побочных эффектов. Логическое поле valid позволяет последующему коду выбирать ветвь выполнения без проверки каждого выходного поля по отдельности.
Параметризация m_zScore: критическое значение хранится в качестве члена класса, а не жестко задано равным 1,96. Это позволяет вызывающему коду создавать экземпляры класса со значением 1,645 (90%-й доверительный интервал), 2,576 (99%-й доверительный интервал) или любым другим квантилем без изменения основной логики. При вызове Init() к переданному значению применяется MathAbs(), что исключает перестановку верхней и нижней границ полосы из-за случайно заданного отрицательного критического значения.
Два варианта использования этого вычислительного ядра: CReturnBuffer и CSharpeCalculator вместе образуют полноценный механизм вычисления скользящей статистики со сложностью O(1), пригодный для повторного использования. Они являются правильным выбором для советника, в котором функция OnTick() вызывается последовательно, а история баров никогда не воспроизводится в неправильном порядке — в таких условиях накопитель с сохранением состояния работает безопасно. Однако пользовательский индикатор, создаваемый в разделе 4, работает внутри механизма обработки графиков MetaTrader, который может воспроизводить историю не в хронологическом порядке (см. ниже). Именно для этой среды требуется архитектура без сохранения состояния, поэтому индикатор не создает экземпляров ни одного из этих классов во время выполнения. Оба файла остаются в проекте как вычислительное ядро, готовое к использованию в советнике; в разделе 4 объясняется, почему сам индикатор использует иной подход.
Раздел 4: Слой привязки пользовательского индикатора
Архитектура: вычисления без сохранения состояния для каждого бара
Слой привязки индикатора связывает математические выводы, приведенные в разделах 1–3, с механизмом визуализации MetaTrader 5. Ключевое архитектурное решение — использование модели вычислений без сохранения состояния для каждого бара: вместо сохранения объекта накопителя текущей суммы скользящего окна между вызовами OnCalculate() отдельная функция ComputeBar() принимает весь массив close[] по константной ссылке и при каждом вызове выполняет полный расчет с помощью двухпроходного алгоритма. Между вызовами состояние не сохраняется.
Такое архитектурное решение напрямую обусловлено особенностью устройства механизма индикаторов MetaTrader 5. Механизм индикаторов может вызывать OnCalculate() с prev_calculated = 0 (полный перерасчет) не только после перезагрузки или изменения параметров, но и после прокрутки видимой области графика или появления новых баров в фоновых окнах графиков. При вызове, обусловленном этими событиями, значение rates_total может не отражать всю загруженную историю: число баров может ограничиваться только барами в видимой области графика. Если реализация сбрасывает накопитель текущей суммы скользящего окна при prev_calculated = 0 и восстанавливает его, используя лишь rates_total баров, результат будет неверным всякий раз, когда rates_total охватывает только часть истории. Архитектура без сохранения состояния позволяет избежать этой ошибки: ComputeBar() использует только close[] и не зависит от сохраненного состояния.
Подключаемые файлы CReturnBuffer.mqh и CSharpeCalculator.mqh включены в проект и по-прежнему доступны для интеграции в советники, однако во время выполнения индикатор не создает экземпляры ни одного из этих классов.
Графическое представление статистических показателей
Результаты работы индикатора представлены в аналитическом рабочем пространстве из двух окон:
- Основное окно графика: отображает исходную динамику цены в виде свечей, служащую отправной точкой для анализа структуры рынка.
- Подокно индикатора: содержит графическое представление статистических показателей с тремя основными элементами:
- Сплошная голубая линия (DRAW_LINE): отображает скользящий коэффициент Шарпа в годовом выражении (SR_ann), динамически колеблющийся примерно от -2,5 до +3,0.
- Красные пунктирные линии (DRAW_DASH): обозначают верхний порог статистической значимости (SR_ann + 1.96 * SE_ann) и нижний порог статистической значимости (SR_ann - 1.96 * SE_ann).
- Сплошной серый уровень: обозначает фиксированную нулевую линию отсчета 0,0.
- Полупрозрачная полоса заливки (DRAW_FILLING): выделяет всю область между верхней и нижней красными пунктирными линиями. Все точки, в которых голубая линия находится внутри этой области заливки, указывают на то, что торговое преимущество стратегии статистически неотличимо от случайного шума на уровне значимости 5 %.
Полный исходный файл индикатора
//+------------------------------------------------------------------+ //| RollingSharpe.mq5 | //| Rolling Annualized Sharpe Ratio with Lo Significance Bands | //| Requires: CReturnBuffer.mqh, CSharpeCalculator.mqh | //+------------------------------------------------------------------+ #property description "Rolling Sharpe Ratio with +/-z*SE significance bands" #property strict //--- Indicator window configurations #property indicator_separate_window #property indicator_buffers 4 #property indicator_plots 4 //--- Plot 1: Sharpe Ratio line #property indicator_label1 "Sharpe Ratio" #property indicator_type1 DRAW_LINE #property indicator_color1 clrCyan #property indicator_style1 STYLE_SOLID #property indicator_width1 2 //--- Plot 2: Upper Standard Error Band #property indicator_label2 "Upper Band (+1.96*SE)" #property indicator_type2 DRAW_LINE #property indicator_color2 clrCrimson #property indicator_style2 STYLE_DASH #property indicator_width2 1 //--- Plot 3: Lower Standard Error Band #property indicator_label3 "Lower Band (-1.96*SE)" #property indicator_type3 DRAW_LINE #property indicator_color3 clrCrimson #property indicator_style3 STYLE_DASH #property indicator_width3 1 //--- Plot 4: Static Baseline Anchor #property indicator_label4 "Zero Line" #property indicator_type4 DRAW_LINE #property indicator_color4 clrDimGray #property indicator_style4 STYLE_SOLID #property indicator_width4 1 //--- Include dependencies #include <RollingSharpe/CReturnBuffer.mqh> #include <RollingSharpe/CSharpeCalculator.mqh> //--- Input parameters input int inp_Window = 60; // Lookback Rolling Window Size input int inp_PeriodsPerYear = 252; // Annualization Periodicity (e.g. Daily = 252) input double inp_ZScore = 1.96;// Statistical Confidence Interval z-score input bool inp_UseLogReturns = true;// Calculate Logarithmic Returns instead of Simple //--- Global indicator data arrays double g_BufSharpe[]; double g_BufUpper[]; double g_BufLower[]; double g_BufZero[]; //--- Shared analytical cached values double g_AnnFactor = 1.0; //+------------------------------------------------------------------+ //| ComputeBar | //| Purpose: Evaluates rolling Sharpe statistics and standard errors | //| for an isolated bar position using multi-pass analysis. | //+------------------------------------------------------------------+ void ComputeBar(const int i, const double &close[], const int window, const double annFactor, const double zScore, const bool useLog, double &outSharpe, double &outUpper, double &outLower) { outSharpe = EMPTY_VALUE; outUpper = EMPTY_VALUE; outLower = EMPTY_VALUE; //--- Verify that enough historical data points exist prior to the index if(i < window) return; double sum = 0.0; //--- Pass 1: Compute empirical mean of returns over the sliding window for(int k = i - window + 1; k <= i; k++) { double prevClose = close[k - 1]; if(prevClose < 1e-10) return; // Abort structural processing if an invalid price point is hit double ret = useLog ? MathLog(close[k] / prevClose) : (close[k] - prevClose) / prevClose; sum += ret; } double mean = sum / (double)window; double ssq = 0.0; //--- Pass 2: Compute unbiased sample variance across the window for(int k = i - window + 1; k <= i; k++) { double prevClose = close[k - 1]; double ret = useLog ? MathLog(close[k] / prevClose) : (close[k] - prevClose) / prevClose; double dev = ret - mean; ssq += dev * dev; } double variance = ssq / (double)(window - 1); //--- Handle edge cases with zero/degenerate market variance if(variance < 1e-24) return; double stdDev = MathSqrt(variance); double sr_raw = mean / stdDev; double sr_ann = sr_raw * annFactor; //--- Apply asymptotic standard error framework using Lo (2002) model equations double se_raw = MathSqrt((1.0 + 0.5 * sr_raw * sr_raw) / (double)window); double se_ann = annFactor * se_raw; //--- Map analytical values onto output destination reference parameters outSharpe = sr_ann; outUpper = sr_ann + zScore * se_ann; outLower = sr_ann - zScore * se_ann; } //+------------------------------------------------------------------+ //| Custom Indicator Initialization Function | //+------------------------------------------------------------------+ int OnInit(void) { //--- Perform input verification checks if(inp_Window < 2) { Alert("RollingSharpe: inp_Window must be >= 2. Aborting."); return(INIT_PARAMETERS_INCORRECT); } if(inp_PeriodsPerYear < 1) { Alert("RollingSharpe: inp_PeriodsPerYear must be >= 1. Aborting."); return(INIT_PARAMETERS_INCORRECT); } if(inp_ZScore <= 0.0) { Alert("RollingSharpe: inp_ZScore must be > 0. Aborting."); return(INIT_PARAMETERS_INCORRECT); } //--- Derive annualization multiplier coefficients g_AnnFactor = MathSqrt((double)inp_PeriodsPerYear); //--- Bind dynamic global array vectors to structural indicator tracks SetIndexBuffer(0, g_BufSharpe, INDICATOR_DATA); SetIndexBuffer(1, g_BufUpper, INDICATOR_DATA); SetIndexBuffer(2, g_BufLower, INDICATOR_DATA); SetIndexBuffer(3, g_BufZero, INDICATOR_DATA); //--- Explicitly assign systemic out-of-bounds rendering targets PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE); PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, EMPTY_VALUE); PlotIndexSetDouble(2, PLOT_EMPTY_VALUE, EMPTY_VALUE); PlotIndexSetDouble(3, PLOT_EMPTY_VALUE, EMPTY_VALUE); //--- Offset rendering boundaries to match lookback window data constraints PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, inp_Window); PlotIndexSetInteger(1, PLOT_DRAW_BEGIN, inp_Window); PlotIndexSetInteger(2, PLOT_DRAW_BEGIN, inp_Window); PlotIndexSetInteger(3, PLOT_DRAW_BEGIN, 0); //--- Set visual tracking float precision limits IndicatorSetInteger(INDICATOR_DIGITS, 4); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Custom Indicator Deinitialization Function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { } //+------------------------------------------------------------------+ //| Custom Indicator Iteration Function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { //--- Ensure dataset sizes exceed minimum parsing window depths if(rates_total < inp_Window + 1) return(0); //--- Assert system security against reverse index array formatting flags if(ArrayGetAsSeries(close)) { Print("RollingSharpe: time-series array ordering detected. Aborting."); return(0); } //--- Establish processing ranges depending on complete or incremental calculations int startBar = (prev_calculated == 0) ? 1 : prev_calculated - 1; //--- Flush baseline data elements if handling a complete data recalculation pass if(prev_calculated == 0) { for(int i = 0; i < MathMin(inp_Window, rates_total); i++) { g_BufSharpe[i] = EMPTY_VALUE; g_BufUpper[i] = EMPTY_VALUE; g_BufLower[i] = EMPTY_VALUE; g_BufZero[i] = 0.0; } //--- Advance initialization pointer past uncomputable warmup bar frames startBar = inp_Window; } //--- Primary analytical calculation execution loop for(int i = startBar; i < rates_total; i++) { g_BufZero[i] = 0.0; double sharpe, upper, lower; ComputeBar(i, close, inp_Window, g_AnnFactor, inp_ZScore, inp_UseLogReturns, sharpe, upper, lower); g_BufSharpe[i] = sharpe; g_BufUpper[i] = upper; g_BufLower[i] = lower; } return(rates_total); } //+------------------------------------------------------------------+
Разбор архитектуры слоя привязки индикатора
ComputeBar() — вычислительный блок без сохранения состояния: функция принимает полный массив close[] по константной ссылке, индекс целевого бара i и четыре параметра конфигурации в виде скалярных значений. Она возвращает коэффициент Шарпа в годовом выражении и значения обеих границ полосы статистической значимости через выходные параметры, передаваемые по ссылке, записывая во все три параметра EMPTY_VALUE, если хотя бы одно из условий корректности данных не выполнено. Функция не имеет побочных эффектов и не сохраняет состояние между вызовами.
В первом проходе перебираются индексы диапазона [i - window + 1, i], при этом на каждом шаге вычисляется доходность за период и накапливается сумма. Затем среднее значение вычисляется как sum / window. Во втором проходе снова перебирается тот же диапазон, суммируются квадраты отклонений от среднего, а результат сохраняется в переменной ssq. Выборочная дисперсия равна ssq / (window - 1). Это классический двухпроходный алгоритм вычисления выборочной дисперсии. Он численно устойчив: в нем используются отклонения от выборочного среднего вместо алгебраического выражения sum(r_t^2) - n * (r_bar^2), при вычислении которого возможна катастрофическая потеря значимости в периоды консолидации с низкой волатильностью.
g_AnnFactor — однократный предварительный расчет в OnInit(): множитель приведения к годовому выражению √N вычисляется один раз с помощью MathSqrt((double)inp_PeriodsPerYear) и сохраняется в переменной g_AnnFactor типа double на уровне модуля. При каждом вызове функции ComputeBar() это заранее вычисленное значение используется напрямую. При полном пересчете 8 000 баров это позволяет избежать 8 000 избыточных вызовов функции MathSqrt() с одинаковым аргументом.
PLOT_EMPTY_VALUE и PLOT_DRAW_BEGIN — два механизма ограничения отрисовки: два независимых механизма предотвращают появление артефактов при отрисовке зоны накопления данных. Во-первых, функция ComputeBar() возвращает EMPTY_VALUE для любого i < window, поэтому в ячейки буфера, соответствующие барам зоны накопления данных, безусловно записывается EMPTY_VALUE. Во-вторых, вызов PlotIndexSetInteger(plot, PLOT_DRAW_BEGIN, inp_Window) предписывает механизму отрисовки соединять точки данных только начиная с бара с индексом inp_Window, предотвращая построение линии от ячейки со значением EMPTY_VALUE до первого допустимого значения.
Автомат состояний prev_calculated и предварительное заполнение зоны накопления данных: при полном пересчете (prev_calculated == 0) цикл for перед началом основного цикла явно записывает EMPTY_VALUE в каждую ячейку буфера с индексом от 0 до inp_Window - 1. Это гарантирует, что в зоне накопления данных не останется устаревших значений от предыдущих проходов вычислений. Затем основной цикл начинается с startBar = inp_Window — первого индекса, для которого функция ComputeBar() может вернуть допустимый результат. При инкрементальных обновлениях присваивание startBar = prev_calculated - 1 обеспечивает повторную обработку последнего подтверждённого бара, чтобы учесть любые изменения его цены закрытия на уровне тиков.
Устойчивость к событиям, вызывающим пересчёт частичной истории: MetaTrader 5 устанавливает prev_calculated = 0 в ряде случаев, в том числе при прокрутке видимой области графика и фоновых обновлениях графика, и не гарантирует, что в этих случаях rates_total отражает всю загруженную историю. Поскольку ComputeBar() при каждом вызове полностью считывает данные входного окна из close[] и не зависит от состояния предыдущих вызовов, результат арифметически корректен для любых баров, представленных в close[], независимо от причины пересчёта.
Функция OnDeinit() пуста: индикатор не хранит состояние в памяти, выделенной в куче: у него нет ни динамических массивов, кроме четырёх индикаторных буферов, управляемых подсистемой индикаторов, ни экземпляров объектов, ни открытых дескрипторов. Функция OnDeinit() присутствует исключительно для соблюдения требований контракта интерфейса индикаторов MQL5.

EURUSD, график H4 с параметрами inp_Window = 60, inp_PeriodsPerYear = 252. Голубая линия коэффициента Шарпа снижается примерно с −0,5 до −1,35 по мере того, как в скользящем окне накапливаются данные о резкой распродаже 5 июня, при этом верхняя граница полос статистической значимости всё время остаётся выше нуля. Линия коэффициента Шарпа остаётся в пределах полос статистической значимости на всём видимом диапазоне графика. Это указывает на режим отрицательного импульса (моментума), а не на подтверждённую направленную альфу, поскольку при окне в 60 баров нижняя граница полос статистической значимости не пересекла нулевую отметку.
Раздел 5: Примечания по развертыванию и калибровка параметра inp_PeriodsPerYear
Рекомендуемые значения параметра inp_PeriodsPerYear в зависимости от таймфрейма и типа инструмента:
| Таймфрейм | Тип инструмента | Рекомендуемое значение |
|---|---|---|
| M1 | FX (24/5) | 525 600 |
| H1 | FX (24/5) | 8 736 |
| H4 | FX (24/5) | 2 184 |
| D1 | FX | 260 |
| D1 | Акции | 252 |
| D1 | Криптовалюта (24/7) | 365 |
| H1 | Криптовалюта (24/7) | 8 760 |
| W1 | Любой | 52 |
Размещение файлов
Два подключаемых файла CReturnBuffer.mqh и CSharpeCalculator.mqh необходимо поместить в папку RollingSharpe внутри каталога MQL5/Include/, чтобы получить следующие пути: MQL5/Include/RollingSharpe/CReturnBuffer.mqh и MQL5/Include/RollingSharpe/CSharpeCalculator.mqh. Файл индикатора RollingSharpe.mq5 необходимо поместить в папку с именем RollingSharpe, расположенную в каталоге MQL5/Indicators/, чтобы путь к нему выглядел следующим образом: MQL5/Indicators/RollingSharpe/RollingSharpe.mq5. Это два разных каталога; если перепутать файлы или разместить их не в том корневом каталоге, компиляция завершится ошибкой: подключаемый файл не найден.
Логарифмические и арифметические доходности
Для обеспечения согласованности временных рядов рекомендуется использовать значение по умолчанию inp_UseLogReturns = true. Логарифмические доходности аддитивны во времени, поэтому их сумма корректно отражает накопленную логарифмическую доходность. Для инструментов с дневными изменениями цены более чем на ±3% — что часто встречается на криптовалютных рынках — разница между коэффициентами Шарпа, рассчитанными по логарифмическим и арифметическим доходностям, становится существенной, и правильным выбором являются логарифмические доходности.
Практический подбор размера окна в зависимости от таймфрейма
На таймфрейме H1 при inp_PeriodsPerYear = 8 736 окно из 60 баров соответствует примерно 2,5 торговым дням — этого недостаточно для статистически надежных выводов с учетом теоретических минимумов, приведенных в разделе 1. Для валютного рынка на таймфрейме H1 более обоснованная настройка — inp_Window = 120 (примерно 5 торговых дней). Для криптовалюты на графике H1 значение inp_Window = 168 охватывает ровно одну календарную неделю. На таймфрейме D1 значение inp_Window = 60 соответствует примерно 3 календарным месяцам и является практическим минимумом для построения полос статистической значимости, в которых не доминирует выборочный шум.
Раздел 6. Технические итоги: условия эксплуатации и ограничения
Распределения доходностей, отличные от нормального, и нарушения предположения о независимых и одинаково распределенных наблюдениях (IID)
Формула Ло была получена при допущении, что доходности являются независимыми и одинаково распределенными (IID) и подчиняются гауссовому распределению. На практике для финансовых доходностей характерны четыре систематических нарушения этого допущения.
1. Тяжелые хвосты (эксцесс)
Для дневных доходностей акций и валютных пар эмпирический коэффициент куртозиса составляет 4–8, тогда как для гауссова распределения он равен 3. При распределениях с тяжелыми хвостами истинная дисперсия SR_hat оказывается больше, чем предсказывает формула Ло. Кристи (2005) показал, что смещение стандартной ошибки составляет приблизительно:
Смещение ≈ ((k - 3) / 4n) * (SR_hat)^2
где k — эксцесс. При k = 6, n = 60 и SR_hat = 1,0 это увеличивает истинную стандартную ошибку (SE) примерно на 0,8 %. На практике это означает, что при распределениях с тяжелыми хвостами полосы статистической значимости, формируемые этим индикатором, дают излишне оптимистичную картину: фактическая 95%-ная полоса немного шире отображаемой.
2. Серийная автокорреляция
Трендовые стратегии в силу своего устройства порождают положительную автокорреляцию в рядах доходностей. В формуле Ло для дисперсии коэффициента Шарпа с поправкой на автокорреляцию SE(SR_hat) заменяется следующим выражением:
SE_AC(SR_hat) = √((1 / n) * (1 + 2 * sum(rho_hat_k)) * (1 + (SR_hat² / 2)))
где rho_hat_k — выборочная автокорреляция при лаге k, а k принимает значения от 1 до q. Положительные значения rho_hat_k увеличивают стандартную ошибку (SE); для стратегий с устойчивым моментумом реализованный здесь вариант, предполагающий независимые и одинаково распределенные наблюдения (IID), будет систематически занижать ширину полос статистической значимости.
3. Нестационарность
При использовании скользящего окна предполагается стационарность доходностей в пределах периода ретроспективного расчета. Структурные разрывы — заявления центральных банков, геополитические события, смены режимов волатильности — нарушают это допущение. Индикатор реагирует на смену режима с задержкой, равной длине окна: требуется inp_window периодов, чтобы доходности прежнего режима полностью вышли из расчетного окна.
4. Приведение к годовому выражению с целочисленным параметром
Параметр inp_PeriodsPerYear по определению является приблизительным. Для графиков H1 фактическое значение зависит от часов торговли инструмента: рынок Форекс работает 24 часа в сутки, 5 дней в неделю, что дает примерно 8 736 баров в год; криптовалютные инструменты торгуются круглосуточно, 7 дней в неделю, что дает 8 760 баров; фондовые индексы на графике H1 могут давать примерно 1 950 баров. Неверная настройка этого параметра масштабирует все значения коэффициента Шарпа в sqrt(N_wrong / N_correct) раз, не меняя форму его кривой, но дает численно неверные значения в годовом выражении, которые вводят в заблуждение при сравнении стратегий.
Структурная задержка и компромиссы при выборе длины окна
Оценка по скользящему окну фиксированной длины реагирует на реальную смену режима исходного коэффициента Шарпа с задержкой в n/2 баров. Это неизбежная цена статистической стабильности. Более короткие окна позволяют реагировать быстрее, но дают оценки с повышенной дисперсией, которые порождают ложные сигналы статистической значимости, что количественно показано в таблице раздела 1.
Формула расчета минимального размера выборки и ее практические следствия для выбора значения inp_Window рассмотрены в разделе 1. Трейдерам, работающим на рынке Форекс на таймфрейме H1, следует задавать inp_Window не менее 120 баров, чтобы сбалансировать чувствительность обнаружения и подавление ложных сигналов в фазах консолидации.
Профиль вычислительной сложности
В функции ComputeBar() индикатор использует двухпроходный алгоритм вычисления среднего и дисперсии. Каждый проход выполняет inp_Window итераций, поэтому вычислительная сложность на один бар составляет O(n), где n = inp_Window. Общее количество операций на один бар составляет:
Арифметические операции в ComputeBar() на один бар
| Операция | Количество | Сложность |
|---|---|---|
| Проход 1: суммирование доходностей | inp_Window итераций, 1 сложение на шаг | O(n) |
| Деление для вычисления среднего | 1 | O(1) |
| Проход 2: квадрат отклонения | inp_Window итераций, 2 умножения + 1 сложение на шаг | O(n) |
| дисперсия, стандартное отклонение, sr_raw, sr_ann | 4 арифметических операции | O(1) |
| Расчёт стандартной ошибки (SE) по формуле Ло (2002) | 1 извлечение квадратного корня, 2 умножения, 1 деление | O(1) |
| Расчёт полос статистической значимости | 2 умножения, 2 сложения | O(1) |
| Итого на один бар | 2 * inp_Window итераций | O(n) |
При полном проходе пересчёта по rates_total барам общее количество операций с плавающей запятой составляет O(n×rates_total). При inp_Window = 60 и 8 000 загруженных барах это составляет примерно 960 000 операций, которые выполняются менее чем за 5 миллисекунд на любом современном процессоре. При inp_Window = 252 и 20 000 барах количество операций достигает примерно 10 миллионов, при этом вычисления по-прежнему занимают значительно меньше 50 миллисекунд. При инкрементном обновлении на каждом тике выполняется 2 * inp_Window итераций для одного бара — 120 операций при inp_Window = 60, что создаёт пренебрежимо малую нагрузку даже при одновременной работе индикатора на 20 торговых инструментах. Вычислительные затраты O(n) — осознанная плата за устранение артефактов отображения, зависящих от состояния; на практике они становятся проблемой лишь при значениях inp_Window выше 5 000, которые не требуются ни в одной практической конфигурации.
Заключение
В этой статье статистическая теория выборочного коэффициента Шарпа и формула стандартной ошибки по Ло связаны с практической реализацией для MetaTrader 5, позволяющей отображать неопределённость в реальном времени. В комплект входят индикатор RollingSharpe.mq5 и два подключаемых файла (CReturnBuffer.mqh, CSharpeCalculator.mqh). Сам индикатор использует процедуру ComputeBar() без сохранения состояния для каждого бара, чтобы избежать артефактов полного пересчета в MetaTrader 5; классы из подключаемых файлов предоставляются для интеграции с советниками или альтернативного использования с сохранением состояния, когда целесообразно сохранять буферы между вызовами. Индикатор отображает линию коэффициента Шарпа в годовом выражении, верхнюю и нижнюю полосы статистической значимости ±z·SE и нулевую опорную линию. Практическое правило: если полосы статистической значимости охватывают ноль, наблюдаемый коэффициент Шарпа статистически неотличим от шума при выбранном доверительном уровне; лишь когда полосы не охватывают ноль, коэффициент Шарпа (SR) для данного окна следует рассматривать как свидетельство наличия торгового преимущества. Внимательно подбирайте параметры inp_Window и inp_PeriodsPerYear: более длинные окна уменьшают выборочный шум, но вносят запаздывание, поэтому настройки должны соответствовать вашему горизонту обнаружения и минимальному значению коэффициента Шарпа, которое требуется обнаруживать.
Программы, использованные в статье:
| # | Название | Тип | Описание |
|---|---|---|---|
| 1 | CReturnBuffer.mqh | Подключаемый файл | Кольцевой буфер фиксированной емкости, реализующий добавление за O(1) с инкрементальным обновлением накопителей текущей суммы и суммы квадратов. Предоставляет методы Init(), Push(), Mean(), Variance(), StdDev(), IsFull(), Get() и Reset(). Используется в CSharpeCalculator.mqh и доступен для самостоятельной интеграции в советники. |
| 2 | CSharpeCalculator.mqh | Подключаемый файл | Механизм расчета скользящего коэффициента Шарпа в годовом выражении на основе CReturnBuffer. Реализует вычисление асимптотической стандартной ошибки по формуле Ло (2002) и при каждом вызове Calculate() возвращает полностью заполненную структуру SSharpeResult, содержащую коэффициент Шарпа в годовом выражении, верхнюю и нижнюю границы полос статистической значимости, стандартную ошибку (SE) и флаг корректности результата. |
| 3 | RollingSharpe.mq5 | Пользовательский индикатор | Пользовательский индикатор MQL5, который отображает скользящий коэффициент Шарпа в годовом выражении в виде сплошной голубой линии с полосами статистической значимости ±z⋅SE_ann в отдельном подокне. Подключает файлы CReturnBuffer.mqh и CSharpeCalculator.mqh. Вычисляет все значения для каждого бара без сохранения состояния с помощью ComputeBar(), считывая данные непосредственно из массива close[]. Настройка осуществляется с помощью параметров inp_Window, inp_PeriodsPerYear, inp_ZScore и inp_UseLogReturns. |
| 4 | Rolling_Sharpe.zip | Архив в формате ZIP | ZIP-архив, содержащий все вложенные файлы и их пути относительно корневой папки терминала. |
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/22978
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Фильтрация Вьеториса — Рипса в MQL5: Построение симплексов и граничной матрицы
Метаразметка классических стратегий (Часть 2): Фильтрация сделок по ADX и определение размера позиций
Разработка динамического мультивалютного советника (Часть 9): Фильтрация шума исполнения с учетом микроструктуры рынка
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования