Generic indicator factory
This commit is contained in:
@@ -682,7 +682,7 @@ begin
|
||||
exit;
|
||||
|
||||
var timeframe := TTimeframe.M15;
|
||||
ExecuteStrategy(Symbol, timeframe, CreateStrategy2(timeframe));
|
||||
// ExecuteStrategy(Symbol, timeframe, CreateStrategy2(timeframe));
|
||||
|
||||
var tstStrat := StrategyTest.CreateStrategy1(timeframe);
|
||||
ExecuteStrategy(Symbol, timeframe, tstStrat.Consumer);
|
||||
|
||||
@@ -242,6 +242,7 @@ type
|
||||
implementation
|
||||
|
||||
uses
|
||||
Winapi.Windows,
|
||||
System.RTTI,
|
||||
Myc.TaskManager;
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@ unit Myc.Data.Pipeline;
|
||||
|
||||
interface
|
||||
|
||||
{$M+}
|
||||
|
||||
uses
|
||||
Myc.Signals,
|
||||
Myc.Mutable,
|
||||
|
||||
+52
-22
@@ -18,6 +18,7 @@ type
|
||||
FTypeInfo: PtypeInfo;
|
||||
FName: string;
|
||||
FOffset: Integer;
|
||||
FSize: Integer;
|
||||
|
||||
procedure FromType(const [ref] Buffer: TBytes; const Src);
|
||||
procedure ToType(const [ref] Buffer: TBytes; var Dst);
|
||||
@@ -30,7 +31,7 @@ type
|
||||
|
||||
property Offset: Integer read FOffset;
|
||||
|
||||
constructor Create(const AName: string; AFieldType: TFieldType; ATypeInfo: PTypeInfo; AOffset: Integer);
|
||||
constructor Create(const AName: string; AFieldType: TFieldType; ATypeInfo: PTypeInfo; AOffset, ASize: Integer);
|
||||
|
||||
procedure Copy(Src, Dst: Pointer);
|
||||
|
||||
@@ -115,10 +116,6 @@ implementation
|
||||
uses
|
||||
System.Rtti;
|
||||
|
||||
const
|
||||
DataSize: array[TDataRecord.TFieldType] of Integer =
|
||||
(sizeof(Double), sizeof(Int64), sizeof(String), sizeof(TDateTime), sizeof(TDataRecord));
|
||||
|
||||
{ TDataRecord }
|
||||
|
||||
constructor TDataRecord.Create(const ALayout: TLayout; const ABuffer: TBytes);
|
||||
@@ -246,12 +243,13 @@ begin
|
||||
Dest.Layout.Fields[i].FinalizeField(Dest.FBuffer);
|
||||
end;
|
||||
|
||||
constructor TDataRecord.TField.Create(const AName: string; AFieldType: TFieldType; ATypeInfo: PTypeInfo; AOffset: Integer);
|
||||
constructor TDataRecord.TField.Create(const AName: string; AFieldType: TFieldType; ATypeInfo: PTypeInfo; AOffset, ASize: Integer);
|
||||
begin
|
||||
FName := AName;
|
||||
FFieldType := AFieldType;
|
||||
FOffset := AOffset;
|
||||
FTypeInfo := ATypeInfo;
|
||||
FSize := ASize;
|
||||
end;
|
||||
|
||||
procedure TDataRecord.TField.InitField(const [ref] Buffer: TBytes);
|
||||
@@ -289,6 +287,9 @@ begin
|
||||
end;
|
||||
|
||||
function TDataRecord.TField.GetSize: Integer;
|
||||
const
|
||||
DataSize: array[TDataRecord.TFieldType] of Integer =
|
||||
(sizeof(Double), sizeof(Int64), sizeof(String), sizeof(TDateTime), sizeof(TDataRecord));
|
||||
begin
|
||||
Result := DataSize[FFieldType];
|
||||
end;
|
||||
@@ -425,6 +426,9 @@ begin
|
||||
end;
|
||||
|
||||
class function TDataRecord.TLayout.Construct(const Def: TArray<TFieldDef>): TLayout;
|
||||
const
|
||||
DataSize: array[TDataRecord.TFieldType] of Integer =
|
||||
(sizeof(Double), sizeof(Int64), sizeof(String), sizeof(TDateTime), sizeof(TDataRecord));
|
||||
var
|
||||
Fields: TArray<TField>;
|
||||
begin
|
||||
@@ -432,7 +436,7 @@ begin
|
||||
var ofs := 0;
|
||||
for var i := 0 to High(Fields) do
|
||||
begin
|
||||
var ti: PTypeInfo;
|
||||
var ti: PTypeInfo := nil;
|
||||
case Def[i].FieldType of
|
||||
dfFloat: ti := TypeInfo(Double);
|
||||
dfInteger: ti := TypeInfo(Int64);
|
||||
@@ -441,7 +445,7 @@ begin
|
||||
dfRecord: ti := TypeInfo(TDataRecord);
|
||||
end;
|
||||
|
||||
Fields[i] := TField.Create(Def[i].Name, Def[i].FieldType, ti, ofs);
|
||||
Fields[i] := TField.Create(Def[i].Name, Def[i].FieldType, ti, ofs, DataSize[Def[i].FieldType]);
|
||||
inc(ofs, Fields[i].AlignedSize);
|
||||
end;
|
||||
|
||||
@@ -477,33 +481,59 @@ begin
|
||||
begin
|
||||
var rf := rttiFields[i];
|
||||
var ft: TFieldType := Default(TFieldType);
|
||||
var supported := true;
|
||||
var size: Integer := 0;
|
||||
|
||||
case rf.FieldType.TypeKind of
|
||||
tkInt64: ft := dfInteger;
|
||||
tkInteger:
|
||||
begin
|
||||
ft := dfInteger;
|
||||
size := sizeof(Integer);
|
||||
end;
|
||||
tkInt64:
|
||||
begin
|
||||
ft := dfInteger;
|
||||
size := sizeof(Int64);
|
||||
end;
|
||||
tkFloat:
|
||||
if rf.FieldType.HasName(GetTypeName(TypeInfo(TDateTime))) then
|
||||
ft := dfTimestamp
|
||||
else if GetTypeData(rf.FieldType.Handle).FloatType = ftDouble then
|
||||
ft := dfFloat
|
||||
begin
|
||||
ft := dfTimestamp;
|
||||
size := sizeof(TDateTime);
|
||||
end
|
||||
else
|
||||
supported := false;
|
||||
tkString, tkWString, tkLString, tkUString: ft := dfString;
|
||||
begin
|
||||
case GetTypeData(rf.FieldType.Handle).FloatType of
|
||||
ftSingle:
|
||||
begin
|
||||
ft := dfFloat;
|
||||
size := sizeof(Single);
|
||||
end;
|
||||
ftDouble:
|
||||
begin
|
||||
ft := dfFloat;
|
||||
size := sizeof(Double);
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
tkString, tkWString, tkLString, tkUString:
|
||||
begin
|
||||
ft := dfString;
|
||||
size := sizeof(String);
|
||||
end;
|
||||
tkMRecord:
|
||||
begin
|
||||
if rf.FieldType.HasName(GetTypeName(TypeInfo(TDataRecord))) then
|
||||
ft := dfRecord
|
||||
else
|
||||
supported := false;
|
||||
begin
|
||||
ft := dfRecord;
|
||||
size := sizeof(TDataRecord);
|
||||
end
|
||||
end;
|
||||
else
|
||||
supported := false;
|
||||
end;
|
||||
|
||||
if not supported then
|
||||
if size = 0 then
|
||||
raise Exception.Create('Type ' + rf.FieldType.Name + ' not supported in data records');
|
||||
|
||||
fields[i] := TField.Create(rf.Name, ft, rf.FieldType.Handle, rf.Offset);
|
||||
fields[i] := TField.Create(rf.Name, ft, rf.FieldType.Handle, rf.Offset, size);
|
||||
end;
|
||||
|
||||
Result.Create(fields);
|
||||
|
||||
@@ -13,6 +13,8 @@ uses
|
||||
Myc.Trade.Indicators;
|
||||
|
||||
type
|
||||
// --- Indicator Templates ---
|
||||
|
||||
[IndicatorName('SMA', 'Simple Moving Average')]
|
||||
[IndicatorHint('Calculates the average of a selected range of prices.')]
|
||||
TSMA = class
|
||||
@@ -270,68 +272,6 @@ type
|
||||
|
||||
implementation
|
||||
|
||||
type
|
||||
// A minimal, self-contained Deque (Double-Ended Queue) using a circular array.
|
||||
TLightDeque = record
|
||||
private
|
||||
FItems: TArray<Integer>;
|
||||
FHead, FCount, FCapacity: Integer;
|
||||
function GetLastIndex: Integer;
|
||||
function GetFirstIndex: Integer;
|
||||
public
|
||||
constructor Create(ACapacity: Integer);
|
||||
procedure AddLast(AValue: Integer);
|
||||
procedure RemoveLast;
|
||||
procedure RemoveFirst;
|
||||
property Count: Integer read FCount;
|
||||
property Last: Integer read GetLastIndex;
|
||||
property First: Integer read GetFirstIndex;
|
||||
end;
|
||||
|
||||
constructor TLightDeque.Create(ACapacity: Integer);
|
||||
begin
|
||||
FCapacity := ACapacity;
|
||||
SetLength(FItems, FCapacity);
|
||||
FHead := 0;
|
||||
FCount := 0;
|
||||
end;
|
||||
|
||||
procedure TLightDeque.AddLast(AValue: Integer);
|
||||
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.GetLastIndex: Integer;
|
||||
begin
|
||||
var tail := (FHead + FCount - 1 + FCapacity) mod FCapacity;
|
||||
Result := FItems[tail];
|
||||
end;
|
||||
|
||||
function TLightDeque.GetFirstIndex: Integer;
|
||||
begin
|
||||
Result := FItems[FHead];
|
||||
end;
|
||||
|
||||
{ TSMA }
|
||||
|
||||
class function TSMA.CreateFactory: TIndicatorFactoryProc<TSMA.TParams, TSMA.TArgs, TSMA.TResult>;
|
||||
@@ -669,8 +609,6 @@ begin
|
||||
Result := CreateMACD(TEMA.CreateEMA(FastPeriod), TEMA.CreateEMA(SlowPeriod), TEMA.CreateEMA(SignalPeriod));
|
||||
end;
|
||||
|
||||
{ TMACD }
|
||||
|
||||
// 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
|
||||
@@ -702,6 +640,69 @@ 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 function TStochastic.CreateFactory: TIndicatorFactoryProc<TStochastic.TParams, TStochastic.TArgs, TStochastic.TResult>;
|
||||
begin
|
||||
Result :=
|
||||
@@ -728,8 +729,7 @@ var
|
||||
buffer: TArray<TOhlcItem>;
|
||||
highDeque: TLightDeque;
|
||||
lowDeque: TLightDeque;
|
||||
currentIndex: Integer;
|
||||
valueCount: Integer;
|
||||
valueCount: Int64;
|
||||
begin
|
||||
if (KPeriod <= 0) then
|
||||
begin
|
||||
@@ -745,46 +745,58 @@ begin
|
||||
SetLength(buffer, KPeriod);
|
||||
highDeque := TLightDeque.Create(KPeriod);
|
||||
lowDeque := TLightDeque.Create(KPeriod);
|
||||
currentIndex := 0;
|
||||
valueCount := 0;
|
||||
|
||||
Result :=
|
||||
function(const Value: TOhlcItem): TStochastic.TResult
|
||||
var
|
||||
currentIndex, firstIndex, lastIndex: Integer;
|
||||
highestHigh, lowestLow: Double;
|
||||
windowStart: Integer;
|
||||
begin
|
||||
inc(valueCount);
|
||||
currentIndex := (valueCount - 1) mod KPeriod;
|
||||
buffer[currentIndex] := Value;
|
||||
|
||||
// Update deques for rolling min/max
|
||||
while (highDeque.Count > 0) and (buffer[highDeque.Last].High <= Value.High) do
|
||||
highDeque.RemoveLast;
|
||||
highDeque.AddLast(currentIndex);
|
||||
// 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) and (buffer[lowDeque.Last].Low >= Value.Low) do
|
||||
lowDeque.RemoveLast;
|
||||
lowDeque.AddLast(currentIndex);
|
||||
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
|
||||
windowStart := (currentIndex - KPeriod + 1 + KPeriod) mod KPeriod;
|
||||
if (valueCount > KPeriod) then
|
||||
begin
|
||||
if (highDeque.First = windowStart - 1) or ((windowStart = 0) and (highDeque.First = KPeriod - 1)) then
|
||||
highDeque.RemoveFirst;
|
||||
if (lowDeque.First = windowStart - 1) or ((windowStart = 0) and (lowDeque.First = KPeriod - 1)) then
|
||||
lowDeque.RemoveFirst;
|
||||
end;
|
||||
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);
|
||||
Result.D := SmaD(Result.K); // Feed NaN to keep SMA in sync
|
||||
end
|
||||
else
|
||||
begin
|
||||
highestHigh := buffer[highDeque.First].High;
|
||||
lowestLow := buffer[lowDeque.First].Low;
|
||||
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)
|
||||
@@ -793,8 +805,6 @@ begin
|
||||
|
||||
Result.D := SmaD(Result.K);
|
||||
end;
|
||||
|
||||
currentIndex := (currentIndex + 1) mod KPeriod;
|
||||
end;
|
||||
end;
|
||||
|
||||
@@ -1042,4 +1052,18 @@ begin
|
||||
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.
|
||||
|
||||
@@ -2,12 +2,12 @@ unit Myc.Trade.Indicators;
|
||||
|
||||
interface
|
||||
|
||||
{$M+}
|
||||
|
||||
uses
|
||||
Myc.Data.Pipeline,
|
||||
Myc.Data.Records;
|
||||
|
||||
{$M+}
|
||||
|
||||
type
|
||||
TIndicatorFactoryProc<TParams, TValue, TResult> = reference to function(const Params: TParams): TConvertFunc<TValue, TResult>;
|
||||
|
||||
@@ -145,7 +145,7 @@ type
|
||||
end;
|
||||
|
||||
var
|
||||
Registry: TIndicatorRegistry;
|
||||
IndicatorRegistry: TIndicatorRegistry;
|
||||
|
||||
implementation
|
||||
|
||||
@@ -459,9 +459,9 @@ begin
|
||||
end;
|
||||
|
||||
initialization
|
||||
Registry := TIndicatorRegistry.Create;
|
||||
IndicatorRegistry := TIndicatorRegistry.Create;
|
||||
|
||||
finalization
|
||||
Registry.Free;
|
||||
IndicatorRegistry.Free;
|
||||
|
||||
end.
|
||||
|
||||
Reference in New Issue
Block a user