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.