unit Myc.Trade.Core.DataPoint; interface uses Myc.Trade.DataPoint; type // The implementation class for IDataSeries. TDataArray = class(TInterfacedObject, IDataSeries, IDataArray) private const ChunkSize = 1024; type TChunk = TArray>; private FChunks: TArray; FCount: Int64; FMaxLookback: 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 GetMaxLookback: Int64; function GetTotalCount: Int64; public constructor Create(AMaxLookback: Int64); procedure Add(const Data: TDataPoint); overload; procedure Add(const Data: array of TDataPoint; NumToAdd: Integer = -1); overload; procedure Clear; property MaxLookback: Int64 read GetMaxLookback; end; type // Null object implementation for IDataSeries TNullDataSeries = class(TInterfacedObject, IDataSeries) public function GetCount: Int64; function GetItems(Idx: Int64): TDataPoint; function GetTotalCount: Int64; function IndexOf(TimeStamp: TDateTime): Int64; end; implementation { TDataArray } constructor TDataArray.Create(AMaxLookback: Int64); begin inherited Create; FMaxLookback := AMaxLookback; 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.GetCount: Int64; begin Result := FCount; if (FMaxLookback > 0) and (Result > FMaxLookback) then Result := FMaxLookback; 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.GetMaxLookback: Int64; begin Result := FMaxLookback; end; function TDataArray.GetTotalCount: Int64; begin Result := FTotalCount; end; function TDataArray.IsIndexInLimits(LogicalIndex: Int64): Boolean; begin Result := (FMaxLookback <= 0) or (LogicalIndex < FMaxLookback); 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 (FMaxLookback <= 0) then Exit; itemsToRemove := 0; if FCount > FMaxLookback then itemsToRemove := FCount - FMaxLookback; 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; 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.IndexOf(TimeStamp: TDateTime): Int64; begin Result := -1; end; end.