# ALF example clients and parser libraries These examples demonstrate how to connect to `pm-alf-gwy` as an **external client** over TCP. They replicate the workflow of `pm-alf-console` but without any ZeroMQ or `edumatcher` package dependency — only a plain TCP socket and the ALF text protocol. ``` alf/ ├── python/ │ ├── alf_parser.py # Protocol library: parse, build, AlfSession │ └── alf_client.py # Interactive client with tab-completion └── c/ ├── alf_parser.h # C library header ├── alf_parser.c # C library implementation ├── alf_client.c # Interactive C client with readline └── Makefile # Build helper ``` ## Prerequisites **Engine and gateway running:** ```bash pm-engine --verbose pm-alf-gwy ``` `TRADER01` must be listed in `engine_config.yaml` under `participants`. **Python:** Python 3.9+, no extra packages needed. **C:** GNU readline. - **macOS:** `brew install readline` - **Linux:** `sudo apt install libreadline-dev` ## ALF wire format ``` VERB|KEY=VALUE|KEY=VALUE\n ``` - Command verbs and field keys are case-insensitive (normalized to uppercase) - Fields without `=` are silently skipped - Duplicate keys: last value wins These normalization rules apply to commands received by the gateway. The example parsers uppercase response verbs and keys, but preserve value case: copy each `ORDER_ID` exactly into subsequent `AMEND` and `CANCEL` requests. ALF prices remain decimal display prices; internal engine tick fields and message-envelope changes do not change this TCP command format. ## Python ### Library: `alf_parser.py` ```python 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=FALSE|REJECT_CODE=ORDER_NOT_FOUND|REASON=missing") print(msg.msg_type) # "ACK" print(msg.fields) # {"ORDER_ID": "abc", "ACCEPTED": "FALSE", "REJECT_CODE": "ORDER_NOT_FOUND", ...} # 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 session example, including a full multiline query and cleanup, see [Using the examples in your own programs](#using-the-examples-in-your-own-programs). `connect(..., timeout=5.0)` bounds the Python handshake and closes the socket on failure. After a successful handshake, reads are blocking. Library users must send `PING` periodically (more often than `WELCOME|IDLE`) themselves; both interactive clients do this automatically. ### Interactive client: `alf_client.py` ```bash cd docs/examples/alf/python # Connect to local gateway python3 alf_client.py --id TRADER01 # Connect to remote gateway python3 alf_client.py --host 10.0.0.5 --port 5565 --id TRADER01 # Custom client name in gateway logs python3 alf_client.py --id MM01 --client "my-mm-bot" ``` At the prompt: ``` [TRADER01]> NEW|SYM=AAPL|SIDE=BUY|TYPE=LIMIT|QTY=100|PRICE=150.00|TAG=order-001 [TRADER01]> AMEND|ID=|PRICE=151.00|RTAG=req-001 [TRADER01]> CANCEL|ID=|RTAG=req-002 [TRADER01]> SYMBOLS [TRADER01]> ORDERS [TRADER01]> POS [TRADER01]> SESSION [TRADER01]> STATUS [TRADER01]> DC|STATE=ON [TRADER01]> DC|STATE=OFF [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. Tab completes command verbs, field names, and enum values. Command history is saved to `~/.alf_client_history`. A `CANCELLED` line shows `reason=` when the exchange, not you, cancelled the order (self-match prevention, an IOC remainder, a kill switch...); a cancel you asked for has no reason. A `QUOTE_ACK` without leg ids is shown as `QUOTE CXL`: it answers `QUOTE_CANCEL`, not a new quote. Errors that end or interrupt a session (`SESSION_LOST` after an engine restart, `SYMBOLS_NOT_READY`, `ENGINE_UNAVAILABLE`, `RATE_LIMITED`...) are followed by a hint saying whether to log on again or retry. The clients display stop `TRIGGERED` notifications before subsequent fills, and show fill trade IDs and maker/taker `LIQUIDITY` when supplied. Drop-copy `DC_FILL` messages are displayed separately and never booked into the local position ledger a second time. Local `POS` starts with this connection's fills, not the gateway's historical holdings; use `POS|GW=` for the engine's position snapshot. Local P&L is realized P&L, and average cost is reset to the execution price when a fill reverses the position. ## C ### Build ```bash cd docs/examples/alf/c make ``` ### Run ```bash # Connect to local gateway ./alf_client --id TRADER01 # Connect to remote gateway ./alf_client --host 10.0.0.5 --port 5565 --id TRADER01 # Disable ANSI colour codes ./alf_client --id TRADER01 --no-color ``` The C client supports the same commands as the Python client. Tab-completion covers command verbs, field names, and common enum values. Its handshake receive timeout is five seconds. As a bounded teaching example, it stores up to 256 cached orders, 512 symbols/positions, and 512 rows in one query response. Parser storage allows 64 fields, 63-byte keys, and 511-byte values; overlong fields and lines are rejected rather than silently truncated. ### C library usage ```c #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" */ ``` Use this fragment inside your own function and include ``. Send all `len` bytes with a partial-write loop; the interactive client's `gwy_send` demonstrates this. ## Using the examples in your own programs - Copy `python/alf_parser.py` beside your Python application. It has no readline or `edumatcher` dependency. `AlfSession` handles the handshake, buffered reads and serialized sends, but your program must run the receive loop, send periodic `PING`, handle errors, and reconnect explicitly. - For C, copy `c/alf_parser.h` and `c/alf_parser.c` and compile them with your application, for example `cc -std=c11 -Wall -Wextra my_bot.c alf_parser.c -o my_bot`. This parser/builder needs no readline; it does not provide a TCP session. Adapt the interactive client's handshake, partial-write, buffered receive, and `select()` code rather than assuming a read or write transfers a line. - Replace terminal display with application handlers. Keep one reader per connection, process unsolicited events between query rows, and finish a query only on its matching `END|TYPE=...`. Preserve full order IDs, correlate requests with `TAG`/`RTAG`, and reconcile `ORDERS` and engine positions after reconnect instead of blindly resending orders. - Review the C capacities above before using the demo with a larger workload. These are teaching clients with plain TCP and in-memory state, not durable trading systems. The [application integration walkthrough](../../books/protocols-and-clients/part-4-writing-clients/030-example-libraries.md#use-alf-as-a-foundation-for-your-own-program) includes a runnable Python query with keepalives, C integration steps, error handling, and reconnect/position-accounting responsibilities. Use the [ALF protocol reference](../../books/protocols-and-clients/part-2-specifications/010-alf.md) for the complete wire contract. ## Differences from `pm-alf-console` | Feature | `pm-alf-console` | `alf_client` (Python/C) | |---------|-----------------|------------------------| | Transport | ZMQ PUSH/SUB directly | TCP via `pm-alf-gwy` | | Machine | Same host as engine | Any host | | Dependencies | `edumatcher` package + ZMQ | Python stdlib (including readline), or C + GNU readline | | P&L / POS | From local fill tracking | From local fill tracking | | `POS\|GW=` (query another gateway) | Yes | Yes | | STATUS | Rich terminal display | Text table | | QLEGS | Yes (local cache) | Yes (engine round trip) | | Tab completion | Full context-aware | Commands, fields, enums | ## Verification From the repository root, with the development environment installed: ```bash poetry run pytest tests/test_alf_examples.py --no-cov ``` The tests compile and run the C code, test parser/session behavior, and drive both interactive clients through the TCP gateway into the real engine handlers. C tests are skipped if the compiler, make, or GNU readline development files are unavailable; Python tests still run. Covered workflows include order entry, amendment, fills and position reversals, cancellation, OCO/combo orders, quotes, queries, stop triggering, drop-copy delivery, validation errors, and clean exit.