Disclaimer


Любые материалы и информация носят исключительно справочный характер; ПАО Московская Биржа не берет на себя каких-либо обязательств, не гарантирует и не предоставляет каких-либо заверений (будь то явных или подразумеваемых) касательно их точности, полноты, качества, коммерческой ценности, безошибочности, соответствия любым методологиям и описаниям или пригодности использования для каких-либо целей, а также касательно их объема, состава и формы представления данных, сроков и своевременности их предоставления. Материалы и информация (целиком или в какой-либо части) не могут быть использованы в каких-либо инвестиционных или коммерческих целях, в том числе для создания каких-либо финансовых инструментов, продуктов или индексов.

Продукт: Агрегированный нетто-объем 2

Агрегированная аналитика по нетто-объему

Содержание:

  • Краткое описание продукта
  • Методология
  • Описание полей
  • Обзор данных
  • Создание производных показателей
  • Статистика сонаправленности нетто-объема и движения цены
  • Итоги
  • Контакты

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

Продукт включает в себя агрегированные дневные данные по величине нетто-объема (покупка - продажа) в бумагах и в деньгах по топ 30, 70 и 100 клиентам. С 10:00 до 18:30 учитываются все сделки всех клиентов в анонимном "стаканном" режиме. В 18:30 выявляются самые крупные клиенты по абсолютной величине нетто-объема за сегодняшний торговый день и их значения суммируются. Таким образом на конец дня получается три значения p30, p70 и p100

На данный момент нетто-объем рассчитывается по десяти инструментам: SBER, GAZP, LKOH, GMKN, VTBR, ROSN, MGNT, ALRS, SBERP, AFLT

Методология


Описание полей

Основные обозначения, используемые далее:

  1. p30, p70, p100 - Совокупный нетто-объем топ 30, 70 и 100 клиентов, в акциях
  2. today_change - изменение цены за сегодня (цена закрытия сегодня к цене открытия сегодня), в процентах
  3. tomorrow_change - изменение цены за следующий день (цена закрятия завтра к цене закрытия сегодня), в процентах

Обзор данных

In [1]:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
%pylab inline
Populating the interactive namespace from numpy and matplotlib
In [2]:
plt.rcParams['figure.figsize'] = (15, 8) 
plt.style.use('ggplot')

Рассмотрим данные на примере акции Сбербанка (SBER). Загрузим исторические значения нетто-объемов с 2014 по 2017 год включительно (4 года)

In [3]:
ticker = 'SBER'
In [4]:
X = pd.read_csv('../netflows_2014_2017/' + ticker + '_netflow.csv')
X['date'] = pd.to_datetime(X['date'])
X = X.set_index('date')
X = X[['p30', 'p70', 'p100', 'oi']][3:]
X.head()
Out[4]:
p30 p70 p100 oi
date
2014-01-10 3333680 1310760 1126430 31124820
2014-01-13 4419280 3384600 2836260 39212280
2014-01-14 -3718740 -3441470 -2529540 37688760
2014-01-15 8258230 6357870 5086300 46134300
2014-01-16 2398870 2589590 2222780 41385660
In [5]:
# Базовая статистика по ключевым показателям
X.describe()
Out[5]:
p30 p70 p100 oi
count 1.000000e+03 1.000000e+03 1.000000e+03 1.000000e+03
mean 1.979914e+05 1.056551e+05 8.627090e+04 6.095437e+07
std 7.998605e+06 6.286671e+06 5.414825e+06 3.578948e+07
min -3.759355e+07 -3.086781e+07 -2.594362e+07 8.448360e+06
25% -3.560330e+06 -2.634188e+06 -2.219885e+06 3.513840e+07
50% -6.378500e+04 -9.621000e+04 -1.111850e+05 5.310237e+07
75% 3.366888e+06 2.579210e+06 2.074978e+06 7.751528e+07
max 3.208886e+07 2.828582e+07 2.494898e+07 2.902350e+08
In [6]:
# Time-Series график. Кумулятивная сумма p30, p70 и p100
X[['p30', 'p70', 'p100']].cumsum().rolling(5).mean().plot()
Out[6]:
<matplotlib.axes._subplots.AxesSubplot at 0x22c9bf77400>
In [7]:
# Распределения величин p30, p70 и p100. Среднее и медиана ~0
X[['p30', 'p70', 'p100']].plot.box()
Out[7]:
<matplotlib.axes._subplots.AxesSubplot at 0x22c9c2ab0b8>


Посчитаем корреляции между p30, p70, p100 и today_change, tomorrow_change. Скачаем историю цен и объемов:

In [8]:
# получение дневных свечей по любой указанной бумаге используя MOEX API 
def get_ohlcv(ticker, date_from, date_till):
    P = pd.DataFrame()
    # используем цикл, так как есть ограничение в выдаче значений за один запрос (max = 500)
    for i in range(5):
        url = 'http://iss.moex.com/iss/engines/stock/markets/shares/boards/tqbr/securities/' + ticker + '/candles.csv' \
        '?from=' + date_from + \
        '&till=' + date_till + \
        '&interval=24' \
        '&start=' + str(500*i)
        P = P.append(pd.read_csv(url, ';',skiprows=2))
    P['date'] = P.apply(lambda row: row['begin'][:10], axis =1)
    P['date'] = pd.to_datetime(P['date'], format = '%Y-%m-%d')
    P = P.set_index('date')
    
    #объем умножаем на 2, чтобы получить оборот
    P['volume'] = 2 * P['volume']
    P['value'] = 2 * P['value']
    
    P.drop(['begin', 'end'], axis = 1, inplace = True)
    return P
In [9]:
Y = get_ohlcv(ticker, '2014-01-01', '2018-01-30')
In [10]:
# change - процентное изменение цены за сегодня (close to open)
Y['today_change'] = 100 * (Y['close'] - Y['open']) / Y['open']

# tom_change - процентное изменение цены, которое будет завтра (close(+1) to сlose(0))
Y['tomorrow_change'] = 100 * Y['close'].pct_change().shift(-1)

Y.head()
Out[10]:
open close high low value volume today_change tomorrow_change
date
2014-01-06 100.20 98.91 100.31 98.62 6.308941e+09 63383600 -1.287425 -0.727934
2014-01-08 99.10 98.19 99.41 97.85 8.359877e+09 84744580 -0.918264 -0.193502
2014-01-09 98.44 98.00 98.77 97.69 9.036778e+09 91973800 -0.446973 1.224490
2014-01-10 97.87 99.20 99.41 97.52 1.021936e+10 103804800 1.358946 1.058468
2014-01-13 99.30 100.25 100.35 99.04 1.238301e+10 124102500 0.956697 -1.007481
In [11]:
# К таблице с нетто-объемом добавляем изменения цен и объемы
X = pd.merge(X, Y[['today_change', 'tomorrow_change', 'volume', 'value']], how='left', left_index = True, right_index = True)
X.head()
Out[11]:
p30 p70 p100 oi today_change tomorrow_change volume value
date
2014-01-10 3333680 1310760 1126430 31124820 1.358946 1.058468 103804800 1.021936e+10
2014-01-13 4419280 3384600 2836260 39212280 0.956697 -1.007481 124102500 1.238301e+10
2014-01-14 -3718740 -3441470 -2529540 37688760 0.060496 1.541717 156970320 1.556252e+10
2014-01-15 8258230 6357870 5086300 46134300 0.820410 -0.119083 155678740 1.561511e+10
2014-01-16 2398870 2589590 2222780 41385660 -0.247770 0.516642 125518560 1.268416e+10
In [16]:
# Кумулятивная сумма p30, p70, p100 (цветные линии) и движение цены - today_change (черная линия)
X[['p30', 'p70', 'p100']].cumsum().rolling(5).mean().plot()
plt.ylabel('В штуках бумаг')
X['today_change'].cumsum().rolling(5).mean().plot(secondary_y = True, c='#000000')
plt.ylabel('%')
Out[16]:
Text(0,0.5,'%')


Корреляции значений p30, p70, p100 с движением цены today_change и tomorrow_change на промежутке за 4 года

In [18]:
X[['p30', 'p70', 'p100', 'today_change', 'tomorrow_change']].corr()
Out[18]:
p30 p70 p100 today_change tomorrow_change
p30 1.000000 0.983902 0.978002 0.739199 0.085658
p70 0.983902 1.000000 0.995490 0.746328 0.094855
p100 0.978002 0.995490 1.000000 0.753358 0.088971
today_change 0.739199 0.746328 0.753358 1.000000 0.070943
tomorrow_change 0.085658 0.094855 0.088971 0.070943 1.000000
  • Сильная корреляцию между p30, p70 и p100 (~0.98)
  • Cильная положительная корреляция между p30, p70, p100 и today_change (~0.75)
  • Cлабая положительная корреляция между p30, p70, p100 и tomorrow_change (~0.09)

Следовательно, можно предположить, что today_change обусловлена p30, p70 и p100. А зависимость между нетто-объемом и tomorrow_change слабая, но тем не менее наблюдается стабильная положительная корреляция. Далее оценим эту зависимость.

Пробуем добиться увеличения значения корреляции между p30, p70, p100 и tomorrow_change:

  1. Нормируем показатели p30, p70 и p100 на объем торгов всего рынка - VOLUME (OI, log(VOLUME) ...)
  2. Посчитаем изменения p30, p70 и p100 относительно предыдущих N дней

Создание производных показателей на основе нетто-объема

In [19]:
Z = X.copy()
Z['p30_vol'] = 100 * Z['p30'] / Z['volume']
Z['p70_vol'] = 100 * Z['p70'] / Z['volume']
Z['p100_vol'] = 100 * Z['p100'] / Z['volume']
Z = Z.astype(float)
In [20]:
Z.corr()
Out[20]:
p30 p70 p100 oi today_change tomorrow_change volume value p30_vol p70_vol p100_vol
p30 1.000000 0.983902 0.978002 0.116485 0.739199 0.085658 0.112916 0.136930 0.879253 0.879285 0.882764
p70 0.983902 1.000000 0.995490 0.093715 0.746328 0.094855 0.088105 0.110940 0.854245 0.884921 0.890116
p100 0.978002 0.995490 1.000000 0.094082 0.753358 0.088971 0.086250 0.111846 0.841797 0.873696 0.887359
oi 0.116485 0.093715 0.094082 1.000000 0.061604 0.088350 0.932113 0.784842 0.063978 0.058432 0.061230
today_change 0.739199 0.746328 0.753358 0.061604 1.000000 0.070943 0.064108 0.121885 0.685178 0.700282 0.712124
tomorrow_change 0.085658 0.094855 0.088971 0.088350 0.070943 1.000000 0.080983 0.082697 0.100420 0.104861 0.102002
volume 0.112916 0.088105 0.086250 0.932113 0.064108 0.080983 1.000000 0.813131 0.058125 0.050007 0.050755
value 0.136930 0.110940 0.111846 0.784842 0.121885 0.082697 0.813131 1.000000 0.083652 0.075511 0.079297
p30_vol 0.879253 0.854245 0.841797 0.063978 0.685178 0.100420 0.058125 0.083652 1.000000 0.979742 0.972260
p70_vol 0.879285 0.884921 0.873696 0.058432 0.700282 0.104861 0.050007 0.075511 0.979742 1.000000 0.994428
p100_vol 0.882764 0.890116 0.887359 0.061230 0.712124 0.102002 0.050755 0.079297 0.972260 0.994428 1.000000


При нормировании p30, p70, p100 на объем корреляция между p30_vol, p70_vol, p100_vol и tomorrow_change стала ~0.1

Посчитаем прирост (дельту) p30, p70 и p100 относительно среднего значения за 1-5 предыдущих дней:

In [21]:
cols = ['p30', 'p70', 'p100', 'p30_vol', 'p70_vol', 'p100_vol']

for col in cols:
    for i in [1,2,3,4,5]:
        Z[col + '_' + str(i)] = Z[col] - Z[col].shift(periods=1, freq=None, axis=0).rolling(i, min_periods = 1).mean()

print('Размерность новой таблицы ' + str(Z.shape))
Z.head()
Размерность новой таблицы (1000, 41)
Out[21]:
p30 p70 p100 oi today_change tomorrow_change volume value p30_vol p70_vol ... p70_vol_1 p70_vol_2 p70_vol_3 p70_vol_4 p70_vol_5 p100_vol_1 p100_vol_2 p100_vol_3 p100_vol_4 p100_vol_5
date
2014-01-10 3333680.0 1310760.0 1126430.0 31124820.0 1.358946 1.058468 103804800.0 1.021936e+10 3.211489 1.262716 ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
2014-01-13 4419280.0 3384600.0 2836260.0 39212280.0 0.956697 -1.007481 124102500.0 1.238301e+10 3.560992 2.727262 ... 1.464546 1.464546 1.464546 1.464546 1.464546 1.200275 1.200275 1.200275 1.200275 1.200275
2014-01-14 -3718740.0 -3441470.0 -2529540.0 37688760.0 0.060496 1.541717 156970320.0 1.556252e+10 -2.369072 -2.192434 ... -4.919695 -4.187423 -4.187423 -4.187423 -4.187423 -3.896894 -3.296757 -3.296757 -3.296757 -3.296757
2014-01-15 8258230.0 6357870.0 5086300.0 46134300.0 0.820410 -0.119083 155678740.0 1.561511e+10 5.304661 4.083968 ... 6.276402 3.816554 3.484787 3.484787 3.484787 4.878654 2.930207 2.680816 2.680816 2.680816
2014-01-16 2398870.0 2589590.0 2222780.0 41385660.0 -0.247770 0.516642 125518560.0 1.268416e+10 1.911168 2.063113 ... -2.020855 1.117346 0.523514 0.592735 0.592735 -1.496299 0.943027 0.457172 0.514313 0.514313

5 rows × 41 columns

In [23]:
# топ 10 показателей с максимальной корреляцией
cols = Z.corr()['tomorrow_change'].sort_values(ascending = False)[1:11]

#показатели, полученные на основе прироста показывают большую корреляцию
cols
Out[23]:
p70_3         0.109256
p70_vol_3     0.108843
p70_vol_4     0.107206
p100_vol_3    0.106920
p70_vol       0.104861
p100_vol_4    0.104418
p100_3        0.103789
p70_1         0.102869
p70_vol_5     0.102313
p100_vol      0.102002
Name: tomorrow_change, dtype: float64

Приросты значений p30, p70 и p100 относительно предыдущих дней показали корреляцию ~0.11

Аноличные значения по другим инструментам:

TICKERPOS vs TOD_CHGPOS vs TOM_CHGPOS/VOL vs TOM_CHGΔPOS/VOL vs TOM_CHG
SBER0.750.090.100.11
GAZP0.730.090.080.11
LKOH0.650.080.080.13
GMKN0.600.040.070.05
MGNT0.600.040.070.06
ROSN0.670.100.110.12
VTBR0.560.100.010.06
ALRS0.450.000.040.07
SBERP0.500.070.100.09
AFLT0.500.060.070.07
  • POS vs TOD_CHG - корреляция между нетто-объемом и today_change
  • POS vs TOM_CHG - корреляция между нетто-объемом и tomorrow_change
  • POS/VOL vs TOM_CHG - корреляция между нетто-объемом (нормированный на объем) и tomorrow_change
  • ΔPOS/VOL vs TOM_CHG - корреляция между приростом (дельтой) нетто-объема относительно предыдущих дней с tomorrow_change

На истории в 4 года оценим, что дает корреляция ~0.1

Статистика сонаправленности нетто-объема и tomorrow_change

Среднее значение величин p30, p70, p100 и их производных находится в окресностях 0. Посчитаем статистику сонаправленности нетто-объема и tomorrow_change по дням и по величине изменения цен(%) на основе одного из показателей p70_vol_3* (корреляция с tomorrow_change ~ 0.11):

*p70 нормированный на объем и дельта относительно среднего значения предыдущих трех дней

In [25]:
Z = Z[1:]
Z = Z[Z['tomorrow_change'] != 0]
Z['feature'] = np.sign(Z['p70_vol_3'])
Z['base'] = np.sign(Z['tomorrow_change'])
pd.crosstab(index = Z['feature'].astype(int), columns = Z['base'].astype(int))
Out[25]:
base -1 1
feature
-1 248 243
1 244 259

  • 248 - Количество дней, когда нетто-объем и tomorrow_change отрицательны (сонаправлены)
  • 259 - Количество дней, когда нетто-объем и tomorrow_change положительны (сонаправлены)
  • 243 - Количество дней, когда нетто-объем отрицателен и tomorrow_change положительна (разнонаправлены)
  • 245 - Количество дней, когда нетто-объем положителен и tomorrow_change отрицательна (разнонаправлены)
Т.е. (248 + 259) = 507 дней из 995 нетто-объем был сонаправлена с tomorrow_change

В таком же разрезе, посчитаем кумулятивную сумму процентых изменений цен в случаях сонаправленности и разнонаправленности:

In [26]:
pd.crosstab(index = Z['feature'].astype(int), columns = Z['base'].astype(int), values = Z['tomorrow_change'].abs().astype(int), aggfunc = 'sum')
Out[26]:
base -1 1
feature
-1 280 234
1 190 325
  • 280 - Нетто-объем и tomorrow_change отрицательны
  • 325 - Нетто-объем и tomorrow_change положительны
  • 234 - Нетто-объем отрицателен и tomorrow_change положителен
  • 191 - Нетто-объем положителен и tomorrow_change отрицателен

При сонаправленности нетто-объема и tomorrow_change изменение цены = 615 (280+325) и при разнонаправленности = 425 (234+191)

Статистика сонаправленности построенной на одном показателе p70_vol_3 равна 180 (615-425).

* Для простоты, проценты складывались кумулятивно. Пример: Если цена менялась +2%, -1%, +1%, получаем +2%


Дневной time-series график оговоренной выше статистики (черная линия - движение цены SBER, пять цветных - накопленная статистика сонаправленности по топ 5 показателям с наибольшей корреляцией):

In [27]:
R = pd.DataFrame()
for col in cols.index[:5]:
    R[col] = Z.apply(lambda x: x['tomorrow_change'] if x[col] > 0 else (-1 * x['tomorrow_change']), axis=1)
R['base'] = Z['tomorrow_change']
R.cumsum().plot(style=['#CE1126', '#E26EB2', '#FFA100', '#8D3C1E', '#63B1E5', '#000000'])
plt.ylabel('%')
Out[27]:
Text(0,0.5,'%')

Аналогичные графики по другим инструментам:

Итоги

  1. На примере одного инструмента (SBER) расчитали базовую статистику, распределение и динамику нетто-объемов (p30, p70, p100) крупных клентов на отрезке 2014-2017 годов
  2. Сильная корреляция между нетто-объемом и today_change (~0.75) и слабая корреляция с tomorrow_change, но тем не менее положительная и стабильная (~0.1)
  3. Посчитана статистика сонаправленности одного из показателей нетто-объема (p70_vol_3) c tomorrow_change (корреляция ~0.11):

Контакты

Ссылка на сайт - https://moex.com/ru/analyticalproducts?netflow2
Email - support.dataproducts@moex.com