Code: Select all
using System;
using cAlgo.API;
using cAlgo.API.Indicators;
using cAlgo.API.Internals;
namespace cAlgo
{
// 1. BITWISE ENUMERATION FOR STATE HIERARCHY
[Flags]
public enum DangerState
{
None = 0,
SpreadSpike = 1 << 0, // Lowest priority visual
VolatilityDrop = 1 << 1,
AugustDrain = 1 << 2,
USHoliday = 1 << 3,
GoldenWeek = 1 << 4,
ChristmasNewYear = 1 << 5 // Highest priority visual
}
[Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
public class HolidayDangerZonesPro : Indicator
{
// =============================================================================
// INPUTS
// =============================================================================
[Parameter("Christmas/NY", Group = "Calendar", DefaultValue = true)]
public bool ShowXmasNY { get; set; }
[Parameter("Golden Week", Group = "Calendar", DefaultValue = true)]
public bool ShowGolden { get; set; }
[Parameter("Thanksgiving", Group = "Calendar", DefaultValue = true)]
public bool ShowUSThanks { get; set; }
[Parameter("August Drain", Group = "Calendar", DefaultValue = false)]
public bool ShowAugust { get; set; }
[Parameter("Enable Vol Filter", Group = "Microstructure", DefaultValue = true)]
public bool UseVolFilter { get; set; }
[Parameter("ATR Length", Group = "Microstructure", DefaultValue = 14)]
public int AtrLen { get; set; }
[Parameter("Baseline SMA Length", Group = "Microstructure", DefaultValue = 100)]
public int BaselineLen { get; set; }
[Parameter("Vol Drop Threshold (%)", Group = "Microstructure", DefaultValue = 70.0)]
public double DropThresholdPct { get; set; }
[Parameter("Enable Spread Proxy", Group = "Microstructure", DefaultValue = true)]
public bool UseSpreadFilter { get; set; }
[Parameter("Max Gap (% of ATR)", Group = "Microstructure", DefaultValue = 15.0)]
public double GapAtrPct { get; set; }
[Parameter("Max Body Size (% of Total)", Group = "Microstructure", DefaultValue = 25.0)]
public double BodyMaxPct { get; set; }
[Parameter("Alpha Transparency", Group = "UI Optimization", DefaultValue = 40, MinValue = 10, MaxValue = 255)]
public int UiAlpha { get; set; }
[Output("Danger Marker", LineColor = "Red", PlotType = PlotType.Points, Thickness = 4)]
public IndicatorDataSeries DangerMarker { get; set; }
// =============================================================================
// CACHED OPTIMIZATIONS
// =============================================================================
private AverageTrueRange _atr;
private SimpleMovingAverage _baselineAtr;
private double _volDropMultiplier;
private double _gapMultiplier;
private double _bodyMultiplier;
private Color _colXmas, _colGolden, _colUS, _colAug, _colVol, _colSpread;
protected override void Initialize()
{
// Indicator Piping
_atr = Indicators.AverageTrueRange(AtrLen, MovingAverageType.Simple);
_baselineAtr = Indicators.SimpleMovingAverage(_atr.Result, BaselineLen);
// Pre-compute multipliers to avoid division in the tick loop
_volDropMultiplier = DropThresholdPct * 0.01;
_gapMultiplier = GapAtrPct * 0.01;
_bodyMultiplier = BodyMaxPct * 0.01;
// Cache colors & alpha channels once (eliminates GC allocs from reflection)
_colXmas = Color.FromArgb(UiAlpha, Color.Red);
_colGolden = Color.FromArgb(UiAlpha, Color.Orange);
_colUS = Color.FromArgb(UiAlpha, Color.Blue);
_colAug = Color.FromArgb(UiAlpha, Color.Purple);
_colVol = Color.FromArgb(UiAlpha, Color.Gray);
_colSpread = Color.FromArgb(UiAlpha, Color.Yellow);
}
public override void Calculate(int index)
{
if (index < BaselineLen) return;
// 2. STATE RESOLUTION
DangerState currentState = EvaluateCalendar(index) | EvaluateMicrostructure(index);
string objName = $"DZ_{index}";
if (currentState != DangerState.None)
{
DangerMarker[index] = Bars.LowPrices[index] - (_atr.Result[index] * 0.5);
Chart.DrawVerticalLine(objName, Bars.OpenTimes[index], ResolveHighestPriorityColor(currentState), 5, LineStyle.Solid);
}
else
{
DangerMarker[index] = double.NaN;
// Only attempt removal if we assume an object might exist
// (cTrader ignores invalid keys gracefully, but skipping this saves cycles)
Chart.RemoveObject(objName);
}
}
// =============================================================================
// ISOLATED EVALUATION METHODS
// =============================================================================
private DangerState EvaluateCalendar(int index)
{
DateTime t = Bars.OpenTimes[index];
DangerState state = DangerState.None;
if (ShowXmasNY && ((t.Month == 12 && t.Day >= 24) || (t.Month == 1 && t.Day <= 2)))
state |= DangerState.ChristmasNewYear;
if (ShowGolden && ((t.Month == 4 && t.Day >= 29) || (t.Month == 5 && t.Day <= 5)))
state |= DangerState.GoldenWeek;
if (ShowUSThanks && t.Month == 11)
{
bool isThanksgiving = t.DayOfWeek == DayOfWeek.Thursday && t.Day >= 22 && t.Day <= 28;
bool isBlackFriday = t.DayOfWeek == DayOfWeek.Friday && t.Day >= 23 && t.Day <= 29;
if (isThanksgiving || isBlackFriday)
state |= DangerState.USHoliday;
}
if (ShowAugust && t.Month == 8)
state |= DangerState.AugustDrain;
return state;
}
private DangerState EvaluateMicrostructure(int index)
{
DangerState state = DangerState.None;
double currentAtr = _atr.Result[index];
if (UseVolFilter && currentAtr < (_baselineAtr.Result[index] * _volDropMultiplier))
state |= DangerState.VolatilityDrop;
if (UseSpreadFilter)
{
double prevClose = index > 0 ? Bars.ClosePrices[index - 1] : Bars.OpenPrices[index];
double gapSize = Math.Abs(Bars.OpenPrices[index] - prevClose);
double candleRange = Bars.HighPrices[index] - Bars.LowPrices[index];
double candleBody = Math.Abs(Bars.ClosePrices[index] - Bars.OpenPrices[index]);
bool isGap = gapSize > (currentAtr * _gapMultiplier);
bool isWick = (candleRange > (currentAtr * 0.8)) && (candleBody < (candleRange * _bodyMultiplier));
if (isGap || isWick)
state |= DangerState.SpreadSpike;
}
return state;
}
// 3. O(1) HIERARCHICAL RESOLUTION
private Color ResolveHighestPriorityColor(DangerState state)
{
// Evaluates from highest flag priority down to lowest
if (state.HasFlag(DangerState.ChristmasNewYear)) return _colXmas;
if (state.HasFlag(DangerState.GoldenWeek)) return _colGolden;
if (state.HasFlag(DangerState.USHoliday)) return _colUS;
if (state.HasFlag(DangerState.AugustDrain)) return _colAug;
if (state.HasFlag(DangerState.VolatilityDrop)) return _colVol;
return _colSpread; // SpreadSpike is the only remaining valid state
}
}
}