Skip to main content

OrderRegistry

Central registry for all orders and families. This is the single source of truth for order state. All lookups and updates go through this registry. Thread-safe through ConcurrentHashMap and careful synchronization.

OrderRegistry.java
package com.bookmap.ordermanagement.core;

import com.bookmap.ordermanagement.model.*;
import velox.api.layer1.common.Log;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;

/**
* Central registry for all orders and families.
*
* This is the single source of truth for order state.
* All lookups and updates go through this registry.
*
* Thread-safe through ConcurrentHashMap and careful synchronization.
*/
public class OrderRegistry {

// Configuration
private final String logPrefix;
private final boolean loggingEnabled;

// Data storage
private final Map<String, OrderRecord> ordersByBrokerId;
private final Map<String, OrderFamily> familiesById;

// Active family tracking
private volatile String activeFamilyId;

// ID generation
private final AtomicLong familyCounter;

/**
* Creates a new order registry.
*
* @param logPrefix Prefix for log messages
* @param loggingEnabled Whether to enable detailed logging
*/
public OrderRegistry(String logPrefix, boolean loggingEnabled) {
this.logPrefix = logPrefix;
this.loggingEnabled = loggingEnabled;
this.ordersByBrokerId = new ConcurrentHashMap<>();
this.familiesById = new ConcurrentHashMap<>();
this.activeFamilyId = null;
this.familyCounter = new AtomicLong(System.currentTimeMillis());
}

// =========================================================================
// Family Management
// =========================================================================

/**
* Creates a new order family.
*
* @param direction Entry direction (BUY for long, SELL for short)
* @param size Number of contracts
* @return The created family
*/
public OrderFamily createFamily(Direction direction, int size) {
String familyId = "FAM_" + familyCounter.incrementAndGet();
OrderFamily family = new OrderFamily(familyId, direction, size);

familiesById.put(familyId, family);
activeFamilyId = familyId;

if (loggingEnabled) {
Log.info(String.format("%s Created family: %s, direction=%s, size=%d",
logPrefix, familyId, direction, size));
}

return family;
}

/**
* Gets a family by ID.
*/
public OrderFamily getFamily(String familyId) {
return familiesById.get(familyId);
}

/**
* Gets the currently active family, or null if none.
*/
public OrderFamily getActiveFamily() {
if (activeFamilyId == null) return null;
return familiesById.get(activeFamilyId);
}

/**
* Gets the active family ID, or null if none.
*/
public String getActiveFamilyId() {
return activeFamilyId;
}

/**
* Clears the active family reference.
*/
public void clearActiveFamily() {
if (loggingEnabled && activeFamilyId != null) {
Log.info(String.format("%s Cleared active family: %s", logPrefix, activeFamilyId));
}
this.activeFamilyId = null;
}

// =========================================================================
// Order Management
// =========================================================================

/**
* Registers a new order with its broker ID.
*
* @param brokerId The broker-assigned order ID
* @param familyId The family this order belongs to
* @param orderType ENTRY, TAKE_PROFIT, or STOP_LOSS
* @param direction BUY or SELL
* @param size Requested size
* @return The created order record
*/
public OrderRecord registerOrder(String brokerId, String familyId,
OrderType orderType, Direction direction, int size) {

OrderRecord record = new OrderRecord(brokerId, familyId, orderType, direction, size);
ordersByBrokerId.put(brokerId, record);

// Link to family
OrderFamily family = familiesById.get(familyId);
if (family != null) {
switch (orderType) {
case ENTRY:
family.setEntryBrokerId(brokerId);
break;
case TAKE_PROFIT:
family.setTpBrokerId(brokerId);
break;
case STOP_LOSS:
family.setSlBrokerId(brokerId);
break;
}
}

if (loggingEnabled) {
Log.info(String.format("%s Registered order: %s, type=%s, family=%s",
logPrefix, brokerId, orderType, familyId));
}

return record;
}

/**
* Finds an order by broker ID.
*/
public OrderRecord findByBrokerId(String brokerId) {
return ordersByBrokerId.get(brokerId);
}

/**
* Updates an order's state.
*
* @param brokerId The order's broker ID
* @param newState The new state
* @return true if order was found and updated
*/
public boolean updateOrderState(String brokerId, OrderState newState) {
OrderRecord record = ordersByBrokerId.get(brokerId);
if (record == null) {
return false;
}

OrderState oldState = record.getState();
record.setState(newState);

if (loggingEnabled) {
Log.info(String.format("%s Order state change: %s [%s] %s -> %s",
logPrefix, brokerId, record.getOrderType(), oldState, newState));
}

return true;
}

/**
* Records a fill on an order.
*
* @param brokerId The order's broker ID
* @param quantity Fill quantity
* @param price Fill price
* @return The updated order record, or null if not found
*/
public OrderRecord recordFill(String brokerId, int quantity, double price) {
OrderRecord record = ordersByBrokerId.get(brokerId);
if (record == null) {
Log.warn(String.format("%s Fill for unknown order: %s", logPrefix, brokerId));
return null;
}

record.addFill(quantity, price);

if (loggingEnabled) {
Log.info(String.format("%s Fill recorded: %s, qty=%d, price=%.2f, total=%d/%d",
logPrefix, brokerId, quantity, price,
record.getFilledQuantity(), record.getRequestedSize()));
}

return record;
}

// =========================================================================
// Family State Queries
// =========================================================================

/**
* Gets all orders belonging to a family.
*/
public List<OrderRecord> getFamilyOrders(String familyId) {
List<OrderRecord> orders = new ArrayList<>();
OrderFamily family = familiesById.get(familyId);

if (family == null) return orders;

for (String brokerId : family.getAllBrokerIds()) {
OrderRecord record = ordersByBrokerId.get(brokerId);
if (record != null) {
orders.add(record);
}
}

return orders;
}

/**
* Checks if a family is complete.
*
* A family is complete when:
* - Entry was REJECTED (family failed before TP/SL placed), OR
* - An EXIT order (TP or SL) has been FILLED
*
* IMPORTANT: Entry being FILLED or CANCELLED alone does NOT make the family complete.
* We must wait for an exit order to fill (or entry to be rejected).
*/
public boolean isFamilyComplete(String familyId) {
OrderFamily family = familiesById.get(familyId);
if (family == null) {
if (loggingEnabled) {
Log.info(String.format("%s isFamilyComplete(%s): false - family not found", logPrefix, familyId));
}
return false;
}

// Check if entry was rejected (family failed before TP/SL)
String entryId = family.getEntryBrokerId();
if (entryId != null) {
OrderRecord entry = ordersByBrokerId.get(entryId);
if (entry != null && entry.getState() == OrderState.REJECTED) {
if (loggingEnabled) {
Log.info(String.format("%s isFamilyComplete(%s): true - entry REJECTED", logPrefix, familyId));
}
return true;
}
}

// Family is complete only when an EXIT order filled
String tpId = family.getTpBrokerId();
String slId = family.getSlBrokerId();

if (tpId != null) {
OrderRecord tp = ordersByBrokerId.get(tpId);
if (tp != null && tp.isFilled()) {
if (loggingEnabled) {
Log.info(String.format("%s isFamilyComplete(%s): true - TP filled", logPrefix, familyId));
}
return true;
}
}

if (slId != null) {
OrderRecord sl = ordersByBrokerId.get(slId);
if (sl != null && sl.isFilled()) {
if (loggingEnabled) {
Log.info(String.format("%s isFamilyComplete(%s): true - SL filled", logPrefix, familyId));
}
return true;
}
}

if (loggingEnabled) {
Log.info(String.format("%s isFamilyComplete(%s): false - waiting for exit fill (TP=%s, SL=%s)",
logPrefix, familyId,
tpId != null ? "registered" : "pending",
slId != null ? "registered" : "pending"));
}
return false;
}

/**
* Determines the close reason for a completed family.
*/
public CloseReason determineFamilyCloseReason(String familyId) {
OrderFamily family = familiesById.get(familyId);
if (family == null) return CloseReason.UNKNOWN;

// Check TP
String tpId = family.getTpBrokerId();
if (tpId != null) {
OrderRecord tpOrder = ordersByBrokerId.get(tpId);
if (tpOrder != null && tpOrder.isFilled()) {
return CloseReason.TP_HIT;
}
}

// Check SL
String slId = family.getSlBrokerId();
if (slId != null) {
OrderRecord slOrder = ordersByBrokerId.get(slId);
if (slOrder != null && slOrder.isFilled()) {
return CloseReason.SL_HIT;
}
}

// Check for rejection
String entryId = family.getEntryBrokerId();
if (entryId != null) {
OrderRecord entryOrder = ordersByBrokerId.get(entryId);
if (entryOrder != null && entryOrder.getState() == OrderState.REJECTED) {
return CloseReason.REJECTED;
}
}

// Must be manual or cancelled
return CloseReason.CANCELLED;
}

// =========================================================================
// Global Queries
// =========================================================================

/**
* Returns true if there are any active (non-terminal) orders.
*/
public boolean hasActiveOrders() {
for (OrderRecord record : ordersByBrokerId.values()) {
if (record.isActive()) {
return true;
}
}
return false;
}

/**
* Returns the count of active orders.
*/
public int getActiveOrderCount() {
int count = 0;
for (OrderRecord record : ordersByBrokerId.values()) {
if (record.isActive()) {
count++;
}
}
return count;
}

/**
* Returns the count of all tracked orders.
*/
public int getTotalOrderCount() {
return ordersByBrokerId.size();
}

/**
* Returns the count of all families.
*/
public int getTotalFamilyCount() {
return familiesById.size();
}

// =========================================================================
// Reset and Cleanup
// =========================================================================

/**
* Clears all state.
*/
public void clear() {
ordersByBrokerId.clear();
familiesById.clear();
activeFamilyId = null;

if (loggingEnabled) {
Log.info(String.format("%s Registry cleared", logPrefix));
}
}

/**
* Removes completed families older than the specified age.
*
* @param maxAgeMs Maximum age in milliseconds
* @return Number of families removed
*/
public int cleanupOldFamilies(long maxAgeMs) {
long cutoff = System.currentTimeMillis() - maxAgeMs;
int removed = 0;

Iterator<Map.Entry<String, OrderFamily>> it = familiesById.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, OrderFamily> entry = it.next();
OrderFamily family = entry.getValue();

if (family.isClosed() && family.getClosedTime() < cutoff) {
// Remove associated orders
for (String brokerId : family.getAllBrokerIds()) {
ordersByBrokerId.remove(brokerId);
}
it.remove();
removed++;
}
}

if (loggingEnabled && removed > 0) {
Log.info(String.format("%s Cleaned up %d old families", logPrefix, removed));
}

return removed;
}
}