Generic indicator factory

This commit is contained in:
Michael Schimmel
2025-07-27 21:58:15 +02:00
parent 7842c3bd87
commit 78e89f345e
4 changed files with 85 additions and 51 deletions
+26 -2
View File
@@ -318,7 +318,19 @@ begin
var Lowest: Double := Double.MaxValue; var Lowest: Double := Double.MaxValue;
var Highest: Double := Double.MinValue; var Highest: Double := Double.MinValue;
var ATR := Ohlc.Chain<Double>(TATR.CreateATR(50)).MakeParallel; var ATR :=
Ohlc
.Chain<TATR.TArgs>(
TConverter<TOhlcItem, TATR.TArgs>.CreateConverter(
function(const Ohlc: TOhlcItem): TATR.TArgs
begin
Result.Close := Ohlc.Close;
Result.High := Ohlc.High;
Result.Low := Ohlc.Low;
end
))
.Chain<Double>(TATR.CreateATR(50))
.MakeParallel;
// next stage // next stage
@@ -622,7 +634,19 @@ begin
var Boli := Closes.MakeParallel.Chain<TBollingerBands.TResult>(TBollingerBands.CreateBollingerBands(20, 2.0)); var Boli := Closes.MakeParallel.Chain<TBollingerBands.TResult>(TBollingerBands.CreateBollingerBands(20, 2.0));
var Rsi := Closes.Chain<Double>(TRSI.CreateRSI(14)); var Rsi := Closes.Chain<Double>(TRSI.CreateRSI(14));
var Macd := Closes.MakeParallel.Chain<TMacd.TResult>(TMACD.CreateMACD(12, 26, 9)); var Macd := Closes.MakeParallel.Chain<TMacd.TResult>(TMACD.CreateMACD(12, 26, 9));
var Stoch := Ohlc.Chain<TStochastic.TResult>(TStochastic.CreateStochastic(14, 3));
var Stoch :=
Ohlc
.Chain<TStochastic.TArgs>(
TConverter<TOhlcItem, TStochastic.TArgs>.CreateConverter(
function(const Ohlc: TOhlcItem): TStochastic.TArgs
begin
Result.Close := Ohlc.Close;
Result.High := Ohlc.High;
Result.Low := Ohlc.Low;
end
))
.Chain<TStochastic.TResult>(TStochastic.CreateStochastic(14, 3));
chart.SetXAxisSeries(timeframe, Timestamps); chart.SetXAxisSeries(timeframe, Timestamps);
+13 -1
View File
@@ -37,7 +37,19 @@ begin
var Hull := Closes.Chain<Double>(THMA.CreateHMA(250)).MakeParallel; var Hull := Closes.Chain<Double>(THMA.CreateHMA(250)).MakeParallel;
var Sma := Closes.Chain<Double>(TSMA.CreateSMA(200)).MakeParallel; var Sma := Closes.Chain<Double>(TSMA.CreateSMA(200)).MakeParallel;
var ATR := Ohlc.Chain<Double>(TATR.CreateATR(50)).MakeParallel;
var ATR :=
Ohlc
.Chain<TATR.TArgs>(
TConverter<TOhlcItem, TATR.TArgs>.CreateConverter(
function(const Ohlc: TOhlcItem): TATR.TArgs
begin
Result.Close := Ohlc.Close;
Result.High := Ohlc.High;
Result.Low := Ohlc.Low;
end
))
.Chain<Double>(TATR.CreateATR(50));
var conv := TConverter.Join<Double>([Ohlc.Field<Double>('Low'), Ohlc.Field<Double>('High'), Closes, ATR, Hull, Sma]); var conv := TConverter.Join<Double>([Ohlc.Field<Double>('Low'), Ohlc.Field<Double>('High'), Closes, ATR, Hull, Sma]);
+2 -11
View File
@@ -35,7 +35,6 @@ type
procedure Copy(Src, Dst: Pointer); procedure Copy(Src, Dst: Pointer);
function GetSize: Integer; inline;
function GetAlignedSize: Integer; inline; function GetAlignedSize: Integer; inline;
public public
@@ -44,7 +43,7 @@ type
property FieldType: TFieldType read FFieldType; property FieldType: TFieldType read FFieldType;
property Name: string read FName; property Name: string read FName;
property Size: Integer read GetSize; property Size: Integer read FSize;
property AlignedSize: Integer read GetAlignedSize; property AlignedSize: Integer read GetAlignedSize;
property TypeInfo: PTypeInfo read FTypeInfo; property TypeInfo: PTypeInfo read FTypeInfo;
end; end;
@@ -286,17 +285,9 @@ begin
Copy(@Src, @Buffer[FOffset]); Copy(@Src, @Buffer[FOffset]);
end; end;
function TDataRecord.TField.GetSize: Integer;
const
DataSize: array[TDataRecord.TFieldType] of Integer =
(sizeof(Double), sizeof(Int64), sizeof(String), sizeof(TDateTime), sizeof(TDataRecord));
begin
Result := DataSize[FFieldType];
end;
function TDataRecord.TField.GetAlignedSize: Integer; function TDataRecord.TField.GetAlignedSize: Integer;
begin begin
Result := (GetSize + (Align - 1)) and not (Align - 1); Result := (FSize + (Align - 1)) and not (Align - 1);
end; end;
procedure TDataRecord.TField.ToType(const [ref] Buffer: TBytes; var Dst); procedure TDataRecord.TField.ToType(const [ref] Buffer: TBytes; var Dst);
+44 -37
View File
@@ -148,7 +148,9 @@ type
DPeriod: Integer; DPeriod: Integer;
end; end;
TArgs = record TArgs = record
Value: TOhlcItem; High: Double;
Low: Double;
Close: Double;
end; end;
TResult = record TResult = record
K: Double; // %K line K: Double; // %K line
@@ -156,12 +158,12 @@ type
end; end;
[IndicatorFactory] [IndicatorFactory]
class function CreateFactory: TIndicatorFactoryProc<TParams, TArgs, TResult>; static; class function CreateFactory: TIndicatorFactoryProc<TParams, TArgs, TResult>; static;
class function CreateStochastic(KPeriod, DPeriod: Integer): TConvertFunc<TOhlcItem, TResult>; overload; static;
// Creates a Stochastic Oscillator using an injectable moving average for the %D line. class function CreateStochastic(KPeriod, DPeriod: Integer): TConvertFunc<TArgs, TResult>; overload; static;
class function CreateStochastic( class function CreateStochastic(
KPeriod: Integer; KPeriod: Integer;
const SmaD: TConvertFunc<Double, Double> const SmaD: TConvertFunc<Double, Double>
): TConvertFunc<TOhlcItem, TStochastic.TResult>; overload; static; ): TConvertFunc<TArgs, TStochastic.TResult>; overload; static;
end; end;
[IndicatorName('StdDev', 'Standard Deviation')] [IndicatorName('StdDev', 'Standard Deviation')]
@@ -214,15 +216,17 @@ type
Period: Integer; Period: Integer;
end; end;
TArgs = record TArgs = record
Value: TOhlcItem; High: Double;
Low: Double;
Close: Double;
end; end;
TResult = record TResult = record
ATR: Double; ATR: Double;
end; end;
[IndicatorFactory] [IndicatorFactory]
class function CreateFactory: TIndicatorFactoryProc<TParams, TArgs, TResult>; static; class function CreateFactory: TIndicatorFactoryProc<TParams, TArgs, TResult>; static;
class function CreateATR(Period: Integer): TConvertFunc<TOhlcItem, Double>; overload; static; class function CreateATR(Period: Integer): TConvertFunc<TArgs, Double>; overload; static;
class function CreateATR(const MovAvgTR: TConvertFunc<Double, Double>): TConvertFunc<TOhlcItem, Double>; overload; static; class function CreateATR(const MovAvgTR: TConvertFunc<Double, Double>): TConvertFunc<TArgs, Double>; overload; static;
end; end;
[IndicatorName('KC', 'Keltner Channels')] [IndicatorName('KC', 'Keltner Channels')]
@@ -235,7 +239,9 @@ type
Multiplier: Double; Multiplier: Double;
end; end;
TArgs = record TArgs = record
Value: TOhlcItem; High: Double;
Low: Double;
Close: Double;
end; end;
TResult = record TResult = record
UpperBand: Double; UpperBand: Double;
@@ -244,12 +250,12 @@ type
end; end;
[IndicatorFactory] [IndicatorFactory]
class function CreateFactory: TIndicatorFactoryProc<TParams, TArgs, TResult>; static; class function CreateFactory: TIndicatorFactoryProc<TParams, TArgs, TResult>; static;
class function CreateKeltnerChannels(Period: Integer; Multiplier: Double): TConvertFunc<TOhlcItem, TResult>; overload; static; class function CreateKeltnerChannels(Period: Integer; Multiplier: Double): TConvertFunc<TArgs, TResult>; overload; static;
class function CreateKeltnerChannels( class function CreateKeltnerChannels(
const MovAvgMiddle: TConvertFunc<Double, Double>; const MovAvgMiddle: TConvertFunc<Double, Double>;
const AtrFunc: TConvertFunc<TOhlcItem, Double>; const AtrFunc: TConvertFunc<TATR.TArgs, Double>;
Multiplier: Double Multiplier: Double
): TConvertFunc<TOhlcItem, TKeltnerChannels.TResult>; overload; static; ): TConvertFunc<TArgs, TKeltnerChannels.TResult>; overload; static;
end; end;
[IndicatorName('Mean', 'Mean Value')] [IndicatorName('Mean', 'Mean Value')]
@@ -708,25 +714,22 @@ begin
Result := Result :=
function(const Params: TParams): TConvertFunc<TArgs, TResult> function(const Params: TParams): TConvertFunc<TArgs, TResult>
var var
stochFunc: TConvertFunc<TOhlcItem, TResult>; stochFunc: TConvertFunc<TArgs, TResult>;
begin begin
stochFunc := CreateStochastic(Params.KPeriod, Params.DPeriod); stochFunc := CreateStochastic(Params.KPeriod, Params.DPeriod);
Result := function(const Value: TArgs): TResult begin Result := stochFunc(Value.Value); end; Result := function(const Value: TArgs): TResult begin Result := stochFunc(Value); end;
end; end;
end; end;
class function TStochastic.CreateStochastic(KPeriod, DPeriod: Integer): TConvertFunc<TOhlcItem, TStochastic.TResult>; class function TStochastic.CreateStochastic(KPeriod, DPeriod: Integer): TConvertFunc<TArgs, TResult>;
begin begin
Result := CreateStochastic(KPeriod, TSMA.CreateSMA(DPeriod)); Result := CreateStochastic(KPeriod, TSMA.CreateSMA(DPeriod));
end; end;
// Creates a Stochastic Oscillator using an injectable moving average for the %D line. // Creates a Stochastic Oscillator using an injectable moving average for the %D line.
class function TStochastic.CreateStochastic( class function TStochastic.CreateStochastic(KPeriod: Integer; const SmaD: TConvertFunc<Double, Double>): TConvertFunc<TArgs, TResult>;
KPeriod: Integer;
const SmaD: TConvertFunc<Double, Double>
): TConvertFunc<TOhlcItem, TStochastic.TResult>;
var var
buffer: TArray<TOhlcItem>; buffer: TArray<TArgs>;
highDeque: TLightDeque; highDeque: TLightDeque;
lowDeque: TLightDeque; lowDeque: TLightDeque;
valueCount: Int64; valueCount: Int64;
@@ -734,7 +737,7 @@ begin
if (KPeriod <= 0) then if (KPeriod <= 0) then
begin begin
Result := Result :=
function(const Value: TOhlcItem): TResult function(const Value: TArgs): TResult
begin begin
Result.K := Double.NaN; Result.K := Double.NaN;
Result.D := Double.NaN; Result.D := Double.NaN;
@@ -748,7 +751,7 @@ begin
valueCount := 0; valueCount := 0;
Result := Result :=
function(const Value: TOhlcItem): TStochastic.TResult function(const Value: TArgs): TStochastic.TResult
var var
currentIndex, firstIndex, lastIndex: Integer; currentIndex, firstIndex, lastIndex: Integer;
highestHigh, lowestLow: Double; highestHigh, lowestLow: Double;
@@ -926,25 +929,25 @@ begin
Result := Result :=
function(const Params: TParams): TConvertFunc<TArgs, TResult> function(const Params: TParams): TConvertFunc<TArgs, TResult>
var var
atrFunc: TConvertFunc<TOhlcItem, Double>; atrFunc: TConvertFunc<TArgs, Double>;
begin begin
atrFunc := CreateATR(Params.Period); atrFunc := CreateATR(Params.Period);
Result := function(const Value: TArgs): TResult begin Result.ATR := atrFunc(Value.Value); end; Result := function(const Value: TArgs): TResult begin Result.ATR := atrFunc(Value); end;
end; end;
end; end;
class function TATR.CreateATR(Period: Integer): TConvertFunc<TOhlcItem, Double>; class function TATR.CreateATR(Period: Integer): TConvertFunc<TArgs, Double>;
begin begin
Result := CreateATR(TEMA.CreateEMA(Period)); Result := CreateATR(TEMA.CreateEMA(Period));
end; end;
// Calculates the Average True Range (ATR) using an injectable moving average. // Calculates the Average True Range (ATR) using an injectable moving average.
class function TATR.CreateATR(const MovAvgTR: TConvertFunc<Double, Double>): TConvertFunc<TOhlcItem, Double>; class function TATR.CreateATR(const MovAvgTR: TConvertFunc<Double, Double>): TConvertFunc<TArgs, Double>;
begin begin
var sourceData: TSeries<TOhlcItem>; var sourceData: TSeries<TArgs>;
Result := Result :=
function(const Value: TOhlcItem): Double function(const Value: TArgs): Double
var var
tr: Double; tr: Double;
begin begin
@@ -968,20 +971,19 @@ end;
{ TKeltnerChannels } { TKeltnerChannels }
class function TKeltnerChannels.CreateFactory: class function TKeltnerChannels.CreateFactory: TIndicatorFactoryProc<TParams, TArgs, TResult>;
TIndicatorFactoryProc<TKeltnerChannels.TParams, TKeltnerChannels.TArgs, TKeltnerChannels.TResult>;
begin begin
Result := Result :=
function(const Params: TParams): TConvertFunc<TArgs, TResult> function(const Params: TParams): TConvertFunc<TArgs, TResult>
var var
kcFunc: TConvertFunc<TOhlcItem, TResult>; kcFunc: TConvertFunc<TArgs, TResult>;
begin begin
kcFunc := CreateKeltnerChannels(Params.Period, Params.Multiplier); kcFunc := CreateKeltnerChannels(Params.Period, Params.Multiplier);
Result := function(const Value: TArgs): TResult begin Result := kcFunc(Value.Value); end; Result := function(const Value: TArgs): TResult begin Result := kcFunc(Value); end;
end; end;
end; end;
class function TKeltnerChannels.CreateKeltnerChannels(Period: Integer; Multiplier: Double): TConvertFunc<TOhlcItem, TResult>; class function TKeltnerChannels.CreateKeltnerChannels(Period: Integer; Multiplier: Double): TConvertFunc<TArgs, TResult>;
begin begin
Result := CreateKeltnerChannels(TEMA.CreateEMA(Period), TATR.CreateATR(Period), Multiplier); Result := CreateKeltnerChannels(TEMA.CreateEMA(Period), TATR.CreateATR(Period), Multiplier);
end; end;
@@ -989,12 +991,12 @@ end;
// Calculates Keltner Channels using an injectable ATR and middle band moving average. // Calculates Keltner Channels using an injectable ATR and middle band moving average.
class function TKeltnerChannels.CreateKeltnerChannels( class function TKeltnerChannels.CreateKeltnerChannels(
const MovAvgMiddle: TConvertFunc<Double, Double>; const MovAvgMiddle: TConvertFunc<Double, Double>;
const AtrFunc: TConvertFunc<TOhlcItem, Double>; const AtrFunc: TConvertFunc<TATR.TArgs, Double>;
Multiplier: Double Multiplier: Double
): TConvertFunc<TOhlcItem, TKeltnerChannels.TResult>; ): TConvertFunc<TArgs, TKeltnerChannels.TResult>;
begin begin
Result := Result :=
function(const Value: TOhlcItem): TKeltnerChannels.TResult function(const Value: TArgs): TKeltnerChannels.TResult
var var
atrValue, middleValue, typicalPrice: Double; atrValue, middleValue, typicalPrice: Double;
begin begin
@@ -1003,7 +1005,12 @@ begin
// Get values from the provided indicator functions. // Get values from the provided indicator functions.
middleValue := MovAvgMiddle(typicalPrice); middleValue := MovAvgMiddle(typicalPrice);
atrValue := AtrFunc(Value);
var atrArgs: TATR.TArgs;
atrArgs.High := Value.High;
atrArgs.Low := Value.Low;
atrArgs.Close := Value.Close;
atrValue := AtrFunc(atrArgs);
// Set default NaN values for the warm-up period. // Set default NaN values for the warm-up period.
Result.MiddleBand := middleValue; Result.MiddleBand := middleValue;
@@ -1064,6 +1071,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.