/**
* Blitz Trading
*/
package executionserver.controller;
import executionserver.domain.ExecutionOrder;
import executionserver.domain.OrderStatus;
import executionserver.domain.RequestTypes;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.mina.common.IoSession;
import org.bson.BasicBSONObject;
import org.slf4j.LoggerFactory;
/**
* ExecutionServer connection object, controls the server operations with FIX
* initiator (client).
*
* @author Sylvio Azevedo <sylvio.azevedo@blitz-trading.com>
*/
public class BsonExecutionController {
// properties
private String clientName;
private IoSession session;
private final DatabaseController database;
private final Object ordersToProcessMutex = new Object();
private List<ExecutionOrder> ordersToProcess;
private boolean running = true;
private QueueWatcher watcher;
private Thread watcherThread;
// logger
private final org.slf4j.Logger logger = LoggerFactory.getLogger(getClass());
/**
* Constructor storing the client name and the connection session.
*
* @param clientName Connection client name
* @param session Connection session reference.
*
* @throws UnknownHostException
*/
public BsonExecutionController(String clientName, IoSession session) throws UnknownHostException {
this.clientName = clientName;
this.session = session;
// instance temporary queue for orders to process.
ordersToProcess = new ArrayList<ExecutionOrder>();
// start a connection to mongo database
database = new DatabaseController();
database.connect(ExecutionServerController.settings);
}
public void startQueueWatcher() {
// start queue watcher
watcher = new QueueWatcher();
watcherThread = new Thread(watcher);
watcherThread.start();
}
public void stopQueueWatcher() {
running = false;
synchronized (ordersToProcessMutex) {
ordersToProcessMutex.notify();
}
}
/**
* Add a new order in the processing queue
*
* @param order Order about be enqueued.
*
* @throws InterruptedException
*/
public void addOrder(ExecutionOrder order) throws InterruptedException {
// put order in the temporary queue
ordersToProcess.add(order);
BasicBSONObject orderUpdate = new BasicBSONObject();
orderUpdate.put("OrderId", order.getId());
orderUpdate.put("Status", OrderStatus.RECEIVED);
orderUpdate.put("QtyRemaining", order.getQty());
// creating and sending the order update message.
Map<String, Object> args = new HashMap<String, Object>();
args.put("Order", orderUpdate);
// Send message "received" to client.
BasicBSONObject response = new BasicBSONObject();
response.put("Handler", "OrderUpdate");
response.put("Args", args);
session.write(response);
synchronized (ordersToProcessMutex) {
ordersToProcessMutex.notify();
}
}
/**
* Order rejection method.
*
* @param order Rejected order reference.
* @param message Rejection message.
*/
private void reject(ExecutionOrder order, String message) {
logger.warn("Order [" + order.getId() + "] has been rejected with message: " + message);
Map<String, Object> args = new HashMap<String, Object>();
args.put("OrderId", order.getId());
args.put("Status", OrderStatus.REJECTED);
args.put("QtyRemaining", order.getQty());
args.put("RejectReason", message);
// Send message "received" to client.
BasicBSONObject response = new BasicBSONObject();
response.put("Handler", "OrderUpdate");
response.put("Args", args);
session.write(response);
}
/**
* Send all available connections (routings) to the client part.
*/
public void sendAvailableConns() {
List<String> routes = new ArrayList<String>();
for (String conn : ExecutionServerController.connections.keySet()) {
routes.add(conn);
}
Map<String, Object> args = new HashMap<String, Object>();
args.put("Routes", routes);
// Send message "received" to client.
BasicBSONObject response = new BasicBSONObject();
response.put("Handler", "ConnectResponse");
response.put("Args", args);
session.write(response);
}
/**
* Send a list of orders from a particular client.
*/
public void sendOrderList() {
List<ExecutionOrder> orderList = database.findOrdersByOwner(clientName);
if (orderList.isEmpty()) {
return;
}
List<BasicBSONObject> ordersListToSend = new ArrayList<BasicBSONObject>();
for (ExecutionOrder order : orderList) {
BasicBSONObject newOrder = new BasicBSONObject();
newOrder.put("OrderId", order.getClientId());
newOrder.put("ClientId", order.getClientId());
newOrder.put("Symbol", order.getSecurity());
newOrder.put("Side", order.getSide());
newOrder.put("Type", order.getOrderType());
newOrder.put("Exchange", order.getExchange());
newOrder.put("Account", order.getAccount());
newOrder.put("Validity", order.getValidity());
newOrder.put("Status", order.getStatus());
newOrder.put("Price", order.getPrice());
newOrder.put("Quantity", order.getQty());
newOrder.put("MinQty", order.getMinQty());
newOrder.put("OpenQty", order.getOpenQty());
newOrder.put("EntryTime", 0);
newOrder.put("StopPx", order.getStopPrice());
newOrder.put("LastPrice", order.getLastPrice());
newOrder.put("LastShares", order.getLastShares());
newOrder.put("Route", order.getRoute());
newOrder.put("Broker", order.getBroker());
if(order.getRejectReason() != null && order.getRejectReason().isEmpty()) {
newOrder.put("RejectReason", order.getRejectReason());
}
ordersListToSend.add(newOrder);
}
Map<String, Object> args = new HashMap<String, Object>();
args.put("OrderList", ordersListToSend);
// Send message "received" to client.
BasicBSONObject response = new BasicBSONObject();
response.put("Handler", "OrderList");
response.put("Args", args);
try {
// send orders to client.
session.write(response);
}
catch (Exception e) {
logger.error("Error sending client list order: " + e.getMessage());
}
}
private class QueueWatcher implements Runnable {
@Override
public void run() {
while (running) {
synchronized (ordersToProcessMutex) {
try {
ordersToProcessMutex.wait(50);
}
catch (InterruptedException ex) {
logger.warn("Queue watcher has been interrupted. All order will be stucked in the queue: " + ex.getMessage());
}
}
while (!ordersToProcess.isEmpty()) {
// remove the head of the queue
ExecutionOrder order = ordersToProcess.remove(0);
switch (order.getReqType()) {
case RequestTypes.REQUEST_NEW_ORDER:
// Check if the appointed fix connection exists.
if (ExecutionServerController.connections.get(order.getRoute()) == null) {
// Send reject message.
reject(order, "[ExecutionServer] Invalid routing name: " + order.getRoute());
return;
} {
// Check if order already exists.
if (database.exists(order)) {
// Send reject message.
reject(order, "[ExecutionServer] Duplicated order id.");
continue;
}
// Send message "accepted" to client.
orderUpdate(order.getId(), order.getQty(), OrderStatus.ACCEPTED);
// Change order status.
order.setOrderStatus(OrderStatus.ACCEPTED);
database.insert(order);
}
break;
case RequestTypes.REQUEST_REPLACE: {
// Retrieve order from database.
ExecutionOrder dbOrder = database.find(order.getId());
// Check if order exists.
if (dbOrder == null) {
// Send reject message.
reject(order, "[ExecutionServer] Order id not found.");
continue;
}
// Sending status of pending replace.
orderUpdate(order.getId(), order.getQty(), OrderStatus.PENDING_REPLACE);
// Update order in the database for cancel
dbOrder.setPrice(order.getPrice());
dbOrder.setQty(order.getQty());
dbOrder.setReqType(order.getReqType());
dbOrder.setStatus(order.getStatus());
dbOrder.setOrderStatus(OrderStatus.PENDING_REPLACE);
database.update(dbOrder);
}
break;
case RequestTypes.REQUEST_CANCEL: {
ExecutionOrder dbOrder = database.find(order.getId());
// Check if order exists.
if (dbOrder == null) {
// Send accepted for bidding message.
reject(order, "[ExecutionServer] Order id not found.");
continue;
}
// Check if order is already filled.
if (dbOrder.getOrderStatus() == OrderStatus.FILLED) {
// Send accepted for bidding message.
reject(order, "[ExecutionServer] Order [ " + order.getId() + "] is filled can't be canceled.");
continue;
}
// Check if order is already filled.
if (dbOrder.getOrderStatus() == OrderStatus.CANCELED) {
// Send accepted for bidding message.
reject(order, "[ExecutionServer] Order [ " + order.getId() + "] is already canceled.");
continue;
}
// Sending status of pending cancel.
orderUpdate(order.getId(), order.getQty(), OrderStatus.PENDING_CANCEL);
// Update order in the database for cancel
dbOrder.setReqType(order.getReqType());
dbOrder.setStatus(order.getStatus());
dbOrder.setOrderStatus(OrderStatus.PENDING_CANCEL);
database.update(dbOrder);
}
break;
}
}
ExecutionServerController.orderNotify();
}
}
}
private void orderUpdate(String id, double qtyRemaining, int orderStatus) {
BasicBSONObject order = new BasicBSONObject();
order.put("OrderId", id);
order.put("Status", orderStatus);
order.put("QtyRemaining", qtyRemaining);
// creating and sending the order update message.
Map<String, Object> args = new HashMap<String, Object>();
args.put("Order", order);
BasicBSONObject response = new BasicBSONObject();
response.put("Handler", "OrderUpdate");
response.put("Args", args);
session.write(response);
}
}