Advertisement IC Markets

Multiple accounts risk on Apex fraud systems

Discuss 1-minute to 15-minute price action setups, fading intraday momentum, key support/resistance zones, and proven short-term trading methodologies.
PTScalper
Site Admin
Posts: 4920
Joined: Mon Jul 20, 2026 1:28 pm

Re: Multiple accounts risk on Apex fraud systems

Post by PTScalper »

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.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Recommended broker for automated trading & scalping IC Markets
PTScalper
Site Admin
Posts: 4920
Joined: Mon Jul 20, 2026 1:28 pm

Re: Multiple accounts risk on Apex fraud systems

Post by PTScalper »

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.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 4920
Joined: Mon Jul 20, 2026 1:28 pm

Re: Multiple accounts risk on Apex fraud systems

Post by PTScalper »

1. The Atomic Position State

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.
PTScalper
Site Admin
Posts: 4920
Joined: Mon Jul 20, 2026 1:28 pm

Re: Multiple accounts risk on Apex fraud systems

Post by PTScalper »

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.

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.
PTScalper
Site Admin
Posts: 4920
Joined: Mon Jul 20, 2026 1:28 pm

Re: Multiple accounts risk on Apex fraud systems

Post by PTScalper »

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.

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.
PTScalper
Site Admin
Posts: 4920
Joined: Mon Jul 20, 2026 1:28 pm

Re: Multiple accounts risk on Apex fraud systems

Post by PTScalper »

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.
LondonNewsTrader
Posts: 421
Joined: Mon Sep 21, 2026 9:30 am

Re: Multiple accounts risk on Apex fraud systems

Post by LondonNewsTrader »

PTScalper wrote: By wrapping the cross-account position check in a SemaphoreSlim(1, 1) async lock, you force multi-threaded strategies to pass single-file through the risk gate.
That works inside one process, but SemaphoreSlim does nothing across processes or machines. If each account runs in its own instance, or the strategies live on two VPSs, each has its own semaphore and the race condition you describe is back. For that case the gate needs a shared store with an atomic operation, a database row lock or a Redis key with a short expiry, for example.

The release must also sit in a finally block. If the risk check throws, a semaphore that is never released silently blocks every later order, which on a release day looks like the platform has frozen.

The optimistic update needs a rollback path too. If the broker rejects the order or fills it partially, GlobalRiskManager holds a position that doesn't exist until the WebSocket event corrects it, and anything that checks exposure in between is working from wrong numbers.

The pre-logging idea is good. A clean record of intent is exactly what helps in any dispute.
LondonScalper
Posts: 965
Joined: Sat Sep 05, 2026 7:54 am

Re: Multiple accounts risk on Apex fraud systems

Post by LondonScalper »

Guilty-until-proven-innocent is the right model for those risk engines. Clever dual-login experiments read as hedging or copy behaviour long before anyone reads your notebook.

My own line stays dull on purpose: one evaluation story at a time, same device fingerprint, written notes on why size and timing look the way they do. If two accounts ever share a roof, they do not share entries, and I assume a human will never “understand the science.”

The durable edge against an automated ban is looking boring. Anything that looks like a lab test is bait.
FXS Prop Desk
Posts: 649
Joined: Wed Oct 07, 2026 9:52 am

Re: Multiple accounts risk on Apex fraud systems

Post by FXS Prop Desk »

Anyone trading multiple prop accounts should check the specific firm's rules first. Many firms limit the number of accounts per person, prohibit copy trading between accounts, or treat hedging across accounts as a violation, and the consequence can be loss of all accounts and payouts. Reading the terms and asking support in writing before setting up a copier is the safest approach.

A number of firms permit copying only between accounts of the same person at that firm, while banning it across firms. The wording differs a lot, so the exact clause matters more than general advice.
FXS Official Team | The Idea Lab & Strategy
Post Reply