Persistance and recovery
This commit is contained in:
@ -10,6 +10,10 @@ The dealer's role is limited to:
|
||||
- Acknowledging messages (35=AI QuoteStatusReport, 35=BN ExecutionAck)
|
||||
- Sending a price quote (35=S)
|
||||
|
||||
This document covers the **happy path** (client accepts) and two **alternative flows**:
|
||||
- **Flow 8.6 — Trade Ended:** Client cancels before or after receiving the quote
|
||||
- **QuoteAck Rejected:** TW rejects the dealer's quote
|
||||
|
||||
## Participants
|
||||
|
||||
| Abbreviation | Role |
|
||||
@ -113,7 +117,7 @@ TW confirms the quote was accepted.
|
||||
|-----|-------|-------|
|
||||
| 1865 | QuoteAckStatus | `1` (ACCEPTED) |
|
||||
|
||||
**Note:** If status is not ACCEPTED, the dealer should log a warning — the quote may have been rejected.
|
||||
**Note:** If status is not ACCEPTED, the dealer logs the rejection (including the `Text` field) and cleans up the trade from memory. See [QuoteAck Rejected](#quoteack-rejected-quote-not-accepted) below.
|
||||
|
||||
### Step 5 — QuoteResponse (35=AJ) — TW → Dealer
|
||||
|
||||
@ -190,6 +194,115 @@ TW sends the full trade summary with additional details (parties, settlement inf
|
||||
|
||||
Same format as Step 8.
|
||||
|
||||
---
|
||||
|
||||
## Alternative Flows
|
||||
|
||||
### Flow 8.6 — Trade Ended (Client Cancels)
|
||||
|
||||
The client changes their mind and ends the trade. This can happen at any point after the QuoteRequest — even before the dealer's quote arrives. TW informs the dealer via QuoteResponse (35=AJ) messages with `_TRDEND` and `_TRDSUMM` suffixes instead of the ExecutionReport chain.
|
||||
|
||||
> **Critical:** The dealer MUST send a QuoteStatusReport (35=AI) ACK for every QuoteResponse. If no ACK is sent, TW will retry the message indefinitely (~every 11 seconds).
|
||||
|
||||
```
|
||||
TW Dealer
|
||||
│ │
|
||||
│ 1. QuoteRequest (35=R) │
|
||||
│ ─────────────────────────────────────────> │
|
||||
│ │
|
||||
│ 2. QuoteStatusReport (35=AI) [ACK] │
|
||||
│ <───────────────────────────────────────── │
|
||||
│ │
|
||||
│ ┌─── Client ends trade ───┐ │
|
||||
│ │ Meanwhile, dealer may │ │
|
||||
│ │ still send Quote (S) │ │
|
||||
│ └─────────────────────────┘ │
|
||||
│ │
|
||||
│ 3. QuoteResponse (35=AJ) [_TRDEND] │
|
||||
│ QuoteRespType=7 (End Trade) │
|
||||
│ ─────────────────────────────────────────> │
|
||||
│ │
|
||||
│ 4. QuoteStatusReport (35=AI) [ACK] │
|
||||
│ <───────────────────────────────────────── │
|
||||
│ │
|
||||
│ 5. QuoteAck (35=CW) [REJECTED] │
|
||||
│ (if quote was sent, TW rejects it) │
|
||||
│ ─────────────────────────────────────────> │
|
||||
│ │
|
||||
│ 6. QuoteResponse (35=AJ) [_TRDSUMM] │
|
||||
│ QuoteRespType=7, TradeSummary=Y │
|
||||
│ ─────────────────────────────────────────> │
|
||||
│ │
|
||||
│ 7. QuoteStatusReport (35=AI) [ACK] │
|
||||
│ <───────────────────────────────────────── │
|
||||
│ │
|
||||
```
|
||||
|
||||
#### Step 3 — QuoteResponse (35=AJ) `_TRDEND` — TW → Dealer
|
||||
|
||||
TW notifies that the client ended the trade.
|
||||
|
||||
| Tag | Field | Value | Notes |
|
||||
|-----|-------|-------|-------|
|
||||
| 693 | QuoteRespID | `..._TRDEND` | Suffix identifies this as trade end |
|
||||
| 694 | QuoteRespType | `7` (End Trade) | |
|
||||
| 131 | QuoteReqID | Same as original | |
|
||||
|
||||
**Dealer action:** Send QuoteStatusReport (35=AI) with `693=QuoteRespID` and `297=0` (ACCEPTED).
|
||||
|
||||
#### Step 5 — QuoteAck (35=CW) `REJECTED` — TW → Dealer
|
||||
|
||||
If the dealer's Quote (35=S) crossed with the TRDEND, TW rejects it. The QuoteAckStatus will be `2` (REJECTED) with a text like "DPL DLRQUOTE received in an invalid state."
|
||||
|
||||
**Dealer action:** Log the rejection and clean up the trade from memory.
|
||||
|
||||
#### Step 6 — QuoteResponse (35=AJ) `_TRDSUMM` — TW → Dealer
|
||||
|
||||
TW sends the final trade summary confirming the outcome.
|
||||
|
||||
| Tag | Field | Value | Notes |
|
||||
|-----|-------|-------|-------|
|
||||
| 693 | QuoteRespID | `..._TRDSUMM` | Final summary message |
|
||||
| 694 | QuoteRespType | `7` (End Trade) | |
|
||||
| 22636 | TradeSummary | `Y` | Confirms this is the summary |
|
||||
|
||||
**Dealer action:** Send QuoteStatusReport (35=AI) ACK. Clean up the trade from memory. This is the **terminal message** — no more messages will follow for this QuoteReqID.
|
||||
|
||||
---
|
||||
|
||||
### QuoteAck Rejected (Quote Not Accepted)
|
||||
|
||||
If TW rejects the dealer's Quote (35=CW with status != ACCEPTED), the trade is dead from the dealer's perspective.
|
||||
|
||||
```
|
||||
TW Dealer
|
||||
│ │
|
||||
│ 1-3. (same as happy path) │
|
||||
│ │
|
||||
│ 4. QuoteAck (35=CW) [REJECTED] │
|
||||
│ QuoteAckStatus != 1 │
|
||||
│ ─────────────────────────────────────────> │
|
||||
│ │
|
||||
│ Trade is terminated. │
|
||||
│ │
|
||||
```
|
||||
|
||||
**Dealer action:** Log the rejection (including the `Text` field with the reason) and remove the trade from memory. No further action needed — TW may or may not send subsequent messages for this QuoteReqID.
|
||||
|
||||
---
|
||||
|
||||
## QuoteRespID Suffix Routing in `handleQuoteResponse`
|
||||
|
||||
All QuoteResponse (35=AJ) messages are routed by the suffix of the `QuoteRespID` (tag 693):
|
||||
|
||||
```
|
||||
QuoteRespID ends with "_TRDREQ" → Trade request (flow 8.4 happy path) — ACK
|
||||
QuoteRespID ends with "_TRDEND" → Trade ended by client (flow 8.6) — ACK
|
||||
QuoteRespID ends with "_TRDSUMM" → Trade summary (flow 8.6 final) — ACK + cleanup
|
||||
QuoteRespID ends with "_LISTEND" → List ended — ACK
|
||||
Other suffix → Ignored (logged)
|
||||
```
|
||||
|
||||
## Code Reference
|
||||
|
||||
The implementation lives in `src/client/fix/manager.go`:
|
||||
@ -197,20 +310,32 @@ The implementation lives in `src/client/fix/manager.go`:
|
||||
| Handler | Triggers on | Action |
|
||||
|---------|------------|--------|
|
||||
| `handleQuoteRequest` | 35=R | Sends 35=AI (ack) + 35=S (quote) |
|
||||
| `handleQuoteAck` | 35=CW | Logs status |
|
||||
| `handleQuoteResponse` | 35=AJ | Sends 35=AI (TRDREQACK) |
|
||||
| `handleQuoteAck` | 35=CW | If rejected: logs + cleans up trade. If accepted: logs |
|
||||
| `handleQuoteResponse` | 35=AJ | Sends 35=AI (ACK). Routes by QuoteRespID suffix. Cleans up on `_TRDSUMM` |
|
||||
| `handleExecutionReport` | 35=8 | Sends 35=BN (ack) + routes by ExecID suffix |
|
||||
| `sendQuoteStatusReport` | — | Builds 35=AI for QuoteRequest ack |
|
||||
| `sendTradeRequestAck` | — | Builds 35=AI for TRDREQACK |
|
||||
| `sendTradeRequestAck` | — | Builds 35=AI for QuoteResponse ack (all suffixes) |
|
||||
| `sendExecutionAck` | — | Builds 35=BN for ExecutionReport ack |
|
||||
|
||||
### ExecID Routing in `handleExecutionReport`
|
||||
|
||||
```
|
||||
ExecID contains "_LISTEND" → Log only, await trade result
|
||||
ExecID contains "_TRDEND" → Log + cleanup trade from memory
|
||||
ExecID contains "_TRDSUMM" → Log trade summary
|
||||
ExecID contains "_TRDEND" → Log trade end
|
||||
ExecID contains "_TRDSUMM" → Log trade summary + cleanup trade from memory
|
||||
ExecType = F (fallback) → Log generic trade result
|
||||
```
|
||||
|
||||
The order matters: ExecID suffix checks run before ExecType checks, because `_TRDEND` and `_TRDSUMM` both have `ExecType=F`.
|
||||
|
||||
### Trade Cleanup Paths
|
||||
|
||||
A trade is removed from memory in any of these scenarios:
|
||||
|
||||
| Trigger | Message | Condition |
|
||||
|---------|---------|-----------|
|
||||
| QuoteAck rejected | 35=CW | `QuoteAckStatus != ACCEPTED` |
|
||||
| QuoteResponse summary | 35=AJ | `QuoteRespID` ends with `_TRDSUMM` (flow 8.6) |
|
||||
| ExecutionReport summary | 35=8 | `ExecID` contains `_TRDSUMM` (flow 8.4) |
|
||||
|
||||
The `loadActiveTrades` recovery function replays today's messages and applies the same cleanup rules to reconstruct accurate state on restart.
|
||||
|
||||
Reference in New Issue
Block a user