Code: Select all
using System;
using System.Linq;
using cAlgo.API;
using cAlgo.API.Indicators;
using cAlgo.API.Internals;
namespace cAlgo
{
[Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
public class AUDNZD_AsianMatrix_Pro : Indicator
{
#region Parameters
[Parameter("Session Start (Server Time)", DefaultValue = "22:00", Group = "Session Timing")]
public string SessionStartStr { get; set; }
[Parameter("Session End (Server Time)", DefaultValue = "06:00", Group = "Session Timing")]
public string SessionEndStr { get; set; }
[Parameter("Trade Window End Hour", DefaultValue = 17, Group = "Session Timing")]
public int TradeWindowEndHour { get; set; }
[Parameter("Max Spread (Pips)", DefaultValue = 2.0, Group = "Microstructure Filters")]
public double MaxSpreadPips { get; set; }
[Parameter("Min Range (Pips)", DefaultValue = 15.0, Group = "Microstructure Filters")]
public double MinRangePips { get; set; }
[Parameter("Max Range (Pips)", DefaultValue = 60.0, Group = "Microstructure Filters")]
public double MaxRangePips { get; set; }
[Parameter("Require Vol Spike", DefaultValue = true, Group = "Microstructure Filters")]
public bool RequireVolSpike { get; set; }
[Parameter("Vol Multiplier vs SMA20", DefaultValue = 1.15, Group = "Microstructure Filters")]
public double VolMultiplier { get; set; }
[Parameter("SL Buffer (Pips)", DefaultValue = 2.5, Group = "Microstructure Filters")]
public double SLBufferPips { get; set; }
[Parameter("Box Color", DefaultValue = "#1E152528", Group = "Visuals")]
public string BoxColorStr { get; set; }
[Parameter("Show HUD", DefaultValue = true, Group = "Visuals")]
public bool ShowHud { get; set; }
#endregion
#region Outputs for cBot Consumption
[Output("Short Signal", LineColor = "Crimson", PlotType = PlotType.Points, Thickness = 4)]
public IndicatorDataSeries BufferShortSignal { get; set; }
[Output("Long Signal", LineColor = "MediumSeaGreen", PlotType = PlotType.Points, Thickness = 4)]
public IndicatorDataSeries BufferLongSignal { get; set; }
[Output("Calculated SL", LineColor = "Transparent")]
public IndicatorDataSeries BufferCalculatedSL { get; set; }
[Output("Target TP1", LineColor = "Transparent")]
public IndicatorDataSeries BufferTargetTP1 { get; set; }
[Output("Target TP2", LineColor = "Transparent")]
public IndicatorDataSeries BufferTargetTP2 { get; set; }
#endregion
#region Internal State
private TimeSpan _sessionStart;
private TimeSpan _sessionEnd;
private SimpleMovingAverage _volSma;
private Bars _dailyBars;
private Color _boxColor;
// Session state
private double _sessHigh = double.MinValue;
private double _sessLow = double.MaxValue;
private DateTime _sessStartTime;
private bool _isAccumulating;
// Alert state
private int _lastAlertIndex = -1;
#endregion
protected override void Initialize()
{
if (!TimeSpan.TryParse(SessionStartStr, out _sessionStart) || !TimeSpan.TryParse(SessionEndStr, out _sessionEnd))
{
Print("Invalid Session Time format. Use HH:MM");
}
_boxColor = Color.FromHex(BoxColorStr);
_volSma = Indicators.SimpleMovingAverage(Bars.TickVolumes, 20);
_dailyBars = MarketData.GetBars(TimeFrame.Daily);
}
public override void Calculate(int index)
{
// Initialize empty buffers (cTrader standard for no-signal)
BufferShortSignal[index] = double.NaN;
BufferLongSignal[index] = double.NaN;
BufferCalculatedSL[index] = double.NaN;
BufferTargetTP1[index] = double.NaN;
BufferTargetTP2[index] = double.NaN;
if (index < 20) return;
DateTime barTime = Bars.OpenTimes[index];
bool inSession = IsInAccumulation(barTime);
// Handle Session State Transitions
if (inSession)
{
if (!_isAccumulating)
{
// Session just started
_isAccumulating = true;
_sessStartTime = barTime;
_sessHigh = Bars.HighPrices[index];
_sessLow = Bars.LowPrices[index];
}
else
{
// Update ongoing session
_sessHigh = Math.Max(_sessHigh, Bars.HighPrices[index]);
_sessLow = Math.Min(_sessLow, Bars.LowPrices[index]);
}
}
else
{
if (_isAccumulating)
{
// Session just ended -> Draw Geometries
_isAccumulating = false;
DrawGeometries(index - 1);
}
// --- Signal Evaluation Phase (Outside Asian Session) ---
if (_sessHigh == double.MinValue || _sessLow == double.MaxValue) return;
double rangePips = (_sessHigh - _sessLow) / Symbol.PipSize;
double sessEQ = (_sessHigh + _sessLow) / 2.0;
// Time and Range Gates
if (barTime.Hour < TradeWindowEndHour && rangePips >= MinRangePips && rangePips <= MaxRangePips)
{
double currentSpreadPips = Symbol.Spread / Symbol.PipSize;
if (currentSpreadPips <= MaxSpreadPips)
{
bool volPass = !RequireVolSpike || (Bars.TickVolumes[index] >= (_volSma.Result[index] * VolMultiplier));
// Bearish Rejection (BSL Sweep)
if (Bars.HighPrices[index] > _sessHigh && Bars.ClosePrices[index] < _sessHigh && Bars.OpenPrices[index] < _sessHigh && volPass)
{
BufferShortSignal[index] = Bars.HighPrices[index] + (5 * Symbol.PipSize);
BufferCalculatedSL[index] = Bars.HighPrices[index] + (SLBufferPips * Symbol.PipSize);
BufferTargetTP1[index] = sessEQ;
BufferTargetTP2[index] = _sessLow;
TriggerAlert(index, "SELL", Bars.ClosePrices[index], BufferCalculatedSL[index], sessEQ, currentSpreadPips);
}
// Bullish Rejection (SSL Sweep)
if (Bars.LowPrices[index] < _sessLow && Bars.ClosePrices[index] > _sessLow && Bars.OpenPrices[index] > _sessLow && volPass)
{
BufferLongSignal[index] = Bars.LowPrices[index] - (5 * Symbol.PipSize);
BufferCalculatedSL[index] = Bars.LowPrices[index] - (SLBufferPips * Symbol.PipSize);
BufferTargetTP1[index] = sessEQ;
BufferTargetTP2[index] = _sessHigh;
TriggerAlert(index, "BUY", Bars.ClosePrices[index], BufferCalculatedSL[index], sessEQ, currentSpreadPips);
}
}
}
}
// HUD execution (Only update on the latest bar to save CPU cycles)
if (IsLastBar && ShowHud)
{
RenderDashboard();
}
}
private bool IsInAccumulation(DateTime t)
{
TimeSpan timeOfDay = t.TimeOfDay;
if (_sessionStart <= _sessionEnd)
return timeOfDay >= _sessionStart && timeOfDay < _sessionEnd;
else
return timeOfDay >= _sessionStart || timeOfDay < _sessionEnd; // Wrap around midnight
}
private void DrawGeometries(int endIdx)
{
string dateId = _sessStartTime.ToString("yyyyMMdd");
DateTime endTime = Bars.OpenTimes[endIdx];
// Draw Accumulation Box
Chart.DrawRectangle($"Box_{dateId}", _sessStartTime, _sessHigh, endTime, _sessLow, _boxColor).IsFilled = true;
// Project Rays forward by 12 hours
DateTime projectionEnd = endTime.AddHours(12);
double eq = (_sessHigh + _sessLow) / 2.0;
var bsl = Chart.DrawTrendLine($"BSL_{dateId}", endTime, _sessHigh, projectionEnd, _sessHigh, Color.Crimson);
bsl.LineStyle = LineStyle.Lines;
var ssl = Chart.DrawTrendLine($"SSL_{dateId}", endTime, _sessLow, projectionEnd, _sessLow, Color.MediumSeaGreen);
ssl.LineStyle = LineStyle.Lines;
var eqLine = Chart.DrawTrendLine($"EQ_{dateId}", endTime, eq, projectionEnd, eq, Color.DarkGray);
eqLine.LineStyle = LineStyle.Dots;
}
private void TriggerAlert(int index, string side, double close, double sl, double tp1, double spread)
{
// Only trigger notifications for the live bar closing
if (IsLastBar && _lastAlertIndex != index)
{
_lastAlertIndex = index;
string payload = $"[AUDNZD Matrix] {side} Sweep Rejected | Spread: {spread:F1} | Close: {close:F5} | SL: {sl:F5} | TP: {tp1:F5}";
Print(payload);
// Uncomment to enable desktop notifications
// Notifications.PlaySound(SoundType.Doorbell);
}
}
private void RenderDashboard()
{
// Calculate 14-Day ADR using LINQ
double adrPips = 0;
if (_dailyBars.Count >= 14)
{
adrPips = _dailyBars.Skip(_dailyBars.Count - 15).Take(14)
.Average(b => b.High - b.Low) / Symbol.PipSize;
}
double curSpread = Symbol.Spread / Symbol.PipSize;
string spreadStatus = curSpread > MaxSpreadPips ? "[FILTERED]" : "[NOMINAL]";
string hud = $"== AUDNZD INSTITUTIONAL MATRIX ==\n" +
$"Spread : {curSpread:F1} pips {spreadStatus}\n" +
$"14-Day ADR: {adrPips:F1} pips\n" +
$"Vol Filter: {(RequireVolSpike ? "Enabled (SMA20 Base)" : "Disabled")}\n" +
$"Engine : C# Runtime Active";
Chart.DrawText("HUD", hud, StaticPosition.TopRight, Color.LightGray);
}
}
}