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, Myc.Data.Scalar, Myc.Data.Keyword, 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, Myc.Core.FileCache, Myc.Trade.DataFeed, 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; Button3: 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 Button3Click(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; {$else} FServer: IDataServer; {$endif} FSymbols: TFuture>; FTerminate: TEvent; FProcessDone: TState; FApplication: IAuraApplication; FModulesItem: TTreeViewItem; FFileCache: IDataFileCache>; FFeed: IScalarRecordFeed; function SelectedSymbol: String; procedure ExecuteStrategy(const Symbol: String; Timeframe: TTimeframe; const Consumer: IConsumer>); public procedure NewWorkspace; function CurrLayout: T; procedure AlignControl(Control: TControl); function CreateStrategy2(Timeframe: TTimeframe): IConsumer>; 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; 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; FFileCache := TDataFileCache>.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 := TAskBidFileServer.Create('\\COFFEE\TickData\Pepperstone'); {$else} FServer := TM1FileServer.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; 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>; var Layout := CurrLayout; 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(equity); var panel := pnlChart.AddPanel; panel.AddDoubleSeries(equity, TAlphaColors.Blue, 3); FProcessDone := FProcessDone + FServer.ProcessData(Symbol, terminated, ticker.Consumer); end; procedure TForm1.Button3Click(Sender: TObject); begin var symbol := SelectedSymbol; if symbol = '' then exit; var layout := CurrLayout; if layout = nil then exit; // 1. Setup Chart var chart := TMycChart.Create(Self); AlignControl(chart); chart.Height := layout.ChildrenRect.Width * 9 / 16; chart.Lookback.Value := 500000; // 2. Define Schema (Keywords & Layout) var kTime := TKeywordRegistry.Intern('Time'); var kClose := TKeywordRegistry.Intern('Close'); var def := TScalarRecordRegistry.Intern( [ TPair.Create(kTime, TScalar.TKind.DateTime), TPair.Create(kClose, TScalar.TKind.Float) ] ); // 3. Build Pipeline var iTime := def.IndexOf(kTime); var iClose := def.IndexOf(kClose); var terminated := TFlag.CreateObserver(FTerminate.Signal).State; var path := '\\COFFEE\TickData\Pepperstone'; // Or FServer.Path if compatible FFeed := TScalarRecordFeed.Create(FFileCache, path, '.m1', true, def); // Converter: Transforms raw IRecordSeries (Columns) into TArray var ticker := FFeed.Ticker.Chain>( function(const Tick: IScalarRecord): TDataPoint begin Result.Time := Tick[iTime].Value.Value.AsDouble; Result.Data := Tick[iClose].Value.Value.AsDouble; end ); // Link Chart to Ticker chart .SetXAxisSeries(TTimeframe.M, ticker.Chain(function(const p: TDataPoint): TDateTime begin Result := p.Time end)); var panel := chart.AddPanel; panel.AddDoubleSeries( ticker.Chain(function(const p: TDataPoint): Double begin Result := p.Data end), TAlphaColors.Orange ); FFeed.SetImportOptions( SizeOf(TM1FileItem), procedure(const Src: Pointer; Dst: TScalar.PValue) var Item: ^TM1FileItem; begin Item := Src; // Field 0: Time (DateTime is stored as Double/OADate in TScalar) Dst.AsDouble := Item.OADateTime; // Move to next field in destination (TScalar values are contiguous) Inc(Dst); // Field 1: Close (Float) // Convert Int64 price to Double using Digits: Price / 10^Digits if Item.Digits > 0 then Dst.AsDouble := Item.Close / Power(10, Item.Digits) else Dst.AsDouble := Item.Close; end ); // Connect pipeline: Server -> Converter FProcessDone := FFeed.ProcessData(symbol, terminated); end; function TForm1.CreateStrategy2(Timeframe: TTimeframe): IConsumer>; type TSignal = record Sig: Double; SL: Double; Entry: Double; pnl: Double; end; var panel: TMycChart.TPanel; begin var ticker := TConverter.CreateIdentity>; Result := ticker.Consumer; var OhlcPoint := ticker.Producer.Chain>(TTradeConverter.CreateOhlcAggregation(Timeframe)); var Ohlc := OhlcPoint.Field('Data'); var Closes := Ohlc.Field('Close'); var Hull := Closes.Chain(THMA.CreateHMA(250)).MakeParallel; var Sma := Closes.Chain(TSMA.CreateSMA(200)).MakeParallel; var Lowest: Double := Double.MaxValue; var Highest: Double := Double.MinValue; var ATR := Ohlc.Chain(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(jmAll, [Ohlc.Field('Low'), Ohlc.Field('High'), Closes, ATR, Hull, Sma]); var Signal := TConverter, TSignal>.CreateConverter( function(const Values: TArray): 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(Signal); var pnl := Signal.Producer.Field('pnl'); var FEquity: Double := 10000; var FInit: Boolean := false; var equity := TConverter.CreateAggregation( function(const Value: Double; const Broadcast: TBroadcastFunc): 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(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')); panel := chart.AddPanel; panel.AddOhlcSeries(Ohlc); panel.AddDoubleSeries(Hull, TAlphaColors.Cornflowerblue, 2); panel.AddDoubleSeries(Sma, TAlphaColors.Brown, 1.5); panel.AddDoubleSeries(Signal.Producer.Field('Entry'), TAlphaColors.Green, 1); panel.AddDoubleSeries(Signal.Producer.Field('SL'), TAlphaColors.Red, 2); var mean := TConverter, Double>.CreateConverter(TMean.CreateMean()); TConverter.Join(jmAll, [Hull, Sma]).Chain(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(equity.Producer); //////////// (* var Params: TEMA.TParam; Params.Period := 20; var indi := TEMA.CreateEMA(Params); var EMAConv := TConverter.CreateConverter(indi); var equityEMA := equity .Producer .Chain(function(const Value: Double): TEMA.TInput begin Result.Price := Value end) .Chain(EMAConv) .Chain(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; 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>); begin var terminated := TFlag.CreateObserver(FTerminate.Signal).State; {$ifdef TICKDATA} 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.CreateTickAggregation(Timeframe)); OhlcPoint.Producer.Link(Consumer); var Producer := 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 ); Producer.Producer.Link(ticker); FProcessDone := FProcessDone + FServer.ProcessData(Symbol, terminated, Producer); {$else} var ticker := TConverter.CreateTicker>; 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; 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>; var OhlcPoint := ticker.Producer.Chain>(TTradeConverter.CreateOhlcAggregation(timeframe)); // var OhlcTicker := TConverter.CreateIdentity>; // var OhlcPoint := OhlcTicker.Sender; var Timestamps := OhlcPoint.Field('Time'); var Ohlc := OhlcPoint.Field('Data'); var Closes := Ohlc.Field('Close'); var Hull := Closes.Chain(THMA.CreateHMA(150)); var Sma := Closes.Chain(TSMA.CreateSMA(50)); var Ema := Closes.Chain(TEMA.CreateEMA(21)); var Boli := Closes.MakeParallel.Chain(TBollingerBands.CreateBollingerBands(20, 2.0)); var Rsi := Closes.Chain(TRSI.CreateRSI(14)); var Macd := Closes.MakeParallel.Chain(TMACD.CreateMACD(12, 26, 9)); var Stoch := Ohlc.Chain(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('UpperBand'), TAlphaColors.Gray); Panel.AddDoubleSeries(Boli.Field('MiddleBand'), TAlphaColors.Darkgray, 1.0); Panel.AddDoubleSeries(Boli.Field('LowerBand'), TAlphaColors.Gray); Panel := chart.AddPanel; Panel.AddDoubleSeries(Rsi, TAlphaColors.Fuchsia); Panel := chart.AddPanel; Panel.AddDoubleSeries(Macd.Field('MacdLine'), TAlphaColors.Orange); Panel.AddDoubleSeries(Macd.Field('SignalLine'), TAlphaColors.Dodgerblue); Panel.AddDoubleSeries(Macd.Field('Histogram'), TAlphaColors.Lightgreen); Panel := chart.AddPanel; Panel.AddDoubleSeries(Stoch.Field('K'), TAlphaColors.Green); Panel.AddDoubleSeries(Stoch.Field('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('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); } ///// 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; 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(tstStrat.Producer); var panel := pnlChart.AddPanel; panel.AddDoubleSeries(tstStrat.Producer, TAlphaColors.Blue, 3); end; end.