Skip to content

Protocol Support Library Examples

Learning objectives

After reading this chapter you will understand:

  • where protocol support-library source code lives
  • the Python and C APIs exposed by each parser library
  • how to run the example clients for each gateway
  • how to validate gateway connectivity quickly using reusable example code
  • how to navigate parser-library source files when integrating external clients

Scope

This chapter covers source-code examples for all external protocol gateways and the REST API:

Protocol Gateway Port Directory
ALF pm-alf-gwy 5565 docs/examples/alf
CALF pm-md-gwy 5570 docs/examples/calf
RALF pm-ralf-gwy 5580 docs/examples/ralf
DC1 pm-dc-gwy 5590 docs/examples/dc
BALF pm-balf-gwy 5560 docs/examples/balf
REST pm-api-gwy 8080 docs/examples/REST

This chapter does not cover the ready-to-use engine configuration templates under docs/examples/ref_data — see Example Engine Configs for those.

See also: Protocol Overview


ALF support library and examples

ALF (pm-alf-gwy) is the primary order-entry gateway. External clients connect over TCP and exchange text lines using the ALF wire format.

Source files

File Description
alf/README.txt Directory overview and quick-start
alf/python/alf_parser.py Python parser, builder, and session helper
alf/python/alf_client.py Interactive Python client with tab-completion
alf/c/alf_parser.h C library header
alf/c/alf_parser.c C library implementation
alf/c/alf_client.c Interactive C client with readline
alf/c/Makefile Build helper

Wire format

VERB|KEY=VALUE|KEY=VALUE\n

Command verbs and field keys are case-insensitive (normalized to uppercase). Segments without = are silently skipped. Duplicate keys: last value wins. Response values preserve case: keep the complete ORDER_ID unchanged when sending AMEND or CANCEL. Prices on this TCP connection are decimal display prices, not the engine's internal integer ticks.

Python API — alf_parser.py

parse_alf_line(line: str) → AlfMessage : Parse one ALF line. Returns an AlfMessage(msg_type, fields) frozen dataclass. Raises AlfParseError on malformed input.

build_alf_line(msg_type: str, fields: dict | None) → str : Build one \n-terminated ALF line. Raises AlfParseError if the message type is invalid, a key is empty or contains |, =, CR or LF, or a value contains |. CR/LF in values are replaced with spaces.

AlfSession.connect(host, port, gateway_id, client_name, timeout) → AlfSession : Open a TCP connection and complete the HELLO/WELCOME handshake. Validates WELCOME, bounds the handshake with timeout (default five seconds), and closes the socket on failure. After success reads block. It does not start a receive loop, keepalives, or automatic reconnects.

AlfSession.send(msg_type, fields) : Build and send one ALF line over the established connection.

AlfSession.recv_msg() → AlfMessage : Block until one complete \n-terminated line arrives and parse it. Buffers fragmented and coalesced TCP data. Use one reader per session.

AlfSession.send_raw(line) : Send one line without a trailing newline; embedded CR/LF are rejected. Both send methods serialize socket writes with a lock.

AlfSession.close() : Send EXIT and close the socket.

Python usage pattern

from alf_parser import parse_alf_line, build_alf_line, AlfSession, AlfMessage

# Parse one line received from the gateway
msg: AlfMessage = parse_alf_line("ACK|ORDER_ID=abc|ACCEPTED=TRUE|SYMBOL=AAPL|TAG=ORDER-001")
print(msg.msg_type)   # "ACK"
print(msg.fields)     # {"ORDER_ID": "abc", "ACCEPTED": "TRUE", "SYMBOL": "AAPL", "TAG": "ORDER-001"}

# Build a line to send
line: str = build_alf_line("NEW", {
    "SYM": "AAPL", "SIDE": "BUY", "TYPE": "LIMIT",
    "QTY": "100", "PRICE": "150.00", "TAG": "ORDER-001",
})
# → "NEW|SYM=AAPL|SIDE=BUY|TYPE=LIMIT|QTY=100|PRICE=150.00|TAG=ORDER-001\n"

For a complete query session, including interleaved events and keepalives, use the application example below.

Run Python interactive client

cd docs/examples/alf/python

# Connect to local gateway as TRADER01
python3 alf_client.py --id TRADER01

# Connect to remote gateway with a custom client name in logs
python3 alf_client.py --host 10.0.0.5 --port 5565 --id MM01 --client "my-mm-bot"

At the prompt, tab-completion is available for commands, field names, and enum values. Command history is persisted to ~/.alf_client_history.

[TRADER01]> NEW|SYM=AAPL|SIDE=BUY|TYPE=LIMIT|QTY=100|PRICE=150.00|TAG=ORDER-001
[TRADER01]> AMEND|ID=<order-id>|PRICE=151.00|RTAG=req-001
[TRADER01]> CANCEL|ID=<order-id>|RTAG=req-002
[TRADER01]> KILL
[TRADER01]> SYMBOLS
[TRADER01]> ORDERS
[TRADER01]> POS
[TRADER01]> SESSION
[TRADER01]> STATUS
[TRADER01]> HELP
[TRADER01]> EXIT

AMEND and CANCEL need the order's full 32-character ID, exactly as the ACK line or the ORDERS table prints it.

C API — alf_parser.h

int alf_parse_line(char *line, alf_message_t *out) : Parse one mutable ALF line in-place. Returns 0 on success, negative on error (-1 null args, -2 empty line, -3 invalid verb, -4 too many fields, -6 verb/key/value exceeds storage).

const char *alf_get_field(const alf_message_t *msg, const char *key) : Return the value for key (case-insensitive), or NULL if absent.

int alf_build_line(char *out, size_t cap, const char *msg_type, const char * const *kv) : Write one ALF line into out. kv is a NULL-terminated array of alternating key/value strings. Returns bytes written, including the newline, or -1 on invalid arguments or overflow. Rejects delimiter-bearing keys/values and replaces CR/LF in values with spaces.

C usage pattern

#include "alf_parser.h"

/* Parse */
char line[] = "ACK|ORDER_ID=abc|ACCEPTED=TRUE";
alf_message_t msg;
if (alf_parse_line(line, &msg) != 0) {
    fprintf(stderr, "Invalid ALF response\n");
    return 1;
}
printf("%s\n", msg.msg_type);                    /* "ACK" */
const char *accepted = alf_get_field(&msg, "ACCEPTED");
if (accepted) printf("%s\n", accepted);           /* "TRUE" */

/* Build */
const char *kv[] = {
    "SYM", "AAPL", "SIDE", "BUY",
    "TYPE", "LIMIT", "QTY", "100", "PRICE", "150.00", "TAG", "ORDER-001", NULL,
};
char buf[4096];
int len = alf_build_line(buf, sizeof(buf), "NEW", kv);
if (len < 0) {
    fprintf(stderr, "Cannot build ALF command\n");
    return 1;
}
/* → "NEW|SYM=AAPL|SIDE=BUY|TYPE=LIMIT|QTY=100|PRICE=150.00|TAG=ORDER-001\n" */

This fragment belongs inside your application's function; include <stdio.h> for its diagnostics. Send all len bytes using a partial-write loop, not a single unchecked write().

Build and run C client

cd docs/examples/alf/c
make

# Connect to local gateway
./alf_client --id TRADER01

# Connect to remote gateway; disable ANSI colours
./alf_client --host 10.0.0.5 --port 5565 --id TRADER01 --no-color

The C client supports the same commands and tab-completion as the Python client.

Use ALF as a foundation for your own program

The examples are standalone source, not an installed edumatcher SDK. Download or copy the files from the table above into your project:

  • Python: copy alf_parser.py beside your application and import it. The parser/session module uses only the standard library and does not require readline. Python 3.9+ is sufficient for these examples; the installed engine has its own runtime requirements.
  • C: copy alf_parser.h and alf_parser.c, include the header, and compile the implementation with your sources, for example cc -std=c11 -Wall -Wextra my_bot.c alf_parser.c -o my_bot. The parser has no readline dependency. Readline is needed only for the interactive client.
  • Treat the interactive clients as lifecycle/event-dispatch references. Replace their terminal display and local caches with your application's handlers and state; do not make a headless bot depend on the REPL.

Configure the participant ID in the engine before connecting. The examples below assume TRADER01 is authorized, the engine and gateway are running, and no other client is using that ID. QUOTE additionally requires the MARKET_MAKER role.

This runnable Python query illustrates one socket reader, periodic inbound activity, a bounded response wait, and matching multiline termination. Save it beside alf_parser.py and run python3 my_bot.py:

import time

from alf_parser import AlfSession

session = AlfSession.connect("127.0.0.1", 5565, "TRADER01", timeout=5.0)
try:
    interval = session.welcome.idle_timeout / 3
    next_ping = time.monotonic() + interval
    deadline = time.monotonic() + 10.0
    session.send("SYMBOLS")
    while True:
        now = time.monotonic()
        if now >= deadline:
            raise TimeoutError("SYMBOLS response did not complete")
        if now >= next_ping:
            session.send("PING")
            next_ping = now + interval
        session.sock.settimeout(min(next_ping - now, deadline - now))
        try:
            msg = session.recv_msg()
        except TimeoutError:
            continue
        print(msg.msg_type, msg.fields)
        if msg.msg_type == "ERR":
            raise RuntimeError(f"Gateway error: {msg.fields}")
        if msg.msg_type == "END" and msg.fields.get("TYPE") == "SYMBOLS":
            break
finally:
    session.close()

The timeout here is an application policy, not the gateway's idle timeout. The helper retains incomplete lines across receive timeouts. For a long-lived bot, continue receiving and dispatching after the initial query. The interactive Python client's _recv_loop and _keepalive_loop show a threaded alternative; its send lock prevents command/keepalive bytes from interleaving.

For a C bot, alf_parser.c only parses/builds messages: it does not connect, authenticate, buffer TCP, schedule keepalives, or reconnect. Start from connect_gateway, do_handshake, gwy_send, process_socket_data, and the select() loop in alf_client.c. These are internal example functions, not exported library APIs. Adapt them into your own transport/event loop:

  1. Connect and send HELLO; validate WELCOME before sending commands.
  2. Buffer bytes until newline; one read can contain part of a line or many lines. Retry interrupted I/O and handle partial writes and disconnects.
  3. Check every parser/builder return code and nullable field lookup.
  4. Send PING more frequently than WELCOME|IDLE, even while receiving heartbeats or broadcasts. Receiving data does not reset gateway idle time.
  5. Dispatch unsolicited events while collecting query rows. Finish only on the matching END|TYPE=...; serialize multiline queries because the example collectors track only one active query at a time.
  6. On shutdown send EXIT and close the socket. On connection loss report the failure, reconnect explicitly, and reconcile engine state before resuming.

Keep the following application responsibilities explicit:

  • Correlate order entry with TAG and amend/cancel requests with RTAG; handle both gateway ERR and engine rejection ACK|ACCEPTED=FALSE. Do not assume the next message after a send is its response or blindly replay an order whose outcome is unknown after disconnect.
  • Preserve full order IDs. FILL includes SYMBOL/SIDE for orders not in your cache and may include LIQUIDITY/TRADE_IDS; stops emit TRIGGERED. Keep ALF decimal prices separate from internal tick representations.
  • DC_FILL is a second report of an execution, not another position change. The demos display it without booking it again.
  • Bare POS and its realized P&L are client-local, starting with fills seen by this connection. Use ORDERS and POS|GW=TRADER01 to reconcile engine orders/positions after reconnect; the query returns POSITION, POS_ENTRY, then END|TYPE=POSITION. STATUS is also a local display, not a wire command.
  • The C demo is bounded: 256 cached orders, 512 symbols/positions and query rows, 64 fields per message, 63-byte keys and 511-byte values. It reports capacity errors instead of silently truncating protocol data. Review these limits and add persistence/reconciliation appropriate to your workload.
  • The examples use plain TCP, not TLS; use a trusted network or a protected transport when deploying outside a local development environment.

From a repository checkout, validate adaptations against the real gateway and engine workflows with:

poetry run pytest tests/test_alf_examples.py --no-cov

The suite includes compiled C behavior checks and interactive Python/C round trips through real engine handlers. C checks require a compiler, make, and GNU readline development files; they are skipped if unavailable.

Manual gateway sanity check

nc 127.0.0.1 5565

Then type:

HELLO|CLIENT=manual01|PROTO=ALF1|ID=TRADER01
NEW|SYM=AAPL|SIDE=BUY|TYPE=MARKET|QTY=10
SYMBOLS
ORDERS

See also:


CALF support library and examples

CALF (pm-md-gwy) is the market data feed gateway. Subscribers connect over TCP and receive a stream of top-of-book, trade, and other market-data events.

The example subscribers are more than a single trivial subscription: they combine TOP, TRADE, STATE (including the session-wide SYM=* wildcard), and Level 2 DEPTH in one client, maintain a small top-of-book cache (incremental MD updates omit unchanged sides), pretty-print the DEPTH ladder, detect gaps in the per-(CH,SYM) SEQ counters, and check WELCOME|CH_SUPPORTED= before relying on channels that may not exist on an older gateway build.

Source files

File Description
calf/README.txt Directory overview and quick-start
calf/calf_parser.py Python parser and builder
calf/calf_subscriber.py Python subscriber example
calf/calf_parser.h C library header
calf/calf_parser.c C library implementation
calf/calf_subscriber.c C subscriber example
calf/Makefile Build helper

Wire format

MSGTYPE|KEY=VALUE|KEY=VALUE\n

Fields missing = raise CalfParseError. Duplicate keys: last value wins (same as ALF), the parser never raises for this case.

Python API — calf_parser.py

parse_calf_line(line: str) → CalfMessage : Parse one CALF line. Returns a CalfMessage(msg_type, fields) frozen dataclass. Raises CalfParseError on malformed input.

build_calf_line(msg_type: str, fields: dict | None) → str : Build one \n-terminated CALF line. Raises CalfParseError for invalid message types or fields containing |.

Python usage pattern

from calf_parser import build_calf_line, parse_calf_line

# Build a HELLO line
line = build_calf_line("HELLO", {"CLIENT": "demo01", "PROTO": "CALF1"})
# → "HELLO|CLIENT=demo01|PROTO=CALF1\n"

# Parse a WELCOME response
msg = parse_calf_line("WELCOME|PROTO=CALF1|GW=md-gwy01|HBINT=1|REPLAY=30")
print(msg.msg_type)          # "WELCOME"
print(msg.fields["HBINT"])   # "1"

# Parse a streaming TOP-OF-BOOK update
top = parse_calf_line("TOP|SYM=AAPL|BID=149.50|ASK=150.00|SEQ=1042")
print(top.fields["BID"])     # "149.50"

Run Python example

cd docs/examples/calf

# Subscribe to TOP/TRADE/STATE/DEPTH for AAPL and MSFT
python3 calf_subscriber.py --host 127.0.0.1 --port 5570 \
    --symbols AAPL,MSFT

# Also subscribe to an index feed (skipped if the gateway doesn't advertise
# INDEX support in WELCOME|CH_SUPPORTED=)
python3 calf_subscriber.py --host 127.0.0.1 --port 5570 \
    --symbols AAPL --index EDU100

# Resume from a known sequence number (single-stream resume)
python3 calf_subscriber.py --host 127.0.0.1 --port 5570 \
    --resume --resume-ch TOP --resume-sym AAPL --lastseq 1042

Channels are not selected with a flag — the client always requests every channel the connected gateway advertises via WELCOME|CH_SUPPORTED= (TOP, TRADE, STATE, DEPTH), plus a separate session-wide SUB|CH=STATE|SYM=* subscription and, if --index is given, SUB|CH=INDEX. Run python3 calf_subscriber.py --help for the full flag list, including --client and --no-state-wildcard (skip the session-wide STATE subscription).

C API — calf_parser.h

int calf_parse_line(char *line, calf_message_t *out) : Parse one mutable CALF line in-place. Returns 0 on success, negative on error.

const char *calf_get_field(const calf_message_t *msg, const char *key) : Return the value for key, or NULL if absent.

C usage pattern

#include "calf_parser.h"

char line[] = "TOP|SYM=AAPL|BID=149.50|ASK=150.00|SEQ=1042";
calf_message_t msg;
calf_parse_line(line, &msg);
printf("%s\n", calf_get_field(&msg, "BID"));  /* "149.50" */

Build and run C example

cd docs/examples/calf
make
./calf_subscriber 127.0.0.1 5570 AAPL,MSFT EDU100

Arguments are positional: host [port [symbols [index_id]]]. symbols is a comma-separated list (default AAPL); index_id is optional (default: skip the INDEX subscription).

Manual gateway sanity check

nc 127.0.0.1 5570

Then type:

HELLO|CLIENT=manual01|PROTO=CALF1
SUB|CH=TOP,TRADE|SYM=AAPL

See also:


RALF support library and examples

RALF (pm-ralf-gwy) is the post-trade dissemination gateway. Clients connect over TCP and receive execution reports, drop-copy events, and audit records.

The example subscribers subscribe to every channel a role is actually entitled to (CLEARING/DROP_COPY → that channel only, AUDIT → all three) in one combined SUB, then put the received EXEC/EOD data to use: a running per-symbol executed-volume tally that de-duplicates EXEC lines by EXEC_ID (a single executed trade is delivered once per subscribed channel, so an AUDIT client receives the same trade up to three times and naively counting raw lines would overstate volume). Both examples also detect gaps in the per-channel SEQ counters, cross-check the tally against each EOD's TRADE_COUNT, and print a final tally on Ctrl-C shutdown.

Source files

File Description
ralf/README.txt Directory overview and quick-start
ralf/ralf_parser.py Python parser and builder
ralf/ralf_subscriber.py Python subscriber example
ralf/ralf_parser.h C library header
ralf/ralf_parser.c C library implementation
ralf/ralf_subscriber.c C subscriber example
ralf/Makefile Build helper

Wire format

MSGTYPE|KEY=VALUE|KEY=VALUE\n

Identical text-line structure to CALF; field parsing rules are the same.

Python API — ralf_parser.py

parse_ralf_line(line: str) → RalfMessage : Parse one RALF line. Returns a RalfMessage(msg_type, fields) frozen dataclass. Raises RalfParseError on malformed input.

build_ralf_line(msg_type: str, fields: dict | None) → str : Build one \n-terminated RALF line. Raises RalfParseError for invalid message types or fields containing |.

Python usage pattern

from ralf_parser import build_ralf_line, parse_ralf_line

# Build a HELLO line
line = build_ralf_line("HELLO", {
    "CLIENT": "demo01",
    "PROTO": "RALF1",
    "ROLE": "CLEARING",
    "LASTSEQ": "0",
})
# → "HELLO|CLIENT=demo01|PROTO=RALF1|ROLE=CLEARING|LASTSEQ=0\n"

# Parse a WELCOME response
msg = parse_ralf_line("WELCOME|PROTO=RALF1|GW=ralf-gwy01|ROLE=CLEARING")
print(msg.msg_type)         # "WELCOME"
print(msg.fields["ROLE"])   # "CLEARING"

# Parse an inbound execution report
fill = parse_ralf_line("FILL|SYM=AAPL|SIDE=BUY|QTY=100|PRICE=150.00|SEQ=77")
print(fill.fields["PRICE"]) # "150.00"

Run Python example

cd docs/examples/ralf

# Subscribe as CLEARING role — --role defaults the subscribed channel(s)
# to exactly what that role may access (here: just the CLEARING channel)
python3 ralf_subscriber.py --host 127.0.0.1 --port 5580 --role CLEARING

# Resume from a known sequence number
python3 ralf_subscriber.py --host 127.0.0.1 --port 5580 \
    --role CLEARING --lastseq 77

Pass --channels explicitly to override a role's default channel set — this is also how to trigger ERR|CODE=ENTITLEMENT_DENIED on purpose, by requesting a channel outside the role's entitlement:

# CLEARING is only entitled to the CLEARING channel, so requesting AUDIT
# gets rejected
python3 ralf_subscriber.py --role CLEARING --channels AUDIT

Run python3 ralf_subscriber.py --help for the full flag list, including --client and --symbols (defaults to *).

C API — ralf_parser.h

int ralf_parse_line(char *line, ralf_message_t *out) : Parse one mutable RALF line in-place. Returns 0 on success, negative on error.

const char *ralf_get_field(const ralf_message_t *msg, const char *key) : Return the value for key, or NULL if absent.

C usage pattern

#include "ralf_parser.h"

char line[] = "FILL|SYM=AAPL|SIDE=BUY|QTY=100|PRICE=150.00|SEQ=77";
ralf_message_t msg;
ralf_parse_line(line, &msg);
printf("%s\n", ralf_get_field(&msg, "PRICE")); /* "150.00" */

Build and run C example

cd docs/examples/ralf
make
./ralf_subscriber 127.0.0.1 5580 AUDIT

Arguments are positional: host [port [role]] (role one of CLEARING, DROP_COPY, AUDIT; default CLEARING).

Manual gateway sanity check

nc 127.0.0.1 5580

Then type:

HELLO|CLIENT=manual01|PROTO=RALF1|ROLE=CLEARING|LASTSEQ=0
SUB|CH=CLEARING|SYM=*

See also:


DC support library and examples

DC1 (pm-dc-gwy) relays the engine's drop-copy feed to plain TCP clients. A client names one participant's gateway ID in HELLO and receives that participant's fills as unsolicited DC_FILL lines; there is no SUB step.

The example subscribers keep a running per-symbol tally of fills, executed quantity, VWAP and the MAKER/TAKER split (DC_FILL carries no side, so it is an activity tally, not a net position), and print it on Ctrl-C shutdown. Drop-copy SEQ is a separate counter for each gateway ID, so a per-ID session sees 1, 2, 3, … with no gaps. The examples report a SEQ that skips ahead (lost fills) and one that goes backwards (the engine restarted and the counter began again at 1).

Source files

File Description
dc/README.txt Directory overview and quick-start
dc/dc_parser.py Python parser and builder
dc/dc_subscriber.py Python subscriber example
dc/dc_parser.h C library header
dc/dc_parser.c C library implementation
dc/dc_subscriber.c C subscriber example
dc/Makefile Build helper

Wire format

MSGTYPE|KEY=VALUE|KEY=VALUE\n

Identical text-line structure to CALF and RALF; field parsing rules are the same.

Python API — dc_parser.py

parse_dc_line(line: str) → DcMessage : Parse one DC1 line. Returns a DcMessage(msg_type, fields) frozen dataclass. Raises DcParseError on malformed input.

build_dc_line(msg_type: str, fields: dict | None) → str : Build one \n-terminated DC1 line. Raises DcParseError for invalid message types or fields containing |.

Python usage pattern

from dc_parser import build_dc_line, parse_dc_line

# Build a HELLO line
line = build_dc_line("HELLO", {"CLIENT": "risk01", "PROTO": "DC1", "ID": "TRADER01"})
# → "HELLO|CLIENT=risk01|PROTO=DC1|ID=TRADER01\n"

# Parse an inbound fill
fill = parse_dc_line(
    "DC_FILL|SEQ=42|ORDER_ID=ORD-001|SYMBOL=AAPL|FILL_QTY=100|FILL_PRICE=150.05|LIQUIDITY=TAKER"
)
print(fill.fields["FILL_PRICE"])  # "150.05"

Run Python example

cd docs/examples/dc
python3 dc_subscriber.py --host 127.0.0.1 --port 5590 --id TRADER01

Run python3 dc_subscriber.py --help for the full flag list, including --client.

C API — dc_parser.h

int dc_parse_line(char *line, dc_message_t *out) : Parse one mutable DC1 line in-place. Returns 0 on success, negative on error.

const char *dc_get_field(const dc_message_t *msg, const char *key) : Return the value for key, or NULL if absent.

C usage pattern

#include "dc_parser.h"

char line[] = "DC_FILL|SEQ=42|SYMBOL=AAPL|FILL_QTY=100|FILL_PRICE=150.05";
dc_message_t msg;
dc_parse_line(line, &msg);
printf("%s\n", dc_get_field(&msg, "FILL_PRICE")); /* "150.05" */

Build and run C example

cd docs/examples/dc
make
./dc_subscriber 127.0.0.1 5590 TRADER01

Arguments are positional: host [port [gateway_id]] (default 127.0.0.1 5590 TRADER01).

Manual gateway sanity check

nc 127.0.0.1 5590

Then type:

HELLO|CLIENT=manual01|PROTO=DC1|ID=TRADER01

See also:


BALF support library and examples

BALF (pm-balf-gwy) is the binary order-entry gateway, intended for ultra-low-latency clients. Frames are fixed-length little-endian structs rather than text lines.

Source files

File Description
balf/balf_parser.py Python binary parser
balf/balf_parser.c C binary parser

Binary frame structure

Every BALF frame begins with an 8-byte header:

Offset Size Field
0 1 Magic (0xBA)
1 1 Version (0x01)
2 1 Message type
3 1 Flags
4 4 Sequence number (LE uint32)

Prices are encoded as fixed-point integers scaled by 100_000_000 (8 decimal places).

Supported message types

Constant Value Description
MSG_LOGON 0x01 Session logon
MSG_LOGON_ACK 0x02 Logon acknowledgement
MSG_NEW_ORDER 0x10 New order
MSG_ORDER_ACK 0x11 Order acknowledgement
MSG_CANCEL_ORDER 0x12 Cancel request
MSG_CANCEL_ACK 0x13 Cancel acknowledgement
MSG_AMEND_ORDER 0x14 Amend request
MSG_AMEND_ACK 0x15 Amend acknowledgement
MSG_EXECUTION_REPORT 0x20 Fill / execution report
MSG_HEARTBEAT 0x30 Heartbeat
MSG_HEARTBEAT_ACK 0x31 Heartbeat acknowledgement
MSG_LOGOUT 0x40 Session logout

Python API — balf_parser.py

parse_header(frame: bytes) → Header : Parse the 8-byte BALF header. Returns a Header(magic, version, msg_type, flags, seq_no) frozen dataclass. Raises ValueError on bad magic or unsupported version.

split_frame(frame: bytes) → tuple[Header, bytes] : Validate frame length against the expected size for the message type, then return (header, body_bytes). Raises ValueError for unknown message types or wrong frame sizes.

decode_price(raw: int) → float : Convert a raw fixed-point integer to a float price value.

parse_logon_ack(body: bytes) → dict : Decode a LOGON_ACK body. Returns a dict with keys gateway_id, accepted, reject_code, message.

describe_logon_ack(ack: dict) → str : One line saying whether the logon succeeded and, if not, what to do. A rejected logon is always followed by the gateway closing the connection, so "retry" means a new TCP connection and a new LOGON.

parse_order_ack(body: bytes) → dict : Decode an ORDER_ACK body. Returns a dict with keys client_order_id, order_id, timestamp_ns, accepted, reject_code, reject_name, reason.

parse_cancel_ack(body: bytes) → dict : Decode a CANCEL_ACK body: client_order_id, order_id, accepted, cancel_reason (0 = you asked, 255 = the exchange decided) and by_exchange.

parse_amend_ack(body: bytes) → dict : Decode an AMEND_ACK body: client_order_id, order_id, new_price, new_quantity, remaining_qty, accepted, priority_reset.

parse_stop_triggered(body: bytes) → dict : Decode a STOP_TRIGGERED body: a stop, stop-limit or trailing stop has fired and its execution reports follow. Keys client_order_id, order_id, stop_price, trigger_price, symbol, order_type, becomes.

parse_execution_report(body: bytes) → dict : Decode an EXECUTION_REPORT body. Returns a dict with keys client_order_id, order_id, fill_price, fill_qty, remaining_qty, timestamp_ns, symbol, side (BUY/SELL) and status (PARTIAL/FILLED). fill_price is already converted to a float via decode_price() — no further scaling is needed.

Python usage pattern

from balf_parser import (
    parse_header, split_frame, decode_price,
    parse_logon_ack, parse_order_ack, parse_execution_report,
    MSG_LOGON_ACK, MSG_ORDER_ACK, MSG_EXECUTION_REPORT,
    FRAME_SIZES,
)

# Decode a raw frame received from TCP
hdr, body = split_frame(raw_bytes)

if hdr.msg_type == MSG_EXECUTION_REPORT:
    report = parse_execution_report(body)
    price = report["fill_price"]  # already decoded to a float
    print(f"fill: {report['symbol']} qty={report['fill_qty']} price={price}")
elif hdr.msg_type == MSG_ORDER_ACK:
    ack = parse_order_ack(body)
    print(f"order_id={ack['order_id']} accepted={ack['accepted']}")

C reference decoder — balf_parser.c

Unlike the ALF and CALF C libraries, balf_parser.c has no header file and its functions are all static — it is a single self-contained reference program, not a linkable library. It builds a synthetic LOGON_ACK frame in memory, decodes it with the same split_frame/parse_header logic a real client would use, and prints the result as a self-test:

cd docs/examples/balf
cc -std=c11 -Wall -Wextra -pedantic -O2 -I../generated \
   balf_parser.c ../generated/edumatcher_order.c \
   ../generated/edumatcher_msg.c -o balf_parser
./balf_parser
# → LOGON_ACK gateway_id=TRADER01 accepted=1 reject_code=0 msg=ok
#   EXECUTION_REPORT ...
#   LOGON_ACK gateway_id=TRADER01 accepted=0 reject_code=255 msg=SYMBOLS_UNAVAILABLE
#   CANCEL_ACK ... / AMEND_ACK ... / STOP_TRIGGERED ...
#   balf_parser.c self-test: OK

EXECUTION_REPORT and STOP_TRIGGERED are decoded by the generated binding in docs/examples/generated (hence the extra sources); LOGON_ACK, CANCEL_ACK and AMEND_ACK are decoded by hand.

There is no BALF C (or Python) network client example — only the parsing logic is provided. A client written on it must handle three things beyond the happy path:

  • A held or refused LOGON_ACK. The gateway withholds the accepted ack until the engine has sent the symbol list, so it can take a moment. If the engine never does, the ack is accepted=0, reject_code=0xFF, msg=SYMBOLS_UNAVAILABLE, and the connection closes: reconnect and log on again. describe_logon_ack() reports this.
  • A LOGOUT from the gateway. The engine restarted and no longer holds the session; the connection closes. Log on again from scratch.
  • An ORDER_ACK with reject_code 0x03 for a symbol the engine does not list. To exercise BALF end-to-end, connect a TCP client to pm-balf-gwy and feed received frames through split_frame() (Python: balf_parser.py) or the C reference decoder's parsing functions.

Run the gateway

pm-balf-gwy

# Override bind port
pm-balf-gwy --port 5560

# Override engine host
pm-balf-gwy --engine-host 10.0.0.5

The gateway binds on port 5560 by default. BALF uses fixed-length binary frames so there is no text-mode manual sanity check; use the Python parser library or a binary client tool to verify connectivity.

See also:


REST API support library and examples

pm-api-gwy exposes a JSON REST API for order submission and session queries. The support library (api_gateway_client) uses only the Python standard library and the C standard socket API — no third-party HTTP libraries are required. The two WebSocket examples need the websockets package (pip install websockets).

Every Python example runs against a real exchange in make test-e2e (tests/test_examples_e2e.py), so they are kept working.

Source files

File Description
REST/python/api_gateway_client.py Python REST client library
REST/python/demo_info.py Print gateway status, symbols, and session info
REST/python/submit_market_order.py Submit a MARKET order from the command line
REST/python/order_lifecycle.py LIMIT with wait=ack and an Idempotency-Key, a safe retry, amend, replace, cancel, and a TICK_VIOLATION refusal
REST/python/book_snapshot.py The order book over REST: top of book for every symbol, one symbol's ladder, *_ticks, seq, UNKNOWN_SYMBOL
REST/python/history_pages.py Read a whole history list by following next_cursor
REST/python/private_stream.py The private WebSocket: snapshot, stream_seq gap detection, reconnect recovery
REST/python/market_data.py Market data: items subscribe, per-topic seq, trades resume
REST/python/admin_halt_reopen.py Admin: halt a symbol, list its orders, dry-run reopen, reopen or resume
REST/c/api_gateway_client.h C library header
REST/c/api_gateway_client.c C library implementation
REST/c/demo_info.c C equivalent of demo_info.py
REST/c/Makefile Build helper

Authentication

All requests require a Bearer token in the Authorization header. The default development key is key-trader-demo.

Configure via environment variables:

export EDUMATCHER_API_URL="http://127.0.0.1:8080"
export EDUMATCHER_API_KEY="key-trader-demo"

Python API — api_gateway_client.py

ApiGatewayClient(base_url, api_key, timeout=5.0) : Construct a client for the given base URL and API key.

client.get_json(path: str, params: dict | None = None) → dict : Issue a GET request (with params as the query string) and return the parsed JSON body.

client.post_json(path: str, payload: dict | None = None, idempotency_key: str | None = None) → dict : Issue a POST request with a JSON body and return the parsed JSON response. With idempotency_key, sends it as the Idempotency-Key header: reuse the same key to retry a write safely (the gateway replays the first reply).

client.patch_json(path, payload) / client.delete_json(path) : Amend and cancel.

client.pages(path: str, key: str, params: dict | None = None) → Iterator[dict] : Every item of a paginated list (key names it: trades, events, ...), following next_cursor until has_more is false.

Errors are two exception types, because a client must react differently:

  • ApiError: the gateway answered with an error envelope; status, code, reject_code, field and message come from it. Nothing reached the engine, so the request may be fixed and sent again.
  • ApiUnreachable: no answer at all. A write may or may not have been sent; retry it only with the same Idempotency-Key.

Both subclass RuntimeError.

Python usage pattern

import uuid

from api_gateway_client import ApiGatewayClient

client = ApiGatewayClient("http://127.0.0.1:8080", "key-trader-demo")

# Query endpoints
status  = client.get_json("/api/v1/status")
symbols = client.get_json("/api/v1/symbols")
session = client.get_json("/api/v1/session")

# Submit a MARKET order
result = client.post_json("/api/v1/orders", {
    "symbol":     "AAPL",
    "side":       "BUY",
    "order_type": "MARKET",
    "quantity":   100,
})
print(result["order_id"], result["status"])

# Submit and wait for the engine ACK; the key makes a retry safe
result = client.post_json("/api/v1/orders?wait=ack", {
    "symbol":     "MSFT",
    "side":       "SELL",
    "order_type": "MARKET",
    "quantity":   50,
}, idempotency_key=str(uuid.uuid4()))
if result["ack_timeout"]:
    print("sent; the ACK was late -- follow it, do not resubmit")
else:
    print(result.get("event"))   # engine acknowledgement event

Run Python examples

cd docs/examples/REST/python

# Print gateway status, symbols, and session information
python3 demo_info.py

# Submit a BUY MARKET order for 100 AAPL
python3 submit_market_order.py --side BUY --symbol AAPL --qty 100

# Submit and block until the engine ACKs
python3 submit_market_order.py --side SELL --symbol MSFT --qty 50 --wait-ack

# Override URL or key on the command line
python3 submit_market_order.py --url http://10.0.0.5:8080 --key mykey \
    --side BUY --symbol TSLA --qty 10

# One order's whole life; --price below the bid so it rests
python3 order_lifecycle.py --symbol AAPL --price 22.70

# The book over REST: every symbol's top of book, then AAPL's ladder
python3 book_snapshot.py --symbol AAPL --levels 5

# Today's trades, page by page
python3 history_pages.py --limit 50

# The private stream: place and cancel one order, watch both events arrive
python3 private_stream.py --symbol AAPL --price 22.70

# Market data: book for every symbol, trades for one; --trade makes a print
python3 market_data.py --symbol AAPL --trade

# Admin (an ADMIN key): halt, inspect, dry-run reopen, reopen
python3 admin_halt_reopen.py --symbol MSFT --key <admin key>

C API — api_gateway_client.h

ApiGatewayClient api_gateway_client(host, port, api_key) : Construct a client struct (stack-allocated).

char *api_gateway_get(const ApiGatewayClient *client, const char *path) : Issue a GET request and return the raw HTTP response body as a heap-allocated string. The caller must free() the result. Returns NULL on network or allocation failure.

C usage pattern

#include "api_gateway_client.h"
#include <stdlib.h>
#include <stdio.h>

const char *key = getenv("EDUMATCHER_API_KEY") ?: "key-trader-demo";
ApiGatewayClient client = api_gateway_client("127.0.0.1", 8080, key);

char *body = api_gateway_get(&client, "/api/v1/symbols");
if (body) {
    printf("%s\n", body);
    free(body);
}

Build and run C example

cd docs/examples/REST/c
make

# Print status, symbols, and session info
./demo_info

Manual API sanity check

curl -s -H "Authorization: Bearer key-trader-demo" \
     http://127.0.0.1:8080/api/v1/status | python3 -m json.tool

curl -s -X POST \
     -H "Authorization: Bearer key-trader-demo" \
     -H "Content-Type: application/json" \
     -d '{"symbol":"AAPL","side":"BUY","order_type":"MARKET","quantity":10}' \
     http://127.0.0.1:8080/api/v1/orders | python3 -m json.tool

See also:


Integration guidance

Use the parser libraries as the protocol boundary in your client code:

  1. Read bytes from TCP (or HTTP for REST).
  2. Split by newline into protocol lines (text protocols) or read fixed-size frames (BALF).
  3. Parse lines or frames using the appropriate parser library.
  4. Route by msg_type and fields.
  5. Track SEQ checkpoints per protocol rules to enable gap detection and replay on reconnect.

This keeps socket handling separate from protocol semantics and makes unit testing straightforward — pass raw strings or byte buffers directly into the parser functions without a network connection.