Common Pitfalls
The section addresses common pitfalls that users may encounter while using the Ember Algorithm API. These pitfalls also apply to Trading Connectors and, to some extent external Java clients.
Sleeping Inside Algorithm Callbacks
A potential pitfall in algorithm code is the use of sleep calls inside event callbacks.
For example:
public void onOrderRejectEvent (OrderRejectEvent event) {
if (event.getDeltixRejectCode() == RISK_ORDER_REJECT_FREQUENCY)) {
logger.info("Waiting 1 second after hitting MaxSubmitFrequency");
Thread.sleep(1000); // BAD!!!
submitNewOrder();
}
}
It's essential to remember that algorithms are designed to be event-driven. If an event callback goes to sleep, the algorithm freezes and stops processing other events such as market data, timers, and trading events.
Best Practice: Event-Driven Approach
To avoid this problem, use an event-driven approach. Instead of using Thread.sleep()
, set a timer to program an event after a specific timeout. This way, the algorithm can continue processing events without unnecessary delays or interruptions.
Not Understanding the Flyweight Pattern
Pitfall: Incorrect Handling of Mutable Objects
In most callbacks, Ember uses the flyweight pattern to optimize memory usage.
Consider the following example:
private CharSequence lastRejectReason;
public void onOrderRejectEvent(OrderRejectEvent reject) {
lastRejectReason = reject.getReason(); /// BAD !!!
}
In this example, lastRejectReason
is assigned the value of reject.getReason()
. However, this approach directly references the original object without making a copy. As a result, changes to the reject
object can inadvertently affect lastRejectReason
, leading to incorrect behavior.
Best Practice: Make Immutable Copies
To avoid unexpected behavior, make an immutable copy when saving information from a callback. The correct approach is as follows:
private final AsciiCharSequence lastRejectReason = new AsciiCharSequence(256);
public void onOrderRejectEvent(OrderRejectEvent reject) {
lastRejectReason.clear().append(reject.getReason()); // making a copy!
}
Here, lastRejectReason
is a flyweight object, and clear()
and append()
methods create a copy of reject.getReason()
before storing it. This ensures that any changes to the original object will not affect lastRejectReason
.
Ember uses the flyweight approach in all callbacks.
Pitfall: Using Mutable Keys in a Map
Consider another example:
private final CharSequenceToObjectMap<Instrument> instruments = new CharSequenceToObjectMap<>();
@Override
public void onFutureUpdate(FutureUpdate futureInstrument) {
CharSequence symbol = futureInstrument.getSymbol();
Instrument instrument = instruments.get(symbol);
if (instrument == null) {
instrument = new Instrument(futureInstrument);
instruments.put (symbol, instrument); // BAD!!! Using mutable key
}
}
Ember uses a mutable flyweight object to decode FutureUpdate
instances from the internal queue or from TimeBase and passes a reference to this callback. Once the callback is finished, Ember reuses the same instance to read the next Futures instrument update. As a result, the character sequence buffer used by the instrument symbol is rewritten with new data.
Best Practice: Use Immutable Copies as Keys
The recommended approach is to make an immutable copy of the instrument symbol inside the Instrument
instance and use it as a dictionary key. This ensures that the key remains unchanged, preventing unintended behavior related to mutable keys in the map.
Decimal Mix-ups
Ember employs DFP encoding for prices and sizes, allowing packing IEE-754 decimal floating point numbers into Java long
data types. However, this encoding introduces the risk of unintentional mix-ups in code.
Pitfall: Incorrect Comparison
In some cases, developers may unintentionally mix up decimal comparisons due to the DFP encoding. For example:
if (request.getDisplayQuantity() > 0) { ... } // BAD: Use Decimal64Utils.isPositive()
Here, getDisplayQuantity()
returns a DFP-encoded long
, but the comparison is made as if it were a regular numeric value, potentially leading to incorrect results.
Pitfall: Incorrect Arithmetic Operations
Similarly, arithmetic operations without proper handling of DFP-encoded values can yield incorrect results. For example:
long midPrice = (bestAsk.getPrice() + bestBid.getPrice()) / 2 ); // BAD: Decimal64Utils helper methods
In this example, the arithmetic operation is performed directly on DFP-encoded values without using the appropriate Decimal64Utils
helper methods, which could lead to unexpected outcomes.
Best Practice: Use @Decimal
Annotation
To mitigate the risk of mix-ups, use the @Decimal
annotation wherever possible to help distinguish DFP numbers in your code. For example:
@Decimal
public long roundOrderPrice(@Decimal long price, Side side, @Alphanumeric long exchangeId) {
}
By using the @Decimal
annotation, you can clearly identify DFP-encoded values and apply appropriate handling, reducing the likelihood of mix-ups.
The Deltix team is looking forward to Project Valhalla to improve this situation. Project Valhalla aims to enhance the Java programming language and virtual machine with value types and specialized generics, which could provide better support for handling DFP-encoded values and prevent some of the potential mix-up issues.