Page 4 of 4

Re: Your Trading Plan Needs These 7 Non-Negotiable Sections

Posted: Wed Sep 23, 2026 8:44 am
by PTScalper
Because cTrader Automate (cAlgo) is built on modern .NET, we can completely eliminate third-party parsing libraries and use Microsoft's native System.Text.Json to handle the webhook payloads.

The architecture gap remains exactly the same: this cBot polls your Python Flask server (which is exposed via Ngrok) every second, parses the JSON, and executes the physical trade on your broker.

Re: Your Trading Plan Needs These 7 Non-Negotiable Sections

Posted: Wed Sep 23, 2026 8:44 am
by PTScalper
The cTrader Automate Execution Logic

Open the Automate tab in cTrader, create a new cBot, and paste this C# code.

Code: Select all

//+------------------------------------------------------------------+
//|                                           TV_Webhook_Receiver.cs |
//+------------------------------------------------------------------+
using System;
using System.Net.Http;
using System.Text.Json;
using cAlgo.API;

namespace cAlgo.Robots
{
    // CRITICAL: FullAccess is required to permit outbound HTTP requests to the Flask server
    [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.FullAccess)]
    public class TVWebhookReceiver : Robot
    {
        [Parameter("Server URL", DefaultValue = "http://localhost:5000/get_latest_alert")]
        public string ServerUrl { get; set; }

        private HttpClient _httpClient;
        private string _lastProcessedSignal = "";

        protected override void OnStart()
        {
            _httpClient = new HttpClient();
            
            // Poll the Flask server every 1 second
            Timer.Start(TimeSpan.FromSeconds(1));
            Print("cBot Initialized. Listening for TradingView signals...");
        }

        protected override void OnTimer()
        {
            try
            {
                // Synchronously fetch the JSON payload
                var response = _httpClient.GetAsync(ServerUrl).GetAwaiter().GetResult();
                
                if (response.IsSuccessStatusCode)
                {
                    string jsonString = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
                    
                    // Prevent duplicate execution of the exact same signal string
                    if (jsonString != _lastProcessedSignal && !string.IsNullOrWhiteSpace(jsonString) && jsonString != "{}")
                    {
                        ParseAndExecute(jsonString);
                        _lastProcessedSignal = jsonString;
                    }
                }
            }
            catch (Exception ex)
            {
                // Suppress printing on every tick if the Flask server is temporarily down
                if (ex.InnerException is System.Net.Sockets.SocketException) return;
                Print("HTTP Request Error: " + ex.Message);
            }
        }

        private void ParseAndExecute(string jsonPayload)
        {
            try
            {
                using (JsonDocument doc = JsonDocument.Parse(jsonPayload))
                {
                    JsonElement root = doc.RootElement;
                    
                    if (!root.TryGetProperty("action", out JsonElement actionElement)) return;
                    
                    string action = actionElement.GetString().ToLower();
                    string symbolString = root.GetProperty("symbol").GetString();
                    
                    // Route the TradingView ticker string to the specific cTrader Market Watch symbol
                    Symbol tradeSymbol = Symbols.GetSymbol(symbolString);
                    if (tradeSymbol == null)
                    {
                        Print("Symbol not found in Market Watch: " + symbolString);
                        return;
                    }

                    // Placeholder for Rule 5 Position Sizing
                    double volume = tradeSymbol.VolumeInUnitsMin;
                    
                    if (action == "long" || action == "short")
                    {
                        TradeType tradeType = action == "long" ? TradeType.Buy : TradeType.Sell;
                        double slPrice = root.GetProperty("sl").GetDouble();
                        double tpPrice = root.GetProperty("tp").GetDouble();
                        
                        // Execute Market Order natively
                        string label = "TV_" + action.ToUpper();
                        var result = ExecuteMarketOrder(tradeType, tradeSymbol.Name, volume, label);
                        
                        // Immediately map the absolute structural price levels from TradingView to the live position
                        if (result.IsSuccessful)
                        {
                            result.Position.ModifyStopLossPrice(slPrice);
                            result.Position.ModifyTakeProfitPrice(tpPrice);
                            Print($"Executed {action.ToUpper()} on {tradeSymbol.Name} | SL: {slPrice} | TP: {tpPrice}");
                        }
                        else
                        {
                            Print("Order Execution Failed: " + result.Error);
                        }
                    }
                    else if (action == "exit_long" || action == "exit_short")
                    {
                        TradeType tradeTypeToClose = action == "exit_long" ? TradeType.Buy : TradeType.Sell;
                        string label = "TV_" + (action == "exit_long" ? "LONG" : "SHORT");
                        
                        // Isolate only the open positions managed by this specific TV webhook
                        var positionsToClose = Positions.FindAll(label, tradeSymbol.Name);
                        foreach (var position in positionsToClose)
                        {
                            if (position.TradeType == tradeTypeToClose)
                            {
                                ClosePosition(position);
                                Print($"Closed {position.TradeType} position on {tradeSymbol.Name} due to TV exit signal.");
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Print("JSON Parsing / Execution Error: " + ex.Message);
            }
        }
        
        protected override void OnStop()
        {
            _httpClient?.Dispose();
        }
    }
}

Re: Your Trading Plan Needs These 7 Non-Negotiable Sections

Posted: Wed Sep 23, 2026 8:45 am
by PTScalper
Critical cTrader Configuration

Access Rights Security: By default, cTrader completely sandboxes all cBots from accessing the internet. You MUST set AccessRights = AccessRights.FullAccess in the class attribute for the HttpClient to function. When you attach the cBot to a chart, cTrader will prompt you to explicitly grant these permissions.

Absolute Structural Exits: Unlike the MQL scripts where we passed the Stop Loss as part of the execution command, the cAlgo API requires executing the trade first, and immediately appending the physical price levels to the open position using ModifyStopLossPrice().

Symbol Suffixes: Just as with MetaTrader, if your specific cTrader broker appends suffixes to assets (e.g., EURUSD.raw), you must append that suffix to the symbolString before passing it to Symbols.GetSymbol().

Re: Your Trading Plan Needs These 7 Non-Negotiable Sections

Posted: Wed Sep 23, 2026 8:45 am
by PTScalper
To implement Fairman's Rule 5 dynamically in cTrader Automate, you must mathematically bridge the physical distance of your stop loss (in pips) with the underlying pip value of the specific asset.

cTrader's API allows you to calculate the exact fiat value of a pip per unit of volume. By dividing your account risk by the stop-loss distance, you get the raw volume required.

Here is the exact method you need to add to your cBot class, along with the updated execution logic for ParseAndExecute:

Re: Your Trading Plan Needs These 7 Non-Negotiable Sections

Posted: Wed Sep 23, 2026 8:46 am
by PTScalper

Code: Select all

// Add this helper method anywhere inside your TVWebhookReceiver class
private double CalculateVolume(Symbol symbol, double entryPrice, double slPrice, double riskPercent)
{
    // 1. Translate percentage risk into physical account currency
    double riskAmount = Account.Equity * (riskPercent / 100);
    
    // 2. Determine the physical distance to the stop loss in pips
    double slDistancePips = Math.Abs(entryPrice - slPrice) / symbol.PipSize;
    
    // Prevent division by zero in the event of a malformed JSON payload
    if (slDistancePips == 0) return symbol.VolumeInUnitsMin; 
    
    // 3. Calculate raw volume: symbol.PipValue is the fiat value of 1 pip for 1 unit of volume
    double rawVolume = riskAmount / (slDistancePips * symbol.PipValue);
    
    // 4. Normalize the volume to strict broker step increments (e.g., micro lots)
    double safeVolume = symbol.NormalizeVolumeInUnits(rawVolume, RoundingMode.Down);
    
    // 5. Ensure the calculated risk isn't smaller than the broker's absolute minimum
    return Math.Max(safeVolume, symbol.VolumeInUnitsMin);
}

Re: Your Trading Plan Needs These 7 Non-Negotiable Sections

Posted: Wed Sep 23, 2026 8:46 am
by PTScalper
Injecting it into the Execution Block

To use this formula, locate the trade execution section inside your existing ParseAndExecute method and replace the static volume placeholder with the dynamic calculation:

Code: Select all

if (action == "long" || action == "short")
{
    TradeType tradeType = action == "long" ? TradeType.Buy : TradeType.Sell;
    double slPrice = root.GetProperty("sl").GetDouble();
    double tpPrice = root.GetProperty("tp").GetDouble();
    
    // Determine the exact execution price based on spread
    double currentPrice = tradeType == TradeType.Buy ? tradeSymbol.Ask : tradeSymbol.Bid;
    
    // Call the Rule 5 sizing formula (e.g., strictly risking 1% of equity)
    double riskPerTradePercent = 1.0;
    double volume = CalculateVolume(tradeSymbol, currentPrice, slPrice, riskPerTradePercent);
    
    string label = "TV_" + action.ToUpper();
    var result = ExecuteMarketOrder(tradeType, tradeSymbol.Name, volume, label);
    
    if (result.IsSuccessful)
    {
        result.Position.ModifyStopLossPrice(slPrice);
        result.Position.ModifyTakeProfitPrice(tpPrice);
        Print($"Executed {action.ToUpper()} | Vol: {volume} | SL: {slPrice} | TP: {tpPrice}");
    }
}

Re: Your Trading Plan Needs These 7 Non-Negotiable Sections

Posted: Wed Sep 23, 2026 8:46 am
by PTScalper
Why this enforces the plan:

Equity-Based Scaling: Because Account.Equity updates in real-time, your lot size automatically shrinks during a drawdown (protecting capital) and compounds during a winning streak.

Asset Agnostic: symbol.PipSize and symbol.PipValue automatically account for the difference between a Forex pair, an Index, or a Crypto asset. The math works identically across all markets without manual tweaks.

Strict Rounding: Passing RoundingMode.Down to the native NormalizeVolumeInUnits method guarantees the cBot will never round up to the nearest lot step, ensuring you never accidentally exceed your 1.0% limit by even a fraction.

Re: Your Trading Plan Needs These 7 Non-Negotiable Sections

Posted: Wed Sep 23, 2026 8:48 am
by PTScalper
By integrating this logic, you solve the biggest failure point of Rule 7: separating the data from the emotion. By automatically pushing a snapshot of the chart to your phone the exact second the trade closes, you force yourself to see what the market actually looked like at the exit, completely stripping away retroactive bias.

cTrader's modern API makes this incredibly efficient. You don't need to save files to your hard drive; the Chart.TakeChartshot() method grabs the image directly into memory as a byte array, and C#'s HttpClient can stream that directly to Telegram's servers.

Re: Your Trading Plan Needs These 7 Non-Negotiable Sections

Posted: Wed Sep 23, 2026 8:48 am
by PTScalper
Add these parameters, event subscriptions, and methods to your existing TVWebhookReceiver class.

Code: Select all

using System;
using System.Net.Http;
using System.Threading.Tasks;
using cAlgo.API;

namespace cAlgo.Robots
{
    [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.FullAccess)]
    public class TVWebhookReceiver : Robot
    {
        // ... (Keep your existing ServerUrl parameter here) ...

        [Parameter("Telegram Bot Token", DefaultValue = "")]
        public string BotToken { get; set; }

        [Parameter("Telegram Chat ID", DefaultValue = "")]
        public string ChatId { get; set; }

        private HttpClient _httpClient;

        protected override void OnStart()
        {
            _httpClient = new HttpClient();
            
            // ... (Keep your existing Webhook polling Timer logic here) ...

            // 1. Subscribe to the event that fires the millisecond a trade closes
            Positions.Closed += OnPositionClosed;
        }

        private void OnPositionClosed(PositionClosedEventArgs args)
        {
            // Only journal the trades managed by this specific Webhook cBot
            if (!args.Position.Label.StartsWith("TV_")) return;

            // CRITICAL: The Chart must be visible in your cTrader workspace to capture a shot
            if (!Chart.IsVisible)
            {
                Print("Cannot take screenshot: Chart is not visible.");
                return;
            }

            try
            {
                // 2. Native cTrader API captures the chart as a raw byte array in memory
                byte[] chartShotBytes = Chart.TakeChartshot();

                if (chartShotBytes != null && chartShotBytes.Length > 0)
                {
                    // 3. Construct the journal entry text
                    string caption = $"✅ Trade Closed: {args.Position.SymbolName} {args.Position.TradeType}\n" +
                                     $"Net Profit: {args.Position.NetProfit}\n" +
                                     $"Pips: {args.Position.Pips}\n" +
                                     $"Exit Reason: {(args.Position.GrossProfit > 0 ? "Take Profit Hit" : "Stop Loss Hit")}";

                    // 4. Offload the HTTP upload to a background thread to prevent freezing the cBot's execution loop
                    Task.Run(() => SendScreenshotToTelegram(chartShotBytes, caption));
                }
            }
            catch (Exception ex)
            {
                Print("Failed to capture Chartshot: " + ex.Message);
            }
        }

        private async Task SendScreenshotToTelegram(byte[] imageBytes, string caption)
        {
            if (string.IsNullOrWhiteSpace(BotToken) || string.IsNullOrWhiteSpace(ChatId))
            {
                Print("Telegram credentials missing. Journaling skipped.");
                return;
            }

            try
            {
                // Official Telegram API endpoint for sending photos
                string apiUrl = $"https://api.telegram.org/bot{BotToken}/sendPhoto";

                using (var form = new MultipartFormDataContent())
                {
                    form.Add(new StringContent(ChatId), "chat_id");
                    form.Add(new StringContent(caption), "caption");

                    // Package the byte array as a file upload object
                    var imageContent = new ByteArrayContent(imageBytes);
                    form.Add(imageContent, "photo", "chartshot.png");

                    HttpResponseMessage response = await _httpClient.PostAsync(apiUrl, form);
                    
                    if (response.IsSuccessStatusCode)
                    {
                        Print("✅ Trade journaled successfully to Telegram.");
                    }
                    else
                    {
                        string errorMsg = await response.Content.ReadAsStringAsync();
                        Print("❌ Telegram API Error: " + errorMsg);
                    }
                }
            }
            catch (Exception ex)
            {
                Print("Telegram Upload Failed: " + ex.Message);
            }
        }
    }
}

Re: Your Trading Plan Needs These 7 Non-Negotiable Sections

Posted: Wed Sep 23, 2026 8:48 am
by PTScalper
Setup Requirements

Bot Token: Talk to the @BotFather account on Telegram, send the /newbot command, and it will give you the HTTP API Token to paste into the cBot parameters.

Chat ID: Start a conversation with your new bot, then visit [https://api.telegram.org/bot](https://api.telegram.org/bot)<YourBotToken>/getUpdates in your web browser. Look for the "chat":{"id":...} field to get your personal Chat ID.

Workspace Visibility: Because Chart.TakeChartshot() takes a physical screenshot of the UI, your cTrader application must be running with the chart actively visible on the screen. If cTrader is minimized or the chart is hidden behind another tab, the screenshot will fail.