Ir para o conteúdo

WebView no cTrader

O WebView é um controlo que pode ser utilizado para exibir uma página web num gráfico ou numa janela de saída de indicador.

O WebView é baseado no Microsoft Edge WebView2. Como resultado, é totalmente compatível com todos os padrões web modernos.

Também pode executar código JavaScript numa página e obter os resultados através do WebView.

Nota

O WebView só funciona em indicadores e cBots .NET 6 ou posteriores.

Utilizar o WebView no cTrader

Como exemplo, iremos exibir o website cTrader.com dentro de uma janela e do gráfico principal.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
using cAlgo.API;

namespace cAlgo.Robots
{
    [Indicator(AccessRights = AccessRights.None, IsOverlay = true)]
    public class WebViewOnWindow : Indicator
    {
        private WebView _webView;

        [Parameter("On Window?", DefaultValue = false)]
        public bool OnWindow { get; set; }

        protected override void Initialize()
        {
            _webView = new WebView
            {
                DefaultBackgroundColor = Color.Red
            };

            _webView.Loaded += OnWebViewLoaded;

            if (OnWindow)
            {
                var window = new Window
                {
                    Child = _webView
                };

                window.Show();
            }
            else
            {
                Chart.AddControl(_webView);
            }           
        }

        public override void Calculate(int index)
        {
        }

        private void OnWebViewLoaded(WebViewLoadedEventArgs args)
        {
            _webView.NavigateAsync("https://ctrader.com/");
        }
    }
}

Após criar uma instância do indicador abaixo, poderá navegar entre várias páginas web no cTrader.com. A sua janela de saída escolhida irá comportar-se de forma semelhante a um navegador web.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
using cAlgo.API;

namespace cAlgo.Robots
{
    [Robot(AccessRights = AccessRights.None)]
    public class WebViewOnWindow : Robot
    {
        private WebView _webView;

        private TextBox _addressTextBox;

        private TextBox _scriptTextbox;

        private Button _executeScriptButton;

        protected override void OnStart()
        {
            var goBackButton = new Button
            {
                Text = "←",
                Margin = 3
            };

            goBackButton.Click += OnGoBackButtonClick;

            var goForwardButton = new Button
            {
                Text = "→",
                Margin = 3
            };

            goForwardButton.Click += OnGoForwardButtonClick;

            _addressTextBox = new TextBox
            {
                Text = "https://ctrader.com/",
                Margin = 3,
                Width = 150,
                MinWidth = 150,
                MaxWidth = 150
            };

            var goButton = new Button
            {
                Text = "→",
                Margin = 3
            };

            goButton.Click += OnGoButtonClick;

            var reloadButton = new Button
            {
                Text = "Reload",
                Margin = 3
            };

            reloadButton.Click += OnReloadButtonClick;

            var stopButton = new Button
            {
                Text = "x",
                Margin = 3
            };

            stopButton.Click += OnStopButtonClick;

            _scriptTextbox = new TextBox
            {
                Text = "alert('Hi');",
                Margin = 3,
                Width = 150,
                MinWidth = 150,
                MaxWidth = 150,
                IsEnabled = false
            };

            _executeScriptButton = new Button
            {
                Text = "Execute Script",
                Margin = 3,
                IsEnabled = false
            };

            _executeScriptButton.Click += OnExecuteScriptButtonClick;

            var addressBarPanel = new StackPanel
            {
                MaxHeight = 50,
                VerticalAlignment = VerticalAlignment.Top,
                BackgroundColor = Color.Black,
                Orientation = Orientation.Horizontal
            };

            addressBarPanel.AddChild(goBackButton);
            addressBarPanel.AddChild(goForwardButton);
            addressBarPanel.AddChild(_addressTextBox);
            addressBarPanel.AddChild(goButton);
            addressBarPanel.AddChild(reloadButton);
            addressBarPanel.AddChild(stopButton);
            addressBarPanel.AddChild(_scriptTextbox);
            addressBarPanel.AddChild(_executeScriptButton);

            _webView = new WebView
            {
                DefaultBackgroundColor = Color.Red
            };

            _webView.NavigationCompleted += OnWebViewNavigationCompleted;
            _webView.WebMessageReceived += OnWebViewWebMessageReceived;
            _webView.Loaded += OnWebViewLoaded;
            _webView.Unloaded += OnWebViewUnloaded;

            var mainGrid = new Grid(2, 1);

            mainGrid.Rows[0].SetHeightToAuto();
            mainGrid.Rows[1].SetHeightInStars(1);

            mainGrid.AddChild(addressBarPanel, 0, 0);
            mainGrid.AddChild(_webView, 1, 0);

            var window = new Window
            {
                Child = mainGrid
            };

            window.Show();
        }

        private void OnWebViewLoaded(WebViewLoadedEventArgs args)
        {
            Print($"Webview loaded, IsLoaded: {args.WebView.IsLoaded}");

            _webView.NavigateAsync(_addressTextBox.Text);
        }

        private void OnWebViewUnloaded(WebViewUnloadedEventArgs args)
        {
            Print($"Webview unloaded, IsLoaded: {args.WebView.IsLoaded}");
        }

        private void OnStopButtonClick(ButtonClickEventArgs args)
        {
            _webView.StopAsync();
        }

        private void OnExecuteScriptButtonClick(ButtonClickEventArgs args)
        {
            var result = _webView.ExecuteScript(_scriptTextbox.Text);

            Print($"IsSuccessful: {result.IsSuccessful} | Json: {result.Json}");
        }

        private void OnReloadButtonClick(ButtonClickEventArgs args)
        {
            _webView.ReloadAsync();
        }

        private void OnGoForwardButtonClick(ButtonClickEventArgs args)
        {
            _webView.GoForwardAsync();
        }

        private void OnGoBackButtonClick(ButtonClickEventArgs args)
        {
            _webView.GoBackAsync();
        }

        private void OnGoButtonClick(ButtonClickEventArgs args)
        {
            _webView.NavigateAsync(_addressTextBox.Text);
        }

        private void OnWebViewWebMessageReceived(WebViewWebMessageReceivedEventArgs args)
        {
            Print($"Source: {args.Source} | Message: {args.Message}");
        }

        private void OnWebViewNavigationCompleted(WebViewNavigationCompletedEventArgs args)
        {
            Print($"{args.HttpStatusCode} | {args.IsSuccessful} | {args.Url}");

            _addressTextBox.Text = args.Url;
            _scriptTextbox.IsEnabled = true;
            _executeScriptButton.IsEnabled = true;
        }
    }
}

Utilizar scripts e lidar com eventos de mensagem

A classe WebView tem dois membros poderosos, nomeadamente o método ExecuteScript() e o evento WebMessageReceived. Pode utilizá-los para criar algos que acedam a uma página web à sua escolha (por exemplo, um feed de notícias Forex ou qualquer página com HTML personalizado), executem JavaScript personalizado nesta página e lidem corretamente com quaisquer mensagens enviadas do WebView de volta para o algo.

Executar scripts

Para executar um script personalizado dentro do seu WebView, tudo o que precisa de fazer é chamar o método WebView.ExecuteScript(String javaScript) como mostrado abaixo.

1
2
private _webView = new WebView();
_webView.ExecuteScript("document.getElementById('nextButton').style.display = 'none';");

O script que precisa de ser executado dentro do WebView pode simplesmente ser passado como uma string para o método ExecuteString().

Note que esta string tem de conter JavaScript totalmente preciso. Por exemplo, tem de incluir um ponto e vírgula após cada linha executável.

JavaScript de múltiplas linhas

Pode passar livremente JavaScript de múltiplas linhas para o método ExecuteScript() como mostrado no exemplo abaixo (note a utilização de um literal de string).

1
2
3
4
_webView.ExecuteScript(@"
const element = document.getElementById('nextButton');
element.style.display = 'none';
"); 

Lidar com eventos de mensagem

O evento WebMessageReceived é acionado quando o conteúdo exibido num WebView envia uma mensagem. Pode reagir a este evento subscrevendo qualquer manipulador de eventos adequado.

1
2
3
4
5
6
private _webView = new WebView();
_webView.WebMessageReceived += OnWebMessageReceived;
private void OnWebMessageReceived(WebViewWebMessageReceivedEventArgs args)
{
    Print($"Source: {args.Source} | Message: {args.Message}");
}

Para enviar uma mensagem de um WebView, tudo o que tem de fazer é chamar window.postMessage() a partir do código JavaScript dentro da página HTML. Por exemplo, pode passar o script no exemplo abaixo para calcular a diferença percentual entre dois números (como possíveis preços de entrada e saída para uma posição).

1
2
3
4
5
function percentChange(oldVal, newVal) {
  let change = ((newVal - oldVal) / oldVal) * 100;
  window.postMessage('Change: ${change}%');
}
percentChange(1.0784, 1.0975);

Variáveis e interpolação de strings

O contexto não é partilhado entre C# e JavaScript quando chama o método ExecuteScript(). Se quiser passar uma variável do âmbito C# para o âmbito JavaScript, terá de utilizar interpolação de strings.

1
2
var a = 2 + 2;
_webView.ExecuteScript($"window.postMessage({a});");

Colocar controlos dentro de um WebView

É possível combinar ExecuteScript() e OnWebMessageReceived para colocar controlos dentro de uma página aberta via WebView que, ao interagir, enviam mensagens de volta para o cBot. No exemplo abaixo, utilizamos esta funcionalidade para permitir negociar diretamente dentro do WebView. Note que o WebView abre uma página HTML personalizada em vez de um recurso existente.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using cAlgo.API;
using cAlgo.API.Collections;
using cAlgo.API.Indicators;
using cAlgo.API.Internals;
using System.Text.Json;

namespace cAlgo.Robots
{
    [Robot(AccessRights = AccessRights.None)]
    public class WebViewInteractionsExample : Robot
    {
        private WebView _webView;

        protected override void OnStart()
        {
            var window = new Window();           
            _webView = new WebView();

            _webView.Loaded += WebView_Loaded;
            _webView.NavigationCompleted += WebView_NavigationCompleted;
            _webView.WebMessageReceived += WebView_WebMessageReceived;

            window.Child = _webView;
            window.Width = 500;
            window.Height = 500;
            window.Show();
            window.Closed += args => Stop(); // stop cBot when window is closed
        }

        private void WebView_WebMessageReceived(WebViewWebMessageReceivedEventArgs args)
        {
            Print("Message received from WebView: {0}", args.Message);

            var tradeMessage = JsonSerializer.Deserialize<TradeMessage>(args.Message);;
            var tradeType = tradeMessage.side == "buy" ? TradeType.Buy : TradeType.Sell;

            ExecuteMarketOrder(tradeType, tradeMessage.symbolName, tradeMessage.volume);
        }

        private void WebView_Loaded(WebViewLoadedEventArgs args)
        {
            args.WebView.NavigateToStringAsync(@"
            <script>
                function setSymbols(symbols) // called from the cBot
                {
                const symbolSelector = document.getElementById('symbolHtml');
                symbolSelector.innerHTML = '';
                symbols.forEach((symbol) => {  
                    symbolSelector.add(new Option(symbol));
                });
                }

                function trade(operation)
                {
                const symbolSelector = document.getElementById('symbolHtml');
                const selectedSymbolName = symbolSelector.options[symbolSelector.selectedIndex].text;

                window.postMessage({
                    side: operation,
                    volume: Number(document.getElementById('volumeHtml').value),
                    symbolName: selectedSymbolName
                }); //send message to cBot
                }
            </script>

            <body bgcolor='white'>
            <div style='display:flex;flex-direction:column;width:300px'>
                <h1>This is HTML page</h1>
                <div>Symbol:</div>
                <select id='symbolHtml'></select>
                <span style='margin-top: 10px'>Volume:</span>
                <input id='volumeHtml' value='1000' />
                <div style='margin-top:10px'>
                    <input type='button' value='Buy' onclick='trade(`buy`)' />
                    <input type='button' value='Sell' onclick='trade(`sell`)' />
                </div>
            </div>
            </body>
        ");          
        }

        private void WebView_NavigationCompleted(WebViewNavigationCompletedEventArgs obj)
        {
            SendSymbolsToHtml();
        }

        private void SendSymbolsToHtml()
        {
            var allSymbols = "";
            foreach (var symbol in Symbols)
            {
                allSymbols = allSymbols + "'" + symbol + "',";
            }

            _webView.ExecuteScript("setSymbols([" + allSymbols + "]);"); // invokes the corresponding function inside HTML
        }        
    }

    class TradeMessage
    {
        public string side { get; set; }
        public double volume { get; set; }
        public string symbolName { get; set; }
    }
}

Através do WebView, o cBot abre uma página HTML personalizada contendo controlos que nos permitem colocar novas ordens. Utilizando o método SendSymbolsToHtml(), o cBot preenche um menu dropdown dentro do HTML com uma lista de todos os símbolos que uma conta pode negociar. Enquanto o cBot está em execução, o utilizador pode selecionar um símbolo, especificar o volume da ordem e depois colocar uma nova ordem diretamente dentro da página web sem nunca interagir com controlos dentro do cTrader.