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β
- Executive Summary
- Core Concepts
- The clientId Mechanism
- API Callback Architecture
- The Correlation Challenge
- Order Lifecycle States
- Bracket Order Tracking
- Implementation Patterns
- Complete Code Examples
- Edge Cases and Error Handling
- Quick Reference
1. Executive Summaryβ
Key Insightβ
The Bookmap API provides two order identification mechanisms:
| Identifier | Source | Purpose |
|---|---|---|
orderId | Broker/Exchange | Broker-assigned unique identifier |
clientId | Developer | Client-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β
| Class | Field/Method | Context |
|---|---|---|
SingleOrderSendParameters | clientId | Base order parameter |
SimpleOrderSendParameters | takeProfitClientId | Bracket TP order |
SimpleOrderSendParameters | stopLossClientId | Bracket SL order |
BracketTier | clientId | Multi-tier bracket |
OrderInfo | clientId | Order state information |
OrderInfoUpdate | clientId | Order update callback |
AbstractSingleOrderSendParametersBuilder | setClientId(String) | Builder method |
AbstractSimpleOrderSendParametersBuilder | setTakeProfitClientId(String) | Builder method |
AbstractSimpleOrderSendParametersBuilder | setStopLossClientId(String) | Builder method |
2.3 Where clientId Does NOT Appearβ
Critical: ExecutionInfo does not contain clientId.
ExecutionInfo fields:
orderId(String) - Broker ID onlysize(int) - Execution sizeprice(double) - Execution priceexecutionId(String) - Unique execution identifiertime(long) - Execution timestampisSimulated(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β
| Field | OrderInfoUpdate | ExecutionInfo |
|---|---|---|
orderId | Γ’Εβ¦ Available | Γ’Εβ¦ Available |
clientId | Γ’Εβ¦ Available | Γ’ΒΕ NOT Available |
status | Γ’Εβ¦ Available | N/A |
filled | Γ’Εβ¦ Available | N/A |
unfilled | Γ’Εβ¦ Available | N/A |
limitPrice | Γ’Εβ¦ Available | N/A |
stopPrice | Γ’Εβ¦ Available | N/A |
size | N/A | Γ’Εβ¦ Available |
price | N/A | Γ’Εβ¦ Available |
executionId | N/A | Γ’Εβ¦ Available |
time | N/A | Γ’Εβ¦ Available |
isSimulated | Γ’Εβ¦ Available | Γ’Εβ¦ Available |
4.3 Guaranteed Callback Sequenceβ
Per Bookmap documentation:
Order with
ExecutionInfo.executionIdis supposed to exist (you should get at least oneonOrderUpdated(OrderInfoUpdate)for that order first).
This guarantees:
onOrderUpdatedis called BEFOREonOrderExecuted- Developers can establish correlation before executions arrive
- 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β
| Status | isActive() | Description |
|---|---|---|
PENDING_SUBMIT | true | Order being sent to broker |
WORKING | true | Order active in market |
PENDING_CANCEL | true | Cancel request sent |
PENDING_MODIFY | true | Modify request sent |
SUSPENDED | true | Bracket child awaiting parent fill |
CANCELLED | false | Order cancelled |
FILLED | false | Order completely filled |
REJECTED | false | Order rejected by broker/exchange |
DISCONNECTED | false | Connection 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β
| Method | Class | Purpose |
|---|---|---|
setClientId(String) | AbstractSingleOrderSendParametersBuilder | Main order ID |
setTakeProfitClientId(String) | AbstractSimpleOrderSendParametersBuilder | TP bracket ID |
setStopLossClientId(String) | AbstractSimpleOrderSendParametersBuilder | SL bracket ID |
11.3 Lookup Strategy by Callbackβ
| Callback | Primary Lookup | Fallback Lookup |
|---|---|---|
onOrderUpdated | clientId (direct) | orderId via mapping |
onOrderExecuted | orderId via mapping | Characteristic 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β
| Mistake | Consequence | Fix |
|---|---|---|
Not setting clientId | Cannot track orders internally | Always call setClientId() |
Assuming ExecutionInfo has clientId | NullPointerException | Use brokerToClientId mapping |
| Not pre-registering orders | Lost orders on submission failure | Register BEFORE sendOrder() |
Using HashMap instead of ConcurrentHashMap | Race conditions | Use 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
Related Documentsβ
OrderManagement.md- General order management guideOrderPlacement.md- Order submission patternsOrdersListener.md- Callback interface documentationExecutionInfo.md- Execution data structureOrderInfo.md- Order information structureSimpleOrderSendParametersBuilder.md- Order builder documentation
Change Logβ
| Version | Date | Changes |
|---|---|---|
| 1.0.0 | 2025-01-11 | Initial comprehensive documentation |