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
drawExample.ts
angular.ts
ExampleDataProvider.ts
theme.ts
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
Discover how to create a Angular Candlestick Chart or Stock Chart using SciChart.js. For high Performance JavaScript Charts, get your free demo now.
Easily create Angular OHLC Chart or Stock Chart using feature-rich SciChart.js chart library. Supports custom colors. Get your free trial now.
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.
Create a Angular Multi-Pane Candlestick / Stock Chart with indicator panels, synchronized zooming, panning and cursors. Get your free trial of SciChart.js now.
Demonstrating the capability of SciChart.js to create a composite 2D & 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.
Create a Angular Multi-Pane Candlestick / Stock Chart with indicator panels, synchronized zooming, panning and cursors. Get your free trial of SciChart.js now.
Create a Angular Depth Chart, using the high performance SciChart.js chart library. Get free demo now.
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.