// *************************************************************************************
// SCICHART® Copyright SciChart Ltd. 2011-2021. All rights reserved.
//
// Web: http://www.scichart.com
// Support: support@scichart.com
// Sales: sales@scichart.com
//
// CreateMultiPaneStockChartsViewModel.cs is part of the SCICHART® Examples. Permission is hereby granted
// to modify, create derivative works, distribute and publish any part of this source
// code whether for commercial, private or personal use.
//
// The SCICHART® examples are distributed in the hope that they will be useful, but
// without any warranty. It is provided "AS IS" without warranty of any kind, either
// expressed or implied.
// *************************************************************************************
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Input;
using System.Windows.Media;
using apiwrappercli;
using apiwrappercli.poco.order;
using SPTrader.BusinessLayer;
using SPTrader.Models;
using SPTrader.Views;
using SciChart.Charting;
using SciChart.Charting.Common.Helpers;
using SciChart.Charting.Model.ChartSeries;
using SciChart.Charting.ViewportManagers;
using SciChart.Charting.Visuals.Annotations;
using SciChart.Charting.Visuals.TradeChart;
using SciChart.Core.Framework;
using SciChart.Data.Model;
using SciChart.Examples.Examples.AnnotateAChart.OverlayTradeMarkers;
using SciChart.Examples.ExternalDependencies.Common;
using SciChart.Examples.ExternalDependencies.Data;
using SPTrader.Common;
using System.Data;
using System.Collections.ObjectModel;
using SciChart.Charting.DrawingTools.TradingAnnotations.ViewModels;
// Separate each class in their own files, as they are partial class of
// ChartAnalysisProControlViewModel
namespace SPTrader.ViewModels
{
// SciChart ViewModel Annotations
public partial class ChartAnalysisProControlViewModel : SPBindableBase
{
private bool _rolloverEnabled = false;
private bool _cursorEnabled = true;
private bool _isPanEnabled;
private bool _isZoomEnabled;
public ICommand ZoomModeCommand { get; private set; }
public ICommand PanModeCommand { get; private set; }
public ICommand ZoomExtentsCommand { get; private set; }
public ICommand CursorModeCommand { get; private set; }
public ICommand RolloverModeCommand { get; private set; }
private Dictionary<string, ObservableCollection<IAnnotationViewModel>> CurrentAnnotationMap = new Dictionary<string, ObservableCollection<IAnnotationViewModel>>();
private SPCurrentPriceAnnotationViewModel currentPriceAnno = null;
public Action OnAnnotationDrawn = null;
private VerticalLineAnnotationViewModel cutoffAnno = null;
private void InitAnnotations()
{
ZoomModeCommand = new ActionCommand(SetZoomMode);
PanModeCommand = new ActionCommand(SetPanMode);
ZoomExtentsCommand = new ActionCommand(ZoomExtends);
CursorModeCommand = new ActionCommand(SetCursorMode);
RolloverModeCommand = new ActionCommand(SetRolloverMode);
}
private void ZoomExtends()
{
//_viewportManager.AnimateZoomExtents(TimeSpan.FromMilliseconds(500));
_viewportManager.ZoomExtents();
}
public bool IsPanEnabled
{
get { return _isPanEnabled; }
set { SetProperty(ref _isPanEnabled, value); }
}
public bool IsZoomEnabled
{
get { return _isZoomEnabled; }
set { SetProperty(ref _isZoomEnabled, value); }
}
public bool CursorEnabled
{
get { return _cursorEnabled; }
set { SetProperty(ref _cursorEnabled, value); }
}
public bool RolloverEnabled
{
get { return _rolloverEnabled; }
set { SetProperty(ref _rolloverEnabled, value); }
}
private void SetPanMode()
{
IsPanEnabled = true;
IsZoomEnabled = false;
}
private void SetZoomMode()
{
IsPanEnabled = false;
IsZoomEnabled = true;
}
protected void SetCursorMode()
{
CursorEnabled = true;
RolloverEnabled = false;
}
protected void SetRolloverMode()
{
CursorEnabled = false;
RolloverEnabled = true;
}
public ICommand SetTradingAnnotationCreationTypeCommand
{
get { return new ActionCommand<Type>(SetTradingAnnotationTypeExecute); }
}
public ICommand SetRegularAnnotationCreationTypeCommand
{
get { return new ActionCommand<Type>(SetRegularAnnotationTypeExecute); }
}
public ICommand DeleteAnnotationCommand
{
get { return new ActionCommand(DeleteSelectedAnnotation); }
}
public ICommand DeleteAllAnnotationCommand
{
get { return new ActionCommand(DeleteAllAnnotation); }
}
private void DeleteSelectedAnnotation()
{
var sel = ChartPaneViewModels[0].SelectedAnnotation;
if (sel == null)
sel = ChartPaneViewModels[0].AnnotationViewModels.FirstOrDefault(x => x.IsSelected == true);
ChartPaneViewModels[0].AnnotationViewModels.Remove(sel);
}
private void DeleteAllAnnotation()
{
var annos = ChartPaneViewModels[0].AnnotationViewModels;
var annolist = new List<IAnnotationViewModel>(annos);
foreach (var anno in annolist)
{
if (anno is SPCurrentPriceAnnotationViewModel) // skip current price anno
continue;
if (anno is VerticalLineAnnotationViewModel) // skip cutoff anno
{
if (anno.StyleKey == "CutOffLineStyle")
continue;
}
ChartPaneViewModels[0].AnnotationViewModels.Remove(anno);
}
}
public void ToggleShowAllAnnotations(bool flag)
{
var annos = ChartPaneViewModels[0].AnnotationViewModels;
var annolist = new List<IAnnotationViewModel>(annos);
foreach (var anno in annolist)
{
if (anno is SPCurrentPriceAnnotationViewModel) // skip current price anno
continue;
if (anno is VerticalLineAnnotationViewModel) // skip cutoff anno
{
if (anno.StyleKey == "CutOffLineStyle")
continue;
}
if (flag)
anno.IsHidden = false;
else
anno.IsHidden = true;
}
}
public void ToggleShowCutOffAnnotations(bool flag)
{
var annos = ChartPaneViewModels[0].AnnotationViewModels;
var annolist = new List<IAnnotationViewModel>(annos);
foreach (var anno in annolist)
{
if (anno is VerticalLineAnnotationViewModel)
{
if (anno.StyleKey == "CutOffLineStyle")
{
if (flag)
anno.IsHidden = false;
else
anno.IsHidden = true;
}
}
}
}
private void SetTradingAnnotationTypeExecute(Type type)
{
IsPanEnabled = false;
IsZoomEnabled = false;
ChartPaneViewModels[0].SetTradingAnnotationTypeExecute(type);
}
private void SetRegularAnnotationTypeExecute(Type type)
{
IsPanEnabled = false;
IsZoomEnabled = false;
ChartPaneViewModels[0].SetRegularAnnotationTypeExecute(type);
}
private void AddCutoffLineAnnotation(DateTime dt, int idx)
{
var anno = new VerticalLineAnnotationViewModel
{
//CoordinateMode = AnnotationCoordinateMode.RelativeY,
X1 = idx,
Y1 = 0,
Y2 = 1,
StyleKey = "CutOffLineStyle",
Tooltip = dt.ToLocalTime().ToString(),
};
ChartPaneViewModels[0].AnnotationViewModels.Add(anno);
}
private void AddCutoffLineAnnotation(ChartDataSeries prices)
{
var dd = prices.CutoffData;
for (int i = 0; i < dd.Count; i++)
{
if (dd[i] == 1)
{
var dt = prices.TimeData[i];
AddCutoffLineAnnotation(dt, i);
}
}
}
public string EcodeAnnotationToBase64<T>()
{
if (ChartPaneViewModels.Count == 0)
return string.Empty;
var annolist = ChartPaneViewModels[0].AnnotationViewModels.Where(x => x.GetType().Name == typeof(T).Name);
var annoSave = new List<IAnnotationViewModel>();
foreach (var anno in annolist)
{
if (anno is SPCurrentPriceAnnotationViewModel) // skip current price anno
continue;
if (anno is VerticalLineAnnotationViewModel) // skip cutoff anno
{
if (anno.StyleKey == "CutOffLineStyle")
continue;
}
var t1 = anno.GetType();
var t2 = typeof(T);
if (t1.Name.Equals(t2.Name))
annoSave.Add(anno);
}
if (annoSave.Count == 0)
return string.Empty;
return annoSave.ToBase64();
}
public void AddSavedAnnotations()
{
ChartPaneViewModels[0].AnnotationViewModels.Clear();
var uid_key = SPCLI.Instance.GetUniqueLoggedInUserId();
var dt = SQLiteChartAnno.Instance.SelectAnnotationRecord(uid_key, prodCode);
var result = dt.AsEnumerable();
string[] b64_anno = new string[20];
foreach (var res in result)
{
var dd = res.ItemArray;
//dd[0]; // id
//dd[1]; // uid_key
//dd[2]; // prodCode
b64_anno[0] = Convert.ToString(dd[3]); // LineAnno
b64_anno[1] = Convert.ToString(dd[4]); // BoxAnno
b64_anno[2] = Convert.ToString(dd[5]); // TextAnno
b64_anno[3] = Convert.ToString(dd[6]); // AxisAnno
b64_anno[4] = Convert.ToString(dd[7]); // LineArrowAnno
b64_anno[5] = Convert.ToString(dd[8]); // HorAnno
b64_anno[6] = Convert.ToString(dd[9]); // VerAnno
b64_anno[7] = Convert.ToString(dd[10]); // BrushAnno
b64_anno[8] = Convert.ToString(dd[11]); // HaSAnno
b64_anno[9] = Convert.ToString(dd[12]); // FibRAnno
b64_anno[10] = Convert.ToString(dd[13]); // FibEAnno
b64_anno[11] = Convert.ToString(dd[14]); // ElliotAnno
b64_anno[12] = Convert.ToString(dd[15]); // PitchAnno
b64_anno[13] = Convert.ToString(dd[16]); // XABCDAnno
break;
}
AddSavedAnnotations<LineAnnotationViewModel>(b64_anno[0]);
AddSavedAnnotations<BoxAnnotationViewModel>(b64_anno[1]);
AddSavedAnnotations<TextAnnotationViewModel>(b64_anno[2]);
AddSavedAnnotations<AxisMarkerAnnotationViewModel>(b64_anno[3]);
AddSavedAnnotations<LineArrowAnnotationViewModel>(b64_anno[4]);
AddSavedAnnotations<HorizontalLineAnnotationViewModel>(b64_anno[5]);
AddSavedAnnotations<VerticalLineAnnotationViewModel>(b64_anno[6]);
AddSavedAnnotations<BrushAnnotationViewModel>(b64_anno[7]);
AddSavedAnnotations<HeadAndShouldersAnnotationViewModel>(b64_anno[8]);
AddSavedAnnotations<FibonacciRetracementAnnotationViewModel>(b64_anno[9]);
AddSavedAnnotations<FibonacciExtensionAnnotationViewModel>(b64_anno[10]);
AddSavedAnnotations<ElliotWaveAnnotationViewModel>(b64_anno[11]);
AddSavedAnnotations<PitchforkAnnotationViewModel>(b64_anno[12]);
AddSavedAnnotations<XabcdAnnotationViewModel>(b64_anno[13]);
}
private void AddSavedAnnotations<T>(string b64)
{
if (!string.IsNullOrEmpty(b64))
{
var annoList = b64.FromBase64<List<T>>();
foreach (var anno in annoList)
{
if (anno is T)
{
var annovm = (IAnnotationViewModel) Convert.ChangeType(anno, typeof(T));
ChartPaneViewModels[0].AnnotationViewModels.Add(annovm);
}
}
}
}
public void AddCurrentPriceAnnotation(double price)
{
currentPriceAnno = new SPCurrentPriceAnnotationViewModel(price)
{
CoordinateMode = AnnotationCoordinateMode.RelativeX,
X1 = 0, // adjust the start position of SPLine head
X2 = 1,
Y1 = price,
Y2 = price,
IsEditable = false,
DragDirections = XyDirection.YDirection
};
ChartPaneViewModels[0].AnnotationViewModels.Add(currentPriceAnno);
}
public void ToggleCurrentPriceAnnotation(bool flag)
{
if (flag)
currentPriceAnno.IsHidden = true;
else
currentPriceAnno.IsHidden = false;
}
public void UpdateCurrentPriceAnnotation(double price)
{
if (currentPriceAnno == null)
{
AddCurrentPriceAnnotation(price);
return;
}
currentPriceAnno.Y1 = price;
currentPriceAnno.Y2 = price;
currentPriceAnno.AdjustMarker(price);
}
public void AddBuyMarkerAnnotation()
{
var trade = new TradeEx()
{
BuySell = OrderSideEnum.Buy,
Price = 29451.0,
Quantity = 5,
ProdCode = "Buy Test",
TradeDate = DateTime.Now.AddDays(-3)
};
var anno = new BuyMarkerAnnotationViewModel()
{
TradeData = trade,
X1 = trade.TradeDate,
Y1 = trade.Price
};
ChartPaneViewModels[0].AnnotationViewModels.Add(anno);
}
public void AddSellMarkerAnnotation()
{
var trade = new TradeEx()
{
BuySell = OrderSideEnum.Sell,
Price = 29551.0,
Quantity = 5,
ProdCode = "Sell Test",
TradeDate = DateTime.Now.AddDays(-1)
};
var anno = new SellMarkerAnnotationViewModel()
{
TradeData = trade,
X1 = trade.TradeDate,
Y1 = trade.Price
};
ChartPaneViewModels[0].AnnotationViewModels.Add(anno);
}
public void AddNewsMarkerAnnotation(string header, string msg)
{
var news = new NewsEvent()
{
Headline = header,
EventDate = DateTime.Now.AddHours(-1),
Body = msg
};
var anno = new NewsBulletAnnotationViewModel()
{
NewsData = news,
X1 = news.EventDate
};
ChartPaneViewModels[0].AnnotationViewModels.Add(anno);
}
}
// SciChart ViewModel Events
public partial class ChartAnalysisProControlViewModel : SPBindableBase, TickerDataListener
{
protected IEventAggregator ea = EventService.Instance.EventAggregator;
protected SPApiProxyWrapper apiProxy { get; private set; }
private string prevProd = string.Empty;
private IUpdateSuspender suspender = null;
private SPChartData spchartdata = new SPChartData();
private string prodCode = string.Empty;
private int selectedTimeIndex;
private int decInPrice = 0;
private ChartDataSeries pseries = null;
private ChartDataOHLC price_last = null;
private BaseRenderableSeriesViewModel chart_series = null;
private IndexRange _xAxisVisibleRange;
private ObservableCollection<SPBaseChartPaneViewModel> _chartPaneViewModels = new ObservableCollection<SPBaseChartPaneViewModel>();
private string _verticalChartGroupId;
private IViewportManager _viewportManager;
private ChartAnalysisProControl parentControl = null;
public Action<string> OnUpdateProd = null;
public Action<string> OnPeriodChange = null;
public ICommand TimeSequencerCommand { get { return new ActionCommand<string>(OnTimePeriodChange); } }
public ChartAnalysisProControlViewModel(ChartAnalysisProControl parent)
{
parentControl = parent;
apiProxy = SPCLI.Instance.ApiProxyWrapper;
InitAnnotations();
// following will render the chart and any changes to style/interval
chart_series = new CandlestickRenderableSeriesViewModel();
// ChartGroup is an ID which is used to synchronize chart pane heights and mouse events. it must be unique per SciChartGroup, but differ if you have many top level SciChartGroups
_verticalChartGroupId = Guid.NewGuid().ToString();
}
public void OnTimePeriodChange(string duration)
{
OnPeriodChange?.Invoke(duration);
}
public void RenderChartForProduct(string prod, int time_idx)
{
if (string.IsNullOrEmpty(prod))
return;
prodCode = prod;
OnUpdateProd?.Invoke(prodCode);
var product = apiProxy.MarketDataService.GetProductData(prod);
parentControl.tbProdName.Text = product.StaticData.ProductName;
decInPrice = product.StaticData.DecInPrice;
apiProxy.MarketDataService.UnsubscribeTicker(prevProd, this); // move to Close
apiProxy.MarketDataService.SubscribeTicker(prodCode, this);
prevProd = prodCode;
RenderChartAtTime(time_idx);
}
public void RenderChartAtTime(int time_idx)
{
int intsec = 60;
switch (time_idx)
{
case 0: intsec = 5; break; // 5s
case 1: intsec = 30; break; // 30s
case 2: intsec = 60; break; // 1m
case 3: intsec = 300; break; // 5m
case 4: intsec = 600; break; // 10m
case 5: intsec = 900; break; // 15m
case 6: intsec = 1800; break; // 30m
case 7: intsec = 3600; break; // 1h
case 8: intsec = 7200; break; // 2h
case 9: intsec = 86400; break; // 1d
case 10: intsec = 604800; break; // 1w
case 11: intsec = 2592000; break; // 1month
case 12: intsec = 31536000; break; // 1y
default: intsec = 86400; break; // 1d
}
RenderNewChart(intsec);
}
private async void RenderNewChart(int intsec)
{
if (string.IsNullOrEmpty(prodCode))
return;
//// save current annotations
//if (_chartPaneViewModels.Count > 0)
//{
// CurrentAnnotationMap[prevProd] = new ObservableCollection<IAnnotationViewModel>(_chartPaneViewModels[0].AnnotationViewModels);
//}
price_last = null; // reset and block ticker push until we get chart data
try
{
var sw = new Stopwatch();
sw.Start();
pseries = await spchartdata.GetChartDataSeriesAsync(prodCode, intsec, 0);
sw.Stop();
Log.Information("Chart: Query took: {0} seconds", sw.ElapsedMilliseconds / 1000.0);
}
catch (Exception e)
{
Log.Information("EXCEPTION: GetChartDataSeriesAsync {0}", e.StackTrace);
return;
}
_chartPaneViewModels.Clear();
try
{
_viewportManager = new DefaultViewportManager();
SetZoomMode();
// first series is chart
Pricevm = new SPPricePaneViewModel(this, pseries, chart_series) { IsFirstChartPane = true, ViewportManager = _viewportManager };
_chartPaneViewModels.Add(Pricevm);
_chartPaneViewModels[0].decInPrice = decInPrice;
}
catch (Exception e)
{
Log.Information("EXCEPTION: New PricePaneViewModel {0}", e.StackTrace);
return;
}
//// load current annotation
//if (CurrentAnnotationMap.ContainsKey(prodCode))
//{
// var annovm = CurrentAnnotationMap[prodCode];
// annovm.ForEach(o => ChartPaneViewModels[0].AnnotationViewModels.Add(o));
//}
AddSavedAnnotations(); // show previous annotations added by user
ShowIndicator(); // initialize all indicators
// set the last price and enable ticker push data
if (pseries != null && pseries.Size > 0)
{
price_last = pseries[pseries.Size - 1];
AddCurrentPriceAnnotation(price_last.Close);
AddCutoffLineAnnotation(pseries);
ToggleShowCutOffAnnotations(false); // default dont show cutoff line
//AddBuyMarkerAnnotation();
//AddSellMarkerAnnotation();
}
RaisePropertyChanged("ChartPaneViewModels");
}
public IEnumerable<string> AllThemes { get { return ThemeManager.AllThemes; } }
public string VerticalChartGroupId
{
get { return _verticalChartGroupId; }
set { SetProperty(ref _verticalChartGroupId, value); }
}
/// <summary>
/// Shared XAxis VisibleRange for all charts
/// </summary>
public IndexRange XVisibleRange
{
get { return _xAxisVisibleRange; }
set { SetProperty(ref _xAxisVisibleRange, value); }
}
public ObservableCollection<SPBaseChartPaneViewModel> ChartPaneViewModels
{
get { return _chartPaneViewModels; }
set { SetProperty(ref _chartPaneViewModels, value); }
}
public void OnTickerPush(TickerPushMessage message)
{
if (!apiProxy.UIHelper.CanRefreshUI(this, AppConstants.REFRESH_UI_THROTTLE))
return;
var data = message.data;
if (price_last != null && prodCode.Equals(data.ProductCode))
{
price_last.Close = data.Price;
if (price_last.Close > price_last.High)
price_last.High = price_last.Close;
if (price_last.Close < price_last.Low)
price_last.Low = price_last.Close;
var price_chart = (IOhlcDataSeries<DateTime, double>)_chartPaneViewModels[0].ChartSeriesViewModels[0].DataSeries;
using (price_chart.SuspendUpdates())
{
// FIX, change to support append, that is, sliding datetime as a new "bar" of data appears
//if (price_chart.XValues.IndexOf(price_last.DateTime) >= 0)
// price_chart.Update(price_last.DateTime, price_last.Open, price_last.High, price_last.Low, price_last.Close);
//else
// price_chart.Append(price_last.DateTime, price_last.Open, price_last.High, price_last.Low, price_last.Close);
price_chart.Update(price_last.DateTime, price_last.Open, price_last.High, price_last.Low, price_last.Close);
// update current price axis
UpdateCurrentPriceAnnotation(price_last.Close);
}
}
}
}
// SciChart ViewModel Indicators
public partial class ChartAnalysisProControlViewModel : SPBindableBase
{
public SPPricePaneViewModel Pricevm = null;
public SPVolumePaneViewModel Volumevm = null;
public SPRsiPaneViewModel Rsivm = null;
public SPMacdPaneViewModel Macdvm = null;
public SPCciPaneViewModel Ccivm = null;
public SPStochPaneViewModel Stochvm = null;
public SPStochRsiPaneViewModel StochRsivm = null;
public SPAdxPaneViewModel Adxvm = null;
public void ShowIndicator()
{
if (Pricevm == null) // main chart display must have been generated
return;
Volumevm = new SPVolumePaneViewModel(this, pseries) { Title = "Volume" }; // IsLastChartPane = true
Macdvm = new SPMacdPaneViewModel(this, pseries) { Title = "MACD" };
Rsivm = new SPRsiPaneViewModel(this, pseries) { Title = "RSI" };
Stochvm = new SPStochPaneViewModel(this, pseries) { Title = "STOCH" };
Adxvm = new SPAdxPaneViewModel(this, pseries) { Title = "ADX" };
StochRsivm = new SPStochRsiPaneViewModel(this, pseries) { Title = "STOCH-RSI" };
Ccivm = new SPCciPaneViewModel(this, pseries) { Title = "CCI" };
// TA on main chart screen
Pricevm.ShowMovingAverages((bool)parentControl.tbSma.IsChecked, true);
Pricevm.ShowExponentialMovingAverages((bool)parentControl.tbEma.IsChecked);
Pricevm.ShowBollingerBands((bool)parentControl.tbBol.IsChecked);
Pricevm.ShowSAR((bool)parentControl.tbSar.IsChecked);
// TA on sub screens
TogglePaneCommand(Macdvm, (bool)parentControl.tbMacd.IsChecked);
TogglePaneCommand(Rsivm, (bool)parentControl.tbRsi.IsChecked);
TogglePaneCommand(Volumevm, (bool)parentControl.tbVol.IsChecked);
TogglePaneCommand(Stochvm, (bool)parentControl.tbStoch.IsChecked);
TogglePaneCommand(Adxvm, (bool)parentControl.tbAdx.IsChecked);
TogglePaneCommand(StochRsivm, (bool)parentControl.tbStochRsi.IsChecked);
TogglePaneCommand(Ccivm, (bool)parentControl.tbCci.IsChecked);
}
public void TogglePaneCommand(SPBaseChartPaneViewModel pane, bool flag)
{
if (flag)
OpenPaneCommand(pane);
else
ClosePaneCommand(pane);
}
protected void OpenPaneCommand(SPBaseChartPaneViewModel pane)
{
_chartPaneViewModels.Add(pane);
}
protected void ClosePaneCommand(SPBaseChartPaneViewModel pane)
{
_chartPaneViewModels.Remove(pane);
}
}
}