unit Myc.Trade.Core.DataPoint; interface uses Myc.Trade.DataPoint; type // The implementation class for IDataSeries. TDataArray = class(TInterfacedObject, IDataSeries, IDataSeriesWriter) const ChunkSize = 1024; type TChunk = TArray>; private FChunks: TArray; FCount: Int64; FLookback: Int64; FTotalCount: Int64; function LogicalToPhysicalIndex(LogicalIndex: Int64): Int64; inline; function IsIndexInLimits(LogicalIndex: Int64): Boolean; procedure Trim; // IDataSeries implementation function GetCount: Int64; function GetItems(Idx: Int64): TDataPoint; function GetLookback: Int64; function GetTotalCount: Int64; function GetWriter: IDataSeriesWriter; public constructor Create(ALookback: Int64); procedure Add(const Data: TDataPoint); overload; procedure Add(const Data: array of TDataPoint; NumToAdd: Integer = -1); overload; procedure Clear; function Immutable: IDataSeries; property Lookback: Int64 read GetLookback; end; // Null object implementation for IDataSeries TNullDataSeries = class(TInterfacedObject, IDataSeries) strict private class var FNull: IDataSeries; private class constructor CreateClass; function GetWriter: IDataSeriesWriter; public function GetCount: Int64; function GetItems(Idx: Int64): TDataPoint; function GetTotalCount: Int64; function IndexOf(TimeStamp: TDateTime): Int64; function Immutable: IDataSeries; class property Null: IDataSeries read FNull; end; // The implementation class for IDataSeries. TStaticDataSeries = class(TInterfacedObject, IDataSeries) private FChunks: TArray.TChunk>; FCount: Int64; FLookback: Int64; FTotalCount: Int64; function LogicalToPhysicalIndex(LogicalIndex: Int64): Int64; inline; function IsIndexInLimits(LogicalIndex: Int64): Boolean; // IDataSeries implementation function GetCount: Int64; function GetItems(Idx: Int64): TDataPoint; function GetLookback: Int64; function GetTotalCount: Int64; function GetWriter: IDataSeriesWriter; public constructor Create(const AChunks: TArray.TChunk>; ACount, ALookback, ATotalCount: Int64); function Immutable: IDataSeries; property Lookback: Int64 read GetLookback; end; implementation { TDataArray } constructor TDataArray.Create(ALookback: Int64); begin inherited Create; FLookback := ALookback; FCount := 0; FTotalCount := 0; FChunks := nil; end; procedure TDataArray.Add(const Data: TDataPoint); begin Assert((FCount = 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 SetLength(FChunks, ci + 1); SetLength(FChunks[ci], ChunkSize); end; FChunks[ci][di] := Data; Inc(FCount); Inc(FTotalCount); Trim; end; procedure TDataArray.Add(const Data: array of TDataPoint; NumToAdd: Integer = -1); var sourceIdx, itemsToCopy, spaceInChunk: Integer; ci, di: Int64; i: Integer; resolvedNumToAdd: Integer; begin if NumToAdd < 0 then resolvedNumToAdd := Length(Data) else resolvedNumToAdd := NumToAdd; if resolvedNumToAdd = 0 then Exit; Assert(resolvedNumToAdd <= Length(Data), 'NumToAdd cannot be larger than the source array'); for i := 1 to resolvedNumToAdd - 1 do Assert(Data[i].Time >= Data[i - 1].Time, 'Input array for Add is not chronologically sorted'); Assert((FCount = 0) or (Data[0].Time >= GetItems(0).Time), 'First new item is older than last existing item'); Inc(FTotalCount, resolvedNumToAdd); var newTotalCount := FCount + resolvedNumToAdd; var requiredChunks := (newTotalCount + ChunkSize - 1) div ChunkSize; if requiredChunks = 0 then requiredChunks := 1; if requiredChunks > Length(FChunks) then SetLength(FChunks, requiredChunks); sourceIdx := 0; while sourceIdx < resolvedNumToAdd do begin ci := FCount div ChunkSize; di := FCount mod ChunkSize; if di = 0 then SetLength(FChunks[ci], ChunkSize); spaceInChunk := ChunkSize - di; itemsToCopy := resolvedNumToAdd - sourceIdx; if spaceInChunk < itemsToCopy then itemsToCopy := spaceInChunk; System.Move(Data[sourceIdx], FChunks[ci][di], itemsToCopy * SizeOf(TDataPoint)); Inc(FCount, itemsToCopy); Inc(sourceIdx, itemsToCopy); Trim; end; end; procedure TDataArray.Clear; begin FChunks := nil; FCount := 0; FTotalCount := 0; end; function TDataArray.Immutable: IDataSeries; begin Result := TStaticDataSeries.Create(FChunks, FCount, FLookback, FTotalCount); end; function TDataArray.GetCount: Int64; begin Result := FCount; if (FLookback > 0) and (Result > FLookback) then Result := FLookback; end; function TDataArray.GetItems(Idx: Int64): TDataPoint; var physicalIndex: Int64; begin Assert(IsIndexInLimits(Idx), 'Index is outside of the configured Lookback limits.'); physicalIndex := LogicalToPhysicalIndex(Idx); Result := FChunks[physicalIndex div ChunkSize][physicalIndex mod ChunkSize]; end; function TDataArray.GetLookback: Int64; begin Result := FLookback; end; function TDataArray.GetTotalCount: Int64; begin Result := FTotalCount; end; function TDataArray.GetWriter: IDataSeriesWriter; begin Result := Self; end; function TDataArray.IsIndexInLimits(LogicalIndex: Int64): Boolean; begin Result := (FLookback <= 0) or (LogicalIndex < FLookback); end; function TDataArray.LogicalToPhysicalIndex(LogicalIndex: Int64): Int64; begin Assert((LogicalIndex >= 0) and (LogicalIndex < FCount), 'Logical index is out of bounds.'); Result := FCount - LogicalIndex - 1; end; procedure TDataArray.Trim; var itemsToRemove, chunksToRemove: Int64; begin if (FCount = 0) or (FLookback <= 0) then Exit; itemsToRemove := 0; if FCount > FLookback then itemsToRemove := FCount - FLookback; if itemsToRemove <= 0 then Exit; chunksToRemove := itemsToRemove div ChunkSize; if chunksToRemove > 0 then begin var itemsInFreedChunks := chunksToRemove * ChunkSize; FChunks := Copy(FChunks, chunksToRemove, Length(FChunks) - chunksToRemove); FCount := FCount - itemsInFreedChunks; end; end; { TNullDataSeries Interface Helper } class constructor TNullDataSeries.CreateClass; begin FNull := TNullDataSeries.Create; end; function TNullDataSeries.Immutable: IDataSeries; begin Result := FNull; end; function TNullDataSeries.GetCount: Int64; begin Result := 0; end; function TNullDataSeries.GetItems(Idx: Int64): TDataPoint; begin Assert(false, 'Index out of bounds.'); Result := Default(TDataPoint); end; function TNullDataSeries.GetTotalCount: Int64; begin Result := 0; end; function TNullDataSeries.GetWriter: IDataSeriesWriter; begin Result := nil; end; function TNullDataSeries.IndexOf(TimeStamp: TDateTime): Int64; begin Result := -1; end; { TStaticDataSeries } constructor TStaticDataSeries.Create(const AChunks: TArray.TChunk>; ACount, ALookback, ATotalCount: Int64); begin inherited Create; FChunks := AChunks; FCount := ACount; FLookback := ALookback; FTotalCount := ATotalCount; end; function TStaticDataSeries.Immutable: IDataSeries; begin Result := Self; end; function TStaticDataSeries.GetCount: Int64; begin Result := FCount; if (FLookback > 0) and (Result > FLookback) then Result := FLookback; end; function TStaticDataSeries.GetItems(Idx: Int64): TDataPoint; var physicalIndex: Int64; begin Assert(IsIndexInLimits(Idx), 'Index is outside of the configured Lookback limits.'); physicalIndex := LogicalToPhysicalIndex(Idx); Result := FChunks[physicalIndex div TDataArray.ChunkSize][physicalIndex mod TDataArray.ChunkSize]; end; function TStaticDataSeries.GetLookback: Int64; begin Result := FLookback; end; function TStaticDataSeries.GetTotalCount: Int64; begin Result := FTotalCount; end; function TStaticDataSeries.GetWriter: IDataSeriesWriter; begin Result := nil; end; function TStaticDataSeries.IsIndexInLimits(LogicalIndex: Int64): Boolean; begin Result := (FLookback <= 0) or (LogicalIndex < FLookback); end; function TStaticDataSeries.LogicalToPhysicalIndex(LogicalIndex: Int64): Int64; begin Assert((LogicalIndex >= 0) and (LogicalIndex < FCount), 'Logical index is out of bounds.'); Result := FCount - LogicalIndex - 1; end; end.