diff --git a/Src/Myc.Trade.DataPoint.pas b/Src/Myc.Trade.DataPoint.pas index 1fd6c36..fbf51d5 100644 --- a/Src/Myc.Trade.DataPoint.pas +++ b/Src/Myc.Trade.DataPoint.pas @@ -6,25 +6,24 @@ 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. + // A data record for an Ask/Bid price pair. 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. + // 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 array whose most recently added element has index 0. - // This managed record stores a series of TDataPoint elements. - TDataSeries = record + // 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 @@ -36,18 +35,19 @@ type 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; @@ -55,10 +55,15 @@ type implementation -uses - Winapi.Windows; +{ TAskBidItem } -{ TDataPoint } +constructor TAskBidItem.Create(AAsk, ABid: Single); +begin + Ask := AAsk; + Bid := ABid; +end; + +{ TDataPoint } constructor TDataPoint.Create(ATime: TDateTime; const AData: T); begin @@ -70,8 +75,8 @@ end; 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). + // Enforce chronological order: new items cannot be older than the newest existing item. + // This is a prerequisite for the binary search in IndexOf to work correctly. Assert((Length(FChunks) = 0) or (Data.Time >= GetItems(0).Time), 'Time stamp older than last item'); var ci := FCount div ChunkSize; @@ -86,7 +91,13 @@ begin end; FChunks[ci][di] := Data; - inc(FCount); + Inc(FCount); +end; + +class operator TDataSeries.Finalize(var Dest: TDataSeries); +begin + Dest.FChunks := nil; + Dest.FCount := 0; end; function TDataSeries.GetCount: Int64; @@ -97,72 +108,49 @@ end; function TDataSeries.GetItems(Idx: Int64): TDataPoint; begin Assert((Idx >= 0) and (Idx < FCount)); + // Convert logical index (0 = newest) to physical index (0 = oldest). 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; + Dest.FChunks := nil; end; -constructor TAskBidItem.Create(AAsk, ABid: Single); +function TDataSeries.IndexOf(TimeStamp: TDateTime): Int64; +var + low, high, mid: Int64; + dataPointTime: TDateTime; begin - Ask := AAsk; - Bid := ABid; + Result := -1; + if FCount = 0 then + Exit; + + low := 0; + high := FCount - 1; + + while (low <= high) do + begin + mid := low + (high - low) div 2; + dataPointTime := GetItems(mid).Time; + + 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; end.