///
/// 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;
}
}
}
}
}
}
}