package com.activequant.trading.virtual;
import java.util.HashMap;
import java.util.Map;
import org.apache.log4j.Logger;
import com.activequant.domainmodel.ETransportType;
import com.activequant.domainmodel.Portfolio;
import com.activequant.domainmodel.TimeStamp;
import com.activequant.domainmodel.exceptions.IncompleteOrderInstructions;
import com.activequant.domainmodel.exceptions.TransportException;
import com.activequant.domainmodel.exceptions.UnsupportedOrderType;
import com.activequant.domainmodel.streaming.BBOEvent;
import com.activequant.domainmodel.streaming.MarketDataSnapshot;
import com.activequant.domainmodel.streaming.OrderStreamEvent;
import com.activequant.domainmodel.streaming.PositionEvent;
import com.activequant.domainmodel.streaming.StreamEvent;
import com.activequant.domainmodel.streaming.TimeStreamEvent;
import com.activequant.domainmodel.trade.event.OrderAcceptedEvent;
import com.activequant.domainmodel.trade.event.OrderCancelSubmittedEvent;
import com.activequant.domainmodel.trade.event.OrderCancelledEvent;
import com.activequant.domainmodel.trade.event.OrderEvent;
import com.activequant.domainmodel.trade.event.OrderFillEvent;
import com.activequant.domainmodel.trade.event.OrderReplacedEvent;
import com.activequant.domainmodel.trade.event.OrderSubmittedEvent;
import com.activequant.domainmodel.trade.event.OrderTerminalEvent;
import com.activequant.domainmodel.trade.event.OrderUpdateSubmittedEvent;
import com.activequant.domainmodel.trade.order.LimitOrder;
import com.activequant.domainmodel.trade.order.Order;
import com.activequant.domainmodel.trade.order.OrderSide;
import com.activequant.interfaces.trading.IExchange;
import com.activequant.interfaces.trading.IOrderTracker;
import com.activequant.interfaces.transport.ITransportFactory;
import com.activequant.interfaces.utils.IEventListener;
import com.activequant.interfaces.utils.IEventSource;
import com.activequant.utils.events.Event;
public class VirtualExchange implements IExchange {
private long virtexOrderId = 0L;
private TimeStamp currentExchangeTime = new TimeStamp(0L);
private Map<String, IOrderTracker> orderTrackers = new HashMap<String, IOrderTracker>();
private Map<String, LimitOrderBook> lobs = new HashMap<String, LimitOrderBook>();
private final Event<OrderEvent> globalOrderEvent = new Event<OrderEvent>();
private ITransportFactory transport;
private Logger log = Logger.getLogger(VirtualExchange.class);
private Portfolio clientPortfolio = new Portfolio();
public VirtualExchange(ITransportFactory transport) {
this.transport = transport;
}
/*
* (non-Javadoc)
*
* @see com.activequant.trading.virtual.IExchange#currentExchangeTime()
*/
@Override
public TimeStamp currentExchangeTime() {
return currentExchangeTime;
}
class VirtualOrderTracker implements IOrderTracker {
private Event<OrderEvent> event = new Event<OrderEvent>();
private LimitOrder order;
private OrderEvent lastState;
VirtualOrderTracker(LimitOrder order) throws IncompleteOrderInstructions {
this.order = order;
if (order.getTradInstId() == null)
throw new IncompleteOrderInstructions("TradInstID missing");
event.addEventListener(new IEventListener<OrderEvent>() {
@Override
public void eventFired(OrderEvent event) {
lastState = event;
}
});
}
public Event<OrderEvent> getEvent() {
return event;
}
@Override
public void update(Order newOrder) {
if (newOrder instanceof LimitOrder) {
LimitOrder le = (LimitOrder) newOrder;
le.setWorkingTimeStamp(currentExchangeTime);
this.order.setWorkingTimeStamp(currentExchangeTime);
this.order.setQuantity(le.getQuantity());
this.order.setLimitPrice(le.getLimitPrice());
getOrderBook(le.getTradInstId()).updateOrder(this.order);
// set out an update event.
OrderEvent oe = new OrderUpdateSubmittedEvent();
oe.setTimeStamp(currentExchangeTime);
oe.setRefOrder(le);
oe.setRefOrderId(le.getOrderId());
getEvent().fire(oe);
sendOrderEvent(le.getTradInstId(), oe);
//
oe = new OrderReplacedEvent();
oe.setRefOrder(le);
oe.setRefOrderId(le.getOrderId());
oe.setTimeStamp(currentExchangeTime);
getEvent().fire(oe);
sendOrderEvent(le.getTradInstId(), oe);
getOrderBook(le.getTradInstId()).match();
}
}
@Override
public void submit() {
order.setOpenQuantity(order.getQuantity());
order.setWorkingTimeStamp(currentExchangeTime());
if (order.getOrderId() == null)
order.setOrderId("OID" + virtexOrderId++);
// add it to the list of local order trackers.
orderTrackers.put(order.getOrderId(), this);
getOrderBook(order.getTradInstId()).addOrder(order);
// send out the submit event
OrderEvent oe = new OrderSubmittedEvent();
oe.setTimeStamp(currentExchangeTime);
oe.setRefOrderId(order.getOrderId());
oe.setRefOrder(order);
oe.setOptionalInstId(order.getTradInstId());
getEvent().fire(oe);
sendOrderEvent(order.getTradInstId(), oe);
// ... and the accepted event
oe = new OrderAcceptedEvent();
oe.setTimeStamp(currentExchangeTime);
oe.setRefOrderId(order.getOrderId());
oe.setRefOrder(order);
oe.setOptionalInstId(order.getTradInstId());
getEvent().fire(oe);
sendOrderEvent(order.getTradInstId(), oe);
getOrderBook(order.getTradInstId()).match();
}
@Override
public String getVenueAssignedId() {
return "VIRTEXOID" + (virtexOrderId++);
}
@Override
public IEventSource<OrderEvent> getOrderEventSource() {
return event;
}
@Override
public Order getOrder() {
return order;
}
@Override
public void cancel() {
getOrderBook(order.getTradInstId()).cancelOrder(order);
OrderEvent oe = new OrderCancelSubmittedEvent();
oe.setTimeStamp(currentExchangeTime);
getEvent().fire(oe);
oe = new OrderCancelledEvent();
oe.setTimeStamp(currentExchangeTime);
getEvent().fire(oe);
}
public OrderEvent lastState() {
return lastState;
}
}
private void sendOrderEvent(String tradInstId, OrderEvent oe) {
try {
transport.getPublisher(ETransportType.TRAD_DATA, tradInstId).send(
new OrderStreamEvent(tradInstId, oe.getTimeStamp(), oe));
globalOrderEvent.fire(oe);
} catch (TransportException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
/*
* (non-Javadoc)
*
* @see com.activequant.trading.virtual.IExchange#prepareOrder()
*/
@Override
public IOrderTracker prepareOrder(Order order) throws UnsupportedOrderType, IncompleteOrderInstructions {
if (!(order instanceof LimitOrder))
throw new UnsupportedOrderType("Order type not supported by exchange: " + order);
LimitOrder limitOrder = (LimitOrder) order;
if (limitOrder.getQuantity() <= 0.0)
throw new IncompleteOrderInstructions("Invalid quantity given: " + limitOrder.getQuantity());
if (limitOrder.getLimitPrice() == null)
throw new IncompleteOrderInstructions("No limit price given.");
// if (limitOrder.getLimitPrice() <= 0.0)
// throw new IncompleteOrderInstructions("Invalid negative limit price given.");
VirtualOrderTracker tracker = new VirtualOrderTracker(limitOrder);
return tracker;
}
public IOrderTracker getOrderTracker(Order order) {
IOrderTracker tracker = orderTrackers.get(order.getOrderId());
return tracker;
}
public void execution(Order order, double price, double quantity) {
if (order.getOrderId() == null)
return;
IOrderTracker trck = getOrderTracker(order);
if (trck == null)
return;
// can only handle our own virtual trackers.
if (trck instanceof VirtualOrderTracker) {
if(quantity == 0.0)
return;
LimitOrder lo = (LimitOrder)trck.getOrder();
OrderFillEvent ofe = new OrderFillEvent();
ofe.setTimeStamp(currentExchangeTime());
ofe.setRefOrder(order);
ofe.setRefOrderId(order.getOrderId());
ofe.setSide(lo.getOrderSide());
ofe.setOptionalInstId(lo.getTradInstId());
ofe.setFillAmount(quantity);
ofe.setFillPrice(price);
// forgot to set left quantity.
ofe.setLeftQuantity(lo.getOpenQuantity());
((VirtualOrderTracker) trck).getEvent().fire(ofe);
sendOrderEvent(lo.getTradInstId(), ofe);
updatePortfolio(lo.getTradInstId(), price, quantity, lo.getOrderSide().getSide());
// Note: open quantity is updated limit order book matcher.
//
if (order instanceof LimitOrder) {
LimitOrder lo2 = (LimitOrder) order;
if (lo.getOpenQuantity() == 0.0) {
OrderTerminalEvent ote = new OrderTerminalEvent();
ote.setTimeStamp(currentExchangeTime());
((VirtualOrderTracker) trck).getEvent().fire(ote);
// also send it to the internal event layer.
// clean up the order tracker.
orderTrackers.remove(trck);
// ok, we also send out the position event.
}
}
}
}
public void updatePortfolio(String tdiId, Double price, Double lastFill, int side) {
// update the position.
double currentPosition = clientPortfolio.getPosition(tdiId);
log.info("Current position " + currentPosition);
double openPrice = clientPortfolio.getOpenPrice(tdiId);
double newPosition = currentPosition + side * lastFill;
//
double newOpenPrice = price;
/// ((currentPosition * openPrice) + (lastFill * side * price));
if(Math.abs(newPosition)>Math.abs(currentPosition) && newPosition!=0.0){
newOpenPrice = ((Math.abs(currentPosition) * openPrice) + (lastFill * price)) / Math.abs(newPosition);
}
//
if (newPosition == 0.0)
newOpenPrice = 0.0;
setPosition(tdiId, newOpenPrice, newPosition);
log.info("Order side: " + side + ", " + price + ", " + lastFill);
log.info("Portfolio updated. New position for " + tdiId + ": " + newPosition + " @ " + newOpenPrice);
//
}
private void setPosition(String tradeableId, Double price, Double newPosition) {
clientPortfolio.setPosition(tradeableId, price, newPosition);
// send out a position event.
PositionEvent pe = new PositionEvent(tradeableId, new TimeStamp(), price, newPosition);
pe.setTimeStamp(currentExchangeTime);
pe.setCreationTime(currentExchangeTime.getNanoseconds());
try {
transport.getPublisher(ETransportType.TRAD_DATA, tradeableId).send(pe);
} catch (TransportException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
public void processStreamEvent(StreamEvent streamEvent) {
if (streamEvent instanceof TimeStreamEvent) {
currentExchangeTime = ((TimeStreamEvent) streamEvent).getTimeStamp();
}
if (streamEvent instanceof MarketDataSnapshot) {
MarketDataSnapshot mds = (MarketDataSnapshot) streamEvent;
String tdiId = mds.getTdiId();
// weed out non-tradeable market data.
if (tdiId == null)
return;
// lookup the tdi for this mdi id.
LimitOrderBook lob = getOrderBook(tdiId);
// clear out limit order book except our own orders.
lob.weedOutForeignOrders();
if (mds.getBidPrices() != null && mds.getBidPrices().length > 0) {
LimitOrder bestBid = new LimitOrder();
bestBid.setWorkingTimeStamp(currentExchangeTime);
bestBid.setOrderSide(OrderSide.BUY);
bestBid.setLimitPrice(mds.getBidPrices()[0]);
bestBid.setQuantity(mds.getBidSizes()[0]);
bestBid.setOpenQuantity(mds.getBidSizes()[0]);
lob.addOrder(bestBid);
}
if (mds.getAskPrices() != null && mds.getAskPrices().length > 0) {
LimitOrder bestAsk = new LimitOrder();
bestAsk.setOrderSide(OrderSide.SELL);
bestAsk.setWorkingTimeStamp(currentExchangeTime);
bestAsk.setLimitPrice(mds.getAskPrices()[0]);
bestAsk.setQuantity(mds.getAskSizes()[0]);
bestAsk.setOpenQuantity(mds.getAskSizes()[0]);
lob.addOrder(bestAsk);
}
// rerun a match.
lob.match();
} else if (streamEvent instanceof BBOEvent) {
BBOEvent nbbo = (BBOEvent) streamEvent;
String instId = nbbo.getTradeableInstrumentId();
// weed out non-tradeable market data.
if (instId == null)
return;
LimitOrderBook lob = getOrderBook(instId);
// clear out limit order book except our own orders.
lob.weedOutForeignOrders();
if (nbbo.getBid() != null) {
LimitOrder bestBid = new LimitOrder();
bestBid.setOrderSide(OrderSide.BUY);
bestBid.setLimitPrice(nbbo.getBid());
bestBid.setQuantity(nbbo.getBidQuantity());
bestBid.setOpenQuantity(bestBid.getQuantity());
lob.addOrder(bestBid);
}
if (nbbo.getAsk() != null) {
LimitOrder bestAsk = new LimitOrder();
bestAsk.setOrderSide(OrderSide.SELL);
bestAsk.setLimitPrice(nbbo.getAsk());
bestAsk.setQuantity(nbbo.getAskQuantity());
bestAsk.setOpenQuantity(bestAsk.getQuantity());
lob.addOrder(bestAsk);
}
// rerun a match.
lob.match();
} else {
log.info("Dropping unknown event type.");
}
}
/*
* (non-Javadoc)
*
* @see
* com.activequant.trading.virtual.IExchange#getOrderBook(java.lang.String)
*/
@Override
public LimitOrderBook getOrderBook(String tradeableInstrumentId) {
if (!lobs.containsKey(tradeableInstrumentId))
lobs.put(tradeableInstrumentId, new LimitOrderBook(this, tradeableInstrumentId));
return lobs.get(tradeableInstrumentId);
}
@Override
public IOrderTracker getOrderTracker(String orderId) {
return orderTrackers.get(orderId);
}
public Event<OrderEvent> getGlobalOrderEvent() {
return globalOrderEvent;
}
public Portfolio getClientPortfolio() {
return clientPortfolio;
}
}