/*
* Copyright (c) 2004-2011 Marco Maccaferri and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Marco Maccaferri - initial API and implementation
*/
package org.eclipsetrader.internal.brokers.paper;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.ValidationEvent;
import javax.xml.bind.ValidationEventHandler;
import javax.xml.namespace.QName;
import javax.xml.transform.stream.StreamSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.core.runtime.Status;
import org.eclipsetrader.core.feed.IFeedIdentifier;
import org.eclipsetrader.core.feed.IPricingListener;
import org.eclipsetrader.core.feed.ITrade;
import org.eclipsetrader.core.feed.PricingDelta;
import org.eclipsetrader.core.feed.PricingEvent;
import org.eclipsetrader.core.instruments.ISecurity;
import org.eclipsetrader.core.markets.IMarket;
import org.eclipsetrader.core.markets.IMarketService;
import org.eclipsetrader.core.markets.MarketPricingEnvironment;
import org.eclipsetrader.core.repositories.IRepositoryService;
import org.eclipsetrader.core.trading.BrokerException;
import org.eclipsetrader.core.trading.IAccount;
import org.eclipsetrader.core.trading.IBroker;
import org.eclipsetrader.core.trading.IOrder;
import org.eclipsetrader.core.trading.IOrderChangeListener;
import org.eclipsetrader.core.trading.IOrderMonitor;
import org.eclipsetrader.core.trading.IOrderRoute;
import org.eclipsetrader.core.trading.IOrderSide;
import org.eclipsetrader.core.trading.IOrderStatus;
import org.eclipsetrader.core.trading.IOrderType;
import org.eclipsetrader.core.trading.IOrderValidity;
import org.eclipsetrader.core.trading.OrderChangeEvent;
import org.eclipsetrader.core.trading.OrderDelta;
import org.eclipsetrader.internal.brokers.paper.transactions.StockTransaction;
public class PaperBroker implements IBroker {
private final String id;
private final String name;
private final IMarketService marketService;
private final IRepositoryService repositoryService;
private MarketPricingEnvironment pricingEnvironment;
private List<OrderMonitor> pendingOrders = new ArrayList<OrderMonitor>();
private ListenerList listeners = new ListenerList(ListenerList.IDENTITY);
private final Log log = LogFactory.getLog(getClass());
private IPricingListener pricingListener = new IPricingListener() {
@Override
public void pricingUpdate(PricingEvent event) {
for (PricingDelta delta : event.getDelta()) {
if (delta.getNewValue() instanceof ITrade) {
IMarket market = marketService.getMarketForSecurity(event.getSecurity());
if (market == null || market.isOpen()) {
processTrade(event.getSecurity(), (ITrade) delta.getNewValue());
}
}
}
}
};
public PaperBroker(String id, String name, IMarketService marketService, IRepositoryService repositoryService) {
this.id = id;
this.name = name;
this.marketService = marketService;
this.repositoryService = repositoryService;
}
/* (non-Javadoc)
* @see org.eclipsetrader.core.trading.IBrokerConnector#getId()
*/
@Override
public String getId() {
return id;
}
/* (non-Javadoc)
* @see org.eclipsetrader.core.trading.IBrokerConnector#getName()
*/
@Override
public String getName() {
return name;
}
/* (non-Javadoc)
* @see org.eclipsetrader.core.trading.IBrokerConnector#connect()
*/
@Override
public void connect() {
if (pricingEnvironment != null) {
pricingEnvironment.removePricingListener(pricingListener);
pricingEnvironment.dispose();
}
pricingEnvironment = new MarketPricingEnvironment(marketService);
List<OrderDelta> list = new ArrayList<OrderDelta>();
for (OrderMonitor monitor : pendingOrders) {
pricingEnvironment.addSecurity(monitor.getOrder().getSecurity());
list.add(new OrderDelta(OrderDelta.KIND_ADDED, monitor));
}
fireUpdateNotifications(list.toArray(new OrderDelta[list.size()]));
pricingEnvironment.addPricingListener(pricingListener);
}
/* (non-Javadoc)
* @see org.eclipsetrader.core.trading.IBrokerConnector#disconnect()
*/
@Override
public void disconnect() {
if (pricingEnvironment != null) {
pricingEnvironment.removePricingListener(pricingListener);
pricingEnvironment.dispose();
pricingEnvironment = null;
}
}
/* (non-Javadoc)
* @see org.eclipsetrader.core.trading.IBrokerConnector#canTrade(org.eclipsetrader.core.instruments.ISecurity)
*/
@Override
public boolean canTrade(ISecurity security) {
return true;
}
/* (non-Javadoc)
* @see org.eclipsetrader.core.trading.IBrokerConnector#getSecurityFromSymbol(java.lang.String)
*/
@Override
public ISecurity getSecurityFromSymbol(String symbol) {
ISecurity security = null;
if (repositoryService != null) {
ISecurity[] securities = repositoryService.getSecurities();
for (int i = 0; i < securities.length; i++) {
IFeedIdentifier identifier = securities[i].getIdentifier();
if (identifier != null && symbol.equals(identifier.getSymbol())) {
security = securities[i];
break;
}
}
}
return security;
}
/* (non-Javadoc)
* @see org.eclipsetrader.core.trading.IBroker#getSymbolFromSecurity(org.eclipsetrader.core.instruments.ISecurity)
*/
@Override
public String getSymbolFromSecurity(ISecurity security) {
return security.getIdentifier() != null ? security.getIdentifier().getSymbol() : null;
}
/* (non-Javadoc)
* @see org.eclipsetrader.core.trading.IBrokerConnector#getAllowedSides()
*/
@Override
public IOrderSide[] getAllowedSides() {
return new IOrderSide[] {
IOrderSide.Buy, IOrderSide.Sell,
};
}
/* (non-Javadoc)
* @see org.eclipsetrader.core.trading.IBrokerConnector#getAllowedTypes()
*/
@Override
public IOrderType[] getAllowedTypes() {
return new IOrderType[] {
IOrderType.Limit, IOrderType.Market,
};
}
/* (non-Javadoc)
* @see org.eclipsetrader.core.trading.IBrokerConnector#getAllowedValidity()
*/
@Override
public IOrderValidity[] getAllowedValidity() {
return new IOrderValidity[] {
IOrderValidity.Day,
};
}
/* (non-Javadoc)
* @see org.eclipsetrader.core.trading.IBrokerConnector#getAllowedRoutes()
*/
@Override
public IOrderRoute[] getAllowedRoutes() {
return new IOrderRoute[0];
}
/* (non-Javadoc)
* @see org.eclipsetrader.core.trading.IBrokerConnector#getOrders()
*/
@Override
public IOrderMonitor[] getOrders() {
synchronized (pendingOrders) {
return pendingOrders.toArray(new IOrderMonitor[pendingOrders.size()]);
}
}
/* (non-Javadoc)
* @see org.eclipsetrader.core.trading.IBrokerConnector#prepareOrder(org.eclipsetrader.core.trading.IOrder)
*/
@Override
public IOrderMonitor prepareOrder(IOrder order) throws BrokerException {
if (order.getAccount() != null && !(order.getAccount() instanceof Account)) {
throw new BrokerException("Invalid account");
}
OrderMonitor monitor = new OrderMonitor(this, order) {
@Override
public void cancel() throws BrokerException {
setStatus(IOrderStatus.Canceled);
if (log.isInfoEnabled()) {
StringBuilder sb = new StringBuilder("Order Cancelled:");
sb.append(" instrument=" + getOrder().getSecurity().getName());
sb.append(", type=" + getOrder().getType());
sb.append(", side=" + getOrder().getSide());
sb.append(", qty=" + getOrder().getQuantity());
if (getOrder().getPrice() != null) {
sb.append(", price=" + getOrder().getPrice());
}
if (getOrder().getReference() != null) {
sb.append(", reference=" + getOrder().getReference());
}
log.info(sb.toString());
}
fireUpdateNotifications(new OrderDelta[] {
new OrderDelta(OrderDelta.KIND_UPDATED, this),
});
}
@Override
public void submit() throws BrokerException {
synchronized (pendingOrders) {
pendingOrders.add(this);
}
SimpleDateFormat idFormatter = new SimpleDateFormat("yyMMddHHmmssSSS");
setId(idFormatter.format(new Date()));
setStatus(IOrderStatus.PendingNew);
if (log.isInfoEnabled()) {
StringBuilder sb = new StringBuilder("Order Submitted:");
sb.append(" instrument=" + getOrder().getSecurity().getName());
sb.append(", type=" + getOrder().getType());
sb.append(", side=" + getOrder().getSide());
sb.append(", qty=" + getOrder().getQuantity());
if (getOrder().getPrice() != null) {
sb.append(", price=" + getOrder().getPrice());
}
if (getOrder().getReference() != null) {
sb.append(", reference=" + getOrder().getReference());
}
log.info(sb.toString());
}
fireUpdateNotifications(new OrderDelta[] {
new OrderDelta(OrderDelta.KIND_UPDATED, this),
});
}
};
if (pricingEnvironment != null) {
pricingEnvironment.addSecurity(order.getSecurity());
}
fireUpdateNotifications(new OrderDelta[] {
new OrderDelta(OrderDelta.KIND_ADDED, monitor),
});
return monitor;
}
protected void processTrade(ISecurity security, ITrade trade) {
List<OrderDelta> deltas = new ArrayList<OrderDelta>();
OrderMonitor[] monitors;
synchronized (pendingOrders) {
monitors = pendingOrders.toArray(new OrderMonitor[pendingOrders.size()]);
}
for (int i = 0; i < monitors.length; i++) {
if (monitors[i].getStatus() != IOrderStatus.PendingNew && monitors[i].getStatus() != IOrderStatus.Partial) {
continue;
}
IOrder order = monitors[i].getOrder();
if (order.getSecurity() == security) {
if (order.getType() == IOrderType.Market) {
fillOrder(monitors[i], monitors[i].getOrder(), trade.getSize(), trade.getPrice());
deltas.add(new OrderDelta(OrderDelta.KIND_UPDATED, monitors[i]));
}
else if (order.getType() == IOrderType.Limit) {
if (order.getSide() == IOrderSide.Buy && trade.getPrice() <= order.getPrice()) {
fillOrder(monitors[i], monitors[i].getOrder(), trade.getSize(), trade.getPrice());
deltas.add(new OrderDelta(OrderDelta.KIND_UPDATED, monitors[i]));
}
else if (order.getSide() == IOrderSide.Sell && trade.getPrice() >= order.getPrice()) {
fillOrder(monitors[i], monitors[i].getOrder(), trade.getSize(), trade.getPrice());
deltas.add(new OrderDelta(OrderDelta.KIND_UPDATED, monitors[i]));
}
}
}
}
if (deltas.size() != 0) {
fireUpdateNotifications(deltas.toArray(new OrderDelta[deltas.size()]));
}
}
protected void fillOrder(OrderMonitor monitor, IOrder order, Long size, Double price) {
double totalPrice = monitor.getFilledQuantity() != null ? monitor.getFilledQuantity() * monitor.getAveragePrice() : 0.0;
long filledQuantity = monitor.getFilledQuantity() != null ? monitor.getFilledQuantity() : 0L;
long remainQuantity = order.getQuantity() - filledQuantity;
long quantity = size != null && size < remainQuantity ? size : remainQuantity;
filledQuantity += quantity;
totalPrice += quantity * price;
monitor.setFilledQuantity(filledQuantity);
monitor.setAveragePrice(totalPrice / filledQuantity);
if (quantity != 0) {
if (order.getSide() == IOrderSide.Buy || order.getSide() == IOrderSide.BuyCover) {
monitor.addTransaction(new StockTransaction(monitor.getOrder().getSecurity(), quantity, price));
}
if (order.getSide() == IOrderSide.Sell || order.getSide() == IOrderSide.SellShort) {
monitor.addTransaction(new StockTransaction(monitor.getOrder().getSecurity(), -quantity, price));
}
}
if (log.isInfoEnabled()) {
StringBuilder sb = new StringBuilder("Order Filled:");
sb.append(" instrument=" + order.getSecurity().getName());
sb.append(", type=" + order.getType());
sb.append(", side=" + order.getSide());
sb.append(", qty=" + order.getQuantity());
if (order.getPrice() != null) {
sb.append(", price=" + order.getPrice());
}
sb.append(", fillQty=" + monitor.getFilledQuantity());
sb.append(", avgPrice=" + monitor.getAveragePrice());
if (order.getReference() != null) {
sb.append(", reference=" + order.getReference());
}
log.info(sb.toString());
}
if (monitor.getFilledQuantity().equals(order.getQuantity())) {
monitor.setStatus(IOrderStatus.Filled);
monitor.fireOrderCompletedEvent();
Account account = (Account) monitor.getOrder().getAccount();
if (account != null) {
account.processCompletedOrder(monitor);
}
}
else {
monitor.setStatus(IOrderStatus.Partial);
}
}
/* (non-Javadoc)
* @see org.eclipsetrader.core.trading.IBroker#addOrderChangeListener(org.eclipsetrader.core.trading.IOrderChangeListener)
*/
@Override
public void addOrderChangeListener(IOrderChangeListener listener) {
listeners.add(listener);
}
/* (non-Javadoc)
* @see org.eclipsetrader.core.trading.IBroker#removeOrderChangeListener(org.eclipsetrader.core.trading.IOrderChangeListener)
*/
@Override
public void removeOrderChangeListener(IOrderChangeListener listener) {
listeners.remove(listener);
}
protected void fireUpdateNotifications(OrderDelta[] deltas) {
if (deltas.length != 0) {
OrderChangeEvent event = new OrderChangeEvent(this, deltas);
Object[] l = listeners.getListeners();
for (int i = 0; i < l.length; i++) {
try {
((IOrderChangeListener) l[i]).orderChanged(event);
} catch (Throwable e) {
Status status = new Status(IStatus.ERROR, Activator.PLUGIN_ID, 0, "Error running listener", e); //$NON-NLS-1$
Activator.log(status);
}
}
}
}
/* (non-Javadoc)
* @see org.eclipsetrader.core.trading.IBroker#getAccounts()
*/
@Override
public IAccount[] getAccounts() {
return Activator.getDefault().getRepository().getAccounts();
}
public void load(File file) throws JAXBException {
if (!file.exists()) {
return;
}
JAXBContext jaxbContext = JAXBContext.newInstance(OrderMonitor[].class);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
unmarshaller.setEventHandler(new ValidationEventHandler() {
@Override
public boolean handleEvent(ValidationEvent event) {
Status status = new Status(IStatus.WARNING, Activator.PLUGIN_ID, 0, "Error validating XML: " + event.getMessage(), null); //$NON-NLS-1$
Activator.log(status);
return true;
}
});
JAXBElement<OrderMonitor[]> element = unmarshaller.unmarshal(new StreamSource(file), OrderMonitor[].class);
if (element != null) {
Calendar today = Calendar.getInstance();
Calendar order = Calendar.getInstance();
for (OrderMonitor monitor : element.getValue()) {
order.setTime(monitor.getOrder().getDate());
if (order.get(Calendar.DAY_OF_YEAR) == today.get(Calendar.DAY_OF_YEAR)) {
pendingOrders.add(monitor);
}
}
}
}
public void save(File file) throws JAXBException, IOException {
if (file.exists()) {
file.delete();
}
JAXBContext jaxbContext = JAXBContext.newInstance(OrderMonitor[].class);
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setEventHandler(new ValidationEventHandler() {
@Override
public boolean handleEvent(ValidationEvent event) {
Status status = new Status(IStatus.WARNING, Activator.PLUGIN_ID, 0, "Error validating XML: " + event.getMessage(), null); //$NON-NLS-1$
Activator.log(status);
return true;
}
});
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
marshaller.setProperty(Marshaller.JAXB_ENCODING, System.getProperty("file.encoding")); //$NON-NLS-1$
OrderMonitor[] elements = pendingOrders.toArray(new OrderMonitor[pendingOrders.size()]);
JAXBElement<OrderMonitor[]> element = new JAXBElement<OrderMonitor[]>(new QName("list"), OrderMonitor[].class, elements);
marshaller.marshal(element, new FileWriter(file));
}
}