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​
| Aspect | Order Visualization (this guide) | Order Tracking |
|---|---|---|
| Purpose | Display events on chart | Manage trading logic |
| Data Source | DataStructureInterface | OrdersListener callbacks |
| Use Case | Post-hoc analysis, visual feedback | Real-time order management |
| Reference | This guide | BookmapOrderTrackingSystem.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 eventsOrderUpdatedEvent- 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​
| Field | Type | Description |
|---|---|---|
alias | String | Instrument alias |
executionInfo | ExecutionInfo | Execution details |
executionInfo.orderId | String | Order ID |
executionInfo.price | double | Execution price |
executionInfo.size | int | Executed quantity |
executionInfo.time | long | Execution timestamp |
executionInfo.isSimulated | boolean | True if simulated |
OrderUpdatedEvent Fields​
| Field | Type | Description |
|---|---|---|
alias | String | Instrument alias |
orderInfoUpdate | OrderInfoUpdate | Order details |
orderInfoUpdate.orderId | String | Order ID |
orderInfoUpdate.status | OrderStatus | Current status |
orderInfoUpdate.isBuy | boolean | True if buy order |
orderInfoUpdate.limitPrice | double | Limit price |
orderInfoUpdate.stopPrice | double | Stop price |
orderInfoUpdate.type | OrderType | Order 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:
- Track
orderId → aliasmapping inonOrderUpdated - Look up the alias when
onOrderExecutedis 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:
| Status | Description |
|---|---|
PENDING_SUBMIT | Order being sent |
WORKING | Order active in market |
FILLED | Order completely filled |
CANCELLED | Order cancelled by user |
REJECTED | Order rejected by exchange |
DISCONNECTED | Order state unknown due to disconnect |
PENDING_CANCEL | Cancel request pending |
PENDING_MODIFY | Modify 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​
- BookmapOrderTrackingSystem - For trading logic, NOT visualization
- OrderUpdatesExecutionsAggregationEvent - Aggregation event class
- OrderExecutedEvent - Execution event class
- OrderUpdatedEvent - Update event class
- DataStructureInterface - Data retrieval interface
- DataStructureInterface.StandardEvents - Standard event types
- ChartVisualizationGuide - Marker rendering basics