Work in Progress

This commit is contained in:
Michael Schimmel
2025-07-15 20:29:19 +02:00
parent 8ebcd81561
commit bc75f08477
13 changed files with 597 additions and 320 deletions
+165 -143
View File
@@ -11,6 +11,7 @@ uses
System.DateUtils,
System.Generics.Collections,
System.Rtti,
System.Math,
FMX.Types,
FMX.Controls,
FMX.Forms,
@@ -32,9 +33,11 @@ uses
Myc.Trade.DataPoint,
Myc.Signals,
Myc.Mutable,
Myc.Trade.DataArray,
Myc.Signals.FMX,
Myc.TaskManager,
Myc.Aura.Module,
Myc.Trade.DataPoint.Impl,
FMX.ListBox,
FMX.Layouts,
FMX.TreeView,
@@ -45,7 +48,6 @@ uses
System.Actions,
FMX.ActnList,
DynamicFMXControl,
FirstStrategy,
Myc.FMX.Chart;
type
@@ -103,6 +105,15 @@ type
property OnEvent: TNotifyEvent read FOnEvent write FOnEvent;
end;
TEquitySum = class(TMycConverter<Double, Double>)
private
FEquity: Double;
protected
function ProcessData(const Value: Double): TState; override;
public
constructor Create(AEquity: Double);
end;
var
Form1: TForm1;
@@ -343,7 +354,7 @@ begin
end
);
var OhlcPoint := lastPrice.Chain<TDataPoint<TOhlcItem>>(TTickAggregation.Create(timeframe));
var OhlcPoint := lastPrice.Chain<TDataPoint<TOhlcItem>>(TConverter.CreateAggregation(timeframe));
var Timestamps := OhlcPoint.Field<TDateTime>('Time');
var Ohlc := OhlcPoint.Field<TOhlcItem>('Data');
@@ -410,6 +421,13 @@ begin
end;
procedure TForm1.Strat2ButtonClick(Sender: TObject);
type
TSignal = record
Sig: Double;
SL: Double;
Entry: Double;
pnl: Double;
end;
begin
var timeframe := TTimeframe.M15;
@@ -417,26 +435,123 @@ begin
var lastPrice :=
ticker.Chain<TDataPoint<Double>>(
function(const Tick: TDataPoint<TAskBidItem>): TDataPoint<Double>
begin
Result.Time := Tick.Time;
Result.Data := 0.5 * (Tick.Data.Ask + Tick.Data.Bid);
end
TConverter.CreateDataPointConverter<TAskBidItem, Double>(
function(const Tick: TAskBidItem): Double begin Result := 0.5 * (Tick.Ask + Tick.Bid); end
)
);
var OhlcPoint := TTickAggregation.Create(timeframe);
lastPrice.Sender.Link(OhlcPoint);
var OhlcPoint := lastPrice.Chain<TDataPoint<TOhlcItem>>(TConverter.CreateAggregation(timeframe));
var Closes :=
TConverter<TDataPoint<TOhlcItem>, Double>
.CreateGeneric(function(const Ohlc: TDataPoint<TOhlcItem>): Double begin Result := Ohlc.Data.Close; end);
var Ohlc := TConverter.CreateSequence<TOhlcItem>(2, OhlcPoint.Field<TOhlcItem>('Data').Sender);
var Hull := TConverter<Double, Double>.CreateGeneric(TIndicators.CreateHMA(150));
var Closes := Ohlc[0].Field<Double>('Close');
var Timestamps :=
TConverter<TDataPoint<TOhlcItem>, TDateTime>
.CreateGeneric(function(const Ohlc: TDataPoint<TOhlcItem>): TDateTime begin Result := Ohlc.Time; end);
OhlcPoint.Sender.Link(TimeStamps);
var Hull := Closes.Chain<Double>(TIndicators.CreateHMA(250));
var Sma := Closes.Chain<Double>(TIndicators.CreateSMA(200));
var HullSeries := TConverter.CreateEndpoint<Double>(Hull.Sender, 5);
var SmaSeries := TConverter.CreateEndpoint<Double>(Sma.Sender, 5);
var Lowest: Double := Double.MaxValue;
var Highest: Double := Double.MinValue;
var curr: TSignal;
curr.SL := Double.NaN;
curr.Entry := Double.NaN;
var ATR := Ohlc[0].Chain<Double>(TIndicators.CreateATR(50));
var ATRSeries := TConverter.CreateEndpoint<Double>(ATR.Sender, 5);
// next stage
var Signal :=
Ohlc[1]
.Chain<TSignal>(
function(const Ohlc: TOhlcItem): TSignal
begin
var pnl: Double := 0;
if Ohlc.Low < Lowest then
Lowest := Ohlc.Low;
if Ohlc.High > Highest then
Highest := Ohlc.High;
Result := curr;
Result.Sig := 0;
pnl := NaN;
if (HullSeries.Value[0] < SmaSeries.Value[0]) and (HullSeries.Value[1] >= SmaSeries.Value[1]) then
begin
if curr.Sig > 0 then
pnl := Ohlc.Close - curr.Entry;
curr.Sig := -1;
curr.SL := Highest;
curr.Entry := Ohlc.Close;
Result := curr;
end
else if (HullSeries.Value[0] > SmaSeries.Value[0]) and (HullSeries.Value[1] <= SmaSeries.Value[1]) then
begin
if curr.Sig < 0 then
pnl := curr.Entry - Ohlc.Close;
curr.Sig := 1;
curr.SL := Lowest;
curr.Entry := Ohlc.Close;
Result := curr;
end;
var atr := 15 * ATRSeries.Value[0];
if curr.Sig > 0 then
begin
if Ohlc.Close > curr.SL then
begin
if curr.SL < Ohlc.Close - atr then
curr.SL := Ohlc.Close - atr;
Result.SL := curr.SL;
end;
if Ohlc.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 Ohlc.Close < curr.SL then
begin
if curr.SL > Ohlc.Close + atr then
curr.SL := Ohlc.Close + atr;
Result.SL := curr.SL;
end;
if Ohlc.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;
end);
var pnl := Signal.Field<Double>('pnl');
var equity: TConverter<Double, Double> := TEquitySum.Create(10000);
pnl.Sender.Link(equity);
var Layout := CurrLayout<TVertScrollBox>;
if Layout = nil then
@@ -451,142 +566,49 @@ begin
chart.Height := Layout.ChildrenRect.Width * 9 / 16;
chart.Lookback.Value := 50000;
/////
chart.SetXAxisSeries(M15, OhlcPoint.Field<TDateTime>('Time').Sender);
{
var panel := chart.AddPanel;
panel.AddOhlcSeries(Ohlc[0].Sender);
panel.AddDoubleSeries(Hull.Sender, TAlphaColors.Cornflowerblue, 2);
panel.AddDoubleSeries(Sma.Sender, TAlphaColors.Brown, 1.5);
panel.AddDoubleSeries(Signal.Field<Double>('SL').Sender, TAlphaColors.Red, 2);
panel.AddDoubleSeries(Signal.Field<Double>('Entry').Sender, TAlphaColors.Green, 1);
chart.SetXAxisSeries(timeframe, Timestamps.Sender);
// panel := chart.AddPanel;
// panel.AddDoubleSeries( equity.Sender, TAlphaColors.Blue, 3 );
var Panel := chart.AddPanel;
var pnlChart := TMycChart.Create(Self);
AlignControl(pnlChart);
pnlChart.Height := Layout.ChildrenRect.Width * 9 / 24;
pnlChart.Lookback.Value := 50000;
pnlChart.SetXAxisCounter<Double>(equity.Sender);
OhlcPoint.Sender.Link(Ohlc);
Panel.AddOhlcSeries(Ohlc.Sender);
Ohlc.Sender.Link(Closes);
var Hull: IMycConverter<Double, Double> := TGenericIndicator<Double, Double>.Create(TIndicators.CreateHMA(150));
Closes.Sender.Link(Hull);
Panel.AddDoubleSeries(Hull.Sender, TAlphaColors.Aliceblue);
// Add SMA (Simple Moving Average)
var Sma: IMycConverter<Double, Double> := TGenericIndicator<Double, Double>.Create(TIndicators.CreateSMA(50));
Closes.Sender.Link(Sma);
Panel.AddDoubleSeries(Sma.Sender, TAlphaColors.Yellow);
// Add EMA (Exponential Moving Average)
var Ema: IMycConverter<Double, Double> := TGenericIndicator<Double, Double>.Create(TIndicators.CreateEMA(21));
Closes.Sender.Link(Ema);
Panel.AddDoubleSeries(Ema.Sender, TAlphaColors.Aqua);
// Add Bollinger Bands (20, 2.0)
var Boli: IMycConverter<Double, TBollingerBandsResult> :=
TGenericIndicator<Double, TBollingerBandsResult>.Create(TIndicators.CreateBollingerBands(20, 2.0));
Closes.Sender.Link(Boli);
var BoliUpper: IMycConverter<TBollingerBandsResult, Double> :=
TMycGenericConverter<TBollingerBandsResult, Double>
.Create(function(const Item: TBollingerBandsResult): Double begin Result := Item.UpperBand; end);
Boli.Sender.Link(BoliUpper);
Panel.AddDoubleSeries(BoliUpper.Sender, TAlphaColors.Gray);
var BoliMiddle: IMycConverter<TBollingerBandsResult, Double> :=
TMycGenericConverter<TBollingerBandsResult, Double>
.Create(function(const Item: TBollingerBandsResult): Double begin Result := Item.MiddleBand; end);
Boli.Sender.Link(BoliMiddle);
Panel.AddDoubleSeries(BoliMiddle.Sender, TAlphaColors.Darkgray, 1.0);
var BoliLower: IMycConverter<TBollingerBandsResult, Double> :=
TMycGenericConverter<TBollingerBandsResult, Double>
.Create(function(const Item: TBollingerBandsResult): Double begin Result := Item.LowerBand; end);
Boli.Sender.Link(BoliLower);
Panel.AddDoubleSeries(BoliLower.Sender, TAlphaColors.Gray);
Panel := chart.AddPanel;
// Add RSI (Relative Strength Index)
var Rsi: IMycConverter<Double, Double> := TGenericIndicator<Double, Double>.Create(TIndicators.CreateRSI(14));
Closes.Sender.Link(Rsi);
Panel.AddDoubleSeries(Rsi.Sender, TAlphaColors.Fuchsia);
// Add MACD (12, 26, 9)
var Macd: IMycConverter<Double, TMacdResult> := TGenericIndicator<Double, TMacdResult>.Create(TIndicators.CreateMACD(12, 26, 9));
Closes.Sender.Link(Macd);
Panel := chart.AddPanel;
var MacdLine: IMycConverter<TMacdResult, Double> :=
TMycGenericConverter<TMacdResult, Double>.Create(function(const Item: TMacdResult): Double begin Result := Item.MacdLine; end);
Macd.Sender.Link(MacdLine);
Panel.AddDoubleSeries(MacdLine.Sender, TAlphaColors.Orange);
var MacdSignal: IMycConverter<TMacdResult, Double> :=
TMycGenericConverter<TMacdResult, Double>.Create(function(const Item: TMacdResult): Double begin Result := Item.SignalLine; end);
Macd.Sender.Link(MacdSignal);
Panel.AddDoubleSeries(MacdSignal.Sender, TAlphaColors.Dodgerblue);
var MacdHist: IMycConverter<TMacdResult, Double> :=
TMycGenericConverter<TMacdResult, Double>.Create(function(const Item: TMacdResult): Double begin Result := Item.Histogram; end);
Macd.Sender.Link(MacdHist);
Panel.AddDoubleSeries(MacdHist.Sender, TAlphaColors.Lightgreen, 1.0);
Panel := chart.AddPanel;
// Add Stochastic Oscillator (14, 3) - This needs OHLC data, not just Close prices.
var Stoch: IMycConverter<TOhlcItem, TStochasticResult> :=
TGenericIndicator<TOhlcItem, TStochasticResult>.Create(TIndicators.CreateStochastic(14, 3));
Ohlc.Sender.Link(Stoch);
var StochK: IMycConverter<TStochasticResult, Double> :=
TMycGenericConverter<TStochasticResult, Double>.Create(function(const Item: TStochasticResult): Double begin Result := Item.K; end);
Stoch.Sender.Link(StochK);
Panel.AddDoubleSeries(StochK.Sender, TAlphaColors.Green);
var StochD: IMycConverter<TStochasticResult, Double> :=
TMycGenericConverter<TStochasticResult, Double>.Create(function(const Item: TStochasticResult): Double begin Result := Item.D; end);
Stoch.Sender.Link(StochD);
Panel.AddDoubleSeries(StochD.Sender, TAlphaColors.Red);
/////
var tickChart := TMycChart.Create(Self);
tickChart.Height := Layout.ChildrenRect.Width * 9 / 16;
AlignControl(tickChart);
tickChart.Lookback.Value := 1000000;
var TickTime: IMycConverter<TDataPoint<TAskBidItem>, TDateTime> :=
TMycGenericConverter<TDataPoint<TAskBidItem>, TDateTime>
.Create(function(const Tick: TDataPoint<TAskBidItem>): TDateTime begin Result := Tick.Time; end);
var TickAsk: IMycConverter<TDataPoint<TAskBidItem>, Double> :=
TMycGenericConverter<TDataPoint<TAskBidItem>, Double>
.Create(function(const Tick: TDataPoint<TAskBidItem>): Double begin Result := Tick.Data.Ask; end);
var TickBid: IMycConverter<TDataPoint<TAskBidItem>, Double> :=
TMycGenericConverter<TDataPoint<TAskBidItem>, Double>
.Create(function(const Tick: TDataPoint<TAskBidItem>): Double begin Result := Tick.Data.Bid; end);
var TickSpread: IMycConverter<TDataPoint<TAskBidItem>, Double> :=
TMycGenericConverter<TDataPoint<TAskBidItem>, Double>
.Create(function(const Tick: TDataPoint<TAskBidItem>): Double begin Result := Tick.Data.Bid-Tick.Data.Ask; end);
ticker.Sender.Link( TickTime );
ticker.Sender.Link( TickAsk );
ticker.Sender.Link( TickBid );
ticker.Sender.Link( TickSpread );
tickChart.SetXAxisSeries( TTimeframe.S, TickTime.Sender );
panel := tickChart.AddPanel;
panel.AddDoubleSeries(TickAsk.Sender, TAlphaColors.Blue);
panel.AddDoubleSeries(TickBid.Sender, TAlphaColors.Red);
panel := tickChart.AddPanel;
panel.AddDoubleSeries(TickSpread.Sender);
panel := pnlChart.AddPanel;
panel.AddDoubleSeries(equity.Sender, TAlphaColors.Blue, 3);
/////
var done := ExecuteStrategy(Symbol, ticker);
FProcessDone := TState.All([FProcessDone, done]);
}
end;
{ TEquitySum<S, T> }
constructor TEquitySum.Create(AEquity: Double);
begin
inherited Create;
FEquity := AEquity;
end;
function TEquitySum.ProcessData(const Value: Double): TState;
begin
if not IsNan(Value) then
begin
FEquity := FEquity + Value;
Result := Broadcast(FEquity);
end;
end;
end.