program CandleChartExport; {$APPTYPE CONSOLE} // Atlas Fin quick start: build OHLCV bars in code, add studies, verify the // indicator values and export the chart to PNG and SVG without a window. uses System.SysUtils, System.Classes, System.Math, System.DateUtils, System.IOUtils, Atlas.FinData, Atlas.FinIndicators, Atlas.FinChart; procedure Check(Condition: Boolean; const Message: string); begin if not Condition then raise Exception.Create('Check failed: ' + Message); Writeln(' ok: ', Message); end; // Deterministic weekday bars: the same prices on every run and platform. procedure FillBars(Data: TAtlasBarSeries; Count: Integer); var Seed: Cardinal; Day: TDateTime; Price, Open, Close, High, Low: Double; I: Integer; function Next: Double; begin Seed := Cardinal((UInt64(Seed) * 1664525 + 1013904223) and $FFFFFFFF); Result := (Seed shr 8) / 16777216; end; begin Seed := 2026; Day := EncodeDate(2026, 1, 5); // a Monday Price := 100; Data.BeginUpdate; try Data.Clear; I := 0; while I < Count do begin if DayOfTheWeek(Day) <= 5 then begin Open := Price * (1 + (Next - 0.5) * 0.01); Close := Open * (1 + (Next - 0.48) * 0.03); High := Max(Open, Close) * (1 + Next * 0.01); Low := Min(Open, Close) * (1 - Next * 0.01); Data.Add(Day, Open, High, Low, Close, Round(500000 + Next * 500000)); Price := Close; Inc(I); end; Day := Day + 1; end; finally Data.EndUpdate; end; end; var Chart: TAtlasFinChart; Loaded: TAtlasBarSeries; Sma, Rsi: TArray; Bands: TAtlasBands; Expected: Double; I, Last: Integer; Svg: string; begin try Chart := TAtlasFinChart.Create(nil); try FillBars(Chart.Data, 120); Last := Chart.Data.Count - 1; Writeln(Format('%d bars from %s to %s', [Chart.Data.Count, FormatDateTime('yyyy-mm-dd', Chart.Data[0].Time), FormatDateTime('yyyy-mm-dd', Chart.Data[Last].Time)])); // Price pane with candles and SMA(20), a volume pane and an RSI(14) pane. Chart.SetupDefault; Chart.Panes[0].Studies.Add(skBollinger, 20); Chart.Panes.Add('MACD', 100).Studies.Add(skMACD); Check(Chart.Panes.Count = 4, 'four panes: Price, Volume, Momentum, MACD'); // The SMA(20) study line equals the mean of the closes it covers. Sma := Chart.Panes[0].Studies[1].Lines(Chart.Data)[0]; Expected := 0; for I := 0 to 19 do Expected := Expected + Chart.Data[I].Close / 20; Check(IsNan(Sma[18]) and SameValue(Sma[19], Expected, 1E-9), 'SMA(20) starts at bar 20 with the mean of the closes'); // The same indicators are available without a chart. Bands := TAtlasIndicators.Bollinger(Chart.Data.Closes, 20, 2); Check((Bands.Upper[Last] > Bands.Middle[Last]) and (Bands.Lower[Last] < Bands.Middle[Last]) and SameValue(Bands.Middle[Last], Sma[Last], 1E-9), 'Bollinger bands enclose the SMA'); Rsi := TAtlasIndicators.RSI(Chart.Data.Closes, 14); Check(IsNan(Rsi[13]) and (Rsi[14] >= 0) and (Rsi[14] <= 100), 'RSI(14) starts at bar 15 and stays within 0..100'); Writeln(Format(' last close %.2f, SMA(20) %.2f, RSI(14) %.1f', [Chart.Data[Last].Close, Sma[Last], Rsi[Last]])); // CSV out and back in. ToCSV writes 15 significant digits, so computed // prices come back equal to within rounding, not bit for bit. Loaded := TAtlasBarSeries.Create; try Loaded.LoadCSV(Chart.Data.ToCSV); Check((Loaded.Count = Chart.Data.Count) and (Loaded[Last].Time = Chart.Data[Last].Time) and SameValue(Loaded[Last].Close, Chart.Data[Last].Close, 1E-9), 'CSV round trip'); finally Loaded.Free; end; // Export: the chart needs no parent or window for this. Chart.SaveToFile('candles.png', 900, 600); Check(TFile.Exists('candles.png') and (TFile.GetSize('candles.png') > 5000), 'candles.png written'); Chart.ZoomTo(Chart.Data.Count - 40, 40); Check((Chart.FirstBar = 80) and (Chart.VisibleBars = 40), 'the view shows the last 40 bars'); Chart.SaveToFile('candles-zoom.png', 900, 600); Svg := Chart.ToSVG(900, 600); Check((Pos(' 0) and (Pos('SMA(20)', Svg) > 0) and (Pos('MACD(12, 26, 9)', Svg) > 0), 'the SVG names the studies in the pane headers'); Chart.SaveToFile('candles.svg', 900, 600); Check(TFile.Exists('candles.svg') and (TFile.GetSize('candles.svg') > 2000), 'candles.svg written'); Writeln('Files written to ', GetCurrentDir); finally Chart.Free; end; except on E: Exception do begin Writeln(E.ClassName, ': ', E.Message); ExitCode := 1; end; end; end.