Serving MOEX(Moscow exchange) API with FastAPI
Tags: fastapi, docker, api
Published: 2022-02-25 16:57:15

**Originally published on my** [blog](https://www.gealber.com/serving-moex-api-fastapi)

Recently I discovered the MOEX API, a few weeks ago to be more precise, with the recent events I thought that it may be interesting for you to know about this API. I say interesting, because you can consume the data that this API provides, basically data from the Moscow Exchange, and see how this Exchange behaves with the current crisis in Ukraine and the sanctions imposed from other countries to Russia. I'm not going to make an analysis of this data here, that's not my interest, you can make it by your own ).

A point that I want to clarify, this is a technical post, don't spec anything else from the reading of this post. If I wish to write about politics, I would write about Cuba, where I lived 26 years of my life, and where there's a lot of things to talk about. I barely know Belarus, that's where I'm leaving now. Saying that, let's start describing the requirements.


## Requirements

* Docker
* An account in the [MOEX API](https://www.moex.com/), this is optional, the API allow you to consumes the data that I will be using for free. The creation of the account is totally free, no credit card needed.

Having docker, is really not necessary to have anything else on your machine maybe. Just in case it would be easier for you to have:

* Python
* The following Python external libraries: 
    - [FastAPI](https://fastapi.tiangolo.com/)

## Introduction

The [MOEX API](https://www.moex.com/) have a lot of interesting data, at least for me, maybe for you is boring. For our use case, what I'm going to do is basically
expose the data that you can use to make some candlestick charts. For this purpose I wrote a pretty simple Python client to interact with this API, you can find it here, is called [moexiss](https://github.com/Gealber/moexiss).

Let's focus on what is important for this tutorial. 
1. I hope, you will see how to define a "model" of the data you want to return on a given endpoint of the API.
2. Define parameters to receive on GET endpoints.
3. Build a Docker container with your FastAPI API, very redundant I know.

## Endpoints

**boards**

Our API will expose the **boards** available in the **stock** _engine_ and the **index** _market_. For this particular endpoint you won't need to provide any parameter. We are going to return the list of boards according to this structure.

```json
[
  {
    "id": 0,
    "board_group_id": 0,
    "boardid": "string",
    "title": "string",
    "is_traded": true
  }
]
```

**securities**

In case of the **securities** endpoint you must provide the _boardid_ parameter in the GET request, and the API will return you a list of available securities, with the following data.

```json
[
  {
    "secid": "string",
    "boardid": "string",
    "name": "string",
    "decimals": 0,
    "shortname": "string",
    "annualhigh": 0,
    "annuallow": 0,
    "currencyid": "string",
    "calcmode": "string"
  }
]
```

**candles**

Last but not least, we are going to serve you also data that you could use to draw some candlestick charts. For this case you will need to provide the following parameters:

1. board, board id that can be fetched with the **boards** endpoint.
2. security, security id that can be fetched with the **securities** endpoint.
3. start_date and end_date, in this case you must provide a range of date that you want to fetch data from. You must take into consideration that there are days where the **index** market is not working, so if you choose one of these days don't be surprised if you don't get any data.

The structure of the data to be returned would be as follow:

```json
{
  "board": "string",
  "security": "string",
  "start_date": "string",
  "end_date": "string",
  "records": [
    {
      "open": 0,
      "close": 0,
      "high": 0,
      "low": 0,
      "value": 0,
      "volume": 0,
      "begin": "string",
      "end": "string"
    }
  ]
}
```

## Code

The tree of the final directory would be this:

```bash
.
├── app.py
├── Dockerfile
├── models
│   ├── __init__.py
│   └── model.py
├── moexiss
│   ├── client.py
│   ├── __init__.py
│   └── README.md
└── requirements.txt
```

The code related to moexiss could be found [here](https://github.com/Gealber/moexiss), feel free to git clone.

First, let's define our models to be returned on the endpoints of the API. In order to define these models we are going to need the following imports,

**importing pydantic and typing**

```python
from pydantic import BaseModel
from typing import List, Optional
```

**Board model**
File: __models/model.py__

```python

class Board(BaseModel):
    id: int
    board_group_id: int
    boardid: str
    title: str
    is_traded: bool
```

Now given this model, the API needs to return a *List* of this model, for that reason our endpoint that will return the **boards** need to be defined in the following way:

**boards endpoint definition**

File: __app.py__

```python

@app.get("/boards", response_model=List[Board])
async def boards():
    # code related to processing boards
    resp = client.boards("stock", "index")
    data = form_data(resp)
    boards = [Board(**d) for d in data]

    return boards
```

The most important detail here, is the **response_model** provided in the decorator call, in this case we are using the **List** from __typing__ library, and specifying that this endpoint will return a _list_ of the model previously defined. Pretty straight forward.

Here I make use of an utility function called __form_data__, the definition of this function would be:

```python
def form_data(resp):
    return [dict(zip(resp["columns"], record)) for record in resp["data"]]
```

Is a small transformation of the data fetched from the MOEX API.


**Security model**

File: __models/model.py__


```python

class Security(BaseModel):
    secid: str
    boardid: str
    name: str
    decimals: int
    shortname: str
    annualhigh: float
    annuallow: float
    currencyid: str
    calcmode: str = None
```

The endpoint for this case, would receive one parameter called _boardid_ that would be a string, and would return a __List__ of **Security**, the code related to it would be as follows:


**securities endpoint definition**

File: __app.py__

```python

@app.get("/securities", response_model=List[Security])
async def securities(boardid: str):
    resp = client.securities("stock", "index", boardid)
    resp["columns"] = [col.lower() for col in resp["columns"]]
    data = form_data(resp)
    secs = [Security(**d) for d in data]

    return secs
```

Notice that the parameter is defined in the definition of the endpoint handler. In case you failed to provide this parameter, FastAPI by default will return you and **422 HTTP Unprocessable Entity**. In the body of this response you could find the reason why the API is returning you this error, is just awesome, that requires zero effort by your part.

In the case of the last endpoint, **candles**, is basically the same, only that here I will need to define another model called __Record__

**Security model**

File: __models/model.py__

```python

class Record(BaseModel):
    open: float
    close: float
    high: float
    low: float
    value: float
    volume: float
    begin: str
    end: str

class Candle(BaseModel):
    board: str
    security: str
    start_date: str
    end_date: str

    records: Optional[List[Record]]
```

Our endpoint would be similar to the previous one, only with more parameters

**candles endpoint definition**

File: __app.py__

```python

@app.get("/candles", response_model=Candle)
async def candles(board: str, security: str, start_date: str, end_date: str):
    args = {
        "engine": "stock",
        "market": "index",
        "board": board,
        "security": security,
        "start_date": start_date,
        "end_date": end_date,
    }
    resp = client.candle_data(**args)

    candle = Candle(**args)
    candle.records = form_data(resp)

    return candle
```

## Docker container

Now for our docker image I use the Python3.9 image as the base image, honestly I tried to make other way at first. I tried to use a [distroless container](https://github.com/GoogleContainerTools/distroless), but is a pain in the ass, really annoying. This is only with Python, with Golang images works like a charm, no complaining about that, but with Python man that's another story. The main problem that you will find is with uvicorn, no more comment on this matters, if you know how let me know.

Back to business, the **Dockerfile** is pretty simple

```dockerfile
FROM python:3.9

WORKDIR /app

COPY . /app

RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt

CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "80"]

```

The image built with this dockerfile is about 991MB, I don't like images so big, that's the reason on trying to use [distroless container](https://github.com/GoogleContainerTools/distroless).

## Note

I don't provide the whole code on purpose, is to force you to write the missing parts by yourself. There's no too much to add really, but at least you do something.