using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Linq; using System.Windows; using System.Windows.Forms; using DevExpress.StockMarketTrader.Model; using DevExpress.StockMarketTrader.Model.BusinessObjects; using DevExpress.StockMarketTrader.StockDataServiceReference; using Application = System.Windows.Forms.Application; namespace DevExpress.StockMarketTrader.ViewModel { public class RealTimeDataViewModel : ViewModelBase { public static bool IsReady = false; protected static void InvokeSync(Delegate method) { if (method != null && Application.OpenForms.Count > 0) { for(int i = 0; i < Application.OpenForms.Count; i++) { Form form = Application.OpenForms[i]; if(form is StockMarketView) form.Invoke(method, new object[] { }); } } } object sem = new object(); string selectedCompanyName = "MSFT"; int volumeCount = 15; int countOfWeek = 1; int candlesCount = 72, currentDate = 0, tickCount = 0, offlineTimeInterval = 500, totalTicks = 5; bool isAllBindingDataUpdate = false, canEndUpdate = true, canUpdate = true; double highestPrice = 0, lowestPrice = 0; public RealTimeDataModel model; Dictionary> defferedUpdate = new Dictionary>(); List dates; List companies; LockableCollection transactionGridBindingData; LockableCollection watchLisBindingData; BindingList volumeChartBindingData; BindingList stockChartBindingData; CompanyTradingDataViewModel selectedCompany; ObservableCollection oldWatchListData; LiveTileViewModel sparklineTileViewModel = new LiveTileViewModel(); List topThreeCompanies = new List() { new LiveTileViewModel(), new LiveTileViewModel(), new LiveTileViewModel() }; EventHandler currentPriceIndexChanged; EventHandler watchListChanged; public event EventHandler CurrentPriceIndexChanged { add { currentPriceIndexChanged += value; } remove { currentPriceIndexChanged -= value; } } public event EventHandler WatchListChanged { add { watchListChanged += value; } remove { watchListChanged -= value; } } Timer TimerCore { get; set; } public int CurrentPriceIndex { get; set; } public int Ticks { get { return totalTicks; } set { totalTicks = value; } } public int TickCount { get { return tickCount; } set { tickCount = value; } } public int CandlesCount { get { return candlesCount; } set { candlesCount = value; } } public bool IsSuspendUpdating { get; set; } public bool IsLoading { get; set; } public string NetworkState { get { return model.NetworkState; } } public CompanyTradingDataViewModel SelectedCompany { get { return selectedCompany; } set { if (value != null) { if(selectedCompanyName != value.CompanyName || selectedCompany != value) { List result = watchLisBindingData.Where(e => e.CompanyName == value.CompanyName).Select(e => e).ToList(); if (result.Count > 0) { selectedCompanyName = value.CompanyName; selectedCompany = result[0]; SetSelectedCompany(); } } } OnPropertyChanged("SelectedCompany"); } } public LiveTileViewModel SparklineTileViewModel { get { return sparklineTileViewModel; } set { sparklineTileViewModel = value; OnPropertyChanged("SparklineTileViewModel"); } } public List TopThreeCompanies { get { return topThreeCompanies; } set { topThreeCompanies = value; OnPropertyChanged("TopThreeCompanies"); } } public LockableCollection WatchListBindingData { get { return watchLisBindingData; } } public BindingList StockChartBindingData { get { return stockChartBindingData; } } public LockableCollection TransactionGridBindingData { get { return transactionGridBindingData; } } public BindingList VolumeChartBindingData { get { return volumeChartBindingData; } } public RealTimeDataViewModel(Timer timer) { TimerCore = timer; TimerCore.Interval = offlineTimeInterval; TimerCore.Tick += new EventHandler(UpdateOnTimer); model = new RealTimeDataModel(); model.UpdateFailed += new EventHandler(OnUpdateFailed); model.Initialized += new EventHandler(OnInitialized); stockChartBindingData = new BindingList(); watchLisBindingData = new LockableCollection(); transactionGridBindingData = new LockableCollection(); volumeChartBindingData = new BindingList(); IsLoading = true; } void OnInitialized(object sender, EventArgs e) { GetDatesAsync(); } void OnUpdateFailed(object sender, RealTimeDataEventArgs e) { } void OnCandlesCountChangedCallBack() { CorrectCurrentDate(); tickCount = 0; candlesCount = CandlesCount + 1; StopTimer(); UpdateAllBindingData(); } void CorrectCurrentDate() { int delta = currentDate; delta = currentDate - (CandlesCount + 1) * Ticks; if (delta > -1) currentDate = delta; else { delta = currentDate - candlesCount * totalTicks; currentDate = delta < 0 ? 0 : delta; } } //void ChangeTimerInterval(int time) { // TimerCore.Interval = time; //} void StartTimer() { InvokeSync(new Action(() => { TimerCore.Start(); })); } void StopTimer() { InvokeSync(new Action(() => { TimerCore.Stop(); })); } void GetDatesAsync() { SetDates(model.Service.GetDates()); //Action action = new Action(SetDates); //AddDefferedDelegate("GetDates", action); //model.BeginGetDates(); } void SetDates(DateTime[] dates) { try { if (dates.Length == 0) throw new Exception(); this.dates = new List(dates); GetCompaniesAsync(); canEndUpdate = true; } catch { canUpdate = false; } } void InitializeChartBindingData() { isAllBindingDataUpdate = true; stockChartBindingData.Clear(); } void GetCompaniesAsync() { SetCompanies(model.Service.GetCompaniesName()); } void SetCompanies(string[] arrayCompanies) { if(model.IsOnline) { this.companies = new List(); for(int i = 0; i < arrayCompanies.Length; i++) this.companies.Add(arrayCompanies[i]); } else this.companies = new List(arrayCompanies); InitializeChartBindingData(); GetBindingDataAsync(); } void GetVolumeDynamicsDataAsyncCompleted2(StockData[] data) { } void GetBindingDataAsync() { GetWatchListBindingDataAsync(); GetCompaniesVolumeAsync(); currentDate = 0; GetVolumeDynamicsDataAsync(); GetStockChartBindingDataAsync(); } void GetVolumeDynamicsDataAsync(int current = 0) { if (companies == null) return; int date = current + totalTicks * ((candlesCount - volumeCount) + 1); GetVolumeDynamicsDataAsyncCompleted(model.Service.GetStockDataFromPeriodByCompanyList(date, volumeCount, totalTicks, companies.ToArray())); } void GetVolumeDynamicsDataAsyncCompleted(CompanyStockData[] cd) { foreach (CompanyStockData companyStockData in cd) { CompanyTradingDataViewModel vm = watchLisBindingData.Where(c => c.CompanyName == companyStockData.CompanyName).Select(c => c).FirstOrDefault(); vm.VolumeDynamics = CreateVolumeDynamicsBindingData(companyStockData.Data); } TryEndUpdateBindingData(); } void GetWatchListBindingDataAsync() { currentDate += candlesCount * totalTicks; UpdateWatchListBindingDataAsyncCompleted(model.Service.GetStockDataByDate(dates[currentDate - 1])); } void GetStockChartBindingDataAsync() { GetStockChartBindingDataAsyncCompleted(model.Service.GetCompanyMultipleDataFromPeriod(currentDate, candlesCount, totalTicks, selectedCompanyName)); } void GetStockChartBindingDataAsyncCompleted(CompanyData[] data) { List newStockChartData = new List(); try { newStockChartData = new List(data); } catch { } AddChartPoints(newStockChartData); } void AddChartPoints(List newStockChartData) { CreateChartBindingData(newStockChartData); UpdateSparklineViewModelSource(); isAllBindingDataUpdate = false; TryEndUpdateBindingData(); } void UpdateSparklineViewModelSource() { SparklineTileViewModel.Source = stockChartBindingData; } void UpdateWatchListBindingDataAsync() { if (dates == null) return; if(currentDate >= 0 && currentDate < dates.Count) UpdateWatchListBindingDataAsyncCompleted(model.Service.GetStockDataByDate(dates[currentDate])); else UpdateWatchListBindingDataAsyncCompleted(model.Service.GetStockDataByDate(dates.FirstOrDefault())); } void UpdateCompaniesVolumeAsync() { if (tickCount == totalTicks || tickCount == 0) GetCompaniesVolumeAsync(); } void UpdateWatchListBindingDataAsyncCompleted(StockData[] gridData) { UpdateWatchList(gridData); UpdateSparklineViewModelCompany(); UpdateTopThreeCompany(); TryEndUpdateBindingData(); } void UpdateTopThreeCompany() { var topThree = GetTopThreeVolume(); if (topThree.Count < 3) return; var result = new List(); foreach (CompaniesVolumeData volumeData in topThree) { LiveTileViewModel vm = new LiveTileViewModel(); vm.CompanyIndex = volumeData.CompanyName; double delta = 0; double increace = CalcIncrease(volumeData.CompanyName, ref delta); vm.Arrow = increace > 0 ? "▲" : "▼"; vm.Persent = Math.Abs(increace) * 100 + "%"; vm.PriceIncrease = Math.Abs(Math.Round(delta, 3)).ToString(); result.Add(vm); } TopThreeCompanies = result; } void GetCompaniesVolumeAsync() { int endIndex = currentDate - totalTicks; if (dates != null && currentDate < dates.Count && endIndex > 0) { DateTime start = dates[currentDate]; DateTime end = dates[endIndex]; GetCompaniesVolumeCompleted(model.Service.GetCompaniesVolumeFromPeriod(start, end)); } } void GetCompaniesVolumeCompleted(CompaniesVolumeData[] data) { UpdateVolumeChartBindingData(data); } void UpdateVolumeChartBindingData(CompaniesVolumeData[] data) { volumeChartBindingData.Clear(); SetVolumeOfSelectedCompany(); int index = 1; for (int i = 0; i < data.Length - 1; i++) { if (data[i].CompanyName == selectedCompanyName) continue; if (volumeChartBindingData.Count < data.Length + 1) volumeChartBindingData.Add(data[i]); index++; } ValidateCompaniesVolume(); TryEndUpdateBindingData(); } void UpdateSparklineViewModelCompany() { SparklineTileViewModel.CompanyIndex = SelectedCompany?.CompanyName; double delta = 0; double increace = CalcIncrease(SelectedCompany?.CompanyName, ref delta); SparklineTileViewModel.Arrow = increace > 0 ? "▲" : "▼"; SparklineTileViewModel.Persent = Math.Abs(increace) * 100 + "%"; } void ValidateCompaniesVolume() { List volumeCompanies = volumeChartBindingData.Select(c => c.CompanyName).ToList(); List exceptCompanies = companies.Except(volumeCompanies).ToList(); for (int i = 0; i < volumeChartBindingData.Count; i++) { CompaniesVolumeData volumeData = volumeChartBindingData[i]; if (!companies.Contains(volumeData.CompanyName)) { var res = watchLisBindingData.Where(c => exceptCompanies.Contains(c.CompanyName)).ToList(); if (res.Count > 0) { double maxVolume = res.Max(c => c.Volume); CompanyTradingDataViewModel vm = watchLisBindingData.Where(c => c.Volume == maxVolume).Select(c => c).FirstOrDefault(); exceptCompanies.Remove(vm.CompanyName); volumeChartBindingData[i] = new CompaniesVolumeData() { CompanyName = vm.CompanyName, Volume = (int)maxVolume }; } } } } void SetVolumeOfSelectedCompany() { if (volumeChartBindingData.Count == 0 && SelectedCompany != null) { CompaniesVolumeData cvd = new CompaniesVolumeData(); cvd.CompanyName = SelectedCompany.CompanyName; cvd.Volume = (int)SelectedCompany.Volume; volumeChartBindingData.Add(cvd); } else if (SelectedCompany != null) { volumeChartBindingData[0].CompanyName = SelectedCompany.CompanyName; volumeChartBindingData[0].Volume = (int)SelectedCompany.Volume; } } void TryEndUpdateBindingData() { if (canEndUpdate) EndUpdateBindingData(); } void EndUpdateBindingData() { if (!TimerCore.Enabled && !isAllBindingDataUpdate) StartTimer(); } void UpdateOnTimer(object sender, EventArgs e) { if (!IsSuspendUpdating) UpdateData(); } void UpdateAllBindingData(bool needUpdateVolumeDynamics = true) { if (canUpdate) { InitProperties(); model.ChangeUserState(); defferedUpdate.Clear(); tickCount = 0; if (needUpdateVolumeDynamics) GetVolumeDynamicsDataAsync(currentDate); UpdateCompaniesVolumeAsync(); UpdateWatchListBindingDataAsync(); InitializeChartBindingData(); GetStockChartBindingDataAsync(); } } void UpdateData() { lock (sem) { int dateLocal = currentDate; if (dates == null || dateLocal < 0 || dateLocal > dates.Count -1) return; StopTimer(); DateTime date = dates[dateLocal]; switch (totalTicks) { case 5: RemoveOldCompanyStockChart(date, 1); break; case 10: RemoveOldCompanyStockChart(date, 2); break; case 20: RemoveOldCompanyStockChart(date, 4); break; } UpdateBindingData(); } } void RemoveOldCompanyStockChart(DateTime date, int count) { if (date.DayOfWeek == DayOfWeek.Monday && countOfWeek == count) { tickCount = 0; if (stockChartBindingData != null && stockChartBindingData.Count > 1) stockChartBindingData.Remove(stockChartBindingData[0]); foreach (CompanyTradingDataViewModel vm in watchLisBindingData) { if (vm.VolumeDynamics != null && ((IList)vm.VolumeDynamics).Count > 0) ((IList)vm.VolumeDynamics).RemoveAt(0); } return; } } void UpdateBindingData() { UpdateStockChartBindingDataAsync(); UpdateWatchListBindingDataAsync(); UpdateCompaniesVolumeAsync(); UpdateVolumeDynamicsAsync(); UpdateTransactionBindingData(); } void UpdateVolumeDynamicsAsync() { if (companies != null) UpdateVolumeDynamicsAsyncCompleted(model.Service.GetStockDataFromDateByCompanyList(dates[currentDate], companies.ToArray())); } void UpdateVolumeDynamicsAsyncCompleted(CompanyStockData[] cd) { foreach (CompanyStockData companynStockData in cd) { try { if (companynStockData.Data.Length == 0) continue; DateTime current = dates[currentDate]; CompanyTradingData ctd = GetCompanyTradingData(companynStockData.Data[0], current); CompanyTradingDataViewModel vm = watchLisBindingData.Where(c => c.CompanyName == companynStockData.CompanyName).Select(c => c).FirstOrDefault(); if (vm == null) continue; IList volDyn = vm.VolumeDynamics as IList; if (vm.VolumeDynamics == null || ((IList)vm.VolumeDynamics).Count == 0) return; if (tickCount == 2) { ctd.Volume = Math.Round((ctd.Volume / 1000000), 2); volDyn.Add(new TradingDataViewModel(ctd)); } if (tickCount != 2) { TradingDataViewModel tdvm = new TradingDataViewModel(volDyn[volDyn.Count - 1].TradingData); tdvm.Volume = Math.Round((ctd.Volume / 1000000), 2); tdvm.Close = ctd.Close; tdvm.Low = lowestPrice; tdvm.High = highestPrice; tdvm.Price = ctd.Price; volDyn[volDyn.Count - 1] = tdvm; } } catch { currentDate++; UpdateStockChartBindingDataAsync(); } } TryEndUpdateBindingData(); } void UpdateStockChartBindingDataAsync() { UpdateStockChartBindingDataAsyncCompleted(model.Service.GetCompanyStockData(dates[currentDate], selectedCompanyName)); } void UpdateStockChartBindingDataAsyncCompleted(StockData[] data) { if (data.Length == 0) { IncrementDateAndUpdateStockChartBindingData(); } else { try { UpdateStockChart(data[0]); UpdateSparklineViewModelSource(); TryEndUpdateBindingData(); } catch { IncrementDateAndUpdateStockChartBindingData(); } } } void IncrementDateAndUpdateStockChartBindingData() { currentDate++; UpdateStockChartBindingDataAsync(); } void UpdateStockChart(StockData newChartData) { tickCount++; DateTime current = dates[currentDate]; CompanyTradingData ctd = GetCompanyTradingData(newChartData, current); switch (totalTicks) { case 5: AddStockChart(current, ctd, 1); break; case 10: AddStockChart(current, ctd, 2); break; default: AddStockChart(current, ctd, 4); break; } currentDate++; } void AddStockChart(DateTime current, CompanyTradingData ctd, int count) { if (current.DayOfWeek == DayOfWeek.Monday && countOfWeek == count) { ctd.Volume = Math.Round((ctd.Volume / 1000000), 2); stockChartBindingData.Add(new TradingDataViewModel(ctd)); highestPrice = ctd.High; lowestPrice = ctd.Low; countOfWeek = 1; return; } if (current.DayOfWeek == DayOfWeek.Monday && countOfWeek != count) { SetStockChartBindingData(ctd); countOfWeek++; return; } if (current.DayOfWeek != DayOfWeek.Monday) SetStockChartBindingData(ctd); } void TryResetCurrentDate() { if (dates != null && currentDate + totalTicks > dates.Count - 1) { currentDate = 0; tickCount = 0; } } void CreateChartBindingData(List newStockChartData) { TryResetCurrentDate(); int index = 0; DateTime firstDate = newStockChartData[0].Data.Date; DateTime startDate = new DateTime(firstDate.Year, firstDate.Month, 1); foreach (CompanyData candleData in newStockChartData) { CompanyTradingData ctd = new CompanyTradingData(candleData.Data, ""); ctd.Close = candleData.ClosePrice; ctd.Volume = Math.Round(((double)ctd.Volume / 1000000), 2); ctd.Low = lowestPrice = candleData.LowPrice; ctd.High = highestPrice = candleData.HighPrice; TradingDataViewModel viewModel = new TradingDataViewModel(ctd) { Date = startDate }; if (stockChartBindingData.Count < candlesCount) { stockChartBindingData.Add(viewModel); } else stockChartBindingData[index] = viewModel; startDate = UpdateDate(startDate, true); index++; } DateTime search = UpdateDate(startDate, false); while (true) { int dateIndex = dates.IndexOf(search); if (dateIndex != -1) { currentDate = dateIndex; break; } search = search.AddDays(-1); } } void UpdateWatchList(StockData[] watchListData) { int count = 0; int isRise = 0; oldWatchListData = new ObservableCollection(watchLisBindingData as IEnumerable); oldWatchListData = new ObservableCollection(); foreach (CompanyTradingDataViewModel item in watchLisBindingData) { oldWatchListData.Add(new CompanyTradingDataViewModel(item.TradingData, item.CompanyName, item.Rise, item.TotalRise)); } watchLisBindingData.BeginUpdate(); foreach (StockData dt in watchListData) { if (companies.Count <= (dt.CompanyID)) continue; CompanyTradingData ctd = new CompanyTradingData(dt, companies[dt.CompanyID]); if (watchLisBindingData.Count < companies.Count) watchLisBindingData.Add(new CompanyTradingDataViewModel(ctd, ctd.CompanyName, isRise, 1)); else { SetWatchListBindingData(count, ctd); count++; } } watchLisBindingData.EndUpdate(); if (watchListChanged != null) watchListChanged(this, EventArgs.Empty); } void SetWatchListBindingData(int index, CompanyTradingData ctd) { watchLisBindingData[index].Rise = 0; if (ctd.Price > watchLisBindingData[index].Price) { watchLisBindingData[index].TotalRise = Math.Min(0.5 + (1 - watchLisBindingData[index].Price / ctd.Price) * 10, 1.3); watchLisBindingData[index].Rise = 180; } else if (ctd.Price < watchLisBindingData[index].Price) { watchLisBindingData[index].TotalRise = Math.Min(0.5 + (1 - ctd.Price / watchLisBindingData[index].Price) * 10, 1.3); watchLisBindingData[index].Rise = -180; } watchLisBindingData[index].Assign(ctd); } void SetStockChartBindingData(CompanyTradingData ctd) { TradingDataViewModel tdvm = null; if (stockChartBindingData.Count > 0) tdvm = new TradingDataViewModel(stockChartBindingData[stockChartBindingData.Count - 1].TradingData); else tdvm = new TradingDataViewModel(); highestPrice = Math.Max(highestPrice, ctd.High); lowestPrice = Math.Min(lowestPrice, ctd.Low); tdvm.Volume = Math.Round((ctd.Volume / 1000000), 2); tdvm.Close = ctd.Close; tdvm.Low = lowestPrice; tdvm.High = highestPrice; tdvm.Price = ctd.Price; if (stockChartBindingData.Count > 0) stockChartBindingData[stockChartBindingData.Count - 1] = tdvm; else stockChartBindingData.Add(tdvm); } void UpdateTransactionBindingData() { TryResetCurrentDate(); if (stockChartBindingData.Count < 1) return; List transactions = model.GetTransactions(stockChartBindingData[stockChartBindingData.Count - 1].Price); SetTransactionGridBindingData(transactions); IsLoading = false; OnPropertyChanged("IsLoading"); } void SetTransactionGridBindingData(List transactions) { transactionGridBindingData.Clear(); transactionGridBindingData.BeginUpdate(); foreach (TransactionData tdvm in transactions) { transactionGridBindingData.Add(tdvm); } transactionGridBindingData.EndUpdate(); CurrentPriceIndex = model.CurrentPriceIndex; RaiseCurrentPriceIndexChanged(); } void RaiseCurrentPriceIndexChanged() { if (currentPriceIndexChanged != null) currentPriceIndexChanged(this, EventArgs.Empty); } void SetSelectedCompany() { StopTimer(); int delta = currentDate - candlesCount * totalTicks; currentDate = delta < 0 ? 0 : delta; UpdateAllBindingData(true); } void InitProperties() { TryResetCurrentDate(); isAllBindingDataUpdate = true; IsLoading = true; model.ClearTransactions(); OnPropertyChanged("IsLoading"); } double CalcIncrease(string company, ref double delta) { var companyData = watchLisBindingData.Where(c => c.CompanyName == company).Select(c => c).FirstOrDefault(); var oldCompanyData = oldWatchListData.Where(c => c.CompanyName == company).Select(c => c).FirstOrDefault(); if (companyData != null && oldCompanyData != null) { double newPrice = companyData.Price; double oldPrice = oldCompanyData.Price; delta = newPrice - oldPrice; return Math.Round(delta / newPrice, 3); } return 0; } CompanyTradingData GetCompanyTradingData(StockData newData, DateTime current) { CompanyTradingData ctd = new CompanyTradingData(newData, ""); return ctd; } DateTime UpdateDate(DateTime startDate, bool add) { DateTime returnDate = new DateTime(); switch (totalTicks) { case 1: if (add) startDate = startDate.AddDays(1); if (!add) returnDate = startDate.AddDays(-1); break; case 5: if (add) startDate = startDate.AddDays(7); if (!add) returnDate = startDate.AddDays(-7); break; case 10: if (add) startDate = startDate.AddDays(14); if (!add) returnDate = startDate.AddDays(-14); break; case 20: if (add) startDate = startDate.AddMonths(1); if (!add) returnDate = startDate.AddMonths(-1); break; } if (add) return startDate; return returnDate; } Delegate GetDefferedDelegate(string key) { if (defferedUpdate.ContainsKey(key) && defferedUpdate[key].Count != 0) return defferedUpdate[key].Dequeue(); return null; } List GetTopThreeVolume() { List result = new List(); if (volumeChartBindingData != null && volumeChartBindingData.Count > 0) { for (int i = 0; i < 3; i++) if (volumeChartBindingData.Count >= i + 1) result.Add(volumeChartBindingData[i]); } return result; } LockableCollection CreateVolumeDynamicsBindingData(StockData[] data) { LockableCollection result = new LockableCollection(); foreach (StockData currentData in data) { CompanyTradingData ctd = new CompanyTradingData(currentData, ""); ctd.Close = double.Parse(currentData.CloseP.ToString()); ctd.Volume = Math.Round(((double)ctd.Volume / 1000000), 2); ctd.Low = double.Parse(currentData.LowP.ToString()); ctd.High = double.Parse(currentData.HighP.ToString()); result.Add(new TradingDataViewModel(ctd)); } return result; } public void OnCandlesCountChanged() { OnCandlesCountChangedCallBack(); } public void SetTicks(int numberOfTicks) { Ticks = numberOfTicks; countOfWeek = 1; } } }