Рыночные данные превращаются в хаос быстрее, чем кажется. CSV-файлы, разрозненные выгрузки из терминалов, многолистовые Excel-книги — всё это работает до первой серьезной задачи. Когда нужно за пять минут найти аномалию в котировках за три года или построить выборку для бэктеста, ручная фильтрация становится врагом. SQL решает эту проблему на корню: нормализует структуру, ускоряет доступ к истории и позволяет строить отчеты без промежуточных склеек в Python.

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

Зачем SQL вообще нужен в работе с рынком

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

В моей практике SQL становится незаменимым, когда требуется:

— хранить историю цен по сотням инструментов без риска перепутать столбцы;
— мгновенно находить нужные свечи, сделки и выбросы по комбинации условий;
— объединять рыночные данные с фундаментальными показателями или внутридневными метриками;
— считать доходность, волатильность, просадки и другие показатели прямо в запросе, не выгружая гигабайты в pandas;
— готовить чистые выборки для Python, BI-систем и ML-моделей.

Для трейдера и кванта SQL — это не про «базы данных ради баз данных». Это про устойчивый, повторяемый способ собирать и проверять гипотезы на исторических данных. Один раз написанный запрос может работать годами, а не до первой опечатки в формуле Excel.

Какие рыночные данные стоит хранить в базе

Заливать в базу вообще всё подряд — прямой путь к дорогой и неповоротливой системе. Лучше сразу определить минимально жизнеспособный набор данных, который закроет 80% задач.

Минимальный состав данных

— **Справочник инструментов**: тикер, биржа, тип актива, валюта, лотность. Это ваш фундамент, без него любая аналитика начинается с угадывания, что за бумага скрывается за `instrument_id`.
— **Исторические свечи**: open, high, low, close, volume, timestamp. Дневные данные — база, внутридневные — опционально, но их лучше сразу проектировать отдельно.
— **Сделки**: цена исполнения, объем, направление, комиссия, время. Агрегировать торговую статистику без этой таблицы невозможно.
— **Портфель**: позиции, средняя цена, количество, дата обновления. Нужен для расчета PnL и контроля рисков.
— **События**: дивиденды, сплиты, листинги, делистинги, ребалансировки. Поправка цен без учета этих событий — гарантированная ошибка в расчетах.

Что лучше хранить отдельно

На практике я всегда разделяю:

— сырые котировки и очищенные данные;
— дневные и внутридневные таймфреймы;
— рыночные данные и результаты расчетов (доходности, скользящие средние);
— справочники и фактические сделки.

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

Базовая структура базы данных для рыночных данных

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

Пример логичной модели

Таблица Назначение Ключевые поля
instruments Справочник инструментов instrument_id, ticker, exchange, asset_class
prices_daily Дневные свечи instrument_id, date, open, high, low, close, volume
prices_intraday Внутридневные данные instrument_id, ts, open, high, low, close, volume
trades Сделки по счету или стратегии trade_id, instrument_id, ts, side, price, qty
positions Позиции и остатки instrument_id, dt, qty, avg_price
corporate_actions Корпоративные события instrument_id, event_date, event_type, value

Главный принцип

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

Как хранить рыночные данные правильно

1. Используйте типы данных без лишней «магии»

Хранение цен и объемов как текст — типичная ошибка новичков. Она ломает фильтры, сортировку и любые расчеты. В финансах важна точность до копейки, поэтому я рекомендую:

— цены — NUMERIC или DECIMAL (float может дать ошибку округления);
— даты и время — DATE, TIMESTAMP;
— объемы — BIGINT или NUMERIC;
— флаги и категории — BOOLEAN, TEXT, ENUM в зависимости от СУБД.

Отдельный нюанс: если вы работаете с криптовалютами, где объемы могут быть дробными и огромными, NUMERIC с запасом по точности — единственный надежный путь.

2. Храните время в одном стандарте

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

— хранить исходный timestamp в UTC;
— конвертировать в Europe/Moscow или другой часовой пояс только на этапе отчета;
— явно фиксировать таймзону в документации проекта.

Это страхует от ошибок при переходе на летнее/зимнее время и упрощает стыковку с данными с американских бирж.

3. Индексы ставьте там, где часто фильтруете

Для рыночных данных почти всегда нужны индексы по:

instrument_id;
date или ts;
— паре instrument_id + date/ts.

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

4. Разделяйте сырье и витрины

Сырые данные лучше хранить отдельно от аналитических витрин.

Raw layer — как пришло от поставщика.
Clean layer — очищенные и нормализованные данные.
Mart layer — таблицы для отчетов, стратегий и моделей.

Так вы сможете объяснить, откуда взялось каждое значение, и быстро исправлять ошибки источника. В одном из проектов мы именно так обнаружили, что провайдер начал поставлять цены с запаздыванием на 15 минут — это было видно только при сравнении raw и clean слоев.

Пример простой схемы таблицы свечей

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

CREATE TABLE prices_daily (
    instrument_id INT NOT NULL,
    date DATE NOT NULL,
    open NUMERIC(12,4),
    high NUMERIC(12,4),
    low NUMERIC(12,4),
    close NUMERIC(12,4),
    volume BIGINT,
    PRIMARY KEY (instrument_id, date)
);

Почему так удобно:

— одна строка — одна свеча по одному инструменту;
— составной первичный ключ защищает от дублей;
— легко делать выборки по инструменту и периоду;
NUMERIC(12,4) покрывает большинство случаев без потери точности и без лишней нагрузки.

Какие запросы трейдеры используют чаще всего

Получить историю по одному инструменту

SELECT date, open, high, low, close, volume
FROM prices_daily
WHERE instrument_id = 15
ORDER BY date;

Это базовый запрос для анализа графика, расчета индикаторов и подготовки данных для Python. Именно с него начинается 90% исследовательских задач.

Найти дневную доходность

SELECT date, close,
       (close - LAG(close) OVER (ORDER BY date)) / LAG(close) OVER (ORDER BY date) AS daily_return
FROM prices_daily
WHERE instrument_id = 15
ORDER BY date;

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

Посчитать среднюю цену и объем за период

SELECT AVG(close) AS avg_close, SUM(volume) AS total_volume
FROM prices_daily
WHERE instrument_id = 15
  AND date BETWEEN '2023-01-01' AND '2023-12-31';

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

Сложную просадку обычно считают в Python, но первичную диагностику можно сделать и в SQL через оконные функции.

WITH drawdowns AS (
    SELECT date, close,
           MAX(close) OVER (ORDER BY date) AS running_max,
           (close - MAX(close) OVER (ORDER BY date)) / MAX(close) OVER (ORDER BY date) AS drawdown
    FROM prices_daily
    WHERE instrument_id = 15
)
SELECT MIN(drawdown) AS max_drawdown
FROM drawdowns;

Такой запрос дает мгновенную оценку риска, не выгружая данные во внешнюю среду.

Какие функции SQL особенно полезны в финансовой аналитике

Функция Зачем нужна Пример применения
WHERE Фильтрация данных выбрать нужный период
GROUP BY Группировка средний объем по инструментам
JOIN Объединение таблиц свечи + справочник инструментов
LAG / LEAD Сравнение с прошлым/следующим значением доходность, сигналы
SUM() OVER Накопительные расчеты equity curve, накопленный PnL
ROW_NUMBER() Нумерация строк поиск первой сделки, дедупликация
CASE Условная логика классификация событий и сигналов

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

Типовой рабочий сценарий: от данных до решения

Шаг 1. Загрузить данные

Источником может быть API брокера, биржевой файл, SQL-дамп или выгрузка из data vendor. Важно сразу зафиксировать источник и версию данных, чтобы потом не гадать, почему изменились результаты бэктеста.

Шаг 2. Проверить качество

Сразу проверьте:

— есть ли пропуски по датам;
— нет ли дублей;
— корректны ли таймзоны;
— не перепутаны ли цены и объемы;
— совпадает ли количество строк с ожидаемым диапазоном.

Шаг 3. Очистить и нормализовать

На этом этапе обычно:

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

Шаг 4. Построить витрину

Например, витрина для стратегии может содержать:

— дату;
— цену закрытия;
— доходность за день;
— скользящую среднюю;
— волатильность;
— сигнал входа;
— фактический результат сделки.

Шаг 5. Использовать в Python или BI

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

— pandas;
— Jupyter;
— Power BI;
— Metabase;
— собственные backtesting-скрипты.

Практический пример: объединяем свечи и справочник инструментов

SELECT p.date, i.ticker, i.exchange, p.close, p.volume
FROM prices_daily p
JOIN instruments i ON p.instrument_id = i.instrument_id
WHERE p.date BETWEEN '2023-01-01' AND '2023-12-31'
ORDER BY i.ticker, p.date;

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

Частые ошибки при работе с рыночными данными в SQL

1. Хранение чисел в текстовом формате

Потом ломаются сортировка, сравнения и вычисления. Цена «10.5» в тексте не равна числу 10.5.

2. Отсутствие уникальности по инструменту и дате

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

3. Смешение сырья и расчетных данных

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

4. Игнорирование таймзоны

Особенно болезненно для внутридневных данных и американских рынков. Однажды мы потратили полдня, выясняя, почему сигналы на открытии NYSE не совпадают с ожидаемыми — оказалось, часть данных была в ET, часть в UTC.

5. Запросы без индексов

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

6. Слишком широкие выборки

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

Как оптимизировать SQL-подход под большие массивы данных

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

Что помогает на практике

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

Когда нужен отдельный data warehouse

Если у вас:

— несколько источников котировок;
— история за много лет;
— десятки инструментов и таймфреймов;
— расчет стратегий и отчетов одновременно;

тогда обычная база уже может быть недостаточна. В этом случае лучше строить слои данных и четко разделять загрузку, обработку и аналитику. На практике я часто использую связку PostgreSQL + Python для таких задач, где SQL закрывает хранение и первичную агрегацию, а сложные вычисления уходят в pandas.

Чек-лист: что проверить перед началом работы

— [ ] Есть ли единый идентификатор инструмента.
— [ ] Хранятся ли даты и время в одном стандарте.
— [ ] Разделены ли сырой и очищенный слои.
— [ ] Есть ли уникальный ключ по инструменту и времени.
— [ ] Настроены ли индексы на частых фильтрах.
— [ ] Понятно ли, как рассчитываются производные метрики.
— [ ] Есть ли проверка на дубли и пропуски.
— [ ] Зафиксированы ли источники данных.

Мини-гайд: с чего начать, если SQL в финансах новый

Для первого этапа достаточно

— освоить SELECT, WHERE, ORDER BY, GROUP BY;
— понять JOIN;
— научиться работать с датами;
— изучить оконные функции;
— потренироваться на свечах, сделках и портфеле.

Хороший учебный набор задач

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

Когда SQL особенно полезен трейдеру, а когда — нет

SQL отлично подходит для:

— подготовки и очистки исторических данных;
— построения отчетов по сделкам;
— агрегации больших массивов;
— объединения источников;
— контроля качества данных.

SQL не лучший инструмент, если нужно:

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

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

Вывод

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

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

FAQ

Чем SQL лучше Excel для рыночных данных?

SQL лучше справляется с большими объемами, поддерживает связи между таблицами, защищает от дублей и позволяет быстро строить повторяемые запросы. Excel хорош для быстрой визуализации, но не для хранения истории и автоматизации.

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

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

Нужно ли хранить внутридневные данные отдельно?

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

Какой ключ лучше использовать для свечей?

Обычно это пара instrument_id + date для дневных данных или instrument_id + timestamp для intraday. Это гарантирует уникальность и позволяет быстро искать по диапазону.

Можно ли считать доходность прямо в SQL?

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