golang coursera
Данное задание я выдаю студентам МГУ/МГУТ/ВШЭ/МИФИ при очных курсах. Для того чтобы они попробовали себя в командной работе. Но на самом деле задание реализуется в одиночку, вероятно это длаже проще будет, особенно после клона реддита :)
Пишем биржу :)
Биржа состоит из 3-х компонентов:
- Клиент
- Брокер
- Биржа
Биржа - центральная точка всей системы. Она сводит между собой различных продавцов и покупателей из разных брокеров, результатом сделок которых является изменение цены.
В нашем хакатоне будут использоваться исторические данные реальных торгов по фьючерсным контрактам на бирже РТС за 2019-05-17 - https://cloud.mail.ru/public/2qHq/pd99CWTLh . При желании можно скачать актуальные тиковые данные с сайта finam.ru
Формат данных <TICKER>;<PER>;<DATE>;<TIME>;<LAST>;<VOL>
:
- TICKER - название торгуемого инструмента
- PER - период, у нас тики ( отдельные сделки ), игнорируйте это поле
- DATE - дата
- TIME - время
- LAST - цена прошедшей сделки
- VOL - объём продшей сделки
Если на биржу ставится заявка на покупку или продажу, то она ставится в очередь и когда цена доходит до неё и хватает объёма - заявка исполняется, брокеру уходит соответствующее уведомление. Если не хватает объёма, то заявка исполняется частичсно, брокеру так же уходит уведомление. Если несколько участников поставилос заявку на одинаковый уровеньт цены, то исполняются в порядке добавления.
Внутри биржи цена образует книгу заявок, то что называется стаканом - https://s.mail.ru/ESbV/M5iGMvkQR - это на каком уровне стоят заявки на покупку или продажу
В связи с тем что наши данные исторические - предполагем, что мы не можем выступить инициатором сделки (т.е. сдвинуть цену, купив по рынку), можем приобрести только если другая сторона выступила инциатором. Это значит что мы встаём в стакан и когда цена доходит до нас - происходит сделка.
Заявки могут быть 2 видов:
- на прокупку - "я хочу купить по цене Х" - когда цена сверху вниз доходит до нашей заявки - она исполняется
- на продажу - "я хочу продать по цене Х" - когда цена снизу вверх доходит до нашей заявки - она исполняется
- визуально это выглядит так https://s.mail.ru/4ikh/Bjmu8zPiv
Помимо исполнения сделок брокер транслирует цену инструментов всем подключенным брокерам. Список транслируемых инструментов берётся из конфига, сами цены - из файла. В связи с тем что цены исторические - мы не смотрим на дату, а просто начинаем таранслировать ту цену что есть. Инфомрация о изменении цены отправляется каждую секунду. Если в секунду ( под конфигом) произошло больше чем 1 сделка - они аггрегируются. Брокеру отправляется OHLCV ( open, high, low, close, volume ), где:
- open - цена открытия интервала (первая сделка)
- high - максимальная цена в интервале
- low - минимальная цена в интервале
- close - цена закрытия интервала ( последняя сделка )
- volume - количество проторгованных контрактов
Формат обмена данными с брокером - protobuf через GRPC
syntax = "proto3";
message OHLCV {
int64 ID = 1; // внутренний идентификатор, просто авто-инкремент
int32 Time = 2;
int32 Interval = 3; // в данном случае - 1 секунда
float Open = 4;
float High = 5;
float Low = 6;
float Close = 7;
int32 Volume = 8;
string Ticker = 9;
}
message Deal {
int64 ID = 1; // DealID который вернулся вам при простановке заявки
int32 BrokerID = 2;
int32 ClientID = 3;
string Ticker = 4;
int32 Volume = 5; // сколько купили-продали
bool Partial = 6; // флаг что сделка клиента исполнилсь частично
int32 Time = 7;
float Price = 8;
}
message DealID {
int64 ID = 1;
int64 BrokerID = 2;
}
message BrokerID {
int64 ID = 1;
}
message CancelResult {
bool success = 1;
}
service Exchange {
// поток ценовых данных от биржи к брокеру
// мы каждую секнуду будем получать отсюда событие с ценами, которые броке аггрегирует у себя в минуты и показывает клиентам
// устанавливается 1 раз брокером
rpc Statistic (BrokerID) returns (stream OHLCV) {}
// отправка на биржу заявки от брокера
rpc Create (Deal) returns (DealID) {}
// отмена заявки
rpc Cancel (DealID) returns (CancelResult) {}
// исполнение заявок от биржи к брокеру
// устанавливается 1 раз брокером и при исполнении какой-то заявки
rpc Results (BrokerID) returns (stream Deal) {}
}
Брокер - это организация, которая предсотавляет своим клиентам доступ на биржу. У неё есть список клиентов, которые могут взаимодействовать посредством неё с биржей, так же она хранит количество их позиицй и историю сделок.
Брокер аггрегирует внутри себя информацию от биржи по ценовым данным, позволяя клиенту посмотреть историю. По-умолчанию, хранится история за последнеи 5 минут (300 секунд).
смотрите сначала скрин у клиента
Брокер предоставляет клиентам JSON-апи (REST или JSON-RPC) или же grpc-апи (отдельный proto-файл от того что у биржи ), через который им доступныы следующие возможности:
- посмотреть свои позиции и баланс - возвращает баланс + список заявок (слайс структур), может быть преобразовано в таблицу на хтмл
-> GET /api/v1/status
<-
{
"body": {
"balance": 10000000,
"positions": [
{"ticker": "SPFB.RTS", "...": "" }
],
"open_orders": [
{"id": 123, "ticker": "SPFB.RTS", "...": "" }
]
}
}
- отправить на биржу заявку на покупку или продажу тикера ( то что вы видите на скрине у клиента )
-> POST /api/v1/deal
{
"deal": {
"ticker": "SPFB.RTS",
"type": "BUY",
"volume": 100,
"price": 11
}
}
<-
{
"body": {
"id": "123"
}
}
- отменить ранее отправленную заявку - принимает ИД заявки
-> POST /api/v1/cancel
{
"id": 123
}
<-
{
"body": {
"id": "123",
"status": "..."
}
}
- посмотреть последнюю истории торгов - возвращает слайс структур, может быть преобразовано в таблицу на хтмл
-> /api/v1/history?ticker=SPFB.RTS
<-
{
"body": {
"ticker": "SPFB.RTS",
"prices": [
{"open": "..."},
"..."
]
}
}
CREATE TABLE `clients` (
`id` int NOT NULL AUTO_INCREMENT PRIMARY KEY,
`login_id` int NOT NULL,
-- `password` varchar(300) NOT NULL,
`balance` int NOT NULL
);
-- INSERT INTO `clients` (`id`, `login`, `password`, `balance`)
-- VALUES (1, 'Vasily', '123456', 200000),
-- VALUES (2, 'Ivan', 'qwerty', 200000),
-- VALUES (3, 'Olga', '1qaz2wsx', 200000);
CREATE TABLE `positions` (
`id` int NOT NULL AUTO_INCREMENT PRIMARY KEY,
`user_id` int NOT NULL,
`ticker` varchar(300) NOT NULL,
`volume` int NOT NULL,
KEY user_id(user_id)
);
-- INSERT INTO `clients` (`user_id`, `ticker`, `amount`)
-- VALUES (1, 'SiM7', '123456', 200000),
-- VALUES (1, 'RIM7', '123456', 200000),
-- VALUES (2, 'RIM7', 'qwerty', 200000);
CREATE TABLE `orders_history` (
`id` int NOT NULL AUTO_INCREMENT PRIMARY KEY,
`time` int NOT NULL,
`user_id` int,
`ticker` varchar(300) NOT NULL,
`volume` int NOT NULL,
`price` float not null,
`is_buy` int not null,
KEY user_id(user_id)
);
CREATE TABLE `request` ( -- запросы
`id` int NOT NULL AUTO_INCREMENT PRIMARY KEY,
`user_id` int,
`ticker` varchar(300) NOT NULL,
`volume` int NOT NULL,
`price` float NOT NULL,
`is_buy` int not null, -- 1 - покупаем, 0 - продаем
KEY user_id(user_id)
);
CREATE TABLE `stat` ( -- запросы
`id` int NOT NULL AUTO_INCREMENT PRIMARY KEY,
`time` int,
`interval` int,
`open` float,
`high` float,
`low` float,
`close` float,
`volume` int,
`ticker` varchar(300),
KEY id(id)
);
Клиент - это любой пользователь АПИ брокера. Это может быть биржевой терминал, веб-сайт, торговый робот.
У вас есть 2 варианта реализации терминала:
- На html - примерно будет такое https://s.mail.ru/Bszh/cxrEKBPqD - можно реализовать даже без знаний JS, на каком-то бутстрапе
- В виде телеграм-бота c Oauth-авторизацией. Этот вариант предпочительнее, потому что можно сразу всем знакомым показать.