Files
MycLib/AuraTrader/MainForm.pas
T
Michael Schimmel ce0cba720a Unit refactoring
2025-07-15 09:13:40 +02:00

593 lines
20 KiB
ObjectPascal

unit MainForm;
interface
uses
System.SysUtils,
System.Types,
System.UITypes,
System.Classes,
System.Variants,
System.DateUtils,
System.Generics.Collections,
System.Rtti,
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.DataPoint,
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,
FirstStrategy,
Myc.FMX.Chart;
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;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure StopButtonClick(Sender: TObject);
procedure TreeViewDblClick(Sender: TObject);
procedure AddWorkspaceActionExecute(Sender: TObject);
procedure Strat2ButtonClick(Sender: TObject);
procedure TestActionExecute(Sender: TObject);
procedure StrategyButtonClick(Sender: TObject);
private
FOnEvent: TNotifyEvent;
{ Private declarations }
FServer: IDataServer<TAuraAskBidFileItem>;
FSymbols: TFuture<TArray<String>>;
FTerminate: TEvent;
FProcessDone: TState;
FApplication: IAuraApplication;
FModulesItem: TTreeViewItem;
function SelectedSymbol: String;
function ExecuteStrategy(const Symbol: String; const Processor: IMycProcessor<TArray<TDataPoint<TAskBidItem>>>): TState;
public
procedure NewWorkspace;
function CurrLayout<T: TControl>: T;
procedure AlignControl(Control: TControl);
published
property OnEvent: TNotifyEvent read FOnEvent write FOnEvent;
end;
var
Form1: TForm1;
implementation
uses
TestModule,
Myc.Trade.Indicators;
{$R *.fmx}
procedure TForm1.NewWorkspace;
begin
var ws: IAuraWorkspace := TMycAuraWorkspace.Create('New workspace', tmTesting);
FApplication.Workspaces.Insert(-1, ws);
var 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;
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]
FServer := TAuraTABFileServer.Create('\\COFFEE\TickData\Pepperstone');
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;
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;
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;
function TForm1.ExecuteStrategy(const Symbol: String; const Processor: IMycProcessor<TArray<TDataPoint<TAskBidItem>>>): TState;
var
dataProvider: TConverter<TArray<TDataPoint<TAuraAskBidFileItem>>, TArray<TDataPoint<TAskBidItem>>>;
begin
var terminated := TFlag.CreateObserver(FTerminate.Signal).State;
dataProvider :=
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
);
dataProvider.Sender.Link(Processor);
Result := FServer.ProcessData(Symbol, terminated, dataProvider);
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.M15;
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>>(TTickAggregation.Create(timeframe));
var Timestamps := OhlcPoint.Field<TDateTime>('Time');
var Ohlc := OhlcPoint.Field<TOhlcItem>('Data');
var Closes := Ohlc.Field<Double>('Close');
var Hull := Closes.Chain<Double>(TIndicators.CreateHMA(150));
var Sma := Closes.Chain<Double>(TIndicators.CreateSMA(50));
var Ema := Closes.Chain<Double>(TIndicators.CreateEMA(21));
var Boli := Closes.Chain<TBollingerBandsResult>(TIndicators.CreateBollingerBands(20, 2.0));
var Rsi := Closes.Chain<Double>(TIndicators.CreateRSI(14));
var Macd := Closes.Chain<TMacdResult>(TIndicators.CreateMACD(12, 26, 9));
var Stoch := Ohlc.Chain<TStochasticResult>(TIndicators.CreateStochastic(14, 3));
chart.SetXAxisSeries(timeframe, Timestamps.Sender);
var Panel := chart.AddPanel;
Panel.AddOhlcSeries(Ohlc.Sender);
Panel.AddDoubleSeries(Hull.Sender, TAlphaColors.Aliceblue);
Panel.AddDoubleSeries(Sma.Sender, TAlphaColors.Yellow);
Panel.AddDoubleSeries(Ema.Sender, TAlphaColors.Aqua);
Panel.AddDoubleSeries(Boli.Field<Double>('UpperBand').Sender, TAlphaColors.Gray);
Panel.AddDoubleSeries(Boli.Field<Double>('MiddleBand').Sender, TAlphaColors.Darkgray, 1.0);
Panel.AddDoubleSeries(Boli.Field<Double>('LowerBand').Sender, TAlphaColors.Gray);
Panel := chart.AddPanel;
Panel.AddDoubleSeries(Rsi.Sender, TAlphaColors.Fuchsia);
Panel := chart.AddPanel;
Panel.AddDoubleSeries(Macd.Field<Double>('MacdLine').Sender, TAlphaColors.Orange);
Panel.AddDoubleSeries(Macd.Field<Double>('SignalLine').Sender, TAlphaColors.Dodgerblue);
Panel.AddDoubleSeries(Macd.Field<Double>('Histogram').Sender, TAlphaColors.Lightgreen);
Panel := chart.AddPanel;
Panel.AddDoubleSeries(Stoch.Field<Double>('K').Sender, TAlphaColors.Green);
Panel.AddDoubleSeries(Stoch.Field<Double>('D').Sender, 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);
}
/////
var done := ExecuteStrategy(Symbol, ticker);
FProcessDone := TState.All([FProcessDone, done]);
end;
procedure TForm1.Strat2ButtonClick(Sender: TObject);
begin
var timeframe := TTimeframe.M15;
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 := TTickAggregation.Create(timeframe);
lastPrice.Sender.Link(OhlcPoint);
var Closes :=
TConverter<TDataPoint<TOhlcItem>, Double>
.CreateGeneric(function(const Ohlc: TDataPoint<TOhlcItem>): Double begin Result := Ohlc.Data.Close; end);
var Hull := TConverter<Double, Double>.CreateGeneric(TIndicators.CreateHMA(150));
var Timestamps :=
TConverter<TDataPoint<TOhlcItem>, TDateTime>
.CreateGeneric(function(const Ohlc: TDataPoint<TOhlcItem>): TDateTime begin Result := Ohlc.Time; end);
OhlcPoint.Sender.Link(TimeStamps);
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(timeframe, Timestamps.Sender);
var Panel := chart.AddPanel;
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);
/////
var done := ExecuteStrategy(Symbol, ticker);
FProcessDone := TState.All([FProcessDone, done]);
}
end;
end.