Files
MycLib/Src/Myc.Trade.DataPoint.pas
T
2025-07-24 10:15:18 +02:00

397 lines
13 KiB
ObjectPascal

unit Myc.Trade.DataPoint;
interface
uses
Myc.Signals,
Myc.Mutable,
Myc.Trade.Types,
Myc.Trade.DataArray,
Myc.DataRecord;
type
TTag = Pointer;
// A generic interface for components that consume data of type T.
IConsumer<T> = interface
function Consume(const Value: T): TState;
end;
IProducer<T> = interface
function Link(const Consumer: IConsumer<T>): TTag;
procedure Unlink(Tag: TTag);
end;
// A converter is a producer that internally uses a consumer to transform data.
IConverter<S, T> = interface(IProducer<T>)
{$region 'private'}
function GetConsumer: IConsumer<S>;
{$endregion}
property Consumer: IConsumer<S> read GetConsumer;
end;
// Interface helper for IProducer providing the null object pattern.
TProducer<T> = record
public
type
TSubscription = record
private
FProducer: IProducer<T>;
FTag: TTag;
public
procedure Unlink;
property Producer: IProducer<T> read FProducer;
end;
private
FProducer: IProducer<T>;
class function GetNull: IProducer<T>; static; inline;
public
constructor Create(const AProducer: IProducer<T>);
// Managed record operators
class operator Initialize(out Dest: TProducer<T>);
class operator Implicit(const A: IProducer<T>): TProducer<T>; overload;
class operator Implicit(const A: TProducer<T>): IProducer<T>; overload;
function Link(const Consumer: IConsumer<T>): TSubscription; inline;
function Chain(const Next: IConsumer<T>): IConsumer<T>; overload; inline;
function Chain<R>(const Next: IConverter<T, R>): TProducer<R>; overload; inline;
function Chain<R>(const Func: TConstFunc<T, R>): TProducer<R>; overload; inline;
function CreateEndpoint(Lookback: Int64): TLazy<TSeries<T>>;
function CreateSequence(Count: Integer): TArray<IProducer<T>>; overload; experimental;
// Extracts the field of a record by it's name (using RTTI).
function Field<R>(const FieldName: String): TProducer<R>; inline;
function MakeParallel: TProducer<T>; inline;
// Provides access to the null object instance.
class property Null: IProducer<T> read GetNull;
end;
// Interface helper for IConverter<S,T> providing the null object pattern.
TConverter<S, T> = record
public
type
TBroadcastProc = reference to function(const Value: T): TState;
private
FConverter: IConverter<S, T>;
function GetConsumer: IConsumer<S>; inline;
function GetProducer: TProducer<T>; inline;
class function GetNull: IConverter<S, T>; static; inline;
public
constructor Create(const AConverter: IConverter<S, T>);
// Managed record operators
class operator Initialize(out Dest: TConverter<S, T>);
class operator Implicit(const A: IConverter<S, T>): TConverter<S, T>; overload;
class operator Implicit(const A: TConverter<S, T>): IConverter<S, T>; overload;
class function Construct(const Consumer: IConsumer<S>; const Producer: TProducer<T>): TConverter<S, T>; static;
class function CreateGeneric(const Func: TConstFunc<S, T>): TConverter<S, T>; static;
class function CreateAggregation(const Func: TConstFunc<S, TBroadcastProc, TState>): TConverter<S, T>; static;
property Consumer: IConsumer<S> read GetConsumer;
property Producer: TProducer<T> read GetProducer;
class property Null: IConverter<S, T> read GetNull;
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 CreateIdentity<T>: TConverter<T, T>; static;
class function CreateTickAggregation(Timeframe: TTimeframe): TConverter<TDataPoint<Double>, TDataPoint<TOhlcItem>>; static;
class function CreateOhlcAggregation(Timeframe: TTimeframe): TConverter<TDataPoint<TOhlcItem>, TDataPoint<TOhlcItem>>; static;
class function CreateDataPointConverter<S, T>(const Func: TConstFunc<S, T>): TConverter<TDataPoint<S>, TDataPoint<T>>; static;
class function FieldToRecord<T>(const Layout: TDataRecord.TLayout; const Name: String): TConverter<T, TDataRecord>; static;
class function FieldOfRecord<T>(const Layout: TDataRecord.TLayout; const Name: String): TConverter<TDataRecord, T>; static;
class function Join<T>(const Producers: TArray<TProducer<T>>): TProducer<TArray<T>>; static;
type
TRecordMapping = record
Layout: TDataRecord.TLayout;
Fields: array of record
FromField, ToField: String
end;
end;
class function DataMapping(
const Inputs: TArray<TRecordMapping>;
const Output: TDataRecord.TLayout
): TConverter<TArray<TDataRecord>, TDataRecord>; static;
class function JoinRecords(
const TargetLayout: TDataRecord.TLayout;
const Mapping: TArray<TRecordMapping>;
const Producers: TArray<TProducer<TDataRecord>>
): TConverter<TArray<TDataRecord>, TDataRecord>; static;
end;
implementation
uses
Myc.Trade.DataPoint.Impl;
{ TProducer<T> }
procedure TProducer<T>.TSubscription.Unlink;
begin
if FTag <> nil then
begin
FProducer.Unlink(FTag);
FTag := nil;
end;
end;
constructor TProducer<T>.Create(const AProducer: IProducer<T>);
begin
FProducer := AProducer;
if not Assigned(FProducer) then
FProducer := Null;
end;
function TProducer<T>.Chain(const Next: IConsumer<T>): IConsumer<T>;
begin
FProducer.Link(Next);
Result := Next;
end;
function TProducer<T>.Chain<R>(const Next: IConverter<T, R>): TProducer<R>;
begin
// REFACTOR: Link to the consumer part, return the converter as the new producer
FProducer.Link(Next.Consumer);
Result := Next;
end;
function TProducer<T>.Chain<R>(const Func: TConstFunc<T, R>): TProducer<R>;
begin
Result := Chain<R>(TMycGenericConverter<T, R>.Create(Func));
end;
function TProducer<T>.CreateEndpoint(Lookback: Int64): TLazy<TSeries<T>>;
begin
Result := TMycDataEndpoint<T>.Create(FProducer, Lookback);
end;
function TProducer<T>.CreateSequence(Count: Integer): TArray<IProducer<T>>;
begin
FProducer.Link(TMycSequence<T>.CreateSequence(Count, Result));
end;
function TProducer<T>.Field<R>(const FieldName: String): TProducer<R>;
begin
Result := Chain<R>(TMycRecordFieldReader<T, R>.Create(FieldName));
end;
class function TProducer<T>.GetNull: IProducer<T>;
begin
Result := TMycProducer<T>.Null;
end;
class operator TProducer<T>.Initialize(out Dest: TProducer<T>);
begin
Dest.FProducer := Null;
end;
class operator TProducer<T>.Implicit(const A: IProducer<T>): TProducer<T>;
begin
Result.Create(A);
end;
class operator TProducer<T>.Implicit(const A: TProducer<T>): IProducer<T>;
begin
Result := A.FProducer;
end;
function TProducer<T>.Link(const Consumer: IConsumer<T>): TSubscription;
begin
Result.FProducer := FProducer;
Result.FTag := FProducer.Link(Consumer);
end;
function TProducer<T>.MakeParallel: TProducer<T>;
begin
Result := Chain<T>(TMycParallelConverter<T>.Create as IConverter<T, T>);
end;
constructor TConverter<S, T>.Create(const AConverter: IConverter<S, T>);
begin
FConverter := AConverter;
if not Assigned(FConverter) then
FConverter := Null;
end;
class function TConverter<S, T>.Construct(const Consumer: IConsumer<S>; const Producer: TProducer<T>): TConverter<S, T>;
begin
Result := TMycComposedConverter<S, T>.Create(Consumer, Producer);
end;
class function TConverter<S, T>.CreateAggregation(const Func: TConstFunc<S, TBroadcastProc, TState>): TConverter<S, T>;
begin
Result := TMycGenericAggregator<S, T>.Create(Func);
end;
class function TConverter<S, T>.CreateGeneric(const Func: TConstFunc<S, T>): TConverter<S, T>;
begin
Result := TMycGenericConverter<S, T>.Create(Func);
end;
function TConverter<S, T>.GetConsumer: IConsumer<S>;
begin
Result := FConverter.Consumer;
end;
function TConverter<S, T>.GetProducer: TProducer<T>;
begin
Result := FConverter;
end;
class function TConverter<S, T>.GetNull: IConverter<S, T>;
begin
Result := TMycConverter<S, T>.Null;
end;
class operator TConverter<S, T>.Initialize(out Dest: TConverter<S, T>);
begin
Dest.FConverter := Null;
end;
class operator TConverter<S, T>.Implicit(const A: IConverter<S, T>): TConverter<S, T>;
begin
Result.Create(A);
end;
class operator TConverter<S, T>.Implicit(const A: TConverter<S, T>): IConverter<S, T>;
begin
Result := A.FConverter;
end;
class function TConverter.CreateTickAggregation(Timeframe: TTimeframe): TConverter<TDataPoint<Double>, TDataPoint<TOhlcItem>>;
begin
Result := TTickAggregation.Create(Timeframe);
end;
{ TConverter }
class function TConverter.CreateCounter<T>: TConverter<T, Int64>;
begin
Result := TMycDataCounter<T>.Create;
end;
class function TConverter.CreateDataPointConverter<S, T>(const Func: TConstFunc<S, T>): TConverter<TDataPoint<S>, TDataPoint<T>>;
begin
var cFunc: TConstFunc<S, T> := Func;
Result :=
TMycGenericConverter<TDataPoint<S>, TDataPoint<T>>.Create(
function(const Value: TDataPoint<S>): TDataPoint<T>
begin
Result.Time := Value.Time;
Result.Data := cFunc(Value.Data);
end
);
end;
class function TConverter.CreateIdentity<T>: TConverter<T, T>;
begin
Result := TMycIdentityConverter<T>.Create;
end;
class function TConverter.CreateRecordField<S, T>(const FieldName: String): TConverter<S, T>;
begin
Result := TMycRecordFieldReader<S, T>.Create(FieldName);
end;
class function TConverter.CreateTicker<T>: TConverter<TArray<T>, T>;
begin
Result := TMycTicker<T>.Create;
end;
class function TConverter.CreateOhlcAggregation(Timeframe: TTimeframe): TConverter<TDataPoint<TOhlcItem>, TDataPoint<TOhlcItem>>;
begin
Result := TOhlcAggregation.Create(Timeframe);
end;
class function TConverter.DataMapping(
const Inputs: TArray<TRecordMapping>;
const Output: TDataRecord.TLayout
): TConverter<TArray<TDataRecord>, TDataRecord>;
var
Idxs: array of array of record
FromIdx, ToIdx: Integer
end;
begin
SetLength(Idxs, Length(Inputs));
for var i := 0 to High(Idxs) do
begin
SetLength(Idxs[i], Length(Inputs[i].Fields));
for var j := 0 to High(Idxs[i]) do
begin
Idxs[i][j].FromIdx := Inputs[i].Layout.IndexOf(Inputs[i].Fields[j].FromField);
Idxs[i][j].ToIdx := Output.IndexOf(Inputs[i].Fields[j].ToField);
end;
end;
Result :=
TConverter<TArray<TDataRecord>, TDataRecord>.CreateGeneric(
function(const Inputs: TArray<TDataRecord>): TDataRecord
begin
Result := TDataRecord.Create(Output);
for var i := 0 to High(Idxs) do
for var j := 0 to High(Idxs[i]) do
Inputs[i].CopyField(Idxs[i][j].FromIdx, Result, Idxs[i][j].ToIdx);
end
);
end;
class function TConverter.FieldOfRecord<T>(const Layout: TDataRecord.TLayout; const Name: String): TConverter<TDataRecord, T>;
begin
var idx := Layout.IndexOf(Name);
Result := TConverter<TDataRecord, T>.CreateGeneric(function(const Value: TDataRecord): T begin Value.GetValue(idx, Result); end);
end;
class function TConverter.FieldToRecord<T>(const Layout: TDataRecord.TLayout; const Name: String): TConverter<T, TDataRecord>;
begin
var idx := Layout.IndexOf(Name);
Result :=
TConverter<T, TDataRecord>.CreateGeneric(
function(const Value: T): TDataRecord
begin
Result := TDataRecord.Create(Layout);
Result.SetValue(idx, Value);
end
);
end;
class function TConverter.Join<T>(const Producers: TArray<TProducer<T>>): TProducer<TArray<T>>;
begin
Result := TMycDataJoin<T>.Create(Producers);
end;
class function TConverter.JoinRecords(
const TargetLayout: TDataRecord.TLayout;
const Mapping: TArray<TRecordMapping>;
const Producers: TArray<TProducer<TDataRecord>>
): TConverter<TArray<TDataRecord>, TDataRecord>;
begin
var RecProvider := Join<TDataRecord>(Producers);
Result := DataMapping(Mapping, TargetLayout);
// REFACTOR: Link to the consumer part of the mapping converter.
RecProvider.Link(Result.Consumer);
end;
end.