示例#1
0
        /// <summary>
        /// Fast index of bar where max(bar[i].X) &lt;= x
        /// </summary>
        /// <returns>The index of the bar closest to X, where max(bar[i].X) &lt;= x.</returns>
        /// <param name="x">The x coordinate.</param>
        /// <param name="startingIndex">starting index</param>
        public int FindByX(double x, int startingIndex = -1)
        {
            if (startingIndex < 0)
            {
                startingIndex = this.winIndex;
            }

            return(OhlcvItem.FindIndex(this.data, x, startingIndex));
        }
示例#2
0
        /// <summary>
        /// Gets the point on the series that is nearest the specified point.
        /// </summary>
        /// <param name="point">The point.</param>
        /// <param name="interpolate">Interpolate the series if this flag is set to <c>true</c>.</param>
        /// <returns>A TrackerHitResult for the current hit.</returns>
        public override TrackerHitResult GetNearestPoint(ScreenPoint point, bool interpolate)
        {
            if (this.XAxis == null || this.YAxis == null || interpolate || this.data.Count == 0)
            {
                return(null);
            }

            var nbars   = this.data.Count;
            var xy      = this.InverseTransform(point);
            var targetX = xy.X;

            // punt if beyond start & end of series
            if (targetX > (this.data[nbars - 1].X + this.minDx))
            {
                return(null);
            }
            else if (targetX < (this.data[0].X - this.minDx))
            {
                return(null);
            }

            var pidx = OhlcvItem.FindIndex(this.data, targetX, this.winIndex);
            var nidx = ((pidx + 1) < this.data.Count) ? pidx + 1 : pidx;

            Func <OhlcvItem, double> distance = bar =>
            {
                var dx = bar.X - xy.X;
                return(dx * dx);
            };

            // determine closest point
            var midx = distance(this.data[pidx]) <= distance(this.data[nidx]) ? pidx : nidx;
            var mbar = this.data[midx];

            var hit = new DataPoint(mbar.X, mbar.Close);

            return(new TrackerHitResult
            {
                Series = this,
                DataPoint = hit,
                Position = this.Transform(hit),
                Item = mbar,
                Index = midx,
                Text = StringHelper.Format(
                    this.ActualCulture,
                    this.TrackerFormatString,
                    mbar,
                    this.XAxis.GetValue(mbar.X),
                    this.YAxis.GetValue(mbar.High),
                    this.YAxis.GetValue(mbar.Low),
                    this.YAxis.GetValue(mbar.Open),
                    this.YAxis.GetValue(mbar.Close),
                    this.YAxis.GetValue(mbar.BuyVolume),
                    this.YAxis.GetValue(mbar.SellVolume))
            });
        }
示例#3
0
        /// <summary>
        /// Append a bar to the series (must be in X order)
        /// </summary>
        /// <param name="bar">The Bar.</param>
        public void Append(OhlcvItem bar)
        {
            if (this.data == null)
            {
                this.data = new List <OhlcvItem>();
            }

            if (this.data.Count > 0 && this.data[this.data.Count - 1].X > bar.X)
            {
                throw new ArgumentException("cannot append bar out of order, must be sequential in X");
            }

            this.data.Add(bar);
        }
        /// <summary>
        /// Append a bar to the series (must be in X order)
        /// </summary>
        /// <param name="bar">Bar object.</param>
        public void Append(OhlcvItem bar)
        {
            if (this.data == null)
            {
                this.data = new List<OhlcvItem>();
            }

            if (this.data.Count > 0 && this.data[this.data.Count - 1].X > bar.X)
            {
                throw new ArgumentException("cannot append bar out of order, must be sequential in X");
            }

            this.data.Add(bar);
        }
示例#5
0
        /// <summary>
        /// Renders the series on the specified rendering context.
        /// </summary>
        /// <param name="rc">The rendering context.</param>
        public override void Render(IRenderContext rc)
        {
            if (this.data == null || this.data.Count == 0)
            {
                return;
            }

            var items  = this.data;
            var nitems = this.data.Count;

            this.VerifyAxes();

            var clippingRect = this.GetClippingRect();

            var datacandlewidth     = (this.BarWidth > 0) ? this.BarWidth : this.minDx * 0.80;
            var halfDataCandleWidth = datacandlewidth * 0.5;

            // colors
            var fillUp   = this.GetSelectableFillColor(this.PositiveColor);
            var fillDown = this.GetSelectableFillColor(this.NegativeColor);

            var barfillUp   = this.PositiveHollow ? OxyColors.Transparent : fillUp;
            var barfillDown = this.NegativeHollow ? OxyColors.Transparent : fillDown;

            var lineUp   = this.GetSelectableColor(this.PositiveColor.ChangeIntensity(this.StrokeIntensity));
            var lineDown = this.GetSelectableColor(this.NegativeColor.ChangeIntensity(this.StrokeIntensity));

            // determine render range
            var xmin = this.XAxis.ActualMinimum;
            var xmax = this.XAxis.ActualMaximum;

            this.winIndex = OhlcvItem.FindIndex(items, xmin, this.winIndex);

            for (int i = this.winIndex; i < nitems; i++)
            {
                var bar = items[i];

                // if item beyond visible range, done
                if (bar.X > xmax)
                {
                    break;
                }

                // check to see whether is valid
                if (!bar.IsValid())
                {
                    continue;
                }

                var p1 = this.Transform(bar.X - halfDataCandleWidth, 0);

                switch (this.VolumeStyle)
                {
                case VolumeStyle.Combined:
                {
                    var p2        = this.Transform(bar.X + halfDataCandleWidth, Math.Abs(bar.BuyVolume - bar.SellVolume));
                    var fillcolor = (bar.BuyVolume > bar.SellVolume) ? barfillUp : barfillDown;
                    var linecolor = (bar.BuyVolume > bar.SellVolume) ? lineUp : lineDown;
                    var rect1     = new OxyRect(p1, p2);
                    rc.DrawClippedRectangleAsPolygon(clippingRect, rect1, fillcolor, linecolor, this.StrokeThickness);
                }

                break;

                case VolumeStyle.PositiveNegative:
                {
                    var p2Buy  = this.Transform(bar.X + halfDataCandleWidth, bar.BuyVolume);
                    var p2Bell = this.Transform(bar.X + halfDataCandleWidth, -bar.SellVolume);

                    var rectBuy  = new OxyRect(p1, p2Buy);
                    var rectSell = new OxyRect(p1, p2Bell);

                    rc.DrawClippedRectangleAsPolygon(clippingRect, rectBuy, fillUp, lineUp, this.StrokeThickness);
                    rc.DrawClippedRectangleAsPolygon(clippingRect, rectSell, fillDown, lineDown, this.StrokeThickness);
                }

                break;

                case VolumeStyle.Stacked:
                {
                    var p2Buy  = this.Transform(bar.X + halfDataCandleWidth, bar.BuyVolume);
                    var p2Sell = this.Transform(bar.X + halfDataCandleWidth, bar.SellVolume);
                    var pBoth  = this.Transform(bar.X - halfDataCandleWidth, bar.BuyVolume + bar.SellVolume);

                    OxyRect rectBuy;
                    OxyRect rectSell;

                    if (bar.BuyVolume > bar.SellVolume)
                    {
                        rectSell = new OxyRect(p1, p2Sell);
                        rectBuy  = new OxyRect(p2Sell, pBoth);
                    }
                    else
                    {
                        rectBuy  = new OxyRect(p1, p2Buy);
                        rectSell = new OxyRect(p2Buy, pBoth);
                    }

                    rc.DrawClippedRectangleAsPolygon(clippingRect, rectBuy, fillUp, lineUp, this.StrokeThickness);
                    rc.DrawClippedRectangleAsPolygon(clippingRect, rectSell, fillDown, lineDown, this.StrokeThickness);

                    break;
                }

                case VolumeStyle.None:
                    break;

                default:
                    throw new ArgumentOutOfRangeException();
                }
            }

            if (this.InterceptStrokeThickness > 0 && this.InterceptLineStyle != LineStyle.None)
            {
                // draw volume y=0 line
                var p1    = this.InverseTransform(clippingRect.BottomLeft);
                var p2    = this.InverseTransform(clippingRect.TopRight);
                var lineA = this.Transform(p1.X, 0);
                var lineB = this.Transform(p2.X, 0);

                rc.DrawClippedLine(
                    clippingRect,
                    new[] { lineA, lineB },
                    0,
                    this.InterceptColor,
                    this.InterceptStrokeThickness,
                    this.InterceptLineStyle.GetDashArray(),
                    LineJoin.Miter,
                    true);
            }
        }
示例#6
0
        /// <summary>
        /// Renders the series on the specified rendering context.
        /// </summary>
        /// <param name="rc">The rendering context.</param>
        // ReSharper disable once FunctionComplexityOverflow
        public override void Render(IRenderContext rc)
        {
            if (this.data == null || this.data.Count == 0)
            {
                return;
            }

            var items  = this.data;
            var nitems = this.data.Count;

            this.VerifyAxes();

            var clippingBar = this.GetClippingRect(this.BarAxis);
            var clippingSep = this.GetSeparationClippingRect();
            var clippingVol = this.GetClippingRect(this.VolumeAxis);

            var datacandlewidth = (this.CandleWidth > 0) ? this.CandleWidth : this.minDx * 0.80;
            var candlewidth     =
                this.XAxis.Transform(items[0].X + datacandlewidth) -
                this.XAxis.Transform(items[0].X) - this.StrokeThickness;

            // colors
            var fillUp   = this.GetSelectableFillColor(this.PositiveColor);
            var fillDown = this.GetSelectableFillColor(this.NegativeColor);

            var barfillUp   = this.PositiveHollow ? OxyColors.Transparent : fillUp;
            var barfillDown = this.NegativeHollow ? OxyColors.Transparent : fillDown;

            var lineUp   = this.GetSelectableColor(this.PositiveColor.ChangeIntensity(this.StrokeIntensity));
            var lineDown = this.GetSelectableColor(this.NegativeColor.ChangeIntensity(this.StrokeIntensity));

            // determine render range
            var xmin = this.XAxis.ActualMinimum;
            var xmax = this.XAxis.ActualMaximum;

            this.winIndex = OhlcvItem.FindIndex(items, xmin, this.winIndex);

            for (int i = this.winIndex; i < nitems; i++)
            {
                var bar = items[i];

                // if item beyond visible range, done
                if (bar.X > xmax)
                {
                    break;
                }

                // check to see whether is valid
                if (!bar.IsValid())
                {
                    continue;
                }

                var fillColor = bar.Close > bar.Open ? barfillUp : barfillDown;
                var lineColor = bar.Close > bar.Open ? lineUp : lineDown;

                var high = this.Transform(bar.X, bar.High);
                var low  = this.Transform(bar.X, bar.Low);

                var open  = this.Transform(bar.X, bar.Open);
                var close = this.Transform(bar.X, bar.Close);

                var max = new ScreenPoint(open.X, Math.Max(open.Y, close.Y));
                var min = new ScreenPoint(open.X, Math.Min(open.Y, close.Y));

                // Bar part
                rc.DrawClippedLine(
                    clippingBar,
                    new[] { high, min },
                    0,
                    lineColor,
                    this.StrokeThickness,
                    null,
                    LineJoin.Miter,
                    true);

                // Lower extent
                rc.DrawClippedLine(
                    clippingBar,
                    new[] { max, low },
                    0,
                    lineColor,
                    this.StrokeThickness,
                    null,
                    LineJoin.Miter,
                    true);

                // Body
                var openLeft = open + new ScreenVector(-candlewidth * 0.5, 0);
                var rect     = new OxyRect(openLeft.X, min.Y, candlewidth, max.Y - min.Y);
                rc.DrawClippedRectangleAsPolygon(
                    clippingBar,
                    rect,
                    fillColor,
                    lineColor,
                    this.StrokeThickness);

                // Volume Part
                if (this.VolumeAxis == null || this.VolumeStyle == VolumeStyle.None)
                {
                    continue;
                }

                var iY0 = this.VolumeAxis.Transform(0);
                switch (this.VolumeStyle)
                {
                case VolumeStyle.Combined:
                {
                    var adj       = this.VolumeAxis.Transform(Math.Abs(bar.BuyVolume - bar.SellVolume));
                    var fillcolor = (bar.BuyVolume > bar.SellVolume) ? barfillUp : barfillDown;
                    var linecolor = (bar.BuyVolume > bar.SellVolume) ? lineUp : lineDown;
                    var rect1     = new OxyRect(openLeft.X, adj, candlewidth, Math.Abs(adj - iY0));
                    rc.DrawClippedRectangleAsPolygon(clippingVol, rect1, fillcolor, linecolor, this.StrokeThickness);
                }

                break;

                case VolumeStyle.PositiveNegative:
                {
                    var buyY  = this.VolumeAxis.Transform(bar.BuyVolume);
                    var sellY = this.VolumeAxis.Transform(-bar.SellVolume);
                    var rect1 = new OxyRect(openLeft.X, buyY, candlewidth, Math.Abs(buyY - iY0));
                    rc.DrawClippedRectangleAsPolygon(clippingVol, rect1, fillUp, lineUp, this.StrokeThickness);
                    var rect2 = new OxyRect(openLeft.X, iY0, candlewidth, Math.Abs(sellY - iY0));
                    rc.DrawClippedRectangleAsPolygon(clippingVol, rect2, fillDown, lineDown, this.StrokeThickness);
                }

                break;

                case VolumeStyle.Stacked:
                    if (bar.BuyVolume > bar.SellVolume)
                    {
                        var buyY     = this.VolumeAxis.Transform(bar.BuyVolume);
                        var sellY    = this.VolumeAxis.Transform(bar.SellVolume);
                        var dyoffset = sellY - iY0;
                        var rect2    = new OxyRect(openLeft.X, sellY, candlewidth, Math.Abs(sellY - iY0));
                        rc.DrawClippedRectangleAsPolygon(clippingVol, rect2, fillDown, lineDown, this.StrokeThickness);
                        var rect1 = new OxyRect(openLeft.X, buyY + dyoffset, candlewidth, Math.Abs(buyY - iY0));
                        rc.DrawClippedRectangleAsPolygon(clippingVol, rect1, fillUp, lineUp, this.StrokeThickness);
                    }
                    else
                    {
                        var buyY     = this.VolumeAxis.Transform(bar.BuyVolume);
                        var sellY    = this.VolumeAxis.Transform(bar.SellVolume);
                        var dyoffset = buyY - iY0;
                        var rect1    = new OxyRect(openLeft.X, buyY, candlewidth, Math.Abs(buyY - iY0));
                        rc.DrawClippedRectangleAsPolygon(clippingVol, rect1, fillUp, lineUp, this.StrokeThickness);
                        var rect2 = new OxyRect(openLeft.X, sellY + dyoffset, candlewidth, Math.Abs(sellY - iY0));
                        rc.DrawClippedRectangleAsPolygon(clippingVol, rect2, fillDown, lineDown, this.StrokeThickness);
                    }

                    break;
                }
            }

            // draw volume & bar separation line
            if (this.VolumeStyle != VolumeStyle.None)
            {
                var ysep = (clippingSep.Bottom + clippingSep.Top) / 2.0;
                rc.DrawClippedLine(
                    clippingSep,
                    new[] { new ScreenPoint(clippingSep.Left, ysep), new ScreenPoint(clippingSep.Right, ysep) },
                    0,
                    this.SeparatorColor,
                    this.SeparatorStrokeThickness,
                    this.SeparatorLineStyle.GetDashArray(),
                    LineJoin.Miter,
                    true);
            }

            // draw volume y=0 line
            if (this.VolumeAxis != null && this.VolumeStyle == VolumeStyle.PositiveNegative)
            {
                var y0 = this.VolumeAxis.Transform(0);
                rc.DrawClippedLine(
                    clippingVol,
                    new[] { new ScreenPoint(clippingVol.Left, y0), new ScreenPoint(clippingVol.Right, y0) },
                    0,
                    OxyColors.Goldenrod,
                    this.SeparatorStrokeThickness,
                    this.SeparatorLineStyle.GetDashArray(),
                    LineJoin.Miter,
                    true);
            }
        }
示例#7
0
        /// <summary>
        /// Renders the series on the specified rendering context.
        /// </summary>
        /// <param name="rc">The rendering context.</param>
        public override void Render(IRenderContext rc)
        {
            if (this.data == null || this.data.Count == 0)
            {
                return;
            }

            var items  = this.data;
            var nitems = this.data.Count;

            this.VerifyAxes();

            var clipping = this.GetClippingRect();

            var datacandlewidth = (this.BarWidth > 0) ? this.BarWidth : this.minDx * 0.80;
            var candlewidth     =
                this.XAxis.Transform(items[0].X + datacandlewidth) -
                this.XAxis.Transform(items[0].X) - this.StrokeThickness;

            // colors
            var fillUp   = this.GetSelectableFillColor(this.PositiveColor);
            var fillDown = this.GetSelectableFillColor(this.NegativeColor);

            var barfillUp   = this.PositiveHollow ? OxyColors.Transparent : fillUp;
            var barfillDown = this.NegativeHollow ? OxyColors.Transparent : fillDown;

            var lineUp   = this.GetSelectableColor(this.PositiveColor.ChangeIntensity(this.StrokeIntensity));
            var lineDown = this.GetSelectableColor(this.NegativeColor.ChangeIntensity(this.StrokeIntensity));

            // determine render range
            var xmin = this.XAxis.ActualMinimum;
            var xmax = this.XAxis.ActualMaximum;

            this.winIndex = OhlcvItem.FindIndex(items, xmin, this.winIndex);

            for (int i = this.winIndex; i < nitems; i++)
            {
                var bar = items[i];

                // if item beyond visible range, done
                if (bar.X > xmax)
                {
                    break;
                }

                // check to see whether is valid
                if (!bar.IsValid())
                {
                    continue;
                }

                var leftX = this.XAxis.Transform(bar.X) - (this.BarWidth / 2.0);
                var y0    = this.YAxis.Transform(0);

                switch (this.VolumeStyle)
                {
                case VolumeStyle.Combined:
                {
                    var adj       = this.YAxis.Transform(Math.Abs(bar.BuyVolume - bar.SellVolume));
                    var fillcolor = (bar.BuyVolume > bar.SellVolume) ? barfillUp : barfillDown;
                    var linecolor = (bar.BuyVolume > bar.SellVolume) ? lineUp : lineDown;
                    var rect1     = new OxyRect(leftX, adj, candlewidth, Math.Abs(adj - y0));
                    rc.DrawClippedRectangleAsPolygon(clipping, rect1, fillcolor, linecolor, this.StrokeThickness);
                }

                break;

                case VolumeStyle.PositiveNegative:
                {
                    var buyY  = this.YAxis.Transform(bar.BuyVolume);
                    var sellY = this.YAxis.Transform(-bar.SellVolume);
                    var rect1 = new OxyRect(leftX, buyY, candlewidth, Math.Abs(buyY - y0));
                    rc.DrawClippedRectangleAsPolygon(clipping, rect1, fillUp, lineUp, this.StrokeThickness);
                    var rect2 = new OxyRect(leftX, y0, candlewidth, Math.Abs(sellY - y0));
                    rc.DrawClippedRectangleAsPolygon(clipping, rect2, fillDown, lineDown, this.StrokeThickness);
                }

                break;

                case VolumeStyle.Stacked:
                    if (bar.BuyVolume > bar.SellVolume)
                    {
                        var buyY     = this.YAxis.Transform(bar.BuyVolume);
                        var sellY    = this.YAxis.Transform(bar.SellVolume);
                        var dyoffset = sellY - y0;
                        var rect2    = new OxyRect(leftX, sellY, candlewidth, Math.Abs(sellY - y0));
                        rc.DrawClippedRectangleAsPolygon(clipping, rect2, fillDown, lineDown, this.StrokeThickness);
                        var rect1 = new OxyRect(leftX, buyY + dyoffset, candlewidth, Math.Abs(buyY - y0));
                        rc.DrawClippedRectangleAsPolygon(clipping, rect1, fillUp, lineUp, this.StrokeThickness);
                    }
                    else
                    {
                        var buyY     = this.YAxis.Transform(bar.BuyVolume);
                        var sellY    = this.YAxis.Transform(bar.SellVolume);
                        var dyoffset = buyY - y0;
                        var rect1    = new OxyRect(leftX, buyY, candlewidth, Math.Abs(buyY - y0));
                        rc.DrawClippedRectangleAsPolygon(clipping, rect1, fillUp, lineUp, this.StrokeThickness);
                        var rect2 = new OxyRect(leftX, sellY + dyoffset, candlewidth, Math.Abs(sellY - y0));
                        rc.DrawClippedRectangleAsPolygon(clipping, rect2, fillDown, lineDown, this.StrokeThickness);
                    }

                    break;
                }
            }

            // draw volume y=0 line
            var intercept = this.YAxis.Transform(0);

            rc.DrawClippedLine(
                clipping,
                new[] { new ScreenPoint(clipping.Left, intercept), new ScreenPoint(clipping.Right, intercept) },
                0,
                this.InterceptColor,
                this.InterceptStrokeThickness,
                this.InterceptLineStyle.GetDashArray(),
                LineJoin.Miter,
                true);
        }