TestStrategy refactored
This commit is contained in:
+105
-103
@@ -20,12 +20,13 @@ uses
|
|||||||
|
|
||||||
function CreateStrategy1(Timeframe: TTimeframe): TConverter<TDataPoint<TOhlcItem>, Double>;
|
function CreateStrategy1(Timeframe: TTimeframe): TConverter<TDataPoint<TOhlcItem>, Double>;
|
||||||
type
|
type
|
||||||
TSignal = record
|
// A record to transfer a detected signal event and the required price data to the next stage.
|
||||||
Sig: Double;
|
TSignalEvent = record
|
||||||
SL: Double;
|
Signal: Integer; // -1 for short, 1 for long, 0 for no new signal
|
||||||
Entry: Double;
|
Close, Low, High, ATR: Double;
|
||||||
pnl: Double;
|
InitialSL: Double; // The calculated SL (Highest/Lowest) at the time of the signal
|
||||||
end;
|
end;
|
||||||
|
|
||||||
begin
|
begin
|
||||||
var ticker := TConverter.CreateIdentity<TDataPoint<TOhlcItem>>;
|
var ticker := TConverter.CreateIdentity<TDataPoint<TOhlcItem>>;
|
||||||
|
|
||||||
@@ -37,133 +38,134 @@ begin
|
|||||||
|
|
||||||
var Hull := Closes.Chain<Double>(TIndicators.CreateHMA(250)).MakeParallel;
|
var Hull := Closes.Chain<Double>(TIndicators.CreateHMA(250)).MakeParallel;
|
||||||
var Sma := Closes.Chain<Double>(TIndicators.CreateSMA(200)).MakeParallel;
|
var Sma := Closes.Chain<Double>(TIndicators.CreateSMA(200)).MakeParallel;
|
||||||
|
|
||||||
var Lowest: Double := Double.MaxValue;
|
|
||||||
var Highest: Double := Double.MinValue;
|
|
||||||
|
|
||||||
var ATR := Ohlc.Chain<Double>(TIndicators.CreateATR(50)).MakeParallel;
|
var ATR := Ohlc.Chain<Double>(TIndicators.CreateATR(50)).MakeParallel;
|
||||||
|
|
||||||
// next stage
|
|
||||||
|
|
||||||
var ATREndPoint := TConverter.CreateEndpoint<Double>(ATR.Sender, 5);
|
|
||||||
var ATRSeries: TSeries<Double>;
|
|
||||||
|
|
||||||
var HullEndPoint := TConverter.CreateEndpoint<Double>(Hull.Sender, 5);
|
|
||||||
var HullSeries: TSeries<Double>;
|
|
||||||
|
|
||||||
var SmaEndPoint := TConverter.CreateEndpoint<Double>(Sma.Sender, 5);
|
|
||||||
var SmaSeries: TSeries<Double>;
|
|
||||||
|
|
||||||
var curr: TSignal;
|
|
||||||
curr.SL := Double.NaN;
|
|
||||||
curr.Entry := Double.NaN;
|
|
||||||
|
|
||||||
var lastHull, lastSma: Double;
|
|
||||||
|
|
||||||
var conv :=
|
var conv :=
|
||||||
TConverter.Join<Double>(
|
TConverter.Join<Double>(
|
||||||
[Ohlc.Field<Double>('Low').Sender, Ohlc.Field<Double>('High').Sender, Closes.Sender, ATR.Sender, Hull.Sender, Sma.Sender]
|
[Ohlc.Field<Double>('Low').Sender, Ohlc.Field<Double>('High').Sender, Closes.Sender, ATR.Sender, Hull.Sender, Sma.Sender]
|
||||||
);
|
);
|
||||||
|
|
||||||
var Signal :=
|
// STAGE 1: Signal Generation. This converter is stateless regarding the trade itself.
|
||||||
TConverter<TArray<Double>, TSignal>.CreateGeneric(
|
// It only detects the crossover event and prepares the data for the next stage.
|
||||||
function(const Values: TArray<Double>): TSignal
|
var Lowest: Double := Double.MaxValue;
|
||||||
|
var Highest: Double := Double.MinValue;
|
||||||
|
var lastHull, lastSma: Double;
|
||||||
|
|
||||||
|
var signalGenerator :=
|
||||||
|
TConverter<TArray<Double>, TSignalEvent>.CreateGeneric(
|
||||||
|
function(const Values: TArray<Double>): TSignalEvent
|
||||||
begin
|
begin
|
||||||
var low := Values[0];
|
Result.Low := Values[0];
|
||||||
var high := Values[1];
|
Result.High := Values[1];
|
||||||
var close := Values[2];
|
Result.Close := Values[2];
|
||||||
var atr := Values[3];
|
Result.ATR := Values[3];
|
||||||
var hull := Values[4];
|
var hull := Values[4];
|
||||||
var sma := Values[5];
|
var sma := Values[5];
|
||||||
|
|
||||||
if low < Lowest then
|
if Result.Low < Lowest then
|
||||||
Lowest := low;
|
Lowest := Result.Low;
|
||||||
if high > Highest then
|
if Result.High > Highest then
|
||||||
Highest := high;
|
Highest := Result.High;
|
||||||
|
|
||||||
Result := curr;
|
Result.Signal := 0;
|
||||||
Result.Sig := 0;
|
Result.InitialSL := Double.NaN;
|
||||||
var pnl: double := NaN;
|
|
||||||
|
|
||||||
if (hull < sma) and (lastHull >= lastSma) then
|
if (hull < sma) and (lastHull >= lastSma) then
|
||||||
begin
|
begin
|
||||||
if curr.Sig > 0 then
|
Result.Signal := -1;
|
||||||
pnl := close - curr.Entry;
|
Result.InitialSL := Highest;
|
||||||
|
Highest := Double.MinValue; // Reset for next trend
|
||||||
curr.Sig := -1;
|
Lowest := Double.MaxValue;
|
||||||
curr.SL := Highest;
|
|
||||||
curr.Entry := close;
|
|
||||||
Result := curr;
|
|
||||||
end
|
end
|
||||||
else if (hull > sma) and (lastHull <= lastSma) then
|
else if (hull > sma) and (lastHull <= lastSma) then
|
||||||
begin
|
begin
|
||||||
if curr.Sig < 0 then
|
Result.Signal := 1;
|
||||||
pnl := curr.Entry - close;
|
Result.InitialSL := Lowest;
|
||||||
|
Highest := Double.MinValue; // Reset for next trend
|
||||||
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;
|
Lowest := Double.MaxValue;
|
||||||
Highest := Double.MinValue;
|
|
||||||
Result.SL := Double.NaN;
|
|
||||||
Result.Entry := Double.NaN;
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
Result.pnl := pnl;
|
|
||||||
|
|
||||||
lastHull := hull;
|
lastHull := hull;
|
||||||
lastSma := sma;
|
lastSma := sma;
|
||||||
end
|
end
|
||||||
);
|
);
|
||||||
|
|
||||||
conv.Link(Signal);
|
// STAGE 2: Position Management. This stateful converter manages the lifecycle
|
||||||
|
// of a single trade (entry, trailing stop, exit) and outputs the PnL.
|
||||||
|
// State variables for the position manager
|
||||||
|
var currSig: Integer := 0;
|
||||||
|
var currSL := Double.NaN;
|
||||||
|
var currEntry := Double.NaN;
|
||||||
|
|
||||||
var pnl := Signal.Field<Double>('pnl');
|
var positionManager :=
|
||||||
|
TConverter<TSignalEvent, Double>.CreateAggregation(
|
||||||
|
function(const Value: TSignalEvent; const Broadcast: TConverter<TSignalEvent, Double>.TBroadcastProc): TState
|
||||||
|
var
|
||||||
|
pnl: Double;
|
||||||
|
begin
|
||||||
|
Result := TState.Null;
|
||||||
|
pnl := Double.NaN;
|
||||||
|
|
||||||
|
// 1. Check for a new signal to open or reverse a position
|
||||||
|
if Value.Signal <> 0 then
|
||||||
|
begin
|
||||||
|
// If a position is already open, close it first
|
||||||
|
if currSig > 0 then
|
||||||
|
pnl := Value.Close - currEntry
|
||||||
|
else if currSig < 0 then
|
||||||
|
pnl := currEntry - Value.Close;
|
||||||
|
|
||||||
|
// Open new position
|
||||||
|
currSig := Value.Signal;
|
||||||
|
currEntry := Value.Close;
|
||||||
|
currSL := Value.InitialSL;
|
||||||
|
end
|
||||||
|
// 2. If no new signal, manage the currently open position
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
var atrValue := 15 * Value.ATR;
|
||||||
|
if currSig > 0 then // Manage long position
|
||||||
|
begin
|
||||||
|
if Value.Close > currSL then
|
||||||
|
if currSL < Value.Close - atrValue then
|
||||||
|
currSL := Value.Close - atrValue;
|
||||||
|
|
||||||
|
if Value.Low <= currSL then
|
||||||
|
begin
|
||||||
|
pnl := currSL - currEntry;
|
||||||
|
currSig := 0; // Close position
|
||||||
|
end;
|
||||||
|
end
|
||||||
|
else if currSig < 0 then // Manage short position
|
||||||
|
begin
|
||||||
|
if Value.Close < currSL then
|
||||||
|
if currSL > Value.Close + atrValue then
|
||||||
|
currSL := Value.Close + atrValue;
|
||||||
|
|
||||||
|
if Value.High >= currSL then
|
||||||
|
begin
|
||||||
|
pnl := currEntry - currSL;
|
||||||
|
currSig := 0; // Close position
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
// 3. If a PnL was generated (trade closed), broadcast it
|
||||||
|
if not IsNan(pnl) then
|
||||||
|
begin
|
||||||
|
currSL := Double.NaN;
|
||||||
|
Broadcast(pnl);
|
||||||
|
end;
|
||||||
|
end
|
||||||
|
);
|
||||||
|
|
||||||
|
// Chain the stages together
|
||||||
|
conv.Link(signalGenerator);
|
||||||
|
signalGenerator.Sender.Link(positionManager);
|
||||||
|
|
||||||
|
// The final equity calculation remains the same, it just consumes the PnL from the position manager
|
||||||
var FEquity: Double := 10000;
|
var FEquity: Double := 10000;
|
||||||
var FInit: Boolean := false;
|
var FInit: Boolean := false;
|
||||||
|
|
||||||
var equity :=
|
var equity :=
|
||||||
TConverter<Double, Double>.CreateAggregation(
|
TConverter<Double, Double>.CreateAggregation(
|
||||||
function(const Value: Double; const Broadcast: TConverter<Double, Double>.TBroadcastProc): TState
|
function(const Value: Double; const Broadcast: TConverter<Double, Double>.TBroadcastProc): TState
|
||||||
@@ -182,7 +184,7 @@ begin
|
|||||||
end
|
end
|
||||||
);
|
);
|
||||||
|
|
||||||
pnl.Sender.Link(equity);
|
positionManager.Sender.Link(equity);
|
||||||
|
|
||||||
Result := TConverter<TDataPoint<TOhlcItem>, Double>.Construct(ticker, equity.Sender);
|
Result := TConverter<TDataPoint<TOhlcItem>, Double>.Construct(ticker, equity.Sender);
|
||||||
end;
|
end;
|
||||||
|
|||||||
Reference in New Issue
Block a user