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; IDataSeriesWriter = interface procedure Add(const Data: TDataPoint); overload; procedure Add(const Data: array of TDataPoint; NumToAdd: Integer = -1); overload; procedure Clear; 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; function Immutable: IDataSeries; function GetWriter: IDataSeriesWriter; 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; property Writer: IDataSeriesWriter read GetWriter; end; // Interface Helper for IDataSeries. // Provides a safe, value-type-like wrapper around the interface. TDataSeries = record private FDataSeries: IDataSeries; function GetCount: Int64; function GetItems(Idx: Int64): TDataPoint; function GetData(Idx: Int64): T; function GetIsWriteable: Boolean; function GetTime(Idx: Int64): TDateTime; class function GetNull: IDataSeries; static; 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 CreateWriteable(MaxLookback: Int64): TDataSeries; static; // Create an immutable version of the given series. function Immutable: TDataSeries; // Writing (only if IsWriteable=true) procedure Add(const Data: array of TDataPoint; NumToAdd: Integer = -1); 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 property Null: IDataSeries read GetNull; property Count: Int64 read GetCount; property Items[Idx: Int64]: TDataPoint read GetItems; default; property Data[Idx: Int64]: T read GetData; property IsWriteable: Boolean read GetIsWriteable; property Time[Idx: Int64]: TDateTime read GetTime; 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; constructor TDataSeries.Create(ADataSeries: IDataSeries); begin if Assigned(ADataSeries) then FDataSeries := ADataSeries else FDataSeries := Null; end; procedure TDataSeries.Add(const Data: array of TDataPoint; NumToAdd: Integer = -1); begin Assert(IsWriteable); FDataSeries.Writer.Add(Data, NumToAdd); end; procedure TDataSeries.Clear; begin Assert(IsWriteable); FDataSeries.Writer.Clear; end; function TDataSeries.Immutable: TDataSeries; begin Result := FDataSeries.Immutable; end; class function TDataSeries.CreateWriteable(MaxLookback: Int64): TDataSeries; 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 := Null; 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.GetIsWriteable: Boolean; begin Result := Assigned(FDataSeries.Writer); end; function TDataSeries.GetTime(Idx: Int64): TDateTime; begin Result := FDataSeries[Idx].Time; end; class function TDataSeries.GetNull: IDataSeries; begin Result := TNullDataSeries.Null; 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.