Added some standard indicators
This commit is contained in:
@@ -2,10 +2,10 @@ unit Myc.Trade.DataArray;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
Myc.Trade.DataPoint;
|
||||
|
||||
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
|
||||
private
|
||||
const
|
||||
@@ -21,7 +21,9 @@ type
|
||||
function GetItems(Idx: Int64): T; inline;
|
||||
public
|
||||
constructor Create(const AChunks: TArray<TChunk>; ACount, ATotalCount: Int64);
|
||||
// Add a singe item
|
||||
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;
|
||||
class function CreateEmpty: TMycDataArray<T>; static;
|
||||
// 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