Español Português
preview
Факторизация матриц: основы

Факторизация матриц: основы

MetaTrader 5Примеры | 23 августа 2024, 15:55
71 0
Daniel Jose
Daniel Jose

Введение

Приветствую всех в своей новой статье, направленной на предоставление образовательного контента.

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

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


Данная формула, узнаваемая всеми, позволяет вычислить корни квадратичной функции и может быть легко написана на любом языке программирования с использованием функций и ресурсов, доступных в этом языке. Но есть и другой способ вычислений - матричный или векторный, как его предпочитают называть некоторые люди. В этой статье мы будем называть его "матричным", поскольку мы будем использовать матрицы для представления вещей. Данный вид факторизации обычно выглядит так:


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

Вопрос не в том, правильно это или нет. Если полученный результат верен, то это не имеет большого значения. В этом случае данная матрица показывает, как именно происходит вращение чего-либо вокруг заданной точки. В этой статье я хочу показать вам, дорогие читатели, как можно при использовании MQL5 или любого другого языка, написать эту матричную форму прямо в коде, без необходимости преобразовывать ее в скалярную форму. Многие люди думают, что это сложно сделать. На самом деле всё гораздо проще, чем кажется. С помощью данной статьи вы научитесь этому.


Почему используются матрицы, а не скалярные формы?

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

Естественно, если мы ищем информацию о языке программирования, мы, скорее всего, столкнемся со скалярной формой написания кода, но почему? Причина заключается в том, что его использование немного более запутанно в момент написания кода. Чтобы понять это, попробуйте написать показанную выше или любую другую матрицу таким образом, будто это строки кода. Да, вам будет очень неудобно. Код выглядит странно и не имеет особого смысла. Однако в скалярной форме понять код будет гораздо проще. Именно поэтому вы не увидите никого, кто бы написал код факторизации в матричной форме.

Однако некоторые факторизации гораздо проще написать в матричной форме, чем в их скалярном эквиваленте. Например, если нужно обрабатывать большие объемы данных, которые легко выразить в виде многомерного массива. Чтобы понять это, давайте сначала подумаем о векторном изображении. Вместо того, чтобы рисовать на экране пиксель за пикселем, мы используем векторы. Это позволит нам вращать, изменять масштаб, выполнять деформацию сдвига и другие манипуляции чрезвычайно простым способом при использовании матриц. Или, другими словами, написание кода вычислений для использования матриц. Таким образом, можно выполнить те же преобразования в скалярной форме, но использование матриц будет намного проще.

Если векторное изображение кажется недостаточно сложным, подумайте о 3D-объектах. Возможно теперь вы понимаете, почему факторизации с помощью матриц так интересны.

В 3D-объекте любое преобразование намного проще выполнить с помощью матриц. Выполнить то же самое скалярным способом очень сложно и долго. Сделать возможно, но это настолько тяжело, что не оправдывает ожиданий. Подумайте о необходимости выполнения ортогональной проекции 3D-объекта, с помощью формул, подобных приведенным в начале статьи. Реализовывать что-то подобное было бы кошмаром. Однако при создании кода, использующего матрицы, сложность 3D-объекта не имеет значения: сделать ортогональную проекцию очень просто.

Именно это используется в видеокартах и программах для 3D-моделирования. По сути, они используют матрицы в своих расчетах, даже если мы не замечаем этого. Так когда же использовать одну форму, а когда другую для написания кода факторизации? Это зависит от вашего опыта и знаний. Не существует жестких правил, которые заставляли бы нас использовать тот или иной метод. Однако важно знать, как программировать оба метода, чтобы сделать всё как можно быстрее, проще и эффективнее.

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


Скалярный способ реализации

Здесь мы покажем кое-что очень простое и понятное. Помните, что данная статья носит образовательный характер. Она не предназначена для программирования чего-либо конкретного.

Для начала создадим небольшую программу на MQL5. То, что мы рассмотрим, можно применить к любому другому языку, который вы захотите использовать. Всё зависит от того, насколько хорошо вы знаете язык. Мы начнем с очень простого кода, как показано ниже.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property indicator_chart_window
04. #property indicator_plots 0
05. //+------------------------------------------------------------------+
06. #include <Canvas\Canvas.mqh>
07. //+------------------------------------------------------------------+
08. CCanvas canvas;
09. //+------------------------------------------------------------------+
10. int OnInit()
11. {
12.     
13.     int px, py;
14.     
15.     px = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0);
16.     py = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, 0);
17. 
18.     canvas.CreateBitmapLabel("BL", 0, 0, px, py, COLOR_FORMAT_ARGB_NORMALIZE);
19.     canvas.Erase(ColorToARGB(clrWhite, 255));
20.         
21.     canvas.Update(true);
22.     
23.     return INIT_SUCCEEDED;
24. }
25. //+------------------------------------------------------------------+
26. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
27. {
28.     return rates_total;
29. }
30. //+------------------------------------------------------------------+
31. void OnDeinit(const int reason)
32. {
33.     canvas.Destroy();
34. }
35. //+------------------------------------------------------------------+

Как вы уже видели, этот код является индикатором. Мы могли бы использовать любой другой, но индикатор будет интереснее по нескольким причинам: мы можем реализовать с ним некоторые вещи и он позволяет нам перехватывать определенные вызовы, поступающие из MetaTrader 5.

Данный код очень прост и практически ничего не делает. Он просто изменяет график на белый или любой другой цвет, который вы хотите использовать. Обратите внимание, что для максимального упрощения генерируемого кода в шестой строке мы используем стандартную библиотеку MQL5. То есть в этой строке я указываю компилятору, что он должен добавить заголовочный файл Canvas.mqh, который создается по умолчанию при установке MetaTrader 5. Это значительно упростит материал, который будет необходимо рассмотреть в этой статье. Затем, в восьмой строке, мы объявляем глобальную переменную, чтобы была возможность обращаться к классу CCanvas. Поскольку речь идет о классе, мы должны использовать его определенным образом. Одна важная деталь: обычно мне нравится использовать классы в качестве указателей. Но поскольку у многих могут возникнуть сомнения, почему я делаю так или иначе, то для максимального упрощения я буду использовать класс, как его обычно используют все, а именно, как простую переменную, которая позволяет нам ссылаться на что-то.

Так, в строках 15 и 16 мы фиксируем размер окна графика, в котором размещается индикатор. Обратите внимание, что мы не создаем для него окно. Это указано в строке 3. Поскольку индикатор вообще ничего не отслеживает, мы избегаем выдачи компилятором предупреждающих сообщений. Для этого мы используем строку 4. Поэтому, если мы применим данный индикатор в подокне, то информация, отображаемая в строках 15 и 16, будет отличаться.

В строке 18 мы сообщаем классу Canvas, что хотим создать область, в которой мы хотим что-то нанести. Данная область проходит от верхнего левого угла (второй и третий параметры) до нижнего левого угла (следующие два параметра). Первый параметр - это имя объекта, созданного классом CCanvas. Последний параметр указывает, какой вид комбинации будет использоваться для рисования в данном регионе. Более подробную информацию о данных режимах рисования можно найти в документации. В этом случае мы будем использовать модель, которая имеет альфа в качестве цвета, то есть мы можем создать прозрачность для всего, что мы рисуем в выбранной области.

Следующее действие - очистка области. Это делается в строке 19, а сразу после этого, в строке 21 указывается, что информация, находящаяся в памяти, должна быть выведена на экран. Важно понимать следующее: информация, которую мы размещаем в области с помощью CCanvas, не будет появляться на экране в то же самое время, когда мы ее создаем. Все рисунки сначала выполняются в памяти. Они не будут отображаться на экране до тех пор, пока не будет выполнена строка 21. Всё очень просто. Теперь, когда у нас есть начальная основа, давайте посмотрим, что мы будем рисовать на экране. Это показано в приведенном ниже коде.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property indicator_chart_window
04. #property indicator_plots 0
05. //+------------------------------------------------------------------+
06. #include <Canvas\Canvas.mqh>
07. //+------------------------------------------------------------------+
08. CCanvas canvas;
09. //+------------------------------------------------------------------+
10. #define _ToRadians(A) (A * (M_PI / 180.0))
11. //+------------------------------------------------------------------+
12. void Arrow(const int x, const int y, const ushort angle)
13. {
14.     int ax[] = {0, 150, 100, 150},
15.         ay[] = {0, -75,   0,  75};
16.     int dx[ax.Size()], dy[ax.Size()];
17.     
18.     for (int c = 0; c < (int)ax.Size(); c++)
19.     {
20.         dx[c] = (int)((ax[c] * cos(_ToRadians(angle))) + (ay[c] * sin(_ToRadians(angle)))) + x;
21.         dy[c] = (int)((ax[c] * (-sin(_ToRadians(angle)))) + (ay[c] * cos(_ToRadians(angle)))) + y;
22.     }
23. 
24.     canvas.FillPolygon(dx, dy, ColorToARGB(clrBlue, 255));
25.     canvas.FillCircle(x, y, 5, ColorToARGB(clrRed, 255));
26. }
27. //+------------------------------------------------------------------+
28. int OnInit()
29. {    
30.     int px, py;
31.     
32.     px = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0);
33.     py = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, 0);
34. 
35.     canvas.CreateBitmapLabel("BL", 0, 0, px, py, COLOR_FORMAT_ARGB_NORMALIZE);
36.     canvas.Erase(ColorToARGB(clrWhite, 255));
37.         
38.     Arrow(px / 2, py / 2, 60);
39. 
40.     canvas.Update(true);
41.     
42.     return INIT_SUCCEEDED;
43. }
44. //+------------------------------------------------------------------+
45. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
46. {
47.     return rates_total;
48. }
49. //+------------------------------------------------------------------+
50. void OnDeinit(const int reason)
51. {
52.     canvas.Destroy();
53. }
54. //+------------------------------------------------------------------+

Обратите внимание, что здесь мы делаем скалярный перевод формулы, приведенной в начале статьи, где используется матричная нотация. Другими словами, мы будем вращать объект. Но что это за объект? Чтобы понять это, нужно взглянуть на следующее изображение. Это то, что мы увидим, когда запустим код в MetaTrader 5.


То есть объект, который мы вращаем, - это стрелка. Но, глядя на этот код, можете ли вы увидеть, где рисуется указанная стрелка? Посмотрите на строку 38, где есть вызов процедуры под названием ARROW (СТРЕЛКА). Данная процедура находится в строке 12. Поэтому, очевидно, стрелка должна находиться где-то внутри процедуры в строке 12, но где? Стрелка определяется в строках 14 и 15. Обратите внимание: у нас есть два массива для рисования данной стрелки. Здесь мы рисуем его очень просто. Теперь начинается самое интересное для нас.

Эта стрелка определяется в абсолютном выражении. То есть мы рисуем ее так, как она была бы представлена в реальном мире, однако мы можем представлять ее и виртуально. Вместо целочисленных значений можно использовать значения с плавающей точкой. Таким образом, умножив значения, рисующие стрелку, на любой скаляр, мы можем определить ее размер. Однако, чтобы не усложнять ситуацию, мы не стали делать этого здесь. Я хочу сделать всё как можно проще, чтобы вы, дорогие читатели, смогли понять, как реализовать факторизацию матрицы.

Итак, в строке 16 есть то, что может смутить многих; но причину, по которой всё написано именно так, легко понять. Поскольку мы не знаем заранее, сколько точек будем использовать для рисования объекта, и нам нужен промежуточный массив, чтобы нарисовать его в строке 24, необходимо каким-то образом сообщить приложению, чтобы оно выделило для нас память. Используя форму из строки 16, мы указываем компилятору, что он должен выделить столько же места, сколько потребовалось бы для рисования точек. То есть у нас есть динамический массив, который используется, но выделяется он статически. Если бы мы не использовали данный способ написания кода, нам пришлось бы создать такой же код, как в процедуре Arrow, собранный так, как показано в приведенном ниже фрагменте.

11. //+------------------------------------------------------------------+
12. void Arrow(const int x, const int y, const ushort angle)
13. {
14.     int ax[] = {0, 150, 100, 150},
15.         ay[] = {0, -75,   0,  75};
16.     int dx[], dy[];
17.     
18.     ArrayResize(dx, ax.Size());
19.     ArrayResize(dy, ax.Size());
20.     
21.     for (int c = 0; c < (int)ax.Size(); c++)
22.     {
23.         dx[c] = (int)((ax[c] * cos(_ToRadians(angle))) + (ay[c] * sin(_ToRadians(angle)))) + x;
24.         dy[c] = (int)((ax[c] * (-sin(_ToRadians(angle)))) + (ay[c] * cos(_ToRadians(angle)))) + y;
25.     }
26. 
27.     canvas.FillPolygon(dx, dy, ColorToARGB(clrBlue, 255));
28.     canvas.FillCircle(x, y, 5, ColorToARGB(clrRed, 255));
29. 
30.     ArrayFree(dx);
31.     ArrayFree(dy);
32. }
33. //+------------------------------------------------------------------+

Обратите внимание, что в строке 16 у нас теперь есть индикация динамического массива. Данный вид массива определяет пространство, которое будет использоваться во время выполнения. Нам нужно внести следующие изменения. В строках 18 и 19 фрагмента мы должны выделить необходимое пространство, чтобы программа не завершилась экстренно во время выполнения. Кроме того, в этом фрагменте мы должны вернуть операционной системе память, выделенную в строках 30 и 31, это считается хорошей практикой программирования. Многие программисты склонны не возвращать выделенные ресурсы, но это превращает приложение в настоящего потребителя ресурсов: приложение не освобождает их, а это делает его управление сложным. Но кроме этих небольших различий, которые можно увидеть между фрагментом и основным кодом, всё остальное работает одинаково.

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

Теперь обратите внимание на что-то интересное. Мы вращаем фигуру и перемещаем ее в определенное место. В частности, мы перемещаем его в точку X и Y, которая в данном случае является центром экрана. Посмотрите на строку 38, где мы определяем местоположение и угол поворота, который здесь равен 60 градусам.

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

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


При этом изображение будет вращаться против часовой стрелки, так что ноль градусов будет соответствовать положению "девяти часов" на часах. Чтобы стало более понятно, 90 градусов - это шесть часов, 180 градусов - три часа, а 270 градусов - двенадцать часов. Помните, что вращение происходит против часовой стрелки. Чтобы изменить данное поведение, необходимо изменить способ вычисления. В этом нет ничего сложного, на самом деле всё очень просто. В случае вращения по часовой стрелке формулу надо будет изменить на приведенную ниже.


Обратите внимание, всё предельно просто. В этом случае три часа будет нулевым углом, шесть часов - углом 90 градусов, девять часов - углом 180 градусов, а двенадцать часов - углом 270 градусов. Хотя кажется, что мы всего лишь изменили значение угла с нуля до 180 градусов, мы сделали нечто большее: теперь направление вращения изменилось со значения "против часовой стрелки" на "по часовой стрелке".

Но это не то, что мы хотели показать. Это всего лишь интересный момент. Чтобы действительно объяснить то, что мы хотим показать, нужно будет сначала представить всё в той форме, которой все обычно пользуются. Именно потому что во многих случаях это проще и легче. Однако это не то, что мы можем легко адаптировать к любой ситуации.

Но сейчас мы не будем подробно останавливаться на этом, поскольку основное внимание в статье уделяется не этому, а моделированию матричных вычислений. Давайте посмотрим, как программисты обычно решают эти проблемы. Самый простой способ - превращение интересующих нас вещей в виртуальные единицы. Мы сначала выполняем все расчеты, а затем преобразуем результат в графические единицы измерения. При создании чего-либо с помощью виртуальных единиц мы будем использовать не целые, а плавающие значения, обычно типа double. Таким образом, тот же код, который мы видели раньше, станет выглядеть так:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property indicator_chart_window
04. #property indicator_plots 0
05. //+------------------------------------------------------------------+
06. #include <Canvas\Canvas.mqh>
07. //+------------------------------------------------------------------+
08. CCanvas canvas;
09. //+------------------------------------------------------------------+
10. #define _ToRadians(A) (A * (M_PI / 180.0))
11. //+------------------------------------------------------------------+
12. void Arrow(const int x, const int y, const ushort angle, const uchar size = 100)
13. {
14.     double ax[] = {0.0,  1.5, 1.0, 1.5},
15.            ay[] = {0.0, -.75, 0.0, .75};
16.     int dx[ax.Size()], dy[ax.Size()];
17.     
18.     for (int c = 0; c < (int)ax.Size(); c++)
19.     {
20.         ax[c] *= size;
21.         ay[c] *= size;
22.         dx[c] = (int) ((ax[c] * cos(_ToRadians(angle))) + (ay[c] * sin(_ToRadians(angle)))) + x;
23.         dy[c] = (int) ((ax[c] *(-sin(_ToRadians(angle)))) + (ay[c] * cos(_ToRadians(angle)))) + y;
24.     }
25. 
26.     canvas.FillPolygon(dx, dy, ColorToARGB(clrGreen, 255));
27.     canvas.FillCircle(x, y, 5, ColorToARGB(clrRed, 255));
28. }
29. //+------------------------------------------------------------------+
30. int OnInit()
31. {    
32.     int px, py;
33.     
34.     px = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0);
35.     py = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, 0);
36. 
37.     canvas.CreateBitmapLabel("BL", 0, 0, px, py, COLOR_FORMAT_ARGB_NORMALIZE);
38.     canvas.Erase(ColorToARGB(clrWhite, 255));
39.         
40.     Arrow(px / 2, py / 2, 60);
41. 
42.     canvas.Update(true);
43.     
44.     return INIT_SUCCEEDED;
45. }
46. //+------------------------------------------------------------------+
47. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
48. {
49.     return rates_total;
50. }
51. //+------------------------------------------------------------------+
52. void OnDeinit(const int reason)
53. {
54.     canvas.Destroy();
55. }
56. //+------------------------------------------------------------------+

Обратите внимание, что почти ничего не изменилось, за исключением того, что теперь мы можем увеличивать масштаб созданного объекта. Это делается в строках 20 и 21. Объект создается на строках 14 и 15. Весь код остается практически неизменным. Тем не менее, это простое изменение уже многое дает для «переноса» приложения на другие сценарии. Например, если пользователь меняет размеры графики, то можно изменить только параметр size в строке 12, чтобы объекты оставались в определенной пропорции.

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


Теперь с помощью матричной факторизации

Поскольку цель данной статьи - обучение читателя, я постараюсь оставить всё на максимально простом уровне. Поэтому мы реализуем только то, что необходимо для демонстрации нужного нам материала, то есть умножение матриц в данном конкретном случае. Но вы, дорогие читатели, увидите, что даже одного этого достаточно, чтобы выполнить умножение матрицы на скаляр. Самая существенная трудность, с которой многие сталкиваются при реализации кода с использованием матричной факторизации, заключается в следующем: в отличие от скалярной факторизации, где почти во всех случаях порядок факторов не меняет результат, при использовании матриц всё не так. Как бы выглядел тот же код, что и в предыдущей теме, только с использованием матриц? Не пугайтесь, но код реализуется так, как показано ниже, так что факторизация выполняется с помощью матриц.
01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property indicator_chart_window
04. #property indicator_plots 0
05. //+------------------------------------------------------------------+
06. #include <Canvas\Canvas.mqh>
07. //+------------------------------------------------------------------+
08. CCanvas canvas;
09. //+------------------------------------------------------------------+
10. #define _ToRadians(A) (A * (M_PI / 180.0))
11. //+------------------------------------------------------------------+
12. void MatrixA_x_MatrixB(const double &A[][], const double &B[][], double &R[][], const int nDim)
13. {
14.     for (int c = 0, size = (int)(B.Size() / nDim); c < size; c++)
15.     {
16.         R[c][0] = (A[0][0] * B[c][0]) + (A[0][1] * B[c][1]);
17.         R[c][1] = (A[1][0] * B[c][0]) + (A[1][1] * B[c][1]);
18.     }
19. }
20. //+------------------------------------------------------------------+
21. void Arrow(const int x, const int y, const ushort angle, const uchar size = 100)
22. {
23.     double M_1[2][2]{
24.                         cos(_ToRadians(angle)), sin(_ToRadians(angle)),
25.                         -sin(_ToRadians(angle)), cos(_ToRadians(angle))
26.                     },
27.            M_2[][2] {
28.                         0.0,  0.0,
29.                         1.5, -.75,
30.                         1.0,  0.0,
31.                         1.5,  .75
32.                     },
33.            M_3[M_2.Size() / 2][2];
34. 
35.     int dx[M_2.Size() / 2], dy[M_2.Size() / 2];
36.     
37.     MatrixA_x_MatrixB(M_1, M_2, M_3, 2);
38.     ZeroMemory(M_1);
39.     M_1[0][0] = M_1[1][1] = size;
40.     MatrixA_x_MatrixB(M_1, M_3, M_2, 2);
41. 
42.     for (int c = 0; c < (int)M_2.Size() / 2; c++)
43.     {
44.         dx[c] = x + (int) M_2[c][0];
45.         dy[c] = y + (int) M_2[c][1];
46.     }
47. 
48.     canvas.FillPolygon(dx, dy, ColorToARGB(clrPurple, 255));
49.     canvas.FillCircle(x, y, 5, ColorToARGB(clrRed, 255));
50. }
51. //+------------------------------------------------------------------+
52. int OnInit()
53. {    
54.     int px, py;
55.     
56.     px = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0);
57.     py = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, 0);
58. 
59.     canvas.CreateBitmapLabel("BL", 0, 0, px, py, COLOR_FORMAT_ARGB_NORMALIZE);
60.     canvas.Erase(ColorToARGB(clrWhite, 255));
61.         
62.     Arrow(px / 2, py / 2, 160);
63. 
64.     canvas.Update(true);
65.     
66.     return INIT_SUCCEEDED;
67. }
68. //+------------------------------------------------------------------+
69. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
70. {
71.     return rates_total;
72. }
73. //+------------------------------------------------------------------+
74. void OnDeinit(const int reason)
75. {
76.     canvas.Destroy();
77. }
78. //+------------------------------------------------------------------+

Мы оставили код максимально простым, без особых доработок, чтобы вы, уважаемые читатели, могли в нем разобраться. Обычно, когда я пишу факторизацию с использованием матричных вычислений, я это делаю несколько иначе, но об этом мы поговорим в одной из следующих статей, поскольку необходимо будет объяснить некоторые детали и меры предосторожности. Поэтому я должен еще объяснить эти детали, и хотя это может показаться простым, если вы не позаботитесь об этом должным образом, вы окажетесь в бездонной яме. Но всё-таки, что делает этот безумный код? Он делает то же самое, что и в предыдущей теме, только здесь мы используем матричное умножение для выполнения тех же самых вычислений. Этот код может показаться вам очень сложным, но нет, он гораздо проще предыдущего. Всё гораздо проще, если вы понимаете, как умножить одну матрицу на другую. Тем не менее, умножение происходит не совсем должным образом. Другими словами, оно не позволит нам сделать некоторые вещи, если нам нужно модифицировать код. Но, как я уже сказал, данная версия предназначена только для демонстрации. Правильный вариант будет рассмотрен в следующей статье. Однако факторизация даст ожидаемый результат, даже если она выполняется несколько странным образом.

Эту деталь необходимо знать, прежде чем использовать данное моделирование в любой деятельности. Но если вы уже имеете некоторое представление о том, как происходит факторизация матриц, остальное будет гораздо проще понять. Обратите внимание, что в строке 12 реализован код, который выполняет факторизацию матрицы. Что-то простое. По сути, мы умножаем первую матрицу на вторую и помещаем результат в третью. Поскольку умножение выполняется на столбцы одной матрицы и строки другой, факторизация выполняется довольно легко. Здесь необходимо соблюдать некоторые правила, например, количество строк в одной матрице должно быть равно количеству столбцов в другой. Это базовая математика о матрицах, поэтому постарайтесь сначала понять ее, если вы не знаете, как действовать дальше или почему это нужно делать именно таким образом. В матрицу A мы помещаем столбцы, а в матрицу B - строки. В нашем случае мы получим нечто эквивалентное тому, что вы видите ниже.

Да, я знаю, что при первом взгляде на это всё может показаться очень запутанным, но всё очень просто. Матрица элементов A должна быть умножена на матрицу элементов B. В результате получим матрицу из элементов R. По сути, это выглядит так:
R11 = (A11 x B11) + (A12 x B12);  R12 = (A21 x B11) + (A22 x B12) R21 = (A11 x B21) + (A12 x B22);  R22 = (A21 x B21) + (A22 x B22) R31 = (A11 x B31) + (A12 x B32);  R32 = (A21 x B31) + (A22 x B32)
и так далее. Обратите внимание, что именно так и было бы сделано, если бы вычисления были скалярными. Именно этим и занимается процедура строки 12. Мне кажется важным, что при построении вычислений у нас гораздо больше свободы. Чтобы понять это, давайте посмотрим на процедуру в строке 21, где строится и представляется стрелка. Обратите внимание, что здесь определись 5 матриц. Мы могли бы использовать меньше. Давайте сначала разберемся в этом. В будущем, может быть, мы рассмотрим, как можно смоделировать это гораздо более интуитивным и понятным способом, но даже в этой модели всё еще довольно просто для понимания. Между строками 23 и 33 мы рисуем наши матрицы, они будут использоваться в расчетах. Теперь, в строке 35, мы объявляем те, которые мы будем использовать для представления стрелки на экране. Между строками 37 и 40 мы производим необходимые вычисления. А теперь внимани:, то, что мы будем делать - очень интересно. И это как с практической точки зрения, так и с точки зрения свободы факторизации. В строке 37 мы сначала умножаем матрицу M_1 на матрицу M_2, и результат попадает в матрицу M_3. Что дает это умножение? Изображение вращается. Однако мы пока не можем представить его на экране, так как масштаб по-прежнему используется в модели, которая находится в матрице M_2. Чтобы представить изображение в правильном масштабе, нужно произвести еще один расчет. Но сначала, в строке 38, мы очищаем содержимое матрицы M_1, а сразу после этого, в строке 39, указываем, как изменяем масштаб изображения. На данный момент у нас есть несколько новых вариантов, однако мы сосредоточимся только на изменении масштаба. В строке 40 мы умножаем данные объекта, которые уже повернуты, на новую матрицу, которая уже содержит нужный, используемый масштаб. Это можно увидеть ниже.

Здесь Sx - новое измерение по оси X, а Sy - новое измерение по оси Y. Поскольку мы задаем оба значения как "Size", изменения будут происходить так, как будто мы увеличиваем объект. Вот почему я сказал, что мы можем сделать гораздо больше. Поскольку процедура FillPolygon не знает, как работать с данной структурой матрицы, нам нужно разделить данные, чтобы FillPolygon могла нарисовать стрелку за нас. У нас есть цикл for в строке 48, и результат тот же, что и в предыдущей теме.


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

В этой статье мы представили довольно простой метод работы с матричными вычислениями. Я постарался представить что-то интересное и в то же время найти способ наиболее простого объяснения того, как делается этот расчет. Однако матричная модель, рассмотренная в данной статье, не является наиболее подходящей для того, чтобы мы могли выполнить все возможные виды факторизации. Поэтому скоро я опубликую новую статью, в которой расскажу о более подходящей модели для представления матриц. Также я дам объяснение, чтобы можно было воспользоваться этим способом факторизации данных. До скорой встречи! Встретимся в следующей статье.


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

Прикрепленные файлы |
App1.mq5 (1.87 KB)
App2.mq5 (1.97 KB)
App3.mq5 (2.62 KB)
Особенности написания Пользовательских Индикаторов Особенности написания Пользовательских Индикаторов
Написание пользовательских индикаторов в торговой системе MetaTrader 4
Нейросети в трейдинге: Иерархический векторный Transformer (HiVT) Нейросети в трейдинге: Иерархический векторный Transformer (HiVT)
Предлагаем познакомиться с методом Иерархический Векторный Transformer (HiVT), который был разработан для быстрого и точного прогнозирования мультимодальных временных рядов.
Особенности написания экспертов Особенности написания экспертов
Написание и тестирование экспертов в торговой системе MetaTrader 4.
Разрабатываем мультивалютный советник (Часть 17): Дальнейшая подготовка к реальной торговле Разрабатываем мультивалютный советник (Часть 17): Дальнейшая подготовка к реальной торговле
Сейчас наш советник использует базу данных для получения строк инициализации одиночных экземпляров торговых стратегий. Однако база данных является достаточно объёмной и содержит много информации, ненужной при реальной работе советника. Попробуем обеспечить работоспособность советника без обязательного подключения к базе данных.