using System;
using System.ComponentModel;
using System.Xml.Serialization;
using System.ComponentModel.DataAnnotations;
using System.Windows.Media;
using NinjaTrader.Cbi;
using NinjaTrader.Gui;
using NinjaTrader.Data;
using NinjaTrader.NinjaScript;
using NinjaTrader.Core.FloatingPoint;
using NinjaTrader.NinjaScript.DrawingTools;
#endregion
namespace NinjaTrader.NinjaScript.Indicators
{
public class BollyAlerts : Indicator
{
private SMA sma;
private StdDev stdDev;
private bool upperAlertTriggered;
private bool lowerAlertTriggered;
// Custom properties for alert
private string soundFilePath = @"C:\\Sounds\\customAlert.wav"; // Set this to your desired sound file path
[XmlElement("SoundFilePath")]
public string SoundFilePath
{
get { return soundFilePath; }
set { soundFilePath = value; }
}
protected override void OnStateChange()
{
if (State == State.SetDefaults)
{
Description = "Bollinger Bands with custom alerts and arrows";
Name = "BollyAlerts";
IsOverlay = true;
IsSuspendedWhileInactive = true;
NumStdDev = 2;
Period = 14;
Calculate = Calculate.OnEachTick; // To check conditions on every tick, not just at bar close
AddPlot(Brushes.Goldenrod, "UpperBand");
AddPlot(Brushes.Goldenrod, "MiddleBand");
AddPlot(Brushes.Goldenrod, "LowerBand");
}
else if (State == State.DataLoaded)
{
sma = SMA(Period);
stdDev = StdDev(Period);
}
}
protected override void OnBarUpdate()
{
// Ensure there are enough bars to calculate the SMA and Bollinger Bands
if (CurrentBar < Period || CurrentBar < BarsRequiredToPlot)
return; // Exit if not enough bars are available
double sma0 = sma[0];
double stdDev0 = stdDev[0];
Upper[0] = sma0 + NumStdDev * stdDev0;
Middle[0] = sma0;
Lower[0] = sma0 - NumStdDev * stdDev0;
// Reset flags on a new bar
if (IsFirstTickOfBar)
{
upperAlertTriggered = false;
lowerAlertTriggered = false;
}
// Create unique arrow tags for each condition
string downArrowTag = "downArrow" + CurrentBar + "_" + DateTime.Now.Ticks;
string upArrowTag = "upArrow" + CurrentBar + "_" + DateTime.Now.Ticks;
// Check if price touched or crossed the upper band (at any point, not just close)
if (High[0] >= Upper[0] && !upperAlertTriggered)
{
// Draw a red arrow above the candle
Draw.ArrowDown(this, downArrowTag, false, 0, High[0] + TickSize, Brushes.Red);
// Trigger alert for Upper Band touch
Alert("UpperBandTouch" + CurrentBar, Priority.High, "Price touched the Upper Bollinger Band", SoundFilePath, 10, Brushes.Red, Brushes.Transparent);
upperAlertTriggered = true; // Mark that the upper alert has been triggered
}
// Check if price touched or crossed the lower band (at any point, not just close)
else if (Low[0] <= Lower[0] && !lowerAlertTriggered)
{
// Draw a green arrow below the candle
Draw.ArrowUp(this, upArrowTag, false, 0, Low[0] - TickSize, Brushes.Green);
// Trigger alert for Lower Band touch
Alert("LowerBandTouch" + CurrentBar, Priority.High, "Price touched the Lower Bollinger Band", SoundFilePath, 10, Brushes.Green, Brushes.Transparent);
lowerAlertTriggered = true; // Mark that the lower alert has been triggered
}
}
region Properties
[Browsable(false)]
[XmlIgnore()]
public Series<double> Upper
{
get { return Values[0]; }
}
[Browsable(false)]
[XmlIgnore()]
public Series<double> Middle
{
get { return Values[1]; }
}
[Browsable(false)]
[XmlIgnore()]
public Series<double> Lower
{
get { return Values[2]; }
}
[Range(0, int.MaxValue), NinjaScriptProperty]
[Display(Name = "NumStdDev", GroupName = "Parameters", Order = 0)]
public double NumStdDev { get; set; }
[Range(1, int.MaxValue), NinjaScriptProperty]
[Display(Name = "Period", GroupName = "Parameters", Order = 1)]
public int Period { get; set; }
#endregion
}
}
region NinjaScript generated code. Neither change nor remove.
namespace NinjaTrader.NinjaScript.Indicators
{
public partial class Indicator : NinjaTrader.Gui.NinjaScript.IndicatorRenderBase
{
private BollyAlerts[] cacheBollyAlerts;
public BollyAlerts BollyAlerts(double numStdDev, int period)
{
return BollyAlerts(Input, numStdDev, period);
}
public BollyAlerts BollyAlerts(ISeries<double> input, double numStdDev, int period)
{
if (cacheBollyAlerts != null)
for (int idx = 0; idx < cacheBollyAlerts.Length; idx++)
if (cacheBollyAlerts[idx] != null && cacheBollyAlerts[idx].NumStdDev == numStdDev && cacheBollyAlerts[idx].Period == period && cacheBollyAlerts[idx].EqualsInput(input))
return cacheBollyAlerts[idx];
return CacheIndicator<BollyAlerts>(new BollyAlerts(){ NumStdDev = numStdDev, Period = period }, input, ref cacheBollyAlerts);
}
}
}
namespace NinjaTrader.NinjaScript.MarketAnalyzerColumns
{
public partial class MarketAnalyzerColumn : MarketAnalyzerColumnBase
{
public Indicators.BollyAlerts BollyAlerts(double numStdDev, int period)
{
return indicator.BollyAlerts(Input, numStdDev, period);
}
public Indicators.BollyAlerts BollyAlerts(ISeries<double> input , double numStdDev, int period)
{
return indicator.BollyAlerts(input, numStdDev, period);
}
}
}
namespace NinjaTrader.NinjaScript.Strategies
{
public partial class Strategy : NinjaTrader.Gui.NinjaScript.StrategyRenderBase
{
public Indicators.BollyAlerts BollyAlerts(double numStdDev, int period)
{
return indicator.BollyAlerts(Input, numStdDev, period);
}
public Indicators.BollyAlerts BollyAlerts(ISeries<double> input , double numStdDev, int period)
{
return indicator.BollyAlerts(input, numStdDev, period);
}
}
}
#endregion
I'm trying to code an indicator that alerts audibly and draws an arrow over candles that touched the upper and lower bands of the standard bollinger. It's done and works correctly, problem is it seems when I try to extend the range or change dates it crashes or doesn't load. Something about indexes and accessing data or something. This was all done with chatgpt, I don't know how to code, so I can't find the problem and solve it. If someone knows a more efficient way to code it, or has a bollinger indicator with alerts that'd be amazing. Above is a picture that shows my goal with the bollinger.

Comment