YOUSEFUL / DELPHI / COMPONENTS
TAtlasBarSeries
A validated, time-ordered list of OHLCV price bars with live update, time lookup, aggregation and CSV.
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
| Member | Kind | What it does |
|---|---|---|
Create | constructor | Creates an empty series. |
Destroy | destructor | Frees the bars. |
Assign | method | Replaces all bars with a copy of another series' bars and raises OnChange. |
BeginUpdate | method | Starts a batch: OnChange is held back until the matching EndUpdate. |
EndUpdate | method | Ends a batch. |
Add | method | Appends a bar and returns its index. |
UpdateLast | method | Replaces the prices and volume of the newest bar and keeps its time: the way to show a bar that is still forming. |
Clear | method | Removes all bars. |
IndexAtOrBefore | method | The index of the bar at Time or, when there is none, of the last bar before it; -1 when Time precedes every bar. |
Aggregate | method | 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. |
Closes | method | A new array of the closing prices, oldest first: the input the indicators expect. |
ToCSV | method | The bars as CSV text: the header time,open,high,low,close,volume, then one line per bar, CR LF line ends. |
LoadCSV | method | Replaces all bars with those in Text. |
Count | property | The number of bars. |
Bars | property | A copy of the bar at Index, 0 being the oldest. |
Version | property | A counter that increases on every change, batched or not. |
OnChange | event | Raised 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.
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.
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.
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.
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.
var I: Integer;
I := Data.IndexAtOrBefore(EncodeDate(2026, 1, 1)); // the bar at 1 January 2026 00:00, or the last one before itAggregate 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.
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.
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.
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.
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.
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.
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;Related
TAtlasBar · TAtlasBarPeriod · EAtlasFinError · TAtlasFinChart