YOUSEFUL / DELPHI / COMPONENTS

TAtlasBarSeries

A validated, time-ordered list of OHLCV price bars with live update, time lookup, aggregation and CSV.

Class Unit Atlas.FinData · Inherits TPersistent · Product Atlas Fin

Description

The data behind TAtlasFinChart (Chart.Data) and a standalone container for price history. Each bar is a TAtlasBar. Every change is validated first, so the series is always consistent: every value is finite and within ±1E100, High is at least Open, Close and Low, Low is at most Open and Close, volume is not negative, times are strictly increasing and lie between the years 1 and 9999. A rejected bar or import raises EAtlasFinError and changes nothing.

Bars can only be appended (Add), the newest replaced (UpdateLast), all removed (Clear) or all replaced (LoadCSV, Assign). There is no way to insert, delete or edit a single older bar, and no binding to a TDataSet.

Every change increases Version and raises OnChange, once per BeginUpdate/EndUpdate batch. The series is not thread-safe; change a chart's series on the main thread.

Members

MemberKindWhat it does
CreateconstructorCreates an empty series.
DestroydestructorFrees the bars.
AssignmethodReplaces all bars with a copy of another series' bars and raises OnChange.
BeginUpdatemethodStarts a batch: OnChange is held back until the matching EndUpdate.
EndUpdatemethodEnds a batch.
AddmethodAppends a bar and returns its index.
UpdateLastmethodReplaces the prices and volume of the newest bar and keeps its time: the way to show a bar that is still forming.
ClearmethodRemoves all bars.
IndexAtOrBeforemethodThe index of the bar at Time or, when there is none, of the last bar before it; -1 when Time precedes every bar.
AggregatemethodReturns a new series, which the caller frees, with one bar per calendar week (starting Monday), month or year: the first open, the highest high, the lowest low, the last close and the summed volume.
ClosesmethodA new array of the closing prices, oldest first: the input the indicators expect.
ToCSVmethodThe bars as CSV text: the header time,open,high,low,close,volume, then one line per bar, CR LF line ends.
LoadCSVmethodReplaces all bars with those in Text.
CountpropertyThe number of bars.
BarspropertyA copy of the bar at Index, 0 being the oldest.
VersionpropertyA counter that increases on every change, batched or not.
OnChangeeventRaised after every change, or once at the end of a batch.

Constructors

Create constructor

constructor Create;

Creates an empty series.

Destructors

Destroy destructor

destructor Destroy; override;

Frees the bars. Never free a chart's own Data.

Properties

Count property

property Count: Integer read GetCount;

The number of bars.

Bars property

property Bars[Index: Integer]: TAtlasBar read GetBar; default;

A copy of the bar at Index, 0 being the oldest. Being the default property, Data[I] is enough. An index outside 0..Count - 1 raises EArgumentOutOfRangeException.

pascal
var Last: TAtlasBar;
Last := Data[Data.Count - 1];

Version property

property Version: Integer read FVersion;

A counter that increases on every change, batched or not. Studies use it to know when to recompute.

Events

OnChange event

property OnChange: TNotifyEvent read FOnChange write FOnChange;

Raised after every change, or once at the end of a batch. A chart assigns its own Data.OnChange to repaint and to follow new bars; do not replace it. For a series of your own, assign it freely.

Methods

Assign method

procedure Assign(Source: TPersistent); override;

Replaces all bars with a copy of another series' bars and raises OnChange. Assigning anything but a series raises the usual EConvertError.

BeginUpdate method

procedure BeginUpdate;

Starts a batch: OnChange is held back until the matching EndUpdate. Batches nest.

pascal
Data.BeginUpdate;
try
  Data.Clear;
  Data.Add(EncodeDate(2026, 9, 14), 10, 12, 9, 11, 1500);
  Data.Add(EncodeDate(2026, 9, 15), 11, 13, 10, 12.5, 1800);
finally
  Data.EndUpdate;   // one OnChange, one repaint
end;

EndUpdate method

procedure EndUpdate;

Ends a batch. When the outermost batch ends and anything changed, OnChange is raised once. Extra calls are ignored.

Add method

function Add(const Time: TDateTime; const Open, High, Low, Close: Double; const Volume: Double = 0): Integer;

Appends a bar and returns its index. Raises EAtlasFinError and adds nothing when a value is NaN, infinite or beyond ±1E100, the time is outside the years 1..9999, High is below Open, Close or Low, Low is above Open or Close, Volume is negative, or Time is not later than the last bar's time.

pascal
Data.Add(EncodeDate(2026, 9, 21), 101.5, 103.2, 100.8, 102.9, 1250000);

UpdateLast method

procedure UpdateLast(const Open, High, Low, Close, Volume: Double);

Replaces the prices and volume of the newest bar and keeps its time: the way to show a bar that is still forming. Validated like Add; raises EAtlasFinError when the series is empty or the values are inconsistent, leaving the bar as it was.

pascal
var Bar: TAtlasBar; Price: Double;
Price := 103.4;
Bar := Data[Data.Count - 1];
Data.UpdateLast(Bar.Open, Max(Bar.High, Price), Min(Bar.Low, Price), Price, Bar.Volume + 100);

Clear method

procedure Clear;

Removes all bars. Does nothing, and raises no OnChange, on an empty series.

IndexAtOrBefore method

function IndexAtOrBefore(const Time: TDateTime): Integer;

The index of the bar at Time or, when there is none, of the last bar before it; -1 when Time precedes every bar. A binary search.

pascal
var I: Integer;
I := Data.IndexAtOrBefore(EncodeDate(2026, 1, 1));   // the bar at 1 January 2026 00:00, or the last one before it

Aggregate method

function Aggregate(Period: TAtlasBarPeriod): TAtlasBarSeries;

Returns a new series, which the caller frees, with one bar per calendar week (starting Monday), month or year: the first open, the highest high, the lowest low, the last close and the summed volume. Each new bar's time is the start of its period (a Monday, the 1st of the month, or 1 January), even when the first bar in it is later. There is no aggregation to days or intraday intervals.

pascal
var Weekly: TAtlasBarSeries;
Weekly := Data.Aggregate(bpWeek);
try
  Chart.Data := Weekly;
finally
  Weekly.Free;
end;

Closes method

function Closes: TArray<Double>;

A new array of the closing prices, oldest first: the input the indicators expect.

pascal
var Sma: TArray<Double>;
Sma := TAtlasIndicators.SMA(Data.Closes, 20);

ToCSV method

function ToCSV: string;

The bars as CSV text: the header time,open,high,low,close,volume, then one line per bar, CR LF line ends. Times are ISO 8601 without a zone, date only (2026-09-07) when the time of day is zero and to the second (2026-09-07T09:30:00) otherwise, so milliseconds are lost. Numbers use . and up to 15 significant digits, so computed prices can come back from LoadCSV differing in their last bits.

pascal
TFile.WriteAllText('prices.csv', Data.ToCSV);

LoadCSV method

procedure LoadCSV(const Text: string);

Replaces all bars with those in Text. The first line must be time,open,high,low,close,volume (any letter case, spaces ignored, a leading byte order mark removed), even when the rows leave the volume out. Each further line has five or six comma-separated fields; blank lines are skipped; quoted fields are not supported. Times are ISO 8601: text without a zone is kept as written, text with Z or an offset is converted to UTC. Numbers use . whatever the locale. Rows must be in increasing time order and pass the same checks as Add. On any error it raises EAtlasFinError naming the line, and the series is left exactly as it was. A header without rows empties the series.

pascal
Data.LoadCSV('time,open,high,low,close,volume'#10 +
  '2026-09-18,10,12,9,11,1500'#10 +
  '2026-09-21T09:30:00,11,11.5,10.8,11.2');

Examples

Load daily bars and view them weekly

Reads a CSV file, keeps nothing if any row is invalid, and shows weekly bars on a chart.

pascal
uses System.SysUtils, System.IOUtils, Atlas.FinData, Atlas.FinChart;

procedure ShowWeekly(Chart: TAtlasFinChart; const FileName: string);
var
  Daily, Weekly: TAtlasBarSeries;
begin
  Daily := TAtlasBarSeries.Create;
  try
    Daily.LoadCSV(TFile.ReadAllText(FileName));   // EAtlasFinError names the bad line
    Weekly := Daily.Aggregate(bpWeek);
    try
      Chart.Data := Weekly;   // copies the bars
      Chart.ShowAll;
    finally
      Weekly.Free;
    end;
  finally
    Daily.Free;
  end;
end;

Build minute bars from trades

Called on the main thread for every trade: updates the forming bar or opens the next one. A trade older than the newest bar raises EAtlasFinError.

pascal
uses System.Math, System.DateUtils, Atlas.FinData;

procedure AddTrade(Data: TAtlasBarSeries; const Time: TDateTime; const Price, Size: Double);
var
  Minute: TDateTime;
  Bar: TAtlasBar;
begin
  Minute := RecodeSecond(RecodeMilliSecond(Time, 0), 0);
  if (Data.Count > 0) and (Data[Data.Count - 1].Time = Minute) then
  begin
    Bar := Data[Data.Count - 1];
    Data.UpdateLast(Bar.Open, Max(Bar.High, Price), Min(Bar.Low, Price), Price, Bar.Volume + Size);
  end
  else
    Data.Add(Minute, Price, Price, Price, Price, Size);
end;

TAtlasBar · TAtlasBarPeriod · EAtlasFinError · TAtlasFinChart

Also in Atlas Fin

TAtlasFinChart