Skip to main content

Bookmap API Order Tracking System

Document Purpose​

This document provides comprehensive documentation on how to implement self-sufficient order tracking in Bookmap add-ons. It covers the critical clientId correlation mechanism, the architectural constraints of the API, and production-ready implementation patterns.


Table of Contents​

  1. Executive Summary
  2. Core Concepts
  3. The clientId Mechanism
  4. API Callback Architecture
  5. The Correlation Challenge
  6. Order Lifecycle States
  7. Bracket Order Tracking
  8. Implementation Patterns
  9. Complete Code Examples
  10. Edge Cases and Error Handling
  11. Quick Reference

1. Executive Summary​

Key Insight​

The Bookmap API provides two order identification mechanisms:

IdentifierSourcePurpose
orderIdBroker/ExchangeBroker-assigned unique identifier
clientIdDeveloperClient-defined tracking identifier

Critical Constraint: The clientId is available in OrderInfoUpdate but NOT in ExecutionInfo. This architectural constraint mandates a dual-index tracking approach where developers must establish a brokerId Ò†’ clientId mapping during order updates to correlate executions.

Architecture Requirement​

Ò”ŒÒ”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”
Γ’β€β€š MANDATORY DUAL-INDEX SYSTEM Γ’β€β€š
Ò”œÒ”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€
Γ’β€β€š PRIMARY INDEX: clientId Ò†’ InternalOrderRecord Γ’β€β€š
Γ’β€β€š BRIDGE INDEX: brokerId Ò†’ clientId (for ExecutionInfo) Γ’β€β€š
Γ’β€β€Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€Λœ

2. Core Concepts​

2.1 What is clientId?​

The clientId is a developer-defined string identifier that allows tracking and correlation of orders independent of broker-assigned IDs.

Source: SingleOrderSendParameters.clientId

/**
* Allows to match order submission request to a response.
* Randomly generated id 24 characters long - this allows to fit it
* into most tag fields
*/
public final String clientId

2.2 Where clientId Appears​

ClassField/MethodContext
SingleOrderSendParametersclientIdBase order parameter
SimpleOrderSendParameterstakeProfitClientIdBracket TP order
SimpleOrderSendParametersstopLossClientIdBracket SL order
BracketTierclientIdMulti-tier bracket
OrderInfoclientIdOrder state information
OrderInfoUpdateclientIdOrder update callback
AbstractSingleOrderSendParametersBuildersetClientId(String)Builder method
AbstractSimpleOrderSendParametersBuildersetTakeProfitClientId(String)Builder method
AbstractSimpleOrderSendParametersBuildersetStopLossClientId(String)Builder method

2.3 Where clientId Does NOT Appear​

Critical: ExecutionInfo does not contain clientId.

ExecutionInfo fields:

  • orderId (String) - Broker ID only
  • size (int) - Execution size
  • price (double) - Execution price
  • executionId (String) - Unique execution identifier
  • time (long) - Execution timestamp
  • isSimulated (boolean) - Simulation flag

This omission is the fundamental reason for the dual-index architecture.


3. The clientId Mechanism​

3.1 Setting clientId on Order Submission​

SimpleOrderSendParametersBuilder builder = 
new SimpleOrderSendParametersBuilder(alias, isBuy, quantity);

// Set main order clientId
builder.setClientId("STRATEGY_001_ENTRY");

// Set bracket order clientIds
builder.setTakeProfitClientId("STRATEGY_001_TP");
builder.setStopLossClientId("STRATEGY_001_SL");

// Configure order parameters
builder.setDuration(OrderDuration.IOC);
builder.setTakeProfitOffset(20);
builder.setStopLossOffset(10);

// Submit order
api.sendOrder(builder.build());

3.2 Receiving clientId in Callbacks​

The clientId you set is returned in OrderInfoUpdate:

@Override
public void onOrderUpdated(OrderInfoUpdate update) {
String brokerId = update.orderId; // Broker assigned: "BRK-12345"
String clientId = update.clientId; // Your ID: "STRATEGY_001_ENTRY"

// Both identifiers available for correlation
}

3.3 clientId Naming Conventions​

Recommended format for structured tracking:

{STRATEGY}_{TIMESTAMP}_{SEQUENCE}_{TYPE}

Examples:
Entry Order: "EMA_CROSS_18D5F3A2B_0001_ENTRY"
Take Profit: "EMA_CROSS_18D5F3A2B_0001_TP"
Stop Loss: "EMA_CROSS_18D5F3A2B_0001_SL"
Bracket Tier 2: "EMA_CROSS_18D5F3A2B_0001_TP2"

Benefits:

  • Strategy identification
  • Timestamp for debugging
  • Sequence for uniqueness
  • Type for role identification
  • Family grouping via common prefix

4. API Callback Architecture​

4.1 OrdersListener Interface​

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

4.2 Callback Data Comparison​

FieldOrderInfoUpdateExecutionInfo
orderIdÒœ… AvailableÒœ… Available
clientIdÒœ… AvailableҝŒ NOT Available
statusÒœ… AvailableN/A
filledÒœ… AvailableN/A
unfilledÒœ… AvailableN/A
limitPriceÒœ… AvailableN/A
stopPriceÒœ… AvailableN/A
sizeN/AÒœ… Available
priceN/AÒœ… Available
executionIdN/AÒœ… Available
timeN/AÒœ… Available
isSimulatedÒœ… AvailableÒœ… Available

4.3 Guaranteed Callback Sequence​

Per Bookmap documentation:

Order with ExecutionInfo.executionId is supposed to exist (you should get at least one onOrderUpdated(OrderInfoUpdate) for that order first).

This guarantees:

  1. onOrderUpdated is called BEFORE onOrderExecuted
  2. Developers can establish correlation before executions arrive
  3. No execution will arrive for an unknown order

4.4 Callback Sequence Diagram​

TIME Ò†’
Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€

Your Code Bookmap Broker/Exchange
Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€ Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€ Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€
Γ’β€β€š Γ’β€β€š Γ’β€β€š
Γ’β€β€š 1. sendOrder() Γ’β€β€š Γ’β€β€š
Γ’β€β€š clientId="ABC_001" Γ’β€β€š Γ’β€β€š
Γ’β€β€š Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€“ΒΊΓ’β€β€š Γ’β€β€š
Γ’β€β€š Γ’β€β€š 2. Submit Γ’β€β€š
Γ’β€β€š Γ’β€β€š Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€“ΒΊΓ’β€β€š
Γ’β€β€š Γ’β€β€š Γ’β€β€š
Γ’β€β€š Γ’β€β€š 3. Acknowledge Γ’β€β€š
Γ’β€β€š Γ’β€β€š orderId="BRK-789" Γ’β€β€š
Γ’β€β€š Γ’β€β€šΓ’β€”β€žΓ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬ Γ’β€β€š
Γ’β€β€š Γ’β€β€š Γ’β€β€š
Γ’β€β€š 4. onOrderUpdated() Γ’β€β€š Γ’β€β€š
Γ’β€β€š orderId="BRK-789" Γ’β€β€š Γ’β€β€š
Γ’β€β€š clientId="ABC_001" Γ’β€β€š Ò† BOTH IDs AVAILABLE Γ’β€β€š
Γ’β€β€šΓ’β€”β€žΓ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬ Γ’β€β€š Γ’β€β€š
Γ’β€β€š Γ’β€β€š Γ’β€β€š
Γ’β€β€š [ESTABLISH MAPPING]Γ’β€β€š Γ’β€β€š
Γ’β€β€š BRK-789 Ò†’ ABC_001 Γ’β€β€š Γ’β€β€š
Γ’β€β€š Γ’β€β€š Γ’β€β€š
Γ’β€β€š Γ’β€β€š 5. Fill Γ’β€β€š
Γ’β€β€š Γ’β€β€šΓ’β€”β€žΓ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬ Γ’β€β€š
Γ’β€β€š Γ’β€β€š Γ’β€β€š
Γ’β€β€š 6. onOrderExecuted() Γ’β€β€š Γ’β€β€š
Γ’β€β€š orderId="BRK-789" Γ’β€β€š Γ’β€β€š
Γ’β€β€š (NO clientId!) Γ’β€β€š Ò† MUST USE MAPPING Γ’β€β€š
Γ’β€β€šΓ’β€”β€žΓ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬ Γ’β€β€š Γ’β€β€š
Γ’β€β€š Γ’β€β€š Γ’β€β€š
Γ’β€β€š [LOOKUP MAPPING] Γ’β€β€š Γ’β€β€š
Γ’β€β€š BRK-789 Ò†’ ABC_001 Γ’β€β€š Γ’β€β€š
Γ’β€β€š Γ’β€β€š Γ’β€β€š

5. The Correlation Challenge​

5.1 Problem Statement​

When onOrderExecuted is called, you receive ExecutionInfo which contains only orderId (broker ID). To correlate this execution with your internal tracking, you must have previously established a mapping from orderId to your clientId.

5.2 Solution: Dual-Index Registry​

public class OrderRegistry {

// PRIMARY INDEX: Your clientId Ò†’ Order Record
// This is your source of truth
private final ConcurrentHashMap<String, InternalOrderRecord> byClientId;

// BRIDGE INDEX: Broker orderId Ò†’ Your clientId
// Required because ExecutionInfo lacks clientId
private final ConcurrentHashMap<String, String> brokerToClientId;
}

5.3 Correlation Establishment​

@Override
public void onOrderUpdated(OrderInfoUpdate update) {
String brokerId = update.orderId;
String clientId = update.clientId;

// CRITICAL: Establish mapping on first update
if (clientId != null && brokerId != null) {
brokerToClientId.put(brokerId, clientId);
}

// Continue with order state processing...
}

5.4 Execution Lookup​

@Override
public void onOrderExecuted(ExecutionInfo execution) {
String brokerId = execution.orderId;

// ExecutionInfo has NO clientId - must use mapping
String clientId = brokerToClientId.get(brokerId);

if (clientId == null) {
// ORPHANED EXECUTION - correlation failed
handleOrphanedExecution(execution);
return;
}

// Found correlation - process execution
InternalOrderRecord record = byClientId.get(clientId);
processExecution(record, execution);
}

6. Order Lifecycle States​

6.1 Bookmap OrderStatus Enum​

StatusisActive()Description
PENDING_SUBMITtrueOrder being sent to broker
WORKINGtrueOrder active in market
PENDING_CANCELtrueCancel request sent
PENDING_MODIFYtrueModify request sent
SUSPENDEDtrueBracket child awaiting parent fill
CANCELLEDfalseOrder cancelled
FILLEDfalseOrder completely filled
REJECTEDfalseOrder rejected by broker/exchange
DISCONNECTEDfalseConnection lost

6.2 State Transition Diagram​

                              Ò”ŒÒ”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”
Γ’β€β€š CREATED Γ’β€β€š
Γ’β€β€š (Internal) Γ’β€β€š
Γ’β€β€Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€Β¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€Λœ
Γ’β€β€š sendOrder()
Ò–¼
Ò”ŒÒ”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”
Γ’β€β€š PENDING_SUBMIT Γ’β€β€š
Γ’β€β€Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€Β¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€Λœ
Γ’β€β€š
Ò”ŒÒ”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”¼Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”
Γ’β€β€š Γ’β€β€š Γ’β€β€š
Ò–¼ Ò–¼ Ò–¼
Ò”ŒÒ”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò” Ò”ŒÒ”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò” Ò”ŒÒ”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”
Γ’β€β€š REJECTED Γ’β€β€š Γ’β€β€š WORKING Γ’β€β€š Γ’β€β€šSUSPENDED Γ’β€β€š
Γ’β€β€Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€Λœ Γ’β€β€Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€Β¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€Λœ Γ’β€β€Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€Β¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€Λœ
Γ’β€β€š Γ’β€β€š
Ò”ŒÒ”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”¼Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò” Γ’β€β€š
Γ’β€β€š Γ’β€β€š Γ’β€β€š Γ’β€β€š
Ò–¼ Ò–¼ Ò–¼ Γ’β€β€š
Ò”ŒÒ”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò” Ò”ŒÒ”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò” Ò”ŒÒ”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”
Γ’β€β€šPENDING_CANCELΓ’β€β€š Γ’β€β€šPENDING_MODIFYΓ’β€β€š Γ’β€β€š WORKING Γ’β€β€šΓ’β€”β€žΓ’β€Λœ
Γ’β€β€Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€Β¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€Λœ Γ’β€β€Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€Β¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€Λœ Γ’β€β€Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€Β¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€Λœ
Γ’β€β€š Γ’β€β€š Γ’β€β€š
Ò–¼ Ò–¼ Ò–¼
Ò”ŒÒ”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò” Ò”ŒÒ”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò” Ò”ŒÒ”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”
Γ’β€β€š CANCELLED Γ’β€β€š Γ’β€β€š WORKING Γ’β€β€š Γ’β€β€š FILLED Γ’β€β€š
Γ’β€β€Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€Λœ Γ’β€β€Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€Λœ Γ’β€β€Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€Λœ

6.3 Internal State Mapping​

Recommended internal states for enhanced tracking:

public enum InternalOrderState {
// Pre-submission
CREATED, // Record created, not yet sent
PENDING_SUBMISSION, // Sent to API, awaiting acknowledgment

// Active states
ACKNOWLEDGED, // Broker confirmed (maps to PENDING_SUBMIT)
WORKING, // Live in market
PARTIALLY_FILLED, // Some quantity executed

// Modification states
PENDING_CANCEL, // Cancel request sent
PENDING_MODIFY, // Modify request sent

// Terminal states
FILLED, // Completely executed
CANCELLED, // Successfully cancelled
REJECTED, // Broker rejected

// Error states
ORPHANED, // Lost broker correlation
TIMEOUT // No response received
}

7. Bracket Order Tracking​

7.1 Bracket Order Structure​

A bracket order consists of:

  • Entry Order: Main order (market or limit)
  • Take Profit Order: Limit order to exit at profit target
  • Stop Loss Order: Stop order to exit at loss limit

7.2 Bracket clientId Configuration​

// Generate family of related clientIds
String familyId = "EMA_18D5F3A2B_0001";
String entryClientId = familyId + "_ENTRY";
String tpClientId = familyId + "_TP";
String slClientId = familyId + "_SL";

SimpleOrderSendParametersBuilder builder =
new SimpleOrderSendParametersBuilder(alias, true, 1);

builder.setClientId(entryClientId);
builder.setTakeProfitOffset(20);
builder.setTakeProfitClientId(tpClientId);
builder.setStopLossOffset(10);
builder.setStopLossClientId(slClientId);
builder.setStopLossTrailingStep(5); // Optional trailing

api.sendOrder(builder.build());

7.3 Bracket Order Lifecycle​

Ò”ŒÒ”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”
Γ’β€β€š BRACKET ORDER LIFECYCLE Γ’β€β€š
Γ’β€β€Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€β‚¬Γ’β€Λœ

PHASE 1: Submission
Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€

You submit ONE order with bracket configuration.
Three clientIds are set: ENTRY, TP, SL


PHASE 2: Entry Working
Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€

onOrderUpdated(ENTRY) Ò†’ status=WORKING
onOrderUpdated(TP) Ò†’ status=SUSPENDED (waiting for entry fill)
onOrderUpdated(SL) Ò†’ status=SUSPENDED (waiting for entry fill)


PHASE 3: Entry Filled
Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€

onOrderUpdated(ENTRY) Ò†’ status=FILLED
onOrderExecuted(ENTRY)
onOrderUpdated(TP) Ò†’ status=WORKING (now active)
onOrderUpdated(SL) Ò†’ status=WORKING (now active)


PHASE 4: Exit (One of Two Scenarios)
Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€Ò”€

Scenario A: Take Profit Hit
onOrderUpdated(TP) Ò†’ status=FILLED
onOrderExecuted(TP)
onOrderUpdated(SL) Ò†’ status=CANCELLED (OCO cancellation)

Scenario B: Stop Loss Hit
onOrderUpdated(SL) Ò†’ status=FILLED
onOrderExecuted(SL)
onOrderUpdated(TP) Ò†’ status=CANCELLED (OCO cancellation)

7.4 Family Tracking Record​

public record OrderFamily(
String familyId, // Common prefix for all orders
String entryClientId, // Entry order clientId
String takeProfitClientId, // TP order clientId (may be null)
String stopLossClientId, // SL order clientId (may be null)
OrderFamilyState state // Current family state
) {}

public enum OrderFamilyState {
PENDING_ENTRY, // Entry not yet filled
ENTRY_WORKING, // Entry in market
BRACKETS_ACTIVE, // Entry filled, TP/SL working
TP_TRIGGERED, // Take profit executed
SL_TRIGGERED, // Stop loss executed
CANCELLED, // User cancelled
ERROR // Inconsistent state
}

8. Implementation Patterns​

8.1 ClientId Generator​

public class ClientIdGenerator {

private final String strategyName;
private final AtomicLong sequence = new AtomicLong(0);

public ClientIdGenerator(String strategyName) {
this.strategyName = sanitize(strategyName);
}

private String sanitize(String name) {
return name.toUpperCase()
.replaceAll("[^A-Z0-9]", "_")
.substring(0, Math.min(12, name.length()));
}

public OrderFamilyIds generateFamily() {
long seq = sequence.incrementAndGet();
String timestamp = Long.toHexString(System.nanoTime()).toUpperCase();
String base = String.format("%s_%s_%04d", strategyName, timestamp, seq);

return new OrderFamilyIds(
base, // familyId
base + "_ENTRY", // entryClientId
base + "_TP", // takeProfitClientId
base + "_SL" // stopLossClientId
);
}

public record OrderFamilyIds(
String familyId,
String entryClientId,
String takeProfitClientId,
String stopLossClientId
) {}
}

8.2 Dual-Index Order Registry​

public class OrderRegistry {

// PRIMARY: clientId Ò†’ Order Record (source of truth)
private final ConcurrentHashMap<String, InternalOrderRecord> byClientId =
new ConcurrentHashMap<>();

// BRIDGE: brokerId Ò†’ clientId (for ExecutionInfo lookup)
private final ConcurrentHashMap<String, String> brokerToClientId =
new ConcurrentHashMap<>();

// FAMILY: familyId Ò†’ OrderFamily
private final ConcurrentHashMap<String, OrderFamily> families =
new ConcurrentHashMap<>();

/**
* Register order BEFORE submission.
* Ensures tracking even if submission fails.
*/
public void registerOrder(String clientId, OrderIntent intent) {
InternalOrderRecord record = InternalOrderRecord.create(clientId, intent);
byClientId.put(clientId, record);
}

/**
* Establish brokerId correlation.
* Called during onOrderUpdated when we first see the brokerId.
*/
public void correlate(String brokerId, String clientId) {
if (brokerId != null && clientId != null) {
brokerToClientId.put(brokerId, clientId);

// Update record with broker ID
InternalOrderRecord record = byClientId.get(clientId);
if (record != null) {
byClientId.put(clientId, record.withBrokerId(brokerId));
}
}
}

/**
* Find by clientId (always works if order was registered).
*/
public Optional<InternalOrderRecord> findByClientId(String clientId) {
return Optional.ofNullable(byClientId.get(clientId));
}

/**
* Find by brokerId (requires prior correlation).
* Used in onOrderExecuted where clientId is not available.
*/
public Optional<InternalOrderRecord> findByBrokerId(String brokerId) {
String clientId = brokerToClientId.get(brokerId);
return clientId != null ? findByClientId(clientId) : Optional.empty();
}

/**
* Universal lookup - tries both methods.
*/
public Optional<InternalOrderRecord> find(String brokerId, String clientId) {
// Prefer clientId (our source of truth)
if (clientId != null) {
Optional<InternalOrderRecord> result = findByClientId(clientId);
if (result.isPresent()) {
// Ensure correlation exists
if (brokerId != null) {
correlate(brokerId, clientId);
}
return result;
}
}
// Fallback to brokerId
return brokerId != null ? findByBrokerId(brokerId) : Optional.empty();
}
}

8.3 Order Event Processor​

public class OrderEventProcessor implements OrdersListener {

private final OrderRegistry registry;
private final double pips;

@Override
public void onOrderUpdated(OrderInfoUpdate update) {
String brokerId = update.orderId;
String clientId = update.clientId;

// STEP 1: Find or create correlation
Optional<InternalOrderRecord> recordOpt = registry.find(brokerId, clientId);

if (recordOpt.isEmpty()) {
handleUnknownOrder(update);
return;
}

InternalOrderRecord record = recordOpt.get();

// STEP 2: Map broker status to internal state
InternalOrderState newState = mapStatus(update.status);

// STEP 3: Update record
registry.updateState(record.clientId(), newState, update);

// STEP 4: Log with full context
logOrderUpdate(record.clientId(), brokerId, update.status, newState);
}

@Override
public void onOrderExecuted(ExecutionInfo execution) {
String brokerId = execution.orderId;

// CRITICAL: ExecutionInfo has NO clientId
// Must use mapping established in onOrderUpdated
Optional<InternalOrderRecord> recordOpt = registry.findByBrokerId(brokerId);

if (recordOpt.isEmpty()) {
handleOrphanedExecution(execution);
return;
}

InternalOrderRecord record = recordOpt.get();

// Process execution with full internal context
processExecution(record, execution);
}

private InternalOrderState mapStatus(OrderStatus status) {
return switch (status) {
case PENDING_SUBMIT -> InternalOrderState.ACKNOWLEDGED;
case WORKING -> InternalOrderState.WORKING;
case PENDING_CANCEL -> InternalOrderState.PENDING_CANCEL;
case PENDING_MODIFY -> InternalOrderState.PENDING_MODIFY;
case CANCELLED -> InternalOrderState.CANCELLED;
case FILLED -> InternalOrderState.FILLED;
case REJECTED -> InternalOrderState.REJECTED;
case SUSPENDED -> InternalOrderState.WORKING; // Bracket child
default -> InternalOrderState.UNKNOWN;
};
}
}

9. Complete Code Examples​

9.1 Minimal Working Example​

@Layer1SimpleAttachable
@Layer1TradingStrategy
@Layer1StrategyName("ClientId Tracking Demo")
@Layer1ApiVersion(Layer1ApiVersionValue.VERSION2)
public class ClientIdTrackingDemo implements CustomModule, OrdersListener {

private Api api;
private String alias;
private double pips;

// Dual-index tracking
private final ConcurrentHashMap<String, OrderRecord> byClientId =
new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, String> brokerToClientId =
new ConcurrentHashMap<>();

private final AtomicInteger orderSequence = new AtomicInteger(0);

@Override
public void initialize(String alias, InstrumentInfo info, Api api,
InitialState initialState) {
this.api = api;
this.alias = alias;
this.pips = info.pips;
}

/**
* Place order with clientId tracking.
*/
public String placeTrackedOrder(boolean isBuy, int quantity,
int tpOffset, int slOffset) {
// Generate unique clientId
String clientId = String.format("DEMO_%d_%d",
System.currentTimeMillis(), orderSequence.incrementAndGet());

// Pre-register BEFORE sending
OrderRecord record = new OrderRecord(clientId, isBuy, quantity);
byClientId.put(clientId, record);

// Build order with clientId
SimpleOrderSendParametersBuilder builder =
new SimpleOrderSendParametersBuilder(alias, isBuy, quantity);
builder.setClientId(clientId);
builder.setDuration(OrderDuration.IOC);

if (tpOffset > 0) {
builder.setTakeProfitOffset(tpOffset);
builder.setTakeProfitClientId(clientId + "_TP");
}
if (slOffset > 0) {
builder.setStopLossOffset(slOffset);
builder.setStopLossClientId(clientId + "_SL");
}

api.sendOrder(builder.build());

Log.info("ORDER SUBMITTED: clientId=" + clientId);
return clientId;
}

@Override
public void onOrderUpdated(OrderInfoUpdate update) {
String brokerId = update.orderId;
String clientId = update.clientId;

// Establish correlation
if (clientId != null && brokerId != null) {
brokerToClientId.put(brokerId, clientId);
}

// Find record
OrderRecord record = clientId != null ? byClientId.get(clientId) : null;
if (record == null && brokerId != null) {
String mappedClientId = brokerToClientId.get(brokerId);
record = mappedClientId != null ? byClientId.get(mappedClientId) : null;
}

if (record != null) {
record.setBrokerId(brokerId);
record.setStatus(update.status);

Log.info(String.format("ORDER UPDATE: clientId=%s, brokerId=%s, status=%s",
record.getClientId(), brokerId, update.status));
}
}

@Override
public void onOrderExecuted(ExecutionInfo execution) {
String brokerId = execution.orderId;

// Must use mapping - ExecutionInfo has no clientId!
String clientId = brokerToClientId.get(brokerId);

if (clientId == null) {
Log.warn("ORPHANED EXECUTION: brokerId=" + brokerId);
return;
}

OrderRecord record = byClientId.get(clientId);
if (record != null) {
record.addFill(execution.size, execution.price * pips);

Log.info(String.format("EXECUTION: clientId=%s, size=%d, price=%.2f",
clientId, execution.size, execution.price * pips));
}
}

@Override
public void stop() {
Log.info("Stopping. Active orders: " + byClientId.size());
}

// Simple order record
private static class OrderRecord {
private final String clientId;
private final boolean isBuy;
private final int quantity;
private String brokerId;
private OrderStatus status;
private int filledQuantity;
private double avgFillPrice;

OrderRecord(String clientId, boolean isBuy, int quantity) {
this.clientId = clientId;
this.isBuy = isBuy;
this.quantity = quantity;
}

String getClientId() { return clientId; }
void setBrokerId(String id) { this.brokerId = id; }
void setStatus(OrderStatus s) { this.status = s; }

void addFill(int size, double price) {
double totalValue = avgFillPrice * filledQuantity + price * size;
filledQuantity += size;
avgFillPrice = totalValue / filledQuantity;
}
}
}

9.2 Production-Ready Implementation​

See separate document: BookmapInstitutionalOrderManager.md for complete production implementation including:

  • Full state machine
  • Bracket family tracking
  • Orphan reconciliation
  • Timeout monitoring
  • Position management
  • Audit logging

10. Edge Cases and Error Handling​

10.1 Orphaned Executions​

An execution arrives but no correlation exists:

private void handleOrphanedExecution(ExecutionInfo execution) {
Log.error(String.format(
"ORPHANED EXECUTION: brokerId=%s, size=%d, price=%.4f, execId=%s",
execution.orderId, execution.size, execution.price, execution.executionId));

// Queue for later reconciliation
orphanedExecutions.put(execution.executionId, execution);

// Attempt reconciliation with pending orders
attemptReconciliation(execution);
}

10.2 Missing clientId in Update​

Some brokers may not return clientId properly:

@Override
public void onOrderUpdated(OrderInfoUpdate update) {
String clientId = update.clientId;

if (clientId == null) {
// Fall back to characteristic matching
clientId = findByCharacteristics(update);
}

// Continue processing...
}

private String findByCharacteristics(OrderInfoUpdate update) {
for (InternalOrderRecord record : pendingOrders.values()) {
if (record.alias().equals(update.instrumentAlias) &&
record.isBuy() == update.isBuy &&
record.requestedSize() == (update.filled + update.unfilled)) {
return record.clientId();
}
}
return null;
}

10.3 Duplicate Order Detection​

private boolean isDuplicate(OrderInfoUpdate update) {
// Check isDuplicate flag (cross-trading scenario)
if (update.isDuplicate) {
Log.info("Duplicate order detected: " + update.orderId);
return true;
}
return false;
}

10.4 Connection Loss​

@Override
public void onOrderUpdated(OrderInfoUpdate update) {
if (update.status == OrderStatus.DISCONNECTED) {
String clientId = brokerToClientId.get(update.orderId);
if (clientId != null) {
registry.updateState(clientId, InternalOrderState.ORPHANED,
"DISCONNECTED", "Connection lost to broker");
}
}
}

11. Quick Reference​

11.1 Required Annotation​

@Layer1TradingStrategy  // MANDATORY for order operations

11.2 Builder Methods for clientId​

MethodClassPurpose
setClientId(String)AbstractSingleOrderSendParametersBuilderMain order ID
setTakeProfitClientId(String)AbstractSimpleOrderSendParametersBuilderTP bracket ID
setStopLossClientId(String)AbstractSimpleOrderSendParametersBuilderSL bracket ID

11.3 Lookup Strategy by Callback​

CallbackPrimary LookupFallback Lookup
onOrderUpdatedclientId (direct)orderId via mapping
onOrderExecutedorderId via mappingCharacteristic match

11.4 Critical Code Pattern​

// ALWAYS establish mapping in onOrderUpdated
@Override
public void onOrderUpdated(OrderInfoUpdate update) {
if (update.clientId != null && update.orderId != null) {
brokerToClientId.put(update.orderId, update.clientId);
}
}

// ALWAYS use mapping in onOrderExecuted
@Override
public void onOrderExecuted(ExecutionInfo execution) {
String clientId = brokerToClientId.get(execution.orderId);
// clientId may be null if correlation failed!
}

11.5 Common Mistakes​

MistakeConsequenceFix
Not setting clientIdCannot track orders internallyAlways call setClientId()
Assuming ExecutionInfo has clientIdNullPointerExceptionUse brokerToClientId mapping
Not pre-registering ordersLost orders on submission failureRegister BEFORE sendOrder()
Using HashMap instead of ConcurrentHashMapRace conditionsUse thread-safe collections

Document Metadata​

Last Updated: 2025-01-11 API Version: Bookmap Level1Api 7.7.0 Author: Generated for RAG ingestion Keywords: Bookmap, order tracking, clientId, orderId, ExecutionInfo, OrderInfoUpdate, bracket orders, order lifecycle, dual-index, correlation


  • OrderManagement.md - General order management guide
  • OrderPlacement.md - Order submission patterns
  • OrdersListener.md - Callback interface documentation
  • ExecutionInfo.md - Execution data structure
  • OrderInfo.md - Order information structure
  • SimpleOrderSendParametersBuilder.md - Order builder documentation

Change Log​

VersionDateChanges
1.0.02025-01-11Initial comprehensive documentation