diff --git a/Cargo.lock b/Cargo.lock
index 8f158098..27ea4389 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -748,6 +748,21 @@ dependencies = [
"typenum",
]
+[[package]]
+name = "custom-fields"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "async-trait",
+ "hotfix",
+ "hotfix-codegen",
+ "hotfix-dictionary",
+ "hotfix-message",
+ "tokio",
+ "tracing",
+ "tracing-subscriber",
+]
+
[[package]]
name = "darling"
version = "0.21.3"
diff --git a/dummy-executor/cmd/executor.go b/dummy-executor/cmd/executor.go
index 91a798b2..4587d92a 100644
--- a/dummy-executor/cmd/executor.go
+++ b/dummy-executor/cmd/executor.go
@@ -109,6 +109,14 @@ func (e *Executor) onNewOrderSingle(msg newordersingle.NewOrderSingle, sessionID
log.Printf("Received NewOrderSingle: ClOrdID=%s Symbol=%s Side=%s Qty=%s",
clOrdID, symbol, string(side), orderQty.String())
+ // Read the optional custom tag (6001 = ClientStrategyId).
+ var clientStrategyID quickfix.FIXInt
+ hasClientStrategyID := false
+ if err := msg.Body.GetField(quickfix.Tag(6001), &clientStrategyID); err == nil {
+ hasClientStrategyID = true
+ log.Printf(" ClientStrategyId=%d", int(clientStrategyID))
+ }
+
// Look up FX rate; default to 1.0000 for unknown pairs.
price, ok := fxRates[symbol]
if !ok {
@@ -133,6 +141,9 @@ func (e *Executor) onNewOrderSingle(msg newordersingle.NewOrderSingle, sessionID
ack.Set(field.NewClOrdID(clOrdID))
ack.Set(field.NewSymbol(symbol))
ack.Set(field.NewOrderQty(orderQty, 2))
+ if hasClientStrategyID {
+ ack.Body.SetField(quickfix.Tag(6001), clientStrategyID)
+ }
if sendErr := quickfix.SendToTarget(ack.ToMessage(), sessionID); sendErr != nil {
log.Printf("Error sending ACK: %v", sendErr)
@@ -156,6 +167,9 @@ func (e *Executor) onNewOrderSingle(msg newordersingle.NewOrderSingle, sessionID
fill.Set(field.NewOrderQty(orderQty, 2))
fill.Set(field.NewLastQty(orderQty, 2))
fill.Set(field.NewLastPx(price, 4))
+ if hasClientStrategyID {
+ fill.Body.SetField(quickfix.Tag(6001), clientStrategyID)
+ }
if sendErr := quickfix.SendToTarget(fill.ToMessage(), sessionID); sendErr != nil {
log.Printf("Error sending FILL: %v", sendErr)
diff --git a/examples/custom-fields/Cargo.toml b/examples/custom-fields/Cargo.toml
new file mode 100644
index 00000000..e75492a8
--- /dev/null
+++ b/examples/custom-fields/Cargo.toml
@@ -0,0 +1,21 @@
+[package]
+name = "custom-fields"
+version = "0.1.0"
+authors.workspace = true
+edition.workspace = true
+license.workspace = true
+publish = false
+
+[dependencies]
+hotfix = { path = "../../crates/hotfix" }
+hotfix-message = { path = "../../crates/hotfix-message" }
+
+anyhow.workspace = true
+async-trait.workspace = true
+tokio = { workspace = true, features = ["full"] }
+tracing.workspace = true
+tracing-subscriber = { workspace = true, features = ["env-filter"] }
+
+[build-dependencies]
+hotfix-codegen = { path = "../../crates/hotfix-codegen" }
+hotfix-dictionary = { path = "../../crates/hotfix-dictionary" }
diff --git a/examples/custom-fields/README.md b/examples/custom-fields/README.md
new file mode 100644
index 00000000..31f562d1
--- /dev/null
+++ b/examples/custom-fields/README.md
@@ -0,0 +1,62 @@
+# Custom Fields — using a custom XML dictionary
+
+This example demonstrates how to use a QuickFIX-style XML dictionary that
+extends the bundled FIX 4.4 spec with your own custom fields, exercising both
+sides of HotFIX's custom-XML support:
+
+- **Build-time codegen** — `build.rs` runs `hotfix-codegen` against
+ `spec/FIX44-custom.xml` to produce typed field constants under a
+ `custom_fix` module (e.g. `custom_fix::CLIENT_STRATEGY_ID`).
+- **Runtime dictionary validation** — the session loads the same XML at
+ startup via `data_dictionary_path` and uses it to validate inbound and
+ outbound messages.
+
+The example sends a `NewOrderSingle (D)` carrying `ClientStrategyId=42`
+and expects the dummy executor to echo the field on the resulting
+`ExecutionReport`s. If the field doesn't round-trip, the example exits
+non-zero with a descriptive error.
+
+## The custom XML
+
+`spec/FIX44-custom.xml` is a verbatim copy of the bundled
+`crates/hotfix-dictionary/src/resources/quickfix/FIX-4.4.xml` with one
+addition: a ``
+in the `` block, plus an optional reference to it on
+`NewOrderSingle` and `ExecutionReport`.
+
+## Using the generated constants
+
+All field constants and typed enums (`Side`, `OrdType`, `OrdStatus`, …) come
+from the `custom_fix` module — including the ones for standard FIX 4.4
+tags. This keeps the example aligned with a single source of truth: the
+custom XML drives both compile-time typing and runtime validation. The
+`hotfix::fix44` re-exports are deliberately not used here, so the
+`hotfix` dependency in `Cargo.toml` doesn't enable the `fix44` feature.
+
+## Running the example
+
+In one terminal, build and start the dummy executor via the existing compose file:
+
+```shell
+docker compose -f example.compose.yml up --build dummy-executor
+```
+
+In another, from the repo root, run the example:
+
+```shell
+cargo run -p custom-fields
+```
+
+Expected log output:
+
+```
+INFO custom_fields: waiting for logon (up to 10s)
+INFO custom_fields::application: logged on
+INFO custom_fields: sending NewOrderSingle ClOrdID=demo-1 ClientStrategyId=42
+INFO custom_fields: received ExecutionReport ClOrdID=demo-1 OrdStatus=New ClientStrategyId=Some(42)
+INFO custom_fields: received ExecutionReport ClOrdID=demo-1 OrdStatus=Filled ClientStrategyId=Some(42)
+INFO custom_fields: order filled, custom field round-tripped successfully
+INFO custom_fields: shutting down
+```
+
+The example should then exit.
diff --git a/examples/custom-fields/build.rs b/examples/custom-fields/build.rs
new file mode 100644
index 00000000..660260e8
--- /dev/null
+++ b/examples/custom-fields/build.rs
@@ -0,0 +1,29 @@
+use hotfix_codegen as codegen;
+use hotfix_dictionary::Dictionary;
+use std::env::var;
+use std::fs::File;
+use std::io::Write;
+use std::path::PathBuf;
+
+fn main() -> std::io::Result<()> {
+ let spec_path = "spec/FIX44-custom.xml";
+ println!("cargo:rerun-if-changed={spec_path}");
+
+ let dict =
+ Dictionary::load_from_file(spec_path).expect("failed to load custom FIX 4.4 dictionary");
+
+ let mut settings = codegen::Settings::default();
+ // The generated code uses `::dict::FieldLocation`, `::FieldType`,
+ // and `::HardCodedFixFieldDefinition` — re-exported by `hotfix-message`
+ // but not by `hotfix`, so we point codegen at `hotfix_message`.
+ settings.hotfix_crate_name = "hotfix_message".to_string();
+
+ let code = codegen::gen_definitions(&dict, &settings);
+
+ let out_dir = PathBuf::from(var("OUT_DIR").expect("OUT_DIR not set by cargo"));
+ let out_path = out_dir.join("custom_fix.rs");
+ let mut file = File::create(&out_path)?;
+ file.write_all(code.as_bytes())?;
+
+ Ok(())
+}
diff --git a/examples/custom-fields/config/test-config.toml b/examples/custom-fields/config/test-config.toml
new file mode 100644
index 00000000..de42b8f7
--- /dev/null
+++ b/examples/custom-fields/config/test-config.toml
@@ -0,0 +1,12 @@
+[[sessions]]
+begin_string = "FIX.4.4"
+sender_comp_id = "dummy-initiator"
+target_comp_id = "dummy-acceptor"
+
+connection_port = 9880
+connection_host = "127.0.0.1"
+
+heartbeat_interval = 30
+reset_on_logon = true
+
+data_dictionary_path = "examples/custom-fields/spec/FIX44-custom.xml"
diff --git a/examples/custom-fields/spec/FIX44-custom.xml b/examples/custom-fields/spec/FIX44-custom.xml
new file mode 100644
index 00000000..e28f95fc
--- /dev/null
+++ b/examples/custom-fields/spec/FIX44-custom.xml
@@ -0,0 +1,6596 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/custom-fields/src/application.rs b/examples/custom-fields/src/application.rs
new file mode 100644
index 00000000..96e619a9
--- /dev/null
+++ b/examples/custom-fields/src/application.rs
@@ -0,0 +1,66 @@
+use std::sync::Arc;
+
+use hotfix::Application;
+use hotfix::Message;
+use hotfix::application::{InboundDecision, OutboundDecision};
+use hotfix::message::Part;
+use hotfix::session::Status;
+use tokio::sync::{Notify, mpsc};
+use tracing::{info, warn};
+
+use crate::custom_fix;
+use crate::messages::{ExecReportSummary, OutboundMsg};
+
+pub struct TestApplication {
+ pub logon_signal: Arc,
+ pub exec_tx: mpsc::UnboundedSender,
+}
+
+#[async_trait::async_trait]
+impl Application for TestApplication {
+ type Outbound = OutboundMsg;
+
+ async fn on_outbound_message(&self, _msg: &OutboundMsg) -> OutboundDecision {
+ OutboundDecision::Send
+ }
+
+ async fn on_inbound_message(&self, msg: &Message) -> InboundDecision {
+ let msg_type: Result<&str, _> = msg.header().get(custom_fix::MSG_TYPE);
+ if !matches!(msg_type, Ok("8")) {
+ return InboundDecision::Accept;
+ }
+
+ let cl_ord_id: Result<&str, _> = msg.get(custom_fix::CL_ORD_ID);
+ let ord_status: Result = msg.get(custom_fix::ORD_STATUS);
+ let client_strategy_id: Option = msg.get(custom_fix::CLIENT_STRATEGY_ID).ok();
+
+ match (cl_ord_id, ord_status) {
+ (Ok(cl_ord_id), Ok(ord_status)) => {
+ let summary = ExecReportSummary {
+ cl_ord_id: cl_ord_id.to_string(),
+ ord_status,
+ client_strategy_id,
+ };
+ if let Err(err) = self.exec_tx.send(summary) {
+ warn!("failed to forward execution report: {err}");
+ }
+ }
+ _ => warn!("execution report missing ClOrdID or OrdStatus"),
+ }
+
+ InboundDecision::Accept
+ }
+
+ async fn on_logout(&mut self, reason: &str) {
+ info!("logged out: {reason}");
+ }
+
+ async fn on_logon(&mut self) {
+ info!("logged on");
+ self.logon_signal.notify_one();
+ }
+
+ async fn on_state_change(&self, from: &Status, to: &Status) {
+ info!("session state changed: {from:?} -> {to:?}");
+ }
+}
diff --git a/examples/custom-fields/src/custom_fix.rs b/examples/custom-fields/src/custom_fix.rs
new file mode 100644
index 00000000..e4ff7107
--- /dev/null
+++ b/examples/custom-fields/src/custom_fix.rs
@@ -0,0 +1,8 @@
+//! Field constants generated at build time from `spec/FIX44-custom.xml`.
+//!
+//! See `build.rs` and the README for how this is produced.
+
+#![allow(dead_code)]
+#![allow(clippy::all)]
+
+include!(concat!(env!("OUT_DIR"), "/custom_fix.rs"));
diff --git a/examples/custom-fields/src/main.rs b/examples/custom-fields/src/main.rs
new file mode 100644
index 00000000..9474a681
--- /dev/null
+++ b/examples/custom-fields/src/main.rs
@@ -0,0 +1,128 @@
+mod application;
+mod custom_fix;
+mod messages;
+
+use std::sync::Arc;
+use std::time::Duration;
+
+use anyhow::{Context, Result, anyhow};
+use hotfix::config::Config;
+use hotfix::field_types::Timestamp;
+use hotfix::initiator::Initiator;
+use hotfix::store::in_memory::InMemoryMessageStore;
+use tokio::sync::{Notify, mpsc};
+use tokio::time::timeout;
+use tracing::{error, info};
+use tracing_subscriber::EnvFilter;
+
+use crate::application::TestApplication;
+use crate::messages::{ExecReportSummary, NewOrderSingle, OutboundMsg};
+
+const CONFIG_PATH: &str = "examples/custom-fields/config/test-config.toml";
+const LOGON_TIMEOUT: Duration = Duration::from_secs(10);
+const FILL_TIMEOUT: Duration = Duration::from_secs(10);
+const STRATEGY_ID: i32 = 42;
+const CL_ORD_ID: &str = "demo-1";
+
+#[tokio::main]
+async fn main() -> Result<()> {
+ tracing_subscriber::fmt()
+ .with_env_filter(
+ EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")),
+ )
+ .init();
+
+ let mut config = Config::load_from_path(CONFIG_PATH).context("failed to load config")?;
+ let session_config = config
+ .sessions
+ .pop()
+ .context("config must include a session")?;
+
+ let logon_signal = Arc::new(Notify::new());
+ let (exec_tx, mut exec_rx) = mpsc::unbounded_channel::();
+
+ let app = TestApplication {
+ logon_signal: logon_signal.clone(),
+ exec_tx,
+ };
+
+ let initiator: Initiator =
+ Initiator::start(session_config, app, InMemoryMessageStore::default())
+ .await
+ .context("failed to start initiator")?;
+
+ info!("waiting for logon (up to {:?})", LOGON_TIMEOUT);
+ timeout(LOGON_TIMEOUT, logon_signal.notified())
+ .await
+ .map_err(|_| anyhow!("session did not log on within {LOGON_TIMEOUT:?}"))?;
+
+ let order = NewOrderSingle {
+ cl_ord_id: CL_ORD_ID.to_string(),
+ symbol: "EUR/USD".to_string(),
+ side: custom_fix::Side::Buy,
+ order_qty: 100,
+ transact_time: Timestamp::utc_now(),
+ client_strategy_id: STRATEGY_ID,
+ };
+ info!("sending NewOrderSingle ClOrdID={CL_ORD_ID} ClientStrategyId={STRATEGY_ID}");
+ initiator
+ .send(OutboundMsg::NewOrderSingle(order))
+ .await
+ .context("failed to send NewOrderSingle")?;
+
+ let result = wait_for_fill(&mut exec_rx).await;
+
+ info!("shutting down");
+ if let Err(err) = initiator.shutdown(false).await {
+ error!("graceful shutdown failed: {err}");
+ }
+
+ result
+}
+
+async fn wait_for_fill(exec_rx: &mut mpsc::UnboundedReceiver) -> Result<()> {
+ let deadline = tokio::time::Instant::now() + FILL_TIMEOUT;
+
+ loop {
+ let remaining = deadline.saturating_duration_since(tokio::time::Instant::now());
+ if remaining.is_zero() {
+ return Err(anyhow!(
+ "did not receive a Filled ExecutionReport within {FILL_TIMEOUT:?}"
+ ));
+ }
+
+ let summary = match timeout(remaining, exec_rx.recv()).await {
+ Ok(Some(s)) => s,
+ Ok(None) => return Err(anyhow!("execution-report channel closed unexpectedly")),
+ Err(_) => {
+ return Err(anyhow!(
+ "did not receive a Filled ExecutionReport within {FILL_TIMEOUT:?}"
+ ));
+ }
+ };
+
+ info!(
+ "received ExecutionReport ClOrdID={} OrdStatus={:?} ClientStrategyId={:?}",
+ summary.cl_ord_id, summary.ord_status, summary.client_strategy_id,
+ );
+
+ let echoed = summary.client_strategy_id.ok_or_else(|| {
+ anyhow!(
+ "ExecutionReport for ClOrdID={} did not echo ClientStrategyId — \
+ the acceptor likely doesn't know about tag 6001",
+ summary.cl_ord_id,
+ )
+ })?;
+
+ if echoed != STRATEGY_ID {
+ return Err(anyhow!(
+ "ExecutionReport ClientStrategyId mismatch: expected {STRATEGY_ID}, got {echoed}",
+ ));
+ }
+
+ if matches!(summary.ord_status, custom_fix::OrdStatus::Filled) {
+ info!("order filled, custom field round-tripped successfully");
+ return Ok(());
+ }
+ }
+}
diff --git a/examples/custom-fields/src/messages.rs b/examples/custom-fields/src/messages.rs
new file mode 100644
index 00000000..50a034fb
--- /dev/null
+++ b/examples/custom-fields/src/messages.rs
@@ -0,0 +1,49 @@
+use hotfix::Message as HotfixMessage;
+use hotfix::field_types::Timestamp;
+use hotfix::message::{OutboundMessage, Part};
+
+use crate::custom_fix;
+
+#[derive(Debug, Clone)]
+pub struct NewOrderSingle {
+ pub cl_ord_id: String,
+ pub symbol: String,
+ pub side: custom_fix::Side,
+ pub order_qty: u32,
+ pub transact_time: Timestamp,
+ pub client_strategy_id: i32,
+}
+
+#[derive(Debug, Clone)]
+pub enum OutboundMsg {
+ NewOrderSingle(NewOrderSingle),
+}
+
+#[derive(Debug, Clone)]
+pub struct ExecReportSummary {
+ pub cl_ord_id: String,
+ pub ord_status: custom_fix::OrdStatus,
+ pub client_strategy_id: Option,
+}
+
+impl OutboundMessage for OutboundMsg {
+ fn write(&self, msg: &mut HotfixMessage) {
+ match self {
+ OutboundMsg::NewOrderSingle(order) => {
+ msg.set(custom_fix::CL_ORD_ID, order.cl_ord_id.as_str());
+ msg.set(custom_fix::SYMBOL, order.symbol.as_str());
+ msg.set(custom_fix::SIDE, order.side);
+ msg.set(custom_fix::ORDER_QTY, order.order_qty);
+ msg.set(custom_fix::TRANSACT_TIME, order.transact_time.clone());
+ msg.set(custom_fix::ORD_TYPE, custom_fix::OrdType::Market);
+ msg.set(custom_fix::CLIENT_STRATEGY_ID, order.client_strategy_id);
+ }
+ }
+ }
+
+ fn message_type(&self) -> &str {
+ match self {
+ OutboundMsg::NewOrderSingle(_) => "D",
+ }
+ }
+}