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 StructuralReclaimPro : Robot
{
// =========================================================================
// 1. PARAMETERS
// =========================================================================
[Parameter("Risk per Trade (%)", DefaultValue = 1.0, MinValue = 0.1, Step = 0.1, Group = "1. Risk Management")]
public double RiskPct { get; set; }
[Parameter("Risk/Reward Ratio", DefaultValue = 2.0, MinValue = 0.5, Step = 0.5, Group = "1. Risk Management")]
public double RrRatio { get; set; }
[Parameter("Pivot Left (Impulse Leg)", DefaultValue = 5, MinValue = 1, Group = "2. Microstructure")]
public int LeftBars { get; set; }
[Parameter("Pivot Right (Confirmation)", DefaultValue = 2, MinValue = 1, Group = "2. Microstructure")]
public int RightBars { get; set; }
[Parameter("HTF Resolution", DefaultValue = "Hour4", Group = "3. HTF Context")]
public TimeFrame HtfResolution { get; set; }
[Parameter("Start Hour (UTC)", DefaultValue = 8, Group = "4. Session")]
public int SessionStart { get; set; }
[Parameter("End Hour (UTC)", DefaultValue = 17, Group = "4. Session")]
public int SessionEnd { get; set; }
// =========================================================================
// STATE & INDICATORS
// =========================================================================
private Bars _htfBars;
private ExponentialMovingAverage _htfEma;
private SetupState _longSetup = new SetupState();
private SetupState _shortSetup = new SetupState();
private double _lastPl = double.NaN;
private double _prevPl = double.NaN;
private double _lastPh = double.NaN;
private double _prevPh = double.NaN;
private const string TriggerLineName = "ReclaimTriggerLine";
private const string SlLineName = "ReclaimSlLine";
// =========================================================================
// INITIALIZATION
// =========================================================================
protected override void OnStart()
{
// Initialize HTF Data and EMA
_htfBars = MarketData.GetBars(HtfResolution);
_htfEma = Indicators.ExponentialMovingAverage(_htfBars.ClosePrices, 20);
}
// =========================================================================
// CORE LOGIC (Evaluated on every closed M15 bar)
// =========================================================================
protected override void OnBar()
{
UpdateStructureAndState();
CheckExecutionOrInvalidation();
UpdateVisuals();
}
private void UpdateStructureAndState()
{
int checkIndex = Bars.Count - 1 - RightBars;
if (checkIndex - LeftBars < 0) return;
bool isPivotLow = true;
bool isPivotHigh = true;
double highestInRight = double.MinValue;
double lowestInRight = double.MaxValue;
// 1. Determine if the checkIndex is a valid pivot
for (int i = checkIndex - LeftBars; i <= checkIndex + RightBars; i++)
{
if (i != checkIndex)
{
if (Bars.LowPrices[i] <= Bars.LowPrices[checkIndex]) isPivotLow = false;
if (Bars.HighPrices[i] >= Bars.HighPrices[checkIndex]) isPivotHigh = false;
}
// Track the micro-extremes during the RightBars confirmation period
if (i > checkIndex)
{
if (Bars.HighPrices[i] > highestInRight) highestInRight = Bars.HighPrices[i];
if (Bars.LowPrices[i] < lowestInRight) lowestInRight = Bars.LowPrices[i];
}
}
// 2. Get HTF Bias (synced to current time to avoid lookahead bias)
int htfIndex = _htfBars.OpenTimes.GetIndexByTime(Bars.OpenTimes.LastValue);
double currentHtfEma = _htfEma.Result[htfIndex];
bool isBullishHtf = Bars.ClosePrices.Last(1) > currentHtfEma;
bool isBearishHtf = Bars.ClosePrices.Last(1) < currentHtfEma;
// 3. Update State Machines
if (isPivotLow)
{
double currentPl = Bars.LowPrices[checkIndex];
_prevPl = double.IsNaN(_lastPl) ? currentPl : _lastPl;
_lastPl = currentPl;
if (_lastPl > _prevPl && isBullishHtf)
{
_longSetup.Activate(currentPl, highestInRight, Bars.OpenTimes[checkIndex]);
_shortSetup.Deactivate();
}
}
if (isPivotHigh)
{
double currentPh = Bars.HighPrices[checkIndex];
_prevPh = double.IsNaN(_lastPh) ? currentPh : _lastPh;
_lastPh = currentPh;
if (_lastPh < _prevPh && isBearishHtf)
{
_shortSetup.Activate(currentPh, lowestInRight, Bars.OpenTimes[checkIndex]);
_longSetup.Deactivate();
}
}
}
private void CheckExecutionOrInvalidation()
{
double close = Bars.ClosePrices.Last(1);
double high = Bars.HighPrices.Last(1);
double low = Bars.LowPrices.Last(1);
bool inSession = Server.Time.Hour >= SessionStart && Server.Time.Hour < SessionEnd;
// KILL SWITCH: Setup swept before execution
if (_longSetup.IsActive && low <= _longSetup.InvalidationLevel)
{
_longSetup.Deactivate();
Chart.DrawText("VoidLong", "X", Bars.OpenTimes.Last(1), low, Color.Gray);
}
if (_shortSetup.IsActive && high >= _shortSetup.InvalidationLevel)
{
_shortSetup.Deactivate();
Chart.DrawText("VoidShort", "X", Bars.OpenTimes.Last(1), high, Color.Gray);
}
// EXECUTION
if (Positions.Count == 0 && inSession)
{
if (_longSetup.IsActive && close > _longSetup.TriggerLevel)
{
ExecuteTrade(TradeType.Buy, _longSetup.InvalidationLevel);
_longSetup.Deactivate();
}
else if (_shortSetup.IsActive && close < _shortSetup.TriggerLevel)
{
ExecuteTrade(TradeType.Sell, _shortSetup.InvalidationLevel);
_shortSetup.Deactivate();
}
}
}
private void ExecuteTrade(TradeType tradeType, double slPrice)
{
double entryPrice = Symbol.Ask;
if (tradeType == TradeType.Sell) entryPrice = Symbol.Bid;
double slPips = Math.Abs(entryPrice - slPrice) / Symbol.PipSize;
if (slPips <= 0) return;
// Fixed Fractional Position Sizing
double riskAmount = Account.Equity * (RiskPct / 100);
double volume = Symbol.NormalizeVolumeInUnits(riskAmount / (slPips * Symbol.PipValue), RoundingMode.Down);
double tpPips = slPips * RrRatio;
ExecuteMarketOrder(tradeType, SymbolName, volume, "ReclaimPA", slPips, tpPips);
}
private void UpdateVisuals()
{
DateTime currentTime = Bars.OpenTimes.LastValue;
if (_longSetup.IsActive)
{
Chart.DrawTrendLine(TriggerLineName, _longSetup.StartTime, _longSetup.TriggerLevel, currentTime, _longSetup.TriggerLevel, Color.Teal, 2, LineStyle.Lines);
Chart.DrawTrendLine(SlLineName, _longSetup.StartTime, _longSetup.InvalidationLevel, currentTime, _longSetup.InvalidationLevel, Color.Gray, 1, LineStyle.Solid);
}
else if (_shortSetup.IsActive)
{
Chart.DrawTrendLine(TriggerLineName, _shortSetup.StartTime, _shortSetup.TriggerLevel, currentTime, _shortSetup.TriggerLevel, Color.Red, 2, LineStyle.Lines);
Chart.DrawTrendLine(SlLineName, _shortSetup.StartTime, _shortSetup.InvalidationLevel, currentTime, _shortSetup.InvalidationLevel, Color.Gray, 1, LineStyle.Solid);
}
else
{
Chart.RemoveObject(TriggerLineName);
Chart.RemoveObject(SlLineName);
}
}
// =========================================================================
// HELPER CLASS
// =========================================================================
private class SetupState
{
public bool IsActive { get; private set; }
public double InvalidationLevel { get; private set; }
public double TriggerLevel { get; private set; }
public DateTime StartTime { get; private set; }
public void Activate(double invalidation, double trigger, DateTime start)
{
IsActive = true;
InvalidationLevel = invalidation;
TriggerLevel = trigger;
StartTime = start;
}
public void Deactivate()
{
IsActive = false;
}
}
}
}