Chart
This commit is contained in:
+441
-223
@@ -3,11 +3,13 @@ unit Myc.Trade.Core.DataPoint;
|
||||
interface
|
||||
|
||||
uses
|
||||
System.Generics.Collections,
|
||||
System.TimeSpan,
|
||||
Myc.Trade.DataPoint;
|
||||
|
||||
type
|
||||
// The implementation class for IDataSeries<T>.
|
||||
TDataArray<T> = class(TInterfacedObject, IDataSeries<T>, IDataSeriesWriter<T>)
|
||||
TMycDataArray<T> = record
|
||||
private
|
||||
const
|
||||
ChunkSize = 1024;
|
||||
type
|
||||
@@ -15,26 +17,34 @@ type
|
||||
private
|
||||
FChunks: TArray<TChunk>;
|
||||
FCount: Int64;
|
||||
FLookback: Int64;
|
||||
FTotalCount: Int64;
|
||||
|
||||
function LogicalToPhysicalIndex(LogicalIndex: Int64): Int64; inline;
|
||||
function IsIndexInLimits(LogicalIndex: Int64): Boolean;
|
||||
procedure Trim;
|
||||
function GetItems(Idx: Int64): TDataPoint<T>; inline;
|
||||
public
|
||||
constructor Create(const AChunks: TArray<TChunk>; ACount: Int64);
|
||||
function Add(const Data: array of TDataPoint<T>; First, Count, Lookback: Int64): TMycDataArray<T>;
|
||||
class function CreateEmpty: TMycDataArray<T>; static;
|
||||
// Helper to create a data array from a raw TArray.
|
||||
class function CreateFromArray(const AData: TArray<TDataPoint<T>>; First, Count: Integer): TMycDataArray<T>; static;
|
||||
property Count: Int64 read FCount;
|
||||
property Items[Idx: Int64]: TDataPoint<T> read GetItems; default;
|
||||
end;
|
||||
|
||||
// IDataSeries<T> implementation
|
||||
// The implementation class for IDataSeries<T>.
|
||||
TMycDataSeries<T> = class(TInterfacedObject, IDataSeries<T>)
|
||||
private
|
||||
FData: TMycDataArray<T>;
|
||||
FLookback: Int64;
|
||||
FTotalCount: Int64;
|
||||
function GetCount: Int64;
|
||||
function GetItems(Idx: Int64): TDataPoint<T>;
|
||||
function GetLookback: Int64;
|
||||
function GetTotalCount: Int64;
|
||||
function GetWriter: IDataSeriesWriter<T>;
|
||||
public
|
||||
constructor Create(ALookback: Int64);
|
||||
procedure Add(const Data: TDataPoint<T>); overload;
|
||||
procedure Add(const Data: array of TDataPoint<T>; NumToAdd: Integer = -1); overload;
|
||||
procedure Clear;
|
||||
function Immutable: IDataSeries<T>;
|
||||
property Lookback: Int64 read GetLookback;
|
||||
constructor Create(ALookback: Int64; const AData: TMycDataArray<T>; ATotalCount: Int64);
|
||||
destructor Destroy; override;
|
||||
function Add(const Data: TArray<TDataPoint<T>>; First, Count: Integer): IDataSeries<T>;
|
||||
class function CreateDataSeries(Lookback: Int64; const AData: TMycDataArray<T>; ATotalCount: Int64): IDataSeries<T>; static;
|
||||
end;
|
||||
|
||||
// Null object implementation for IDataSeries<T>
|
||||
@@ -43,217 +53,247 @@ type
|
||||
class var
|
||||
FNull: IDataSeries<T>;
|
||||
private
|
||||
class constructor CreateClass;
|
||||
function GetWriter: IDataSeriesWriter<T>;
|
||||
public
|
||||
FTotalCount: Int64;
|
||||
function GetCount: Int64;
|
||||
function GetItems(Idx: Int64): TDataPoint<T>;
|
||||
function GetTotalCount: Int64;
|
||||
function IndexOf(TimeStamp: TDateTime): Int64;
|
||||
function Immutable: IDataSeries<T>;
|
||||
function GetLookback: Int64;
|
||||
class constructor CreateClass;
|
||||
public
|
||||
constructor Create(ATotalCount: Int64);
|
||||
function Add(const Data: TArray<TDataPoint<T>>; First, Count: Integer): IDataSeries<T>;
|
||||
class property Null: IDataSeries<T> read FNull;
|
||||
end;
|
||||
|
||||
// The implementation class for IDataSeries<T>.
|
||||
TStaticDataSeries<T> = class(TInterfacedObject, IDataSeries<T>)
|
||||
// A virtual series that combines a base series and an array of new data without copying.
|
||||
TCompositeDataSeries<T> = class(TInterfacedObject, IDataSeries<T>)
|
||||
private
|
||||
FChunks: TArray<TDataArray<T>.TChunk>;
|
||||
FCount: Int64;
|
||||
FBaseSeries: IDataSeries<T>;
|
||||
FAddedData: TMycDataArray<T>;
|
||||
FLookback: Int64;
|
||||
FTotalCount: Int64;
|
||||
|
||||
function LogicalToPhysicalIndex(LogicalIndex: Int64): Int64; inline;
|
||||
function IsIndexInLimits(LogicalIndex: Int64): Boolean;
|
||||
|
||||
// IDataSeries<T> implementation
|
||||
FCount: Int64;
|
||||
function GetCount: Int64;
|
||||
function GetItems(Idx: Int64): TDataPoint<T>;
|
||||
function GetLookback: Int64;
|
||||
function GetTotalCount: Int64;
|
||||
function GetWriter: IDataSeriesWriter<T>;
|
||||
|
||||
public
|
||||
constructor Create(const AChunks: TArray<TDataArray<T>.TChunk>; ACount, ALookback, ATotalCount: Int64);
|
||||
function Immutable: IDataSeries<T>;
|
||||
property Lookback: Int64 read GetLookback;
|
||||
constructor Create(const ABaseSeries: IDataSeries<T>; const AAddedData: TMycDataArray<T>);
|
||||
function Add(const Data: TArray<TDataPoint<T>>; First, Count: Integer): IDataSeries<T>;
|
||||
class function CreateComposite(
|
||||
const BaseSeries: IDataSeries<T>;
|
||||
const Data: TArray<TDataPoint<T>>;
|
||||
First, Count: Integer
|
||||
): IDataSeries<T>; static;
|
||||
end;
|
||||
|
||||
TConvertSeries<T, S> = class(TInterfacedObject, IDataSeries<S>)
|
||||
private
|
||||
FSource: IDataSeries<T>;
|
||||
FConvertFunc: TDataSeries<T>.TConvertFunc<S>;
|
||||
function GetCount: Int64;
|
||||
function GetItems(Idx: Int64): TDataPoint<S>;
|
||||
function GetLookback: Int64;
|
||||
function GetTotalCount: Int64;
|
||||
public
|
||||
constructor Create(const ASource: IDataSeries<T>; const AConvertFunc: TDataSeries<T>.TConvertFunc<S>);
|
||||
function Add(const Data: TArray<TDataPoint<S>>; First, Count: Integer): IDataSeries<S>;
|
||||
end;
|
||||
|
||||
TAggregateDataSeries<T, S> = class(TInterfacedObject, IDataSeries<S>)
|
||||
public
|
||||
// Defines the function signature for aggregating a set of source data points into a single target value.
|
||||
type
|
||||
TAggregateFunc = reference to function(const ASourcePoints: TArray<TDataPoint<T>>): S;
|
||||
private
|
||||
FSource: IDataSeries<T>;
|
||||
FTimeFrame: TDateTime;
|
||||
FAggregateFunc: TAggregateFunc;
|
||||
FCachedItems: TDictionary<Int64, TDataPoint<S>>;
|
||||
FBaseTime: TDateTime;
|
||||
FAggregatedCount: Int64;
|
||||
function GetCount: Int64;
|
||||
function GetItems(Idx: Int64): TDataPoint<S>;
|
||||
function GetLookback: Int64;
|
||||
function GetTotalCount: Int64;
|
||||
procedure CalculateAggregatedCount;
|
||||
public
|
||||
constructor Create(const ASource: IDataSeries<T>; ATimeFrame: TDateTime; const AAggregateFunc: TAggregateFunc);
|
||||
destructor Destroy; override;
|
||||
function Add(const Data: TArray<TDataPoint<S>>; First, Count: Integer): IDataSeries<S>;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
{ TDataArray<T> }
|
||||
uses
|
||||
System.SysUtils,
|
||||
System.Math;
|
||||
|
||||
constructor TDataArray<T>.Create(ALookback: Int64);
|
||||
{ TMycDataArray<T> }
|
||||
|
||||
constructor TMycDataArray<T>.Create(const AChunks: TArray<TChunk>; ACount: Int64);
|
||||
begin
|
||||
inherited Create;
|
||||
FLookback := ALookback;
|
||||
FCount := 0;
|
||||
FTotalCount := 0;
|
||||
FChunks := nil;
|
||||
FChunks := AChunks;
|
||||
FCount := ACount;
|
||||
end;
|
||||
|
||||
procedure TDataArray<T>.Add(const Data: TDataPoint<T>);
|
||||
class function TMycDataArray<T>.CreateEmpty: TMycDataArray<T>;
|
||||
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;
|
||||
Result.FChunks := nil;
|
||||
Result.FCount := 0;
|
||||
end;
|
||||
|
||||
procedure TDataArray<T>.Add(const Data: array of TDataPoint<T>; NumToAdd: Integer = -1);
|
||||
class function TMycDataArray<T>.CreateFromArray(const AData: TArray<TDataPoint<T>>; First, Count: Integer): TMycDataArray<T>;
|
||||
begin
|
||||
// Use the Add method on an empty array to perform the chunking logic.
|
||||
Result := CreateEmpty.Add(AData, First, Count, Count);
|
||||
end;
|
||||
|
||||
function TMycDataArray<T>.Add(const Data: array of TDataPoint<T>; First, Count, Lookback: Int64): TMycDataArray<T>;
|
||||
var
|
||||
sourceIdx, itemsToCopy, spaceInChunk: Integer;
|
||||
ci, di: Int64;
|
||||
i: Integer;
|
||||
resolvedNumToAdd: Integer;
|
||||
destPhysicalIdx, sourcePhysicalIdx: Int64;
|
||||
itemsToSkip: Int64;
|
||||
numNewChunks: Integer;
|
||||
newChunks: TArray<TChunk>;
|
||||
destChunkIdx, destSubIdx: Integer;
|
||||
sumCount, newCount: Int64;
|
||||
begin
|
||||
if NumToAdd < 0 then
|
||||
resolvedNumToAdd := Length(Data)
|
||||
else
|
||||
resolvedNumToAdd := NumToAdd;
|
||||
if Count < 0 then
|
||||
Count := Length(Data) - First;
|
||||
if (Lookback <= 0) or (Count = 0) then
|
||||
exit(Self);
|
||||
|
||||
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(Count <= (Length(Data) - First), 'Count cannot be larger than the source array');
|
||||
for var i := First + 1 to First + Count - 1 do
|
||||
Assert(Data[i].Time >= Data[i - 1].Time, 'Input array for Add is not chronologically sorted');
|
||||
if FCount > 0 then
|
||||
Assert(Data[First].Time >= Self.Items[0].Time, 'First new item is older than last existing item');
|
||||
|
||||
Assert((FCount = 0) or (Data[0].Time >= GetItems(0).Time), 'First new item is older than last existing item');
|
||||
sumCount := FCount + Count;
|
||||
newCount := sumCount;
|
||||
if (Lookback > 0) and (newCount > Lookback) then
|
||||
newCount := Lookback;
|
||||
itemsToSkip := sumCount - newCount;
|
||||
|
||||
Inc(FTotalCount, resolvedNumToAdd);
|
||||
numNewChunks := 0;
|
||||
if newCount > 0 then
|
||||
numNewChunks := (newCount - 1) div ChunkSize + 1;
|
||||
SetLength(newChunks, numNewChunks);
|
||||
|
||||
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
|
||||
for destPhysicalIdx := 0 to newCount - 1 do
|
||||
begin
|
||||
ci := FCount div ChunkSize;
|
||||
di := FCount mod ChunkSize;
|
||||
destChunkIdx := destPhysicalIdx div ChunkSize;
|
||||
destSubIdx := destPhysicalIdx mod ChunkSize;
|
||||
|
||||
if di = 0 then
|
||||
SetLength(FChunks[ci], ChunkSize);
|
||||
if destSubIdx = 0 then
|
||||
SetLength(newChunks[destChunkIdx], ChunkSize);
|
||||
|
||||
spaceInChunk := ChunkSize - di;
|
||||
itemsToCopy := resolvedNumToAdd - sourceIdx;
|
||||
if spaceInChunk < itemsToCopy then
|
||||
itemsToCopy := spaceInChunk;
|
||||
sourcePhysicalIdx := itemsToSkip + destPhysicalIdx;
|
||||
|
||||
System.Move(Data[sourceIdx], FChunks[ci][di], itemsToCopy * SizeOf(TDataPoint<T>));
|
||||
|
||||
Inc(FCount, itemsToCopy);
|
||||
Inc(sourceIdx, itemsToCopy);
|
||||
|
||||
Trim;
|
||||
if sourcePhysicalIdx < FCount then
|
||||
begin
|
||||
newChunks[destChunkIdx][destSubIdx] := FChunks[sourcePhysicalIdx div ChunkSize][sourcePhysicalIdx mod ChunkSize];
|
||||
end
|
||||
else
|
||||
begin
|
||||
newChunks[destChunkIdx][destSubIdx] := Data[First + (sourcePhysicalIdx - FCount)];
|
||||
end;
|
||||
end;
|
||||
|
||||
Result := TMycDataArray<T>.Create(newChunks, newCount);
|
||||
end;
|
||||
|
||||
procedure TDataArray<T>.Clear;
|
||||
begin
|
||||
FChunks := nil;
|
||||
FCount := 0;
|
||||
FTotalCount := 0;
|
||||
end;
|
||||
|
||||
function TDataArray<T>.Immutable: IDataSeries<T>;
|
||||
begin
|
||||
Result := TStaticDataSeries<T>.Create(FChunks, FCount, FLookback, FTotalCount);
|
||||
end;
|
||||
|
||||
function TDataArray<T>.GetCount: Int64;
|
||||
begin
|
||||
Result := FCount;
|
||||
if (FLookback > 0) and (Result > FLookback) then
|
||||
Result := FLookback;
|
||||
end;
|
||||
|
||||
function TDataArray<T>.GetItems(Idx: Int64): TDataPoint<T>;
|
||||
function TMycDataArray<T>.GetItems(Idx: Int64): TDataPoint<T>;
|
||||
var
|
||||
physicalIndex: Int64;
|
||||
begin
|
||||
Assert(IsIndexInLimits(Idx), 'Index is outside of the configured Lookback limits.');
|
||||
Assert((Idx >= 0) and (Idx < FCount), 'Logical index is out of bounds.');
|
||||
physicalIndex := LogicalToPhysicalIndex(Idx);
|
||||
Result := FChunks[physicalIndex div ChunkSize][physicalIndex mod ChunkSize];
|
||||
end;
|
||||
|
||||
function TDataArray<T>.GetLookback: Int64;
|
||||
function TMycDataArray<T>.LogicalToPhysicalIndex(LogicalIndex: Int64): Int64;
|
||||
begin
|
||||
Result := FCount - LogicalIndex - 1;
|
||||
end;
|
||||
|
||||
{ TMycDataSeries<T> }
|
||||
|
||||
constructor TMycDataSeries<T>.Create(ALookback: Int64; const AData: TMycDataArray<T>; ATotalCount: Int64);
|
||||
begin
|
||||
inherited Create;
|
||||
FLookback := ALookback;
|
||||
FData := AData;
|
||||
FTotalCount := ATotalCount;
|
||||
end;
|
||||
|
||||
destructor TMycDataSeries<T>.Destroy;
|
||||
begin
|
||||
inherited;
|
||||
end;
|
||||
|
||||
class function TMycDataSeries<T>.CreateDataSeries(Lookback: Int64; const AData: TMycDataArray<T>; ATotalCount: Int64): IDataSeries<T>;
|
||||
begin
|
||||
if Lookback > 0 then
|
||||
Result := TMycDataSeries<T>.Create(Lookback, AData, ATotalCount)
|
||||
else
|
||||
Result := TNullDataSeries<T>.Null;
|
||||
end;
|
||||
|
||||
function TMycDataSeries<T>.Add(const Data: TArray<TDataPoint<T>>; First, Count: Integer): IDataSeries<T>;
|
||||
var
|
||||
newData: TMycDataArray<T>;
|
||||
newTotalCount: Int64;
|
||||
begin
|
||||
if Count < 0 then
|
||||
Count := Length(Data) - First;
|
||||
newData := FData.Add(Data, First, Count, FLookback);
|
||||
newTotalCount := FTotalCount + Count;
|
||||
Result := TMycDataSeries<T>.Create(FLookback, newData, newTotalCount);
|
||||
end;
|
||||
|
||||
function TMycDataSeries<T>.GetCount: Int64;
|
||||
begin
|
||||
Result := FData.Count;
|
||||
end;
|
||||
|
||||
function TMycDataSeries<T>.GetItems(Idx: Int64): TDataPoint<T>;
|
||||
begin
|
||||
Assert((Idx >= 0) and (Idx < FData.Count), 'Index is out of bounds.');
|
||||
Result := FData.Items[Idx];
|
||||
end;
|
||||
|
||||
function TMycDataSeries<T>.GetLookback: Int64;
|
||||
begin
|
||||
Result := FLookback;
|
||||
end;
|
||||
|
||||
function TDataArray<T>.GetTotalCount: Int64;
|
||||
function TMycDataSeries<T>.GetTotalCount: Int64;
|
||||
begin
|
||||
Result := FTotalCount;
|
||||
end;
|
||||
|
||||
function TDataArray<T>.GetWriter: IDataSeriesWriter<T>;
|
||||
begin
|
||||
Result := Self;
|
||||
end;
|
||||
|
||||
function TDataArray<T>.IsIndexInLimits(LogicalIndex: Int64): Boolean;
|
||||
begin
|
||||
Result := (FLookback <= 0) or (LogicalIndex < FLookback);
|
||||
end;
|
||||
|
||||
function TDataArray<T>.LogicalToPhysicalIndex(LogicalIndex: Int64): Int64;
|
||||
begin
|
||||
Assert((LogicalIndex >= 0) and (LogicalIndex < FCount), 'Logical index is out of bounds.');
|
||||
Result := FCount - LogicalIndex - 1;
|
||||
end;
|
||||
|
||||
procedure TDataArray<T>.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<T> Interface Helper }
|
||||
{ TNullDataSeries<T> }
|
||||
|
||||
class constructor TNullDataSeries<T>.CreateClass;
|
||||
begin
|
||||
FNull := TNullDataSeries<T>.Create;
|
||||
FNull := TNullDataSeries<T>.Create(0);
|
||||
end;
|
||||
|
||||
function TNullDataSeries<T>.Immutable: IDataSeries<T>;
|
||||
constructor TNullDataSeries<T>.Create(ATotalCount: Int64);
|
||||
begin
|
||||
Result := FNull;
|
||||
inherited Create;
|
||||
FTotalCount := ATotalCount;
|
||||
end;
|
||||
|
||||
function TNullDataSeries<T>.Add(const Data: TArray<TDataPoint<T>>; First, Count: Integer): IDataSeries<T>;
|
||||
begin
|
||||
if Count < 0 then
|
||||
Count := Length(Data) - First;
|
||||
|
||||
if Count > 0 then
|
||||
Result := TNullDataSeries<T>.Create(FTotalCount + Count)
|
||||
else
|
||||
Result := Self;
|
||||
end;
|
||||
|
||||
function TNullDataSeries<T>.GetCount: Int64;
|
||||
@@ -263,81 +303,259 @@ end;
|
||||
|
||||
function TNullDataSeries<T>.GetItems(Idx: Int64): TDataPoint<T>;
|
||||
begin
|
||||
Assert(false, 'Index out of bounds.');
|
||||
Assert(false, 'Data series is empty.');
|
||||
Result := Default(TDataPoint<T>);
|
||||
end;
|
||||
|
||||
function TNullDataSeries<T>.GetTotalCount: Int64;
|
||||
function TNullDataSeries<T>.GetLookback: Int64;
|
||||
begin
|
||||
Result := 0;
|
||||
end;
|
||||
|
||||
function TNullDataSeries<T>.GetWriter: IDataSeriesWriter<T>;
|
||||
begin
|
||||
Result := nil;
|
||||
end;
|
||||
|
||||
function TNullDataSeries<T>.IndexOf(TimeStamp: TDateTime): Int64;
|
||||
begin
|
||||
Result := -1;
|
||||
end;
|
||||
|
||||
{ TStaticDataSeries<T> }
|
||||
|
||||
constructor TStaticDataSeries<T>.Create(const AChunks: TArray<TDataArray<T>.TChunk>; ACount, ALookback, ATotalCount: Int64);
|
||||
begin
|
||||
inherited Create;
|
||||
FChunks := AChunks;
|
||||
FCount := ACount;
|
||||
FLookback := ALookback;
|
||||
FTotalCount := ATotalCount;
|
||||
end;
|
||||
|
||||
function TStaticDataSeries<T>.Immutable: IDataSeries<T>;
|
||||
begin
|
||||
Result := Self;
|
||||
end;
|
||||
|
||||
function TStaticDataSeries<T>.GetCount: Int64;
|
||||
begin
|
||||
Result := FCount;
|
||||
if (FLookback > 0) and (Result > FLookback) then
|
||||
Result := FLookback;
|
||||
end;
|
||||
|
||||
function TStaticDataSeries<T>.GetItems(Idx: Int64): TDataPoint<T>;
|
||||
var
|
||||
physicalIndex: Int64;
|
||||
begin
|
||||
Assert(IsIndexInLimits(Idx), 'Index is outside of the configured Lookback limits.');
|
||||
physicalIndex := LogicalToPhysicalIndex(Idx);
|
||||
Result := FChunks[physicalIndex div TDataArray<T>.ChunkSize][physicalIndex mod TDataArray<T>.ChunkSize];
|
||||
end;
|
||||
|
||||
function TStaticDataSeries<T>.GetLookback: Int64;
|
||||
begin
|
||||
Result := FLookback;
|
||||
end;
|
||||
|
||||
function TStaticDataSeries<T>.GetTotalCount: Int64;
|
||||
function TNullDataSeries<T>.GetTotalCount: Int64;
|
||||
begin
|
||||
Result := FTotalCount;
|
||||
end;
|
||||
|
||||
function TStaticDataSeries<T>.GetWriter: IDataSeriesWriter<T>;
|
||||
{ TCompositeDataSeries<T> }
|
||||
|
||||
constructor TCompositeDataSeries<T>.Create(const ABaseSeries: IDataSeries<T>; const AAddedData: TMycDataArray<T>);
|
||||
begin
|
||||
Result := nil;
|
||||
inherited Create;
|
||||
FBaseSeries := ABaseSeries;
|
||||
FAddedData := AAddedData;
|
||||
FLookback := FBaseSeries.Lookback;
|
||||
Assert(FLookback > 0);
|
||||
|
||||
FCount := FBaseSeries.Count + FAddedData.Count;
|
||||
if FCount > FLookback then
|
||||
FCount := FLookback;
|
||||
end;
|
||||
|
||||
function TStaticDataSeries<T>.IsIndexInLimits(LogicalIndex: Int64): Boolean;
|
||||
function TCompositeDataSeries<T>.Add(const Data: TArray<TDataPoint<T>>; First, Count: Integer): IDataSeries<T>;
|
||||
var
|
||||
newAddedData: TMycDataArray<T>;
|
||||
itemsInBase: Int64;
|
||||
lookbackForAdd: Int64;
|
||||
begin
|
||||
Result := (FLookback <= 0) or (LogicalIndex < FLookback);
|
||||
if Count < 0 then
|
||||
Count := Length(Data) - First;
|
||||
if Count = 0 then
|
||||
exit(Self);
|
||||
|
||||
itemsInBase := FBaseSeries.Count;
|
||||
lookbackForAdd := FLookback - itemsInBase;
|
||||
if lookbackForAdd < 0 then
|
||||
lookbackForAdd := 0;
|
||||
|
||||
newAddedData := FAddedData.Add(Data, First, Count, lookbackForAdd);
|
||||
|
||||
// Optimization: If the added data fills the entire lookback window,
|
||||
// the base series is no longer relevant. We can return a simpler TMycDataSeries.
|
||||
if newAddedData.Count >= FLookback then
|
||||
begin
|
||||
Result := TMycDataSeries<T>.Create(FLookback, newAddedData, GetTotalCount + Count);
|
||||
end
|
||||
else
|
||||
begin
|
||||
Result := TCompositeDataSeries<T>.Create(FBaseSeries, newAddedData);
|
||||
end;
|
||||
end;
|
||||
|
||||
function TStaticDataSeries<T>.LogicalToPhysicalIndex(LogicalIndex: Int64): Int64;
|
||||
class function TCompositeDataSeries<T>.CreateComposite(
|
||||
const BaseSeries: IDataSeries<T>;
|
||||
const Data: TArray<TDataPoint<T>>;
|
||||
First, Count: Integer
|
||||
): IDataSeries<T>;
|
||||
begin
|
||||
Assert((LogicalIndex >= 0) and (LogicalIndex < FCount), 'Logical index is out of bounds.');
|
||||
Result := FCount - LogicalIndex - 1;
|
||||
if Count < 0 then
|
||||
Count := Length(Data) - First;
|
||||
if Count = 0 then
|
||||
exit(BaseSeries);
|
||||
|
||||
Result := TCompositeDataSeries<T>.Create(BaseSeries, TMycDataArray<T>.CreateFromArray(Data, First, Count));
|
||||
end;
|
||||
|
||||
function TCompositeDataSeries<T>.GetCount: Int64;
|
||||
begin
|
||||
Result := FCount;
|
||||
end;
|
||||
|
||||
function TCompositeDataSeries<T>.GetItems(Idx: Int64): TDataPoint<T>;
|
||||
var
|
||||
addedCount: Int64;
|
||||
begin
|
||||
Assert((Idx >= 0) and (Idx < FCount), 'Logical index is out of bounds.');
|
||||
addedCount := FAddedData.Count;
|
||||
if Idx < addedCount then
|
||||
begin
|
||||
Result := FAddedData.Items[Idx];
|
||||
end
|
||||
else
|
||||
begin
|
||||
Result := FBaseSeries.Items[Idx - addedCount];
|
||||
end;
|
||||
end;
|
||||
|
||||
function TCompositeDataSeries<T>.GetLookback: Int64;
|
||||
begin
|
||||
Result := FLookback;
|
||||
end;
|
||||
|
||||
function TCompositeDataSeries<T>.GetTotalCount: Int64;
|
||||
begin
|
||||
Result := FBaseSeries.TotalCount + FAddedData.Count;
|
||||
end;
|
||||
|
||||
{ TConvertSeries<T, S> }
|
||||
|
||||
constructor TConvertSeries<T, S>.Create(const ASource: IDataSeries<T>; const AConvertFunc: TDataSeries<T>.TConvertFunc<S>);
|
||||
begin
|
||||
inherited Create;
|
||||
FSource := ASource;
|
||||
FConvertFunc := AConvertFunc;
|
||||
end;
|
||||
|
||||
function TConvertSeries<T, S>.Add(const Data: TArray<TDataPoint<S>>; First, Count: Integer): IDataSeries<S>;
|
||||
begin
|
||||
Result := TCompositeDataSeries<S>.CreateComposite(Self, Data, First, Count);
|
||||
end;
|
||||
|
||||
function TConvertSeries<T, S>.GetCount: Int64;
|
||||
begin
|
||||
Result := FSource.Count;
|
||||
end;
|
||||
|
||||
function TConvertSeries<T, S>.GetItems(Idx: Int64): TDataPoint<S>;
|
||||
var
|
||||
P: TDataPoint<T>;
|
||||
begin
|
||||
P := FSource[Idx];
|
||||
Result.Create(P.Time, FConvertFunc(P));
|
||||
end;
|
||||
|
||||
function TConvertSeries<T, S>.GetLookback: Int64;
|
||||
begin
|
||||
Result := FSource.Lookback;
|
||||
end;
|
||||
|
||||
function TConvertSeries<T, S>.GetTotalCount: Int64;
|
||||
begin
|
||||
Result := FSource.TotalCount;
|
||||
end;
|
||||
|
||||
{ TAggregateDataSeries<T, S> }
|
||||
|
||||
constructor TAggregateDataSeries<T, S>.Create(const ASource: IDataSeries<T>; ATimeFrame: TDateTime; const AAggregateFunc: TAggregateFunc);
|
||||
begin
|
||||
inherited Create;
|
||||
FSource := ASource;
|
||||
FTimeFrame := ATimeFrame;
|
||||
FAggregateFunc := AAggregateFunc;
|
||||
FCachedItems := TDictionary<Int64, TDataPoint<S>>.Create;
|
||||
FAggregatedCount := -1; // -1 indicates that it has not been calculated yet
|
||||
end;
|
||||
|
||||
destructor TAggregateDataSeries<T, S>.Destroy;
|
||||
begin
|
||||
FCachedItems.Free;
|
||||
inherited;
|
||||
end;
|
||||
|
||||
procedure TAggregateDataSeries<T, S>.CalculateAggregatedCount;
|
||||
var
|
||||
totalTimeSpan: Double;
|
||||
begin
|
||||
if FSource.Count = 0 then
|
||||
begin
|
||||
FBaseTime := 0;
|
||||
FAggregatedCount := 0;
|
||||
end
|
||||
else
|
||||
begin
|
||||
// The timestamp of the oldest element serves as the anchor for our time grid.
|
||||
FBaseTime := FSource.Items[FSource.Count - 1].Time;
|
||||
// Total duration covered by the source series.
|
||||
totalTimeSpan := FSource.Items[0].Time - FBaseTime;
|
||||
if totalTimeSpan >= 0 then
|
||||
FAggregatedCount := Trunc(totalTimeSpan / FTimeFrame) + 1
|
||||
else
|
||||
FAggregatedCount := 0;
|
||||
end;
|
||||
end;
|
||||
|
||||
function TAggregateDataSeries<T, S>.Add(const Data: TArray<TDataPoint<S>>; First, Count: Integer): IDataSeries<S>;
|
||||
begin
|
||||
// Adding to an aggregated series is complex.
|
||||
// The most straightforward approach is to create a composite series.
|
||||
Result := TCompositeDataSeries<S>.CreateComposite(Self, Data, First, Count);
|
||||
end;
|
||||
|
||||
function TAggregateDataSeries<T, S>.GetCount: Int64;
|
||||
begin
|
||||
if FAggregatedCount = -1 then
|
||||
CalculateAggregatedCount;
|
||||
Result := FAggregatedCount;
|
||||
end;
|
||||
|
||||
function TAggregateDataSeries<T, S>.GetItems(Idx: Int64): TDataPoint<S>;
|
||||
var
|
||||
startTime, endTime: TDateTime;
|
||||
sourcePoints: TList<TDataPoint<T>>;
|
||||
sourceIdx: Int64;
|
||||
begin
|
||||
Assert((Idx >= 0) and (Idx < GetCount), 'Index is out of bounds.');
|
||||
|
||||
if FCachedItems.TryGetValue(Idx, Result) then
|
||||
exit;
|
||||
|
||||
// Calculate the time window for the requested aggregated data point.
|
||||
// Index 0 is the newest, so we calculate backwards from the total count.
|
||||
endTime := FBaseTime + (FAggregatedCount - Idx) * FTimeFrame;
|
||||
startTime := endTime - FTimeFrame;
|
||||
|
||||
sourcePoints := TList<TDataPoint<T>>.Create;
|
||||
try
|
||||
// Find all source data points that fall into this time window.
|
||||
// We can optimize the start of the search using the IndexOf function.
|
||||
sourceIdx := TDataSeries<T>(FSource).IndexOf(endTime);
|
||||
if sourceIdx = -1 then
|
||||
sourceIdx := 0; // If endTime is after the last element, start at the newest.
|
||||
|
||||
while (sourceIdx < FSource.Count) do
|
||||
begin
|
||||
var P := FSource.Items[sourceIdx];
|
||||
if P.Time < startTime then
|
||||
break; // We have moved past our time window
|
||||
|
||||
if P.Time < endTime then // Time is within [startTime, endTime)
|
||||
begin
|
||||
sourcePoints.Add(P);
|
||||
end;
|
||||
Inc(sourceIdx);
|
||||
end;
|
||||
|
||||
// The aggregation function expects the data in chronological order (oldest first),
|
||||
// but we collected it in reverse. So we reverse the list.
|
||||
sourcePoints.Reverse;
|
||||
Result.Create(endTime, FAggregateFunc(sourcePoints.ToArray));
|
||||
finally
|
||||
sourcePoints.Free;
|
||||
end;
|
||||
|
||||
// Cache the result for future calls
|
||||
FCachedItems.Add(Idx, Result);
|
||||
end;
|
||||
|
||||
function TAggregateDataSeries<T, S>.GetLookback: Int64;
|
||||
begin
|
||||
Result := FSource.Lookback; // The lookback is defined by the source series.
|
||||
end;
|
||||
|
||||
function TAggregateDataSeries<T, S>.GetTotalCount: Int64;
|
||||
begin
|
||||
// The total count of aggregated items is simply its current count, as it's a view.
|
||||
Result := GetCount;
|
||||
end;
|
||||
|
||||
end.
|
||||
|
||||
Reference in New Issue
Block a user