Skip to content

Commit

Permalink
Merge pull request #1 from Dnomyar/add-binary-tree
Browse files Browse the repository at this point in the history
Add binary tree
  • Loading branch information
Dnomyar authored Dec 3, 2018
2 parents eede7c2 + 0349d77 commit 8ab25b4
Show file tree
Hide file tree
Showing 9 changed files with 588 additions and 78 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
.idea/**/dictionaries
.idea/**/shelf

# Sensitive or high-churn files
# Sensitive or depth-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
Expand Down
30 changes: 24 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,29 @@ This project following the hexagonal architecture :
- the package `domain` contains the business logic and the business objects.


## Efficiency of operations
- `N`ew order in the book : `O(log(book_depth))`
- `U`pdate an order in the book : `O(log(book_depth))`
- `D`elete an order in the book : `O(log(book_depth))`

Overall, the complexity of the algorithm is `O(n * log(book_depth))` where `n` is the number of operations.


### Data structure: immutable indexed AVL Tree
#### Description
To have this this complexity, the data structure used is an immutable AVL tree (self-balanced tree). The tree is transformed to be able to have this interface :
```scala
sealed trait AVLIndexedTree[+T] {
def insert[U >: T](index: Int, elementToInsert: U): AVLIndexedTree[U]
def delete[U >: T](index: Int): AVLIndexedTree[U]
def updated[U >: T](index: Int, elementToInsert: U): AVLIndexedTree[U]
}
```
This interface allows to insert, delete and update nodes of the tree using indexes instead of values.

#### How is works ?
Every node know the number of children in the left and the right side. This enable to know the which side to choose for a given index.

## Usage

### Run
Expand Down Expand Up @@ -38,8 +61,6 @@ Where n is the book depth and price is in $.
sbt test
```

## Complexity
The complexity of the algorithm itself is `O(|updateOrderBookCommands| + book_depth)`. The parser part could probably be improved to be quicker.


## Example
Expand All @@ -54,13 +75,10 @@ U B 1 5 40
```

```
sbt "run lot-of-updates.txt 10.0 100"
sbt "run lot-of-updates.txt 10.0 2"
```
- Output
```
50.0,40,60.0,10
40.0,40,70.0,20
```
- Visually

![Visual example](./images/example.png)
11 changes: 5 additions & 6 deletions src/main/scala/order/book/domain/OrderBook.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import scala.util.Try

case class OrderBook private[OrderBook] (bids: OrderBookSide, asks: OrderBookSide) {

// O(1)
def applyChange(changeRequest: UpdateOrderBookCommand): Try[OrderBook] =
updateSide(changeRequest.side)(_.applyOrderChange(
changeRequest.instruction,
Expand All @@ -26,17 +25,17 @@ case class OrderBook private[OrderBook] (bids: OrderBookSide, asks: OrderBookSid
}


// O(book_depth)
def project(tickSize: TickSize) : List[OrderBookProjection] =
bids.orders.zip(asks.orders)
def project(bookSize: Int, tickSize: TickSize) : List[OrderBookProjection] =
(0 until bookSize)
.zipAll(bids.getOrders, 0, EmptyOrder).map(_._2)
.zipAll(asks.getOrders, EmptyOrder, EmptyOrder)
.map((OrderBookProjection.fromOrders(tickSize) _).tupled).toList

}


object OrderBook {
def apply(bookDepth: Int): OrderBook =
new OrderBook(OrderBookSide(bookDepth), OrderBookSide(bookDepth))
def empty: OrderBook = new OrderBook(OrderBookSide.empty, OrderBookSide.empty)
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,13 @@ import scala.util.Try

class OrderBookComputerImplementation extends OrderBookComputer {

// O(|updateOrderBookCommands| + book_depth)
def compute(updateOrderBookCommands: Iterator[UpdateOrderBookCommand], tickSize: TickSize, bookDepth: Int): Try[List[OrderBookProjection]] =
updateOrderBookCommands
.filter(_.priceLevelIndex <= bookDepth)
.foldLeft(Try(OrderBook(bookDepth))){
.foldLeft(Try(OrderBook.empty)){
case (bookTry, order) => bookTry.flatMap(_.applyChange(order))
}
.map(_.project(tickSize))
.map(_.project(bookDepth, tickSize))


}
48 changes: 11 additions & 37 deletions src/main/scala/order/book/domain/OrderBookSide.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,54 +2,28 @@ package order.book.domain

import order.book.domain.commands.OrderBookInstruction
import order.book.domain.commands.OrderBookInstruction.{Delete, New, Update}
import order.book.domain.errors.{CannotDeleteEmptyOrderError, UpdatingNonExistingValueError}
import order.book.domain.datastructure.AVLIndexedTree

import scala.util.{Failure, Success, Try}
import scala.util.{Success, Try}

case class OrderBookSide private[OrderBookSide] (orders: Vector[OrderBookOrder]) {
class OrderBookSide private[OrderBookSide] (orders: AVLIndexedTree[OrderBookOrder]) {

// O(1)
def applyOrderChange(instruction: OrderBookInstruction,
priceLevelIndex: Int,
price: TickPrice,
quantity: Quantity): Try[OrderBookSide] = {

val updateOrdersIndexed = updateOrders(priceLevelIndex)(_)

instruction match {
case New =>
updateOrdersIndexed{
case order: Order => Success(order.plus(quantity))
case EmptyOrder => Success(EmptyOrder.withPrice(price).plus(quantity))
}

case Update =>
updateOrdersIndexed{
case order: Order => Success(order.replace(quantity))
case EmptyOrder => Failure(new UpdatingNonExistingValueError(priceLevelIndex, price, quantity))
}

case Delete =>
updateOrdersIndexed{
case order: Order => order.minus(quantity)
case EmptyOrder => Failure(new CannotDeleteEmptyOrderError(priceLevelIndex, price, quantity))
}
}
val zeroStartingIndex = priceLevelIndex - 1
Success(instruction match {
case New => new OrderBookSide(orders.insert(zeroStartingIndex, Order(price, quantity)))
case Update => new OrderBookSide(orders.updated(zeroStartingIndex, Order(price, quantity)))
case Delete => new OrderBookSide(orders.delete(zeroStartingIndex))
})
}

def getOrders: List[OrderBookOrder] = orders.toList

// O(updateOrders) = O(updateOrder)
private def updateOrders(priceLevelIndex: Int)(updateOrder: OrderBookOrder => Try[OrderBookOrder]): Try[OrderBookSide] = {
val vectorIndex = priceLevelIndex - 1

for{
orderToUpdate <- Try(orders(vectorIndex)) // O(1)
newOrder <- updateOrder(orderToUpdate)
ordersUpdated <- Try(orders.updated(vectorIndex, newOrder)) // O(1)
} yield copy(orders = ordersUpdated)
}
}

object OrderBookSide {
def apply(bookDepth: Int): OrderBookSide = new OrderBookSide(Vector.fill(bookDepth)(EmptyOrder))
def empty: OrderBookSide = new OrderBookSide(AVLIndexedTree.empty)
}
Loading

0 comments on commit 8ab25b4

Please sign in to comment.