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.Trade.DataProvider, 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; procedure RandomButtonClick(Sender: TObject); procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure LoadButtonClick(Sender: TObject); procedure ChartButtonClick(Sender: TObject); procedure StopButtonClick(Sender: TObject); procedure LayoutResized(Sender: TObject); procedure TreeViewDblClick(Sender: TObject); procedure AddWorkspaceActionExecute(Sender: TObject); procedure TestActionExecute(Sender: TObject); procedure StrategyButtonClick(Sender: TObject); private const cnt = 20; type TRndItem = record Stream: IDataStream; Data: TMutable>; Labl: TLabel; end; private FOnEvent: TNotifyEvent; { Private declarations } FServer: IDataServer; FSymbols: TFuture>; FRandom: TList; 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; 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.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.S15; var ticker := TTicker>.Create; var OhlcPoint := TTicksToBars.Create(timeframe); ticker.Sender.Link(OhlcPoint); var Timestamps: IMycConverter, TDateTime> := TMycGenericConverter, TDateTime> .Create(function(const Ohlc: TDataPoint): TDateTime begin Result := Ohlc.Time; end); OhlcPoint.Sender.Link(TimeStamps); chart.SetXAxisSeries(timeframe, Timestamps.Sender); var Panel := chart.Panels[0]; var Ohlc: IMycConverter, TOhlcItem> := TMycGenericConverter, TOhlcItem> .Create(function(const Ohlc: TDataPoint): TOhlcItem begin Result := Ohlc.Data; end); OhlcPoint.Sender.Link(Ohlc); Panel.AddOhlcSeries(Ohlc.Sender); var Closes: IMycConverter := TMycGenericConverter.Create(function(const Ohlc: TOhlcItem): Double begin Result := Ohlc.Close; end); Ohlc.Sender.Link(Closes); var Hull: IMycConverter := TGenericIndicator.Create(TIndicators.CreateHMA(150)); Closes.Sender.Link(Hull); Panel.AddDoubleSeries(Hull.Sender, TAlphaColors.Aliceblue); // Add SMA (Simple Moving Average) var Sma: IMycConverter := TGenericIndicator.Create(TIndicators.CreateSMA(50)); Closes.Sender.Link(Sma); Panel.AddDoubleSeries(Sma.Sender, TAlphaColors.Yellow); // Add EMA (Exponential Moving Average) var Ema: IMycConverter := TGenericIndicator.Create(TIndicators.CreateEMA(21)); Closes.Sender.Link(Ema); Panel.AddDoubleSeries(Ema.Sender, TAlphaColors.Aqua); // Add Bollinger Bands (20, 2.0) var Boli: IMycConverter := TGenericIndicator.Create(TIndicators.CreateBollingerBands(20, 2.0)); Closes.Sender.Link(Boli); var BoliUpper: IMycConverter := TMycGenericConverter .Create(function(const Item: TBollingerBandsResult): Double begin Result := Item.UpperBand; end); Boli.Sender.Link(BoliUpper); Panel.AddDoubleSeries(BoliUpper.Sender, TAlphaColors.Gray); var BoliMiddle: IMycConverter := TMycGenericConverter .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 := TMycGenericConverter .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 := TGenericIndicator.Create(TIndicators.CreateRSI(14)); Closes.Sender.Link(Rsi); Panel.AddDoubleSeries(Rsi.Sender, TAlphaColors.Fuchsia); // Add MACD (12, 26, 9) var Macd: IMycConverter := TGenericIndicator.Create(TIndicators.CreateMACD(12, 26, 9)); Closes.Sender.Link(Macd); Panel := chart.AddPanel; var MacdLine: IMycConverter := TMycGenericConverter.Create(function(const Item: TMacdResult): Double begin Result := Item.MacdLine; end); Macd.Sender.Link(MacdLine); Panel.AddDoubleSeries(MacdLine.Sender, TAlphaColors.Orange); var MacdSignal: IMycConverter := TMycGenericConverter.Create(function(const Item: TMacdResult): Double begin Result := Item.SignalLine; end); Macd.Sender.Link(MacdSignal); Panel.AddDoubleSeries(MacdSignal.Sender, TAlphaColors.Dodgerblue); var MacdHist: IMycConverter := TMycGenericConverter.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 := TGenericIndicator.Create(TIndicators.CreateStochastic(14, 3)); Ohlc.Sender.Link(Stoch); var StochK: IMycConverter := TMycGenericConverter.Create(function(const Item: TStochasticResult): Double begin Result := Item.K; end); Stoch.Sender.Link(StochK); Panel.AddDoubleSeries(StochK.Sender, TAlphaColors.Green); var StochD: IMycConverter := TMycGenericConverter.Create(function(const Item: TStochasticResult): Double begin Result := Item.D; end); Stoch.Sender.Link(StochD); Panel.AddDoubleSeries(StochD.Sender, TAlphaColors.Red); var done := ExecuteStrategy(Symbol, ticker); ///// FProcessDone := TState.All([FProcessDone, done]); 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; FRandom := TList.Create; // 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); FRandom.Free; end; procedure TForm1.LayoutResized(Sender: TObject); begin LogMemo.Lines.Add('resize'); end; procedure TForm1.LoadButtonClick(Sender: TObject); begin if SymbolsComboBox.ItemIndex < 0 then exit; var Layout := CurrLayout; if Layout = nil then exit; var Stream := FServer.CreateStream(FSymbols.WaitFor[SymbolsComboBox.ItemIndex]); var Data := TDataStreamProvider.Create(30000, 10000, Stream); var path := TPath.Create(Self); path.Parent := Layout; path.Align := TAlignLayout.None; path.ProcessSignal( Data.Changed, procedure begin var Prices := Data.Value; if Prices.Count > 0 then begin path.BeginUpdate; try path.Data.Clear; path.Data.MoveTo(PointF(path.Width - 1, Prices[0].Data.Ask)); for var i := 1 to Prices.Count - 1 do path.Data.LineTo(PointF(path.Width - i - 1, Prices[i].Data.Ask)); finally path.EndUpdate; end; end; end ); end; procedure TForm1.RandomButtonClick(Sender: TObject); begin var Layout := CurrLayout; if Layout = nil then exit; var rnd: TRndItem; rnd.Stream := FServer.CreateStream(FSymbols.WaitFor[Random(Length(FSymbols.WaitFor))]); rnd.Data := TDataStreamProvider.Create(3000, 1000, rnd.Stream); rnd.Labl := TLabel.Create(Self); rnd.Labl.Parent := Layout; rnd.Labl.Align := TAlignLayout.None; rnd.Labl.WordWrap := false; rnd.Labl.Width := 300; rnd.Labl.Text := rnd.Stream.Symbol + ' loading...'; FRandom.Add(rnd); var Proc := procedure(idx: Integer) begin FRandom[idx] .Labl .ProcessSignal( FRandom[idx].Data.Changed, procedure begin var data := FRandom[idx].Data.Value; if data.Count > 0 then begin var dp := data[0]; FRandom[idx].Labl.Text := FRandom[idx].Stream.Symbol + ' ' + dp.Time.ToString + ' ' + data.TotalCount.ToString + ' ' + dp.Data.Ask.ToString; end; end); end; Proc(FRandom.Count - 1); 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; procedure TForm1.ChartButtonClick(Sender: TObject); begin var Layout := CurrLayout; if Layout = nil then exit; var Symbol := SelectedSymbol; if Symbol = '' then exit; var currPathData := TWriteable>.CreateWriteable.Protect; var currLog := TWriteable.CreateWriteable.Protect; const width = 300; var terminated := TFlag.CreateObserver(FTerminate.Signal).State; var done := TaskManager.RunTask( nil, function: TState begin var Prices := TDataSeries.CreateDataSeries(width); Result := FServer.ProcessData( Symbol, terminated, TMycGenericProcessor>>.Create( function(const Values: TArray>): TState begin Result := TState.Null; Prices := Prices.Add(Values); currLog.Value := Prices.TotalCount.ToString; var PathData := TPathData.Create; if Prices.Count > 0 then begin var n := Prices.Count; PathData.MoveTo(PointF(n - 1, Prices[0].Data.Ask)); for var i := 1 to n - 1 do PathData.LineTo(PointF(n - i - 1, Prices[i].Data.Ask)); for var i := n - 1 downto 0 do PathData.LineTo(PointF(n - i - 1, Prices[i].Data.Bid)); end; PathData.ClosePath; currPathData.Value := TObjectRef.Create(PathData); end ) ); end ); FProcessDone := TState.All([FProcessDone, done]); var path := TPath.Create(Self); path.Parent := Layout; path.Align := TAlignLayout.None; path.ProcessSignal( currPathData.Changed, procedure(out IsDone: Boolean) begin if currPathData.Value <> nil then path.Data.Assign(currPathData.Value.Obj); IsDone := done.IsSet; end ); 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: IMycConverter>, TArray>>; begin var terminated := TFlag.CreateObserver(FTerminate.Signal).State; dataProvider := TMycGenericConverter>, TArray>>.Create( 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; end.