diff --git a/AuraTrader/AuraTrader.dpr b/AuraTrader/AuraTrader.dpr
index 19e7cf9..b482af9 100644
--- a/AuraTrader/AuraTrader.dpr
+++ b/AuraTrader/AuraTrader.dpr
@@ -7,7 +7,8 @@ uses
MainForm in 'MainForm.pas' {Form1},
TestModule in 'TestModule.pas',
DynamicFMXControl in 'DynamicFMXControl.pas',
- Myc.Trade.Pipeline.Impl in '..\Src\Myc.Trade.Pipeline.Impl.pas';
+ Myc.Trade.Pipeline.Impl in '..\Src\Myc.Trade.Pipeline.Impl.pas',
+ TestMethodCallFromRecordParams in 'TestMethodCallFromRecordParams.pas';
{$R *.res}
diff --git a/AuraTrader/AuraTrader.dproj b/AuraTrader/AuraTrader.dproj
index 15ce6c0..cc7a5be 100644
--- a/AuraTrader/AuraTrader.dproj
+++ b/AuraTrader/AuraTrader.dproj
@@ -138,6 +138,7 @@
+
Base
diff --git a/AuraTrader/MainForm.fmx b/AuraTrader/MainForm.fmx
index 20bd505..2e0137d 100644
--- a/AuraTrader/MainForm.fmx
+++ b/AuraTrader/MainForm.fmx
@@ -205,6 +205,7 @@ object Form1: TForm1
TabOrder = 0
Text = 'Button1'
TextSettings.Trimming = None
+ OnClick = Button1Click
end
end
end
diff --git a/AuraTrader/MainForm.pas b/AuraTrader/MainForm.pas
index 9a33a0b..3103136 100644
--- a/AuraTrader/MainForm.pas
+++ b/AuraTrader/MainForm.pas
@@ -52,7 +52,8 @@ uses
DynamicFMXControl,
Myc.FMX.Chart,
Myc.Trade.Indicators,
- StrategyTest;
+ StrategyTest,
+ TestMethodCallFromRecordParams;
type
TForm1 = class(TForm)
@@ -87,6 +88,7 @@ type
procedure StopButtonClick(Sender: TObject);
procedure TreeViewDblClick(Sender: TObject);
procedure AddWorkspaceActionExecute(Sender: TObject);
+ procedure Button1Click(Sender: TObject);
procedure Strat2ButtonClick(Sender: TObject);
procedure TestActionExecute(Sender: TObject);
procedure StrategyButtonClick(Sender: TObject);
@@ -285,6 +287,11 @@ begin
Control.Align := TAlignLayout.Top;
end;
+procedure TForm1.Button1Click(Sender: TObject);
+begin
+ Test1(LogMemo.Lines);
+end;
+
function TForm1.CreateStrategy2(Timeframe: TTimeframe): IConsumer>;
type
TSignal = record
@@ -478,21 +485,20 @@ begin
pnlChart.SetXAxisCounter(equity.Producer);
////////////
- var EMAFactory := TEMA.Create;
- var Params := TDataRecord.Create(EMAFactory.Params);
- Params.SetValue('Period', 20);
+ var Params: TEMA.TParam;
+ Params.Period := 20;
- var indi := EMAFActory.CreateIndicator(Params);
+ var indi := TEMA.CreateEMA(Params);
- var EMAConv := TConverter.CreateConverter(indi);
+ var EMAConv := TConverter.CreateConverter(indi);
var equityEMA :=
equity
.Producer
- .Chain(EMAFactory.Input.FieldAsRecord('Price'))
- .Chain(EMAConv)
- .Chain(EMAFactory.Output.FieldOfRecord('MA'));
+ .Chain(function(const Value: Double): TEMA.TInput begin Result.Price := Value end)
+ .Chain(EMAConv)
+ .Chain(function(const Value: TEMA.TResult): Double begin Result := Value.MA end);
//////////////
@@ -675,7 +681,7 @@ begin
exit;
var timeframe := TTimeframe.M15;
- //ExecuteStrategy(Symbol, timeframe, CreateStrategy2(timeframe));
+ ExecuteStrategy(Symbol, timeframe, CreateStrategy2(timeframe));
var tstStrat := StrategyTest.CreateStrategy1(timeframe);
ExecuteStrategy(Symbol, timeframe, tstStrat.Consumer);
diff --git a/AuraTrader/TestMethodCallFromRecordParams.pas b/AuraTrader/TestMethodCallFromRecordParams.pas
new file mode 100644
index 0000000..4fc90ad
--- /dev/null
+++ b/AuraTrader/TestMethodCallFromRecordParams.pas
@@ -0,0 +1,134 @@
+unit TestMethodCallFromRecordParams;
+
+interface
+
+// We need RTTI here!
+{$M+}
+
+uses
+ System.Rtti,
+ System.SysUtils,
+ System.TypInfo,
+ System.Classes,
+ Myc.Data.Records;
+
+type
+ InvokeAttribute = class(TCustomAttribute);
+ FactoryAttribute = class(TCustomAttribute);
+
+ TMyProc = reference to function(const [ref] Value: TValue): TResult;
+ TFactoryProc = reference to function(const [ref] Params: TParams): TMyProc;
+
+ TMyWorker = record
+ type
+ TParams = record
+ Log: Int64;
+ text: String;
+ end;
+
+ TValue = record
+ xyz: Double;
+ end;
+
+ TResult = record
+ val: Int64;
+ desc: String;
+ end;
+
+ [Factory]
+ class function CreateFactory: TFactoryProc; static;
+ end;
+
+procedure Test1(const Log: TStrings);
+
+implementation
+
+class function TMyWorker.CreateFactory: TFactoryProc;
+begin
+ Result :=
+ function(const [ref] Params: TParams): TMyProc
+ begin
+ var Log := TStrings(Params.Log);
+ var text := Params.text;
+
+ Result :=
+ function(const [ref] Value: TValue): TResult
+ begin
+ // Use Format to avoid locale issues with float conversion
+ Log.Add(Format('Val=%f (...%s)', [Value.xyz, text]));
+ Result.desc := 'done';
+ end;
+ end;
+end;
+
+type
+ TInvokeProc = reference to procedure(const Value, Res: TDataRecord);
+
+function CreateInvoker(Ctx: TRttiContext; CallFunc: TValue): TInvokeProc;
+begin
+ var rttiMyProc := Ctx.GetType(CallFunc.TypeInfo);
+ var workerInvokeMethod := rttiMyProc.GetMethod('Invoke');
+ var workerParamTypeInfo := workerInvokeMethod.GetParameters[0].ParamType.Handle;
+ var args: array[0..0] of TValue;
+ Result :=
+ procedure(const Value, Res: TDataRecord)
+ begin
+ TValue.MakeWithoutCopy(Value.RawData, workerParamTypeInfo, args[0], true);
+ workerInvokeMethod.Invoke(CallFunc, args).ExtractRawData(Res.RawData);
+ end;
+end;
+
+procedure Test1(const Log: TStrings);
+begin
+ var params := TDataRecord.FromRecord;
+ params.SetValue('Log', Int64(Log));
+ params.SetValue('text', 'The quick brown fox jumps...');
+
+ var workerValueParam := TDataRecord.FromRecord;
+ workerValueParam.SetValue('xyz', 3.14159);
+
+ var finalResultRec := TDataRecord.FromRecord;
+
+ var invoke: TInvokeProc;
+
+ ////////////
+
+ var Ctx := TRttiContext.Create;
+ var rttiType := Ctx.GetType(TypeInfo(TMyWorker));
+
+ // Find and invoke the method marked with [Factory] attribute.
+ for var method in rttiType.GetMethods do
+ begin
+ if method.HasAttribute then
+ begin
+ // First invocation returns the factory procedure itself (an interface).
+ var createFuncValue := method.Invoke(TValue.Empty, []);
+ // Anonymous methods are interfaces, so we get the TRttiInterfaceType.
+ var rttiCreateFunc := Ctx.GetType(createFuncValue.TypeInfo) as TRttiInterfaceType;
+ // The actual code is in the 'Invoke' method of that interface.
+ var factoryInvokeMethod := rttiCreateFunc.GetMethod('Invoke');
+
+ // Prepare the argument for the factory proc: a pointer to 'params'
+ var factoryArg: array[0..0] of TValue;
+ var factoryParamTypeInfo := factoryInvokeMethod.GetParameters[0].ParamType.Handle;
+ TValue.Make(params.RawData, factoryParamTypeInfo, factoryArg[0]);
+
+ var myProcValue := factoryInvokeMethod.Invoke(createFuncValue, factoryArg);
+
+ invoke := CreateInvoker(Ctx, myProcValue);
+ break;
+ end;
+ end;
+
+ //////////
+
+ Assert(Assigned(invoke));
+
+ invoke(workerValueParam, finalResultRec);
+
+ var desc := finalResultRec.GetValue('desc');
+ Log.Add('Final result description: ' + desc);
+ Assert(desc = 'done');
+end;
+
+end.
diff --git a/Src/Myc.Data.Records.pas b/Src/Myc.Data.Records.pas
index fa35036..41378c1 100644
--- a/Src/Myc.Data.Records.pas
+++ b/Src/Myc.Data.Records.pas
@@ -15,11 +15,12 @@ type
TField = record
private
FFieldType: TFieldType;
+ FTypeInfo: PtypeInfo;
FName: string;
FOffset: Integer;
- procedure FromType(const [ref] Buffer: TBytes; SrcType: PTypeInfo; const Src);
- procedure ToType(const [ref] Buffer: TBytes; DstType: PTypeInfo; var Dst);
+ procedure FromType(const [ref] Buffer: TBytes; const Src);
+ procedure ToType(const [ref] Buffer: TBytes; var Dst);
procedure InitField(const [ref] Buffer: TBytes);
procedure AssignField(const [ref] Dest: TBytes; const [ref] Source: TBytes);
@@ -29,7 +30,7 @@ type
property Offset: Integer read FOffset;
- constructor Create(const AName: string; AFieldType: TFieldType; AOffset: Integer);
+ constructor Create(const AName: string; AFieldType: TFieldType; ATypeInfo: PTypeInfo; AOffset: Integer);
function GetSize: Integer; inline;
function GetAlignedSize: Integer; inline;
@@ -39,6 +40,7 @@ type
property Name: string read FName;
property Size: Integer read GetSize;
property AlignedSize: Integer read GetAlignedSize;
+ property TypeInfo: PTypeInfo read FTypeInfo;
end;
TFieldDef = record
@@ -67,6 +69,7 @@ type
private
FLayout: TLayout;
FBuffer: TBytes;
+ function GetRawData: Pointer; inline;
public
constructor Create(const ALayout: TLayout);
@@ -77,14 +80,18 @@ type
class function FromRecord: TDataRecord; overload; static;
class function FromRecord(const Src: T): TDataRecord; overload; static;
+ procedure CopyFrom(const Src: T);
+
procedure SetValue(const Name: String; const Value: T); overload;
procedure SetValue(Idx: Integer; const Value); overload;
function GetValue(const Name: String): T; overload;
procedure GetValue(Idx: Integer; out Value); overload;
+ function GetValueRef(Idx: Integer): Pointer; overload;
procedure CopyValue(const SrcRec: TDataRecord; SrcIdx, DstIdx: Integer);
+ property RawData: Pointer read GetRawData;
property Layout: TLayout read FLayout;
end;
@@ -113,6 +120,19 @@ begin
FLayout.Fields[i].InitField(FBuffer);
end;
+procedure TDataRecord.CopyFrom(const Src: T);
+begin
+ var ctx := TRttiContext.Create;
+ var rttiType := ctx.GetType(TypeInfo(T));
+
+ for var i := 0 to High(FLayout.FFields) do
+ begin
+ var P: PByte := @Src;
+ inc(P, FLayout.Fields[i].Offset);
+ FLayout.Fields[i].FromType(FBuffer, Src);
+ end;
+end;
+
procedure TDataRecord.CopyValue(const SrcRec: TDataRecord; SrcIdx, DstIdx: Integer);
begin
Assert(SrcRec.Layout.Fields[SrcIdx].FieldType = FLayout.Fields[SrcIdx].FieldType);
@@ -131,24 +151,12 @@ end;
class function TDataRecord.FromRecord(const Src: T): TDataRecord;
begin
Result.Create(TLayout.FromRecord);
+ Result.CopyFrom(Src);
+end;
- var ctx := TRttiContext.Create;
- var rttiType := ctx.GetType(TypeInfo(T));
- var rttiFields := rttiType.GetFields;
- var fields: TArray;
-
- for var i := 0 to High(rttiFields) do
- begin
- var rf := rttiFields[i];
- var idx := Result.Layout.IndexOf(rf.Name);
-
- if (idx >= 0) then
- begin
- var S: PByte := @Src;
- inc(S, rf.Offset);
- Result.Layout.Fields[idx].FromType(Result.FBuffer, rf.FieldType.Handle, S^);
- end;
- end;
+function TDataRecord.GetRawData: Pointer;
+begin
+ Result := @FBuffer[0];
end;
procedure TDataRecord.GetValue(Idx: Integer; out Value);
@@ -183,14 +191,25 @@ function TDataRecord.GetValue(const Name: String): T;
begin
var idx := FLayout.IndexOf(Name);
if (idx >= 0) then
- FLayout.Fields[idx].ToType(FBuffer, TypeInfo(T), Result);
+ begin
+ Assert(FLayout.Fields[idx].TypeInfo = TypeInfo(T));
+ FLayout.Fields[idx].ToType(FBuffer, Result);
+ end;
+end;
+
+function TDataRecord.GetValueRef(Idx: Integer): Pointer;
+begin
+ Result := @FBuffer[FLayout.Fields[Idx].Offset];
end;
procedure TDataRecord.SetValue(const Name: String; const Value: T);
begin
var idx := FLayout.IndexOf(Name);
if (idx >= 0) then
- FLayout.Fields[idx].FromType(FBuffer, TypeInfo(T), Value);
+ begin
+ Assert(FLayout.Fields[idx].TypeInfo = TypeInfo(T));
+ FLayout.Fields[idx].FromType(FBuffer, Value);
+ end;
end;
class operator TDataRecord.Assign(var Dest: TDataRecord; const [ref] Src: TDataRecord);
@@ -211,11 +230,12 @@ begin
Dest.Layout.Fields[i].FinalizeField(Dest.FBuffer);
end;
-constructor TDataRecord.TField.Create(const AName: string; AFieldType: TFieldType; AOffset: Integer);
+constructor TDataRecord.TField.Create(const AName: string; AFieldType: TFieldType; ATypeInfo: PTypeInfo; AOffset: Integer);
begin
FName := AName;
FFieldType := AFieldType;
FOffset := AOffset;
+ FTypeInfo := ATypeInfo;
end;
procedure TDataRecord.TField.InitField(const [ref] Buffer: TBytes);
@@ -246,7 +266,7 @@ begin
end;
end;
-procedure TDataRecord.TField.FromType(const [ref] Buffer: TBytes; SrcType: PTypeInfo; const Src);
+procedure TDataRecord.TField.FromType(const [ref] Buffer: TBytes; const Src);
begin
Assert(FOffset + Size <= Length(Buffer));
@@ -254,9 +274,9 @@ begin
case FFieldType of
dfFloat:
begin
- Assert(SrcType.Kind = tkFloat);
- case GetTypeData(SrcType).FloatType of
- ftSingle: PDouble(Dst)^ := PSingle(@Src)^;
+ Assert(FTypeInfo.Kind = tkFloat);
+ case GetTypeData(FTypeInfo).FloatType of
+ ftSingle: PSingle(Dst)^ := PSingle(@Src)^;
ftDouble: PDouble(Dst)^ := PDouble(@Src)^;
else
Assert(false);
@@ -264,8 +284,8 @@ begin
end;
dfInteger:
begin
- case SrcType.Kind of
- tkInteger: PInt64(Dst)^ := PInteger(@Src)^;
+ case FTypeInfo.Kind of
+ tkInteger: PInteger(Dst)^ := PInteger(@Src)^;
tkInt64: PInt64(Dst)^ := PInt64(@Src)^;
else
Assert(false);
@@ -273,19 +293,19 @@ begin
end;
dfString:
begin
- Assert(SrcType.Kind in [tkString, tkLString, tkUString, tkWString]);
+ Assert(FTypeInfo.Kind in [tkString, tkLString, tkUString, tkWString]);
PString(Dst)^ := PString(@Src)^;
end;
dfTimestamp:
begin
- Assert(SrcType.Kind = tkFloat);
- Assert(SrcType.Name = PTypeInfo(TypeInfo(TDateTime)).Name);
+ Assert(FTypeInfo.Kind = tkFloat);
+ Assert(FTypeInfo.Name = PTypeInfo(System.TypeInfo(TDateTime)).Name);
PDateTime(Dst)^ := PDateTime(@Src)^;
end;
dfRecord:
begin
- Assert(SrcType = TypeInfo(TDataRecord));
- Assert(SrcType.Name = PTypeInfo(TypeInfo(TDataRecord)).Name);
+ Assert(FTypeInfo = System.TypeInfo(TDataRecord));
+ Assert(FTypeInfo.Name = PTypeInfo(System.TypeInfo(TDataRecord)).Name);
TDataRecord(Dst^) := TDataRecord(Src);
end;
else
@@ -303,15 +323,15 @@ begin
Result := (GetSize + (Align - 1)) and not (Align - 1);
end;
-procedure TDataRecord.TField.ToType(const [ref] Buffer: TBytes; DstType: PTypeInfo; var Dst);
+procedure TDataRecord.TField.ToType(const [ref] Buffer: TBytes; var Dst);
begin
var Src := @Buffer[FOffset];
case FFieldType of
dfFloat:
begin
- Assert(DstType.Kind = tkFloat);
- case GetTypeData(DstType).FloatType of
- ftSingle: PSingle(@Dst)^ := PDouble(Src)^;
+ Assert(FTypeInfo.Kind = tkFloat);
+ case GetTypeData(FTypeInfo).FloatType of
+ ftSingle: PSingle(@Dst)^ := PSingle(Src)^;
ftDouble: PDouble(@Dst)^ := PDouble(Src)^;
else
Assert(false);
@@ -319,8 +339,8 @@ begin
end;
dfInteger:
begin
- case DstType.Kind of
- tkInteger: PInteger(@Dst)^ := PInt64(Src)^;
+ case FTypeInfo.Kind of
+ tkInteger: PInteger(@Dst)^ := PInteger(Src)^;
tkInt64: PInt64(@Dst)^ := PInt64(Src)^;
else
Assert(false);
@@ -328,18 +348,18 @@ begin
end;
dfString:
begin
- Assert(DstType.Kind in [tkString, tkLString, tkUString, tkWString]);
+ Assert(FTypeInfo.Kind in [tkString, tkLString, tkUString, tkWString]);
PString(@Dst)^ := PString(Src)^;
end;
dfTimestamp:
begin
- Assert(DstType.Kind = tkFloat);
- Assert(DstType.Name = PTypeInfo(TypeInfo(TDateTime)).Name);
+ Assert(FTypeInfo.Kind = tkFloat);
+ Assert(FTypeInfo.Name = PTypeInfo(System.TypeInfo(TDateTime)).Name);
PDateTime(@Dst)^ := PDateTime(Src)^;
end;
dfRecord:
begin
- Assert(DstType = TypeInfo(TDataRecord));
+ Assert(FTypeInfo = System.TypeInfo(TDataRecord));
TDataRecord(Dst) := TDataRecord(Src^);
end;
else
@@ -383,9 +403,20 @@ begin
var ofs := 0;
for var i := 0 to High(Fields) do
begin
- Fields[i] := TField.Create(Def[i].Name, Def[i].FieldType, ofs);
+ var ti: PTypeInfo;
+ case Def[i].FieldType of
+ dfFloat: ti := TypeInfo(Double);
+ dfInteger: ti := TypeInfo(Int64);
+ dfString: ti := TypeInfo(String);
+ dfTimestamp: ti := TypeInfo(TDateTime);
+ dfRecord: ti := TypeInfo(TDataRecord);
+ end;
+
+ Fields[i] := TField.Create(Def[i].Name, Def[i].FieldType, ti, ofs);
inc(ofs, Fields[i].AlignedSize);
end;
+
+ Result.Create(Fields);
end;
{ TLayout }
@@ -398,7 +429,7 @@ begin
var fields: TArray;
// Add each field and its value to the builder
- var ofs := 0;
+ // The internal layout has to be the same as the original record
SetLength(fields, Length(rttiFields));
for var i := 0 to High(rttiFields) do
begin
@@ -407,12 +438,14 @@ begin
var supported := true;
case rf.FieldType.TypeKind of
- tkInteger, tkInt64: ft := dfInteger;
+ tkInt64: ft := dfInteger;
tkFloat:
if rf.FieldType.HasName(GetTypeName(TypeInfo(TDateTime))) then
ft := dfTimestamp
+ else if GetTypeData(rf.FieldType.Handle).FloatType = ftDouble then
+ ft := dfFloat
else
- ft := dfFloat;
+ supported := false;
tkString, tkWString, tkLString, tkUString: ft := dfString;
tkMRecord:
begin
@@ -428,8 +461,7 @@ begin
if not supported then
raise Exception.Create('Type ' + rf.FieldType.Name + ' not supported in data records');
- fields[i] := TField.Create(rf.Name, ft, ofs);
- inc(ofs, fields[i].AlignedSize);
+ fields[i] := TField.Create(rf.Name, ft, rf.FieldType.Handle, rf.Offset);
end;
Result.Create(fields);
diff --git a/Src/Myc.Trade.Indicators.pas b/Src/Myc.Trade.Indicators.pas
index e4e6140..ef07e11 100644
--- a/Src/Myc.Trade.Indicators.pas
+++ b/Src/Myc.Trade.Indicators.pas
@@ -86,25 +86,7 @@ type
class function CreateMean: TConvertFunc, Double>; static;
end;
- TIndicatorFactory = class
- type
- TFunc = TConvertFunc;
- private
- FParams: TDataRecord.TLayout;
- FInput: TDataRecord.TLayout;
- FOutput: TDataRecord.TLayout;
-
- public
- constructor Create(const AParams, AInput, AOutput: TDataRecord.TLayout);
-
- function CreateIndicator(const Params: TDataRecord): TFunc; virtual; abstract;
-
- property Params: TDataRecord.TLayout read FParams;
- property Input: TDataRecord.TLayout read FInput;
- property Output: TDataRecord.TLayout read FOutput;
- end;
-
- TEMA = class(TIndicatorFactory)
+ TEMA = class
type
TParam = record
Period: Integer;
@@ -119,35 +101,9 @@ type
end;
public
- constructor Create;
- function CreateIndicator(const Params: TDataRecord): TIndicatorFactory.TFunc; override;
+ class function CreateEMA(const Param: TParam): TConvertFunc; static;
end;
- TMACD = class
- type
- TParam = record
- Fast: TConvertFunc;
- Slow: TConvertFunc;
- Signal: TConvertFunc;
- end;
-
- TInput = record
- Price: Double;
- end;
-
- TResult = record
- MacdLine: Double;
- SignalLine: Double;
- Histogram: Double;
- end;
-
- public
- class function CreateMACD(const Param: TParam): TConvertFunc; static;
- end;
-
-var
- Registry: TList;
-
implementation
{ TIndicators }
@@ -548,79 +504,36 @@ begin
end;
end;
-// Creates a MACD indicator from three provided moving average functions.
-class function TMACD.CreateMACD(const Param: TParam): TConvertFunc;
+class function TEMA.CreateEMA(const Param: TParam): TConvertFunc;
begin
- Result :=
- function(const Input: TInput): TResult
- var
- fastVal, slowVal: Double;
- begin
- fastVal := Param.Fast(Input.Price);
- slowVal := Param.Slow(Input.Price);
+ var Period := Param.Period;
+ var lastEma: Double := Double.NaN;
+ var sourceData: TSeries;
+ var multiplier := 2 / (Period + 1);
- if IsNan(slowVal) then // slowVal will be the last one to become non-NaN
+ Result :=
+ function(const Value: TInput): TResult
+ begin
+ sourceData.Add(Value.Price, Period);
+
+ if (sourceData.Count < Period) then
begin
- Result.MacdLine := Double.NaN;
- Result.SignalLine := Double.NaN;
- Result.Histogram := Double.NaN;
+ Result.MA := Double.NaN;
+ Exit;
+ end;
+
+ if not IsNan(lastEma) then
+ begin
+ // Subsequent EMA calculation
+ lastEma := (Value.Price - lastEma) * multiplier + lastEma;
end
else
begin
- Result.MacdLine := fastVal - slowVal;
- Result.SignalLine := Param.Signal(Result.MacdLine);
- if not IsNan(Result.SignalLine) then
- Result.Histogram := Result.MacdLine - Result.SignalLine
- else
- Result.Histogram := Double.NaN;
+ // First EMA is a SMA of the initial period
+ lastEma := TIndicators.CalculateSMA(sourceData, Period);
end;
+ Result.MA := lastEma;
end;
end;
-constructor TEMA.Create;
-begin
- inherited
- Create(TDataRecord.TLayout.FromRecord, TDataRecord.TLayout.FromRecord, TDataRecord.TLayout.FromRecord)
-end;
-
-function TEMA.CreateIndicator(const Params: TDataRecord): TIndicatorFactory.TFunc;
-begin
- var Period := Params.GetValue('Period');
-
- var Input := TDataRecord.TLayout.FromRecord;
- var inPrice := Input.IndexOf('Price');
-
- var Output := TDataRecord.TLayout.FromRecord;
- var outMA := Output.IndexOf('MA');
-
- var CalcEMA := TIndicators.CreateEMA(Period);
-
- Result :=
- function(const Input: TDataRecord): TDataRecord
- begin
- var Price: Double;
- Input.GetValue(inPrice, Price);
-
- var MA := CalcEMA(Price);
-
- Result := TDataRecord.FromRecord;
- Result.SetValue(outMA, MA);
- end;
-end;
-
-constructor TIndicatorFactory.Create(const AParams, AInput, AOutput: TDataRecord.TLayout);
-begin
- inherited Create;
- FParams := AParams;
- FInput := AInput;
- FOutput := AOutput;
-end;
-
-initialization
- Registry := TList.Create;
- Registry.Add(TEMA.Create);
-
-finalization
- Registry.Free;
-
end.
diff --git a/Test/TestDataRecord.pas b/Test/TestDataRecord.pas
index 18a017f..8aad4ef 100644
--- a/Test/TestDataRecord.pas
+++ b/Test/TestDataRecord.pas
@@ -219,7 +219,7 @@ begin
// Setup: Create the outer record containing the nested TDataRecord
outerSrc.MyNestedRecord := nestedDataRecord;
- // Place a record on the heap. FAILS: This erases the nested record data!!
+ // Place a record on the heap. FAILS: This erases the nested record data!! -- Fixed 25.07.2025!
TDataRecord.FromRecord(outerSrc);
// Test: Create the main TDataRecord from the outer record instance