Fibonacci levels
Fibonacci levels
A convention-driven way to build Fibonacci tools: expose one level-set property and get user-editable ratios, colours and visibility for free, with ready-made default sets to start from.
ADVANCED What it is
Fibonacci tools follow a convention rather than a special base class. You expose a single
FibLevelSet Levels property — an ObservableCollection<FibLevel> —
tag it with [DrawingToolProperty], and the editor picks it up reflectively. That one property
gives the user per-level control over ratio, colour and visibility with no extra wiring.
The types live in TradeStrike.Pipeline.Drawing.Tools and are deliberately small. A
FibLevel is a ratio, an optional per-level colour (where null means "inherit the
tool's stroke colour"), and a visibility flag. A FibLevelSet is an observable collection of
them, so it binds directly in the WPF list editor.
FibLevels.csnamespace TradeStrike.Pipeline.Drawing.Tools;
public sealed class FibLevel
{
public double Ratio { get; set; } // applied to the tool's anchor range
public ChartColor? Color { get; set; } // null = inherit the tool's Color
public bool IsVisible { get; set; } = true;
public FibLevel() { }
public FibLevel(double ratio, ChartColor? color = null, bool visible = true);
public FibLevel Clone();
}
public sealed class FibLevelSet : ObservableCollection<FibLevel>
{
public FibLevelSet();
public FibLevelSet(IEnumerable<FibLevel> items);
public FibLevelSet Clone(); // deep copy
public void ReplaceWith(IEnumerable<FibLevel> other);
}
Ready-made level sets
You rarely build a level set by hand. The static FibLevels helper ships the standard ratio
ladders and palettes, and factory methods that return a populated FibLevelSet. Some pair each
ratio with a TradingView-style colour; others leave the colour null so every level inherits
the tool's stroke colour.
FibLevels.cs// Ratio ladders (raw arrays):
FibLevels.StandardRetracements; // 0, .236, .382, .5, .618, .786, 1
FibLevels.StandardExtensions; // 1.272, 1.414, 1.618, 2, 2.618, 3.618, 4.236
FibLevels.StandardRetracementsAndExtensions;
FibLevels.StandardSpeedRatios; // .382, .5, .618
FibLevels.StandardTimeZones; // 1, 2, 3, 5, 8, 13, 21, 34, 55, 89
FibLevels.GannAngles; // (Price, Time) slope pairs for a Gann fan
// Factory methods returning a populated FibLevelSet:
FibLevels.DefaultRetracementSet(); // ladder + per-level colours
FibLevels.DefaultExtensionSet();
FibLevels.DefaultRetracementSetInheritColor(); // every level inherits the tool's Color
FibLevels.DefaultSpeedRatioSetInheritColor();
FibLevels.DefaultCircleRatioSetInheritColor();
Rendering the levels
In OnRender you derive each level's price from the two anchors (the ratio interpolates
between anchor 0's price and anchor 1's price), convert it to a pixel Y with
PriceToPixelY, and draw a horizontal line out to the plot area's right edge. Each level uses
its own colour, falling back to the tool's Color when none is set.
FibRetracement.csusing System;
using TradeStrike.Pipeline.Drawing;
using TradeStrike.Pipeline.Drawing.Tools;
using TradeStrike.Pipeline.Plots;
[DrawingToolMetadata("Fib Retracement", Category = "Fibonacci", AnchorCount = 2)]
public sealed class FibRetracement : DrawingToolBase
{
public FibRetracement() : base(anchorCount: 2) { }
[DrawingToolProperty(DisplayName = "Levels", Category = "Levels", Order = 10,
Description = "Configurable ratios with per-level visibility and colour.")]
public FibLevelSet Levels { get; set; } = FibLevels.DefaultRetracementSet();
public override void OnRender(IDrawingToolRenderContext ctx)
{
double p0 = Anchors[0].Price, p1 = Anchors[1].Price;
double xL = Math.Min(ctx.AnchorToPixel(Anchors[0]).X, ctx.AnchorToPixel(Anchors[1]).X);
double xR = ctx.PlotArea.Right;
foreach (var lvl in Levels)
{
if (!lvl.IsVisible) continue;
double price = p0 + lvl.Ratio * (p1 - p0);
double y = ctx.Viewport.PriceToPixelY(price);
ChartColor stroke = lvl.Color ?? Color; // per-level, else tool colour
ctx.Renderer.DrawLine(xL, y, xR, y, stroke, LineWidthPx);
ctx.Renderer.DrawText(xL + 4, y - 4, $"{lvl.Ratio:0.000} ({price:0.##})", stroke, 11);
}
if (ctx.IsSelected)
{
ctx.DrawHandle(ctx.AnchorToPixel(Anchors[0]).X, ctx.AnchorToPixel(Anchors[0]).Y);
ctx.DrawHandle(ctx.AnchorToPixel(Anchors[1]).X, ctx.AnchorToPixel(Anchors[1]).Y);
}
}
public override bool HitTest(double px, double py, IDrawingToolRenderContext ctx, double tol = 6)
{
double p0 = Anchors[0].Price, p1 = Anchors[1].Price;
double xL = Math.Min(ctx.AnchorToPixel(Anchors[0]).X, ctx.AnchorToPixel(Anchors[1]).X);
double xR = ctx.PlotArea.Right;
foreach (var lvl in Levels)
{
if (!lvl.IsVisible) continue;
double price = p0 + lvl.Ratio * (p1 - p0);
double y = ctx.Viewport.PriceToPixelY(price);
if (px >= xL && px <= xR && DrawingGeometry.DistanceToHorizontalLine(py, y) <= tol)
return true;
}
return false;
}
}
FibLevel and FibLevelSet
offer a deep Clone(), and FibLevelSet.ReplaceWith swaps contents in-place via
Clear+Add so the observable change notifications fire as the editor expects. Iterating-and-editing the live
set in place can confuse the bound editor — clone, mutate the copy, then ReplaceWith.