Русский
Русский
English
Статистика
Реклама

Временные ряды

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

19.11.2020 02:23:00 | Автор: admin
Задача построения временных рядов на графиках решалась человеком уже в средневековье. Разработчики современных программных систем визуализации данных уделяют ей довольно много внимания. Сегодня для конкретного практического случая обработки временных рядов можно выбрать из десятков подходящих инструментов наиболее подходящий. Тем не менее, остаются случаи, для которых в наиболее популярных продуктах не хватает некоторых возможностей.

Характеристика рассматриваемого класса задач анализа данных


Необходимо исследовать многомерный временной ряд при следующих условиях:

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

Особенности работы с точки зрения среды визуализации данных


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

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

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

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

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

Существующие системы


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



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



Такой интерфейс пользователя трудно назвать оптимальным для рассматриваемых задач.
Существенно более эффективное взаимодействие с пользователем строится посредством реализации принципа WYSIWYG в работе с областью построения. Следующая анимация показывает взаимодействие пользователя с таким интерфейсом.



Пример записан в приложении Advanced Grapher, но аналогичную опцию поддерживают многие другие системы, например, библиотека MetricsGraphics.js.

Выигрыш в скорости работы по сравнению с вариантом MS Excel здесь очевиден. Вся задача масштабирования решается в один клик:

  • левая кнопка мыши нажата в точке, соответствующей углу новой прямоугольной области;
  • курсор перемещен в противоположный угол новой области;
  • левая кнопка мыши отпущена.



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

Второй недостаток также связан с вертикальным масштабированием. Состоит он в невозможности реализации этого интерфейса для задач рассматриваемого класса. Проблема состоит в том, что единственным кликом в нашем случае пользователь вводит уже не 4, а 6, 8 или более значений, в зависимости от количества шкал оси ординат. Каждая шкала оси ординат на графике получает новые значения верхней и нижней границ, но фактически все эти границы, сколько бы их ни было, определяются двумя числами. Эти числа ординаты положений курсора мыши в начале и конце клика. Задача пользователя не просто усложняется по сравнению со случаем одномерного ряда. Она еще и перестает быть решаемой: общий интервал, обеспечивающий приемлемый масштаб для каждого ряда, не всегда существует.
Для примера на рисунке представлен один из возможных на практике результатов такого масштабирования.



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

Доработка пользовательского интерфейса


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

Как правило, определившись с временным интервалом, пользователь определяет локальные экстремумы, чтобы наилучшим образом представить область значений. Оптимальное решение для большинства случаев состоит в совмещении области значений и отображаемого интервала шкалы (возможны и алгоритмы с более тонкой логикой, когда отображаемая область имеет небольшой отступ выше и ниже области значений; различия между этими алгоритмами не принципиальны).

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



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

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



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

Ограничение применимости


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

Cortex и не только распределённый Prometheus

04.03.2021 10:11:49 | Автор: admin

В последнее время Prometheus стал де-факто стандартом для сбора и хранения метрик. Он удобен для разработчиков ПО - экспорт метрик можно реализовать в несколько строк кода. Для DevOps/SRE, в свою очередь, есть простой язык PromQL для получения метрик из хранилища и их визуализации в той же Grafana.

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

Недостатки

  • Отсутствие отказоустойчивости

    Prometheus работает только в единственном экземпляре, никакого HA.

  • Отсутствие распределения нагрузки

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

  • Нет поддержки multi-tenancy

    Все метрики летят в один большой котёл и разгребать их потом используя PromQL и метки не всегда удобно. Часто хочется разделить различные приложения и\или команды по своим песочницам чтобы они друг другу не мешали.

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

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

Но есть и минусы:

  • После того как один хост из пары упадёт/перезагрузится/whatever - у них случится рассинхронизация. В метриках будут пропуски.

  • Все метрики приложения должны умещаться на один хост

  • Управлять таким зоопарком будет сложнее - какие-то из Prometheus могут быть недогружены, какие-то перегружены. В случае запуска в каком-нибудь Kubernetes это не так важно.

Давайте рассмотрим какими ещё способами можно решить это.

PromQL прокси

Например promxy, который размещается перед 2 или более инстансами Prometheus и делает fan-out входящих запросов на все из них. Затем дедуплицирует полученные метрики и, таким образом, закрывает пропуски в метриках (если, конечно, они не попали на один и тот же временной интервал).

Минусы подобного решения на поверхности:

  • Один запрос нагружает сразу все инстансы за прокси

  • Прокси решает только проблему с пропусками в метриках.

Но для тех, у кого нагрузка укладывается в возможности одного Prometheus (либо ее можно грамотно раскидать по нескольким HA-парам) и кому не нужен multi-tenancy - это очень хороший вариант.

Thanos

Thanos - это уже более продвинутое решение.

Он устанавливает рядом с каждым инстансом Prometheus так называемый Sidecar - отдельный демон, который подглядывает за блоками данных, которые генерирует Prometheus. Как только блок закрывается - Sidecar загружает его в объектное хранилище (S3/GCS/Azure). Длина блоков в Prometheus прибита гвоздями и равна 2 часам.

Также он является прокси между GRPC Thanos StoreAPI и Prometheus для получения метрик, которые еще не были загружены в объектное хранилище.

Отдельный компонент Querier реализует PromQL: в зависимости от временного интервала запроса и настроек глубины хранения данных в Prometheus он может направить его в объектное хранилище, в Sidecar или в разбить на два подзапроса - для свежих данных запрос пойдёт через Sidecar в Prometheus, а для более старых - в объектное хранилище.

Отказоустойчивость свежих данных в Thanos реализуется примерно так же как и в promxy - делается fan-out запросов на все причастные сервера, результаты накладываются друг на друга и дедуплицируются. Задача по защите исторических данных лежит на объектном хранилище.

Multi-tenancy есть в некотором зачаточном состоянии, но в эту сторону проект, судя по всему, не развивается особо.

Cortex

Это наиболее сложный и функциональный проект. Его начали разрабатывать в Grafana Labs для своего SaaS решения по хранению метрик и несколько лет назад выложили в open source, с тех пор разработка идёт на гитхабе.

Как можно видеть на диаграмме выше - в нём очень много компонентов. Но бояться не стоит - большую часть из них можно не использовать, либо запускать в рамках одного процесса - single binary mode.

Так как Cortex изначально разрабатывался как SaaS решение - в нём поддерживается end-to-end multi-tenancy.

Хранение метрик

На данный момент в Cortex есть два движка. Оба они хранят данные в объектном хранилище, среди которых поддерживаются:

  • S3

  • GCS

  • Azure

  • OpenStack Swift (экспериментально)

  • Любая примонтированная ФС (например - NFS или GlusterFS). Хранить блоки на локальной ФС смысла нет т.к. они должны быть доступны всему кластеру.

Далее я буду для краткости называть объектное хранилище просто S3.

Chunks Storage

Изначальный движок в Cortex - он хранит каждую timeseries в отдельном чанке в S3 или в NoSQL (Cassandra/Amazon DynamoDB/Google BigTable), а метаданные (индексы) хранятся в NoSQL.

Chunks Storage, думается, со временем совсем выпилят - насколько я слышал, Grafana Labs свои метрики уже мигрировали в Blocks Storage.

Blocks Storage

Новый, более простой и быстрый движок, основанный на Thanos. Который, в свою очередь, использует формат блоков самого Prometheus. С ним нет нужды в NoSQL и модуле Table Manager (но нужен другой - Store Gateway).

Thanos, в данном, случае является внешней vendored зависимостью в коде Cortex. Есть разговоры о том, чтобы объединить два проекта в один, но когда это будет неизвестно (и будет ли вообще).

Архитектура

Далее я буду рассматривать работу с Blocks Storage.

Упрощённо принцип работы следующий:

  • Prometheus собирает метрики с endpoint-ов и периодически отправляет их в Cortex используя Remote Write протокол. По сути это HTTP POST с телом в виде сериализованных в Protocol Buffers метрик сжатый потом Snappy. В самом Prometheus, при этом, можно поставить минимальный retention period - например 1 день или меньше- читаться из него ничего не будет.

  • Модуль Distributor внутри Cortex принимает, валидирует, проверяет per-tenant и глобальные лимиты и опционально шардит пришедшие метрики. Далее он передает их одному или нескольким Ingester (в зависимости от того применяется ли шардинг).

    Также в рамках этого модуля работает HA Tracker (о нём ниже).

  • Ingester ответственен за запись метрик в долговременное хранилище и выдачу их для выполнения запросов. Изначально метрики записываются в локальную ФС в виде блоков длиной 2 часа. Затем, по истечении некоторого времени, они загружаются в S3 и удаляются с локальной ФС.

    Также поддерживается репликация и zone awareness для дублирования блоков по различным availability domain (стойки, ДЦ, AWS AZ и так далее)

  • Store-Gateway служит для отдачи блоков из S3.

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

  • Querier реализует PromQL.

    При получении запроса анализирует его и, если необходимо, разбивает на два - одна часть пойдёт в Store Gateway (для более старых данных), а другая - в Ingester для свежих.

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

  • Compactor периодически просыпается и сканирует объектное хранилище на предмет блоков, которые можно склеить в более крупные. Это приводит к более эффективному хранению и быстрым запросам.

    Старые блоки не удаляются сразу, а маркируются и удаляются на следующих итерациях чтобы дать время Store-Gateway обнаружить новые, которые уже сжаты.

Отказоустойчивость

Помимо репликации данных между Ingester-ами нам необходимо обеспечить отказоустойчивость самих Prometheus. В Cortex это реализовано просто и элегантно:

  • Два (или более) Prometheus настраиваются на сбор метрик с одних и тех же endpoint-ов

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

    Например так:

  external_labels:    __ha_group__: group_1    __ha_replica__: replica_2
  • При приёме метрик Cortex из каждой группы выбирает один Prometheus и сохраняет метрики только от него

  • Остальным отвечает HTTP 202 Accepted и отправляет в /dev/null всё что они прислали

  • Если же активный инстанс перестал присылать метрики (сработал таймаут) - Cortex переключается на приём от кого-то из оставшихся в живых.

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

Авторизация

Каждый запрос на запись метрик из Prometheus должен содержать HTTP-заголовок X-Scope-OrgId равный идентификатору клиента (далее я буду называть их просто tenant, хорошего перевода не придумал). Метрики каждого tenant-а полностью изолированны друг от друга - они хранятся в разных директориях в S3 бакете и на локальной ФС

Таким же образом происходит и чтение метрик - в PromQL запросах тоже нужно тоже указывать этот заголовок.

При этом никакой реальной авторизации Cortex не проводит - он слепо доверяет этому заголовку. Auth Gateway есть в роадмапе, но когда он будет готов неизвестно. Даже просто добавить этот заголовок напрямую в Prometheus нельзя, только используя промежуточный HTTP прокси.

Для более гибкой интеграции Prometheus & Cortex я набросал простенький Remote Write прокси - cortex-tenant, который может вытаскивать ID клиента из меток Prometheus. Это позволяет использовать один инстанс (или HA-группу) Prometheus для отправки метрик нескольким разным клиентам. Мы используем этот функционал для разграничения данных разных команд, приложений и сред.

Авторизацию можно отключить, тогда Cortex не будет проверять наличие заголовка в запросах и будет подразумевать что он всегда равен fake - то есть multi-tenancy будет отключен, все метрики будут падать в один котёл.

При необходимости данные одного клиента можно полностью удалить из кластера - пока это API экспериментально, но работает.

Настройка Cortex

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

Для всех остальных гораздо проще установить несколько HA-пар Prometheus (например на каждую команду или каждый проект) и поверх них натянуть promxy

Так как документация имеет некоторое количество белых пятен - я хочу рассмотреть настройку простого кластера Cortex в режиме single binary - все модули у нас будут работать в рамках одного и того же процесса.

Danger Zone! Дальше много конфигов!

Зависимости

Нам понадобится ряд внешних сервисов для работы.

  • etcd для согласования кластера и хранения Hash Ring

    Cortex также поддерживает Consul и Gossip-протокол, которому не нужно внешнее KV-хранилище. Но для HA-трекера Gossip не поддерживается из-за больших задержек при сходимости. Так что будем юзать etcd

  • memcached для кеширования всего и вся.

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

    Также есть DNS-based discovery через SRV-записи, если не хочется указывать вручную.

  • minio для реализации распределённого S3 хранилища.

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

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

    minio поддерживает Erasure Coding для отказоустойчивости, что есть хорошо.

  • HAProxy для связывания компонентов воедино

  • cortex-tenant для распределения метрик по tenant-ам

  • Prometheus собственно для сбора метрик

Общие вводные

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

    3 страйпа не поддерживает minio c Erasure Coding - он нарезает от 4 до 16 дисков в один EC-набор. В реальном проекте лучше использовать 5 или какое-либо большее нечетное число чтобы не было Split Brain.

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

  • Все данные будем хранить в /data

  • Конфиги я буду приводить для одного хоста, для остальных обычно достаточно поменять адреса и\или хостнеймы

  • В качестве ОС используем RHEL7, но различия с другими дистрибутивами минимальны

  • У нас всё это дело, конечно, раскатывается через Ansible, но плейбук довольно сильно завязан на нашу инфраструктуру. Я постараюсь потом его подчистить и выложить

  • Некоторые RPM пакеты я собираю вручную (etcd, HAProxy и т.п.) с помощью FPM т.к. в репозиториях древние версии.

/etc/hosts
10.0.0.1 ctx110.0.0.2 ctx210.0.0.3 ctx310.0.0.4 ctx4

etcd

Как и Zookeeper, с настройками по умолчанию etcd - бомба замедленного действия. Он не удаляет ненужные снапшоты и разрастается до бесконечности. Зачем так сделано - мне не понятно.

Поэтому настроим его соответственно:

/etc/etcd/etcd.conf
ETCD_NAME="ctx1"ETCD_LOGGER="zap"ETCD_LOG_LEVEL="warn"ETCD_DATA_DIR="/data/etcd/ctx1.etcd"ETCD_LISTEN_CLIENT_URLS="http://personeltest.ru/away/10.0.0.1:2379,http://127.0.0.1:2379"ETCD_LISTEN_PEER_URLS="http://personeltest.ru/away/10.0.0.1:2380"ETCD_ADVERTISE_CLIENT_URLS="http://personeltest.ru/away/10.0.0.1:2379"ETCD_INITIAL_CLUSTER_TOKEN="cortex"ETCD_INITIAL_ADVERTISE_PEER_URLS="http://personeltest.ru/away/10.0.0.1:2380"ETCD_AUTO_COMPACTION_RETENTION="30m"ETCD_AUTO_COMPACTION_MODE="periodic"ETCD_SNAPSHOT_COUNT="10000"ETCD_MAX_SNAPSHOTS="5"ETCD_INITIAL_CLUSTER="ctx1=http://ctx1:2380,ctx2=http://ctx2:2380,ctx3=http://ctx3:2380,ctx4=http://ctx4:2380"

memcached

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

/etc/sysconfig/memcached
PORT="11211"USER="memcached"MAXCONN="512"CACHESIZE="2048"OPTIONS="--lock-memory --threads=8 --max-item-size=64m"

Minio

Тут минимум настроек.

По сути мы просто перечисляем хосты, которые будут использоваться для хранения данных (+ путь до директории где данные хранить - /data/minio) и указываем ключи S3. В моем случае это были ВМ с одним диском, если у вас их несколько - то формат URL несколько меняется.

По умолчанию используется странное распределение дисков под данные и под коды Рида-Соломона: половина сырого объема уходит под redundancy. Так как у нас всего 4 хоста - это не особо важно. Но на большем по размеру кластере лучше использовать Storage Classes для снижения доли Parity-дисков.

/etc/minio/minio.env
MINIO_ACCESS_KEY="foo"MINIO_SECRET_KEY="bar"MINIO_PROMETHEUS_AUTH_TYPE="public"LISTEN="0.0.0.0:9000"ARGS="http://personeltest.ru/away/ctx{1...4}/data/minio"

Также нужно будет создать бакет с помощью minio-client - в нашем случае пусть называется cortex

HAProxy

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

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

На больших кластерах (сотни-тысячи хостов) такая схема может быть узким местом, но если вы работаете с такими, то и сами это знаете :)

/etc/haproxy/haproxy.cfg
global    daemon    maxconn 10000    log 127.0.0.1 local2    chroot /var/emptydefaults    mode http    http-reuse safe    hash-type map-based sdbm avalanche    balance roundrobin    retries 3    retry-on all-retryable-errors    timeout connect 2s    timeout client 300s    timeout server 300s    timeout http-request 300s    option splice-auto    option dontlog-normal    option dontlognull    option forwardfor    option http-ignore-probes    option http-keep-alive    option redispatch 1    option srvtcpka    option tcp-smart-accept    option tcp-smart-connect    option allbackupslisten stats    bind 0.0.0.0:6666    http-request use-service prometheus-exporter if { path /metrics }    stats enable    stats refresh 30s    stats show-node    stats uri /frontend fe_cortex    bind 0.0.0.0:8090 tfo    default_backend be_cortexfrontend fe_cortex_tenant    bind 0.0.0.0:8009 tfo    default_backend be_cortex_tenantfrontend fe_minio    bind 0.0.0.0:9001 tfo    default_backend be_miniobackend be_cortex    option httpchk GET /ready    http-check expect rstring ^ready    server ctx1 10.0.0.1:9009 check observe layer7 inter 5s    server ctx2 10.0.0.2:9009 check observe layer7 inter 5s    server ctx3 10.0.0.3:9009 check observe layer7 inter 5s    server ctx4 10.0.0.4:9009 check observe layer7 inter 5sbackend be_cortex_tenant    option httpchk GET /alive    http-check expect status 200    server ctx1 10.0.0.1:8008 check observe layer7 inter 5s    server ctx2 10.0.0.2:8008 check observe layer7 inter 5s backup    server ctx3 10.0.0.3:8008 check observe layer7 inter 5s backup    server ctx4 10.0.0.4:8008 check observe layer7 inter 5s backupbackend be_minio    balance leastconn    option httpchk GET /minio/health/live    http-check expect status 200    server ctx1 10.0.0.1:9000 check observe layer7 inter 5s    server ctx2 10.0.0.2:9000 check observe layer7 inter 5s backup    server ctx3 10.0.0.3:9000 check observe layer7 inter 5s backup    server ctx4 10.0.0.4:9000 check observe layer7 inter 5s backup

cortex-tenant

Это просто прокси между Prometheus и Cortex. Главное - выбрать уникальное имя метки для хранения там tenant ID. В нашем случае это ctx_tenant

/etc/cortex-tenant.yml
listen: 0.0.0.0:8008target: http://127.0.0.1:8090/api/v1/pushlog_level: warntimeout: 10stimeout_shutdown: 10stenant:  label: ctx_tenant  label_remove: true  header: X-Scope-OrgID

Prometheus

В случае 4 хостов Prometheus-ы можно разбить их на две HA-пары, каждую со своим ID группы и раскидать job-ы по ним.

host1 /etc/prometheus/prometheus.yml
global:  scrape_interval: 60s  scrape_timeout: 5s  external_labels:    __ha_group__: group_1    __ha_replica__: replica_1remote_write:  - name: cortex_tenant    url: http://127.0.0.1:8080/pushscrape_configs:  - job_name: job1    scrape_interval: 60s    static_configs:      - targets:          - ctx1:9090        labels:          ctx_tenant: foobar  - job_name: job2    scrape_interval: 60s    static_configs:      - targets:          - ctx2:9090        labels:          ctx_tenant: deadbeef
host2 /etc/prometheus/prometheus.yml
global:  scrape_interval: 60s  scrape_timeout: 5s  external_labels:    __ha_group__: group_1    __ha_replica__: replica_2remote_write:  - name: cortex_tenant    url: http://127.0.0.1:8080/pushscrape_configs:  - job_name: job1    scrape_interval: 60s    static_configs:      - targets:          - ctx1:9090        labels:          ctx_tenant: foobar  - job_name: job2    scrape_interval: 60s    static_configs:      - targets:          - ctx2:9090        labels:          ctx_tenant: deadbeef

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

Cortex

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

Многие модули, такие как Distributor, Ingester, Compactor, Ruler кластеризуются с помощью Hash-Ring в etcd. На весь кластер выделяется некоторое количество токенов, которые распределяются между всеми участниками кольца равномерно.

Упрощенно - при приходе, допустим, новой метрики её метки хешируются, результат делится по модулю на количество токенов и в итоге направляется на хост, который владеет данным диапазоном токенов. Если он не отвечает - то отправляют следующему по кольцу хосту.

Если хост выходит из кластера (помер, перезагружается и т.п.), то его диапазон перераспределяется по остальным.

Все настройки у нас будут лежать в /etc/cortex/cortex.yml

Также т.к. Cortex сделан для работы в контейнерах - всё можно настроить через командную строку чтобы не пропихивать конфиг в контейнер.

Глобальные настройки

# Список модулей для загрузкиtarget: all,compactor,ruler,alertmanager# Требовать ли заголовок X-Scope-OrgIdauth_enabled: true# Портыserver:  http_listen_port: 9009  grpc_listen_port: 9095limits:  # Разрешаем HA-трекинг  accept_ha_samples: true  # Названия меток, которые мы используем в Prometheus для  # маркировки групп и реплик  ha_cluster_label: __ha_group__  ha_replica_label: __ha_replica__  # Максимальный период в прошлое на который мы можем делать  # PromQL запросы (1 год).  # Всё что больше будет обрезано до этого периода.  # Это нужно для реализации retention period.  # Для фактического удаления старых блоков нужно еще настроить lifecycle  # правило в бакете S3 на пару дней глубже  max_query_lookback: 8760h

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

Другой трудностью изначально было то, что Cortex не поддерживал гибкий список модулей, которые нужно активировать. Была возможность либо указать all, который на самом деле ни разу не all:

# cortex -modulesalertmanagerallcompactorconfigsdistributor *flusheringester *purger *querier *query-frontend *query-schedulerruler *store-gateway *table-manager *Modules marked with * are included in target All.

Либо указать строго один модуль.

Поэтому пришлось сделать пулл-реквест чтобы добавить возможность загружать список любых модулей. В данном случае мы используем all + compactor, ruler и alertmanager

Хранилище

storage:  # Выбираем хранилище Blocks Storage  engine: blocks# Конфигурируем егоblocks_storage:  # Тип бэкенда  backend: s3    # Параметры доступа к S3  s3:    endpoint: 127.0.0.1:9001    bucket_name: cortex    access_key_id: foo    secret_access_key: bar    # TLS у нас нет    insecure: true    tsdb:    # Где хранить локальные блоки до загрузки в S3    dir: /data/cortex/tsdb    # Через какое время их удалять    retention_period: 12h    # Сжимать Write-Ahead Log    wal_compression_enabled: true  bucket_store:    # Где хранить индексы блоков, найденных в S3    # По сути это должно быть в модуле Store-Gateway,    # но по какой-то причине тут    sync_dir: /data/cortex/tsdb-sync    # Как часто сканировать S3 в поиске новых блоков    sync_interval: 1m    # Настраиваем различные кеши на наши memcached    # Каждый кеш имеет свой префикс ключей, так что пересекаться они не будут    index_cache:      backend: memcached      memcached:        addresses: ctx1:11211,ctx2:11211,ctx3:11211,ctx4:11211    chunks_cache:      backend: memcached      memcached:        addresses: ctx1:11211,ctx2:11211,ctx3:11211,ctx4:11211    metadata_cache:      backend: memcached      memcached:        addresses: ctx1:11211,ctx2:11211,ctx3:11211,ctx4:11211

Distributor

distributor:  ha_tracker:    # Включить HA-трекер для Prometheus    enable_ha_tracker: true    # Таймаут после которого срабатывает failover на другую реплику Prometheus.    # Нужно настроить так чтобы метрики приходили не реже этого интервала,    # иначе будут ложные срабатывания.    ha_tracker_failover_timeout: 30s    # Настраиваем etcd для HA-трекера    kvstore:      store: etcd      etcd:        endpoints:         - http://ctx1:2379         - http://ctx2:2379         - http://ctx3:2379         - http://ctx4:2379  # Настраиваем etcd для Hash-Ring дистрибьютеров  ring:    kvstore:      store: etcd      etcd:        endpoints:         - http://ctx1:2379         - http://ctx2:2379         - http://ctx3:2379         - http://ctx4:2379

Ingester

ingester:  lifecycler:    address: 10.0.0.1    # Название зоны доступности    availability_zone: dc1    # Немного ждём чтобы всё устаканилось перед перераспределением    # токенов на себя    join_after: 10s    # Храним токены чтобы не генерировать их каждый раз при запуске    tokens_file_path: /data/cortex/ingester_tokens    ring:      # На сколько Ingester-ов реплицировать метрики.      # Если указана зона доступности, то реплики будут выбираться из разных зон      replication_factor: 2      # etcd для Hash-Ring Ingester-ов      kvstore:        store: etcd        etcd:          endpoints:           - http://ctx1:2379           - http://ctx2:2379           - http://ctx3:2379           - http://ctx4:2379

Querier

По поводу подбора правильных величин лучше почитать документацию.

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

querier:  # Временные файлы  active_query_tracker_dir: /data/cortex/query-tracker  # Запросы с глубиной больше этой будут направляться в S3  query_store_after: 6h  # Запросы с глубиной меньше этой отправляются в Ingester-ы  query_ingesters_within: 6h5mfrontend_worker:  frontend_address: 127.0.0.1:9095query_range:  # Запросы будут разбиваться на куски такой длины и выполняться параллельно  split_queries_by_interval: 24h  # Выравнивать интервал запроса по его шагу  align_queries_with_step: true  # Включить кеширование результатов  cache_results: true    # Кешируем в memcached  results_cache:    # Сжимаем    compression: snappy    cache:      # TTL кеша      default_validity: 60s      memcached:        expiration: 60s      memcached_client:        addresses: ctx1:11211,ctx2:11211,ctx3:11211,ctx4:11211

Store-Gateway

Этот модуль подгружает из S3 бакета заголовки блоков (комбинации меток, временные интервалы и т.п.).

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

store_gateway:  # Включаем шардинг  sharding_enabled: true  sharding_ring:    # Включаем zone awareness    zone_awareness_enabled: true    # Идентификатор зоны    instance_availability_zone: dc1    # Сколько реплик держать    replication_factor: 2    # Hash-ring для Store-Gateway    kvstore:      store: etcd      etcd:        endpoints:         - http://ctx1:2379         - http://ctx2:2379         - http://ctx3:2379         - http://ctx4:2379

Compactor

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

compactor:  # Временная директория для блоков.  # Должно быть достаточно много места чтобы можно было загрузить блоки,  # скомпактить их и сохранить результат.  data_dir: /data/cortex/compactor  # Как часто запускать компакцию  compaction_interval: 30m  # Hash-Ring для компакторов  sharding_enabled: true  sharding_ring:    kvstore:      store: etcd      etcd:        endpoints:         - http://ctx1:2379         - http://ctx2:2379         - http://ctx3:2379         - http://ctx4:2379

Ruler + AlertManager

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

  • Правила в стандартном Prometheus формате мы будем складывать в /data/cortex/rules/<tenant>/rulesN.yml на каждом хосте. Можно использовать для этого S3 или другие хранилища - см. документацию

  • Cortex периодически сканирует хранилище и перезагружает правила

  • Конфиги AlertManager в стандартном формате складываем в /data/cortex/alert-rules/<tenant>.yml

    Аналогично можно складывать в S3 и т.п.

  • Cortex запускает инстанс AlertManager (внутри своего процесса) отдельно для каждого tenant, если находит конфигурацию в хранилище

ruler:  # Временные файлы  rule_path: /data/cortex/rules-tmp  # Включаем шардинг  enable_sharding: true  # Какому AlertManager-у сообщать об алертах  alertmanager_url: http://ctx1:9009/alertmanager  # Откуда загружать правила  storage:    type: local    local:      directory: /data/cortex/rules    # Hash-ring для Ruler-ов  ring:    kvstore:      store: etcd      etcd:        endpoints:         - http://ctx1:2379         - http://ctx2:2379         - http://ctx3:2379         - http://ctx4:2379alertmanager:  # Где хранить состояние алертов  data_dir: /data/cortex/alert-data  # Внешний URL нашего инстанса (нужен для генерации ссылок и т.п.)  external_url: http://ctx1:9009/alertmanager  # Кластеринг - какой адрес слушать и какой анонсировать пирам  cluster_bind_address: 0.0.0.0:9094  cluster_advertise_address: 10.0.0.1:9094  # Список пиров  peers:    - ctx2:9094    - ctx3:9094    - ctx4:9094  # Откуда загружать настройки  storage:    type: local    local:      path: /data/cortex/alert-rules

Заключение

Вот и всё, можно запускать все сервисы - сначала зависимости, потом Cortex, затем - Prometheus.

Я не претендую на полноту, но этого должно быть достаточно чтобы начать работать.

Нужно учитывать, что Cortex активно развивается и на момент написания статьи часть параметров в master-ветке и документации (которая генерируется из неё) уже объявлено deprecated. Так что, вполне возможно, в новых версиях нужно будет конфиги немного исправлять.

Если есть вопросы и\или замечания - пишите, постараюсь добавить в статью.

Подробнее..

Корреляция между временными рядами что может быть проще?

16.02.2021 12:20:24 | Автор: admin

Предварительные замечания

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

Сказ о том, почему я не справился с оформлением

Для начала, я внимательно прочитал все советы для новичков о том, как оформить статью. Я честно старался им следовать. Подготовив статью в гуглдоках, я проверил текст, расставил ссылки и примечания, добавил формулы и картинки, предварительно сохраненные в habrastorage. После чего воспользовался рекомендованным хабраконвертером.

Я был готов, что при конвертации все сноски исчезнут, и что спойлеры придется заново расставить вручную. Меня не смущало, что все форматирование надо будет проверить, ссылки на рисунки - поправить, а формулы - набрать еще раз. Единственное, к чему я не был готов - что при копировании текста из хабраконвертора в окно редактирования статьи исчезнут АБСОЛЮТНО ВСЕ теги. Причем, буфер обмена у меня работает правильно - в Notepad конвертированный текст вставляется без ошибок.

Конечно, первое, что приходит в голову в такой ситуации: я ведь использую режим wysiwyg (это следует из адресной строки браузера), может, все дело в этом? Наверно, надо переключиться в режим редактирования исходного текста и что-нибудь там поправить? Но на странице редактора просто нет такой кнопки! Получается, что режим wysiwyg - основной и единственный... Но ведь тогда я должен сразу видеть все гиперссылки? А их просто нет?! Или для этого все же надо переключиться в режим "просмотра оформленной публикации"? Но такой кнопки тоже нет! Есть только кнопка отправить на модерацию. Нажав которую, я больше ничего в своем посте изменить не смогу...

Ошеломленный, но не сломленный до конца, я честно взялся за редактирование статьи. Сначала я попытался расставить заголовки. Как это делают нормальные люди в большинстве нормальных редакторов? Выделяют будущий заголовок и нажимают кнопку "Heading N". Но как оказалось, в окне редактирования Хабра сделать это нельзя! Выделяю текст, появляется контекстное меню... но такой опции там просто нет (см. скриншоты)! Как нет и соответствующей кнопки рядом с окном редактора. А по нажатию рекомендованной клавиши "/" выделенный текст просто заменяется на "/" (самое прикольное, что меню, - видимо, относящееся к уже уничтоженному тексту, - при этом все же всплывает!) Если же просто нажать слэш, ничего не выделяя, то контекстное меню позволяет вставить только формулу или изображение. Но не заголовок (см. скриншоты).

Чтобы получить доступ к более полному меню, надо почему-то сначала создать пустую строку. При этом кнопка "...", продублированная в правом верхнем углу любого абзаца, содержит только одну опцию "Удалить". Признаюсь, мне это показалось немножко странным. Но ведь Хабр - это сайт, сделанный профессионалами IT для общения профессионалов IT?! Наверно, я просто что-то не до конца понимаю в современном эргономически выверенном дизайне?!

Ладно, приступим к вставке рисунков. Оформляя статью в гуглдоках, я заранее загрузил их на habrastorage. Теперь надо только открыть хранилище и добавить ссылки в статью. На всякий случай, вхожу в хранилище и... где мои файлы, загруженные в прошлый раз? Их нет (см. скриншоты)! Хотя я всегда логинился под своим именем.

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

Выбираю в контекстном меню редактора пункт "Картинка". Я уже почти не надеюсь увидеть перед собой "ноготки" от изображений, ранее загруженных мной в habrastorage, хотя по здравому смыслу именно такой результат (с возможностью выбора нужной картинки) был бы самым логичным. И верно - на экране всплывает обычное окно выбора файла. Ничего не поделаешь, надо вручную копипастить ссылку на ранее загруженное изображение. Вставляю... и получаю ответ habrastorage: "Файл не найден".

Вы знаете, это все-таки слишком.

Конечно, любое сообщество вправе ставить входные барьеры и отсеивать тех, кто заведомо профнепригоден. Но я же пишу статью про статобработку, а не собеседуюсь на оформителя публикаций в стеке технологий ушедшего века. Хотя, даже в редакторах MS DOS я все-таки мог перемещаться по тексту клавишами "вправо" и "влево", прыгать на соседнее слово с помощью "Ctrl+вправо" и "Ctrl+влево", а также выделять текст, добавляя к этим клавишам Shift. Поэтому мне трудно понять, почему в 2021г, работая в одном из самых популярных браузеров Google Chrome (версия 87.0.4280.141-64), на сайте Хабра я лишен этих возможностей. Или почему невозможно вставить слово, скопированное в буфер обмена из начала строки, в конец той же самой строки (оно почему-то обрамляется двумя пустыми строчками сверху и снизу). Или почему при копировании фрагмента, содержащего только что оформленную гиперссылку, в соседний абзац, эта гиперссылка таинственно исчезает. Нет, я не критикую редактор Хабра - но как человек, генерирующий и набирающий сотни страниц оригинального текста в год, хочу все-таки высказать сомнение в том, что такое поведение редактора, хмм, удобно для всех. Впрочем, я совсем не профессионал IT, а всего лишь научный сотрудник и по совместительству программист. Возможно, я и правда просто не дорос до нужного уровня.

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

Однако потратив полный рабочий день на подготовку статьи, я не готов потратить еще два раза по столько на преодоление барьера хабраредактора. Как бы высоко я не оценивал местное сообщество, я просто не вижу смысла осваивать ущербный ретроредактор в качестве теста на профпригодность. Невольно закрадывается сомнение в уровне профессионализма команды Хабра, которая не может (или не хочет?) дать новым пользователям простые и понятные инструменты для оформления публикаций. Или хотя бы доходчиво объяснить, как пользоваться теми, что есть. Но если так, то стоит ли прилагать столько усилий, чтобы стать частью такого сообщества?

P.S. И - да, я несколько раз пытался задать эти вопросы редакторам, писал на почту neo@habr.team, прежде чем высказать все это здесь. Даже пробовал задавать такие вопросы авторам статей, посвященных оформлению хабр-публикаций (хотя это с моей стороны уже почти спам). Но ответов не получил. Поэтому и решил обернуть свою просьбу о помощи вот в такой вот немного странный формат..

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

А теперь обращаюсь к читателям:

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

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

Содержание:

Дополнительное замечание про распределения: нормально ли, что анализируя данные геофизического мониторинга, мы никогда не встретимся с нормальным распределением?


Часть 1 необходимая тривиальщина

Если вы еще не совсем забыли прочитанное в учебниках по статистике, можете смело этот раздел пропустить, и переходить сразу к части 2-ой.

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

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

Например, чтобы оценить наличие связи между двумя случайными величинами X и Y, мы можем вычислить коэффициент Rxy корреляции между ними.

Для независимых случайных величин с не слишком экзотическими свойствами значение Rxy будет обычно приближаться к нулю по мере роста объема той выборки, по которой мы оцениваем Rxy. Прелесть и мощь математики проявляется в том, что, имея самую минимальную информацию об исходных случайных величинах (достаточно знать их функцию распределения), мы можем точно сказать, как именно (по какому закону) будет приближаться к нулю Rxy, если X и Y действительно независимы. Например, если X и Y имеют нормальное распределение, то 95%-ный доверительный уровень z95 можно приблизительно оценить по формуле:

z95=2/sqrt(N)

где N это количество пар измерений в выборке (будем считать, что их достаточно много). Говоря простыми словами, если мы оценим коэффициент корреляции Rxy между X и Y по выборке, содержащей 100 пар значений, то для независимых X и Y лишь в одном случае из 20 модуль Rxy окажется больше 0.2. А значениеz99=3/sqrt(N) =0.3) и вовсе будет превышено лишь в 1% случаев. Поэтому, получив в такой ситуации Rxy=0.4 (то есть, намного выше, чем z), мы говорим, что произошло очень редкое и маловероятное, в рамках выдвинутой гипотезы, событие. Настолько редкое, что гипотеза о независимости X и Y (иногда ее называют "нулевая гипотеза"), скорее всего, неверна, и ее надо отвергнуть. Именно так обычно доказывают, что X и Y статистически связаны.

Мало-мальски искушенный читатель, наверно, уже начинает зевать: зачем я опять повторяю известные вещи? Все сказанное выше, действительно, очень похоже на правду. Однако, как говорили еще в древнем Риме, тут не вся правда.

Первый (и очень важный) подводный камень состоит в том, что наши расчеты (которые только что привели к противоречию) на самом деле опирались не только на предположение о независимости случайных величин X и Y, но также еще и на предположение о нормальности их распределения. Аномальное (в рамках нашей модели) значение Rxy действительно говорит, что модель, по всей видимости, неверна. Однако ошибка может быть в любом месте. Вполне возможно, что в X и Y действительно независимы, просто они имеют другое (не гауссовское) распределение. При котором значение Rxy=0.4 при объеме выборки N=100 вполне обыденно и типично.

В частности, наши формулы для оценки z будут бесстыдно врать, если окажется, что дисперсия X и Y бесконечна. Простейший пример такой ситуации это так называемое "пушечное" распределение. Давайте поставиморудие на вращающийся лафет, повернем его в случайное положение (все углы от 0 до 180 равновероятны) и пальнем в прямую бесконечную стену. (Не забываем, что это модель, и снаряд летит по прямой). Теперь, чтобы ввести случайную величину L с бесконечным матожиданием и бесконечной дисперсией, достаточно взять линейку и измерить то расстояние, которое пролетит ядро

до попадания в стенку

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

К счастью для большинства аналитиков, столь экзотические распределения в обычной практической жизни встречаются редко. Бывает, что распределение может оказаться, например, равномерным, однако это не очень сильно повлияет на уровни значимости z статистики Rxy . Они лишь немного изменятся по сравнению со значениями, рассчитанными для гауссовых X и Y. Конечно, есть еще проблема "тяжелых хвостов", или выбросов, но она сравнительно просто может быть решена выбраковкой таких значений перед началом анализа.

Так все-таки, нужно ли проверять функцию распределения исходных случайных величин на нормальность? Формально, конечно, да... Но могу вас заверить, что для достаточно длинного экспериментального ряда (если, конечно, это не выходной сигнал гауссовского генератора белого шума) такая проверка всегда покажет, что распределение достоверно отличается от нормального. Поэтому общепринятый метод заключается в том, что в такой ситуации уровни значимости все равно вычисляются по стандартным формулам для случайных величин с гауссовым распределением. А затем оговаривается, что поскольку условия применимости этих формул немного нарушены, реальная значимость вместо 99%-ной может оказаться, например, 97%-ной. Считается, что такие различия не играют особой роли, если рассчитанный уровень значимости превышается многократно. Например, если при объеме выборки 10000 корреляция Rxy=0.25 (а оценка 3/sqrt(N) дает значение z99=0.03), то исходную гипотезу о независимости X и Y все равно можно смело отвергнуть. Ведь значение z превышено на порядок!

Буквоеды, конечно, скажут, что подобный вывод не является математически строгим. Но реальный мир всегда отличается от абстрактной модели. При обработке результатов любого эксперимента мы неизбежно должны принимать какие-то допущения, доказать которые невозможно. Вот и в описанном выше примере, несмотря на отсутствие строгости, этот вывод будет верным по существу, так как для действительно независимых случайных величин X и Y такое событие (Rxy=0.25 при объеме выборки 10000) практически невероятно ни при каком разумном распределении X и Y.

Ну что, переходим к расчетам?

Часть 2. Критерий истины практика

А теперь приведу пару фактов, сопоставление которых друг с другом полностью разрушает только что сформулированную, такую стройную и прекрасную картину нашего модельного мира.

Факт первый: при числе измерений порядка 10000 для любых независимых случайных величин с "адекватным" распределением вероятность получения |Rxy| > 0.1 исчезающе мала. Во всяком случае, она существенно меньше, чем 0.01.

Факт второй: если посчитать коэффициент корреляции между любыми геофизическими параметрами, для которых имеются достаточно длинные ряды наблюдений, то сплошь рядом окажется, что Rxy по модулю больше, чем 0.1. Иногда много больше. Причем, это верно для любых пар рядов. В зависимости от конкретного набора параметров, такие "суперзначимые" корреляции могут наблюдаться в половине всех случаев или даже в трех четвертях. Точная цифра не имеет значения ведь согласно теории вероятностей, для действительно независимых величин они должны обнаруживаться чуть чаще, чем никогда. Так что же, все до одного геофизические параметры правда взаимозависимы? Погодите немного с ответом...

Не будем зацикливаться на геофизике. Посчитаем, к примеру, коэффициент корреляции между уровнем воды в скважине на Камчатке и активностью тараканов в аквариуме в подвале лаборатории на Памире (активность измерялась автоматически, непрерывно в течение 5 лет, в рамках эксперимента по прогнозу землетрясений биологическим способом): Внезапно, корреляция равна Rxy= 0.35 при числе измерений около 20 000 (рисунок с графиками сигналов).

А вот для уровня активности сомиков в соседнем аквариуме расчеты дают значение Rxy= +0.16 (рисунок корреляционного поля). Неужели животные как-то чувствуют происходящее в скважине за тысячи километров?!

Дальше еще интереснее. Возьмем ряды чисел микроземлетрясений, произошедших в 1975-1985 годах в нескольких сейсмоактивных районах, и формально их сдвинем по времени лет на 20 (просто добавим поправку к календарю). Теперь прокоррелируем эти ряды, например, с изменениями солнечного радиоизлучения на волне 2800 МГц (10.7 см) в 1955-1965гг. Здравый смысл говорит, что после такого сдвига всякая корреляция должна исчезать. А вот и неправда! Сами значения Rxy при сдвиге, разумеется, поменяются. Но они все равно на порядки выше формальных 99%-ных уровней значимости. Хотя ни о какой причинно-следственной связи при подобном временном сдвиге

и речи не может быть

Например, для сейсмических станций NN 1-6 Гармского полигона корреляция получилась равной +0.04, +0.12, -0.01, +0.28, +0.45 и +0.17при количестве наблюдений около 3800 шт на каждой станции, что соответствует значению z=0.05. А всего из 15 станций значимая на 99%-ном уровне корреляция наблюдается в 9 случаях...

Те же зашкаливающие корреляции наблюдаются и практически для любых социальных процессов, а также в эконометрике. Правда, тут длина временных рядов по объективным причинам намного меньше десятки, в лучшем случае сотни точек. Зато можно коррелировать практически что угодно! Например, успеваемость школьников в Нижневартовске с урожаем кокосов на Филиппинах. А среднегодовую заболеваемость легочными инфекциями в Санкт-Петербурге (возьмем наблюдения за последние 30 лет) с индексом Доу-Джонса. Впрочем, вместо индекса Доу-Джонса с тем же успехом можно подставить в формулы урожайность пшеницы во Франции в двадцатом веке (только добавьте +100 к номеру года), или поголовье овец в Австралии в девятнадцатом (тут уже придется добавить две сотни). Можно даже проделать небольшой трюк, и интерпретировать число 1899 (и другие номера лет), как номер суток, считая с определенной даты. Если подобрать начальную дату так, чтобы этот ряд хронологически совместился с количеством вызовов скорой помощи в сутки в среднем российском городе (для которого вам повезет найти эти данные), итоговый вывод от этого не изменится! То есть, пробуя коррелировать разные случайно выбранные пары достаточно длинных экспериментальных рядов, мы будем лишь изредка получать Rxy, близкий к нулю. Гораздо чаще коэффициент корреляции будет намного выше любых формальных

уровней значимости

Необходимое уточнение: "гораздо чаще" получится, только если наблюдений достаточно много десятки и сотни тысяч отсчетов. Чем короче ряды, чем меньше значение N и чем выше значение z. Поэтому для коротких рядов вероятность, что коэффициент корреляции случайно попадет в интервал [-z, +z], возрастает.

Получается, что абсолютно любые процессы взаимосвязаны? Только для доказательства наличия "значимой" корреляции нужно нарастить длину ряда? Но как тогда быть с примерами, где мы просто "сдвинули" ряд во времени или вообще подменилишаг временной шкалы? Ведь речь тут идет уже не о синхронных процессах (при очень большом желании, поверить в связь школьников и кокосов все-таки можно), а именно о случайно наложенных друг на друга произвольных экспериментальных сигналах?!

Я понимаю, что у половины читателей рука уже тянется к пистолету, чтобы приставить его к виску автора и потребовать доказательства, то есть данные. Сразу скажу, что данных в табличном виде не будет. Оригинальные данные я не имею права выкладывать в сеть (но их описание и картинки можно найти, например, вот тут: раз, два, три). А давать ссылки на общедоступные данные просто не вижу смысла всегда можно возразить, что они специально подобраны. Однако, вы легко можете проверить написанное выше, прокоррелировав между собой пару десятков случайно выбранных рядов долговременных наблюдений практически любых природных и/или социальных процессов, доступных в Сети. Чем более длинные серии вы загрузите, тем ощутимее окажется разница между корреляциями, ожидаемыми для случайных независимых переменных, и фактически полученными значениями Rxy. Самое сложное, что вам придется для этого сделать это импортировать данные в выбранную программу статистического анализа. После этого расчет коэффициента корреляции обычно выполняется нажатием одной кнопки.

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

Так вот, НЕДЕЛАЙТЕЭТОГО!

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

Часть 3. А вот и ответы

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

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

Повторю еще раз другими словами. Для описания случайного процесса, в отличие от случайной величины, недостаточно задать его функцию распределения один раз. Просто потому, что в разные моменты времени t она может быть разной. А еще для случайного процесса надо определить функцию совместного распределения вероятностей для моментов времени t и t+dt и так далее. Чтобы оценить эти функции, наблюдая за случайным процессом, нужна не одна реализация, а целый ансамбль. Ну, хотя бы десяток реализаций. Причем, это обязательно должны быть реализации одного и того же случайного процесса. Тогда и только тогда для каждого момента времени у нас будет несколько измерений одной и той же случайной величины. Как их обрабатывать дальше, мы уже знаем из школьного вузовского курса статистики.

Но что же делать, если у нас есть только одна Земля, одна Камчатка, один Памир? Как изучать взаимосвязи между процессами, каждый из которых мы наблюдаем в единственном экземпляре?! (тут должна быть театральная пауза ;-)

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

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

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

Ну а теперь пришло время поставить финальную точку.

На самом деле, все упомянутые в этой статье временные ряды (как, впрочем, и подавляющее большинство других подобных сигналов) вовсе не являются эргодическими. И если доказать эргодичность процесса достаточно сложно (я бы сказал, практически нереально), то вот опровергнуть ее часто можно без особых усилий. Достаточно просто вспомнить, что практически все экспериментальные временные ряды существенно нестационарны. Огромный массив накопленных экспериментальных данных однозначно свидетельствует, что априорная "базовая модель" почти любого природного процесса это вовсе не белый шум (для которого действительно можно заигрывать с эргодичностью). Нет, спектры большинства реальных сигналов имеют степенной вид. А именно, спектральная мощность W пропорциональна периоду T в некоторой положительной степени: WT . В электронике, геофизике и во многих других прикладных областях показатель степени чаще всего лежит где-то между 0.5 и 2.0. В предельном случае (когда показатель степени =2), мы имеем процесс с независимыми случайными приращениями. Для такого процесса каждое следующее значение (в момент времени t+1) состоит из значения в момент времени t и случайной добавки. Про такой процесс говорят, что он имеет бесконечную память. Но если текущие значения ряда зависят от предыдущих, то такой процесс нельзя считать стационарным.

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

Да, конечно, мы можем формально подставить измеренные значения в формулы, и даже посчитать что-то внешне напоминающее Rxy. Однако каким будет теоретическое распределение этой статистики, никому не известно. (Кстати говоря: если Вы можете аккуратно посчитать это распределение для рядов со степенным спектром, то, пожалуйста, напишите это в комментариях или мне в личку. Я сам, к сожалению, не умею - но думаю, что такой результат был бы достаточно интересен для геофизиков, занимающихся обработкой экспериментальных рядов). Ясно только, что классические доверительные границы в этом случае считать бесполезно. Они просто не имеют никакого отношения к делу, так как мы имеем все основания отвергнуть "нулевую модель" вне зависимости от того, получится ли у нас |Rxy| >> z, или же будет Rxy = 0. Ведь занявшись анализом временных рядов, мы уже вышли за пределы этой модели, сформулированной для работы со случайными величинами. А это значит, что полная (правильная) формулировка модели теперь должна включать не два, а три постулата. А именно,

Если выполнены три условия

У1) X и Y статистически независимыУ2) что-нибудь про функции распределения X и Y У3) X и Y это случайные величины (то есть анализируемая выборка составлена из пар (Xi, Yi), извлеченных из одной и той же генеральной совокупности)

ТО значение Rxy почти никогда, по модулю, не будет превышать z.

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

Часть 4. И еще раз про доказательство от противного

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

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

  2. На втором шаге мы берем экспериментальные данные и вычисляем некоторую совместную статистику X и Y. В данном случае это статистика Rxy.

  3. Третий шаг состоит в вычислении функции распределения Rxy при условии, что все сформулированные ранее допущения например, условия У1, У2 и У3 истинны.

  4. На четвертом шаге мы сравниваем фактически полученное значение Rxy с теоретическим распределением этой величины. Если оказывается, что вероятность случайно наблюдать именно такое значение Rxy пренебрежимо мала, то отсюда делается вывод, что не все исходные допущения истинны. Проще говоря, что хотя бы одно из условий У1, У2 и У3 ложно.

  5. Наконец, если у нас нет абсолютно никаких сомнений в истинности всех прочих предположений, кроме допущения о статистической независимости X и Y, то мы делаем вывод об ошибочности именно этого допущения. То есть, если мы уверены в истинности У2 и У3, то ложным должно быть условие У1. Что, собственно и означает: связь есть!

Теперь понятно, почему эта схема "сбоит" при работе с нестационарными временными рядами. Сравнивая вычисленный коэффициент корреляции с теоретическими уровнями значимости z, мы не учитываем, что теоретическое распределение z рассчитано для одной модели, а коэффициент корреляции Rxy вычислен совсем для другой. Если упустить из вида этот нюанс, можно получать "значимые корреляции" через раз. Что порою и наблюдается даже в статьях, напечатанных в рецензируемых научных журналах.

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

численный эксперимент

со случайными, независимыми, но не эргодическими рядами. Возьмите полсотни реализаций белого шума S(t) длиной по миллиону значений (t=0,1,... 1000000). Проинтегрируйте каждую такую реализацию по правилу:P(0)=0, P(t+1)=P(t)+S(t).И потом посчитайте значение парного коэффициента корреляции для случайно взятых рядов P(t). Или для их фрагментов (только не слишком коротких). Просто по построению, все эти ряды и фрагменты абсолютно независимы друг от друга. Можно даже не вычислять, чему равен уровень значимости z для коэффициента корреляции Rxy при подобном объеме выборки. Так как первый же тест покажет разницу на порядки. Надеюсь, мне не нужно дополнительно пояснять, что полученный результат совершенно не связан с возможной неидеальностью генератора случайных чисел?Похожий, но чуть менее впечатляющий результат можно получить и для рядов с другими значениями степенного параметра спектра в пределах указанного диапазона. Все дело в том, что такие ряды, в простонародном названии фликкер-шум, страдают так называемой низкочастотной расходимостью спектра. Это значит, что на любом интервале времени максимальную амплитуду имеют те вариации, чья характерная длительность сопоставима с длиной ряда. Если мы рассматриваем ограниченную во времени серию наблюдений, это очень похоже на линейный тренд. А корреляция между двумя линейными трендами, как известно, всегда равна 1. Точнее, оценивать корреляцию для линейно спадающих или растущих функций бессмысленно. Чтобы построить линейную функцию, нужно ровно две точки, которые и определяют число степеней свободы процесса. Можно до бесконечности увеличивать частоту дискретизации такого сигнала, количество информации (= число независимых значений данных) от этого не изменится. Поэтому в формулу для оценки уровня значимости z коэффициента корреляции Rxy в этом случае надо подставлять значение N=2. Ну и чему равно z в таком случае?

Как говорится, вот то-то же и оно.

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

В сухом остатке

  1. Временной ряд это не набор значений некоторой случайной величины! Когда мы формируем выборку из некоторой генеральной совокупности, все измерения можно переставлять как угодно - от этого ничего не изменится. А вот значения временного ряда переставлять нельзя - они всегда индексированы. Принципиально важно, что любые статистики случайного процесса (=временного ряда) явно зависят (или могут зависеть) от времени. Собственно, при анализе наблюдений именно эта зависимость нас чаще всего и интересует.

  2. Для оценки статистик случайного процесса одной реализации, вообще говоря, недостаточно! Это почти то же самое, как оценивать дисперсию случайной величины по единственному ее измерению. Чтобы использовать методы матстатистики, нужен целый пакет рядов (ансамбль реализаций случайного процесса). Если речь идет о парной статистике (корреляция между X и Y), нужен ансамбль из пар временных рядов. Располагая таким набором, мы можем оценивать коэффициент Rxy корреляции между случайными процессами X и Y, как функцию времени t. А вот говорить о корреляции случайных процессов безотносительно момента времени t, в общем случае, беспредметно. Так как значения Rxy в разные моменты времени t, вообще говоря, будут разные.

  3. Увы, но на практике у нас обычно имеется только одна Вселенная и только одна реализация каждого временного ряда, . Чтобы использовать статистические методы при работе с такими данными, приходится опираться на гипотезу эргодичности. Она предполагает, что вместо вычисления какой-то статистики (например, среднего) по ансамблю реализаций, мы можем взять один ряд, усреднить по времени, и получить то же самое. Если ряд эргодический, этот подход реально работает!

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

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

P.S. Автор благодарит за полезные замечания и советы И.Цуркиса. При оформлении коллажа использована картинка с вот этого сайта.


Дополнительное замечание про распределения: нормально ли, что анализируя данные геофизического мониторинга, мы никогда не встречаемся с нормальным распределением?

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

идеальных моделей

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

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

Подробнее..

Инструменты для алготрейдинга на Python. SMA Полосы Боллинджера на акциях Северстали код готовой стратегии

31.05.2021 18:12:13 | Автор: admin

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

Предыдущая статья о "Расчете дневного изменения цены"

Когда я писал прошлую статью (она была первой из цикла) я не предполагал, что читатели разделятся на 2 категории:
1. Те, кто верят, что в алготрейдинг
2. Те, кто верят, что я шарлатан

Для обоих групп я напоминаю, что цель алготрейдинга - это увеличить вероятность получить прибыль от сделки
Или же, как говорят в "теории игр" - сделать математическое ожидание от игры положительным

Поэтому, предлагаю аудитории договориться о следующем:
1. Если ваш комментарий несет научный смысл, то пишите его под постом в Хабре.
2. Если ваш комментарий несет дискуссионный посыл, то прошу задавать его в специально созданном канале в телеге:

Собственно, здесь я перехожу к сути данной статьи.

SMA (Simple Moving Average, Скользящее среднее) - индикатор, основанный на подсчете среднего значения цены закрытия ценной бумаги.

Для тех, кто не знает что такое SMA, приведу алгоритм его подсчета:
1. Взять цену закрытия "close" ценной бумаги за период от t1 до t2 и отсортировать ее от t1 к t2.
2. Взять таймфрейм из первых N значений цены close.
3. Посчитать среднее арифметическое значение таймфрейма (simple average).
4. Сдвинуть таймфрейм вперед на одно значение (происходит moving) и выполнить пункт 3
5. Пункт 4 проводить до тех пор, пока таймфрейм не дойдет до точки t2

Отрисуем график SMA (N=20) для цены close акций Северсталь (тикер CHMF) за 27 мая 2021г.:

По графику видно, что SMA является сглаженной версией цены Close с временным лагом в 20 периодов.

Полосы Боллинджера (Bollinger Bands)

В 1980х годах Джон Боллинджер предложил рассчитывать не только SMA, но и STD (standart deviation, среднеквадратическое отклонение). Таким образом, мы будем видеть не только график изменения средней цены, но и ее волатильность.

Обычно, значения std устанавливают равным 2. В таком случае, с вероятностью в 95% следующее значение цены close будет лежать внутри полосы Боллинджера и только в 5% случаях оно будет выходить из этой полосы.

В тех местах, где цена close близка к нижней грани полосы Боллинджера, стоимость акций считается низкой. И, наоборот, если цена close близка к верхней грани полосы Боллинджера, стоимость акций считается высокой.

И тут у трейдера срабатывает чуйка: покупаем на низах, продаем на хаях (никак не наоборот).

Весь код с использованием полос Боллинджера привел на Google Colab. Данная стратегия принесла +1,7% за 1 день (но это не точно).

В следующей статье поговорим об RSI

Подробнее..

Временные ряды. Простые решения

22.04.2021 00:12:56 | Автор: admin


Привет, Хабр!

В этой статье мы рассмотрим несколько простых подходов прогнозирования временных рядов.

Материал, изложенный в статье, на мой взгляд, хорошо дополняет первую неделю курса Прикладные задачи анализа данных от МФТИ и Яндекс. На обозначенном курсе можно получить теоретические знания, достаточные для решения задач прогнозирования рядов динамики, а в качестве практического закрепления материала предлагается с помощью модели ARIMA библиотеки scipy сформировать прогноз заработной платы в Российской Федерации на год вперед. В статье, мы также будем формировать прогноз заработной платы, но при этом будем использовать не библиотеку scipy, а библиотеку sklearn. Фишка в том, что в scipy уже предусмотрена модель ARIMA, а sklearn не располагает готовой моделью, поэтому нам придется потрудиться ручками. Таким образом, нам для решения задачи, в каком то смысле, необходимо будет разобраться как устроена модель изнутри. Также, в качестве дополнительного материала, в статье, задача прогнозирования решается с помощью однослойной нейронной сети библиотеки pytorch.

Весь код написан на python 3 в jupyter notebook. Более того, notebook устроен таким образом, что вместо данных о заработной плате можно подставить многие другие ряды динамики, например данные о ценах на сахар, поменять период прогнозирования, валидации и обучения, добавить иные внешние факторы и сформировать соответствующий прогноз. Другими словами, в работе используется простенький самописный тренажерчик, с помощью которого можно прогнозировать различные ряды динамики. Код можно посмотреть здесь

План статьи


  1. Краткое описание тренажера.
  2. Решение в лоб прогнозирование временных рядов с использованием только сырых данных прошлых значений рядов динамики.
  3. Добавление экзогенных переменных.
  4. Коррекция гетероскедастичности через логарифмирование исходных данных.
  5. Приведение ряда к стационарному.
  6. Прогнозирование с помощью однослойной нейронной сети.
  7. Сравнение подходов.
  8. Полезные ссылки

Краткое описание тренажера



Import the data
Здесь все просто импортируем данные. Иногда бывает так, что сырых данных достаточно для формирования более-менее внятного прогноза. Именно первый и второй прогнозы в статье моделируются на основании сырых данных, то есть для прогноза заработной платы используются необработанные данные о заработной плате в прошлые периоды.

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

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

Split to train, test & forecast
В этом блоке кода временные ряды разбиваются на периоды обучения, тестирования и прогнозирования путем добавления нового столбца с соответствующими значениями train, test, forecast. То есть, мы не создаем три отдельных таблицы для каждого периода, а просто добавляем столбец, на основании, которого в дальнейшем будем разделять данные.

Extraction exogenous time-series features
Бывает полезным выделить дополнительные внешние (экзогенные) признаки из временного ряда. Например, указать выходной это день или нет, указать количество дней в месяце (или количество рабочих дней в месяце) и др. Как правило эти признаки вытаскиваются из самого временного ряда без какого либо ручного вмешательства.

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

Exogenous values
В этом блоке кода мы объединяем все экзогенные данные в одну таблицу.

Union the data (create dataset)
В этом блоке кода мы объединяем значения временного ряда и экзогенных признаков в одну таблицу. Другими словами готовим датасет, на основании, которого будем обучать модель, тестировать качество и формировать прогноз.

Learning the model
Здесь все понятно мы просто обучаем модель.

Preprocessing data: predict & forecast
В случае, если мы для обучения модели использовали предобаботанные данные (логарифмированные, обработанные функцией бокса-кокса, стационарный ряд и др.), то качество модели для начала оценивается на предобработанных данных и только потом уже на сырых данных. Если, мы данные не предобрабатывали, то данный этап пропускается.

Row data: predict & forecast
Данный этап является заключительным. Если, модель обучалась на предобработанных данных, например, мы их прологарифмировали, то для получения прогноза заработной платы в рублях, а не логарифма рублей, нам следует перевести прогноз обратно в рубли.

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

Решение в лоб


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

Давайте возьмем значения заработной платы в 12-ть прошлых месяцев для предсказания заработной платы в 13-й месяц. Другими словами для каждого целевого значения у нас будет 12 признаков.

Признаки будем подавать на вход Ridge Regression библиотеки sklearn. Параметры модели берем по умолчанию за исключением параметра alpha, его установили на 0, то есть по сути мы используем обычную регрессию.

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

Давайте посмотрим, что у нас получилось.





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

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



Попробуем добавить в модель экзогенные переменные.

Добавление экзогенных переменных


Мы будем использовать 2 внешних признака: количество дней в месяце и номер месяца (от 1 до 12). Признак Номер месяца мы бинаризируем, в итоге у нас получится 12 столбцов для каждого месяца со значениями 0 или 1.

Сформируем новый датасет и посмотрим на качество модели.

Смотрим графики





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

Давайте теперь проведем первую предобработку данных.

Коррекция гетероскедастичности.


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



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

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



Обучим модель на прологарифмированном ряду

Смотрим графики




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

Приведение ряда к стационарному


Приводить ряд к стационарному будем следующим образом:

  • Определяем разницу между целевым значением заработной платы и значением год назад: t (t-12) = dif_1
  • Определяем разницу между полученным и смещенным на 1 месяц значением: dif_1 (dif_1-1) = dif_2

В итоге получаем следующий временной ряд.



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

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

Давайте посмотрим, что получилось.



Вот так выглядит предсказание стационарного ряда. Как и ожидали не очень-то и хорошо :)

А вот предсказание и прогноз заработной платы.

Смотрим графики




Качество заметно улучшилось и прогноз визуально стал выглядеть правдоподобным.

Теперь сформируем прогноз без использования экзогенных переменных

Смотрим графики




Качество еще улучшилось и правдоподобность прогноза сохранилась :)

Прогнозирование с помощью однослойной нейронной сети


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

Для начала посмотрим на саму сеть

Смотрим код
class Model_1(nn.Module):    def __init__(self, input_size, output_size):        super(Model_1, self).__init__()        self.input_size = input_size        self.output_size = output_size        self.linear = nn.Linear(self.input_size, self.output_size)            def forward(self, x):        output = self.linear(x)        return output



Теперь пару слов о том, как будем ее обучать.

  1. Фиксируем random seed для целей воспроизводимости результата
  2. Инициализируем модель
  3. Задаем функцию потерь MSELoss
  4. Выбираем в качестве оптимизатора Adam optimizer
  5. Указываем начальный шаг обучения и определяем условие, при котором шаг понижается. Отмечу, что правильный выбор шага и его дальнейшее изменение (обычно уменьшение) приносит хорошие плоды
  6. Указываем количество эпох обучения
  7. Запускаем обучение
  8. На вход сети подаем целиком датасет, так как он очень маленький и разбивать его на батчи не имеет смысла
  9. При обучении, каждую тысячу эпох формируем графики значения функции потерь на обучающей и тестовой выборках. Это позволяет нам контролировать переобучение или не дообучение модели.

Ниже приведен код обучения сети на первом датасете. Для каждого датасета незначительно менялись параметры: количество эпох обучения и шаг обучения.

Смотрим код
# fix the random seedSEED = 42random_init(SEED)# initialization model, loss function, optimizatormodel = Model_1(len(features),1)loss_func = nn.MSELoss()opt = torch.optim.Adam(model.parameters(), lr=5e-2)# set the epoch numbers, initialization list for every loss after learning on epochepochs = 15000losses_train = []losses_test = []# initialization counter for calculation epoch numberscounter = 0# start the learning modelfor epoch in range(epochs):    model.train()#     make prediction targets on train data    y_pred_train = model(torch.tensor(X_train.to_numpy(), dtype=torch.float))#     calculate loss    loss = loss_func(y_pred_train,                      torch.reshape(torch.tensor(y_train.to_numpy(), dtype=torch.float),(-1,1)))#     bacward loss to model and calculate new parameters (coefficients) with fixed learning rate    loss.backward()    opt.step()    opt.zero_grad()# add loss to list losses     losses_train.append(loss)        model.eval()    y_pred_test = model(torch.tensor(X_test.to_numpy(), dtype=torch.float))    loss_test = loss_func(y_pred_test,                      torch.reshape(torch.tensor(y_test.to_numpy(), dtype=torch.float),(-1,1)))    losses_test.append(loss_test)        # make the mini report for every 1000 epoch        if epoch % 1000 == 0 and epoch > 0:        print ('Epoch:', epoch // 1000)        print ('Learning rate:', opt.param_groups[0]['lr'])        print ('Last loss on TRAIN data:', losses_train[-1].cpu().detach().numpy(),              '              Last loss on TEST data:', losses_test[-1].cpu().detach().numpy())#         print ('Last loss on TEST data:', losses_test[-1].cpu().detach().numpy())        fig, (ax1, ax2) = plt.subplots(1, 2)#         fig.suptitle('MSE on TRAIN & TEST DATA')        fig.set_figheight(3)        fig.set_figwidth(12)        ax1.plot(np.arange(counter,epoch,1), np.array([float(i) for i in losses_train][-1000:]), color = 'darkred')        plt.xlabel("Epoch")        plt.ylabel("Loss on TRAIN data")        ax2.plot(np.arange(counter,epoch,1), np.array([float(i) for i in losses_test][-1000:]), color = 'darkred')        plt.xlabel("Epoch")        plt.ylabel("Loss on TEST data")        plt.show()                counter += 1000        #   reduce learning rate    if epoch == 1000:        opt = torch.optim.Adam(model.parameters(), lr=7e-3)


Не будем рассматривать качество предсказаний для каждого датасета отдельно (желающие могут посмотреть подробности на гите). Давайте сравним итоговые результаты.

Качество на тестовой выборке с использованием Ridge Regression



Качество на тестовой выборке с использованием Single layer NN



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

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

Полезные ссылки


  1. Исходники на гитхабе
  2. Курс Прикладные задачи анализа данных от МФТИ и Яндекс
  3. Государственная статистика ЕМИСС (данные по заработной плате)
  4. LSTM for time series prediction
  5. Лекция 9. Прогнозирование на основе регрессионной модели. Computer Science Center
  6. Картинка под заголовком взята отсюда :)
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru