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;