Files
MycLib/AuraTrader/MainForm.pas
T
Michael Schimmel 27f1cc5486 Data Types
2025-08-25 10:26:48 +02:00

762 lines
23 KiB
ObjectPascal

unit MainForm;
interface
{.$define TICKDATA}
uses
System.SysUtils,
System.Types,
System.UITypes,
System.Classes,
System.Variants,
System.DateUtils,
System.Generics.Collections,
System.Rtti,
System.Math,
FMX.Types,
FMX.Controls,
FMX.Forms,
FMX.Graphics,
FMX.Dialogs,
FMX.Controls.Presentation,
FMX.StdCtrls,
FMX.ListView.Types,
FMX.ListView.Appearances,
FMX.ListView.Adapters.Base,
FMX.ListView,
FMX.Memo.Types,
FMX.ScrollBox,
FMX.Memo,
FMX.Objects,
Myc.Futures,
Myc.Trade.Types,
Myc.Trade.DataStream,
Myc.Trade.Pipeline,
Myc.Data.Series,
Myc.Data.Pipeline,
Myc.Signals,
Myc.Mutable,
Myc.Signals.FMX,
Myc.TaskManager,
Myc.Aura.Module,
FMX.ListBox,
FMX.Layouts,
FMX.TreeView,
FMX.TabControl,
FMX.Menus,
System.ImageList,
FMX.ImgList,
System.Actions,
FMX.ActnList,
DynamicFMXControl,
Myc.FMX.Chart,
Myc.Trade.Indicators,
Myc.Trade.Indicators.Common,
StrategyTest,
TestMethodCallFromRecordParams;
type
TForm1 = class(TForm)
LogMemo: TMemo;
MainPanel: TPanel;
SymbolsComboBox: TComboBox;
RandomBox: TCheckBox;
LoadButton: TButton;
RandomButton: TButton;
ChartButton: TButton;
StopButton: TButton;
Splitter1: TSplitter;
ActionList: TActionList;
AddWorkspaceAction: TAction;
WorkspacePanel: TPanel;
TabControl: TTabControl;
ToolBar: TToolBar;
AddWorkspaceButton: TSpeedButton;
ObjectsPanel: TPanel;
ObjectsTabControl: TTabControl;
ModulesTabItem: TTabItem;
TreeView: TTreeView;
TestButton: TSpeedButton;
TestAction: TAction;
TestPopup: TPopup;
FlowLayout: TFlowLayout;
StrategyButton: TSpeedButton;
Strat2Button: TSpeedButton;
Button1: TButton;
Button2: TButton;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure StopButtonClick(Sender: TObject);
procedure TreeViewDblClick(Sender: TObject);
procedure AddWorkspaceActionExecute(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure LogMemoChange(Sender: TObject);
procedure Strat2ButtonClick(Sender: TObject);
procedure TestActionExecute(Sender: TObject);
procedure StrategyButtonClick(Sender: TObject);
private
FOnEvent: TNotifyEvent;
{ Private declarations }
{$ifdef TICKDATA}
FServer: IDataServer<TAuraAskBidFileItem>;
{$else}
FServer: IDataServer<TOhlcItem>;
{$endif}
FSymbols: TFuture<TArray<String>>;
FTerminate: TEvent;
FProcessDone: TState;
FApplication: IAuraApplication;
FModulesItem: TTreeViewItem;
function SelectedSymbol: String;
procedure ExecuteStrategy(const Symbol: String; Timeframe: TTimeframe; const Consumer: IConsumer<TDataPoint<TOhlcItem>>);
public
procedure NewWorkspace;
function CurrLayout<T: TControl>: T;
procedure AlignControl(Control: TControl);
function CreateStrategy2(Timeframe: TTimeframe): IConsumer<TDataPoint<TOhlcItem>>;
published
property OnEvent: TNotifyEvent read FOnEvent write FOnEvent;
end;
var
Form1: TForm1;
implementation
uses
Myc.Data.Records,
TestModule,
Strategy2;
{$R *.fmx}
procedure TForm1.NewWorkspace;
var
tab: TTabItem;
begin
var ws: IAuraWorkspace := TMycAuraWorkspace.Create('New workspace', tmTesting);
FApplication.Workspaces.Insert(-1, ws);
tab := TabControl.Add;
tab.Text := ws.Caption;
tab.Tag := NativeInt(ws);
var scrollbox := TVertScrollBox.Create(Self);
scrollbox.Parent := tab;
scrollbox.Align := TAlignLayout.Client;
tab.ProcessSignal(ws.Name.Changed, procedure begin tab.Text := ws.Caption; end);
end;
procedure TForm1.StopButtonClick(Sender: TObject);
begin
FTerminate.Notify;
TaskManager.WaitFor(FProcessDone);
var Layout := CurrLayout<TVertScrollBox>;
if Layout <> nil then
begin
Layout.Content.DeleteChildren;
Layout.Repaint;
end;
end;
procedure TForm1.TreeViewDblClick(Sender: TObject);
begin
var sel := TreeView.Selected as TTreeViewItem;
var parent := sel.ParentItem;
if parent = nil then
exit;
if parent = FModulesItem then
begin
if (TabControl.ActiveTab <> nil) and (TabControl.ActiveTab.Tag <> 0) then
begin
var ws := IAuraWorkspace(TabControl.ActiveTab.Tag);
var module := FApplication.Modules[sel.TagString];
if (ws <> nil) and (module <> nil) then
module.SetupWorkspace(ws);
end;
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
FApplication := TMycAuraApplication.Create;
FModulesItem := TTreeViewItem.Create(Self);
FModulesItem.Text := 'Modules';
const modName = 'Test_Module';
FApplication.RegisterModule(modName, TTestModule.Create('Test-Module', 1));
TreeView.AddObject(FModulesItem);
FModulesItem.ProcessSignal(
FApplication.ModuleNames.Changed,
procedure
begin
FModulesItem.BeginUpdate;
try
while FModulesItem.Count > 0 do
FModulesItem[0].Free;
var mods := FApplication.ModuleNames.Value;
for var i := 0 to High(mods) do
begin
var ModItem := TTreeViewItem.Create(Self);
ModItem.Text := mods[i];
ModItem.DragMode := TDragMode.dmAutomatic;
ModItem.Text := 'Module-' + mods[i];
ModItem.TagString := mods[i];
FModulesItem.AddObject(ModItem);
end;
FModulesItem.ExpandAll;
finally
FModulesItem.EndUpdate;
end;
end
);
FTerminate := TEvent.CreateEvent;
// Create an instance of the TAuraTABFileServer. The server can be reused for multiple stream creations. [364]
{$ifdef TICKDATA}
FServer := TAuraTABFileServer.Create('\\COFFEE\TickData\Pepperstone');
{$else}
FServer := TAuraM1FileServer.Create('\\COFFEE\TickData\Pepperstone');
{$endif}
SymbolsComboBox.Enabled := false;
ChartButton.Enabled := false;
LoadButton.Enabled := false;
FSymbols := FServer.EnumerateSymbols;
SymbolsComboBox.ProcessSignal(
FSymbols.Done.Signal,
procedure
begin
SymbolsComboBox.BeginUpdate;
try
SymbolsComboBox.Items.Clear;
SymbolsComboBox.Items.AddStrings(FSymbols.WaitFor);
if SymbolsComboBox.Items.Count > 0 then
begin
if SymbolsComboBox.ItemIndex < 0 then
SymbolsComboBox.ItemIndex := SymbolsComboBox.Items.IndexOf('GER40');
SymbolsComboBox.Enabled := true;
ChartButton.Enabled := true;
LoadButton.Enabled := true;
end;
finally
SymbolsComboBox.EndUpdate;
end;
end
);
NewWorkspace;
IndicatorRegistry.LogRegistry(LogMemo.Lines);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
FSymbols.WaitFor;
FTerminate.Notify;
TaskManager.WaitFor(FProcessDone);
end;
procedure TForm1.TestActionExecute(Sender: TObject);
begin
TestPopup.IsOpen := TestAction.Checked;
end;
procedure TForm1.AddWorkspaceActionExecute(Sender: TObject);
begin
NewWorkspace;
end;
procedure TForm1.AlignControl(Control: TControl);
begin
var Layout := CurrLayout<TVertScrollBox>;
if Layout = nil then
exit;
Control.Parent := Layout;
Control.Width := Layout.Width;
Control.Position.Y := Layout.ChildrenRect.Bottom + 1;
Control.Anchors := [TAnchorKind.akLeft, TAnchorKind.akRight];
Control.Align := TAlignLayout.Top;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
Test1(LogMemo.Lines);
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
var Symbol := SelectedSymbol;
if Symbol = '' then
exit;
var terminated := TFlag.CreateObserver(FTerminate.Signal).State;
var ticker := TConverter.CreateTicker<TDataPoint<TOhlcItem>>;
var Layout := CurrLayout<TVertScrollBox>;
if Layout = nil then
exit;
var chart1 := TMycChart.Create(Self);
AlignControl(chart1);
chart1.Height := Layout.ChildrenRect.Width * 9 / 20;
chart1.Lookback.Value := 50000;
var chart2 := TMycChart.Create(Self);
AlignControl(chart2);
chart2.Height := Layout.ChildrenRect.Width * 9 / 20;
chart2.Lookback.Value := 50000;
var equity := Strategy2.CreateStrategy2(ticker.Producer, LogMemo.Lines, chart1, chart2);
var pnlChart := TMycChart.Create(Self);
AlignControl(pnlChart);
pnlChart.Height := Layout.ChildrenRect.Width * 9 / 24;
pnlChart.Lookback.Value := 50000;
pnlChart.SetXAxisCounter<Double>(equity);
var panel := pnlChart.AddPanel;
panel.AddDoubleSeries(equity, TAlphaColors.Blue, 3);
FProcessDone := FProcessDone + FServer.ProcessData(Symbol, terminated, ticker.Consumer);
end;
function TForm1.CreateStrategy2(Timeframe: TTimeframe): IConsumer<TDataPoint<TOhlcItem>>;
type
TSignal = record
Sig: Double;
SL: Double;
Entry: Double;
pnl: Double;
end;
var
panel: TMycChart.TPanel;
begin
var ticker := TConverter.CreateIdentity<TDataPoint<TOhlcItem>>;
Result := ticker.Consumer;
var OhlcPoint := ticker.Producer.Chain<TDataPoint<TOhlcItem>>(TTradeConverter.CreateOhlcAggregation(Timeframe));
var Ohlc := OhlcPoint.Field<TOhlcItem>('Data');
var Closes := Ohlc.Field<Double>('Close');
var Hull := Closes.Chain<Double>(THMA.CreateHMA(250)).MakeParallel;
var Sma := Closes.Chain<Double>(TSMA.CreateSMA(200)).MakeParallel;
var Lowest: Double := Double.MaxValue;
var Highest: Double := Double.MinValue;
var ATR := Ohlc.Chain<Double>(TATR.CreateATR(50)).MakeParallel;
// next stage
var curr: TSignal;
curr.SL := Double.NaN;
curr.Entry := Double.NaN;
var lastHull, lastSma: Double;
var conv := TConverter.Join<Double>(jmAll, [Ohlc.Field<Double>('Low'), Ohlc.Field<Double>('High'), Closes, ATR, Hull, Sma]);
var Signal :=
TConverter<TArray<Double>, TSignal>.CreateConverter(
function(const Values: TArray<Double>): 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.Chain<TSignal>(Signal);
var pnl := Signal.Producer.Field<Double>('pnl');
var FEquity: Double := 10000;
var FInit: Boolean := false;
var equity :=
TConverter<Double, Double>.CreateAggregation(
function(const Value: Double; const Broadcast: TBroadcastFunc<Double>): 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.Chain<Double>(equity);
var Layout := CurrLayout<TVertScrollBox>;
if Layout = nil then
exit;
var Symbol := SelectedSymbol;
if Symbol = '' then
exit;
var chart := TMycChart.Create(Self);
AlignControl(chart);
chart.Height := Layout.ChildrenRect.Width * 9 / 16;
chart.Lookback.Value := 50000;
chart.SetXAxisSeries(M15, OhlcPoint.Field<TDateTime>('Time'));
panel := chart.AddPanel;
panel.AddOhlcSeries(Ohlc);
panel.AddDoubleSeries(Hull, TAlphaColors.Cornflowerblue, 2);
panel.AddDoubleSeries(Sma, TAlphaColors.Brown, 1.5);
panel.AddDoubleSeries(Signal.Producer.Field<Double>('Entry'), TAlphaColors.Green, 1);
panel.AddDoubleSeries(Signal.Producer.Field<Double>('SL'), TAlphaColors.Red, 2);
var mean := TConverter<TArray<Double>, Double>.CreateConverter(TMean.CreateMean());
TConverter.Join<Double>(jmAll, [Hull, Sma]).Chain<Double>(mean);
panel.AddDoubleSeries(mean.Producer, TAlphaColors.Blue, 5);
var pnlChart := TMycChart.Create(Self);
AlignControl(pnlChart);
pnlChart.Height := Layout.ChildrenRect.Width * 9 / 24;
pnlChart.Lookback.Value := 50000;
pnlChart.SetXAxisCounter<Double>(equity.Producer);
////////////
(*
var Params: TEMA.TParam;
Params.Period := 20;
var indi := TEMA.CreateEMA(Params);
var EMAConv := TConverter<TEMA.TInput, TEMA.TResult>.CreateConverter(indi);
var equityEMA :=
equity
.Producer
.Chain<TEMA.TInput>(function(const Value: Double): TEMA.TInput begin Result.Price := Value end)
.Chain<TEMA.TResult>(EMAConv)
.Chain<Double>(function(const Value: TEMA.TResult): Double begin Result := Value.MA end);
*)
//////////////
panel := pnlChart.AddPanel;
panel.AddDoubleSeries(equity.Producer, TAlphaColors.Blue, 3);
// panel.AddDoubleSeries(equityEMA, TAlphaColors.Gray, 2);
/////
end;
function TForm1.CurrLayout<T>: T;
begin
if TabControl.ActiveTab = nil then
exit(nil);
var Res: T := nil;
TabControl.ActiveTab.EnumControls(
function(Control: TControl): TEnumControlsResult
begin
Result := TEnumControlsResult.Continue;
if Control is T then
begin
Res := Control as T;
Result := TEnumControlsResult.Stop;
end;
end
);
Result := Res;
end;
procedure TForm1.ExecuteStrategy(const Symbol: String; Timeframe: TTimeframe; const Consumer: IConsumer<TDataPoint<TOhlcItem>>);
begin
var terminated := TFlag.CreateObserver(FTerminate.Signal).State;
{$ifdef TICKDATA}
var ticker := TConverter.CreateTicker<TDataPoint<TAskBidItem>>;
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
);
var OhlcPoint := lastPrice.Chain<TDataPoint<TOhlcItem>>(TConverter.CreateTickAggregation(Timeframe));
OhlcPoint.Producer.Link(Consumer);
var Producer :=
TConverter<TArray<TDataPoint<TAuraAskBidFileItem>>, TArray<TDataPoint<TAskBidItem>>>.CreateGeneric(
function(const Values: TArray<TDataPoint<TAuraAskBidFileItem>>): TArray<TDataPoint<TAskBidItem>>
begin
SetLength(Result, Length(Values));
for var i := 0 to High(Result) do
begin
Result[i].Time := Values[i].Time;
Result[i].Data.Ask := Values[i].Data.Ask;
Result[i].Data.Bid := Values[i].Data.Bid;
end;
end
);
Producer.Producer.Link(ticker);
FProcessDone := FProcessDone + FServer.ProcessData(Symbol, terminated, Producer);
{$else}
var ticker := TConverter.CreateTicker<TDataPoint<TOhlcItem>>;
ticker.Producer.Chain(Consumer);
FProcessDone := FProcessDone + FServer.ProcessData(Symbol, terminated, ticker.Consumer);
{$endif}
end;
procedure TForm1.LogMemoChange(Sender: TObject);
begin
if LogMemo.Lines.Count > 1000 then
LogMemo.Lines.Clear;
end;
function TForm1.SelectedSymbol: String;
begin
Result := '';
if RandomBox.IsChecked then
Result := FSymbols.WaitFor[Random(Length(FSymbols.WaitFor))]
else if SymbolsComboBox.ItemIndex >= 0 then
Result := FSymbols.WaitFor[SymbolsComboBox.ItemIndex];
end;
procedure TForm1.StrategyButtonClick(Sender: TObject);
begin
var Layout := CurrLayout<TVertScrollBox>;
if Layout = nil then
exit;
var Symbol := SelectedSymbol;
if Symbol = '' then
exit;
var chart := TMycChart.Create(Self);
AlignControl(chart);
chart.Height := Layout.ChildrenRect.Width * 9 / 16;
chart.Lookback.Value := 50000;
/////
var timeframe := TTimeframe.H;
var ticker := TConverter.CreateIdentity<TDataPoint<TOhlcItem>>;
var OhlcPoint := ticker.Producer.Chain<TDataPoint<TOhlcItem>>(TTradeConverter.CreateOhlcAggregation(timeframe));
// var OhlcTicker := TConverter.CreateIdentity<TDataPoint<TOhlcItem>>;
// var OhlcPoint := OhlcTicker.Sender;
var Timestamps := OhlcPoint.Field<TDateTime>('Time');
var Ohlc := OhlcPoint.Field<TOhlcItem>('Data');
var Closes := Ohlc.Field<Double>('Close');
var Hull := Closes.Chain<Double>(THMA.CreateHMA(150));
var Sma := Closes.Chain<Double>(TSMA.CreateSMA(50));
var Ema := Closes.Chain<Double>(TEMA.CreateEMA(21));
var Boli := Closes.MakeParallel.Chain<TBollingerBands.TResult>(TBollingerBands.CreateBollingerBands(20, 2.0));
var Rsi := Closes.Chain<Double>(TRSI.CreateRSI(14));
var Macd := Closes.MakeParallel.Chain<TMacd.TResult>(TMACD.CreateMACD(12, 26, 9));
var Stoch := Ohlc.Chain<TStochastic.TResult>(TStochastic.CreateStochastic(14, 3));
chart.SetXAxisSeries(timeframe, Timestamps);
var Panel := chart.AddPanel;
Panel.AddOhlcSeries(Ohlc);
Panel.AddDoubleSeries(Hull, TAlphaColors.Aliceblue);
Panel.AddDoubleSeries(Sma, TAlphaColors.Yellow);
Panel.AddDoubleSeries(Ema, TAlphaColors.Aqua);
Panel.AddDoubleSeries(Boli.Field<Double>('UpperBand'), TAlphaColors.Gray);
Panel.AddDoubleSeries(Boli.Field<Double>('MiddleBand'), TAlphaColors.Darkgray, 1.0);
Panel.AddDoubleSeries(Boli.Field<Double>('LowerBand'), TAlphaColors.Gray);
Panel := chart.AddPanel;
Panel.AddDoubleSeries(Rsi, TAlphaColors.Fuchsia);
Panel := chart.AddPanel;
Panel.AddDoubleSeries(Macd.Field<Double>('MacdLine'), TAlphaColors.Orange);
Panel.AddDoubleSeries(Macd.Field<Double>('SignalLine'), TAlphaColors.Dodgerblue);
Panel.AddDoubleSeries(Macd.Field<Double>('Histogram'), TAlphaColors.Lightgreen);
Panel := chart.AddPanel;
Panel.AddDoubleSeries(Stoch.Field<Double>('K'), TAlphaColors.Green);
Panel.AddDoubleSeries(Stoch.Field<Double>('D'), TAlphaColors.Red);
/////
{
var tickChart := TMycChart.Create(Self);
tickChart.Height := Layout.ChildrenRect.Width * 9 / 16;
AlignControl(tickChart);
tickChart.Lookback.Value := 1000000;
var TickTime := ticker.Field<TDateTime>('Time');
var TickData := ticker.Field<TAskBidItem>('Data');
var TickAsk := TickData.Field<Double>('Ask');
var TickBid := TickData.Field<Double>('Bid');
var TickSpread := TickData.Chain<Double>(function(const Tick: TAskBidItem): Double begin Result := Tick.Bid - Tick.Ask; end);
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);
}
/////
ExecuteStrategy(Symbol, timeframe, ticker.Consumer);
end;
procedure TForm1.Strat2ButtonClick(Sender: TObject);
begin
var Symbol := SelectedSymbol;
if Symbol = '' then
exit;
var timeframe := TTimeframe.M15;
// ExecuteStrategy(Symbol, timeframe, CreateStrategy2(timeframe));
var tstStrat := StrategyTest.CreateStrategy1(timeframe);
ExecuteStrategy(Symbol, timeframe, tstStrat.Consumer);
var Layout := CurrLayout<TVertScrollBox>;
if Layout = nil then
exit;
var pnlChart := TMycChart.Create(Self);
AlignControl(pnlChart);
pnlChart.Height := Layout.ChildrenRect.Width * 9 / 24;
pnlChart.Lookback.Value := 50000;
pnlChart.SetXAxisCounter<Double>(tstStrat.Producer);
var panel := pnlChart.AddPanel;
panel.AddDoubleSeries(tstStrat.Producer, TAlphaColors.Blue, 3);
end;
end.