Files
MycLib/AuraTrader/MainForm.pas
T
2025-07-17 01:40:42 +02:00

665 lines
21 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.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,
FMX.TabControl,
FMX.Menus,
System.ImageList,
FMX.ImgList,
System.Actions,
FMX.ActnList,
DynamicFMXControl,
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 }
{$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 Processor: IMycProcessor<TDataPoint<TOhlcItem>>);
public
procedure NewWorkspace;
function CurrLayout<T: TControl>: T;
procedure AlignControl(Control: TControl);
function CreateStrategy2(Timeframe: TTimeframe): IMycProcessor<TDataPoint<TOhlcItem>>;
published
property OnEvent: TNotifyEvent read FOnEvent write FOnEvent;
end;
TEquitySum = class(TMycConverter<Double, Double>)
private
FEquity: Double;
FInit: Boolean;
protected
function ProcessData(const Value: Double): TState; override;
public
constructor Create(AEquity: Double);
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;
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;
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.CreateStrategy2(Timeframe: TTimeframe): IMycProcessor<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;
var OhlcPoint := ticker.Chain<TDataPoint<TOhlcItem>>(TConverter.CreateOhlcAggregation(Timeframe));
var Ohlc := TConverter.CreateSequence<TOhlcItem>(2, OhlcPoint.Field<TOhlcItem>('Data').Sender);
var Closes := Ohlc[0].Field<Double>('Close');
var Hull := Closes.Chain<Double>(TIndicators.CreateHMA(250)).MakeParallel;
var Sma := Closes.Chain<Double>(TIndicators.CreateSMA(200)).MakeParallel;
var Lowest: Double := Double.MaxValue;
var Highest: Double := Double.MinValue;
var ATR := Ohlc[0].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 Signal :=
Ohlc[1]
.Chain<TSignal>(
function(const Ohlc: TOhlcItem): TSignal
begin
if Ohlc.Low < Lowest then
Lowest := Ohlc.Low;
if Ohlc.High > Highest then
Highest := Ohlc.High;
Result := curr;
Result.Sig := 0;
var pnl: double := NaN;
ATREndPoint.Update(ATRSeries);
HullEndPoint.Update(HullSeries);
SmaEndPoint.Update(SmaSeries);
if (HullSeries[0] < SmaSeries[0]) and (HullSeries[1] >= SmaSeries[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[0] > SmaSeries[0]) and (HullSeries[1] <= SmaSeries[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[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
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').Sender);
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>('Entry').Sender, TAlphaColors.Green, 1);
panel.AddDoubleSeries(Signal.Field<Double>('SL').Sender, TAlphaColors.Red, 2);
var pnlChart := TMycChart.Create(Self);
AlignControl(pnlChart);
pnlChart.Height := Layout.ChildrenRect.Width * 9 / 24;
pnlChart.Lookback.Value := 50000;
pnlChart.SetXAxisCounter<Double>(equity.Sender);
panel := pnlChart.AddPanel;
panel.AddDoubleSeries(equity.Sender, TAlphaColors.Blue, 3);
/////
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 Processor: IMycProcessor<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.Sender.Link(Processor);
var 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(ticker);
FProcessDone := FProcessDone + FServer.ProcessData(Symbol, terminated, dataProvider);
{$else}
var ticker := TConverter.CreateTicker<TDataPoint<TOhlcItem>>;
ticker.Sender.Link(Processor);
FProcessDone := FProcessDone + FServer.ProcessData(Symbol, terminated, ticker);
{$endif}
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 OhlcPoint := TConverter.CreateIdentity<TDataPoint<TOhlcItem>>;
var timeframe := TTimeframe.H;
var Timestamps := OhlcPoint.Field<TDateTime>('Time');
var Ohlc := OhlcPoint.Field<TOhlcItem>('Data');
var Closes := Ohlc.Field<Double>('Close');
var Hull := Closes.MakeParallel.Chain<Double>(TIndicators.CreateHMA(150));
var Sma := Closes.MakeParallel.Chain<Double>(TIndicators.CreateSMA(50));
var Ema := Closes.MakeParallel.Chain<Double>(TIndicators.CreateEMA(21));
var Boli := Closes.MakeParallel.Chain<TBollingerBandsResult>(TIndicators.CreateBollingerBands(20, 2.0));
var Rsi := Closes.MakeParallel.Chain<Double>(TIndicators.CreateRSI(14));
var Macd := Closes.MakeParallel.Chain<TMacdResult>(TIndicators.CreateMACD(12, 26, 9));
var Stoch := Ohlc.MakeParallel.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);
}
/////
ExecuteStrategy(Symbol, timeframe, OhlcPoint);
end;
procedure TForm1.Strat2ButtonClick(Sender: TObject);
begin
var Symbol := SelectedSymbol;
if Symbol = '' then
exit;
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;
{ TEquitySum<S, T> }
constructor TEquitySum.Create(AEquity: Double);
begin
inherited Create;
FEquity := AEquity;
FInit := false;
end;
function TEquitySum.ProcessData(const Value: 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;
end.