Технические индикаторы как цифровые фильтры
Введение
В Code Base за несколько лет скопилось большое количество индикаторов. Многие из них являются копиями друг друга, другие – лишь небольшие модификации. После долгих часов визуального сравнения индикаторов на графиках, невольно возникнет вопрос: "Неужели нет более объективного и продуктивного способа для сравнения?" Такой способ есть. Но нужно признать, что индикатор – это фильтр, причем цифровой. Обратимся к Википедии.
Фильтр (от лат. filtrum «войлок») — понятия, устройства, механизмы, выделяющие (или удаляющие) из исходного объекта некоторую часть с заданными свойствами.
Согласны, что индикаторы помогают убрать «лишнее» и лучше сосредоточится на необходимом? Теперь посмотрим, что такое цифровой фильтр.
Цифровой фильтр — в электронике любой фильтр, обрабатывающий цифровой сигнал с целью выделения и/или подавления определённых частот этого сигнала.
То есть, цифровой фильтр – это фильтр обрабатывающий дискретные сигналы. А цены, которые мы видим в терминале, как раз и есть дискретный сигнал, то есть значения записываются через какой-то промежуток времени, а не постоянно. Например, на графике H1 значение записывается каждый час, а на графике M5 - каждые 5 минут. Многие индикаторы можно отнести к группе линейных фильтров. Собственно, о них и пойдет речь в этой статье.
Теперь, когда мы выяснили, что имеем дело с цифровыми фильтрами, разберемся с теорией, так как именно она поможет ответить на вопрос, какие параметры сравнивать.
1. Частоты и периоды
Сразу скажу, чтобы в дальнейшем не было недопонимания: любую кривую можно представить как сумму синусоид.
Период колебаний — время между двумя последовательными прохождениями тела через одно и то же положение в одном и том же направлении, величина, обратная частоте.
Проще всего это определение понять на синусоиде. Для примера возьмем период, равный 10 отсчетам. Считать для простоты будем в барах.
Рис. 1. Пример периодического сигнала
Как видно из рисунка, за 10 отсчетов линия делает полный цикл, а 11-ый бар является первой точкой нового цикла.
А какая частота у этой синусоиды? В определении говорится, что период - величина обратная частоте. Тогда, если у нас период равен 10 (бар), то частота будет 1/10=0.1 (1/бар).
Вообще, в физике периоды (T) измеряют в секундах (с), а частоты (f) в герцах (Гц). Если имеем дело с минутным таймфреймом, то T=60*10=600 секунд, а f=1/Т=1/600=0.001667 герц. Герцы и секунды более актуальны в аналоговых фильтрах, в цифровых обычно используют отсчеты (как мы бары), а когда необходимо, умножают на нужное количество секунд.
Наверное, у вас возник вопрос, причем тут синусоиды, если начали говорить о фильтрах? Синусоиды мне нужны для объяснения физического смысла фильтров и перехода к частотам, так как в литературе оперируют именно этим понятием. Теперь возьмем не одну синусоиду, а 7 с периодами от 10 до 70 с шагом 10 баров. Бары в верхнем подокне на рис. 2 служат как ориентир, чтобы визуально оценить число отсчетов.
Рис. 2. Семь синусоид с одинаковой амплитудой с периодами 10, 20, ... 70 баров.
Масштаб довольно крупный, но все равно можно запутаться. А представьте, что синусоид было бы в разы больше.
Так выглядит их сумма:
Рис. 3. Сумма семи синусоид, приведенных на рис. 2
Другое дело частоты:
Рис. 4. Спектр суммы синусоид (в частотах)
Для отображения 7 синусоид достаточно 7 отсчетов. Обратите внимание на цвета, они соответствуют предыдущему рисунку. Сначала идут медленные синусоиды, потом быстрые. При этом самая низкая возможная частота - это 0 (постоянная составляющая), а самая высокая 0.5 (1/бар). Для периодов картинка будет противоположная.
Рис. 5. Спектр суммы синусоид (в периодах)
Мы помним, что частота равна 1/период, поэтому период должен быть в пределах от 2 до бесконечности. Почему именно 0.5 и 2? Дело в том, что одну синусоиду можно обозначить минимум двумя отсчетами, об этом говорит теорема Котельникова. Для восстановления без потерь аналогового (непрерывного) сигнала нам понадобится два или больше отсчетов на одну синусоиду (0.5 получается из 1/2).
Для наглядности, чтобы не было путаницы с периодами и частотами, приведу таблицу
Период | | 100 | 50 | 16 | 10 | 4 | 2 |
Частота | 0 | 0.01 | 0.02 | 0.0625 | 0.1 | 0.25 | 0.5 |
Я так подробно рассмотрел понятия период и частота, так как это основа. Весь дальнейший материал будет связан с этими терминами.
2. Цифровые фильтры
Вот мы и добрались до самих фильтров. Представьте, что нам нужно убрать синусоиды с периодом, меньшим 50.
Рис. 6. Медленные (низкочастотные) составляющие суммы синусоид (периоды 50, 60 и 70 баров)
Хорошо, если известны начальные составляющие, а что делать, когда у нас есть только сумма? Для этого нужен фильтр нижних частот (ФНЧ) с частотой среза 1/45 (1/бар).
Результат фильтрации будет выглядеть так:
Рис. 7. Результат фильтрации (синяя линия) суммы синусоид при помощи ФНЧ
Теперь оставим синусоиды с периодами 10, 20, 30. С этой задачей справится фильтр верхних частот (ФВЧ) с частотой среза 1/35 (1/бар).
Рис. 8. Высокочастотные составляющие суммы синусоид (периоды 10, 20 и 30 баров)
Рис. 9. Результат фильтрации (синяя линия) суммы синусоид при помощи ФВЧ
Чтобы оставить периоды 30,40,50, нужен полосовой фильтр (ПФ) с частотами среза 1/25 и 1/55 (1/бар).
Рис. 10. Синусоиды с периодами 30, 40 и 50 баров
Рис. 11. Результат полосовой фильтрации (30-50 баров) суммы синусоид
А чтобы наоборот вырезать периоды 30,40,50, нужен заграждающий (режекторный) фильтр с теми же частотами среза 1/25 и 1/55 (1/бар).
Рис. 12. Синусоиды с периодами 10,20,60 и 70 баров
Рис. 13. Результат работы заграждающего фильтра (30-50 баров) от суммы синусоид
Подведем промежуточный итог в виде рисунка:
Рис. 14. Частотные характеристики идеальных фильтров: нижних частот (ФНЧ), верхних частот (ФВЧ), полосового (ПФ) и заграждающего (ЗФ)
Рассмотренные выше фильтры идеализированы. В реальности все не так.
Рис. 15. Переходная полоса в фильтрах
Между полосой пропускания и полосой задержания есть переходная полоса. Ее крутизна спада измеряется в дБ/октаву или дБ/декаду. Октава - отрезок между произвольным значением частоты и ее удвоенным значением. Декада - отрезок между произвольным значением частоты и в десять раз большим значением. Формально переходная полоса располагается между частотой среза и полосой задержания. Забегая вперед, скажу, что частоту среза по спектру в большинстве случаев определяют по уровню 3 дБ.
Затухание - подавление частот в полосе задержания. Измеряется в децибелах.
Биения в полосе пропускания. Так как мы имеем дело с реальными фильтрами, то есть искажения в полосе пропускания, некоторые частоты по амплитуде больше, другие - меньше. Измеряется в децибелах.
Для перевода в дБ в большинстве случаев будет достаточно таблицы
дБ | раз |
---|---|
0.5 | 1.06 |
1 | 1.12 |
3 | 1.41 |
6 | 2 |
10 | 3.16 |
20 | 10 |
30 | 31.6 |
40 | 100 |
60 | 1000 |
Например, чтобы получить результат для 60 дБ можно найти знания 20 и 40 дБ, а затем перемножить их.
Теперь, когда мы знаем основные параметры фильтра, перейдем к практической части статьи.
3. Поиск ядра
Можно сказать, что цифровой фильтр полностью описывается его импульсной характеристикой (ядро). Импульсная характеристика - это реакция фильтра на единичный импульс. Фильтры бывают БИХ (бесконечная импульсная характеристика, например, Exponential Moving Average, EMA) и КИХ (конечная импульсная характеристика, например, Simple Moving Average, SMA).
Теперь можно взяться за MetaEditor. Для начала сделаем единичный импульс. Это будет очень простой индикатор, цель которого всего лишь вывести один отсчет, равный единице. В MetaEditor'е жмем создать, выбираем "Пользовательский индикатор", жмем "Далее":
Рис. 16. Создание пользовательского индикатора в Мастере MQL5
Задаем имя "Impulse":
Рис. 17. Общие параметры индикатора
Выбираем обработчик событий:
Рис. 18. Обработчики событий индикатора
Добавляем индикаторную линию и вывод в окне. Готово.
Рис. 19. Параметры отображения индикатора
Код индикатора будет выглядеть так:
//+------------------------------------------------------------------+ //| Impulse.mq5 | //| Copyright 2012, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2012, MetaQuotes Software Corp." #property link "https://www.mql5.com" #property version "1.00" #property indicator_separate_window #property indicator_buffers 1 #property indicator_plots 1 //--- plot Label1 #property indicator_label1 "Label1" #property indicator_type1 DRAW_LINE #property indicator_color1 clrRed #property indicator_style1 STYLE_SOLID #property indicator_width1 1 //--- input parameters //--- indicator buffers double Label1Buffer[]; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- indicator buffers mapping SetIndexBuffer(0,Label1Buffer,INDICATOR_DATA); ArraySetAsSeries(Label1Buffer,true); //--- return(0); } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) { //--- ArrayInitialize(Label1Buffer,0.0); Label1Buffer[1023]=1.; //--- return value of prev_calculated for next call return(rates_total); } //+------------------------------------------------------------------+
В функцию OnInit() добавим
ArraySetAsSeries(Label1Buffer,true);
чтобы индексация была с конца массива.
ArrayInitialize(Label1Buffer,0.0); Label1Buffer[1023]=1.;
Обнуляем все значения и добавляем 1 в 1023 ячейку массива индикатора.
Компилируем (F7) и получаем такую красоту
Рис. 20. Индикатор Impuse
Теперь, если набросить сверху какой-нибудь индикатор, можно увидеть его импульсную характеристику длиной до 1024 отсчетов (см. Примеры).
Посмотреть на ядро фильтра конечно здорово, но большую часть информации можно получить только из частотного представления. Для этого нужно сделать спектроанализатор. Или воспользоваться готовым решением с минимальными трудозатратами. Пойдем по второму пути и задействуем индикатор SpecAnalyzer, описанный в статье "Строим анализатор спектра".
Вот как выглядит этот чудо-индикатор:
Рис. 21. Индикатор SpecAnalyzer
Чтобы им воспользоваться, нужно провести небольшую подготовительную работу. Об этом в следующем разделе.
4. Адаптация для спектроанализатора
Кнопка "External Data" позволяет использовать данные из индикатора SAInpData.
В оригинале записан массив, представляющий ядро фильтра. Мы же переделаем файл так, чтобы можно было передавать в спектроанализатор любые индикаторы с графика. В модифицированном индикаторе предполагается сделать автоматический и ручной режим. В автоматическом режиме будет браться первый найденный индикатор на графике. В ручном режиме можно будет задавать подокно и номер индикатора в списке. При этом нужно самостоятельно добавлять на график индикатор Impulse и на него набрасывать требуемый индикатор, чтобы получить ядро.
Итак, приступим. Создадим новый индикатор по той же схеме, как и Impulse. Добавим входные параметры:
input bool Automatic=true; // Автопоиск input int Window=0; // Номер подокна input int Indicator=0; // Номер индикатора
Если Automatic=true, то используется автоматический режим и остальные входные параметры игнорируются. Если Automatic=false, то используется ручной режим с указанием номера подокна и индикатора.
Далее на глобальном уровне добавляем переменные типа integer для хранения хендлов.
int Impulse=0; // хэндл единичного импульса int Handle=0; // хэндл искомого индикатора int Kernel=0; // хэндл ядра фильтра
В Impulse будет храниться хэндл индикатора Impulse. В Handle - хэндл индикатора, ядро которого мы хотим посмотреть в спектроанализаторе. В Kernel хранится хэндл целевого индикатора, который построен от индикатора Impulse, или, проще говоря, ядро целевого индикатора.
ФункцияOnInit():
int OnInit() { //--- indicator buffers mapping SetIndexBuffer(0,DataBuffer,INDICATOR_DATA); Impulse=iCustom(NULL,0,"SpecAnalyzer\\Impulse");//получаем хэндл единичного импульса if(Impulse==INVALID_HANDLE) { Alert("Неудачная инициализация Impulse"); return(INIT_FAILED); } //--- return(0); }
Так как по ходу работы программы индикатор Impulse не меняется, то его хэндл следует получить в функции OnInit(). Также проверяем ошибку получения хэндла. В случае неудачи выводим сообщение "Неудачная инициализация Impulse" и прерываем работу индикатора с ключом INIT_FAILED.
Функция OnDeinit():
void OnDeinit(const int reason) { //--- удаление индикаторов IndicatorRelease(Impulse); IndicatorRelease(Handle); IndicatorRelease(Kernel); }
В функции OnDeinit() удаляем использовавшиеся индикаторы.
Функция OnCalculate():
static bool Flag=false; //флаг ошибки if(Flag) return(rates_total); //если флаг, то выход
В начале функции добавляем статическую переменную Flag. Если при выполнении программы появятся ошибки, то Flag примет значение true, и дальнейшие итерации функции OnCalculate() будут прерываться в самом начале.
Далее следует блок, связанный с ручным режимом.
string Name; //короткое имя искомого индикатора if(!Automatic)//если ручной режим { if(ChartIndicatorsTotal(0,Window)>0)//если индикатор есть { Name=ChartIndicatorName(0,Window,Indicator);//ищем его имя Handle=ChartIndicatorGet(0,Window,Name);//ищем хендл } else//иначе { Alert("Нет индикатора"); Flag=true; return(rates_total); } if(Handle==INVALID_HANDLE)//если ошибка получения хендла { Alert("Нет индикатора"); Flag=true; return(rates_total); } CopyBuffer(Handle,0,0,1024,DataBuffer);//выводим ядро на график return(rates_total); }
Если входной параметр Automatic=false, то запускается ручной режим. Делается проверка на присутствие индикатора. В случае успеха ищем имя и хэндл, проверяем хэндл на ошибку, копируем данные в буфер индикатора. При неудаче выводим сообщение "Нет индикатора", Flag переводим в состояние true, прерываем выполнение функции OnCalculate().
Блок автоматического режима намного интереснее. Он состоит из поиска индикатора на графике и создания ядра.
Итак, поиск индикатора. Основная цель - получить хэндл.
if(ChartIndicatorsTotal(0,0)>0)//если индикатор в главном окне то { Name=ChartIndicatorName(0,0,0);//ищем его имя if(Name!="SpecAnalyzer")//если это не SpecAnalyzer Handle=ChartIndicatorGet(0,0,Name);//ищем хендл else { Alert("Индикатор не найден"); Flag=true; return(rates_total); } } else//иначе if(ChartIndicatorsTotal(0,1)>0)//если индикатор в первом подокне { Name=ChartIndicatorName(0,1,0);//ищем его имя if(Name!="SAInpData")//если это не SAInpData Handle=ChartIndicatorGet(0,1,Name);//ищем хендл else//иначе { Alert("Индикатор не найден"); Flag=true; return(rates_total); } } if(Handle==INVALID_HANDLE)//если ошибка получения хендла { Alert("Нет индикатора"); Flag=true; return(rates_total); }
Сначала ищем индикатор в главном подокне графика, и проверяем, чтобы это не был SpecAnalyzer. Если в главном окне индикатора нет, то ищем в следующем подокне (с учетом, что здесь может быть SAInpData). В остальном действия сходны с ручным режимом.
Создание индикатора. Получаем параметры найденного индикатора и создаем такой же индикатор, только построенный от Impulse:
ENUM_INDICATOR indicator_type;//тип найденного индикатора MqlParam parameters[]; //его параметры int parameters_cnt=0; //число параметров //--- получаем тип индикатора, значения параметров и их количество parameters_cnt=IndicatorParameters(Handle,indicator_type,parameters); //--- указываем, что на вход индикатора должен поступает единичный импульс parameters[parameters_cnt-1].integer_value=Impulse; //--- получаем хендл индикатора от единичного импульса - ядро фильтра Kernel=IndicatorCreate(NULL,0,indicator_type,parameters_cnt,parameters); if(Kernel==INVALID_HANDLE)//если ошибка получения хендла { Alert("Неудачная инициализация Kernel"); Flag=true; return(rates_total); } CopyBuffer(Kernel,0,0,1024,DataBuffer);//выводим ядро на график
indicator_type - переменная специального перечисляемого типа ENUM_INDICATOR. Предназначена для получения типа индикатора.
parameters[] - массив типа MqlParam, специальная структура для хранения и передачи параметров индикаторов.С помощью функции IndicatorParameters получаем информацию об индикаторе с графика. Далее в массив с параметрами вносим небольшие изменения. В последнюю ячейку, где хранится название таймсерии (close, low, handle и т.д.) в поле integer_value вносим хэндл индикатора Impulse. Затем функцией IndicatorCreate создаем новый индикатор, который является ядром. Делаем проверку хэндла, выводим ядро на график.
Также небольшим изменениям подвергся индикатор SpecAnalyzer. Добавлены входные параметры:
input bool Automatic=true; //Автопоиск input int Window=0; //Номер подокна input int Indicator=0; //Номер индикатора
И соответственно, изменен вызов SAInpData:
ExtHandle=iCustom(NULL,0,"SpecAnalyzer\\SAInpData",Automatoc,Window,Indicator);
Индикатор SAInpData можно использовать самостоятельно для просмотра импульсной характеристики.
5. Примеры
Чтобы это все заработало, достаточно закинуть папку SpecAnalyzer в MetaTrader 5\MQL5\Indicators. Затем запускаем MetaTrader 5, открываем новый график инструмента EURUSD:
Рис. 22. Открытие нового графика EURUSD
Добавляем интересующий индикатор, например MA(16):
Рис. 23. Добавление индикатора Moving Average на график EURUSD
Запускаем SpecAnalyzer:
Рис. 24. Запуск индикатора SpecAnalyzer
Появится окно с параметрами:
Рис. 25. Параметры индикатора SpecAnalyzer
Для автоматического режима достаточно нажать "OK", что мы и делаем. В ручном режиме нужно будет true заменить на false и ввести расположение интересующего индикатора.
Итак, мы нажали "OK". В появившемся окне спектроанализатора жмем кнопку "External Data"
Рис. 26. Режим выбора входных данных индикатора SpecAnalyzer
Теперь рассмотрим работу в ручном режиме. Во-первых, добавляем индикатор Impulse на график:
Рис. 27. Добавление индикатора Impulse
От него строим целевой индикатор. Для этого перетаскиваем индикатор мышкой в окно Impulse и в параметрах выбираем применить к данным предыдущего индикатора
Рис. 28. Построение индикатора Moving Average на данных индикатора Impulse
Должно получиться так:
Рис. 29. Результат расчета индикатора Moving Average на единичном импульсе Impulse
Теперь смотрим список индикаторов по щелчку правой кнопкой мыши:
Рис. 30. Расположение индикаторов в списке
Наш индикатор находится в подокне №1 и имеет порядковый номер 1 (не забываем, что нумерация начинается с нуля, а не единицы). Запускаем SpecAnalyzer. Ставим false, 1, 1. Жмем кнопку "External Data".
Свойства индикатора можно менять на лету. Попробуйте с помощью списка индикаторов поменять период, и посмотрите, как реагирует спектроанализатор.
Перед тем, как показать примеры, нужно сказать об особенности индикатора SpecAnalyzer. Отсчеты на его шкале - это не периоды, а отсчеты частотной сетки. Спектроанализатор работает с ядром длиной до 1024 отсчетов. Это означает, что шаг частотной сетки равен 1/1024=0,0009765625. Тогда значение 128 на шкале соответствует частоте 0.125 или периоду 8.
шкала | период |
---|---|
16 | 64 |
32 | 32 |
64 | 16 |
128 | 8 |
256 | 4 |
384 | 2.67 |
512 | 2 |
SMA (16)
Рис. 31. Импульсная характеристика индикатора Simple Moving Average (КИХ фильтр)
Рис. 32. Частотная характеристика индикатора Simple Moving Average
Видно, что это НЧ фильтр, так как преобладают низкие частоты. Плохое подавление в полосе задержания.
EMA (16)
Рис. 33. Импульсная характеристика индикатора Exponential Moving Average (БИХ фильтр)
Рис. 34. Частотная характеристика индикатора Exponential Moving Average
Индикатор Exponential Moving Average также представляет собой НЧ фильтр. Линия довольно гладкая, но по сравнению с предыдущим индикатором переходная полоса шире. Подавление примерно на том же уровне.
Теперь посмотрим на результаты индикатора "Универсальный цифровой фильтр".
Фильтр нижних частот
Рис. 35. Импульсная характеристика (ядро) фильтра нижних частот
Рис. 36.
Частотная характеристика
фильтра нижних частот
Фильтр верхних частот
Рис. 37. Импульсная характеристика (ядро) фильтра верхних частот
Рис. 38. Частотная характеристика фильтра верхних частот
Полосовой фильтр
Рис. 39. Импульсная характеристика (ядро) полосового фильтра
Рис. 40. Частотная характеристика полосового фильтра
Заключение
В заключение хотелось бы сказать, что параметры фильтров сильно связаны друг с другом. Улучшение одних влечет ухудшение других. Поэтому параметры нужно выбирать исходя из поставленной задачи.
Например, если необходимо увеличенное подавление частот в полосе задержания, то придется пожертвовать крутизной спада. Если оба параметра нужно сделать хорошими, тогда придется увеличить длину ядра, что в свою очередь повлияет на отставание индикатора от цены или увеличит искажения в полосе пропускания.
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Это не автор статьи пришел к такому выводу, это уже давно всем известно, с 1800-ых годов, уже аж 200 с лишним лет.
Кстати между прочим, автор сей теоремы был еще и представителем школы утопического социализма.
Франсуа Мария Шарль Фурье
Фурье, насколько помню, пришел не совсем к такому выводу. Он пришел к выводу, что любую сложную функцию можно представит суммой более простых, а не суммой синусоид. В статье рассматривается частный случай. И было бы, на мой взгляд, правильней в статье написать не "любую кривую можно представить как сумму синусоид", а "любую периодическую кривую можно представить как сумму синусоид".
Если исходить из предположения, что котировки это нечто периодическое, тогда изложенная теория применима для тех.анализа. Если котировки не имеют периодичности, то изложенная теория будет бесполезна. ИМХО.Фурье, насколько помню, пришел не совсем к такому выводу. Он пришел к выводу, что любую сложную функцию можно представит суммой более простых, а не суммой синусоид. В статье рассматривается частный случай. И было бы, на мой взгляд, правильней в статье написать не "любую кривую можно представить как сумму синусоид", а "любую периодическую кривую можно представить как сумму синусоид".
Если исходить из предположения, что котировки это нечто периодическое, тогда изложенная теория применима для тех.анализа. Если котировки не имеют периодичности, то изложенная теория будет бесполезна. ИМХО.Более простых это каких? Необязательно гадать, достаточно в учебник или в справочник заглянуть. Про эту периодичность и непереодичность уж сколько здесь было разговров, и уж сколько можно об одном и том же? Непериодическая функция тоже разлагается, на ограниченном участке времени принимается, что это один период и прекрасно разлагается.
https://www.google.ru/search?ie=UTF-8&hl=ru&q=%D1%80%D1%8F%D0%B4%20%D1%84%D1%83%D1%80%D1%8C%D0%B5
Непериодическая функция тоже разлагается, на ограниченном участке времени принимается, что это один период и прекрасно разлагается.
Конечно это можно делать. Теоретически все красиво. А практически вне этого ограниченного участка это разложение бесполезно. Или почти бесполезно.
P.S. Посмотрел ученики, которые вылезли по ссылке. Вот здесь хороший пример того, что вне диапазона можно получить абсурд.
Конечно это можно делать. Теоретически все красиво. А практически вне этого ограниченного участка это разложение бесполезно. Или почти бесполезно.
P.S. Посмотрел ученики, которые вылезли по ссылке. Вот здесь хороший пример того, что вне диапазона можно получить абсурд.
Для экстраполяции не стоит и пытаться.
Разложить на составляющие, некоторые откинуть, собрать назад. По полученному смотреть направление (куда наклон).
Для экстраполяции не стоит и пытаться.
Разложить на составляющие, некоторые откинуть, собрать назад. По полученному смотреть направление (куда наклон).