Hand cranked codecs
are a pain to write and a pain to upgrade, I have written many over the years
! The solution is to generate the code.
An XML model defines
the internal model with POJO's that all components can work with eg
NewOrderSingle, NewOrderAck, TradeNew. It also defines external models which
can be client or exchange, FIX variants or binary protocols such as ETS,
Millenium, UTP etc. Finally it defines codecs which specify how to translate
external model to/from internal model.
Sample Internal
Event for a New Order Single
<Base
id="BaseOrderRequest" src="client"
extends="CommonClientHeader">
<Attribute
typeId="Instrument"
name="instrument"
mandatory="Y"
outbound="delegate"/>
<Attribute
typeId="ClientProfile"
name="client"
mandatory="Y"
outbound="delegate"/>
<Attribute
typeId="viewstring[CLORDID_LENGTH]"
tag="11"
name="clOrdId"
mandatory="Y"
outbound="delegate"/>
<Attribute
typeId="viewstring[CLORDID_LENGTH]"
tag="41"
name="origClOrdId"
mandatory="Y"
outbound="delegate"/>
<Attribute
typeId="viewstring[SECURITYID_LENGTH]" tag="48" name="securityId" mandatory="Y" outbound="delegate"/>
<Attribute
typeId="viewstring[SYMBOL_LENGTH]"
tag="55"
name="symbol"
mandatory="Y"
outbound="delegate"/>
<Attribute
typeId="Currency"
tag="15"
name="currency"
mandatory="N"
outbound="seperate"/>
<Attribute
typeId="SecurityIDSource"
tag="22"
mandatory="Y"
outbound="delegate"/>
<Attribute
typeId="UTCTimestamp"
tag="60"
name="transactTime" mandatory="Y" outbound="delegate"/>
<Attribute
typeId="UTCTimestamp"
tag="52"
name="sendingTime"
outbound="seperate"/>
<Attribute typeId="Side"
tag="54"
mandatory="Y"
outbound="delegate"/>
<Attribute
typeId="viewstring[SRC_LINKID_LENGTH]" name="srcLinkId" mandatory="N" outbound="delegate"/>
</Base>
<Base
id="OrderRequest" src="client"
extends="BaseOrderRequest">
<Attribute
typeId="viewstring[ACCOUNT_LENGTH]" tag="1" name="account" mandatory="N"
outbound="delegate"/>
<Attribute
typeId="viewstring[TEXT_LENGTH]" tag="58" name="text" mandatory="N"
outbound="delegate"/>
<Attribute
typeId="viewstring[EXDESTINATION_LENGTH]" tag="100"
name="exDest"
mandatory="N" outbound="delegate"/>
<Attribute
typeId="viewstring[SECURITYEXCH_LENGTH]" tag="207"
name="securityExchange" mandatory="N"
outbound="delegate"/>
<Attribute typeId="double" tag="44" name="price" mandatory="Y" outbound="seperate"/>
<Attribute typeId="int"
tag="38"
name="orderQty"
mandatory="Y"
outbound="seperate"/>
<Attribute
typeId="ExecInst"
tag="18"
outbound="delegate"/>
<Attribute
typeId="HandlInst"
tag="21"
outbound="delegate"/>
<Attribute
typeId="OrderCapacity"
tag="528"
outbound="seperate"/>
<Attribute typeId="OrdType" tag="40" mandatory="Y" outbound="delegate"/>
<Attribute
typeId="SecurityType"
tag="167"
outbound="delegate"/>
<Attribute
typeId="SecurityIDSource"
tag="22"
outbound="delegate"/>
<Attribute
typeId="TimeInForce"
tag="59"
outbound="delegate"/>
<Attribute
typeId="BookingType"
tag="775"
outbound="delegate"/>
<Attribute typeId="long"
name="orderReceived" mandatory="Y" outbound="delegate"/>
<Attribute typeId="long"
name="orderSent"
mandatory="Y"
outbound="delegateGetAndSet"/>
</Base>
<Event
id="NewOrderSingle" extends="OrderRequest"
src="client">
<Attribute
typeId="viewstring[CLORDID_LENGTH]"
tag="11"
name="clOrdId"
mandatory="Y"
outbound="seperate"/>
</Event>
Sample external
definition for a New Order in ETI … note each field must have a dictionary
entry which defines its type in the external model.
<Message
id="NewOrderRequestSimple"
msgType="10125">
<Field id="msgSeqNum" mand="Y"/>
<Field id="senderSubID" mand="Y"/>
<Field id="price" mand="Y"/>
<Field
id="senderLocationID"
mand="N"/>
<Field id="clOrdId" mand="Y"/>
<Field id="orderQty" mand="Y"/>
<Field id="filler1c" len="4"/> <!-- maxShow tag210 -->
<Field
id="simpleSecurityID"
mand="Y"/>
<Field id="accountType" mand="N"/>
<Field id="side" mand="Y"/>
<Field
id="priceValidityCheckType"
mand="Y"/>
<Field id="timeInForce" mand="Y"/>
<Field id="execInst" mand="Y"/>
<Field
id="uniqueClientCode"
mand="N"/>
<Field id="filler3" len="3"
comment="pad3"/>
</Message>
Sample CODEC for a
New Order in BSE ETI
<MessageMap
id="BaseBSEOrder" messageId="" ignore="true"
extends="BaseRequest">
<Map
field="marketSegmentID"><Hook type="encode"
code="encodeMarketSegmentID( msg.getInstrument() )"/></Map>
<Map eventAttr="securityId"
field="simpleSecurityID">
<Hook type="encode"
code="encodeSimpleSecurityId( msg.getInstrument() )"/>
<Hook type="decode"
code="_securityId = _builder.decodeUInt()"/>
</Map>
<Map
field="priceValidityCheckType"><Hook type="encode"
code="_builder.encodeByte( (byte)0 )"/></Map>
<Map
field="accountType"><Hook type="encode"
code="_builder.encodeByte( (byte)20 )"/></Map>
<Map
field="maxPricePercentage"><Hook type="encode"
code="_builder.encodePrice( 0.5 )"/></Map>
<Map
field="senderLocationID"><Hook type="encode"
code="_builder.encodeLong( _locationId )"/></Map>
<Map
field="orderCapacity"><Hook type="encode"
code="_builder.encodeByte( (byte)1 )"/></Map>
<Map
field="positionEffect"><Hook type="encode"
code="_builder.encodeByte( (byte)'C' )"/></Map>
<Map
field="account"><Hook type="encode"
code="_builder.encodeStringFixedWidth( _account, 2
)"/></Map>
<Map
field="applSeqIndicator"><Hook type="encode"
code="_builder.encodeByte( (byte)0 )"/></Map>
<Map
field="execInst"><Hook type="encode"
code="_builder.encodeByte( (byte)2 )"/></Map>
<Map
field="uniqueClientCode">
<Hook type="encode"
code="_builder.encodeStringFixedWidth( _uniqueClientCode, 12 )"/>
<Hook type="decode"
code="_builder.skip( 12 )"/>
</Map>
</MessageMap>
<MessageMap
id="NewLimitOrder"
eventId="NewOrderSingle"
messageId="NewOrderRequestSimple" extends="BaseBSEOrder"
encodeFunc="encodeNOS">
<Map
field="productComplex"><Hook type="encode"
code="_builder.encodeByte( (byte)1 )"/></Map>
<Hook type="postDecode"
code="enrich( msg ) ; msg.setOrdType( OrdType.Limit )"/>
</MessageMap>
Note hooks allow
overriding of the default code generation which is based on comparing the
internal model dictionary entry with the external model dictionary entry. Map
entries are only added for fields that don’t want the default behaviour.
Sample Generated
Encoder for NOS :-
public final
void encodeNewLimitOrder( final NewOrderSingle msg ) {
final int now = _tzCalculator.getNowUTC();
_builder.start( MSG_NewOrderRequestSimple
);
if ( _debug ) {
_dump.append( " encodeMap=" ).append(
"NewOrderRequestSimple" ).append( " eventType=" ).append( "NewOrderSingle"
).append( " : " );
}
if ( _debug ) _dump.append( "\nField:
" ).append( "msgSeqNum" ).append( " : " );
_builder.encodeUInt(
(int)msg.getMsgSeqNum() );
if ( _debug ) _dump.append( "\nHook :
" ).append( "senderSubID" ).append( " : " ).append(
"encode" ).append( " : " );
_builder.encodeUInt( _senderSubID ); //
senderSubID;
if ( _debug ) _dump.append( "\nField:
" ).append( "price" ).append( " : " );
_builder.encodeDecimal( msg.getPrice() );
if ( _debug ) _dump.append( "\nHook :
" ).append( "senderLocationID" ).append( " : "
).append( "encode" ).append( " : " );
_builder.encodeLong( _locationId );
if ( _debug ) _dump.append( "\nField:
" ).append( "clOrdId" ).append( " : " );
_builder.encodeStringAsLong(
msg.getClOrdId() );
if ( _debug ) _dump.append( "\nField:
" ).append( "orderQty" ).append( " : " );
_builder.encodeQty( (int)msg.getOrderQty()
);
if ( _debug ) _dump.append( "\nField:
" ).append( "filler1c" ).append( " : " );
_builder.encodeFiller( 4 );
if ( _debug ) _dump.append( "\nHook :
" ).append( "simpleSecurityID" ).append( " : "
).append( "encode" ).append( " : " );
encodeSimpleSecurityId( msg.getInstrument()
);
if ( _debug ) _dump.append( "\nHook :
" ).append( "accountType" ).append( " : " ).append(
"encode" ).append( " : " );
_builder.encodeByte( (byte)20 );
if ( _debug ) _dump.append( "\nField:
" ).append( "side" ).append( " : " );
_builder.encodeByte( transformSide(
msg.getSide() ) );
if ( _debug ) _dump.append( "\nHook :
" ).append( "priceValidityCheckType" ).append( " : "
).append( "encode" ).append( " : " );
_builder.encodeByte( (byte)0 );
if ( _debug ) _dump.append( "\nField:
" ).append( "timeInForce" ).append( " : " );
final TimeInForce tTimeInForceBase =
msg.getTimeInForce();
final byte tTimeInForce = (
tTimeInForceBase == null ) ?
DEFAULT_TimeInForce : transformTimeInForce( tTimeInForceBase );
_builder.encodeByte( tTimeInForce );
if ( _debug ) _dump.append( "\nHook :
" ).append( "execInst" ).append( " : " ).append(
"encode" ).append( " : " );
_builder.encodeByte( (byte)2 );
if ( _debug ) _dump.append( "\nHook :
" ).append( "uniqueClientCode" ).append( " : "
).append( "encode" ).append( " : " );
_builder.encodeStringFixedWidth(
_uniqueClientCode, 12 );
if ( _debug ) _dump.append( "\nField:
" ).append( "filler3" ).append( " : " );
_builder.encodeFiller( 3 );
_builder.end();
}
Sample debug log
output … essential for debugging the model when testing exchange connectivity.
{ ENCODE msgType=10125 encodeMap=NewOrderRequestSimple eventType=NewOrderSingle :
Field:
msgSeqNum : uint 10, bytes=4, offset=16,
raw=[ 0A 00 00 00 ]
Hook :
senderSubID : encode : uint 810701002,
bytes=4, offset=20, raw=[ CA 50 52 30 ]
Field: price :
decimal 62.9, bytes=8, offset=24, raw=[
80 C8 E9 76 01 00 00 00 ]
Hook :
senderLocationID : encode : long 1234567890123456, bytes=8, offset=32, raw=[ C0 BA 8A 3C D5 62
04 00 ]
Field: clOrdId
: stringAsLong 38470008 (len=8), bytes=8, offset=40, raw=[ 78 01 4B 02 00 00
00 00 ]
Field: orderQty
: qty 10, bytes=4, offset=48, raw=[ 0A
00 00 00 ]
Field: filler1c
: filler len=4, bytes=4, offset=52, raw=[ 00 00 00 00 ]
Hook :
simpleSecurityID : encode : int 1000627,
bytes=4, offset=56, raw=[ B3 44 0F 00 ]
Hook :
accountType : encode : byte ^T, bytes=1,
offset=60, raw=[ 14 ]
Field: side :
byte ^A, bytes=1, offset=61, raw=[ 01 ]
Hook :
priceValidityCheckType : encode : byte ^@,
bytes=1, offset=62, raw=[ 00 ]
Field:
timeInForce : byte ^@, bytes=1,
offset=63, raw=[ 00 ]
Hook : execInst
: encode : byte ^B, bytes=1, offset=64,
raw=[ 02 ]
Hook :
uniqueClientCode : encode : stringFixedWidth OWN (len=12),
bytes=12, offset=65, raw=[ 4F 57 4E 00 00 00 00 00 00 00 00 00 ]
Field: filler3
: filler len=3, bytes=3, offset=77, raw=[ 00 00 00 ]
} bytes=80
16:15:12.445
[info]
OUT [exchangeSession1]:
0000
P....'...............PR0...v.......<.b..x.K.......
0050
.......D.......OWN............
0100
1
10 20 30 40 50
OUT [exchangeSession1]:
0000 50 00 00
00 8D 27 00 00 00 00 00 00 00 00 00 00 0A 00 00 00 CA 50 52 30 80 C8 E9 76 01
00 00 00 C0 BA 8A 3C D5 62 04 00 78 01 4B 02 00 00 00 00 0A 00
0050 00 00 00
00 00 00 B3 44 0F 00 14 01 00 00 02 4F 57 4E 00 00 00 00 00 00 00 00 00 00 00
00
0100
1
2 3 4
5 6 7
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
byteCount=80
16:15:12.445
[info] OUT [exchangeSession1]:
NewOrderSingleImpl , clOrdId=38470008, account=, text=, exDest=,
securityExchange=, price=62.9, orderQty=10, execInst=null, handlInst=null,
orderCapacity=null, ordType=Limit, securityType=, securityIDSource=,
timeInForce=Day, bookingType=null, orderReceived=[null], orderSent=[null],
instrument=1000627, client=, origClOrdId=, securityId=, symbol=, currency=,
transactTime=[null], sendingTime=[null], side=Buy, srcLinkId=, onBehalfOfId=,
msgSeqNum=10, possDupFlag=N
Because the code is
generated for both encoding and decoding, then the exchange simulator can
simulate any exchange in the model.
I wrote the code
generator from scratch in a couple of weeks, its completely custom and not
general purpose.
The resulting
internal POJO's interfaces, and CODEC's for external to internal translation
result in over 100,000 lines of generated high quality code.