Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
5dfa0e3
Added a Divisible mixin trait; example os usage is contained in the s…
Mar 4, 2017
e0bf017
Added a sanity check on the input to the split function.
Mar 4, 2017
ebc11da
Merge updates from develop.
Mar 9, 2017
e631418
Merge updates.
Mar 9, 2017
75da648
Refactored the Divisible trait.
Mar 9, 2017
6466377
Merge pull request #8 from EconomicSL/splittable-orders
bherd-rb Mar 10, 2017
bff7c19
Added a new package for multi-unit order books.
Mar 13, 2017
c5e3a10
Copied over files from singleunit package.
Mar 13, 2017
a15bafc
More progress towards multi-unit order book implementation.
Mar 13, 2017
856433b
Fixed merge conflicts.
Mar 16, 2017
a234b20
Draft of new SortedOrderBooks...
Mar 19, 2017
59b21f2
Refactored to take into account changes to Sorted*.
Mar 19, 2017
67078d9
Cleaned up the type signatures.
Mar 19, 2017
12e9555
Refactored to take into account changes to Sorted*.
Mar 19, 2017
13bf707
Project reorg.
Mar 19, 2017
6adefb5
Removed unnecessary + operator; just use updated method.
Mar 19, 2017
d930403
Started work on FourHeapOrderBook logic
Mar 22, 2017
a0ec10f
Fixed merge conflicts.
Mar 27, 2017
e9011bd
Continued work...
Mar 28, 2017
89be6a9
Merge updates form develop.
Apr 3, 2017
5116881
Slowly making progess!
Apr 3, 2017
f9d0eb0
Fixed compiler errors.
Apr 3, 2017
9df2ea0
Added test to travis.yml
Apr 3, 2017
8e81dec
Significant progress!
Apr 3, 2017
95b319c
Fixed orderings.
Apr 3, 2017
bb940d0
More progress...I hope...
Apr 4, 2017
f879acf
fixed merge commits.
Apr 6, 2017
61aebe2
fixed merge conflitcts.
Apr 6, 2017
01433b8
Merge pull request #13 from EconomicSL/multi-unit-sorted-orders
Apr 6, 2017
155040d
Fixed merge conflicts.
Jun 5, 2017
035235d
Fixed compiler errors and warnings.
Jun 5, 2017
fb9b336
Created a new orders package.
Jun 5, 2017
868c422
Started work on generic orderbook.
Jun 7, 2017
8813e90
Now compiling!
Jun 7, 2017
cd026cd
A bit lost now...
Jun 7, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,9 @@ lazy val commonSettings = Seq(
compileOrder := CompileOrder.ScalaThenJava
)


// Define additional testing configurations
lazy val Functional = config("functional") extend Test


// finally define the full project build settings
lazy val core = (project in file(".")).
settings(commonSettings: _*).
Expand All @@ -43,4 +41,4 @@ lazy val core = (project in file(".")).
"org.scalatest" %% "scalatest" % "3.0.1" % "functional, test"
),
parallelExecution in Functional := true
)
)
2 changes: 2 additions & 0 deletions src/main/scala/org/economicsl/auctions/Price.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ case class Price(value: Currency) extends AnyVal {
Price(value + that.value)
}

def unary_- : Price = Price(-value) // todo consider making Price fully numeric to get this for free!

}


Expand Down
2 changes: 2 additions & 0 deletions src/main/scala/org/economicsl/auctions/Quantity.scala
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ case class Quantity(value: Long) extends AnyVal {
Quantity(value - that.value)
}

def unary_- : Quantity = Quantity(-value) // todo consider making Quantity fully numeric to get this for free!

}


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
/*
Copyright 2017 EconomicSL

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package org.economicsl.auctions.multiunit.orderbooks

import org.economicsl.auctions.Tradable
import org.economicsl.auctions.multiunit.orders.{AskOrder, BidOrder}


class FourHeapOrderBook[K, T <: Tradable] private(matched: MatchedOrders[K, T], unMatched: UnMatchedOrders[K, T]) {

require(matched.bidOrders.headOption.forall{ case (_, b1) => unMatched.bidOrders.headOption.forall{ case (_, b2) => b1.limit >= b2.limit } })

require(unMatched.askOrders.headOption.forall{ case (_, a1) => matched.askOrders.headOption.forall{ case (_, a2) => a1.limit >= a2.limit } })

/*
/** Add a new `AskOrder` to the `OrderBook`.
*
* @param key
* @param order
* @return
* @note adding a new `AskOrder` is non-trivial and there are several cases to consider.
*/
def + (key: K, order: AskOrder[T]): FourHeapOrderBook[K, T] = {
(unMatched.bidOrders.headOption, matched.askOrders.headOption) match {
case (Some((out, bidOrder)), Some((in, askOrder))) =>
if (order.limit <= bidOrder.limit && askOrder.limit <= bidOrder.limit) {
val remainingUnMatched = unMatched - (out, bidOrder)
val (updatedMatched, additionalUnMatched) = matched + (key -> order, out -> bidOrder)
val default = new FourHeapOrderBook(updatedMatched, remainingUnMatched)
additionalUnMatched.fold(default)(orders => new FourHeapOrderBook(updatedMatched, remainingUnMatched.mergeWith(orders)))
} else if (order.limit <= askOrder.limit) {
val (updatedMatched, additionalUnMatched) = matched - (in, askOrder) // what happens if queue is empty?
updatedMatched + (key -> order, ???) additionalUnMatched.bidOrders
new FourHeapOrderBook(updatedMatched, unMatched.mergeWith(additionalUnMatched))
???
} else {
new FourHeapOrderBook(matched, unMatched + (key, order))
}
case (Some((out, bidOrder)), None) =>
if (order.limit <= bidOrder.limit) {
val (updatedMatched, optionalUnMatched) = matched + (key -> order, out -> bidOrder)
optionalUnMatched match {
case Some(additionalUnMatched) =>
new FourHeapOrderBook(updatedMatched, unMatched.mergeWith(additionalUnMatched))
case None =>
new FourHeapOrderBook(updatedMatched, unMatched)
}
} else {
new FourHeapOrderBook(matched, unMatched + (key, order))
}
case (None, Some((in ,askOrder))) =>
if (order.limit <= askOrder.limit) {
//val (updatedMatched, additionalUnMatched) = matched.swap(key -> order, in)
//new FourHeapOrderBook(updatedMatched, unMatched.mergeWith(additionalUnMatched))
???
} else {
new FourHeapOrderBook(matched, unMatched + (key, order))
}
case (None, None) => new FourHeapOrderBook(matched, unMatched + (key, order))
}
}

/** Add a new `BidOrder` to the `OrderBook`.
*
* @param key
* @param order
* @return
*/
def + (key: K, order: BidOrder[T]): FourHeapOrderBook[K, T] = {
(matched.bidOrders.headOption, unMatched.askOrders.headOption) match {
case (Some((in, bidOrder)), Some((out, askOrder))) =>
if (order.limit >= askOrder.limit && bidOrder.limit >= askOrder.limit) {
val residualUnMatched = unMatched - out
val (updatedMatched, optionalUnMatched) = matched + (out -> askOrder, key -> order)
optionalUnMatched match {
case Some(additionalUnMatched) =>
new FourHeapOrderBook(updatedMatched, residualUnMatched.mergeWith(additionalUnMatched))
case None =>
new FourHeapOrderBook(updatedMatched, residualUnMatched)
}
} else if (order.limit >= bidOrder.limit) {
//val (updatedMatched, additionalUnMatched) = matched.swap(key -> order, in)
//new FourHeapOrderBook(updatedMatched, unMatched.mergeWith(additionalUnMatched))
???
} else {
new FourHeapOrderBook(matched, unMatched + (key, order))
}
case (Some((in ,bidOrder)), None) =>
if (order.limit >= bidOrder.limit) {
//val (updatedMatched, additionalUnMatched) = matched.swap(key -> order, in)
//new FourHeapOrderBook(updatedMatched, unMatched.mergeWith(additionalUnMatched))
???
} else {
new FourHeapOrderBook(matched, unMatched + (key, order))
}
case (None, Some((out, askOrder))) =>
if (order.limit >= askOrder.limit) {
val (updatedMatched, optionalUnMatched) = matched + (out -> askOrder, key -> order)
optionalUnMatched match {
case Some(additionalUnMatched) =>
new FourHeapOrderBook(updatedMatched, unMatched.mergeWith(additionalUnMatched))
case None =>
new FourHeapOrderBook(updatedMatched, unMatched)
}
} else {
new FourHeapOrderBook(matched, unMatched + (key, order))
}
case (None, None) => new FourHeapOrderBook(matched, unMatched + (key, order))
}
}

def contains(key: K): Boolean = matched.contains(key) || unMatched.contains(key)

def isEmpty: Boolean = matched.isEmpty && unMatched.isEmpty

def nonEmpty: Boolean = matched.nonEmpty || unMatched.nonEmpty

/*def - (key: K): FourHeapOrderBook[K, T] = {
if (matched.contains(key) && unMatched.contains(key)) {
val (residualMatched, additionalUnMatched) = matched - key
val residualUnMatched = unMatched - key
new FourHeapOrderBook(residualMatched, residualUnMatched.mergeWith(additionalUnMatched))
} else if (matched.contains(key)) {
val (residualMatched, additionalUnMatched) = matched - key
new FourHeapOrderBook(residualMatched, unMatched.mergeWith(additionalUnMatched))
} else {
val residualUnMatched = unMatched - key
new FourHeapOrderBook(matched, unMatched.mergeWith(residualUnMatched))
}
}

/** Update an existing `AskOrder`.
*
* @note If an `AskOrder[T]` associated with the `key` already exists in the `OrderBook`, then the existing order
* (or orders in the case that the previously submitted order was split) are removed from this `OrderBook`
* before the `order` is added to this `OrderBook`.
*/
def updated(key: K, order: AskOrder[T]): FourHeapOrderBook[K, T] = {
if (contains(key)) this - key + (key -> order) else this + (key -> order)
}

/** Update an existing `BidOrder`.
*
* @note If an `AskOrder[T]` associated with the `key` already exists in the `OrderBook`, then the existing order
* (or orders in the case that the previously submitted order was split) are removed from this `OrderBook`
* before the `order` is added to this `OrderBook`.
*/
def updated(key: K, order: BidOrder[T]): FourHeapOrderBook[K, T] = {
if (contains(key)) this - key + (key -> order) else this + (key -> order)
}*/

*/
}


object FourHeapOrderBook {

def empty[K, T <: Tradable](implicit askOrdering: Ordering[K], bidOrdering: Ordering[K]): FourHeapOrderBook[K, T] = {
val matchedOrders = MatchedOrders.empty[K, T](askOrdering.reverse, bidOrdering)
val unMatchedOrders = UnMatchedOrders.empty[K, T](askOrdering, bidOrdering.reverse)
new FourHeapOrderBook(matchedOrders, unMatchedOrders)
}


}

Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
Copyright 2017 EconomicSL

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package org.economicsl.auctions.multiunit.orderbooks

import org.economicsl.auctions.{Quantity, Tradable}
import org.economicsl.auctions.multiunit.orders.{AskOrder, BidOrder}


protected[orderbooks] final case class MatchedOrders[K, T <: Tradable](askOrders: SortedAskOrders[K, T],
bidOrders: SortedBidOrders[K, T]) {

/* Number of units supplied must equal the number of units demanded. */
require(askOrders.numberUnits == bidOrders.numberUnits)

/* Limit price of the first `BidOrder` must exceed the limit price of the first `AskOrder`. */
require(bidOrders.headOption.forall{ case (_, bidOrder) => askOrders.headOption.forall{ case (_, askOrder) => bidOrder.limit >= askOrder.limit } })

val askOrdering: Ordering[K] = askOrders.ordering

val bidOrdering: Ordering[K] = bidOrders.ordering

/** Add a new pair of ask and bid orders into the MatchedOrders.
*
* @note Unless the quantities of the `AskOrder` and `BidOrder` match exactly, then the larger order must
* be rationed in order to maintain the invariant that the total quantity of supply matches the total quantity
* of demand.
*/
def + (kv1: (K, AskOrder[T]), kv2: (K, BidOrder[T])): (MatchedOrders[K, T], Option[UnMatchedOrders[K, T]]) = {
val excessDemand = kv2._2.quantity - kv1._2.quantity
if (excessDemand < Quantity(0)) {
val (matched, rationed) = (kv1._2.withQuantity(kv2._2.quantity), kv1._2.withQuantity(-excessDemand)) // split the askOrder into a matched and rationed component
val rationedOrders = UnMatchedOrders.empty[K, T](askOrders.ordering, bidOrders.ordering)
(MatchedOrders(askOrders + (kv1._1, matched), bidOrders + (kv2._1, kv2._2)), Some(rationedOrders + (kv1._1, rationed)))
} else if (excessDemand > Quantity(0)) {
val (matched, rationed) = (kv2._2.withQuantity(kv1._2.quantity), kv2._2.withQuantity(excessDemand)) // split the bidOrder into a matched and residual component
val rationedOrders = UnMatchedOrders.empty[K, T](askOrders.ordering, bidOrders.ordering)
(MatchedOrders(askOrders + (kv1._1, kv1._2), bidOrders + (kv2._1, matched)), Some(rationedOrders + (kv2._1, rationed)))
} else {
(MatchedOrders(askOrders + (kv1._1, kv1._2), bidOrders + (kv2._1, kv2._2)), None)
}
}

def - (key: K, order: AskOrder[T]): (MatchedOrders[K, T], UnMatchedOrders[K, T]) = {
val remainingAskOrders = askOrders - (key, order)
val (matched, unMatched) = bidOrders.splitAt(order.quantity)
val empty = SortedAskOrders.empty[K, T](askOrdering)
(MatchedOrders(remainingAskOrders, matched), UnMatchedOrders(empty, unMatched))
}

def - (key: K, order: BidOrder[T]): (MatchedOrders[K, T], UnMatchedOrders[K, T]) = {
val remainingBidOrders = bidOrders - (key, order)
val (matched, unMatched) = askOrders.splitAt(order.quantity)
val empty = SortedBidOrders.empty[K, T](bidOrdering)
(MatchedOrders(matched, remainingBidOrders), UnMatchedOrders(unMatched, empty))
}

def contains(key: K): Boolean = askOrders.contains(key) || bidOrders.contains(key)

def isEmpty: Boolean = askOrders.isEmpty && bidOrders.isEmpty

def nonEmpty: Boolean = askOrders.nonEmpty && bidOrders.nonEmpty

/*
def removeAndReplace(askOrder: (UUID, AskOrder[T]), bidOrder: (UUID, BidOrder[T])): MatchedOrders[K, T] = {
new MatchedOrders(askOrders - askOrder._1, bidOrders.updated(bidOrder._1, bidOrder._2))
}

def updated(askOrder: (UUID, AskOrder[T]), bidOrder: (UUID, BidOrder[T])): MatchedOrders[K, T] = {
new MatchedOrders(askOrders + askOrder, bidOrders + bidOrder)
}
*/

}


object MatchedOrders {

/** Create an instance of `MatchedOrders`.
*
* @return
* @note the heap used to store store the `AskOrder` instances is ordered from high to low
* based on `limit` price; the heap used to store store the `BidOrder` instances is
* ordered from low to high based on `limit` price.
*/
def empty[K, T <: Tradable](askOrdering: Ordering[K], bidOrdering: Ordering[K]): MatchedOrders[K, T] = {
new MatchedOrders(SortedAskOrders.empty(askOrdering), SortedBidOrders.empty(bidOrdering))
}

}
Loading