Advertisement IC Markets

Write Entry Rules So Specific a Stranger Could Follow Them

Master exponential money management, position sizing calculators, strict daily stop-loss limits, and overcoming FOMO on micro-timeframes.
PTScalper
Site Admin
Posts: 3349
Joined: Mon Jul 20, 2026 1:28 pm

Re: Write Entry Rules So Specific a Stranger Could Follow Them

Post by PTScalper »

Translating this strategy from TradingView (Pine Script) to MetaTrader (MQL4 and MQL5) requires a shift in architecture. Pine Script is designed specifically for charting and hides the complex mechanics of order routing. MQL4 and MQL5 are based on C++, meaning you have to explicitly program the trade execution, lot sizing math, and memory management for the indicators.

A critical difference to understand: In MetaTrader (which is primarily used for Forex and CFDs), "Volume" does not represent actual shares or contracts traded. It represents Tick Volume—the number of times the price changed during that candle. The strategy will still work to measure momentum, but it is measuring activity frequency rather than actual capital flow.

Here are the complete Expert Advisors (EAs) for both MT4 and MT5. They include the core entry rules, bidirectional long/short logic, dynamic risk-based lot sizing (1% risk), and percentage-based Take Profits and Stop Losses.
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: 3349
Joined: Mon Jul 20, 2026 1:28 pm

Re: Write Entry Rules So Specific a Stranger Could Follow Them

Post by PTScalper »

MetaTrader 4 (MQL4) Expert Advisor

To use this, open MetaEditor in MT4, click New > Expert Advisor, name it "CompleteStranger", and paste this code over the default template.

Code: Select all

//+------------------------------------------------------------------+
//|                                              CompleteStranger.mq4|
//|                                      The Complete Stranger Rule  |
//+------------------------------------------------------------------+
#property strict

// --- Settings ---
input double RiskPercent       = 1.0; // Account Risk per Trade (%)
input double StopLossPercent   = 1.0; // Stop Loss Distance (%)
input double TakeProfitPercent = 2.0; // Take Profit Distance (%)
input int    MagicNumber       = 10001; // Unique EA Identifier

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
    // 1. Ensure we only have one open trade at a time
    if (OrdersTotal() > 0) 
    {
        for (int i = 0; i < OrdersTotal(); i++) 
        {
            if (OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
                if (OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
                    return; // A trade is already active, do nothing
        }
    }

    // 2. Calculate Indicators on the previous closed candle (shift 1)
    double ema20 = iMA(Symbol(), 0, 20, 0, MODE_EMA, PRICE_CLOSE, 1);
    double rsi14 = iRSI(Symbol(), 0, 14, PRICE_CLOSE, 1);
    double closePrice = Close[1];
    
    // Calculate 20-period Simple Moving Average of Volume
    long volSum = 0;
    for(int i = 1; i <= 20; i++) volSum += Volume[i];
    double volSma20 = (double)volSum / 20.0;
    long currentVol = Volume[1];

    // 3. Define Entry Rules
    bool enterLong  = (closePrice > ema20) && (rsi14 > 50) && (currentVol > volSma20);
    bool enterShort = (closePrice < ema20) && (rsi14 < 50) && (currentVol > volSma20);

    // 4. Execute Trades
    if (enterLong || enterShort)
    {
        double tickSize = MarketInfo(Symbol(), MODE_TICKSIZE);
        double tickValue = MarketInfo(Symbol(), MODE_TICKVALUE);
        double minLot = MarketInfo(Symbol(), MODE_MINLOT);
        double lotStep = MarketInfo(Symbol(), MODE_LOTSTEP);

        // Dynamic Position Sizing Math
        double riskAmount = AccountBalance() * (RiskPercent / 100.0);
        double slDistance = Ask * (StopLossPercent / 100.0);
        
        // Prevent division by zero
        if(slDistance == 0 || tickSize == 0) return;
        
        double ticksAtRisk = slDistance / tickSize;
        double riskPerLot = ticksAtRisk * tickValue;
        double rawLots = riskAmount / riskPerLot;
        
        // Round lots to broker requirements
        double lots = MathFloor(rawLots / lotStep) * lotStep;
        if(lots < minLot) lots = minLot;

        if (enterLong)
        {
            double sl = Ask * (1.0 - (StopLossPercent / 100.0));
            double tp = Ask * (1.0 + (TakeProfitPercent / 100.0));
            
            OrderSend(Symbol(), OP_BUY, lots, Ask, 3, NormalizeDouble(sl, Digits), NormalizeDouble(tp, Digits), "Complete Stranger Long", MagicNumber, 0, clrGreen);
        }
        else if (enterShort)
        {
            double sl = Bid * (1.0 + (StopLossPercent / 100.0));
            double tp = Bid * (1.0 - (TakeProfitPercent / 100.0));
            
            OrderSend(Symbol(), OP_SELL, lots, Bid, 3, NormalizeDouble(sl, Digits), NormalizeDouble(tp, Digits), "Complete Stranger Short", MagicNumber, 0, clrRed);
        }
    }
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3349
Joined: Mon Jul 20, 2026 1:28 pm

Re: Write Entry Rules So Specific a Stranger Could Follow Them

Post by PTScalper »

MetaTrader 5 (MQL5) Expert Advisor

MQL5 is heavily object-oriented. Instead of pulling indicators directly on every tick, you must create "handles" in the initialization phase and load them into memory arrays when a tick arrives.

To use this, open MetaEditor in MT5, click New > Expert Advisor, name it "CompleteStranger5", and paste this code.

Code: Select all

//+------------------------------------------------------------------+
//|                                             CompleteStranger5.mq5|
//|                                      The Complete Stranger Rule  |
//+------------------------------------------------------------------+
#property strict
#include <Trade\Trade.mqh>

// --- Settings ---
input double RiskPercent       = 1.0; // Account Risk per Trade (%)
input double StopLossPercent   = 1.0; // Stop Loss Distance (%)
input double TakeProfitPercent = 2.0; // Take Profit Distance (%)
input ulong  MagicNumber       = 10001; // Unique EA Identifier

// Global variables
CTrade trade;
int emaHandle;
int rsiHandle;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
    trade.SetExpertMagicNumber(MagicNumber);
    
    // Initialize Indicator Handles
    emaHandle = iMA(_Symbol, _Period, 20, 0, MODE_EMA, PRICE_CLOSE);
    rsiHandle = iRSI(_Symbol, _Period, 14, PRICE_CLOSE);
    
    if(emaHandle == INVALID_HANDLE || rsiHandle == INVALID_HANDLE)
    {
        Print("Failed to load indicators");
        return(INIT_FAILED);
    }
    return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
    // 1. Ensure only one open position
    if (PositionsTotal() > 0)
    {
        for (int i = 0; i < PositionsTotal(); i++)
        {
            ulong ticket = PositionGetTicket(i);
            if (PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == MagicNumber)
                return;
        }
    }

    // 2. Fetch Indicator Data
    double emaArray[], rsiArray[], closeArray[];
    long volArray[];
    
    ArraySetAsSeries(emaArray, true);
    ArraySetAsSeries(rsiArray, true);
    ArraySetAsSeries(closeArray, true);
    ArraySetAsSeries(volArray, true);
    
    // Copy the last 2 candles for price/indicators (index 1 is the last closed candle)
    if(CopyBuffer(emaHandle, 0, 0, 2, emaArray) <= 0) return;
    if(CopyBuffer(rsiHandle, 0, 0, 2, rsiArray) <= 0) return;
    if(CopyClose(_Symbol, _Period, 0, 2, closeArray) <= 0) return;
    
    // Fetch last 21 candles of tick volume to get the current + average of previous 20
    if(CopyTickVolume(_Symbol, _Period, 0, 21, volArray) <= 0) return;

    double ema20 = emaArray[1];
    double rsi14 = rsiArray[1];
    double closePrice = closeArray[1];
    long currentVol = volArray[1];
    
    // Calculate Volume SMA over the last 20 closed candles (index 1 to 20)
    long volSum = 0;
    for(int i = 1; i <= 20; i++) volSum += volArray[i];
    double volSma20 = (double)volSum / 20.0;

    // 3. Define Entry Rules
    bool enterLong  = (closePrice > ema20) && (rsi14 > 50) && (currentVol > volSma20);
    bool enterShort = (closePrice < ema20) && (rsi14 < 50) && (currentVol > volSma20);

    // 4. Execute Trades with Dynamic Risk Sizing
    if (enterLong || enterShort)
    {
        double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
        double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
        double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
        double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
        
        double askPrice = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
        double bidPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID);

        double riskAmount = AccountInfoDouble(ACCOUNT_BALANCE) * (RiskPercent / 100.0);
        double slDistance = askPrice * (StopLossPercent / 100.0);
        
        if(slDistance == 0 || tickSize == 0) return;
        
        double ticksAtRisk = slDistance / tickSize;
        double riskPerLot = ticksAtRisk * tickValue;
        double rawLots = riskAmount / riskPerLot;
        
        double lots = MathFloor(rawLots / lotStep) * lotStep;
        if(lots < minLot) lots = minLot;

        if (enterLong)
        {
            double sl = askPrice * (1.0 - (StopLossPercent / 100.0));
            double tp = askPrice * (1.0 + (TakeProfitPercent / 100.0));
            trade.Buy(lots, _Symbol, askPrice, sl, tp, "Complete Stranger Long");
        }
        else if (enterShort)
        {
            double sl = bidPrice * (1.0 + (StopLossPercent / 100.0));
            double tp = bidPrice * (1.0 - (TakeProfitPercent / 100.0));
            trade.Sell(lots, _Symbol, bidPrice, sl, tp, "Complete Stranger Short");
        }
    }
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3349
Joined: Mon Jul 20, 2026 1:28 pm

Re: Write Entry Rules So Specific a Stranger Could Follow Them

Post by PTScalper »

Translating this strategy to cTrader requires using C# and the cAlgo API. cTrader is highly favored by algorithmic traders because its API is much cleaner and more modern than MetaTrader's.

In cTrader, position sizing is handled in "Units" (e.g., 100,000 units = 1 standard lot). The script below handles the dynamic 1% risk calculations automatically, converting your percentage-based stop loss into exactly how many units you need to buy to cap your risk perfectly.

Here is the complete cBot.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3349
Joined: Mon Jul 20, 2026 1:28 pm

Re: Write Entry Rules So Specific a Stranger Could Follow Them

Post by PTScalper »

cTrader Automate cBot (C#)

To use this, open cTrader, go to the Automate tab on the left, click New cBot, name it "CompleteStranger", and paste this code over the default template. Click Build (the hammer icon) at the top to compile it.

Code: Select all

using System;
using cAlgo.API;
using cAlgo.API.Indicators;
using cAlgo.API.Internals;

namespace cAlgo.Robots
{
    [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class CompleteStranger : Robot
    {
        // ==========================================
        // 1. SETTINGS INPUTS (Adjustable in UI)
        // ==========================================
        [Parameter("Risk per Trade (%)", DefaultValue = 1.0, MinValue = 0.1, Step = 0.1, Group = "Risk Management")]
        public double RiskPercent { get; set; }

        [Parameter("Stop Loss (%)", DefaultValue = 1.0, MinValue = 0.1, Step = 0.1, Group = "Risk Management")]
        public double SlPercent { get; set; }

        [Parameter("Take Profit (%)", DefaultValue = 2.0, MinValue = 0.1, Step = 0.1, Group = "Risk Management")]
        public double TpPercent { get; set; }

        // ==========================================
        // 2. INDICATOR VARIABLES
        // ==========================================
        private ExponentialMovingAverage _ema20;
        private RelativeStrengthIndex _rsi14;
        private SimpleMovingAverage _volSma20;

        // ==========================================
        // 3. INITIALIZATION
        // ==========================================
        protected override void OnStart()
        {
            // Initialize indicators using the cAlgo API
            _ema20 = Indicators.ExponentialMovingAverage(Bars.ClosePrices, 20);
            _rsi14 = Indicators.RelativeStrengthIndex(Bars.ClosePrices, 14);
            
            // In cTrader, TickVolumes is used for activity measurement (same as MT4/MT5)
            _volSma20 = Indicators.SimpleMovingAverage(Bars.TickVolumes, 20);
        }

        // ==========================================
        // 4. CORE LOGIC (Executes on every closed candle)
        // ==========================================
        protected override void OnBar()
        {
            // Ensure we only take one trade at a time per symbol
            if (Positions.FindAll("CompleteStranger", SymbolName).Length > 0)
                return;

            // Get the index of the most recently closed candle (Bars.Count - 1 is currently forming)
            int index = Bars.Count - 2;

            double close = Bars.ClosePrices[index];
            double ema = _ema20.Result[index];
            double rsi = _rsi14.Result[index];
            double volume = Bars.TickVolumes[index];
            double volSma = _volSma20.Result[index];

            // Define the rules exactly as written
            bool enterLong = (close > ema) && (rsi > 50) && (volume > volSma);
            bool enterShort = (close < ema) && (rsi < 50) && (volume > volSma);

            if (enterLong)
            {
                ExecuteDynamicTrade(TradeType.Buy);
            }
            else if (enterShort)
            {
                ExecuteDynamicTrade(TradeType.Sell);
            }
        }

        // ==========================================
        // 5. DYNAMIC POSITION SIZING & EXECUTION
        // ==========================================
        private void ExecuteDynamicTrade(TradeType tradeType)
        {
            // Determine price based on direction
            double entryPrice = (tradeType == TradeType.Buy) ? Symbol.Ask : Symbol.Bid;

            // cTrader ExecuteMarketOrder requires Stop Loss and Take Profit to be defined in PIPS, not percentages
            double slDistanceInPrice = entryPrice * (SlPercent / 100.0);
            double slInPips = slDistanceInPrice / Symbol.PipSize;
            
            double tpDistanceInPrice = entryPrice * (TpPercent / 100.0);
            double tpInPips = tpDistanceInPrice / Symbol.PipSize;

            // Calculate how much money we are willing to lose (e.g., 1% of Equity)
            double riskAmount = Account.Equity * (RiskPercent / 100.0);

            // Calculate the monetary risk of a 1-unit trade moving against us to our stop loss
            double riskPerUnit = slInPips * Symbol.PipValue;

            if (riskPerUnit <= 0) return; // Prevent division by zero

            // Calculate exact units needed to match our risk amount
            double rawVolume = riskAmount / riskPerUnit;

            // Normalize the volume to ensure it meets the broker's minimum/step requirements (e.g., rounding to 1,000s)
            double normalizedVolume = Symbol.NormalizeVolumeInUnits(rawVolume, RoundingMode.Down);

            // Abort if the required size is smaller than the broker allows
            if (normalizedVolume < Symbol.VolumeInUnitsMin)
            {
                Print("Calculated position size is too small for this broker. Minimum required: ", Symbol.VolumeInUnitsMin);
                return;
            }

            // Execute the trade
            ExecuteMarketOrder(tradeType, SymbolName, normalizedVolume, "CompleteStranger", slInPips, tpInPips);
        }
    }
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3349
Joined: Mon Jul 20, 2026 1:28 pm

Re: Write Entry Rules So Specific a Stranger Could Follow Them

Post by PTScalper »

Why cTrader Handles Sizing Better

Look at the ExecuteDynamicTrade method. Unlike MetaTrader, where you have to do complex lot-step math, cTrader provides a built-in helper method: Symbol.

NormalizeVolumeInUnits(rawVolume, RoundingMode.Down). You calculate exactly how many units you want based on your 1% risk math, and cTrader automatically snaps that number down to the nearest safe increment your broker accepts, eliminating invalid volume errors.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3349
Joined: Mon Jul 20, 2026 1:28 pm

Re: Write Entry Rules So Specific a Stranger Could Follow Them

Post by PTScalper »

To replace the fixed Take Profit with a Trailing Stop Loss in cTrader, we must remove the Take Profit parameter entirely and write a custom trailing function inside the OnTick() method.

While cTrader has a built-in automated trailing stop toggle, it forces the trailing distance to be exactly the same as the initial stop loss. By writing our own logic, we can keep the initial Stop Loss at 1% and set the Trailing Stop to step behind at 1.5%, exactly like we did in the Pine Script version.

Here is the updated cBot.

Code: Select all

using System;
using cAlgo.API;
using cAlgo.API.Indicators;
using cAlgo.API.Internals;

namespace cAlgo.Robots
{
    [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class CompleteStrangerTrailing : Robot
    {
        // ==========================================
        // 1. SETTINGS INPUTS (Adjustable in UI)
        // ==========================================
        [Parameter("Risk per Trade (%)", DefaultValue = 1.0, MinValue = 0.1, Step = 0.1, Group = "Risk Management")]
        public double RiskPercent { get; set; }

        [Parameter("Initial Stop Loss (%)", DefaultValue = 1.0, MinValue = 0.1, Step = 0.1, Group = "Risk Management")]
        public double SlPercent { get; set; }

        [Parameter("Trailing Distance (%)", DefaultValue = 1.5, MinValue = 0.1, Step = 0.1, Group = "Risk Management")]
        public double TrailPercent { get; set; }

        // ==========================================
        // 2. INDICATOR VARIABLES
        // ==========================================
        private ExponentialMovingAverage _ema20;
        private RelativeStrengthIndex _rsi14;
        private SimpleMovingAverage _volSma20;

        // ==========================================
        // 3. INITIALIZATION
        // ==========================================
        protected override void OnStart()
        {
            _ema20 = Indicators.ExponentialMovingAverage(Bars.ClosePrices, 20);
            _rsi14 = Indicators.RelativeStrengthIndex(Bars.ClosePrices, 14);
            _volSma20 = Indicators.SimpleMovingAverage(Bars.TickVolumes, 20);
        }

        // ==========================================
        // 4. CORE ENTRY LOGIC (Executes on closed candles)
        // ==========================================
        protected override void OnBar()
        {
            if (Positions.FindAll("CompleteStranger", SymbolName).Length > 0)
                return;

            int index = Bars.Count - 2;

            double close = Bars.ClosePrices[index];
            double ema = _ema20.Result[index];
            double rsi = _rsi14.Result[index];
            double volume = Bars.TickVolumes[index];
            double volSma = _volSma20.Result[index];

            bool enterLong = (close > ema) && (rsi > 50) && (volume > volSma);
            bool enterShort = (close < ema) && (rsi < 50) && (volume > volSma);

            if (enterLong) ExecuteDynamicTrade(TradeType.Buy);
            else if (enterShort) ExecuteDynamicTrade(TradeType.Sell);
        }

        // ==========================================
        // 5. CUSTOM TRAILING STOP LOGIC (Executes every tick)
        // ==========================================
        protected override void OnTick()
        {
            var positions = Positions.FindAll("CompleteStranger", SymbolName);
            
            foreach (var position in positions)
            {
                if (position.TradeType == TradeType.Buy)
                {
                    // Calculate theoretical trailing stop below current Bid price
                    double newStopLoss = Symbol.Bid * (1.0 - (TrailPercent / 100.0));
                    
                    // The Ratchet: Only move the stop UP, and require at least a 1 pip difference to avoid spamming the broker API
                    if (position.StopLoss.HasValue && newStopLoss > position.StopLoss.Value + Symbol.PipSize)
                    {
                        // Round to broker digits to ensure smooth execution
                        ModifyPosition(position, Math.Round(newStopLoss, Symbol.Digits), null);
                    }
                }
                else if (position.TradeType == TradeType.Sell)
                {
                    // Calculate theoretical trailing stop above current Ask price
                    double newStopLoss = Symbol.Ask * (1.0 + (TrailPercent / 100.0));
                    
                    // The Ratchet: Only move the stop DOWN, and require at least a 1 pip difference
                    if (position.StopLoss.HasValue && newStopLoss < position.StopLoss.Value - Symbol.PipSize)
                    {
                        ModifyPosition(position, Math.Round(newStopLoss, Symbol.Digits), null);
                    }
                }
            }
        }

        // ==========================================
        // 6. DYNAMIC POSITION SIZING & EXECUTION
        // ==========================================
        private void ExecuteDynamicTrade(TradeType tradeType)
        {
            double entryPrice = (tradeType == TradeType.Buy) ? Symbol.Ask : Symbol.Bid;

            double slDistanceInPrice = entryPrice * (SlPercent / 100.0);
            double slInPips = slDistanceInPrice / Symbol.PipSize;

            double riskAmount = Account.Equity * (RiskPercent / 100.0);
            double riskPerUnit = slInPips * Symbol.PipValue;

            if (riskPerUnit <= 0) return; 

            double rawVolume = riskAmount / riskPerUnit;
            double normalizedVolume = Symbol.NormalizeVolumeInUnits(rawVolume, RoundingMode.Down);

            if (normalizedVolume < Symbol.VolumeInUnitsMin)
            {
                Print("Calculated position size is too small for this broker.");
                return;
            }

            // Execute the trade with a Stop Loss, but set Take Profit to 'null'
            ExecuteMarketOrder(tradeType, SymbolName, normalizedVolume, "CompleteStranger", slInPips, null);
        }
    }
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3349
Joined: Mon Jul 20, 2026 1:28 pm

Re: Write Entry Rules So Specific a Stranger Could Follow Them

Post by PTScalper »

How the Custom Trailing Logic Works

1.) Notice how the OnTick() function behaves.

2.) Unlike OnBar(), which waits for a 5-minute candle to close, OnTick() fires literally every single time the price updates, monitoring the true maximum and minimum reach of the asset.

3.) To prevent your cBot from sending thousands of useless micro-requests to your broker's server (which can get your API connection temporarily banned), I added an API spam check: newStopLoss > position.StopLoss.Value + Symbol.PipSize. The robot will mathematically track the new stop level in the background, but will only officially send an update order to your broker once the new stop has stepped forward by at least 1 full pip.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3349
Joined: Mon Jul 20, 2026 1:28 pm

Re: Write Entry Rules So Specific a Stranger Could Follow Them

Post by PTScalper »

Handling time filters in cTrader is slightly more complex than in Pine Script because cTrader servers typically run on UTC time, while your trading strategy is based on New York (EST/EDT) time.

To solve this, we use the .NET TimeZoneInfo class to dynamically convert the broker's server time into New York time, accurately handling Daylight Saving Time shifts automatically.

Here is the complete, finalized cBot. It now includes dynamic risk sizing, custom trailing stops, and New York market hours with an automated End-of-Day flatten window.

Code: Select all

using System;
using cAlgo.API;
using cAlgo.API.Indicators;
using cAlgo.API.Internals;

namespace cAlgo.Robots
{
    [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class CompleteStrangerPro : Robot
    {
        // ==========================================
        // 1. RISK MANAGEMENT SETTINGS
        // ==========================================
        [Parameter("Risk per Trade (%)", DefaultValue = 1.0, MinValue = 0.1, Step = 0.1, Group = "Risk Management")]
        public double RiskPercent { get; set; }

        [Parameter("Initial Stop Loss (%)", DefaultValue = 1.0, MinValue = 0.1, Step = 0.1, Group = "Risk Management")]
        public double SlPercent { get; set; }

        [Parameter("Trailing Distance (%)", DefaultValue = 1.5, MinValue = 0.1, Step = 0.1, Group = "Risk Management")]
        public double TrailPercent { get; set; }

        // ==========================================
        // 2. TIME FILTER SETTINGS
        // ==========================================
        [Parameter("Session Start (NY Time)", DefaultValue = "09:30", Group = "Time Filters")]
        public string SessionStartStr { get; set; }

        [Parameter("Session End (NY Time)", DefaultValue = "16:00", Group = "Time Filters")]
        public string SessionEndStr { get; set; }

        [Parameter("Force EOD Close?", DefaultValue = true, Group = "Time Filters")]
        public bool ForceEodClose { get; set; }

        [Parameter("EOD Close Start (NY Time)", DefaultValue = "15:45", Group = "Time Filters")]
        public string EodCloseStartStr { get; set; }

        // ==========================================
        // 3. INTERNAL VARIABLES
        // ==========================================
        private ExponentialMovingAverage _ema20;
        private RelativeStrengthIndex _rsi14;
        private SimpleMovingAverage _volSma20;

        private TimeSpan _sessionStart;
        private TimeSpan _sessionEnd;
        private TimeSpan _eodCloseStart;
        private TimeZoneInfo _nyTimeZone;

        // ==========================================
        // 4. INITIALIZATION
        // ==========================================
        protected override void OnStart()
        {
            _ema20 = Indicators.ExponentialMovingAverage(Bars.ClosePrices, 20);
            _rsi14 = Indicators.RelativeStrengthIndex(Bars.ClosePrices, 14);
            _volSma20 = Indicators.SimpleMovingAverage(Bars.TickVolumes, 20);

            // Parse time strings into TimeSpan objects for fast comparison
            TimeSpan.TryParse(SessionStartStr, out _sessionStart);
            TimeSpan.TryParse(SessionEndStr, out _sessionEnd);
            TimeSpan.TryParse(EodCloseStartStr, out _eodCloseStart);

            // Load the New York time zone to handle UTC conversions and Daylight Saving Time
            _nyTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
        }

        // ==========================================
        // 5. TIME HELPER METHODS
        // ==========================================
        private TimeSpan GetCurrentNyTime()
        {
            // Convert current UTC server time to New York time
            DateTime nyTime = TimeZoneInfo.ConvertTimeFromUtc(Server.TimeInUtc, _nyTimeZone);
            return nyTime.TimeOfDay;
        }

        // ==========================================
        // 6. CORE ENTRY LOGIC (Executes on closed candles)
        // ==========================================
        protected override void OnBar()
        {
            // Check time filters first
            TimeSpan currentNyTime = GetCurrentNyTime();
            bool inSession = currentNyTime >= _sessionStart && currentNyTime <= _sessionEnd;
            bool inEodWindow = currentNyTime >= _eodCloseStart && currentNyTime <= _sessionEnd;
            bool canTrade = inSession && !(ForceEodClose && inEodWindow);

            if (!canTrade) return;

            // Ensure we don't open multiple positions
            if (Positions.FindAll("CompleteStranger", SymbolName).Length > 0) return;

            int index = Bars.Count - 2;
            double close = Bars.ClosePrices[index];
            double ema = _ema20.Result[index];
            double rsi = _rsi14.Result[index];
            double volume = Bars.TickVolumes[index];
            double volSma = _volSma20.Result[index];

            bool enterLong = (close > ema) && (rsi > 50) && (volume > volSma);
            bool enterShort = (close < ema) && (rsi < 50) && (volume > volSma);

            if (enterLong) ExecuteDynamicTrade(TradeType.Buy);
            else if (enterShort) ExecuteDynamicTrade(TradeType.Sell);
        }

        // ==========================================
        // 7. TRAILING STOP & EOD CLOSE (Executes every tick)
        // ==========================================
        protected override void OnTick()
        {
            var positions = Positions.FindAll("CompleteStranger", SymbolName);
            if (positions.Length == 0) return;

            TimeSpan currentNyTime = GetCurrentNyTime();
            bool inEodWindow = currentNyTime >= _eodCloseStart && currentNyTime <= _sessionEnd;

            foreach (var position in positions)
            {
                // Force Close if in the EOD window
                if (ForceEodClose && inEodWindow)
                {
                    ClosePosition(position);
                    continue; // Skip trailing logic since the trade is closing
                }

                // Trailing Stop Logic
                if (position.TradeType == TradeType.Buy)
                {
                    double newStopLoss = Symbol.Bid * (1.0 - (TrailPercent / 100.0));
                    
                    if (position.StopLoss.HasValue && newStopLoss > position.StopLoss.Value + Symbol.PipSize)
                    {
                        ModifyPosition(position, Math.Round(newStopLoss, Symbol.Digits), null);
                    }
                }
                else if (position.TradeType == TradeType.Sell)
                {
                    double newStopLoss = Symbol.Ask * (1.0 + (TrailPercent / 100.0));
                    
                    if (position.StopLoss.HasValue && newStopLoss < position.StopLoss.Value - Symbol.PipSize)
                    {
                        ModifyPosition(position, Math.Round(newStopLoss, Symbol.Digits), null);
                    }
                }
            }
        }

        // ==========================================
        // 8. DYNAMIC POSITION SIZING & EXECUTION
        // ==========================================
        private void ExecuteDynamicTrade(TradeType tradeType)
        {
            double entryPrice = (tradeType == TradeType.Buy) ? Symbol.Ask : Symbol.Bid;

            double slDistanceInPrice = entryPrice * (SlPercent / 100.0);
            double slInPips = slDistanceInPrice / Symbol.PipSize;

            double riskAmount = Account.Equity * (RiskPercent / 100.0);
            double riskPerUnit = slInPips * Symbol.PipValue;

            if (riskPerUnit <= 0) return; 

            double rawVolume = riskAmount / riskPerUnit;
            double normalizedVolume = Symbol.NormalizeVolumeInUnits(rawVolume, RoundingMode.Down);

            if (normalizedVolume < Symbol.VolumeInUnitsMin)
            {
                Print("Calculated position size is too small for this broker.");
                return;
            }

            ExecuteMarketOrder(tradeType, SymbolName, normalizedVolume, "CompleteStranger", slInPips, null);
        }
    }
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3349
Joined: Mon Jul 20, 2026 1:28 pm

Re: Write Entry Rules So Specific a Stranger Could Follow Them

Post by PTScalper »

How the Time Integration Works

OnStart(): When you launch the cBot, it loads the Windows system definitions for "Eastern Standard Time". It parses your 09:30 and 15:45 text inputs into strict TimeSpan objects so the robot can compare time instantaneously without slowing down order execution.

OnBar(): Every 5 minutes, before it even checks the EMA or RSI, the bot verifies canTrade. If the clock currently reads 15:50 NY time, canTrade becomes false and the script stops running entirely, preventing any new trades from opening right before the bell.

OnTick(): While a trade is open, the bot checks the clock on every single price tick. If that tick happens after 15:45 NY time, the bot triggers ClosePosition(position) at the current market price and skips the trailing stop calculation.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Post Reply