TSeries + DataEndpoint

This commit is contained in:
Michael Schimmel
2025-07-15 11:44:44 +02:00
parent 1a07468ad8
commit 8ebcd81561
8 changed files with 228 additions and 147 deletions
+7 -9
View File
@@ -17,22 +17,22 @@ uses
type
TChartSeriesReceiver<T> = class(TMycProcessor<T>)
strict private
FCurrData: TMycDataArray<T>;
FCurrData: TSeries<T>;
FLookback: TMutable<Int64>;
private
FData: TWriteable<TMycDataArray<T>>;
FData: TWriteable<TSeries<T>>;
protected
function ProcessData(const Value: T): TState; override;
public
constructor Create(const ALookback: TMutable<Int64>);
property Data: TWriteable<TMycDataArray<T>> read FData;
property Data: TWriteable<TSeries<T>> read FData;
end;
TChartSeriesProcessor<T> = class(TMycChart.TSeries)
strict private
FDataSeries: TMycDataArray<T>;
FDataSeries: TSeries<T>;
private
FData: TMycDataArray<T>;
FData: TSeries<T>;
FDataProvider: TDataProvider<T>;
FReceiver: TChartSeriesReceiver<T>;
FReceiverTag: TDataProvider<T>.TTag;
@@ -44,7 +44,7 @@ type
public
constructor Create(const ADataProvider: TDataProvider<T>; const ALookback: TMutable<Int64>);
destructor Destroy; override;
property Data: TMycDataArray<T> read FData;
property Data: TSeries<T> read FData;
end;
{ TChartCustomLayer }
@@ -146,8 +146,7 @@ constructor TChartSeriesReceiver<T>.Create(const ALookback: TMutable<Int64>);
begin
inherited Create;
FLookback := ALookback;
FCurrData := TMycDataArray<T>.CreateEmpty;
FData := TWriteable<TMycDataArray<T>>.CreateWriteable(FCurrData).Protect;
FData := TWriteable<TSeries<T>>.CreateWriteable(FCurrData).Protect;
end;
function TChartSeriesReceiver<T>.ProcessData(const Value: T): TState;
@@ -163,7 +162,6 @@ constructor TChartSeriesProcessor<T>.Create(const ADataProvider: TDataProvider<T
begin
inherited Create;
FDataProvider := ADataProvider;
FDataSeries := TMycDataArray<T>.CreateEmpty;
FData := FDataSeries;
FReceiver := TChartSeriesReceiver<T>.Create(ALookback);
+21 -20
View File
@@ -6,7 +6,7 @@ type
// A series is an array of values with the newest ite at index=0. Each series counts the total of added items since creation, but
// it actually may contain less items, because the array size is limited by the lookback parameter, when adding items.
// Series are immutable.
TMycDataArray<T> = record
TSeries<T> = record
private
const
ChunkSize = 1024;
@@ -21,13 +21,14 @@ type
function GetItems(Idx: Int64): T; inline;
public
constructor Create(const AChunks: TArray<TChunk>; ACount, ATotalCount: Int64);
class operator Initialize(out Dest: TSeries<T>);
// Add a singe item
function Add(const Data: T; Lookback: Int64): TMycDataArray<T>; overload;
function Add(const Data: T; Lookback: Int64): TSeries<T>; overload;
// Add a ranmge of items
function Add(const Data: array of T; First, Count, Lookback: Int64): TMycDataArray<T>; overload;
class function CreateEmpty: TMycDataArray<T>; static;
function Add(const Data: array of T; First, Count, Lookback: Int64): TSeries<T>; overload;
// Helper to create a data array from a raw TArray.
class function CreateFromArray(const AData: TArray<T>; First, Count: Integer): TMycDataArray<T>; static;
class function CreateFromArray(const AData: TArray<T>; First, Count: Integer): TSeries<T>; static;
property Count: Int64 read FCount;
property TotalCount: Int64 read FTotalCount;
property Items[Idx: Int64]: T read GetItems; default;
@@ -35,28 +36,22 @@ type
implementation
{ TMycDataArray<T> }
{ TSeries<T> }
constructor TMycDataArray<T>.Create(const AChunks: TArray<TChunk>; ACount, ATotalCount: Int64);
constructor TSeries<T>.Create(const AChunks: TArray<TChunk>; ACount, ATotalCount: Int64);
begin
FChunks := AChunks;
FCount := ACount;
FTotalCount := ATotalCount;
end;
class function TMycDataArray<T>.CreateEmpty: TMycDataArray<T>;
begin
Result.FChunks := nil;
Result.FCount := 0;
end;
class function TMycDataArray<T>.CreateFromArray(const AData: TArray<T>; First, Count: Integer): TMycDataArray<T>;
class function TSeries<T>.CreateFromArray(const AData: TArray<T>; First, Count: Integer): TSeries<T>;
begin
// Use the Add method on an empty array to perform the chunking logic.
Result := CreateEmpty.Add(AData, First, Count, Count);
Result.Add(AData, First, Count, Count);
end;
function TMycDataArray<T>.Add(const Data: array of T; First, Count, Lookback: Int64): TMycDataArray<T>;
function TSeries<T>.Add(const Data: array of T; First, Count, Lookback: Int64): TSeries<T>;
var
destPhysicalIdx, sourcePhysicalIdx: Int64;
itemsToSkip: Int64;
@@ -103,15 +98,15 @@ begin
end;
end;
Result := TMycDataArray<T>.Create(newChunks, newCount, FTotalCount + Count);
Result := TSeries<T>.Create(newChunks, newCount, FTotalCount + Count);
end;
function TMycDataArray<T>.Add(const Data: T; Lookback: Int64): TMycDataArray<T>;
function TSeries<T>.Add(const Data: T; Lookback: Int64): TSeries<T>;
begin
Result := Add([Data], 0, 1, Lookback);
end;
function TMycDataArray<T>.GetItems(Idx: Int64): T;
function TSeries<T>.GetItems(Idx: Int64): T;
var
physicalIndex: Int64;
begin
@@ -120,9 +115,15 @@ begin
Result := FChunks[physicalIndex div ChunkSize][physicalIndex mod ChunkSize];
end;
function TMycDataArray<T>.LogicalToPhysicalIndex(LogicalIndex: Int64): Int64;
function TSeries<T>.LogicalToPhysicalIndex(LogicalIndex: Int64): Int64;
begin
Result := FCount - LogicalIndex - 1;
end;
class operator TSeries<T>.Initialize(out Dest: TSeries<T>);
begin
Dest.FCount := 0;
Dest.FTotalCount := 0;
end;
end.
+59 -2
View File
@@ -5,9 +5,11 @@ interface
uses
System.SysUtils,
Myc.Signals,
Myc.Mutable,
Myc.Core.Notifier,
Myc.Trade.Types,
Myc.Trade.DataPoint,
Myc.Core.Notifier;
Myc.Trade.DataArray,
Myc.Trade.DataPoint;
type
// Abstract base class for data consumers.
@@ -126,6 +128,23 @@ type
constructor Create(const Controller: IInterface; const AProc: TProc);
end;
// Endpoint that collects data into a series.
TMycDataEndpoint<T> = class(TInterfacedObject, TMutable<TSeries<T>>.IMutable)
private
FProcessor: TMycContainedProcessor<T>;
FTag: TDataProvider<T>.TTag;
FDataProvider: TDataProvider<T>;
FLookback: Int64;
FData: TSeries<T>;
FChanged: TEvent;
function GetChanged: TSignal;
function GetValue: TSeries<T>;
function ProcessData(const Value: T): TState;
public
constructor Create(const ADataProvider: TDataProvider<T>; ALookback: Int64);
destructor Destroy; override;
end;
implementation
uses
@@ -354,4 +373,42 @@ begin
Result := FProc(Value);
end;
{ TMycDataEndpoint<T> }
constructor TMycDataEndpoint<T>.Create(const ADataProvider: TDataProvider<T>; ALookback: Int64);
begin
inherited Create;
FDataProvider := ADataProvider;
FLookback := ALookback;
FProcessor := TMycContainedProcessor<T>.Create(Self, ProcessData);
FTag := FDataProvider.Link(FProcessor);
end;
destructor TMycDataEndpoint<T>.Destroy;
begin
FDataProvider.Unlink(FTag);
FProcessor.Free;
inherited;
end;
function TMycDataEndpoint<T>.GetChanged: TSignal;
begin
Result := FChanged.Signal;
end;
function TMycDataEndpoint<T>.GetValue: TSeries<T>;
begin
Result := FData;
end;
function TMycDataEndpoint<T>.ProcessData(const Value: T): TState;
begin
Result := TState.Null;
// Add new data point, respecting the lookback period.
FData := FData.Add(Value, FLookback);
FChanged.Notify;
end;
end.
+26 -36
View File
@@ -4,21 +4,19 @@ interface
uses
Myc.Signals,
Myc.Trade.Types;
Myc.Mutable,
Myc.Trade.Types,
Myc.Trade.DataArray;
type
// Represents a time-stamped data point in a series.
TDataPoint<T> = record
Time: TDateTime;
Data: T;
constructor Create(ATime: TDateTime; const AData: T);
end;
// A generic interface for components that process data of type T.
IMycProcessor<T> = interface
function ProcessData(const Value: T): TState;
end;
// Interface helper for IDataProvider providing the null object pattern.
TDataProvider<T> = record
public
type
TTag = Pointer;
IDataProvider = interface
@@ -26,14 +24,14 @@ type
procedure Unlink(Tag: TTag);
end;
{$REGION 'private'}
strict private
class var
FNull: IDataProvider;
class constructor CreateClass;
private
FDataProvider: IDataProvider;
{$ENDREGION}
public
constructor Create(const ADataProvider: IDataProvider);
@@ -50,15 +48,18 @@ type
class property Null: IDataProvider read FNull;
end;
// Interface helper for IMycConverter<S,T> providing the null object pattern.
// Interface helper for IConverter<S,T> providing the null object pattern.
TConverter<S, T> = record
public
type
IConverter = interface(IMycProcessor<S>)
{$region 'private'}
function GetSender: TDataProvider<T>.IDataProvider;
{$endregion}
property Sender: TDataProvider<T>.IDataProvider read GetSender;
end;
{$REGION 'private'}
{$region 'private'}
strict private
class var
FNull: IConverter;
@@ -66,7 +67,7 @@ type
private
FConverter: IConverter;
function GetSender: TDataProvider<T>; inline;
{$ENDREGION}
{$endregion}
public
constructor Create(const AConverter: IConverter);
@@ -83,18 +84,21 @@ type
function Chain<R>(const Next: TConverter<T, R>): TConverter<T, R>; overload; inline;
function Chain<R>(const Func: TConstFunc<T, R>): TConverter<T, R>; overload; inline;
// Extracts the field of a record by it's name (using RTTI).
function Field<R>(const FieldName: String): TConverter<T, R>; overload; inline;
// Provides access to the null object instance.
class property Null: IConverter read FNull;
// Wrapper for IMycConverter.Sender
// Wrapper for IConverter.Sender
property Sender: TDataProvider<T> read GetSender;
end;
// Factory for creating specific converter instances.
TConverter = record
class function CreateCounter<T>: TConverter<T, Int64>; static;
class function CreateTicker<T>: TConverter<TArray<T>, T>; static;
class function CreateRecordField<S, T>(const FieldName: String): TConverter<S, T>; static;
class function CreateEndpoint<T>(const DataProvider: TDataProvider<T>; Lookback: Int64): TMutable<TSeries<T>>; static;
end;
implementation
@@ -102,14 +106,6 @@ implementation
uses
Myc.Trade.DataPoint.Impl;
{ TDataPoint<T> }
constructor TDataPoint<T>.Create(ATime: TDateTime; const AData: T);
begin
Time := ATime;
Data := AData;
end;
{ TDataProvider<T> }
class constructor TDataProvider<T>.CreateClass;
@@ -121,38 +117,32 @@ end;
constructor TDataProvider<T>.Create(const ADataProvider: IDataProvider);
begin
FDataProvider := ADataProvider;
// Ensure that the internal interface is never nil.
if not Assigned(FDataProvider) then
FDataProvider := FNull;
end;
class operator TDataProvider<T>.Initialize(out Dest: TDataProvider<T>);
begin
// Initialize new record instances with the null object.
Dest.FDataProvider := FNull;
end;
class operator TDataProvider<T>.Implicit(const A: IDataProvider): TDataProvider<T>;
begin
// Allow implicit conversion from the interface to the helper.
Result.Create(A);
end;
class operator TDataProvider<T>.Implicit(const A: TDataProvider<T>): IDataProvider;
begin
// Allow implicit conversion from the helper to the interface.
Result := A.FDataProvider;
end;
function TDataProvider<T>.Link(const Receiver: IMycProcessor<T>): TTag;
begin
// Forward the call to the wrapped interface.
Result := FDataProvider.Link(Receiver);
end;
procedure TDataProvider<T>.Unlink(Tag: TTag);
begin
// Forward the call to the wrapped interface.
FDataProvider.Unlink(Tag);
end;
@@ -160,14 +150,12 @@ end;
class constructor TConverter<S, T>.CreateClass;
begin
// Create the singleton null object instance.
FNull := TNullConverter<S, T>.Create;
end;
constructor TConverter<S, T>.Create(const AConverter: IConverter);
begin
FConverter := AConverter;
// Ensure that the internal interface is never nil.
if not Assigned(FConverter) then
FConverter := FNull;
end;
@@ -190,44 +178,46 @@ end;
function TConverter<S, T>.Field<R>(const FieldName: String): TConverter<T, R>;
begin
Result := Chain<R>(TMycRecordFieldReader<T, R>.Create(FieldName));
Result := Chain<R>(TConverter.CreateRecordField<T, R>(FieldName));
end;
function TConverter<S, T>.GetSender: TDataProvider<T>;
begin
// Forward the call to the wrapped interface.
Result := FConverter.Sender;
end;
class operator TConverter<S, T>.Initialize(out Dest: TConverter<S, T>);
begin
// Initialize new record instances with the null object.
Dest.FConverter := FNull;
end;
class operator TConverter<S, T>.Implicit(const A: IConverter): TConverter<S, T>;
begin
// Allow implicit conversion from the interface to the helper.
Result.Create(A);
end;
class operator TConverter<S, T>.Implicit(const A: TConverter<S, T>): IConverter;
begin
// Allow implicit conversion from the helper to the interface.
Result := A.FConverter;
end;
function TConverter<S, T>.ProcessData(const Value: S): TState;
begin
// Forward the call to the wrapped interface.
Result := FConverter.ProcessData(Value);
end;
{ TConverter }
class function TConverter.CreateCounter<T>: TConverter<T, Int64>;
begin
Result := TMycDataCounter<T>.Create;
end;
class function TConverter.CreateEndpoint<T>(const DataProvider: TDataProvider<T>; Lookback: Int64): TMutable<TSeries<T>>;
begin
Result := TMycDataEndpoint<T>.Create(DataProvider, Lookback);
end;
class function TConverter.CreateRecordField<S, T>(const FieldName: String): TConverter<S, T>;
begin
Result := TMycRecordFieldReader<S, T>.Create(FieldName);
+1
View File
@@ -11,6 +11,7 @@ uses
Myc.Futures,
Myc.Signals,
Myc.Mutable,
Myc.Trade.Types,
Myc.Trade.DataPoint,
Myc.Core.FileCache;
+13 -13
View File
@@ -31,9 +31,9 @@ type
TIndicators = record
private
class function CalculateSMA(const Series: TMycDataArray<Double>; const Period: Integer): Double; static;
class function CalculateStdDev(const Series: TMycDataArray<Double>; const Period: Integer): Double; static;
class function CalculateWMA(const Series: TMycDataArray<Double>; const Period: Integer): Double; static;
class function CalculateSMA(const Series: TSeries<Double>; const Period: Integer): Double; static;
class function CalculateStdDev(const Series: TSeries<Double>; const Period: Integer): Double; static;
class function CalculateWMA(const Series: TSeries<Double>; const Period: Integer): Double; static;
public
// Simple Moving Average
class function CreateSMA(Period: Integer): TConstFunc<Double, Double>; static;
@@ -55,7 +55,7 @@ implementation
{ TIndicators }
class function TIndicators.CalculateSMA(const Series: TMycDataArray<Double>; const Period: Integer): Double;
class function TIndicators.CalculateSMA(const Series: TSeries<Double>; const Period: Integer): Double;
var
i: Integer;
sum: Double;
@@ -70,7 +70,7 @@ begin
Result := sum / Period;
end;
class function TIndicators.CalculateStdDev(const Series: TMycDataArray<Double>; const Period: Integer): Double;
class function TIndicators.CalculateStdDev(const Series: TSeries<Double>; const Period: Integer): Double;
var
i: Integer;
mean, sumOfSquares: Double;
@@ -86,7 +86,7 @@ begin
Result := Sqrt(sumOfSquares / Period);
end;
class function TIndicators.CalculateWMA(const Series: TMycDataArray<Double>; const Period: Integer): Double;
class function TIndicators.CalculateWMA(const Series: TSeries<Double>; const Period: Integer): Double;
var
i: Integer;
numerator: Double;
@@ -114,7 +114,7 @@ end;
class function TIndicators.CreateBollingerBands(Period: Integer; Multiplier: Double): TConstFunc<Double, TBollingerBandsResult>;
begin
var sourceData := TMycDataArray<Double>.CreateEmpty;
var sourceData: TSeries<Double>;
Result :=
function(const Value: Double): TBollingerBandsResult
var
@@ -138,7 +138,7 @@ end;
class function TIndicators.CreateEMA(Period: Integer): TConstFunc<Double, Double>;
begin
var lastEma: Double := Double.NaN;
var sourceData := TMycDataArray<Double>.CreateEmpty;
var sourceData: TSeries<Double>;
var multiplier := 2 / (Period + 1);
Result :=
@@ -170,8 +170,8 @@ class function TIndicators.CreateHMA(Period: Integer): TConstFunc<Double, Double
begin
var periodHalf := Period div 2;
var periodSqrt := Round(Sqrt(Period));
var sourceData := TMycDataArray<Double>.CreateEmpty;
var diffSeries := TMycDataArray<Double>.CreateEmpty;
var sourceData: TSeries<Double>;
var diffSeries: TSeries<Double>;
Result :=
function(const Value: Double): Double
@@ -244,7 +244,7 @@ class function TIndicators.CreateRSI(Period: Integer): TConstFunc<Double, Double
begin
var avgGain: Double := Double.NaN;
var avgLoss: Double := Double.NaN;
var sourceData := TMycDataArray<Double>.CreateEmpty;
var sourceData: TSeries<Double>;
Result :=
function(const Value: Double): Double
@@ -301,7 +301,7 @@ end;
class function TIndicators.CreateSMA(Period: Integer): TConstFunc<Double, Double>;
begin
var sourceData := TMycDataArray<Double>.CreateEmpty;
var sourceData: TSeries<Double>;
Result :=
function(const Value: Double): Double
begin
@@ -315,7 +315,7 @@ end;
class function TIndicators.CreateStochastic(KPeriod, DPeriod: Integer): TConstFunc<TOhlcItem, TStochasticResult>;
begin
var sourceData := TMycDataArray<TOhlcItem>.CreateEmpty;
var sourceData: TSeries<TOhlcItem>;
var smaD := CreateSMA(DPeriod);
Result :=
+15
View File
@@ -21,6 +21,13 @@ type
constructor Create(AOpen, AHigh, ALow, AClose, AVolume: Double);
end;
// Represents a time-stamped data point in a series.
TDataPoint<T> = record
Time: TDateTime;
Data: T;
constructor Create(ATime: TDateTime; const AData: T);
end;
TConstFunc<S, T> = reference to function(const Value: S): T;
implementation
@@ -44,4 +51,12 @@ begin
Volume := AVolume;
end;
{ TDataPoint<T> }
constructor TDataPoint<T>.Create(ATime: TDateTime; const AData: T);
begin
Time := ATime;
Data := AData;
end;
end.