unit Myc.Trade.Indicators.Common;

interface

uses
    System.SysUtils,
    System.Math,
    System.Rtti,
    Myc.Data.Pipeline,
    Myc.Data.Series,
    Myc.Data.Types,
    Myc.Data.Value,
    Myc.Trade.Types,
    Myc.Trade.Indicators;

type
    // --- Indicator Templates ---

    [IndicatorName('SMA', 'Simple Moving Average')]
    [IndicatorHint('Calculates the average of a selected range of prices.')]
    TSMA = class
    strict private
        class var
            FFactory: IDataMethodValue;
        class constructor CreateClass;
    public
        type
            TParams = record
                Period: Integer;
            end;
            TArgs = record
                Value: Double;
            end;
            TResult = record
                SMA: Double;
            end;
        [IndicatorFactory]
        class function CreateFactory: TIndicatorFactoryProc<TParams, TArgs, TResult>; static;
        class function CreateSMA(Period: Integer): TConvertFunc<Double, Double>; static;

        // Provides the factory method for this indicator as a data value.
        class property Factory: IDataMethodValue read FFactory;
    end;

    [IndicatorName('EMA', 'Exponential Moving Average')]
    [IndicatorHint('A moving average that places greater weight on the most recent data points.')]
    TEMA = class
    strict private
        class var
            FFactory: IDataMethodValue;
        class constructor CreateClass;
    public
        type
            TParams = record
                Period: Integer;
            end;
            TArgs = record
                Value: Double;
            end;
            TResult = record
                EMA: Double;
            end;
        [IndicatorFactory]
        class function CreateFactory: TIndicatorFactoryProc<TParams, TArgs, TResult>; static;
        class function CreateEMA(Period: Integer): TConvertFunc<Double, Double>; static;
        // Provides the factory method for this indicator as a data value.
        class property Factory: IDataMethodValue read FFactory;
    end;

    [IndicatorName('WMA', 'Weighted Moving Average')]
    [IndicatorHint('A moving average that places greater weight on more recent data points.')]
    TWMA = class
    strict private
        class var
            FFactory: IDataMethodValue;
        class constructor CreateClass;
    public
        type
            TParams = record
                Period: Integer;
            end;
            TArgs = record
                Value: Double;
            end;
            TResult = record
                WMA: Double;
            end;
        [IndicatorFactory]
        class function CreateFactory: TIndicatorFactoryProc<TParams, TArgs, TResult>; static;
        class function CreateWMA(Period: Integer): TConvertFunc<Double, Double>; static;
        // Provides the factory method for this indicator as a data value.
        class property Factory: IDataMethodValue read FFactory;
    end;

    [IndicatorName('HMA', 'Hull Moving Average')]
    [IndicatorHint('A fast, smooth moving average that minimizes lag.')]
    THMA = class
    strict private
        class var
            FFactory: IDataMethodValue;
        class constructor CreateClass;
    public
        type
            TParams = record
                Period: Integer;
            end;
            TArgs = record
                Value: Double;
            end;
            TResult = record
                HMA: Double;
            end;
        [IndicatorFactory]
        class function CreateFactory: TIndicatorFactoryProc<TParams, TArgs, TResult>; static;
        class function CreateHMA(Period: Integer): TConvertFunc<Double, Double>; static;
        // Provides the factory method for this indicator as a data value.
        class property Factory: IDataMethodValue read FFactory;
    end;

    [IndicatorName('RSI', 'Relative Strength Index')]
    [IndicatorHint('A momentum indicator measuring the magnitude of recent price changes.')]
    TRSI = class
    strict private
        class var
            FFactory: IDataMethodValue;
        class constructor CreateClass;
    public
        type
            TParams = record
                Period: Integer;
            end;
            TArgs = record
                Value: Double;
            end;
            TResult = record
                RSI: Double;
            end;
        [IndicatorFactory]
        class function CreateFactory: TIndicatorFactoryProc<TParams, TArgs, TResult>; static;
        class function CreateRSI(Period: Integer): TConvertFunc<Double, Double>; static;
        // Provides the factory method for this indicator as a data value.
        class property Factory: IDataMethodValue read FFactory;
    end;

    [IndicatorName('MACD', 'Moving Average Convergence Divergence')]
    [IndicatorHint('A trend-following momentum indicator showing the relationship between two EMAs.')]
    TMACD = class
    strict private
        class var
            FFactory: IDataMethodValue;
        class constructor CreateClass;
    public
        type
            TParams = record
                FastPeriod: Integer;
                SlowPeriod: Integer;
                SignalPeriod: Integer;
            end;
            TArgs = record
                Value: Double;
            end;
            TResult = record
                MacdLine: Double;
                SignalLine: Double;
                Histogram: Double;
            end;
        [IndicatorFactory]
        class function CreateFactory: TIndicatorFactoryProc<TParams, TArgs, TResult>; static;
        class function CreateMACD(FastPeriod, SlowPeriod, SignalPeriod: Integer): TConvertFunc<Double, TResult>; overload; static;
        class function CreateMACD(
            const EmaFast,
            EmaSlow,
            EmaSignal: TConvertFunc<Double, Double>
        ): TConvertFunc<Double, TMACD.TResult>; overload; static;
        // Provides the factory method for this indicator as a data value.
        class property Factory: IDataMethodValue read FFactory;
    end;

    [IndicatorName('Stoch', 'Stochastic Oscillator')]
    [IndicatorHint('A momentum indicator comparing a closing price to a range of its prices.')]
    TStochastic = class
    strict private
        class var
            FFactory: IDataMethodValue;
        class constructor CreateClass;
    public
        type
            TParams = record
                KPeriod: Integer;
                DPeriod: Integer;
            end;
            TArgs = record
                Value: TOhlcItem;
            end;
            TResult = record
                K: Double; // %K line
                D: Double; // %D line (signal line)
            end;
        [IndicatorFactory]
        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: Integer;
            const SmaD: TConvertFunc<Double, Double>
        ): TConvertFunc<TOhlcItem, TStochastic.TResult>; overload; static;
        // Provides the factory method for this indicator as a data value.
        class property Factory: IDataMethodValue read FFactory;
    end;

    [IndicatorName('StdDev', 'Standard Deviation')]
    [IndicatorHint('Measures the amount of variation or dispersion of a set of values.')]
    TStdDev = class
    strict private
        class var
            FFactory: IDataMethodValue;
        class constructor CreateClass;
    public
        type
            TParams = record
                Period: Integer;
            end;
            TArgs = record
                Value: Double;
            end;
            TResult = record
                StdDev: Double;
            end;
        [IndicatorFactory]
        class function CreateFactory: TIndicatorFactoryProc<TParams, TArgs, TResult>; static;
        class function CreateStdDev(Period: Integer): TConvertFunc<Double, Double>; static;
        // Provides the factory method for this indicator as a data value.
        class property Factory: IDataMethodValue read FFactory;
    end;

    [IndicatorName('BB', 'Bollinger Bands')]
    [IndicatorHint('Characterizes prices and volatility over time using standard deviation bands.')]
    TBollingerBands = class
    strict private
        class var
            FFactory: IDataMethodValue;
        class constructor CreateClass;
    public
        type
            TParams = record
                Period: Integer;
                Multiplier: Double;
            end;
            TArgs = record
                Value: Double;
            end;
            TResult = record
                UpperBand: Double;
                MiddleBand: Double;
                LowerBand: Double;
            end;
        [IndicatorFactory]
        class function CreateFactory: TIndicatorFactoryProc<TParams, TArgs, TResult>; static;
        class function CreateBollingerBands(Period: Integer; Multiplier: Double): TConvertFunc<Double, TResult>; static;
        // Provides the factory method for this indicator as a data value.
        class property Factory: IDataMethodValue read FFactory;
    end;

    [IndicatorName('ATR', 'Average True Range')]
    [IndicatorHint('Measures market volatility by decomposing the entire range of an asset price.')]
    TATR = class
    strict private
        class var
            FFactory: IDataMethodValue;
        class constructor CreateClass;
    public
        type
            TParams = record
                Period: Integer;
            end;
            TArgs = record
                Value: TOhlcItem;
            end;
            TResult = record
                ATR: Double;
            end;
        [IndicatorFactory]
        class function CreateFactory: TIndicatorFactoryProc<TParams, TArgs, TResult>; static;
        class function CreateATR(Period: Integer): TConvertFunc<TOhlcItem, Double>; overload; static;
        class function CreateATR(const MovAvgTR: TConvertFunc<Double, Double>): TConvertFunc<TOhlcItem, Double>; overload; static;
        // Provides the factory method for this indicator as a data value.
        class property Factory: IDataMethodValue read FFactory;
    end;

    [IndicatorName('KC', 'Keltner Channels')]
    [IndicatorHint('A volatility-based indicator composed of an EMA and two ATR-based outer lines.')]
    TKeltnerChannels = class
    strict private
        class var
            FFactory: IDataMethodValue;
        class constructor CreateClass;
    public
        type
            TParams = record
                Period: Integer;
                Multiplier: Double;
            end;
            TArgs = record
                Value: TOhlcItem;
            end;
            TResult = record
                UpperBand: Double;
                MiddleBand: Double;
                LowerBand: Double;
            end;
        [IndicatorFactory]
        class function CreateFactory: TIndicatorFactoryProc<TParams, TArgs, TResult>; static;
        class function CreateKeltnerChannels(Period: Integer; Multiplier: Double): TConvertFunc<TOhlcItem, TResult>; overload; static;
        class function CreateKeltnerChannels(
            const MovAvgMiddle: TConvertFunc<Double, Double>;
            const AtrFunc: TConvertFunc<TOhlcItem, Double>;
            Multiplier: Double
        ): TConvertFunc<TOhlcItem, TKeltnerChannels.TResult>; overload; static;
        // Provides the factory method for this indicator as a data value.
        class property Factory: IDataMethodValue read FFactory;
    end;

    [IndicatorName('Mean', 'Mean Value')]
    [IndicatorHint('Calculates the arithmetic mean of an array of values.')]
    TMean = class
    strict private
        class var
            FFactory: IDataMethodValue;
        class constructor CreateClass;
    public
        type
            TParams = record
            end;
            TArgs = record
                Values: TArray<Double>;
            end;
            TResult = record
                Mean: Double;
            end;
        [IndicatorFactory]
        class function CreateFactory: TIndicatorFactoryProc<TParams, TArgs, TResult>; static;
        class function CreateMean: TConvertFunc<TArray<Double>, Double>; static;
        // Provides the factory method for this indicator as a data value.
        class property Factory: IDataMethodValue read FFactory;
    end;

implementation

{ TSMA }

class constructor TSMA.CreateClass;
begin
    var paramsType := TDataType.Ordinal;
    var argsType := TDataType.Float;
    var resultType := TDataType.Float;

    var indicatorMethodType := TDataType.MethodOf(argsType, resultType);
    var factoryMethodType := TDataType.MethodOf(paramsType, indicatorMethodType);

    FFactory :=
        factoryMethodType.CreateValue(
            function(const Params: TDataType.TValue): TDataType.TValue
            begin
                // Local variable is necessary here to capture the created function.
                var SMA := CreateSMA(Params.AsOrdinal.Value);
                Result :=
                    indicatorMethodType.CreateValue(
                        function(const Args: TDataType.TValue): TDataType.TValue
                        begin
                            Result := TDataType.Float.CreateValue(SMA(Args.AsFloat.Value));
                        end
                    );
            end
        );
end;

class function TSMA.CreateFactory: TIndicatorFactoryProc<TSMA.TParams, TSMA.TArgs, TSMA.TResult>;
begin
    Result :=
        function(const Params: TParams): TConvertFunc<TArgs, TResult>
        var
            smaFunc: TConvertFunc<Double, Double>;
        begin
            smaFunc := CreateSMA(Params.Period);
            Result := function(const Value: TArgs): TResult begin Result.SMA := smaFunc(Value.Value); end;
        end;
end;

class function TSMA.CreateSMA(Period: Integer): TConvertFunc<Double, Double>;
begin
    // Implemented using a rolling sum and a circular array for O(1) performance.
    var sum: Double;
    var buffer: TArray<Double>;
    var currentIndex: Integer;
    var isReady: Boolean;

    if (Period <= 0) then
    begin
        Result := function(const Value: Double): Double begin Result := Double.NaN; end;
        exit;
    end;

    sum := 0.0;
    SetLength(buffer, Period);
    currentIndex := 0;
    isReady := false;

    Result :=
        function(const Value: Double): Double
        begin
            if not isReady then
            begin
                // --- Warm-up phase ---
                // Fill the buffer until it has 'Period' elements.
                sum := sum + Value;
                buffer[currentIndex] := Value;
                inc(currentIndex);

                if (currentIndex < Period) then
                begin
                    Result := Double.NaN;
                    exit;
                end
                else
                begin
                    // The buffer is now full, the first SMA can be calculated.
                    isReady := true;
                    currentIndex := 0; // Wrap index for the next write.
                    Result := sum / Period;
                    exit;
                end;
            end;

            // --- Rolling phase ---
            // Subtract the oldest value (which is being overwritten).
            sum := sum - buffer[currentIndex];
            // Add the new value.
            sum := sum + Value;
            // Store the new value in the circular buffer.
            buffer[currentIndex] := Value;

            // Advance the index for the next write.
            currentIndex := (currentIndex + 1) mod Period;

            Result := sum / Period;
        end;
end;

{ TEMA }

class constructor TEMA.CreateClass;
begin
    var paramsType := TDataType.Ordinal;
    var argsType := TDataType.Float;
    var resultType := TDataType.Float;

    var indicatorMethodType := TDataType.MethodOf(argsType, resultType);
    var factoryMethodType := TDataType.MethodOf(paramsType, indicatorMethodType);

    FFactory :=
        factoryMethodType.CreateValue(
            function(const Params: TDataType.TValue): TDataType.TValue
            begin
                var EMA := CreateEMA(Params.AsOrdinal.Value);
                Result :=
                    indicatorMethodType.CreateValue(
                        function(const Args: TDataType.TValue): TDataType.TValue
                        begin
                            Result := TDataType.Float.CreateValue(EMA(Args.AsFloat.Value));
                        end
                    );
            end
        );
end;

class function TEMA.CreateFactory: TIndicatorFactoryProc<TEMA.TParams, TEMA.TArgs, TEMA.TResult>;
begin
    Result :=
        function(const Params: TParams): TConvertFunc<TArgs, TResult>
        var
            emaFunc: TConvertFunc<Double, Double>;
        begin
            emaFunc := CreateEMA(Params.Period);
            Result := function(const Value: TArgs): TResult begin Result.EMA := emaFunc(Value.Value); end;
        end;
end;

class function TEMA.CreateEMA(Period: Integer): TConvertFunc<Double, Double>;
begin
    var lastEma: Double := Double.NaN;
    var sourceData: TSeries<Double>;
    var multiplier: Double;

    if (Period > 0) then
        multiplier := 2 / (Period + 1)
    else
        multiplier := 0;

    Result :=
        function(const Value: Double): Double
        begin
            sourceData.Add(Value, Period);

            if (sourceData.Count < Period) then
            begin
                Result := Double.NaN;
                exit;
            end;

            if not IsNan(lastEma) then
            begin
                // Subsequent EMA calculation
                lastEma := (Value - lastEma) * multiplier + lastEma;
            end
            else
            begin
                // First EMA is a SMA of the initial period. Calculate it directly.
                var sum: Double := 0.0;
                var i: Integer;
                for i := 0 to Period - 1 do
                    sum := sum + sourceData[i];

                if (Period > 0) then
                    lastEma := sum / Period
                else
                    lastEma := 0.0;
            end;
            Result := lastEma;
        end;
end;

{ TWMA }

class constructor TWMA.CreateClass;
begin
    var paramsType := TDataType.Ordinal;
    var argsType := TDataType.Float;
    var resultType := TDataType.Float;

    var indicatorMethodType := TDataType.MethodOf(argsType, resultType);
    var factoryMethodType := TDataType.MethodOf(paramsType, indicatorMethodType);

    FFactory :=
        factoryMethodType.CreateValue(
            function(const Params: TDataType.TValue): TDataType.TValue
            begin
                var WMA := CreateWMA(Params.AsOrdinal.Value);
                Result :=
                    indicatorMethodType.CreateValue(
                        function(const Args: TDataType.TValue): TDataType.TValue
                        begin
                            Result := TDataType.Float.CreateValue(WMA(Args.AsFloat.Value));
                        end
                    );
            end
        );
end;

class function TWMA.CreateFactory: TIndicatorFactoryProc<TWMA.TParams, TWMA.TArgs, TWMA.TResult>;
begin
    Result :=
        function(const Params: TParams): TConvertFunc<TArgs, TResult>
        var
            wmaFunc: TConvertFunc<Double, Double>;
        begin
            wmaFunc := CreateWMA(Params.Period);
            Result := function(const Value: TArgs): TResult begin Result.WMA := wmaFunc(Value.Value); end;
        end;
end;

class function TWMA.CreateWMA(Period: Integer): TConvertFunc<Double, Double>;
begin
    // Corrected O(1) implementation using a rolling window.
    var weightedSum: Double;
    var simpleSum: Double;
    var buffer: TArray<Double>;
    var currentIndex: Integer;
    var valueCount: Integer;
    var denominator: Int64;

    if (Period <= 0) then
    begin
        Result := function(const Value: Double): Double begin Result := Double.NaN; end;
        exit;
    end;

    weightedSum := 0.0;
    simpleSum := 0.0;
    SetLength(buffer, Period);
    currentIndex := 0;
    valueCount := 0;
    denominator := Period * (Period + 1) div 2;

    if (denominator = 0) then
    begin
        Result := function(const Value: Double): Double begin Result := Double.NaN; end;
        exit;
    end;

    Result :=
        function(const Value: Double): Double
        var
            oldestValue: Double;
        begin
            inc(valueCount);

            // Get the value that will be overwritten. Initially, this is 0.0.
            oldestValue := buffer[currentIndex];

            // --- Corrected Rolling Calculation ---
            // IMPORTANT: Update weightedSum BEFORE simpleSum, using the old simpleSum.
            weightedSum := weightedSum - simpleSum + (Period * Value);
            simpleSum := simpleSum - oldestValue + Value;

            // Store the new value and advance the circular buffer index.
            buffer[currentIndex] := Value;
            currentIndex := (currentIndex + 1) mod Period;

            // The indicator is not ready until the buffer is filled for the first time.
            if (valueCount < Period) then
                exit(Double.NaN);

            Result := weightedSum / denominator;
        end;
end;

{ THMA }

class constructor THMA.CreateClass;
begin
    var paramsType := TDataType.Ordinal;
    var argsType := TDataType.Float;
    var resultType := TDataType.Float;

    var indicatorMethodType := TDataType.MethodOf(argsType, resultType);
    var factoryMethodType := TDataType.MethodOf(paramsType, indicatorMethodType);

    FFactory :=
        factoryMethodType.CreateValue(
            function(const Params: TDataType.TValue): TDataType.TValue
            begin
                var HMA := CreateHMA(Params.AsOrdinal.Value);
                Result :=
                    indicatorMethodType.CreateValue(
                        function(const Args: TDataType.TValue): TDataType.TValue
                        begin
                            Result := TDataType.Float.CreateValue(HMA(Args.AsFloat.Value));
                        end
                    );
            end
        );
end;

class function THMA.CreateFactory: TIndicatorFactoryProc<THMA.TParams, THMA.TArgs, THMA.TResult>;
begin
    Result :=
        function(const Params: TParams): TConvertFunc<TArgs, TResult>
        var
            hmaFunc: TConvertFunc<Double, Double>;
        begin
            hmaFunc := CreateHMA(Params.Period);
            Result := function(const Value: TArgs): TResult begin Result.HMA := hmaFunc(Value.Value); end;
        end;
end;

class function THMA.CreateHMA(Period: Integer): TConvertFunc<Double, Double>;
begin
    // Implemented as a pipeline of three efficient WMA indicators.
    var wmaFuncHalf := TWMA.CreateWMA(Period div 2);
    var wmaFuncFull := TWMA.CreateWMA(Period);
    var wmaFuncFinal := TWMA.CreateWMA(Round(Sqrt(Period)));

    Result :=
        function(const Value: Double): Double
        var
            wmaHalf, wmaFull, diff: Double;
        begin
            // Step 1: Calculate the two WMAs on the source data.
            wmaHalf := wmaFuncHalf(Value);
            wmaFull := wmaFuncFull(Value);

            // Wait until the longest WMA has a valid value.
            if IsNan(wmaFull) then
                exit(Double.NaN);

            // Step 2: Calculate the intermediate difference value.
            diff := 2 * wmaHalf - wmaFull;

            // Step 3: The final WMA is calculated on the difference series.
            // This will correctly return NaN during its own warm-up phase.
            Result := wmaFuncFinal(diff);
        end;
end;

{ TRSI }

class constructor TRSI.CreateClass;
begin
    var paramsType := TDataType.Ordinal;
    var argsType := TDataType.Float;
    var resultType := TDataType.Float;

    var indicatorMethodType := TDataType.MethodOf(argsType, resultType);
    var factoryMethodType := TDataType.MethodOf(paramsType, indicatorMethodType);

    FFactory :=
        factoryMethodType.CreateValue(
            function(const Params: TDataType.TValue): TDataType.TValue
            begin
                var RSI := CreateRSI(Params.AsOrdinal.Value);
                Result :=
                    indicatorMethodType.CreateValue(
                        function(const Args: TDataType.TValue): TDataType.TValue
                        begin
                            Result := TDataType.Float.CreateValue(RSI(Args.AsFloat.Value));
                        end
                    );
            end
        );
end;

class function TRSI.CreateFactory: TIndicatorFactoryProc<TRSI.TParams, TRSI.TArgs, TRSI.TResult>;
begin
    Result :=
        function(const Params: TParams): TConvertFunc<TArgs, TResult>
        var
            rsiFunc: TConvertFunc<Double, Double>;
        begin
            rsiFunc := CreateRSI(Params.Period);
            Result := function(const Value: TArgs): TResult begin Result.RSI := rsiFunc(Value.Value); end;
        end;
end;

class function TRSI.CreateRSI(Period: Integer): TConvertFunc<Double, Double>;
begin
    var avgGain: Double := Double.NaN;
    var avgLoss: Double := Double.NaN;
    var sourceData: TSeries<Double>;

    Result :=
        function(const Value: Double): Double
        var
            change, gain, loss, rs: Double;
            gainSum, lossSum: Double;
            i: Integer;
        begin
            sourceData.Add(Value, Period + 1);
            Result := Double.NaN;

            if (sourceData.Count <= Period) then
                Exit;

            // Initial calculation for the first full period
            if IsNan(avgGain) then
            begin
                gainSum := 0;
                lossSum := 0;
                for i := 0 to Period - 1 do
                begin
                    change := sourceData[i] - sourceData[i + 1];
                    if (change > 0) then
                        gainSum := gainSum + change
                    else
                        lossSum := lossSum - change;
                end;
                avgGain := gainSum / Period;
                avgLoss := lossSum / Period;
            end
            else // Smoothed calculation for subsequent values
            begin
                change := sourceData[0] - sourceData[1];
                gain := 0;
                loss := 0;
                if (change > 0) then
                    gain := change
                else
                    loss := -change;

                avgGain := (avgGain * (Period - 1) + gain) / Period;
                avgLoss := (avgLoss * (Period - 1) + loss) / Period;
            end;

            if (avgLoss = 0) then
                Result := 100
            else
            begin
                rs := avgGain / avgLoss;
                Result := 100 - (100 / (1 + rs));
            end;
        end;
end;

{ TMACD }

class constructor TMACD.CreateClass;
begin
    var paramsType :=
        TDataType.RecordOf(
            [
                TDataRecordField.Create('FastPeriod', TDataType.Ordinal),
                TDataRecordField.Create('SlowPeriod', TDataType.Ordinal),
                TDataRecordField.Create('SignalPeriod', TDataType.Ordinal)
            ]
        );
    var argsType := TDataType.Float;
    var resultType :=
        TDataType.RecordOf(
            [
                TDataRecordField.Create('MacdLine', TDataType.Float),
                TDataRecordField.Create('SignalLine', TDataType.Float),
                TDataRecordField.Create('Histogram', TDataType.Float)
            ]
        );

    var indicatorMethodType := TDataType.MethodOf(argsType, resultType);
    var factoryMethodType := TDataType.MethodOf(paramsType, indicatorMethodType);

    FFactory :=
        factoryMethodType.CreateValue(
            function(const Params: TDataType.TValue): TDataType.TValue
            var
                paramsRec: IDataRecordValue;
            begin
                paramsRec := Params.AsRecord;
                var MACD :=
                    CreateMACD(
                        TDataType.TValue(paramsRec.Items[0]).AsOrdinal.Value,
                        TDataType.TValue(paramsRec.Items[1]).AsOrdinal.Value,
                        TDataType.TValue(paramsRec.Items[2]).AsOrdinal.Value
                    );

                Result :=
                    indicatorMethodType.CreateValue(
                        function(const Args: TDataType.TValue): TDataType.TValue
                        begin
                            var res := MACD(Args.AsFloat.Value);
                            Result :=
                                resultType.CreateValue(
                                    [
                                        TDataType.Float.CreateValue(res.MacdLine),
                                        TDataType.Float.CreateValue(res.SignalLine),
                                        TDataType.Float.CreateValue(res.Histogram)
                                    ]
                                );
                        end
                    );
            end
        );
end;

class function TMACD.CreateFactory: TIndicatorFactoryProc<TMACD.TParams, TMACD.TArgs, TMACD.TResult>;
begin
    Result :=
        function(const Params: TParams): TConvertFunc<TArgs, TResult>
        var
            macdFunc: TConvertFunc<Double, TResult>;
        begin
            macdFunc := CreateMACD(Params.FastPeriod, Params.SlowPeriod, Params.SignalPeriod);
            Result := function(const Value: TArgs): TResult begin Result := macdFunc(Value.Value); end;
        end;
end;

class function TMACD.CreateMACD(FastPeriod, SlowPeriod, SignalPeriod: Integer): TConvertFunc<Double, TResult>;
begin
    Result := CreateMACD(TEMA.CreateEMA(FastPeriod), TEMA.CreateEMA(SlowPeriod), TEMA.CreateEMA(SignalPeriod));
end;

// Creates a MACD indicator from three provided moving average functions.
class function TMACD.CreateMACD(const EmaFast, EmaSlow, EmaSignal: TConvertFunc<Double, Double>): TConvertFunc<Double, TMACD.TResult>;
begin
    Result :=
        function(const Value: Double): TMACD.TResult
        var
            fastVal, slowVal: Double;
        begin
            fastVal := EmaFast(Value);
            slowVal := EmaSlow(Value);

            if IsNan(slowVal) then // slowVal will be the last one to become non-NaN
            begin
                Result.MacdLine := Double.NaN;
                Result.SignalLine := Double.NaN;
                Result.Histogram := Double.NaN;
            end
            else
            begin
                Result.MacdLine := fastVal - slowVal;
                Result.SignalLine := EmaSignal(Result.MacdLine);
                if not IsNan(Result.SignalLine) then
                    Result.Histogram := Result.MacdLine - Result.SignalLine
                else
                    Result.Histogram := Double.NaN;
            end;
        end;
end;

{ TStochastic }

type
    // A minimal, self-contained Deque (Double-Ended Queue) using a circular array.
    // This local type is used to implement the O(1) sliding window for Stochastic.
    TLightDeque = record
    private
        FItems: TArray<Int64>; // Stores absolute value counts, not indices
        FHead, FCount, FCapacity: Integer;
        function GetLastValue: Int64;
        function GetFirstValue: Int64;
    public
        constructor Create(ACapacity: Integer);
        procedure AddLast(AValue: Int64);
        procedure RemoveLast;
        procedure RemoveFirst;
        property Count: Integer read FCount;
        property Last: Int64 read GetLastValue;
        property First: Int64 read GetFirstValue;
    end;

constructor TLightDeque.Create(ACapacity: Integer);
begin
    FCapacity := ACapacity;
    SetLength(FItems, FCapacity);
    FHead := 0;
    FCount := 0;
end;

procedure TLightDeque.AddLast(AValue: Int64);
begin
    if (FCount < FCapacity) then
    begin
        var tail := (FHead + FCount) mod FCapacity;
        FItems[tail] := AValue;
        inc(FCount);
    end;
end;

procedure TLightDeque.RemoveLast;
begin
    if (FCount > 0) then
        dec(FCount);
end;

procedure TLightDeque.RemoveFirst;
begin
    if (FCount > 0) then
    begin
        FHead := (FHead + 1) mod FCapacity;
        dec(FCount);
    end;
end;

function TLightDeque.GetLastValue: Int64;
begin
    var tail := (FHead + FCount - 1 + FCapacity) mod FCapacity;
    Result := FItems[tail];
end;

function TLightDeque.GetFirstValue: Int64;
begin
    Result := FItems[FHead];
end;

class constructor TStochastic.CreateClass;
begin
    var paramsType :=
        TDataType.RecordOf([TDataRecordField.Create('KPeriod', TDataType.Ordinal), TDataRecordField.Create('DPeriod', TDataType.Ordinal)]);

    var ohlcType :=
        TDataType.RecordOf(
            [
                TDataRecordField.Create('Open', TDataType.Float),
                TDataRecordField.Create('High', TDataType.Float),
                TDataRecordField.Create('Low', TDataType.Float),
                TDataRecordField.Create('Close', TDataType.Float),
                TDataRecordField.Create('Volume', TDataType.Float)
            ]
        );

    var argsType := ohlcType;
    var resultType := TDataType.RecordOf([TDataRecordField.Create('K', TDataType.Float), TDataRecordField.Create('D', TDataType.Float)]);

    var indicatorMethodType := TDataType.MethodOf(argsType, resultType);
    var factoryMethodType := TDataType.MethodOf(paramsType, indicatorMethodType);

    FFactory :=
        factoryMethodType.CreateValue(
            function(const Params: TDataType.TValue): TDataType.TValue
            var
                paramsRec: IDataRecordValue;
            begin
                paramsRec := Params.AsRecord;
                var Stochastic :=
                    CreateStochastic(
                        TDataType.TValue(paramsRec.Items[0]).AsOrdinal.Value,
                        TDataType.TValue(paramsRec.Items[1]).AsOrdinal.Value
                    );

                Result :=
                    indicatorMethodType.CreateValue(
                        function(const Args: TDataType.TValue): TDataType.TValue
                        var
                            ohlcRec: IDataRecordValue;
                            ohlcVal: TOhlcItem;
                        begin
                            ohlcRec := Args.AsRecord;

                            ohlcVal.Open := TDataType.TValue(ohlcRec.Items[0]).AsFloat.Value;
                            ohlcVal.High := TDataType.TValue(ohlcRec.Items[1]).AsFloat.Value;
                            ohlcVal.Low := TDataType.TValue(ohlcRec.Items[2]).AsFloat.Value;
                            ohlcVal.Close := TDataType.TValue(ohlcRec.Items[3]).AsFloat.Value;
                            ohlcVal.Volume := TDataType.TValue(ohlcRec.Items[4]).AsFloat.Value;

                            var res := Stochastic(ohlcVal);

                            Result := resultType.CreateValue([TDataType.Float.CreateValue(res.K), TDataType.Float.CreateValue(res.D)]);
                        end
                    );
            end
        );
end;

class function TStochastic.CreateFactory: TIndicatorFactoryProc<TStochastic.TParams, TStochastic.TArgs, TStochastic.TResult>;
begin
    Result :=
        function(const Params: TParams): TConvertFunc<TArgs, TResult>
        var
            stochFunc: TConvertFunc<TOhlcItem, TResult>;
        begin
            stochFunc := CreateStochastic(Params.KPeriod, Params.DPeriod);
            Result := function(const Value: TArgs): TResult begin Result := stochFunc(Value.Value); end;
        end;
end;

class function TStochastic.CreateStochastic(KPeriod, DPeriod: Integer): TConvertFunc<TOhlcItem, TStochastic.TResult>;
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<Double, Double>
): TConvertFunc<TOhlcItem, TStochastic.TResult>;
var
    buffer: TArray<TOhlcItem>;
    highDeque: TLightDeque;
    lowDeque: TLightDeque;
    valueCount: Int64;
begin
    if (KPeriod <= 0) then
    begin
        Result :=
            function(const Value: TOhlcItem): TResult
            begin
                Result.K := Double.NaN;
                Result.D := Double.NaN;
            end;
        exit;
    end;

    SetLength(buffer, KPeriod);
    highDeque := TLightDeque.Create(KPeriod);
    lowDeque := TLightDeque.Create(KPeriod);
    valueCount := 0;

    Result :=
        function(const Value: TOhlcItem): TStochastic.TResult
        var
            currentIndex, firstIndex, lastIndex: Integer;
            highestHigh, lowestLow: Double;
        begin
            inc(valueCount);
            currentIndex := (valueCount - 1) mod KPeriod;
            buffer[currentIndex] := Value;

            // Update deques using absolute valueCount as item identifier
            while (highDeque.Count > 0) do
            begin
                lastIndex := (highDeque.Last - 1) mod KPeriod;
                if (buffer[lastIndex].High <= Value.High) then
                    highDeque.RemoveLast
                else
                    break;
            end;
            highDeque.AddLast(valueCount);

            while (lowDeque.Count > 0) do
            begin
                lastIndex := (lowDeque.Last - 1) mod KPeriod;
                if (buffer[lastIndex].Low >= Value.Low) then
                    lowDeque.RemoveLast
                else
                    break;
            end;
            lowDeque.AddLast(valueCount);

            // Remove indices that are now outside the window
            while (highDeque.Count > 0) and (highDeque.First <= valueCount - KPeriod) do
                highDeque.RemoveFirst;
            while (lowDeque.Count > 0) and (lowDeque.First <= valueCount - KPeriod) do
                lowDeque.RemoveFirst;

            // Calculate Indicator
            if (valueCount < KPeriod) then
            begin
                Result.K := Double.NaN;
                Result.D := SmaD(Result.K); // Feed NaN to keep SMA in sync
            end
            else
            begin
                firstIndex := (highDeque.First - 1) mod KPeriod;
                highestHigh := buffer[firstIndex].High;

                firstIndex := (lowDeque.First - 1) mod KPeriod;
                lowestLow := buffer[firstIndex].Low;

                if (highestHigh > lowestLow) then
                    Result.K := 100 * (Value.Close - lowestLow) / (highestHigh - lowestLow)
                else
                    Result.K := 100;

                Result.D := SmaD(Result.K);
            end;
        end;
end;

{ TStdDev }

class constructor TStdDev.CreateClass;
begin
    var paramsType := TDataType.Ordinal;
    var argsType := TDataType.Float;
    var resultType := TDataType.Float;

    var indicatorMethodType := TDataType.MethodOf(argsType, resultType);
    var factoryMethodType := TDataType.MethodOf(paramsType, indicatorMethodType);

    FFactory :=
        factoryMethodType.CreateValue(
            function(const Params: TDataType.TValue): TDataType.TValue
            begin
                var StdDev := CreateStdDev(Params.AsOrdinal.Value);
                Result :=
                    indicatorMethodType.CreateValue(
                        function(const Args: TDataType.TValue): TDataType.TValue
                        begin
                            Result := TDataType.Float.CreateValue(StdDev(Args.AsFloat.Value));
                        end
                    );
            end
        );
end;

class function TStdDev.CreateFactory: TIndicatorFactoryProc<TStdDev.TParams, TStdDev.TArgs, TStdDev.TResult>;
begin
    Result :=
        function(const Params: TParams): TConvertFunc<TArgs, TResult>
        var
            stdDevFunc: TConvertFunc<Double, Double>;
        begin
            stdDevFunc := CreateStdDev(Params.Period);
            Result := function(const Value: TArgs): TResult begin Result.StdDev := stdDevFunc(Value.Value); end;
        end;
end;

class function TStdDev.CreateStdDev(Period: Integer): TConvertFunc<Double, Double>;
begin
    // O(1) implementation using rolling sums of X and X^2 to calculate variance.
    var sumX, sumX2: Double;
    var buffer: TArray<Double>;
    var currentIndex: Integer;
    var valueCount: Integer;

    if (Period <= 1) then // StdDev requires at least 2 data points
    begin
        Result := function(const Value: Double): Double begin Result := Double.NaN; end;
        exit;
    end;

    sumX := 0.0;
    sumX2 := 0.0;
    SetLength(buffer, Period);
    currentIndex := 0;
    valueCount := 0;

    Result :=
        function(const Value: Double): Double
        var
            oldestValue, variance, mean: Double;
        begin
            inc(valueCount);
            oldestValue := buffer[currentIndex];
            buffer[currentIndex] := Value;

            // Update sums incrementally
            sumX := sumX - oldestValue + Value;
            sumX2 := sumX2 - (oldestValue * oldestValue) + (Value * Value);

            // Advance index
            currentIndex := (currentIndex + 1) mod Period;

            if (valueCount < Period) then
                exit(Double.NaN);

            // Variance = E[X^2] - (E[X])^2
            mean := sumX / Period;
            variance := (sumX2 / Period) - (mean * mean);

            // Prevent negative variance from floating point inaccuracies
            if (variance < 0) then
                variance := 0;

            Result := Sqrt(variance);
        end;
end;

{ TBollingerBands }

class constructor TBollingerBands.CreateClass;
begin
    var paramsType :=
        TDataType.RecordOf([TDataRecordField.Create('Period', TDataType.Ordinal), TDataRecordField.Create('Multiplier', TDataType.Float)]);
    var argsType := TDataType.Float;
    var resultType :=
        TDataType.RecordOf(
            [
                TDataRecordField.Create('UpperBand', TDataType.Float),
                TDataRecordField.Create('MiddleBand', TDataType.Float),
                TDataRecordField.Create('LowerBand', TDataType.Float)
            ]
        );

    var indicatorMethodType := TDataType.MethodOf(argsType, resultType);
    var factoryMethodType := TDataType.MethodOf(paramsType, indicatorMethodType);

    FFactory :=
        factoryMethodType.CreateValue(
            function(const Params: TDataType.TValue): TDataType.TValue
            var
                paramsRec: IDataRecordValue;
            begin
                paramsRec := Params.AsRecord;
                var BollingerBands :=
                    CreateBollingerBands(
                        TDataType.TValue(paramsRec.Items[0]).AsOrdinal.Value,
                        TDataType.TValue(paramsRec.Items[1]).AsFloat.Value
                    );

                Result :=
                    indicatorMethodType.CreateValue(
                        function(const Args: TDataType.TValue): TDataType.TValue
                        begin
                            var res := BollingerBands(Args.AsFloat.Value);
                            Result :=
                                resultType.CreateValue(
                                    [
                                        TDataType.Float.CreateValue(res.UpperBand),
                                        TDataType.Float.CreateValue(res.MiddleBand),
                                        TDataType.Float.CreateValue(res.LowerBand)
                                    ]
                                );
                        end
                    );
            end
        );
end;

class function TBollingerBands.CreateFactory:
    TIndicatorFactoryProc<TBollingerBands.TParams, TBollingerBands.TArgs, TBollingerBands.TResult>;
begin
    Result :=
        function(const Params: TParams): TConvertFunc<TArgs, TResult>
        var
            bbFunc: TConvertFunc<Double, TResult>;
        begin
            bbFunc := CreateBollingerBands(Params.Period, Params.Multiplier);
            Result := function(const Value: TArgs): TResult begin Result := bbFunc(Value.Value); end;
        end;
end;

class function TBollingerBands.CreateBollingerBands(Period: Integer; Multiplier: Double): TConvertFunc<Double, TBollingerBands.TResult>;
begin
    // Implemented as a pipeline of efficient SMA and StdDev indicators.
    var smaFunc := TSMA.CreateSMA(Period);
    var stdDevFunc := TStdDev.CreateStdDev(Period);

    Result :=
        function(const Value: Double): TResult
        var
            middleBand, stdDev: Double;
        begin
            // Calculate middle band (SMA) and standard deviation in parallel.
            middleBand := smaFunc(Value);
            stdDev := stdDevFunc(Value);

            // Wait until both indicators are ready (they have the same period).
            if IsNan(middleBand) then
            begin
                Result.MiddleBand := Double.NaN;
                Result.UpperBand := Double.NaN;
                Result.LowerBand := Double.NaN;
            end
            else
            begin
                Result.MiddleBand := middleBand;
                Result.UpperBand := middleBand + (stdDev * Multiplier);
                Result.LowerBand := middleBand - (stdDev * Multiplier);
            end;
        end;
end;

{ TATR }

class constructor TATR.CreateClass;
begin
    var paramsType := TDataType.Ordinal;
    var ohlcType :=
        TDataType.RecordOf(
            [
                TDataRecordField.Create('Open', TDataType.Float),
                TDataRecordField.Create('High', TDataType.Float),
                TDataRecordField.Create('Low', TDataType.Float),
                TDataRecordField.Create('Close', TDataType.Float),
                TDataRecordField.Create('Volume', TDataType.Float)
            ]
        );
    var argsType := ohlcType;
    var resultType := TDataType.Float;

    var indicatorMethodType := TDataType.MethodOf(argsType, resultType);
    var factoryMethodType := TDataType.MethodOf(paramsType, indicatorMethodType);

    FFactory :=
        factoryMethodType.CreateValue(
            function(const Params: TDataType.TValue): TDataType.TValue
            begin
                var ATR := CreateATR(Params.AsOrdinal.Value);

                Result :=
                    indicatorMethodType.CreateValue(
                        function(const Args: TDataType.TValue): TDataType.TValue
                        var
                            ohlcRec: IDataRecordValue;
                            ohlcVal: TOhlcItem;
                        begin
                            ohlcRec := Args.AsRecord;
                            ohlcVal.Open := TDataType.TValue(ohlcRec.Items[0]).AsFloat.Value;
                            ohlcVal.High := TDataType.TValue(ohlcRec.Items[1]).AsFloat.Value;
                            ohlcVal.Low := TDataType.TValue(ohlcRec.Items[2]).AsFloat.Value;
                            ohlcVal.Close := TDataType.TValue(ohlcRec.Items[3]).AsFloat.Value;
                            ohlcVal.Volume := TDataType.TValue(ohlcRec.Items[4]).AsFloat.Value;
                            Result := TDataType.Float.CreateValue(ATR(ohlcVal));
                        end
                    );
            end
        );
end;

class function TATR.CreateFactory: TIndicatorFactoryProc<TATR.TParams, TATR.TArgs, TATR.TResult>;
begin
    Result :=
        function(const Params: TParams): TConvertFunc<TArgs, TResult>
        var
            atrFunc: TConvertFunc<TOhlcItem, Double>;
        begin
            atrFunc := CreateATR(Params.Period);
            Result := function(const Value: TArgs): TResult begin Result.ATR := atrFunc(Value.Value); end;
        end;
end;

class function TATR.CreateATR(Period: Integer): TConvertFunc<TOhlcItem, Double>;
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<Double, Double>): TConvertFunc<TOhlcItem, Double>;
begin
    var sourceData: TSeries<TOhlcItem>;

    Result :=
        function(const Value: TOhlcItem): Double
        var
            tr: Double;
        begin
            // We only need the previous bar to calculate true range.
            sourceData.Add(Value, 2);

            if (sourceData.Count < 2) then
            begin
                // Feed a dummy value to keep the moving average count in sync. It will correctly return NaN.
                Result := MovAvgTR(0);
                Exit;
            end;

            // Calculate current True Range.
            tr := Max(Value.High - Value.Low, Max(Abs(Value.High - sourceData[1].Close), Abs(Value.Low - sourceData[1].Close)));

            // Feed the calculated TR into the provided moving average function.
            Result := MovAvgTR(tr);
        end;
end;

{ TKeltnerChannels }

class constructor TKeltnerChannels.CreateClass;
begin
    var paramsType :=
        TDataType.RecordOf([TDataRecordField.Create('Period', TDataType.Ordinal), TDataRecordField.Create('Multiplier', TDataType.Float)]);
    var ohlcType :=
        TDataType.RecordOf(
            [
                TDataRecordField.Create('Open', TDataType.Float),
                TDataRecordField.Create('High', TDataType.Float),
                TDataRecordField.Create('Low', TDataType.Float),
                TDataRecordField.Create('Close', TDataType.Float),
                TDataRecordField.Create('Volume', TDataType.Float)
            ]
        );
    var argsType := ohlcType;
    var resultType :=
        TDataType.RecordOf(
            [
                TDataRecordField.Create('UpperBand', TDataType.Float),
                TDataRecordField.Create('MiddleBand', TDataType.Float),
                TDataRecordField.Create('LowerBand', TDataType.Float)
            ]
        );

    var indicatorMethodType := TDataType.MethodOf(argsType, resultType);
    var factoryMethodType := TDataType.MethodOf(paramsType, indicatorMethodType);

    FFactory :=
        factoryMethodType.CreateValue(
            function(const Params: TDataType.TValue): TDataType.TValue
            var
                paramsRec: IDataRecordValue;
            begin
                paramsRec := Params.AsRecord;
                var KeltnerChannels :=
                    CreateKeltnerChannels(
                        TDataType.TValue(paramsRec.Items[0]).AsOrdinal.Value,
                        TDataType.TValue(paramsRec.Items[1]).AsFloat.Value
                    );

                Result :=
                    indicatorMethodType.CreateValue(
                        function(const Args: TDataType.TValue): TDataType.TValue
                        var
                            ohlcRec: IDataRecordValue;
                            ohlcVal: TOhlcItem;
                        begin
                            ohlcRec := Args.AsRecord;
                            ohlcVal.Open := TDataType.TValue(ohlcRec.Items[0]).AsFloat.Value;
                            ohlcVal.High := TDataType.TValue(ohlcRec.Items[1]).AsFloat.Value;
                            ohlcVal.Low := TDataType.TValue(ohlcRec.Items[2]).AsFloat.Value;
                            ohlcVal.Close := TDataType.TValue(ohlcRec.Items[3]).AsFloat.Value;
                            ohlcVal.Volume := TDataType.TValue(ohlcRec.Items[4]).AsFloat.Value;

                            var res := KeltnerChannels(ohlcVal);
                            Result :=
                                resultType.CreateValue(
                                    [
                                        TDataType.Float.CreateValue(res.UpperBand),
                                        TDataType.Float.CreateValue(res.MiddleBand),
                                        TDataType.Float.CreateValue(res.LowerBand)
                                    ]
                                );
                        end
                    );
            end
        );
end;

class function TKeltnerChannels.CreateFactory:
    TIndicatorFactoryProc<TKeltnerChannels.TParams, TKeltnerChannels.TArgs, TKeltnerChannels.TResult>;
begin
    Result :=
        function(const Params: TParams): TConvertFunc<TArgs, TResult>
        var
            kcFunc: TConvertFunc<TOhlcItem, TResult>;
        begin
            kcFunc := CreateKeltnerChannels(Params.Period, Params.Multiplier);
            Result := function(const Value: TArgs): TResult begin Result := kcFunc(Value.Value); end;
        end;
end;

class function TKeltnerChannels.CreateKeltnerChannels(Period: Integer; Multiplier: Double): TConvertFunc<TOhlcItem, TResult>;
begin
    Result := CreateKeltnerChannels(TEMA.CreateEMA(Period), TATR.CreateATR(Period), Multiplier);
end;

// Calculates Keltner Channels using an injectable ATR and middle band moving average.
class function TKeltnerChannels.CreateKeltnerChannels(
    const MovAvgMiddle: TConvertFunc<Double, Double>;
    const AtrFunc: TConvertFunc<TOhlcItem, Double>;
    Multiplier: Double
): TConvertFunc<TOhlcItem, TKeltnerChannels.TResult>;
begin
    Result :=
        function(const Value: TOhlcItem): TKeltnerChannels.TResult
        var
            atrValue, middleValue, typicalPrice: Double;
        begin
            // Calculate Typical Price for the middle band.
            typicalPrice := (Value.High + Value.Low + Value.Close) / 3.0;

            // Get values from the provided indicator functions.
            middleValue := MovAvgMiddle(typicalPrice);
            atrValue := AtrFunc(Value);

            // Set default NaN values for the warm-up period.
            Result.MiddleBand := middleValue;
            Result.UpperBand := Double.NaN;
            Result.LowerBand := Double.NaN;

            // Once both middle band and ATR have valid (non-NaN) values, calculate the channels.
            if not IsNan(middleValue) and not IsNan(atrValue) then
            begin
                Result.UpperBand := middleValue + (atrValue * Multiplier);
                Result.LowerBand := middleValue - (atrValue * Multiplier);
            end;
        end;
end;

{ TMean }

class constructor TMean.CreateClass;
begin
    var argsType := TDataType.ArrayOf(TDataType.Float);
    var resultType := TDataType.Float;

    var indicatorMethodType := TDataType.MethodOf(argsType, resultType);
    var factoryMethodType := TDataType.MethodOf(TDataType.Void, indicatorMethodType);

    FFactory :=
        factoryMethodType.CreateValue(
            function(const Params: TDataType.TValue): TDataType.TValue
            begin
                var Mean := CreateMean();
                Result :=
                    indicatorMethodType.CreateValue(
                        function(const Args: TDataType.TValue): TDataType.TValue
                        var
                            i: Integer;
                            valuesArray: IDataArrayValue;
                            values: TArray<Double>;
                        begin
                            valuesArray := Args.AsArray;
                            SetLength(values, valuesArray.ElementCount);
                            for i := 0 to valuesArray.ElementCount - 1 do
                                values[i] := TDataType.TValue(valuesArray.Items[i]).AsFloat.Value;

                            Result := TDataType.Float.CreateValue(Mean(values));
                        end
                    );
            end
        );
end;

class function TMean.CreateFactory: TIndicatorFactoryProc<TMean.TParams, TMean.TArgs, TMean.TResult>;
begin
    Result :=
        function(const Params: TParams): TConvertFunc<TArgs, TResult>
        var
            meanFunc: TConvertFunc<TArray<Double>, Double>;
        begin
            meanFunc := CreateMean();
            Result := function(const Value: TArgs): TResult begin Result.Mean := meanFunc(Value.Values); end;
        end;
end;

class function TMean.CreateMean: TConvertFunc<TArray<Double>, Double>;
begin
    Result :=
        function(const Value: TArray<Double>): Double
        var
            i: Integer;
            sum: Double;
        begin
            if Length(Value) = 0 then
                exit(NaN);

            sum := 0.0;
            for i := 0 to High(Value) do
                sum := sum + Value[i];

            Result := sum / Length(Value);
        end;
end;

initialization
    IndicatorRegistry.RegisterTemplate<TSMA>;
    IndicatorRegistry.RegisterTemplate<TEMA>;
    IndicatorRegistry.RegisterTemplate<TWMA>;
    IndicatorRegistry.RegisterTemplate<THMA>;
    IndicatorRegistry.RegisterTemplate<TRSI>;
    IndicatorRegistry.RegisterTemplate<TMACD>;
    IndicatorRegistry.RegisterTemplate<TStochastic>;
    IndicatorRegistry.RegisterTemplate<TStdDev>;
    IndicatorRegistry.RegisterTemplate<TBollingerBands>;
    IndicatorRegistry.RegisterTemplate<TATR>;
    IndicatorRegistry.RegisterTemplate<TKeltnerChannels>;
    IndicatorRegistry.RegisterTemplate<TMean>;

end.
