Page 3 of 3

Re: Calibrating my spread filter that blocks revenge during CPI week

Posted: Mon Oct 05, 2026 5:13 pm
by PTScalper
3. Integrating with the cBot

Update the ProExecutionFilter to listen for closed positions and capture the TradeResult from ExecuteMarketOrder. Because cTrader executes market orders synchronously, you get the Position.Id immediately upon a successful fill.

Code: Select all

public class ProExecutionFilter : Robot
    {
        // ... (existing parameters and properties) ...

        protected override void OnStart()
        {
            // ... (existing init logic) ...
            
            _logger = new SqlServerLogger(DbConnectionString);
            _validator = new ExecutionValidator(this, _cpiStart, _cpiEnd);
            _hud = new ChartHud(this);

            Chart.KeyDown += OnChartKeyDown;
            Positions.Closed += OnPositionsClosed; // Subscribe to closure events
            
            UpdateHud();
        }

        private void TryExecute(TradeType tradeType)
        {
            ValidationResult result = _validator.Validate();

            if (!result.IsValid)
            {
                Print($"🔴 REJECTED: {result.Reason} | Spread: {result.CurrentSpread:F1}");
                _logger.LogExecutionAsync(result, SymbolName, tradeType, VolumeInUnits, false);
                return;
            }

            // Await the synchronous broker execution
            TradeResult tradeResult = ExecuteMarketOrder(tradeType, SymbolName, VolumeInUnits, "ExecutionFilter", 0, 0);

            if (tradeResult.IsSuccessful)
            {
                Print($"🟢 ACCEPTED | Spread: {result.CurrentSpread:F1}");
                
                // Log the accepted trade and map it to the generated Position ID
                _logger.LogExecutionAsync(result, SymbolName, tradeType, VolumeInUnits, true, tradeResult.Position.Id);
            }
        }

        private void OnPositionsClosed(PositionClosedEventArgs args)
        {
            // Only update database for positions opened by this specific bot
            if (args.Position.Label == "ExecutionFilter")
            {
                _logger.UpdateOutcomeAsync(args.Position.Id, args.Position.NetProfit);
            }
        }
    }
By querying this new structure at the end of a quarter, you can isolate IsAccepted = 1 and group your win rate and net profitability into spread brackets (e.g., < 0.3 pips, 0.3-0.5 pips, > 0.5 pips) to see precisely where your execution costs start eroding your edge on the 15-minute chart.