diff --git a/AuraTrader/MainForm.pas b/AuraTrader/MainForm.pas index 19874ed..52a422b 100644 --- a/AuraTrader/MainForm.pas +++ b/AuraTrader/MainForm.pas @@ -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); diff --git a/Src/Myc.Data.Pipeline.Impl.pas b/Src/Myc.Data.Pipeline.Impl.pas index 71cc27c..51b96d8 100644 --- a/Src/Myc.Data.Pipeline.Impl.pas +++ b/Src/Myc.Data.Pipeline.Impl.pas @@ -242,6 +242,7 @@ type implementation uses + Winapi.Windows, System.RTTI, Myc.TaskManager; diff --git a/Src/Myc.Data.Pipeline.pas b/Src/Myc.Data.Pipeline.pas index 8e113d4..7734c8d 100644 --- a/Src/Myc.Data.Pipeline.pas +++ b/Src/Myc.Data.Pipeline.pas @@ -2,6 +2,8 @@ unit Myc.Data.Pipeline; interface +{$M+} + uses Myc.Signals, Myc.Mutable, diff --git a/Src/Myc.Data.Records.pas b/Src/Myc.Data.Records.pas index a3e28e0..07cbce7 100644 --- a/Src/Myc.Data.Records.pas +++ b/Src/Myc.Data.Records.pas @@ -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): TLayout; +const + DataSize: array[TDataRecord.TFieldType] of Integer = + (sizeof(Double), sizeof(Int64), sizeof(String), sizeof(TDateTime), sizeof(TDataRecord)); var Fields: TArray; 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); diff --git a/Src/Myc.Trade.Indicators.Common.pas b/Src/Myc.Trade.Indicators.Common.pas index 1e373f2..d257bf7 100644 --- a/Src/Myc.Trade.Indicators.Common.pas +++ b/Src/Myc.Trade.Indicators.Common.pas @@ -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; - 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; @@ -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): TConvertFunc; 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; // 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; begin Result := @@ -728,8 +729,7 @@ var buffer: TArray; 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; + IndicatorRegistry.RegisterTemplate; + IndicatorRegistry.RegisterTemplate; + IndicatorRegistry.RegisterTemplate; + IndicatorRegistry.RegisterTemplate; + IndicatorRegistry.RegisterTemplate; + IndicatorRegistry.RegisterTemplate; + IndicatorRegistry.RegisterTemplate; + IndicatorRegistry.RegisterTemplate; + IndicatorRegistry.RegisterTemplate; + IndicatorRegistry.RegisterTemplate; + IndicatorRegistry.RegisterTemplate; + end. diff --git a/Src/Myc.Trade.Indicators.pas b/Src/Myc.Trade.Indicators.pas index 0d50d87..6e5f7d4 100644 --- a/Src/Myc.Trade.Indicators.pas +++ b/Src/Myc.Trade.Indicators.pas @@ -2,12 +2,12 @@ unit Myc.Trade.Indicators; interface +{$M+} + uses Myc.Data.Pipeline, Myc.Data.Records; -{$M+} - type TIndicatorFactoryProc = reference to function(const Params: TParams): TConvertFunc; @@ -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.