FIXEdge Java Trade Capture solution

FIXEdge Java Trade Capture solution

Trade Capture solution in FIXEdge Java is available starting from version 1.13.0.

ICE Trade Capture setup (root rule template)

This document provides a copy/paste root rule template to run the ICE integration using:

  • container/src/main/dist/conf/ICECore_extension.groovy (session orchestration + subscription sequencing)

  • container/src/main/dist/conf/ICEPersist_extension.groovy (optional JDBC persistence)

Prerequisites

  • FIX session named ICE is configured and can connect. A sample initiator is shipped as container/src/main/dist/conf/session/s_fix_ICE.properties (adjust host/credentials for your environment); schedules.xml must include <schedule id="ICE"> so FEJ starts it on load.

  • If you want persistence, the datasource name you reference (example: ice_datasource) must be configured in history.properties.

Root rule template

Use a Groovy package that matches your deployment (example: configuration.icetcinfej.businessrules).
sessionIds must list every FIX session id (comma-separated) that runs ICE trade capture; it drives ICECore_extension, ICEPersist_extension (async consumer JDBC resolution), and the connect event below.

package configuration.icetcinfej.businessrules import com.epam.fej.routing.RoutingContext import static dsl.CommonRulesDsl.rulesDSL rulesDSL(routingContext as RoutingContext) { // --- Persistence wiring (optional, but recommended) --- // Declares a JDBC repository name used by ICEPersist_extension.groovy. histories { db("ice_datasource") {} } config { // FIX session ids (comma-separated). Same list drives ICECore + ICEPersist (and should match s_fix_<id>.properties + schedules). set "sessionIds" value "ICE" // Example second session: set "sessionIds" value "ICE,ICE1" // --- Security Definition Request (35=c, 321=3): "<SecurityID>:<CFICode>" (48 + 461) --- set "ICE.secDefSettings" value (["5:FXXXXX", "0:OXXXXX", "1:OXXXXX"]) // Per-session copy when using multiple ICE sessions (ICECore reads "<sessionId>.secDefSettings"): // set "ICE1.secDefSettings" value (["5:FXXXXX", "0:OXXXXX"]) // --- UDS (35=c, 321=101): "<SecurityID>:<anything>" (only the part before ':' is sent as 48) --- set "ICE.udsSettings" value (["5:FXXXXX", "0:OXXXXX", "1:OXXXXX"]) // set "ICE1.udsSettings" value ([]) // JDBC: per-session first, then shared fallback (ICEPersist_extension.groovy). set "ICE.datasource" value "ice_datasource" // set "ICE1.datasource" value "ice_datasource" // Persist path for ICECore (UDS/SecDef daily flags, AE dedup, last report time). set "persistPath" value "./logs/persist" // Optional: async JDBC via JMS — set per FIX session id (not one flag for all sessions). set "ICE.persist.async" value true set "ICE1.persist.async" value true } extension { set "**/ICECore_extension.groovy;**/ICEPersist_extension.groovy" } eventRules { // session(...) uses full-regex match on session id; align with sessionIds (single: "ICE", two: "(ICE|ICE1)"). FixSessionStateEventRule("On ICE session(s) connected") { condition { session "ICE" sessionState connected } action { log "On ICE session connected" } } } messageRules { messageRule("ICE observe: Security Definition response (35=d)") { source { id "ICE" } condition { msgType "d" } action { log "On d message received" } } messageRule("ICE observe: UDS (msgType UDS)") { source { id "ICE" } condition { msgType "UDS" } action { log "On UDS message received" } } messageRule("ICE observe: Trade Capture Report (35=AE) + dedup check") { source { id "ICE" } condition { msgType "AE" } action { log "On AE message received" custom { ctx -> def duplicated = RoutingContext.isTrdCapRepDuplicated(ctx.sourceParams.id, ctx.message, routingContext) if (duplicated) { log "AE is duplicated (skipping custom processing)" } } } } } }

Multi-session observe / events: when sessionIds is ICE,ICE1, set the same alternation everywhere you filter by source, e.g. session "(ICE|ICE1)" and source { id "(ICE|ICE1)" } (regex is full match on the session id).

Local ActiveMQ (optional, for JMS / async persist)

Download and run ActiveMQ from https://activemq.apache.org/components/classic/download/ (e.g., bin\activemq.bat start).

Start a local ActiveMQ instance.

ActiveMQ exposes OpenWire on localhost:61616 and a web console on port 8161.

Async JDBC persist (JMS queue)

Async offload is per FIX session: set <sessionId>.persist.async to true for each session that should enqueue (examples: ICE.persist.async, ICE1.persist.async). There is no single global flag that applies to every id in sessionIds.

How to set up async mode (checklist)

Do these in order before relying on async persist in production.

  1. JDBC persistence still required
    Async only changes where saves run (off the FIX thread). You still need:

    • histories { db("ice_datasource") {} } (or your repository name) in the root rule.

    • ICE.datasource and/or <sessionId>.datasource pointing at that repository name (see Notes below).

    • The JDBC URL/user/password for that repository in history.properties (or your deployment’s history config), same as synchronous ICE persist.

  2. JMS broker running
    Start ActiveMQ (see Local ActiveMQ above) or any broker compatible with jms-adaptor.properties Connection1. By default ProviderURI is tcp://localhost:61616. Adjust jms.adaptor.Connection.Connection1.* if your broker URL or credentials differ.

  3. Register ICE persist JMS clients
    In container/src/main/dist/conf/jms-adaptor.properties:

    • Set jms.adaptor.ClientNames to include both endpoints, for example:

      • jms.adaptor.ClientNames=ICE_PERSIST_IN, ICE_PERSIST_OUT
    • Remove or avoid leaving ClientNames empty while async is enabled in rules - otherwise enqueue to ICE_PERSIST_OUT will not work.

    • Update DeliveryMode and TimeToLive parameters to handle many messages without loss:

      • jms.adaptor.Client.ICE_PERSIST_OUT.DeliveryMode = Persist jms.adaptor.Client.ICE_PERSIST_OUT.TimeToLive = 43200000 jms.adaptor.Client.ICE_PERSIST_IN.DeliveryMode = Persist jms.adaptor.Client.ICE_PERSIST_IN.TimeToLive = 43200000

The file already defines ICE_PERSIST_OUT (producer) and ICE_PERSIST_IN (consumer) on queue ICE.PERSIST.JDBC with MessageType = Bytes (raw FIX bytes). See the block starting at “ICE async persist queue” in the same file.

  1. Start JMS schedules with FEJ
    In container/src/main/dist/conf/schedules.xml, uncomment the ICE_PERSIST_IN and ICE_PERSIST_OUT schedules (the block commented with “ICE async JDBC persist”). Both must start onLoad="true" so the producer and consumer connect when FEJ loads.

  2. Turn on async per FIX session in root rules
    In your root rule config { ... } (same package as Root rule template above), for each FIX session id that should offload JDBC:

    set "ICE.persist.async" value true // set "ICE1.persist.async" value true

    Match sessionIds: every session listed there can have its own <sessionId>.persist.async. Sessions you omit or set to false keep synchronous JDBC on the FIX thread.

  3. Reload / restart
    Restart FEJ (or reload configuration per your ops process) so jms-adaptor.properties, schedules.xml, and business rules are picked up together.

Flow: for a session with ICE.persist.async=true (or ICE1.persist.async=true, etc.), inbound FIX on that session → ICEPersist_extension.groovy enqueues raw bytes to ICE_PERSIST_OUT → queue ICE.PERSIST.JDBCICE_PERSIST_IN → same entity mapping and JdbcRepository.save() as in synchronous mode. The JMS consumer session id is internal (ICE_PERSIST_IN); it is not the FIX session id.

Datasource resolution for the async consumer: for messages that originated on a FIX session, JDBC uses that session’s <sessionId>.datasource if set; otherwise ICE.datasource. For work pulled from the queue without a prior FIX context, ICEPersist_extension.groovy resolves JDBC by scanning sessionIds in order, then ICE.datasource (see class comment on IcePersistAsyncConfig in ICEPersist_extension.groovy).

Default: each <sessionId>.persist.async is unset or false → that session persists on the FIX thread (unless the message was already produced by the async path).

Quick disable: set each <sessionId>.persist.async to false or remove those lines; optionally clear ICE_PERSIST_OUT / ICE_PERSIST_IN from jms.adaptor.ClientNames and comment the ICE_PERSIST_* schedules again so FEJ does not hold JMS connections.

Notes

  • Package and imports in the template match a typical business-rules module; adjust package to your project layout. RoutingContext is required for isTrdCapRepDuplicated in the sample messageRule.

  • Per-session async JDBC: ICEPersist_extension.groovy reads ICE.persist.async only for FIX session ICE, ICE1.persist.async for ICE1, and so on. Mixed setups (one session async, another sync) are supported.

  • ICE.secDefSettings must include CFICode (tag 461) because ICE requires it for outgoing Security Definition Request (35=c with 321=3).

  • Persistence is controlled by per-session <sessionId>.datasource, with optional shared fallback ICE.datasource (checked after the per-session property):

    • if it’s set, ICEPersist_extension.groovy saves mapped entities to the JDBC repository.

    • if it’s not set, persistence rules will skip saving.

  • Turning off async JDBC offload: see Quick disable under Async JDBC persist (JMS queue) above (same steps as disabling ICE_PERSIST_* clients and schedules).

  • How to turn off JDBC saving:

    • Option 1 (recommended): remove (or comment out) ICE.datasource and any <sessionId>.datasource from your root rule config. ICEPersist_extension.groovy will keep running but will not write anything.

    • Option 2: remove ICEPersist_extension.groovy from the extension { ... } list if you don’t want persistence rules loaded at all.

  • The ICE core extension stores lightweight state in persistPath to avoid repeating UDS/SecDef requests every day and to support AE dedup.

Where isTrdCapRepDuplicated cache is stored (and how to clean it)

RoutingContext.isTrdCapRepDuplicated(...) is provided by ICECore_extension.groovy and checks whether the current AE report was already seen. Internally it stores a dedup key in the routing context persistence map.

  • Storage location: persistPath/<sessionId>_<weekBucket> (example: ./logs/persist/ICE_2871)

    • The <weekBucket> is computed by weeksSinceEpochLegacy() (roughly “current epoch week”).

  • On-disk format: FEJ persistence maps are backed by an embedded file DB. Expect files like:

    • <alias>.data, <alias>.script, <alias>.properties, <alias>.log

    • They are created under the alias path derived from persistPath.

  • Why it can grow: every unique AE adds a new dedup key entry; long-running sessions with high AE volume will increase disk usage under persistPath.

Cleanup recommendation (disk space)

  • Rotate by age: keep only the last N week buckets per session (example: last 2–4 weeks) under persistPath, delete older ICE_<weekBucket>* folders/files.

  • Planned maintenance: perform cleanup during a maintenance window (best-effort: stop FEJ or disconnect the session) to avoid deleting files that are currently open.

  • If you need to force refresh / clear cache: delete the current week bucket for the session (example: ./logs/persist/ICE_2871*). This will reset:

    • AE dedup cache

    • last trade capture timestamp

    • “UDS/SecDef already refreshed today” flags