StackTrading Docs

SRS: UC_7.1 — Anti-Fraud Reconciliation

BA in ChargeDate CreatedVersionDocument References
Trang Nguyen2026-10-04v1Zapier 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

FieldContent
IDUC_7.1
Use CaseAnti-Fraud Reconciliation (Payout Reconciliation Monitor).
DescriptionThe 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 FlowFlow 5 — Payouts (Bonus), Step 1 "Reconciliation"
Zapier Table—
3rd PartyIronbeam — 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

IDCondition
P-1Flow 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-2The trader has an active, mapped sub-account at the relevant venue for theirAsset_Class — Rithmic + Ironbeam for Futures, or MT5 / TraderEvolution for Forex.
P-3user_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.status is 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_general for this case too, using the same template as a tolerance mismatch — see Exc-1 and BR_7.1.4.

5. Basic Flow

  1. 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.

  2. Call the reconciliation endpoint. The middleware calls POST /verify-payout-reconciliation, input user_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 as Rithmic_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 as cashBalance - 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.
  3. 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.
    • Why cashBalance, not a dedicated realized-P&L field: Ironbeam's Account Balance endpoints (GET /v2/account/{accountId}/balance and GET /v2/account/getall) do not return a separate realizedPnL field. 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 inside cashBalance — so cashBalance - Futures_Capital is 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.
  4. 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's Current_Level (Tolerance_Threshold) — see BR_7.1.3 for the exact bands. This check remains diagnostic and non-blocking regardless of asset class.

  5. 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_general Slack 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 = false from the endpoint directly — there is no separate template or wording for the two.

  6. 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_equity and institution_balance is at or below the applicable tolerance band for the trader's Current_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.

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's Status line 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 rangeTolerance
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 from cashBalance because Ironbeam's balance endpoints (GET /v2/account/{accountId}/balance, GET /v2/account/getall) expose no dedicated realizedPnL field; since this UC runs right after Lock & Liquidate (Flow 4) with all positions flat (openTradeEquity = 0), realized P&L is already fully settled inside cashBalance.
  • 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_pnl or 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-ledger step already overwrote virtual_equity to Level_Starting_Capital before this UC runs (see P-1) — for Forex, virtual_equity is 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_equity plays 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

DateVersionSectionChange FromChange ToSource
2026-10-04v1All (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-08v1Overview, §5 step 3(b)/5, §7 Exc-1, BR_7.1.2, BR_7.1.3, BR_7.1.4Forexinstitution_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-08v1BR_7.1.5BR_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-26v1Overview (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) patternClient 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 conflatedUser 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-xxv1Overview (Description), §5 Basic Flow (step 2, step 3(a), step 4), BR_7.1.5Futures 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-27v1§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 onlyUser 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-27v1Overview (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.4Every 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_generalExtended 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 channelUser instruction 2026-10-27 — "hãy update tên kênh slack này đối với cả các alert mechanism khác"

On this page