Code: Select all
using System;
using cAlgo.API;
using cAlgo.API.Internals;
using cAlgo.API.Indicators;
using cAlgo.Indicators;
namespace cAlgo
{
[Indicator(IsOverlay = true, TimeZone = TimeZones.EasternStandardTime, AccessRights = AccessRights.None)]
public class GoldInventoryRiskAuditor : Indicator
{
// --- INPUTS: Risk Windows (New York Time) ---
[Parameter("Tier-1 Start", DefaultValue = "08:25", Group = "Time Risk Zones")]
public string Tier1StartStr { get; set; }
[Parameter("Tier-1 End", DefaultValue = "08:45", Group = "Time Risk Zones")]
public string Tier1EndStr { get; set; }
[Parameter("Swap Start", DefaultValue = "16:45", Group = "Time Risk Zones")]
public string SwapStartStr { get; set; }
[Parameter("Swap End", DefaultValue = "17:15", Group = "Time Risk Zones")]
public string SwapEndStr { get; set; }
[Parameter("FOMC Start", DefaultValue = "13:55", Group = "Time Risk Zones")]
public string FomcStartStr { get; set; }
[Parameter("FOMC End", DefaultValue = "14:15", Group = "Time Risk Zones")]
public string FomcEndStr { get; set; }
// --- INPUTS: Correlation Tracking ---
[Parameter("Correlation Lookback", DefaultValue = 20, Group = "Stacked Risk Matrix")]
public int CorrLength { get; set; }
[Parameter("Silver Symbol", DefaultValue = "XAGUSD", Group = "Stacked Risk Matrix")]
public string SymbolAg { get; set; }
[Parameter("USD/JPY Symbol", DefaultValue = "USDJPY", Group = "Stacked Risk Matrix")]
public string SymbolUj { get; set; }
[Parameter("Danger Threshold", DefaultValue = 0.80, Group = "Stacked Risk Matrix")]
public double CorrThreshold { get; set; }
// Core variables
private Bars _barsAg;
private Bars _barsUj;
private TimeSpan _t1Start, _t1End, _swStart, _swEnd, _fmStart, _fmEnd;
// UI and Alert State
private Grid _auditBoard;
private TextBlock _txtAgStatus;
private TextBlock _txtUjStatus;
private bool _wasAgDanger = false;
private bool _wasUjDanger = false;
protected override void Initialize()
{
// Parse Times
_t1Start = TimeSpan.Parse(Tier1StartStr);
_t1End = TimeSpan.Parse(Tier1EndStr);
_swStart = TimeSpan.Parse(SwapStartStr);
_swEnd = TimeSpan.Parse(SwapEndStr);
_fmStart = TimeSpan.Parse(FomcStartStr);
_fmEnd = TimeSpan.Parse(FomcEndStr);
// Fetch Secondary Market Data
_barsAg = MarketData.GetBars(TimeFrame, SymbolAg);
_barsUj = MarketData.GetBars(TimeFrame, SymbolUj);
// Initialize UI Dashboard
BuildDashboard();
}
public override void Calculate(int index)
{
// 1. Time Zone Logic
TimeSpan timeOfDay = Bars.OpenTimes[index].TimeOfDay;
bool inTier1 = IsInTimeWindow(timeOfDay, _t1Start, _t1End);
bool inSwap = IsInTimeWindow(timeOfDay, _swStart, _swEnd);
bool inFomc = IsInTimeWindow(timeOfDay, _fmStart, _fmEnd);
// 2. Visuals: Paint Danger Zones Over Bars
if (inTier1 || inFomc)
{
DrawRiskBox(index, "Macro", Color.FromArgb(75, Color.Red));
}
else if (inSwap)
{
DrawRiskBox(index, "Swap", Color.FromArgb(75, Color.Blue));
}
// 3. Correlation Logic (Calculate on every tick for last bar, or historical for past bars)
double corrAg = CalculateCorrelation(Bars.ClosePrices, _barsAg.ClosePrices, index, CorrLength, _barsAg);
double corrUj = CalculateCorrelation(Bars.ClosePrices, _barsUj.ClosePrices, index, CorrLength, _barsUj);
bool dangerAg = corrAg >= CorrThreshold;
bool dangerUj = corrUj <= -CorrThreshold;
// 4. Update UI & Alerts (Only on Live Last Bar)
if (IsLastBar)
{
UpdateDashboard(corrAg, corrUj, dangerAg, dangerUj);
// Alert Triggers (Only fires once when crossing into danger)
if (dangerAg && !_wasAgDanger)
{
Notifications.ShowPopup($"XAU/{SymbolAg} correlation breached ({corrAg:F2}). Do not stack risk.");
Notifications.PlaySound(SoundType.Warning);
}
if (dangerUj && !_wasUjDanger)
{
Notifications.ShowPopup($"XAU/{SymbolUj} inverse correlation breached ({corrUj:F2}). Do not stack risk.");
Notifications.PlaySound(SoundType.Warning);
}
_wasAgDanger = dangerAg;
_wasUjDanger = dangerUj;
}
}
// --- HELPER: Correlation Math ---
private double CalculateCorrelation(DataSeries mainSeries, DataSeries secSeries, int index, int lookback, Bars secBars)
{
double sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0, sumY2 = 0;
int n = 0;
for (int i = 0; i < lookback; i++)
{
int currIdx = index - i;
if (currIdx < 0) break;
// Sync secondary bar by main bar's open time to ensure data aligns perfectly
DateTime time = Bars.OpenTimes[currIdx];
int secIdx = secBars.OpenTimes.GetIndexByTime(time);
if (secIdx < 0) continue;
double x = mainSeries[currIdx];
double y = secSeries[secIdx];
sumX += x;
sumY += y;
sumXY += x * y;
sumX2 += x * x;
sumY2 += y * y;
n++;
}
if (n == 0) return 0;
double denominator = Math.Sqrt((n * sumX2 - sumX * sumX) * (n * sumY2 - sumY * sumY));
if (denominator == 0) return 0;
return (n * sumXY - sumX * sumY) / denominator;
}
// --- HELPER: Time Check ---
private bool IsInTimeWindow(TimeSpan current, TimeSpan start, TimeSpan end)
{
if (start <= end) return current >= start && current <= end;
return current >= start || current <= end; // Handles overnight wraps if needed
}
// --- HELPER: Draw Risk Box ---
private void DrawRiskBox(int index, string prefix, Color color)
{
// Extends the box slightly above and below the bar so it acts like a background strip
double top = Bars.HighPrices[index] + (10 * Symbol.PipSize);
double bottom = Bars.LowPrices[index] - (10 * Symbol.PipSize);
Chart.DrawRectangle($"{prefix}_{index}", index - 0.5, bottom, index + 0.5, top, color)
.IsFilled = true;
}
// --- UI DASHBOARD BUILDER ---
private void BuildDashboard()
{
_auditBoard = new Grid(6, 2)
{
BackgroundColor = Color.FromArgb(220, 20, 20, 20),
HorizontalAlignment = HorizontalAlignment.Right,
VerticalAlignment = VerticalAlignment.Bottom,
Margin = new Thickness(10),
ShowGridLines = true
};
// Headers
AddCell(_auditBoard, 0, 0, "XAU DESK AUDIT", Color.FromArgb(255, 40, 40, 40), Color.White);
AddCell(_auditBoard, 0, 1, "STATUS", Color.FromArgb(255, 40, 40, 40), Color.White);
// Process Rules
AddCell(_auditBoard, 1, 0, "Flat Before Tier-1", Color.Transparent, Color.Gray);
AddCell(_auditBoard, 1, 1, "Review", Color.FromArgb(200, 150, 0, 0), Color.White);
AddCell(_auditBoard, 2, 0, "Overnight Swap Leftovers", Color.Transparent, Color.Gray);
AddCell(_auditBoard, 2, 1, "Review", Color.FromArgb(200, 0, 0, 150), Color.White);
// Correlation Rows (Hold references to status textblocks so we can update them live)
AddCell(_auditBoard, 3, 0, $"{SymbolAg} Correlation", Color.Transparent, Color.White);
_txtAgStatus = AddCell(_auditBoard, 3, 1, "Calculating...", Color.Gray, Color.White);
AddCell(_auditBoard, 4, 0, $"{SymbolUj} Correlation", Color.Transparent, Color.White);
_txtUjStatus = AddCell(_auditBoard, 4, 1, "Calculating...", Color.Gray, Color.White);
AddCell(_auditBoard, 5, 0, "Broker vs. Entry Costs", Color.Transparent, Color.Gray);
AddCell(_auditBoard, 5, 1, "Pending", Color.FromArgb(200, 200, 100, 0), Color.White);
Chart.AddControl(_auditBoard);
}
private TextBlock AddCell(Grid grid, int row, int col, string text, Color bgColor, Color fgColor)
{
var border = new Border
{
BackgroundColor = bgColor,
BorderThickness = new Thickness(1),
BorderColor = Color.FromArgb(100, 100, 100, 100)
};
var textBlock = new TextBlock
{
Text = text,
ForegroundColor = fgColor,
Margin = new Thickness(8, 4, 8, 4),
VerticalAlignment = VerticalAlignment.Center,
HorizontalAlignment = HorizontalAlignment.Left,
FontWeight = FontWeight.Bold
};
border.Child = textBlock;
grid.AddChild(border, row, col);
return textBlock;
}
private void UpdateDashboard(double corrAg, double corrUj, bool dangerAg, bool dangerUj)
{
_txtAgStatus.Text = dangerAg ? $"STACKED ({corrAg:F2})" : $"{corrAg:F2}";
_txtAgStatus.Parent.BackgroundColor = dangerAg ? Color.FromArgb(200, 150, 0, 0) : Color.FromArgb(150, 60, 60, 60);
_txtUjStatus.Text = dangerUj ? $"STACKED ({corrUj:F2})" : $"{corrUj:F2}";
_txtUjStatus.Parent.BackgroundColor = dangerUj ? Color.FromArgb(200, 150, 0, 0) : Color.FromArgb(150, 60, 60, 60);
}
}
}