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¶
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.pybeside 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.handalf_parser.c, include the header, and compile the implementation with your sources, for examplecc -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:
- Connect and send
HELLO; validateWELCOMEbefore sending commands. - 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.
- Check every parser/builder return code and nullable field lookup.
- Send
PINGmore frequently thanWELCOME|IDLE, even while receiving heartbeats or broadcasts. Receiving data does not reset gateway idle time. - 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. - On shutdown send
EXITand 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
TAGand amend/cancel requests withRTAG; handle both gatewayERRand engine rejectionACK|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.
FILLincludesSYMBOL/SIDEfor orders not in your cache and may includeLIQUIDITY/TRADE_IDS; stops emitTRIGGERED. Keep ALF decimal prices separate from internal tick representations. DC_FILLis a second report of an execution, not another position change. The demos display it without booking it again.- Bare
POSand its realized P&L are client-local, starting with fills seen by this connection. UseORDERSandPOS|GW=TRADER01to reconcile engine orders/positions after reconnect; the query returnsPOSITION,POS_ENTRY, thenEND|TYPE=POSITION.STATUSis 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:
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¶
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¶
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¶
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¶
Then type:
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¶
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¶
Arguments are positional: host [port [role]] (role one of CLEARING,
DROP_COPY, AUDIT; default CLEARING).
Manual gateway sanity check¶
Then type:
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¶
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¶
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¶
Arguments are positional: host [port [gateway_id]] (default
127.0.0.1 5590 TRADER01).
Manual gateway sanity check¶
Then type:
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 isaccepted=0,reject_code=0xFF,msg=SYMBOLS_UNAVAILABLE, and the connection closes: reconnect and log on again.describe_logon_ack()reports this. - A
LOGOUTfrom the gateway. The engine restarted and no longer holds the session; the connection closes. Log on again from scratch. - An
ORDER_ACKwithreject_code0x03for a symbol the engine does not list. To exercise BALF end-to-end, connect a TCP client topm-balf-gwyand feed received frames throughsplit_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:
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,fieldandmessagecome 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 sameIdempotency-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¶
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:
- Read bytes from TCP (or HTTP for REST).
- Split by newline into protocol lines (text protocols) or read fixed-size frames (BALF).
- Parse lines or frames using the appropriate parser library.
- Route by
msg_typeand fields. - Track
SEQcheckpoints 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.