unit MainForm; interface 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 } FServer: IDataServer; FSymbols: TFuture>; FTerminate: TEvent; FProcessDone: TState; FApplication: IAuraApplication; FModulesItem: TTreeViewItem; function SelectedSymbol: String; function ExecuteStrategy(const Symbol: String; const Processor: IMycProcessor>>): TState; public procedure NewWorkspace; function CurrLayout: T; procedure AlignControl(Control: TControl); published property OnEvent: TNotifyEvent read FOnEvent write FOnEvent; end; TEquitySum = class(TMycConverter) private FEquity: Double; 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; 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; 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; 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>>): TState; var dataProvider: TConverter>, TArray>>; begin var terminated := TFlag.CreateObserver(FTerminate.Signal).State; dataProvider := TConverter>, TArray>>.CreateGeneric( function(const Values: TArray>): TArray> 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; 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>; var lastPrice := ticker.Chain>( function(const Tick: TDataPoint): TDataPoint begin Result.Time := Tick.Time; Result.Data := 0.5 * (Tick.Data.Ask + Tick.Data.Bid); end ); var OhlcPoint := lastPrice.Chain>(TConverter.CreateAggregation(timeframe)); var Timestamps := OhlcPoint.Field('Time'); var Ohlc := OhlcPoint.Field('Data'); var Closes := Ohlc.Field('Close'); var Hull := Closes.Chain(TIndicators.CreateHMA(150)); var Sma := Closes.Chain(TIndicators.CreateSMA(50)); var Ema := Closes.Chain(TIndicators.CreateEMA(21)); var Boli := Closes.Chain(TIndicators.CreateBollingerBands(20, 2.0)); var Rsi := Closes.Chain(TIndicators.CreateRSI(14)); var Macd := Closes.Chain(TIndicators.CreateMACD(12, 26, 9)); var Stoch := Ohlc.Chain(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('UpperBand').Sender, TAlphaColors.Gray); Panel.AddDoubleSeries(Boli.Field('MiddleBand').Sender, TAlphaColors.Darkgray, 1.0); Panel.AddDoubleSeries(Boli.Field('LowerBand').Sender, TAlphaColors.Gray); Panel := chart.AddPanel; Panel.AddDoubleSeries(Rsi.Sender, TAlphaColors.Fuchsia); Panel := chart.AddPanel; Panel.AddDoubleSeries(Macd.Field('MacdLine').Sender, TAlphaColors.Orange); Panel.AddDoubleSeries(Macd.Field('SignalLine').Sender, TAlphaColors.Dodgerblue); Panel.AddDoubleSeries(Macd.Field('Histogram').Sender, TAlphaColors.Lightgreen); Panel := chart.AddPanel; Panel.AddDoubleSeries(Stoch.Field('K').Sender, TAlphaColors.Green); Panel.AddDoubleSeries(Stoch.Field('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('Time'); var TickData := ticker.Field('Data'); var TickAsk := TickData.Field('Ask'); var TickBid := TickData.Field('Bid'); var TickSpread := TickData.Chain(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); type TSignal = record Sig: Double; SL: Double; Entry: Double; pnl: Double; end; begin var timeframe := TTimeframe.M15; var ticker := TConverter.CreateTicker>; var lastPrice := ticker.Chain>( TConverter.CreateDataPointConverter( function(const Tick: TAskBidItem): Double begin Result := 0.5 * (Tick.Ask + Tick.Bid); end ) ); var OhlcPoint := lastPrice.Chain>(TConverter.CreateAggregation(timeframe)); var Ohlc := TConverter.CreateSequence(2, OhlcPoint.Field('Data').Sender); var Closes := Ohlc[0].Field('Close'); var Hull := Closes.Chain(TIndicators.CreateHMA(250)); var Sma := Closes.Chain(TIndicators.CreateSMA(200)); var HullSeries := TConverter.CreateEndpoint(Hull.Sender, 5); var SmaSeries := TConverter.CreateEndpoint(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(TIndicators.CreateATR(50)); var ATRSeries := TConverter.CreateEndpoint(ATR.Sender, 5); // next stage var Signal := Ohlc[1] .Chain( 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('pnl'); var equity: TConverter := TEquitySum.Create(10000); pnl.Sender.Link(equity); var Layout := CurrLayout; 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('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('SL').Sender, TAlphaColors.Red, 2); panel.AddDoubleSeries(Signal.Field('Entry').Sender, TAlphaColors.Green, 1); // panel := chart.AddPanel; // panel.AddDoubleSeries( equity.Sender, TAlphaColors.Blue, 3 ); var pnlChart := TMycChart.Create(Self); AlignControl(pnlChart); pnlChart.Height := Layout.ChildrenRect.Width * 9 / 24; pnlChart.Lookback.Value := 50000; pnlChart.SetXAxisCounter(equity.Sender); panel := pnlChart.AddPanel; panel.AddDoubleSeries(equity.Sender, TAlphaColors.Blue, 3); ///// var done := ExecuteStrategy(Symbol, ticker); FProcessDone := TState.All([FProcessDone, done]); end; { TEquitySum } 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.