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:
| Property | Type | Description |
|---|---|---|
orderId | String | Unique identifier for the order |
limitPrice | double | Limit price (raw format, divide by pips for display) |
stopPrice | double | Stop price for stop orders |
size | int | Order quantity |
filled | int | Quantity already filled |
unfilled | int | Remaining quantity |
isBuy | boolean | true = buy order, false = sell order |
status | OrderStatus | Current order status |
instrumentAlias | String | Instrument 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:
| Status | Description | isActive() |
|---|---|---|
PENDING_SUBMIT | Order being sent | true |
WORKING | Order active in market | true |
PENDING_CANCEL | Cancel request sent | true |
PENDING_MODIFY | Modify request sent | true |
CANCELLED | Order cancelled | false |
FILLED | Order completely filled | false |
REJECTED | Order rejected | false |
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:
| Property | Type | Description |
|---|---|---|
orderId | String | Order that was executed |
price | double | Execution price (raw format) |
size | int | Executed quantity |
isSimulated | boolean | true 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​
- Order Placement - Sending new orders
- Api Interface - Api methods for order operations
- Historical Data - Handling orders during replay
- Javadoc: OrdersListener
- Javadoc: OrderInfo
- Javadoc: OrderStatus