using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Globalization; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.DataVisualization; using System.Windows.Controls.DataVisualization.Charting; using System.Windows.Data; using System.Windows.Media; using TradeIdeas.MiscSupport; using TradeIdeas.TIProData; using static TradeIdeas.TIProData.InsiderTrade; namespace TradeIdeas.TIProGUI.EnhancedSingleStockWindow { public class InsidersTab : TabItem, IMasterWindowResize { private WPF_SingleStock _mainWindow = null; private string _id = ""; private EnhancedSingleStockWindow _eWin = null; private Chart _insiderPieChart = null; private DataGrid _insidersDataGrid = null; private ResourceDictionaryCollection _emptySeriesPalette; //for the gray pie chart on emty data private ResourceDictionaryCollection _pieSeriesPalette; private ObservableCollection _data = new ObservableCollection(); public ObservableCollection _pieData = new ObservableCollection(); private Border _dataOverLay; //Historical Update Labels private Label _itrades_1; private Label _itrades_3; private Label _itrades_12; private Label _buys_1; private Label _buys_3; private Label _buys_12; private Label _sells_1; private Label _sells_3; private Label _sells_12; private Label _totShares_1; private Label _totShares_3; private Label _totShares_12; private Label _shrsBought_1; private Label _shrsBought_3; private Label _shrsBought_12; private Label _shrsSold_1; private Label _shrsSold_3; private Label _shrsSold_12; //lock object for synchronization; private readonly object _syncLock = new object(); public InsidersTab() { } public void onMasterWindowResize(SizeChangedEventArgs e) { throw new NotImplementedException(); } public void setId(string value) { _id = value; } public void setSingleStockWindowInstance(WPF_SingleStock instance) { //Enable the cross acces to this collection elsewhere...need to implement this in order to view the proper grid data.. BindingOperations.EnableCollectionSynchronization(_data, _syncLock); _mainWindow = instance; _mainWindow.DataContext = this; _insidersDataGrid = _mainWindow.insidersDataGrid; _insiderPieChart = _mainWindow.insiderPieChart; ObservableCollection columns = _insidersDataGrid.Columns; DataGridColumn dateCol = columns[0]; //This is the "Date" column. It will *always* be the first, although the *view* may change by user shifting positions of columns. DataGridTextColumn txtCol = (DataGridTextColumn)dateCol; txtCol.Binding.StringFormat = this.getLocaleDateFormat(); _dataOverLay = _mainWindow.DataOverlay; //Historical Labels: _itrades_1 = _mainWindow.itrades_1; _itrades_3 = _mainWindow.itrades_3; _itrades_12 = _mainWindow.itrades_12; _buys_1 = _mainWindow.buys_1; _buys_3 = _mainWindow.buys_3; _buys_12 = _mainWindow.buys_12; _sells_1= _mainWindow.sells_1; _sells_3 = _mainWindow.sells_3; _sells_12 =_mainWindow.sells_12; _totShares_1 = _mainWindow.totShares_1; _totShares_3 = _mainWindow.totShares_3; _totShares_12 = _mainWindow.totShares_12; _shrsBought_1 = _mainWindow.shrsBought_1; _shrsBought_3 = _mainWindow.shrsBought_3; _shrsBought_12 = _mainWindow.shrsBought_12; _shrsSold_1 = _mainWindow.shrsSold_1; _shrsSold_3 = _mainWindow.shrsSold_3; _shrsSold_12 = _mainWindow.shrsSold_12; _itrades_1.Content = ""; _itrades_3.Content = ""; _itrades_12.Content = ""; _buys_1.Content = ""; _buys_3.Content = ""; _buys_12.Content = ""; _sells_1.Content = ""; _sells_3.Content = ""; _sells_12.Content = ""; _totShares_1.Content = ""; _totShares_3.Content = ""; _totShares_12.Content = ""; _shrsBought_1.Content = ""; _shrsBought_3.Content = ""; _shrsBought_12.Content = ""; _shrsSold_1.Content = ""; _shrsSold_3.Content = ""; _shrsSold_12.Content = ""; _mainWindow.formLoadedEvent += _MaininWindow_formLoadedEvent; } private String getLocaleDateFormat() { string format = ""; DateTimeFormatInfo DTFormat = CultureInfo.CurrentCulture.DateTimeFormat; string mdPattern = DTFormat.MonthDayPattern; string dateSeparator = DTFormat.DateSeparator; // Check current culture if (mdPattern.Substring(0, 1).Equals("d")) format = "dd" + dateSeparator + "MM" + dateSeparator + "yyyy"; else format = "MM" + dateSeparator + "dd" + dateSeparator + "yyyy"; return format; } private void _MaininWindow_formLoadedEvent(double mainWindowWidth) { //I needed to create a custom palette for the pie chart. The following block of code was // how I was able to achieve this,and was-inspired from: https://stackoverflow.com/questions/2425504/wpf-toolkit-pie-chart-style-colors //The nullSeries palette is comprised only of one color, which is gray, and is used for a pie chart with empty data _pieSeriesPalette = new ResourceDictionaryCollection(); _emptySeriesPalette = new ResourceDictionaryCollection(); Brush buyBrush = new SolidColorBrush(Colors.ForestGreen); Brush sellBrush = new SolidColorBrush(Colors.DarkRed); Brush nullBrush = new SolidColorBrush(Color.FromRgb(208, 208, 208)); Style stylePieBuy = new Style(typeof(PieDataPoint)); Style stylePieSell = new Style(typeof(PieDataPoint)); Style stylePieNull = new Style(typeof(PieDataPoint)); stylePieNull.BasedOn = (Style)FindResource("PieDataPointStyle"); //this style is where the pie chart tool tip is disabled stylePieBuy.Setters.Add(new Setter(PieDataPoint.BackgroundProperty, buyBrush)); stylePieSell.Setters.Add(new Setter(PieDataPoint.BackgroundProperty, sellBrush)); stylePieNull.Setters.Add(new Setter(PieDataPoint.BackgroundProperty, nullBrush)); ResourceDictionary pieDataPointStylesBuy = new ResourceDictionary(); ResourceDictionary pieDataPointStylesSell = new ResourceDictionary(); ResourceDictionary pieDataPointStylesNull = new ResourceDictionary(); pieDataPointStylesBuy.Add("DataPointStyle", stylePieBuy); pieDataPointStylesSell.Add("DataPointStyle", stylePieSell); pieDataPointStylesNull.Add("DataPointStyle", stylePieNull); _pieSeriesPalette.Add(pieDataPointStylesBuy); _pieSeriesPalette.Add(pieDataPointStylesSell); _emptySeriesPalette.Add(pieDataPointStylesNull); //customization of piechart palette ^^^^^^^^^^^^ object obj = _mainWindow.Tag; if(obj as EnhancedSingleStockWindow != null) { _eWin = (EnhancedSingleStockWindow)obj; _eWin.onTransactionSelectedUpdate += EWin_onTransactionSelectedUpdate; } _insidersDataGrid.ItemsSource = _data; _insiderPieChart.Palette = _pieSeriesPalette; ((PieSeries)_insiderPieChart.Series[0]).ItemsSource = _pieData; _mainWindow.onInsiderDataReceived += _mainWindow_onInsiderDataReceived; _mainWindow.receiveTopListDataEvent += MainWindow_receiveTopListDataEvent; updatePieChartData(null,0,0); } private void EWin_onTransactionSelectedUpdate(List selectedItems) { if (selectedItems.Count > 0) { List results = getFilteredResults(selectedItems,_iTrades); lock (_syncLock) { _data.Clear(); } populateGrid(results); } else { //TODO view *all* data, including those not categorized as buy or sell (e.g. "unknown","option execute"...etc) //lock (_syncLock) //{ _data.Clear(); //} populateGrid(_iTrades); } } private void MainWindow_receiveTopListDataEvent(TopListInfo metaData, RowData rowData, DateTime? start, DateTime? end, bool validDataReceived) { if (!validDataReceived) { lock (_syncLock) { doShowEmptyOverlay(true); updatePieChartData(null,0, 0); _data.Clear(); clearHistoricalLabels(); } } } protected List _iTrades = new List(); //a master list.. it's unfiltered data. private void _mainWindow_onInsiderDataReceived(List iData, string id) { //TODO check pre existing filters //Some very interesting threading issues(e.g. access collections on non-ui threads) have arisen when populating the insider data grid. We want to make sure that when we have multiple Single Stock windows //open, that we're updating the right data collection (right Single Stock window). Inspiration from: https://stackoverflow.com/questions/14336750/upgrading-to-net-4-5-an-itemscontrol-is-inconsistent-with-its-items-source/16367902 lock (_syncLock) { _iTrades = new List(iData); String debug = this._mainWindow.getCurrentSymbol(); if (iData.Count > 0 && iData[0].Symbol == this._mainWindow.getCurrentSymbol() && _data.Count > 0 && _data[0].Symbol == this._mainWindow.getCurrentSymbol()) { doShowEmptyOverlay(false); return; } else if (iData.Count > 0 && iData[0].Symbol == this._mainWindow.getCurrentSymbol()) { doShowEmptyOverlay(false); _data.Clear(); if (_eWin != null) _eWin.fetchSelectedFilters(); } else if (iData.Count == 0 && _data.Count > 0 && _data[0].Symbol != this._mainWindow.getCurrentSymbol()) { doShowEmptyOverlay(true); _data.Clear(); updatePieChartData(null, 0, 0); clearHistoricalLabels(); } else if (iData.Count == 0) { doShowEmptyOverlay(true); } } } private void populateGrid(List iData) { int buyTally = 0; int sellTally = 0; int totalTally = 0; HistoryMonthlyDataCalculator one_Month_calc = new HistoryMonthlyDataCalculator(30); HistoryMonthlyDataCalculator three_Month_calc = new HistoryMonthlyDataCalculator(90); HistoryMonthlyDataCalculator year_calc = new HistoryMonthlyDataCalculator(365); one_Month_calc.addInsiderTrade(iData); three_Month_calc.addInsiderTrade(iData); year_calc.addInsiderTrade(iData); if (iData.Count == 0) clearHistoricalLabels(); foreach (InsiderTrade trade in iData) { InsiderData data = new InsiderData(); data.Date = trade.Date; data.FilerName = trade.FilerName; data.OwnershipType = trade.OwnershipType.ToString(); data.PriceFrom = trade.PriceFrom; data.PriceTo = trade.PriceTo; data.RelationshipType = getRelationAbbreviation(trade.RelationshipType.ToString()); data.Shares = trade.Shares; data.Symbol = trade.Symbol; data.TotalSharesOwned = trade.TotalSharesOwned; data.TransactionType = getTransactionAbbreviation(trade.TransactionType.ToString()); String rel = trade.TransactionType.ToString(); if (rel == "Sell" || rel == "DispositionNonOpenMarket" || rel == "AutomaticSell") { sellTally++; data.Shares = -trade.Shares; } if (rel == "Buy" || rel == "AcquisitionNonOpenMarket" || rel == "AutomaticBuy") { buyTally++; } totalTally++; lock (_syncLock) { if (data.Symbol == this._mainWindow.getCurrentSymbol()) { _data.Add(data); if (totalTally == iData.Count) { updatePieChartData(year_calc); } updateHistoricalData(one_Month_calc, three_Month_calc, year_calc); } } } } private void doShowEmptyOverlay(bool value) { this.Dispatcher.InvokeAsync(() => { if (value) _dataOverLay.Visibility = Visibility.Visible; else _dataOverLay.Visibility = Visibility.Hidden; }); } private List getFilteredResults(List selected,List trades) { List retVal = new List(); foreach (InsiderTransactionType t in selected) { List results = trades.Where(x => x.TransactionType.Equals(t)).ToList(); retVal.AddRange(results); } return retVal; } private void updatePieChartData(HistoryMonthlyDataCalculator calculator,int buy = 0, int sell = 0) { this.Dispatcher.InvokeAsync(() => { String buyString = " "; String sellString = " "; if (calculator == null) { buyString = "Buy " + buy; sellString = "Sell " + sell; } else { buy = calculator.SharesBought; sell = calculator.SharesSold; buyString = "Buy " + getSmarterFormattedValue(buy); sellString = "Sell " + getSmarterFormattedValue(sell); } _pieData.Clear(); if (buy == 0 && sell == 0) //empty pie { _pieData.Clear(); _insiderPieChart.Palette = _emptySeriesPalette; //must change pallette _pieData.Add(new PiePoint { BuyOrSell = sellString, Value = 1 });// grey _insiderPieChart.LegendStyle = (Style)FindResource("customLegendWidth"); } else { _insiderPieChart.Palette = _pieSeriesPalette; _pieData.Add(new PiePoint { BuyOrSell = buyString, Value = buy }); //green _pieData.Add(new PiePoint { BuyOrSell = sellString, Value = sell });//red _insiderPieChart.LegendStyle = (Style)FindResource("normalLegendWidth"); ((PieSeries)_insiderPieChart.Series[0]).ItemsSource = _pieData; PieSeries series = ((PieSeries)_insiderPieChart.Series[0]); series.DataContext = _pieData; } }); } private String getSmarterFormattedValue(int val) { Double test = (Double)val; String retVal = ""; if (test < 1000) { retVal = test.ToString(); } else if (test < 1000000) { double t = (test / 1.0e+3); retVal = String.Format("{0:f2}", t) + "K"; } else if (test < 1000000000) { double t = (test / 1.0e+6); retVal = String.Format("{0:f2}", t) + "M"; } else { double t = (test / 1.0e+9); retVal = String.Format("{0:f2}", t) + "B"; } return retVal; } private void updateHistoricalData(HistoryMonthlyDataCalculator oneMonth, HistoryMonthlyDataCalculator threeMonth, HistoryMonthlyDataCalculator year) { this.Dispatcher.InvokeAsync(() => { _itrades_1.Content = oneMonth.TotalInsiderTrades; _itrades_3.Content = threeMonth.TotalInsiderTrades; _itrades_12.Content = year.TotalInsiderTrades; _buys_1.Content = oneMonth.NumberOfBuys; _buys_3.Content = threeMonth.NumberOfBuys; _buys_12.Content = year.NumberOfBuys; _sells_1.Content = oneMonth.NumberOfSells; _sells_3.Content = threeMonth.NumberOfSells; _sells_12.Content = year.NumberOfSells; _totShares_1.Content = oneMonth.TotalShares; _totShares_3.Content = threeMonth.TotalShares; _totShares_12.Content = year.TotalShares; _shrsBought_1.Content = oneMonth.SharesBought; _shrsBought_3.Content = threeMonth.SharesBought; _shrsBought_12.Content = year.SharesBought; _shrsSold_1.Content = oneMonth.SharesSold; _shrsSold_3.Content = threeMonth.SharesSold; _shrsSold_12.Content = year.SharesSold; }); } public void clearHistoricalLabels() { this.Dispatcher.InvokeAsync(() => { _itrades_1.Content = _itrades_3.Content = _itrades_12.Content = ""; _buys_1.Content = _buys_3.Content = _buys_12.Content = ""; _sells_1.Content = _sells_3.Content = _sells_12.Content = ""; _totShares_1.Content = _totShares_3.Content = _totShares_12.Content = ""; _shrsBought_1.Content = _shrsBought_3.Content = _shrsBought_12.Content = ""; _shrsSold_1.Content = _shrsSold_3.Content = _shrsSold_12.Content = ""; }); } private String getTransactionAbbreviation(String trns) { String retVal = trns; switch (trns) { case "AutomaticSell": retVal = "AS"; break; case "AutomaticBuy": retVal = "AB"; break; case "DispositionNonOpenMarket": retVal = "D"; break; case "AcquisitionNonOpenMarket": retVal = "A"; break; case "OptionExecute": retVal = "OE"; break; case "Unknown": retVal = "U"; break; } return retVal; } private String getRelationAbbreviation(String rel) { String retVal = rel; switch(rel) { case "BeneficialOwner10Percent": retVal = "BenOwn10"; break; } return retVal; } public void unLoadEventHandlers() { _mainWindow.formLoadedEvent -= _MaininWindow_formLoadedEvent; _mainWindow.onInsiderDataReceived -= _mainWindow_onInsiderDataReceived; _mainWindow.receiveTopListDataEvent -= MainWindow_receiveTopListDataEvent; if (_eWin != null) _eWin.onTransactionSelectedUpdate -= EWin_onTransactionSelectedUpdate; } } public class PiePoint : INotifyPropertyChanged { private string _buyOrSell; private int _value; public string BuyOrSell { get { return _buyOrSell; } set { if (_buyOrSell != value) { _buyOrSell = value; OnPropertyChanged("BuyOrSell"); } } } public int Value { get { return _value; } set { if (_value != value) { _value = value; OnPropertyChanged("Value"); } } } public event PropertyChangedEventHandler PropertyChanged; private void OnPropertyChanged(String propName) { PropertyChangedEventHandler handler = PropertyChanged; if (handler != null) { handler(this, new PropertyChangedEventArgs(propName)); } } } public class HistoryMonthlyDataCalculator //this class is tailoed for computing cumulative monthly data from InsiderTrade object { private int _insiderTrades = 0; private int _numberOfBuys = 0; private int _numberOfSells = 0; private int _totalShares = 0; private int _sharesBought = 0; private int _sharesSold = 0; private int _lookBackInDays = 0; //This is the number ofmonths to "look back" for computations. That is, if we enter "90", //we'll compute 3 months worth of data. public HistoryMonthlyDataCalculator(int lookBack) { _lookBackInDays = lookBack; } public int TotalInsiderTrades { get { return _insiderTrades; } } public int NumberOfBuys { get { return _numberOfBuys; } } public int NumberOfSells { get { return _numberOfSells; } } public int TotalShares { get { return _totalShares; } } public int SharesBought { get { return _sharesBought; } } public int SharesSold { get { return _sharesSold; } } public void addInsiderTrade(List iTrades) { foreach (InsiderTrade trade in iTrades) { if (trade.Date != null) { DateTime dtTest = ServerFormats.Now.AddDays(-_lookBackInDays); dtTest = new DateTime(dtTest.Year, dtTest.Month, dtTest.Day, 0, 0, 0); if (dtTest <= trade.Date) tallyData(trade); } } } private void tallyData(InsiderTrade trade) { String rel = trade.TransactionType.ToString(); if (rel == "Sell" || rel == "DispositionNonOpenMarket" || rel == "AutomaticSell") { if (trade.Shares != null) { _numberOfSells++; _insiderTrades++; _sharesSold += trade.Shares.Value; _totalShares += trade.Shares.Value; } } if (rel == "Buy" || rel == "AcquisitionNonOpenMarket" || rel == "AutomaticBuy") { if (trade.Shares != null) { _numberOfBuys++; _insiderTrades++; _sharesBought += trade.Shares.Value; _totalShares += trade.Shares.Value; } } } } }