Skip to main content

Order Event Visualization Guide

This guide covers visualizing order and execution events on Bookmap charts. This is distinct from order tracking for trading logic (see BookmapOrderTrackingSystem.md for that use case).

Overview​

Purpose​

This guide focuses on displaying order events visually:

  • Execution markers showing where fills occurred
  • Cancelled order indicators
  • Order flow visualization

Distinction from Order Tracking​

AspectOrder Visualization (this guide)Order Tracking
PurposeDisplay events on chartManage trading logic
Data SourceDataStructureInterfaceOrdersListener callbacks
Use CasePost-hoc analysis, visual feedbackReal-time order management
ReferenceThis guideBookmapOrderTrackingSystem.md

Accessing Order Events via DataStructureInterface​

StandardEvents.ORDER​

Order events are retrieved using StandardEvents.ORDER:

ArrayList<TreeResponseInterval> intervalResponse = dataStructureInterface.get(
t0, // Start time (nanoseconds)
intervalWidth, // Interval duration (nanoseconds)
intervalsNumber, // Number of intervals
alias, // Instrument alias
new StandardEvents[] { StandardEvents.ORDER } // Request order events
);

Extracting from TreeResponseInterval​

The order events are stored as OrderUpdatesExecutionsAggregationEvent:

for (int i = 1; i <= intervalsNumber; ++i) {
OrderUpdatesExecutionsAggregationEvent orders =
(OrderUpdatesExecutionsAggregationEvent) intervalResponse.get(i)
.events.get(StandardEvents.ORDER.toString());

// Process the orders...
}

Processing OrderUpdatesExecutionsAggregationEvent​

Event Structure​

The OrderUpdatesExecutionsAggregationEvent contains a list orderUpdates with mixed types:

public final List<Event> orderUpdates;

This list can contain:

  • OrderExecutedEvent - Order fill events
  • OrderUpdatedEvent - Order status change events

Distinguishing Event Types​

for (Object object : orders.orderUpdates) {
if (object instanceof OrderExecutedEvent) {
OrderExecutedEvent execEvent = (OrderExecutedEvent) object;

// Access execution data
String alias = execEvent.alias;
ExecutionInfo execInfo = execEvent.executionInfo;
double price = execInfo.price;
int size = execInfo.size;
String orderId = execInfo.orderId;
long time = execInfo.time;
boolean isSimulated = execInfo.isSimulated;

} else if (object instanceof OrderUpdatedEvent) {
OrderUpdatedEvent updateEvent = (OrderUpdatedEvent) object;

// Access order update data
String alias = updateEvent.alias;
OrderInfoUpdate orderInfo = updateEvent.orderInfoUpdate;
OrderStatus status = orderInfo.status;
boolean isBuy = orderInfo.isBuy;
double limitPrice = orderInfo.limitPrice;
double stopPrice = orderInfo.stopPrice;
OrderType type = orderInfo.type;
}
}

OrderExecutedEvent Fields​

FieldTypeDescription
aliasStringInstrument alias
executionInfoExecutionInfoExecution details
executionInfo.orderIdStringOrder ID
executionInfo.pricedoubleExecution price
executionInfo.sizeintExecuted quantity
executionInfo.timelongExecution timestamp
executionInfo.isSimulatedbooleanTrue if simulated

OrderUpdatedEvent Fields​

FieldTypeDescription
aliasStringInstrument alias
orderInfoUpdateOrderInfoUpdateOrder details
orderInfoUpdate.orderIdStringOrder ID
orderInfoUpdate.statusOrderStatusCurrent status
orderInfoUpdate.isBuybooleanTrue if buy order
orderInfoUpdate.limitPricedoubleLimit price
orderInfoUpdate.stopPricedoubleStop price
orderInfoUpdate.typeOrderTypeOrder type

Creating Markers for Order Events​

Execution Marker Placement​

From Layer1ApiMarkersDemo.java:

for (Object object : orders.orderUpdates) {
if (object instanceof OrderExecutedEvent) {
OrderExecutedEvent orderExecutedEvent = (OrderExecutedEvent) object;

// Convert price to level number for PRIMARY chart
double priceLevel = orderExecutedEvent.executionInfo.price /
pipsMap.getOrDefault(orderExecutedEvent.alias, 1.0);

result.add(new Marker(
priceLevel,
-orderIcon.getHeight() / 2, // Center vertically
-orderIcon.getWidth() / 2, // Center horizontally
orderIcon
));
}
}

Cancelled Order Marker Placement​

for (Object object : orders.orderUpdates) {
if (object instanceof OrderUpdatedEvent) {
OrderUpdatedEvent orderUpdatedEvent = (OrderUpdatedEvent) object;

// Only mark cancelled orders
if (orderUpdatedEvent.orderInfoUpdate.status == OrderStatus.CANCELLED) {
double priceLevel = getActivePrice(orderUpdatedEvent.orderInfoUpdate) /
pipsMap.getOrDefault(orderUpdatedEvent.alias, 1.0);

result.add(new Marker(
priceLevel,
-orderIcon.getHeight() / 2,
-orderIcon.getWidth() / 2,
orderIcon
));
}
}
}

Price Extraction for Different Order Types​

The "active price" depends on the order type:

private double getActivePrice(OrderInfoUpdate orderInfoUpdate) {
// Stop orders use stopPrice, others use limitPrice
return (orderInfoUpdate.type == OrderType.STP ||
orderInfoUpdate.type == OrderType.STP_LMT)
? orderInfoUpdate.stopPrice
: orderInfoUpdate.limitPrice;
}

Real-time Order Visualization​

Using OnlineValueCalculatorAdapter​

For real-time visualization, implement callbacks in OnlineValueCalculatorAdapter:

@Override
public OnlineValueCalculatorAdapter createOnlineValueCalculator(
String indicatorName, String indicatorAlias, long time,
Consumer<Object> listener, InvalidateInterface invalidateInterface) {

BufferedImage orderIcon = orderIcons.get(indicatorAlias);

return new OnlineValueCalculatorAdapter() {

// Map order IDs to their instrument aliases
private Map<String, String> orderIdToAlias = new HashMap<>();

@Override
public void onOrderExecuted(ExecutionInfo executionInfo) {
// Look up the alias for this order
String alias = orderIdToAlias.get(executionInfo.orderId);

if (alias != null && alias.equals(indicatorAlias)) {
Double pips = pipsMap.get(alias);
if (pips != null) {
listener.accept(new Marker(
executionInfo.price / pips, // Convert to level number
-orderIcon.getHeight() / 2,
-orderIcon.getWidth() / 2,
orderIcon
));
} else {
Log.info("Unknown pips for instrument " + alias);
}
}
}

@Override
public void onOrderUpdated(OrderInfoUpdate orderInfoUpdate) {
// Track order ID to alias mapping
orderIdToAlias.put(orderInfoUpdate.orderId, orderInfoUpdate.instrumentAlias);

// Check for cancelled/disconnected orders
if (orderInfoUpdate.instrumentAlias.equals(indicatorAlias)) {
if (orderInfoUpdate.status == OrderStatus.CANCELLED ||
orderInfoUpdate.status == OrderStatus.DISCONNECTED) {

Double pips = pipsMap.get(orderInfoUpdate.instrumentAlias);
if (pips != null) {
listener.accept(new Marker(
getActivePrice(orderInfoUpdate) / pips,
-orderIcon.getHeight() / 2,
-orderIcon.getWidth() / 2,
orderIcon
));
} else {
Log.info("Unknown pips for instrument " + orderInfoUpdate.instrumentAlias);
}
}
}
}
};
}

Why Track orderIdToAlias?​

The onOrderExecuted callback provides ExecutionInfo which contains the orderId but not the instrument alias. To know which instrument an execution belongs to:

  1. Track orderId → alias mapping in onOrderUpdated
  2. Look up the alias when onOrderExecuted is called
// In onOrderUpdated - always called before execution
orderIdToAlias.put(orderInfoUpdate.orderId, orderInfoUpdate.instrumentAlias);

// In onOrderExecuted - look up the alias
String alias = orderIdToAlias.get(executionInfo.orderId);

Complete Example: Execution and Cancellation Markers​

@Layer1Attachable
@Layer1StrategyName("Order Visualization Demo")
@Layer1ApiVersion(Layer1ApiVersionValue.VERSION2)
public class OrderVisualizationDemo implements
Layer1ApiFinishable,
Layer1ApiAdminAdapter,
Layer1ApiInstrumentListener,
OnlineCalculatable {

private static final String INDICATOR_NAME = "Order Events";

private Layer1ApiProvider provider;
private DataStructureInterface dataStructureInterface;
private Map<String, Double> pipsMap = new ConcurrentHashMap<>();
private Map<String, String> indicatorsFullNameToUserName = new HashMap<>();
private Map<String, BufferedImage> orderIcons = Collections.synchronizedMap(new HashMap<>());

// Different icons for different event types
private BufferedImage executionIcon;
private BufferedImage cancelledIcon;

public OrderVisualizationDemo(Layer1ApiProvider provider) {
this.provider = provider;
ListenableHelper.addListeners(provider, this);

// Create execution icon (green circle)
executionIcon = new BufferedImage(12, 12, BufferedImage.TYPE_INT_ARGB);
Graphics g = executionIcon.getGraphics();
g.setColor(Color.GREEN);
g.fillOval(0, 0, 11, 11);

// Create cancelled icon (red X)
cancelledIcon = new BufferedImage(12, 12, BufferedImage.TYPE_INT_ARGB);
g = cancelledIcon.getGraphics();
g.setColor(Color.RED);
g.drawLine(0, 0, 11, 11);
g.drawLine(11, 0, 0, 11);
}

@Override
public void onUserMessage(Object data) {
if (data instanceof UserMessageLayersChainCreatedTargeted) {
UserMessageLayersChainCreatedTargeted msg = (UserMessageLayersChainCreatedTargeted) data;
if (msg.targetClass == getClass()) {
provider.sendUserMessage(new Layer1ApiDataInterfaceRequestMessage(
dsi -> this.dataStructureInterface = dsi
));

Layer1ApiUserMessageModifyIndicator indicatorMsg = Layer1ApiUserMessageModifyIndicator
.builder(OrderVisualizationDemo.class, INDICATOR_NAME)
.setIsAdd(true)
.setGraphType(GraphType.PRIMARY)
.setOnlineCalculatable(this)
.setIndicatorLineStyle(IndicatorLineStyle.NONE)
.build();

indicatorsFullNameToUserName.put(indicatorMsg.fullName, indicatorMsg.userName);
provider.sendUserMessage(indicatorMsg);
}
}
}

@Override
public void onInstrumentAdded(String alias, InstrumentInfo instrumentInfo) {
pipsMap.put(alias, instrumentInfo.pips);
}

@Override
public void calculateValuesInRange(String indicatorName, String alias,
long t0, long intervalWidth, int intervalsNumber, CalculatedResultListener listener) {

if (dataStructureInterface == null) {
listener.setCompleted();
return;
}

ArrayList<TreeResponseInterval> intervalResponse = dataStructureInterface.get(
t0, intervalWidth, intervalsNumber, alias,
new StandardEvents[] { StandardEvents.ORDER }
);

Double pips = pipsMap.getOrDefault(alias, 1.0);

for (int i = 1; i <= intervalsNumber; ++i) {
OrderUpdatesExecutionsAggregationEvent orders =
(OrderUpdatesExecutionsAggregationEvent) intervalResponse.get(i)
.events.get(StandardEvents.ORDER.toString());

ArrayList<Marker> result = new ArrayList<>();

for (Object object : orders.orderUpdates) {
if (object instanceof OrderExecutedEvent) {
OrderExecutedEvent execEvent = (OrderExecutedEvent) object;
double priceLevel = execEvent.executionInfo.price / pips;

result.add(new Marker(
priceLevel,
-executionIcon.getHeight() / 2,
-executionIcon.getWidth() / 2,
executionIcon
));

} else if (object instanceof OrderUpdatedEvent) {
OrderUpdatedEvent updateEvent = (OrderUpdatedEvent) object;

if (updateEvent.orderInfoUpdate.status == OrderStatus.CANCELLED ||
updateEvent.orderInfoUpdate.status == OrderStatus.REJECTED) {

double priceLevel = getActivePrice(updateEvent.orderInfoUpdate) / pips;

result.add(new Marker(
priceLevel,
-cancelledIcon.getHeight() / 2,
-cancelledIcon.getWidth() / 2,
cancelledIcon
));
}
}
}

listener.provideResponse(result.isEmpty() ? Double.NaN : result);
}

listener.setCompleted();
}

@Override
public OnlineValueCalculatorAdapter createOnlineValueCalculator(
String indicatorName, String indicatorAlias, long time,
Consumer<Object> listener, InvalidateInterface invalidateInterface) {

Double pips = pipsMap.get(indicatorAlias);

return new OnlineValueCalculatorAdapter() {
private Map<String, String> orderIdToAlias = new HashMap<>();
private Map<String, Boolean> orderIdToBuy = new HashMap<>();

@Override
public void onOrderExecuted(ExecutionInfo executionInfo) {
String alias = orderIdToAlias.get(executionInfo.orderId);
if (alias != null && alias.equals(indicatorAlias) && pips != null) {
listener.accept(new Marker(
executionInfo.price / pips,
-executionIcon.getHeight() / 2,
-executionIcon.getWidth() / 2,
executionIcon
));
}
}

@Override
public void onOrderUpdated(OrderInfoUpdate orderInfoUpdate) {
orderIdToAlias.put(orderInfoUpdate.orderId, orderInfoUpdate.instrumentAlias);
orderIdToBuy.put(orderInfoUpdate.orderId, orderInfoUpdate.isBuy);

if (orderInfoUpdate.instrumentAlias.equals(indicatorAlias)) {
if (orderInfoUpdate.status == OrderStatus.CANCELLED ||
orderInfoUpdate.status == OrderStatus.REJECTED ||
orderInfoUpdate.status == OrderStatus.DISCONNECTED) {

if (pips != null) {
listener.accept(new Marker(
getActivePrice(orderInfoUpdate) / pips,
-cancelledIcon.getHeight() / 2,
-cancelledIcon.getWidth() / 2,
cancelledIcon
));
}
}
}
}
};
}

private double getActivePrice(OrderInfoUpdate orderInfoUpdate) {
return (orderInfoUpdate.type == OrderType.STP ||
orderInfoUpdate.type == OrderType.STP_LMT)
? orderInfoUpdate.stopPrice
: orderInfoUpdate.limitPrice;
}

@Override
public void finish() {
provider.sendUserMessage(new Layer1ApiUserMessageModifyIndicator(
OrderVisualizationDemo.class, INDICATOR_NAME, false
));
}

@Override public void onInstrumentRemoved(String alias) {}
@Override public void onInstrumentNotFound(String symbol, String exchange, String type) {}
@Override public void onInstrumentAlreadySubscribed(String symbol, String exchange, String type) {}
}

OrderStatus Values​

Common status values for filtering:

StatusDescription
PENDING_SUBMITOrder being sent
WORKINGOrder active in market
FILLEDOrder completely filled
CANCELLEDOrder cancelled by user
REJECTEDOrder rejected by exchange
DISCONNECTEDOrder state unknown due to disconnect
PENDING_CANCELCancel request pending
PENDING_MODIFYModify request pending

Filtering Example​

// Only show terminal states
if (orderInfoUpdate.status == OrderStatus.CANCELLED ||
orderInfoUpdate.status == OrderStatus.REJECTED ||
orderInfoUpdate.status == OrderStatus.FILLED) {
// Create marker
}

Cross-References​