Java tutorial
/* * JASA Java Auction Simulator API * Copyright (C) 2013 Steve Phelps * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. */ package net.sourceforge.jasa.market; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.PriorityQueue; import java.util.TreeSet; import org.apache.commons.collections.iterators.CollatingIterator; import org.apache.log4j.Logger; /** * <p> * This class provides market order-matching services using the 4-Heap * algorithm. See: * </p> * * <p> * "Flexible Double Auctions for Electronic Commerce: Theory and Implementation" * by Wurman, Walsh and Wellman 1998. * </p> * * @author Steve Phelps * @version $Revision$ */ public class FourHeapOrderBook implements OrderBook, Serializable { /** * Matched bids in ascending order */ protected PriorityQueue<Order> bIn = new PriorityQueue<Order>(new TreeSet<Order>(greaterThan)); /** * Unmatched bids in descending order */ protected PriorityQueue<Order> bOut = new PriorityQueue<Order>(new TreeSet<Order>(lessThan)); /** * Matched asks in descending order */ protected PriorityQueue<Order> sIn = new PriorityQueue<Order>(new TreeSet<Order>(lessThan)); /** * Unmatched asks in ascending order */ protected PriorityQueue<Order> sOut = new PriorityQueue<Order>(new TreeSet<Order>(greaterThan)); protected static AscendingOrderComparator greaterThan = new AscendingOrderComparator(); protected static DescendingOrderComparator lessThan = new DescendingOrderComparator(); static Logger logger = Logger.getLogger(FourHeapOrderBook.class); public FourHeapOrderBook() { initialise(); } public void removeAll(Order shout) { for (Order i = shout; i != null; i = i.getChild()) { remove(i); } } /** * Removed an order from one of the unmatched collections (sOut or bOut) * without rebalancing the book. * * @param shout The order to be removed. */ public void removeUnmatchedOrder(Order shout) { if (shout.isAsk()) { sOut.remove(shout); } else { bOut.remove(shout); } } /** * Remove an order from the book, rebalancing if necessary. * * @param shout The order to be removed. */ public void remove(Order shout) { if (shout.isAsk()) { removeAsk(shout); } else { removeBid(shout); } checkIntegrity(); } protected void removeAsk(Order shout) { if (sIn.remove(shout)) { reinsert(bIn, shout.getQuantity()); } else { sOut.remove(shout); } } protected void removeBid(Order shout) { if (bIn.remove(shout)) { reinsert(sIn, shout.getQuantity()); } else { bOut.remove(shout); } } public String toString() { return "sIn = " + sIn + "\nbIn = " + bIn + "\nsOut = " + sOut + "\nbOut = " + bOut; } /** * Log the current state of the market. */ public void printState() { logger.info("Auction state:\n"); prettyPrint("Matched bids", bIn); prettyPrint("Matched asks", sIn); prettyPrint("Runner-up bids", bOut); prettyPrint("Runner-up asks", sOut); } @SuppressWarnings("all") public void prettyPrint(String title, PriorityQueue shouts) { logger.info(title); logger.info("--------------"); Iterator i = shouts.iterator(); while (i.hasNext()) { Order shout = (Order) i.next(); logger.info(shout.toPrettyString()); } logger.info(""); } /** * Insert a shout into a binary heap. * * @param heap * The heap to insert into * @param shout * The shout to insert * */ private static void insertShout(PriorityQueue<Order> heap, Order shout) throws DuplicateShoutException { try { heap.add(shout); } catch (IllegalArgumentException e) { logger.error(e); e.printStackTrace(); throw new DuplicateShoutException("Duplicate shout: " + shout.toString()); } } /** * Insert an unmatched ask into the appropriate heap. */ public void insertUnmatchedAsk(Order ask) throws DuplicateShoutException { assert ask.isAsk(); insertShout(sOut, ask); } /** * Insert an unmatched bid into the appropriate heap. */ public void insertUnmatchedBid(Order bid) throws DuplicateShoutException { assert bid.isBid(); insertShout(bOut, bid); } /** * Get the highest unmatched bid. */ public Order getHighestUnmatchedBid() { if (bOut.isEmpty()) { return null; } return (Order) bOut.peek(); } /** * Get the lowest matched bid */ public Order getLowestMatchedBid() { if (bIn.isEmpty()) { return null; } return (Order) bIn.peek(); } /** * Get the lowest unmatched ask. */ public Order getLowestUnmatchedAsk() { if (sOut.isEmpty()) { return null; } return (Order) sOut.peek(); } /** * Get the highest matched ask. */ public Order getHighestMatchedAsk() { if (sIn.isEmpty()) { return null; } return (Order) sIn.peek(); } /** * Unify the shout at the top of the heap with the supplied shout, so that * quantity(shout) = quantity(top(heap)). This is achieved by splitting the * supplied shout or the shout at the top of the heap. * * @param shout * The shout. * @param to * The heap being moved to. * * @return A reference to the, possibly modified, shout. * */ protected Order unifyShout(Order shout, PriorityQueue<Order> from, PriorityQueue<Order> to) { Order top = (Order) from.peek(); if (shout.getQuantity() > top.getQuantity()) { shout = shout.splat(shout.getQuantity() - top.getQuantity()); } else { if (top.getQuantity() > shout.getQuantity()) { Order remainder = top.split(top.getQuantity() - shout.getQuantity()); from.add(remainder); assert ((remainder.isBid && from == bOut) || ((!remainder.isBid) && from == sOut)); to.add(from.remove()); // to.add(remainder); return shout; } } to.add(from.remove()); return shout; } protected int displaceShout(Order shout, PriorityQueue<Order> from, PriorityQueue<Order> to) throws DuplicateShoutException { shout = unifyShout(shout, from, to); // to.add(from.remove()); insertShout(from, shout); return shout.getQuantity(); } public int promoteShout(Order shout, PriorityQueue<Order> from, PriorityQueue<Order> to, PriorityQueue<Order> matched) throws DuplicateShoutException { shout = unifyShout(shout, from, to); // to.add(from.remove()); insertShout(matched, shout); checkIntegrity(); return shout.getQuantity(); } public int displaceHighestMatchedAsk(Order ask) throws DuplicateShoutException { assert ask.isAsk(); return displaceShout(ask, sIn, sOut); } public int displaceLowestMatchedBid(Order bid) throws DuplicateShoutException { assert bid.isBid(); return displaceShout(bid, bIn, bOut); } public int promoteHighestUnmatchedBid(Order ask) throws DuplicateShoutException { assert ask.isAsk(); return promoteShout(ask, bOut, bIn, sIn); } public int promoteLowestUnmatchedAsk(Order bid) throws DuplicateShoutException { assert bid.isBid(); return promoteShout(bid, sOut, sIn, bIn); } public void add(Order shout) throws DuplicateShoutException { if (shout.isBid()) { addBid(shout); } else { addAsk(shout); } checkIntegrity(); } protected void addBid(Order bid) throws DuplicateShoutException { int uninsertedUnits = bid.getQuantity(); while (uninsertedUnits > 0) { Order sOutTop = getLowestUnmatchedAsk(); Order bInTop = getLowestMatchedBid(); if (sOutTop != null && sOutTop.matches(bid) && (bInTop == null || bInTop.getPriceAsDouble() >= sOutTop.getPriceAsDouble())) { // found match uninsertedUnits -= promoteLowestUnmatchedAsk(bid); } else if (bInTop != null && bid.getPriceAsDouble() > bInTop.getPriceAsDouble()) { uninsertedUnits -= displaceLowestMatchedBid(bid); } else { insertUnmatchedBid(bid); uninsertedUnits -= bid.getQuantity(); } } } protected void addAsk(Order ask) throws DuplicateShoutException { int uninsertedUnits = ask.getQuantity(); while (uninsertedUnits > 0) { Order sInTop = getHighestMatchedAsk(); Order bOutTop = getHighestUnmatchedBid(); if (bOutTop != null && bOutTop.matches(ask) && (sInTop == null || sInTop.matches(bOutTop))) { uninsertedUnits -= promoteHighestUnmatchedBid(ask); } else if (sInTop != null && ask.getPriceAsDouble() < sInTop.getPriceAsDouble()) { uninsertedUnits -= displaceHighestMatchedAsk(ask); } else { insertUnmatchedAsk(ask); uninsertedUnits -= ask.getQuantity(); } } } @SuppressWarnings("unchecked") public Iterator<Order> askIterator() { return new CollatingIterator(greaterThan, sIn.iterator(), sOut.iterator()); } @SuppressWarnings("unchecked") public Iterator<Order> bidIterator() { return new CollatingIterator(lessThan, bIn.iterator(), bOut.iterator()); } /** * <p> * Return a list of matched bids and asks. The list is of the form * </p> * <br> ( b0, a0, b1, a1 .. bn, an )<br> * * <p> * where bi is the ith bid and a0 is the ith ask. A typical auctioneer would * clear by matching bi with ai for all i at some price. * </p> */ public List<Order> matchOrders() { try { ArrayList<Order> result = new ArrayList<Order>(sIn.size() + bIn.size()); while (!sIn.isEmpty()) { Order sInTop = (Order) sIn.remove(); Order bInTop = (Order) bIn.remove(); int nS = sInTop.getQuantity(); int nB = bInTop.getQuantity(); // assert nS == nB; if (nS < nB) { // split the bid Order remainder = bInTop.split(nB - nS); addBid(remainder); } else if (nB < nS) { // split the ask Order remainder = sInTop.split(nS - nB); addAsk(remainder); } // assert bInTop.getAgent() != sInTop.getAgent(); result.add(bInTop); result.add(sInTop); bInTop.setFilled(true); sInTop.setFilled(true); } assert bIn.isEmpty(); checkIntegrity(); return result; } catch (DuplicateShoutException e) { throw new RuntimeException(e); } } protected void initialise() { bIn.clear(); bOut.clear(); sIn.clear(); sOut.clear(); } public synchronized void reset() { initialise(); } /** * Remove, possibly several, shouts from heap such that quantity(heap) is * reduced by the supplied quantity and reinsert the shouts using the * standard insertion logic. quantity(heap) is defined as the total quantity * of every shout in the heap. * * @param heap * The heap to remove shouts from. * @param quantity * The total quantity to remove. */ protected void reinsert(PriorityQueue<Order> heap, int quantity) { while (quantity > 0) { Order top = (Order) heap.remove(); if (top.getQuantity() > quantity) { heap.add(top.split(top.getQuantity() - quantity)); } quantity -= top.getQuantity(); try { if (top.isBid()) { addBid(top); } else { addAsk(top); } } catch (DuplicateShoutException e) { throw new AuctionRuntimeException("Invalid market state"); } } } /** * Compute the total number of orders in the book. */ public int size() { return bIn.size() + bOut.size() + sIn.size() + sOut.size(); } @Override public boolean isEmpty() { return bIn.isEmpty() && sIn.isEmpty() && bOut.isEmpty() && bIn.isEmpty(); } @Override public int getDepth() { return Math.max(bOut.size(), sOut.size()); } @Override public List<Order> getUnmatchedBids() { ArrayList<Order> bids = new ArrayList<Order>(bOut); Collections.sort(bids, lessThan); return bids; } @Override public List<Order> getUnmatchedAsks() { ArrayList<Order> asks = new ArrayList<Order>(sOut); Collections.sort(asks, greaterThan); return asks; } public void checkIntegrity() { // These conditions are violated when we have unmatched orders from // the same trader on both sides of the book. However this does not // appear to violate the integrity of the auction state. See // FourHeapTest.testSameSide(). // TODO: Prove this and then update integrity checks to take into // account unmatched orders from the same trader. Order bInTop = getLowestMatchedBid(); Order sInTop = getHighestMatchedAsk(); Order bOutTop = getHighestUnmatchedBid(); Order sOutTop = getLowestUnmatchedAsk(); assert (bInTop == null || bInTop.isBid); assert (bOutTop == null || bOutTop.isBid); assert (sInTop == null || !sInTop.isBid); assert (sOutTop == null || !sOutTop.isBid); // // checkBalanced(bInTop, bOutTop, "bIn >= bOut"); // checkBalanced(sOutTop, sInTop, "sOut >= sIn"); // checkBalanced(sOutTop, bOutTop, "sOut >= bOut"); // checkBalanced(bInTop, sInTop, "bIn >= sIn"); } protected void checkBalanced(Order s1, Order s2, String condition) { if (!((s1 == null || s2 == null) || s1.getPriceAsDouble() >= (s2.getPriceAsDouble()))) { throw new RuntimeException("Heaps not balanced! - " + condition); } } }