Skip to main content

Order Management

This guide covers order management in Bookmap add-ons, including tracking active orders, handling order state changes, processing executions, and cancelling orders. Proper order management is critical for risk management - without it, trading strategies will fail.

Required Annotation​

Any add-on that places or manages orders must include the @Layer1TradingStrategy annotation:

@Layer1SimpleAttachable
@Layer1TradingStrategy // REQUIRED for order operations
@Layer1StrategyName("My Trading Strategy")
@Layer1ApiVersion(Layer1ApiVersionValue.VERSION2)
public class MyStrategy implements CustomModule, OrdersListener {
// ...
}

Without @Layer1TradingStrategy, order-related API methods will not function.

OrdersListener Interface​

Implement OrdersListener to receive order updates and execution notifications.

public interface OrdersListener {
void onOrderUpdated(OrderInfoUpdate orderInfoUpdate);
void onOrderExecuted(ExecutionInfo executionInfo);
}

onOrderUpdated​

Called when any order state changes occur:

  • Order placed (new order acknowledged)
  • Order modified (price or size changed)
  • Order partially filled
  • Order fully filled
  • Order cancelled
  • Order rejected
@Override
public void onOrderUpdated(OrderInfoUpdate orderInfoUpdate) {
String orderId = orderInfoUpdate.orderId;
OrderStatus status = orderInfoUpdate.status;

Log.info("Order " + orderId + " status: " + status);
}

onOrderExecuted​

Called specifically when an order fills (partial or complete execution):

@Override
public void onOrderExecuted(ExecutionInfo executionInfo) {
String orderId = executionInfo.orderId;
double price = executionInfo.price;
int size = executionInfo.size;

Log.info("Execution: " + size + " @ " + price);
}

Order State Tracking​

Maintain a map of active orders to track working orders in real-time.

Basic Order Tracking Pattern​

private Map<String, OrderInfo> activeOrders = new HashMap<>();

@Override
public void onOrderUpdated(OrderInfoUpdate orderInfoUpdate) {
String orderId = orderInfoUpdate.orderId;
boolean isActive = orderInfoUpdate.status.isActive();

if (isActive) {
activeOrders.put(orderId, orderInfoUpdate);
} else {
activeOrders.remove(orderId);
}
}

Thread-Safe Order Tracking​

For add-ons with multiple threads or complex logic, use concurrent collections:

private final ConcurrentHashMap<String, OrderInfo> activeOrders = new ConcurrentHashMap<>();

@Override
public void onOrderUpdated(OrderInfoUpdate orderInfoUpdate) {
String orderId = orderInfoUpdate.orderId;

if (orderInfoUpdate.status.isActive()) {
activeOrders.put(orderId, orderInfoUpdate);
} else {
activeOrders.remove(orderId);
}
}

Tracking by Order Type​

Separate tracking for different order purposes:

private final Map<String, OrderInfo> entryOrders = new HashMap<>();
private final Map<String, OrderInfo> stopLossOrders = new HashMap<>();
private final Map<String, OrderInfo> takeProfitOrders = new HashMap<>();

@Override
public void onOrderUpdated(OrderInfoUpdate orderInfoUpdate) {
String orderId = orderInfoUpdate.orderId;
boolean isActive = orderInfoUpdate.status.isActive();

// Determine order type and track accordingly
if (isEntryOrder(orderId)) {
updateOrderMap(entryOrders, orderId, orderInfoUpdate, isActive);
} else if (isStopLoss(orderId)) {
updateOrderMap(stopLossOrders, orderId, orderInfoUpdate, isActive);
} else if (isTakeProfit(orderId)) {
updateOrderMap(takeProfitOrders, orderId, orderInfoUpdate, isActive);
}
}

private void updateOrderMap(Map<String, OrderInfo> map, String orderId,
OrderInfo order, boolean isActive) {
if (isActive) {
map.put(orderId, order);
} else {
map.remove(orderId);
}
}

OrderInfo Properties​

The OrderInfo class contains essential order information:

PropertyTypeDescription
orderIdStringUnique identifier for the order
limitPricedoubleLimit price (raw format, divide by pips for display)
stopPricedoubleStop price for stop orders
sizeintOrder quantity
filledintQuantity already filled
unfilledintRemaining quantity
isBuybooleantrue = buy order, false = sell order
statusOrderStatusCurrent order status
instrumentAliasStringInstrument identifier

OrderInfoUpdate​

OrderInfoUpdate extends OrderInfo with additional update context. It can be stored as OrderInfo since it inherits all properties.

OrderStatus​

The OrderStatus enum represents order states:

StatusDescriptionisActive()
PENDING_SUBMITOrder being senttrue
WORKINGOrder active in markettrue
PENDING_CANCELCancel request senttrue
PENDING_MODIFYModify request senttrue
CANCELLEDOrder cancelledfalse
FILLEDOrder completely filledfalse
REJECTEDOrder rejectedfalse

Checking Order State​

@Override
public void onOrderUpdated(OrderInfoUpdate orderInfoUpdate) {
OrderStatus status = orderInfoUpdate.status;

if (status.isActive()) {
// Order is still working
}

if (status == OrderStatus.FILLED) {
// Order completely filled
onOrderFilled(orderInfoUpdate);
}

if (status == OrderStatus.REJECTED) {
// Order was rejected - handle error
onOrderRejected(orderInfoUpdate);
}

if (status == OrderStatus.CANCELLED) {
// Order was cancelled
onOrderCancelled(orderInfoUpdate);
}
}

Cancelling Orders​

Use OrderCancelParameters to cancel orders through the API.

Cancel Single Order​

private Api api;

private void cancelOrder(String orderId) {
api.updateOrder(new OrderCancelParameters(orderId));
}

Cancel All Active Orders​

private void cancelAllOrders() {
for (String orderId : activeOrders.keySet()) {
api.updateOrder(new OrderCancelParameters(orderId));
}
}

Cancel Orders by Side​

private void cancelBuyOrders() {
for (OrderInfo order : activeOrders.values()) {
if (order.isBuy) {
api.updateOrder(new OrderCancelParameters(order.orderId));
}
}
}

private void cancelSellOrders() {
for (OrderInfo order : activeOrders.values()) {
if (!order.isBuy) {
api.updateOrder(new OrderCancelParameters(order.orderId));
}
}
}

Conditional Cancellation​

Cancel orders based on market conditions:

@Override
public void onBbo(int bidPrice, int bidSize, int askPrice, int askSize) {
for (OrderInfo order : activeOrders.values()) {
double orderLevel = order.limitPrice / pips;

boolean tooCloseToMarket = order.isBuy
? bidPrice - orderLevel <= CANCEL_DISTANCE
: orderLevel - askPrice <= CANCEL_DISTANCE;

if (tooCloseToMarket) {
api.updateOrder(new OrderCancelParameters(order.orderId));
}
}
}

ExecutionInfo Properties​

The ExecutionInfo class provides execution details:

PropertyTypeDescription
orderIdStringOrder that was executed
pricedoubleExecution price (raw format)
sizeintExecuted quantity
isSimulatedbooleantrue if simulated execution

Processing Executions​

private double totalPnL = 0;
private int position = 0;

@Override
public void onOrderExecuted(ExecutionInfo executionInfo) {
double execPrice = executionInfo.price * pips; // Convert to display price
int execSize = executionInfo.size;

// Find the order to determine direction
OrderInfo order = activeOrders.get(executionInfo.orderId);
if (order != null) {
if (order.isBuy) {
position += execSize;
} else {
position -= execSize;
}
}

Log.info("Position: " + position + " after execution @ " + execPrice);
}

Complete Example​

@Layer1SimpleAttachable
@Layer1TradingStrategy
@Layer1StrategyName("Order Manager Demo")
@Layer1ApiVersion(Layer1ApiVersionValue.VERSION2)
public class OrderManagerDemo implements CustomModule, OrdersListener {

private Api api;
private double pips;
private final Map<String, OrderInfo> activeOrders = new HashMap<>();
private int position = 0;

@Override
public void initialize(String alias, InstrumentInfo info, Api api, InitialState initialState) {
this.api = api;
this.pips = info.pips;
Log.info("Order Manager initialized for " + alias);
}

@Override
public void onOrderUpdated(OrderInfoUpdate orderInfoUpdate) {
String orderId = orderInfoUpdate.orderId;
OrderStatus status = orderInfoUpdate.status;

// Log state change
double displayPrice = orderInfoUpdate.limitPrice * pips;
Log.info(String.format("Order %s: %s @ %.2f, Status: %s",
orderId,
orderInfoUpdate.isBuy ? "BUY" : "SELL",
displayPrice,
status));

// Update tracking map
if (status.isActive()) {
activeOrders.put(orderId, orderInfoUpdate);
} else {
activeOrders.remove(orderId);

// Handle terminal states
if (status == OrderStatus.REJECTED) {
handleRejection(orderInfoUpdate);
}
}

Log.info("Active orders: " + activeOrders.size());
}

@Override
public void onOrderExecuted(ExecutionInfo executionInfo) {
OrderInfo order = activeOrders.get(executionInfo.orderId);

if (order != null) {
int size = executionInfo.size;
position += order.isBuy ? size : -size;

double execPrice = executionInfo.price * pips;
Log.info(String.format("FILL: %d @ %.2f | Position: %d",
size, execPrice, position));
}
}

private void handleRejection(OrderInfoUpdate order) {
Log.warn("Order rejected: " + order.orderId);
// Implement rejection handling logic
}

public void cancelAllOrders() {
Log.info("Cancelling " + activeOrders.size() + " orders");
for (String orderId : new ArrayList<>(activeOrders.keySet())) {
api.updateOrder(new OrderCancelParameters(orderId));
}
}

public int getActiveOrderCount() {
return activeOrders.size();
}

public int getPosition() {
return position;
}

@Override
public void stop() {
cancelAllOrders();
Log.info("Order Manager stopped. Final position: " + position);
}
}

Risk Management Considerations​

Always Track Orders​

Never assume order state - always track via onOrderUpdated:

// WRONG - assuming order is filled after sending
api.sendOrder(orderParams);
position += orderSize; // Dangerous assumption!

// CORRECT - update position only on confirmed execution
@Override
public void onOrderExecuted(ExecutionInfo executionInfo) {
position += executionInfo.size * (order.isBuy ? 1 : -1);
}

Handle Rejections​

Always implement rejection handling:

if (status == OrderStatus.REJECTED) {
Log.error("Order rejected: " + orderId);
// Alert user, adjust strategy, or retry
}

Cancel on Stop​

Clean up orders when add-on stops:

@Override
public void stop() {
for (String orderId : activeOrders.keySet()) {
api.updateOrder(new OrderCancelParameters(orderId));
}
}

Validate Before Sending​

Check conditions before placing orders:

private boolean canPlaceOrder() {
// Check position limits
if (Math.abs(position) >= MAX_POSITION) {
return false;
}

// Check active order limits
if (activeOrders.size() >= MAX_ACTIVE_ORDERS) {
return false;
}

return true;
}

See Also​