| name | algo-test |
| description | Write unit tests for Ember trading algorithms using the algo test framework (BaseAbstractAlgorithmTest). Use when asked to create or modify algo unit tests, add test coverage, or understand how the test framework works. |
Ember Algo Unit Test Framework
Tests extend BaseAbstractAlgorithmTest<YourAlgorithm> from deltix.ember.service.algorithm.
The living tutorial is at:
ember/algo-test/src/main/java/deltix/ember/service/algorithm/samples/tutorial/Test_AlgoTestFrameworkTutorial.java
Minimal Test Class Structure
class Test_MyAlgorithm extends BaseAbstractAlgorithmTest<MyAlgorithm> {
@Override
protected MyAlgorithm createAlgorithm() {
return new MyAlgorithm(getAlgorithmContext(), getCacheSettings());
}
@Override
protected void initAlgorithm(MyAlgorithm algorithm) {
super.initAlgorithm(algorithm);
defineSyntheticInstrument("AAPL");
simulateMarketFeedBBO("AAPL", "149.99", 100, "150.00", 200);
}
@Test
public void happyPath() {
simulateNewOrderRequest().symbol("AAPL").quantity("10").limitPrice("150.00").account("MAIN");
verifyNewOrderRequest("quantity:10", "account:MAIN");
verifyOrderPendingNewEvent("orderId:Parent#1");
}
}
Core Pattern
Each test is a sequence of simulate (inputs to the algo) and verify (outputs from the algo) calls.
simulate* — inbound events: order requests from clients, exchange events, market data, time
verify* — outbound messages: what the algo sends to exchanges or back to clients
- The framework's
@AfterEach automatically asserts no unexpected messages remain
simulate* Methods
Client Order Requests
simulateNewOrderRequest()
simulateNewOrderRequest(String orderId)
simulateCancelRequest(String orderId)
simulateReplaceOrderRequest(String orderId, String originalOrderId)
simulateOrderStatusRequest(String orderId)
Exchange/Venue Events (child order responses)
simulateOrderNewEvent(String orderId)
simulateOrderPendingNewEvent(String orderId)
simulateTradeEvent(String orderId, String price, String quantity)
simulateOrderCancelEvent(String orderId)
simulateOrderCancelEvent(String orderId, String cancelRequestId)
simulateOrderCancelRejectEvent(String orderId)
simulateOrderReplaceEvent(String originalOrderId, String orderId)
simulateOrderReplaceRejectEvent(String originalOrderId, String orderId)
simulateOrderRejectEvent(String orderId)
simulateOrderRestateEvent(String orderId)
Market Data
simulateMarketFeedBBO(CharSequence symbol, String bidPrice, int bidSize, String askPrice, int askSize)
simulateMarketFeedTrade(CharSequence symbol, String price, String size)
simulateMarket(String symbol, String... orderBookEntries)
simulateOrderBook(String symbol, long exchangeId, String... entries)
simulateUnknownMarketPrice(CharSequence symbol)
Order book entry format — separator line divides asks (above) from bids (below):
"7.001 @ 1440.99" // ask
"0.001 @ 1440.98" // ask
"---------------" // bid/ask separator
"10.941 @ 1440.95" // bid
"0.075 @ 1440.90" // bid
Time
simulateTimeAdvance(Duration.ofSeconds(5))
setTime(long epochMillis)
verify* Methods
All take "fieldName:value" pairs as varargs. Field names match the Deltix Trading API event fields.
verifyNewOrderRequest(String... attrs)
verifyReplaceOrderRequest(String... attrs)
verifyCancelOrderRequest(String... attrs)
verifyOrderPendingNewEvent(String... attrs)
verifyOrderNewEvent(String... attrs)
verifyOrderPendingCancelEvent(String... attrs)
verifyOrderCancelEvent(String... attrs)
verifyOrderCancelRejectEvent(String... attrs)
verifyOrderTradeEvent(String... attrs)
verifyOrderReplaceEvent(String... attrs)
verifyOrderReplaceRejectEvent(String... attrs)
verifyOrderPendingReplaceEvent(String... attrs)
verifyOrderRejectEvent(String... attrs)
verifyOrderRestateEvent(String... attrs)
verifyOrderStatusEvent(String... attrs)
verifyPositionRequest(String... attrs)
verifyMessage(Class<?> messageClass, String... attrs)
verifyNoMessagesFromAlgorithm()
OrderRequestBuilder
Fluent builder returned by simulateNewOrderRequest() and simulateReplaceOrderRequest().
simulateNewOrderRequest()
.symbol("AAPL")
.quantity("10")
.limitPrice("150.00")
.stopPrice("149.00")
.orderType(OrderType.MARKET)
.side(Side.SELL)
.displayQty("50")
.trader("myTrader")
.account("MAIN")
.exchange("NYSE")
.timeInForce(TimeInForce.DAY)
.custom("duration:00:00:05; skew:15")
.doSomethingNasty(req -> req.setFlags(1 << OrderFlags.ALL_OR_NONE.ordinal()))
Auto-inference (setAutomateSomeAttrs)
By default the builder infers some fields automatically:
.limitPrice() → also sets OrderType.LIMIT
.stopPrice() → also sets OrderType.STOP
.quantity("10") → sets Side.BUY; .quantity("-10") → sets Side.SELL
Disable with setAutomateSomeAttrs(false) in the constructor when the algo needs explicit control (e.g., testing MARKET orders, CUSTOM type, or when .orderType() must be set before price):
public Test_MyAlgorithm() {
super("BTCUSD", InstrumentType.FX);
setAutomateSomeAttrs(false);
}
Custom Attributes
To use named custom attributes in custom("name:value"), override getAttributeKeyByName:
@Override
protected int getAttributeKeyByName(String name) {
if (name.equals("duration")) return MyAlgorithm.DURATION_ATTR_KEY;
return super.getAttributeKeyByName(name);
}
Instrument Definition
Call in initAlgorithm() or @BeforeEach (before tests run).
defineSyntheticInstrument(String symbol)
defineFutureInstrument(String symbol)
defineCurrencyInstrument(String symbol)
defineCurrencyInstrument("BTCUSD", 0.01, 3, "0.02")
defineFutureInstrument("EDZ7", 0.0025, 0, "1")
defineCurrencyInstrument("EURUSD", algorithm)
defineFutureInstrument("EDZ7", algorithm)
Calling the no-algorithm overload routes through algorithmMock. Pass algorithm explicitly when you need to push instruments inside initAlgorithm(A algorithm) or a @BeforeEach that runs after construction:
@Override
protected void initAlgorithm(MyAlgorithm algorithm) {
super.initAlgorithm(algorithm);
defineCurrencyInstrument("EURUSD", algorithm);
defineCurrencyInstrument("EURUSD.LP1", algorithm);
}
Auto-Generated IDs
The framework auto-increments order IDs:
- Parent orders:
Parent#1, Parent#2, …
- Child orders:
Child#1, Child#2, …
- Cancel requests:
CancelRequest#1, …
Override simulateNewOrderRequest() in your test class to set defaults shared by all tests:
@Override
protected OrderRequestBuilder simulateNewOrderRequest() {
return super.simulateNewOrderRequest().symbol("AAPL");
}
Common Test Patterns
Reject Invalid Order
simulateNewOrderRequest().quantity("10").account("INVALID");
verifyOrderRejectEvent("reason:Bad account");
Child Order Submit + Fill
simulateNewOrderRequest().quantity("10").limitPrice("100.00").account("GOOD");
verifyNewOrderRequest("quantity:10", "account:GOOD");
verifyOrderPendingNewEvent("orderId:Parent#1");
simulateTradeEvent("Child#1", "100.00", "10");
verifyOrderTradeEvent("orderId:Parent#1", "tradePrice:100.00", "tradeQuantity:10");
Parent Cancel Propagation
simulateCancelRequest("Parent#1");
verifyCancelOrderRequest("orderId:Child#1");
simulateOrderCancelEvent("Child#1");
verifyOrderCancelEvent("orderId:Parent#1");
Timer / Timeout
simulateNewOrderRequest().quantity("10").custom("duration:00:00:05");
verifyNewOrderRequest("orderId:Child#1");
verifyOrderPendingNewEvent("orderId:Parent#1");
simulateTimeAdvance(Duration.ofSeconds(5));
verifyCancelOrderRequest("orderId:Child#1");
verifyOrderCancelEvent("orderId:Parent#1", "reason:Order timeout");
Market Data Guard
simulateUnknownMarketPrice("AAPL");
simulateNewOrderRequest().quantity("10");
verifyOrderRejectEvent("reason:No market data");
Verify N Events of the Same Type
for (int i = 0; i < 3; i++)
verifyOrderCancelEvent("reason:Order Expired");
Advanced Setup
Enable Verbose Logging
public Test_MyAlgorithm() {
LOGGER.setLevel(LogLevel.TRACE);
}
Multi-Symbol Subscription
Override to let the algorithm subscribe to multiple instruments:
@Override
protected MarketSubscription getAlgorithmMarketSubscription() {
MarketSubscription subscription = super.getAlgorithmMarketSubscription();
when(subscription.isSubscribedToAll()).thenReturn(true);
when(subscription.isSubscribedToAllInstruments()).thenReturn(true);
return subscription;
}
Or subscribe to a specific list:
when(subscription.getSymbols()).thenReturn(Arrays.asList("BTCUSD", "LTCUSD"));
Session Status Events
Algos that talk to LPs check session connectivity. Send a connected event directly to the algorithm:
MutableSessionStatusEvent event = new MutableSessionStatusEvent();
event.setSourceId(AlphanumericCodec.encode("LP1"));
event.setStatus(SessionStatus.CONNECTED);
algorithm.onSessionStatusEvent(event);
Typically wrapped in a helper and called from @BeforeEach:
@BeforeEach
public void initSessions() {
simulateTradingSessionStatusConnected("LP1");
simulateTradingSessionStatusConnected("LP2");
}
private void simulateTradingSessionStatusConnected(String lp) {
MutableSessionStatusEvent event = new MutableSessionStatusEvent();
event.setSourceId(AlphanumericCodec.encode(lp));
event.setStatus(SessionStatus.CONNECTED);
algorithm.onSessionStatusEvent(event);
}
Capture Side-Channel Output
Algorithms that publish to output streams (auctions, RFQ, etc.) use createOutputChannel(). Override getAlgorithmContext() to capture those messages:
private final List<AuctionMessage> auctionMessages = new ArrayList<>();
@Override
protected AlgorithmContext getAlgorithmContext() {
AlgorithmContext mock = super.getAlgorithmContext();
when(mock.createOutputChannel(anyString(), any(Class[].class))).thenReturn(new EmptyMessageChannel() {
@Override
public void send(Object msg) {
if (msg instanceof AuctionMessage)
auctionMessages.add((AuctionMessage) msg);
else
fail("Unexpected message type: " + msg.getClass().getSimpleName());
}
});
return mock;
}
See Test_AuctionAlgorithmMock and AbstractRFQAlgorithmTest for complete examples.
SingleLegExecutionAlgoUnitTest
Most algo tests extend SingleLegExecutionAlgoUnitTest<A> (which itself extends BaseAbstractAlgorithmTest). It wires up a single symbol and instrument type in the constructor, pre-sets symbol on all order requests, and adds convenience overloads:
class Test_MyAlgo extends SingleLegExecutionAlgoUnitTest<MyAlgorithm> {
public Test_MyAlgo() {
super("BTCUSD", InstrumentType.FX);
}
@Override
protected MyAlgorithm createAlgorithm() {
return new MyAlgorithm(getAlgorithmContext(), getCacheSettings());
}
}
Extra methods available only in SingleLegExecutionAlgoUnitTest:
simulateMarketFeedBBO("149.99", 100, "150.00", 200)
simulateMarketFeedBBO(bidPrice, bidSize, askPrice, askSize)
simulateMarketFeedTrade(price)
simulateOrderBook(String... entries)
simulateOrderBook(long exchangeId, String... entries)
simulateCancelRequest("Parent#1", "EXCH-ORDER-999")
The protected symbol field holds the configured symbol string and can be used in test assertions.