English 中文 Español Deutsch 日本語 Português
preview
Как построить советник, работающий автоматически (Часть 04): Ручные триггеры (I)

Как построить советник, работающий автоматически (Часть 04): Ручные триггеры (I)

MetaTrader 5Трейдинг | 3 февраля 2023, 16:31
1 742 0
Daniel Jose
Daniel Jose

Введение

В предыдущей статье " Как построить советник, торгующий автоматически (Часть 03) - Новые функции" мы закончили рассмотрение системы ордеров, поэтому если вы не читали её или не смогли усвоить её содержание, я предлагаю вам вернуться назад и попытаться усвоить то, что мы изучили. Здесь я больше не буду касаться системы ордеров, с этого момента мы будем заниматься другой темой, в частности триггерами.

Система триггеров - это, пожалуй, самая сложная часть и в то же время она порождает еще больше путаницы, сомнений и проблем, потому что не существует на 100% безошибочного метода. У них всех есть какие-либо недостатки или проблемы. Некоторые из них имеют большую вероятность ошибок, в то время как другие имеют меньшую вероятность. Но всегда помните об этом правиле: не полагайтесь на триггеры, какими бы они ни были, поскольку они могут подвести, когда вы меньше всего этого ожидаете. И сбой в системе триггеров может оставить вас без какой-то хорошей возможности или принести большие убытки, поэтому всегда будьте осторожны с триггерами.

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

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

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


Как отправлять ордеры и открывать позиции на рынке

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

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

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

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

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#define def_MouseName "MOUSE_H"
//+------------------------------------------------------------------+
#define def_BtnLeftClick(A)     ((A & 0x01) == 0x01)
#define def_SHIFT_Press(A)      ((A & 0x04) == 0x04)
#define def_CTRL_Press(A)       ((A & 0x08) == 0x08)
//+------------------------------------------------------------------+

Здесь задано имя для создаваемого объекта, горизонтальная линия чтобы указать позицию в цене там, где находится указатель мыши на графике. Также было создано 3 определения, чтобы иметь более легкий доступ к некоторой информации, которая будет генерировать событие мыши. Это такой способ тестирования событий щелчка мыши и нажатия клавиш. Данные определения доступа будут использованы позже в советнике, а определение имени объекта будет завершено в конце файла с использованием следующей строки:

//+------------------------------------------------------------------+
#undef def_MouseName
//+------------------------------------------------------------------+

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

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

class C_Mouse
{
        private :
                struct st00
                {
                        long    Id;
                        color   Cor;
                        double  PointPerTick,
                                Price;
                        uint    BtnStatus;
                }m_Infos;
//+------------------------------------------------------------------+
                void CreateLineH(void)
                        {
                                ObjectCreate(m_Infos.Id, def_MouseName, OBJ_HLINE, 0, 0, 0);
                                ObjectSetString(m_Infos.Id, def_MouseName, OBJPROP_TOOLTIP, "\n");
                                ObjectSetInteger(m_Infos.Id, def_MouseName, OBJPROP_BACK, false);
                                ObjectSetInteger(m_Infos.Id, def_MouseName, OBJPROP_COLOR, m_Infos.Cor);
                        }
//+------------------------------------------------------------------+
inline double AdjustPrice(const double value)
                        {
                                return MathRound(value / m_Infos.PointPerTick) * m_Infos.PointPerTick;
                        }
//+------------------------------------------------------------------+

Не пугайтесь, когда увидите приведенный выше код, эта часть всего лишь небольшой пример. Всё, что мы делаем - это объявляем структуру данных , которая будет содержать некоторые глобальные переменные внутри класса, но они будут частными для класса. Затем мы используем процедуру, которая создаст объект графика на платформе MetaTrader 5. Этот объект будет горизонтальной линией, а мы настроим некоторые его свойства и пойдем дальше.

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

Теперь давайте рассмотрим процедуры, которые будут публичными в нашем классе C_Mouse. Начнем с конструктора класса:

                C_Mouse(const color cor)
                        {
                                m_Infos.Id = ChartID();
                                m_Infos.Cor = cor;
                                m_Infos.PointPerTick = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
                                ChartSetInteger(m_Infos.Id, CHART_EVENT_MOUSE_MOVE, true);
                                ChartSetInteger(m_Infos.Id, CHART_EVENT_OBJECT_DELETE, true);
                                CreateLineH();
                        }

Здесь представлены две строки, которые могут показаться вам странными, если вы не очень хорошо знакомы с MQL5, поэтому давайте дадим краткое пояснение по ним. Эта строка сообщает платформе MT5, что наш код, советник, желает получать события, генерируемые мышью. Существует еще один тип событий, также генерируемых мышью, который возникает при использовании колеса прокрутки (scroll), но мы не хотим и не будем использовать его, поэтому нам достаточно этого типа событий.

Уже эта строка сообщит платформе MetaTrader 5 о том, что мы хотим знать, был ли удален какой-либо объект графика. Если это произойдет, будет сгенерировано событие, информирующее нас о том, какой именно элемент был удален с графика. Этот тип событий бывает очень полезен, когда мы хотим сохранить определенные критические элементы для идеального и правильного выполнения задачи. Тогда, если пользователь случайно удалит какой-либо критический элемент, MetaTrader 5 сообщит коду (в данном случае советнику), что элемент был удален, и мы сможем его воссоздать. Теперь давайте рассмотрим код деструктора класса:

                ~C_Mouse()
                        {
                                ChartSetInteger(m_Infos.Id, CHART_EVENT_OBJECT_DELETE, false);
                                ObjectDelete(m_Infos.Id, def_MouseName);
                        }

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

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

const void GetStatus(double &Price, uint &BtnStatus) const
                        {
                                Price = m_Infos.Price;
                                BtnStatus = m_Infos.BtnStatus;
                        }

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

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

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

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

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

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

Последняя необходимая нам процедура показана ниже:

                void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
                        {
                                int w;
                                datetime dt;
                                
                                switch (id)
                                        {
                                                case CHARTEVENT_OBJECT_DELETE:
                                                        if (sparam == def_MouseName) CreateLineH();
                                                        break;
                                                case CHARTEVENT_MOUSE_MOVE:
                                                        ChartXYToTimePrice(m_Infos.Id, (int)lparam, (int)dparam, w, dt, m_Infos.Price);
                                                        ObjectMove(m_Infos.Id, def_MouseName, 0, 0, m_Infos.Price = AdjustPrice(m_Infos.Price));
                                                        m_Infos.BtnStatus = (uint)sparam;
                                                        ChartRedraw();
                                                        break;
                                        }
                        }

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

Давайте разберемся, что же происходит в этой системе обработки сообщений. Здесь мы воспроизводим именно те данные, которые будут получены обработчиком событий OnChartEvent, и это происходит намеренно, поскольку мы хотим обрабатывать события здесь, внутри класса.

Как только это будет установлено, мы будем определять, какие события мы будут обработаны. Первое - это событие удаления объекта графика, которое генерируется из-за того, что ранее мы сообщили MetaTrader 5 о нашем желании получать информацию об удалении любого объекта с графика, и если удаленный объект - это линии мыши, то такой объект будет немедленно воссоздан.

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

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

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

#include <Generic Auto Trader\C_Orders.mqh>
#include <Generic Auto Trader\C_Mouse.mqh>
//+------------------------------------------------------------------+
C_Orders *manager;
C_Mouse *mouse;
//+------------------------------------------------------------------+
input int       user01   = 1;           //Fator de alavancagem
input double    user02   = 100;         //Take Profit ( FINANCEIRO )
input double    user03   = 75;          //Stop Loss ( FINANCEIRO )
input bool      user04   = true;        //Day Trade ?
input color     user05  = clrBlack;     //Color Mouse
//+------------------------------------------------------------------+
#define def_MAGIC_NUMBER 987654321
//+------------------------------------------------------------------+
int OnInit()
{
        manager = new C_Orders(def_MAGIC_NUMBER);
        mouse = new C_Mouse(user05);
        
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        delete mouse;
        delete manager;
}
//+------------------------------------------------------------------+
void OnTick()
{
}
//+------------------------------------------------------------------+

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

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

После этого мы инициализируем класс C_Mouse, и таким образом советник будет готов к работе. Не забудьте вызвать деструкторы в конце советника. Однако, если вы сами забудете это сделать, то компилятор обычно делает это за вас; хорошая практика делать это в коде, чтобы все знали, в каком месте перестают ссылаться на класс.

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

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        uint    BtnStatus;
        double  Price;
        static double mem = 0;
        
        (*mouse).DispatchMessage(id, lparam, dparam, sparam);
        (*mouse).GetStatus(Price, BtnStatus);
        if (def_SHIFT_Press(BtnStatus) != def_CTRL_Press(BtnStatus))
        {
                if (def_BtnLeftClick(BtnStatus) && (mem == 0)) (*manager).CreateOrder(def_SHIFT_Press(BtnStatus) ? ORDER_TYPE_BUY : ORDER_TYPE_SELL, mem = Price, user03, user02, user01, user04);
        }else mem = 0;
}

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

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

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

Теперь давайте проверим состояние клавиш Shift и Ctrl с использованием определений, находящихся в заголовочном файле C_Mouse.mqh. Они будут служить для указания того, отправляем ли мы ордер на покупку или на продажу. По этой причине они должны иметь разные значения. Если это происходит, мы проведем новое тестирование.

Но на этот раз мы проверим, был ли щелчок левой кнопкой мыши или нетЕсли это также верно и цена памяти равна нулю, то мы отправим отложенный ордер на сервер, используя данные, указанные в зоне взаимодействия с пользователем. Решение о покупке или продаже зависит от использования клавиш Shift и Ctrl: клавиша Shift указывает на то, что мы будем покупать, а клавиша Ctrl - на то, что мы будем продавать. Таким образом, мы можем разместить столько ордеров, сколько нам нужно для тестирования системы, но, как уже говорилось выше, здесь всё еще чего-то не хватает.

В предыдущей статье я предлагал вам испытание: попробовать сгенерировать код для совершения сделки на рынке. Если вы не смогли выполнить его, то это нормально и вам не стоит отчаиваться; но было бы здорово, если бы вы сами смогли справиться с задачей и не смотрели на мое решение. Оно находится чуть ниже, в обновлении кода обработки событий OnChartEvent.

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        uint    BtnStatus;
        double  Price;
        static double mem = 0;
        
        (*mouse).DispatchMessage(id, lparam, dparam, sparam);
        (*mouse).GetStatus(Price, BtnStatus);
        if (TerminalInfoInteger(TERMINAL_KEYSTATE_CONTROL))
        {
                if (TerminalInfoInteger(TERMINAL_KEYSTATE_UP))  (*manager).ToMarket(ORDER_TYPE_BUY, user03, user02, user01, user04);
                if (TerminalInfoInteger(TERMINAL_KEYSTATE_DOWN) (*manager).ToMarket(ORDER_TYPE_SELL, user03, user02, user01, user04);
        }
        if (def_SHIFT_Press(BtnStatus) != def_CTRL_Press(BtnStatus))
        {
                if (def_BtnLeftClick(BtnStatus) && (mem == 0)) (*manager).CreateOrder(def_SHIFT_Press(BtnStatus) ? ORDER_TYPE_BUY : ORDER_TYPE_SELL, mem = Price, user03, user02, user01, user04);
        }else mem = 0;
}

Здесь представлен способ отправки рыночных ордеров на торговый сервер. Мы имеем такое поведение: когда клавиша CTRL нажата, данное условие будет здесь истинным, т.е. теперь мы можем проверить второе условие. Это второе условие указывает на то, покупаем ли мы по рыночной цене или продаем по рыночной цене. Для этого мы будем использовать клавиши со стрелками, если вы нажмете CTRL + Стрелка вверх, вы будете покупать по рыночной цене, следуя инструкциям, представленным в области взаимодействия с пользователем, теперь, если вы нажмете CTRL + Стрелка вниз, вы будете продавать по рыночной цене, следуя тем же инструкциям.

Эти инструкции можно увидеть на рисунке 01:

Рисунок 1

Рисунок 01 - Настройки советника.


Заключение

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

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

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


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

Прикрепленные файлы |
Как построить советник, работающий автоматически (Часть 05): Ручные триггеры (II) Как построить советник, работающий автоматически (Часть 05): Ручные триггеры (II)
Сегодня мы рассмотрим, как создать советник, который просто и безопасно работает в автоматическом режиме. В конце предыдущей статьи я подумал, что было бы уместно разрешить использование советника вручную хотя бы на время.
Теория категорий в MQL5 (Часть 1) Теория категорий в MQL5 (Часть 1)
Теория категорий представляет собой разнообразный и расширяющийся раздел математики, который пока относительно не освещен в MQL-сообществе. Эта серия статей призвана осветить некоторые из ее концепций для создания открытой библиотеки и дальнейшему использованию этого замечательного раздела в создании торговых стратегий.
Моральное ожидание в трейдинге Моральное ожидание в трейдинге
Эта статья посвящена моральному ожиданию. Мы рассмотрим несколько примеров его применения в трейдинге, и каких результатов можно добиться с его помощью.
Как построить советник, работающий автоматически (Часть 03): Новые функции Как построить советник, работающий автоматически (Часть 03): Новые функции
Сегодня вы научитесь создавать советник, который просто и безопасно работает в автоматическом режиме. В предыдущей статье мы начали разрабатывать систему ордеров, которой будем пользоваться в автоматическом советнике. Однако мы создали только одну из необходимых функций или процедур.