Angular Chart Hoverable Buy Sell Marker Annotations

Demonstrates how to add Hoverable Buy/Sell Markers (annotations) and News/Dividend bullets to a Angular Stock Chart using SciChart.js, High Performance JavaScript Charts

Fullscreen

Edit

 Edit

Docs

drawExample.ts

angular.ts

ExampleDataProvider.ts

theme.ts

Copy to clipboard
Minimise
Fullscreen
1import {
2    AnnotationHoverEventArgs,
3    AnnotationHoverModifier,
4    AUTO_COLOR,
5    CategoryAxis,
6    CustomAnnotation,
7    EAnnotationType,
8    EAxisAlignment,
9    ECoordinateMode,
10    EHorizontalAnchorPoint,
11    ELineType,
12    ENumericFormat,
13    EVerticalAnchorPoint,
14    FastCandlestickRenderableSeries,
15    FastLineRenderableSeries,
16    FastMountainRenderableSeries,
17    IAnnotation,
18    LegendModifier,
19    MouseWheelZoomModifier,
20    NativeTextAnnotation,
21    NumberRange,
22    NumericAxis,
23    OhlcDataSeries,
24    RightAlignedOuterVerticallyStackedAxisLayoutStrategy,
25    SciChartSurface,
26    SmartDateLabelProvider,
27    TextAnnotation,
28    Thickness,
29    TTargetsSelector,
30    XyDataSeries,
31    ZoomExtentsModifier,
32    ZoomPanModifier,
33} from "scichart";
34import { fetchMultiPaneData } from "../../../ExampleData/ExampleDataProvider";
35import { appTheme } from "../../../theme";
36
37// tslint:disable:no-empty
38// tslint:disable:max-line-length
39
40const getTradingData = async (startPoints?: number, maxPoints?: number) => {
41    const { dateValues, openValues, highValues, lowValues, closeValues, volumeValues } = await fetchMultiPaneData();
42
43    if (maxPoints !== undefined) {
44        return {
45            dateValues: dateValues.slice(startPoints, startPoints + maxPoints),
46            openValues: openValues.slice(startPoints, startPoints + maxPoints),
47            highValues: highValues.slice(startPoints, startPoints + maxPoints),
48            lowValues: lowValues.slice(startPoints, startPoints + maxPoints),
49            closeValues: closeValues.slice(startPoints, startPoints + maxPoints),
50            volumeValues: volumeValues.slice(startPoints, startPoints + maxPoints),
51        };
52    }
53
54    return { dateValues, openValues, highValues, lowValues, closeValues, volumeValues };
55};
56
57export const drawExample = async (rootElement: string | HTMLDivElement) => {
58    const [chart, data] = await Promise.all([
59        SciChartSurface.create(rootElement, { theme: appTheme.SciChartJsTheme }),
60        getTradingData(775, 100),
61    ]);
62    const { sciChartSurface, wasmContext } = chart;
63    const { dateValues, openValues, highValues, lowValues, closeValues } = data;
64
65    // Add an XAxis, YAxis
66    const xAxis = new CategoryAxis(wasmContext, { labelProvider: new SmartDateLabelProvider() });
67    xAxis.growBy = new NumberRange(0.01, 0.01);
68    sciChartSurface.xAxes.add(xAxis);
69    sciChartSurface.yAxes.add(
70        new NumericAxis(wasmContext, {
71            growBy: new NumberRange(0.1, 0.1),
72            labelFormat: ENumericFormat.Decimal,
73            labelPrecision: 2,
74        })
75    );
76
77    // Add a Candlestick series with some values to the chart
78
79    sciChartSurface.renderableSeries.add(
80        new FastCandlestickRenderableSeries(wasmContext, {
81            dataSeries: new OhlcDataSeries(wasmContext, {
82                xValues: dateValues,
83                openValues,
84                highValues,
85                lowValues,
86                closeValues,
87            }),
88            strokeUp: appTheme.VividSkyBlue,
89            strokeDown: appTheme.VividSkyBlue,
90            brushUp: appTheme.VividSkyBlue,
91            brushDown: "Transparent",
92        })
93    );
94
95    sciChartSurface.annotations.add(
96        new NativeTextAnnotation({
97            x1: 20,
98            y1: 20,
99            xCoordinateMode: ECoordinateMode.Pixel,
100            yCoordinateMode: ECoordinateMode.Pixel,
101            text: "Hover over the markers to see how well the random trading algorithm did.",
102        })
103    );
104
105    let position = 0;
106    let equity = 0;
107    let balance = 100;
108    let avgPrice = 0;
109
110    const positionDataSeries = new XyDataSeries(wasmContext, { dataSeriesName: "Position" });
111    const balanceDataSeries = new XyDataSeries(wasmContext, { dataSeriesName: "Balance" });
112
113    // Trade at random!
114    for (let i = 0; i < dateValues.length; i++) {
115        const low = lowValues[i];
116        const high = highValues[i];
117        const price = low + Math.random() * (high - low);
118        if (Math.random() < 0.2) {
119            const t = equity / (equity + balance);
120            if (Math.random() > t) {
121                // Buy
122                const quantity = Math.floor((Math.random() * balance) / price);
123                const size = quantity * price;
124                avgPrice = (avgPrice * position + size) / (position + quantity);
125                position += quantity;
126                balance -= size;
127                sciChartSurface.annotations.add(new TradeAnnotation(i, true, quantity, price, low, avgPrice));
128            } else {
129                // Sell
130                const quantity = Math.floor((Math.random() * equity) / price);
131                const size = quantity * price;
132                position -= quantity;
133                balance += size;
134                const pnl = (price - avgPrice) * quantity;
135                sciChartSurface.annotations.add(new TradeAnnotation(i, false, quantity, price, high, pnl));
136            }
137        }
138        equity = position * closeValues[i];
139        positionDataSeries.append(i, position);
140        balanceDataSeries.append(i, balance + equity);
141
142        // Every 25th bar, add a news bullet
143        if (i % 20 === 0) {
144            sciChartSurface.annotations.add(newsBulletAnnotation(i));
145        }
146    }
147
148    //const positionAxis = new NumericAxis(wasmContext, { id: "Position", axisAlignment: EAxisAlignment.Left });
149    const balanceAxis = new NumericAxis(wasmContext, {
150        id: "Balance",
151        //visibleRange: new NumberRange(90, 110),
152        growBy: new NumberRange(0.1, 0.1),
153        stackedAxisLength: "20%",
154    });
155    sciChartSurface.yAxes.add(balanceAxis);
156
157    sciChartSurface.annotations.add(
158        new NativeTextAnnotation({
159            x1: 20,
160            y1: 0.99,
161            xCoordinateMode: ECoordinateMode.Pixel,
162            yCoordinateMode: ECoordinateMode.Relative,
163            yAxisId: balanceAxis.id,
164            verticalAnchorPoint: EVerticalAnchorPoint.Bottom,
165            text: "Profit and Loss Curve",
166        })
167    );
168
169    sciChartSurface.layoutManager.rightOuterAxesLayoutStrategy =
170        new RightAlignedOuterVerticallyStackedAxisLayoutStrategy();
171
172    const positionSeries = new FastLineRenderableSeries(wasmContext, {
173        dataSeries: positionDataSeries,
174        stroke: AUTO_COLOR,
175        yAxisId: "Position",
176        lineType: ELineType.Digital,
177    });
178    const balanceSeries = new FastMountainRenderableSeries(wasmContext, {
179        dataSeries: balanceDataSeries,
180        stroke: appTheme.VividPurple,
181        fill: appTheme.MutedPurple,
182        yAxisId: "Balance",
183        zeroLineY: 100,
184    });
185    sciChartSurface.renderableSeries.add(balanceSeries);
186
187    const targetsSelector: TTargetsSelector<IAnnotation> = (modifer) => {
188        return modifer.getAllTargets().filter((t) => "quantity" in t);
189    };
190    sciChartSurface.chartModifiers.add(
191        new AnnotationHoverModifier({
192            targets: targetsSelector,
193        })
194    );
195
196    return { sciChartSurface, wasmContext };
197};
198
199class TradeAnnotation extends CustomAnnotation {
200    public isBuy: boolean;
201    public quantity: number;
202    public price: number;
203    public change: number;
204
205    private priceAnnotation: CustomAnnotation;
206    private toolTipAnnotation: TextAnnotation;
207
208    public onHover(args: AnnotationHoverEventArgs) {
209        const { x1, x2 } = this.getAdornerAnnotationBorders(true);
210        const viewRect = this.parentSurface.seriesViewRect;
211        if (args.isHovered && !this.priceAnnotation) {
212            this.priceAnnotation = tradePriceAnnotation(this.x1, this.price, this.isBuy);
213            this.toolTipAnnotation = new TextAnnotation({
214                yCoordShift: this.isBuy ? 20 : -20,
215                x1: this.x1,
216                y1: this.y1,
217                verticalAnchorPoint: this.isBuy ? EVerticalAnchorPoint.Top : EVerticalAnchorPoint.Bottom,
218                horizontalAnchorPoint:
219                    x1 < viewRect.left + 50
220                        ? EHorizontalAnchorPoint.Left
221                        : x2 > viewRect.right - 50
222                        ? EHorizontalAnchorPoint.Right
223                        : EHorizontalAnchorPoint.Center,
224                background: this.isBuy ? appTheme.VividGreen : appTheme.VividRed,
225                textColor: "black",
226                padding: new Thickness(0, 0, 5, 0),
227                fontSize: 16,
228                text: `${this.quantity} @${this.price.toFixed(3)} ${
229                    this.isBuy ? "Avg Price" : "PnL"
230                } ${this.change.toFixed(3)}`,
231            });
232            this.parentSurface.annotations.add(this.priceAnnotation, this.toolTipAnnotation);
233        } else if (this.priceAnnotation) {
234            this.parentSurface.annotations.remove(this.priceAnnotation, true);
235            this.parentSurface.annotations.remove(this.toolTipAnnotation, true);
236            this.priceAnnotation = undefined;
237            this.toolTipAnnotation = undefined;
238        }
239    }
240
241    public constructor(
242        timeStamp: number,
243        isBuy: boolean,
244        quantity: number,
245        tradePrice: number,
246        markerPrice: number,
247        change: number
248    ) {
249        super({
250            x1: timeStamp,
251            y1: markerPrice,
252            verticalAnchorPoint: isBuy ? EVerticalAnchorPoint.Top : EVerticalAnchorPoint.Bottom,
253            horizontalAnchorPoint: EHorizontalAnchorPoint.Center,
254        });
255        this.isBuy = isBuy;
256        this.quantity = quantity;
257        this.price = tradePrice;
258        this.change = change;
259        this.onHover = this.onHover.bind(this);
260        this.hovered.subscribe(this.onHover);
261    }
262
263    public override getSvgString(annotation: CustomAnnotation): string {
264        if (this.isBuy) {
265            return `<svg id="Capa_1" xmlns="http://www.w3.org/2000/svg">
266            <g transform="translate(-54.867218,-75.091687)">
267                <path style="fill:${appTheme.VividGreen};fill-opacity:0.77;stroke:${appTheme.VividGreen};stroke-width:2px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
268                    d="m 55.47431,83.481251 c 7.158904,-7.408333 7.158904,-7.408333 7.158904,-7.408333 l 7.158906,7.408333 H 66.212668 V 94.593756 H 59.053761 V 83.481251 Z"
269                "/>
270            </g>
271        </svg>`;
272        } else {
273            return `<svg id="Capa_1" xmlns="http://www.w3.org/2000/svg">
274            <g transform="translate(-54.616083,-75.548914)">
275                <path style="fill:${appTheme.VividRed};fill-opacity:0.77;stroke:${appTheme.VividRed};stroke-width:2px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
276                d="m 55.47431,87.025547 c 7.158904,7.408333 7.158904,7.408333 7.158904,7.408333 L 69.79212,87.025547 H 66.212668 V 75.913042 h -7.158907 v 11.112505 z"
277                />
278            </g>
279        </svg>`;
280        }
281    }
282}
283
284const tradePriceAnnotation = (timestamp: number, price: number, isBuy: boolean): CustomAnnotation => {
285    return new CustomAnnotation({
286        x1: timestamp,
287        y1: price,
288        verticalAnchorPoint: EVerticalAnchorPoint.Center,
289        horizontalAnchorPoint: EHorizontalAnchorPoint.Right,
290        svgString: `<svg xmlns="http://www.w3.org/2000/svg">
291            <path style="fill: transparent; stroke:${
292                isBuy ? appTheme.VividGreen : appTheme.VividRed
293            }; stroke-width: 3px;" d="M 0 0 L 10 10 L 0 20"></path>
294        </svg>`,
295    });
296};
297
298const newsBulletAnnotation = (x1: number): CustomAnnotation => {
299    return new CustomAnnotation({
300        x1,
301        y1: 0.99, // using YCoordinateMode.Relative and 0.99, places the annotation at the bottom of the viewport
302        yCoordinateMode: ECoordinateMode.Relative,
303        verticalAnchorPoint: EVerticalAnchorPoint.Bottom,
304        horizontalAnchorPoint: EHorizontalAnchorPoint.Center,
305        svgString: `<svg id="Capa_1" xmlns="http://www.w3.org/2000/svg">
306              <g
307                 inkscape:label="Layer 1"
308                 inkscape:groupmode="layer"
309                 id="layer1"
310                 transform="translate(-55.430212,-77.263552)">
311                <rect
312                   style="fill:${appTheme.ForegroundColor};fill-opacity:1;stroke:${appTheme.Background};stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.66666667"
313                   id="rect4528"
314                   width="50"
315                   height="18"
316                   x="55.562504"
317                   y="77.395844"
318                   rx="2"
319                   ry="2" />
320                <text
321                   xml:space="preserve"
322                   style="font-style:normal;font-weight:normal;font-size:10.58333302px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:${appTheme.Background};fill-opacity:1;stroke:none;stroke-width:0.26458332"
323                   x="59.688622"
324                   y="91.160347"
325                   id="text4540"><tspan
326                     sodipodi:role="line"
327                     id="tspan4538"
328                     x="57.688622"
329                     y="89.160347"
330                     style=\"font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';fill:${appTheme.Background};fill-opacity:1;stroke-width:0.26458332\">Dividend</tspan></text>
331              </g>
332            </svg>`,
333    });
334};
335

See Also: Financial Charts (8 Demos)

Angular Candlestick Chart | Chart Examples | SciChart.js | SciChart.js Demo

Angular Candlestick Chart

Discover how to create a Angular Candlestick Chart or Stock Chart using SciChart.js. For high Performance JavaScript Charts, get your free demo now.

Angular OHLC Chart | JavaScript Chart Examples | SciChart | SciChart.js Demo

Angular OHLC Chart

Easily create Angular OHLC Chart or Stock Chart using feature-rich SciChart.js chart library. Supports custom colors. Get your free trial now.

Angular Realtime Ticking Stock Chart | SciChart.js | SciChart.js Demo

Angular Realtime Ticking Stock Charts

Create a Angular Realtime Ticking Candlestick / Stock Chart with live ticking and updating, using the high performance SciChart.js chart library. Get free demo now.

Angular Multi-Pane Stock Chart using Subcharts | View JavaScript Charts | SciChart.js Demo

Angular Multi-Pane Stock Charts using Subcharts

Create a Angular Multi-Pane Candlestick / Stock Chart with indicator panels, synchronized zooming, panning and cursors. Get your free trial of SciChart.js now.

Tenor Curves Demo | SciChart.js Demo

Tenor Curves Demo

Demonstrating the capability of SciChart.js to create a composite 2D &amp; 3D Chart application. An example like this could be used to visualize Tenor curves in a financial setting, or other 2D/3D data combined on a single screen.

Angular Multi-Pane Stock Chart | View JavaScript Charts | SciChart.js Demo

Angular Multi-Pane Stock Charts using Sync Multi-Chart

Create a Angular Multi-Pane Candlestick / Stock Chart with indicator panels, synchronized zooming, panning and cursors. Get your free trial of SciChart.js now.

Angular Market Depth Chart | SciChart.js Demo

Angular Market Depth Chart

Create a Angular Depth Chart, using the high performance SciChart.js chart library. Get free demo now.

Angular User Annotated Stock Chart | Chart Examples | SciChart.js | SciChart.js Demo

Angular User Annotated Stock Chart

This demo shows you how to create a <strong>{frameworkName} User Annotated Stock Chart</strong> using SciChart.js. Custom modifiers allow you to add lines and markers, then use the built in serialisation functions to save and reload the chart, including the data and all your custom annotations.

SciChart Ltd, 16 Beaufort Court, Admirals Way, Docklands, London, E14 9XL.