-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathStockTicker.cs
247 lines (219 loc) · 9.31 KB
/
StockTicker.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using System.Windows.Forms.VisualStyles;
namespace StockDataVisualizer
{
/// <summary>
/// Key Concepts demonstrated
/// 1. Multithreading/Cancellation of Threads/System Mutex/ThreadPool
/// 2. Events
/// 3. Func
/// 4. Configurability of tickers through Config
/// 5. Singleton Logger
/// 6. FileStreams
/// 7. Error handling in UI
/// 8. Fast Updating/Responsive UI with quick shutdown (cancellation tokens)
/// 9. LinQ
/// </summary>
public partial class StockDataVisualizer : Form
{
private Thread server = null;
private List<Stock> stocks = new List<Stock>();
private static String[] tickers;
private CancellationTokenSource _cancellationToken;
public static List<string> TickerSymbols
{
get
{
return tickers.ToList();
}
}
public StockDataVisualizer()
{
InitializeComponent();
InitGrid();
this.stockTickerGrid.DataSource = stocks;
var binding = new Binding("Text", stocks, null);
this.stockTickerGrid.DataBindings.Add(binding);
//:~ Register Event handler for grid updating
StockPriceTickerUpdate.Handler += UpdateReceived;
this.FormClosing += new FormClosingEventHandler(StockDataVisualizer_FormClosing);
this.toolStripStatusLabel.Text = String.Format("Log File {0}", Logger.Instance.GetLoggerFileName);
}
/// <summary>
/// Returns list of configured stock tickers
/// </summary>
public List<Stock> StockRepository
{
get { return stocks; }
set { stocks = value; }
}
/// <summary>
/// InitGrid - initializes Grid with random values
/// </summary>
public void InitGrid()
{
AppSettingsReader appSettings = new AppSettingsReader();
string configFileTickerSymbols = (string)appSettings.GetValue("ConfiguredTickers", typeof(string));
tickers = configFileTickerSymbols.Split(',');
int init = 100;
var r = new Random();
foreach(var ticker in tickers)
{
stocks.Add(new Stock() { Company=ticker, Ask=r.Next(100,700), Bid=r.Next(100,750), MarketPrice=r.Next(100,1000),Close= r.Next(100,700)+init, Open=r.Next(150,800)+init, Volume=init+100 });
init++;
}
}
/// <summary>
/// The Main handler for any StockPriceChangeEvent which causes the List to
/// be updated with new values and also update the grid
/// </summary>
/// <param name="sender">Sender of the event</param>
/// <param name="evt">StockPrice change object</param>
public void UpdateReceived(object sender, StockPriceChangeEvent evt)
{
var stockObj = stocks.Find(x => x.Company.Equals(evt.Company));
var oldBidValue = stockObj.Bid;
var oldAskValue = stockObj.Ask;
var oldBidAskSpread = stockObj.BidAskSpread;
var oldMarketPrice = stockObj.MarketPrice;
stockObj.Bid = evt.BidPrice;
stockObj.Ask = evt.AskPrice;
stockObj.Volume = stockObj.Volume + 1;
//:~ Use Func to calculate Mid Market Price
Func<double, double, double> calculateMidMarketPrice = (bidPrice, askPrice) => (bidPrice + askPrice)/2.0;
stockObj.MarketPrice = calculateMidMarketPrice(evt.BidPrice, evt.AskPrice);
//:~ Use Func to calculate difference between bid and askPrice difference
Func<double, double, double> calculateBidAskSpread = (bidPrice, askPrice) => (askPrice - bidPrice) / 100.0;
double spread = calculateBidAskSpread(evt.BidPrice, evt.AskPrice);
stockObj.BidAskSpread = spread;
//:~ Use Func to streamline method to determine paint color
Func<double, double, Color> determineColorToPaint = (x, y) => x > y ? Color.Green : Color.Red;
int rowIndex = FindCompany(evt);
//:~ Hate this hack and the *IF*
if (!_cancellationToken.IsCancellationRequested)
{
this.stockTickerGrid["Bid", rowIndex].Style.BackColor = determineColorToPaint(((double) stockObj.Bid),(double) oldBidValue);
this.stockTickerGrid["Ask", rowIndex].Style.BackColor = determineColorToPaint(((double) stockObj.Ask),(double) oldAskValue);
this.stockTickerGrid["BidAskSpread", rowIndex].Style.BackColor = determineColorToPaint(oldBidAskSpread,spread);
this.stockTickerGrid["MarketPrice", rowIndex].Style.BackColor = determineColorToPaint(oldMarketPrice,stockObj.MarketPrice);
}
}
/// <summary>
/// Helper method to find out the rowIndex in the grid for a given stock object
/// </summary>
/// <param name="evt">StockPrice Change event object</param>
/// <returns>rowindex as int</returns>
private int FindCompany(StockPriceChangeEvent evt)
{
int rowIndex = -1, index = -1;
TickerSymbols.ToList().ForEach((symbol) =>
{
++index;
if (_cancellationToken.IsCancellationRequested)
return;
if ((string)stockTickerGrid["Company", index].Value == evt.Company)
{
rowIndex = stockTickerGrid["Company", index].RowIndex;
}
});
return rowIndex;
}
/// <summary>
/// Starts the Ticker - Invoked upon clicking start button
/// </summary>
/// <param name="sender">Start Button</param>
/// <param name="e">EventArgs</param>
private void startbtn_Click(object sender, EventArgs e)
{
Logger.Instance.Log("Start Button clicked, Need to start ticker thread");
_cancellationToken = new CancellationTokenSource();
server = new Thread(new RandomPriceGenerator(_cancellationToken.Token).StartServer);
server.Start();
this.startbtn.Enabled = false;
this.stopbtn.Enabled = true;
}
/// <summary>
/// Stops the Ticker - Invoked upon clicking stop button
/// </summary>
/// <param name="sender">Stop Button</param>
/// <param name="e">EventArgs</param>
private void stopbtn_Click(object sender, EventArgs e)
{
Logger.Instance.Log("Stop Button clicked, Need to gracefully shutdown thread");
this.startbtn.Enabled = true;
this.stopbtn.Enabled = false;
try
{
_cancellationToken.Cancel();
}
catch (ObjectDisposedException ex)
{
Logger.Instance.Log("Object Disposed Exception caught, Possible bug lurking");
//Don't rethrow
}
catch (AggregateException aex)
{
Logger.Instance.Log("Aggregate Exception caught, Multiple errors might have occured");
//Don't rethrow
}
server.Join();
}
/// <summary>
/// Method to Invoke the Stock Analyzer Form
/// </summary>
/// <param name="sender">Find Stock performance button</param>
/// <param name="e">EventArgs click event</param>
private void FindStkPerf_Click(object sender, EventArgs e)
{
var form = new StockAnalyzer(this);
form.ShowDialog(this);// modal
}
/// <summary>
/// Method to Invoke the Best Worst Stocks Form
/// </summary>
/// <param name="sender">Find Best Worst Stock performance button</param>
/// <param name="e">EventArgs click event</param>
private void FindPerf_Click(object sender, EventArgs e)
{
var form = new BestWorstStocks(this);
form.ShowDialog(this);
}
/// <summary>
/// Forcibly terminate threads upon form close
/// </summary>
/// <param name="sender">Form</param>
/// <param name="e">Closing events</param>
void StockDataVisualizer_FormClosing(object sender, FormClosingEventArgs e)
{
Logger.Instance.Log("App shutting down...");
try
{
//:~ I Always hated server.Abort() :~
// Unregister the listener, as the flood of events are still
// getting processed !
StockPriceTickerUpdate.Handler -= UpdateReceived;
if(null != _cancellationToken)
_cancellationToken.Cancel();
}
catch (ObjectDisposedException)
{
Logger.Instance.Log("Object Disposed Exception caught, Possible bug lurking");
//Don't rethrow
}
catch(AggregateException)
{
Logger.Instance.Log("Aggregate Exception caught, Multiple errors might have occured");
//Don't rethrow
}
}
}
}