grind-t-moex 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- grind_t_moex-0.1.0/PKG-INFO +62 -0
- grind_t_moex-0.1.0/README.md +51 -0
- grind_t_moex-0.1.0/pyproject.toml +31 -0
- grind_t_moex-0.1.0/pyproject.toml.orig +30 -0
- grind_t_moex-0.1.0/src/moex/__init__.py +42 -0
- grind_t_moex-0.1.0/src/moex/bond_amortizations.py +51 -0
- grind_t_moex-0.1.0/src/moex/bond_coupons.py +47 -0
- grind_t_moex-0.1.0/src/moex/bond_securities.py +30 -0
- grind_t_moex-0.1.0/src/moex/bonds.py +87 -0
- grind_t_moex-0.1.0/src/moex/bonds_market_data.py +97 -0
- grind_t_moex-0.1.0/src/moex/bonds_market_yield.py +58 -0
- grind_t_moex-0.1.0/src/moex/core/__init__.py +0 -0
- grind_t_moex-0.1.0/src/moex/core/fetch.py +34 -0
- grind_t_moex-0.1.0/src/moex/core/parse_block.py +14 -0
- grind_t_moex-0.1.0/src/moex/core/resource.py +17 -0
- grind_t_moex-0.1.0/src/moex/core/types.py +54 -0
- grind_t_moex-0.1.0/src/moex/daily_table.py +24 -0
- grind_t_moex-0.1.0/src/moex/py.typed +0 -0
- grind_t_moex-0.1.0/src/moex/securities.py +75 -0
- grind_t_moex-0.1.0/src/moex/security_description.py +167 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: grind-t-moex
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Async Python client for the Moscow Exchange ISS API
|
|
5
|
+
Author: grind-t
|
|
6
|
+
Author-email: grind-t <grindt.gd@gmail.com>
|
|
7
|
+
Requires-Dist: httpx>=0.28.1
|
|
8
|
+
Requires-Dist: pydantic>=2.13.5
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
12
|
+
# grind-t-moex
|
|
13
|
+
|
|
14
|
+
Async Python client for the [Moscow Exchange ISS API](https://iss.moex.com/iss/reference/).
|
|
15
|
+
Python port of [`@grind-t/moex`](https://github.com/grind-t/moex).
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
uv add grind-t-moex
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
import asyncio
|
|
27
|
+
|
|
28
|
+
import httpx
|
|
29
|
+
import moex
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
async def main():
|
|
33
|
+
bonds = await moex.get_moex_bonds(primary_board=1)
|
|
34
|
+
print(bonds[0]["SECID"], bonds[0]["COUPONPERCENT"])
|
|
35
|
+
|
|
36
|
+
# Reuse one connection pool for several requests
|
|
37
|
+
async with httpx.AsyncClient() as client:
|
|
38
|
+
description = await moex.get_moex_security_description("SU26238RMFS4", client=client)
|
|
39
|
+
amortizations = await moex.get_moex_bond_amortizations("SU26238RMFS4", client=client)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
asyncio.run(main())
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Functions
|
|
46
|
+
|
|
47
|
+
| Function | ISS resource |
|
|
48
|
+
| --- | --- |
|
|
49
|
+
| `get_moex_bonds(primary_board=)` | `engines/stock/markets/bonds/securities` → `securities` |
|
|
50
|
+
| `get_moex_bonds_market_data()` | `engines/stock/markets/bonds/securities` → `marketdata` |
|
|
51
|
+
| `get_moex_bonds_market_yield()` | `engines/stock/markets/bonds/securities` → `marketdata_yields` |
|
|
52
|
+
| `get_moex_bond_coupons(...)` | `statistics/engines/stock/markets/bonds/bondization` → `coupons` |
|
|
53
|
+
| `get_moex_bond_amortizations(id, ...)` | `securities/{id}/bondization` → `amortizations` |
|
|
54
|
+
| `get_moex_bond_securities()` | all traded bonds from `securities`, paginated |
|
|
55
|
+
| `get_moex_securities(...)` | `securities` → `securities` |
|
|
56
|
+
| `get_moex_security_description(id)` | `securities/{id}` → `description` |
|
|
57
|
+
| `get_moex_daily_table()` | `engines/stock` → `dailytable` |
|
|
58
|
+
|
|
59
|
+
Low-level helpers: `moex_fetch(path, block, params)`, `get_moex_resource`, `parse_moex_block`.
|
|
60
|
+
|
|
61
|
+
All functions accept an optional `client: httpx.AsyncClient`. The `from` query
|
|
62
|
+
parameter is passed as `from_`.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# grind-t-moex
|
|
2
|
+
|
|
3
|
+
Async Python client for the [Moscow Exchange ISS API](https://iss.moex.com/iss/reference/).
|
|
4
|
+
Python port of [`@grind-t/moex`](https://github.com/grind-t/moex).
|
|
5
|
+
|
|
6
|
+
## Installation
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
uv add grind-t-moex
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Usage
|
|
13
|
+
|
|
14
|
+
```python
|
|
15
|
+
import asyncio
|
|
16
|
+
|
|
17
|
+
import httpx
|
|
18
|
+
import moex
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
async def main():
|
|
22
|
+
bonds = await moex.get_moex_bonds(primary_board=1)
|
|
23
|
+
print(bonds[0]["SECID"], bonds[0]["COUPONPERCENT"])
|
|
24
|
+
|
|
25
|
+
# Reuse one connection pool for several requests
|
|
26
|
+
async with httpx.AsyncClient() as client:
|
|
27
|
+
description = await moex.get_moex_security_description("SU26238RMFS4", client=client)
|
|
28
|
+
amortizations = await moex.get_moex_bond_amortizations("SU26238RMFS4", client=client)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
asyncio.run(main())
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Functions
|
|
35
|
+
|
|
36
|
+
| Function | ISS resource |
|
|
37
|
+
| --- | --- |
|
|
38
|
+
| `get_moex_bonds(primary_board=)` | `engines/stock/markets/bonds/securities` → `securities` |
|
|
39
|
+
| `get_moex_bonds_market_data()` | `engines/stock/markets/bonds/securities` → `marketdata` |
|
|
40
|
+
| `get_moex_bonds_market_yield()` | `engines/stock/markets/bonds/securities` → `marketdata_yields` |
|
|
41
|
+
| `get_moex_bond_coupons(...)` | `statistics/engines/stock/markets/bonds/bondization` → `coupons` |
|
|
42
|
+
| `get_moex_bond_amortizations(id, ...)` | `securities/{id}/bondization` → `amortizations` |
|
|
43
|
+
| `get_moex_bond_securities()` | all traded bonds from `securities`, paginated |
|
|
44
|
+
| `get_moex_securities(...)` | `securities` → `securities` |
|
|
45
|
+
| `get_moex_security_description(id)` | `securities/{id}` → `description` |
|
|
46
|
+
| `get_moex_daily_table()` | `engines/stock` → `dailytable` |
|
|
47
|
+
|
|
48
|
+
Low-level helpers: `moex_fetch(path, block, params)`, `get_moex_resource`, `parse_moex_block`.
|
|
49
|
+
|
|
50
|
+
All functions accept an optional `client: httpx.AsyncClient`. The `from` query
|
|
51
|
+
parameter is passed as `from_`.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "grind-t-moex"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Async Python client for the Moscow Exchange ISS API"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.10"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"httpx>=0.28.1",
|
|
9
|
+
"pydantic>=2.13.5",
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
[[project.authors]]
|
|
13
|
+
name = "grind-t"
|
|
14
|
+
email = "grindt.gd@gmail.com"
|
|
15
|
+
|
|
16
|
+
[build-system]
|
|
17
|
+
requires = ["uv_build>=0.12.9,<0.13.0"]
|
|
18
|
+
build-backend = "uv_build"
|
|
19
|
+
|
|
20
|
+
[tool.uv.build-backend]
|
|
21
|
+
module-name = "moex"
|
|
22
|
+
|
|
23
|
+
[tool.pytest.ini_options]
|
|
24
|
+
asyncio_mode = "auto"
|
|
25
|
+
markers = ["e2e: hits the live MOEX ISS API"]
|
|
26
|
+
|
|
27
|
+
[dependency-groups]
|
|
28
|
+
dev = [
|
|
29
|
+
"pytest>=9.1.1",
|
|
30
|
+
"pytest-asyncio>=1.4.0",
|
|
31
|
+
]
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "grind-t-moex"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Async Python client for the Moscow Exchange ISS API"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "grind-t", email = "grindt.gd@gmail.com" }
|
|
8
|
+
]
|
|
9
|
+
requires-python = ">=3.10"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"httpx>=0.28.1",
|
|
12
|
+
"pydantic>=2.13.5",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
[build-system]
|
|
16
|
+
requires = ["uv_build>=0.12.9,<0.13.0"]
|
|
17
|
+
build-backend = "uv_build"
|
|
18
|
+
|
|
19
|
+
[tool.uv.build-backend]
|
|
20
|
+
module-name = "moex"
|
|
21
|
+
|
|
22
|
+
[tool.pytest.ini_options]
|
|
23
|
+
asyncio_mode = "auto"
|
|
24
|
+
markers = ["e2e: hits the live MOEX ISS API"]
|
|
25
|
+
|
|
26
|
+
[dependency-groups]
|
|
27
|
+
dev = [
|
|
28
|
+
"pytest>=9.1.1",
|
|
29
|
+
"pytest-asyncio>=1.4.0",
|
|
30
|
+
]
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from .bond_amortizations import MoexBondAmortization, get_moex_bond_amortizations
|
|
2
|
+
from .bond_coupons import MoexBondCoupon, get_moex_bond_coupons
|
|
3
|
+
from .bond_securities import get_moex_bond_securities
|
|
4
|
+
from .bonds import MoexBond, get_moex_bonds
|
|
5
|
+
from .bonds_market_data import MoexBondMarketData, get_moex_bonds_market_data
|
|
6
|
+
from .bonds_market_yield import MoexBondMarketYield, get_moex_bonds_market_yield
|
|
7
|
+
from .core.fetch import moex_fetch, moex_get_json
|
|
8
|
+
from .core.parse_block import parse_moex_block
|
|
9
|
+
from .core.resource import get_moex_resource
|
|
10
|
+
from .core.types import Engine, Market
|
|
11
|
+
from .daily_table import MoexDailyTableRow, get_moex_daily_table
|
|
12
|
+
from .securities import MoexSecurity, get_moex_securities
|
|
13
|
+
from .security_description import (
|
|
14
|
+
MoexSecurityDescription,
|
|
15
|
+
get_moex_security_description,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"Engine",
|
|
20
|
+
"Market",
|
|
21
|
+
"MoexBond",
|
|
22
|
+
"MoexBondAmortization",
|
|
23
|
+
"MoexBondCoupon",
|
|
24
|
+
"MoexBondMarketData",
|
|
25
|
+
"MoexBondMarketYield",
|
|
26
|
+
"MoexDailyTableRow",
|
|
27
|
+
"MoexSecurity",
|
|
28
|
+
"MoexSecurityDescription",
|
|
29
|
+
"get_moex_bond_amortizations",
|
|
30
|
+
"get_moex_bond_coupons",
|
|
31
|
+
"get_moex_bond_securities",
|
|
32
|
+
"get_moex_bonds",
|
|
33
|
+
"get_moex_bonds_market_data",
|
|
34
|
+
"get_moex_bonds_market_yield",
|
|
35
|
+
"get_moex_daily_table",
|
|
36
|
+
"get_moex_resource",
|
|
37
|
+
"get_moex_securities",
|
|
38
|
+
"get_moex_security_description",
|
|
39
|
+
"moex_fetch",
|
|
40
|
+
"moex_get_json",
|
|
41
|
+
"parse_moex_block",
|
|
42
|
+
]
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from datetime import date
|
|
2
|
+
from typing import Literal
|
|
3
|
+
|
|
4
|
+
import httpx
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
from .core.fetch import moex_fetch
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class MoexBondAmortization(BaseModel):
|
|
11
|
+
isin: str = Field(pattern=r"^[A-Z]{2}[A-Z0-9]{9}[0-9]$")
|
|
12
|
+
name: str = Field(min_length=1)
|
|
13
|
+
# Not always whole: e.g. gold-linked bonds have a fractional issue volume.
|
|
14
|
+
issuevalue: float = Field(gt=0)
|
|
15
|
+
amortdate: date
|
|
16
|
+
facevalue: float = Field(gt=0)
|
|
17
|
+
initialfacevalue: float = Field(gt=0)
|
|
18
|
+
faceunit: str = Field(pattern=r"^[A-Z]{3}$")
|
|
19
|
+
# May exceed 100 for indexed bonds; 0 for future payments not yet known.
|
|
20
|
+
valueprc: float = Field(ge=0)
|
|
21
|
+
value: float = Field(ge=0)
|
|
22
|
+
# None when the payment amount is not known yet.
|
|
23
|
+
value_rub: float | None = Field(gt=0)
|
|
24
|
+
data_source: Literal["amortization", "maturity"]
|
|
25
|
+
secid: str = Field(min_length=1)
|
|
26
|
+
primary_boardid: str = Field(pattern=r"^[A-Z0-9]{4}$")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
async def get_moex_bond_amortizations(
|
|
30
|
+
id: str,
|
|
31
|
+
*,
|
|
32
|
+
lang: Literal["ru", "en"] | None = None,
|
|
33
|
+
from_: str | None = None,
|
|
34
|
+
till: str | None = None,
|
|
35
|
+
limit: int | None = None,
|
|
36
|
+
start: int | None = None,
|
|
37
|
+
client: httpx.AsyncClient | None = None,
|
|
38
|
+
) -> list[MoexBondAmortization]:
|
|
39
|
+
rows = await moex_fetch(
|
|
40
|
+
f"securities/{id}/bondization.json",
|
|
41
|
+
"amortizations",
|
|
42
|
+
{
|
|
43
|
+
"lang": lang,
|
|
44
|
+
"from": from_,
|
|
45
|
+
"till": till,
|
|
46
|
+
"limit": "unlimited" if limit is None else limit,
|
|
47
|
+
"start": start,
|
|
48
|
+
},
|
|
49
|
+
client,
|
|
50
|
+
)
|
|
51
|
+
return [MoexBondAmortization.model_validate(row) for row in rows]
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from datetime import date
|
|
2
|
+
from typing import Literal
|
|
3
|
+
|
|
4
|
+
import httpx
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
from .core.fetch import moex_fetch
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class MoexBondCoupon(BaseModel):
|
|
11
|
+
# None for some old (1990s) bonds.
|
|
12
|
+
isin: str | None = Field(pattern=r"^[A-Z]{2}[A-Z0-9]{9}[0-9]$")
|
|
13
|
+
name: str = Field(min_length=1)
|
|
14
|
+
# Not always whole (e.g. gold-linked bonds); 0 for some structured bonds.
|
|
15
|
+
issuevalue: float | None = Field(ge=0)
|
|
16
|
+
coupondate: date
|
|
17
|
+
recorddate: date | None
|
|
18
|
+
startdate: date | None
|
|
19
|
+
initialfacevalue: float | None = Field(gt=0)
|
|
20
|
+
# 0 for some fully amortized bonds.
|
|
21
|
+
facevalue: float = Field(ge=0)
|
|
22
|
+
faceunit: str = Field(pattern=r"^[A-Z]{3}$")
|
|
23
|
+
# None when the payment amount is not known yet.
|
|
24
|
+
value: float | None = Field(gt=0)
|
|
25
|
+
# Annualized, so may exceed 100 for short-period structured bonds.
|
|
26
|
+
valueprc: float | None = Field(gt=0)
|
|
27
|
+
value_rub: float | None = Field(gt=0)
|
|
28
|
+
secid: str = Field(min_length=1)
|
|
29
|
+
primary_boardid: str | None = Field(pattern=r"^[A-Z0-9]{4}$")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
async def get_moex_bond_coupons(
|
|
33
|
+
*,
|
|
34
|
+
lang: Literal["ru", "en"] | None = None,
|
|
35
|
+
from_: str | None = None,
|
|
36
|
+
till: str | None = None,
|
|
37
|
+
limit: Literal[5, 10, 20, 100] | None = None,
|
|
38
|
+
start: int | None = None,
|
|
39
|
+
client: httpx.AsyncClient | None = None,
|
|
40
|
+
) -> list[MoexBondCoupon]:
|
|
41
|
+
rows = await moex_fetch(
|
|
42
|
+
"statistics/engines/stock/markets/bonds/bondization.json",
|
|
43
|
+
"coupons",
|
|
44
|
+
{"lang": lang, "from": from_, "till": till, "limit": limit, "start": start},
|
|
45
|
+
client,
|
|
46
|
+
)
|
|
47
|
+
return [MoexBondCoupon.model_validate(row) for row in rows]
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import httpx
|
|
2
|
+
|
|
3
|
+
from .securities import MoexSecurity, get_moex_securities
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
async def get_moex_bond_securities(
|
|
7
|
+
*, client: httpx.AsyncClient | None = None
|
|
8
|
+
) -> list[MoexSecurity]:
|
|
9
|
+
if client is None:
|
|
10
|
+
async with httpx.AsyncClient() as own_client:
|
|
11
|
+
return await get_moex_bond_securities(client=own_client)
|
|
12
|
+
|
|
13
|
+
securities: list[MoexSecurity] = []
|
|
14
|
+
start = 0
|
|
15
|
+
|
|
16
|
+
while True:
|
|
17
|
+
chunk = await get_moex_securities(
|
|
18
|
+
engine="stock",
|
|
19
|
+
market="bonds",
|
|
20
|
+
is_trading=1,
|
|
21
|
+
start=start,
|
|
22
|
+
limit=100,
|
|
23
|
+
client=client,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
securities.extend(chunk)
|
|
27
|
+
start += len(chunk)
|
|
28
|
+
|
|
29
|
+
if len(chunk) < 100:
|
|
30
|
+
return securities
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
from datetime import date
|
|
2
|
+
from typing import Annotated, Literal
|
|
3
|
+
|
|
4
|
+
import httpx
|
|
5
|
+
from pydantic import BaseModel, BeforeValidator, Field
|
|
6
|
+
|
|
7
|
+
from .core.fetch import moex_fetch
|
|
8
|
+
|
|
9
|
+
# ISS sends "0000-00-00" instead of null for a missing date.
|
|
10
|
+
MaybeDate = Annotated[
|
|
11
|
+
date | None, BeforeValidator(lambda v: None if v == "0000-00-00" else v)
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class MoexBond(BaseModel):
|
|
16
|
+
SECID: str = Field(min_length=1)
|
|
17
|
+
BOARDID: str = Field(pattern=r"^[A-Z0-9]{4}$")
|
|
18
|
+
SHORTNAME: str = Field(min_length=1)
|
|
19
|
+
# None when the bond has not traded yet (or for a while).
|
|
20
|
+
PREVWAPRICE: float | None = Field(gt=0)
|
|
21
|
+
# No bounds: ranges from -100 to six-figure values for distressed bonds.
|
|
22
|
+
YIELDATPREVWAPRICE: float | None
|
|
23
|
+
COUPONVALUE: float = Field(ge=0)
|
|
24
|
+
# None for bonds without a coupon schedule (mostly structured bonds).
|
|
25
|
+
NEXTCOUPON: MaybeDate
|
|
26
|
+
ACCRUEDINT: float = Field(ge=0)
|
|
27
|
+
PREVPRICE: float | None = Field(gt=0)
|
|
28
|
+
LOTSIZE: int = Field(gt=0)
|
|
29
|
+
# Not always whole: amortized or indexed bonds have fractional face values.
|
|
30
|
+
FACEVALUE: float = Field(gt=0)
|
|
31
|
+
BOARDNAME: str = Field(min_length=1)
|
|
32
|
+
STATUS: Literal["A", "N"]
|
|
33
|
+
# None for perpetual bonds and some bonds with a call option.
|
|
34
|
+
MATDATE: MaybeDate
|
|
35
|
+
DECIMALS: int = Field(ge=0)
|
|
36
|
+
# 0 for bonds without periodic coupons.
|
|
37
|
+
COUPONPERIOD: int = Field(ge=0)
|
|
38
|
+
ISSUESIZE: int = Field(gt=0)
|
|
39
|
+
PREVLEGALCLOSEPRICE: float | None = Field(gt=0)
|
|
40
|
+
# None when the bond has no previous trading day.
|
|
41
|
+
PREVDATE: MaybeDate
|
|
42
|
+
SECNAME: str = Field(min_length=1)
|
|
43
|
+
REMARKS: str | None = Field(pattern=r"^[A-Z]+$")
|
|
44
|
+
MARKETCODE: str = Field(pattern=r"^[A-Z]{3,4}$")
|
|
45
|
+
INSTRID: str = Field(pattern=r"^[A-Z]{4}$")
|
|
46
|
+
# Always null in live data.
|
|
47
|
+
SECTORID: None
|
|
48
|
+
MINSTEP: float = Field(gt=0)
|
|
49
|
+
FACEUNIT: str = Field(pattern=r"^[A-Z]{3}$")
|
|
50
|
+
BUYBACKPRICE: float | None = Field(gt=0)
|
|
51
|
+
BUYBACKDATE: MaybeDate
|
|
52
|
+
ISIN: str = Field(pattern=r"^[A-Z]{2}[A-Z0-9]{9}[0-9]$")
|
|
53
|
+
LATNAME: str = Field(min_length=1)
|
|
54
|
+
REGNUMBER: str | None = Field(min_length=1)
|
|
55
|
+
CURRENCYID: str = Field(pattern=r"^[A-Z]{3}$")
|
|
56
|
+
ISSUESIZEPLACED: int | None = Field(gt=0)
|
|
57
|
+
LISTLEVEL: Literal[1, 2, 3]
|
|
58
|
+
SECTYPE: str = Field(pattern=r"^[0-9A-Z]$")
|
|
59
|
+
# None when the rate is not known yet (e.g. floaters, structured bonds).
|
|
60
|
+
COUPONPERCENT: float | None = Field(gt=0)
|
|
61
|
+
OFFERDATE: date | None
|
|
62
|
+
SETTLEDATE: date
|
|
63
|
+
LOTVALUE: float = Field(gt=0)
|
|
64
|
+
FACEVALUEONSETTLEDATE: float | None = Field(gt=0)
|
|
65
|
+
CALLOPTIONDATE: date | None
|
|
66
|
+
PUTOPTIONDATE: date | None
|
|
67
|
+
DATEYIELDFROMISSUER: date | None
|
|
68
|
+
BONDTYPE: str | None = Field(min_length=1)
|
|
69
|
+
BONDSUBTYPE: Literal[
|
|
70
|
+
"До погашения", "До оферты (put)", "До оферты (call)", "Бессрочные"
|
|
71
|
+
]
|
|
72
|
+
COUPON_DETAILS: str | None = Field(min_length=1)
|
|
73
|
+
FACEVALUE_TYPE: Literal["Фиксированный", "Индексированный"]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
async def get_moex_bonds(
|
|
77
|
+
*,
|
|
78
|
+
primary_board: Literal[1, 0] | None = None,
|
|
79
|
+
client: httpx.AsyncClient | None = None,
|
|
80
|
+
) -> list[MoexBond]:
|
|
81
|
+
rows = await moex_fetch(
|
|
82
|
+
"engines/stock/markets/bonds/securities.json",
|
|
83
|
+
"securities",
|
|
84
|
+
{"primary_board": primary_board},
|
|
85
|
+
client,
|
|
86
|
+
)
|
|
87
|
+
return [MoexBond.model_validate(row) for row in rows]
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
from datetime import datetime, time
|
|
2
|
+
|
|
3
|
+
import httpx
|
|
4
|
+
from pydantic import BaseModel, Field
|
|
5
|
+
|
|
6
|
+
from .core.fetch import moex_fetch
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class MoexBondMarketData(BaseModel):
|
|
10
|
+
# Not unique: a bond may trade on several boards.
|
|
11
|
+
SECID: str = Field(min_length=1)
|
|
12
|
+
# None when there are no quotes (e.g. outside trading hours).
|
|
13
|
+
BID: float | None = Field(gt=0)
|
|
14
|
+
# Always null in live data.
|
|
15
|
+
BIDDEPTH: None
|
|
16
|
+
OFFER: float | None = Field(gt=0)
|
|
17
|
+
# Always null in live data.
|
|
18
|
+
OFFERDEPTH: None
|
|
19
|
+
# No bounds: quotes may cross during auctions. ISS sends 0 rather than null
|
|
20
|
+
# for this and the other non-nullable numeric fields below when there is no
|
|
21
|
+
# data, even for bonds that did not trade.
|
|
22
|
+
SPREAD: float
|
|
23
|
+
BIDDEPTHT: int | None = Field(ge=0)
|
|
24
|
+
OFFERDEPTHT: int | None = Field(ge=0)
|
|
25
|
+
# None when the bond has not traded today.
|
|
26
|
+
OPEN: float | None = Field(gt=0)
|
|
27
|
+
LOW: float | None = Field(gt=0)
|
|
28
|
+
HIGH: float | None = Field(gt=0)
|
|
29
|
+
LAST: float | None = Field(gt=0)
|
|
30
|
+
LASTCHANGE: float
|
|
31
|
+
LASTCHANGEPRCNT: float
|
|
32
|
+
QTY: int = Field(ge=0)
|
|
33
|
+
# None on boards without regular trading stats (e.g. TQOD).
|
|
34
|
+
VALUE: float | None = Field(ge=0)
|
|
35
|
+
# No bounds: ranges from -100 to five-figure values for distressed bonds.
|
|
36
|
+
YIELD: float | None
|
|
37
|
+
VALUE_USD: float | None = Field(ge=0)
|
|
38
|
+
WAPRICE: float | None = Field(gt=0)
|
|
39
|
+
LASTCNGTOLASTWAPRICE: float
|
|
40
|
+
WAPTOPREVWAPRICEPRCNT: float
|
|
41
|
+
WAPTOPREVWAPRICE: float
|
|
42
|
+
YIELDATWAPRICE: float | None
|
|
43
|
+
YIELDTOPREVYIELD: float
|
|
44
|
+
CLOSEYIELD: float
|
|
45
|
+
CLOSEPRICE: float | None = Field(gt=0)
|
|
46
|
+
MARKETPRICETODAY: float | None = Field(gt=0)
|
|
47
|
+
MARKETPRICE: float | None = Field(gt=0)
|
|
48
|
+
LASTTOPREVPRICE: float
|
|
49
|
+
NUMTRADES: int | None = Field(ge=0)
|
|
50
|
+
VOLTODAY: int | None = Field(ge=0)
|
|
51
|
+
VALTODAY: int | None = Field(ge=0)
|
|
52
|
+
VALTODAY_USD: int | None = Field(ge=0)
|
|
53
|
+
BOARDID: str = Field(pattern=r"^[A-Z0-9]{4}$")
|
|
54
|
+
# Open set of one-letter codes (N, T, B, C, a seen so far).
|
|
55
|
+
TRADINGSTATUS: str = Field(pattern=r"^[A-Za-z]$")
|
|
56
|
+
UPDATETIME: time
|
|
57
|
+
# In days; 0 when not computed.
|
|
58
|
+
DURATION: int = Field(ge=0)
|
|
59
|
+
# Always null in live data.
|
|
60
|
+
NUMBIDS: None
|
|
61
|
+
NUMOFFERS: None
|
|
62
|
+
CHANGE: float | None
|
|
63
|
+
TIME: time
|
|
64
|
+
# Always null in live data.
|
|
65
|
+
HIGHBID: None
|
|
66
|
+
LOWOFFER: None
|
|
67
|
+
PRICEMINUSPREVWAPRICE: float | None
|
|
68
|
+
# Always null in live data.
|
|
69
|
+
LASTBID: None
|
|
70
|
+
LASTOFFER: None
|
|
71
|
+
LCURRENTPRICE: float | None = Field(gt=0)
|
|
72
|
+
LCLOSEPRICE: float | None = Field(gt=0)
|
|
73
|
+
MARKETPRICE2: float | None = Field(gt=0)
|
|
74
|
+
OPENPERIODPRICE: float | None = Field(gt=0)
|
|
75
|
+
SEQNUM: int = Field(gt=0)
|
|
76
|
+
SYSTIME: datetime
|
|
77
|
+
VALTODAY_RUR: int | None = Field(ge=0)
|
|
78
|
+
# Implied rates, set only for floaters and linkers; no bounds.
|
|
79
|
+
IRICPICLOSE: float | None
|
|
80
|
+
BEICLOSE: float | None
|
|
81
|
+
CBRCLOSE: float | None
|
|
82
|
+
YIELDTOOFFER: float | None
|
|
83
|
+
YIELDLASTCOUPON: float | None
|
|
84
|
+
TRADINGSESSION: str | None = Field(min_length=1)
|
|
85
|
+
CALLOPTIONYIELD: float | None
|
|
86
|
+
CALLOPTIONDURATION: float | None = Field(ge=0)
|
|
87
|
+
ZSPREAD: float | None
|
|
88
|
+
ZSPREADATWAPRICE: float | None
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
async def get_moex_bonds_market_data(
|
|
92
|
+
*, client: httpx.AsyncClient | None = None
|
|
93
|
+
) -> list[MoexBondMarketData]:
|
|
94
|
+
rows = await moex_fetch(
|
|
95
|
+
"engines/stock/markets/bonds/securities.json", "marketdata", client=client
|
|
96
|
+
)
|
|
97
|
+
return [MoexBondMarketData.model_validate(row) for row in rows]
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from datetime import date, datetime
|
|
2
|
+
from typing import Literal
|
|
3
|
+
|
|
4
|
+
import httpx
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
from .core.fetch import moex_fetch
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class MoexBondMarketYield(BaseModel):
|
|
11
|
+
# Not unique: a bond may trade on several boards.
|
|
12
|
+
SECID: str = Field(min_length=1)
|
|
13
|
+
BOARDID: str = Field(pattern=r"^[A-Z0-9]{4}$")
|
|
14
|
+
# Price the yields below are computed from; set even for bonds that did not
|
|
15
|
+
# trade today.
|
|
16
|
+
PRICE: float = Field(gt=0)
|
|
17
|
+
# None for bonds without a maturity date (perpetuals).
|
|
18
|
+
YIELDDATE: date | None
|
|
19
|
+
# Timestamp of the zero-coupon yield curve used in the calculation.
|
|
20
|
+
ZCYCMOMENT: datetime
|
|
21
|
+
YIELDDATETYPE: Literal["MATDATE", "OFFER", "MBS"]
|
|
22
|
+
# No bounds: ranges from -100s to eight-figure values for distressed bonds.
|
|
23
|
+
# None when the yield cannot be computed (e.g. perpetuals).
|
|
24
|
+
EFFECTIVEYIELD: float | None
|
|
25
|
+
# In days.
|
|
26
|
+
DURATION: int | None = Field(ge=0)
|
|
27
|
+
# In basis points; no bounds.
|
|
28
|
+
ZSPREADBP: int | None
|
|
29
|
+
GSPREADBP: int | None
|
|
30
|
+
# None when the bond has not traded today.
|
|
31
|
+
WAPRICE: float | None = Field(gt=0)
|
|
32
|
+
EFFECTIVEYIELDWAPRICE: float | None
|
|
33
|
+
DURATIONWAPRICE: int | None = Field(ge=0)
|
|
34
|
+
# Implied rates, set only for floaters and linkers; no bounds.
|
|
35
|
+
IR: float | None
|
|
36
|
+
ICPI: float | None
|
|
37
|
+
BEI: float | None
|
|
38
|
+
# Always null in live data.
|
|
39
|
+
CBR: None
|
|
40
|
+
# Set only when YIELDDATETYPE is OFFER.
|
|
41
|
+
YIELDTOOFFER: float | None
|
|
42
|
+
YIELDLASTCOUPON: float | None
|
|
43
|
+
# Last trade time; 23:59:59 of the last trading day when the bond has not
|
|
44
|
+
# traded today.
|
|
45
|
+
TRADEMOMENT: datetime
|
|
46
|
+
SEQNUM: int = Field(gt=0)
|
|
47
|
+
SYSTIME: datetime
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
async def get_moex_bonds_market_yield(
|
|
51
|
+
*, client: httpx.AsyncClient | None = None
|
|
52
|
+
) -> list[MoexBondMarketYield]:
|
|
53
|
+
rows = await moex_fetch(
|
|
54
|
+
"engines/stock/markets/bonds/securities.json",
|
|
55
|
+
"marketdata_yields",
|
|
56
|
+
client=client,
|
|
57
|
+
)
|
|
58
|
+
return [MoexBondMarketYield.model_validate(row) for row in rows]
|
|
File without changes
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
import httpx
|
|
4
|
+
|
|
5
|
+
from .parse_block import parse_moex_block
|
|
6
|
+
from .resource import Params, get_moex_resource
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
async def moex_get_json(
|
|
10
|
+
path: str,
|
|
11
|
+
block: str,
|
|
12
|
+
params: Params | None = None,
|
|
13
|
+
client: httpx.AsyncClient | None = None,
|
|
14
|
+
) -> dict[str, Any]:
|
|
15
|
+
url = get_moex_resource(path, block, params)
|
|
16
|
+
|
|
17
|
+
if client is None:
|
|
18
|
+
async with httpx.AsyncClient() as own_client:
|
|
19
|
+
response = await own_client.get(url)
|
|
20
|
+
else:
|
|
21
|
+
response = await client.get(url)
|
|
22
|
+
|
|
23
|
+
response.raise_for_status()
|
|
24
|
+
return response.json()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
async def moex_fetch(
|
|
28
|
+
path: str,
|
|
29
|
+
block: str,
|
|
30
|
+
params: Params | None = None,
|
|
31
|
+
client: httpx.AsyncClient | None = None,
|
|
32
|
+
) -> list[Any]:
|
|
33
|
+
data = await moex_get_json(path, block, params, client)
|
|
34
|
+
return parse_moex_block(data[block])
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from collections.abc import Mapping, Sequence
|
|
2
|
+
from typing import Any, TypeVar
|
|
3
|
+
|
|
4
|
+
T = TypeVar("T")
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def parse_moex_block(block: Mapping[str, Sequence[Any]]) -> list[T]:
|
|
8
|
+
columns = block["columns"]
|
|
9
|
+
records = []
|
|
10
|
+
for row in block["data"]:
|
|
11
|
+
records.append(
|
|
12
|
+
{column: row[i] if i < len(row) else None for i, column in enumerate(columns)}
|
|
13
|
+
)
|
|
14
|
+
return records # type: ignore[return-value]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from collections.abc import Mapping
|
|
2
|
+
|
|
3
|
+
import httpx
|
|
4
|
+
|
|
5
|
+
BASE_URL = "https://iss.moex.com/iss/"
|
|
6
|
+
|
|
7
|
+
Params = Mapping[str, str | int | None]
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_moex_resource(path: str, block: str, params: Params | None = None) -> httpx.URL:
|
|
11
|
+
query = {
|
|
12
|
+
key: str(value) for key, value in (params or {}).items() if value is not None
|
|
13
|
+
}
|
|
14
|
+
query["iss.meta"] = "off"
|
|
15
|
+
query["iss.only"] = block
|
|
16
|
+
|
|
17
|
+
return httpx.URL(BASE_URL + path.lstrip("/"), params=query)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from typing import Literal
|
|
2
|
+
|
|
3
|
+
# https://iss.moex.com/iss/index.json?iss.only=engines
|
|
4
|
+
Engine = Literal[
|
|
5
|
+
"stock",
|
|
6
|
+
"state",
|
|
7
|
+
"currency",
|
|
8
|
+
"futures",
|
|
9
|
+
"commodity",
|
|
10
|
+
"interventions",
|
|
11
|
+
"offboard",
|
|
12
|
+
"agro",
|
|
13
|
+
"otc",
|
|
14
|
+
"quotes",
|
|
15
|
+
"money",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
# https://iss.moex.com/iss/index.json?iss.only=markets
|
|
19
|
+
Market = Literal[
|
|
20
|
+
"repo",
|
|
21
|
+
"deposit",
|
|
22
|
+
"otcindices",
|
|
23
|
+
"index",
|
|
24
|
+
"shares",
|
|
25
|
+
"bonds",
|
|
26
|
+
"ndm",
|
|
27
|
+
"otc",
|
|
28
|
+
"ccp",
|
|
29
|
+
"qnv",
|
|
30
|
+
"mamc",
|
|
31
|
+
"foreignshares",
|
|
32
|
+
"foreignndm",
|
|
33
|
+
"moexboard",
|
|
34
|
+
"gcc",
|
|
35
|
+
"credit",
|
|
36
|
+
"selt",
|
|
37
|
+
"futures",
|
|
38
|
+
"main",
|
|
39
|
+
"forts",
|
|
40
|
+
"options",
|
|
41
|
+
"fortsiqs",
|
|
42
|
+
"optionsiqs",
|
|
43
|
+
"sharesndm",
|
|
44
|
+
"nonresndm",
|
|
45
|
+
"nonresrepo",
|
|
46
|
+
"nonresccp",
|
|
47
|
+
"grain",
|
|
48
|
+
"sugar",
|
|
49
|
+
"auctions",
|
|
50
|
+
"buyauctions",
|
|
51
|
+
"saleauctions",
|
|
52
|
+
"standard",
|
|
53
|
+
"classica",
|
|
54
|
+
]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Imported as a module: the `date` field would otherwise shadow the type.
|
|
2
|
+
import datetime as dt
|
|
3
|
+
|
|
4
|
+
import httpx
|
|
5
|
+
from pydantic import BaseModel
|
|
6
|
+
|
|
7
|
+
from .core.fetch import moex_fetch
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# Exceptions to the regular calendar: weekday holidays and working weekends.
|
|
11
|
+
class MoexDailyTableRow(BaseModel):
|
|
12
|
+
date: dt.date
|
|
13
|
+
is_work_day: bool
|
|
14
|
+
# None (or 00:00:00 for both) on some non-working days, so start < stop
|
|
15
|
+
# does not always hold.
|
|
16
|
+
start_time: dt.time | None
|
|
17
|
+
stop_time: dt.time | None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
async def get_moex_daily_table(
|
|
21
|
+
*, client: httpx.AsyncClient | None = None
|
|
22
|
+
) -> list[MoexDailyTableRow]:
|
|
23
|
+
rows = await moex_fetch("engines/stock.json", "dailytable", client=client)
|
|
24
|
+
return [MoexDailyTableRow.model_validate(row) for row in rows]
|
|
File without changes
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
from typing import Annotated, Literal
|
|
2
|
+
|
|
3
|
+
import httpx
|
|
4
|
+
from pydantic import BaseModel, BeforeValidator, Field
|
|
5
|
+
|
|
6
|
+
from .core.fetch import moex_fetch
|
|
7
|
+
from .core.types import Engine, Market
|
|
8
|
+
|
|
9
|
+
# ISS sends "" (and for isin even "None") instead of null for some indices,
|
|
10
|
+
# currencies and old state bonds.
|
|
11
|
+
MaybeStr = Annotated[
|
|
12
|
+
str | None, BeforeValidator(lambda v: None if v in ("", "None") else v)
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# Issuer fields (emitent_*), isin and regnumber are None for derivatives,
|
|
17
|
+
# indices, currencies and commodities, and for some foreign securities.
|
|
18
|
+
class MoexSecurity(BaseModel):
|
|
19
|
+
# \w: an old commodity future has a Cyrillic letter ("R1Н4").
|
|
20
|
+
secid: str = Field(pattern=r"^[\w.+-]+$")
|
|
21
|
+
# None for some delisted securities and futures spreads.
|
|
22
|
+
shortname: str | None = Field(min_length=1)
|
|
23
|
+
regnumber: MaybeStr = Field(min_length=1)
|
|
24
|
+
name: str | None = Field(min_length=1)
|
|
25
|
+
# The check digit is missing for a few bond indices.
|
|
26
|
+
isin: MaybeStr = Field(pattern=r"^[A-Z]{2}[A-Z0-9]{9}[0-9]?$")
|
|
27
|
+
# None for some futures spreads.
|
|
28
|
+
is_traded: bool | None
|
|
29
|
+
emitent_id: int | None = Field(gt=0)
|
|
30
|
+
emitent_title: str | None = Field(min_length=1)
|
|
31
|
+
# Not always a real INN: foreign issuers have short registry numbers.
|
|
32
|
+
emitent_inn: str | None = Field(pattern=r"^[0-9]{1,12}$")
|
|
33
|
+
# Leading zeros are sometimes dropped.
|
|
34
|
+
emitent_okpo: str | None = Field(pattern=r"^[0-9]{5,8}$")
|
|
35
|
+
# Open sets, e.g. "common_share" in "stock_shares", "option" in
|
|
36
|
+
# "futures_options".
|
|
37
|
+
type: str = Field(pattern=r"^[a-z]+(_[a-z]+)*$")
|
|
38
|
+
group: str = Field(pattern=r"^[a-z]+(_[a-z]+)*$")
|
|
39
|
+
# None for some delisted securities (mostly bonds); 3 letters for a few old
|
|
40
|
+
# boards ("FOB", "BKT").
|
|
41
|
+
primary_boardid: str | None = Field(pattern=r"^[A-Z0-9]{3,4}$")
|
|
42
|
+
# Only set for some stock market securities (shares, bonds, funds).
|
|
43
|
+
marketprice_boardid: str | None = Field(pattern=r"^[A-Z0-9]{4}$")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
async def get_moex_securities(
|
|
47
|
+
*,
|
|
48
|
+
q: str | None = None,
|
|
49
|
+
lang: Literal["ru", "en"] | None = None,
|
|
50
|
+
engine: Engine | None = None,
|
|
51
|
+
is_trading: Literal[1, 0] | None = None,
|
|
52
|
+
market: Market | None = None,
|
|
53
|
+
group_by: Literal["group", "type"] | None = None,
|
|
54
|
+
limit: Literal[5, 10, 20, 100] | None = None,
|
|
55
|
+
group_by_filter: str | None = None,
|
|
56
|
+
start: int | None = None,
|
|
57
|
+
client: httpx.AsyncClient | None = None,
|
|
58
|
+
) -> list[MoexSecurity]:
|
|
59
|
+
rows = await moex_fetch(
|
|
60
|
+
"securities.json",
|
|
61
|
+
"securities",
|
|
62
|
+
{
|
|
63
|
+
"q": q,
|
|
64
|
+
"lang": lang,
|
|
65
|
+
"engine": engine,
|
|
66
|
+
"is_trading": is_trading,
|
|
67
|
+
"market": market,
|
|
68
|
+
"group_by": group_by,
|
|
69
|
+
"limit": limit,
|
|
70
|
+
"group_by_filter": group_by_filter,
|
|
71
|
+
"start": start,
|
|
72
|
+
},
|
|
73
|
+
client,
|
|
74
|
+
)
|
|
75
|
+
return [MoexSecurity.model_validate(row) for row in rows]
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
from datetime import date, time
|
|
2
|
+
from typing import Literal
|
|
3
|
+
|
|
4
|
+
import httpx
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
from .core.fetch import moex_get_json
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# Keys vary by security type; only TYPE, TYPENAME, GROUP and GROUPNAME are
|
|
11
|
+
# present for every security. Absent keys are None.
|
|
12
|
+
class MoexSecurityDescription(BaseModel):
|
|
13
|
+
# Absent for currency fixings and OTC currency indices.
|
|
14
|
+
SECID: str | None = Field(default=None, min_length=1)
|
|
15
|
+
# Absent for old commodity futures and some indices.
|
|
16
|
+
SHORTNAME: str | None = Field(default=None, min_length=1)
|
|
17
|
+
NAME: str | None = Field(default=None, min_length=1)
|
|
18
|
+
LATSHORTNAME: str | None = Field(default=None, min_length=1)
|
|
19
|
+
LATNAME: str | None = Field(default=None, min_length=1)
|
|
20
|
+
CALCMODE: str | None = Field(default=None, pattern=r"^[A-Z0-9-]+$")
|
|
21
|
+
CALCMODEDESCR: str | None = Field(default=None, min_length=1)
|
|
22
|
+
CURRENCYID: str | None = Field(default=None, pattern=r"^[A-Z]{3}$")
|
|
23
|
+
FREQUENCY: str | None = Field(default=None, min_length=1)
|
|
24
|
+
SCHEDULE: str | None = Field(default=None, min_length=1)
|
|
25
|
+
DECIMALS: int | None = Field(default=None, ge=0)
|
|
26
|
+
INITIALVALUE: float | None = Field(default=None, gt=0)
|
|
27
|
+
ISSUEDATE: date | None = None
|
|
28
|
+
TRADINGSESSION: str | None = Field(default=None, min_length=1)
|
|
29
|
+
# iNAV indices mark the fund with a leading or trailing underscore.
|
|
30
|
+
UNDERLYINGTICKER: str | None = Field(default=None, pattern=r"^_?[A-Z0-9]+_?$")
|
|
31
|
+
UNDERLYINGISIN: str | None = Field(
|
|
32
|
+
default=None, pattern=r"^_?[A-Z]{2}[A-Z0-9]{9}[0-9]_?$"
|
|
33
|
+
)
|
|
34
|
+
REBALANCE: str | None = Field(default=None, min_length=1)
|
|
35
|
+
WEIGHTLIMITTYPE: str | None = Field(default=None, min_length=1)
|
|
36
|
+
TYPENAME: str = Field(min_length=1)
|
|
37
|
+
GROUP: str = Field(pattern=r"^[a-z_]+$")
|
|
38
|
+
TYPE: str = Field(pattern=r"^[a-z_]+$")
|
|
39
|
+
GROUPNAME: str = Field(min_length=1)
|
|
40
|
+
FIRSTCALCDATE: date | None = None
|
|
41
|
+
# A count, but "переменное" (variable) for some indices.
|
|
42
|
+
CONSTITUENTS: str | None = Field(default=None, min_length=1)
|
|
43
|
+
DESCRIPTION: str | None = Field(default=None, min_length=1)
|
|
44
|
+
INITIALCAPITALIZATION: float | None = Field(default=None, gt=0)
|
|
45
|
+
INITIALD: float | None = Field(default=None, gt=0)
|
|
46
|
+
ISSUENAME: str | None = Field(default=None, min_length=1)
|
|
47
|
+
# One index has a truncated 11-character ISIN (check digit missing).
|
|
48
|
+
ISIN: str | None = Field(default=None, pattern=r"^[A-Z]{2}[A-Z0-9]{9}[0-9]?$")
|
|
49
|
+
# Not always whole: down to 1e-05 for some shares.
|
|
50
|
+
FACEVALUE: float | None = Field(default=None, gt=0)
|
|
51
|
+
# Includes non-ISO codes such as SUR, GLD and BKT.
|
|
52
|
+
FACEUNIT: str | None = Field(default=None, pattern=r"^[A-Z]{3}$")
|
|
53
|
+
HASPROSPECTUS: bool | None = None
|
|
54
|
+
HASDEFAULT: bool | None = None
|
|
55
|
+
HASTECHNICALDEFAULT: bool | None = None
|
|
56
|
+
# A 0/1 flag, though ISS declares it a number.
|
|
57
|
+
EMITENTMISMATCHCUR: int | None = Field(default=None, ge=0, le=1)
|
|
58
|
+
ISQUALIFIEDINVESTORS: bool | None = None
|
|
59
|
+
QUALINVESTORGROUP: str | None = Field(default=None, min_length=1)
|
|
60
|
+
EMITTER_ID: int | None = Field(default=None, gt=0)
|
|
61
|
+
REGNUMBER: str | None = Field(default=None, min_length=1)
|
|
62
|
+
ISSUESIZE: int | None = Field(default=None, gt=0)
|
|
63
|
+
REGISTRY_DATE: date | None = None
|
|
64
|
+
DECISIONDATE: date | None = None
|
|
65
|
+
LISTLEVEL: int | None = Field(default=None, ge=1, le=3)
|
|
66
|
+
MORNINGSESSION: bool | None = None
|
|
67
|
+
EVENINGSESSION: bool | None = None
|
|
68
|
+
WEEKENDSESSION: bool | None = None
|
|
69
|
+
MATDATE: date | None = None
|
|
70
|
+
INITIALFACEVALUE: float | None = Field(default=None, gt=0)
|
|
71
|
+
STARTDATEMOEX: date | None = None
|
|
72
|
+
ISCONCESSIONAGREEMENT: bool | None = None
|
|
73
|
+
INCLUDEDBYMOEX: bool | None = None
|
|
74
|
+
DAYSTOREDEMPTION: int | None = Field(default=None, ge=0)
|
|
75
|
+
COUPONFREQUENCY: int | None = Field(default=None, gt=0)
|
|
76
|
+
COUPONDATE: date | None = None
|
|
77
|
+
# 0 for some structured bonds.
|
|
78
|
+
COUPONPERCENT: float | None = Field(default=None, ge=0)
|
|
79
|
+
COUPONVALUE: float | None = Field(default=None, ge=0)
|
|
80
|
+
BOND_TYPE: str | None = Field(default=None, min_length=1)
|
|
81
|
+
BOND_SUBTYPE: (
|
|
82
|
+
Literal["До погашения", "До оферты (put)", "До оферты (call)", "Бессрочные"]
|
|
83
|
+
| None
|
|
84
|
+
) = None
|
|
85
|
+
EARLYREPAYMENT: bool | None = None
|
|
86
|
+
COUPON_BENCHMARK: str | None = Field(default=None, pattern=r"^[A-Z]+$")
|
|
87
|
+
# ISS declares it a string, e.g. "1.30".
|
|
88
|
+
COUPON_BENCHMARK_SPREAD: str | None = Field(
|
|
89
|
+
default=None, pattern=r"^-?[0-9]+\.[0-9]+$"
|
|
90
|
+
)
|
|
91
|
+
AMORTBOND: bool | None = None
|
|
92
|
+
BUYBACKDATE: date | None = None
|
|
93
|
+
EMITTER_VALUE_BUILDING: bool | None = None
|
|
94
|
+
LOTSIZE: int | None = Field(default=None, gt=0)
|
|
95
|
+
TIMETABLE: str | None = Field(default=None, min_length=1)
|
|
96
|
+
DELIVERYTYPE: str | None = Field(default=None, min_length=1)
|
|
97
|
+
FRSTTRADE: date | None = None
|
|
98
|
+
LSTTRADE: date | None = None
|
|
99
|
+
LSTDELDATE: date | None = None
|
|
100
|
+
ASSETCODE: str | None = Field(default=None, pattern=r"^[A-Za-z0-9]+$")
|
|
101
|
+
EXECTYPE: str | None = Field(default=None, min_length=1)
|
|
102
|
+
CONTRACTNAME: str | None = Field(default=None, min_length=1)
|
|
103
|
+
GROUPTYPE: str | None = Field(default=None, min_length=1)
|
|
104
|
+
UNIT: str | None = Field(default=None, min_length=1)
|
|
105
|
+
EXPIRATION_TYPE: str | None = Field(default=None, min_length=1)
|
|
106
|
+
EXPIRATION_TIME: time | None = None
|
|
107
|
+
SETTLETYPE: Literal["Расчетный", "Поставочный"] | None = None
|
|
108
|
+
OPTIONTYPE: Literal["C", "P"] | None = None
|
|
109
|
+
STRIKE: float | None = Field(default=None, gt=0)
|
|
110
|
+
MARGINSTYLE: Literal["Премиальный", "Маржируемый"] | None = None
|
|
111
|
+
UNDERLYINGASSET: str | None = Field(default=None, min_length=1)
|
|
112
|
+
SERIES_NAME: str | None = Field(default=None, min_length=1)
|
|
113
|
+
DEPOSITARY_ORG_NAME: str | None = Field(default=None, min_length=1)
|
|
114
|
+
# Not always whole: e.g. 0.2 shares per receipt.
|
|
115
|
+
SHARESPERRECEIPT: float | None = Field(default=None, gt=0)
|
|
116
|
+
HIGHRISK: bool | None = None
|
|
117
|
+
SECSUBTYPE: str | None = Field(default=None, min_length=1)
|
|
118
|
+
SUBORDBOND: bool | None = None
|
|
119
|
+
PROGRAMREGISTRYNUMBER: str | None = Field(default=None, min_length=1)
|
|
120
|
+
STRUCTBOND: bool | None = None
|
|
121
|
+
ASSET: str | None = Field(default=None, min_length=1)
|
|
122
|
+
PARENTINDEX: str | None = Field(default=None, min_length=1)
|
|
123
|
+
ISPARENT: bool | None = None
|
|
124
|
+
BBG: str | None = Field(default=None, min_length=1)
|
|
125
|
+
RIC: str | None = Field(default=None, min_length=1)
|
|
126
|
+
VOLUME_UNIT: str | None = Field(default=None, min_length=1)
|
|
127
|
+
DEPOSITARYNOTETYPE: str | None = Field(default=None, min_length=1)
|
|
128
|
+
ISSUESIZEPLANNED: int | None = Field(default=None, gt=0)
|
|
129
|
+
PERPETUAL_FUTURES: bool | None = None
|
|
130
|
+
FOSECTYPE: str | None = Field(default=None, pattern=r"^[A-Z0-9]+$")
|
|
131
|
+
# The fields below come only with old commodity futures.
|
|
132
|
+
FACEUNITNAME: str | None = Field(default=None, min_length=1)
|
|
133
|
+
DELIVERYMINQTY: str | None = Field(default=None, min_length=1)
|
|
134
|
+
PRICEFULLNAME: str | None = Field(default=None, min_length=1)
|
|
135
|
+
MINPRICEFLUCT: str | None = Field(default=None, min_length=1)
|
|
136
|
+
DURATION: int | None = Field(default=None, gt=0)
|
|
137
|
+
DELCOMMISSION: str | None = Field(default=None, min_length=1)
|
|
138
|
+
COMMISSIONRATE: float | None = Field(default=None, gt=0)
|
|
139
|
+
FINALPRICEMETHOD: str | None = Field(default=None, min_length=1)
|
|
140
|
+
STARTDELDATE: date | None = None
|
|
141
|
+
PRICEMVTLIMITEXTNAME: str | None = Field(default=None, min_length=1)
|
|
142
|
+
SHARELIMIT: float | None = Field(default=None, gt=0, le=100)
|
|
143
|
+
THRESHOLD: int | None = Field(default=None, gt=0)
|
|
144
|
+
MAXORDVOL: int | None = Field(default=None, gt=0)
|
|
145
|
+
DMRATEEXTNAME: str | None = Field(default=None, min_length=1)
|
|
146
|
+
TIMECOURSEINT: time | None = None
|
|
147
|
+
TIMECOURSECLR: time | None = None
|
|
148
|
+
# Only for the bi-currency basket.
|
|
149
|
+
BASKET_DESCR1: str | None = Field(default=None, min_length=1)
|
|
150
|
+
BASKET_DESCR2: str | None = Field(default=None, min_length=1)
|
|
151
|
+
BASKET_DESCR3: str | None = Field(default=None, min_length=1)
|
|
152
|
+
BASKET_DESCR4: str | None = Field(default=None, min_length=1)
|
|
153
|
+
BASKET_DESCR5: str | None = Field(default=None, min_length=1)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
async def get_moex_security_description(
|
|
157
|
+
id: str, *, client: httpx.AsyncClient | None = None
|
|
158
|
+
) -> MoexSecurityDescription:
|
|
159
|
+
data = await moex_get_json(f"securities/{id}.json", "description", client=client)
|
|
160
|
+
description = data["description"]
|
|
161
|
+
columns = description["columns"]
|
|
162
|
+
name_index = columns.index("name")
|
|
163
|
+
value_index = columns.index("value")
|
|
164
|
+
|
|
165
|
+
# Values are strings; the model converts them by field type.
|
|
166
|
+
result = {row[name_index]: row[value_index] for row in description["data"]}
|
|
167
|
+
return MoexSecurityDescription.model_validate(result)
|