forked from reactiveui/ReactiveUI
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathWatchListViewModel.cs
More file actions
171 lines (143 loc) · 5.52 KB
/
WatchListViewModel.cs
File metadata and controls
171 lines (143 loc) · 5.52 KB
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
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Text;
using System.Windows.Input;
using ReactiveUI;
namespace MobileSample_Android.ViewModels
{
// borrowed from https://github.com/SignalR/SignalR-StockTicker/blob/master/SignalR.StockTicker/SignalR.StockTicker/SignalR.StockTicker/StockTicker.cs
public class WatchListViewModel : ReactiveObject
{
private readonly object marketStateLock = new object();
private readonly double rangePercent = 0.002;
private readonly ReactiveList<WatchListItemViewModel> stocks = new ReactiveList<WatchListItemViewModel>();
private readonly TimeSpan updateInterval = TimeSpan.FromMilliseconds(250);
private readonly Random updateOrNotRandom = new Random();
private readonly object updateStockPricesLock = new object();
private volatile MarketState marketState = MarketState.Closed;
private IDisposable timer;
private volatile bool updatingStockPrices;
public ICommand OpenMarketCommand { get; private set; }
public ICommand CloseMarketCommand { get; private set; }
public ICommand ResetCommand { get; private set; }
public WatchListViewModel()
{
var openCmd = ReactiveCommand.CreateAsyncObservable(this.WhenAnyValue(vm => vm.MarketState, m => m == MarketState.Closed),
_ => Observable.Start(OpenMarket), RxApp.MainThreadScheduler);
OpenMarketCommand = openCmd;
var closeCmd = ReactiveCommand.CreateAsyncObservable(
this.WhenAnyValue(vm => vm.MarketState, m => m == MarketState.Open),
_ => Observable.Start(CloseMarket), RxApp.MainThreadScheduler);
CloseMarketCommand = closeCmd;
var resetCmd = ReactiveCommand.CreateAsyncObservable(
this.WhenAnyValue(vm => vm.MarketState, m => m == MarketState.Closed),
_ => Observable.Start(Reset), RxApp.MainThreadScheduler);
ResetCommand = resetCmd;
LoadDefaultStocks();
}
public IReadOnlyReactiveList<WatchListItemViewModel> Stocks
{
get { return stocks; }
}
public MarketState MarketState
{
get { return marketState; }
private set
{
marketState = value;
this.RaisePropertyChanged(); // can't use ref as it's volatile
}
}
private bool TryUpdateStockPrice(WatchListItemViewModel stock)
{
// Randomly choose whether to udpate this stock or not
var r = updateOrNotRandom.NextDouble();
if (r > 0.1)
{
return false;
}
// Update the stock price by a random factor of the range percent
var random = new Random((int)Math.Floor(stock.Price));
var percentChange = random.NextDouble()*rangePercent;
var pos = random.NextDouble() > 0.51;
var change = Math.Round(stock.Price*(decimal)percentChange, 2);
change = pos ? change : -change;
stock.Price += change;
return true;
}
private void UpdateStockPrices()
{
// This function must be re-entrant as it's running as a timer interval handler
lock (updateStockPricesLock)
{
if (!updatingStockPrices)
{
updatingStockPrices = true;
foreach (var stock in stocks)
{
if (TryUpdateStockPrice(stock))
{
//BroadcastStockPrice(stock);
}
}
updatingStockPrices = false;
}
}
}
public void OpenMarket()
{
lock (marketStateLock)
{
if (MarketState != MarketState.Open)
{
timer = Observable.Timer(updateInterval, updateInterval, RxApp.MainThreadScheduler)
.Subscribe(_ => UpdateStockPrices());
MarketState = MarketState.Open;
}
}
}
public void CloseMarket()
{
lock (marketStateLock)
{
if (MarketState == MarketState.Open)
{
if (timer != null)
{
timer.Dispose();
}
MarketState = MarketState.Closed;
}
}
}
public void Reset()
{
lock (marketStateLock)
{
if (MarketState != MarketState.Closed)
{
throw new InvalidOperationException("Market must be closed before it can be reset.");
}
LoadDefaultStocks();
}
}
private void LoadDefaultStocks()
{
using (stocks.SuppressChangeNotifications())
{
stocks.Clear();
stocks.Add(new WatchListItemViewModel("MSFT") {Price = 36.91m});
stocks.Add(new WatchListItemViewModel("AAPL") {Price = 545.09m});
stocks.Add(new WatchListItemViewModel("GOOG") {Price = 1107.32m});
stocks.Add(new WatchListItemViewModel("FB") {Price = 54.77m});
}
}
}
public enum MarketState
{
Closed,
Open
}
}