Initial commit

This commit is contained in:
Tim Kächele 2025-04-21 09:28:33 +02:00
commit 87c81fd7c0
9 changed files with 328 additions and 0 deletions

143
.gitignore vendored Normal file
View File

@ -0,0 +1,143 @@
# IDE
.idea
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# Custom files
*ready
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/

24
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,24 @@
repos:
- repo: https://github.com/pycqa/isort
rev: 5.10.1
hooks:
- id: isort
name: isort (python)
args:
- --profile=black
- repo: https://github.com/psf/black
rev: 22.6.0
hooks:
- id: black
language_version: python3
types: [ python ]
- repo: https://github.com/myint/autoflake
rev: v1.5.1
hooks:
- id: autoflake
language_version: python3
types: [ python ]
args:
- --remove-all-unused-imports
- --recursive
- --in-place

43
app/api.py Normal file
View File

@ -0,0 +1,43 @@
from typing import Optional
from fastapi import FastAPI
from pydantic import BaseModel, Field, condecimal, conint, constr, root_validator
from app.types import Order, OrderSide, OrderType
app = FastAPI()
class CreateOrderModel(BaseModel):
type_: OrderType = Field(..., alias="type")
side: OrderSide
instrument: constr(min_length=12, max_length=12)
limit_price: Optional[condecimal(decimal_places=2)]
quantity: conint(gt=0)
@root_validator
def validator(cls, values: dict):
if values.get("type_") == "market" and values.get("limit_price"):
raise ValueError(
"Providing a `limit_price` is prohibited for type `market`"
)
if values.get("type_") == "limit" and not values.get("limit_price"):
raise ValueError("Attribute `limit_price` is required for type `limit`")
return values
class CreateOrderResponseModel(Order):
pass
@app.post(
"/orders",
status_code=201,
response_model=CreateOrderResponseModel,
response_model_by_alias=True,
)
async def create_order(model: CreateOrderModel):
# TODO: Add your implementation here
raise NotImplementedError()

24
app/stock_exchange.py Normal file
View File

@ -0,0 +1,24 @@
import random
import time
from app.types import Order
class OrderPlacementError(Exception):
pass
def place_order(order: Order):
"""dummy function that is symbolic standing for placing an order at the stock exchange.
Please do not modify the content of this method."""
if not order:
raise ValueError("Required order parameter not provided")
if random.random() >= 0.9:
raise OrderPlacementError(
"Failed to place order at stock exchange. Connection not available"
)
# it is an expensive operation
time.sleep(0.5)

27
app/types.py Normal file
View File

@ -0,0 +1,27 @@
from datetime import datetime
from enum import Enum
from typing import Optional
from pydantic import BaseModel, Field, condecimal, conint, constr
class OrderSide(Enum):
BUY = "buy"
SELL = "sell"
class OrderType(Enum):
MARKET = "market"
LIMIT = "limit"
class Order(BaseModel):
# id generated by the database
id_: str = Field(..., alias="id")
created_at: datetime
type_: OrderType = Field(..., alias="type")
side: OrderSide
instrument: constr(min_length=12, max_length=12)
limit_price: Optional[condecimal(decimal_places=2)]
quantity: conint(gt=0)

50
readme.md Normal file
View File

@ -0,0 +1,50 @@
# Backend engineering task
### Intro
The repository contains a bare setup for a REST API to place orders via a `POST - /orders` request, it can be found in [./app/api.py](./app/api.py).
Additionally, it contains [a building block](./app/stock_exchange.py) that symbolizes placing an order at the stock exchange.
The folder [tests](./tests) contains the setup for tests with the framework pytest.
The challenge in this repository is a simplified version of what we are dealing with at lemon.markets 🍋.
## Your task
We would like you to finalize the missing parts in this repository so that the following requirements are fulfilled:
1. A valid request to `POST /orders` should result in the order being stored in a database of your choice.
2. The created order should be placed at the stock exchange (use the method `place_order` provided in stock_exchange.py).
3. The endpoint should return a status code of 201 and the created order details, provided that the order has been saved in the database **AND** it is guaranteed that the order **will** be placed on the stock exchange.
4. In the case of an error in the endpoint, it should return the status code 500 and the body `{"message": "Internal server error while placing the order"}`
5. The API should be highly scalable and reliable. The reliability of the provided stock exchange should not impact the reliability of the `POST /orders` endpoint
Additionally, please add some tests and document how you would test the application as a whole and its pieces.
For the implementation you can choose to use this Python setup or implement it in Javascript/Typescript.
Please include a `solution.md` file where you document your decisions, assumptions, and also improvements you would like to incorporate in the future.
We value your time ⏰, so we do not expect you to spend more than 4 hours preparing the solution. 🤗
Focus on implementing the main task first and afterwards jump on additional improvements as you see fit.
### Bonus tasks
* How would you change the system if we would receive a high volume of async updates to the orders placed through a socket connection on the stock exchange, e.g. execution information? Please outline the changes in the `solution.md`.
* Feel free to add a GitHub actions workflow to test the application.
* Feel free to add a Dockerfile.
## Code quality
In order to maintain a certain amount of code quality, we are using pre-commit hooks
in this repository, which are installed by a 3rd-party tool called [pre-commit](https://pre-commit.com/).
Please follow [this documentation](https://pre-commit.com/#install) to install `pre-commit`
on your local machine. After that just execute the following command to install the hooks
to your git folder:
```shell
pre-commit install
```
### Everything process related
Please create a fork or clone of this repository, commit all of your work to a new branch, and provide us with a link to the solution via mail 📩, at least 6h before the review meeting.

5
requirements-dev.txt Normal file
View File

@ -0,0 +1,5 @@
-r requirements.txt
pytest
pytest-cov
pre-commit
requests

3
requirements.txt Normal file
View File

@ -0,0 +1,3 @@
fastapi==0.85.0
uvicorn[standard]==0.18.3
pydantic==1.10.2

9
tests/conftest.py Normal file
View File

@ -0,0 +1,9 @@
import pytest as pytest
from starlette.testclient import TestClient
from app.api import app
@pytest.fixture
def client() -> TestClient:
return TestClient(app)