/// /// Supply / Demand zones indicator /// Uses "pivot points" (aka fractals) to approximate supply/demand zones. /// Old supply/demand zones are removed as soon as the prices action fully crosses the zone. /// The indicator displays up to a maximum number of supply or demand zones. /// Version 1 - First version /// Version 2 - Search either side of the pivot to find a better value for upper/lower bound of zone /// Version 3 - Minor changes to code structure, fixed bug with drawing new zones /// namespace Broker.StrategyLanguage.Indicator { public class _SupplyDemand : BaseIndicator { // Main constructor (leave empty) public _SupplyDemand(object _ctx) : base(_ctx) {} // Zone class for supply or demand private class Zone { public double upper; public double lower; public DateTime start; public DateTime end; private BaseTechnique obj; private Color colour1; private Color colour2; private ITrendLineDrw top; private ITrendLineDrw bottom; private ITrendLineDrw left; private ITrendLineDrw right; private ITextDrw text; private bool demand; // Constructor public Zone(BaseTechnique obj, Color colour, bool demand) { this.obj = obj; colour1 = colour; colour2 = Color.FromArgb(colour.A, colour.R / 2, colour.G / 2, colour.B / 2); this.demand = demand; top = null; bottom = null; left = null; right = null; text = null; } // Draw the zone public void Draw() { // Clear it first Clear(); // Top line top = obj.DrwTrendLine.Create(new DrwCoordinate(start, upper), new DrwCoordinate(end, upper)); top.Size = demand ? 1 : 0; top.Color = demand ? colour1 : colour2; // Bottom line bottom = obj.DrwTrendLine.Create(new DrwCoordinate(start, lower), new DrwCoordinate(end, lower)); bottom.Size = demand ? 0 : 1; bottom.Color = demand ? colour2 : colour1; // Left line left = obj.DrwTrendLine.Create(new DrwCoordinate(start, upper), new DrwCoordinate(start, lower)); left.Size = 0; left.Style = ETrendLineStyle.ToolDotted; left.Color = colour2; // Right line right = obj.DrwTrendLine.Create(new DrwCoordinate(end, upper), new DrwCoordinate(end, lower)); right.Size = 0; right.Style = ETrendLineStyle.ToolDotted; right.Color = colour2; // Label double price = demand ? upper : lower; text = obj.DrwText.Create(new DrwCoordinate(end, price), string.Format(" {0}", price)); text.Color = colour1; text.HStyle = EHorTextStyle.Right; text.Size = 8; } // Clear the zone public void Clear() { if (top != null) { top.Delete(); top = null; } if (bottom != null) { bottom.Delete(); bottom = null; } if (left != null) { left.Delete(); left = null; } if (right != null) { right.Delete(); right = null; } if (text != null) { text.Delete(); text = null; } } } // Plots... private IPlot plot1; private IPlot plot2; // Variables... private PivotHighVS highPivot; private PivotLowVS lowPivot; private List supplyZones; private List demandZones; private int strength = 2; private int maxNumZones = 3; private ISeries top; private ISeries bottom; private bool supplyAlert; private bool demandAlert; // Properties... [Input] public int Strength { get { return strength; } set { strength = value; } } [Input] public int MaxNumZones { get { return maxNumZones; } set { maxNumZones = value; } } // Construct (called when the indicator is added to the chart) protected override void Construct() { // NOTE: plots are not used, they are just for user selection of colours plot1 = AddPlot(new PlotInfo("Supply Zone", PlotType.Line, Color.Red, Color.Empty, 1, 0, false)); plot2 = AddPlot(new PlotInfo("Demand Zone", PlotType.Line, Color.Blue, Color.Empty, 1, 0, false)); // Pivots highPivot = new PivotHighVS(this); lowPivot = new PivotLowVS(this); // Supply / Demand zones supplyZones = new List(); demandZones = new List(); } // Initialize (called after construction or if inputs change) protected override void Initialize() { // Price series top = new SeriesExpression(delegate (int bar) { return Math.Max(Bars.Open[bar], Bars.Close[bar]); } ); bottom = new SeriesExpression(delegate (int bar) { return Math.Min(Bars.Open[bar], Bars.Close[bar]); } ); // High pivot highPivot.price = Bars.High; highPivot.instance = new ConstantExpression(1); highPivot.leftstrength = new ConstantExpression(strength); highPivot.rightstrength = new ConstantExpression(strength); highPivot.length = new ConstantExpression(strength + 1); // Low pivot lowPivot.price = Bars.Low; lowPivot.instance = new ConstantExpression(1); lowPivot.leftstrength = new ConstantExpression(strength); lowPivot.rightstrength = new ConstantExpression(strength); lowPivot.length = new ConstantExpression(strength + 1); // Supply / Demand zones supplyZones.Clear(); demandZones.Clear(); // Alert flags supplyAlert = false; demandAlert = false; } // Execute (called each bar) protected override void Execute() { if (Bars.Status == BarStatus.Close) { // Clear the alert flags (prevents to many alerts per bar) supplyAlert = false; demandAlert = false; // New supply zones... if (highPivot.Value != -1) { // Look either side of the pivot to see the 'range of this zone' double upper = double.MinValue; double lower = double.MinValue; int bar = 0; for (int i = -strength; i <= strength; ++i) { // Highest high if (Bars.High[strength + i] >= upper) { upper = Bars.High[strength + i]; bar = i; } // Highest bottom if (bottom[strength + i] >= lower) { lower = bottom[strength + i]; bar = i; } } // Create the supply zone Zone z = new Zone(this, plot1.Colors.Value, false); z.start = Bars.Time[strength + bar]; z.end = Bars.TimeValue; z.upper = upper; z.lower = lower; // Only add the zone if it does not overlap with existing valid zone if ((supplyZones.Count == 0) || (z.upper < supplyZones[0].lower)) { supplyZones.Insert(0, z); //IArrowDrw arrow = DrwArrow.Create(new DrwCoordinate(Bars.Time[strength], Bars.High[strength]), true); //arrow.Color = plot1.Colors.Value; } } // New demand zones... if (lowPivot.Value != -1) { // Look either side of the pivot to see the 'range of this zone' double upper = double.MaxValue; double lower = double.MaxValue; int bar = 0; for (int i = -strength; i <= strength; ++i) { // Lowest low if (Bars.Low[strength + i] <= lower) { lower = Bars.Low[strength + i]; bar = i; } // Lowest top if (top[strength + i] <= upper) { upper = top[strength + i]; bar = i; } } // Create the demand zone Zone z = new Zone(this, plot2.Colors.Value, true); z.start = Bars.Time[strength + bar]; z.end = Bars.TimeValue; z.upper = upper; z.lower = lower; // Only add the zone if it does not overlap with existing valid zone if ((demandZones.Count == 0) || (z.lower > demandZones[0].upper)) { demandZones.Insert(0, z); //IArrowDrw arrow = DrwArrow.Create(new DrwCoordinate(Bars.Time[strength], Bars.Low[strength]), false); //arrow.Color = plot2.Colors.Value; } } } // Update the supply zones for (int i = (supplyZones.Count - 1); i >= 0; --i) { // Update the end time supplyZones[i].end = Bars.TimeValue; // Check for zones that need to be removed on bar close if (Bars.Status == BarStatus.Close) { if (supplyZones[i].upper <= Bars.CloseValue) { // Clear the zone supplyZones[i].Clear(); // Remove from list supplyZones.RemoveAt(i); } } } // Update the demand zones for (int i = (demandZones.Count - 1); i >= 0; --i) { // Update the end time demandZones[i].end = Bars.TimeValue; // Check for zones that need to be removed on bar close if (Bars.Status == BarStatus.Close) { if (demandZones[i].lower >= Bars.CloseValue) { // Clear the zone demandZones[i].Clear(); // Remove from list demandZones.RemoveAt(i); } } } if (Bars.LastBarOnChart) { // NOTE: there is a very strange phenomenon regarding drawing objects. If I call Clear() // and then Draw() in the same loop, then new objects are not drawn. But if Clear() and // Draw() are in different loops it works. I'm confident that my code is ok. It appears // to be some strange quirk with Strategy Trader framework, possibly even related to // garbage collection of deleted objects or something. // Clear supply zones for (int i = 0; i < Math.Min(supplyZones.Count, maxNumZones); ++i) { supplyZones[i].Clear(); } // Clear demand zones for (int i = 0; i < Math.Min(demandZones.Count, maxNumZones); ++i) { demandZones[i].Clear(); } // Draw supply zones for (int i = 0; i < Math.Min(supplyZones.Count, maxNumZones); ++i) { supplyZones[i].Draw(); } // Draw demand zones for (int i = 0; i < Math.Min(demandZones.Count, maxNumZones); ++i) { demandZones[i].Draw(); } // Check for alerts if (supplyZones.Count > 0) { if (Functions.CrossesOver(this, Bars.Close, new ConstantExpression(supplyZones[0].lower))) { if (!supplyAlert) { //Alerts.Alert("Entering supply zone"); supplyAlert = true; } } } // Check for alerts if (demandZones.Count > 0) { if (Functions.CrossesUnder(this, Bars.Close, new ConstantExpression(demandZones[0].upper))) { if (!demandAlert) { //Alerts.Alert("Entering demand zone"); demandAlert = true; } } } } } } }