unit Myc.Trade.DataPoint; interface uses System.SysUtils; type // A specific data record structure for Ask and Bid prices. // Contains Ask and Bid prices as Single-precision floating-point numbers. TAskBidItem = packed record Ask: Single; Bid: Single; constructor Create(AAsk, ABid: Single); end; // Represents a single data point in a time series. // Combines a timestamp with generic data. TDataPoint = record Time: TDateTime; Data: T; constructor Create(ATime: TDateTime; const AData: T); end; // A time-ordered array whose most recently added element has index 0. // This managed record stores a series of TDataPoint elements. TDataSeries = record const ChunkSize = 1024; type TChunk = TArray>; private FChunks: TArray; FCount: Int64; function GetCount: Int64; function GetItems(Idx: Int64): TDataPoint; public // Adds a new data point to the series. // Asserts that the new data point's time is not older than the current newest item. procedure Add(const Data: TDataPoint); // 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; // Initializes the TDataSeries record. class operator Initialize(out Dest: TDataSeries); class operator Finalize(var Dest: TDataSeries); // The total number of data points in the series. 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; end; implementation uses Winapi.Windows; { TDataPoint } constructor TDataPoint.Create(ATime: TDateTime; const AData: T); begin Time := ATime; Data := AData; end; { TDataSeries } procedure TDataSeries.Add(const Data: TDataPoint); begin // Assert that the new data point's time is NOT OLDER than the current newest item. // This means data must be added in chronologically ascending order (or equal timestamp). Assert((Length(FChunks) = 0) or (Data.Time >= GetItems(0).Time), 'Time stamp older than last item'); var ci := FCount div ChunkSize; var di := FCount mod ChunkSize; if di = 0 then begin Assert(ci = Length(FChunks)); Assert(di = 0); SetLength(FChunks, ci + 1); SetLength(FChunks[ci], ChunkSize); end; FChunks[ci][di] := Data; inc(FCount); end; function TDataSeries.GetCount: Int64; begin Result := FCount; end; function TDataSeries.GetItems(Idx: Int64): TDataPoint; begin Assert((Idx >= 0) and (Idx < FCount)); Idx := FCount - Idx - 1; Result := FChunks[Idx div ChunkSize][Idx mod ChunkSize]; end; function TDataSeries.IndexOf(TimeStamp: TDateTime): Int64; var low, high, mid: Int64; dataPointTime: TDateTime; debugStr: string; begin Result := -1; if FCount = 0 then Exit; low := 0; high := FCount - 1; // Start der Debug-Ausgabe OutputDebugString(PChar(Format('--- IndexOf Search for %.15f ---', [TimeStamp]))); while (low <= high) do begin mid := low + (high - low) div 2; dataPointTime := GetItems(mid).Time; // Ausgabe der Werte in jeder Iteration debugStr := Format('low: %d, high: %d, mid: %d, dataPointTime: %.15f', [low, high, mid, dataPointTime]); OutputDebugString(PChar(debugStr)); if (dataPointTime = TimeStamp) then begin OutputDebugString(' -> Match found!'); Result := mid; Exit; end else if (dataPointTime < TimeStamp) then begin OutputDebugString(' -> dataPointTime < TimeStamp'); Result := mid; high := mid - 1; end else // dataPointTime > TimeStamp begin OutputDebugString(' -> dataPointTime > TimeStamp'); low := mid + 1; end; end; OutputDebugString(PChar('--- Search Finished ---')); end; class operator TDataSeries.Finalize(var Dest: TDataSeries); begin Dest.FChunks := nil; Dest.FCount := 0; end; class operator TDataSeries.Initialize(out Dest: TDataSeries); begin Dest.FCount := 0; end; constructor TAskBidItem.Create(AAsk, ABid: Single); begin Ask := AAsk; Bid := ABid; end; end.