unit Myc.Trade.DataPoint; interface uses System.SysUtils; type // A data record for an Ask/Bid price pair. TAskBidItem = packed record Ask: Single; Bid: Single; constructor Create(AAsk, ABid: Single); end; // Represents a time-stamped data point in a series. TDataPoint = record Time: TDateTime; Data: T; constructor Create(ATime: TDateTime; const AData: T); end; // A time-ordered series of data points, optimized for chronological additions. // The most recently added element has the logical index 0. TDataSeries = record private const ChunkSize = 1024; type TChunk = TArray>; private FChunks: TArray; FCount: Int64; // Converts a logical index (0=newest) to a physical storage index (0=oldest). function LogicalToPhysicalIndex(LogicalIndex: Int64): Int64; inline; function GetCount: Int64; function GetData(Idx: Int64): T; function GetItems(Idx: Int64): TDataPoint; function GetTime(Idx: Int64): TDateTime; procedure SetData(Idx: Int64; const Value: T); public // Adds a new data point to the series. procedure Add(const Data: TDataPoint); // Clears all data from the series. procedure Clear; // Searches for a data point by its timestamp. // Returns the logical index of the matching item. // If no exact match, returns the index of the item immediately preceding the timestamp. // Returns -1 if the timestamp is before the oldest item in the series. function IndexOf(TimeStamp: TDateTime): Int64; class operator Initialize(out Dest: TDataSeries); class operator Finalize(var Dest: TDataSeries); property Count: Int64 read GetCount; // Accesses data points by their logical index. // Index 0 is the newest element, Index (Count - 1) is the oldest. property Items[Idx: Int64]: TDataPoint read GetItems; default; property Time[Idx: Int64]: TDateTime read GetTime; property Data[Idx: Int64]: T read GetData write SetData; end; implementation { TAskBidItem } constructor TAskBidItem.Create(AAsk, ABid: Single); begin Ask := AAsk; Bid := ABid; end; { TDataPoint } constructor TDataPoint.Create(ATime: TDateTime; const AData: T); begin Time := ATime; Data := AData; end; { TDataSeries } procedure TDataSeries.Add(const Data: TDataPoint); begin // Enforce chronological order for new items. Assert((FCount = 0) or (Data.Time >= GetTime(0)), 'Time stamp older than last item'); var ci := FCount div ChunkSize; var di := FCount mod ChunkSize; if (di = 0) then begin SetLength(FChunks, ci + 1); SetLength(FChunks[ci], ChunkSize); end; FChunks[ci][di] := Data; Inc(FCount); end; procedure TDataSeries.Clear; begin FChunks := nil; FCount := 0; end; class operator TDataSeries.Finalize(var Dest: TDataSeries); begin Dest.Clear; end; function TDataSeries.GetCount: Int64; begin Result := FCount; end; function TDataSeries.GetData(Idx: Int64): T; var physicalIndex: Int64; begin physicalIndex := LogicalToPhysicalIndex(Idx); Result := FChunks[physicalIndex div ChunkSize][physicalIndex mod ChunkSize].Data; end; function TDataSeries.GetItems(Idx: Int64): TDataPoint; var physicalIndex: Int64; begin physicalIndex := LogicalToPhysicalIndex(Idx); Result := FChunks[physicalIndex div ChunkSize][physicalIndex mod ChunkSize]; end; function TDataSeries.GetTime(Idx: Int64): TDateTime; var physicalIndex: Int64; begin physicalIndex := LogicalToPhysicalIndex(Idx); Result := FChunks[physicalIndex div ChunkSize][physicalIndex mod ChunkSize].Time; end; class operator TDataSeries.Initialize(out Dest: TDataSeries); begin Dest.FCount := 0; Dest.FChunks := nil; end; function TDataSeries.IndexOf(TimeStamp: TDateTime): Int64; var low, high, mid: Int64; dataPointTime: TDateTime; begin Result := -1; if FCount = 0 then Exit; low := 0; high := FCount - 1; while (low <= high) do begin mid := low + (high - low) div 2; dataPointTime := GetTime(mid); if (dataPointTime = TimeStamp) then begin Result := mid; Exit; end else if (dataPointTime < TimeStamp) then begin Result := mid; high := mid - 1; end else // dataPointTime > TimeStamp begin low := mid + 1; end; end; end; function TDataSeries.LogicalToPhysicalIndex(LogicalIndex: Int64): Int64; begin Assert((LogicalIndex >= 0) and (LogicalIndex < FCount), 'Logical index is out of bounds.'); Result := FCount - LogicalIndex - 1; end; procedure TDataSeries.SetData(Idx: Int64; const Value: T); var physicalIndex: Int64; begin physicalIndex := LogicalToPhysicalIndex(Idx); FChunks[physicalIndex div ChunkSize][physicalIndex mod ChunkSize].Data := Value; end; end.