From 75675b8dc1f35fcac338f451be33e42d8d2ef3b3 Mon Sep 17 00:00:00 2001 From: Michael Schimmel Date: Mon, 28 Jul 2025 00:55:39 +0200 Subject: [PATCH] Testing TValue as Params --- AuraTrader/MainForm.pas | 28 +----- AuraTrader/StrategyTest.pas | 13 +-- Src/Myc.Trade.Indicators.Common.pas | 80 ++++++++--------- Src/Myc.Trade.Indicators.pas | 131 ++++++++++++++-------------- 4 files changed, 107 insertions(+), 145 deletions(-) diff --git a/AuraTrader/MainForm.pas b/AuraTrader/MainForm.pas index 62c6eb1..45b171e 100644 --- a/AuraTrader/MainForm.pas +++ b/AuraTrader/MainForm.pas @@ -293,6 +293,7 @@ end; procedure TForm1.Button1Click(Sender: TObject); begin Test1(LogMemo.Lines); + end; function TForm1.CreateStrategy2(Timeframe: TTimeframe): IConsumer>; @@ -321,19 +322,7 @@ begin var Lowest: Double := Double.MaxValue; var Highest: Double := Double.MinValue; - var ATR := - Ohlc - .Chain( - TConverter.CreateConverter( - function(const Ohlc: TOhlcItem): TATR.TArgs - begin - Result.Close := Ohlc.Close; - Result.High := Ohlc.High; - Result.Low := Ohlc.Low; - end - )) - .Chain(TATR.CreateATR(50)) - .MakeParallel; + var ATR := Ohlc.Chain(TATR.CreateATR(50)).MakeParallel; // next stage @@ -638,18 +627,7 @@ begin var Rsi := Closes.Chain(TRSI.CreateRSI(14)); var Macd := Closes.MakeParallel.Chain(TMACD.CreateMACD(12, 26, 9)); - var Stoch := - Ohlc - .Chain( - TConverter.CreateConverter( - function(const Ohlc: TOhlcItem): TStochastic.TArgs - begin - Result.Close := Ohlc.Close; - Result.High := Ohlc.High; - Result.Low := Ohlc.Low; - end - )) - .Chain(TStochastic.CreateStochastic(14, 3)); + var Stoch := Ohlc.Chain(TStochastic.CreateStochastic(14, 3)); chart.SetXAxisSeries(timeframe, Timestamps); diff --git a/AuraTrader/StrategyTest.pas b/AuraTrader/StrategyTest.pas index c4f1b0f..5101b70 100644 --- a/AuraTrader/StrategyTest.pas +++ b/AuraTrader/StrategyTest.pas @@ -38,18 +38,7 @@ begin var Hull := Closes.Chain(THMA.CreateHMA(250)).MakeParallel; var Sma := Closes.Chain(TSMA.CreateSMA(200)).MakeParallel; - var ATR := - Ohlc - .Chain( - TConverter.CreateConverter( - function(const Ohlc: TOhlcItem): TATR.TArgs - begin - Result.Close := Ohlc.Close; - Result.High := Ohlc.High; - Result.Low := Ohlc.Low; - end - )) - .Chain(TATR.CreateATR(50)); + var ATR := Ohlc.Chain(TATR.CreateATR(50)); var conv := TConverter.Join([Ohlc.Field('Low'), Ohlc.Field('High'), Closes, ATR, Hull, Sma]); diff --git a/Src/Myc.Trade.Indicators.Common.pas b/Src/Myc.Trade.Indicators.Common.pas index cd2bbf5..d257bf7 100644 --- a/Src/Myc.Trade.Indicators.Common.pas +++ b/Src/Myc.Trade.Indicators.Common.pas @@ -6,6 +6,7 @@ uses System.SysUtils, System.Math, System.Rtti, + Myc.Data.Records, Myc.Data.Pipeline, Myc.Data.Series, Myc.Trade.Types, @@ -147,9 +148,7 @@ type DPeriod: Integer; end; TArgs = record - High: Double; - Low: Double; - Close: Double; + Value: TOhlcItem; end; TResult = record K: Double; // %K line @@ -157,12 +156,12 @@ type end; [IndicatorFactory] class function CreateFactory: TIndicatorFactoryProc; static; - - class function CreateStochastic(KPeriod, DPeriod: Integer): TConvertFunc; overload; static; + class function CreateStochastic(KPeriod, DPeriod: Integer): TConvertFunc; overload; static; + // Creates a Stochastic Oscillator using an injectable moving average for the %D line. class function CreateStochastic( KPeriod: Integer; const SmaD: TConvertFunc - ): TConvertFunc; overload; static; + ): TConvertFunc; overload; static; end; [IndicatorName('StdDev', 'Standard Deviation')] @@ -215,17 +214,15 @@ type Period: Integer; end; TArgs = record - High: Double; - Low: Double; - Close: Double; + Value: TOhlcItem; end; TResult = record ATR: Double; end; [IndicatorFactory] class function CreateFactory: TIndicatorFactoryProc; static; - class function CreateATR(Period: Integer): TConvertFunc; overload; static; - class function CreateATR(const MovAvgTR: TConvertFunc): TConvertFunc; overload; static; + class function CreateATR(Period: Integer): TConvertFunc; overload; static; + class function CreateATR(const MovAvgTR: TConvertFunc): TConvertFunc; overload; static; end; [IndicatorName('KC', 'Keltner Channels')] @@ -238,9 +235,7 @@ type Multiplier: Double; end; TArgs = record - High: Double; - Low: Double; - Close: Double; + Value: TOhlcItem; end; TResult = record UpperBand: Double; @@ -249,12 +244,12 @@ type end; [IndicatorFactory] class function CreateFactory: TIndicatorFactoryProc; static; - class function CreateKeltnerChannels(Period: Integer; Multiplier: Double): TConvertFunc; overload; static; + class function CreateKeltnerChannels(Period: Integer; Multiplier: Double): TConvertFunc; overload; static; class function CreateKeltnerChannels( const MovAvgMiddle: TConvertFunc; - const AtrFunc: TConvertFunc; + const AtrFunc: TConvertFunc; Multiplier: Double - ): TConvertFunc; overload; static; + ): TConvertFunc; overload; static; end; [IndicatorName('Mean', 'Mean Value')] @@ -713,22 +708,25 @@ begin Result := function(const Params: TParams): TConvertFunc var - stochFunc: TConvertFunc; + stochFunc: TConvertFunc; begin stochFunc := CreateStochastic(Params.KPeriod, Params.DPeriod); - Result := function(const Value: TArgs): TResult begin Result := stochFunc(Value); end; + Result := function(const Value: TArgs): TResult begin Result := stochFunc(Value.Value); end; end; end; -class function TStochastic.CreateStochastic(KPeriod, DPeriod: Integer): TConvertFunc; +class function TStochastic.CreateStochastic(KPeriod, DPeriod: Integer): TConvertFunc; begin Result := CreateStochastic(KPeriod, TSMA.CreateSMA(DPeriod)); end; // Creates a Stochastic Oscillator using an injectable moving average for the %D line. -class function TStochastic.CreateStochastic(KPeriod: Integer; const SmaD: TConvertFunc): TConvertFunc; +class function TStochastic.CreateStochastic( + KPeriod: Integer; + const SmaD: TConvertFunc +): TConvertFunc; var - buffer: TArray; + buffer: TArray; highDeque: TLightDeque; lowDeque: TLightDeque; valueCount: Int64; @@ -736,7 +734,7 @@ begin if (KPeriod <= 0) then begin Result := - function(const Value: TArgs): TResult + function(const Value: TOhlcItem): TResult begin Result.K := Double.NaN; Result.D := Double.NaN; @@ -750,7 +748,7 @@ begin valueCount := 0; Result := - function(const Value: TArgs): TStochastic.TResult + function(const Value: TOhlcItem): TStochastic.TResult var currentIndex, firstIndex, lastIndex: Integer; highestHigh, lowestLow: Double; @@ -928,25 +926,25 @@ begin Result := function(const Params: TParams): TConvertFunc var - atrFunc: TConvertFunc; + atrFunc: TConvertFunc; begin atrFunc := CreateATR(Params.Period); - Result := function(const Value: TArgs): TResult begin Result.ATR := atrFunc(Value); end; + Result := function(const Value: TArgs): TResult begin Result.ATR := atrFunc(Value.Value); end; end; end; -class function TATR.CreateATR(Period: Integer): TConvertFunc; +class function TATR.CreateATR(Period: Integer): TConvertFunc; begin Result := CreateATR(TEMA.CreateEMA(Period)); end; // Calculates the Average True Range (ATR) using an injectable moving average. -class function TATR.CreateATR(const MovAvgTR: TConvertFunc): TConvertFunc; +class function TATR.CreateATR(const MovAvgTR: TConvertFunc): TConvertFunc; begin - var sourceData: TSeries; + var sourceData: TSeries; Result := - function(const Value: TArgs): Double + function(const Value: TOhlcItem): Double var tr: Double; begin @@ -970,19 +968,20 @@ end; { TKeltnerChannels } -class function TKeltnerChannels.CreateFactory: TIndicatorFactoryProc; +class function TKeltnerChannels.CreateFactory: + TIndicatorFactoryProc; begin Result := function(const Params: TParams): TConvertFunc var - kcFunc: TConvertFunc; + kcFunc: TConvertFunc; begin kcFunc := CreateKeltnerChannels(Params.Period, Params.Multiplier); - Result := function(const Value: TArgs): TResult begin Result := kcFunc(Value); end; + Result := function(const Value: TArgs): TResult begin Result := kcFunc(Value.Value); end; end; end; -class function TKeltnerChannels.CreateKeltnerChannels(Period: Integer; Multiplier: Double): TConvertFunc; +class function TKeltnerChannels.CreateKeltnerChannels(Period: Integer; Multiplier: Double): TConvertFunc; begin Result := CreateKeltnerChannels(TEMA.CreateEMA(Period), TATR.CreateATR(Period), Multiplier); end; @@ -990,12 +989,12 @@ end; // Calculates Keltner Channels using an injectable ATR and middle band moving average. class function TKeltnerChannels.CreateKeltnerChannels( const MovAvgMiddle: TConvertFunc; - const AtrFunc: TConvertFunc; + const AtrFunc: TConvertFunc; Multiplier: Double -): TConvertFunc; +): TConvertFunc; begin Result := - function(const Value: TArgs): TKeltnerChannels.TResult + function(const Value: TOhlcItem): TKeltnerChannels.TResult var atrValue, middleValue, typicalPrice: Double; begin @@ -1004,12 +1003,7 @@ begin // Get values from the provided indicator functions. middleValue := MovAvgMiddle(typicalPrice); - - var atrArgs: TATR.TArgs; - atrArgs.High := Value.High; - atrArgs.Low := Value.Low; - atrArgs.Close := Value.Close; - atrValue := AtrFunc(atrArgs); + atrValue := AtrFunc(Value); // Set default NaN values for the warm-up period. Result.MiddleBand := middleValue; diff --git a/Src/Myc.Trade.Indicators.pas b/Src/Myc.Trade.Indicators.pas index 0090992..98f73bd 100644 --- a/Src/Myc.Trade.Indicators.pas +++ b/Src/Myc.Trade.Indicators.pas @@ -5,12 +5,13 @@ interface {$M+} uses + System.TypInfo, System.Rtti, Myc.Data.Pipeline, System.Classes; type - TIndicatorFactoryProc = reference to function(const Params: TParams): TConvertFunc; + TIndicatorFactoryProc = reference to function(const Params: TParams): TConvertFunc; (* Sample definition of an indicator template: @@ -65,23 +66,24 @@ type TFieldDef = record public Identifier: String; - TypeName: String; + // The handle provides direct access to the type information of the field. + Handle: PTypeInfo; end; TFieldLayout = TArray; // Interface for creating an indicator instance. Only contains functional aspects. IIndicatorFactory = interface - function GetParams: TFieldLayout; - function GetArgs: TFieldLayout; - function GetResults: TFieldLayout; + function GetParamType: PTypeInfo; + function GetArgType: PTypeInfo; + function GetResultType: PTypeInfo; function CreateIndicator(const Params: TValue): TConvertFunc; - // Provides the layout for the indicator's parameters, arguments, and results. - property Params: TFieldLayout read GetParams; - property Args: TFieldLayout read GetArgs; - property Results: TFieldLayout read GetResults; + // Provides direct access to the type information of parameters, arguments, and results. + property ParamType: PTypeInfo read GetParamType; + property ArgType: PTypeInfo read GetArgType; + property ResultType: PTypeInfo read GetResultType; end; TGenericIndicatorFactory = class(TInterfacedObject, IIndicatorFactory) @@ -90,23 +92,21 @@ type FName: String; FShortName: String; FHint: String; - FParams: TFieldLayout; - FArgs: TFieldLayout; - FResults: TFieldLayout; - - class function LayoutFromRecordType(RecordType: TRttiType): TFieldLayout; static; + FParamType: PTypeInfo; + FArgType: PTypeInfo; + FResultType: PTypeInfo; public constructor Create( const AFactoryProc: TIndicatorFactoryProc; const AShortName, AName, AHint: String; - const AParams, AArgs, AResults: TFieldLayout + const AParamType, AArgType, AResultType: PTypeInfo ); // IIndicatorFactory - function GetParams: TFieldLayout; - function GetArgs: TFieldLayout; - function GetResults: TFieldLayout; + function GetParamType: PTypeInfo; + function GetArgType: PTypeInfo; + function GetResultType: PTypeInfo; function CreateIndicator(const Params: TValue): TConvertFunc; overload; class function CreateFromTemplate: TGenericIndicatorFactory; @@ -165,8 +165,7 @@ var implementation uses - System.SysUtils, - System.TypInfo; + System.SysUtils; constructor IndicatorNameAttribute.Create(const AShortName, AName: string); begin @@ -186,7 +185,7 @@ end; constructor TGenericIndicatorFactory.Create( const AFactoryProc: TIndicatorFactoryProc; const AShortName, AName, AHint: String; - const AParams, AArgs, AResults: TFieldLayout + const AParamType, AArgType, AResultType: PTypeInfo ); begin inherited Create; @@ -194,30 +193,9 @@ begin FShortName := AShortName; FName := AName; FHint := AHint; - FParams := AParams; - FArgs := AArgs; - FResults := AResults; -end; - -class function TGenericIndicatorFactory.LayoutFromRecordType(RecordType: TRttiType): TFieldLayout; -var - rttiRecordType: TRttiRecordType; - field: TRttiField; - i: Integer; -begin - if not RecordType.IsRecord then - raise EArgumentException.CreateFmt('Record type expected, but got %s', [RecordType.Name]); - - rttiRecordType := RecordType as TRttiRecordType; - var fields := rttiRecordType.GetFields; - SetLength(Result, Length(fields)); - i := 0; - for field in fields do - begin - Result[i].Identifier := field.Name; - Result[i].TypeName := field.FieldType.Name; - Inc(i); - end; + FParamType := AParamType; + FArgType := AArgType; + FResultType := AResultType; end; class function TGenericIndicatorFactory.CreateFromTemplate: TGenericIndicatorFactory; @@ -228,7 +206,6 @@ var templateFactoryMethod: TRttiMethod; factoryProc: TIndicatorFactoryProc; shortName, name, hint: string; - paramsLayout, argsLayout, resultsLayout: TFieldLayout; begin // This function creates a generic factory from a template class. // It uses RTTI to find the necessary types by inspecting the factory method signature, @@ -300,11 +277,6 @@ begin Assert(argsType.TypeKind = tkRecord); Assert(resultType.TypeKind = tkRecord); - // Generate layouts from RTTI types. - paramsLayout := LayoutFromRecordType(paramsType); - argsLayout := LayoutFromRecordType(argsType); - resultsLayout := LayoutFromRecordType(resultType); - // Extract metadata from attributes on the template type T. shortName := ''; name := ''; @@ -349,8 +321,6 @@ begin // The parameter for this 'Invoke' call is the TParams record. // Wrap the incoming TValue 'Params' into a TValue for the call. - var factoryParamTypeInfo := currentFactoryInvoke.GetParameters[0].ParamType.Handle; - Assert(factoryParamTypeInfo = paramsType.Handle); // This call returns the worker proc (e.g., a TIndicatorProc) as a TValue. var workerProc := currentFactoryInvoke.Invoke(factoryProc, [Params]); @@ -369,23 +339,23 @@ begin end; end; - // Create the final factory instance with all layouts and extracted metadata. - Result := TGenericIndicatorFactory.Create(factoryProc, shortName, name, hint, paramsLayout, argsLayout, resultsLayout); + // Create the final factory instance with all type handles and extracted metadata. + Result := TGenericIndicatorFactory.Create(factoryProc, shortName, name, hint, paramsType.Handle, argsType.Handle, resultType.Handle); end; -function TGenericIndicatorFactory.GetParams: TFieldLayout; +function TGenericIndicatorFactory.GetParamType: PTypeInfo; begin - Result := FParams; + Result := FParamType; end; -function TGenericIndicatorFactory.GetArgs: TFieldLayout; +function TGenericIndicatorFactory.GetArgType: PTypeInfo; begin - Result := FArgs; + Result := FArgType; end; -function TGenericIndicatorFactory.GetResults: TFieldLayout; +function TGenericIndicatorFactory.GetResultType: PTypeInfo; begin - Result := FResults; + Result := FResultType; end; function TGenericIndicatorFactory.CreateIndicator(const Params: TValue): TConvertFunc; @@ -451,6 +421,36 @@ procedure TIndicatorRegistry.LogRegistry(const Log: TStrings); var item: TItem; + // Generates a field layout from a type information handle. + function LayoutFromTypeInfo(TypeInfo: PTypeInfo): TFieldLayout; + var + Ctx: TRttiContext; + rttiType: TRttiType; + rttiRecordType: TRttiRecordType; + field: TRttiField; + i: Integer; + begin + // This helper uses RTTI to inspect the record type and build the layout. + Ctx := TRttiContext.Create; + rttiType := Ctx.GetType(TypeInfo); + if not rttiType.IsRecord then + begin + SetLength(Result, 0); + exit; + end; + + rttiRecordType := rttiType as TRttiRecordType; + var fields := rttiRecordType.GetFields; + SetLength(Result, Length(fields)); + i := 0; + for field in fields do + begin + Result[i].Identifier := field.Name; + Result[i].Handle := field.FieldType.Handle; + Inc(i); + end; + end; + procedure DoLog(const Txt: String); begin Log.Add(Txt); @@ -468,7 +468,7 @@ var else begin for field in Layout do - DoLog(Format(' - %s: %s', [field.Identifier, field.TypeName])); + DoLog(Format(' - %s: %s', [field.Identifier, field.Handle.Name])); end; end; @@ -493,9 +493,10 @@ begin if not item.Hint.IsEmpty then DoLog(Format(' Hint: %s', [item.Hint])); - LogLayout('Params', item.Factory.Params); - LogLayout('Args', item.Factory.Args); - LogLayout('Result', item.Factory.Results); + // Generate layouts on-the-fly from the factory's type info. + LogLayout('Params', LayoutFromTypeInfo(item.Factory.ParamType)); + LogLayout('Args', LayoutFromTypeInfo(item.Factory.ArgType)); + LogLayout('Result', LayoutFromTypeInfo(item.Factory.ResultType)); DoLog(''); end; end;