diff --git a/samples/WPF.Sample/App.xaml b/samples/WPF.Sample/App.xaml index 0a303af..dd67ae6 100644 --- a/samples/WPF.Sample/App.xaml +++ b/samples/WPF.Sample/App.xaml @@ -10,7 +10,7 @@ - + @@ -30,99 +31,186 @@ - \ No newline at end of file + diff --git a/samples/WPF.Sample/Services/Chart.cs b/samples/WPF.Sample/Services/Chart.cs index 5d20f74..dd0645b 100644 --- a/samples/WPF.Sample/Services/Chart.cs +++ b/samples/WPF.Sample/Services/Chart.cs @@ -1,6 +1,7 @@ -using CefSharp; +using CefSharp; using CefSharp.Wpf; using System.Collections.Generic; +using System.Globalization; using System.Linq; namespace Trading.UI.Sample.Services @@ -14,9 +15,15 @@ public class Chart : ChromiumWebBrowser, IChart { public void LoadData(string name, IEnumerable data) { - var dataStr = data.Select(ohlc => $"{{t: {ohlc.Time.ToUnixTimeMilliseconds()},o: {ohlc.Open},h: {ohlc.High},l: {ohlc.Low},c: {ohlc.Close}}}"); + // Format the numbers with InvariantCulture so they always use '.' as the decimal + // separator. With the current culture, cultures that use ',' (e.g. German) produced + // invalid JavaScript like "o: 1,2345", which threw a SyntaxError in createChart and + // left the chart blank. + var dataStr = data.Select(ohlc => string.Format(CultureInfo.InvariantCulture, + "{{t: {0},o: {1},h: {2},l: {3},c: {4}}}", + ohlc.Time.ToUnixTimeMilliseconds(), ohlc.Open, ohlc.High, ohlc.Low, ohlc.Close)); this.GetMainFrame().ExecuteJavaScriptAsync($"createChart('{name}',[{string.Join(',', dataStr)}]);"); } } -} \ No newline at end of file +} diff --git a/samples/WPF.Sample/Services/ChartingService.cs b/samples/WPF.Sample/Services/ChartingService.cs index 64b7eee..489b21e 100644 --- a/samples/WPF.Sample/Services/ChartingService.cs +++ b/samples/WPF.Sample/Services/ChartingService.cs @@ -35,7 +35,11 @@ public IChart GetChart() private void Browser_Loaded(object sender, System.Windows.RoutedEventArgs e) { - (sender as ChromiumWebBrowser).Address = Path.Combine(Environment.CurrentDirectory, "Chart.js", "chart.html"); + // Resolve relative to the app directory (not the current working directory, which can + // differ depending on how the app was launched) and hand CefSharp a proper file:// URI. + var chartHtmlPath = Path.Combine(AppContext.BaseDirectory, "Chart.js", "chart.html"); + + (sender as ChromiumWebBrowser).Address = new Uri(chartHtmlPath).AbsoluteUri; } } } \ No newline at end of file diff --git a/samples/WPF.Sample/ViewModels/ShellViewModel.cs b/samples/WPF.Sample/ViewModels/ShellViewModel.cs index e60a412..8defb2f 100644 --- a/samples/WPF.Sample/ViewModels/ShellViewModel.cs +++ b/samples/WPF.Sample/ViewModels/ShellViewModel.cs @@ -7,6 +7,7 @@ using Prism.Regions; using Prism.Services.Dialogs; using System; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Reactive.Linq; @@ -407,20 +408,31 @@ private async void OnAccountChanged() private async Task FillConversionSymbols(AccountModel account) { + // The conversion chain only depends on the symbol's quote asset (the deposit asset is + // constant for the account), so request each distinct quote asset once and reuse the + // result for every symbol that shares it. This turns hundreds of sequential network + // round-trips into a handful, which is the main driver of the account-load time. + var conversionSymbolsCache = new Dictionary>(); + foreach (var symbol in account.Symbols) { - if (symbol.QuoteAsset.AssetId != account.DepositAsset.AssetId) + if (symbol.QuoteAsset.AssetId == account.DepositAsset.AssetId) { - var conversionLightSymbols = await _apiService.GetConversionSymbols(account.Id, account.IsLive, symbol.QuoteAsset.AssetId, account.DepositAsset.AssetId); - - var conversionSymbolModels = conversionLightSymbols.Select(iLightSymbol => account.Symbols.FirstOrDefault(iSymbol => iSymbol.Id == iLightSymbol.SymbolId)).Where(iSymbol => iSymbol is not null); + symbol.ConversionSymbols.Add(symbol); - symbol.ConversionSymbols.AddRange(conversionSymbolModels); + continue; } - else + + if (conversionSymbolsCache.TryGetValue(symbol.QuoteAsset.AssetId, out var conversionSymbolModels) is false) { - symbol.ConversionSymbols.Add(symbol); + var conversionLightSymbols = await _apiService.GetConversionSymbols(account.Id, account.IsLive, symbol.QuoteAsset.AssetId, account.DepositAsset.AssetId); + + conversionSymbolModels = conversionLightSymbols.Select(iLightSymbol => account.Symbols.FirstOrDefault(iSymbol => iSymbol.Id == iLightSymbol.SymbolId)).Where(iSymbol => iSymbol is not null).ToArray(); + + conversionSymbolsCache[symbol.QuoteAsset.AssetId] = conversionSymbolModels; } + + symbol.ConversionSymbols.AddRange(conversionSymbolModels); } } diff --git a/samples/WPF.Sample/Views/ApiConfigurationView.xaml b/samples/WPF.Sample/Views/ApiConfigurationView.xaml index 720be08..70aaf66 100644 --- a/samples/WPF.Sample/Views/ApiConfigurationView.xaml +++ b/samples/WPF.Sample/Views/ApiConfigurationView.xaml @@ -14,8 +14,8 @@ mc:Ignorable="d"> @@ -27,50 +27,60 @@ - + - - + - + - +