using System; using System.Drawing; using System.Windows.Forms; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using QuantConnect.Interfaces; using QuantConnect.Logging; using QuantConnect.Messaging; using QuantConnect.Packets; using Gecko; using Gecko.JQuery; namespace QuantConnect.Views.WinForms { public partial class LeanWinForm : Form, IDesktopMessageHandler { private readonly GeckoWebBrowser _geckoBrowser; private readonly WebBrowser _monoBrowser; private readonly QueueLogHandler _logging; private bool _liveMode = false; private AlgorithmNodePacket _job; /// /// Create the UX. /// public LeanWinForm() { InitializeComponent(); //Form Setup: CenterToScreen(); WindowState = FormWindowState.Maximized; Text = "QuantConnect Lean Algorithmic Trading Engine: v" + Globals.Version; //GECKO WEB BROWSER: Create the browser control // https://www.nuget.org/packages/GeckoFX/ // -> If you don't have IE. #if !__MonoCS__ Gecko.Xpcom.Initialize(); _geckoBrowser = new GeckoWebBrowser { Dock = DockStyle.Fill, Name = "browser" }; splitPanel.Panel1.Controls.Add(_geckoBrowser); #else // MONO WEB BROWSER: Create the browser control // Default shipped with VS and Mono. Works OK in Windows, and compiles in linux. _monoBrowser = new WebBrowser() {Dock = DockStyle.Fill, Name = "Browser"}; splitPanel.Panel1.Controls.Add(_monoBrowser); #endif _logging = new QueueLogHandler(); } /// /// This method is called when a new job is received. /// /// The job that is being executed public void Initialize(AlgorithmNodePacket job) { _job = job; //Show warnings if the API token and UID aren't set. if (_job.UserId == 0) { MessageBox.Show("Your user id is not set. Please check your config.json file 'job-user-id' property.", "LEAN Algorithmic Trading", MessageBoxButtons.OK, MessageBoxIcon.Error); } if (_job.Channel == "") { MessageBox.Show("Your API token is not set. Please check your config.json file 'api-access-token' property.", "LEAN Algorithmic Trading", MessageBoxButtons.OK, MessageBoxIcon.Error); } _liveMode = job is LiveNodePacket; var url = GetUrl(job, _liveMode); #if !__MonoCS__ _geckoBrowser.Navigate(url); #else _monoBrowser.Navigate(url); #endif } /// /// Displays the Backtest results packet /// /// Backtest results public void DisplayBacktestResultsPacket(BacktestResultPacket packet) { if (packet.Progress != 1) return; //Remove previous event handler: var url = GetUrl(_job, _liveMode, true); //Generate JSON: var jObj = new JObject(); var dateFormat = "yyyy-MM-dd HH:mm:ss"; dynamic final = jObj; final.dtPeriodStart = packet.PeriodStart.ToString(dateFormat); final.dtPeriodFinished = packet.PeriodFinish.AddDays(1).ToString(dateFormat); dynamic resultData = new JObject(); resultData.version = 3; resultData.results = JObject.FromObject(packet.Results); resultData.statistics = JObject.FromObject(packet.Results.Statistics); resultData.iTradeableDates = 1; resultData.ranking = null; final.oResultData = resultData; var json = JsonConvert.SerializeObject(final); //GECKO RESULT SET: #if !__MonoCS__ _geckoBrowser.DOMContentLoaded += (sender, args) => { var executor = new JQueryExecutor(_geckoBrowser.Window); executor.ExecuteJQuery("window.jnBacktest = JSON.parse('" + json + "');"); executor.ExecuteJQuery("$.holdReady(false)"); }; _geckoBrowser.Navigate(url); #else //MONO WEB BROWSER RESULT SET: _monoBrowser.DocumentCompleted += (sender, args) => { if (_monoBrowser.Document == null) return; _monoBrowser.Document.InvokeScript("eval", new object[] { "window.jnBacktest = JSON.parse('" + json + "');" }); _monoBrowser.Document.InvokeScript("eval", new object[] { "$.holdReady(false)" }); }; _monoBrowser.Navigate(url); #endif foreach (var pair in packet.Results.Statistics) { _logging.Trace("STATISTICS:: " + pair.Key + " " + pair.Value); } } /// /// Display a handled error /// public void DisplayHandledErrorPacket(HandledErrorPacket packet) { var hstack = (!string.IsNullOrEmpty(packet.StackTrace) ? (Environment.NewLine + " " + packet.StackTrace) : string.Empty); _logging.Error(packet.Message + hstack); } /// /// Display a runtime error /// public void DisplayRuntimeErrorPacket(RuntimeErrorPacket packet) { var rstack = (!string.IsNullOrEmpty(packet.StackTrace) ? (Environment.NewLine + " " + packet.StackTrace) : string.Empty); _logging.Error(packet.Message + rstack); } /// /// Display a log packet /// public void DisplayLogPacket(LogPacket packet) { _logging.Trace(packet.Message); } /// /// Display a debug packet /// /// public void DisplayDebugPacket(DebugPacket packet) { _logging.Trace(packet.Message); } /// /// Get the URL for the embedded charting /// /// Job packet for the URL /// Is this a live mode chart? /// Hold the ready signal to inject data private static string GetUrl(AlgorithmNodePacket job, bool liveMode = false, bool holdReady = false) { var url = ""; var hold = holdReady == false ? "0" : "1"; var embedPage = liveMode ? "embeddedLive" : "embedded"; url = string.Format( "https://www.quantconnect.com/terminal/{0}?user={1}&token={2}&pid={3}&version={4}&holdReady={5}&bid={6}", embedPage, job.UserId, job.Channel, job.ProjectId, Globals.Version, hold, job.AlgorithmId); return url; } /// /// Update the status label at the bottom of the form /// private void timer_Tick(object sender, EventArgs e) { StatisticsToolStripStatusLabel.Text = string.Concat("Performance: CPU: ", OS.CpuUsage.ToString("0.0"), "%", " Ram: ", OS.TotalPhysicalMemoryUsed, " Mb"); if (_logging == null) return; LogEntry log; while (_logging.Logs.TryDequeue(out log)) { switch (log.MessageType) { case LogType.Debug: LogTextBox.AppendText(log.ToString(), Color.Black); break; default: case LogType.Trace: LogTextBox.AppendText(log.ToString(), Color.Black); break; case LogType.Error: LogTextBox.AppendText(log.ToString(), Color.DarkRed); break; } } } /// /// Closing the form exit the LEAN engine too. /// /// /// private void LeanWinForm_FormClosed(object sender, FormClosedEventArgs e) { Log.Trace("LeanWinForm(): Form closed."); #if !__MonoCS__ _geckoBrowser.Dispose(); #endif Environment.Exit(0); } } }