using System; using System.ComponentModel; using Abt.Controls.SciChart; using Abt.Controls.SciChart.Model.DataSeries; using MisterCharts.Charting; using MisterCharts.KDB; using MisterCharts.TechnicalIndicators; using MisterCharts.Trading; namespace MisterCharts.ViewModel { public abstract class BaseChartViewModel : INotifyPropertyChanged { #region INotifyPropertyChanged Members public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged(string propertyName) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } #endregion protected static readonly int DEFAULT_NUMBER_OF_BARS = 80; protected static readonly int VISUAL_RANGE_THRESHOLD = 4; protected static readonly int VISUAL_RANGE_DELTA = 2; #region Fields private readonly BaseChart _chart; protected IOhlcDataSeries _priceSeries; protected IXyDataSeries _volumeSeries; protected IndexRange _xVisibleRange; protected readonly string _chartGroup; protected MovingAverage _movingAverage; protected BollingerBands _bollingerBands; protected IXyDataSeries _lowerBandSeries; protected IXyDataSeries _middleBandSeries; protected IXyDataSeries _upperBandSeries; protected bool _isDataLoaded; protected string _errorMessage; #endregion protected BaseChartViewModel(BaseChart chart) { _chart = chart; _chartGroup = "ChartGroup" + chart.ChartID; IsDataRetrivalEnabled = true; _isDataLoaded = false; _errorMessage = null; XVisibleRange = new IndexRange(0, 1); } #region Methods public abstract int RetrieveDataAndInitializeChart(); public void InitializeChartData(NewKDBData data) { AppHelper.WriteLogMessage("{0} -> Initializing chart with {1}", Title, data); PriceData = new OhlcDataSeries(); PriceData.Append(data.Time, data.Open, data.High, data.Low, data.Close); VolumeData = new XyDataSeries(); VolumeData.Append(data.Time, data.Volume); int startIndex = PriceData.Count > DEFAULT_NUMBER_OF_BARS ? PriceData.Count - DEFAULT_NUMBER_OF_BARS : 0; XVisibleRange = new IndexRange(startIndex, PriceData.Count + VISUAL_RANGE_DELTA); AppHelper.WriteLogMessage(" |-> Visual range set to [{0},{1}]", _xVisibleRange.Min, _xVisibleRange.Max); IsDataLoaded = true; PriceData.InvalidateParentSurface(RangeMode.None); VolumeData.InvalidateParentSurface(RangeMode.None); } public void DequeueDataMessages() { if (_chart.DataQueue.Count > 0) { AppHelper.WriteLogMessage("{0} -> DequeueDataMessages({1})", Title, _chart.DataQueue.Count); DataMessage message; while (_chart.DataQueue.TryDequeue(out message)) { if (message.Type == DataType.TradeUpdate) { ProcessTrades((TradeUpdateMessage)message); } } } DoTradesPostProcessing(); } public abstract void ProcessTrades(TradeUpdateMessage tradeMessage); public abstract void DoTradesPostProcessing(); public void CheckAndUpdateXVisibleRange(int newDataPoints) { if (_xVisibleRange.Max + newDataPoints >= PriceData.Count - VISUAL_RANGE_THRESHOLD) { int difference = _xVisibleRange.Diff; int max = PriceData.Count + VISUAL_RANGE_DELTA; XVisibleRange = new IndexRange(max - difference, max); AppHelper.WriteLogMessage(" |-> XVisibleRange set to [{0}],{1}] for #DataSlots={2}", _xVisibleRange.Min, _xVisibleRange.Max, PriceData.Count); } } #endregion #region Technical Indicators public bool IsTechnicalIndicatorActive(TechnicalIndicatorType type) { return (type == TechnicalIndicatorType.MovingAverage && _movingAverage != null) || (type == TechnicalIndicatorType.BollingerBands && _bollingerBands != null); } public bool ActivateMovingAverage(MovingAverage movingAverage) { bool indicatorActivated = false; if (movingAverage != null) { IsDataRetrivalEnabled = false; AppHelper.WriteLogMessage("{0} -> Activating {1}", Title, movingAverage); _movingAverage = movingAverage; MiddleBandData = new XyDataSeries { SeriesName = "MA-" + movingAverage.Type }; int dataPointsProcessed = 0; for (int i = 0; i < PriceData.CloseValues.Count - 1; i++) { double value = PriceData.CloseValues[i]; if (double.IsNaN(value) == false) { _movingAverage.Push(value); MiddleBandData.Append(PriceData.XValues[i], _movingAverage.IsReady ? _movingAverage.Current : double.NaN); dataPointsProcessed++; } else { MiddleBandData.Append(PriceData.XValues[i], double.NaN); } } double lastValue = PriceData.CloseValues[PriceData.CloseValues.Count - 1]; if (double.IsNaN(lastValue) == false) { _movingAverage.Push(lastValue); } AppHelper.WriteLogMessage(" |-> Results: TotalDataSlots={0} DataPointsProcessed={1}", PriceData.CloseValues.Count, dataPointsProcessed); indicatorActivated = true; IsDataRetrivalEnabled = true; } else { AppHelper.WriteLogMessage("ERROR :: {0} -> Could not activate Moving Average (instance is null)", Title); } return indicatorActivated; } public bool ActivateBollingerBands(BollingerBands bollingerBands) { bool indicatorActivated = false; if (bollingerBands != null) { IsDataRetrivalEnabled = false; AppHelper.WriteLogMessage("{0} -> Activating {1}", Title, bollingerBands); _bollingerBands = bollingerBands; LowerBandData = new XyDataSeries { SeriesName = "BB-Lower" }; MiddleBandData = new XyDataSeries { SeriesName = "MA-" + bollingerBands.Type }; UpperBandData = new XyDataSeries { SeriesName = "BB-Upper" }; int dataPointsProcessed = 0; for (int i = 0; i < PriceData.CloseValues.Count - 1; i++) { double value = PriceData.CloseValues[i]; if (double.IsNaN(value) == false) { _bollingerBands.Push(value); if (_bollingerBands.IsReady) { MiddleBandData.Append(PriceData.XValues[i], _bollingerBands.MiddleValue); LowerBandData.Append(PriceData.XValues[i], _bollingerBands.LowerValue); UpperBandData.Append(PriceData.XValues[i], _bollingerBands.UpperValue); } else { MiddleBandData.Append(PriceData.XValues[i], double.NaN); LowerBandData.Append(PriceData.XValues[i], double.NaN); UpperBandData.Append(PriceData.XValues[i], double.NaN); } dataPointsProcessed++; } else { MiddleBandData.Append(PriceData.XValues[i], double.NaN); LowerBandData.Append(PriceData.XValues[i], double.NaN); UpperBandData.Append(PriceData.XValues[i], double.NaN); } } double lastValue = PriceData.CloseValues[PriceData.CloseValues.Count - 1]; if (double.IsNaN(lastValue) == false) { _bollingerBands.Push(lastValue); } AppHelper.WriteLogMessage(" |-> Results: TotalDataSlots={0} DataPointsProcessed={1}", PriceData.CloseValues.Count, dataPointsProcessed); indicatorActivated = true; IsDataRetrivalEnabled = true; } else { AppHelper.WriteLogMessage("ERROR :: {0} -> Could not activate Bollinger Bands (instance is null)", Title); } return indicatorActivated; } protected void UpdateTechnicalIndicators(DateTime intervalStart, Double value) { if (_movingAverage != null) { _movingAverage.Push(value); MiddleBandData.Append(intervalStart, _movingAverage.IsReady ? _movingAverage.Current : double.NaN); AppHelper.WriteLogMessage(" |-> MovingAverage: New data point added={0:F2}", _movingAverage.Current); } if (_bollingerBands != null) { _bollingerBands.Push(value); if (_bollingerBands.IsReady) { MiddleBandData.Append(intervalStart, _bollingerBands.MiddleValue); LowerBandData.Append(intervalStart, _bollingerBands.LowerValue); UpperBandData.Append(intervalStart, _bollingerBands.UpperValue); } else { MiddleBandData.Append(intervalStart, double.NaN); LowerBandData.Append(intervalStart, double.NaN); UpperBandData.Append(intervalStart, double.NaN); } AppHelper.WriteLogMessage(" |-> BollingerBands: New data point added=({0:F2},{1:F2},{2:F2})", _bollingerBands.LowerValue, _bollingerBands.MiddleValue, _bollingerBands.UpperValue); } } protected void InsertDataGapInTechnicalIndicators(DateTime intervalStart) { if (_movingAverage != null) { MiddleBandData.Append(intervalStart, double.NaN); } if (_bollingerBands != null) { LowerBandData.Append(intervalStart, double.NaN); MiddleBandData.Append(intervalStart, double.NaN); UpperBandData.Append(intervalStart, double.NaN); } } public void DeactivateTechnicalIndicators(TechnicalIndicatorType type) { AppHelper.WriteLogMessage("{0} -> Deactivating {1}", Title, type); if (type == TechnicalIndicatorType.MovingAverage) { _movingAverage = null; MiddleBandData = null; } if (type == TechnicalIndicatorType.BollingerBands) { _bollingerBands = null; LowerBandData = null; MiddleBandData = null; UpperBandData = null; } } #endregion #region Properties #region Chart public string Title { get { return _chart.Title; } } public IOhlcDataSeries PriceData { get { return _priceSeries; } set { _priceSeries = value; OnPropertyChanged("PriceData"); } } public IXyDataSeries LowerBandData { get { return _lowerBandSeries; } set { _lowerBandSeries = value; OnPropertyChanged("LowerBandData"); } } public IXyDataSeries MiddleBandData { get { return _middleBandSeries; } set { _middleBandSeries = value; OnPropertyChanged("MiddleBandData"); } } public IXyDataSeries UpperBandData { get { return _upperBandSeries; } set { _upperBandSeries = value; OnPropertyChanged("UpperBandData"); } } public IXyDataSeries VolumeData { get { return _volumeSeries; } set { _volumeSeries = value; OnPropertyChanged("VolumeData"); } } public IndexRange XVisibleRange { get { return _xVisibleRange; } set { if (!Equals(value, _xVisibleRange)) { _xVisibleRange = value; OnPropertyChanged("XVisibleRange"); } } } public string ChartGroup { get { return _chartGroup; } } public bool DisplayVolume { get { return _chart.DisplayVolume; } } public bool IsDataLoaded { get { return _isDataLoaded; } set { if (value != _isDataLoaded) { _isDataLoaded = value; OnPropertyChanged("IsDataLoaded"); } } } public bool IsDataRetrivalEnabled { get; protected set; } #endregion #region Overlay public MarketDailyIndicators Indicators { get; protected set; } public string ErrorMessage { get { return _errorMessage; } set { if (value != _errorMessage) { _errorMessage = value; OnPropertyChanged("ErrorMessage"); } } } #endregion #endregion } }