English Deutsch 日本語
preview
Машинное обучение и Data Science (Часть 40): Использование уровней Фибоначчи в данных машинного обучения

Машинное обучение и Data Science (Часть 40): Использование уровней Фибоначчи в данных машинного обучения

MetaTrader 5Эксперты |
79 0
Omega J Msigwa
Omega J Msigwa

Содержание


Происхождение чисел Фибоначчи

Числа Фибоначчи восходят к древнему математику Леонардо Пизанскому, также известному как Фибоначчи.

В своей книге "Liber Abaci", опубликованной в 1202 году, Фибоначчи представил последовательность чисел, ныне известную как последовательность Фибоначчи. Эта последовательность начинается с 0 и 1, а каждое последующее число является суммой двух предыдущих.

Эта последовательность имеет особую значимость, так как проявляется во многих природных явлениях, включая рост растений и животных.

В биологии (хотя и не идеально) логарифмическая спираль, наблюдаемая в раковинах некоторых животных и насекомых, приближенно соответствует числам Фибоначчи.

Предположение о росте по законам Фибоначчи также можно заметить в популяции кроликов и семейных деревьях пчел.

Числа Фибоначчи также обнаруживаются в структуре ДНК некоторых млекопитающих и человека.

Источник изображения: unsplash.com

Эти числа универсальны, так как встречаются практически повсеместно. Ниже приведены некоторые общие термины, относящиеся к работе с числами Фибоначчи.

Последовательность Фибоначчи

В математике последовательность Фибоначчи — это последовательность, в которой каждый элемент является суммой двух предыдущих. Числа, участвующие в последовательности Фибоначчи, называются числами Фибоначчи.

Последовательность Фибоначчи, золотой прямоугольник

Последовательность Фибоначчи можно выразить с помощью следующей формулы:

Формула последовательности Фибоначчи

Где n больше единицы (n>1).

Золотое сечение

Это математическая концепция, описывающая отношение между двумя величинами, при котором отношение меньшей величины к большей равно отношению большей величины к их сумме.

Золотое сечение приблизительно равно 1,6180339887 и обозначается греческой буквой Фи (φ).

Золотое сечение не идентично Фи, но близко! Оно отражает соотношение между двумя последовательными числами в последовательности Фибоначчи.

Если разделить большее число на меньшее, результат будет близок к числу Фи. Чем дальше по последовательности Фибоначчи, тем ближе значение к Фи. Однако оно никогда не будет точно равно Фи, так как Фи невозможно выразить в виде дроби. Это иррациональное число.

Иллюстрация расчета золотого сечения

Это число наблюдается в различных природных и созданных людьми структурах и считается универсальным принципом красоты и гармонии.


Понимание уровней коррекции Фибоначчи в трейдинге

Уровни коррекции Фибоначчи — это горизонтальные линии, указывающие потенциальные уровни поддержки и сопротивления, на которых цена может развернуться. Они строятся с использованием принципов чисел Фибоначчи, описанных выше.

Этот инструмент часто используется трейдерами в MetaTrader 5 для различных целей, таких как установка торговых целей (stop loss и take profit) и определение линий поддержки и сопротивления, где цены, вероятнее всего, развернутся.

В MetaTrader 5 инструмент находится в меню Вставка > Объекты > Фибоначчи.

Уровни коррекции Фибоначчи в MetaTrader 5

Ниже показаны уровни коррекции Фибоначчи на графике EURUSD с таймфреймом 1 час.

На первый взгляд эти уровни коррекции выглядят надежным инструментом для определения торговых уровней и разворотов рынка. Мы же пойдем дальше и исследуем эффективность уровней Фибоначчи в машинном обучении и искусственном интеллекте (AI), особенно золотого сечения (61,8% или 0,618).

Рассмотрим различные способы математического построения уровней Фибоначчи, а также будем использовать их для создания целевой переменной, которую модели машинного обучения могут использовать для изучения и прогнозирования направления рынка.


Создание целевой переменной с использованием уровней Фибоначчи

Для обучения модели, которая понимает взаимосвязи в данных с помощью машинного обучения с учителем, нам нужна правильно сформированная целевая переменная. Поскольку уровень Фибоначчи — это просто число, представляющее определенный ценовой уровень, мы можем собрать цену рынка на нужном уровне Фибоначчи и использовать ее в качестве целевой переменной для задачи регрессии.

Для задачи классификации мы создаем классы на основе движения рынка относительно линий Фибоначчи. Например, если рынок движется вверх и превышает рассчитанный уровень Фибоначчи, это может считаться бычьим сигналом (обозначается 1), а если рынок движется вниз и проходит определенный уровень Фибоначчи, это медвежий сигнал (обозначается 0). Другие случаи можно пометить как None (обозначается -1).

Для задачи классификации

Импорт.

import pandas as pd
import numpy as np

Функции.

def create_fib_clftargetvar(price: pd.Series, lookback_window: int=10, lookahead_window: int=10, fib_level: float=0.618):
    """
    Creates a target variable based on Fibonacci breakthroughs in price data.
    
    Parameters:
    - price: pd.Series of price data (close, open, high, or low)
    - lookback_window: int - number of past periods to calculate high/low
    - lookahead_window: int - number of future periods to assess breakout
    - fib_level: float - Fibonacci retracement level (e.g. 0.618)
    
    Returns:
    - pd.Series: with values
        1 => Bullish fib level reached
        0 => Bearish fib level reached
       -1 => False breakthrough or no fib hit
    """


    high = price.rolling(lookback_window).max()
    low = price.rolling(lookback_window).min()

    fib_level_value = high - (high - low) * fib_level # calculate the Fibonacci level in market price
    
    price_ahead = price.shift(-lookahead_window) # future price values
    
    target_var = []
    
    for i in range(len(price)):
        
        if np.isnan(price_ahead.iloc[i]) or np.isnan(fib_level_value.iloc[i]) or np.isnan(price.iloc[i]):
            target_var.append(np.nan)
            continue
        
        # let's detect bull and bearish movement afterwards
        
        if price_ahead.iloc[i] > price.iloc[i]: # The market went bullish
            if price_ahead.iloc[i] >= fib_level_value.iloc[i]:
                target_var.append(1) # bullish Fibonacci target reached
            else:
                target_var.append(-1) # false breakthrough
            
        else: # The market went bearish
            if price_ahead.iloc[i] <= fib_level_value.iloc[i]:
                target_var.append(0) # bearish Fibonacci target reached
            else:
                target_var.append(-1) # false breakthrough
                
    return target_var

Уровень Фибоначчи применительно к рыночным данным рассчитывается по формуле:

fib_level_value = high - (high - low) * fib_level

Поскольку это задача классификации, где нужно предсказать реакцию рынка на основе предыдущего уровня Фибоначчи, нужно заглянуть в будущее и определить тренд. Затем проверяется, пересекла ли будущая цена уровень Фибоначчи (для восходящего тренда — сверху, для нисходящего — снизу) и при необходимости сгенерировать сигнал на покупку или продажу. Сигнал удержания устанавливается, если цена не достигла уровня Фибоначчи в обоих направлениях.

Создадим целевую переменную с помощью этой функции и добавим результат в DataFrame.

df["Fib signals"] = create_fib_clftargetvar(price=df["Close"], 
                                             lookback_window=10, 
                                             lookahead_window=5,
                                             fib_level=0.618)

df.dropna(inplace=True) # drop nan(s) caused by the shifting operation

df

Результат.

Open High Low Close Fib signals
9 1.3492 1.3495 1.3361 1.3362 0.0
10 1.3364 1.3405 1.3350 1.3371 0.0
11 1.3370 1.3376 1.3277 1.3300 0.0
12 1.3302 1.3313 1.3248 1.3279 -1.0
13 1.3279 1.3293 1.3260 1.3266 0.0


Для задачи регрессии

def create_fib_regtargetvar(price: pd.Series, lookback_window: int=10, fib_level: float=0.618):
    """
    This function helps us in calculating the target variable based on fibonacci breakthroughs given a price
    
    price:
        Can be close, open, high, low
    
    """
    
    high = price.rolling(lookback_window).max()
    low = price.rolling(lookback_window).min()

    return high - (high - low) * fib_level

Для задачи регрессии сдвиг значений для получения будущей информации не требуется, так как при ручной торговле мы сравниваем будущие цены с уровнем Фибоначчи, рассчитанным на предыдущем окне (lookback_window), и определяем, было ли пересечение вверх или вниз.

Цель — обучить регрессионную модель прогнозировать следующее значение уровня Фибоначчи на основе предыдущих данных.

df["Fibonacci Level"] = create_fib_regtargetvar(price=df["Close"], 
                                         lookback_window=10,
                                         fib_level=0.618)

df.dropna(inplace=True)

df.head(5)

Ниже представлен итоговый Dataframe - в него добавили столбец с уровнями Фибоначчи.

Open High Low Close Fibonacci Level
9 1.3492 1.3495 1.3361 1.3362 1.343840
10 1.3364 1.3405 1.3350 1.3371 1.342923
11 1.3370 1.3376 1.3277 1.3300 1.339015
12 1.3302 1.3313 1.3248 1.3279 1.337717
13 1.3279 1.3293 1.3260 1.3266 1.335195



Обучение классификатора на целевой переменной Фибоначчи

Начнем с целевой переменной классификации "Fib signals" и обучим данные на простой модели RandomForestClassifier.

Импорт.

from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
from sklearn.utils.class_weight import compute_class_weight
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import RobustScaler

Разделение на обучающую и тестовую выборки.

X = df.drop(columns=[
    "Fib signals"
])

y = df["Fib signals"]

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42, shuffle=False)

Модель.

class_weights = compute_class_weight('balanced', classes=np.unique(y_train), y=y_train)
weight_dict = dict(zip(np.unique(y_train), class_weights))

model = RandomForestClassifier(n_estimators=100,
                               min_samples_split=2,
                               max_depth=10,
                               class_weight=weight_dict,
                               random_state=42
                              )

clf_pipeline = Pipeline(steps=[
    ("scaler", RobustScaler()),
    ("rfc", model)
])

clf_pipeline.fit(X_train, y_train)

Модель случайного леса, основанная на деревьях решений, не требует масштабирования, но так как значения Open, High, Low и Close (OHLC) являются непрерывными и подвержены выбросам, мы используем RobustScaler, чтобы убрать влияние этих выбросов.

После обучения модель тестируем на обучающих и тестовых данных.

y_train_pred = clf_pipeline.predict(X_train)

print("Train Classification report\n",classification_report(y_train, y_train_pred))

y_test_pred = clf_pipeline.predict(X_test)

print("Test Classification report\n",classification_report(y_test, y_test_pred))

Результат.

Train Classification report
               precision    recall  f1-score   support

        -1.0       0.53      0.55      0.54      4403
         0.0       0.59      0.64      0.61      7122
         1.0       0.67      0.60      0.64      8294

    accuracy                           0.61     19819
   macro avg       0.60      0.60      0.60     19819
weighted avg       0.61      0.61      0.61     19819

Test Classification report
               precision    recall  f1-score   support

        -1.0       0.22      0.22      0.22      1810
         0.0       0.38      0.60      0.46      3181
         1.0       0.42      0.20      0.27      3504

    accuracy                           0.35      8495
   macro avg       0.34      0.34      0.32      8495
weighted avg       0.36      0.35      0.33      8495

Результат на обучающих данных выглядит впечатляюще, но на тестовых — хуже. Это указывает на то, что модель не научилась распознавать паттерны вне обучающей выборки. 

Это может быть связано с различными факторами, такими как недостаток признаков для улавливания значимых рыночных паттернов (одних только значений цен OHLC может быть недостаточно) или грубый способ определения тренда при создании целевой переменной, из-за чего модель пропускает промежуточные бары. 

Поскольку наша цель была обучить модель прогнозировать пересечения уровня Фибоначчи, данный результат классификации может быть ошибочным, так как он неполный. Продолжим использовать его и проанализируем на тестовых данных в реальном торговом окружении.

Сохраним обученную модель в формате ONNX для внешнего использования в MQL5.

import skl2onnx
from skl2onnx import convert_sklearn
from skl2onnx.common.data_types import FloatTensorType
# Define the initial type of the model’s input
initial_type = [('input', FloatTensorType([None, X_train.shape[1]]))]

# Convert the pipeline to ONNX
onnx_model = convert_sklearn(clf_pipeline, initial_types=initial_type, target_opset=13)

# Save the ONNX model to a file
with open(f"{symbol}.{timeframe}.Fibonnacitarg-RFC.onnx", "wb") as f:
    f.write(onnx_model.SerializeToString())


Обучение модели регрессора на основе целевой переменной по уровням Фибоначчи

Принципы для обучения модели регрессора остаются теми же, в данном случае меняются только тип модели и целевая переменная.

Импорт.

from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import r2_score

Разделение на обучающую и тестовую выборки.

X = df.drop(columns=[
    "Fibonacci Level"
])

y = df["Fibonacci Level"]

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42, shuffle=False)

Модель случайного леса.

model = RandomForestRegressor(n_estimators=100,
                               min_samples_split=2,
                               max_depth=10,
                               random_state=42
                              )

reg_pipeline = Pipeline(steps=[
    ("scaler", RobustScaler()),
    ("rfr", model)
])

reg_pipeline.fit(X_train, y_train)

Наконец, мы можем протестировать модель регрессора как на обучающей, так и на тестовой выборке.

y_train_pred = reg_pipeline.predict(X_train)

print("Train accuracy score:",r2_score(y_train, y_train_pred))

y_test_pred = reg_pipeline.predict(X_test)

print("Test accuracy score:",r2_score(y_test, y_test_pred))

Результат.

Train accuracy score: 0.9990321734526452
Test accuracy score: 0.9565827587164671

Мало что можно сказать об оценке R2 от регрессионной модели, но на тестовой выборке 0,9565 — неплохой результат для данных вне обучающей выборки. 

Сохраняем эту обученную модель в формате ONNX для использования в MQL5.

# Define the initial type of the model’s input
initial_type = [('input', FloatTensorType([None, X_train.shape[1]]))]

# Convert the pipeline to ONNX
onnx_model = convert_sklearn(reg_pipeline, initial_types=initial_type, target_opset=13)

# Save the ONNX model to a file
with open(f"{symbol}.{timeframe}.Fibonnacitarg-RFR.onnx", "wb") as f:
    f.write(onnx_model.SerializeToString())

Теперь протестируем прогнозную способность моделей в реальных торговых условиях.


Тестирование моделей машинного обучения с уровнями Фибоначчи в тестере стратегий

Загружаем модели случайного леса в формате ONNX как ресурсы для советника.

#resource "\\Files\\EURUSD.PERIOD_H4.Fibonnacitarg-RFC.onnx" as uchar rfc_onnx[]
#resource "\\Files\\EURUSD.PERIOD_H4.Fibonnacitarg-RFR.onnx" as uchar rfr_onnx[]

Далее импортируем библиотеку для загрузки классификатора Random Forest и регрессора в формате ONNX.

#include <Random Forest.mqh>

CRandomForestClassifier rfc;
CRandomForestRegressor rfr;

Используем те же значения окна lookahead и lookback, что и при обучении. Это позволит корректно определять продолжительность удержания позиций и время для закрытия.

input group "Models configs";

input target_var_type fib_target = CLASSIFIER; //Model type
input int lookahead_window = 5;
input int lookback_window = 10;

Переменная fib_target помогает выбрать тип модели для использования.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Setting the symbol and timeframe
   
   if (!MQLInfoInteger(MQL_TESTER) && !MQLInfoInteger(MQL_DEBUG))
     if (!ChartSetSymbolPeriod(0, symbol_, timeframe_))
       {
         printf("%s failed to set symbol %s and timeframe %s",__FUNCTION__,symbol_,EnumToString(timeframe_));
         return INIT_FAILED;
       }

//---

   m_trade.SetExpertMagicNumber(magic_number);
   m_trade.SetDeviationInPoints(slippage);
   m_trade.SetMarginMode();
   m_trade.SetTypeFillingBySymbol(Symbol());
   
//---
   
   switch(fib_target)
     {
      case  REGRESSOR:
      
         if (!rfr.Init(rfr_onnx))
            {
               printf("%s failed to initialize the random forest regressor",__FUNCTION__);
               return INIT_FAILED;
            }
                    
        break;
      case CLASSIFIER:
            
         if (!rfc.Init(rfc_onnx))
            {
               printf("%s failed to initialize the random forest classifier",__FUNCTION__);
               return INIT_FAILED;
            }
            
        break;
     }

//---

   return(INIT_SUCCEEDED);
  }

В функции OnTick сигналы модели формируются на основе OHLC, как мы делали при обучении.

Эти сигналы используются для открытия сделок на покупку и продажу.

void OnTick()
  {
//--- Getting signals from the model
   
   if (!isNewBar())
      return;
      
    vector x = {
       iOpen(Symbol(), Period(), 1),
       iHigh(Symbol(), Period(), 1),
       iLow(Symbol(), Period(), 1),
       iClose(Symbol(), Period(), 1)
    };
    
    long signal = 0;
    
    switch(fib_target)
      {
       case  REGRESSOR:
         {
            double pred_fib = rfr.predict(x);         
            signal = pred_fib>iClose(Symbol(), Period(), 0)?1:0; //If the predicted fibonacci is greater than the current close price, thats bullish otherwise thats bearish signal
         }
         
         break;
       case CLASSIFIER:
       
         signal = rfc.predict(x).cls;         
         
         break;
      }

//--- Trading based on the signals received from the model
   
   MqlTick ticks;
   if (!SymbolInfoTick(Symbol(), ticks))
      {
         printf("Failed to obtain ticks information, Error = %d",GetLastError());
         return;
      }
      
   double volume_ = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MIN);
   
   if (signal == 1)
     {        
        if (!PosExists(POSITION_TYPE_BUY) && !PosExists(POSITION_TYPE_SELL))  
            m_trade.Buy(volume_, Symbol(), ticks.ask);
     }
     
   if (signal == 0)
     {        
        if (!PosExists(POSITION_TYPE_SELL) && !PosExists(POSITION_TYPE_BUY))  
             m_trade.Sell(volume_, Symbol(), ticks.bid);
     } 

//--- Closing trades 
   
   switch(fib_target)
     {
      case CLASSIFIER:
        
        CloseTradeAfterTime((Timeframe2Minutes(Period())*lookahead_window)*60); //Close the trade after a certain lookahead and according the the trained timeframe  
        
        break;
        
      case REGRESSOR:
      
        CloseTradeAfterTime((Timeframe2Minutes(Period())*lookback_window)*60); //Close the trade after a certain lookahead and according the the trained timeframe
        
        break;
     }
  }

Закрытие сделок зависит от типа модели и параметров окон lookahead_window и lookback_window.

Для классификатора сделки закрываются после количества баров на текущем таймфрейме, равного окну lookahead_window.

Для моделей регрессора сделки будут закрываться после количества баров, равного lookback_window баров, тоже на текущем таймфрейме.

Это соответствует созданию целевых переменных в Python-скрипте.

Наконец, мы можем протестировать эти две модели на тестере стратегий.

Мы обучали модели на данных с 01.01.2005 to 01.01.2023, поэтому тестировать будем на данных вне выборки: с 01.01.2023 по 31.12.2023.

Тип модели: Классификатор

Тип модели: Регрессор

Модель регрессора показал отличные результаты, учитывая что это новые данные — 57,42% выигрышных сделок. 

Для упрощения, в торговом советнике я преобразовал непрерывный результат регрессора Random Forest в бинарный сигнал.

signal = pred_fib>iClose(Symbol(), Period(), 0)?1:0; //If the predicted Fibonacci is greater than the current close price, that's bullish otherwise that's bearish signal

Это полностью меняет способ интерпретации прогнозируемого уровня Фибоначчи, поскольку, в отличие от ручной торговли, где мы обычно открываем сделку в конце тренда после получения сигнала его подтверждения, здесь подход иной. В ручной торговле мы, как правило, устанавливаем торговые цели на определенном уровне Фибоначчи (чаще всего 61,8%).

Используя данный подход, мы предполагаем, что наши модели машинного обучения уже выявили этот паттерн в пределах заданных окон lookback и lookahead, использованных на этапе обучения. Следовательно, нам остается лишь открывать сделки и удерживать их в течение заранее определенного количества баров.

Ключевым фактором здесь являются значения окон lookahead и lookback, главным образом потому, что при ручном использовании инструмента Фибоначчи мы обычно не учитываем количество баров, применяемых для расчета уровней (минимумов и максимумов). Как правило, мы просто размещаем инструмент там, где считаем нужным.

Хотя инструмент работает в ручной торговле, он может создавать иллюзию корректности его размещения, тогда как на практике мы просто устанавливаем его в удобной для нас точке, не опираясь на чtтко определtнные правила.

Именно эти два параметра (окна lookahead и lookback) необходимо оптимизировать, если мы хотим исследовать эффективность уровней Фибоначчи при создании целевой переменной и их использовании в машинном обучении в целом. 


Заключительные мысли

Уровни коррекции Фибоначчи являются действенным инструментом для формирования целевой переменной в задачах машинного обучения, что подтверждается отчетом из тестера стратегий, полученным на регрессионной модели. Даже при использовании минимального набора признаков — Open, High, Low и Close — которые сами по себе не формируют большого количества устойчивых паттернов, модели смогли выявить определенные закономерности и показать результаты лучше случайного угадывания, опираясь на информацию, извлеченную из уровней Фибоначчи.

Как ни интерпретировать полученные результаты, на мой взгляд, они выглядят впечатляюще.

Тем не менее, на текущем этапе идея еще недостаточно проработана. Необходимо расширить набор признаков, добавив значения технических индикаторов и подтверждения торговых стратегий, чтобы помочь модели, основанной на уровнях Фибоначчи, улавливать более сложные рыночные паттерны. Также рекомендую проработать другие уровни Фибоначчи.

При дальнейшем развитии данной концепции она может оказаться особенно эффективной на рынках акций и фондовых индексов, где коррекции регулярно происходят в рамках долгосрочных бычьих трендов, а также на старших таймфреймах (например, дневном), где рыночные данные характеризуются меньшим уровнем шума.


Таблица вложений

Имя файла и путь Описание и назначение
Experts\Fibonacci AI based.mq5 Главный советник для тестирования моделей машинного обучения.
Include\Random Forest.mqh Содержит классы для загрузки и развертывания классификатора и регрессора Random Forest, представленных в формате .ONNX.
Files\*.onnx Модели машинного обучения в формате ONNX.
Files\*.csv  CSV-файлы, содержащие наборы данных, которые будут использоваться для обучения моделей машинного обучения. 
Python\fibbonanci-in-ml.ipynb
Скрипт на Python для обработки данных и обучения моделей случайного леса.

Источники и ссылки

Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/18078

Прикрепленные файлы |
Attachments.zip (1525.28 KB)
Особенности написания Пользовательских Индикаторов Особенности написания Пользовательских Индикаторов
Написание пользовательских индикаторов в торговой системе MetaTrader 4
Разработка торговых стратегий с использованием осцилляторов Parafrac и Parafrac V2: Оценка эффективности при одиночном входе Разработка торговых стратегий с использованием осцилляторов Parafrac и Parafrac V2: Оценка эффективности при одиночном входе
В этой статье в качестве торговых инструментов представлены осциллятор ParaFrac и его модель V2. В ней описаны три торговые стратегии, разработанные с использованием этих индикаторов. Каждая стратегия была протестирована и оптимизирована для выявления ее сильных и слабых сторон. Сравнительный анализ выявил различия в производительности между оригинальной моделью и моделью V2.
Особенности написания экспертов Особенности написания экспертов
Написание и тестирование экспертов в торговой системе MetaTrader 4.
Преодоление ограничений машинного обучения (Часть 2): Отсутствие воспроизводимости Преодоление ограничений машинного обучения (Часть 2): Отсутствие воспроизводимости
В статье рассматривается, почему результаты торговли могут значительно различаться у разных брокеров, даже при использовании одной и той же стратегии и финансового символа, из-за децентрализованного ценообразования и расхождений в данных. Эта статья помогает разработчикам MQL5 понять, почему их продукты могут получать неоднозначные отзывы на MQL5 Marketplace, и призывает разработчиков адаптировать свои подходы к конкретным брокерам для обеспечения прозрачных и воспроизводимых результатов. В случае широкого распространения это может стать важной, узкоспециализированной передовой практикой, которая принесет пользу нашему сообществу.