SRS: UC_7.1 — Anti-Fraud Reconciliation
| BA in Charge | Date Created | Version | Document References |
|---|---|---|---|
| Trang Nguyen | 2026-10-04 | v1 | Zapier Integration V7.pdf §Flow 5 (Payouts); RFQ_ Stack Trading Prop Tech V7.pdf §3.2 (POST /verify-payout-reconciliation); QnA_STAGE5_PAYOUT_WITHDRAWAL.md A-01→A-08, A-69, A-88→A-92 (client answered 2026-09-10, A-05/A-92 reconfirmed 2026-09-28); docs/BA/scratch/Flow5_AntiFraud_Compliance_Activity_Flow.md (background analysis); updated 2026-10-08 per BA instruction (see §9 Update History) |
UC_7.1 — Anti-Fraud Reconciliation
1. Overview
| Field | Content |
|---|---|
| ID | UC_7.1 |
| Use Case | Anti-Fraud Reconciliation (Payout Reconciliation Monitor). |
| Description | The first step Flow 5 executes after Flow 4 (Promotion) completes for a trader. It compares two values to detect data divergence between Stack Trading's own books and the broker side — but what is compared differs by asset class, not just the data source. For Futures, it reconciles the trader's net P&L against the realized P&L on the mapped Ironbeam sub-account: Rithmic Net P&L (Rithmic_Account_Balance - Max_Futures_Notional) against Ironbeam Realized P&L (cashBalance - Futures_Capital, since Ironbeam exposes no dedicated realizedPnL field and all realized P&L is already settled inside cashBalance once positions are flat post-Lock & Liquidate) — a per-trader, system-to-system comparison on both sides (Ref: BR_7.1.5). For Forex, the comparison pair is structurally different: because the LIVE Forex account at LMAX is a corporate Omnibus account that aggregates every trader's orders together, LMAX has no visibility into individual trader sub-accounts — so this endpoint never calls the LMAX API for an individual trader's payout. Instead, the Forex comparison is the trader's own PostgreSQL Virtual Ledger (ledgers.virtual_equity) against that same trader's individual TraderEvolution or MT5 sub-account equity/balance — one side internal, one side the execution platform, with no omnibus/institution-side value involved at all. A mismatch beyond a per-level tolerance band never blocks, delays, or flags the payout to the trader — it only raises a silent Severity-1 alert to Ops. UC_7.2 runs immediately afterward regardless of this UC's outcome. |
| Zapier Flow | Flow 5 — Payouts (Bonus), Step 1 "Reconciliation" |
| Zapier Table | — |
| 3rd Party | Ironbeam — the Futures FCM that legally custodies the trader's mapped sub-account funds and whose balance is read as institution_balance for Futures. MT5 / TraderEvolution — for Forex, the execution-platform side read as the trader's individual sub-account equity/balance. LMAX is not called by this endpoint — LMAX is the LIVE Forex corporate Omnibus account/liquidity provider, but it has no per-trader visibility, so it plays no role in this UC's reconciliation (see Overview, BR_7.1.2). |
No wireframe — backend monitoring job with no UI screen of its own. Its only externally visible output is a Slack message to #ops_general; it never writes to Users.status, never locks the dashboard, and never surfaces an "Under Review" state to the trader.
2. Trigger
Event-driven — fires immediately upon Flow 4 (Promotion) completing for a trader. This is the first step Flow 5 (Payouts) executes once triggered; UC_7.2 runs immediately after this UC, regardless of this UC's outcome (see §4 Post-conditions). This UC has no independent schedule or UI trigger of its own.
3. Pre-conditions
| ID | Condition |
|---|---|
| P-1 | Flow 4 (Promotion) has completed for the trader, including itsreset-ledger step — virtual_equity has already been overwritten to Level_Starting_Capital for the new level, and the prior cycle's P&L has been archived to historical_pnl. |
| P-2 | The trader has an active, mapped sub-account at the relevant venue for theirAsset_Class — Rithmic + Ironbeam for Futures, or MT5 / TraderEvolution for Forex. |
| P-3 | user_id is valid and resolvable to exactly one Asset_Class and one Current_Level (used by BR_7.1.3's tolerance lookup). |
4. Post-conditions
Reconciliation within tolerance
- No side effect. No alert is raised. UC_7.2 proceeds immediately.
Reconciliation beyond tolerance
- A Severity-1 Slack alert is posted to
#ops_general, silently.Users.statusis unchanged and the payout is not delayed. UC_7.2 proceeds immediately, in parallel with (not blocked by) the alert.
Endpoint error / timeout
- The payout still proceeds (fail-open, based on verified ISV platform equity). A Severity-1 Slack alert is now posted to
#ops_generalfor this case too, using the same template as a tolerance mismatch — see Exc-1 and BR_7.1.4.
5. Basic Flow
-
Flow 5 begins. On Flow 4 (Promotion) completion, Flow 5 (Payouts) is triggered for the trader, and this UC's reconciliation call is its first step.
-
Call the reconciliation endpoint. The middleware calls
POST /verify-payout-reconciliation, inputuser_id. The endpoint returns:{ "pnl_match": bool, "broker_equity": float, "institution_balance": float }pnl_match— the endpoint's own true/false verdict for whether the two values below are considered matching.broker_equity— for Futures, this field name carries the Rithmic Net P&L, computed asRithmic_Account_Balance - Max_Futures_Notional(not the raw Rithmic balance — see step 3(a)). For Forex, this field name is reused to carry the trader's individual TraderEvolution or MT5 sub-account equity/balance — see step 3(b).institution_balance— for Futures, this field name carries the Ironbeam Realized P&L, computed ascashBalance - Futures_Capital(not the raw Ironbeam balance — see step 3(a)). For Forex, this field name is reused to carry the trader's own PostgreSQL Virtual Ledger value (ledgers.virtual_equity) — not an LMAX/omnibus balance; see step 3(b) for why.
-
Branch by
Asset_Class— the two fields above mean different things depending on asset class:(a) Futures — reconcile Net P&L, not raw balance:
- Reconcile Futures payouts by comparing the trader's net P&L against the realized P&L on the mapped Ironbeam sub-account and internal ledger:
- Rithmic Net P&L =
Rithmic_Account_Balance - Max_Futures_Notional. - Ironbeam Realized P&L =
cashBalance - Futures_Capital.
- Rithmic Net P&L =
- Why
cashBalance, not a dedicated realized-P&L field: Ironbeam's Account Balance endpoints (GET /v2/account/{accountId}/balanceandGET /v2/account/getall) do not return a separaterealizedPnLfield. Because this UC runs immediately after Lock & Liquidate (Flow 4) when all open positions are flat (openTradeEquity = 0), all realized P&L is already settled directly insidecashBalance— socashBalance - Futures_Capitalis the realized P&L for the period. - Both sides are still external systems (Rithmic, the ISV, vs. Ironbeam, the FCM) — this remains a per-trader, system-to-system comparison; only the shape of what's compared changed, from raw balance-vs-balance to Net-P&L-vs-Realized-P&L (Ref: BR_7.1.5).
(b) Forex — structurally different from Futures, not just a different data source:
- The trader's own PostgreSQL Virtual Ledger value,
ledgers.virtual_equity— an internal system value, not a counterparty/custody-side value. broker_equity(field name reused) = the trader's individual sub-account equity/balance on TraderEvolution or MT5 — the execution-platform side.- The comparison is therefore internal-ledger-vs-execution-platform for Forex, rather than execution-platform-vs-custodian as it is for Futures — see BR_7.1.2.
- Reconcile Futures payouts by comparing the trader's net P&L against the realized P&L on the mapped Ironbeam sub-account and internal ledger:
-
Apply the per-level tolerance band. The absolute difference between the two compared values —
|Rithmic_Net_PnL - Ironbeam_Realized_PnL|for Futures, or|broker_equity - institution_balance|for Forex — is compared against a tolerance that scales with the trader'sCurrent_Level(Tolerance_Threshold) — see BR_7.1.3 for the exact bands. This check remains diagnostic and non-blocking regardless of asset class. -
Evaluate the result:
-
If the difference is within the applicable band (
pnl_match = true) → no further action this cycle (see §6 Alt-1). -
If the difference exceeds the applicable band (
pnl_match = false) → the Middleware silently dispatches a high-priority Severity-1 (SEV-1) alert to the internal#ops_generalSlack channel for post-disbursement database reconciliation by engineering, using the template below. The alert does not identify a root cause (fraud vs. clearing/rounding/fee timing) — Ops must investigate independently. The client's own framing is that most such alerts are expected to be "minor administrative artifacts, such as market data fee deductions," not malicious activity.🚨 CRITICAL: Payout Reconciliation Mismatch User ID: [user_id] Execution Platform: [Rithmic / MT5 / TraderEvolution] Broker Equity: $[broker_equity] Institution Balance: $[institution_balance] Variance: $[variance_amount] Status: Payout disbursed. Manual database reconciliation required immediately.This same template applies to both cases that reach this branch — a true tolerance-band mismatch and
pnl_match = falsefrom the endpoint directly — there is no separate template or wording for the two.
-
-
Always proceed, regardless of step 5's outcome. Flow 5 continues to UC_7.2 (Compliance Check) immediately. Step 5's outcome has no bearing on whether, when, or how much the trader is paid — see §4 Post-conditions and BR_7.1.1.
6. Alternative Flows
- Alt-1 — Difference within tolerance; no alert.
- Context: at step 5, the absolute difference between
broker_equityandinstitution_balanceis at or below the applicable tolerance band for the trader'sCurrent_Level. - No Slack alert is raised. Flow 5 proceeds to UC_7.2 exactly as it would after a mismatch — there is no behavioral difference visible outside this UC between Alt-1 and a mismatch beyond tolerance, other than the Slack alert itself. This is the expected common case.
- Context: at step 5, the absolute difference between
7. Exceptional Flows
- Exc-1 — The reconciliation endpoint times out or returns a 5xx error (no verdict, as distinct from a mismatch).
- The system still fails open — it proceeds with the automated payout based on the verified ISV platform equity.
- It raises the same Silent Alert (SEV-1 to
#ops_general, same template as §5 step 5) used for a tolerance mismatch, since there is no verdict to log as a pass. The alert'sStatusline still reads "Payout disbursed. Manual database reconciliation required immediately." — Flow 5 moves to UC_7.2 (Compliance Check) immediately regardless.
8. Business Rules
BR_7.1.1: Reconciliation is monitoring-only — it never gates, halts, or delays the payout
Regardless of the result of the comparison in §5 step 5 — within tolerance, beyond tolerance, or undeterminable due to an endpoint failure — Flow 5 always proceeds to UC_7.2 (Compliance Check) and, from there, to payout execution. There is no code path in which this UC blocks, holds, or delays money, locks the trader's dashboard.
BR_7.1.2: Forex comparison is internal Virtual Ledger vs. individual sub-account — LMAX (Omnibus) is never called
Futures and Forex are not the same comparison pattern with different data sources — they compare structurally different pairs:
- Futures:
broker_equity(Rithmic, the ISV) vs.institution_balance(Ironbeam, the FCM/custodian) — both sides are external systems, a per-trader, system-to-system comparison. - Forex: the trader's own PostgreSQL Virtual Ledger (
ledgers.virtual_equity) vs. that same trader's individual TraderEvolution or MT5 sub-account equity/balance — one side internal (Stack Trading's own ledger), one side the execution platform. No institution/custodian-side value is read for Forex at all.
Why LMAX is excluded: the LIVE Forex account at LMAX is a corporate Omnibus account — every Forex trader's orders are aggregated together at LMAX, and LMAX has no visibility into individual trader sub-accounts. There is no per-trader balance this endpoint could read from LMAX, so POST /verify-payout-reconciliation never calls the LMAX API for an individual trader's payout.
The per-level tolerance bands in BR_7.1.3 apply identically to both comparison pairs — only the inputs being compared differ by asset class.
(Source: client-confirmed, relayed by user (BA session) 2026-10-26 — "Because our LMAX account is a corporate Omnibus account, all live Forex traders' orders are aggregated together at LMAX. LMAX has no visibility into individual trader sub-accounts. Therefore, do not call the LMAX API for individual trader payouts in Flow 5 (POST /verify-payout-reconciliation). For Forex payouts, the anti-fraud check compares the user's PostgreSQL Virtual Ledger (ledgers.virtual_equity) directly against their individual TraderEvolution or MT5 sub-account equity/balance (using the level-based tolerance thresholds shown in the sheet)." — supersedes the prior "pending client confirmation" / omnibus-provider framing)
BR_7.1.3: Per-level tolerance band
The allowed absolute difference between the two compared values scales with the trader's Current_Level, using 4 fixed bands:
| Level range | Tolerance |
|---|---|
| L1–L6 | $100 |
| L7–L12 | $500 |
| L13–L18 | $2,500 |
| L19–L24 | $10,000 |
BR_7.1.4: Fail-open on endpoint error or timeout, with Silent Alert
If POST /verify-payout-reconciliation times out or returns a 5xx error, the system still proceeds with the payout based on the verified ISV platform equity — the payout itself is never held up (A-06). This case now also raises the same Silent Alert (SEV-1 to #ops_general) used for a tolerance mismatch — see §5 step 5 for the template and Exc-1 for the full rationale. It is no longer treated identically to a silent within-tolerance pass.
(Source: QnA_STAGE5_PAYOUT_WITHDRAWAL.md A-06; alert behavior per BA update 2026-10-08)
BR_7.1.5: Comparison shape differs by asset class — Futures is Net-P&L-vs-Realized-P&L; Forex remains balance-vs-balance
Futures (supersedes the prior balance-vs-balance framing for this asset class — client update): the comparison is Net P&L vs. Realized P&L, not raw balance vs. balance:
- Rithmic Net P&L =
Rithmic_Account_Balance - Max_Futures_Notional. - Ironbeam Realized P&L =
cashBalance - Futures_Capital— derived fromcashBalancebecause Ironbeam's balance endpoints (GET /v2/account/{accountId}/balance,GET /v2/account/getall) expose no dedicatedrealizedPnLfield; since this UC runs right after Lock & Liquidate (Flow 4) with all positions flat (openTradeEquity = 0), realized P&L is already fully settled insidecashBalance. - The check is
|Rithmic_Net_PnL - Ironbeam_Realized_PnL| <= Tolerance_Threshold(BR_7.1.3) — still diagnostic and non-blocking: on breach, fire the silent SEV-1 Slack alert and proceed with the payout (Ref: BR_7.1.1).
Forex (unchanged — client-confirmed A-88, relayed 2026-09-28): the comparison remains strictly balance vs. balance — broker_equity (the trader's individual TraderEvolution/MT5 sub-account equity/balance) against institution_balance (the trader's own PostgreSQL Virtual Ledger ledgers.virtual_equity — see BR_7.1.2). It is not a P&L-based comparison for Forex.
This closes three points that were previously open, now scoped per asset class:
- Neither side reads or compares against
historical_pnlor any cumulative-period P&L figure — Futures uses the two P&L figures defined above, Forex uses the two point-in-time balance/equity values. - It does not matter that Flow 4's
reset-ledgerstep already overwrotevirtual_equitytoLevel_Starting_Capitalbefore this UC runs (see P-1) — for Forex,virtual_equityis one side of the comparison by design (per BR_7.1.2), so the reset is expected input, not a timing bug to work around. For Futures,virtual_equityplays no role in the comparison either way. - The comparison is a snapshot taken at the moment this UC runs, not an aggregation over the challenge/level period — both sides (for either asset class) are read fresh at call time (§5 step 2).
(Source: Forex — QnA_STAGE5_PAYOUT_WITHDRAWAL.md A-88, client confirmation relayed by BA, 2026-09-28. Futures — client update relayed by user (BA session), supersedes the prior balance-vs-balance framing for Futures only.)
9. Update History
| Date | Version | Section | Change From | Change To | Source |
|---|---|---|---|---|---|
| 2026-10-04 | v1 | All (new) | UC_7.1 not yet written.Zapier Integration V7.pdf / RFQ_ Stack Trading Prop Tech V7.pdf described this step as a payout-blocking gate ("If mismatch → STOP. Red flag.") comparing Futures per-trader (Rithmic↔Ironbeam) and Forex firm-wide omnibus (Forex.com+IBKR), with a flat $100 tolerance. | Initial SRS. Reclassified as a monitoring-only job per client answers 2026-09-10 (A-03, A-06) — never gates, halts, or delays payout under any circumstance. Forex comparison changed from omnibus to PostgreSQL Virtual Ledger ↔ MT5/TraderEvolution per-trader (A-05). Tolerance changed from flat $100 to 4 level-based bands (A-04). Four core definitional questions (A-88→A-91) and one tolerance-vs-fee conflict (A-69) remain open and are flagged inline perBR_7.1.5 rather than guessed at. | Zapier Integration V7.pdf §Flow 5; RFQ_ Stack Trading Prop Tech V7.pdf §3.2; QnA_STAGE5_PAYOUT_WITHDRAWAL.md A-01→A-08, A-69, A-88→A-92 |
| 2026-10-08 | v1 | Overview, §5 step 3(b)/5, §7 Exc-1, BR_7.1.2, BR_7.1.3, BR_7.1.4 | Forexinstitution_balance written as the PostgreSQL Virtual Ledger (A-05, 2026-09-10 wording); BR_7.1.2 existed as a dedicated "Futures vs. Forex compare different values" rule; Zapier Table C listed as hosting the 4 tolerance bands; Table C Base_CME_Data_Cost cited as a possible gap source against the L1–L6 band; Exc-1 (timeout/5xx) was fully silent, no alert. | Forexinstitution_balance reverted to the Omnibus comparison (same structure as Futures), with the liquidity provider itself now pending — client is sourcing a replacement for Forex.com/IBKR. BR_7.1.2 removed — Futures and Forex now compare the same kind of data (broker_equity vs institution_balance), differing only in data source, so a dedicated "structurally different" rule no longer applies. Zapier Table description cleared — the 4 tolerance bands are managed in the DB, not Table C. Removed the Base_CME_Data_Cost-vs-band gap note — that cost is not deducted from either compared balance, so it does not create a gap. Added the Silent Alert template (🚨 CRITICAL: Payout Reconciliation Mismatch...) to §5 step 5, applying to both a tolerance mismatch and pnl_match = false; the same alert now also fires on Exc-1 (timeout/5xx), which previously raised no alert at all. | BA instruction, 2026-10-08 |
| 2026-10-08 | v1 | BR_7.1.5 | BR_7.1.5 framed as "[OPEN — blocking]" — 4 unresolved questions (A-88→A-91) about what is actually being compared and snapshot timing, with no client answer. | Resolved — client-confirmed. Client confirmed the comparison is broker_equity (final cash balance logged on Rithmic/MT5/TraderEvolution) against institution_balance (A-88) — a balance-vs-balance comparison, not P&L-vs-equity/balance. This closes A-89/A-90/A-91 as moot, since the comparison never touches virtual_equity/the internal ledger that reset-ledger overwrites, and is a point-in-time snapshot, not a cumulative-period figure. BR_7.1.5 rewritten from "open/blocking" to a confirmed rule. | BA instruction, 2026-10-08 (client confirmation relayed 2026-09-28) |
| 2026-10-26 | v1 | Overview (Description, 3rd Party), §5 Basic Flow (step 2, step 3(b)), §8 (new BR_7.1.2) | Forex institution_balance was documented as "the aggregated omnibus corporate account balance held at the Forex liquidity provider" with the provider itself flagged pending client confirmation. BR_7.1.2's number sat unused since the 2026-10-08 row above removed it. No rule explained why Forex can't follow the Futures (execution-platform-vs-custodian) pattern | Client confirmed the LIVE Forex account at LMAX is a corporate Omnibus account — every trader's orders are aggregated together there, and LMAX has no per-trader visibility. POST /verify-payout-reconciliation therefore never calls the LMAX API for an individual trader's payout. The Forex comparison is instead the trader's own PostgreSQL Virtual Ledger (ledgers.virtual_equity) against that trader's individual TraderEvolution or MT5 sub-account equity/balance — using the same level-based tolerance bands (BR_7.1.3) already defined. Rewrote Overview Description/3rd Party and §5 step 2/3(b) to state this pairing explicitly (replacing the "pending" framing); added new BR_7.1.2 (reusing the number freed on 2026-10-08) documenting the Futures-vs-Forex structural difference and the LMAX-Omnibus exclusion reason, cross-referencing CHR-41 (LMAX as the firm's aggregate Forex LP for Flow 27/28 — a separate, firm-wide reconciliation, out of this UC's scope) to avoid the two being conflated | User instruction 2026-10-26 — client-confirmed: "Because our LMAX account is a corporate Omnibus account, all live Forex traders' orders are aggregated together at LMAX. LMAX has no visibility into individual trader sub-accounts. Therefore, do not call the LMAX API for individual trader payouts in Flow 5 (POST /verify-payout-reconciliation). For Forex payouts, the anti-fraud check compares the user's PostgreSQL Virtual Ledger (ledgers.virtual_equity) directly against their individual TraderEvolution or MT5 sub-account equity/balance (using the level-based tolerance thresholds shown in the sheet)." |
| 2026-11-xx | v1 | Overview (Description), §5 Basic Flow (step 2, step 3(a), step 4), BR_7.1.5 | Futures comparison was documented as raw balance-vs-balance — broker_equity (Rithmic balance) vs. institution_balance (Ironbeam balance) — per BR_7.1.5's 2026-09-28 client confirmation (A-88). No formula distinguished Net P&L from raw balance; Ironbeam's lack of a realizedPnL field was not addressed. | Client update: Futures reconciliation now compares Net P&L vs. Realized P&L, not raw balance — Rithmic_Net_PnL = Rithmic_Account_Balance - Max_Futures_Notional vs. Ironbeam_Realized_PnL = cashBalance - Futures_Capital (the latter derived from cashBalance because Ironbeam's GET /v2/account/{accountId}/balance / GET /v2/account/getall expose no realizedPnL field, and because this UC runs post-Lock & Liquidate with all positions flat, cashBalance already holds the settled realized P&L). Check remains ` | Rithmic_Net_PnL - Ironbeam_Realized_PnL |
| 2026-10-27 | v1 | §5 Basic Flow step 5 (pnl_match = false branch — Slack channel changed) | The pnl_match = false branch dispatched its SEV-1 alert to #operations — the same channel used by every other alert case in this UC (ISV platform check failure, Exc-1 timeout/5xx) | Changed the pnl_match = false alert's destination to #ops_general, distinct from #operations used by this UC's other alert cases (step 5's ISV-platform-check-failure branch, Exc-1's timeout/5xx branch), which remain on #operations — this change is scoped to the pnl_match = false case only | User instruction 2026-10-27 — "hãy update cho tôi tên kênh slack để push alert nếu PnL match = false là #ops_general thay cho kênh #operation" |
| 2026-10-27 | v1 | Overview (wireframe-less note); §4 Post-conditions (tolerance-mismatch + endpoint-error entries); §5 Basic Flow step 5 (tolerance-mismatch sentence); §7 Exc-1; §8 BR_7.1.4 | Every other alert mechanism in this UC (tolerance-band mismatch's own sentence, and the endpoint timeout/5xx case in Exc-1/BR_7.1.4) still posted to #operations, inconsistent with the pnl_match = false branch which had just been moved to #ops_general | Extended the #ops_general channel to every alert mechanism in this UC — the tolerance-band mismatch and endpoint timeout/5xx cases now also post to #ops_general, same as pnl_match = false. This UC no longer posts to #operations anywhere; all 3 alert paths (tolerance mismatch, pnl_match = false, endpoint error/timeout) are unified on one channel | User instruction 2026-10-27 — "hãy update tên kênh slack này đối với cả các alert mechanism khác" |