Added some standard indicators
This commit is contained in:
@@ -12,7 +12,8 @@ uses
|
|||||||
DynamicFMXControl in 'DynamicFMXControl.pas',
|
DynamicFMXControl in 'DynamicFMXControl.pas',
|
||||||
FirstStrategy in 'FirstStrategy.pas',
|
FirstStrategy in 'FirstStrategy.pas',
|
||||||
Myc.Trade.DataArray in '..\Src\Myc.Trade.DataArray.pas',
|
Myc.Trade.DataArray in '..\Src\Myc.Trade.DataArray.pas',
|
||||||
Myc.FMX.Chart.Series in '..\Src\Myc.FMX.Chart.Series.pas';
|
Myc.FMX.Chart.Series in '..\Src\Myc.FMX.Chart.Series.pas',
|
||||||
|
Myc.Trade.Indicators in '..\Src\Myc.Trade.Indicators.pas';
|
||||||
|
|
||||||
{$R *.res}
|
{$R *.res}
|
||||||
|
|
||||||
|
|||||||
@@ -141,6 +141,7 @@
|
|||||||
<DCCReference Include="FirstStrategy.pas"/>
|
<DCCReference Include="FirstStrategy.pas"/>
|
||||||
<DCCReference Include="..\Src\Myc.Trade.DataArray.pas"/>
|
<DCCReference Include="..\Src\Myc.Trade.DataArray.pas"/>
|
||||||
<DCCReference Include="..\Src\Myc.FMX.Chart.Series.pas"/>
|
<DCCReference Include="..\Src\Myc.FMX.Chart.Series.pas"/>
|
||||||
|
<DCCReference Include="..\Src\Myc.Trade.Indicators.pas"/>
|
||||||
<BuildConfiguration Include="Base">
|
<BuildConfiguration Include="Base">
|
||||||
<Key>Base</Key>
|
<Key>Base</Key>
|
||||||
</BuildConfiguration>
|
</BuildConfiguration>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ unit FirstStrategy;
|
|||||||
interface
|
interface
|
||||||
|
|
||||||
uses
|
uses
|
||||||
|
System.SysUtils,
|
||||||
System.Generics.Collections,
|
System.Generics.Collections,
|
||||||
Myc.Signals,
|
Myc.Signals,
|
||||||
Myc.Lazy,
|
Myc.Lazy,
|
||||||
@@ -42,29 +43,26 @@ type
|
|||||||
property Timeframe: TTimeframe read GetTimeframe;
|
property Timeframe: TTimeframe read GetTimeframe;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// Implements the Hull Moving Average indicator.
|
TIndicator<S, T> = class(TMycConverter<S, T>)
|
||||||
THullMovingAverage = class(TMycConverter<Double, Double>)
|
strict private
|
||||||
private
|
|
||||||
FPeriod: Integer;
|
|
||||||
FPeriodHalf: Integer;
|
|
||||||
FPeriodSqrt: Integer;
|
|
||||||
// Source data for HMA calculation
|
|
||||||
FSourceData: TMycDataArray<Double>;
|
|
||||||
// Intermediate data series for HMA calculation (2*WMA(n/2) - WMA(n))
|
|
||||||
FDiffSeries: TMycDataArray<Double>;
|
|
||||||
FQueue: TQueue;
|
FQueue: TQueue;
|
||||||
// Calculates the Weighted Moving Average for the most recent data.
|
|
||||||
function CalculateWMA(const Series: TMycDataArray<Double>; const Period: Integer): Double;
|
|
||||||
protected
|
protected
|
||||||
function ProcessData(const Value: Double): TState; override;
|
function ProcessData(const Value: S): TState; override; final;
|
||||||
|
function Calculate(const Value: S): T; virtual; abstract;
|
||||||
|
end;
|
||||||
|
|
||||||
|
TGenericIndicator<S, T> = class(TIndicator<S, T>)
|
||||||
|
private
|
||||||
|
FFunc: TFunc<S, T>;
|
||||||
|
protected
|
||||||
|
function Calculate(const Value: S): T; override; final;
|
||||||
public
|
public
|
||||||
constructor Create(const APeriod: Integer);
|
constructor Create(const AFunc: TFunc<S, T>);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
implementation
|
implementation
|
||||||
|
|
||||||
uses
|
uses
|
||||||
System.SysUtils,
|
|
||||||
System.DateUtils,
|
System.DateUtils,
|
||||||
System.Math;
|
System.Math;
|
||||||
|
|
||||||
@@ -149,89 +147,6 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
{ THullMovingAverage }
|
|
||||||
|
|
||||||
constructor THullMovingAverage.Create(const APeriod: Integer);
|
|
||||||
begin
|
|
||||||
inherited Create;
|
|
||||||
FPeriod := APeriod;
|
|
||||||
FPeriodHalf := APeriod div 2;
|
|
||||||
FPeriodSqrt := Round(Sqrt(APeriod));
|
|
||||||
|
|
||||||
// Initialize data arrays.
|
|
||||||
FSourceData := TMycDataArray<Double>.CreateEmpty;
|
|
||||||
FDiffSeries := TMycDataArray<Double>.CreateEmpty;
|
|
||||||
end;
|
|
||||||
|
|
||||||
function THullMovingAverage.CalculateWMA(const Series: TMycDataArray<Double>; const Period: Integer): Double;
|
|
||||||
var
|
|
||||||
i: Integer;
|
|
||||||
numerator: Double;
|
|
||||||
denominator: Int64;
|
|
||||||
begin
|
|
||||||
// Ensure there is enough data to calculate the WMA
|
|
||||||
if (Series.Count < Period) or (Period <= 0) then
|
|
||||||
Exit(0.0);
|
|
||||||
|
|
||||||
numerator := 0;
|
|
||||||
// The sum of weights (1 + 2 + ... + Period)
|
|
||||||
denominator := Period * (Period + 1) div 2;
|
|
||||||
|
|
||||||
if (denominator = 0) then
|
|
||||||
Exit(0.0);
|
|
||||||
|
|
||||||
for i := 0 to Period - 1 do
|
|
||||||
begin
|
|
||||||
// Newest data (index 0) gets the highest weight (Period)
|
|
||||||
numerator := numerator + Series[i] * (Period - i);
|
|
||||||
end;
|
|
||||||
|
|
||||||
Result := numerator / denominator;
|
|
||||||
end;
|
|
||||||
|
|
||||||
function THullMovingAverage.ProcessData(const Value: Double): TState;
|
|
||||||
begin
|
|
||||||
Result :=
|
|
||||||
FQueue.Enqueue(
|
|
||||||
function: TState
|
|
||||||
var
|
|
||||||
price: Double;
|
|
||||||
wmaHalf, wmaFull, diff: Double;
|
|
||||||
hma: Double;
|
|
||||||
begin
|
|
||||||
price := Value;
|
|
||||||
|
|
||||||
// Default HMA to NaN for the warm-up period.
|
|
||||||
hma := Double.NaN;
|
|
||||||
|
|
||||||
// Add new price to the source data array, respecting the lookback period.
|
|
||||||
FSourceData := FSourceData.Add(price, FPeriod);
|
|
||||||
|
|
||||||
// Check if there is enough data to start the first stage of calculation.
|
|
||||||
if (FSourceData.Count >= FPeriod) then
|
|
||||||
begin
|
|
||||||
// Calculate the two WMAs for the first step.
|
|
||||||
wmaHalf := CalculateWMA(FSourceData, FPeriodHalf);
|
|
||||||
wmaFull := CalculateWMA(FSourceData, FPeriod);
|
|
||||||
|
|
||||||
// Calculate the difference and add to the intermediate series.
|
|
||||||
diff := 2 * wmaHalf - wmaFull;
|
|
||||||
FDiffSeries := FDiffSeries.Add(diff, FPeriodSqrt);
|
|
||||||
|
|
||||||
// Check if there is enough intermediate data for the final calculation.
|
|
||||||
if (FDiffSeries.Count >= FPeriodSqrt) then
|
|
||||||
begin
|
|
||||||
// Calculate the final HMA value, overwriting the default 0.0.
|
|
||||||
hma := CalculateWMA(FDiffSeries, FPeriodSqrt);
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
|
|
||||||
// Broadcast the result
|
|
||||||
Result := Broadcast(hma);
|
|
||||||
end
|
|
||||||
);
|
|
||||||
end;
|
|
||||||
|
|
||||||
{ TMycGenericConverter<S, T> }
|
{ TMycGenericConverter<S, T> }
|
||||||
|
|
||||||
constructor TMycGenericConverter<S, T>.Create(const AFunc: TConvertFunc);
|
constructor TMycGenericConverter<S, T>.Create(const AFunc: TConvertFunc);
|
||||||
@@ -245,4 +160,20 @@ begin
|
|||||||
Result := Broadcast(FFunc(Value));
|
Result := Broadcast(FFunc(Value));
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
function TIndicator<S, T>.ProcessData(const Value: S): TState;
|
||||||
|
begin
|
||||||
|
Result := FQueue.Enqueue(function: TState begin Result := Broadcast(Calculate(Value)); end);
|
||||||
|
end;
|
||||||
|
|
||||||
|
constructor TGenericIndicator<S, T>.Create(const AFunc: TFunc<S, T>);
|
||||||
|
begin
|
||||||
|
inherited Create;
|
||||||
|
FFunc := AFunc;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TGenericIndicator<S, T>.Calculate(const Value: S): T;
|
||||||
|
begin
|
||||||
|
Result := FFunc(Value);
|
||||||
|
end;
|
||||||
|
|
||||||
end.
|
end.
|
||||||
|
|||||||
+76
-17
@@ -121,7 +121,8 @@ var
|
|||||||
implementation
|
implementation
|
||||||
|
|
||||||
uses
|
uses
|
||||||
TestModule;
|
TestModule,
|
||||||
|
Myc.Trade.Indicators;
|
||||||
|
|
||||||
{$R *.fmx}
|
{$R *.fmx}
|
||||||
|
|
||||||
@@ -180,24 +181,82 @@ begin
|
|||||||
|
|
||||||
Ohlc.Sender.Link(Closes);
|
Ohlc.Sender.Link(Closes);
|
||||||
|
|
||||||
for var i := 0 to 3 do
|
var Hull: IMycConverter<Double, Double> := TGenericIndicator<Double, Double>.Create(TIndicators.CreateHMA(150));
|
||||||
begin
|
Closes.Sender.Link(Hull);
|
||||||
var Hull: IMycConverter<Double, Double> := THullMovingAverage.Create(50 + (500 * i));
|
chart.AddDoubleSeries(Hull.Sender, TAlphaColors.Aliceblue);
|
||||||
Closes.Sender.Link(Hull);
|
|
||||||
var col: TAlphaColorRec;
|
|
||||||
col.R := 25 * i;
|
|
||||||
col.G := 255 - 25 * i;
|
|
||||||
col.B := 100 + 5 * i;
|
|
||||||
col.A := 255;
|
|
||||||
chart.AddDoubleSeries(Hull.Sender, col.Color);
|
|
||||||
end;
|
|
||||||
|
|
||||||
// var Hull: IMycConverter<Double, Double> := THullMovingAverage.Create(250);
|
// Add SMA (Simple Moving Average)
|
||||||
//
|
var Sma: IMycConverter<Double, Double> := TGenericIndicator<Double, Double>.Create(TIndicators.CreateSMA(50));
|
||||||
// Closes.Sender.Link(Hull);
|
Closes.Sender.Link(Sma);
|
||||||
//
|
chart.AddDoubleSeries(Sma.Sender, TAlphaColors.Yellow);
|
||||||
// chart.AddDoubleSeries(Hull.Sender);
|
|
||||||
|
|
||||||
|
// Add EMA (Exponential Moving Average)
|
||||||
|
var Ema: IMycConverter<Double, Double> := TGenericIndicator<Double, Double>.Create(TIndicators.CreateEMA(21));
|
||||||
|
Closes.Sender.Link(Ema);
|
||||||
|
chart.AddDoubleSeries(Ema.Sender, TAlphaColors.Aqua);
|
||||||
|
|
||||||
|
// Add Bollinger Bands (20, 2.0)
|
||||||
|
var Boli: IMycConverter<Double, TBollingerBandsResult> :=
|
||||||
|
TGenericIndicator<Double, TBollingerBandsResult>.Create(TIndicators.CreateBollingerBands(20, 2.0));
|
||||||
|
Closes.Sender.Link(Boli);
|
||||||
|
|
||||||
|
var BoliUpper: IMycConverter<TBollingerBandsResult, Double> :=
|
||||||
|
TMycGenericConverter<TBollingerBandsResult, Double>
|
||||||
|
.Create(function(const Item: TBollingerBandsResult): Double begin Result := Item.UpperBand; end);
|
||||||
|
Boli.Sender.Link(BoliUpper);
|
||||||
|
chart.AddDoubleSeries(BoliUpper.Sender, TAlphaColors.Gray);
|
||||||
|
|
||||||
|
var BoliMiddle: IMycConverter<TBollingerBandsResult, Double> :=
|
||||||
|
TMycGenericConverter<TBollingerBandsResult, Double>
|
||||||
|
.Create(function(const Item: TBollingerBandsResult): Double begin Result := Item.MiddleBand; end);
|
||||||
|
Boli.Sender.Link(BoliMiddle);
|
||||||
|
chart.AddDoubleSeries(BoliMiddle.Sender, TAlphaColors.Darkgray, 1.0);
|
||||||
|
|
||||||
|
var BoliLower: IMycConverter<TBollingerBandsResult, Double> :=
|
||||||
|
TMycGenericConverter<TBollingerBandsResult, Double>
|
||||||
|
.Create(function(const Item: TBollingerBandsResult): Double begin Result := Item.LowerBand; end);
|
||||||
|
Boli.Sender.Link(BoliLower);
|
||||||
|
chart.AddDoubleSeries(BoliLower.Sender, TAlphaColors.Gray);
|
||||||
|
|
||||||
|
var rsiChart := TMycChart.Create(Self);
|
||||||
|
AlignControl(rsiChart);
|
||||||
|
rsiChart.Height := Layout.ChildrenRect.Width * 9 / 32;
|
||||||
|
rsiChart.Lookback.Value := 50000;
|
||||||
|
rsiChart.SetXAxisSeries<TDateTime>(Timestamps.Sender);
|
||||||
|
|
||||||
|
// Add RSI (Relative Strength Index)
|
||||||
|
var Rsi: IMycConverter<Double, Double> := TGenericIndicator<Double, Double>.Create(TIndicators.CreateRSI(14));
|
||||||
|
Closes.Sender.Link(Rsi);
|
||||||
|
rsiChart.AddDoubleSeries(Rsi.Sender, TAlphaColors.Fuchsia);
|
||||||
|
{
|
||||||
|
// Add MACD (12, 26, 9)
|
||||||
|
var Macd: IMycConverter<Double, TMacdResult> := TGenericIndicator<Double, TMacdResult>.Create(TIndicators.CreateMACD(12, 26, 9));
|
||||||
|
Closes.Sender.Link(Macd);
|
||||||
|
|
||||||
|
var MacdLine: IMycConverter<TMacdResult, Double> := TMycGenericConverter<TMacdResult, Double>.Create(function(const Item: TMacdResult): Double begin Result := Item.MacdLine; end);
|
||||||
|
Macd.Sender.Link(MacdLine);
|
||||||
|
chart.AddDoubleSeries(MacdLine.Sender, TAlphaColors.Orange);
|
||||||
|
|
||||||
|
var MacdSignal: IMycConverter<TMacdResult, Double> := TMycGenericConverter<TMacdResult, Double>.Create(function(const Item: TMacdResult): Double begin Result := Item.SignalLine; end);
|
||||||
|
Macd.Sender.Link(MacdSignal);
|
||||||
|
chart.AddDoubleSeries(MacdSignal.Sender, TAlphaColors.Dodgerblue);
|
||||||
|
|
||||||
|
var MacdHist: IMycConverter<TMacdResult, Double> := TMycGenericConverter<TMacdResult, Double>.Create(function(const Item: TMacdResult): Double begin Result := Item.Histogram; end);
|
||||||
|
Macd.Sender.Link(MacdHist);
|
||||||
|
chart.AddDoubleSeries(MacdHist.Sender, TAlphaColors.Lightgreen, 1.0);
|
||||||
|
|
||||||
|
// Add Stochastic Oscillator (14, 3) - This needs OHLC data, not just Close prices.
|
||||||
|
var Stoch: IMycConverter<TOhlcItem, TStochasticResult> := TGenericIndicator<TOhlcItem, TStochasticResult>.Create(TIndicators.CreateStochastic(14, 3));
|
||||||
|
Ohlc.Sender.Link(Stoch);
|
||||||
|
|
||||||
|
var StochK: IMycConverter<TStochasticResult, Double> := TMycGenericConverter<TStochasticResult, Double>.Create(function(const Item: TStochasticResult): Double begin Result := Item.K; end);
|
||||||
|
Stoch.Sender.Link(StochK);
|
||||||
|
chart.AddDoubleSeries(StochK.Sender, TAlphaColors.Green);
|
||||||
|
|
||||||
|
var StochD: IMycConverter<TStochasticResult, Double> := TMycGenericConverter<TStochasticResult, Double>.Create(function(const Item: TStochasticResult): Double begin Result := Item.D; end);
|
||||||
|
Stoch.Sender.Link(StochD);
|
||||||
|
chart.AddDoubleSeries(StochD.Sender, TAlphaColors.Red);
|
||||||
|
}
|
||||||
OhlcPoint.Sender.Link(TimeStamps);
|
OhlcPoint.Sender.Link(TimeStamps);
|
||||||
|
|
||||||
chart.SetXAxisSeries<TDateTime>(Timestamps.Sender);
|
chart.SetXAxisSeries<TDateTime>(Timestamps.Sender);
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ unit Myc.Trade.DataArray;
|
|||||||
|
|
||||||
interface
|
interface
|
||||||
|
|
||||||
uses
|
|
||||||
Myc.Trade.DataPoint;
|
|
||||||
|
|
||||||
type
|
type
|
||||||
|
// A series is an array of values with the newest ite at index=0. Each series counts the total of added items since creation, but
|
||||||
|
// it actually may contain less items, because the array size is limited by the lookback parameter, when adding items.
|
||||||
|
// Series are immutable.
|
||||||
TMycDataArray<T> = record
|
TMycDataArray<T> = record
|
||||||
private
|
private
|
||||||
const
|
const
|
||||||
@@ -21,7 +21,9 @@ type
|
|||||||
function GetItems(Idx: Int64): T; inline;
|
function GetItems(Idx: Int64): T; inline;
|
||||||
public
|
public
|
||||||
constructor Create(const AChunks: TArray<TChunk>; ACount, ATotalCount: Int64);
|
constructor Create(const AChunks: TArray<TChunk>; ACount, ATotalCount: Int64);
|
||||||
|
// Add a singe item
|
||||||
function Add(const Data: T; Lookback: Int64): TMycDataArray<T>; overload;
|
function Add(const Data: T; Lookback: Int64): TMycDataArray<T>; overload;
|
||||||
|
// Add a ranmge of items
|
||||||
function Add(const Data: array of T; First, Count, Lookback: Int64): TMycDataArray<T>; overload;
|
function Add(const Data: array of T; First, Count, Lookback: Int64): TMycDataArray<T>; overload;
|
||||||
class function CreateEmpty: TMycDataArray<T>; static;
|
class function CreateEmpty: TMycDataArray<T>; static;
|
||||||
// Helper to create a data array from a raw TArray.
|
// Helper to create a data array from a raw TArray.
|
||||||
|
|||||||
@@ -0,0 +1,355 @@
|
|||||||
|
unit Myc.Trade.Indicators;
|
||||||
|
|
||||||
|
interface
|
||||||
|
|
||||||
|
uses
|
||||||
|
System.SysUtils,
|
||||||
|
System.Math,
|
||||||
|
Myc.Trade.DataArray,
|
||||||
|
Myc.Trade.DataPoint;
|
||||||
|
|
||||||
|
type
|
||||||
|
// Result for the Moving Average Convergence Divergence (MACD) indicator.
|
||||||
|
TMacdResult = record
|
||||||
|
MacdLine: Double;
|
||||||
|
SignalLine: Double;
|
||||||
|
Histogram: Double;
|
||||||
|
end;
|
||||||
|
|
||||||
|
// Result for the Stochastic Oscillator indicator.
|
||||||
|
TStochasticResult = record
|
||||||
|
K: Double; // %K line
|
||||||
|
D: Double; // %D line (signal line)
|
||||||
|
end;
|
||||||
|
|
||||||
|
// Result for the Bollinger Bands indicator.
|
||||||
|
TBollingerBandsResult = record
|
||||||
|
UpperBand: Double;
|
||||||
|
MiddleBand: Double;
|
||||||
|
LowerBand: Double;
|
||||||
|
end;
|
||||||
|
|
||||||
|
TIndicators = record
|
||||||
|
private
|
||||||
|
class function CalculateSMA(const Series: TMycDataArray<Double>; const Period: Integer): Double; static;
|
||||||
|
class function CalculateStdDev(const Series: TMycDataArray<Double>; const Period: Integer): Double; static;
|
||||||
|
class function CalculateWMA(const Series: TMycDataArray<Double>; const Period: Integer): Double; static;
|
||||||
|
public
|
||||||
|
// Simple Moving Average
|
||||||
|
class function CreateSMA(Period: Integer): TFunc<Double, Double>; static;
|
||||||
|
// Exponential Moving Average
|
||||||
|
class function CreateEMA(Period: Integer): TFunc<Double, Double>; static;
|
||||||
|
// Hull Moving Average
|
||||||
|
class function CreateHMA(Period: Integer): TFunc<Double, Double>; static;
|
||||||
|
// Relative Strength Index
|
||||||
|
class function CreateRSI(Period: Integer): TFunc<Double, Double>; static;
|
||||||
|
// Moving Average Convergence Divergence
|
||||||
|
class function CreateMACD(FastPeriod, SlowPeriod, SignalPeriod: Integer): TFunc<Double, TMacdResult>; static;
|
||||||
|
// Stochastic Oscillator
|
||||||
|
class function CreateStochastic(KPeriod, DPeriod: Integer): TFunc<TOhlcItem, TStochasticResult>; static;
|
||||||
|
// Bollinger Bands
|
||||||
|
class function CreateBollingerBands(Period: Integer; Multiplier: Double): TFunc<Double, TBollingerBandsResult>; static;
|
||||||
|
end;
|
||||||
|
|
||||||
|
implementation
|
||||||
|
|
||||||
|
{ TIndicators }
|
||||||
|
|
||||||
|
class function TIndicators.CalculateSMA(const Series: TMycDataArray<Double>; const Period: Integer): Double;
|
||||||
|
var
|
||||||
|
i: Integer;
|
||||||
|
sum: Double;
|
||||||
|
begin
|
||||||
|
if (Series.Count < Period) or (Period <= 0) then
|
||||||
|
Exit(0.0);
|
||||||
|
|
||||||
|
sum := 0;
|
||||||
|
for i := 0 to Period - 1 do
|
||||||
|
sum := sum + Series[i];
|
||||||
|
|
||||||
|
Result := sum / Period;
|
||||||
|
end;
|
||||||
|
|
||||||
|
class function TIndicators.CalculateStdDev(const Series: TMycDataArray<Double>; const Period: Integer): Double;
|
||||||
|
var
|
||||||
|
i: Integer;
|
||||||
|
mean, sumOfSquares: Double;
|
||||||
|
begin
|
||||||
|
if (Series.Count < Period) or (Period <= 0) then
|
||||||
|
Exit(0.0);
|
||||||
|
|
||||||
|
mean := CalculateSMA(Series, Period);
|
||||||
|
sumOfSquares := 0;
|
||||||
|
for i := 0 to Period - 1 do
|
||||||
|
sumOfSquares := sumOfSquares + Power(Series[i] - mean, 2);
|
||||||
|
|
||||||
|
Result := Sqrt(sumOfSquares / Period);
|
||||||
|
end;
|
||||||
|
|
||||||
|
class function TIndicators.CalculateWMA(const Series: TMycDataArray<Double>; const Period: Integer): Double;
|
||||||
|
var
|
||||||
|
i: Integer;
|
||||||
|
numerator: Double;
|
||||||
|
denominator: Int64;
|
||||||
|
begin
|
||||||
|
// Ensure there is enough data to calculate the WMA
|
||||||
|
if (Series.Count < Period) or (Period <= 0) then
|
||||||
|
Exit(0.0);
|
||||||
|
|
||||||
|
numerator := 0;
|
||||||
|
// The sum of weights (1 + 2 + ... + Period)
|
||||||
|
denominator := Period * (Period + 1) div 2;
|
||||||
|
|
||||||
|
if (denominator = 0) then
|
||||||
|
Exit(0.0);
|
||||||
|
|
||||||
|
for i := 0 to Period - 1 do
|
||||||
|
begin
|
||||||
|
// Newest data (index 0) gets the highest weight (Period)
|
||||||
|
numerator := numerator + Series[i] * (Period - i);
|
||||||
|
end;
|
||||||
|
|
||||||
|
Result := numerator / denominator;
|
||||||
|
end;
|
||||||
|
|
||||||
|
class function TIndicators.CreateBollingerBands(Period: Integer; Multiplier: Double): TFunc<Double, TBollingerBandsResult>;
|
||||||
|
begin
|
||||||
|
var sourceData := TMycDataArray<Double>.CreateEmpty;
|
||||||
|
Result :=
|
||||||
|
function(Value: Double): TBollingerBandsResult
|
||||||
|
var
|
||||||
|
stdDev: Double;
|
||||||
|
begin
|
||||||
|
sourceData := sourceData.Add(Value, Period);
|
||||||
|
Result.MiddleBand := Double.NaN;
|
||||||
|
Result.UpperBand := Double.NaN;
|
||||||
|
Result.LowerBand := Double.NaN;
|
||||||
|
|
||||||
|
if (sourceData.Count >= Period) then
|
||||||
|
begin
|
||||||
|
Result.MiddleBand := CalculateSMA(sourceData, Period);
|
||||||
|
stdDev := CalculateStdDev(sourceData, Period);
|
||||||
|
Result.UpperBand := Result.MiddleBand + (stdDev * Multiplier);
|
||||||
|
Result.LowerBand := Result.MiddleBand - (stdDev * Multiplier);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
class function TIndicators.CreateEMA(Period: Integer): TFunc<Double, Double>;
|
||||||
|
begin
|
||||||
|
var lastEma: Double := Double.NaN;
|
||||||
|
var sourceData := TMycDataArray<Double>.CreateEmpty;
|
||||||
|
var multiplier := 2 / (Period + 1);
|
||||||
|
|
||||||
|
Result :=
|
||||||
|
function(Value: Double): Double
|
||||||
|
begin
|
||||||
|
sourceData := 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
|
||||||
|
lastEma := CalculateSMA(sourceData, Period);
|
||||||
|
end;
|
||||||
|
Result := lastEma;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
class function TIndicators.CreateHMA(Period: Integer): TFunc<Double, Double>;
|
||||||
|
begin
|
||||||
|
var periodHalf := Period div 2;
|
||||||
|
var periodSqrt := Round(Sqrt(Period));
|
||||||
|
var sourceData := TMycDataArray<Double>.CreateEmpty;
|
||||||
|
var diffSeries := TMycDataArray<Double>.CreateEmpty;
|
||||||
|
|
||||||
|
Result :=
|
||||||
|
function(Value: Double): Double
|
||||||
|
var
|
||||||
|
price: Double;
|
||||||
|
wmaHalf, wmaFull, diff: Double;
|
||||||
|
begin
|
||||||
|
price := Value;
|
||||||
|
|
||||||
|
// Default HMA to NaN for the warm-up period.
|
||||||
|
Result := Double.NaN;
|
||||||
|
|
||||||
|
// Add new price to the source data array, respecting the lookback period.
|
||||||
|
sourceData := sourceData.Add(price, Period);
|
||||||
|
|
||||||
|
// Check if there is enough data to start the first stage of calculation.
|
||||||
|
if (sourceData.Count >= Period) then
|
||||||
|
begin
|
||||||
|
// Calculate the two WMAs for the first step.
|
||||||
|
wmaHalf := CalculateWMA(sourceData, periodHalf);
|
||||||
|
wmaFull := CalculateWMA(sourceData, Period);
|
||||||
|
|
||||||
|
// Calculate the difference and add to the intermediate series.
|
||||||
|
diff := 2 * wmaHalf - wmaFull;
|
||||||
|
diffSeries := diffSeries.Add(diff, periodSqrt);
|
||||||
|
|
||||||
|
// Check if there is enough intermediate data for the final calculation.
|
||||||
|
if (diffSeries.Count >= periodSqrt) then
|
||||||
|
begin
|
||||||
|
// Calculate the final HMA value
|
||||||
|
Result := CalculateWMA(diffSeries, periodSqrt);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
class function TIndicators.CreateMACD(FastPeriod, SlowPeriod, SignalPeriod: Integer): TFunc<Double, TMacdResult>;
|
||||||
|
begin
|
||||||
|
var emaFast := CreateEMA(FastPeriod);
|
||||||
|
var emaSlow := CreateEMA(SlowPeriod);
|
||||||
|
var emaSignal := CreateEMA(SignalPeriod);
|
||||||
|
|
||||||
|
Result :=
|
||||||
|
function(Value: Double): TMacdResult
|
||||||
|
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;
|
||||||
|
|
||||||
|
class function TIndicators.CreateRSI(Period: Integer): TFunc<Double, Double>;
|
||||||
|
begin
|
||||||
|
var avgGain: Double := Double.NaN;
|
||||||
|
var avgLoss: Double := Double.NaN;
|
||||||
|
var sourceData := TMycDataArray<Double>.CreateEmpty;
|
||||||
|
|
||||||
|
Result :=
|
||||||
|
function(Value: Double): Double
|
||||||
|
var
|
||||||
|
change, gain, loss, rs: Double;
|
||||||
|
gainSum, lossSum: Double;
|
||||||
|
i: Integer;
|
||||||
|
begin
|
||||||
|
sourceData := 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;
|
||||||
|
|
||||||
|
class function TIndicators.CreateSMA(Period: Integer): TFunc<Double, Double>;
|
||||||
|
begin
|
||||||
|
var sourceData := TMycDataArray<Double>.CreateEmpty;
|
||||||
|
Result :=
|
||||||
|
function(Value: Double): Double
|
||||||
|
begin
|
||||||
|
sourceData := sourceData.Add(Value, Period);
|
||||||
|
if (sourceData.Count >= Period) then
|
||||||
|
Result := CalculateSMA(sourceData, Period)
|
||||||
|
else
|
||||||
|
Result := Double.NaN;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
class function TIndicators.CreateStochastic(KPeriod, DPeriod: Integer): TFunc<TOhlcItem, TStochasticResult>;
|
||||||
|
begin
|
||||||
|
var sourceData := TMycDataArray<TOhlcItem>.CreateEmpty;
|
||||||
|
var smaD := CreateSMA(DPeriod);
|
||||||
|
|
||||||
|
Result :=
|
||||||
|
function(Value: TOhlcItem): TStochasticResult
|
||||||
|
var
|
||||||
|
i: Integer;
|
||||||
|
highestHigh, lowestLow: Double;
|
||||||
|
begin
|
||||||
|
sourceData := sourceData.Add(Value, KPeriod);
|
||||||
|
Result.K := Double.NaN;
|
||||||
|
Result.D := Double.NaN;
|
||||||
|
|
||||||
|
if (sourceData.Count >= KPeriod) then
|
||||||
|
begin
|
||||||
|
highestHigh := -MaxDouble;
|
||||||
|
lowestLow := MaxDouble;
|
||||||
|
for i := 0 to KPeriod - 1 do
|
||||||
|
begin
|
||||||
|
// Correctly use High and Low fields
|
||||||
|
if (sourceData[i].High > highestHigh) then
|
||||||
|
highestHigh := sourceData[i].High;
|
||||||
|
if (sourceData[i].Low < lowestLow) then
|
||||||
|
lowestLow := sourceData[i].Low;
|
||||||
|
end;
|
||||||
|
|
||||||
|
if (highestHigh > lowestLow) then
|
||||||
|
// Correctly use the current Close
|
||||||
|
Result.K := 100 * (sourceData[0].Close - lowestLow) / (highestHigh - lowestLow)
|
||||||
|
else
|
||||||
|
Result.K := 100; // Or 50, depends on convention
|
||||||
|
|
||||||
|
Result.D := smaD(Result.K);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
end.
|
||||||
Reference in New Issue
Block a user