Удобная компоновка (в расте)

Страницы:  1

Ответить
 

Professor Seleznov


Почитал у известного чела о пост-условиях (“посткондишнс”) на свифте - и знаете, раст все равно лучше. Тупо приятнее язык даже для создания торговой биржи со своей мудреной бизнес-логикой - тикерами, логикой стопов и лимитных ордеров, многопоточностью, паттерн матчингом, децентрализованной сетью, графиками, распределениями парето, гаусса, стьюдента. Не трогая деревативов и разницу форвард/фьючерс, есть “аск” и “бид”, три вида ордеров, тикеры, взлеты и падения, вискарь-тревоги-роботы - короче, что нужно сделаем (кроме роботов), а для всего остального мастер кард (или МИР, а для европейцев “е-ц” карты).
Простейшая реализаций биржы (тикера) складывается всего из двух куч (binary heap) или двух стаканов: “аск” (продажа) и “бид” (покупка), и следствием их пересечения является сделка. Кто-либо создает ордер на покупку, указывая количество, и ордер исполняется мгновенно по верхней цене в стакане. Если хочется купить дешевле, создается лимитный ордер с указанием цены, а сделка ожидает продавца, согласного продать вам по указанной цене. С точки зрения реализации нет особой разницы между “asap” и лимитными ордерами. Чуть сложнее стопы.
Стопы (ордеры) зависят от цен на табло и исполняются на продажу по любой цене (мгновенно) при цене на табло равной или меньше указанной в ордере. Стопы могут тригернуть другие стопы, поэтому в реализцаии чуть сложнее.
Биржа на бумаге выглядит как-то так: StockRaw -> StockRawWithStop -> StockWithHistory -> Broker -> Client. В расте вполне естественно завернуть в “енамы” данные и удобно раскрыть паттерн матчингом принимающей стороны на соответсвуещем уровне, например: Intent::Buy(Limit(Price(p), Quantity(q))) или Intent::Sell(Asap(Quantity(q))) - брокеру важно лишь намерение клиента (intent), или лимитный ордер обработается на нижнем уровне.
Можно отметить, что в расте очень удобно компоновать - ниже четыре варианта покрыты двумя “кейзами”, содержимое элегантно развернуто, слеплено и завернуто обратно:
match (x,y) {
(Some(xs), Some(ys)) => Some(vec![xs, ys].concat()),
(x,y) => x.or(y)
}
Пост-условие (“посткондишнс”) - это такой способ не потерять корректность в слегка запутанной бизнес-логике. В примере (ниже) мы используем “дифференц” между нашей биржей и ордером, и далее (в методе “deal”) усиливаем пост-условие, гарантируя количество ордеров на бирже до и после сделки.
impl Stock {
/// postcondition:
/// stock.quantity - new_stock.quantity <= order.quantity * 2
fn difference(&mut self, x: Order) {
self.ask.pop()
.map(|Reverse(a)| a - x)
.map(|a| if a.quantity != 0 { self.ask.push(Reverse(a)); } );
self.bid.pop()
.map(|b| b - x)
.map(|b| if b.quantity != 0 { self.bid.push(b); } );
}
/// postcondition:
/// stock.quantity - new_stock.quantity = ret.quantity * 2
fn deal(&mut self) -> Option<Deal> {
self.ask.peek()
.zip(self.bid.peek())
.and_then(|(Reverse(a), b)| if a <= b { Some((*a,*b)) } else { None })
.map(|(a, b)| {
self.difference(a & b);
Deal::new(a, b)
})
}
}
Многопоточное программирование сложная штука, и в расте, и где-либо еще. Тупо, как мне кажется, не хватает примитивов - не тех которые атомики, а на уровне компоновки. Сейчас многопоточное выглядит, сорян за метафору - как одновременная обжарка и заморозка мясного. В обычном программировании у нас есть функции, структуры, петли (loop), рекурсия - у раста, кстати, прекрасные примитивы для работы с петлями (std::iter::successors и std::iter::from_fn) - и мы можем действительно создавать хорошие системы, но относительно медленные. Поэтому ускоряем самым тупым и простым способом - мутексом (что-то типа static STOCK: Mutex<Stock> = Mutex::new(Stock::new())), не забывая подавать ордеры пачками (batch), компенсируя стоимость лока.
Пишем юнит тесты, строим графиками три распределения (на рисунке ниже), и одно из них (парето) взято по логарифмической шкале. На графике в течение года четыре клиента пытаются выиграть у рынка на каждой минуте, а три фонда - со сделками раз в день (что-то типа Client{vol: 0.01, p: 0.5, quantity: 1000, distribution, s: Strategy::Limit}).
pic
Три распределения
Для сравнения график (геометрического) броуновского движения актива с волатильностью в один процент на день или 15.7 % в год.
pic
Броуновское движение
Исполнение биржи (тикера) довольно-таки прямолинейное, и для реализации децентрализованной биржи нам нужен еще и хеш, и проверка голосованием - отправляем (на проверку) результат сделки - если не ок, берем версию ребят, в противном случае коммитимся после “драйрана” (dry_run). Хеш берем равным ордеру юзера и результату сделки. “ДрайРан” удобно вынести в “трэйт”, и для реализации без багов помогает соблюдение пост-условий.
impl Stock  {
/// postcondition:
/// stock.quantity == new_stock.quantity
fn ask_dry_run(&mut self, order: Order) -> Vec<Deal> {
self.ask.push(Reverse(order));
let (quantity, deals) = self.execute_ask_dry_run();
if quantity != order.quantity { self.ask.pop(); }
deals
}
/// postcondition:
/// stock.bid.quantity == new_stock.bid.quantity
fn execute_ask_dry_run(&mut self) -> (u32, Vec<Deal>) {
let deals = self.execute();
for d in &deals { self.bid.push(d.bid); }
(deals.iter().map(|d| d.quantity).sum(), deals)
}
}
Раст не “эрланг”, но что-то умеет в сообщениях: довольно удобно добавить обратный адрес (часть канала) в сообщение:
impl Host {
fn ask_verify(&self, p: Payload) -> Vec<Receiver<Msg>> {
self.hosts.iter().map(|host| {
let (tx, rx) = channel();
host.send((p, tx));
return rx;
})
.collect()
}
}
На сегодня все. Удачного кодинга -Источник
 
Loading...
Error