YOUSEFUL / DELPHI / SAMPLE PROJECTS

Interactive financial chart

VCL application Product: Atlas Fin · Components: TAtlasFinChart, TAtlasBarSeries, TAtlasFinPane, TAtlasFinStudy

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:

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

Project files

Download all 34 projects

The ZIP contains every sample project with its README. See getting started for the search path to add.

Source

InteractiveFinChart.dpr

pascal
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

pascal
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.