unit Myc.Trade.DataPoint; interface 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. IDataSeries = interface function GetCount: Int64; function GetItems(Idx: Int64): TDataPoint; function GetTotalCount: Int64; 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; // The total number of items ever added to the series. property TotalCount: Int64 read GetTotalCount; end; IDataArray = interface(IDataSeries) procedure Add(const Data: TDataPoint); overload; procedure Add(const Data: array of TDataPoint; NumToAdd: Integer = -1); overload; procedure Clear; end; // Interface Helper for IDataSeries. // Provides a safe, value-type-like wrapper around the interface. TDataSeries = record strict private class var FNull: IDataSeries; private FDataSeries: IDataSeries; FMaxLookback: Int64; class constructor CreateClass; function GetCount: Int64; function GetItems(Idx: Int64): TDataPoint; function GetData(Idx: Int64): T; function GetTime(Idx: Int64): TDateTime; function GetMaxLookback: Int64; function GetTotalCount: Int64; public constructor Create(ADataSeries: IDataSeries); class operator Initialize(out Dest: TDataSeries); class operator Finalize(var Dest: TDataSeries); class operator Implicit(const A: TDataSeries): IDataSeries; class operator Implicit(const A: IDataSeries): TDataSeries; class function CreateArray(MaxLookback: Int64): IDataArray; static; // 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 property Null: IDataSeries read FNull; property Count: Int64 read GetCount; property Items[Idx: Int64]: TDataPoint read GetItems; default; property Data[Idx: Int64]: T read GetData; property Time[Idx: Int64]: TDateTime read GetTime; property MaxLookback: Int64 read GetMaxLookback; property TotalCount: Int64 read GetTotalCount; end; implementation uses Myc.Trade.Core.DataPoint; { 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 Interface Helper } class constructor TDataSeries.CreateClass; begin FNull := TNullDataSeries.Create; end; constructor TDataSeries.Create(ADataSeries: IDataSeries); begin if Assigned(ADataSeries) then FDataSeries := ADataSeries else FDataSeries := FNull; end; class function TDataSeries.CreateArray(MaxLookback: Int64): IDataArray; begin Result := TDataArray.Create(MaxLookback); end; class operator TDataSeries.Finalize(var Dest: TDataSeries); begin Dest.FDataSeries := nil; end; class operator TDataSeries.Initialize(out Dest: TDataSeries); begin Dest.FDataSeries := FNull; end; class operator TDataSeries.Implicit(const A: TDataSeries): IDataSeries; begin Result := A.FDataSeries; end; class operator TDataSeries.Implicit(const A: IDataSeries): TDataSeries; begin Result.Create(A); end; function TDataSeries.GetCount: Int64; begin Result := FDataSeries.GetCount; end; function TDataSeries.GetItems(Idx: Int64): TDataPoint; begin Result := FDataSeries[Idx]; end; function TDataSeries.GetData(Idx: Int64): T; begin Result := FDataSeries[Idx].Data; end; function TDataSeries.GetTime(Idx: Int64): TDateTime; begin Result := FDataSeries[Idx].Time; end; function TDataSeries.GetMaxLookback: Int64; begin Result := FMaxLookback; end; function TDataSeries.GetTotalCount: Int64; begin Result := FDataSeries.GetTotalCount; end; function TDataSeries.IndexOf(TimeStamp: TDateTime): Int64; var low, high, mid: Int64; dataPointTime: TDateTime; begin Result := -1; if Count = 0 then Exit; low := 0; high := Count - 1; while (low <= high) do begin mid := low + (high - low) div 2; dataPointTime := GetItems(mid).Time; // Use GetItems to stay within the class if (dataPointTime = TimeStamp) then begin Result := mid; break; end else if (dataPointTime < TimeStamp) then begin Result := mid; high := mid - 1; end else begin low := mid + 1; end; end; end; end.