YOUSEFUL / DELPHI / SAMPLE PROJECTS
Interactive financial chart
A VCL program that shows what TAtlasFinChart does on screen. The form is built in code (CreateNew), with a
toolbar above a chart that fills the rest of the window. The chart has four panes on one shared time axis:
| Pane | Weight | Studies |
|---|---|---|
| Price | 300 | candles, Bollinger bands (20, 2), EMA(50) at line width 2 |
| Volume | 90 | volume bars |
| MACD | 110 | MACD(12, 26, 9): line, signal and histogram |
| RSI | 90 | RSI(14) on its fixed 0..100 scale |
It demonstrates:
- Crosshair.
OnCrosshairfills the toolbar label with the date, open, high, low, close and volume of the bar under the mouse, plus the RSI value read from the study's cachedLines. The pane headers show the same bar's values. - Zoom and scroll. The Zoom in and Zoom out buttons call
ZoomBy(0.8, ...)andZoomBy(1.25, ...)about the centre bar. Show all callsShowAll. Newest callsScrollBy(MaxInt), which stops at the newest bar. The chart's own input works too: drag to scroll, turn the wheel to zoom about the cursor, double-click to show everything, drag the border between two panes to resize them, and use the keys listed below. - View tracking.
OnViewChangewrites the visible range into the form caption, for exampleAtlas Fin: bars 141 to 260 of 260. - Price style. The drop-down sets the first price study's
Kindto candles, OHLC bars, a close line or a close area. - Logarithmic scale. The check box sets
Panes[0].Logarithmic. The scale is rounded out to whole powers of ten. These prices (roughly 250 to 300) therefore sit in a narrow band of a 100 to 1000 scale. That is how the product behaves today, not a fault of the sample. - Live bars. Live bars starts a timer that moves the newest bar with
UpdateLastand adds a new bar on every eighth tick. The chart starts on the newest 120 bars; a view that shows the newest bar follows new bars. Scroll back and the view stays where you left it. Following does not raiseOnViewChange, so the timer updates the caption itself.
Keyboard, once the chart has focus (it takes focus when clicked): Left and Right scroll one bar, Ctrl+Left and
Ctrl+Right scroll a page, Home and End go to the oldest and newest bars, Up or numeric-keypad + zooms in, and
Down or numeric-keypad - zooms out.
Build and run
Open InteractiveFinChart.dpr in Delphi 13; the IDE creates the project file. Add the Atlas Fin src folder and the Atlas Plot src folder to the project's search path, then build and run for Win32 or Win64. No packages need to be installed.
Expected result
A 1100 × 720 window opens. It shows 120 candles from March to August 2026 with Bollinger bands and a thicker EMA
line, and green and red volume bars below them. Under the volume are the MACD pane and the RSI pane. The price
pane header ends with the date of the newest visible bar (28 August 2026). Moving the mouse draws a dashed vertical
line through every pane and a horizontal line with a value tag at the right-hand axis. Over the newest bar the
toolbar label reads 28/08/2026 O 271.65 H 277.80 L 269.81 C 275.74 Vol 1011433 RSI 57.3; the date follows
your Windows short date format. The data comes from a fixed RandSeed, so these numbers are the same on every run.
Adapting it
- Replace
FillBarswithFChart.Data.LoadCSV(...)or your ownData.Addcalls. Wrap bulk loads inBeginUpdate/EndUpdateso the chart repaints once. - For real live data, call
UpdateLastwhile a bar is forming andAddwhen the next one opens, on the main thread (for example fromTThread.Queue). The series is not thread-safe. - Do not assign
FChart.Data.OnChange: the chart uses that event itself to repaint and to follow new bars.
Project files
The ZIP contains every sample project with its README. See getting started for the search path to add.
Source
InteractiveFinChart.dpr
program InteractiveFinChart;
// Atlas Fin: a four-pane chart with crosshair read-out, zoom and scroll
// buttons, a switchable price style, a logarithmic scale and simulated live
// bars. The form is built in code; there is no .dfm.
uses
Vcl.Forms,
InteractiveFinChartForm in 'InteractiveFinChartForm.pas';
var
Form: TFinChartForm;
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TFinChartForm, Form);
Application.Run;
end.
InteractiveFinChartForm.pas
unit InteractiveFinChartForm;
interface
uses
System.SysUtils, System.Classes, System.Math, System.DateUtils,
Vcl.Forms, Vcl.Controls, Vcl.StdCtrls, Vcl.ExtCtrls,
Atlas.FinData, Atlas.FinChart;
type
TFinChartForm = class(TForm)
private
FChart: TAtlasFinChart;
FStatus: TLabel;
FStyle: TComboBox;
FLog, FLive: TCheckBox;
FTimer: TTimer;
FTicks: Integer;
function AddButton(AParent: TWinControl; ALeft: Integer; const ACaption: string; AOnClick: TNotifyEvent): TButton;
procedure FillBars(Count: Integer);
procedure ZoomInClick(Sender: TObject);
procedure ZoomOutClick(Sender: TObject);
procedure ShowAllClick(Sender: TObject);
procedure NewestClick(Sender: TObject);
procedure StyleChanged(Sender: TObject);
procedure LogChanged(Sender: TObject);
procedure LiveChanged(Sender: TObject);
procedure TimerTick(Sender: TObject);
procedure CrosshairMoved(Sender: TObject; BarIndex: Integer);
procedure ViewChanged(Sender: TObject);
public
constructor Create(AOwner: TComponent); override;
end;
implementation
constructor TFinChartForm.Create(AOwner: TComponent);
var
Bar: TPanel;
Price: TAtlasFinPane;
begin
inherited CreateNew(AOwner);
ClientWidth := 1100;
ClientHeight := 720;
Position := poScreenCenter;
Bar := TPanel.Create(Self);
Bar.Parent := Self;
Bar.Align := alTop;
Bar.Height := 40;
Bar.BevelOuter := bvNone;
AddButton(Bar, 8, 'Zoom in', ZoomInClick);
AddButton(Bar, 92, 'Zoom out', ZoomOutClick);
AddButton(Bar, 176, 'Show all', ShowAllClick);
AddButton(Bar, 260, 'Newest', NewestClick);
FStyle := TComboBox.Create(Self);
FStyle.Parent := Bar;
FStyle.SetBounds(356, 8, 110, 24);
FStyle.Style := csDropDownList;
FStyle.Items.CommaText := 'Candles,OHLC,"Close line","Close area"';
FStyle.ItemIndex := 0;
FStyle.OnChange := StyleChanged;
FLog := TCheckBox.Create(Self);
FLog.Parent := Bar;
FLog.SetBounds(480, 10, 110, 20);
FLog.Caption := 'Logarithmic';
FLog.OnClick := LogChanged;
FLive := TCheckBox.Create(Self);
FLive.Parent := Bar;
FLive.SetBounds(596, 10, 90, 20);
FLive.Caption := 'Live bars';
FLive.OnClick := LiveChanged;
FStatus := TLabel.Create(Self);
FStatus.Parent := Bar;
FStatus.SetBounds(700, 12, 390, 20);
FStatus.Caption := 'Move the mouse over the chart';
FTimer := TTimer.Create(Self);
FTimer.Interval := 400;
FTimer.Enabled := False;
FTimer.OnTimer := TimerTick;
FChart := TAtlasFinChart.Create(Self);
FChart.Parent := Self;
FChart.Align := alClient;
FChart.ValueFormat := '0.00';
FChart.OnCrosshair := CrosshairMoved;
FChart.OnViewChange := ViewChanged;
Price := FChart.Panes.Add('Price', 300);
Price.Studies.Add(skCandles);
Price.Studies.Add(skBollinger, 20);
Price.Studies.Add(skEMA, 50).LineWidth := 2;
FChart.Panes.Add('Volume', 90).Studies.Add(skVolume);
FChart.Panes.Add('MACD', 110).Studies.Add(skMACD);
FChart.Panes.Add('RSI', 90).Studies.Add(skRSI, 14);
FillBars(260);
// Start on the newest 120 bars; this view follows live bars as they arrive.
FChart.ZoomTo(FChart.Data.Count - 120, 120);
ActiveControl := FChart;
end;
function TFinChartForm.AddButton(AParent: TWinControl; ALeft: Integer; const ACaption: string;
AOnClick: TNotifyEvent): TButton;
begin
Result := TButton.Create(Self);
Result.Parent := AParent;
Result.SetBounds(ALeft, 8, 80, 24);
Result.Caption := ACaption;
Result.OnClick := AOnClick;
end;
procedure TFinChartForm.FillBars(Count: Integer);
var
Day: TDateTime;
Price, Open, Close: Double;
I: Integer;
begin
RandSeed := 7; // the same walk on every run
Day := EncodeDate(2025, 9, 1);
Price := 250;
FChart.Data.BeginUpdate;
try
I := 0;
while I < Count do
begin
if DayOfTheWeek(Day) <= 5 then
begin
Open := Price * (1 + (Random - 0.5) * 0.008);
Close := Open * (1 + (Random - 0.49) * 0.03);
FChart.Data.Add(Day, Open, Max(Open, Close) * (1 + Random * 0.01), Min(Open, Close) * (1 - Random * 0.01),
Close, Round(800000 + Random * 600000));
Price := Close;
Inc(I);
end;
Day := Day + 1;
end;
finally
FChart.Data.EndUpdate;
end;
end;
procedure TFinChartForm.ZoomInClick(Sender: TObject);
begin
FChart.ZoomBy(0.8, FChart.FirstBar + FChart.VisibleBars div 2);
end;
procedure TFinChartForm.ZoomOutClick(Sender: TObject);
begin
FChart.ZoomBy(1.25, FChart.FirstBar + FChart.VisibleBars div 2);
end;
procedure TFinChartForm.ShowAllClick(Sender: TObject);
begin
FChart.ShowAll;
end;
procedure TFinChartForm.NewestClick(Sender: TObject);
begin
FChart.ScrollBy(MaxInt); // clamps to the newest bar
end;
procedure TFinChartForm.StyleChanged(Sender: TObject);
const
Kinds: array[0..3] of TAtlasStudyKind = (skCandles, skOhlc, skCloseLine, skCloseArea);
begin
FChart.Panes[0].Studies[0].Kind := Kinds[FStyle.ItemIndex];
end;
procedure TFinChartForm.LogChanged(Sender: TObject);
begin
FChart.Panes[0].Logarithmic := FLog.Checked;
end;
procedure TFinChartForm.LiveChanged(Sender: TObject);
begin
FTimer.Enabled := FLive.Checked;
end;
procedure TFinChartForm.TimerTick(Sender: TObject);
var
Last: TAtlasBar;
Day: TDateTime;
Close: Double;
begin
Last := FChart.Data[FChart.Data.Count - 1];
Inc(FTicks);
if FTicks mod 8 = 0 then
begin
// A new bar on the next weekday. A view parked on the newest bar follows it,
// but following does not raise OnViewChange, so refresh the caption here.
Day := Last.Time + 1;
while DayOfTheWeek(Day) > 5 do Day := Day + 1;
FChart.Data.Add(Day, Last.Close, Last.Close, Last.Close, Last.Close, 0);
ViewChanged(FChart);
end
else
begin
// The forming bar moves: only the newest bar is replaced.
Close := Last.Close * (1 + (Random - 0.5) * 0.01);
FChart.Data.UpdateLast(Last.Open, Max(Last.High, Close), Min(Last.Low, Close), Close, Last.Volume + 25000);
end;
end;
procedure TFinChartForm.CrosshairMoved(Sender: TObject; BarIndex: Integer);
var
Bar: TAtlasBar;
Rsi: Double;
RsiText: string;
begin
if BarIndex < 0 then
begin
FStatus.Caption := '';
Exit;
end;
Bar := FChart.Data[BarIndex];
// Study lines are cached against the data version, so this costs nothing.
Rsi := FChart.Panes[3].Studies[0].Lines(FChart.Data)[0][BarIndex];
if IsNan(Rsi) then RsiText := '-' else RsiText := FormatFloat('0.0', Rsi);
FStatus.Caption := Format('%s O %.2f H %.2f L %.2f C %.2f Vol %.0f RSI %s',
[FormatDateTime('ddddd', Bar.Time), Bar.Open, Bar.High, Bar.Low, Bar.Close, Bar.Volume, RsiText]);
end;
procedure TFinChartForm.ViewChanged(Sender: TObject);
begin
Caption := Format('Atlas Fin: bars %d to %d of %d (drag to scroll, wheel to zoom, drag a pane border, double-click for all)',
[FChart.FirstBar + 1, FChart.FirstBar + FChart.VisibleBars, FChart.Data.Count]);
end;
end.