콘텐츠로 이동

cTrader에서 WebView 사용

WebView는 차트나 지표 출력 창에 웹 페이지를 표시하는 데 사용할 수 있는 컨트롤입니다.

WebView는 Microsoft Edge WebView2를 기반으로 합니다. 결과적으로 모든 현대 웹 표준과 완벽하게 호환됩니다.

또한 페이지에서 JavaScript 코드를 실행하고 WebView를 통해 결과를 얻을 수도 있습니다.

참고

WebView는 .NET 6 이상의 지표 및 cBot에서만 작동합니다.

cTrader에서 WebView 사용

예를 들어, cTrader.com 웹사이트를 창과 메인 차트 내부에 표시합니다.

 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/");
        }
    }
}

아래 지표의 인스턴스를 생성한 후, cTrader.com의 다양한 웹 페이지 사이를 탐색할 수 있습니다. 선택한 출력 창은 웹 브라우저와 유사하게 동작합니다.

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

스크립트 사용 및 메시지 이벤트 처리

WebView 클래스에는 ExecuteScript() 메서드와 WebMessageReceived 이벤트라는 두 가지 강력한 멤버가 있습니다. 이를 사용하여 원하는 웹 페이지(예: Forex 뉴스 피드 또는 사용자 정의 HTML이 포함된 페이지)에 접근하고, 이 페이지에서 사용자 정의 JavaScript를 실행하며, WebView에서 알고로 전송된 메시지를 올바르게 처리하는 알고를 만들 수 있습니다.

스크립트 실행

WebView 내부에서 사용자 정의 스크립트를 실행하려면 아래와 같이 WebView.ExecuteScript(String javaScript) 메서드를 호출하기만 하면 됩니다.

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

WebView 내부에서 실행해야 하는 스크립트는 ExecuteString() 메서드에 문자열로 전달할 수 있습니다.

이 문자열은 정확한 JavaScript를 포함해야 합니다. 예를 들어, 모든 실행 가능한 줄 뒤에 세미콜론을 포함해야 합니다.

다중 줄 JavaScript

아래 예제와 같이 다중 줄 JavaScript를 ExecuteScript() 메서드에 자유롭게 전달할 수 있습니다(문자열 리터럴 사용 참조).

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

메시지 이벤트 처리

WebMessageReceived 이벤트는 WebView에 표시된 콘텐츠가 메시지를 보낼 때 발생합니다. 이 이벤트에 반응하려면 적절한 이벤트 핸들러를 구독하면 됩니다.

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}");
}

WebView에서 메시지를 보내려면 HTML 페이지 내부의 JavaScript 코드에서 window.postMessage()를 호출하기만 하면 됩니다. 예를 들어, 아래 예제의 스크립트를 전달하여 두 숫자(예: 포지션의 가능한 진입 및 청산 가격) 사이의 백분율 차이를 계산할 수 있습니다.

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

변수 및 문자열 보간

ExecuteScript() 메서드를 호출할 때 C#과 JavaScript 사이의 컨텍스트는 공유되지 않습니다. C# 범위에서 JavaScript 범위로 변수를 전달하려면 문자열 보간을 사용해야 합니다.

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

WebView 내부에 컨트롤 배치

ExecuteScript()OnWebMessageReceived를 결합하여 WebView를 통해 열린 페이지 내부에 컨트롤을 배치하고, 상호 작용 시 메시지를 cBot으로 다시 보낼 수 있습니다. 아래 예제에서는 이 기능을 사용하여 WebView 내부에서 직접 거래할 수 있도록 합니다. WebView는 기존 리소스가 아닌 사용자 정의 HTML 페이지를 엽니다.

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

WebView를 통해 cBot은 새로운 주문을 할 수 있는 컨트롤이 포함된 사용자 정의 HTML 페이지를 엽니다. SendSymbolsToHtml() 메서드를 사용하여 cBot은 HTML 내부의 드롭다운 메뉴를 계정이 거래할 수 있는 모든 심볼 목록으로 채웁니다. cBot이 실행되는 동안 사용자는 심볼을 선택하고 주문 거래량을 지정한 후 웹 페이지 내부에서 직접 새로운 주문을 할 수 있습니다.