Replace your cBot code in cTrader Automate with the script below and press Ctrl + B to build:
Code: Select all
using System;
using System.Collections.Generic;
using System.Linq;
using cAlgo.API;
using cAlgo.API.Indicators;
using cAlgo.API.Internals;
namespace cAlgo.Robots
{
[Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
public class XAUUSD_LiquidityScalper : Robot
{
#region 1. Strategy & Setup Filters
[Parameter("Enable Liquidity Sweep Setup", Group = "1. Strategy & Setup Filters", DefaultValue = true)]
public bool InpEnableSweepSetup { get; set; }
[Parameter("Enable M15 EMA Pullback Setup", Group = "1. Strategy & Setup Filters", DefaultValue = true)]
public bool InpEnablePullbackSetup { get; set; }
[Parameter("M15 Fast EMA Period", Group = "1. Strategy & Setup Filters", DefaultValue = 20, MinValue = 5)]
public int InpEmaFast { get; set; }
[Parameter("M15 Slow EMA Period", Group = "1. Strategy & Setup Filters", DefaultValue = 50, MinValue = 10)]
public int InpEmaSlow { get; set; }
[Parameter("M5 ATR Period", Group = "1. Strategy & Setup Filters", DefaultValue = 14, MinValue = 5)]
public int InpAtrPeriod { get; set; }
[Parameter("H4 Swing Lookback (Bars)", Group = "1. Strategy & Setup Filters", DefaultValue = 10, MinValue = 3)]
public int InpH4SwingLookback { get; set; }
[Parameter("Max Sweep Overshoot ($ USD)", Group = "1. Strategy & Setup Filters", DefaultValue = 2.50, MinValue = 0.20, Step = 0.10)]
public double InpMaxSweepOvershootUSD { get; set; }
[Parameter("EMA Touch Tolerance ($ USD)", Group = "1. Strategy & Setup Filters", DefaultValue = 0.35, MinValue = 0.05, Step = 0.05)]
public double InpTouchToleranceUSD { get; set; }
#endregion
#region 2. XAU/USD Stop-Loss & Target Calibration
[Parameter("ATR Multiplier Beyond Wick", Group = "2. Stop-Loss & Targets", DefaultValue = 1.0, MinValue = 0.2, Step = 0.1)]
public double InpAtrStopMultiplier { get; set; }
[Parameter("Min Structural Stop ($ USD)", Group = "2. Stop-Loss & Targets", DefaultValue = 1.20, MinValue = 0.50, Step = 0.10)]
public double InpMinStopUSD { get; set; }
[Parameter("Max Structural Stop ($ USD)", Group = "2. Stop-Loss & Targets", DefaultValue = 3.50, MinValue = 1.50, Step = 0.10)]
public double InpMaxStopUSD { get; set; }
[Parameter("Partial Close & BE Trigger (R)", Group = "2. Stop-Loss & Targets", DefaultValue = 1.5, MinValue = 0.5, Step = 0.1)]
public double InpPartialCloseRR { get; set; }
[Parameter("Partial Close Ratio (0.5 = 50%)", Group = "2. Stop-Loss & Targets", DefaultValue = 0.50, MinValue = 0.10, MaxValue = 0.90, Step = 0.05)]
public double InpPartialCloseRatio { get; set; }
[Parameter("Start M15 Structural Trail (R)", Group = "2. Stop-Loss & Targets", DefaultValue = 2.0, MinValue = 1.0, Step = 0.1)]
public double InpTrailStartRR { get; set; }
[Parameter("Final Hard Target (R)", Group = "2. Stop-Loss & Targets", DefaultValue = 3.5, MinValue = 1.5, Step = 0.1)]
public double InpFinalTargetRR { get; set; }
[Parameter("Time Stop if < 0.5R (Mins, 0=Off)", Group = "2. Stop-Loss & Targets", DefaultValue = 45, MinValue = 0)]
public int InpTimeStopMinutes { get; set; }
#endregion
#region 3. Risk, ECN Commission & Profit-Lock Scaling
[Parameter("Baseline Risk Per Trade (%)", Group = "3. Risk & Profit-Lock Scaling", DefaultValue = 0.50, MinValue = 0.10, MaxValue = 5.0, Step = 0.05)]
public double InpBaseRiskPct { get; set; }
[Parameter("Round-Turn Commission / Lot ($)", Group = "3. Risk & Profit-Lock Scaling", DefaultValue = 7.00, MinValue = 0.0, Step = 0.50)]
public double InpCommissionPerLotRT { get; set; }
[Parameter("Enable Profit-Lock Scaling", Group = "3. Risk & Profit-Lock Scaling", DefaultValue = true)]
public bool InpUseProfitLockScaling { get; set; }
[Parameter("Gain % to Trigger 50% Lock", Group = "3. Risk & Profit-Lock Scaling", DefaultValue = 2.00, MinValue = 0.50, Step = 0.25)]
public double InpProfitLockTriggerPct { get; set; }
[Parameter("% of Unlocked Buffer Added to Risk", Group = "3. Risk & Profit-Lock Scaling", DefaultValue = 25.0, MinValue = 5.0, MaxValue = 100.0, Step = 5.0)]
public double InpUnlockedBufferRiskPct { get; set; }
[Parameter("Hard Cap on Scaled Risk (%)", Group = "3. Risk & Profit-Lock Scaling", DefaultValue = 1.25, MinValue = 0.25, MaxValue = 5.0, Step = 0.25)]
public double InpMaxScaledRiskPct { get; set; }
[Parameter("Daily Max Drawdown Breaker (%)", Group = "3. Risk & Profit-Lock Scaling", DefaultValue = 1.50, MinValue = 0.50, Step = 0.25)]
public double InpMaxDailyDrawdownPct { get; set; }
[Parameter("Max Consecutive Losses / Day", Group = "3. Risk & Profit-Lock Scaling", DefaultValue = 3, MinValue = 1)]
public int InpMaxDailyConsecLosses { get; set; }
#endregion
#region 4. Raw ECN Spread & Session Windows (UTC)
[Parameter("Max Allowed Live Spread ($ USD)", Group = "4. Spread & Session Windows (UTC)", DefaultValue = 0.25, MinValue = 0.05, Step = 0.05)]
public double InpMaxSpreadUSD { get; set; }
[Parameter("Asian Start Hour (UTC)", Group = "4. Spread & Session Windows (UTC)", DefaultValue = 0, MinValue = 0, MaxValue = 23)]
public int InpAsianStartHour { get; set; }
[Parameter("Asian End Hour (UTC)", Group = "4. Spread & Session Windows (UTC)", DefaultValue = 7, MinValue = 1, MaxValue = 23)]
public int InpAsianEndHour { get; set; }
[Parameter("Trade London Open Window", Group = "4. Spread & Session Windows (UTC)", DefaultValue = true)]
public bool InpTradeLondonSession { get; set; }
[Parameter("London Start Hour (UTC)", Group = "4. Spread & Session Windows (UTC)", DefaultValue = 7, MinValue = 0, MaxValue = 23)]
public int InpLondonStartHour { get; set; }
[Parameter("London End Hour (UTC)", Group = "4. Spread & Session Windows (UTC)", DefaultValue = 10, MinValue = 1, MaxValue = 23)]
public int InpLondonEndHour { get; set; }
[Parameter("Trade London-NY Overlap", Group = "4. Spread & Session Windows (UTC)", DefaultValue = true)]
public bool InpTradeNYOverlap { get; set; }
[Parameter("NY Overlap Start Hour (UTC)", Group = "4. Spread & Session Windows (UTC)", DefaultValue = 12, MinValue = 0, MaxValue = 23)]
public int InpNYStartHour { get; set; }
[Parameter("NY Overlap End Hour (UTC)", Group = "4. Spread & Session Windows (UTC)", DefaultValue = 16, MinValue = 1, MaxValue = 23)]
public int InpNYEndHour { get; set; }
[Parameter("Bot Label", Group = "4. Spread & Session Windows (UTC)", DefaultValue = "XAU_Scalper_2026")]
public string InpBotLabel { get; set; }
#endregion
#region Private Fields & Multi-Timeframe Series
private Bars _m5Bars;
private Bars _m15Bars;
private Bars _h4Bars;
private Bars _d1Bars;
private ExponentialMovingAverage _emaFastM15;
private ExponentialMovingAverage _emaSlowM15;
private AverageTrueRange _atrM5;
// Risk & Profit-Lock State
private int _currentDayOfYear = -1;
private double _dayStartEquity;
private double _baseReferenceEq;
private double _lockedEquityFloor;
private int _dailyConsecLosses;
private bool _dailyHalted;
// Liquidity Pool Cache
private double _pdh;
private double _pdl;
private double _asianHigh;
private double _asianLow;
private double _h4SwingHigh;
private double _h4SwingLow;
// Track 1R price distance and Partial-Close execution per Position ID
private readonly HashSet<int> _partialClosedIds = new HashSet<int>();
private readonly Dictionary<int, double> _initialRiskDistByPosId = new Dictionary<int, double>();
#endregion
protected override void OnStart()
{
// Load Multi-Timeframe Bars
_m5Bars = MarketData.GetBars(TimeFrame.Minute5);
_m15Bars = MarketData.GetBars(TimeFrame.Minute15);
_h4Bars = MarketData.GetBars(TimeFrame.Hour4);
_d1Bars = MarketData.GetBars(TimeFrame.Daily);
// Initialize Indicators
_emaFastM15 = Indicators.ExponentialMovingAverage(_m15Bars.ClosePrices, InpEmaFast);
_emaSlowM15 = Indicators.ExponentialMovingAverage(_m15Bars.ClosePrices, InpEmaSlow);
_atrM5 = Indicators.AverageTrueRange(_m5Bars, InpAtrPeriod, MovingAverageType.Exponential);
// Initialize Equity & Profit-Lock State
double eq = Account.Equity;
_dayStartEquity = eq;
_baseReferenceEq = eq;
_lockedEquityFloor = eq * (1.0 - InpMaxDailyDrawdownPct / 100.0);
_currentDayOfYear = Server.TimeInUtc.DayOfYear;
Positions.Closed += OnPositionClosed;
Print("XAUUSD_LiquidityScalper initialized on {0}. PipSize: {1}, TickSize: {2}, LotSize: {3}",
SymbolName, Symbol.PipSize, Symbol.TickSize, Symbol.LotSize);
}
protected override void OnStop()
{
Positions.Closed -= OnPositionClosed;
ClearChartDrawings();
}
protected override void OnTick()
{
// 1. Daily Reset & Circuit Breaker Audit
CheckDailyResetAndDrawdown();
// 2. Manage Open Positions on Every Tick
var activePositions = Positions.FindAll(InpBotLabel, SymbolName);
if (activePositions.Length > 0)
{
ManageOpenPositions(activePositions);
}
// 3. Update HUD Display on Every Tick for Live Spread & Equity Feedback
UpdateChartHUD();
}
protected override void OnBar()
{
if (_dailyHalted)
return;
// One active position at a time on XAU/USD
if (Positions.FindAll(InpBotLabel, SymbolName).Length > 0)
return;
// 1. Verify Active Session Window (UTC)
if (!IsWithinActiveSession())
return;
// 2. Verify Live Spread Filter ($ USD)
double liveSpreadUSD = Symbol.Ask - Symbol.Bid;
if (liveSpreadUSD > InpMaxSpreadUSD)
return;
// 3. Refresh H4/D1 & Asian Session Liquidity Pools & Draw Chart Lines
UpdateLiquidityPools();
DrawLiquidityLines();
double currentAtr = _atrM5.Result.Last(1);
if (double.IsNaN(currentAtr) || currentAtr <= 0.0)
return;
// 4. Evaluate Setup 1: Liquidity Sweep
if (InpEnableSweepSetup)
{
if (CheckBullishSweep(currentAtr, out double buySweepSl))
{
ExecuteScalpTrade(TradeType.Buy, buySweepSl, "XAU_Sweep_Buy");
return;
}
if (CheckBearishSweep(currentAtr, out double sellSweepSl))
{
ExecuteScalpTrade(TradeType.Sell, sellSweepSl, "XAU_Sweep_Sell");
return;
}
}
// 5. Evaluate Setup 2: M15 EMA 20/50 Trend Pullback
if (InpEnablePullbackSetup)
{
double m15Fast = _emaFastM15.Result.Last(1);
double m15Slow = _emaSlowM15.Result.Last(1);
if (CheckBullishPullback(m15Fast, m15Slow, currentAtr, out double buyPbSl))
{
ExecuteScalpTrade(TradeType.Buy, buyPbSl, "XAU_Pullback_Buy");
return;
}
if (CheckBearishPullback(m15Fast, m15Slow, currentAtr, out double sellPbSl))
{
ExecuteScalpTrade(TradeType.Sell, sellPbSl, "XAU_Pullback_Sell");
return;
}
}
}
#region Chart Drawings (Liquidity Lines & Top-Right HUD)
private void DrawLiquidityLines()
{
if (_pdh > 0) Chart.DrawHorizontalLine("Line_PDH", _pdh, Color.Gold, 1, LineStyle.Lines);
if (_pdl > 0) Chart.DrawHorizontalLine("Line_PDL", _pdl, Color.OrangeRed, 1, LineStyle.Lines);
if (_asianHigh > 0) Chart.DrawHorizontalLine("Line_AsianHigh", _asianHigh, Color.DodgerBlue, 1, LineStyle.Dots);
if (_asianLow > 0) Chart.DrawHorizontalLine("Line_AsianLow", _asianLow, Color.DodgerBlue, 1, LineStyle.Dots);
if (_h4SwingHigh > 0) Chart.DrawHorizontalLine("Line_H4High", _h4SwingHigh, Color.MediumPurple, 2, LineStyle.Solid);
if (_h4SwingLow > 0) Chart.DrawHorizontalLine("Line_H4Low", _h4SwingLow, Color.MediumPurple, 2, LineStyle.Solid);
}
private void ClearChartDrawings()
{
Chart.RemoveObject("Line_PDH");
Chart.RemoveObject("Line_PDL");
Chart.RemoveObject("Line_AsianHigh");
Chart.RemoveObject("Line_AsianLow");
Chart.RemoveObject("Line_H4High");
Chart.RemoveObject("Line_H4Low");
Chart.RemoveObject("XAU_Scalper_HUD");
}
private void UpdateChartHUD()
{
double liveSpreadUSD = Symbol.Ask - Symbol.Bid;
double eq = Account.Equity;
double gainPct = ((eq - _baseReferenceEq) / _baseReferenceEq) * 100.0;
// Calculate indicative lot size for current default risk at current ATR
double sampleAtr = _atrM5.Result.Last(1);
double sampleLots = sampleAtr > 0 ? CalculateVolumeInUnits(sampleAtr * InpAtrStopMultiplier) / Symbol.LotSize : 0.0;
string sessionStatus = "OUT OF SESSION";
int hour = Server.TimeInUtc.Hour;
bool inLondon = InpTradeLondonSession && (hour >= InpLondonStartHour && hour < InpLondonEndHour);
bool inNY = InpTradeNYOverlap && (hour >= InpNYStartHour && hour < InpNYEndHour);
if (inLondon) sessionStatus = "LONDON OPEN";
if (inNY) sessionStatus = "NY OVERLAP";
string hudText = string.Format(
"=== XAU/USD SCALPER HUD ===\n" +
"Session: {0} (UTC {1:D2}:00)\n" +
"Live Spread: ${1:F2} (Max: ${2:F2})\n" +
"Equity: ${3:F2} | Gain: {4:+0.00;-0.00}%\n" +
"Locked Floor: ${5:F2}\n" +
"Consec. Losses: {6}/{7} | Halted: {8}\n" +
"Indicative Lot Size: {9:F2} Lots\n" +
"Active Positions: {10}",
sessionStatus,
hour,
liveSpreadUSD,
InpMaxSpreadUSD,
eq,
gainPct,
_lockedEquityFloor,
_dailyConsecLosses,
InpMaxDailyConsecLosses,
_dailyHalted,
sampleLots,
Positions.FindAll(InpBotLabel, SymbolName).Length
);
Chart.DrawStaticText("XAU_Scalper_HUD", hudText, VerticalAlignment.Top, HorizontalAlignment.Right, Color.White);
}
#endregion
#region Position Closed Event & Profit-Lock Scaling State
private void OnPositionClosed(PositionClosedEventArgs args)
{
Position pos = args.Position;
if (pos.Label != InpBotLabel || pos.SymbolName != SymbolName)
return;
if (Positions.FindAll(InpBotLabel, SymbolName).Length == 0)
{
_partialClosedIds.Remove(pos.Id);
_initialRiskDistByPosId.Remove(pos.Id);
if (pos.NetProfit < -1.0)
_dailyConsecLosses++;
else if (pos.NetProfit > 1.0)
_dailyConsecLosses = 0;
}
double eq = Account.Equity;
double gainPct = ((eq - _baseReferenceEq) / _baseReferenceEq) * 100.0;
if (InpUseProfitLockScaling && gainPct >= InpProfitLockTriggerPct)
{
double gainedUSD = eq - _baseReferenceEq;
_lockedEquityFloor = _baseReferenceEq + (gainedUSD * 0.50);
_baseReferenceEq = eq;
Print("Profit-Lock Triggered! New Locked Equity Floor: ${0:F2} | New Reference: ${1:F2}",
_lockedEquityFloor, _baseReferenceEq);
}
}
private void CheckDailyResetAndDrawdown()
{
DateTime utcNow = Server.TimeInUtc;
double eq = Account.Equity;
if (utcNow.DayOfYear != _currentDayOfYear)
{
_currentDayOfYear = utcNow.DayOfYear;
_dayStartEquity = eq;
_dailyConsecLosses = 0;
_dailyHalted = false;
}
if (_dailyHalted)
return;
double dailyDDPct = ((_dayStartEquity - eq) / _dayStartEquity) * 100.0;
if (dailyDDPct >= InpMaxDailyDrawdownPct)
{
_dailyHalted = true;
Print("Circuit Breaker: Max Daily Drawdown ({0:F2}%) reached. Trading halted for today.", dailyDDPct);
}
else if (_dailyConsecLosses >= InpMaxDailyConsecLosses)
{
_dailyHalted = true;
Print("Circuit Breaker: {0} Consecutive Losses reached. Trading halted for today.", _dailyConsecLosses);
}
}
#endregion
#region Liquidity Pool Mapping
private void UpdateLiquidityPools()
{
if (_d1Bars.Count >= 2)
{
_pdh = _d1Bars.HighPrices.Last(1);
_pdl = _d1Bars.LowPrices.Last(1);
}
_h4SwingHigh = 0.0;
_h4SwingLow = double.MaxValue;
int h4Count = _h4Bars.Count;
for (int i = 1; i <= InpH4SwingLookback && (h4Count - 1 - i) >= 0; i++)
{
double h = _h4Bars.HighPrices.Last(i);
double l = _h4Bars.LowPrices.Last(i);
if (h > _h4SwingHigh) _h4SwingHigh = h;
if (l < _h4SwingLow) _h4SwingLow = l;
}
if (_h4SwingLow == double.MaxValue) _h4SwingLow = 0.0;
DateTime utcNow = Server.TimeInUtc;
DateTime asianStart = new DateTime(utcNow.Year, utcNow.Month, utcNow.Day, InpAsianStartHour, 0, 0, DateTimeKind.Utc);
DateTime asianEnd = new DateTime(utcNow.Year, utcNow.Month, utcNow.Day, InpAsianEndHour, 0, 0, DateTimeKind.Utc);
_asianHigh = 0.0;
_asianLow = double.MaxValue;
int m15Count = _m15Bars.Count;
for (int i = 1; i <= 100 && (m15Count - 1 - i) >= 0; i++)
{
int idx = m15Count - 1 - i;
DateTime barTime = _m15Bars.OpenTimes[idx];
if (barTime < asianStart)
break;
if (barTime >= asianStart && barTime < asianEnd)
{
double h = _m15Bars.HighPrices[idx];
double l = _m15Bars.LowPrices[idx];
if (h > _asianHigh) _asianHigh = h;
if (l < _asianLow) _asianLow = l;
}
}
if (_asianLow == double.MaxValue) _asianLow = 0.0;
}
#endregion
#region Entry Setup Detection
private bool CheckBullishSweep(double atr, out double outSlPrice)
{
outSlPrice = 0.0;
double open1 = Bars.OpenPrices.Last(1);
double close1 = Bars.ClosePrices.Last(1);
double high1 = Bars.HighPrices.Last(1);
double low1 = Bars.LowPrices.Last(1);
double low2 = Bars.LowPrices.Last(2);
double high2 = Bars.HighPrices.Last(2);
double range1 = high1 - low1;
double body1 = close1 - open1;
if (range1 <= 0.0 || body1 <= 0.45 * range1)
return false;
double sweepLow = Math.Min(low1, low2);
double[] levels = { _pdl, _asianLow, _h4SwingLow };
foreach (double lvl in levels)
{
if (lvl <= 0.0) continue;
double overshoot = lvl - sweepLow;
if (overshoot > 0.0 && overshoot <= InpMaxSweepOvershootUSD &&
close1 > lvl && (close1 > high2 || body1 > 0.60 * range1))
{
outSlPrice = sweepLow - (atr * InpAtrStopMultiplier);
return true;
}
}
return false;
}
private bool CheckBearishSweep(double atr, out double outSlPrice)
{
outSlPrice = 0.0;
double open1 = Bars.OpenPrices.Last(1);
double close1 = Bars.ClosePrices.Last(1);
double high1 = Bars.HighPrices.Last(1);
double low1 = Bars.LowPrices.Last(1);
double high2 = Bars.HighPrices.Last(2);
double low2 = Bars.LowPrices.Last(2);
double range1 = high1 - low1;
double body1 = open1 - close1;
if (range1 <= 0.0 || body1 <= 0.45 * range1)
return false;
double sweepHigh = Math.Max(high1, high2);
double[] levels = { _pdh, _asianHigh, _h4SwingHigh };
foreach (double lvl in levels)
{
if (lvl <= 0.0) continue;
double overshoot = sweepHigh - lvl;
if (overshoot > 0.0 && overshoot <= InpMaxSweepOvershootUSD &&
close1 < lvl && (close1 < low2 || body1 > 0.60 * range1))
{
outSlPrice = sweepHigh + (atr * InpAtrStopMultiplier);
return true;
}
}
return false;
}
private bool CheckBullishPullback(double emaFast, double emaSlow, double atr, out double outSlPrice)
{
outSlPrice = 0.0;
if (emaFast <= emaSlow)
return false;
double open1 = Bars.OpenPrices.Last(1);
double close1 = Bars.ClosePrices.Last(1);
double low1 = Bars.LowPrices.Last(1);
double high2 = Bars.HighPrices.Last(2);
double low2 = Bars.LowPrices.Last(2);
double pullbackLow = Math.Min(low1, low2);
bool touchedValueZone = (pullbackLow <= emaFast + InpTouchToleranceUSD) &&
(pullbackLow >= emaSlow - InpTouchToleranceUSD);
bool bullishBoS = (close1 > open1) && (close1 > emaFast) && (close1 > high2);
if (touchedValueZone && bullishBoS)
{
outSlPrice = pullbackLow - (atr * InpAtrStopMultiplier);
return true;
}
return false;
}
private bool CheckBearishPullback(double emaFast, double emaSlow, double atr, out double outSlPrice)
{
outSlPrice = 0.0;
if (emaFast >= emaSlow)
return false;
double open1 = Bars.OpenPrices.Last(1);
double close1 = Bars.ClosePrices.Last(1);
double high1 = Bars.HighPrices.Last(1);
double high2 = Bars.HighPrices.Last(2);
double low2 = Bars.LowPrices.Last(2);
double pullbackHigh = Math.Max(high1, high2);
bool touchedValueZone = (pullbackHigh >= emaFast - InpTouchToleranceUSD) &&
(pullbackHigh <= emaSlow + InpTouchToleranceUSD);
bool bearishBoS = (close1 < open1) && (close1 < emaFast) && (close1 < low2);
if (touchedValueZone && bearishBoS)
{
outSlPrice = pullbackHigh + (atr * InpAtrStopMultiplier);
return true;
}
return false;
}
#endregion
#region Execution & Volume Sizing
private void ExecuteScalpTrade(TradeType tradeType, double rawSlPrice, string comment)
{
double entryPrice = (tradeType == TradeType.Buy) ? Symbol.Ask : Symbol.Bid;
double slDistUSD = Math.Abs(entryPrice - rawSlPrice);
if (slDistUSD < InpMinStopUSD)
slDistUSD = InpMinStopUSD;
else if (slDistUSD > InpMaxStopUSD)
return;
double tpDistUSD = slDistUSD * InpFinalTargetRR;
double slPips = Math.Round(slDistUSD / Symbol.PipSize, 1);
double tpPips = Math.Round(tpDistUSD / Symbol.PipSize, 1);
double volumeInUnits = CalculateVolumeInUnits(slDistUSD);
if (volumeInUnits < Symbol.VolumeInUnitsMin)
return;
TradeResult result = ExecuteMarketOrder(tradeType, SymbolName, volumeInUnits, InpBotLabel, slPips, tpPips, comment);
if (result.IsSuccessful && result.Position != null)
{
_initialRiskDistByPosId[result.Position.Id] = slDistUSD;
}
}
private double CalculateVolumeInUnits(double slDistUSD)
{
double eq = Account.Equity;
double riskUSD = eq * (InpBaseRiskPct / 100.0);
if (InpUseProfitLockScaling && eq > _lockedEquityFloor)
{
double unlockedProfit = Math.Max(0.0, eq - _baseReferenceEq);
if (unlockedProfit > 0.0)
{
double bonusRiskUSD = unlockedProfit * (InpUnlockedBufferRiskPct / 100.0);
double maxRiskUSD = eq * (InpMaxScaledRiskPct / 100.0);
riskUSD = Math.Min(riskUSD + bonusRiskUSD, maxRiskUSD);
}
}
if (Symbol.TickSize <= 0.0 || Symbol.TickValue <= 0.0 || Symbol.LotSize <= 0.0)
return 0.0;
double priceLossPerUnit = (slDistUSD / Symbol.TickSize) * Symbol.TickValue;
double priceLossPerLot = priceLossPerUnit * Symbol.LotSize;
double totalLossPerLot = priceLossPerLot + InpCommissionPerLotRT;
if (totalLossPerLot <= 0.0)
return 0.0;
double rawLots = riskUSD / totalLossPerLot;
double rawUnits = rawLots * Symbol.LotSize;
double normUnits = Symbol.NormalizeVolumeInUnits(rawUnits, RoundingMode.Down);
if (normUnits < Symbol.VolumeInUnitsMin)
return 0.0;
if (normUnits > Symbol.VolumeInUnitsMax)
normUnits = Symbol.VolumeInUnitsMax;
return normUnits;
}
#endregion
#region Position Management
private void ManageOpenPositions(Position[] positions)
{
foreach (Position pos in positions)
{
double openPrice = pos.EntryPrice;
double currentPrice = (pos.TradeType == TradeType.Buy) ? Symbol.Bid : Symbol.Ask;
double profitDist = (pos.TradeType == TradeType.Buy)
? (currentPrice - openPrice)
: (openPrice - currentPrice);
if (!_initialRiskDistByPosId.TryGetValue(pos.Id, out double initialRiskDist) || initialRiskDist <= 0.0)
{
initialRiskDist = pos.TakeProfit.HasValue
? Math.Abs(pos.TakeProfit.Value - openPrice) / InpFinalTargetRR
: InpMinStopUSD;
_initialRiskDistByPosId[pos.Id] = initialRiskDist;
}
double currentR = profitDist / initialRiskDist;
if (InpTimeStopMinutes > 0)
{
double elapsedMins = (Server.TimeInUtc - pos.EntryTime).TotalMinutes;
if (elapsedMins >= InpTimeStopMinutes && currentR < 0.5)
{
ClosePosition(pos);
Print("Time Stop executed on Position #{0} after {1:F0} mins (R = {2:F2})",
pos.Id, elapsedMins, currentR);
continue;
}
}
if (currentR >= InpPartialCloseRR && !_partialClosedIds.Contains(pos.Id))
{
double rawCloseUnits = pos.VolumeInUnits * InpPartialCloseRatio;
double normCloseUnits = Symbol.NormalizeVolumeInUnits(rawCloseUnits, RoundingMode.Down);
if (normCloseUnits >= Symbol.VolumeInUnitsMin &&
(pos.VolumeInUnits - normCloseUnits) >= Symbol.VolumeInUnitsMin)
{
ClosePosition(pos, normCloseUnits);
}
double commOffsetUSD = 0.15;
double newSlPrice = (pos.TradeType == TradeType.Buy)
? Math.Round(openPrice + commOffsetUSD, Symbol.Digits)
: Math.Round(openPrice - commOffsetUSD, Symbol.Digits);
bool shouldModifySL = (pos.TradeType == TradeType.Buy)
? (!pos.StopLoss.HasValue || newSlPrice > pos.StopLoss.Value)
: (!pos.StopLoss.HasValue || newSlPrice < pos.StopLoss.Value);
if (shouldModifySL)
{
ModifyPosition(pos, newSlPrice, pos.TakeProfit);
}
_partialClosedIds.Add(pos.Id);
}
if (currentR >= InpTrailStartRR && _m15Bars.Count >= 2)
{
double trailBuffer = 0.20;
if (pos.TradeType == TradeType.Buy)
{
double m15Low = Math.Round(_m15Bars.LowPrices.Last(1) - trailBuffer, Symbol.Digits);
if ((!pos.StopLoss.HasValue || m15Low > pos.StopLoss.Value) && m15Low < currentPrice)
{
ModifyPosition(pos, m15Low, pos.TakeProfit);
}
}
else
{
double m15High = Math.Round(_m15Bars.HighPrices.Last(1) + trailBuffer, Symbol.Digits);
if ((!pos.StopLoss.HasValue || m15High < pos.StopLoss.Value) && m15High > currentPrice)
{
ModifyPosition(pos, m15High, pos.StopLoss); // Bugfix: preserve TP
}
}
}
}
}
private bool IsWithinActiveSession()
{
int hour = Server.TimeInUtc.Hour;
bool inLondon = InpTradeLondonSession && (hour >= InpLondonStartHour && hour < InpLondonEndHour);
bool inNY = InpTradeNYOverlap && (hour >= InpNYStartHour && hour < InpNYEndHour);
return inLondon || inNY;
}
#endregion
}
}