From 5dfa0e3dcd35ef7a3378ae74d0ca062d9e1ee063 Mon Sep 17 00:00:00 2001 From: davidrpugh Date: Sat, 4 Mar 2017 08:35:44 +0300 Subject: [PATCH 01/25] Added a Divisible mixin trait; example os usage is contained in the sandbox. --- .../org/economicsl/auctions/Divisible.scala | 25 +++++++++++++++++++ .../scala/org/economicsl/auctions/sandbox.sc | 21 +++++++++++++++- 2 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 src/main/scala/org/economicsl/auctions/Divisible.scala diff --git a/src/main/scala/org/economicsl/auctions/Divisible.scala b/src/main/scala/org/economicsl/auctions/Divisible.scala new file mode 100644 index 0000000..085c91a --- /dev/null +++ b/src/main/scala/org/economicsl/auctions/Divisible.scala @@ -0,0 +1,25 @@ +/* +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 + + +/** Mixin trait providing behavior necessary to split an order into two orders. */ +trait Divisible[+T <: Tradable, O <: Order[T] with SinglePricePoint[T] with Divisible[T, O]] { + this: Order[T] with SinglePricePoint[T] => + + def split(residual: Quantity): (O, O) + +} \ No newline at end of file diff --git a/src/main/scala/org/economicsl/auctions/sandbox.sc b/src/main/scala/org/economicsl/auctions/sandbox.sc index 576c392..4cc6ce9 100644 --- a/src/main/scala/org/economicsl/auctions/sandbox.sc +++ b/src/main/scala/org/economicsl/auctions/sandbox.sc @@ -53,4 +53,23 @@ val orderBook5 = orderBook4 + order8 // take a look at paired orders val (pairedOrders, _) = orderBook5.takeWhileMatched -pairedOrders.toList \ No newline at end of file +pairedOrders.toList + + +// Why can this not be made covariant in T? Can divisible orders be constructed using an auxiliary constructor on LimitAskOrder? +case class DivisibleLimitAskOrder[T <: Tradable](issuer: UUID, limit: Price, quantity: Quantity, tradable: T) extends + LimitAskOrder[T] with Divisible[T, DivisibleLimitAskOrder[T]] { + + def split(residual: Quantity): (DivisibleLimitAskOrder[T], DivisibleLimitAskOrder[T]) = { + val remaining = Quantity(quantity.value - residual.value) + (this.copy(quantity = remaining), this.copy(quantity = residual)) + } + +} + + +// usage example for a divisible order +val divisibleOrder: DivisibleLimitAskOrder[Google] = DivisibleLimitAskOrder(issuer, Price(9.56), Quantity(3), google) +val(filled, residual) = divisibleOrder.split(Quantity(2)) +filled.quantity +residual.quantity \ No newline at end of file From e0bf017fa1b4bff056c5428e6303cce432d99db0 Mon Sep 17 00:00:00 2001 From: davidrpugh Date: Sat, 4 Mar 2017 10:08:38 +0300 Subject: [PATCH 02/25] Added a sanity check on the input to the split function. --- src/main/scala/org/economicsl/auctions/sandbox.sc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/scala/org/economicsl/auctions/sandbox.sc b/src/main/scala/org/economicsl/auctions/sandbox.sc index 4cc6ce9..a37181b 100644 --- a/src/main/scala/org/economicsl/auctions/sandbox.sc +++ b/src/main/scala/org/economicsl/auctions/sandbox.sc @@ -61,6 +61,7 @@ case class DivisibleLimitAskOrder[T <: Tradable](issuer: UUID, limit: Price, qua LimitAskOrder[T] with Divisible[T, DivisibleLimitAskOrder[T]] { def split(residual: Quantity): (DivisibleLimitAskOrder[T], DivisibleLimitAskOrder[T]) = { + require(residual.value < this.quantity.value) // can this be check be lifted into the type system? val remaining = Quantity(quantity.value - residual.value) (this.copy(quantity = remaining), this.copy(quantity = residual)) } From 75da6489c51caee892e9ca46dda498a0fbc2226d Mon Sep 17 00:00:00 2001 From: davidrpugh Date: Thu, 9 Mar 2017 16:05:33 +0300 Subject: [PATCH 03/25] Refactored the Divisible trait. --- .../auctions/{ => multiunit}/Divisible.scala | 8 +++++--- .../auctions/multiunit/LimitAskOrder.scala | 13 +++++++++--- .../auctions/multiunit/LimitBidOrder.scala | 11 ++++++++-- .../auctions/multiunit/MarketAskOrder.scala | 10 +++++++++- .../auctions/multiunit/MarketBidOrder.scala | 10 +++++++++- .../scala/org/economicsl/auctions/sandbox.sc | 20 ++----------------- 6 files changed, 44 insertions(+), 28 deletions(-) rename src/main/scala/org/economicsl/auctions/{ => multiunit}/Divisible.scala (74%) diff --git a/src/main/scala/org/economicsl/auctions/Divisible.scala b/src/main/scala/org/economicsl/auctions/multiunit/Divisible.scala similarity index 74% rename from src/main/scala/org/economicsl/auctions/Divisible.scala rename to src/main/scala/org/economicsl/auctions/multiunit/Divisible.scala index 085c91a..ecc43be 100644 --- a/src/main/scala/org/economicsl/auctions/Divisible.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/Divisible.scala @@ -13,13 +13,15 @@ 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 +package org.economicsl.auctions.multiunit + +import org.economicsl.auctions.{Order, Quantity, Tradable} /** Mixin trait providing behavior necessary to split an order into two orders. */ -trait Divisible[+T <: Tradable, O <: Order[T] with SinglePricePoint[T] with Divisible[T, O]] { +trait Divisible[+T <: Tradable, +O <: Order[T] with SinglePricePoint[T] with Divisible[T, O]] { this: Order[T] with SinglePricePoint[T] => - def split(residual: Quantity): (O, O) + def withQuantity(residual: Quantity): O } \ No newline at end of file diff --git a/src/main/scala/org/economicsl/auctions/multiunit/LimitAskOrder.scala b/src/main/scala/org/economicsl/auctions/multiunit/LimitAskOrder.scala index 78266e5..72ab2b5 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/LimitAskOrder.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/LimitAskOrder.scala @@ -20,8 +20,8 @@ import java.util.UUID import org.economicsl.auctions.{AskOrder, Price, Quantity, Tradable} -/** Base trait for a limit order to sell some `Tradable`. */ -trait LimitAskOrder[+T <: Tradable] extends AskOrder[T] with SinglePricePoint[T] +/** Base trait for a multi-unit limit order to sell some `Tradable`. */ +trait LimitAskOrder[+T <: Tradable] extends AskOrder[T] with SinglePricePoint[T] with Divisible[T, LimitAskOrder[T]] /** Companion object for `LimitAskOrder`. @@ -37,7 +37,14 @@ object LimitAskOrder { } private[this] case class SinglePricePointImpl[+T <: Tradable](issuer: UUID, limit: Price, quantity: Quantity, tradable: T) - extends LimitAskOrder[T] + extends LimitAskOrder[T] { + + def withQuantity(residual: Quantity): LimitAskOrder[T] = { + require(residual.value < quantity.value) + copy(quantity = residual) + } + + } } diff --git a/src/main/scala/org/economicsl/auctions/multiunit/LimitBidOrder.scala b/src/main/scala/org/economicsl/auctions/multiunit/LimitBidOrder.scala index cdc258c..acf302d 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/LimitBidOrder.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/LimitBidOrder.scala @@ -20,7 +20,7 @@ import java.util.UUID import org.economicsl.auctions.{BidOrder, Price, Quantity, Tradable} /** Base trait for a limit order to buy some `Tradable`. */ -trait LimitBidOrder[+T <: Tradable] extends BidOrder[T] with SinglePricePoint[T] +trait LimitBidOrder[+T <: Tradable] extends BidOrder[T] with SinglePricePoint[T] with Divisible[T, LimitBidOrder[T]] /** Companion object for `LimitBidOrder`. @@ -36,7 +36,14 @@ object LimitBidOrder { } private[this] case class SinglePricePointImpl[+T <: Tradable](issuer: UUID, limit: Price, quantity: Quantity, tradable: T) - extends LimitBidOrder[T] + extends LimitBidOrder[T] { + + def withQuantity(residual: Quantity): LimitBidOrder[T] = { + require(residual.value < quantity.value) + copy(quantity = residual) + } + + } } diff --git a/src/main/scala/org/economicsl/auctions/multiunit/MarketAskOrder.scala b/src/main/scala/org/economicsl/auctions/multiunit/MarketAskOrder.scala index 22b1884..1bcf960 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/MarketAskOrder.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/MarketAskOrder.scala @@ -28,6 +28,7 @@ trait MarketAskOrder[+T <: Tradable] extends LimitAskOrder[T] { } + /** Companion object for `MarketAskOrder`. * * Provides constructor for default implementation of `MarketAskOrder` trait. @@ -39,7 +40,14 @@ object MarketAskOrder { } private[this] case class SinglePricePointImpl[+T <: Tradable](issuer: UUID, quantity: Quantity, tradable: T) - extends MarketAskOrder[T] + extends MarketAskOrder[T] { + + def withQuantity(residual: Quantity): MarketAskOrder[T] = { + require(residual.value < quantity.value) + copy(quantity = residual) + } + + } } diff --git a/src/main/scala/org/economicsl/auctions/multiunit/MarketBidOrder.scala b/src/main/scala/org/economicsl/auctions/multiunit/MarketBidOrder.scala index 23c599c..c0c4cb5 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/MarketBidOrder.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/MarketBidOrder.scala @@ -28,6 +28,7 @@ trait MarketBidOrder[+T <: Tradable] extends LimitBidOrder[T] { } + /** Companion object for `MarketBidOrder`. * * Provides constructor for default implementation of `MarketBidOrder` trait. @@ -39,6 +40,13 @@ object MarketBidOrder { } private[this] case class SinglePricePointImpl[+T <: Tradable](issuer: UUID, quantity: Quantity, tradable: T) - extends MarketBidOrder[T] + extends MarketBidOrder[T] { + + def withQuantity(residual: Quantity): MarketBidOrder[T] = { + require(residual.value < quantity.value) + copy(quantity = residual) + } + + } } diff --git a/src/main/scala/org/economicsl/auctions/sandbox.sc b/src/main/scala/org/economicsl/auctions/sandbox.sc index c1579f6..722c49f 100644 --- a/src/main/scala/org/economicsl/auctions/sandbox.sc +++ b/src/main/scala/org/economicsl/auctions/sandbox.sc @@ -55,22 +55,6 @@ val orderBook5 = orderBook4 + order8 val (pairedOrders, _) = orderBook5.takeWhileMatched pairedOrders.toList - -// Why can this not be made covariant in T? Can divisible orders be constructed using an auxiliary constructor on LimitAskOrder? -case class DivisibleLimitAskOrder[T <: Tradable](issuer: UUID, limit: Price, quantity: Quantity, tradable: T) extends - LimitAskOrder[T] with Divisible[T, DivisibleLimitAskOrder[T]] { - - def split(residual: Quantity): (DivisibleLimitAskOrder[T], DivisibleLimitAskOrder[T]) = { - require(residual.value < this.quantity.value) // can this be check be lifted into the type system? - val remaining = Quantity(quantity.value - residual.value) - (this.copy(quantity = remaining), this.copy(quantity = residual)) - } - -} - - -// usage example for a divisible order -val divisibleOrder: DivisibleLimitAskOrder[Google] = DivisibleLimitAskOrder(issuer, Price(9.56), Quantity(3), google) -val(filled, residual) = divisibleOrder.split(Quantity(2)) -filled.quantity +// currently all multi-unit orders are divisible... +val residual = order7.withQuantity(Quantity(2)) residual.quantity \ No newline at end of file From bff7c19494bf60ddffb71a80cb6fc2a7c978c344 Mon Sep 17 00:00:00 2001 From: davidrpugh Date: Mon, 13 Mar 2017 08:59:16 +0300 Subject: [PATCH 04/25] Added a new package for multi-unit order books. --- .../multiunit/orderbooks/package.scala | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 src/main/scala/org/economicsl/auctions/multiunit/orderbooks/package.scala diff --git a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/package.scala b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/package.scala new file mode 100644 index 0000000..5783629 --- /dev/null +++ b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/package.scala @@ -0,0 +1,20 @@ +/* +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 + + +/** Documentation for multi-unit orderbooks goes here! */ +package object orderbooks From c5e3a10931faacc29c9c6a24c9c89df0ff34d7af Mon Sep 17 00:00:00 2001 From: davidrpugh Date: Mon, 13 Mar 2017 09:15:54 +0300 Subject: [PATCH 05/25] Copied over files from singleunit package. --- .../orderbooks/FourHeapOrderBook.scala | 89 +++++++++++++++++++ .../multiunit/orderbooks/MatchedOrders.scala | 82 +++++++++++++++++ .../orderbooks/UnMatchedOrders.scala | 60 +++++++++++++ .../multiunit/orderbooks/package.scala | 73 ++++++++++++++- 4 files changed, 303 insertions(+), 1 deletion(-) create mode 100644 src/main/scala/org/economicsl/auctions/multiunit/orderbooks/FourHeapOrderBook.scala create mode 100644 src/main/scala/org/economicsl/auctions/multiunit/orderbooks/MatchedOrders.scala create mode 100644 src/main/scala/org/economicsl/auctions/multiunit/orderbooks/UnMatchedOrders.scala diff --git a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/FourHeapOrderBook.scala b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/FourHeapOrderBook.scala new file mode 100644 index 0000000..d8aeadd --- /dev/null +++ b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/FourHeapOrderBook.scala @@ -0,0 +1,89 @@ +/* +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.{LimitAskOrder, LimitBidOrder} + + +class FourHeapOrderBook[T <: Tradable] private(matchedOrders: MatchedOrders[T], unMatchedOrders: UnMatchedOrders[T]) { + + def - (order: LimitAskOrder[T]): FourHeapOrderBook[T] = { + if (unMatchedOrders.contains(order)) { + new FourHeapOrderBook(matchedOrders, unMatchedOrders - order) + } else { + val bidOrder = matchedOrders.bidOrders.head + new FourHeapOrderBook(matchedOrders - (order, bidOrder), unMatchedOrders + bidOrder) + } + } + + def - (order: LimitBidOrder[T]): FourHeapOrderBook[T] = { + if (unMatchedOrders.contains(order)) { + new FourHeapOrderBook(matchedOrders, unMatchedOrders - order) + } else { + val askOrder = matchedOrders.askOrders.head + new FourHeapOrderBook(matchedOrders - (askOrder, order), unMatchedOrders + askOrder) + } + } + + def + (order: LimitAskOrder[T]): FourHeapOrderBook[T] = { + (matchedOrders.askOrders.headOption, unMatchedOrders.bidOrders.headOption) match { + case (Some(askOrder), Some(bidOrder)) if order.limit <= bidOrder.limit && askOrder.limit <= bidOrder.limit => + new FourHeapOrderBook(matchedOrders + (order, bidOrder), unMatchedOrders - bidOrder) + case (None, Some(bidOrder)) if order.limit <= bidOrder.limit => + new FourHeapOrderBook(matchedOrders + (order, bidOrder), unMatchedOrders - bidOrder) + case (Some(askOrder), Some(_)) if order.limit < askOrder.limit => + new FourHeapOrderBook(matchedOrders.replace(askOrder, order), unMatchedOrders + askOrder) + case _ => + new FourHeapOrderBook(matchedOrders, unMatchedOrders + order) + } + } + + def + (order: LimitBidOrder[T]): FourHeapOrderBook[T] = { + (matchedOrders.bidOrders.headOption, unMatchedOrders.askOrders.headOption) match { + case (Some(bidOrder), Some(askOrder)) if order.limit >= askOrder.limit && bidOrder.limit >= askOrder.limit => + new FourHeapOrderBook(matchedOrders + (askOrder, order), unMatchedOrders - askOrder) + case (None, Some(askOrder)) if order.limit >= askOrder.limit => + new FourHeapOrderBook(matchedOrders + (askOrder, order), unMatchedOrders - askOrder) + case (Some(bidOrder), Some(_)) if order.limit > bidOrder.limit => + new FourHeapOrderBook(matchedOrders.replace(bidOrder, order), unMatchedOrders + bidOrder) + case _ => + new FourHeapOrderBook(matchedOrders, unMatchedOrders + order) + } + } + + def takeWhileMatched: (Stream[(LimitAskOrder[T], LimitBidOrder[T])], FourHeapOrderBook[T]) = { + (matchedOrders.zipped, withEmptyMatchedOrders) + } + + private[this] def withEmptyMatchedOrders: FourHeapOrderBook[T] = { + val (askOrdering, bidOrdering) = (matchedOrders.askOrdering, matchedOrders.bidOrdering) + new FourHeapOrderBook[T](MatchedOrders.empty(askOrdering, bidOrdering), unMatchedOrders) + } + +} + + +object FourHeapOrderBook { + + def empty[T <: Tradable](implicit askOrdering: Ordering[LimitAskOrder[T]], bidOrdering: Ordering[LimitBidOrder[T]]): FourHeapOrderBook[T] = { + val matchedOrders = MatchedOrders.empty(askOrdering.reverse, bidOrdering.reverse) + val unMatchedOrders = UnMatchedOrders.empty(askOrdering, bidOrdering) + new FourHeapOrderBook(matchedOrders, unMatchedOrders) + } + +} + diff --git a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/MatchedOrders.scala b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/MatchedOrders.scala new file mode 100644 index 0000000..ef40cd6 --- /dev/null +++ b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/MatchedOrders.scala @@ -0,0 +1,82 @@ +/* +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.{LimitAskOrder, LimitBidOrder} + + +private[orderbooks] class MatchedOrders[T <: Tradable] private(val askOrders: SortedAskOrders[T], val bidOrders: SortedBidOrders[T]) { + + require(askOrders.numberUnits == bidOrders.numberUnits) // number of units must be the same! + require(bidOrders.headOption.forall(bidOrder => bidOrder.limit >= askOrders.head.limit)) // value of lowest bid must exceed value of highest ask! + + def + (orders: (LimitAskOrder[T], LimitBidOrder[T])): MatchedOrders[T] = { + new MatchedOrders(askOrders + orders._1, bidOrders + orders._2) + } + + def - (orders: (LimitAskOrder[T], LimitBidOrder[T])): MatchedOrders[T] = { + new MatchedOrders(askOrders - orders._1, bidOrders - orders._2) + } + + val askOrdering: Ordering[LimitAskOrder[T]] = askOrders.ordering + + val bidOrdering: Ordering[LimitBidOrder[T]] = bidOrders.ordering + + def contains(order: LimitAskOrder[T]): Boolean = askOrders.contains(order) + + def contains(order: LimitBidOrder[T]): Boolean = bidOrders.contains(order) + + def replace(existing: LimitAskOrder[T], incoming: LimitAskOrder[T]): MatchedOrders[T] = { + new MatchedOrders(askOrders - existing + incoming, bidOrders) + } + + def replace(existing: LimitBidOrder[T], incoming: LimitBidOrder[T]): MatchedOrders[T] = { + new MatchedOrders(askOrders, bidOrders - existing + incoming) + } + + def zipped: Stream[(LimitAskOrder[T], LimitBidOrder[T])] = { + @annotation.tailrec + def loop(askOrders: SortedAskOrders[T], bidOrders: SortedBidOrders[T], pairedOrders: Stream[(LimitAskOrder[T], LimitBidOrder[T])]): Stream[(LimitAskOrder[T], LimitBidOrder[T])] = { + if (askOrders.isEmpty || bidOrders.isEmpty) { + pairedOrders + } else { + val pair = (askOrders.head, bidOrders.head) + loop(askOrders.tail, bidOrders.tail, Stream.cons(pair, pairedOrders)) + } + } + loop(askOrders, bidOrders, Stream.empty[(LimitAskOrder[T], LimitBidOrder[T])]) + } + +} + + +private[orderbooks] object MatchedOrders { + + /** Create an instance of `MatchedOrders`. + * + * @param askOrdering + * @param bidOrdering + * @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[T <: Tradable](askOrdering: Ordering[LimitAskOrder[T]], bidOrdering: Ordering[LimitBidOrder[T]]): MatchedOrders[T] = { + new MatchedOrders(SortedAskOrders.empty(askOrdering), SortedBidOrders.empty(bidOrdering)) + } + +} diff --git a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/UnMatchedOrders.scala b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/UnMatchedOrders.scala new file mode 100644 index 0000000..91083e8 --- /dev/null +++ b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/UnMatchedOrders.scala @@ -0,0 +1,60 @@ +/* +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.{LimitAskOrder, LimitBidOrder} + + +private[orderbooks] class UnMatchedOrders[T <: Tradable] private(val askOrders: SortedAskOrders[T], val bidOrders: SortedBidOrders[T]) { + + require(bidOrders.headOption.forall(bidOrder => bidOrder.limit <= askOrders.head.limit)) + + def + (order: LimitAskOrder[T]): UnMatchedOrders[T] = new UnMatchedOrders(askOrders + order, bidOrders) + + def + (order: LimitBidOrder[T]): UnMatchedOrders[T] = new UnMatchedOrders(askOrders, bidOrders + order) + + def - (order: LimitAskOrder[T]): UnMatchedOrders[T] = new UnMatchedOrders(askOrders - order, bidOrders) + + def - (order: LimitBidOrder[T]): UnMatchedOrders[T] = new UnMatchedOrders(askOrders, bidOrders - order) + + val askOrdering: Ordering[LimitAskOrder[T]] = askOrders.ordering + + val bidOrdering: Ordering[LimitBidOrder[T]] = bidOrders.ordering + + def contains(order: LimitAskOrder[T]): Boolean = askOrders.contains(order) + + def contains(order: LimitBidOrder[T]): Boolean = bidOrders.contains(order) + +} + + +private[orderbooks] object UnMatchedOrders { + + /** Create an instance of `UnMatchedOrders`. + * + * @param askOrdering + * @param bidOrdering + * @return + * @note the heap used to store store the `AskOrder` instances is ordered from low to high + * based on `limit` price; the heap used to store store the `BidOrder` instances is + * ordered from high to low based on `limit` price. + */ + def empty[T <: Tradable](askOrdering: Ordering[LimitAskOrder[T]], bidOrdering: Ordering[LimitBidOrder[T]]): UnMatchedOrders[T] = { + new UnMatchedOrders(SortedAskOrders.empty(askOrdering), SortedBidOrders.empty(bidOrdering)) + } + +} diff --git a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/package.scala b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/package.scala index 5783629..844f6dc 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/package.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/package.scala @@ -15,6 +15,77 @@ limitations under the License. */ package org.economicsl.auctions.multiunit +import org.economicsl.auctions.{Quantity, Tradable} + +import scala.collection.immutable + /** Documentation for multi-unit orderbooks goes here! */ -package object orderbooks +package object orderbooks { + + class SortedAskOrders[T <: Tradable] private(orders: immutable.TreeSet[LimitAskOrder[T]], val numberUnits: Quantity) { + + def + (order: LimitAskOrder[T]): SortedAskOrders[T] = { + new SortedAskOrders(orders + order, Quantity(numberUnits.value + order.quantity.value)) + } + + def - (order: LimitAskOrder[T]): SortedAskOrders[T] = { + new SortedAskOrders(orders - order, Quantity(numberUnits.value - order.quantity.value)) + } + + def contains(order: LimitAskOrder[T]): Boolean = orders.contains(order) + + def head: LimitAskOrder[T] = orders.head + + val headOption: Option[LimitAskOrder[T]] = orders.headOption + + val isEmpty: Boolean = orders.isEmpty + + val ordering: Ordering[LimitAskOrder[T]] = orders.ordering + + def tail: SortedAskOrders[T] = new SortedAskOrders(orders.tail, Quantity(numberUnits.value - head.quantity.value)) + + } + + object SortedAskOrders { + + def empty[T <: Tradable](ordering: Ordering[LimitAskOrder[T]]): SortedAskOrders[T] = { + new SortedAskOrders(immutable.TreeSet.empty[LimitAskOrder[T]](ordering), Quantity(0)) + } + + } + + + class SortedBidOrders[T <: Tradable] private(orders: immutable.TreeSet[LimitBidOrder[T]], val numberUnits: Quantity) { + + def + (order: LimitBidOrder[T]): SortedBidOrders[T] = { + new SortedBidOrders(orders + order, Quantity(numberUnits.value + order.quantity.value)) + } + + def - (order: LimitBidOrder[T]): SortedBidOrders[T] = { + new SortedBidOrders(orders - order, Quantity(numberUnits.value - order.quantity.value)) + } + + def contains(order: LimitBidOrder[T]): Boolean = orders.contains(order) + + def head: LimitBidOrder[T] = orders.head + + val headOption: Option[LimitBidOrder[T]] = orders.headOption + + val isEmpty: Boolean = orders.isEmpty + + val ordering: Ordering[LimitBidOrder[T]] = orders.ordering + + def tail: SortedBidOrders[T] = new SortedBidOrders(orders.tail, Quantity(numberUnits.value - head.quantity.value)) + + } + + object SortedBidOrders { + + def empty[T <: Tradable](ordering: Ordering[LimitBidOrder[T]]): SortedBidOrders[T] = { + new SortedBidOrders(immutable.TreeSet.empty[LimitBidOrder[T]](ordering), Quantity(0)) + } + + } + +} From a15bafcd927ce1962c3642d0d822c459c1e92e06 Mon Sep 17 00:00:00 2001 From: davidrpugh Date: Mon, 13 Mar 2017 20:08:47 +0300 Subject: [PATCH 06/25] More progress towards multi-unit order book implementation. --- .../multiunit/orderbooks/MatchedOrders.scala | 19 +++++++------- .../multiunit/orderbooks/package.scala | 26 ++++++++++++++++--- 2 files changed, 32 insertions(+), 13 deletions(-) diff --git a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/MatchedOrders.scala b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/MatchedOrders.scala index ef40cd6..aecc097 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/MatchedOrders.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/MatchedOrders.scala @@ -28,10 +28,18 @@ private[orderbooks] class MatchedOrders[T <: Tradable] private(val askOrders: So new MatchedOrders(askOrders + orders._1, bidOrders + orders._2) } + def ++ (orders: (TraversableOnce[LimitAskOrder[T]], TraversableOnce[LimitBidOrder[T]])): MatchedOrders[T] = { + new MatchedOrders(askOrders ++ orders._1, bidOrders ++ orders._2) + } + def - (orders: (LimitAskOrder[T], LimitBidOrder[T])): MatchedOrders[T] = { new MatchedOrders(askOrders - orders._1, bidOrders - orders._2) } + def -- (orders: (TraversableOnce[LimitAskOrder[T]], TraversableOnce[LimitBidOrder[T]])): MatchedOrders[T] = { + new MatchedOrders(askOrders -- orders._1, bidOrders -- orders._2) + } + val askOrdering: Ordering[LimitAskOrder[T]] = askOrders.ordering val bidOrdering: Ordering[LimitBidOrder[T]] = bidOrders.ordering @@ -49,16 +57,7 @@ private[orderbooks] class MatchedOrders[T <: Tradable] private(val askOrders: So } def zipped: Stream[(LimitAskOrder[T], LimitBidOrder[T])] = { - @annotation.tailrec - def loop(askOrders: SortedAskOrders[T], bidOrders: SortedBidOrders[T], pairedOrders: Stream[(LimitAskOrder[T], LimitBidOrder[T])]): Stream[(LimitAskOrder[T], LimitBidOrder[T])] = { - if (askOrders.isEmpty || bidOrders.isEmpty) { - pairedOrders - } else { - val pair = (askOrders.head, bidOrders.head) - loop(askOrders.tail, bidOrders.tail, Stream.cons(pair, pairedOrders)) - } - } - loop(askOrders, bidOrders, Stream.empty[(LimitAskOrder[T], LimitBidOrder[T])]) + ??? } } diff --git a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/package.scala b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/package.scala index 844f6dc..839d6bd 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/package.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/package.scala @@ -15,9 +15,9 @@ limitations under the License. */ package org.economicsl.auctions.multiunit -import org.economicsl.auctions.{Quantity, Tradable} +import org.economicsl.auctions.{Order, Quantity, Tradable} -import scala.collection.immutable +import scala.collection.{GenIterable, immutable} /** Documentation for multi-unit orderbooks goes here! */ @@ -29,10 +29,18 @@ package object orderbooks { new SortedAskOrders(orders + order, Quantity(numberUnits.value + order.quantity.value)) } + def ++ (additional: TraversableOnce[LimitAskOrder[T]]): SortedAskOrders[T] = { + new SortedAskOrders(orders ++ additional, Quantity(numberUnits.value + additional.reduce((a1, a2) => Quantity(a1.quantity.value + a2.quantity.value)).value)) + } + def - (order: LimitAskOrder[T]): SortedAskOrders[T] = { new SortedAskOrders(orders - order, Quantity(numberUnits.value - order.quantity.value)) } + def -- (excess: TraversableOnce[LimitAskOrder[T]]): SortedAskOrders[T] = { + new SortedAskOrders(orders -- excess, Quantity(numberUnits.value - excess.reduce((a1, a2) => Quantity(a1.quantity.value + a2.quantity.value)).value)) + } + def contains(order: LimitAskOrder[T]): Boolean = orders.contains(order) def head: LimitAskOrder[T] = orders.head @@ -62,13 +70,21 @@ package object orderbooks { new SortedBidOrders(orders + order, Quantity(numberUnits.value + order.quantity.value)) } + def ++ (additional: GenIterable[LimitBidOrder[T]]): SortedBidOrders[T] = { + new SortedBidOrders(orders ++ additional, Quantity(numberUnits.value + totalQuantity(additional).value)) + } + def - (order: LimitBidOrder[T]): SortedBidOrders[T] = { new SortedBidOrders(orders - order, Quantity(numberUnits.value - order.quantity.value)) } + def -- (excess: TraversableOnce[LimitBidOrder[T]]): SortedBidOrders[T] = { + new SortedBidOrders(orders -- excess, Quantity(numberUnits.value - excess.reduce((b1, b2) => Quantity(b1.quantity.value + b2.quantity.value)).value)) + } + def contains(order: LimitBidOrder[T]): Boolean = orders.contains(order) - def head: LimitBidOrder[T] = orders.head + def head: LimitBidOrder[T] = orders.head val headOption: Option[LimitBidOrder[T]] = orders.headOption @@ -88,4 +104,8 @@ package object orderbooks { } + private[this] def totalQuantity[T <: Tradable](orders: GenIterable[Order[T] with SinglePricePoint[T]]): Quantity = { + orders.aggregate[Quantity](Quantity(0))((total, order) => Quantity(total.value + order.quantity.value), (q1, q2) => Quantity(q1.value + q2.value)) + } + } From a234b20f363428db97bd0ada45b7363ae0f9ea10 Mon Sep 17 00:00:00 2001 From: davidrpugh Date: Sun, 19 Mar 2017 05:38:11 +0300 Subject: [PATCH 07/25] Draft of new SortedOrderBooks... --- .../orderbooks/SortedAskOrders.scala | 66 +++++++++++++++++++ .../orderbooks/SortedBidOrders.scala | 66 +++++++++++++++++++ 2 files changed, 132 insertions(+) create mode 100644 src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedAskOrders.scala create mode 100644 src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrders.scala diff --git a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedAskOrders.scala b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedAskOrders.scala new file mode 100644 index 0000000..97ca24e --- /dev/null +++ b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedAskOrders.scala @@ -0,0 +1,66 @@ +/* +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 java.util.UUID + +import org.economicsl.auctions.{Quantity, Tradable} +import org.economicsl.auctions.multiunit.LimitAskOrder + +import scala.collection.immutable.TreeSet + + +private[orderbooks] class SortedAskOrders[T <: Tradable] private(existing: Map[UUID, LimitAskOrder[T]], + sorted: TreeSet[LimitAskOrder[T]], + val numberUnits: Quantity) { + + def - (uuid: UUID): SortedAskOrders[T] = existing.get(uuid) match { + case Some(order) => + val remaining = Quantity(numberUnits.value - order.quantity.value) + new SortedAskOrders(existing - uuid, sorted - order, remaining) + case None => this + } + + def contains(uuid: UUID): Boolean = existing.contains(uuid) + + def head: LimitAskOrder[T] = sorted.head + + val headOption: Option[LimitAskOrder[T]] = sorted.headOption + + val isEmpty: Boolean = existing.isEmpty && sorted.isEmpty + + val ordering: Ordering[LimitAskOrder[T]] = sorted.ordering + + def tail: SortedAskOrders[T] = { + val remainingQuantity = Quantity(numberUnits.value - head.quantity.value) + new SortedAskOrders(existing.tail, sorted.tail, remainingQuantity) + } + + def updated(uuid: UUID, order: LimitAskOrder[T]): SortedAskOrders[T] = { + val additional = Quantity(numberUnits.value + order.quantity.value) + new SortedAskOrders(existing.updated(uuid, order), sorted + order, additional) + } + +} + +object SortedAskOrders { + + def empty[T <: Tradable](ordering: Ordering[LimitAskOrder[T]]): SortedAskOrders[T] = { + new SortedAskOrders(Map.empty[UUID, LimitAskOrder[T]], TreeSet.empty[LimitAskOrder[T]](ordering), Quantity(0)) + } + +} + diff --git a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrders.scala b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrders.scala new file mode 100644 index 0000000..9091fcb --- /dev/null +++ b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrders.scala @@ -0,0 +1,66 @@ +/* +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 java.util.UUID + +import org.economicsl.auctions.{Quantity, Tradable} +import org.economicsl.auctions.multiunit.LimitBidOrder + +import scala.collection.immutable.TreeSet + + +private[orderbooks] class SortedBidOrders[T <: Tradable] private(existing: Map[UUID, LimitBidOrder[T]], + sorted: TreeSet[LimitBidOrder[T]], + val numberUnits: Quantity) { + + def - (uuid: UUID): SortedBidOrders[T] = existing.get(uuid) match { + case Some(order) => + val remaining = Quantity(numberUnits.value - order.quantity.value) + new SortedBidOrders(existing - uuid, sorted - order, remaining) + case None => this + } + + def contains(uuid: UUID): Boolean = existing.contains(uuid) + + def head: LimitBidOrder[T] = sorted.head + + val headOption: Option[LimitBidOrder[T]] = sorted.headOption + + val isEmpty: Boolean = existing.isEmpty && sorted.isEmpty + + val ordering: Ordering[LimitBidOrder[T]] = sorted.ordering + + def tail: SortedBidOrders[T] = { + val remainingQuantity = Quantity(numberUnits.value - head.quantity.value) + new SortedBidOrders(existing.tail, sorted.tail, remainingQuantity) + } + + def updated(uuid: UUID, order: LimitBidOrder[T]): SortedBidOrders[T] = { + val additional = Quantity(numberUnits.value + order.quantity.value) + new SortedBidOrders(existing.updated(uuid, order), sorted + order, additional) + } + +} + + +object SortedBidOrders { + + def empty[T <: Tradable](ordering: Ordering[LimitBidOrder[T]]): SortedBidOrders[T] = { + new SortedBidOrders(Map.empty[UUID, LimitBidOrder[T]], TreeSet.empty[LimitBidOrder[T]](ordering), Quantity(0)) + } + +} From 59b21f29030dc31b954d21c32f73ca94f2ae350f Mon Sep 17 00:00:00 2001 From: davidrpugh Date: Sun, 19 Mar 2017 05:57:06 +0300 Subject: [PATCH 08/25] Refactored to take into account changes to Sorted*. --- .../orderbooks/UnMatchedOrders.scala | 33 +++++++++++++------ 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/UnMatchedOrders.scala b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/UnMatchedOrders.scala index 91083e8..b6a45d9 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/UnMatchedOrders.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/UnMatchedOrders.scala @@ -15,29 +15,42 @@ limitations under the License. */ package org.economicsl.auctions.multiunit.orderbooks +import java.util.UUID + import org.economicsl.auctions.Tradable import org.economicsl.auctions.multiunit.{LimitAskOrder, LimitBidOrder} -private[orderbooks] class UnMatchedOrders[T <: Tradable] private(val askOrders: SortedAskOrders[T], val bidOrders: SortedBidOrders[T]) { +private[orderbooks] class UnMatchedOrders[T <: Tradable] private(val askOrders: SortedAskOrders[T], + val bidOrders: SortedBidOrders[T]) { require(bidOrders.headOption.forall(bidOrder => bidOrder.limit <= askOrders.head.limit)) - def + (order: LimitAskOrder[T]): UnMatchedOrders[T] = new UnMatchedOrders(askOrders + order, bidOrders) - - def + (order: LimitBidOrder[T]): UnMatchedOrders[T] = new UnMatchedOrders(askOrders, bidOrders + order) - - def - (order: LimitAskOrder[T]): UnMatchedOrders[T] = new UnMatchedOrders(askOrders - order, bidOrders) - - def - (order: LimitBidOrder[T]): UnMatchedOrders[T] = new UnMatchedOrders(askOrders, bidOrders - order) + /** Remove an order from the collection of unmatched orders. */ + def - (uuid: UUID): UnMatchedOrders[T] = { + if (askOrders.contains(uuid)) { + new UnMatchedOrders(askOrders - uuid, bidOrders) + } else { + new UnMatchedOrders(askOrders, bidOrders - uuid) + } + } val askOrdering: Ordering[LimitAskOrder[T]] = askOrders.ordering val bidOrdering: Ordering[LimitBidOrder[T]] = bidOrders.ordering - def contains(order: LimitAskOrder[T]): Boolean = askOrders.contains(order) + /** Check whether an order is contained in the collection of unmatched orders using. */ + def contains(uuid: UUID): Boolean = askOrders.contains(uuid) || bidOrders.contains(uuid) + + /** Add a `LimitAskOrder` to the collection of unmatched orders. */ + def updated(uuid: UUID, order: LimitAskOrder[T]): UnMatchedOrders[T] = { + new UnMatchedOrders(askOrders.updated(uuid, order), bidOrders) + } - def contains(order: LimitBidOrder[T]): Boolean = bidOrders.contains(order) + /** Add a `LimitBidOrder` to the collection of unmatched orders. */ + def updated(uuid: UUID, order: LimitBidOrder[T]): UnMatchedOrders[T] = { + new UnMatchedOrders(askOrders, bidOrders.updated(uuid, order)) + } } From 67078d914e39ffa06f5533e33bd479aaa6c9b1b5 Mon Sep 17 00:00:00 2001 From: davidrpugh Date: Sun, 19 Mar 2017 05:58:36 +0300 Subject: [PATCH 09/25] Cleaned up the type signatures. --- .../orderbooks/FourHeapOrderBook.scala | 64 +++++++++---------- 1 file changed, 29 insertions(+), 35 deletions(-) diff --git a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/FourHeapOrderBook.scala b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/FourHeapOrderBook.scala index d8aeadd..54ad75d 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/FourHeapOrderBook.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/FourHeapOrderBook.scala @@ -15,58 +15,52 @@ limitations under the License. */ package org.economicsl.auctions.multiunit.orderbooks +import java.util.UUID + import org.economicsl.auctions.Tradable import org.economicsl.auctions.multiunit.{LimitAskOrder, LimitBidOrder} class FourHeapOrderBook[T <: Tradable] private(matchedOrders: MatchedOrders[T], unMatchedOrders: UnMatchedOrders[T]) { - def - (order: LimitAskOrder[T]): FourHeapOrderBook[T] = { - if (unMatchedOrders.contains(order)) { - new FourHeapOrderBook(matchedOrders, unMatchedOrders - order) + def - (uuid: UUID): FourHeapOrderBook[T] = { + if (unMatchedOrders.contains(uuid)) { + new FourHeapOrderBook(matchedOrders, unMatchedOrders - uuid) } else { - val bidOrder = matchedOrders.bidOrders.head - new FourHeapOrderBook(matchedOrders - (order, bidOrder), unMatchedOrders + bidOrder) + ??? } } - def - (order: LimitBidOrder[T]): FourHeapOrderBook[T] = { - if (unMatchedOrders.contains(order)) { - new FourHeapOrderBook(matchedOrders, unMatchedOrders - order) - } else { - val askOrder = matchedOrders.askOrders.head - new FourHeapOrderBook(matchedOrders - (askOrder, order), unMatchedOrders + askOrder) - } + def + (kv: (UUID, LimitAskOrder[T])): FourHeapOrderBook[T] = { + updated(kv._1, kv._2) } - def + (order: LimitAskOrder[T]): FourHeapOrderBook[T] = { - (matchedOrders.askOrders.headOption, unMatchedOrders.bidOrders.headOption) match { - case (Some(askOrder), Some(bidOrder)) if order.limit <= bidOrder.limit && askOrder.limit <= bidOrder.limit => - new FourHeapOrderBook(matchedOrders + (order, bidOrder), unMatchedOrders - bidOrder) - case (None, Some(bidOrder)) if order.limit <= bidOrder.limit => - new FourHeapOrderBook(matchedOrders + (order, bidOrder), unMatchedOrders - bidOrder) - case (Some(askOrder), Some(_)) if order.limit < askOrder.limit => - new FourHeapOrderBook(matchedOrders.replace(askOrder, order), unMatchedOrders + askOrder) - case _ => - new FourHeapOrderBook(matchedOrders, unMatchedOrders + order) - } + def + (kv: (UUID, LimitBidOrder[T])): FourHeapOrderBook[T] = { + updated(kv._1, kv._2) + } + + def takeWhileMatched: (Stream[(LimitAskOrder[T], LimitBidOrder[T])], FourHeapOrderBook[T]) = { + (matchedOrders.zipped, withEmptyMatchedOrders) } - def + (order: LimitBidOrder[T]): FourHeapOrderBook[T] = { - (matchedOrders.bidOrders.headOption, unMatchedOrders.askOrders.headOption) match { - case (Some(bidOrder), Some(askOrder)) if order.limit >= askOrder.limit && bidOrder.limit >= askOrder.limit => - new FourHeapOrderBook(matchedOrders + (askOrder, order), unMatchedOrders - askOrder) - case (None, Some(askOrder)) if order.limit >= askOrder.limit => - new FourHeapOrderBook(matchedOrders + (askOrder, order), unMatchedOrders - askOrder) - case (Some(bidOrder), Some(_)) if order.limit > bidOrder.limit => - new FourHeapOrderBook(matchedOrders.replace(bidOrder, order), unMatchedOrders + bidOrder) - case _ => - new FourHeapOrderBook(matchedOrders, unMatchedOrders + order) + def updated(uuid: UUID, order: LimitAskOrder[T]): FourHeapOrderBook[T] = { + (matchedOrders.askOrders.headOption, unMatchedOrders.bidOrders.headOption) match { + case (Some(askOrder), Some(bidOrder)) if order.value <= bidOrder.value && askOrder.value <= bidOrder.value => + ??? + case (Some(askOrder), _) if order.value <= askOrder.value => + ??? + case _ => new FourHeapOrderBook(matchedOrders, unMatchedOrders.updated(uuid, order)) } } - def takeWhileMatched: (Stream[(LimitAskOrder[T], LimitBidOrder[T])], FourHeapOrderBook[T]) = { - (matchedOrders.zipped, withEmptyMatchedOrders) + def updated(uuid: UUID, order: LimitBidOrder[T]): FourHeapOrderBook[T] = { + (matchedOrders.askOrders.headOption, unMatchedOrders.bidOrders.headOption) match { + case (Some(askOrder), Some(bidOrder)) if order.value <= bidOrder.value && askOrder.value <= bidOrder.value => + ??? + case (Some(askOrder), _) if order.value <= askOrder.value => + ??? + case _ => new FourHeapOrderBook(matchedOrders, unMatchedOrders.updated(uuid, order)) + } } private[this] def withEmptyMatchedOrders: FourHeapOrderBook[T] = { From 12e9555ee990172c9bd21bb858839f47667b8b9d Mon Sep 17 00:00:00 2001 From: davidrpugh Date: Sun, 19 Mar 2017 06:02:56 +0300 Subject: [PATCH 10/25] Refactored to take into account changes to Sorted*. --- .../multiunit/orderbooks/MatchedOrders.scala | 43 +++++++++---------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/MatchedOrders.scala b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/MatchedOrders.scala index aecc097..27073fc 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/MatchedOrders.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/MatchedOrders.scala @@ -15,45 +15,42 @@ limitations under the License. */ package org.economicsl.auctions.multiunit.orderbooks +import java.util.UUID + import org.economicsl.auctions.Tradable import org.economicsl.auctions.multiunit.{LimitAskOrder, LimitBidOrder} -private[orderbooks] class MatchedOrders[T <: Tradable] private(val askOrders: SortedAskOrders[T], val bidOrders: SortedBidOrders[T]) { - - require(askOrders.numberUnits == bidOrders.numberUnits) // number of units must be the same! - require(bidOrders.headOption.forall(bidOrder => bidOrder.limit >= askOrders.head.limit)) // value of lowest bid must exceed value of highest ask! - - def + (orders: (LimitAskOrder[T], LimitBidOrder[T])): MatchedOrders[T] = { - new MatchedOrders(askOrders + orders._1, bidOrders + orders._2) - } - - def ++ (orders: (TraversableOnce[LimitAskOrder[T]], TraversableOnce[LimitBidOrder[T]])): MatchedOrders[T] = { - new MatchedOrders(askOrders ++ orders._1, bidOrders ++ orders._2) - } +private[orderbooks] class MatchedOrders[T <: Tradable] private(val askOrders: SortedAskOrders[T], + val bidOrders: SortedBidOrders[T]) { - def - (orders: (LimitAskOrder[T], LimitBidOrder[T])): MatchedOrders[T] = { - new MatchedOrders(askOrders - orders._1, bidOrders - orders._2) - } + require(askOrders.numberUnits == bidOrders.numberUnits) + require(bidOrders.headOption.forall(bidOrder => askOrders.headOption.forall(askOrder => bidOrder.value >= askOrder.value))) // value of lowest bid must exceed value of highest ask! - def -- (orders: (TraversableOnce[LimitAskOrder[T]], TraversableOnce[LimitBidOrder[T]])): MatchedOrders[T] = { - new MatchedOrders(askOrders -- orders._1, bidOrders -- orders._2) + def - (orders: ((UUID, LimitAskOrder[T]), (UUID, LimitBidOrder[T]))): MatchedOrders[T] = { + val ((uuid1, _), (uuid2, _)) = orders + new MatchedOrders(askOrders - uuid1, bidOrders - uuid2) } val askOrdering: Ordering[LimitAskOrder[T]] = askOrders.ordering val bidOrdering: Ordering[LimitBidOrder[T]] = bidOrders.ordering - def contains(order: LimitAskOrder[T]): Boolean = askOrders.contains(order) + def contains(uuid: UUID): Boolean = askOrders.contains(uuid) || bidOrders.contains(uuid) - def contains(order: LimitBidOrder[T]): Boolean = bidOrders.contains(order) + def replace(existing: (UUID, LimitAskOrder[T]), incoming: (UUID, LimitAskOrder[T])): MatchedOrders[T] = { + val updatedAskOrders = (askOrders - existing._1).updated(incoming._1, incoming._2) + new MatchedOrders(updatedAskOrders, bidOrders) + } - def replace(existing: LimitAskOrder[T], incoming: LimitAskOrder[T]): MatchedOrders[T] = { - new MatchedOrders(askOrders - existing + incoming, bidOrders) + def replace(existing: (UUID, LimitBidOrder[T]), incoming: (UUID, LimitBidOrder[T])): MatchedOrders[T] = { + val updatedBidOrders = (bidOrders - existing._1).updated(incoming._1, incoming._2) + new MatchedOrders(askOrders, updatedBidOrders) } - def replace(existing: LimitBidOrder[T], incoming: LimitBidOrder[T]): MatchedOrders[T] = { - new MatchedOrders(askOrders, bidOrders - existing + incoming) + def updated(askOrder: (UUID, LimitAskOrder[T]), bidOrder: (UUID, LimitBidOrder[T])): MatchedOrders[T] = { + val (uuid1, order1) = askOrder; val (uuid2, order2) = bidOrder + new MatchedOrders(askOrders.updated(uuid1, order1), bidOrders.updated(uuid2, order2)) } def zipped: Stream[(LimitAskOrder[T], LimitBidOrder[T])] = { From 13bf7070103740ccb0c028f57f5eb4bea4c62220 Mon Sep 17 00:00:00 2001 From: davidrpugh Date: Sun, 19 Mar 2017 06:03:16 +0300 Subject: [PATCH 11/25] Project reorg. --- .../multiunit/orderbooks/package.scala | 83 +------------------ 1 file changed, 1 insertion(+), 82 deletions(-) diff --git a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/package.scala b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/package.scala index 839d6bd..4de1b9a 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/package.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/package.scala @@ -23,88 +23,7 @@ import scala.collection.{GenIterable, immutable} /** Documentation for multi-unit orderbooks goes here! */ package object orderbooks { - class SortedAskOrders[T <: Tradable] private(orders: immutable.TreeSet[LimitAskOrder[T]], val numberUnits: Quantity) { - - def + (order: LimitAskOrder[T]): SortedAskOrders[T] = { - new SortedAskOrders(orders + order, Quantity(numberUnits.value + order.quantity.value)) - } - - def ++ (additional: TraversableOnce[LimitAskOrder[T]]): SortedAskOrders[T] = { - new SortedAskOrders(orders ++ additional, Quantity(numberUnits.value + additional.reduce((a1, a2) => Quantity(a1.quantity.value + a2.quantity.value)).value)) - } - - def - (order: LimitAskOrder[T]): SortedAskOrders[T] = { - new SortedAskOrders(orders - order, Quantity(numberUnits.value - order.quantity.value)) - } - - def -- (excess: TraversableOnce[LimitAskOrder[T]]): SortedAskOrders[T] = { - new SortedAskOrders(orders -- excess, Quantity(numberUnits.value - excess.reduce((a1, a2) => Quantity(a1.quantity.value + a2.quantity.value)).value)) - } - - def contains(order: LimitAskOrder[T]): Boolean = orders.contains(order) - - def head: LimitAskOrder[T] = orders.head - - val headOption: Option[LimitAskOrder[T]] = orders.headOption - - val isEmpty: Boolean = orders.isEmpty - - val ordering: Ordering[LimitAskOrder[T]] = orders.ordering - - def tail: SortedAskOrders[T] = new SortedAskOrders(orders.tail, Quantity(numberUnits.value - head.quantity.value)) - - } - - object SortedAskOrders { - - def empty[T <: Tradable](ordering: Ordering[LimitAskOrder[T]]): SortedAskOrders[T] = { - new SortedAskOrders(immutable.TreeSet.empty[LimitAskOrder[T]](ordering), Quantity(0)) - } - - } - - - class SortedBidOrders[T <: Tradable] private(orders: immutable.TreeSet[LimitBidOrder[T]], val numberUnits: Quantity) { - - def + (order: LimitBidOrder[T]): SortedBidOrders[T] = { - new SortedBidOrders(orders + order, Quantity(numberUnits.value + order.quantity.value)) - } - - def ++ (additional: GenIterable[LimitBidOrder[T]]): SortedBidOrders[T] = { - new SortedBidOrders(orders ++ additional, Quantity(numberUnits.value + totalQuantity(additional).value)) - } - - def - (order: LimitBidOrder[T]): SortedBidOrders[T] = { - new SortedBidOrders(orders - order, Quantity(numberUnits.value - order.quantity.value)) - } - - def -- (excess: TraversableOnce[LimitBidOrder[T]]): SortedBidOrders[T] = { - new SortedBidOrders(orders -- excess, Quantity(numberUnits.value - excess.reduce((b1, b2) => Quantity(b1.quantity.value + b2.quantity.value)).value)) - } - - def contains(order: LimitBidOrder[T]): Boolean = orders.contains(order) - - def head: LimitBidOrder[T] = orders.head - - val headOption: Option[LimitBidOrder[T]] = orders.headOption - - val isEmpty: Boolean = orders.isEmpty - - val ordering: Ordering[LimitBidOrder[T]] = orders.ordering - - def tail: SortedBidOrders[T] = new SortedBidOrders(orders.tail, Quantity(numberUnits.value - head.quantity.value)) - - } - - object SortedBidOrders { - - def empty[T <: Tradable](ordering: Ordering[LimitBidOrder[T]]): SortedBidOrders[T] = { - new SortedBidOrders(immutable.TreeSet.empty[LimitBidOrder[T]](ordering), Quantity(0)) - } - - } - - private[this] def totalQuantity[T <: Tradable](orders: GenIterable[Order[T] with SinglePricePoint[T]]): Quantity = { + def totalQuantity[T <: Tradable](orders: GenIterable[Order[T] with SinglePricePoint[T]]): Quantity = { orders.aggregate[Quantity](Quantity(0))((total, order) => Quantity(total.value + order.quantity.value), (q1, q2) => Quantity(q1.value + q2.value)) } From 6adefb5d088bb324446c82b00dd2dbf0fefa86c1 Mon Sep 17 00:00:00 2001 From: davidrpugh Date: Sun, 19 Mar 2017 06:10:54 +0300 Subject: [PATCH 12/25] Removed unnecessary + operator; just use updated method. --- .../auctions/multiunit/orderbooks/FourHeapOrderBook.scala | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/FourHeapOrderBook.scala b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/FourHeapOrderBook.scala index 54ad75d..9e785f4 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/FourHeapOrderBook.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/FourHeapOrderBook.scala @@ -31,14 +31,6 @@ class FourHeapOrderBook[T <: Tradable] private(matchedOrders: MatchedOrders[T], } } - def + (kv: (UUID, LimitAskOrder[T])): FourHeapOrderBook[T] = { - updated(kv._1, kv._2) - } - - def + (kv: (UUID, LimitBidOrder[T])): FourHeapOrderBook[T] = { - updated(kv._1, kv._2) - } - def takeWhileMatched: (Stream[(LimitAskOrder[T], LimitBidOrder[T])], FourHeapOrderBook[T]) = { (matchedOrders.zipped, withEmptyMatchedOrders) } From d9304037de2ce044b7ff7b103f5419ac2c94a4b3 Mon Sep 17 00:00:00 2001 From: davidrpugh Date: Wed, 22 Mar 2017 15:29:07 +0300 Subject: [PATCH 13/25] Started work on FourHeapOrderBook logic --- .../multiunit/orderbooks/FourHeapOrderBook.scala | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/FourHeapOrderBook.scala b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/FourHeapOrderBook.scala index 9e785f4..6f9c7b6 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/FourHeapOrderBook.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/FourHeapOrderBook.scala @@ -17,7 +17,7 @@ package org.economicsl.auctions.multiunit.orderbooks import java.util.UUID -import org.economicsl.auctions.Tradable +import org.economicsl.auctions.{Quantity, Tradable} import org.economicsl.auctions.multiunit.{LimitAskOrder, LimitBidOrder} @@ -38,7 +38,16 @@ class FourHeapOrderBook[T <: Tradable] private(matchedOrders: MatchedOrders[T], def updated(uuid: UUID, order: LimitAskOrder[T]): FourHeapOrderBook[T] = { (matchedOrders.askOrders.headOption, unMatchedOrders.bidOrders.headOption) match { case (Some(askOrder), Some(bidOrder)) if order.value <= bidOrder.value && askOrder.value <= bidOrder.value => - ??? + val excessDemand = bidOrder.quantity - order.quantity + if (excessDemand > Quantity(0.0)) { + val (filled, residual) = bidOrder.split() + new FourHeapOrderBook(matchedOrders.updated((uuid, order), (???, filled)), unMatchedOrders - ???) + } else if (excessDemand < Quantity(0.0)) { + ??? + } else { + new FourHeapOrderBook(matchedOrders.updated((uuid, order), (???, bidOrder)), unMatchedOrders - ???) + } + case (Some(askOrder), _) if order.value <= askOrder.value => ??? case _ => new FourHeapOrderBook(matchedOrders, unMatchedOrders.updated(uuid, order)) From e9011bd173d9ce452ab42b6c72c29b9ad8a2ebda Mon Sep 17 00:00:00 2001 From: davidrpugh Date: Tue, 28 Mar 2017 10:33:50 +0300 Subject: [PATCH 14/25] Continued work... --- .../multiunit/orderbooks/FourHeapOrderBook.scala | 13 +++++++------ .../multiunit/orderbooks/SortedAskOrders.scala | 16 ++++++++-------- .../multiunit/orderbooks/SortedBidOrders.scala | 14 +++++++------- 3 files changed, 22 insertions(+), 21 deletions(-) diff --git a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/FourHeapOrderBook.scala b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/FourHeapOrderBook.scala index 6f9c7b6..6226f0d 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/FourHeapOrderBook.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/FourHeapOrderBook.scala @@ -37,12 +37,13 @@ class FourHeapOrderBook[T <: Tradable] private(matchedOrders: MatchedOrders[T], def updated(uuid: UUID, order: LimitAskOrder[T]): FourHeapOrderBook[T] = { (matchedOrders.askOrders.headOption, unMatchedOrders.bidOrders.headOption) match { - case (Some(askOrder), Some(bidOrder)) if order.value <= bidOrder.value && askOrder.value <= bidOrder.value => + case (Some((_, askOrder)), Some((???, bidOrder))) if order.value <= bidOrder.value && askOrder.value <= bidOrder.value => val excessDemand = bidOrder.quantity - order.quantity - if (excessDemand > Quantity(0.0)) { - val (filled, residual) = bidOrder.split() - new FourHeapOrderBook(matchedOrders.updated((uuid, order), (???, filled)), unMatchedOrders - ???) - } else if (excessDemand < Quantity(0.0)) { + if (excessDemand > Quantity(0)) { + val residualUnMatchedOrders = unMatchedOrders - ??? + val (filled, residual) = (bidOrder.withQuantity(order.quantity), bidOrder.withQuantity(excessDemand)) + new FourHeapOrderBook(matchedOrders.updated((uuid, order), (???, filled)), residualUnMatchedOrders.updated(???, residual)) + } else if (excessDemand < Quantity(0)) { ??? } else { new FourHeapOrderBook(matchedOrders.updated((uuid, order), (???, bidOrder)), unMatchedOrders - ???) @@ -56,7 +57,7 @@ class FourHeapOrderBook[T <: Tradable] private(matchedOrders: MatchedOrders[T], def updated(uuid: UUID, order: LimitBidOrder[T]): FourHeapOrderBook[T] = { (matchedOrders.askOrders.headOption, unMatchedOrders.bidOrders.headOption) match { - case (Some(askOrder), Some(bidOrder)) if order.value <= bidOrder.value && askOrder.value <= bidOrder.value => + case (Some((???, askOrder)), Some((_, bidOrder))) if order.value <= bidOrder.value && askOrder.value <= bidOrder.value => ??? case (Some(askOrder), _) if order.value <= askOrder.value => ??? diff --git a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedAskOrders.scala b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedAskOrders.scala index 97ca24e..8e453be 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedAskOrders.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedAskOrders.scala @@ -24,13 +24,13 @@ import scala.collection.immutable.TreeSet private[orderbooks] class SortedAskOrders[T <: Tradable] private(existing: Map[UUID, LimitAskOrder[T]], - sorted: TreeSet[LimitAskOrder[T]], + sorted: TreeSet[(UUID, LimitAskOrder[T])], val numberUnits: Quantity) { def - (uuid: UUID): SortedAskOrders[T] = existing.get(uuid) match { case Some(order) => val remaining = Quantity(numberUnits.value - order.quantity.value) - new SortedAskOrders(existing - uuid, sorted - order, remaining) + new SortedAskOrders(existing - uuid, sorted - ((uuid, order)), remaining) case None => this } @@ -38,20 +38,20 @@ private[orderbooks] class SortedAskOrders[T <: Tradable] private(existing: Map[U def head: LimitAskOrder[T] = sorted.head - val headOption: Option[LimitAskOrder[T]] = sorted.headOption + val headOption: Option[(UUID, LimitAskOrder[T])] = sorted.headOption val isEmpty: Boolean = existing.isEmpty && sorted.isEmpty - val ordering: Ordering[LimitAskOrder[T]] = sorted.ordering + val ordering: Ordering[(UUID, LimitAskOrder[T])] = sorted.ordering def tail: SortedAskOrders[T] = { - val remainingQuantity = Quantity(numberUnits.value - head.quantity.value) + val remainingQuantity = numberUnits - head.quantity new SortedAskOrders(existing.tail, sorted.tail, remainingQuantity) } def updated(uuid: UUID, order: LimitAskOrder[T]): SortedAskOrders[T] = { - val additional = Quantity(numberUnits.value + order.quantity.value) - new SortedAskOrders(existing.updated(uuid, order), sorted + order, additional) + val additional = numberUnits + order.quantity + new SortedAskOrders(existing.updated(uuid, order), sorted + ((uuid, order)), additional) } } @@ -59,7 +59,7 @@ private[orderbooks] class SortedAskOrders[T <: Tradable] private(existing: Map[U object SortedAskOrders { def empty[T <: Tradable](ordering: Ordering[LimitAskOrder[T]]): SortedAskOrders[T] = { - new SortedAskOrders(Map.empty[UUID, LimitAskOrder[T]], TreeSet.empty[LimitAskOrder[T]](ordering), Quantity(0)) + new SortedAskOrders(Map.empty[UUID, LimitAskOrder[T]], TreeSet.empty[(UUID, LimitAskOrder[T])](ordering), Quantity(0)) } } diff --git a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrders.scala b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrders.scala index 9091fcb..baa6786 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrders.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrders.scala @@ -24,13 +24,13 @@ import scala.collection.immutable.TreeSet private[orderbooks] class SortedBidOrders[T <: Tradable] private(existing: Map[UUID, LimitBidOrder[T]], - sorted: TreeSet[LimitBidOrder[T]], + sorted: TreeSet[(UUID, LimitBidOrder[T])], val numberUnits: Quantity) { def - (uuid: UUID): SortedBidOrders[T] = existing.get(uuid) match { case Some(order) => val remaining = Quantity(numberUnits.value - order.quantity.value) - new SortedBidOrders(existing - uuid, sorted - order, remaining) + new SortedBidOrders(existing - uuid, sorted - ((uuid, order)), remaining) case None => this } @@ -38,20 +38,20 @@ private[orderbooks] class SortedBidOrders[T <: Tradable] private(existing: Map[U def head: LimitBidOrder[T] = sorted.head - val headOption: Option[LimitBidOrder[T]] = sorted.headOption + val headOption: Option[(UUID, LimitBidOrder[T])] = sorted.headOption val isEmpty: Boolean = existing.isEmpty && sorted.isEmpty val ordering: Ordering[LimitBidOrder[T]] = sorted.ordering def tail: SortedBidOrders[T] = { - val remainingQuantity = Quantity(numberUnits.value - head.quantity.value) + val remainingQuantity = numberUnits - head.quantity new SortedBidOrders(existing.tail, sorted.tail, remainingQuantity) } def updated(uuid: UUID, order: LimitBidOrder[T]): SortedBidOrders[T] = { - val additional = Quantity(numberUnits.value + order.quantity.value) - new SortedBidOrders(existing.updated(uuid, order), sorted + order, additional) + val additional = numberUnits + order.quantity + new SortedBidOrders(existing.updated(uuid, order), sorted + ((uuid, order)), additional) } } @@ -60,7 +60,7 @@ private[orderbooks] class SortedBidOrders[T <: Tradable] private(existing: Map[U object SortedBidOrders { def empty[T <: Tradable](ordering: Ordering[LimitBidOrder[T]]): SortedBidOrders[T] = { - new SortedBidOrders(Map.empty[UUID, LimitBidOrder[T]], TreeSet.empty[LimitBidOrder[T]](ordering), Quantity(0)) + new SortedBidOrders(Map.empty[UUID, LimitBidOrder[T]], TreeSet.empty[(UUID, LimitBidOrder[T])](ordering), Quantity(0)) } } From 511688147a823a65399ebf2fbeca4dcaf4631040 Mon Sep 17 00:00:00 2001 From: davidrpugh Date: Mon, 3 Apr 2017 14:53:12 +0300 Subject: [PATCH 15/25] Slowly making progess! --- .../auctions/multiunit/LimitAskOrder.scala | 6 +-- .../auctions/multiunit/LimitBidOrder.scala | 6 +-- .../orderbooks/FourHeapOrderBook.scala | 32 ++++++++++--- .../multiunit/orderbooks/MatchedOrders.scala | 37 ++++++++------- .../orderbooks/SortedAskOrders.scala | 42 +++++++++++++---- .../orderbooks/SortedBidOrders.scala | 46 ++++++++++++++----- 6 files changed, 118 insertions(+), 51 deletions(-) diff --git a/src/main/scala/org/economicsl/auctions/multiunit/LimitAskOrder.scala b/src/main/scala/org/economicsl/auctions/multiunit/LimitAskOrder.scala index 72ab2b5..cbd613d 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/LimitAskOrder.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/LimitAskOrder.scala @@ -39,9 +39,9 @@ object LimitAskOrder { private[this] case class SinglePricePointImpl[+T <: Tradable](issuer: UUID, limit: Price, quantity: Quantity, tradable: T) extends LimitAskOrder[T] { - def withQuantity(residual: Quantity): LimitAskOrder[T] = { - require(residual.value < quantity.value) - copy(quantity = residual) + def withQuantity(quantity: Quantity): LimitAskOrder[T] = { + require(quantity.value < quantity.value) + copy(quantity = quantity) } } diff --git a/src/main/scala/org/economicsl/auctions/multiunit/LimitBidOrder.scala b/src/main/scala/org/economicsl/auctions/multiunit/LimitBidOrder.scala index acf302d..89c75c2 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/LimitBidOrder.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/LimitBidOrder.scala @@ -38,9 +38,9 @@ object LimitBidOrder { private[this] case class SinglePricePointImpl[+T <: Tradable](issuer: UUID, limit: Price, quantity: Quantity, tradable: T) extends LimitBidOrder[T] { - def withQuantity(residual: Quantity): LimitBidOrder[T] = { - require(residual.value < quantity.value) - copy(quantity = residual) + def withQuantity(quantity: Quantity): LimitBidOrder[T] = { + require(quantity.value < quantity.value) + copy(quantity = quantity) } } diff --git a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/FourHeapOrderBook.scala b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/FourHeapOrderBook.scala index 6226f0d..99467c2 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/FourHeapOrderBook.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/FourHeapOrderBook.scala @@ -27,7 +27,17 @@ class FourHeapOrderBook[T <: Tradable] private(matchedOrders: MatchedOrders[T], if (unMatchedOrders.contains(uuid)) { new FourHeapOrderBook(matchedOrders, unMatchedOrders - uuid) } else { - ??? + val askOrder = matchedOrders.askOrders(uuid) + val (uuid2, bidOrder) = matchedOrders.bidOrders.head + val excessDemand = bidOrder.quantity - askOrder.quantity + if (excessDemand > Quantity(0)) { + val (matched, residual) = split(bidOrder, excessDemand) + new FourHeapOrderBook(matchedOrders.removeAndReplace((uuid, askOrder), (uuid2, residual)), unMatchedOrders.updated(uuid2, matched)) + } else if (excessDemand < Quantity(0)) { + ??? // split the ask order; removed the matched portion of the askOrder and the bidOrder; recurse with the residual askOrder; add residual askOrder to unMatchedOrders + } else { + ??? // remove the askOrder and the bidOrder from the matched orders + } } } @@ -37,16 +47,16 @@ class FourHeapOrderBook[T <: Tradable] private(matchedOrders: MatchedOrders[T], def updated(uuid: UUID, order: LimitAskOrder[T]): FourHeapOrderBook[T] = { (matchedOrders.askOrders.headOption, unMatchedOrders.bidOrders.headOption) match { - case (Some((_, askOrder)), Some((???, bidOrder))) if order.value <= bidOrder.value && askOrder.value <= bidOrder.value => + case (Some((_, askOrder)), Some((uuid2, bidOrder))) if order.value <= bidOrder.value && askOrder.value <= bidOrder.value => val excessDemand = bidOrder.quantity - order.quantity if (excessDemand > Quantity(0)) { - val residualUnMatchedOrders = unMatchedOrders - ??? - val (filled, residual) = (bidOrder.withQuantity(order.quantity), bidOrder.withQuantity(excessDemand)) - new FourHeapOrderBook(matchedOrders.updated((uuid, order), (???, filled)), residualUnMatchedOrders.updated(???, residual)) + val residualUnMatchedOrders = unMatchedOrders - uuid2 + val (filled, residual) = (bidOrder.withQuantity(order.quantity), bidOrder.withQuantity(excessDemand)) // split the bidOrder! + new FourHeapOrderBook(matchedOrders.updated((uuid, order), (uuid2, filled)), residualUnMatchedOrders.updated(uuid2, residual)) } else if (excessDemand < Quantity(0)) { ??? } else { - new FourHeapOrderBook(matchedOrders.updated((uuid, order), (???, bidOrder)), unMatchedOrders - ???) + new FourHeapOrderBook(matchedOrders.updated((uuid, order), (uuid2, bidOrder)), unMatchedOrders - uuid2) } case (Some(askOrder), _) if order.value <= askOrder.value => @@ -70,6 +80,16 @@ class FourHeapOrderBook[T <: Tradable] private(matchedOrders: MatchedOrders[T], new FourHeapOrderBook[T](MatchedOrders.empty(askOrdering, bidOrdering), unMatchedOrders) } + private[this] def split(order: LimitAskOrder[T], residual: Quantity): (LimitAskOrder[T], LimitAskOrder[T]) = { + val matched = order.quantity - residual // todo consider checking that order.quantity is greater than residual! + (order.withQuantity(matched), order.withQuantity(residual)) + } + + private[this] def split(order: LimitBidOrder[T], residual: Quantity): (LimitBidOrder[T], LimitBidOrder[T]) = { + val matched = order.quantity - residual // todo consider checking that order.quantity is greater than residual! + (order.withQuantity(matched), order.withQuantity(residual)) + } + } diff --git a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/MatchedOrders.scala b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/MatchedOrders.scala index 27073fc..a0e8c3a 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/MatchedOrders.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/MatchedOrders.scala @@ -17,19 +17,26 @@ package org.economicsl.auctions.multiunit.orderbooks import java.util.UUID -import org.economicsl.auctions.Tradable +import org.economicsl.auctions.{Quantity, Tradable} import org.economicsl.auctions.multiunit.{LimitAskOrder, LimitBidOrder} private[orderbooks] class MatchedOrders[T <: Tradable] private(val askOrders: SortedAskOrders[T], val bidOrders: SortedBidOrders[T]) { - require(askOrders.numberUnits == bidOrders.numberUnits) - require(bidOrders.headOption.forall(bidOrder => askOrders.headOption.forall(askOrder => bidOrder.value >= askOrder.value))) // value of lowest bid must exceed value of highest ask! - - def - (orders: ((UUID, LimitAskOrder[T]), (UUID, LimitBidOrder[T]))): MatchedOrders[T] = { - val ((uuid1, _), (uuid2, _)) = orders - new MatchedOrders(askOrders - uuid1, bidOrders - uuid2) + require(askOrders.quantity == bidOrders.quantity) + require(bidOrders.headOption.forall{ case (_, bidOrder) => askOrders.headOption.forall{ case (_, askOrder) => bidOrder.value >= askOrder.value }}) // value of lowest bid must exceed value of highest ask! + + def - (uuid: UUID): (MatchedOrders[T], Option[SortedAskOrders[T]], Option[SortedBidOrders[T]]) = { + if (askOrders.contains(uuid)) { + val removedOrder = askOrders(uuid) + val (matched, residual) = bidOrders.splitAt(removedOrder.quantity) + (new MatchedOrders(askOrders - uuid, residual), None, Some(matched)) + } else { + val removedOrder = bidOrders(uuid) + val (matched, residual) = askOrders.splitAt(removedOrder.quantity) + (new MatchedOrders(residual, bidOrders - uuid), Some(matched), None) + } } val askOrdering: Ordering[LimitAskOrder[T]] = askOrders.ordering @@ -38,14 +45,12 @@ private[orderbooks] class MatchedOrders[T <: Tradable] private(val askOrders: So def contains(uuid: UUID): Boolean = askOrders.contains(uuid) || bidOrders.contains(uuid) - def replace(existing: (UUID, LimitAskOrder[T]), incoming: (UUID, LimitAskOrder[T])): MatchedOrders[T] = { - val updatedAskOrders = (askOrders - existing._1).updated(incoming._1, incoming._2) - new MatchedOrders(updatedAskOrders, bidOrders) + def removeAndReplace(askOrder: (UUID, LimitAskOrder[T]), bidOrder: (UUID, LimitBidOrder[T])): MatchedOrders[T] = { + new MatchedOrders(askOrders - askOrder._1, bidOrders.updated(bidOrder._1, bidOrder._2)) } - def replace(existing: (UUID, LimitBidOrder[T]), incoming: (UUID, LimitBidOrder[T])): MatchedOrders[T] = { - val updatedBidOrders = (bidOrders - existing._1).updated(incoming._1, incoming._2) - new MatchedOrders(askOrders, updatedBidOrders) + def removeAndReplace(bidOrder: (UUID, LimitBidOrder[T]), askOrder: (UUID, LimitAskOrder[T])): MatchedOrders[T] = { + new MatchedOrders(askOrders.updated(askOrder._1, askOrder._2), bidOrders - bidOrder._1) } def updated(askOrder: (UUID, LimitAskOrder[T]), bidOrder: (UUID, LimitBidOrder[T])): MatchedOrders[T] = { @@ -64,15 +69,13 @@ private[orderbooks] object MatchedOrders { /** Create an instance of `MatchedOrders`. * - * @param askOrdering - * @param bidOrdering * @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[T <: Tradable](askOrdering: Ordering[LimitAskOrder[T]], bidOrdering: Ordering[LimitBidOrder[T]]): MatchedOrders[T] = { - new MatchedOrders(SortedAskOrders.empty(askOrdering), SortedBidOrders.empty(bidOrdering)) + def empty[T <: Tradable]: MatchedOrders[T] = { + new MatchedOrders(SortedAskOrders.empty, SortedBidOrders.empty) } } diff --git a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedAskOrders.scala b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedAskOrders.scala index 8e453be..d9140af 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedAskOrders.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedAskOrders.scala @@ -25,18 +25,24 @@ import scala.collection.immutable.TreeSet private[orderbooks] class SortedAskOrders[T <: Tradable] private(existing: Map[UUID, LimitAskOrder[T]], sorted: TreeSet[(UUID, LimitAskOrder[T])], - val numberUnits: Quantity) { + val quantity: Quantity) { + + def apply(uuid: UUID): LimitAskOrder[T] = existing(uuid) + + def + (kv: (UUID, LimitAskOrder[T])): SortedAskOrders[T] = { + new SortedAskOrders(existing + kv, sorted + kv, quantity + kv._2.quantity) + } def - (uuid: UUID): SortedAskOrders[T] = existing.get(uuid) match { case Some(order) => - val remaining = Quantity(numberUnits.value - order.quantity.value) + val remaining = Quantity(quantity.value - order.quantity.value) new SortedAskOrders(existing - uuid, sorted - ((uuid, order)), remaining) case None => this } def contains(uuid: UUID): Boolean = existing.contains(uuid) - def head: LimitAskOrder[T] = sorted.head + def head: (UUID, LimitAskOrder[T]) = sorted.head val headOption: Option[(UUID, LimitAskOrder[T])] = sorted.headOption @@ -44,22 +50,38 @@ private[orderbooks] class SortedAskOrders[T <: Tradable] private(existing: Map[U val ordering: Ordering[(UUID, LimitAskOrder[T])] = sorted.ordering - def tail: SortedAskOrders[T] = { - val remainingQuantity = numberUnits - head.quantity - new SortedAskOrders(existing.tail, sorted.tail, remainingQuantity) + def splitAt(quantity: Quantity): (SortedAskOrders[T], SortedAskOrders[T]) = { + + def split(order: LimitAskOrder[T], quantity: Quantity): (LimitAskOrder[T], LimitAskOrder[T]) = { + val residual = order.quantity - Quantity(1) + (order.withQuantity(Quantity(1)), order.withQuantity(residual)) + } + + @annotation.tailrec + def loop(in: SortedAskOrders[T], out: SortedAskOrders[T]): (SortedAskOrders[T], SortedAskOrders[T]) = { + if (in.quantity == quantity) { + (in, out) + } else { + val (uuid, askOrder) = out.head + val (matched, residual) = split(askOrder, Quantity(1)) + loop(in + (uuid -> matched), out.updated(uuid, residual)) + } + } + loop(SortedAskOrders.empty[T], this) } def updated(uuid: UUID, order: LimitAskOrder[T]): SortedAskOrders[T] = { - val additional = numberUnits + order.quantity - new SortedAskOrders(existing.updated(uuid, order), sorted + ((uuid, order)), additional) + val askOrder = this(uuid) + val change = askOrder.quantity - order.quantity + new SortedAskOrders(existing.updated(uuid, order), sorted - ((uuid, askOrder)) + ((uuid, order)), quantity + change) } } object SortedAskOrders { - def empty[T <: Tradable](ordering: Ordering[LimitAskOrder[T]]): SortedAskOrders[T] = { - new SortedAskOrders(Map.empty[UUID, LimitAskOrder[T]], TreeSet.empty[(UUID, LimitAskOrder[T])](ordering), Quantity(0)) + def empty[T <: Tradable]: SortedAskOrders[T] = { + new SortedAskOrders(Map.empty[UUID, LimitAskOrder[T]], TreeSet.empty[(UUID, LimitAskOrder[T])], Quantity(0)) } } diff --git a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrders.scala b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrders.scala index baa6786..93c86ce 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrders.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrders.scala @@ -25,33 +25,55 @@ import scala.collection.immutable.TreeSet private[orderbooks] class SortedBidOrders[T <: Tradable] private(existing: Map[UUID, LimitBidOrder[T]], sorted: TreeSet[(UUID, LimitBidOrder[T])], - val numberUnits: Quantity) { + val quantity: Quantity) { + + def apply(uuid: UUID): LimitBidOrder[T] = existing(uuid) + + def + (kv: (UUID, LimitBidOrder[T])): SortedBidOrders[T] = { + new SortedBidOrders(existing + kv, sorted + kv, quantity + kv._2.quantity) + } def - (uuid: UUID): SortedBidOrders[T] = existing.get(uuid) match { case Some(order) => - val remaining = Quantity(numberUnits.value - order.quantity.value) + val remaining = Quantity(quantity.value - order.quantity.value) new SortedBidOrders(existing - uuid, sorted - ((uuid, order)), remaining) case None => this } def contains(uuid: UUID): Boolean = existing.contains(uuid) - def head: LimitBidOrder[T] = sorted.head + def head: (UUID, LimitBidOrder[T]) = sorted.head val headOption: Option[(UUID, LimitBidOrder[T])] = sorted.headOption val isEmpty: Boolean = existing.isEmpty && sorted.isEmpty - val ordering: Ordering[LimitBidOrder[T]] = sorted.ordering - - def tail: SortedBidOrders[T] = { - val remainingQuantity = numberUnits - head.quantity - new SortedBidOrders(existing.tail, sorted.tail, remainingQuantity) + val ordering: Ordering[(UUID, LimitBidOrder[T])] = sorted.ordering + + def splitAt(quantity: Quantity): (SortedBidOrders[T], SortedBidOrders[T]) = { + + def split(order: LimitBidOrder[T], quantity: Quantity): (LimitBidOrder[T], LimitBidOrder[T]) = { + val residual = order.quantity - Quantity(1) + (order.withQuantity(Quantity(1)), order.withQuantity(residual)) + } + + @annotation.tailrec + def loop(in: SortedBidOrders[T], out: SortedBidOrders[T]): (SortedBidOrders[T], SortedBidOrders[T]) = { + if (in.quantity == quantity) { + (in, out) + } else { + val (uuid, bidOrder) = out.head + val (matched, residual) = split(bidOrder, Quantity(1)) + loop(in + (uuid -> matched), out.updated(uuid, residual)) + } + } + loop(SortedBidOrders.empty[T], this) } def updated(uuid: UUID, order: LimitBidOrder[T]): SortedBidOrders[T] = { - val additional = numberUnits + order.quantity - new SortedBidOrders(existing.updated(uuid, order), sorted + ((uuid, order)), additional) + val bidOrder = this(uuid) + val change = bidOrder.quantity - order.quantity + new SortedBidOrders(existing.updated(uuid, order), sorted - ((uuid, bidOrder)) + ((uuid, order)), quantity + change) } } @@ -59,8 +81,8 @@ private[orderbooks] class SortedBidOrders[T <: Tradable] private(existing: Map[U object SortedBidOrders { - def empty[T <: Tradable](ordering: Ordering[LimitBidOrder[T]]): SortedBidOrders[T] = { - new SortedBidOrders(Map.empty[UUID, LimitBidOrder[T]], TreeSet.empty[(UUID, LimitBidOrder[T])](ordering), Quantity(0)) + def empty[T <: Tradable]: SortedBidOrders[T] = { + new SortedBidOrders(Map.empty[UUID, LimitBidOrder[T]], TreeSet.empty[(UUID, LimitBidOrder[T])], Quantity(0)) } } From f9d0eb0d32c6f071e6d956b6045030ffc25f719b Mon Sep 17 00:00:00 2001 From: davidrpugh Date: Mon, 3 Apr 2017 16:43:42 +0300 Subject: [PATCH 16/25] Fixed compiler errors. --- .../auctions/multiunit/LimitAskOrder.scala | 2 +- .../auctions/multiunit/LimitBidOrder.scala | 2 +- .../orderbooks/FourHeapOrderBook.scala | 10 +++--- .../multiunit/orderbooks/MatchedOrders.scala | 12 +++---- .../orderbooks/SortedAskOrders.scala | 12 ++++--- .../orderbooks/SortedBidOrders.scala | 12 ++++--- .../orderbooks/UnMatchedOrders.scala | 8 ++--- .../org/economicsl/auctions/Security.scala | 6 ++++ .../orderbooks/SortedAskOrdersSpec.scala | 34 +++++++++++++++++++ .../orderbooks/SortedBidOrdersSpec.scala | 34 +++++++++++++++++++ 10 files changed, 105 insertions(+), 27 deletions(-) create mode 100644 src/test/scala/org/economicsl/auctions/Security.scala create mode 100644 src/test/scala/org/economicsl/auctions/multiunit/orderbooks/SortedAskOrdersSpec.scala create mode 100644 src/test/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrdersSpec.scala diff --git a/src/main/scala/org/economicsl/auctions/multiunit/LimitAskOrder.scala b/src/main/scala/org/economicsl/auctions/multiunit/LimitAskOrder.scala index cbd613d..b6db398 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/LimitAskOrder.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/LimitAskOrder.scala @@ -40,7 +40,7 @@ object LimitAskOrder { extends LimitAskOrder[T] { def withQuantity(quantity: Quantity): LimitAskOrder[T] = { - require(quantity.value < quantity.value) + require(quantity <= this.quantity) copy(quantity = quantity) } diff --git a/src/main/scala/org/economicsl/auctions/multiunit/LimitBidOrder.scala b/src/main/scala/org/economicsl/auctions/multiunit/LimitBidOrder.scala index 89c75c2..f6a7c65 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/LimitBidOrder.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/LimitBidOrder.scala @@ -39,7 +39,7 @@ object LimitBidOrder { extends LimitBidOrder[T] { def withQuantity(quantity: Quantity): LimitBidOrder[T] = { - require(quantity.value < quantity.value) + require(quantity <= this.quantity) copy(quantity = quantity) } diff --git a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/FourHeapOrderBook.scala b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/FourHeapOrderBook.scala index 99467c2..2649cca 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/FourHeapOrderBook.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/FourHeapOrderBook.scala @@ -32,7 +32,7 @@ class FourHeapOrderBook[T <: Tradable] private(matchedOrders: MatchedOrders[T], val excessDemand = bidOrder.quantity - askOrder.quantity if (excessDemand > Quantity(0)) { val (matched, residual) = split(bidOrder, excessDemand) - new FourHeapOrderBook(matchedOrders.removeAndReplace((uuid, askOrder), (uuid2, residual)), unMatchedOrders.updated(uuid2, matched)) + ??? // new FourHeapOrderBook(matchedOrders.removeAndReplace((uuid, askOrder), (uuid2, residual)), unMatchedOrders.updated(uuid2, matched)) } else if (excessDemand < Quantity(0)) { ??? // split the ask order; removed the matched portion of the askOrder and the bidOrder; recurse with the residual askOrder; add residual askOrder to unMatchedOrders } else { @@ -59,7 +59,7 @@ class FourHeapOrderBook[T <: Tradable] private(matchedOrders: MatchedOrders[T], new FourHeapOrderBook(matchedOrders.updated((uuid, order), (uuid2, bidOrder)), unMatchedOrders - uuid2) } - case (Some(askOrder), _) if order.value <= askOrder.value => + case (Some((_, askOrder)), _) if order.value <= askOrder.value => ??? case _ => new FourHeapOrderBook(matchedOrders, unMatchedOrders.updated(uuid, order)) } @@ -67,9 +67,9 @@ class FourHeapOrderBook[T <: Tradable] private(matchedOrders: MatchedOrders[T], def updated(uuid: UUID, order: LimitBidOrder[T]): FourHeapOrderBook[T] = { (matchedOrders.askOrders.headOption, unMatchedOrders.bidOrders.headOption) match { - case (Some((???, askOrder)), Some((_, bidOrder))) if order.value <= bidOrder.value && askOrder.value <= bidOrder.value => + case (Some((uuid2, askOrder)), Some((_, bidOrder))) if order.value <= bidOrder.value && askOrder.value <= bidOrder.value => ??? - case (Some(askOrder), _) if order.value <= askOrder.value => + case (Some((_, askOrder)), _) if order.value <= askOrder.value => ??? case _ => new FourHeapOrderBook(matchedOrders, unMatchedOrders.updated(uuid, order)) } @@ -95,7 +95,7 @@ class FourHeapOrderBook[T <: Tradable] private(matchedOrders: MatchedOrders[T], object FourHeapOrderBook { - def empty[T <: Tradable](implicit askOrdering: Ordering[LimitAskOrder[T]], bidOrdering: Ordering[LimitBidOrder[T]]): FourHeapOrderBook[T] = { + def empty[T <: Tradable](implicit askOrdering: Ordering[(UUID, LimitAskOrder[T])], bidOrdering: Ordering[(UUID, LimitBidOrder[T])]): FourHeapOrderBook[T] = { val matchedOrders = MatchedOrders.empty(askOrdering.reverse, bidOrdering.reverse) val unMatchedOrders = UnMatchedOrders.empty(askOrdering, bidOrdering) new FourHeapOrderBook(matchedOrders, unMatchedOrders) diff --git a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/MatchedOrders.scala b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/MatchedOrders.scala index a0e8c3a..0ea05f4 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/MatchedOrders.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/MatchedOrders.scala @@ -39,9 +39,9 @@ private[orderbooks] class MatchedOrders[T <: Tradable] private(val askOrders: So } } - val askOrdering: Ordering[LimitAskOrder[T]] = askOrders.ordering + val askOrdering: Ordering[(UUID, LimitAskOrder[T])] = askOrders.ordering - val bidOrdering: Ordering[LimitBidOrder[T]] = bidOrders.ordering + val bidOrdering: Ordering[(UUID, LimitBidOrder[T])] = bidOrders.ordering def contains(uuid: UUID): Boolean = askOrders.contains(uuid) || bidOrders.contains(uuid) @@ -49,10 +49,6 @@ private[orderbooks] class MatchedOrders[T <: Tradable] private(val askOrders: So new MatchedOrders(askOrders - askOrder._1, bidOrders.updated(bidOrder._1, bidOrder._2)) } - def removeAndReplace(bidOrder: (UUID, LimitBidOrder[T]), askOrder: (UUID, LimitAskOrder[T])): MatchedOrders[T] = { - new MatchedOrders(askOrders.updated(askOrder._1, askOrder._2), bidOrders - bidOrder._1) - } - def updated(askOrder: (UUID, LimitAskOrder[T]), bidOrder: (UUID, LimitBidOrder[T])): MatchedOrders[T] = { val (uuid1, order1) = askOrder; val (uuid2, order2) = bidOrder new MatchedOrders(askOrders.updated(uuid1, order1), bidOrders.updated(uuid2, order2)) @@ -74,8 +70,8 @@ private[orderbooks] object MatchedOrders { * 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[T <: Tradable]: MatchedOrders[T] = { - new MatchedOrders(SortedAskOrders.empty, SortedBidOrders.empty) + def empty[T <: Tradable](askOrdering: Ordering[(UUID, LimitAskOrder[T])], bidOrdering: Ordering[(UUID, LimitBidOrder[T])]): MatchedOrders[T] = { + new MatchedOrders(SortedAskOrders.empty(askOrdering), SortedBidOrders.empty(bidOrdering)) } } diff --git a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedAskOrders.scala b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedAskOrders.scala index d9140af..00c2191 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedAskOrders.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedAskOrders.scala @@ -26,6 +26,7 @@ import scala.collection.immutable.TreeSet private[orderbooks] class SortedAskOrders[T <: Tradable] private(existing: Map[UUID, LimitAskOrder[T]], sorted: TreeSet[(UUID, LimitAskOrder[T])], val quantity: Quantity) { + assert(existing.size == sorted.size) def apply(uuid: UUID): LimitAskOrder[T] = existing(uuid) @@ -50,6 +51,8 @@ private[orderbooks] class SortedAskOrders[T <: Tradable] private(existing: Map[U val ordering: Ordering[(UUID, LimitAskOrder[T])] = sorted.ordering + val size: Int = existing.size + def splitAt(quantity: Quantity): (SortedAskOrders[T], SortedAskOrders[T]) = { def split(order: LimitAskOrder[T], quantity: Quantity): (LimitAskOrder[T], LimitAskOrder[T]) = { @@ -67,21 +70,22 @@ private[orderbooks] class SortedAskOrders[T <: Tradable] private(existing: Map[U loop(in + (uuid -> matched), out.updated(uuid, residual)) } } - loop(SortedAskOrders.empty[T], this) + loop(SortedAskOrders.empty[T](sorted.ordering), this) } def updated(uuid: UUID, order: LimitAskOrder[T]): SortedAskOrders[T] = { val askOrder = this(uuid) - val change = askOrder.quantity - order.quantity + val change = order.quantity - askOrder.quantity new SortedAskOrders(existing.updated(uuid, order), sorted - ((uuid, askOrder)) + ((uuid, order)), quantity + change) } } + object SortedAskOrders { - def empty[T <: Tradable]: SortedAskOrders[T] = { - new SortedAskOrders(Map.empty[UUID, LimitAskOrder[T]], TreeSet.empty[(UUID, LimitAskOrder[T])], Quantity(0)) + def empty[T <: Tradable](implicit ordering: Ordering[(UUID, LimitAskOrder[T])]): SortedAskOrders[T] = { + new SortedAskOrders(Map.empty[UUID, LimitAskOrder[T]], TreeSet.empty[(UUID, LimitAskOrder[T])](ordering), Quantity(0)) } } diff --git a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrders.scala b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrders.scala index 93c86ce..84e3417 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrders.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrders.scala @@ -27,6 +27,8 @@ private[orderbooks] class SortedBidOrders[T <: Tradable] private(existing: Map[U sorted: TreeSet[(UUID, LimitBidOrder[T])], val quantity: Quantity) { + assert(existing.size == sorted.size) + def apply(uuid: UUID): LimitBidOrder[T] = existing(uuid) def + (kv: (UUID, LimitBidOrder[T])): SortedBidOrders[T] = { @@ -50,6 +52,8 @@ private[orderbooks] class SortedBidOrders[T <: Tradable] private(existing: Map[U val ordering: Ordering[(UUID, LimitBidOrder[T])] = sorted.ordering + val size: Int = existing.size + def splitAt(quantity: Quantity): (SortedBidOrders[T], SortedBidOrders[T]) = { def split(order: LimitBidOrder[T], quantity: Quantity): (LimitBidOrder[T], LimitBidOrder[T]) = { @@ -67,12 +71,12 @@ private[orderbooks] class SortedBidOrders[T <: Tradable] private(existing: Map[U loop(in + (uuid -> matched), out.updated(uuid, residual)) } } - loop(SortedBidOrders.empty[T], this) + loop(SortedBidOrders.empty[T](sorted.ordering), this) } def updated(uuid: UUID, order: LimitBidOrder[T]): SortedBidOrders[T] = { val bidOrder = this(uuid) - val change = bidOrder.quantity - order.quantity + val change = order.quantity - bidOrder.quantity new SortedBidOrders(existing.updated(uuid, order), sorted - ((uuid, bidOrder)) + ((uuid, order)), quantity + change) } @@ -81,8 +85,8 @@ private[orderbooks] class SortedBidOrders[T <: Tradable] private(existing: Map[U object SortedBidOrders { - def empty[T <: Tradable]: SortedBidOrders[T] = { - new SortedBidOrders(Map.empty[UUID, LimitBidOrder[T]], TreeSet.empty[(UUID, LimitBidOrder[T])], Quantity(0)) + def empty[T <: Tradable](implicit ordering: Ordering[(UUID, LimitBidOrder[T])]): SortedBidOrders[T] = { + new SortedBidOrders(Map.empty[UUID, LimitBidOrder[T]], TreeSet.empty[(UUID, LimitBidOrder[T])](ordering), Quantity(0)) } } diff --git a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/UnMatchedOrders.scala b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/UnMatchedOrders.scala index b6a45d9..656e34b 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/UnMatchedOrders.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/UnMatchedOrders.scala @@ -24,7 +24,7 @@ import org.economicsl.auctions.multiunit.{LimitAskOrder, LimitBidOrder} private[orderbooks] class UnMatchedOrders[T <: Tradable] private(val askOrders: SortedAskOrders[T], val bidOrders: SortedBidOrders[T]) { - require(bidOrders.headOption.forall(bidOrder => bidOrder.limit <= askOrders.head.limit)) + require(bidOrders.headOption.forall{ case (_, bidOrder) => bidOrder.limit <= askOrders.head._2.limit }) /** Remove an order from the collection of unmatched orders. */ def - (uuid: UUID): UnMatchedOrders[T] = { @@ -35,9 +35,9 @@ private[orderbooks] class UnMatchedOrders[T <: Tradable] private(val askOrders: } } - val askOrdering: Ordering[LimitAskOrder[T]] = askOrders.ordering + val askOrdering: Ordering[(UUID, LimitAskOrder[T])] = askOrders.ordering - val bidOrdering: Ordering[LimitBidOrder[T]] = bidOrders.ordering + val bidOrdering: Ordering[(UUID, LimitBidOrder[T])] = bidOrders.ordering /** Check whether an order is contained in the collection of unmatched orders using. */ def contains(uuid: UUID): Boolean = askOrders.contains(uuid) || bidOrders.contains(uuid) @@ -66,7 +66,7 @@ private[orderbooks] object UnMatchedOrders { * based on `limit` price; the heap used to store store the `BidOrder` instances is * ordered from high to low based on `limit` price. */ - def empty[T <: Tradable](askOrdering: Ordering[LimitAskOrder[T]], bidOrdering: Ordering[LimitBidOrder[T]]): UnMatchedOrders[T] = { + def empty[T <: Tradable](askOrdering: Ordering[(UUID, LimitAskOrder[T])], bidOrdering: Ordering[(UUID, LimitBidOrder[T])]): UnMatchedOrders[T] = { new UnMatchedOrders(SortedAskOrders.empty(askOrdering), SortedBidOrders.empty(bidOrdering)) } diff --git a/src/test/scala/org/economicsl/auctions/Security.scala b/src/test/scala/org/economicsl/auctions/Security.scala new file mode 100644 index 0000000..e62e7e4 --- /dev/null +++ b/src/test/scala/org/economicsl/auctions/Security.scala @@ -0,0 +1,6 @@ +package org.economicsl.auctions + + +sealed trait Security extends Tradable + +class GoogleStock extends Security \ No newline at end of file diff --git a/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/SortedAskOrdersSpec.scala b/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/SortedAskOrdersSpec.scala new file mode 100644 index 0000000..35ed542 --- /dev/null +++ b/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/SortedAskOrdersSpec.scala @@ -0,0 +1,34 @@ +package org.economicsl.auctions.multiunit.orderbooks + +import java.util.UUID + +import org.economicsl.auctions.{GoogleStock, Price, Quantity, multiunit} +import org.scalatest.{FlatSpec, Matchers} + + +class SortedAskOrdersSpec extends FlatSpec with Matchers { + + "A SortedAskOrderBook" should "update an existing order" in { + + // Create a multi-unit limit ask order + val issuer = UUID.randomUUID() + val google = new GoogleStock + val order = multiunit.LimitAskOrder(issuer, Price(10), Quantity(100), google) + + // Create an empty order book and add the order + val empty = SortedAskOrders.empty[GoogleStock] + val nonEmpty = empty + (issuer -> order) + nonEmpty.head should be ((issuer, order)) + + // Create a revised order and update the order book + val revised = order.withQuantity(Quantity(1)) + val updated = nonEmpty.updated(issuer, revised) + + // Check that update is successful! + updated.head should be ((issuer, revised)) + updated.size should be (1) + updated.quantity should be (revised.quantity) + + } + +} diff --git a/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrdersSpec.scala b/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrdersSpec.scala new file mode 100644 index 0000000..d51adae --- /dev/null +++ b/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrdersSpec.scala @@ -0,0 +1,34 @@ +package org.economicsl.auctions.multiunit.orderbooks + +import java.util.UUID + +import org.economicsl.auctions.{GoogleStock, Price, Quantity, multiunit} +import org.scalatest.{FlatSpec, Matchers} + + +class SortedBidOrdersSpec extends FlatSpec with Matchers { + + "A SortedBidOrderBook" should "update an existing order" in { + + // Create a multi-unit limit ask order + val issuer = UUID.randomUUID() + val google = new GoogleStock + val order = multiunit.LimitBidOrder(issuer, Price(10), Quantity(100), google) + + // Create an empty order book and add the order + val empty = SortedBidOrders.empty[GoogleStock] + val nonEmpty = empty + (issuer -> order) + nonEmpty.head should be ((issuer, order)) + + // Create a revised order and update the order book + val revised = order.withQuantity(Quantity(1)) + val updated = nonEmpty.updated(issuer, revised) + + // Check that update is successful! + updated.head should be ((issuer, revised)) + updated.size should be (1) + updated.quantity should be (revised.quantity) + + } + +} From 9df2ea09228a97c427116a970e4bd93401dc69a2 Mon Sep 17 00:00:00 2001 From: davidrpugh Date: Mon, 3 Apr 2017 16:53:36 +0300 Subject: [PATCH 17/25] Added test to travis.yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 10e5cde..01c41c3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,7 +19,7 @@ branches: - develop script: - - sbt clean compile + - sbt clean compile test before_cache: # Tricks to avoid unnecessary cache updates From 8e81dec353dc16af50b2d073a6a618079c9a0838 Mon Sep 17 00:00:00 2001 From: davidrpugh Date: Mon, 3 Apr 2017 18:45:35 +0300 Subject: [PATCH 18/25] Significant progress! --- build.sbt | 11 +++++-- .../auctions/multiunit/LimitAskOrder.scala | 1 + .../orderbooks/SortedAskOrders.scala | 24 ++++++++------- .../orderbooks/SortedBidOrders.scala | 18 ++++++----- .../orderbooks/SortedAskOrdersSpec.scala | 30 +++++++++++++++++++ .../orderbooks/SortedBidOrdersSpec.scala | 30 +++++++++++++++++++ 6 files changed, 94 insertions(+), 20 deletions(-) diff --git a/build.sbt b/build.sbt index f9929b9..a3dbf6c 100644 --- a/build.sbt +++ b/build.sbt @@ -1,17 +1,22 @@ -import xsbti.compile - name := "auctions" version := "0.1.0-alpha" scalaVersion := "2.12.1" + // Useful scala compiler options scalacOptions ++= Seq( "-feature", // tells the compiler to provide information about misused language features "-language:implicitConversions" // eliminates the need to import implicit conversions for each usage ) +libraryDependencies ++= Seq( + "org.scalactic" %% "scalactic" % "3.0.1", + "org.scalatest" %% "scalatest" % "3.0.1" % "test" +) // In our project Java depends on Scala, but not the other way round! -compileOrder := CompileOrder.ScalaThenJava \ No newline at end of file +compileOrder := CompileOrder.ScalaThenJava + + diff --git a/src/main/scala/org/economicsl/auctions/multiunit/LimitAskOrder.scala b/src/main/scala/org/economicsl/auctions/multiunit/LimitAskOrder.scala index b6db398..a2344f6 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/LimitAskOrder.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/LimitAskOrder.scala @@ -40,6 +40,7 @@ object LimitAskOrder { extends LimitAskOrder[T] { def withQuantity(quantity: Quantity): LimitAskOrder[T] = { + println(quantity, this.quantity) require(quantity <= this.quantity) copy(quantity = quantity) } diff --git a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedAskOrders.scala b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedAskOrders.scala index 00c2191..6678381 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedAskOrders.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedAskOrders.scala @@ -56,21 +56,25 @@ private[orderbooks] class SortedAskOrders[T <: Tradable] private(existing: Map[U def splitAt(quantity: Quantity): (SortedAskOrders[T], SortedAskOrders[T]) = { def split(order: LimitAskOrder[T], quantity: Quantity): (LimitAskOrder[T], LimitAskOrder[T]) = { - val residual = order.quantity - Quantity(1) - (order.withQuantity(Quantity(1)), order.withQuantity(residual)) + val residual = order.quantity - quantity + (order.withQuantity(quantity), order.withQuantity(residual)) } @annotation.tailrec def loop(in: SortedAskOrders[T], out: SortedAskOrders[T]): (SortedAskOrders[T], SortedAskOrders[T]) = { - if (in.quantity == quantity) { - (in, out) - } else { - val (uuid, askOrder) = out.head - val (matched, residual) = split(askOrder, Quantity(1)) - loop(in + (uuid -> matched), out.updated(uuid, residual)) - } - } + val unMatched = quantity - in.quantity + val (uuid, askOrder) = out.head + if (unMatched > askOrder.quantity) { + loop(in + (uuid -> askOrder), out - uuid) + } else if (unMatched < askOrder.quantity) { + val (matched, residual) = split(askOrder, unMatched) + (in + (uuid -> matched), out.updated(uuid, residual)) + } else { + (in + (uuid -> askOrder), out - uuid) + } + } loop(SortedAskOrders.empty[T](sorted.ordering), this) + } def updated(uuid: UUID, order: LimitAskOrder[T]): SortedAskOrders[T] = { diff --git a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrders.scala b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrders.scala index 84e3417..daecd66 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrders.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrders.scala @@ -57,21 +57,25 @@ private[orderbooks] class SortedBidOrders[T <: Tradable] private(existing: Map[U def splitAt(quantity: Quantity): (SortedBidOrders[T], SortedBidOrders[T]) = { def split(order: LimitBidOrder[T], quantity: Quantity): (LimitBidOrder[T], LimitBidOrder[T]) = { - val residual = order.quantity - Quantity(1) - (order.withQuantity(Quantity(1)), order.withQuantity(residual)) + val residual = order.quantity - quantity + (order.withQuantity(quantity), order.withQuantity(residual)) } @annotation.tailrec def loop(in: SortedBidOrders[T], out: SortedBidOrders[T]): (SortedBidOrders[T], SortedBidOrders[T]) = { - if (in.quantity == quantity) { - (in, out) + val unMatched = quantity - in.quantity + val (uuid, bidOrder) = out.head + if (unMatched > bidOrder.quantity) { + loop(in + (uuid -> bidOrder), out - uuid) + } else if (unMatched < bidOrder.quantity) { + val (matched, residual) = split(bidOrder, unMatched) + (in + (uuid -> matched), out.updated(uuid, residual)) } else { - val (uuid, bidOrder) = out.head - val (matched, residual) = split(bidOrder, Quantity(1)) - loop(in + (uuid -> matched), out.updated(uuid, residual)) + (in + (uuid -> bidOrder), out - uuid) } } loop(SortedBidOrders.empty[T](sorted.ordering), this) + } def updated(uuid: UUID, order: LimitBidOrder[T]): SortedBidOrders[T] = { diff --git a/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/SortedAskOrdersSpec.scala b/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/SortedAskOrdersSpec.scala index 35ed542..3c7c08b 100644 --- a/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/SortedAskOrdersSpec.scala +++ b/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/SortedAskOrdersSpec.scala @@ -31,4 +31,34 @@ class SortedAskOrdersSpec extends FlatSpec with Matchers { } + "A SortedAskOrderBook" should "split itself into two pieces" in { + + val google = new GoogleStock + + // Create some multi-unit limit ask orders + val issuer1 = UUID.randomUUID() + val order1 = multiunit.LimitAskOrder(issuer1, Price(10), Quantity(10), google) + + val issuer2 = UUID.randomUUID() + val order2 = multiunit.LimitAskOrder(issuer2, Price(5), Quantity(15), google) + + val issuer3 = UUID.randomUUID() + val order3 = multiunit.LimitAskOrder(issuer3, Price(15), Quantity(100), google) + + // Create an empty order book and add the orders + val empty = SortedAskOrders.empty[GoogleStock] + val nonEmpty = empty + (issuer1 -> order1) + (issuer2 -> order2) + (issuer3 -> order3) + + // Create a revised order and update the order book + val (matched, residual) = nonEmpty.splitAt(Quantity(57)) + + // Check that splitAt was successful + matched.quantity should be (Quantity(57)) + matched.size should be(3) + + residual.quantity should be (Quantity(125 - 57)) + residual.size should be(1) + + } + } diff --git a/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrdersSpec.scala b/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrdersSpec.scala index d51adae..cf23139 100644 --- a/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrdersSpec.scala +++ b/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrdersSpec.scala @@ -31,4 +31,34 @@ class SortedBidOrdersSpec extends FlatSpec with Matchers { } + "A SortedBidOrderBook" should "split itself into two pieces" in { + + val google = new GoogleStock + + // Create some multi-unit limit bid orders + val issuer1 = UUID.randomUUID() + val order1 = multiunit.LimitBidOrder(issuer1, Price(10), Quantity(10), google) + + val issuer2 = UUID.randomUUID() + val order2 = multiunit.LimitBidOrder(issuer2, Price(5), Quantity(15), google) + + val issuer3 = UUID.randomUUID() + val order3 = multiunit.LimitBidOrder(issuer3, Price(15), Quantity(100), google) + + // Create an empty order book and add the orders + val empty = SortedBidOrders.empty[GoogleStock] + val nonEmpty = empty + (issuer1 -> order1) + (issuer2 -> order2) + (issuer3 -> order3) + + // Create a revised order and update the order book + val (matched, residual) = nonEmpty.splitAt(Quantity(57)) + + // Check that splitAt was successful + matched.quantity should be (Quantity(57)) + matched.size should be(1) + + residual.quantity should be (Quantity(125 - 57)) + residual.size should be(3) + + } + } From 95b319cc7fe98050010c78cbdb9c295c91a11ee8 Mon Sep 17 00:00:00 2001 From: davidrpugh Date: Mon, 3 Apr 2017 20:52:01 +0300 Subject: [PATCH 19/25] Fixed orderings. --- src/main/scala/org/economicsl/auctions/Price.scala | 2 ++ .../org/economicsl/auctions/multiunit/LimitAskOrder.scala | 5 +++-- .../org/economicsl/auctions/multiunit/LimitBidOrder.scala | 4 +++- .../org/economicsl/auctions/multiunit/SinglePricePoint.scala | 4 ++-- .../auctions/multiunit/orderbooks/SortedBidOrders.scala | 2 +- .../org/economicsl/auctions/singleunit/LimitAskOrder.scala | 1 - 6 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/main/scala/org/economicsl/auctions/Price.scala b/src/main/scala/org/economicsl/auctions/Price.scala index dcf0eac..3ca8e18 100644 --- a/src/main/scala/org/economicsl/auctions/Price.scala +++ b/src/main/scala/org/economicsl/auctions/Price.scala @@ -23,6 +23,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! + } diff --git a/src/main/scala/org/economicsl/auctions/multiunit/LimitAskOrder.scala b/src/main/scala/org/economicsl/auctions/multiunit/LimitAskOrder.scala index a2344f6..1596f39 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/LimitAskOrder.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/LimitAskOrder.scala @@ -30,7 +30,9 @@ trait LimitAskOrder[+T <: Tradable] extends AskOrder[T] with SinglePricePoint[T] */ object LimitAskOrder { - implicit def ordering[O <: LimitAskOrder[_ <: Tradable]]: Ordering[O] = SinglePricePoint.ordering[O] + implicit def ordering[O <: LimitAskOrder[_ <: Tradable]]: Ordering[(UUID, O)] = { + Ordering.by{case (uuid, order) => (order.limit, uuid) } + } def apply[T <: Tradable](issuer: UUID, limit: Price, quantity: Quantity, tradable: T): LimitAskOrder[T] = { SinglePricePointImpl(issuer, limit, quantity, tradable) @@ -40,7 +42,6 @@ object LimitAskOrder { extends LimitAskOrder[T] { def withQuantity(quantity: Quantity): LimitAskOrder[T] = { - println(quantity, this.quantity) require(quantity <= this.quantity) copy(quantity = quantity) } diff --git a/src/main/scala/org/economicsl/auctions/multiunit/LimitBidOrder.scala b/src/main/scala/org/economicsl/auctions/multiunit/LimitBidOrder.scala index f6a7c65..8a54880 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/LimitBidOrder.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/LimitBidOrder.scala @@ -29,7 +29,9 @@ trait LimitBidOrder[+T <: Tradable] extends BidOrder[T] with SinglePricePoint[T] */ object LimitBidOrder { - implicit def ordering[O <: LimitBidOrder[_ <: Tradable]]: Ordering[O] = SinglePricePoint.ordering[O].reverse + implicit def ordering[O <: LimitBidOrder[_ <: Tradable]]: Ordering[(UUID, O)] = { + Ordering.by{case (uuid, order) => (-order.limit, uuid) } + } def apply[T <: Tradable](issuer: UUID, limit: Price, quantity: Quantity, tradable: T): LimitBidOrder[T] = { SinglePricePointImpl(issuer, limit, quantity, tradable) diff --git a/src/main/scala/org/economicsl/auctions/multiunit/SinglePricePoint.scala b/src/main/scala/org/economicsl/auctions/multiunit/SinglePricePoint.scala index 1c4cc70..819fa1b 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/SinglePricePoint.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/SinglePricePoint.scala @@ -43,14 +43,14 @@ trait SinglePricePoint[+T <: Tradable] extends PriceQuantitySchedule[T] { /** Companion object for the `SinglePricePoint` trait. * - * Defines a basic ordering for anything that mixes in the `SinglePricePoint` trait. + * Defines a basic ordering for any `Order` that mixes in the `SinglePricePoint` trait. */ object SinglePricePoint { /** All `Order` instances that mixin `SinglePricePoint` are ordered by `limit` from lowest to highest. * * @tparam O the sub-type of `Order with SinglePricePoint` that is being ordered. - * @return and `Ordering` defined over `Order with SinglePricePoint` instances. + * @return an `Ordering` defined over `Order with SinglePricePoint` instances. */ def ordering[O <: Order[_ <: Tradable] with SinglePricePoint[_ <: Tradable]]: Ordering[O] = { Ordering.by(o => (o.limit, o.issuer)) diff --git a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrders.scala b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrders.scala index daecd66..51301b9 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrders.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrders.scala @@ -24,7 +24,7 @@ import scala.collection.immutable.TreeSet private[orderbooks] class SortedBidOrders[T <: Tradable] private(existing: Map[UUID, LimitBidOrder[T]], - sorted: TreeSet[(UUID, LimitBidOrder[T])], + val sorted: TreeSet[(UUID, LimitBidOrder[T])], val quantity: Quantity) { assert(existing.size == sorted.size) diff --git a/src/main/scala/org/economicsl/auctions/singleunit/LimitAskOrder.scala b/src/main/scala/org/economicsl/auctions/singleunit/LimitAskOrder.scala index 6be7d39..c413f94 100644 --- a/src/main/scala/org/economicsl/auctions/singleunit/LimitAskOrder.scala +++ b/src/main/scala/org/economicsl/auctions/singleunit/LimitAskOrder.scala @@ -27,7 +27,6 @@ object LimitAskOrder { implicit def ordering[O <: LimitAskOrder[_ <: Tradable]]: Ordering[O] = SingleUnit.ordering[O] - def apply[T <: Tradable](issuer: UUID, limit: Price, tradable: T): LimitAskOrder[T] = { SingleUnitImpl(issuer, limit, tradable) } From bb940d0c1be2b99349395e50c7c6be1ceac4ced5 Mon Sep 17 00:00:00 2001 From: davidrpugh Date: Tue, 4 Apr 2017 14:30:06 +0300 Subject: [PATCH 20/25] More progress...I hope... --- .../org/economicsl/auctions/Quantity.scala | 2 + .../orderbooks/FourHeapOrderBook.scala | 184 +++++++++++++----- .../multiunit/orderbooks/MatchedOrders.scala | 69 ++++++- .../orderbooks/SortedAskOrders.scala | 10 +- .../orderbooks/SortedBidOrders.scala | 6 + .../orderbooks/UnMatchedOrders.scala | 32 ++- .../orderbooks/FourHeapOrderBookSpec.scala | 40 ++++ .../orderbooks/MatchedOrdersSpec.scala | 72 +++++++ .../orderbooks/SortedAskOrdersSpec.scala | 2 +- 9 files changed, 352 insertions(+), 65 deletions(-) create mode 100644 src/test/scala/org/economicsl/auctions/multiunit/orderbooks/FourHeapOrderBookSpec.scala create mode 100644 src/test/scala/org/economicsl/auctions/multiunit/orderbooks/MatchedOrdersSpec.scala diff --git a/src/main/scala/org/economicsl/auctions/Quantity.scala b/src/main/scala/org/economicsl/auctions/Quantity.scala index 5139fa0..cf789ed 100644 --- a/src/main/scala/org/economicsl/auctions/Quantity.scala +++ b/src/main/scala/org/economicsl/auctions/Quantity.scala @@ -27,6 +27,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! + } diff --git a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/FourHeapOrderBook.scala b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/FourHeapOrderBook.scala index 2649cca..a84b9a7 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/FourHeapOrderBook.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/FourHeapOrderBook.scala @@ -23,55 +23,157 @@ import org.economicsl.auctions.multiunit.{LimitAskOrder, LimitBidOrder} class FourHeapOrderBook[T <: Tradable] private(matchedOrders: MatchedOrders[T], unMatchedOrders: UnMatchedOrders[T]) { + val isEmpty: Boolean = matchedOrders.isEmpty && unMatchedOrders.isEmpty + + val nonEmpty: Boolean = matchedOrders.nonEmpty || unMatchedOrders.nonEmpty + + /** Remove an order from the `OrderBook`. + * + * @param uuid the universal unique identifier corresponding to the order that should be removed. + * @return A new `OrderBook` with the order corresponding to the `uuid` removed. + * @note Because multi-unit orders are divisible, the order with the `uuid` might have been split in which case it + * will exist in both the matched and unmatched order sets. + */ def - (uuid: UUID): FourHeapOrderBook[T] = { - if (unMatchedOrders.contains(uuid)) { - new FourHeapOrderBook(matchedOrders, unMatchedOrders - uuid) + if (matchedOrders.contains(uuid) && unMatchedOrders.contains(uuid)) { + val (residualMatched, additionalUnMatched) = matchedOrders - uuid + val residualUnMatched = unMatchedOrders - uuid + new FourHeapOrderBook(residualMatched, residualUnMatched.mergeWith(additionalUnMatched)) + } else if (matchedOrders.contains(uuid)) { + val (residualMatched, additionalUnMatched) = matchedOrders - uuid + new FourHeapOrderBook(residualMatched, unMatchedOrders.mergeWith(additionalUnMatched)) } else { - val askOrder = matchedOrders.askOrders(uuid) - val (uuid2, bidOrder) = matchedOrders.bidOrders.head - val excessDemand = bidOrder.quantity - askOrder.quantity - if (excessDemand > Quantity(0)) { - val (matched, residual) = split(bidOrder, excessDemand) - ??? // new FourHeapOrderBook(matchedOrders.removeAndReplace((uuid, askOrder), (uuid2, residual)), unMatchedOrders.updated(uuid2, matched)) - } else if (excessDemand < Quantity(0)) { - ??? // split the ask order; removed the matched portion of the askOrder and the bidOrder; recurse with the residual askOrder; add residual askOrder to unMatchedOrders - } else { - ??? // remove the askOrder and the bidOrder from the matched orders - } + val residualUnMatched = unMatchedOrders - uuid + new FourHeapOrderBook(matchedOrders, unMatchedOrders.mergeWith(residualUnMatched)) } } - def takeWhileMatched: (Stream[(LimitAskOrder[T], LimitBidOrder[T])], FourHeapOrderBook[T]) = { - (matchedOrders.zipped, withEmptyMatchedOrders) + /** Add a new `LimitAskOrder` to the `OrderBook`. + * + * @param uuid + * @param order + * @return + * @note adding a new `LimitAskOrder` is non-trivial and there are several cases to consider. + */ + def + (uuid: UUID, order: LimitAskOrder[T]): FourHeapOrderBook[T] = { + (unMatchedOrders.bidOrders.headOption, matchedOrders.askOrders.headOption) match { + case (Some((out, bidOrder)), Some((in, askOrder))) => + if (order.limit <= bidOrder.limit && askOrder.limit <= bidOrder.limit) { + val residualUnMatched = unMatchedOrders - out + val (updatedMatched, optionalUnMatched) = matchedOrders + (uuid -> order, out -> bidOrder) + optionalUnMatched match { + case Some(additionalUnMatched) => + new FourHeapOrderBook(updatedMatched, residualUnMatched.mergeWith(additionalUnMatched)) + case None => + new FourHeapOrderBook(updatedMatched, residualUnMatched) + } + } else if (order.limit <= askOrder.limit) { + val (updatedMatched, additionalUnMatched) = matchedOrders.swap(uuid, order, in) + new FourHeapOrderBook(updatedMatched, unMatchedOrders.mergeWith(additionalUnMatched)) + } else { + new FourHeapOrderBook(matchedOrders, unMatchedOrders + (uuid, order)) + } + case (Some((out, bidOrder)), None) => + if (order.limit <= bidOrder.limit) { + val (updatedMatched, optionalUnMatched) = matchedOrders + (uuid -> order, out -> bidOrder) + optionalUnMatched match { + case Some(additionalUnMatched) => + new FourHeapOrderBook(updatedMatched, unMatchedOrders.mergeWith(additionalUnMatched)) + case None => + new FourHeapOrderBook(updatedMatched, unMatchedOrders) + } + } else { + new FourHeapOrderBook(matchedOrders, unMatchedOrders + (uuid, order)) + } + case (None, Some((in ,askOrder))) => + if (order.limit <= askOrder.limit) { + val (updatedMatched, additionalUnMatched) = matchedOrders.swap(uuid, order, in) + new FourHeapOrderBook(updatedMatched, unMatchedOrders.mergeWith(additionalUnMatched)) + } else { + new FourHeapOrderBook(matchedOrders, unMatchedOrders + (uuid, order)) + } + case (None, None) => new FourHeapOrderBook(matchedOrders, unMatchedOrders + (uuid, order)) + } } - def updated(uuid: UUID, order: LimitAskOrder[T]): FourHeapOrderBook[T] = { - (matchedOrders.askOrders.headOption, unMatchedOrders.bidOrders.headOption) match { - case (Some((_, askOrder)), Some((uuid2, bidOrder))) if order.value <= bidOrder.value && askOrder.value <= bidOrder.value => - val excessDemand = bidOrder.quantity - order.quantity - if (excessDemand > Quantity(0)) { - val residualUnMatchedOrders = unMatchedOrders - uuid2 - val (filled, residual) = (bidOrder.withQuantity(order.quantity), bidOrder.withQuantity(excessDemand)) // split the bidOrder! - new FourHeapOrderBook(matchedOrders.updated((uuid, order), (uuid2, filled)), residualUnMatchedOrders.updated(uuid2, residual)) - } else if (excessDemand < Quantity(0)) { - ??? + /** Add a new `LimitBidOrder` to the `OrderBook`. + * + * @param uuid + * @param order + * @return + */ + def + (uuid: UUID, order: LimitBidOrder[T]): FourHeapOrderBook[T] = { + (matchedOrders.bidOrders.headOption, unMatchedOrders.askOrders.headOption) match { + case (Some((in, bidOrder)), Some((out, askOrder))) => + if (order.limit >= askOrder.limit && bidOrder.limit >= askOrder.limit) { + val residualUnMatched = unMatchedOrders - out + val (updatedMatched, optionalUnMatched) = matchedOrders + (out -> askOrder, uuid -> 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) = matchedOrders.swap(uuid, order, in) + new FourHeapOrderBook(updatedMatched, unMatchedOrders.mergeWith(additionalUnMatched)) + } else { + new FourHeapOrderBook(matchedOrders, unMatchedOrders + (uuid, order)) + } + case (Some((in ,bidOrder)), None) => + if (order.limit >= bidOrder.limit) { + val (updatedMatched, additionalUnMatched) = matchedOrders.swap(uuid, order, in) + new FourHeapOrderBook(updatedMatched, unMatchedOrders.mergeWith(additionalUnMatched)) + } else { + new FourHeapOrderBook(matchedOrders, unMatchedOrders + (uuid, order)) + } + case (None, Some((out, askOrder))) => + if (order.limit >= askOrder.limit) { + val (updatedMatched, optionalUnMatched) = matchedOrders + (out -> askOrder, uuid -> order) + optionalUnMatched match { + case Some(additionalUnMatched) => + new FourHeapOrderBook(updatedMatched, unMatchedOrders.mergeWith(additionalUnMatched)) + case None => + new FourHeapOrderBook(updatedMatched, unMatchedOrders) + } } else { - new FourHeapOrderBook(matchedOrders.updated((uuid, order), (uuid2, bidOrder)), unMatchedOrders - uuid2) + new FourHeapOrderBook(matchedOrders, unMatchedOrders + (uuid, order)) } + case (None, None) => new FourHeapOrderBook(matchedOrders, unMatchedOrders + (uuid, order)) + } + } + + def contains(uuid: UUID): Boolean = matchedOrders.contains(uuid) || unMatchedOrders.contains(uuid) + + def takeWhileMatched: (Stream[(LimitAskOrder[T], LimitBidOrder[T])], FourHeapOrderBook[T]) = { + (matchedOrders.zipped, withEmptyMatchedOrders) + } - case (Some((_, askOrder)), _) if order.value <= askOrder.value => - ??? - case _ => new FourHeapOrderBook(matchedOrders, unMatchedOrders.updated(uuid, order)) + /** Update an existing `LimitAskOrder`. + * + * @note Because multi-unit orders are divisible, an order with the `uuid` might have been split in which case it + * will exist in both the matched and unmatched order sets. Thus if + */ + def update(uuid: UUID, order: LimitAskOrder[T]): FourHeapOrderBook[T] = { + if (contains(uuid)) { + val residualOrderBook = this - uuid + residualOrderBook + (uuid, order) + } else { + this + (uuid, order) } } - def updated(uuid: UUID, order: LimitBidOrder[T]): FourHeapOrderBook[T] = { - (matchedOrders.askOrders.headOption, unMatchedOrders.bidOrders.headOption) match { - case (Some((uuid2, askOrder)), Some((_, bidOrder))) if order.value <= bidOrder.value && askOrder.value <= bidOrder.value => - ??? - case (Some((_, askOrder)), _) if order.value <= askOrder.value => - ??? - case _ => new FourHeapOrderBook(matchedOrders, unMatchedOrders.updated(uuid, order)) + /** Update an existing `LimitBidOrder`. + * + * @note Because multi-unit orders are divisible, an order with the `uuid` might have been split in which case it + * will exist in both the matched and unmatched order sets. Thus if + */ + def update(uuid: UUID, order: LimitBidOrder[T]): FourHeapOrderBook[T] = { + if (contains(uuid)) { + val residualOrderBook = this - uuid + residualOrderBook + (uuid, order) + } else { + this + (uuid, order) } } @@ -80,16 +182,6 @@ class FourHeapOrderBook[T <: Tradable] private(matchedOrders: MatchedOrders[T], new FourHeapOrderBook[T](MatchedOrders.empty(askOrdering, bidOrdering), unMatchedOrders) } - private[this] def split(order: LimitAskOrder[T], residual: Quantity): (LimitAskOrder[T], LimitAskOrder[T]) = { - val matched = order.quantity - residual // todo consider checking that order.quantity is greater than residual! - (order.withQuantity(matched), order.withQuantity(residual)) - } - - private[this] def split(order: LimitBidOrder[T], residual: Quantity): (LimitBidOrder[T], LimitBidOrder[T]) = { - val matched = order.quantity - residual // todo consider checking that order.quantity is greater than residual! - (order.withQuantity(matched), order.withQuantity(residual)) - } - } diff --git a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/MatchedOrders.scala b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/MatchedOrders.scala index 0ea05f4..957f3f1 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/MatchedOrders.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/MatchedOrders.scala @@ -24,18 +24,45 @@ import org.economicsl.auctions.multiunit.{LimitAskOrder, LimitBidOrder} private[orderbooks] class MatchedOrders[T <: Tradable] private(val askOrders: SortedAskOrders[T], val bidOrders: SortedBidOrders[T]) { - require(askOrders.quantity == bidOrders.quantity) - require(bidOrders.headOption.forall{ case (_, bidOrder) => askOrders.headOption.forall{ case (_, askOrder) => bidOrder.value >= askOrder.value }}) // value of lowest bid must exceed value of highest ask! + assert(askOrders.quantity == bidOrders.quantity) + assert(bidOrders.headOption.forall{ case (_, bidOrder) => askOrders.headOption.forall{ case (_, askOrder) => bidOrder.value >= askOrder.value }}) // value of lowest bid must exceed value of highest ask! - def - (uuid: UUID): (MatchedOrders[T], Option[SortedAskOrders[T]], Option[SortedBidOrders[T]]) = { + val isEmpty: Boolean = askOrders.isEmpty && bidOrders.isEmpty + + val nonEmpty: Boolean = askOrders.nonEmpty && bidOrders.nonEmpty + + /** Add a new pair of ask and bid orders into the MatchedOrders. + * @note Unless the quantities of the `LimitAskOrder` and `LimitBidOrder` 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: (UUID, LimitAskOrder[T]), kv2: (UUID, LimitBidOrder[T])): (MatchedOrders[T], Option[UnMatchedOrders[T]]) = { + val ((uuid1, askOrder), (uuid2, bidOrder)) = (kv1, kv2) + val excessDemand = bidOrder.quantity - askOrder.quantity + if (excessDemand < Quantity(0)) { + val (matched, rationed) = (askOrder.withQuantity(bidOrder.quantity), askOrder.withQuantity(-excessDemand)) // split the askOrder into a matched and rationed component + val rationedOrders = UnMatchedOrders.empty[T](askOrders.ordering, bidOrders.ordering) + (new MatchedOrders(askOrders + (uuid1 -> matched), bidOrders + kv2), Some(rationedOrders + (uuid1, rationed))) + } else if (excessDemand > Quantity(0)) { + val (matched, rationed) = (bidOrder.withQuantity(askOrder.quantity), bidOrder.withQuantity(excessDemand)) // split the bidOrder into a matched and residual component + val rationedOrders = UnMatchedOrders.empty[T](askOrders.ordering, bidOrders.ordering) + (new MatchedOrders(askOrders + kv1, bidOrders + (uuid2 -> matched)), Some(rationedOrders + (uuid2, rationed))) + } else { + (new MatchedOrders(askOrders + kv1, bidOrders + kv2), None) + } + } + + def - (uuid: UUID): (MatchedOrders[T], UnMatchedOrders[T]) = { if (askOrders.contains(uuid)) { val removedOrder = askOrders(uuid) - val (matched, residual) = bidOrders.splitAt(removedOrder.quantity) - (new MatchedOrders(askOrders - uuid, residual), None, Some(matched)) + val (unMatched, residual) = bidOrders.splitAt(removedOrder.quantity) + // (new MatchedOrders(askOrders - uuid, residual), UnMatchedOrders.withEmptyAskOrders(unMatched)) + ??? } else { val removedOrder = bidOrders(uuid) - val (matched, residual) = askOrders.splitAt(removedOrder.quantity) - (new MatchedOrders(residual, bidOrders - uuid), Some(matched), None) + val (unMatched, residual) = askOrders.splitAt(removedOrder.quantity) + // (new MatchedOrders(residual, bidOrders - uuid), UnMatchedOrders.withEmptyBidOrders(unMatched)) + ??? } } @@ -45,13 +72,37 @@ private[orderbooks] class MatchedOrders[T <: Tradable] private(val askOrders: So def contains(uuid: UUID): Boolean = askOrders.contains(uuid) || bidOrders.contains(uuid) + def swap(uuid: UUID, order: LimitAskOrder[T], existing: UUID): (MatchedOrders[T], UnMatchedOrders[T]) = { + /*val askOrder = askOrders(existing) + if (order.quantity > askOrder.quantity) { + // if order.quantity > askOrder.quantity, then we need to split the order into matched and rationed components; remove the askOrder and add it to the unmatched orders along with the rationed component or order; finally add the matched component of order to the matched orders. + val (matched, rationed) = (order.withQuantity(askOrder.quantity), order.withQuantity(residual)) + val residualAskOrders = askOrders - existing + val empty = UnMatchedOrders.empty[T](???, ???) + val unMatchedOrders = empty ++ ((existing, askOrder), (uuid, rationed)) + (new MatchedOrders(residualAskOrders + (uuid, matched), bidOrders), unMatchedOrders) + ??? + } else if (order.quantity < askOrder.quantity) { + // if order.quantity < askOrder.quantity, then we need to remove the askOrder and split it into matched and rationed components; add the order and the match component into the matched set and then add the rationed component of askOrder into the unMatched set. + ??? + } else { + // if quantities match then we just need to remove askOrder and add it to unmatched orders; then add order to matched orders + val residualAskOrders = askOrders - existing + (new MatchedOrders(residualAskOrders + (uuid -> order), bidOrders), UnMatchedOrders.withEmptyBidOrders(askOrder)) + }*/ + ??? + } + + def swap(uuid: UUID, order: LimitBidOrder[T], existing: UUID): (MatchedOrders[T], UnMatchedOrders[T]) = { + ??? + } + def removeAndReplace(askOrder: (UUID, LimitAskOrder[T]), bidOrder: (UUID, LimitBidOrder[T])): MatchedOrders[T] = { new MatchedOrders(askOrders - askOrder._1, bidOrders.updated(bidOrder._1, bidOrder._2)) } def updated(askOrder: (UUID, LimitAskOrder[T]), bidOrder: (UUID, LimitBidOrder[T])): MatchedOrders[T] = { - val (uuid1, order1) = askOrder; val (uuid2, order2) = bidOrder - new MatchedOrders(askOrders.updated(uuid1, order1), bidOrders.updated(uuid2, order2)) + new MatchedOrders(askOrders + askOrder, bidOrders + bidOrder) } def zipped: Stream[(LimitAskOrder[T], LimitBidOrder[T])] = { diff --git a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedAskOrders.scala b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedAskOrders.scala index 6678381..71c1112 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedAskOrders.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedAskOrders.scala @@ -49,10 +49,16 @@ private[orderbooks] class SortedAskOrders[T <: Tradable] private(existing: Map[U val isEmpty: Boolean = existing.isEmpty && sorted.isEmpty + val nonEmpty: Boolean = existing.nonEmpty && sorted.nonEmpty + val ordering: Ordering[(UUID, LimitAskOrder[T])] = sorted.ordering val size: Int = existing.size + def mergeWith(other: SortedAskOrders[T]): SortedAskOrders[T] = { + ??? + } + def splitAt(quantity: Quantity): (SortedAskOrders[T], SortedAskOrders[T]) = { def split(order: LimitAskOrder[T], quantity: Quantity): (LimitAskOrder[T], LimitAskOrder[T]) = { @@ -68,7 +74,7 @@ private[orderbooks] class SortedAskOrders[T <: Tradable] private(existing: Map[U loop(in + (uuid -> askOrder), out - uuid) } else if (unMatched < askOrder.quantity) { val (matched, residual) = split(askOrder, unMatched) - (in + (uuid -> matched), out.updated(uuid, residual)) + (in + (uuid -> matched), out.update(uuid, residual)) } else { (in + (uuid -> askOrder), out - uuid) } @@ -77,7 +83,7 @@ private[orderbooks] class SortedAskOrders[T <: Tradable] private(existing: Map[U } - def updated(uuid: UUID, order: LimitAskOrder[T]): SortedAskOrders[T] = { + def update(uuid: UUID, order: LimitAskOrder[T]): SortedAskOrders[T] = { val askOrder = this(uuid) val change = order.quantity - askOrder.quantity new SortedAskOrders(existing.updated(uuid, order), sorted - ((uuid, askOrder)) + ((uuid, order)), quantity + change) diff --git a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrders.scala b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrders.scala index 51301b9..d37e15d 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrders.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrders.scala @@ -50,10 +50,16 @@ private[orderbooks] class SortedBidOrders[T <: Tradable] private(existing: Map[U val isEmpty: Boolean = existing.isEmpty && sorted.isEmpty + val nonEmpty: Boolean = existing.nonEmpty && sorted.nonEmpty + val ordering: Ordering[(UUID, LimitBidOrder[T])] = sorted.ordering val size: Int = existing.size + def mergeWith(other: SortedBidOrders[T]): SortedBidOrders[T] = { + ??? + } + def splitAt(quantity: Quantity): (SortedBidOrders[T], SortedBidOrders[T]) = { def split(order: LimitBidOrder[T], quantity: Quantity): (LimitBidOrder[T], LimitBidOrder[T]) = { diff --git a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/UnMatchedOrders.scala b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/UnMatchedOrders.scala index 656e34b..8f0a723 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/UnMatchedOrders.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/UnMatchedOrders.scala @@ -21,17 +21,31 @@ import org.economicsl.auctions.Tradable import org.economicsl.auctions.multiunit.{LimitAskOrder, LimitBidOrder} -private[orderbooks] class UnMatchedOrders[T <: Tradable] private(val askOrders: SortedAskOrders[T], - val bidOrders: SortedBidOrders[T]) { +private[orderbooks] case class UnMatchedOrders[T <: Tradable](askOrders: SortedAskOrders[T], + bidOrders: SortedBidOrders[T]) { - require(bidOrders.headOption.forall{ case (_, bidOrder) => bidOrder.limit <= askOrders.head._2.limit }) + assert(bidOrders.headOption.forall{ case (_, bidOrder) => askOrders.headOption.forall{ case (_, askOrder) => bidOrder.limit <= askOrder.limit } }) + + val isEmpty: Boolean = askOrders.isEmpty && bidOrders.isEmpty + + val nonEmpty: Boolean = askOrders.nonEmpty || bidOrders.nonEmpty + + /** Add a new `LimitAskOrder` to the collection of unmatched orders .*/ + def + (uuid: UUID, order: LimitAskOrder[T]): UnMatchedOrders[T] = { + UnMatchedOrders(askOrders + (uuid -> order), bidOrders) + } + + /** Add a new `LimitBidOrder` to the collection of unmatched orders .*/ + def + (uuid: UUID, order: LimitBidOrder[T]): UnMatchedOrders[T] = { + UnMatchedOrders(askOrders, bidOrders + (uuid -> order)) + } /** Remove an order from the collection of unmatched orders. */ def - (uuid: UUID): UnMatchedOrders[T] = { if (askOrders.contains(uuid)) { - new UnMatchedOrders(askOrders - uuid, bidOrders) + UnMatchedOrders(askOrders - uuid, bidOrders) } else { - new UnMatchedOrders(askOrders, bidOrders - uuid) + UnMatchedOrders(askOrders, bidOrders - uuid) } } @@ -42,14 +56,18 @@ private[orderbooks] class UnMatchedOrders[T <: Tradable] private(val askOrders: /** Check whether an order is contained in the collection of unmatched orders using. */ def contains(uuid: UUID): Boolean = askOrders.contains(uuid) || bidOrders.contains(uuid) + def mergeWith(other: UnMatchedOrders[T]): UnMatchedOrders[T] = { + UnMatchedOrders(askOrders.mergeWith(other.askOrders), bidOrders.mergeWith(bidOrders)) + } + /** Add a `LimitAskOrder` to the collection of unmatched orders. */ def updated(uuid: UUID, order: LimitAskOrder[T]): UnMatchedOrders[T] = { - new UnMatchedOrders(askOrders.updated(uuid, order), bidOrders) + UnMatchedOrders(askOrders.update(uuid, order), bidOrders) } /** Add a `LimitBidOrder` to the collection of unmatched orders. */ def updated(uuid: UUID, order: LimitBidOrder[T]): UnMatchedOrders[T] = { - new UnMatchedOrders(askOrders, bidOrders.updated(uuid, order)) + UnMatchedOrders(askOrders, bidOrders.updated(uuid, order)) } } diff --git a/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/FourHeapOrderBookSpec.scala b/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/FourHeapOrderBookSpec.scala new file mode 100644 index 0000000..55fcf14 --- /dev/null +++ b/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/FourHeapOrderBookSpec.scala @@ -0,0 +1,40 @@ +package org.economicsl.auctions.multiunit.orderbooks + +import java.util.UUID + +import org.economicsl.auctions.{GoogleStock, Price, Quantity, multiunit} +import org.scalatest.{FlatSpec, Matchers} + + +class FourHeapOrderBookSpec extends FlatSpec with Matchers { + + "An empty FourHeapOrderBook" should "accept a new order" in { + + val google = new GoogleStock + + // Create some multi-unit limit ask orders + val issuer1 = UUID.randomUUID() + val order1 = multiunit.LimitAskOrder(issuer1, Price(10), Quantity(10), google) + + val issuer2 = UUID.randomUUID() + val order2 = multiunit.LimitBidOrder(issuer2, Price(15), Quantity(10), google) + + // Create an empty orderBook + val empty = FourHeapOrderBook.empty[GoogleStock] + + // Add a limit ask order + val uuid1 = UUID.randomUUID() + val orderBook = empty + (uuid1, order1) + assert(orderBook.nonEmpty) + + // add a limit bid Order + val uuid2 = UUID.randomUUID() + val orderBook2 = empty + (uuid2, order2) + assert(orderBook2.nonEmpty) + + val orderBook3 = empty + (uuid1, order1) + (uuid2, order2) + assert(orderBook.nonEmpty) + + } + +} diff --git a/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/MatchedOrdersSpec.scala b/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/MatchedOrdersSpec.scala new file mode 100644 index 0000000..ccc89e5 --- /dev/null +++ b/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/MatchedOrdersSpec.scala @@ -0,0 +1,72 @@ +package org.economicsl.auctions.multiunit.orderbooks + +import java.util.UUID + +import org.economicsl.auctions.multiunit +import org.economicsl.auctions.{GoogleStock, Price, Quantity} +import org.scalatest.{FlatSpec, Matchers} + + +class MatchedOrdersSpec extends FlatSpec with Matchers { + + "A set of MatchedOrders" should " accept a " in { + val google = new GoogleStock + + // Create some multi-unit limit ask orders + val issuer1 = UUID.randomUUID() + val order1 = multiunit.LimitAskOrder(issuer1, Price(10), Quantity(10), google) + + val issuer2 = UUID.randomUUID() + val order2 = multiunit.LimitBidOrder(issuer2, Price(15), Quantity(10), google) + + val empty = MatchedOrders.empty[GoogleStock](multiunit.LimitAskOrder.ordering.reverse, multiunit.LimitBidOrder.ordering.reverse) + val (matched, optionalUnMatched) = empty + (issuer1 -> order1, issuer2 -> order2) + + assert(matched.nonEmpty) + optionalUnMatched should be (None) + } + + "A set of MatchedOrders" should " accept a sdasdf " in { + val google = new GoogleStock + + // Create some multi-unit limit ask orders + val issuer1 = UUID.randomUUID() + val order1 = multiunit.LimitAskOrder(issuer1, Price(10), Quantity(100), google) + + val issuer2 = UUID.randomUUID() + val order2 = multiunit.LimitBidOrder(issuer2, Price(15), Quantity(10), google) + + // Create an empty set of matched orders and add the newly created orders + val empty = MatchedOrders.empty[GoogleStock](multiunit.LimitAskOrder.ordering.reverse, multiunit.LimitBidOrder.ordering.reverse) + val (matched, optionalUnMatched) = empty + (issuer1 -> order1, issuer2 -> order2) + + assert(matched.nonEmpty) + val rationed = order1.quantity - order2.quantity + val rationedOrder = order1.withQuantity(rationed) + optionalUnMatched.map(rationedOrders => rationedOrders.askOrders.head) should be (Some((issuer1, rationedOrder))) + + } + + + "A set of MatchedOrders" should " accept a fdgdfg " in { + val google = new GoogleStock + + // Create some multi-unit limit ask orders + val issuer1 = UUID.randomUUID() + val order1 = multiunit.LimitAskOrder(issuer1, Price(10), Quantity(10), google) + + val issuer2 = UUID.randomUUID() + val order2 = multiunit.LimitBidOrder(issuer2, Price(15), Quantity(100), google) + + // Create an empty set of matched orders and add the newly created orders + val empty = MatchedOrders.empty[GoogleStock](multiunit.LimitAskOrder.ordering.reverse, multiunit.LimitBidOrder.ordering.reverse) + val (matched, optionalUnMatched) = empty + (issuer1 -> order1, issuer2 -> order2) + + assert(matched.nonEmpty) + val rationed = order2.quantity - order1.quantity + val rationedOrder = order2.withQuantity(rationed) + optionalUnMatched.map(rationedOrders => rationedOrders.bidOrders.head) should be (Some((issuer2, rationedOrder))) + + } + +} diff --git a/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/SortedAskOrdersSpec.scala b/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/SortedAskOrdersSpec.scala index 3c7c08b..103d947 100644 --- a/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/SortedAskOrdersSpec.scala +++ b/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/SortedAskOrdersSpec.scala @@ -22,7 +22,7 @@ class SortedAskOrdersSpec extends FlatSpec with Matchers { // Create a revised order and update the order book val revised = order.withQuantity(Quantity(1)) - val updated = nonEmpty.updated(issuer, revised) + val updated = nonEmpty.update(issuer, revised) // Check that update is successful! updated.head should be ((issuer, revised)) From 035235dda49978c0da5da1874e87caab01bd74f6 Mon Sep 17 00:00:00 2001 From: davidrpugh Date: Mon, 5 Jun 2017 09:05:52 +0300 Subject: [PATCH 21/25] Fixed compiler errors and warnings. --- .../auctions/multiunit/Divisible.scala | 27 --------------- .../auctions/multiunit/LimitAskOrder.scala | 16 +++++---- .../auctions/multiunit/LimitBidOrder.scala | 14 ++++---- .../auctions/multiunit/MarketAskOrder.scala | 19 +++-------- .../auctions/multiunit/MarketBidOrder.scala | 19 +++-------- .../economicsl/auctions/multiunit/Order.scala | 20 ++++++++--- .../orderbooks/FourHeapOrderBook.scala | 26 +++++++------- .../multiunit/orderbooks/MatchedOrders.scala | 29 +++++++++------- .../orderbooks/SortedAskOrders.scala | 34 +++++++++---------- .../orderbooks/SortedBidOrders.scala | 34 +++++++++---------- .../orderbooks/UnMatchedOrders.scala | 27 ++++++++------- .../multiunit/orderbooks/package.scala | 6 ++-- .../orderbooks/SortedAskOrdersSpec.scala | 6 ++-- .../orderbooks/SortedBidOrdersSpec.scala | 6 ++-- 14 files changed, 127 insertions(+), 156 deletions(-) delete mode 100644 src/main/scala/org/economicsl/auctions/multiunit/Divisible.scala diff --git a/src/main/scala/org/economicsl/auctions/multiunit/Divisible.scala b/src/main/scala/org/economicsl/auctions/multiunit/Divisible.scala deleted file mode 100644 index ecc43be..0000000 --- a/src/main/scala/org/economicsl/auctions/multiunit/Divisible.scala +++ /dev/null @@ -1,27 +0,0 @@ -/* -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 - -import org.economicsl.auctions.{Order, Quantity, Tradable} - - -/** Mixin trait providing behavior necessary to split an order into two orders. */ -trait Divisible[+T <: Tradable, +O <: Order[T] with SinglePricePoint[T] with Divisible[T, O]] { - this: Order[T] with SinglePricePoint[T] => - - def withQuantity(residual: Quantity): O - -} \ No newline at end of file diff --git a/src/main/scala/org/economicsl/auctions/multiunit/LimitAskOrder.scala b/src/main/scala/org/economicsl/auctions/multiunit/LimitAskOrder.scala index 4b9adac..2078498 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/LimitAskOrder.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/LimitAskOrder.scala @@ -17,7 +17,7 @@ package org.economicsl.auctions.multiunit import java.util.UUID -import org.economicsl.auctions.{Price, Quantity, SinglePricePoint, Tradable} +import org.economicsl.auctions.{Price, Quantity, Tradable} /** An order to sell multiple units of a tradable at a per-unit price greater than or equal to the limit price. @@ -30,8 +30,14 @@ import org.economicsl.auctions.{Price, Quantity, SinglePricePoint, Tradable} * @author davidrpugh * @since 0.1.0 */ -class LimitAskOrder[+T <: Tradable](val issuer: UUID, val limit: Price, val quantity: Quantity, val tradable: T) - extends AskOrder[T] with SinglePricePoint[T] +case class LimitAskOrder[+T <: Tradable](issuer: UUID, limit: Price, quantity: Quantity, tradable: T) + extends AskOrder[T] { + + def withQuantity(quantity: Quantity): LimitAskOrder[T] = { + copy(quantity = quantity) + } + +} /** Companion object for `LimitAskOrder`. @@ -47,9 +53,5 @@ object LimitAskOrder { Ordering.by{case (uuid, order) => (order.limit, uuid) } } - def apply[T <: Tradable](issuer: UUID, limit: Price, quantity: Quantity, tradable: T): LimitAskOrder[T] = { - new LimitAskOrder[T](issuer, limit, quantity, tradable) - } - } diff --git a/src/main/scala/org/economicsl/auctions/multiunit/LimitBidOrder.scala b/src/main/scala/org/economicsl/auctions/multiunit/LimitBidOrder.scala index 7cfca3a..86e39d4 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/LimitBidOrder.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/LimitBidOrder.scala @@ -30,8 +30,14 @@ import org.economicsl.auctions.{Price, Quantity, SinglePricePoint, Tradable} * @author davidrpugh * @since 0.1.0 */ -class LimitBidOrder[+T <: Tradable](val issuer: UUID, val limit: Price, val quantity: Quantity, val tradable: T) - extends BidOrder[T] with SinglePricePoint[T] +case class LimitBidOrder[+T <: Tradable](issuer: UUID, limit: Price, quantity: Quantity, tradable: T) + extends BidOrder[T] { + + def withQuantity(quantity: Quantity): LimitBidOrder[T] = { + copy(quantity = quantity) + } + +} /** Companion object for `LimitBidOrder`. @@ -45,9 +51,5 @@ object LimitBidOrder { implicit def ordering[O <: LimitBidOrder[_ <: Tradable]]: Ordering[O] = SinglePricePoint.ordering[O] - def apply[T <: Tradable](issuer: UUID, limit: Price, quantity: Quantity, tradable: T): LimitBidOrder[T] = { - new LimitBidOrder[T](issuer, limit, quantity, tradable) - } - } diff --git a/src/main/scala/org/economicsl/auctions/multiunit/MarketAskOrder.scala b/src/main/scala/org/economicsl/auctions/multiunit/MarketAskOrder.scala index c699cbf..ea2048a 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/MarketAskOrder.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/MarketAskOrder.scala @@ -17,7 +17,7 @@ package org.economicsl.auctions.multiunit import java.util.UUID -import org.economicsl.auctions.{Price, Quantity, SinglePricePoint, Tradable} +import org.economicsl.auctions.{Price, Quantity, Tradable} /** An order to sell a multiple units of a tradable at any positive price. @@ -29,23 +29,12 @@ import org.economicsl.auctions.{Price, Quantity, SinglePricePoint, Tradable} * @author davidrpugh * @since 0.1.0 */ -class MarketAskOrder[+T <: Tradable](val issuer: UUID, val quantity: Quantity, val tradable: T) - extends AskOrder[T] with SinglePricePoint[T] { +case class MarketAskOrder[+T <: Tradable](issuer: UUID, quantity: Quantity, tradable: T) extends AskOrder[T] { val limit: Price = Price.MinValue -} - - -/** Companion object for `MarketAskOrder`. - * - * @author davidrpugh - * @since 0.1.0 - */ -object MarketAskOrder { - - def apply[T <: Tradable](issuer: UUID, quantity: Quantity, tradable: T): MarketAskOrder[T] = { - new MarketAskOrder[T](issuer, quantity, tradable) + def withQuantity(quantity: Quantity): MarketAskOrder[T] = { + copy(quantity = quantity) } } diff --git a/src/main/scala/org/economicsl/auctions/multiunit/MarketBidOrder.scala b/src/main/scala/org/economicsl/auctions/multiunit/MarketBidOrder.scala index d87dc6f..375a3a9 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/MarketBidOrder.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/MarketBidOrder.scala @@ -17,7 +17,7 @@ package org.economicsl.auctions.multiunit import java.util.UUID -import org.economicsl.auctions.{Price, Quantity, SinglePricePoint, Tradable} +import org.economicsl.auctions.{Price, Quantity, Tradable} /** An order to buy multiple units of a tradable at any positive price. @@ -29,23 +29,12 @@ import org.economicsl.auctions.{Price, Quantity, SinglePricePoint, Tradable} * @author davidrpugh * @since 0.1.0 */ -class MarketBidOrder[+T <: Tradable](val issuer: UUID, val quantity: Quantity, val tradable: T) - extends BidOrder[T] with SinglePricePoint[T] { +case class MarketBidOrder[+T <: Tradable](issuer: UUID, quantity: Quantity, tradable: T) extends BidOrder[T] { val limit: Price = Price.MaxValue -} - - -/** Companion object for `MarketBidOrder`. - * - * @author davidrpugh - * @since 0.1.0 - */ -object MarketBidOrder { - - def apply[T <: Tradable](issuer: UUID, quantity: Quantity, tradable: T): MarketBidOrder[T] = { - new MarketBidOrder[T](issuer, quantity, tradable) + def withQuantity(quantity: Quantity): MarketBidOrder[T] = { + copy(quantity = quantity) } } diff --git a/src/main/scala/org/economicsl/auctions/multiunit/Order.scala b/src/main/scala/org/economicsl/auctions/multiunit/Order.scala index 0308970..9ce44be 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/Order.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/Order.scala @@ -15,7 +15,7 @@ limitations under the License. */ package org.economicsl.auctions.multiunit -import org.economicsl.auctions.{Contract, OrderLike, Tradable} +import org.economicsl.auctions._ /** Base trait for all multi-unit orders. @@ -24,7 +24,11 @@ import org.economicsl.auctions.{Contract, OrderLike, Tradable} * @author davidrpugh * @since 0.1.0 */ -sealed trait Order[+T <: Tradable] extends Contract with OrderLike[T] +sealed trait Order[+T <: Tradable] extends Contract with OrderLike[T] with SinglePricePoint[T] { + + def withQuantity(quantity: Quantity): Order[T] + +} /** Base trait for all multi-unit orders to sell a particular `Tradable`. @@ -33,7 +37,11 @@ sealed trait Order[+T <: Tradable] extends Contract with OrderLike[T] * @author davidrpugh * @since 0.1.0 */ -trait AskOrder[+T <: Tradable] extends Order[T] +trait AskOrder[+T <: Tradable] extends Order[T] { + + def withQuantity(quantity: Quantity): AskOrder[T] + +} /** Base trait for all multi-unit orders to buy a particular `Tradable` @@ -42,4 +50,8 @@ trait AskOrder[+T <: Tradable] extends Order[T] * @author davidrpugh * @since 0.1.0 */ -trait BidOrder[+T <: Tradable] extends Order[T] \ No newline at end of file +trait BidOrder[+T <: Tradable] extends Order[T] { + + def withQuantity(quantity: Quantity): BidOrder[T] + +} \ No newline at end of file diff --git a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/FourHeapOrderBook.scala b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/FourHeapOrderBook.scala index a84b9a7..85a6c47 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/FourHeapOrderBook.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/FourHeapOrderBook.scala @@ -17,8 +17,8 @@ package org.economicsl.auctions.multiunit.orderbooks import java.util.UUID -import org.economicsl.auctions.{Quantity, Tradable} -import org.economicsl.auctions.multiunit.{LimitAskOrder, LimitBidOrder} +import org.economicsl.auctions.Tradable +import org.economicsl.auctions.multiunit.{AskOrder, BidOrder} class FourHeapOrderBook[T <: Tradable] private(matchedOrders: MatchedOrders[T], unMatchedOrders: UnMatchedOrders[T]) { @@ -48,14 +48,14 @@ class FourHeapOrderBook[T <: Tradable] private(matchedOrders: MatchedOrders[T], } } - /** Add a new `LimitAskOrder` to the `OrderBook`. + /** Add a new `AskOrder` to the `OrderBook`. * * @param uuid * @param order * @return - * @note adding a new `LimitAskOrder` is non-trivial and there are several cases to consider. + * @note adding a new `AskOrder` is non-trivial and there are several cases to consider. */ - def + (uuid: UUID, order: LimitAskOrder[T]): FourHeapOrderBook[T] = { + def + (uuid: UUID, order: AskOrder[T]): FourHeapOrderBook[T] = { (unMatchedOrders.bidOrders.headOption, matchedOrders.askOrders.headOption) match { case (Some((out, bidOrder)), Some((in, askOrder))) => if (order.limit <= bidOrder.limit && askOrder.limit <= bidOrder.limit) { @@ -96,13 +96,13 @@ class FourHeapOrderBook[T <: Tradable] private(matchedOrders: MatchedOrders[T], } } - /** Add a new `LimitBidOrder` to the `OrderBook`. + /** Add a new `BidOrder` to the `OrderBook`. * * @param uuid * @param order * @return */ - def + (uuid: UUID, order: LimitBidOrder[T]): FourHeapOrderBook[T] = { + def + (uuid: UUID, order: BidOrder[T]): FourHeapOrderBook[T] = { (matchedOrders.bidOrders.headOption, unMatchedOrders.askOrders.headOption) match { case (Some((in, bidOrder)), Some((out, askOrder))) => if (order.limit >= askOrder.limit && bidOrder.limit >= askOrder.limit) { @@ -145,16 +145,16 @@ class FourHeapOrderBook[T <: Tradable] private(matchedOrders: MatchedOrders[T], def contains(uuid: UUID): Boolean = matchedOrders.contains(uuid) || unMatchedOrders.contains(uuid) - def takeWhileMatched: (Stream[(LimitAskOrder[T], LimitBidOrder[T])], FourHeapOrderBook[T]) = { + def takeWhileMatched: (Stream[(AskOrder[T], BidOrder[T])], FourHeapOrderBook[T]) = { (matchedOrders.zipped, withEmptyMatchedOrders) } - /** Update an existing `LimitAskOrder`. + /** Update an existing `AskOrder`. * * @note Because multi-unit orders are divisible, an order with the `uuid` might have been split in which case it * will exist in both the matched and unmatched order sets. Thus if */ - def update(uuid: UUID, order: LimitAskOrder[T]): FourHeapOrderBook[T] = { + def update(uuid: UUID, order: AskOrder[T]): FourHeapOrderBook[T] = { if (contains(uuid)) { val residualOrderBook = this - uuid residualOrderBook + (uuid, order) @@ -163,12 +163,12 @@ class FourHeapOrderBook[T <: Tradable] private(matchedOrders: MatchedOrders[T], } } - /** Update an existing `LimitBidOrder`. + /** Update an existing `BidOrder`. * * @note Because multi-unit orders are divisible, an order with the `uuid` might have been split in which case it * will exist in both the matched and unmatched order sets. Thus if */ - def update(uuid: UUID, order: LimitBidOrder[T]): FourHeapOrderBook[T] = { + def update(uuid: UUID, order: BidOrder[T]): FourHeapOrderBook[T] = { if (contains(uuid)) { val residualOrderBook = this - uuid residualOrderBook + (uuid, order) @@ -187,7 +187,7 @@ class FourHeapOrderBook[T <: Tradable] private(matchedOrders: MatchedOrders[T], object FourHeapOrderBook { - def empty[T <: Tradable](implicit askOrdering: Ordering[(UUID, LimitAskOrder[T])], bidOrdering: Ordering[(UUID, LimitBidOrder[T])]): FourHeapOrderBook[T] = { + def empty[T <: Tradable](implicit askOrdering: Ordering[(UUID, AskOrder[T])], bidOrdering: Ordering[(UUID, BidOrder[T])]): FourHeapOrderBook[T] = { val matchedOrders = MatchedOrders.empty(askOrdering.reverse, bidOrdering.reverse) val unMatchedOrders = UnMatchedOrders.empty(askOrdering, bidOrdering) new FourHeapOrderBook(matchedOrders, unMatchedOrders) diff --git a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/MatchedOrders.scala b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/MatchedOrders.scala index 957f3f1..5f5800f 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/MatchedOrders.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/MatchedOrders.scala @@ -18,25 +18,28 @@ package org.economicsl.auctions.multiunit.orderbooks import java.util.UUID import org.economicsl.auctions.{Quantity, Tradable} -import org.economicsl.auctions.multiunit.{LimitAskOrder, LimitBidOrder} +import org.economicsl.auctions.multiunit.{AskOrder, BidOrder} private[orderbooks] class MatchedOrders[T <: Tradable] private(val askOrders: SortedAskOrders[T], val bidOrders: SortedBidOrders[T]) { - assert(askOrders.quantity == bidOrders.quantity) - assert(bidOrders.headOption.forall{ case (_, bidOrder) => askOrders.headOption.forall{ case (_, askOrder) => bidOrder.value >= askOrder.value }}) // value of lowest bid must exceed value of highest ask! + /* 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 } }) // value of lowest bid must exceed value of highest ask! val isEmpty: Boolean = askOrders.isEmpty && bidOrders.isEmpty val nonEmpty: Boolean = askOrders.nonEmpty && bidOrders.nonEmpty /** Add a new pair of ask and bid orders into the MatchedOrders. - * @note Unless the quantities of the `LimitAskOrder` and `LimitBidOrder` match exactly, then the larger order must + * @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: (UUID, LimitAskOrder[T]), kv2: (UUID, LimitBidOrder[T])): (MatchedOrders[T], Option[UnMatchedOrders[T]]) = { + def + (kv1: (UUID, AskOrder[T]), kv2: (UUID, BidOrder[T])): (MatchedOrders[T], Option[UnMatchedOrders[T]]) = { val ((uuid1, askOrder), (uuid2, bidOrder)) = (kv1, kv2) val excessDemand = bidOrder.quantity - askOrder.quantity if (excessDemand < Quantity(0)) { @@ -66,13 +69,13 @@ private[orderbooks] class MatchedOrders[T <: Tradable] private(val askOrders: So } } - val askOrdering: Ordering[(UUID, LimitAskOrder[T])] = askOrders.ordering + val askOrdering: Ordering[(UUID, AskOrder[T])] = askOrders.ordering - val bidOrdering: Ordering[(UUID, LimitBidOrder[T])] = bidOrders.ordering + val bidOrdering: Ordering[(UUID, BidOrder[T])] = bidOrders.ordering def contains(uuid: UUID): Boolean = askOrders.contains(uuid) || bidOrders.contains(uuid) - def swap(uuid: UUID, order: LimitAskOrder[T], existing: UUID): (MatchedOrders[T], UnMatchedOrders[T]) = { + def swap(uuid: UUID, order: AskOrder[T], existing: UUID): (MatchedOrders[T], UnMatchedOrders[T]) = { /*val askOrder = askOrders(existing) if (order.quantity > askOrder.quantity) { // if order.quantity > askOrder.quantity, then we need to split the order into matched and rationed components; remove the askOrder and add it to the unmatched orders along with the rationed component or order; finally add the matched component of order to the matched orders. @@ -93,19 +96,19 @@ private[orderbooks] class MatchedOrders[T <: Tradable] private(val askOrders: So ??? } - def swap(uuid: UUID, order: LimitBidOrder[T], existing: UUID): (MatchedOrders[T], UnMatchedOrders[T]) = { + def swap(uuid: UUID, order: BidOrder[T], existing: UUID): (MatchedOrders[T], UnMatchedOrders[T]) = { ??? } - def removeAndReplace(askOrder: (UUID, LimitAskOrder[T]), bidOrder: (UUID, LimitBidOrder[T])): MatchedOrders[T] = { + def removeAndReplace(askOrder: (UUID, AskOrder[T]), bidOrder: (UUID, BidOrder[T])): MatchedOrders[T] = { new MatchedOrders(askOrders - askOrder._1, bidOrders.updated(bidOrder._1, bidOrder._2)) } - def updated(askOrder: (UUID, LimitAskOrder[T]), bidOrder: (UUID, LimitBidOrder[T])): MatchedOrders[T] = { + def updated(askOrder: (UUID, AskOrder[T]), bidOrder: (UUID, BidOrder[T])): MatchedOrders[T] = { new MatchedOrders(askOrders + askOrder, bidOrders + bidOrder) } - def zipped: Stream[(LimitAskOrder[T], LimitBidOrder[T])] = { + def zipped: Stream[(AskOrder[T], BidOrder[T])] = { ??? } @@ -121,7 +124,7 @@ private[orderbooks] object MatchedOrders { * 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[T <: Tradable](askOrdering: Ordering[(UUID, LimitAskOrder[T])], bidOrdering: Ordering[(UUID, LimitBidOrder[T])]): MatchedOrders[T] = { + def empty[T <: Tradable](askOrdering: Ordering[(UUID, AskOrder[T])], bidOrdering: Ordering[(UUID, BidOrder[T])]): MatchedOrders[T] = { new MatchedOrders(SortedAskOrders.empty(askOrdering), SortedBidOrders.empty(bidOrdering)) } diff --git a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedAskOrders.scala b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedAskOrders.scala index 71c1112..7ce2418 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedAskOrders.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedAskOrders.scala @@ -18,40 +18,40 @@ package org.economicsl.auctions.multiunit.orderbooks import java.util.UUID import org.economicsl.auctions.{Quantity, Tradable} -import org.economicsl.auctions.multiunit.LimitAskOrder +import org.economicsl.auctions.multiunit.AskOrder import scala.collection.immutable.TreeSet -private[orderbooks] class SortedAskOrders[T <: Tradable] private(existing: Map[UUID, LimitAskOrder[T]], - sorted: TreeSet[(UUID, LimitAskOrder[T])], - val quantity: Quantity) { +private[orderbooks] class SortedAskOrders[T <: Tradable] private(existing: Map[UUID, AskOrder[T]], + sorted: TreeSet[(UUID, AskOrder[T])], + val numberUnits: Quantity) { assert(existing.size == sorted.size) - def apply(uuid: UUID): LimitAskOrder[T] = existing(uuid) + def apply(uuid: UUID): AskOrder[T] = existing(uuid) - def + (kv: (UUID, LimitAskOrder[T])): SortedAskOrders[T] = { - new SortedAskOrders(existing + kv, sorted + kv, quantity + kv._2.quantity) + def + (kv: (UUID, AskOrder[T])): SortedAskOrders[T] = { + new SortedAskOrders(existing + kv, sorted + kv, numberUnits + kv._2.quantity) } def - (uuid: UUID): SortedAskOrders[T] = existing.get(uuid) match { case Some(order) => - val remaining = Quantity(quantity.value - order.quantity.value) + val remaining = Quantity(numberUnits.value - order.quantity.value) new SortedAskOrders(existing - uuid, sorted - ((uuid, order)), remaining) case None => this } def contains(uuid: UUID): Boolean = existing.contains(uuid) - def head: (UUID, LimitAskOrder[T]) = sorted.head + def head: (UUID, AskOrder[T]) = sorted.head - val headOption: Option[(UUID, LimitAskOrder[T])] = sorted.headOption + val headOption: Option[(UUID, AskOrder[T])] = sorted.headOption val isEmpty: Boolean = existing.isEmpty && sorted.isEmpty val nonEmpty: Boolean = existing.nonEmpty && sorted.nonEmpty - val ordering: Ordering[(UUID, LimitAskOrder[T])] = sorted.ordering + val ordering: Ordering[(UUID, AskOrder[T])] = sorted.ordering val size: Int = existing.size @@ -61,14 +61,14 @@ private[orderbooks] class SortedAskOrders[T <: Tradable] private(existing: Map[U def splitAt(quantity: Quantity): (SortedAskOrders[T], SortedAskOrders[T]) = { - def split(order: LimitAskOrder[T], quantity: Quantity): (LimitAskOrder[T], LimitAskOrder[T]) = { + def split(order: AskOrder[T], quantity: Quantity): (AskOrder[T], AskOrder[T]) = { val residual = order.quantity - quantity (order.withQuantity(quantity), order.withQuantity(residual)) } @annotation.tailrec def loop(in: SortedAskOrders[T], out: SortedAskOrders[T]): (SortedAskOrders[T], SortedAskOrders[T]) = { - val unMatched = quantity - in.quantity + val unMatched = quantity - in.numberUnits val (uuid, askOrder) = out.head if (unMatched > askOrder.quantity) { loop(in + (uuid -> askOrder), out - uuid) @@ -83,10 +83,10 @@ private[orderbooks] class SortedAskOrders[T <: Tradable] private(existing: Map[U } - def update(uuid: UUID, order: LimitAskOrder[T]): SortedAskOrders[T] = { + def update(uuid: UUID, order: AskOrder[T]): SortedAskOrders[T] = { val askOrder = this(uuid) val change = order.quantity - askOrder.quantity - new SortedAskOrders(existing.updated(uuid, order), sorted - ((uuid, askOrder)) + ((uuid, order)), quantity + change) + new SortedAskOrders(existing.updated(uuid, order), sorted - ((uuid, askOrder)) + ((uuid, order)), numberUnits + change) } } @@ -94,8 +94,8 @@ private[orderbooks] class SortedAskOrders[T <: Tradable] private(existing: Map[U object SortedAskOrders { - def empty[T <: Tradable](implicit ordering: Ordering[(UUID, LimitAskOrder[T])]): SortedAskOrders[T] = { - new SortedAskOrders(Map.empty[UUID, LimitAskOrder[T]], TreeSet.empty[(UUID, LimitAskOrder[T])](ordering), Quantity(0)) + def empty[T <: Tradable](implicit ordering: Ordering[(UUID, AskOrder[T])]): SortedAskOrders[T] = { + new SortedAskOrders(Map.empty[UUID, AskOrder[T]], TreeSet.empty[(UUID, AskOrder[T])](ordering), Quantity(0)) } } diff --git a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrders.scala b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrders.scala index d37e15d..db3a253 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrders.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrders.scala @@ -18,41 +18,41 @@ package org.economicsl.auctions.multiunit.orderbooks import java.util.UUID import org.economicsl.auctions.{Quantity, Tradable} -import org.economicsl.auctions.multiunit.LimitBidOrder +import org.economicsl.auctions.multiunit.BidOrder import scala.collection.immutable.TreeSet -private[orderbooks] class SortedBidOrders[T <: Tradable] private(existing: Map[UUID, LimitBidOrder[T]], - val sorted: TreeSet[(UUID, LimitBidOrder[T])], - val quantity: Quantity) { +private[orderbooks] class SortedBidOrders[T <: Tradable] private(existing: Map[UUID, BidOrder[T]], + val sorted: TreeSet[(UUID, BidOrder[T])], + val numberUnits: Quantity) { assert(existing.size == sorted.size) - def apply(uuid: UUID): LimitBidOrder[T] = existing(uuid) + def apply(uuid: UUID): BidOrder[T] = existing(uuid) - def + (kv: (UUID, LimitBidOrder[T])): SortedBidOrders[T] = { - new SortedBidOrders(existing + kv, sorted + kv, quantity + kv._2.quantity) + def + (kv: (UUID, BidOrder[T])): SortedBidOrders[T] = { + new SortedBidOrders(existing + kv, sorted + kv, numberUnits + kv._2.quantity) } def - (uuid: UUID): SortedBidOrders[T] = existing.get(uuid) match { case Some(order) => - val remaining = Quantity(quantity.value - order.quantity.value) + val remaining = Quantity(numberUnits.value - order.quantity.value) new SortedBidOrders(existing - uuid, sorted - ((uuid, order)), remaining) case None => this } def contains(uuid: UUID): Boolean = existing.contains(uuid) - def head: (UUID, LimitBidOrder[T]) = sorted.head + def head: (UUID, BidOrder[T]) = sorted.head - val headOption: Option[(UUID, LimitBidOrder[T])] = sorted.headOption + val headOption: Option[(UUID, BidOrder[T])] = sorted.headOption val isEmpty: Boolean = existing.isEmpty && sorted.isEmpty val nonEmpty: Boolean = existing.nonEmpty && sorted.nonEmpty - val ordering: Ordering[(UUID, LimitBidOrder[T])] = sorted.ordering + val ordering: Ordering[(UUID, BidOrder[T])] = sorted.ordering val size: Int = existing.size @@ -62,14 +62,14 @@ private[orderbooks] class SortedBidOrders[T <: Tradable] private(existing: Map[U def splitAt(quantity: Quantity): (SortedBidOrders[T], SortedBidOrders[T]) = { - def split(order: LimitBidOrder[T], quantity: Quantity): (LimitBidOrder[T], LimitBidOrder[T]) = { + def split(order: BidOrder[T], quantity: Quantity): (BidOrder[T], BidOrder[T]) = { val residual = order.quantity - quantity (order.withQuantity(quantity), order.withQuantity(residual)) } @annotation.tailrec def loop(in: SortedBidOrders[T], out: SortedBidOrders[T]): (SortedBidOrders[T], SortedBidOrders[T]) = { - val unMatched = quantity - in.quantity + val unMatched = quantity - in.numberUnits val (uuid, bidOrder) = out.head if (unMatched > bidOrder.quantity) { loop(in + (uuid -> bidOrder), out - uuid) @@ -84,10 +84,10 @@ private[orderbooks] class SortedBidOrders[T <: Tradable] private(existing: Map[U } - def updated(uuid: UUID, order: LimitBidOrder[T]): SortedBidOrders[T] = { + def updated(uuid: UUID, order: BidOrder[T]): SortedBidOrders[T] = { val bidOrder = this(uuid) val change = order.quantity - bidOrder.quantity - new SortedBidOrders(existing.updated(uuid, order), sorted - ((uuid, bidOrder)) + ((uuid, order)), quantity + change) + new SortedBidOrders(existing.updated(uuid, order), sorted - ((uuid, bidOrder)) + ((uuid, order)), numberUnits + change) } } @@ -95,8 +95,8 @@ private[orderbooks] class SortedBidOrders[T <: Tradable] private(existing: Map[U object SortedBidOrders { - def empty[T <: Tradable](implicit ordering: Ordering[(UUID, LimitBidOrder[T])]): SortedBidOrders[T] = { - new SortedBidOrders(Map.empty[UUID, LimitBidOrder[T]], TreeSet.empty[(UUID, LimitBidOrder[T])](ordering), Quantity(0)) + def empty[T <: Tradable](implicit ordering: Ordering[(UUID, BidOrder[T])]): SortedBidOrders[T] = { + new SortedBidOrders(Map.empty[UUID, BidOrder[T]], TreeSet.empty[(UUID, BidOrder[T])](ordering), Quantity(0)) } } diff --git a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/UnMatchedOrders.scala b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/UnMatchedOrders.scala index 8f0a723..c8b089b 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/UnMatchedOrders.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/UnMatchedOrders.scala @@ -18,25 +18,26 @@ package org.economicsl.auctions.multiunit.orderbooks import java.util.UUID import org.economicsl.auctions.Tradable -import org.economicsl.auctions.multiunit.{LimitAskOrder, LimitBidOrder} +import org.economicsl.auctions.multiunit.{AskOrder, BidOrder} private[orderbooks] case class UnMatchedOrders[T <: Tradable](askOrders: SortedAskOrders[T], bidOrders: SortedBidOrders[T]) { - assert(bidOrders.headOption.forall{ case (_, bidOrder) => askOrders.headOption.forall{ case (_, askOrder) => bidOrder.limit <= askOrder.limit } }) + /* Limit price of "best" `BidOrder` instance must not exceed the limit price of the "best" `AskOrder` instance. */ + require(bidOrders.headOption.forall{ case (_, bidOrder) => askOrders.headOption.forall{ case (_, askOrder) => bidOrder.limit <= askOrder.limit } }) val isEmpty: Boolean = askOrders.isEmpty && bidOrders.isEmpty val nonEmpty: Boolean = askOrders.nonEmpty || bidOrders.nonEmpty - /** Add a new `LimitAskOrder` to the collection of unmatched orders .*/ - def + (uuid: UUID, order: LimitAskOrder[T]): UnMatchedOrders[T] = { + /** Add a new `AskOrder` to the collection of unmatched orders .*/ + def + (uuid: UUID, order: AskOrder[T]): UnMatchedOrders[T] = { UnMatchedOrders(askOrders + (uuid -> order), bidOrders) } - /** Add a new `LimitBidOrder` to the collection of unmatched orders .*/ - def + (uuid: UUID, order: LimitBidOrder[T]): UnMatchedOrders[T] = { + /** Add a new `BidOrder` to the collection of unmatched orders .*/ + def + (uuid: UUID, order: BidOrder[T]): UnMatchedOrders[T] = { UnMatchedOrders(askOrders, bidOrders + (uuid -> order)) } @@ -49,9 +50,9 @@ private[orderbooks] case class UnMatchedOrders[T <: Tradable](askOrders: SortedA } } - val askOrdering: Ordering[(UUID, LimitAskOrder[T])] = askOrders.ordering + val askOrdering: Ordering[(UUID, AskOrder[T])] = askOrders.ordering - val bidOrdering: Ordering[(UUID, LimitBidOrder[T])] = bidOrders.ordering + val bidOrdering: Ordering[(UUID, BidOrder[T])] = bidOrders.ordering /** Check whether an order is contained in the collection of unmatched orders using. */ def contains(uuid: UUID): Boolean = askOrders.contains(uuid) || bidOrders.contains(uuid) @@ -60,13 +61,13 @@ private[orderbooks] case class UnMatchedOrders[T <: Tradable](askOrders: SortedA UnMatchedOrders(askOrders.mergeWith(other.askOrders), bidOrders.mergeWith(bidOrders)) } - /** Add a `LimitAskOrder` to the collection of unmatched orders. */ - def updated(uuid: UUID, order: LimitAskOrder[T]): UnMatchedOrders[T] = { + /** Add a `AskOrder` to the collection of unmatched orders. */ + def updated(uuid: UUID, order: AskOrder[T]): UnMatchedOrders[T] = { UnMatchedOrders(askOrders.update(uuid, order), bidOrders) } - /** Add a `LimitBidOrder` to the collection of unmatched orders. */ - def updated(uuid: UUID, order: LimitBidOrder[T]): UnMatchedOrders[T] = { + /** Add a `BidOrder` to the collection of unmatched orders. */ + def updated(uuid: UUID, order: BidOrder[T]): UnMatchedOrders[T] = { UnMatchedOrders(askOrders, bidOrders.updated(uuid, order)) } @@ -84,7 +85,7 @@ private[orderbooks] object UnMatchedOrders { * based on `limit` price; the heap used to store store the `BidOrder` instances is * ordered from high to low based on `limit` price. */ - def empty[T <: Tradable](askOrdering: Ordering[(UUID, LimitAskOrder[T])], bidOrdering: Ordering[(UUID, LimitBidOrder[T])]): UnMatchedOrders[T] = { + def empty[T <: Tradable](askOrdering: Ordering[(UUID, AskOrder[T])], bidOrdering: Ordering[(UUID, BidOrder[T])]): UnMatchedOrders[T] = { new UnMatchedOrders(SortedAskOrders.empty(askOrdering), SortedBidOrders.empty(bidOrdering)) } diff --git a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/package.scala b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/package.scala index 4de1b9a..997a66e 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/package.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/package.scala @@ -15,15 +15,15 @@ limitations under the License. */ package org.economicsl.auctions.multiunit -import org.economicsl.auctions.{Order, Quantity, Tradable} +import org.economicsl.auctions.{Quantity, Tradable} -import scala.collection.{GenIterable, immutable} +import scala.collection.GenIterable /** Documentation for multi-unit orderbooks goes here! */ package object orderbooks { - def totalQuantity[T <: Tradable](orders: GenIterable[Order[T] with SinglePricePoint[T]]): Quantity = { + def totalQuantity[T <: Tradable](orders: GenIterable[Order[T]]): Quantity = { orders.aggregate[Quantity](Quantity(0))((total, order) => Quantity(total.value + order.quantity.value), (q1, q2) => Quantity(q1.value + q2.value)) } diff --git a/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/SortedAskOrdersSpec.scala b/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/SortedAskOrdersSpec.scala index 103d947..97f8670 100644 --- a/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/SortedAskOrdersSpec.scala +++ b/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/SortedAskOrdersSpec.scala @@ -27,7 +27,7 @@ class SortedAskOrdersSpec extends FlatSpec with Matchers { // Check that update is successful! updated.head should be ((issuer, revised)) updated.size should be (1) - updated.quantity should be (revised.quantity) + updated.numberUnits should be (revised.quantity) } @@ -53,10 +53,10 @@ class SortedAskOrdersSpec extends FlatSpec with Matchers { val (matched, residual) = nonEmpty.splitAt(Quantity(57)) // Check that splitAt was successful - matched.quantity should be (Quantity(57)) + matched.numberUnits should be (Quantity(57)) matched.size should be(3) - residual.quantity should be (Quantity(125 - 57)) + residual.numberUnits should be (Quantity(125 - 57)) residual.size should be(1) } diff --git a/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrdersSpec.scala b/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrdersSpec.scala index cf23139..a1e8bd1 100644 --- a/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrdersSpec.scala +++ b/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrdersSpec.scala @@ -27,7 +27,7 @@ class SortedBidOrdersSpec extends FlatSpec with Matchers { // Check that update is successful! updated.head should be ((issuer, revised)) updated.size should be (1) - updated.quantity should be (revised.quantity) + updated.numberUnits should be (revised.quantity) } @@ -53,10 +53,10 @@ class SortedBidOrdersSpec extends FlatSpec with Matchers { val (matched, residual) = nonEmpty.splitAt(Quantity(57)) // Check that splitAt was successful - matched.quantity should be (Quantity(57)) + matched.numberUnits should be (Quantity(57)) matched.size should be(1) - residual.quantity should be (Quantity(125 - 57)) + residual.numberUnits should be (Quantity(125 - 57)) residual.size should be(3) } From fb9b336feb692886b8cedaa0bd1da125fc95ff7b Mon Sep 17 00:00:00 2001 From: davidrpugh Date: Mon, 5 Jun 2017 09:16:44 +0300 Subject: [PATCH 22/25] Created a new orders package. --- .../orderbooks/FourHeapOrderBook.scala | 3 ++- .../multiunit/orderbooks/MatchedOrders.scala | 3 ++- .../orderbooks/SortedAskOrders.scala | 2 +- .../orderbooks/SortedBidOrders.scala | 2 +- .../orderbooks/UnMatchedOrders.scala | 3 ++- .../multiunit/orderbooks/package.scala | 1 + .../{ => orders}/LimitAskOrder.scala | 2 +- .../{ => orders}/LimitBidOrder.scala | 2 +- .../{ => orders}/MarketAskOrder.scala | 2 +- .../{ => orders}/MarketBidOrder.scala | 2 +- .../multiunit/{ => orders}/Order.scala | 2 +- .../auctions/multiunit/orders/package.scala | 19 ++++++++++++++++++ .../orderbooks/FourHeapOrderBookSpec.scala | 5 +++-- .../orderbooks/MatchedOrdersSpec.scala | 20 ++++++++++--------- .../orderbooks/SortedAskOrdersSpec.scala | 10 ++++++---- .../orderbooks/SortedBidOrdersSpec.scala | 10 ++++++---- 16 files changed, 59 insertions(+), 29 deletions(-) rename src/main/scala/org/economicsl/auctions/multiunit/{ => orders}/LimitAskOrder.scala (96%) rename src/main/scala/org/economicsl/auctions/multiunit/{ => orders}/LimitBidOrder.scala (96%) rename src/main/scala/org/economicsl/auctions/multiunit/{ => orders}/MarketAskOrder.scala (95%) rename src/main/scala/org/economicsl/auctions/multiunit/{ => orders}/MarketBidOrder.scala (95%) rename src/main/scala/org/economicsl/auctions/multiunit/{ => orders}/Order.scala (96%) create mode 100644 src/main/scala/org/economicsl/auctions/multiunit/orders/package.scala diff --git a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/FourHeapOrderBook.scala b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/FourHeapOrderBook.scala index 85a6c47..3414a62 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/FourHeapOrderBook.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/FourHeapOrderBook.scala @@ -18,7 +18,8 @@ package org.economicsl.auctions.multiunit.orderbooks import java.util.UUID import org.economicsl.auctions.Tradable -import org.economicsl.auctions.multiunit.{AskOrder, BidOrder} +import org.economicsl.auctions.multiunit.BidOrder +import org.economicsl.auctions.multiunit.orders.{AskOrder, BidOrder} class FourHeapOrderBook[T <: Tradable] private(matchedOrders: MatchedOrders[T], unMatchedOrders: UnMatchedOrders[T]) { diff --git a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/MatchedOrders.scala b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/MatchedOrders.scala index 5f5800f..c7971ec 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/MatchedOrders.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/MatchedOrders.scala @@ -18,7 +18,8 @@ package org.economicsl.auctions.multiunit.orderbooks import java.util.UUID import org.economicsl.auctions.{Quantity, Tradable} -import org.economicsl.auctions.multiunit.{AskOrder, BidOrder} +import org.economicsl.auctions.multiunit.BidOrder +import org.economicsl.auctions.multiunit.orders.{AskOrder, BidOrder} private[orderbooks] class MatchedOrders[T <: Tradable] private(val askOrders: SortedAskOrders[T], diff --git a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedAskOrders.scala b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedAskOrders.scala index 7ce2418..cf9aa30 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedAskOrders.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedAskOrders.scala @@ -17,8 +17,8 @@ package org.economicsl.auctions.multiunit.orderbooks import java.util.UUID +import org.economicsl.auctions.multiunit.orders.AskOrder import org.economicsl.auctions.{Quantity, Tradable} -import org.economicsl.auctions.multiunit.AskOrder import scala.collection.immutable.TreeSet diff --git a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrders.scala b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrders.scala index db3a253..26930d0 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrders.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrders.scala @@ -17,8 +17,8 @@ package org.economicsl.auctions.multiunit.orderbooks import java.util.UUID +import org.economicsl.auctions.multiunit.orders.BidOrder import org.economicsl.auctions.{Quantity, Tradable} -import org.economicsl.auctions.multiunit.BidOrder import scala.collection.immutable.TreeSet diff --git a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/UnMatchedOrders.scala b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/UnMatchedOrders.scala index c8b089b..f4b1cc6 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/UnMatchedOrders.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/UnMatchedOrders.scala @@ -18,7 +18,8 @@ package org.economicsl.auctions.multiunit.orderbooks import java.util.UUID import org.economicsl.auctions.Tradable -import org.economicsl.auctions.multiunit.{AskOrder, BidOrder} +import org.economicsl.auctions.multiunit.BidOrder +import org.economicsl.auctions.multiunit.orders.{AskOrder, BidOrder} private[orderbooks] case class UnMatchedOrders[T <: Tradable](askOrders: SortedAskOrders[T], diff --git a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/package.scala b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/package.scala index 997a66e..a01f59a 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/package.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/package.scala @@ -15,6 +15,7 @@ limitations under the License. */ package org.economicsl.auctions.multiunit +import org.economicsl.auctions.multiunit.orders.Order import org.economicsl.auctions.{Quantity, Tradable} import scala.collection.GenIterable diff --git a/src/main/scala/org/economicsl/auctions/multiunit/LimitAskOrder.scala b/src/main/scala/org/economicsl/auctions/multiunit/orders/LimitAskOrder.scala similarity index 96% rename from src/main/scala/org/economicsl/auctions/multiunit/LimitAskOrder.scala rename to src/main/scala/org/economicsl/auctions/multiunit/orders/LimitAskOrder.scala index 2078498..b2b6241 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/LimitAskOrder.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/orders/LimitAskOrder.scala @@ -13,7 +13,7 @@ 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 +package org.economicsl.auctions.multiunit.orders import java.util.UUID diff --git a/src/main/scala/org/economicsl/auctions/multiunit/LimitBidOrder.scala b/src/main/scala/org/economicsl/auctions/multiunit/orders/LimitBidOrder.scala similarity index 96% rename from src/main/scala/org/economicsl/auctions/multiunit/LimitBidOrder.scala rename to src/main/scala/org/economicsl/auctions/multiunit/orders/LimitBidOrder.scala index 86e39d4..64913bb 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/LimitBidOrder.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/orders/LimitBidOrder.scala @@ -13,7 +13,7 @@ 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 +package org.economicsl.auctions.multiunit.orders import java.util.UUID diff --git a/src/main/scala/org/economicsl/auctions/multiunit/MarketAskOrder.scala b/src/main/scala/org/economicsl/auctions/multiunit/orders/MarketAskOrder.scala similarity index 95% rename from src/main/scala/org/economicsl/auctions/multiunit/MarketAskOrder.scala rename to src/main/scala/org/economicsl/auctions/multiunit/orders/MarketAskOrder.scala index ea2048a..a949674 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/MarketAskOrder.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/orders/MarketAskOrder.scala @@ -13,7 +13,7 @@ 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 +package org.economicsl.auctions.multiunit.orders import java.util.UUID diff --git a/src/main/scala/org/economicsl/auctions/multiunit/MarketBidOrder.scala b/src/main/scala/org/economicsl/auctions/multiunit/orders/MarketBidOrder.scala similarity index 95% rename from src/main/scala/org/economicsl/auctions/multiunit/MarketBidOrder.scala rename to src/main/scala/org/economicsl/auctions/multiunit/orders/MarketBidOrder.scala index 375a3a9..223eb28 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/MarketBidOrder.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/orders/MarketBidOrder.scala @@ -13,7 +13,7 @@ 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 +package org.economicsl.auctions.multiunit.orders import java.util.UUID diff --git a/src/main/scala/org/economicsl/auctions/multiunit/Order.scala b/src/main/scala/org/economicsl/auctions/multiunit/orders/Order.scala similarity index 96% rename from src/main/scala/org/economicsl/auctions/multiunit/Order.scala rename to src/main/scala/org/economicsl/auctions/multiunit/orders/Order.scala index 9ce44be..b357bf8 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/Order.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/orders/Order.scala @@ -13,7 +13,7 @@ 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 +package org.economicsl.auctions.multiunit.orders import org.economicsl.auctions._ diff --git a/src/main/scala/org/economicsl/auctions/multiunit/orders/package.scala b/src/main/scala/org/economicsl/auctions/multiunit/orders/package.scala new file mode 100644 index 0000000..c0fd62a --- /dev/null +++ b/src/main/scala/org/economicsl/auctions/multiunit/orders/package.scala @@ -0,0 +1,19 @@ +/* +Copyright (c) 2017 KAPSARC + +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 + + +package object orders diff --git a/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/FourHeapOrderBookSpec.scala b/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/FourHeapOrderBookSpec.scala index 55fcf14..5178b4c 100644 --- a/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/FourHeapOrderBookSpec.scala +++ b/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/FourHeapOrderBookSpec.scala @@ -2,6 +2,7 @@ package org.economicsl.auctions.multiunit.orderbooks import java.util.UUID +import org.economicsl.auctions.multiunit.orders.{LimitAskOrder, LimitBidOrder} import org.economicsl.auctions.{GoogleStock, Price, Quantity, multiunit} import org.scalatest.{FlatSpec, Matchers} @@ -14,10 +15,10 @@ class FourHeapOrderBookSpec extends FlatSpec with Matchers { // Create some multi-unit limit ask orders val issuer1 = UUID.randomUUID() - val order1 = multiunit.LimitAskOrder(issuer1, Price(10), Quantity(10), google) + val order1 = LimitAskOrder(issuer1, Price(10), Quantity(10), google) val issuer2 = UUID.randomUUID() - val order2 = multiunit.LimitBidOrder(issuer2, Price(15), Quantity(10), google) + val order2 = LimitBidOrder(issuer2, Price(15), Quantity(10), google) // Create an empty orderBook val empty = FourHeapOrderBook.empty[GoogleStock] diff --git a/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/MatchedOrdersSpec.scala b/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/MatchedOrdersSpec.scala index ccc89e5..2fcdc5d 100644 --- a/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/MatchedOrdersSpec.scala +++ b/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/MatchedOrdersSpec.scala @@ -3,6 +3,8 @@ package org.economicsl.auctions.multiunit.orderbooks import java.util.UUID import org.economicsl.auctions.multiunit +import org.economicsl.auctions.multiunit.orders +import org.economicsl.auctions.multiunit.orders.{LimitAskOrder, LimitBidOrder} import org.economicsl.auctions.{GoogleStock, Price, Quantity} import org.scalatest.{FlatSpec, Matchers} @@ -14,12 +16,12 @@ class MatchedOrdersSpec extends FlatSpec with Matchers { // Create some multi-unit limit ask orders val issuer1 = UUID.randomUUID() - val order1 = multiunit.LimitAskOrder(issuer1, Price(10), Quantity(10), google) + val order1 = LimitAskOrder(issuer1, Price(10), Quantity(10), google) val issuer2 = UUID.randomUUID() - val order2 = multiunit.LimitBidOrder(issuer2, Price(15), Quantity(10), google) + val order2 = LimitBidOrder(issuer2, Price(15), Quantity(10), google) - val empty = MatchedOrders.empty[GoogleStock](multiunit.LimitAskOrder.ordering.reverse, multiunit.LimitBidOrder.ordering.reverse) + val empty = MatchedOrders.empty[GoogleStock](orders.LimitAskOrder.ordering.reverse, orders.LimitBidOrder.ordering.reverse) val (matched, optionalUnMatched) = empty + (issuer1 -> order1, issuer2 -> order2) assert(matched.nonEmpty) @@ -31,13 +33,13 @@ class MatchedOrdersSpec extends FlatSpec with Matchers { // Create some multi-unit limit ask orders val issuer1 = UUID.randomUUID() - val order1 = multiunit.LimitAskOrder(issuer1, Price(10), Quantity(100), google) + val order1 = orders.LimitAskOrder(issuer1, Price(10), Quantity(100), google) val issuer2 = UUID.randomUUID() - val order2 = multiunit.LimitBidOrder(issuer2, Price(15), Quantity(10), google) + val order2 = orders.LimitBidOrder(issuer2, Price(15), Quantity(10), google) // Create an empty set of matched orders and add the newly created orders - val empty = MatchedOrders.empty[GoogleStock](multiunit.LimitAskOrder.ordering.reverse, multiunit.LimitBidOrder.ordering.reverse) + val empty = MatchedOrders.empty[GoogleStock](orders.LimitAskOrder.ordering.reverse, orders.LimitBidOrder.ordering.reverse) val (matched, optionalUnMatched) = empty + (issuer1 -> order1, issuer2 -> order2) assert(matched.nonEmpty) @@ -53,13 +55,13 @@ class MatchedOrdersSpec extends FlatSpec with Matchers { // Create some multi-unit limit ask orders val issuer1 = UUID.randomUUID() - val order1 = multiunit.LimitAskOrder(issuer1, Price(10), Quantity(10), google) + val order1 = orders.LimitAskOrder(issuer1, Price(10), Quantity(10), google) val issuer2 = UUID.randomUUID() - val order2 = multiunit.LimitBidOrder(issuer2, Price(15), Quantity(100), google) + val order2 = orders.LimitBidOrder(issuer2, Price(15), Quantity(100), google) // Create an empty set of matched orders and add the newly created orders - val empty = MatchedOrders.empty[GoogleStock](multiunit.LimitAskOrder.ordering.reverse, multiunit.LimitBidOrder.ordering.reverse) + val empty = MatchedOrders.empty[GoogleStock](orders.LimitAskOrder.ordering.reverse, orders.LimitBidOrder.ordering.reverse) val (matched, optionalUnMatched) = empty + (issuer1 -> order1, issuer2 -> order2) assert(matched.nonEmpty) diff --git a/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/SortedAskOrdersSpec.scala b/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/SortedAskOrdersSpec.scala index 97f8670..558b91d 100644 --- a/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/SortedAskOrdersSpec.scala +++ b/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/SortedAskOrdersSpec.scala @@ -2,6 +2,8 @@ package org.economicsl.auctions.multiunit.orderbooks import java.util.UUID +import org.economicsl.auctions.multiunit.orders +import org.economicsl.auctions.multiunit.orders.LimitAskOrder import org.economicsl.auctions.{GoogleStock, Price, Quantity, multiunit} import org.scalatest.{FlatSpec, Matchers} @@ -13,7 +15,7 @@ class SortedAskOrdersSpec extends FlatSpec with Matchers { // Create a multi-unit limit ask order val issuer = UUID.randomUUID() val google = new GoogleStock - val order = multiunit.LimitAskOrder(issuer, Price(10), Quantity(100), google) + val order = LimitAskOrder(issuer, Price(10), Quantity(100), google) // Create an empty order book and add the order val empty = SortedAskOrders.empty[GoogleStock] @@ -37,13 +39,13 @@ class SortedAskOrdersSpec extends FlatSpec with Matchers { // Create some multi-unit limit ask orders val issuer1 = UUID.randomUUID() - val order1 = multiunit.LimitAskOrder(issuer1, Price(10), Quantity(10), google) + val order1 = orders.LimitAskOrder(issuer1, Price(10), Quantity(10), google) val issuer2 = UUID.randomUUID() - val order2 = multiunit.LimitAskOrder(issuer2, Price(5), Quantity(15), google) + val order2 = orders.LimitAskOrder(issuer2, Price(5), Quantity(15), google) val issuer3 = UUID.randomUUID() - val order3 = multiunit.LimitAskOrder(issuer3, Price(15), Quantity(100), google) + val order3 = orders.LimitAskOrder(issuer3, Price(15), Quantity(100), google) // Create an empty order book and add the orders val empty = SortedAskOrders.empty[GoogleStock] diff --git a/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrdersSpec.scala b/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrdersSpec.scala index a1e8bd1..2c05aa8 100644 --- a/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrdersSpec.scala +++ b/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrdersSpec.scala @@ -2,6 +2,8 @@ package org.economicsl.auctions.multiunit.orderbooks import java.util.UUID +import org.economicsl.auctions.multiunit.orders +import org.economicsl.auctions.multiunit.orders.LimitBidOrder import org.economicsl.auctions.{GoogleStock, Price, Quantity, multiunit} import org.scalatest.{FlatSpec, Matchers} @@ -13,7 +15,7 @@ class SortedBidOrdersSpec extends FlatSpec with Matchers { // Create a multi-unit limit ask order val issuer = UUID.randomUUID() val google = new GoogleStock - val order = multiunit.LimitBidOrder(issuer, Price(10), Quantity(100), google) + val order = LimitBidOrder(issuer, Price(10), Quantity(100), google) // Create an empty order book and add the order val empty = SortedBidOrders.empty[GoogleStock] @@ -37,13 +39,13 @@ class SortedBidOrdersSpec extends FlatSpec with Matchers { // Create some multi-unit limit bid orders val issuer1 = UUID.randomUUID() - val order1 = multiunit.LimitBidOrder(issuer1, Price(10), Quantity(10), google) + val order1 = orders.LimitBidOrder(issuer1, Price(10), Quantity(10), google) val issuer2 = UUID.randomUUID() - val order2 = multiunit.LimitBidOrder(issuer2, Price(5), Quantity(15), google) + val order2 = orders.LimitBidOrder(issuer2, Price(5), Quantity(15), google) val issuer3 = UUID.randomUUID() - val order3 = multiunit.LimitBidOrder(issuer3, Price(15), Quantity(100), google) + val order3 = orders.LimitBidOrder(issuer3, Price(15), Quantity(100), google) // Create an empty order book and add the orders val empty = SortedBidOrders.empty[GoogleStock] From 868c4222eb5fd84f0154b766b4c0981c2b591ff4 Mon Sep 17 00:00:00 2001 From: davidrpugh Date: Wed, 7 Jun 2017 07:14:46 +0300 Subject: [PATCH 23/25] Started work on generic orderbook. --- .../multiunit/orderbooks/OrderBook.scala | 127 ++++++++++++++++++ .../auctions/multiunit/orderbooks/sandbox.sc | 0 .../org/economicsl/auctions/Security.scala | 6 - .../orderbooks/FourHeapOrderBookSpec.scala | 41 ------ .../orderbooks/MatchedOrdersSpec.scala | 74 ---------- .../orderbooks/SortedBidOrdersSpec.scala | 66 +-------- 6 files changed, 131 insertions(+), 183 deletions(-) create mode 100644 src/main/scala/org/economicsl/auctions/multiunit/orderbooks/OrderBook.scala create mode 100644 src/main/scala/org/economicsl/auctions/multiunit/orderbooks/sandbox.sc delete mode 100644 src/test/scala/org/economicsl/auctions/Security.scala delete mode 100644 src/test/scala/org/economicsl/auctions/multiunit/orderbooks/FourHeapOrderBookSpec.scala delete mode 100644 src/test/scala/org/economicsl/auctions/multiunit/orderbooks/MatchedOrdersSpec.scala diff --git a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/OrderBook.scala b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/OrderBook.scala new file mode 100644 index 0000000..648cc53 --- /dev/null +++ b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/OrderBook.scala @@ -0,0 +1,127 @@ +/* +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.multiunit.orders.{AskOrder, Order} +import org.economicsl.auctions.{Quantity, Tradable} + +import scala.collection.{GenIterable, immutable} + + +/** + * + * @param existing sorted mapping from keys to collections of orders sharing a particular key. + * @param numberUnits + * @tparam K + * @tparam O + */ +class OrderBook[K, O <: Order[_ <:Tradable]] private(existing: immutable.TreeMap[K, immutable.Queue[O]], val numberUnits: Quantity) { + + val ordering: Ordering[K] = existing.ordering + + /** Return a new `OrderBook` instance containing an additional `AskOrder` instance. + * + * @param key + * @param order + * @return + * @note if this `OrderBook` instance contains an existing collection of `AskOrder` instances sharing the same + * key as the new `AskOrder` instance, the new `AskOrder` instance is added to the existing collection. + */ + def + (key: K, order: O): OrderBook[K, O] = { + val current = existing.getOrElse(key, empty) + new OrderBook(existing + (key -> current.enqueue(order)), numberUnits + order.quantity) + } + + /** Return a new `OrderBook` instance containing an collection of `AskOrder` instances. + * + * @param key + * @param orders + * @return + * @note if this `OrderBook` instance contains an existing collection of `AskOrder` instances sharing the same + * key, then the new collection replaces the existing collection. + */ + def + (key: K, orders: immutable.Queue[O]): OrderBook[K, O] = { + val currentUnits = aggregate(existing.getOrElse(key, empty)) + val incomingUnits = aggregate(orders) + val change = currentUnits - incomingUnits + new OrderBook(existing + (key -> orders), numberUnits + change) + } + + def - (key: K): OrderBook[K, O] = { + existing.get(key) match { + case Some(orders) => + val removedUnits = orders.foldLeft(Quantity(0))((total, order) => total + order.quantity) + new OrderBook(existing - key, numberUnits - removedUnits) + case None => this + } + } + + def - (key: K, order: O): OrderBook[K, O] = { + existing.get(key) match { + case Some(orders) => + val residualOrders = orders.diff(immutable.Queue(order)) // multi-set diff! + if (residualOrders.isEmpty) { + new OrderBook(existing - key, numberUnits - order.quantity) + } else { + new OrderBook(existing + (key -> residualOrders), numberUnits - order.quantity) + } + case None => this + } + } + + def contains(key: K): Boolean = existing.contains(key) + + def foldLeft[B](z: B)(op: (B, (K, immutable.Queue[O])) => B): B = { + existing.foldLeft(z)(op) + } + + def getOrElse(key: K, default: => immutable.Queue[O]): immutable.Queue[O] = { + existing.getOrElse(key, default) + } + + def headOption: Option[(K, O)] = { + existing.headOption.flatMap{ case (key, orders) => orders.headOption.map(order => (key, order)) } + } + + def isEmpty: Boolean = existing.isEmpty + + def mergeWith(other: OrderBook[K, O]): OrderBook[K, O] = { + other.foldLeft(this) { case (ob, (key, orders)) => + val currentOrders = ob.getOrElse(key, empty) + ob + (key, currentOrders.enqueue(orders)) + } + } + + def nonEmpty: Boolean = existing.nonEmpty + + /* Use of GenIterable allows for use on both standard and parallel collections. */ + private[this] def aggregate(orders: GenIterable[O]): Quantity = { + orders.aggregate(Quantity(0))((total, order) => total + order.quantity, _ + _ ) + } + + private[this] def empty: immutable.Queue[O] = immutable.Queue.empty[O] + +} + + +object OrderBook { + + def empty[K, O <: Order[_ <: Tradable]](implicit ordering: Ordering[K]): OrderBook[K, O] = { + new OrderBook(immutable.TreeMap.empty[K, immutable.Queue[O]](ordering), Quantity(0)) + } + +} + diff --git a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/sandbox.sc b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/sandbox.sc new file mode 100644 index 0000000..e69de29 diff --git a/src/test/scala/org/economicsl/auctions/Security.scala b/src/test/scala/org/economicsl/auctions/Security.scala deleted file mode 100644 index e62e7e4..0000000 --- a/src/test/scala/org/economicsl/auctions/Security.scala +++ /dev/null @@ -1,6 +0,0 @@ -package org.economicsl.auctions - - -sealed trait Security extends Tradable - -class GoogleStock extends Security \ No newline at end of file diff --git a/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/FourHeapOrderBookSpec.scala b/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/FourHeapOrderBookSpec.scala deleted file mode 100644 index 5178b4c..0000000 --- a/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/FourHeapOrderBookSpec.scala +++ /dev/null @@ -1,41 +0,0 @@ -package org.economicsl.auctions.multiunit.orderbooks - -import java.util.UUID - -import org.economicsl.auctions.multiunit.orders.{LimitAskOrder, LimitBidOrder} -import org.economicsl.auctions.{GoogleStock, Price, Quantity, multiunit} -import org.scalatest.{FlatSpec, Matchers} - - -class FourHeapOrderBookSpec extends FlatSpec with Matchers { - - "An empty FourHeapOrderBook" should "accept a new order" in { - - val google = new GoogleStock - - // Create some multi-unit limit ask orders - val issuer1 = UUID.randomUUID() - val order1 = LimitAskOrder(issuer1, Price(10), Quantity(10), google) - - val issuer2 = UUID.randomUUID() - val order2 = LimitBidOrder(issuer2, Price(15), Quantity(10), google) - - // Create an empty orderBook - val empty = FourHeapOrderBook.empty[GoogleStock] - - // Add a limit ask order - val uuid1 = UUID.randomUUID() - val orderBook = empty + (uuid1, order1) - assert(orderBook.nonEmpty) - - // add a limit bid Order - val uuid2 = UUID.randomUUID() - val orderBook2 = empty + (uuid2, order2) - assert(orderBook2.nonEmpty) - - val orderBook3 = empty + (uuid1, order1) + (uuid2, order2) - assert(orderBook.nonEmpty) - - } - -} diff --git a/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/MatchedOrdersSpec.scala b/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/MatchedOrdersSpec.scala deleted file mode 100644 index 2fcdc5d..0000000 --- a/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/MatchedOrdersSpec.scala +++ /dev/null @@ -1,74 +0,0 @@ -package org.economicsl.auctions.multiunit.orderbooks - -import java.util.UUID - -import org.economicsl.auctions.multiunit -import org.economicsl.auctions.multiunit.orders -import org.economicsl.auctions.multiunit.orders.{LimitAskOrder, LimitBidOrder} -import org.economicsl.auctions.{GoogleStock, Price, Quantity} -import org.scalatest.{FlatSpec, Matchers} - - -class MatchedOrdersSpec extends FlatSpec with Matchers { - - "A set of MatchedOrders" should " accept a " in { - val google = new GoogleStock - - // Create some multi-unit limit ask orders - val issuer1 = UUID.randomUUID() - val order1 = LimitAskOrder(issuer1, Price(10), Quantity(10), google) - - val issuer2 = UUID.randomUUID() - val order2 = LimitBidOrder(issuer2, Price(15), Quantity(10), google) - - val empty = MatchedOrders.empty[GoogleStock](orders.LimitAskOrder.ordering.reverse, orders.LimitBidOrder.ordering.reverse) - val (matched, optionalUnMatched) = empty + (issuer1 -> order1, issuer2 -> order2) - - assert(matched.nonEmpty) - optionalUnMatched should be (None) - } - - "A set of MatchedOrders" should " accept a sdasdf " in { - val google = new GoogleStock - - // Create some multi-unit limit ask orders - val issuer1 = UUID.randomUUID() - val order1 = orders.LimitAskOrder(issuer1, Price(10), Quantity(100), google) - - val issuer2 = UUID.randomUUID() - val order2 = orders.LimitBidOrder(issuer2, Price(15), Quantity(10), google) - - // Create an empty set of matched orders and add the newly created orders - val empty = MatchedOrders.empty[GoogleStock](orders.LimitAskOrder.ordering.reverse, orders.LimitBidOrder.ordering.reverse) - val (matched, optionalUnMatched) = empty + (issuer1 -> order1, issuer2 -> order2) - - assert(matched.nonEmpty) - val rationed = order1.quantity - order2.quantity - val rationedOrder = order1.withQuantity(rationed) - optionalUnMatched.map(rationedOrders => rationedOrders.askOrders.head) should be (Some((issuer1, rationedOrder))) - - } - - - "A set of MatchedOrders" should " accept a fdgdfg " in { - val google = new GoogleStock - - // Create some multi-unit limit ask orders - val issuer1 = UUID.randomUUID() - val order1 = orders.LimitAskOrder(issuer1, Price(10), Quantity(10), google) - - val issuer2 = UUID.randomUUID() - val order2 = orders.LimitBidOrder(issuer2, Price(15), Quantity(100), google) - - // Create an empty set of matched orders and add the newly created orders - val empty = MatchedOrders.empty[GoogleStock](orders.LimitAskOrder.ordering.reverse, orders.LimitBidOrder.ordering.reverse) - val (matched, optionalUnMatched) = empty + (issuer1 -> order1, issuer2 -> order2) - - assert(matched.nonEmpty) - val rationed = order2.quantity - order1.quantity - val rationedOrder = order2.withQuantity(rationed) - optionalUnMatched.map(rationedOrders => rationedOrders.bidOrders.head) should be (Some((issuer2, rationedOrder))) - - } - -} diff --git a/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrdersSpec.scala b/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrdersSpec.scala index 2c05aa8..5725319 100644 --- a/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrdersSpec.scala +++ b/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrdersSpec.scala @@ -1,66 +1,8 @@ package org.economicsl.auctions.multiunit.orderbooks -import java.util.UUID - -import org.economicsl.auctions.multiunit.orders -import org.economicsl.auctions.multiunit.orders.LimitBidOrder -import org.economicsl.auctions.{GoogleStock, Price, Quantity, multiunit} -import org.scalatest.{FlatSpec, Matchers} - - -class SortedBidOrdersSpec extends FlatSpec with Matchers { - - "A SortedBidOrderBook" should "update an existing order" in { - - // Create a multi-unit limit ask order - val issuer = UUID.randomUUID() - val google = new GoogleStock - val order = LimitBidOrder(issuer, Price(10), Quantity(100), google) - - // Create an empty order book and add the order - val empty = SortedBidOrders.empty[GoogleStock] - val nonEmpty = empty + (issuer -> order) - nonEmpty.head should be ((issuer, order)) - - // Create a revised order and update the order book - val revised = order.withQuantity(Quantity(1)) - val updated = nonEmpty.updated(issuer, revised) - - // Check that update is successful! - updated.head should be ((issuer, revised)) - updated.size should be (1) - updated.numberUnits should be (revised.quantity) - - } - - "A SortedBidOrderBook" should "split itself into two pieces" in { - - val google = new GoogleStock - - // Create some multi-unit limit bid orders - val issuer1 = UUID.randomUUID() - val order1 = orders.LimitBidOrder(issuer1, Price(10), Quantity(10), google) - - val issuer2 = UUID.randomUUID() - val order2 = orders.LimitBidOrder(issuer2, Price(5), Quantity(15), google) - - val issuer3 = UUID.randomUUID() - val order3 = orders.LimitBidOrder(issuer3, Price(15), Quantity(100), google) - - // Create an empty order book and add the orders - val empty = SortedBidOrders.empty[GoogleStock] - val nonEmpty = empty + (issuer1 -> order1) + (issuer2 -> order2) + (issuer3 -> order3) - - // Create a revised order and update the order book - val (matched, residual) = nonEmpty.splitAt(Quantity(57)) - - // Check that splitAt was successful - matched.numberUnits should be (Quantity(57)) - matched.size should be(1) - - residual.numberUnits should be (Quantity(125 - 57)) - residual.size should be(3) - - } +/** + * Created by pughdr on 6/6/2017. + */ +class SortedBidOrdersSpec { } From 8813e904181942cd8409b1cc3030dc7192ac306c Mon Sep 17 00:00:00 2001 From: davidrpugh Date: Wed, 7 Jun 2017 08:20:03 +0300 Subject: [PATCH 24/25] Now compiling! --- .../multiunit/orderbooks/OrderBook.scala | 47 ++++++++++++------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/OrderBook.scala b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/OrderBook.scala index 648cc53..5c37d8f 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/OrderBook.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/OrderBook.scala @@ -15,9 +15,10 @@ limitations under the License. */ package org.economicsl.auctions.multiunit.orderbooks -import org.economicsl.auctions.multiunit.orders.{AskOrder, Order} +import org.economicsl.auctions.multiunit.orders.Order import org.economicsl.auctions.{Quantity, Tradable} +import scala.collection.generic.CanBuildFrom import scala.collection.{GenIterable, immutable} @@ -28,7 +29,9 @@ import scala.collection.{GenIterable, immutable} * @tparam K * @tparam O */ -class OrderBook[K, O <: Order[_ <:Tradable]] private(existing: immutable.TreeMap[K, immutable.Queue[O]], val numberUnits: Quantity) { +class OrderBook[K, O <: Order[_ <:Tradable], CC <: GenIterable[O]] private(existing: immutable.TreeMap[K, CC], + val numberUnits: Quantity) + (implicit cbf: CanBuildFrom[CC, O, CC]) { val ordering: Ordering[K] = existing.ordering @@ -40,9 +43,11 @@ class OrderBook[K, O <: Order[_ <:Tradable]] private(existing: immutable.TreeMap * @note if this `OrderBook` instance contains an existing collection of `AskOrder` instances sharing the same * key as the new `AskOrder` instance, the new `AskOrder` instance is added to the existing collection. */ - def + (key: K, order: O): OrderBook[K, O] = { + def + (key: K, order: O): OrderBook[K, O, CC] = { val current = existing.getOrElse(key, empty) - new OrderBook(existing + (key -> current.enqueue(order)), numberUnits + order.quantity) + val builder = cbf(current) + builder += order + new OrderBook(existing + (key -> builder.result()), numberUnits + order.quantity) } /** Return a new `OrderBook` instance containing an collection of `AskOrder` instances. @@ -53,14 +58,14 @@ class OrderBook[K, O <: Order[_ <:Tradable]] private(existing: immutable.TreeMap * @note if this `OrderBook` instance contains an existing collection of `AskOrder` instances sharing the same * key, then the new collection replaces the existing collection. */ - def + (key: K, orders: immutable.Queue[O]): OrderBook[K, O] = { + def + (key: K, orders: CC): OrderBook[K, O, CC] = { val currentUnits = aggregate(existing.getOrElse(key, empty)) val incomingUnits = aggregate(orders) val change = currentUnits - incomingUnits new OrderBook(existing + (key -> orders), numberUnits + change) } - def - (key: K): OrderBook[K, O] = { + def - (key: K): OrderBook[K, O, CC] = { existing.get(key) match { case Some(orders) => val removedUnits = orders.foldLeft(Quantity(0))((total, order) => total + order.quantity) @@ -69,10 +74,12 @@ class OrderBook[K, O <: Order[_ <:Tradable]] private(existing: immutable.TreeMap } } - def - (key: K, order: O): OrderBook[K, O] = { + def - (key: K, order: O): OrderBook[K, O, CC] = { existing.get(key) match { case Some(orders) => - val residualOrders = orders.diff(immutable.Queue(order)) // multi-set diff! + val builder = cbf() + orders.foreach(o => if (o != order) builder += o) + val residualOrders = builder.result() if (residualOrders.isEmpty) { new OrderBook(existing - key, numberUnits - order.quantity) } else { @@ -84,11 +91,11 @@ class OrderBook[K, O <: Order[_ <:Tradable]] private(existing: immutable.TreeMap def contains(key: K): Boolean = existing.contains(key) - def foldLeft[B](z: B)(op: (B, (K, immutable.Queue[O])) => B): B = { + def foldLeft[B](z: B)(op: (B, (K, CC)) => B): B = { existing.foldLeft(z)(op) } - def getOrElse(key: K, default: => immutable.Queue[O]): immutable.Queue[O] = { + def getOrElse(key: K, default: => CC): CC = { existing.getOrElse(key, default) } @@ -98,10 +105,12 @@ class OrderBook[K, O <: Order[_ <:Tradable]] private(existing: immutable.TreeMap def isEmpty: Boolean = existing.isEmpty - def mergeWith(other: OrderBook[K, O]): OrderBook[K, O] = { + def mergeWith(other: OrderBook[K, O, CC]): OrderBook[K, O, CC] = { other.foldLeft(this) { case (ob, (key, orders)) => - val currentOrders = ob.getOrElse(key, empty) - ob + (key, currentOrders.enqueue(orders)) + val current = ob.getOrElse(key, empty) + val builder = cbf(current) + orders.foreach(order => builder += order) + ob + (key, builder.result()) } } @@ -112,16 +121,18 @@ class OrderBook[K, O <: Order[_ <:Tradable]] private(existing: immutable.TreeMap orders.aggregate(Quantity(0))((total, order) => total + order.quantity, _ + _ ) } - private[this] def empty: immutable.Queue[O] = immutable.Queue.empty[O] + private[this] def empty: CC = { + cbf().result() + } } object OrderBook { - def empty[K, O <: Order[_ <: Tradable]](implicit ordering: Ordering[K]): OrderBook[K, O] = { - new OrderBook(immutable.TreeMap.empty[K, immutable.Queue[O]](ordering), Quantity(0)) + def empty[K, O <: Order[_ <: Tradable], CC <: GenIterable[O]](implicit ordering: Ordering[K], cbf: CanBuildFrom[CC, O, CC]): OrderBook[K, O, CC] = { + val initial = immutable.TreeMap.empty(ordering) + new OrderBook[K, O, CC](initial, Quantity(0))(cbf) } -} - +} \ No newline at end of file From cd026cd5d2cea898384aeb57d5ab7a0d1f66ed4a Mon Sep 17 00:00:00 2001 From: davidrpugh Date: Wed, 7 Jun 2017 12:30:25 +0300 Subject: [PATCH 25/25] A bit lost now... --- .../orderbooks/FourHeapOrderBook.scala | 171 ++++++++---------- .../multiunit/orderbooks/MatchedOrders.scala | 102 ++++------- .../orderbooks/SortedAskOrders.scala | 146 ++++++++++----- .../orderbooks/SortedBidOrders.scala | 131 +++++++++----- .../orderbooks/UnMatchedOrders.scala | 61 +++---- .../auctions/multiunit/orderbooks/sandbox.sc | 13 ++ .../orderbooks/SortedAskOrdersSpec.scala | 61 +++---- .../orderbooks/SortedBidOrdersSpec.scala | 47 ++++- 8 files changed, 393 insertions(+), 339 deletions(-) diff --git a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/FourHeapOrderBook.scala b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/FourHeapOrderBook.scala index 3414a62..d29f407 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/FourHeapOrderBook.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/FourHeapOrderBook.scala @@ -15,100 +15,76 @@ limitations under the License. */ package org.economicsl.auctions.multiunit.orderbooks -import java.util.UUID - import org.economicsl.auctions.Tradable -import org.economicsl.auctions.multiunit.BidOrder import org.economicsl.auctions.multiunit.orders.{AskOrder, BidOrder} -class FourHeapOrderBook[T <: Tradable] private(matchedOrders: MatchedOrders[T], unMatchedOrders: UnMatchedOrders[T]) { - - val isEmpty: Boolean = matchedOrders.isEmpty && unMatchedOrders.isEmpty +class FourHeapOrderBook[K, T <: Tradable] private(matched: MatchedOrders[K, T], unMatched: UnMatchedOrders[K, T]) { - val nonEmpty: Boolean = matchedOrders.nonEmpty || unMatchedOrders.nonEmpty + require(matched.bidOrders.headOption.forall{ case (_, b1) => unMatched.bidOrders.headOption.forall{ case (_, b2) => b1.limit >= b2.limit } }) - /** Remove an order from the `OrderBook`. - * - * @param uuid the universal unique identifier corresponding to the order that should be removed. - * @return A new `OrderBook` with the order corresponding to the `uuid` removed. - * @note Because multi-unit orders are divisible, the order with the `uuid` might have been split in which case it - * will exist in both the matched and unmatched order sets. - */ - def - (uuid: UUID): FourHeapOrderBook[T] = { - if (matchedOrders.contains(uuid) && unMatchedOrders.contains(uuid)) { - val (residualMatched, additionalUnMatched) = matchedOrders - uuid - val residualUnMatched = unMatchedOrders - uuid - new FourHeapOrderBook(residualMatched, residualUnMatched.mergeWith(additionalUnMatched)) - } else if (matchedOrders.contains(uuid)) { - val (residualMatched, additionalUnMatched) = matchedOrders - uuid - new FourHeapOrderBook(residualMatched, unMatchedOrders.mergeWith(additionalUnMatched)) - } else { - val residualUnMatched = unMatchedOrders - uuid - new FourHeapOrderBook(matchedOrders, unMatchedOrders.mergeWith(residualUnMatched)) - } - } + 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 uuid + * @param key * @param order * @return * @note adding a new `AskOrder` is non-trivial and there are several cases to consider. */ - def + (uuid: UUID, order: AskOrder[T]): FourHeapOrderBook[T] = { - (unMatchedOrders.bidOrders.headOption, matchedOrders.askOrders.headOption) match { + 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 residualUnMatched = unMatchedOrders - out - val (updatedMatched, optionalUnMatched) = matchedOrders + (uuid -> order, out -> bidOrder) - optionalUnMatched match { - case Some(additionalUnMatched) => - new FourHeapOrderBook(updatedMatched, residualUnMatched.mergeWith(additionalUnMatched)) - case None => - new FourHeapOrderBook(updatedMatched, residualUnMatched) - } + 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) = matchedOrders.swap(uuid, order, in) - new FourHeapOrderBook(updatedMatched, unMatchedOrders.mergeWith(additionalUnMatched)) + 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(matchedOrders, unMatchedOrders + (uuid, order)) + new FourHeapOrderBook(matched, unMatched + (key, order)) } case (Some((out, bidOrder)), None) => if (order.limit <= bidOrder.limit) { - val (updatedMatched, optionalUnMatched) = matchedOrders + (uuid -> order, out -> bidOrder) + val (updatedMatched, optionalUnMatched) = matched + (key -> order, out -> bidOrder) optionalUnMatched match { case Some(additionalUnMatched) => - new FourHeapOrderBook(updatedMatched, unMatchedOrders.mergeWith(additionalUnMatched)) + new FourHeapOrderBook(updatedMatched, unMatched.mergeWith(additionalUnMatched)) case None => - new FourHeapOrderBook(updatedMatched, unMatchedOrders) + new FourHeapOrderBook(updatedMatched, unMatched) } } else { - new FourHeapOrderBook(matchedOrders, unMatchedOrders + (uuid, order)) + new FourHeapOrderBook(matched, unMatched + (key, order)) } case (None, Some((in ,askOrder))) => if (order.limit <= askOrder.limit) { - val (updatedMatched, additionalUnMatched) = matchedOrders.swap(uuid, order, in) - new FourHeapOrderBook(updatedMatched, unMatchedOrders.mergeWith(additionalUnMatched)) + //val (updatedMatched, additionalUnMatched) = matched.swap(key -> order, in) + //new FourHeapOrderBook(updatedMatched, unMatched.mergeWith(additionalUnMatched)) + ??? } else { - new FourHeapOrderBook(matchedOrders, unMatchedOrders + (uuid, order)) + new FourHeapOrderBook(matched, unMatched + (key, order)) } - case (None, None) => new FourHeapOrderBook(matchedOrders, unMatchedOrders + (uuid, order)) + case (None, None) => new FourHeapOrderBook(matched, unMatched + (key, order)) } } /** Add a new `BidOrder` to the `OrderBook`. * - * @param uuid + * @param key * @param order * @return */ - def + (uuid: UUID, order: BidOrder[T]): FourHeapOrderBook[T] = { - (matchedOrders.bidOrders.headOption, unMatchedOrders.askOrders.headOption) match { + 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 = unMatchedOrders - out - val (updatedMatched, optionalUnMatched) = matchedOrders + (out -> askOrder, uuid -> order) + val residualUnMatched = unMatched - out + val (updatedMatched, optionalUnMatched) = matched + (out -> askOrder, key -> order) optionalUnMatched match { case Some(additionalUnMatched) => new FourHeapOrderBook(updatedMatched, residualUnMatched.mergeWith(additionalUnMatched)) @@ -116,83 +92,88 @@ class FourHeapOrderBook[T <: Tradable] private(matchedOrders: MatchedOrders[T], new FourHeapOrderBook(updatedMatched, residualUnMatched) } } else if (order.limit >= bidOrder.limit) { - val (updatedMatched, additionalUnMatched) = matchedOrders.swap(uuid, order, in) - new FourHeapOrderBook(updatedMatched, unMatchedOrders.mergeWith(additionalUnMatched)) + //val (updatedMatched, additionalUnMatched) = matched.swap(key -> order, in) + //new FourHeapOrderBook(updatedMatched, unMatched.mergeWith(additionalUnMatched)) + ??? } else { - new FourHeapOrderBook(matchedOrders, unMatchedOrders + (uuid, order)) + new FourHeapOrderBook(matched, unMatched + (key, order)) } case (Some((in ,bidOrder)), None) => if (order.limit >= bidOrder.limit) { - val (updatedMatched, additionalUnMatched) = matchedOrders.swap(uuid, order, in) - new FourHeapOrderBook(updatedMatched, unMatchedOrders.mergeWith(additionalUnMatched)) + //val (updatedMatched, additionalUnMatched) = matched.swap(key -> order, in) + //new FourHeapOrderBook(updatedMatched, unMatched.mergeWith(additionalUnMatched)) + ??? } else { - new FourHeapOrderBook(matchedOrders, unMatchedOrders + (uuid, order)) + new FourHeapOrderBook(matched, unMatched + (key, order)) } case (None, Some((out, askOrder))) => if (order.limit >= askOrder.limit) { - val (updatedMatched, optionalUnMatched) = matchedOrders + (out -> askOrder, uuid -> order) + val (updatedMatched, optionalUnMatched) = matched + (out -> askOrder, key -> order) optionalUnMatched match { case Some(additionalUnMatched) => - new FourHeapOrderBook(updatedMatched, unMatchedOrders.mergeWith(additionalUnMatched)) + new FourHeapOrderBook(updatedMatched, unMatched.mergeWith(additionalUnMatched)) case None => - new FourHeapOrderBook(updatedMatched, unMatchedOrders) + new FourHeapOrderBook(updatedMatched, unMatched) } } else { - new FourHeapOrderBook(matchedOrders, unMatchedOrders + (uuid, order)) + new FourHeapOrderBook(matched, unMatched + (key, order)) } - case (None, None) => new FourHeapOrderBook(matchedOrders, unMatchedOrders + (uuid, order)) + case (None, None) => new FourHeapOrderBook(matched, unMatched + (key, order)) } } - def contains(uuid: UUID): Boolean = matchedOrders.contains(uuid) || unMatchedOrders.contains(uuid) + def contains(key: K): Boolean = matched.contains(key) || unMatched.contains(key) - def takeWhileMatched: (Stream[(AskOrder[T], BidOrder[T])], FourHeapOrderBook[T]) = { - (matchedOrders.zipped, withEmptyMatchedOrders) + 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 Because multi-unit orders are divisible, an order with the `uuid` might have been split in which case it - * will exist in both the matched and unmatched order sets. Thus if + * @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 update(uuid: UUID, order: AskOrder[T]): FourHeapOrderBook[T] = { - if (contains(uuid)) { - val residualOrderBook = this - uuid - residualOrderBook + (uuid, order) - } else { - this + (uuid, order) - } + 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 Because multi-unit orders are divisible, an order with the `uuid` might have been split in which case it - * will exist in both the matched and unmatched order sets. Thus if + * @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 update(uuid: UUID, order: BidOrder[T]): FourHeapOrderBook[T] = { - if (contains(uuid)) { - val residualOrderBook = this - uuid - residualOrderBook + (uuid, order) - } else { - this + (uuid, order) - } - } - - private[this] def withEmptyMatchedOrders: FourHeapOrderBook[T] = { - val (askOrdering, bidOrdering) = (matchedOrders.askOrdering, matchedOrders.bidOrdering) - new FourHeapOrderBook[T](MatchedOrders.empty(askOrdering, bidOrdering), unMatchedOrders) - } + 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[T <: Tradable](implicit askOrdering: Ordering[(UUID, AskOrder[T])], bidOrdering: Ordering[(UUID, BidOrder[T])]): FourHeapOrderBook[T] = { - val matchedOrders = MatchedOrders.empty(askOrdering.reverse, bidOrdering.reverse) - val unMatchedOrders = UnMatchedOrders.empty(askOrdering, bidOrdering) + 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) } + } diff --git a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/MatchedOrders.scala b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/MatchedOrders.scala index c7971ec..fbbe30d 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/MatchedOrders.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/MatchedOrders.scala @@ -15,108 +15,78 @@ limitations under the License. */ package org.economicsl.auctions.multiunit.orderbooks -import java.util.UUID - import org.economicsl.auctions.{Quantity, Tradable} -import org.economicsl.auctions.multiunit.BidOrder import org.economicsl.auctions.multiunit.orders.{AskOrder, BidOrder} -private[orderbooks] class MatchedOrders[T <: Tradable] private(val askOrders: SortedAskOrders[T], - val bidOrders: SortedBidOrders[T]) { +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 } }) // value of lowest bid must exceed value of highest ask! + require(bidOrders.headOption.forall{ case (_, bidOrder) => askOrders.headOption.forall{ case (_, askOrder) => bidOrder.limit >= askOrder.limit } }) - val isEmpty: Boolean = askOrders.isEmpty && bidOrders.isEmpty + val askOrdering: Ordering[K] = askOrders.ordering - val nonEmpty: Boolean = askOrders.nonEmpty && bidOrders.nonEmpty + 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: (UUID, AskOrder[T]), kv2: (UUID, BidOrder[T])): (MatchedOrders[T], Option[UnMatchedOrders[T]]) = { - val ((uuid1, askOrder), (uuid2, bidOrder)) = (kv1, kv2) - val excessDemand = bidOrder.quantity - askOrder.quantity + 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) = (askOrder.withQuantity(bidOrder.quantity), askOrder.withQuantity(-excessDemand)) // split the askOrder into a matched and rationed component - val rationedOrders = UnMatchedOrders.empty[T](askOrders.ordering, bidOrders.ordering) - (new MatchedOrders(askOrders + (uuid1 -> matched), bidOrders + kv2), Some(rationedOrders + (uuid1, rationed))) + 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) = (bidOrder.withQuantity(askOrder.quantity), bidOrder.withQuantity(excessDemand)) // split the bidOrder into a matched and residual component - val rationedOrders = UnMatchedOrders.empty[T](askOrders.ordering, bidOrders.ordering) - (new MatchedOrders(askOrders + kv1, bidOrders + (uuid2 -> matched)), Some(rationedOrders + (uuid2, rationed))) + 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 { - (new MatchedOrders(askOrders + kv1, bidOrders + kv2), None) + (MatchedOrders(askOrders + (kv1._1, kv1._2), bidOrders + (kv2._1, kv2._2)), None) } } - def - (uuid: UUID): (MatchedOrders[T], UnMatchedOrders[T]) = { - if (askOrders.contains(uuid)) { - val removedOrder = askOrders(uuid) - val (unMatched, residual) = bidOrders.splitAt(removedOrder.quantity) - // (new MatchedOrders(askOrders - uuid, residual), UnMatchedOrders.withEmptyAskOrders(unMatched)) - ??? - } else { - val removedOrder = bidOrders(uuid) - val (unMatched, residual) = askOrders.splitAt(removedOrder.quantity) - // (new MatchedOrders(residual, bidOrders - uuid), UnMatchedOrders.withEmptyBidOrders(unMatched)) - ??? - } + 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)) } - val askOrdering: Ordering[(UUID, AskOrder[T])] = askOrders.ordering - - val bidOrdering: Ordering[(UUID, BidOrder[T])] = bidOrders.ordering - - def contains(uuid: UUID): Boolean = askOrders.contains(uuid) || bidOrders.contains(uuid) - - def swap(uuid: UUID, order: AskOrder[T], existing: UUID): (MatchedOrders[T], UnMatchedOrders[T]) = { - /*val askOrder = askOrders(existing) - if (order.quantity > askOrder.quantity) { - // if order.quantity > askOrder.quantity, then we need to split the order into matched and rationed components; remove the askOrder and add it to the unmatched orders along with the rationed component or order; finally add the matched component of order to the matched orders. - val (matched, rationed) = (order.withQuantity(askOrder.quantity), order.withQuantity(residual)) - val residualAskOrders = askOrders - existing - val empty = UnMatchedOrders.empty[T](???, ???) - val unMatchedOrders = empty ++ ((existing, askOrder), (uuid, rationed)) - (new MatchedOrders(residualAskOrders + (uuid, matched), bidOrders), unMatchedOrders) - ??? - } else if (order.quantity < askOrder.quantity) { - // if order.quantity < askOrder.quantity, then we need to remove the askOrder and split it into matched and rationed components; add the order and the match component into the matched set and then add the rationed component of askOrder into the unMatched set. - ??? - } else { - // if quantities match then we just need to remove askOrder and add it to unmatched orders; then add order to matched orders - val residualAskOrders = askOrders - existing - (new MatchedOrders(residualAskOrders + (uuid -> order), bidOrders), UnMatchedOrders.withEmptyBidOrders(askOrder)) - }*/ - ??? + 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 swap(uuid: UUID, order: BidOrder[T], existing: UUID): (MatchedOrders[T], UnMatchedOrders[T]) = { - ??? - } + 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[T] = { + /* + 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[T] = { + def updated(askOrder: (UUID, AskOrder[T]), bidOrder: (UUID, BidOrder[T])): MatchedOrders[K, T] = { new MatchedOrders(askOrders + askOrder, bidOrders + bidOrder) } - - def zipped: Stream[(AskOrder[T], BidOrder[T])] = { - ??? - } + */ } -private[orderbooks] object MatchedOrders { +object MatchedOrders { /** Create an instance of `MatchedOrders`. * @@ -125,7 +95,7 @@ private[orderbooks] object MatchedOrders { * 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[T <: Tradable](askOrdering: Ordering[(UUID, AskOrder[T])], bidOrdering: Ordering[(UUID, BidOrder[T])]): MatchedOrders[T] = { + def empty[K, T <: Tradable](askOrdering: Ordering[K], bidOrdering: Ordering[K]): MatchedOrders[K, T] = { new MatchedOrders(SortedAskOrders.empty(askOrdering), SortedBidOrders.empty(bidOrdering)) } diff --git a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedAskOrders.scala b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedAskOrders.scala index cf9aa30..e33fbf1 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedAskOrders.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedAskOrders.scala @@ -15,87 +15,133 @@ limitations under the License. */ package org.economicsl.auctions.multiunit.orderbooks -import java.util.UUID - import org.economicsl.auctions.multiunit.orders.AskOrder import org.economicsl.auctions.{Quantity, Tradable} -import scala.collection.immutable.TreeSet - - -private[orderbooks] class SortedAskOrders[T <: Tradable] private(existing: Map[UUID, AskOrder[T]], - sorted: TreeSet[(UUID, AskOrder[T])], - val numberUnits: Quantity) { - assert(existing.size == sorted.size) - - def apply(uuid: UUID): AskOrder[T] = existing(uuid) - - def + (kv: (UUID, AskOrder[T])): SortedAskOrders[T] = { - new SortedAskOrders(existing + kv, sorted + kv, numberUnits + kv._2.quantity) +import scala.collection.immutable + + +/** + * + * @param existing sorted mapping from keys to collections of orders sharing a particular key. + * @param numberUnits + * @tparam K + * @tparam T + */ +class SortedAskOrders[K, T <: Tradable] private(existing: immutable.TreeMap[K, immutable.Queue[AskOrder[T]]], val numberUnits: Quantity) { + + val ordering: Ordering[K] = existing.ordering + + /** Return a new `SortedAskOrders` instance containing an additional `AskOrder` instance. + * + * @param key + * @param order + * @return + * @note if this `SortedAskOrders` instance contains an existing collection of `AskOrder` instances sharing the same + * key as the new `AskOrder` instance, the new `AskOrder` instance is added to the existing collection. + */ + def + (key: K, order: AskOrder[T]): SortedAskOrders[K, T] = { + val current = existing.getOrElse(key, empty) + new SortedAskOrders(existing + (key -> current.enqueue(order)), numberUnits + order.quantity) } - def - (uuid: UUID): SortedAskOrders[T] = existing.get(uuid) match { - case Some(order) => - val remaining = Quantity(numberUnits.value - order.quantity.value) - new SortedAskOrders(existing - uuid, sorted - ((uuid, order)), remaining) - case None => this + /** Return a new `SortedAskOrders` instance containing an collection of `AskOrder` instances. + * + * @param key + * @param orders + * @return + * @note if this `SortedAskOrders` instance contains an existing collection of `AskOrder` instances sharing the same + * key, then the new collection replaces the existing collection. + */ + def + (key: K, orders: immutable.Queue[AskOrder[T]]): SortedAskOrders[K, T] = { + val currentUnits = aggregate(existing.getOrElse(key, empty)) + val incomingUnits = aggregate(orders) + val change = currentUnits - incomingUnits + new SortedAskOrders(existing + (key -> orders), numberUnits + change) } - def contains(uuid: UUID): Boolean = existing.contains(uuid) + def - (key: K): SortedAskOrders[K, T] = { + existing.get(key) match { + case Some(orders) => + val removedUnits = orders.foldLeft(Quantity(0))((total, order) => total + order.quantity) + new SortedAskOrders(existing - key, numberUnits - removedUnits) + case None => this + } + } - def head: (UUID, AskOrder[T]) = sorted.head + def - (key: K, order: AskOrder[T]): SortedAskOrders[K, T] = { + existing.get(key) match { + case Some(orders) => + val residualOrders = orders.diff(immutable.Queue(order)) // multi-set diff! + if (residualOrders.isEmpty) { + new SortedAskOrders(existing - key, numberUnits - order.quantity) + } else { + new SortedAskOrders(existing + (key -> residualOrders), numberUnits - order.quantity) + } + case None => this + } + } - val headOption: Option[(UUID, AskOrder[T])] = sorted.headOption + def contains(key: K): Boolean = existing.contains(key) - val isEmpty: Boolean = existing.isEmpty && sorted.isEmpty + def foldLeft[B](z: B)(op: (B, (K, immutable.Queue[AskOrder[T]])) => B): B = { + existing.foldLeft(z)(op) + } - val nonEmpty: Boolean = existing.nonEmpty && sorted.nonEmpty + def getOrElse(key: K, default: => immutable.Queue[AskOrder[T]]): immutable.Queue[AskOrder[T]] = { + existing.getOrElse(key, default) + } - val ordering: Ordering[(UUID, AskOrder[T])] = sorted.ordering + def headOption: Option[(K, AskOrder[T])] = { + existing.headOption.flatMap{ case (key, orders) => orders.headOption.map(order => (key, order)) } + } - val size: Int = existing.size + def isEmpty: Boolean = existing.isEmpty - def mergeWith(other: SortedAskOrders[T]): SortedAskOrders[T] = { - ??? + def mergeWith(other: SortedAskOrders[K, T]): SortedAskOrders[K, T] = { + other.foldLeft(this) { case (ob, (key, orders)) => + val currentOrders = ob.getOrElse(key, empty) + ob + (key, currentOrders.enqueue(orders)) + } } - def splitAt(quantity: Quantity): (SortedAskOrders[T], SortedAskOrders[T]) = { + def nonEmpty: Boolean = existing.nonEmpty - def split(order: AskOrder[T], quantity: Quantity): (AskOrder[T], AskOrder[T]) = { - val residual = order.quantity - quantity - (order.withQuantity(quantity), order.withQuantity(residual)) - } + def splitAt(quantity: Quantity): (SortedAskOrders[K, T], SortedAskOrders[K, T]) = { @annotation.tailrec - def loop(in: SortedAskOrders[T], out: SortedAskOrders[T]): (SortedAskOrders[T], SortedAskOrders[T]) = { - val unMatched = quantity - in.numberUnits - val (uuid, askOrder) = out.head - if (unMatched > askOrder.quantity) { - loop(in + (uuid -> askOrder), out - uuid) - } else if (unMatched < askOrder.quantity) { - val (matched, residual) = split(askOrder, unMatched) - (in + (uuid -> matched), out.update(uuid, residual)) - } else { - (in + (uuid -> askOrder), out - uuid) + def loop(prefix: SortedAskOrders[K, T], suffix: SortedAskOrders[K, T]): (SortedAskOrders[K, T], SortedAskOrders[K, T]) = { + suffix.headOption match { + case Some((key, askOrder)) => + val remainingQuantity = quantity - prefix.numberUnits + if (remainingQuantity > askOrder.quantity) { + loop(prefix + (key, askOrder), suffix - (key, askOrder)) + } else if (remainingQuantity < askOrder.quantity) { + (prefix + (key, askOrder.withQuantity(remainingQuantity)), suffix - (key, askOrder) + (key, askOrder.withQuantity(askOrder.quantity - remainingQuantity))) + } else { + (prefix + (key, askOrder), suffix - (key, askOrder)) + } + case None => + (prefix, SortedAskOrders.empty(ordering)) } } - loop(SortedAskOrders.empty[T](sorted.ordering), this) + loop(SortedAskOrders.empty[K, T](ordering), this) } - def update(uuid: UUID, order: AskOrder[T]): SortedAskOrders[T] = { - val askOrder = this(uuid) - val change = order.quantity - askOrder.quantity - new SortedAskOrders(existing.updated(uuid, order), sorted - ((uuid, askOrder)) + ((uuid, order)), numberUnits + change) + private[this] def aggregate(orders: immutable.Queue[AskOrder[T]]): Quantity = { + orders.aggregate(Quantity(0))((total, order) => total + order.quantity, _ + _ ) } + private[this] def empty: immutable.Queue[AskOrder[T]] = immutable.Queue.empty[AskOrder[T]] + } object SortedAskOrders { - def empty[T <: Tradable](implicit ordering: Ordering[(UUID, AskOrder[T])]): SortedAskOrders[T] = { - new SortedAskOrders(Map.empty[UUID, AskOrder[T]], TreeSet.empty[(UUID, AskOrder[T])](ordering), Quantity(0)) + def empty[K, T <: Tradable](implicit ordering: Ordering[K]): SortedAskOrders[K, T] = { + new SortedAskOrders(immutable.TreeMap.empty[K, immutable.Queue[AskOrder[T]]](ordering), Quantity(0)) } } diff --git a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrders.scala b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrders.scala index 26930d0..85044d5 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrders.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrders.scala @@ -15,79 +15,112 @@ limitations under the License. */ package org.economicsl.auctions.multiunit.orderbooks -import java.util.UUID - import org.economicsl.auctions.multiunit.orders.BidOrder import org.economicsl.auctions.{Quantity, Tradable} -import scala.collection.immutable.TreeSet - +import scala.collection.immutable -private[orderbooks] class SortedBidOrders[T <: Tradable] private(existing: Map[UUID, BidOrder[T]], - val sorted: TreeSet[(UUID, BidOrder[T])], - val numberUnits: Quantity) { - assert(existing.size == sorted.size) +class SortedBidOrders[K, T <: Tradable] private(existing: immutable.TreeMap[K, immutable.Queue[BidOrder[T]]], val numberUnits: Quantity) { - def apply(uuid: UUID): BidOrder[T] = existing(uuid) + val ordering: Ordering[K] = existing.ordering - def + (kv: (UUID, BidOrder[T])): SortedBidOrders[T] = { - new SortedBidOrders(existing + kv, sorted + kv, numberUnits + kv._2.quantity) + /** Return a new `SortedBidOrders` instance containing an additional `BidOrder` instance. + * + * @param key + * @param order + * @return + * @note if this `SortedBidOrders` instance contains an existing collection of `BidOrder` instances sharing the same + * key as the new `BidOrder` instance, the new `BidOrder` instance is added to the existing collection. + */ + def + (key: K, order: BidOrder[T]): SortedBidOrders[K, T] = { + val current = existing.getOrElse(key, immutable.Queue.empty[BidOrder[T]]) + new SortedBidOrders(existing + (key -> current.enqueue(order)), numberUnits + order.quantity) } - def - (uuid: UUID): SortedBidOrders[T] = existing.get(uuid) match { - case Some(order) => - val remaining = Quantity(numberUnits.value - order.quantity.value) - new SortedBidOrders(existing - uuid, sorted - ((uuid, order)), remaining) - case None => this + /** Return a new `SortedBidOrders` instance containing an collection of `BidOrder` instances. + * + * @param key + * @param orders + * @return + * @note if this `SortedBidOrders` instance contains an existing collection of `BidOrder` instances sharing the same + * key, then the new collection replaces the existing collection. + */ + def + (key: K, orders: immutable.Queue[BidOrder[T]]): SortedBidOrders[K, T] = { + val currentOrders = existing.getOrElse(key, immutable.Queue.empty[BidOrder[T]]) + val currentUnits = currentOrders.foldLeft(Quantity(0)){ case (total, order) => total + order.quantity } + val incomingUnits = orders.foldLeft(Quantity(0)){ case (total, order) => total + order.quantity } + val netChange: Quantity = currentUnits - incomingUnits + new SortedBidOrders(existing + (key -> orders), numberUnits + netChange) } - def contains(uuid: UUID): Boolean = existing.contains(uuid) + def - (key: K): SortedBidOrders[K, T] = { + existing.get(key) match { + case Some(orders) => + val removedUnits = orders.foldLeft(Quantity(0))((total, order) => total + order.quantity) + new SortedBidOrders(existing - key, numberUnits - removedUnits) + case None => this + } + } - def head: (UUID, BidOrder[T]) = sorted.head + def - (key: K, order: BidOrder[T]): SortedBidOrders[K, T] = { + existing.get(key) match { + case Some(orders) => + val residualOrders = orders.diff(immutable.Queue(order)) // multi-set diff available on SeqLike collections! + if (residualOrders.isEmpty) { + new SortedBidOrders(existing - key, numberUnits - order.quantity) + } else { + new SortedBidOrders(existing + (key -> residualOrders), numberUnits - order.quantity) + } + case None => this + } + } - val headOption: Option[(UUID, BidOrder[T])] = sorted.headOption + def contains(key: K): Boolean = existing.contains(key) - val isEmpty: Boolean = existing.isEmpty && sorted.isEmpty + def foldLeft[B](z: B)(op: (B, (K, immutable.Queue[BidOrder[T]])) => B): B = { + existing.foldLeft(z)(op) + } - val nonEmpty: Boolean = existing.nonEmpty && sorted.nonEmpty + def getOrElse(key: K, default: => immutable.Queue[BidOrder[T]]): immutable.Queue[BidOrder[T]] = { + existing.getOrElse(key, default) + } - val ordering: Ordering[(UUID, BidOrder[T])] = sorted.ordering + def headOption: Option[(K, BidOrder[T])] = { + existing.headOption.flatMap{ case (key, orders) => orders.headOption.map(order => (key, order)) } + } - val size: Int = existing.size + def isEmpty: Boolean = existing.isEmpty - def mergeWith(other: SortedBidOrders[T]): SortedBidOrders[T] = { - ??? + def mergeWith(other: SortedBidOrders[K, T]): SortedBidOrders[K, T] = { + other.foldLeft(this) { case (ob, (key, orders)) => + val currentOrders = ob.getOrElse(key, immutable.Queue.empty[BidOrder[T]]) + ob + (key, currentOrders.enqueue(orders)) + } } - def splitAt(quantity: Quantity): (SortedBidOrders[T], SortedBidOrders[T]) = { + def nonEmpty: Boolean = existing.nonEmpty - def split(order: BidOrder[T], quantity: Quantity): (BidOrder[T], BidOrder[T]) = { - val residual = order.quantity - quantity - (order.withQuantity(quantity), order.withQuantity(residual)) - } + def splitAt(quantity: Quantity): (SortedBidOrders[K, T], SortedBidOrders[K, T]) = { @annotation.tailrec - def loop(in: SortedBidOrders[T], out: SortedBidOrders[T]): (SortedBidOrders[T], SortedBidOrders[T]) = { - val unMatched = quantity - in.numberUnits - val (uuid, bidOrder) = out.head - if (unMatched > bidOrder.quantity) { - loop(in + (uuid -> bidOrder), out - uuid) - } else if (unMatched < bidOrder.quantity) { - val (matched, residual) = split(bidOrder, unMatched) - (in + (uuid -> matched), out.updated(uuid, residual)) - } else { - (in + (uuid -> bidOrder), out - uuid) + def loop(prefix: SortedBidOrders[K, T], suffix: SortedBidOrders[K, T]): (SortedBidOrders[K, T], SortedBidOrders[K, T]) = { + suffix.headOption match { + case Some((key, bidOrder)) => + val remainingQuantity = quantity - prefix.numberUnits + if (remainingQuantity > bidOrder.quantity) { + loop(prefix + (key, bidOrder), suffix - (key, bidOrder)) + } else if (remainingQuantity < bidOrder.quantity) { + (prefix + (key, bidOrder.withQuantity(remainingQuantity)), suffix - (key, bidOrder) + (key, bidOrder.withQuantity(bidOrder.quantity - remainingQuantity))) + } else { + (prefix + (key, bidOrder), suffix - (key, bidOrder)) + } + case None => + (prefix, SortedBidOrders.empty[K, T](ordering)) } } - loop(SortedBidOrders.empty[T](sorted.ordering), this) - - } + loop(SortedBidOrders.empty[K, T](ordering), this) - def updated(uuid: UUID, order: BidOrder[T]): SortedBidOrders[T] = { - val bidOrder = this(uuid) - val change = order.quantity - bidOrder.quantity - new SortedBidOrders(existing.updated(uuid, order), sorted - ((uuid, bidOrder)) + ((uuid, order)), numberUnits + change) } } @@ -95,8 +128,8 @@ private[orderbooks] class SortedBidOrders[T <: Tradable] private(existing: Map[U object SortedBidOrders { - def empty[T <: Tradable](implicit ordering: Ordering[(UUID, BidOrder[T])]): SortedBidOrders[T] = { - new SortedBidOrders(Map.empty[UUID, BidOrder[T]], TreeSet.empty[(UUID, BidOrder[T])](ordering), Quantity(0)) + def empty[K, T <: Tradable](implicit ordering: Ordering[K]): SortedBidOrders[K, T] = { + new SortedBidOrders(immutable.TreeMap.empty[K, immutable.Queue[BidOrder[T]]](ordering), Quantity(0)) } } diff --git a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/UnMatchedOrders.scala b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/UnMatchedOrders.scala index f4b1cc6..8d74b68 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/UnMatchedOrders.scala +++ b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/UnMatchedOrders.scala @@ -15,67 +15,58 @@ limitations under the License. */ package org.economicsl.auctions.multiunit.orderbooks -import java.util.UUID - import org.economicsl.auctions.Tradable -import org.economicsl.auctions.multiunit.BidOrder import org.economicsl.auctions.multiunit.orders.{AskOrder, BidOrder} -private[orderbooks] case class UnMatchedOrders[T <: Tradable](askOrders: SortedAskOrders[T], - bidOrders: SortedBidOrders[T]) { +protected[orderbooks] final case class UnMatchedOrders[K, T <: Tradable](askOrders: SortedAskOrders[K, T], + bidOrders: SortedBidOrders[K, T]) { /* Limit price of "best" `BidOrder` instance must not exceed the limit price of the "best" `AskOrder` instance. */ require(bidOrders.headOption.forall{ case (_, bidOrder) => askOrders.headOption.forall{ case (_, askOrder) => bidOrder.limit <= askOrder.limit } }) - val isEmpty: Boolean = askOrders.isEmpty && bidOrders.isEmpty - - val nonEmpty: Boolean = askOrders.nonEmpty || bidOrders.nonEmpty - - /** Add a new `AskOrder` to the collection of unmatched orders .*/ - def + (uuid: UUID, order: AskOrder[T]): UnMatchedOrders[T] = { - UnMatchedOrders(askOrders + (uuid -> order), bidOrders) + def + (key: K, order: AskOrder[T]): UnMatchedOrders[K, T] = { + new UnMatchedOrders(askOrders + (key, order), bidOrders) } - /** Add a new `BidOrder` to the collection of unmatched orders .*/ - def + (uuid: UUID, order: BidOrder[T]): UnMatchedOrders[T] = { - UnMatchedOrders(askOrders, bidOrders + (uuid -> order)) + def + (key: K, order: BidOrder[T]): UnMatchedOrders[K, T] = { + new UnMatchedOrders(askOrders, bidOrders + (key, order)) } - /** Remove an order from the collection of unmatched orders. */ - def - (uuid: UUID): UnMatchedOrders[T] = { - if (askOrders.contains(uuid)) { - UnMatchedOrders(askOrders - uuid, bidOrders) + /** Remove a collection of order from the collection of unmatched orders. */ + def - (key: K): UnMatchedOrders[K, T] = { + if (askOrders.contains(key)) { + new UnMatchedOrders(askOrders - key, bidOrders) } else { - UnMatchedOrders(askOrders, bidOrders - uuid) + new UnMatchedOrders(askOrders, bidOrders - key) } } - val askOrdering: Ordering[(UUID, AskOrder[T])] = askOrders.ordering + def - (key: K, order: AskOrder[T]): UnMatchedOrders[K, T] = { + val remainingAskOrders = askOrders - (key, order) + UnMatchedOrders(remainingAskOrders, bidOrders) + } - val bidOrdering: Ordering[(UUID, BidOrder[T])] = bidOrders.ordering + def - (key: K, order: BidOrder[T]): UnMatchedOrders[K, T] = { + val remainingBidOrders = bidOrders - (key, order) + UnMatchedOrders(askOrders, remainingBidOrders) + } /** Check whether an order is contained in the collection of unmatched orders using. */ - def contains(uuid: UUID): Boolean = askOrders.contains(uuid) || bidOrders.contains(uuid) + def contains(key: K): Boolean = askOrders.contains(key) || bidOrders.contains(key) - def mergeWith(other: UnMatchedOrders[T]): UnMatchedOrders[T] = { - UnMatchedOrders(askOrders.mergeWith(other.askOrders), bidOrders.mergeWith(bidOrders)) - } + def isEmpty: Boolean = askOrders.isEmpty && bidOrders.isEmpty - /** Add a `AskOrder` to the collection of unmatched orders. */ - def updated(uuid: UUID, order: AskOrder[T]): UnMatchedOrders[T] = { - UnMatchedOrders(askOrders.update(uuid, order), bidOrders) - } + def nonEmpty: Boolean = askOrders.nonEmpty || bidOrders.nonEmpty - /** Add a `BidOrder` to the collection of unmatched orders. */ - def updated(uuid: UUID, order: BidOrder[T]): UnMatchedOrders[T] = { - UnMatchedOrders(askOrders, bidOrders.updated(uuid, order)) + def mergeWith(other: UnMatchedOrders[K, T]): UnMatchedOrders[K, T] = { + new UnMatchedOrders(askOrders.mergeWith(other.askOrders), bidOrders.mergeWith(bidOrders)) } } -private[orderbooks] object UnMatchedOrders { +object UnMatchedOrders { /** Create an instance of `UnMatchedOrders`. * @@ -86,7 +77,7 @@ private[orderbooks] object UnMatchedOrders { * based on `limit` price; the heap used to store store the `BidOrder` instances is * ordered from high to low based on `limit` price. */ - def empty[T <: Tradable](askOrdering: Ordering[(UUID, AskOrder[T])], bidOrdering: Ordering[(UUID, BidOrder[T])]): UnMatchedOrders[T] = { + def empty[K, T <: Tradable](implicit askOrdering: Ordering[K], bidOrdering: Ordering[K]): UnMatchedOrders[K, T] = { new UnMatchedOrders(SortedAskOrders.empty(askOrdering), SortedBidOrders.empty(bidOrdering)) } diff --git a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/sandbox.sc b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/sandbox.sc index e69de29..41787fa 100644 --- a/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/sandbox.sc +++ b/src/main/scala/org/economicsl/auctions/multiunit/orderbooks/sandbox.sc @@ -0,0 +1,13 @@ +import org.economicsl.auctions.{Currency, Price, Tradable} +import org.economicsl.auctions.multiunit.orderbooks.OrderBook +import org.economicsl.auctions.multiunit.orders.{AskOrder, BidOrder} + +import scala.collection.immutable + +case class GoogleStock(tick: Currency = 1) extends Tradable + +val ob = OrderBook.empty[Price, AskOrder[GoogleStock], immutable.Queue[AskOrder[GoogleStock]]] + +val ob2 = OrderBook.empty[Price, AskOrder[GoogleStock], immutable.Set[AskOrder[GoogleStock]]] + +val ob3 = OrderBook.empty[Price, BidOrder[GoogleStock], immutable.List[BidOrder[GoogleStock]]] diff --git a/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/SortedAskOrdersSpec.scala b/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/SortedAskOrdersSpec.scala index 558b91d..39865e5 100644 --- a/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/SortedAskOrdersSpec.scala +++ b/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/SortedAskOrdersSpec.scala @@ -2,65 +2,46 @@ package org.economicsl.auctions.multiunit.orderbooks import java.util.UUID -import org.economicsl.auctions.multiunit.orders import org.economicsl.auctions.multiunit.orders.LimitAskOrder -import org.economicsl.auctions.{GoogleStock, Price, Quantity, multiunit} +import org.economicsl.auctions.{GoogleStock, Price, Quantity} import org.scalatest.{FlatSpec, Matchers} class SortedAskOrdersSpec extends FlatSpec with Matchers { - "A SortedAskOrderBook" should "update an existing order" in { + "A SortedAskOrderBook" should "be able to add a new ask order" in { // Create a multi-unit limit ask order val issuer = UUID.randomUUID() - val google = new GoogleStock + val google = GoogleStock(tick = 1) val order = LimitAskOrder(issuer, Price(10), Quantity(100), google) // Create an empty order book and add the order - val empty = SortedAskOrders.empty[GoogleStock] - val nonEmpty = empty + (issuer -> order) - nonEmpty.head should be ((issuer, order)) + val empty = SortedAskOrders.empty[(Price, UUID), GoogleStock] + empty.numberUnits should be(Quantity(0)) + empty.contains((order.limit, order.issuer)) should be(false) - // Create a revised order and update the order book - val revised = order.withQuantity(Quantity(1)) - val updated = nonEmpty.update(issuer, revised) - - // Check that update is successful! - updated.head should be ((issuer, revised)) - updated.size should be (1) - updated.numberUnits should be (revised.quantity) + val nonEmpty = empty + ((order.limit, order.issuer), order) + nonEmpty.headOption should be (Some(((order.limit, order.issuer), order))) + nonEmpty.numberUnits should be(Quantity(100)) + nonEmpty.contains((order.limit, order.issuer)) should be(true) } - "A SortedAskOrderBook" should "split itself into two pieces" in { - - val google = new GoogleStock - - // Create some multi-unit limit ask orders - val issuer1 = UUID.randomUUID() - val order1 = orders.LimitAskOrder(issuer1, Price(10), Quantity(10), google) - - val issuer2 = UUID.randomUUID() - val order2 = orders.LimitAskOrder(issuer2, Price(5), Quantity(15), google) - - val issuer3 = UUID.randomUUID() - val order3 = orders.LimitAskOrder(issuer3, Price(15), Quantity(100), google) - - // Create an empty order book and add the orders - val empty = SortedAskOrders.empty[GoogleStock] - val nonEmpty = empty + (issuer1 -> order1) + (issuer2 -> order2) + (issuer3 -> order3) + "A SortedAskOrderBook" should "be able to remove an existing ask order" in { - // Create a revised order and update the order book - val (matched, residual) = nonEmpty.splitAt(Quantity(57)) - - // Check that splitAt was successful - matched.numberUnits should be (Quantity(57)) - matched.size should be(3) + // Create a multi-unit limit ask order + val issuer = UUID.randomUUID() + val google = GoogleStock(tick = 1) + val order = LimitAskOrder(issuer, Price(10), Quantity(100), google) - residual.numberUnits should be (Quantity(125 - 57)) - residual.size should be(1) + // Create an empty order book, add the order, and then remove it + val empty = SortedAskOrders.empty[(Price, UUID), GoogleStock] + val nonEmpty = empty + ((order.limit, order.issuer), order) + val withoutOrders = nonEmpty - ((order.limit, order.issuer), order) + withoutOrders.isEmpty should be(true) } + } diff --git a/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrdersSpec.scala b/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrdersSpec.scala index 5725319..7903f46 100644 --- a/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrdersSpec.scala +++ b/src/test/scala/org/economicsl/auctions/multiunit/orderbooks/SortedBidOrdersSpec.scala @@ -1,8 +1,47 @@ package org.economicsl.auctions.multiunit.orderbooks -/** - * Created by pughdr on 6/6/2017. - */ -class SortedBidOrdersSpec { +import java.util.UUID + +import org.economicsl.auctions.multiunit.orders.LimitBidOrder +import org.economicsl.auctions.{GoogleStock, Price, Quantity} +import org.scalatest.{FlatSpec, Matchers} + + +class SortedBidOrdersSpec extends FlatSpec with Matchers { + + "A SortedBidOrderBook" should "be able to add a new ask order" in { + + // Create a multi-unit limit ask order + val issuer = UUID.randomUUID() + val google = GoogleStock(tick = 1) + val order = LimitBidOrder(issuer, Price(10), Quantity(100), google) + + // Create an empty order book and add the order + val empty = SortedBidOrders.empty[(Price, UUID), GoogleStock] + empty.numberUnits should be(Quantity(0)) + empty.contains((order.limit, order.issuer)) should be(false) + + val nonEmpty = empty + ((order.limit, order.issuer), order) + nonEmpty.headOption should be (Some(((order.limit, order.issuer), order))) + nonEmpty.numberUnits should be(Quantity(100)) + nonEmpty.contains((order.limit, order.issuer)) should be(true) + + } + + "A SortedBidOrderBook" should "be able to remove an existing ask order" in { + + // Create a multi-unit limit ask order + val issuer = UUID.randomUUID() + val google = GoogleStock(tick = 1) + val order = LimitBidOrder(issuer, Price(10), Quantity(100), google) + + // Create an empty order book, add the order, and then remove it + val empty = SortedBidOrders.empty[(Price, UUID), GoogleStock] + val nonEmpty = empty + ((order.limit, order.issuer), order) + val withoutOrders = nonEmpty - ((order.limit, order.issuer), order) + withoutOrders.isEmpty should be(true) + + } + }