package org.cryptocoinpartners.module;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.inject.Singleton;
import org.cryptocoinpartners.enumeration.OrderState;
import org.cryptocoinpartners.esper.annotation.When;
import org.cryptocoinpartners.schema.Book;
import org.cryptocoinpartners.schema.Event;
import org.cryptocoinpartners.schema.Fill;
import org.cryptocoinpartners.schema.Market;
import org.cryptocoinpartners.schema.Offer;
import org.cryptocoinpartners.schema.Order;
import org.cryptocoinpartners.schema.SpecificOrder;
import org.cryptocoinpartners.schema.Trade;
/**
* MockOrderService simulates the Filling of Orders by looking at broadcast Book data for price and volume information.
*
* @author Tim Olson
*/
@Singleton
@SuppressWarnings("UnusedDeclaration")
public class MockOrderService extends BaseOrderService {
private static ExecutorService mockOrderService = Executors.newFixedThreadPool(1);
@Override
protected void handleSpecificOrder(SpecificOrder specificOrder) {
if (specificOrder.getStopPrice() != null)
reject(specificOrder, "Stop prices unsupported");
specificOrder.setEntryTime(context.getTime());
addOrder(specificOrder);
updateOrderState(specificOrder, OrderState.PLACED, true);
// specificOrder.persit();
//TODO when placing the order it is on the same listener so it needs to be routed.
}
private class updateBookRunnable implements Runnable {
private final Event event;
// protected Logger log;
public updateBookRunnable(Event event) {
this.event = event;
}
@Override
public void run() {
updateBook(event);
}
}
@SuppressWarnings("ConstantConditions")
@When("@Priority(9) select * from Book(Book.bidVolumeAsDouble>0, Book.askVolumeAsDouble<0)")
// @When("@Priority(9) select * from Book")
private void handleBook(Book b) {
log.trace("handleBook: Book Recieved: " + b);
updateBook(b);
//mockOrderService.submit(new updateBookRunnable(b));
}
@SuppressWarnings("ConstantConditions")
@When("@Priority(9) select * from Trade(Trade.volumeCount!=0)")
private void handleTrade(Trade t) {
log.trace("handleTrade: Book Recieved: " + t);
updateBook(t);
// mockOrderService.submit(new updateBookRunnable(t));
}
@SuppressWarnings("ConstantConditions")
private synchronized void updateBook(Event event) {
Market market = null;
List<Offer> asks = new ArrayList<>();
Book b = null;
Trade t = null;
List<Offer> bids = new ArrayList<>();
if (event instanceof Book) {
b = (Book) event;
market = b.getMarket();
asks = b.getAsks();
bids = b.getBids();
}
if (event instanceof Trade) {
t = (Trade) event;
market = t.getMarket();
//if Trade is a sell then it must have big the ask
if (t.getVolume().isNegative()) {
Offer bestBid = new Offer(market, t.getTime(), t.getTimeReceived(), t.getPrice().getCount(), t.getVolume().negate().getCount());
asks.add(bestBid);
} else {
Offer bestAsk = new Offer(market, t.getTime(), t.getTimeReceived(), t.getPrice().getCount(), t.getVolume().negate().getCount());
bids.add(bestAsk);
}
}
List<Fill> fills = Collections.synchronizedList(new ArrayList<Fill>());
List<SpecificOrder> filledOrders = Collections.synchronizedList(new ArrayList<SpecificOrder>());
// todo multiple Orders may be filled with the same Offer. We should deplete the Offers as we fill
for (Order pendingOrder : pendingOrders) {
if (pendingOrder instanceof SpecificOrder) {
SpecificOrder order = (SpecificOrder) pendingOrder;
// Iterator<SpecificOrder> itOrder = getPendingOrders().iterator();
// while (itOrder.hasNext()) {
// SpecificOrder order = itOrder.next();
if (order.getUnfilledVolumeCount() == 0) {
filledOrders.add(order);
// pendingOrders.remove(order);
break;
}
if (order.getMarket().equals(market)) {
// buy order, so hit ask
if (order.isBid()) {
long remainingVolume = order.getUnfilledVolumeCount();
for (Offer ask : asks) {
if (order.getLimitPrice() == null)
log.debug("null limit");
//
if ((order.getLimitPrice() != null && order.getLimitPrice().getCount() < ask.getPriceCount()) || ask == null)
// || ask.getVolumeCount() == 0 || ask.getPriceCount() == 0)
break;
// synchronized (lock) {
if (t != null) {
log.debug("filled by a trade");
}
long fillVolume = Math.min(Math.abs(ask.getVolumeCount()), Math.abs(remainingVolume));
if (fillVolume == 0)
return;
Fill fill = fillFactory.create(order, ask.getTime(), ask.getTime(), ask.getMarket(), ask.getPriceCount(),
Math.min(Math.abs(ask.getVolumeCount()), Math.abs(order.getUnfilledVolumeCount())),
Long.toString(ask.getTime().getMillis()));
if (fill.getVolume() == null || fill.getVolume().isZero())
log.debug("fille zero lots" + (order.getUnfilledVolumeCount()));
if (fill.getVolume().abs().compareTo(order.getVolume().abs()) > 0)
log.debug("overfilled" + (order.getUnfilledVolumeCount()));
fills.add(fill);
remainingVolume -= fillVolume;
logFill(order, ask, fill);
if (remainingVolume == 0) {
filledOrders.add(order);
//pendingOrders.remove(order);
break;
}
}
}
// i--;
// --removeOrder(order);
// }
// break;
// if sell order, fill if limint<=Bid
if (order.isAsk()) {
long remainingVolume = order.getUnfilledVolumeCount(); // this will be negative
// we need to thingk about maker and taker
//which side of the book do we hit
for (Offer bid : bids) {
if (order.getLimitPrice() == null)
log.debug("null limit");
if ((order.getLimitPrice() != null && order.getLimitPrice().getCount() > bid.getPriceCount()) || bid == null)
//|| bid.getVolumeCount() == 0 || bid.getPriceCount() == 0)
break;
// synchronized (lock) {
if (t != null) {
log.debug("filled by a trade");
}
long fillVolume = -Math.min(Math.abs(bid.getVolumeCount()), Math.abs(remainingVolume));
if (fillVolume == 0)
return;
Fill fill = fillFactory.create(order, context.getTime(), context.getTime(), bid.getMarket(), bid.getPriceCount(), fillVolume,
Long.toString(bid.getTime().getMillis()));
if (fill.getVolume() == null || fill.getVolume().isZero())
log.debug("fille zero lots" + (order.getUnfilledVolumeCount()));
if (fill.getVolume().abs().compareTo(order.getVolume().abs()) > 0)
log.debug("overfilled");
fills.add(fill);
remainingVolume -= fillVolume;
logFill(order, bid, fill);
if (remainingVolume == 0) {
filledOrders.add(order);
// pendingOrders.remove(order);
break;
}
} // }
// break;
}
}
}
}
cancelSpecificOrder(filledOrders);
for (Fill fill : fills) {
log.trace(fills.toString());
handleFillProcessing(fill);
//context.route(fill);
//context.publish(fill);
log.debug("filled");
}
}
@SuppressWarnings("finally")
@Override
protected synchronized boolean cancelSpecificOrder(SpecificOrder order) {
boolean deleted = false;
try {
if (pendingOrders.remove(order))
deleted = true;
else {
if (!pendingOrders.contains(order)) {
log.error("Unable to cancel order as not present in mock order book. Order:" + order);
// deleted = false;
}
}
} catch (Error | Exception e) {
log.error("Unable to cancel order :" + order + ". full stack trace" + e);
} finally {
return deleted;
}
}
private void addOrder(SpecificOrder order) {
pendingOrders.add(order);
log.debug("Order: " + order + " added to mock order book");
// mockOrderService.submit(new updateBookRunnable(quotes.getLastBook(order.getMarket())));
}
private void logFill(SpecificOrder order, Offer offer, Fill fill) {
// if (log.isDebugEnabled())
log.info("Mock fill of Order " + order + " with Offer " + offer + ": " + fill);
}
// private static Object lock = new Object();
private static final Collection<SpecificOrder> pendingOrders = new CopyOnWriteArrayList<SpecificOrder>();
// new CopyOnWriteArrayList<SpecificOrder>();
//private QuoteService quotes;
// @Override
// }
@Override
public void init() {
Set<org.cryptocoinpartners.schema.Order> cointraderOpenOrders = new HashSet<org.cryptocoinpartners.schema.Order>();
super.init();
// Once we have all the order loaded, let's add all the open specific orders to the mock order book (pendingOrders)
if (stateOrderMap.get(OrderState.NEW) != null)
cointraderOpenOrders.addAll(stateOrderMap.get(OrderState.NEW));
if (stateOrderMap.get(OrderState.PLACED) != null)
cointraderOpenOrders.addAll(stateOrderMap.get(OrderState.PLACED));
if (stateOrderMap.get(OrderState.PARTFILLED) != null)
cointraderOpenOrders.addAll(stateOrderMap.get(OrderState.PARTFILLED));
if (stateOrderMap.get(OrderState.PARTFILLED) != null)
cointraderOpenOrders.addAll(stateOrderMap.get(OrderState.ROUTED));
if (stateOrderMap.get(OrderState.CANCELLING) != null)
cointraderOpenOrders.addAll(stateOrderMap.get(OrderState.CANCELLING));
for (org.cryptocoinpartners.schema.Order openOrder : cointraderOpenOrders) {
if (openOrder instanceof SpecificOrder)
addOrder((SpecificOrder) openOrder);
}
}
}