| a |
Ask |
a2 |
Average Daily Volume |
a5 |
Ask Size |
| b |
Bid |
b2 |
Ask (Real-time) |
b3 |
Bid (Real-time) |
| b4 |
Book Value |
b6 |
Bid Size |
c |
Change & Percent Change |
| c1 |
Change |
c3 |
Commission |
c6 |
Change (Real-time) |
| c8 |
After Hours Change (Real-time) |
d |
Dividend/Share |
d1 |
Last Trade Date |
| d2 |
Trade Date |
e |
Earnings/Share |
e1 |
Error Indication (returned for symbol changed / invalid) |
| e7 |
EPS Estimate Current Year |
e8 |
EPS Estimate Next Year |
e9 |
EPS Estimate Next Quarter |
| f6 |
Float Shares |
g |
Day's Low |
h |
Day's High |
| j |
52-week Low |
k |
52-week High |
g1 |
Holdings Gain Percent |
| g3 |
Annualized Gain |
g4 |
Holdings Gain |
g5 |
Holdings Gain Percent (Real-time) |
| g6 |
Holdings Gain (Real-time) |
i |
More Info |
i5 |
Order Book (Real-time) |
| j1 |
Market Capitalization |
j3 |
Market Cap (Real-time) |
j4 |
EBITDA |
| j5 |
Change From 52-week Low |
j6 |
Percent Change From 52-week Low |
k1 |
Last Trade (Real-time) With Time |
| k2 |
Change Percent (Real-time) |
k3 |
Last Trade Size |
k4 |
Change From 52-week High |
| k5 |
Percent Change From 52-week High |
l |
Last Trade (With Time) |
l1 |
Last Trade (Price Only) |
| l2 |
High Limit |
l3 |
Low Limit |
m |
Day's Range |
| m2 |
Day's Range (Real-time) |
m3 |
50-day Moving Average |
m4 |
200-day Moving Average |
| m5 |
Change From 200-day Moving Average |
m6 |
Percent Change From 200-day Moving Average |
m7 |
Change From 50-day Moving Average |
| m8 |
Percent Change From 50-day Moving Average |
n |
Name |
n4 |
Notes |
| o |
Open |
p |
Previous Close |
p1 |
Price Paid |
| p2 |
Change in Percent |
p5 |
Price/Sales |
p6 |
Price/Book |
| q |
Ex-Dividend Date |
r |
P/E Ratio |
r1 |
Dividend Pay Date |
| r2 |
P/E Ratio (Real-time) |
r5 |
PEG Ratio |
r6 |
Price/EPS Estimate Current Year |
| r7 |
Price/EPS Estimate Next Year |
s |
Symbol |
s1 |
Shares Owned |
| s7 |
Short Ratio |
t1 |
Last Trade Time |
t6 |
Trade Links |
| t7 |
Ticker Trend |
t8 |
1 yr Target Price |
v |
Volume |
| v1 |
Holdings Value |
v7 |
Holdings Value (Real-time) |
w |
52-week Range |
| w1 |
Day's Value Change |
w4 |
Day's Value Change (Real-time) |
x |
Stock Exchange |
| y |
Dividend Yield |
|
|
So, for example:
http://finance.yahoo.com/d/quotes.csv?s=GE&f=nkqwxyr1l9t5p4
returns this:
"GENERAL ELEC CO",32.98,"Jun 26","21.30 - 32.98","NYSE",2.66,"Jul
25",28.55,"Jul 3","-0.21%"
To get started I knew that I would need a Quote class to hold the returned data,
and some sort of Utility class to handle the WebRequest and massage the data
into a usable form:
Quote Class:
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace ChatWebApp
{
public class Quote
{
public string Ticker { get; set; }
public string LastTrade { get; set; }
public string Time { get; set;}
public string Open { get; set; }
public string High { get; set; }
public string Low { get; set; }
public string Change { get; set; }
public Quote(){}
public Quote( string ticker, string lastTrade, string time, string open, string high, string low, string change)
{
this.Ticker = ticker;
this.LastTrade = lastTrade;
this.Time = time;
this.Open = open;
this.High = high;
this.Low = low;
this.Change = change;
}
}
Note above that I'm declaring all properties as type string since this is only for
display. To do computations or charting, you might want to use int, Decimal and
DateTime for your properties.
QuoteUtility Class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Web;
namespace ChatWebApp
{
public static class QuoteUtility
{
public static string quoteUrl1 = "http://download.finance.yahoo.com/d/quotes.csv?s=";
// symbol-last trade-last trade time -Open-High-Low-Change (real time)
public static string quoteUrl2 = "&f=sl1t1ohgc6";
public static List<Quote> GetQuotes (string[] tickers)
{
List<Quote>
quotes = new List<Quote>();
string fullQuoteUrl = quoteUrl1;
string symbolsString = String.Empty;
foreach(string q in tickers)
{
fullQuoteUrl
+= q + "+";
}
// remove the "+" sign from the end
fullQuoteUrl
= fullQuoteUrl.TrimEnd(new char[] {'+'});
fullQuoteUrl
+= quoteUrl2;
WebClient
wc = new WebClient();
string rawData = wc.DownloadString(fullQuoteUrl);
// clear out quote marks - don't want
rawData
= rawData.Replace("\"", "");
wc.Dispose();
string[] quoteLines = rawData.Split(new char[] {'\r', '\n'});
foreach(string ql in quoteLines )
{
if (ql != String.Empty)
{
string[] rawQuote = ql.Split(',');
Quote
quote = new Quote(rawQuote[0], rawQuote[1], rawQuote[2], rawQuote[3], rawQuote[4], rawQuote[5],
rawQuote[6]);
quotes.Add(quote);
}
}
return quotes;
}
}
Note that the GetQuotes method accepts a string array of multiple symbols. In this
example, I only get one quote at a time to keep it simple.
Now in the actual ChatService class, I have added the following:
// MasterQuotes is a Dictionary of stock symbols and a list of client sessionIds
that are subscribed to that stock
private Dictionary<string, List<string>> MasterQuotes = new Dictionary<string, List<String>>();
This is used to hold the stock symbol and a List of the sessionIds of each connected
client that has subscribed to the stock.
The logic for handling this is implemented as follows:
protected override void OnMessage(string sessionId, DuplexMessage data)
{
if (data is JoinChatMessage)
{
//If a chatter joined, let all other chatters know
JoinChatMessage
msg = (JoinChatMessage)data;
chatters.Add(sessionId,
msg.nickname);
PushToAllClients(data);
}
else if (data is TextChatMessageToServer)
{
//If a chatter sent a message, broadcast it to all other chatters
TextChatMessageToServer
msg = (TextChatMessageToServer)data;
// Check for a stock subscription---
if (msg.text.ToLower().Contains("subscribe"))
{
string symbol = msg.text.Split(' ')[1];
symbol
= symbol.ToUpper();
// is the symbol already there? If not, add it:
if(!MasterQuotes.ContainsKey( symbol))
{
MasterQuotes.Add(symbol,
new List<string> {sessionId });
}
else
{
//sessionIds is List<String> containing the sessionIds subscribed to this stock
symbol
var
sessionIds = MasterQuotes[symbol];
sessionIds.Add(sessionId);
}
}
else
{
TextChatMessageFromServer
outMsg = new TextChatMessageFromServer();
outMsg.text
= msg.text;
outMsg.textColor
= msg.textColor;
//Incoming chat message does not have the chatter's nickname, so we add it
outMsg.nickname
= chatters[sessionId];
PushToAllClients(outMsg);
}
}
}
So for every message that is sent to the server by a connected client, if it contains
"subscribe", we parse out the stock symbol. Then we check to see if
the symbol has already been stored, and if so, we add the sessionId of the client
to the List. If not, we add the symbol to the Dictionary first, and a new List<string>
containing the sessionId.
Finally, the Timer fires the StockUpdate method every 30 seconds:
void StockUpdate(object o)
{
// iterate all the symbols we have stored for clients
foreach (string symbol in MasterQuotes.Keys)
{
StockTickerMessage
stm = new StockTickerMessage();
// get the list of sessionIds that are subscribed to this stock symbol...
var
clients = MasterQuotes[symbol];
stm.stock
= symbol;
// get the quote for this stock (can also do multiple symbols but keeping it simple
for now)
var
quotes = QuoteUtility.GetQuotes(new string[] {stm.stock});
stm.price
= Decimal.Parse(quotes[0].LastTrade);
stm.LastTradeTime
= quotes[0].Time;
stm.Change
= quotes[0].Change;
stm.High
= quotes[0].High;
stm.Open
= quotes[0].Open;
stm.Low
= quotes[0].Low;
// send out the stock update message to all the clients subscribed to this symbol
PushToSelectedClients(stm,
clients);
}
}
PushToSelectedClients:
protected void PushToSelectedClients(DuplexMessage message,List<string> sessions)
{
lock (syncRoot)
{
// send stock symbol update to every client who is subscribed to this stock ticker...
foreach (string session in sessions)
{
PushMessageToClient(session,
message);
}
}
}
A sample session:

You can download the Visual Studio 2008 Silverlight 3 Solution here. I hope this gives you some good ideas on using Duplex.