Testing TValue as Params

This commit is contained in:
Michael Schimmel
2025-07-27 23:12:48 +02:00
parent 78e89f345e
commit 1ddc295c9d
4 changed files with 176 additions and 172 deletions
+3
View File
@@ -51,6 +51,7 @@ uses
FMX.ActnList, FMX.ActnList,
DynamicFMXControl, DynamicFMXControl,
Myc.FMX.Chart, Myc.FMX.Chart,
Myc.Trade.Indicators,
Myc.Trade.Indicators.Common, Myc.Trade.Indicators.Common,
StrategyTest, StrategyTest,
TestMethodCallFromRecordParams; TestMethodCallFromRecordParams;
@@ -255,6 +256,8 @@ begin
); );
NewWorkspace; NewWorkspace;
IndicatorRegistry.LogRegistry(LogMemo.Lines);
end; end;
procedure TForm1.FormDestroy(Sender: TObject); procedure TForm1.FormDestroy(Sender: TObject);
+8 -70
View File
@@ -5,6 +5,7 @@ interface
uses uses
System.SysUtils, System.SysUtils,
System.Classes, System.Classes,
System.Rtti,
Myc.Data.Records, Myc.Data.Records,
Myc.Data.Pipeline, Myc.Data.Pipeline,
Myc.Trade.Indicators; Myc.Trade.Indicators;
@@ -52,8 +53,6 @@ type
end; end;
procedure Test1(const Log: TStrings); procedure Test1(const Log: TStrings);
procedure TestSma(const Log: TStrings);
implementation implementation
uses uses
@@ -81,20 +80,18 @@ procedure Test1(const Log: TStrings);
begin begin
var fact := TGenericIndicatorFactory.CreateFromTemplate<TMyWorker>; var fact := TGenericIndicatorFactory.CreateFromTemplate<TMyWorker>;
var params := TDataRecord.FromRecord<TMyWorker.TParams>; var params: TMyWorker.TParams;
params.SetValue<Int64>('Log', Int64(Log)); params.Log := Int64(Log);
params.SetValue<String>('text', 'The quick brown fox jumps...'); params.text := 'The quick brown fox jumps...';
var indi := fact.CreateIndicator(params); var indi := fact.CreateIndicator<TMyWorker.TParams, TMyWorker.TArgs, TMyWorker.TResult>(params);
var args := TDataRecord.FromRecord<TMyWorker.TArgs>; var args: TMyWorker.TArgs;
args.SetValue<Double>('xyz', 3.14159); args.xyz := 3.14159;
var res := indi(args); var res := indi(args);
var desc := res.GetValue<String>('desc'); Log.Add('Final result description: ' + res.desc);
Log.Add('Final result description: ' + desc);
Assert(desc = 'done');
end; end;
{ TSmaIndicator } { TSmaIndicator }
@@ -147,63 +144,4 @@ begin
end; end;
end; end;
procedure TestSma(const Log: TStrings);
var
fact: TGenericIndicatorFactory;
params: TDataRecord;
indi: TConvertFunc<TDataRecord, TDataRecord>;
args: TDataRecord;
res: TDataRecord;
smaValue: Double;
begin
Log.Add('');
Log.Add('--- Testing SMA ---');
fact := TGenericIndicatorFactory.CreateFromTemplate<TSmaIndicator>;
// 1. Setup indicator parameters.
params := TDataRecord.FromRecord<TSmaIndicator.TParams>;
params.SetValue<Integer>('Period', 3);
Log.Add(Format('Creating SMA with Period=%d', [params.GetValue<Integer>('Period')]));
// 2. Create the indicator instance.
indi := fact.CreateIndicator(params);
// 3. Create the arguments record (can be reused).
args := TDataRecord.FromRecord<TSmaIndicator.TArgs>;
// 4. Feed values and check results.
args.SetValue<Double>('Value', 10.0);
res := indi(args);
smaValue := res.GetValue<Double>('Sma');
Log.Add(Format('Input: 10.0 -> SMA: %f', [smaValue]));
Assert(SameValue(smaValue, 10.0)); // Avg of [10] is 10
args.SetValue<Double>('Value', 11.0);
res := indi(args);
smaValue := res.GetValue<Double>('Sma');
Log.Add(Format('Input: 11.0 -> SMA: %f', [smaValue]));
Assert(SameValue(smaValue, 10.5)); // Avg of [10, 11] is 10.5
args.SetValue<Double>('Value', 12.0);
res := indi(args);
smaValue := res.GetValue<Double>('Sma');
Log.Add(Format('Input: 12.0 -> SMA: %f', [smaValue]));
Assert(SameValue(smaValue, 11.0)); // Avg of [10, 11, 12] is 11
args.SetValue<Double>('Value', 13.0);
res := indi(args);
smaValue := res.GetValue<Double>('Sma');
Log.Add(Format('Input: 13.0 -> SMA: %f', [smaValue]));
Assert(SameValue(smaValue, 12.0)); // Avg of [11, 12, 13] is 12
args.SetValue<Double>('Value', 14.0);
res := indi(args);
smaValue := res.GetValue<Double>('Sma');
Log.Add(Format('Input: 14.0 -> SMA: %f', [smaValue]));
Assert(SameValue(smaValue, 13.0)); // Avg of [12, 13, 14] is 13
Log.Add('SMA test successful.');
end;
end. end.
+1 -2
View File
@@ -6,7 +6,6 @@ uses
System.SysUtils, System.SysUtils,
System.Math, System.Math,
System.Rtti, System.Rtti,
Myc.Data.Records,
Myc.Data.Pipeline, Myc.Data.Pipeline,
Myc.Data.Series, Myc.Data.Series,
Myc.Trade.Types, Myc.Trade.Types,
@@ -1071,6 +1070,6 @@ initialization
IndicatorRegistry.RegisterTemplate<TBollingerBands>; IndicatorRegistry.RegisterTemplate<TBollingerBands>;
IndicatorRegistry.RegisterTemplate<TATR>; IndicatorRegistry.RegisterTemplate<TATR>;
IndicatorRegistry.RegisterTemplate<TKeltnerChannels>; IndicatorRegistry.RegisterTemplate<TKeltnerChannels>;
// IndicatorRegistry.RegisterTemplate<TMean>; IndicatorRegistry.RegisterTemplate<TMean>;
end. end.
+164 -100
View File
@@ -5,8 +5,9 @@ interface
{$M+} {$M+}
uses uses
System.Rtti,
Myc.Data.Pipeline, Myc.Data.Pipeline,
Myc.Data.Records; System.Classes;
type type
TIndicatorFactoryProc<TParams, TValue, TResult> = reference to function(const Params: TParams): TConvertFunc<TValue, TResult>; TIndicatorFactoryProc<TParams, TValue, TResult> = reference to function(const Params: TParams): TConvertFunc<TValue, TResult>;
@@ -61,47 +62,56 @@ type
property Hint: string read FHint; property Hint: string read FHint;
end; end;
TFieldDef = record
public
Identifier: String;
TypeName: String;
end;
TFieldLayout = TArray<TFieldDef>;
// Interface for creating an indicator instance. Only contains functional aspects. // Interface for creating an indicator instance. Only contains functional aspects.
IIndicatorFactory = interface IIndicatorFactory = interface
{$region 'private'} function GetParams: TFieldLayout;
function GetArgumentLayout: TDataRecord.TLayout; function GetArgs: TFieldLayout;
function GetParameterLayout: TDataRecord.TLayout; function GetResults: TFieldLayout;
function GetResultLayout: TDataRecord.TLayout;
{$endregion}
function CreateIndicator(const Params: TDataRecord): TConvertFunc<TDataRecord, TDataRecord>; function CreateIndicator(const Params: TValue): TConvertFunc<TValue, TValue>;
property ParameterLayout: TDataRecord.TLayout read GetParameterLayout; // Provides the layout for the indicator's parameters, arguments, and results.
property ArgumentLayout: TDataRecord.TLayout read GetArgumentLayout; property Params: TFieldLayout read GetParams;
property ResultLayout: TDataRecord.TLayout read GetResultLayout; property Args: TFieldLayout read GetArgs;
property Results: TFieldLayout read GetResults;
end; end;
TGenericIndicatorFactory = class(TInterfacedObject, IIndicatorFactory) TGenericIndicatorFactory = class(TInterfacedObject, IIndicatorFactory)
private private
FParameterLayout: TDataRecord.TLayout; FFactoryProc: TIndicatorFactoryProc<TValue, TValue, TValue>;
FFactoryProc: TIndicatorFactoryProc<TDataRecord, TDataRecord, TDataRecord>;
FName: String; FName: String;
FArgumentLayout: TDataRecord.TLayout;
FResultLayout: TDataRecord.TLayout;
FShortName: String; FShortName: String;
FHint: String; FHint: String;
function GetArgumentLayout: TDataRecord.TLayout; FParams: TFieldLayout;
function GetParameterLayout: TDataRecord.TLayout; FArgs: TFieldLayout;
function GetResultLayout: TDataRecord.TLayout; FResults: TFieldLayout;
class function LayoutFromRecordType(RecordType: TRttiType): TFieldLayout; static;
public public
constructor Create( constructor Create(
const AParameterLayout, AArgumentLayout, AResultLayout: TDataRecord.TLayout; const AFactoryProc: TIndicatorFactoryProc<TValue, TValue, TValue>;
const AFactoryProc: TIndicatorFactoryProc<TDataRecord, TDataRecord, TDataRecord>; const AShortName, AName, AHint: String;
const AShortName, AName, AHint: String const AParams, AArgs, AResults: TFieldLayout
); );
// IIndicatorFactory
function GetParams: TFieldLayout;
function GetArgs: TFieldLayout;
function GetResults: TFieldLayout;
function CreateIndicator(const Params: TValue): TConvertFunc<TValue, TValue>; overload;
class function CreateFromTemplate<T>: TGenericIndicatorFactory; class function CreateFromTemplate<T>: TGenericIndicatorFactory;
function CreateIndicator(const Params: TDataRecord): TConvertFunc<TDataRecord, TDataRecord>; function CreateIndicator<TParams, TArgs, TResult>(const Params: TParams): TConvertFunc<TArgs, TResult>; overload;
property ParameterLayout: TDataRecord.TLayout read GetParameterLayout;
property ArgumentLayout: TDataRecord.TLayout read GetArgumentLayout;
property ResultLayout: TDataRecord.TLayout read GetResultLayout;
property Name: String read FName; property Name: String read FName;
property ShortName: String read FShortName; property ShortName: String read FShortName;
@@ -120,6 +130,7 @@ type
FHint: string; FHint: string;
public public
constructor Create(const AFactory: IIndicatorFactory; const AShortName, AName, AHint: string); constructor Create(const AFactory: IIndicatorFactory; const AShortName, AName, AHint: string);
function CreateIndicator<TParams, TArgs, TResult>(const Params: TParams): TConvertFunc<TArgs, TResult>; overload;
property Factory: IIndicatorFactory read FFactory; property Factory: IIndicatorFactory read FFactory;
property Name: string read FName; property Name: string read FName;
property ShortName: string read FShortName; property ShortName: string read FShortName;
@@ -127,10 +138,14 @@ type
end; end;
private private
FItems: TArray<TItem>; FItems: TArray<TItem>;
public public
constructor Create; constructor Create;
destructor Destroy; override; destructor Destroy; override;
// Writes the contents of the registry (just like the log)
procedure LogRegistry(const Log: TStrings);
// Register indicator from a template class // Register indicator from a template class
procedure RegisterTemplate<TIndicatorTemplate>; procedure RegisterTemplate<TIndicatorTemplate>;
@@ -151,8 +166,7 @@ implementation
uses uses
System.SysUtils, System.SysUtils,
System.TypInfo, System.TypInfo;
System.Rtti;
constructor IndicatorNameAttribute.Create(const AShortName, AName: string); constructor IndicatorNameAttribute.Create(const AShortName, AName: string);
begin begin
@@ -170,19 +184,40 @@ end;
{ TGenericIndicatorFactory } { TGenericIndicatorFactory }
constructor TGenericIndicatorFactory.Create( constructor TGenericIndicatorFactory.Create(
const AParameterLayout, AArgumentLayout, AResultLayout: TDataRecord.TLayout; const AFactoryProc: TIndicatorFactoryProc<TValue, TValue, TValue>;
const AFactoryProc: TIndicatorFactoryProc<TDataRecord, TDataRecord, TDataRecord>; const AShortName, AName, AHint: String;
const AShortName, AName, AHint: String const AParams, AArgs, AResults: TFieldLayout
); );
begin begin
inherited Create; inherited Create;
FParameterLayout := AParameterLayout;
FArgumentLayout := AArgumentLayout;
FResultLayout := AResultLayout;
FFactoryProc := AFactoryProc; FFactoryProc := AFactoryProc;
FShortName := AShortName; FShortName := AShortName;
FName := AName; FName := AName;
FHint := AHint; 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;
end; end;
class function TGenericIndicatorFactory.CreateFromTemplate<T>: TGenericIndicatorFactory; class function TGenericIndicatorFactory.CreateFromTemplate<T>: TGenericIndicatorFactory;
@@ -191,14 +226,14 @@ var
rttiType: TRttiType; rttiType: TRttiType;
paramsType, argsType, resultType: TRttiType; paramsType, argsType, resultType: TRttiType;
templateFactoryMethod: TRttiMethod; templateFactoryMethod: TRttiMethod;
parameterLayout, argumentLayout, resultLayout: TDataRecord.TLayout; factoryProc: TIndicatorFactoryProc<TValue, TValue, TValue>;
factoryProc: TIndicatorFactoryProc<TDataRecord, TDataRecord, TDataRecord>;
shortName, name, hint: string; shortName, name, hint: string;
paramsLayout, argsLayout, resultsLayout: TFieldLayout;
begin begin
// This function creates a generic factory from a template class. // This function creates a generic factory from a template class.
// It uses RTTI to find the necessary types by inspecting the factory method signature, // It uses RTTI to find the necessary types by inspecting the factory method signature,
// and then constructs a set of wrappers to adapt the specific types of the template // and then constructs a set of wrappers to adapt the specific types of the template
// to the generic TDataRecord used by this factory. // to the generic TValue used by this factory.
Ctx := TRttiContext.Create; Ctx := TRttiContext.Create;
rttiType := Ctx.GetType(TypeInfo(T)); rttiType := Ctx.GetType(TypeInfo(T));
@@ -265,10 +300,10 @@ begin
Assert(argsType.TypeKind = tkRecord); Assert(argsType.TypeKind = tkRecord);
Assert(resultType.TypeKind = tkRecord); Assert(resultType.TypeKind = tkRecord);
// Create the layouts for parameters, arguments, and results. // Generate layouts from RTTI types.
parameterLayout := TDataRecord.TLayout.FromRecord(paramsType.Handle); paramsLayout := LayoutFromRecordType(paramsType);
argumentLayout := TDataRecord.TLayout.FromRecord(argsType.Handle); argsLayout := LayoutFromRecordType(argsType);
resultLayout := TDataRecord.TLayout.FromRecord(resultType.Handle); resultsLayout := LayoutFromRecordType(resultType);
// Extract metadata from attributes on the template type T. // Extract metadata from attributes on the template type T.
shortName := ''; shortName := '';
@@ -300,95 +335,68 @@ begin
// Create the main factory procedure. This is a double-nested anonymous method // Create the main factory procedure. This is a double-nested anonymous method
// that wraps the template's specific factory and worker functions. // that wraps the template's specific factory and worker functions.
factoryProc := factoryProc :=
function(const Params: TDataRecord): TConvertFunc<TDataRecord, TDataRecord> function(const Params: TValue): TConvertFunc<TValue, TValue>
begin begin
// Outer anonymous method: This is the factory proc. // Outer anonymous method: This is the factory proc.
// It gets called with a TDataRecord of parameters. // It gets called with a TValue wrapping the parameters record.
// 1. Invoke the template's static factory method (e.g., TMyWorker.CreateFactory) // 1. Invoke the template's static factory method (e.g., TMyWorker.CreateFactory)
var factoryProcAsValue := templateFactoryMethod.Invoke(TValue.Empty, []); var factoryProc := templateFactoryMethod.Invoke(TValue.Empty, []);
// 2. Invoke the factory proc itself to get the actual worker proc. // 2. Invoke the factory proc itself to get the actual worker proc.
var rttiFactoryProc := Ctx.GetType(factoryProcAsValue.TypeInfo) as TRttiInterfaceType; var rttiFactoryProc := Ctx.GetType(factoryProc.TypeInfo) as TRttiInterfaceType;
var currentFactoryInvoke := rttiFactoryProc.GetMethod('Invoke'); var currentFactoryInvoke := rttiFactoryProc.GetMethod('Invoke');
// The parameter for this 'Invoke' call is the TParams record. // The parameter for this 'Invoke' call is the TParams record.
// Wrap the incoming TDataRecord 'Params' into a TValue for the call. // Wrap the incoming TValue 'Params' into a TValue for the call.
var factoryParamTypeInfo := currentFactoryInvoke.GetParameters[0].ParamType.Handle; var factoryParamTypeInfo := currentFactoryInvoke.GetParameters[0].ParamType.Handle;
Assert(factoryParamTypeInfo = paramsType.Handle); Assert(factoryParamTypeInfo = paramsType.Handle);
var factoryArg: array[0..0] of TValue;
TValue.Make(Params.RawData, factoryParamTypeInfo, factoryArg[0]);
// This call returns the worker proc (e.g., a TIndicatorProc<TValue, TResult>) as a TValue. // This call returns the worker proc (e.g., a TIndicatorProc<TValue, TResult>) as a TValue.
var workerProcAsValue := currentFactoryInvoke.Invoke(factoryProcAsValue, factoryArg); var workerProc := currentFactoryInvoke.Invoke(factoryProc, [Params]);
// 3. Return a new anonymous method that wraps the worker proc. // 3. Return a new anonymous method that wraps the worker proc.
// This wrapper conforms to the generic TIndicatorProc<TDataRecord, TDataRecord> signature. // This wrapper conforms to the generic TIndicatorProc<TValue, TValue> signature.
var rttiWorkerProc := Ctx.GetType(workerProcAsValue.TypeInfo); var rttiWorkerProc := Ctx.GetType(workerProc.TypeInfo);
var currentIndicatorInvoke := rttiWorkerProc.GetMethod('Invoke'); var currentIndicatorInvoke := rttiWorkerProc.GetMethod('Invoke');
var indicatorParamTypeInfo := currentIndicatorInvoke.GetParameters[0].ParamType.Handle;
Result := Result :=
function(const Args: TDataRecord): TDataRecord function(const Args: TValue): TValue
begin begin
// Sadly, it's not possible to inject a buffer into a TValue. So we need to copy both Args and Result.
// Inner anonymous method: This is the actual indicator proc wrapper. // Inner anonymous method: This is the actual indicator proc wrapper.
// The layouts are captured from the outer scope.
Assert(Args.Layout = argumentLayout);
// Prepare argument for the indicator proc invocation.
// TValue just carries the data, ownership is held by the caller.
var argVal: array[0..0] of TValue;
TValue.MakeWithoutCopy(Args.RawData, indicatorParamTypeInfo, argVal[0], true);
// Invoke the actual indicator proc. // Invoke the actual indicator proc.
var resultAsTValue := currentIndicatorInvoke.Invoke(workerProcAsValue, argVal); Result := currentIndicatorInvoke.Invoke(workerProc, [Args]);
// The result is a TValue containing the result record.
// Raw copy and erase all data from the TValue, leaving it as an empty capsule. Ownership is taken
// over to the resulting TDataRecord. This works because the memory layouts are exactly the same.
Assert(TDataRecord.TLayout.FromRecord(resultAsTValue.TypeInfo) = resultLayout);
var buf: TBytes;
var resultSize := resultLayout.Size;
SetLength(buf, resultSize);
var src := resultAsTValue.GetReferenceToRawData;
Move(src^, buf[0], resultSize);
FillChar(src^, resultSize, 0);
Result.Create(resultLayout, buf);
end; end;
end; end;
// Create the final factory instance with all layouts and extracted metadata. // Create the final factory instance with all layouts and extracted metadata.
Result := TGenericIndicatorFactory.Create(parameterLayout, argumentLayout, resultLayout, factoryProc, shortName, name, hint); Result := TGenericIndicatorFactory.Create(factoryProc, shortName, name, hint, paramsLayout, argsLayout, resultsLayout);
end; end;
function TGenericIndicatorFactory.CreateIndicator(const Params: TDataRecord): TConvertFunc<TDataRecord, TDataRecord>; function TGenericIndicatorFactory.GetParams: TFieldLayout;
begin
Result := FParams;
end;
function TGenericIndicatorFactory.GetArgs: TFieldLayout;
begin
Result := FArgs;
end;
function TGenericIndicatorFactory.GetResults: TFieldLayout;
begin
Result := FResults;
end;
function TGenericIndicatorFactory.CreateIndicator(const Params: TValue): TConvertFunc<TValue, TValue>;
begin begin
Assert(
(not Assigned(FParameterLayout.Fields)) or (Params.Layout = FParameterLayout),
'Invalid parameter layout for indicator creation'
);
Result := FFactoryProc(Params); Result := FFactoryProc(Params);
end; end;
function TGenericIndicatorFactory.GetArgumentLayout: TDataRecord.TLayout; function TGenericIndicatorFactory.CreateIndicator<TParams, TArgs, TResult>(const Params: TParams): TConvertFunc<TArgs, TResult>;
begin begin
Result := FArgumentLayout; var cIndicator := FFactoryProc(TValue.From<TParams>(Params));
end; Result := function(const Args: TArgs): TResult begin Result := cIndicator(TValue.From<TArgs>(Args)).AsType<TResult>; end;
function TGenericIndicatorFactory.GetParameterLayout: TDataRecord.TLayout;
begin
Result := FParameterLayout;
end;
function TGenericIndicatorFactory.GetResultLayout: TDataRecord.TLayout;
begin
Result := FResultLayout;
end; end;
{ TIndicatorRegistry.TItem } { TIndicatorRegistry.TItem }
@@ -402,6 +410,12 @@ begin
FHint := AHint; FHint := AHint;
end; end;
function TIndicatorRegistry.TItem.CreateIndicator<TParams, TArgs, TResult>(const Params: TParams): TConvertFunc<TArgs, TResult>;
begin
var cIndicator := FFactory.CreateIndicator(TValue.From<TParams>(Params));
Result := function(const Args: TArgs): TResult begin Result := cIndicator(TValue.From<TArgs>(Args)).AsType<TResult>; end;
end;
{ TIndicatorRegistry } { TIndicatorRegistry }
constructor TIndicatorRegistry.Create; constructor TIndicatorRegistry.Create;
@@ -433,13 +447,63 @@ begin
Result := nil; Result := nil;
end; end;
procedure TIndicatorRegistry.LogRegistry(const Log: TStrings);
var
item: TItem;
procedure DoLog(const Txt: String);
begin
Log.Add(Txt);
end;
procedure LogLayout(const LayoutName: string; const Layout: TFieldLayout);
var
field: TFieldDef;
begin
DoLog(Format(' %s:', [LayoutName]));
if Length(Layout) = 0 then
begin
DoLog(' (none)');
end
else
begin
for field in Layout do
DoLog(Format(' - %s: %s', [field.Identifier, field.TypeName]));
end;
end;
begin
if not Assigned(Log) then
exit;
Log.Clear;
DoLog(Format('Indicator Registry Content (%d items)', [Length(FItems)]));
DoLog('==========================================');
DoLog('');
if Length(FItems) = 0 then
begin
DoLog('(Registry is empty)');
exit;
end;
for item in FItems do
begin
DoLog(Format('Indicator "%s" (%s)', [item.Name, item.ShortName]));
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);
DoLog('');
end;
end;
procedure TIndicatorRegistry.RegisterIndicator(const Factory: IIndicatorFactory; const ShortName, Name, Hint: String); procedure TIndicatorRegistry.RegisterIndicator(const Factory: IIndicatorFactory; const ShortName, Name, Hint: String);
var var
item: TItem; item: TItem;
begin begin
if not Assigned(Factory) then
raise EArgumentException.Create('Factory');
if Assigned(Find(ShortName)) then if Assigned(Find(ShortName)) then
raise EArgumentException.CreateFmt('Indicator with ShortName "%s" is already registered.', [ShortName]); raise EArgumentException.CreateFmt('Indicator with ShortName "%s" is already registered.', [ShortName]);