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<TAuraAskBidFileItem>;
{$else}
        FServer: IDataServer<TOhlcItem>;
{$endif}
        FSymbols: TFuture<TArray<String>>;
        FTerminate: TEvent;
        FProcessDone: TState;
        FApplication: IAuraApplication;
        FModulesItem: TTreeViewItem;
        FFileCache: IDataFileCache<TArray<TScalarBatch>>;
        FFeed: IScalarBatchLoader;
        function SelectedSymbol: String;
        procedure ExecuteStrategy(const Symbol: String; Timeframe: TTimeframe; const Consumer: IConsumer<TDataPoint<TOhlcItem>>);

    public
        procedure NewWorkspace;
        function CurrLayout<T: TControl>: T;
        procedure AlignControl(Control: TControl);
        function CreateStrategy2(Timeframe: TTimeframe): IConsumer<TDataPoint<TOhlcItem>>;

    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<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;

    FFileCache := TDataFileCache<TArray<TScalarBatch>>.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<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;

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<TDataPoint<TOhlcItem>>;

    var Layout := CurrLayout<TVertScrollBox>;
    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<Double>(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<TVertScrollBox>;
    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 :=
        TScalarRecord.TRegistry.Intern(
            [
                TPair<IKeyword, TScalar.TKind>.Create(kTime, TScalar.TKind.DateTime),
                TPair<IKeyword, TScalar.TKind>.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 :=
        TScalarBatchLoader.Create(
            FFileCache,
            path,
            '.m1',
            true,
            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
        );

    // Converter: Transforms raw IRecordSeries (Columns) into TArray<TDataPoint>

    var unpacker := TScalarBatchLoader.CreateTicker(def);

    var ticker :=
        unpacker.Producer.Chain<TDataPoint<Double>>(
            function(const Tick: IScalarRecord): TDataPoint<Double>
            begin
                Result.Time := Tick[iTime].Value.Value.AsDouble;
                Result.Data := Tick[iClose].Value.Value.AsDouble;
            end
        );

    FFeed.BatchProducer.Chain(unpacker.Consumer);

    // Link Chart to Ticker
    chart
        .SetXAxisSeries(TTimeframe.M, ticker.Chain<TDateTime>(function(const p: TDataPoint<Double>): TDateTime begin Result := p.Time end));

    var panel := chart.AddPanel;
    panel.AddDoubleSeries(
        ticker.Chain<Double>(function(const p: TDataPoint<Double>): Double begin Result := p.Data end),
        TAlphaColors.Orange
    );

    // Connect pipeline: Server -> Converter
    FProcessDone := FFeed.ProcessData(symbol, terminated);
end;

function TForm1.CreateStrategy2(Timeframe: TTimeframe): IConsumer<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.Consumer;

    var OhlcPoint := ticker.Producer.Chain<TDataPoint<TOhlcItem>>(TTradeConverter.CreateOhlcAggregation(Timeframe));

    var Ohlc := OhlcPoint.Field<TOhlcItem>('Data');

    var Closes := Ohlc.Field<Double>('Close');

    var Hull := Closes.Chain<Double>(THMA.CreateHMA(250)).MakeParallel;
    var Sma := Closes.Chain<Double>(TSMA.CreateSMA(200)).MakeParallel;

    var Lowest: Double := Double.MaxValue;
    var Highest: Double := Double.MinValue;

    var ATR := Ohlc.Chain<Double>(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<Double>(jmAll, [Ohlc.Field<Double>('Low'), Ohlc.Field<Double>('High'), Closes, ATR, Hull, Sma]);

    var Signal :=
        TConverter<TArray<Double>, TSignal>.CreateConverter(
            function(const Values: TArray<Double>): 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<TSignal>(Signal);

    var pnl := Signal.Producer.Field<Double>('pnl');

    var FEquity: Double := 10000;
    var FInit: Boolean := false;

    var equity :=
        TConverter<Double, Double>.CreateAggregation(
            function(const Value: Double; const Broadcast: TBroadcastFunc<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
        );

    pnl.Chain<Double>(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'));

    panel := chart.AddPanel;
    panel.AddOhlcSeries(Ohlc);
    panel.AddDoubleSeries(Hull, TAlphaColors.Cornflowerblue, 2);
    panel.AddDoubleSeries(Sma, TAlphaColors.Brown, 1.5);
    panel.AddDoubleSeries(Signal.Producer.Field<Double>('Entry'), TAlphaColors.Green, 1);
    panel.AddDoubleSeries(Signal.Producer.Field<Double>('SL'), TAlphaColors.Red, 2);

    var mean := TConverter<TArray<Double>, Double>.CreateConverter(TMean.CreateMean());

    TConverter.Join<Double>(jmAll, [Hull, Sma]).Chain<Double>(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<Double>(equity.Producer);

    ////////////

    (*
    var Params: TEMA.TParam;
    Params.Period := 20;

    var indi := TEMA.CreateEMA(Params);

    var EMAConv := TConverter<TEMA.TInput, TEMA.TResult>.CreateConverter(indi);

    var equityEMA :=
        equity
            .Producer
            .Chain<TEMA.TInput>(function(const Value: Double): TEMA.TInput begin Result.Price := Value end)
            .Chain<TEMA.TResult>(EMAConv)
            .Chain<Double>(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>: 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<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.Producer.Link(Consumer);

    var Producer :=
        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
        );

    Producer.Producer.Link(ticker);

    FProcessDone := FProcessDone + FServer.ProcessData(Symbol, terminated, Producer);
{$else}

    var ticker := TConverter.CreateTicker<TDataPoint<TOhlcItem>>;

    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<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.H;

    var ticker := TConverter.CreateIdentity<TDataPoint<TOhlcItem>>;

    var OhlcPoint := ticker.Producer.Chain<TDataPoint<TOhlcItem>>(TTradeConverter.CreateOhlcAggregation(timeframe));

    //    var OhlcTicker := TConverter.CreateIdentity<TDataPoint<TOhlcItem>>;
    //    var OhlcPoint := OhlcTicker.Sender;

    var Timestamps := OhlcPoint.Field<TDateTime>('Time');
    var Ohlc := OhlcPoint.Field<TOhlcItem>('Data');
    var Closes := Ohlc.Field<Double>('Close');

    var Hull := Closes.Chain<Double>(THMA.CreateHMA(150));
    var Sma := Closes.Chain<Double>(TSMA.CreateSMA(50));
    var Ema := Closes.Chain<Double>(TEMA.CreateEMA(21));
    var Boli := Closes.MakeParallel.Chain<TBollingerBands.TResult>(TBollingerBands.CreateBollingerBands(20, 2.0));
    var Rsi := Closes.Chain<Double>(TRSI.CreateRSI(14));
    var Macd := Closes.MakeParallel.Chain<TMacd.TResult>(TMACD.CreateMACD(12, 26, 9));

    var Stoch := Ohlc.Chain<TStochastic.TResult>(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<Double>('UpperBand'), TAlphaColors.Gray);
    Panel.AddDoubleSeries(Boli.Field<Double>('MiddleBand'), TAlphaColors.Darkgray, 1.0);
    Panel.AddDoubleSeries(Boli.Field<Double>('LowerBand'), TAlphaColors.Gray);

    Panel := chart.AddPanel;
    Panel.AddDoubleSeries(Rsi, TAlphaColors.Fuchsia);

    Panel := chart.AddPanel;
    Panel.AddDoubleSeries(Macd.Field<Double>('MacdLine'), TAlphaColors.Orange);
    Panel.AddDoubleSeries(Macd.Field<Double>('SignalLine'), TAlphaColors.Dodgerblue);
    Panel.AddDoubleSeries(Macd.Field<Double>('Histogram'), TAlphaColors.Lightgreen);

    Panel := chart.AddPanel;
    Panel.AddDoubleSeries(Stoch.Field<Double>('K'), TAlphaColors.Green);
    Panel.AddDoubleSeries(Stoch.Field<Double>('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<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, 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<TVertScrollBox>;
    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<Double>(tstStrat.Producer);

    var panel := pnlChart.AddPanel;
    panel.AddDoubleSeries(tstStrat.Producer, TAlphaColors.Blue, 3);
end;

end.
