Dealing with the "Double Counting" Problem
Because your wrapper optimistically assumes the trade filled at the exact quantity requested, a naive WebSocket integration will double-count positions (once upon order submission, once upon receiving the WebSocket fill).
To fix this without sacrificing latency, modify the GlobalRiskManager to track Pending and Confirmed quantities separately:
Submission Phase: When SubmitOrderAsync fires, it adds to the PendingQuantity.
Reconciliation Phase: When the WebSocket receives the ExecutionReport, the GlobalRiskManager subtracts from PendingQuantity and adds the exact amount to ConfirmedQuantity.
Risk Check Adjustment: The EvaluateCrossAccountHedgeAsync method then calculates total exposure as (PendingQuantity + ConfirmedQuantity) before allowing the next trade.
Multiple accounts risk on Apex fraud systems
Re: Multiple accounts risk on Apex fraud systems
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: Multiple accounts risk on Apex fraud systems
To solve the double-counting problem, we need to introduce a PositionState class that uses System.Threading.Interlocked to atomically update values without locking the entire dictionary.
The strategy shifts from a simple integer to a dual-state model:
Pending: Registered instantly inside the risk check lock.
Confirmed: Updated asynchronously by the WebSocket, releasing the pending amount.
Here is the updated implementation.
The strategy shifts from a simple integer to a dual-state model:
Pending: Registered instantly inside the risk check lock.
Confirmed: Updated asynchronously by the WebSocket, releasing the pending amount.
Here is the updated implementation.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: Multiple accounts risk on Apex fraud systems
1. The Atomic Position State
This class tracks the net exposure securely across multiple threads.
This class tracks the net exposure securely across multiple threads.
Code: Select all
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
public class PositionState
{
private int _pendingQuantity;
private int _confirmedQuantity;
public int PendingQuantity => _pendingQuantity;
public int ConfirmedQuantity => _confirmedQuantity;
// Total exposure is what the risk gate evaluates
public int NetExposure => _pendingQuantity + _confirmedQuantity;
public void AddPending(int amount)
{
Interlocked.Add(ref _pendingQuantity, amount);
}
public void ConfirmFill(int pendingToRelease, int confirmedAmount)
{
// Remove from pending, add to confirmed atomically
Interlocked.Add(ref _pendingQuantity, -pendingToRelease);
Interlocked.Add(ref _confirmedQuantity, confirmedAmount);
}
public void RevertPending(int amount)
{
Interlocked.Add(ref _pendingQuantity, -amount);
}
}Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: Multiple accounts risk on Apex fraud systems
2. The Updated Risk Manager
The GlobalRiskManager now registers the pending quantity inside the SemaphoreSlim lock. This is critical: if you check the risk and then release the lock before adding the pending quantity, a concurrent thread could slip an opposing order through the gap.
The GlobalRiskManager now registers the pending quantity inside the SemaphoreSlim lock. This is critical: if you check the risk and then release the lock before adding the pending quantity, a concurrent thread could slip an opposing order through the gap.
Code: Select all
public class GlobalRiskManager
{
// Dictionary mapping Instrument -> (AccountId -> PositionState)
private readonly ConcurrentDictionary<string, ConcurrentDictionary<string, PositionState>> _positions = new();
private readonly SemaphoreSlim _gateLock = new SemaphoreSlim(1, 1);
private PositionState GetOrAddPositionState(string instrument, string accountId)
{
var accountPositions = _positions.GetOrAdd(instrument, _ => new ConcurrentDictionary<string, PositionState>());
return accountPositions.GetOrAdd(accountId, _ => new PositionState());
}
public async Task EvaluateAndRegisterOrderAsync(TradeOrder order)
{
await _gateLock.WaitAsync();
try
{
var instrumentPositions = _positions.GetOrAdd(order.Instrument, _ => new ConcurrentDictionary<string, PositionState>());
bool isAttemptingLong = order.Side == OrderSide.Buy;
foreach (var kvp in instrumentPositions)
{
var otherAccountId = kvp.Key;
var state = kvp.Value;
var netPos = state.NetExposure; // Uses Pending + Confirmed
if (netPos == 0 || otherAccountId == order.AccountId) continue;
bool isOtherAccountLong = netPos > 0;
if (isAttemptingLong != isOtherAccountLong)
{
throw new RiskGateException(
$"Cross-account hedge rejected: Account {otherAccountId} holds {netPos} net {order.Instrument}. " +
$"Account {order.AccountId} attempted to {(isAttemptingLong ? "Buy" : "Sell")}.");
}
}
// Risk passed. Optimistically register the pending order IMMEDIATELY
// inside the lock to block concurrent opposite orders.
int signedQty = isAttemptingLong ? order.Quantity : -order.Quantity;
GetOrAddPositionState(order.Instrument, order.AccountId).AddPending(signedQty);
}
finally
{
_gateLock.Release();
}
}
// Called by the WebSocket service when an ExecutionReport arrives
public void ConfirmWebSocketFill(string accountId, string instrument, int requestedQty, int filledQty, OrderSide side)
{
var state = GetOrAddPositionState(instrument, accountId);
int signedRequested = side == OrderSide.Buy ? requestedQty : -requestedQty;
int signedFilled = side == OrderSide.Buy ? filledQty : -filledQty;
state.ConfirmFill(signedRequested, signedFilled);
}
// Called by the Wrapper if the synchronous REST API call fails
public void RevertFailedOrder(string accountId, string instrument, int requestedQty, OrderSide side)
{
var state = GetOrAddPositionState(instrument, accountId);
int signedQty = side == OrderSide.Buy ? requestedQty : -requestedQty;
state.RevertPending(signedQty);
}
}Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: Multiple accounts risk on Apex fraud systems
3. The Refactored Execution Wrapper
The execution wrapper must now handle scenarios where the order passes the internal risk check but is rejected by the broker API (e.g., insufficient margin or network timeout). In these cases, it must revert the pending quantity so the system doesn't permanently lock up "ghost" exposure.
The execution wrapper must now handle scenarios where the order passes the internal risk check but is rejected by the broker API (e.g., insufficient margin or network timeout). In these cases, it must revert the pending quantity so the system doesn't permanently lock up "ghost" exposure.
Code: Select all
public class OrderExecutionWrapper
{
private readonly GlobalRiskManager _riskManager;
private readonly IBrokerApi _broker;
private readonly IAuditLogger _logger;
public OrderExecutionWrapper(GlobalRiskManager riskManager, IBrokerApi broker, IAuditLogger logger)
{
_riskManager = riskManager;
_broker = broker;
_logger = logger;
}
public async Task<string> SubmitOrderAsync(TradeOrder order)
{
await _logger.LogOrderAttemptAsync(order);
// Step 1: Lock, check risk, and register pending quantity
await _riskManager.EvaluateAndRegisterOrderAsync(order);
try
{
// Step 2: Fire to broker
var brokerOrderId = await _broker.ExecuteMarketOrderAsync(order);
await _logger.LogOrderSentAsync(order, brokerOrderId);
return brokerOrderId;
// We DO NOT update position here anymore.
// The WebSocket handles the confirmation and releases the pending state.
}
catch (Exception ex)
{
// Step 3: If API submission fails, revert the pending state immediately
_riskManager.RevertFailedOrder(order.AccountId, order.Instrument, order.Quantity, order.Side);
await _logger.LogOrderRejectedAsync(order, ex.Message);
throw;
}
}
}Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Re: Multiple accounts risk on Apex fraud systems
This architecture ensures your net exposure calculation is always ahead of real-time trading flow while remaining perfectly self-correcting via the WebSocket reconciliation loop.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.