x1o / moexer

A thin wrapper around MOEX ISS REST interface
GNU General Public License v3.0
2 stars 1 forks source link

moexer

Moscow Exchange (MOEX) provides a REST interface to its Informational and Statistical Server (ISS), see https://fs.moex.com/files/8888.

moexer is a thin wrapper around the REST interface. It allows to quickly fetch e.g. price candles for a particular security, obtain its profile information and so on. The data is returned as tibbles, making it easy to subsequently process and analyse it.

Installation

You can install the released version of moexer from CRAN with:

install.packages("moexer")

or the latest version from github:

devtools::install_github("x1o/moexer")

High-Level Interface to ISS

Suppose you want to download monthly candles from Jan 1, 2020 until the present day for the Sberbank common shares.

library(moexer)
library(dplyr)
library(purrr)

Each security on MOEX has an ID, e.g. a 4-letter ticker symbol for a share (“LKOH”) or an ISIN for a bond (“RU000A0JXPU3”).

Find the corresponding security ID:

search_security(query = 'Sberbank') |> 
    slice_head(n = 10) |> 
    select(secid, name, is_traded, type, primary_boardid)
| secid | name | is_traded | type | primary_boardid | |:-------------|:-------------------------|----------:|:----------------|:----------------| | SBER | Сбербанк России ПАО ао | 1 | common_share | TQBR | | SBERP | Сбербанк России ПАО ап | 1 | preferred_share | TQBR | | RU000A103WV8 | Сбербанк ПАО 001Р-SBER33 | 1 | exchange_bond | TQCB | | RU000A105SD9 | Сбербанк ПАО 001Р-SBER42 | 1 | exchange_bond | TQCB | | RU000A101QW2 | Сбербанк ПАО 001Р-SBER16 | 1 | exchange_bond | TQCB | | RU000A1069P3 | Сбербанк ПАО 002Р-SBER44 | 1 | exchange_bond | TQCB | | RU000A103661 | Сбербанк ПАО 001Р-SBER27 | 1 | exchange_bond | TQCB | | RU000A103KG4 | Сбербанк ПАО 001Р-SBERD1 | 1 | exchange_bond | TQCB | | RU000A103G42 | Сбербанк ПАО 001Р-SBER29 | 1 | exchange_bond | TQCB | | RU000A102YG7 | Сбербанк ПАО 001Р-SBER25 | 1 | exchange_bond | TQCB |

We can verify that SBER is indeed the symbol we were looking for and check the profile information:

sber_info <- get_security_info(secid = 'SBER')
sber_info$description |> 
    select(name, title, value)
| name | title | value | |:---------------------|:-------------------------------------------------|:-----------------------| | SECID | Код ценной бумаги | SBER | | NAME | Полное наименование | Сбербанк России ПАО ао | | SHORTNAME | Краткое наименование | Сбербанк | | ISIN | ISIN код | RU0009029540 | | REGNUMBER | Номер государственной регистрации | 10301481B | | ISSUESIZE | Объем выпуска | 21586948000 | | FACEVALUE | Номинальная стоимость | 3 | | FACEUNIT | Валюта номинала | SUR | | ISSUEDATE | Дата начала торгов | 2007-07-20 | | LATNAME | Английское наименование | Sberbank | | LISTLEVEL | Уровень листинга | 1 | | ISQUALIFIEDINVESTORS | Бумаги для квалифицированных инвесторов | 0 | | MORNINGSESSION | Допуск к утренней дополнительной торговой сессии | 1 | | EVENINGSESSION | Допуск к вечерней дополнительной торговой сессии | 1 | | TYPENAME | Вид/категория ценной бумаги | Акция обыкновенная | | GROUP | Код типа инструмента | stock_shares | | TYPE | Тип бумаги | common_share | | GROUPNAME | Типа инструмента | Акции | | EMITTER_ID | Код эмитента | 1199 |
sber_info$boards |> 
    slice_head(n = 10) |> 
    select(secid, boardid, title, is_traded, history_from, history_till, currencyid)
| secid | boardid | title | is_traded | history_from | history_till | currencyid | |:------|:--------|:-------------------------------------------|----------:|:-------------|:-------------|:-----------| | SBER | TQBR | Т+: Акции и ДР - безадрес. | 1 | 2013-03-25 | 2024-03-13 | RUB | | SBER | EQBR | Основной режим: А1-Акции и паи - безадрес. | 0 | 2011-11-21 | 2013-08-30 | RUB | | SBER | SPEQ | Поставка по СК (акции) | 1 | 2018-06-29 | 2023-12-22 | RUB | | SBER | SMAL | Т+: Неполные лоты (акции) - безадрес. | 1 | 2011-11-21 | 2024-03-13 | RUB | | SBER | TQDP | Крупные пакеты - Акции - безадрес. | 0 | NA | NA | RUB | | SBER | EQDP | Крупные пакеты - Акции - безадрес. | 0 | 2011-12-12 | 2019-03-01 | RUB | | SBER | RPMO | РЕПО-М - адрес. | 1 | 2019-04-22 | 2024-03-13 | RUB | | SBER | PTEQ | РПС с ЦК: Акции и ДР - адрес. | 1 | 2013-03-26 | 2024-03-13 | RUB | | SBER | MXBD | MOEX Board | 0 | 2015-08-03 | 2024-03-13 | NA | | SBER | CLMR | Classica - безадрес. | 0 | 2012-02-13 | 2015-07-31 | RUB |

Fetch the SBER candles:

get_candles(secid = 'SBER', from = '2020-01-01', till = '2022-01-01', interval = 'monthly') |> 
    head()
| secid | open | close | high | low | value | volume | begin | end | |:------|-------:|-------:|-------:|-------:|-------------:|-----------:|:-----------|:-----------| | SBER | 255.99 | 252.20 | 270.80 | 251.40 | 194032391970 | 747137520 | 2020-01-01 | 2020-01-31 | | SBER | 251.80 | 233.36 | 259.77 | 231.00 | 229515686975 | 919822790 | 2020-02-01 | 2020-02-28 | | SBER | 238.93 | 187.21 | 241.00 | 172.15 | 585178686681 | 3001736660 | 2020-03-01 | 2020-03-31 | | SBER | 183.20 | 197.25 | 205.44 | 182.00 | 339626472208 | 1768222700 | 2020-04-01 | 2020-04-30 | | SBER | 195.68 | 200.50 | 205.00 | 183.33 | 262827471698 | 1359045230 | 2020-05-01 | 2020-05-29 | | SBER | 203.10 | 203.22 | 223.15 | 200.75 | 320424161576 | 1522268370 | 2020-06-01 | 2020-06-30 |

get_candles() also supports specifying date-times for the from, till arguments, e.g. '2020-01-01 09:00:00'. This is most useful for obtaining intraday candles, e.g with interval = 'hourly' — see options('moexer.candle.intervals').

If till = NULL, all candles up to today are fetched.

get_candles() is vectorised over secid, so it is possible to, say, fetch candles for both the common and the preferred shares. The returned object has class MoexCandles for which there’s an appropriate plot() method:

get_candles(
    secid = c('SBER', 'SBERP'), 
    from = '2020-01-01', 
    till = '2022-01-01', 
    interval = 'monthly'
) |> 
    plot()

Low-level Interface to ISS

Request

ISS is accessed via HTTP and/or HTTPS.

The interface is “RESTful”, meaning the endpoint parameters can be passed as a query string, but they also form the path of the URL. For instance, to receive SBER candles, one would need to form a URL as below:

<base_url>/securities/SBER/candles?from=2020-01-10&till=2020-01-17

For a list of all public endpoints, see http://iss.moex.com/iss/reference/.

Response

ISS is capable of returning data in several formats; the present package uses JSON internally.

Every response consists of named sections (“blocks”).

Every block contains the following sub-sections:

All response blocks are parsed as tibbles using the information above.

Query String Parameters

The parameters can be applied to some sections (1), all section (2) or modify “system” ISS parameters (3).

  1. <block>.<parameter>=<value> (applicable for block <block>)
    • <block>.columns=<id_1>,<id_2>,...: only select these column in the block <block>
  2. <parameter>=<value> (all response blocks)
  3. iss.<parameter>=<value> (system parameter)
    • iss.only=<block_1>,<block_2>,...: only return these blocks

Examples

The main function for working with low-level requests is query_iss().

Simple Queries

Fetch possible values for certain market objects. This returns a list of sections such as engines, markets, etc, each being a dataframe.

iss_index <- query_iss('index')
names(iss_index)
#>  [1] "engines"             "markets"             "boards"             
#>  [4] "boardgroups"         "durations"           "securitytypes"      
#>  [7] "securitygroups"      "securitycollections"
iss_index$engines
| id | name | title | |-----:|:--------------|:---------------------------------| | 1 | stock | Фондовый рынок и рынок депозитов | | 2 | state | Рынок ГЦБ (размещение) | | 3 | currency | Валютный рынок | | 4 | futures | Срочный рынок | | 5 | commodity | Товарный рынок | | 6 | interventions | Товарные интервенции | | 7 | offboard | ОТС-система | | 9 | agro | Агро | | 1012 | otc | ОТС с ЦК | | 1282 | quotes | Квоты |

Query string parameters are specified as params argument; debug_output = TRUE results in the request URL being printed:

df <- query_iss(
    'index',
    params = list(
        iss.only = 'engines,markets', 
        markets.columns = 'id,market_name,market_title'
    ),
    debug_output = TRUE
)
#>  http://iss.moex.com/iss/index.json?iss.only=engines,markets&markets.columns=id,market_name,market_title
df$engines |> head()
| id | name | title | |----:|:--------------|:---------------------------------| | 1 | stock | Фондовый рынок и рынок депозитов | | 2 | state | Рынок ГЦБ (размещение) | | 3 | currency | Валютный рынок | | 4 | futures | Срочный рынок | | 5 | commodity | Товарный рынок | | 6 | interventions | Товарные интервенции |
df$markets |> head()
| id | market_name | market_title | |----:|:------------|:--------------------------| | 5 | index | Индексы фондового рынка | | 1 | shares | Рынок акций | | 2 | bonds | Рынок облигаций | | 4 | ndm | Режим переговорных сделок | | 29 | otc | ОТС | | 27 | ccp | РЕПО с ЦК |
show_df <- function(df) {
    print(nrow(df))
    bind_rows(head(df), tail(df))
}

Queries Involving Cursor

Some queries return large responses, which are split into pages that must accessed using start query string parameter as indicated in the auxiliary section history.cursor, also returned by ISS:

df <- query_iss(
    'history/engines/stock/markets/shares/securities/MOEX',
    params = list(
        from = '2021-09-01',
        till = '2021-12-31',
        start = 10
    )
)
show_df(df$history[,1:10])
#>  [1] 100
| BOARDID | TRADEDATE | SHORTNAME | SECID | NUMTRADES | VALUE | OPEN | LOW | HIGH | LEGALCLOSEPRICE | |:--------|:-----------|:----------|:------|----------:|-------------:|-------:|-------:|-------:|----------------:| | SMAL | 2021-09-08 | МосБиржа | MOEX | 16 | 3.869140e+03 | 184.91 | 183.00 | 186.00 | NA | | TQBR | 2021-09-08 | МосБиржа | MOEX | 20040 | 1.331147e+09 | 187.00 | 181.30 | 187.83 | 182.88 | | SMAL | 2021-09-09 | МосБиржа | MOEX | 22 | 5.684990e+03 | 182.50 | 182.00 | 184.49 | NA | | TQBR | 2021-09-09 | МосБиржа | MOEX | 21268 | 1.162550e+09 | 182.95 | 181.02 | 184.88 | 183.18 | | SMAL | 2021-09-10 | МосБиржа | MOEX | 22 | 4.622800e+03 | 182.35 | 182.35 | 187.40 | NA | | TQBR | 2021-09-10 | МосБиржа | MOEX | 17626 | 1.390805e+09 | 182.84 | 182.58 | 187.78 | 184.90 | | TQBR | 2021-11-12 | МосБиржа | MOEX | 22104 | 1.369847e+09 | 171.49 | 168.01 | 171.66 | 169.73 | | SMAL | 2021-11-15 | МосБиржа | MOEX | 11 | 3.743810e+03 | 158.08 | 158.08 | 172.70 | NA | | TQBR | 2021-11-15 | МосБиржа | MOEX | 20860 | 1.276717e+09 | 169.27 | 168.82 | 173.70 | 171.69 | | SMAL | 2021-11-16 | МосБиржа | MOEX | 10 | 3.443480e+03 | 172.90 | 168.80 | 173.97 | NA | | TQBR | 2021-11-16 | МосБиржа | MOEX | 23585 | 1.410013e+09 | 172.18 | 168.22 | 173.09 | 168.58 | | SMAL | 2021-11-17 | МосБиржа | MOEX | 9 | 2.547600e+03 | 168.51 | 168.49 | 172.39 | NA |
df$history.cursor
| INDEX | TOTAL | PAGESIZE | |------:|------:|---------:| | 10 | 174 | 100 |

An adverb following_cursor() decorates query_iss() to follow the cursor until the requested information is received completely. The .cursor dataframe is dropped.

following_cursor(query_iss)(
    'history/engines/stock/markets/shares/securities/MOEX',
    params = list(
        from = '2021-09-01',
        till = '2021-12-31',
        start = 10
    )
) |> 
    pluck('history') |> 
    select(1:10) |> 
    show_df()
#>  [1] 164
| BOARDID | TRADEDATE | SHORTNAME | SECID | NUMTRADES | VALUE | OPEN | LOW | HIGH | LEGALCLOSEPRICE | |:--------|:-----------|:----------|:------|----------:|-------------:|-------:|-------:|-------:|----------------:| | SMAL | 2021-09-08 | МосБиржа | MOEX | 16 | 3.869140e+03 | 184.91 | 183.00 | 186.00 | NA | | TQBR | 2021-09-08 | МосБиржа | MOEX | 20040 | 1.331147e+09 | 187.00 | 181.30 | 187.83 | 182.88 | | SMAL | 2021-09-09 | МосБиржа | MOEX | 22 | 5.684990e+03 | 182.50 | 182.00 | 184.49 | NA | | TQBR | 2021-09-09 | МосБиржа | MOEX | 21268 | 1.162550e+09 | 182.95 | 181.02 | 184.88 | 183.18 | | SMAL | 2021-09-10 | МосБиржа | MOEX | 22 | 4.622800e+03 | 182.35 | 182.35 | 187.40 | NA | | TQBR | 2021-09-10 | МосБиржа | MOEX | 17626 | 1.390805e+09 | 182.84 | 182.58 | 187.78 | 184.90 | | SMAL | 2021-12-28 | МосБиржа | MOEX | 3 | 9.473100e+02 | 159.90 | 150.13 | 159.90 | NA | | TQBR | 2021-12-28 | МосБиржа | MOEX | 19040 | 1.097535e+09 | 152.15 | 151.27 | 153.34 | 152.38 | | SMAL | 2021-12-29 | МосБиржа | MOEX | 14 | 3.190540e+03 | 154.86 | 143.02 | 156.98 | NA | | TQBR | 2021-12-29 | МосБиржа | MOEX | 15518 | 8.011846e+08 | 151.84 | 150.80 | 152.59 | 152.10 | | SMAL | 2021-12-30 | МосБиржа | MOEX | 11 | 2.600790e+03 | 153.01 | 145.79 | 165.34 | NA | | TQBR | 2021-12-30 | МосБиржа | MOEX | 17425 | 8.129008e+08 | 152.57 | 152.10 | 153.67 | 153.18 |

Queries With Automatical start Increment

Some responses are large yet are not accompanied by a .cursor block.

query_iss(
    'engines/stock/markets/shares/boards/TQBR/securities/SBER/candles',
    params = list(
        from = URLencode('2020-01-10 10:00:00'),
        till = URLencode('2020-01-10 23:59:59'),
        interval = 1,
        start = 10
    ),
    debug_output = TRUE
) |> 
    pluck('candles') |> 
    show_df()
#>  http://iss.moex.com/iss/engines/stock/markets/shares/boards/TQBR/securities/SBER/candles.json?from=2020-01-10%2010:00:00&till=2020-01-10%2023:59:59&interval=1&start=10
#>  [1] 500
| open | close | high | low | value | volume | begin | end | |-------:|-------:|-------:|-------:|---------:|-------:|:--------------------|:--------------------| | 257.00 | 257.30 | 257.39 | 256.90 | 30515548 | 118660 | 2020-01-10 10:10:00 | 2020-01-10 10:10:59 | | 257.24 | 257.32 | 257.38 | 257.20 | 14548053 | 56550 | 2020-01-10 10:11:00 | 2020-01-10 10:11:59 | | 257.30 | 257.37 | 257.38 | 257.16 | 5638369 | 21920 | 2020-01-10 10:12:00 | 2020-01-10 10:12:59 | | 257.28 | 257.30 | 257.47 | 257.23 | 4317722 | 16780 | 2020-01-10 10:13:00 | 2020-01-10 10:13:59 | | 257.30 | 257.44 | 257.44 | 257.30 | 8505667 | 33050 | 2020-01-10 10:14:00 | 2020-01-10 10:14:59 | | 257.43 | 257.32 | 257.43 | 257.20 | 12989804 | 50490 | 2020-01-10 10:15:00 | 2020-01-10 10:15:59 | | 257.86 | 257.85 | 257.87 | 257.80 | 3640487 | 14120 | 2020-01-10 18:24:00 | 2020-01-10 18:24:59 | | 257.84 | 257.85 | 257.97 | 257.80 | 19307540 | 74860 | 2020-01-10 18:25:00 | 2020-01-10 18:25:59 | | 257.84 | 257.70 | 257.84 | 257.70 | 5734978 | 22250 | 2020-01-10 18:26:00 | 2020-01-10 18:26:59 | | 257.71 | 257.71 | 257.82 | 257.70 | 8255241 | 32030 | 2020-01-10 18:27:00 | 2020-01-10 18:27:59 | | 257.77 | 257.82 | 257.83 | 257.71 | 9292759 | 36050 | 2020-01-10 18:28:00 | 2020-01-10 18:28:59 | | 257.80 | 257.90 | 257.97 | 257.75 | 21327564 | 82730 | 2020-01-10 18:29:00 | 2020-01-10 18:29:59 |

These queries may accessed by query_iss() wrapped into fetching_fully decorator, which automatically increases the value of start parameter until no more data is received:

fetching_fully(query_iss)(
    'engines/stock/markets/shares/boards/TQBR/securities/SBER/candles',
    params = list(
       from = URLencode('2020-01-10 10:00:00'),
       till = URLencode('2020-01-10 23:59:59'),
       interval = 1,
       start = 10
    )
) |> 
    pluck('candles') |> 
    show_df()
#>  [1] 515
| open | close | high | low | value | volume | begin | end | |-------:|-------:|-------:|-------:|----------:|--------:|:--------------------|:--------------------| | 257.00 | 257.30 | 257.39 | 256.90 | 30515548 | 118660 | 2020-01-10 10:10:00 | 2020-01-10 10:10:59 | | 257.24 | 257.32 | 257.38 | 257.20 | 14548053 | 56550 | 2020-01-10 10:11:00 | 2020-01-10 10:11:59 | | 257.30 | 257.37 | 257.38 | 257.16 | 5638369 | 21920 | 2020-01-10 10:12:00 | 2020-01-10 10:12:59 | | 257.28 | 257.30 | 257.47 | 257.23 | 4317722 | 16780 | 2020-01-10 10:13:00 | 2020-01-10 10:13:59 | | 257.30 | 257.44 | 257.44 | 257.30 | 8505667 | 33050 | 2020-01-10 10:14:00 | 2020-01-10 10:14:59 | | 257.43 | 257.32 | 257.43 | 257.20 | 12989804 | 50490 | 2020-01-10 10:15:00 | 2020-01-10 10:15:59 | | 257.81 | 257.81 | 257.85 | 257.80 | 17768959 | 68920 | 2020-01-10 18:39:00 | 2020-01-10 18:39:59 | | 258.19 | 258.19 | 258.19 | 258.19 | 328812711 | 1273530 | 2020-01-10 18:45:00 | 2020-01-10 18:45:59 | | 258.19 | 258.19 | 258.19 | 258.19 | 3268685 | 12660 | 2020-01-10 18:46:00 | 2020-01-10 18:46:59 | | 258.19 | 258.19 | 258.19 | 258.19 | 206552 | 800 | 2020-01-10 18:47:00 | 2020-01-10 18:47:59 | | 258.19 | 258.19 | 258.19 | 258.19 | 258190 | 1000 | 2020-01-10 18:48:00 | 2020-01-10 18:48:59 | | 258.19 | 258.19 | 258.19 | 258.19 | 25899039 | 100310 | 2020-01-10 18:49:00 | 2020-01-10 18:49:59 |

Some endpoints do not support start parameter – fetching_fully() knows how to handle that:

fetching_fully(query_iss)('turnovers', params = list(iss.only = 'turnovers'))$turnovers
#>  ! Received identical sections: the endpoint probably does not support `start` parameter
| NAME | ID | VALTODAY | VALTODAY_USD | NUMTRADES | UPDATETIME | TITLE | |:----------|-----:|-------------:|-------------:|----------:|:--------------------|:---------------------------------| | stock | 1 | 9.897573e+05 | 10811.71471 | 564420 | 2024-03-14 10:50:33 | Фондовый рынок и рынок депозитов | | currency | 3 | 5.758771e+05 | 6290.65148 | 34415 | 2024-03-14 10:50:33 | Валютный рынок | | futures | 4 | 9.605037e+04 | 1049.21596 | 307945 | 2024-03-14 10:50:33 | Срочный рынок | | commodity | 5 | NA | NA | NA | 2024-03-14 07:00:00 | Товарный рынок | | agro | 9 | NA | NA | NA | 2024-03-14 10:30:03 | Агро | | otc | 1012 | 2.767398e+00 | 0.03023 | 928 | 2024-03-14 10:50:33 | ОТС с ЦК | | quotes | 1282 | NA | NA | NA | 2024-03-14 09:30:01 | Квоты | | TOTALS | NA | 1.661688e+06 | 18151.61237 | 907708 | 2024-03-14 10:50:33 | Всего по Московской Бирже |