diff --git a/AuraTrader/AuraTrader.dproj b/AuraTrader/AuraTrader.dproj index 0ded5a4..18a82b0 100644 --- a/AuraTrader/AuraTrader.dproj +++ b/AuraTrader/AuraTrader.dproj @@ -4,7 +4,7 @@ 20.3 FMX True - Debug + Release Win64 AuraTrader 3 diff --git a/AuraTrader/MainForm.pas b/AuraTrader/MainForm.pas index 653c0f2..c236e08 100644 --- a/AuraTrader/MainForm.pas +++ b/AuraTrader/MainForm.pas @@ -52,7 +52,8 @@ uses DynamicFMXControl, Myc.FMX.Chart, Myc.Trade.Indicators, - Myc.DataRecord; + Myc.DataRecord, + StrategyTest; type TForm1 = class(TForm) @@ -117,16 +118,6 @@ type property OnEvent: TNotifyEvent read FOnEvent write FOnEvent; end; - TEquitySum = class(TMycConverter) - private - FEquity: Double; - FInit: Boolean; - protected - function ProcessData(const Value: Double): TState; override; - public - constructor Create(AEquity: Double); - end; - var Form1: TForm1; @@ -426,7 +417,28 @@ begin var pnl := Signal.Field('pnl'); - var equity: TConverter := TEquitySum.Create(10000); + var FEquity: Double; + var FInit: Boolean; + + var equity := + TConverter.CreateAggregation( + function(const Value: Double; const Broadcast: TConverter.TBroadcastProc): TState + begin + if not FInit then + begin + FInit := true; + Broadcast(FEquity); + end; + + if not IsNan(Value) then + begin + FEquity := FEquity + Value; + Result := Broadcast(FEquity); + end; + end + ); + + // var equity: TConverter := TEquitySum.Create(10000); pnl.Sender.Link(equity); var Layout := CurrLayout; @@ -635,7 +647,27 @@ begin var pnl := Signal.Field('pnl'); - var equity: TConverter := TEquitySum.Create(10000); + var FEquity: Double := 10000; + var FInit: Boolean := false; + + var equity := + TConverter.CreateAggregation( + function(const Value: Double; const Broadcast: TConverter.TBroadcastProc): TState + begin + if not FInit then + begin + FInit := true; + Broadcast(FEquity); + end; + + if not IsNan(Value) then + begin + FEquity := FEquity + Value; + Result := Broadcast(FEquity); + end; + end + ); + pnl.Sender.Link(equity); var Layout := CurrLayout; @@ -867,38 +899,22 @@ begin var timeframe := TTimeframe.M15; ExecuteStrategy(Symbol, timeframe, CreateStrategy2(timeframe)); - // timeframe := M5; - // ExecuteStrategy(Symbol, timeframe, CreateStrategy2(timeframe)); - // timeframe := H4; - // ExecuteStrategy(Symbol, timeframe, CreateStrategy2(timeframe)); - // timeframe := D; - // ExecuteStrategy(Symbol, timeframe, CreateStrategy2(timeframe)); - // timeframe := M; - // ExecuteStrategy(Symbol, timeframe, CreateStrategy2(timeframe)); -end; + var tstStrat := StrategyTest.CreateStrategy1(timeframe); + ExecuteStrategy(Symbol, timeframe, tstStrat); -{ TEquitySum } + var Layout := CurrLayout; + if Layout = nil then + exit; -constructor TEquitySum.Create(AEquity: Double); -begin - inherited Create; - FEquity := AEquity; - FInit := false; -end; + var pnlChart := TMycChart.Create(Self); + AlignControl(pnlChart); + pnlChart.Height := Layout.ChildrenRect.Width * 9 / 24; + pnlChart.Lookback.Value := 50000; -function TEquitySum.ProcessData(const Value: Double): TState; -begin - if not FInit then - begin - FInit := true; - Broadcast(FEquity); - end; + pnlChart.SetXAxisCounter(tstStrat.Sender); - if not IsNan(Value) then - begin - FEquity := FEquity + Value; - Result := Broadcast(FEquity); - end; + var panel := pnlChart.AddPanel; + panel.AddDoubleSeries(tstStrat.Sender, TAlphaColors.Blue, 3); end; end. diff --git a/AuraTrader/StrategyTest.pas b/AuraTrader/StrategyTest.pas new file mode 100644 index 0000000..2cf8e6f --- /dev/null +++ b/AuraTrader/StrategyTest.pas @@ -0,0 +1,190 @@ +unit StrategyTest; + +interface + +uses + Myc.Signals, + Myc.Trade.Types, + Myc.Trade.DataPoint, + Myc.Trade.DataArray, + Myc.DataRecord, + Myc.Trade.Indicators; + +function CreateStrategy1(Timeframe: TTimeframe): TConverter, Double>; + +implementation + +uses + System.SysUtils, + System.Math; + +function CreateStrategy1(Timeframe: TTimeframe): TConverter, Double>; +type + TSignal = record + Sig: Double; + SL: Double; + Entry: Double; + pnl: Double; + end; +begin + var ticker := TConverter.CreateIdentity>; + + var OhlcPoint := ticker.Chain>(TConverter.CreateOhlcAggregation(Timeframe)); + + var Ohlc := OhlcPoint.Field('Data'); + + var Closes := Ohlc.Field('Close'); + + var Hull := Closes.Chain(TIndicators.CreateHMA(250)).MakeParallel; + var Sma := Closes.Chain(TIndicators.CreateSMA(200)).MakeParallel; + + var Lowest: Double := Double.MaxValue; + var Highest: Double := Double.MinValue; + + var ATR := Ohlc.Chain(TIndicators.CreateATR(50)).MakeParallel; + + // next stage + + var ATREndPoint := TConverter.CreateEndpoint(ATR.Sender, 5); + var ATRSeries: TSeries; + + var HullEndPoint := TConverter.CreateEndpoint(Hull.Sender, 5); + var HullSeries: TSeries; + + var SmaEndPoint := TConverter.CreateEndpoint(Sma.Sender, 5); + var SmaSeries: TSeries; + + var curr: TSignal; + curr.SL := Double.NaN; + curr.Entry := Double.NaN; + + var lastHull, lastSma: Double; + + var conv := + TConverter.Join( + [Ohlc.Field('Low').Sender, Ohlc.Field('High').Sender, Closes.Sender, ATR.Sender, Hull.Sender, Sma.Sender] + ); + + var Signal := + TConverter, TSignal>.CreateGeneric( + function(const Values: TArray): TSignal + begin + var low := Values[0]; + var high := Values[1]; + var close := Values[2]; + var atr := Values[3]; + var hull := Values[4]; + var sma := Values[5]; + + if low < Lowest then + Lowest := low; + if high > Highest then + Highest := high; + + Result := curr; + Result.Sig := 0; + var pnl: double := NaN; + + if (hull < sma) and (lastHull >= lastSma) then + begin + if curr.Sig > 0 then + pnl := close - curr.Entry; + + curr.Sig := -1; + curr.SL := Highest; + curr.Entry := close; + Result := curr; + end + else if (hull > sma) and (lastHull <= lastSma) then + begin + if curr.Sig < 0 then + pnl := curr.Entry - close; + + curr.Sig := 1; + curr.SL := Lowest; + curr.Entry := close; + Result := curr; + end; + + atr := 15 * atr; + if curr.Sig > 0 then + begin + if close > curr.SL then + begin + if curr.SL < close - atr then + curr.SL := close - atr; + Result.SL := curr.SL; + end; + + if low <= curr.SL then + begin + pnl := curr.SL - curr.Entry; + curr.Sig := 0; + Result.Sig := 0; + curr.SL := NaN; + end; + end + else if curr.Sig < 0 then + begin + if close < curr.SL then + begin + if curr.SL > close + atr then + curr.SL := close + atr; + Result.SL := curr.SL; + end; + + if high >= curr.SL then + begin + pnl := curr.Entry - curr.SL; + curr.Sig := 0; + Result.Sig := 0; + curr.SL := NaN; + end; + end; + + if Result.Sig <> 0 then + begin + Lowest := Double.MaxValue; + Highest := Double.MinValue; + Result.SL := Double.NaN; + Result.Entry := Double.NaN; + end; + + Result.pnl := pnl; + + lastHull := hull; + lastSma := sma; + end + ); + + conv.Link(Signal); + + var pnl := Signal.Field('pnl'); + + var FEquity: Double := 10000; + var FInit: Boolean := false; + + var equity := + TConverter.CreateAggregation( + function(const Value: Double; const Broadcast: TConverter.TBroadcastProc): TState + begin + if not FInit then + begin + FInit := true; + Broadcast(FEquity); + end; + + if not IsNan(Value) then + begin + FEquity := FEquity + Value; + Result := Broadcast(FEquity); + end; + end + ); + + pnl.Sender.Link(equity); + + Result := TConverter, Double>.Construct(ticker, equity.Sender); +end; + +end. diff --git a/Src/Myc.Trade.DataPoint.Impl.pas b/Src/Myc.Trade.DataPoint.Impl.pas index 9ddcac8..b675a73 100644 --- a/Src/Myc.Trade.DataPoint.Impl.pas +++ b/Src/Myc.Trade.DataPoint.Impl.pas @@ -89,6 +89,15 @@ type constructor Create(const AFunc: TConstFunc); end; + TMycGenericAggregator = class(TMycConverter) + private + FFunc: TConstFunc.TBroadcastProc, TState>; + protected + function ProcessData(const Value: S): TState; override; + public + constructor Create(const AFunc: TConstFunc.TBroadcastProc, TState>); + end; + TMycGenericParallelConverter = class(TMycConverter) private FFunc: TConstFunc; @@ -235,6 +244,17 @@ type property Sender: TMycContainedDataProvider> read FSender implements TDataProvider>.IDataProvider; end; + TMycComposedConverter = class(TMycProcessor, TConverter.IConverter) + private + FProcessor: IMycProcessor; + FDataProvider: TDataProvider; + protected + function ProcessData(const Value: S): TState; override; + function GetSender: TDataProvider.IDataProvider; + public + constructor Create(const AProcessor: IMycProcessor; const ADataProvider: TDataProvider); + end; + implementation uses @@ -892,4 +912,32 @@ begin FSender.Broadcast(Arr); end; +constructor TMycGenericAggregator.Create(const AFunc: TConstFunc.TBroadcastProc, TState>); +begin + inherited Create; + FFunc := AFunc; +end; + +function TMycGenericAggregator.ProcessData(const Value: S): TState; +begin + Result := FFunc(Value, function(const Value: T): TState begin Result := FSender.Broadcast(Value); end); +end; + +constructor TMycComposedConverter.Create(const AProcessor: IMycProcessor; const ADataProvider: TDataProvider); +begin + inherited Create; + FProcessor := AProcessor; + FDataProvider := ADataProvider; +end; + +function TMycComposedConverter.GetSender: TDataProvider.IDataProvider; +begin + Result := FDataProvider; +end; + +function TMycComposedConverter.ProcessData(const Value: S): TState; +begin + Result := FProcessor.ProcessData(Value); +end; + end. diff --git a/Src/Myc.Trade.DataPoint.pas b/Src/Myc.Trade.DataPoint.pas index 0ca7d3c..93bc30e 100644 --- a/Src/Myc.Trade.DataPoint.pas +++ b/Src/Myc.Trade.DataPoint.pas @@ -67,6 +67,8 @@ type property Sender: TDataProvider.IDataProvider read GetSender; end; + TBroadcastProc = reference to function(const Value: T): TState; + strict private class var FNull: IConverter; @@ -82,7 +84,9 @@ type class operator Implicit(const A: IConverter): TConverter; overload; class operator Implicit(const A: TConverter): IConverter; overload; + class function Construct(const Processor: IMycProcessor; const DataProvider: TDataProvider): TConverter; static; class function CreateGeneric(const Func: TConstFunc): TConverter; static; + class function CreateAggregation(const Func: TConstFunc): TConverter; static; class function CreateParallel(const Func: TConstFunc): TConverter; static; function Chain(const Next: TConverter): TConverter; overload; inline; @@ -221,6 +225,16 @@ begin Result := Chain(TMycGenericParallelConverter.Create(Func)); end; +class function TConverter.Construct(const Processor: IMycProcessor; const DataProvider: TDataProvider): TConverter; +begin + Result := TMycComposedConverter.Create(Processor, DataProvider); +end; + +class function TConverter.CreateAggregation(const Func: TConstFunc): TConverter; +begin + Result := TMycGenericAggregator.Create(Func); +end; + class function TConverter.CreateGeneric(const Func: TConstFunc): TConverter; begin Result := TMycGenericConverter.Create(Func); diff --git a/Src/Myc.Trade.Types.pas b/Src/Myc.Trade.Types.pas index d7012b0..eb0282e 100644 --- a/Src/Myc.Trade.Types.pas +++ b/Src/Myc.Trade.Types.pas @@ -29,6 +29,7 @@ type end; TConstFunc = reference to function(const Value: S): T; + TConstFunc = reference to function(const Value1: S; const Value2: T): U; TConstProc = reference to procedure(const Value: T); TConstFuncPredicate = reference to function(const Value: S; out Res: T): Boolean;