Write Entry Rules So Specific a Stranger Could Follow Them
Re: Write Entry Rules So Specific a Stranger Could Follow Them
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.
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.
Re: Write Entry Rules So Specific a Stranger Could Follow Them
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.
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.
Re: Write Entry Rules So Specific a Stranger Could Follow Them
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.
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.
Re: Write Entry Rules So Specific a Stranger Could Follow Them
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.
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.
Re: Write Entry Rules So Specific a Stranger Could Follow Them
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.
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.
Re: Write Entry Rules So Specific a Stranger Could Follow Them
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.
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.
Re: Write Entry Rules So Specific a Stranger Could Follow Them
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.
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.
Re: Write Entry Rules So Specific a Stranger Could Follow Them
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.
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.
Re: Write Entry Rules So Specific a Stranger Could Follow Them
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.
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.
Re: Write Entry Rules So Specific a Stranger Could Follow Them
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.
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.