I started out with a Silverlight-enabled WCF Service that would allow me to use some
previous code I'd written to accept a Stock (or index) Symbol, a beginning date
and an ending date, and which would call out to the appropriate Yahoo Finance
historical data url to get the csv- formatted list of historical quotes. This
older code massaged the list into a DataTable. For Silverlight, I created a StockData
class and simply iterated over the list of DataRows, turning it into a List<T>
of type StockData, which is perfect for Silverlight to consume on the client
side:
public class StockData
{
//Date Open High Low Close Volume Adj
Close
public DateTime Date { get; set; }
public double Open { get; set; }
public double High { get; set; }
public double Low { get; set;}
public double Close { get; set; }
public long Volume { get; set; }
public double AdjClose { get; set; }
public StockData() {}
public StockData( DateTime date, double open, double high, double low,
double close, long volume, double adjClose)
{
Date
= date;
Open
= open;
High
= high;
Low
= low;
Close
= close;
Volume
= volume;
AdjClose
= adjClose;
}
}
When this is returned to the Silverlight calling application, it is exposed as an
ObservableCollection which makes it easy to databind to the Chart control. Here
is the "guts" of the Downloader class:
using System;
using System.Data;
using System.Net;
namespace StockCharts.Web
{
public class Downloader
{
private string urlTemplate =
@"http://ichart.finance.yahoo.com/table.csv?s=[symbol]&a=" +
"[startMonth]&b=[startDay]&c=[startYear]&d=[endMonth]&e=" +
"[endDay]&f=[endYear]&g=d&ignore=.csv";
public DataTable UpdateSymbol(string symbol, DateTime? startDate, DateTime? endDate)
{
if (!endDate.HasValue) endDate = DateTime.Now;
if (!startDate.HasValue) startDate = DateTime.Now.AddYears(-2);
if (String.IsNullOrEmpty(symbol))
throw new ArgumentException("Symbol invalid: " + symbol);
// NOTE: Yahoo's scheme uses a month number 1 less than actual e.g. Jan. ="0"
int strtMo = startDate.Value.Month - 1;
string startMonth = strtMo.ToString();
string startDay = startDate.Value.Day.ToString();
string startYear = startDate.Value.Year.ToString();
int endMo = endDate.Value.Month - 1;
string endMonth = endMo.ToString();
string endDay = endDate.Value.Day.ToString();
string endYear = endDate.Value.Year.ToString();
urlTemplate
= urlTemplate.Replace("[symbol]", symbol);
urlTemplate
= urlTemplate.Replace("[startMonth]", startMonth);
urlTemplate
= urlTemplate.Replace("[startDay]", startDay);
urlTemplate
= urlTemplate.Replace("[startYear]", startYear);
urlTemplate
= urlTemplate.Replace("[endMonth]", endMonth);
urlTemplate
= urlTemplate.Replace("[endDay]", endDay);
urlTemplate
= urlTemplate.Replace("[endYear]", endYear);
string history = String.Empty;
var
wc = new WebClient();
try
{
history
= wc.DownloadString(urlTemplate);
}
catch (WebException wex)
{
throw;
}
finally
{
wc.Dispose();
}
var
dt = new DataTable();
// trim off unused characters from end of line
history
= history.Replace("\r", "");
// split to array on end of line
string[] rows = history.Split('\n');
// split to colums
string[] colNames = rows[0].Split(',');
// add the columns to the DataTable
foreach (string colName in colNames)
dt.Columns.Add(colName);
DataRow
row = null;
string[] rowValues;
object[] rowItems;
// split the rows
for (int i = rows.Length - 1; i > 0; i--)
{
rowValues
= rows[i].Split(',');
row
= dt.NewRow();
rowItems
= ConvertStringArrayToObjectArray(rowValues);
if (rowItems[0] != null && (string) rowItems[0] != "")
{
row.ItemArray
= rowItems;
dt.Rows.Add(row);
}
}
return dt;
}
private object[] ConvertStringArrayToObjectArray(string[] input)
{
int elements = input.Length;
var
objArray = new object[elements];
input.CopyTo(objArray,
0);
return objArray;
}
}
}
For most developers, the above should be self-explanatory. Note the use of nullable
DateTime parameters to allow for a default chart span. Next, I need an OperationContract
in my service that makes a call to the above method:
[OperationContract]
public List<StockData> GetStockData(string symbol, DateTime startDate, DateTime endDate)
{
List<StockData>
stockList = new List<StockData>();
Downloader
d = new Downloader();
DataTable
dt= d.UpdateSymbol(symbol, startDate, endDate);
// Date Open High Low Close Volume Adj
Close
foreach(DataRow row in dt.Rows)
{
DateTime
date = Convert.ToDateTime(row["Date"]);
double open = Convert.ToDouble(row["Open"]);
double high = Convert.ToDouble(row["High"]);
double low = Convert.ToDouble(row["Low"]);
double close = Convert.ToDouble(row["Close"]);
long volume = Convert.ToInt64(row["Volume"]);
double adjClose = Convert.ToDouble(row["Adj Close"]);
StockData
sd = new StockData(date, open, high, low, close, volume, adjClose);
stockList.Add(sd);
}
return stockList;
}
Finally, here is the XAML Markup in my Page, and then, following that, the codebehind
code in Page.xaml.cs:
<UserControl xmlns:charting="clr-namespace:Microsoft.Windows.Controls.DataVisualization.Charting;assembly=Microsoft.Windows.Controls.DataVisualization" x:Class="StockCharts.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="600" Height="400">
<Grid x:Name="LayoutRoot" Background="White">
<charting:Chart x:Name="Chart1" Width="750" Height="350" VerticalAlignment="Top" >
<charting:Chart.Series>
<charting:LineSeries
ItemsSource="{Binding}"
DependentValueBinding="{Binding Close}"
IndependentValueBinding="{Binding Date}" >
<charting:LineSeries.DataPointStyle>
<Style
TargetType="Control">
<Setter
Property="Visibility" Value="Collapsed"/>
</Style>
</charting:LineSeries.DataPointStyle>
</charting:LineSeries>
</charting:Chart.Series>
</charting:Chart>
<TextBox
x:Name="text1" VerticalAlignment="Bottom" HorizontalAlignment="Left" Text="YHOO"></TextBox>
<Controls:Button
xmlns:Controls="clr-namespace:System.Windows.Controls;assembly=System.Windows" Width="200" Height="24" Content="Chart It!" VerticalAlignment="Bottom" Click="Button_Click" ></Controls:Button>
</Grid>
</UserControl>
And here is the codebehind that does the work:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using StockCharts.StockService;
namespace StockCharts
{
public partial class Page : UserControl
{
public Page()
{
InitializeComponent();
}
void c_GetStockDataCompleted(object sender, GetStockDataCompletedEventArgs e)
{
ObservableCollection
stockDatas = e.Result;
Chart1.LegendTitle
= null;
Chart1.Title
= text1.Text;
Dispatcher.BeginInvoke(()
=> this.Chart1.DataContext= stockDatas);
}
private void Button_Click(object sender, RoutedEventArgs e)
{
string symbol = this.text1.Text;
StockService.stocksClient
c = new stocksClient();
c.GetStockDataCompleted
+= new EventHandler(c_GetStockDataCompleted);
c.GetStockDataAsync(symbol,
DateTime.Now.AddYears(-2), DateTime.Now);
}
}
}
The result, when you fill in a stock or index symbol and press the button, looks
like this:

Obviously this is just a start - If I had time and really wanted to make a top-rate
stock charting app in Silverlight, I'd add a lower chart for the volume, and
a selection of technical analysis indicators such as moving averages, stochastics,
Relative Strength, and more. I'd probably also add an autocomplete control for
selecting the desired stock symbol from a typeahead dropdown list.
And of course, if I really wanted to get "flashy" (no pun intended) I'd
think about how I can animate the chart as it appears.
But as a proof-of-concept, I can safely say that it doesn't take much to create really
stunning chart graphics in Silverlight- no matter what the business application
may be.
Microsoft's stock chart doesn't look very pretty right now, but then neither does
anybody else's. It could be a while before we bottom out and have a new bull
market -- a long while, unfortunately. If you really want to see a picture of
a disaster, type in FNM in the symbol textbox. That's -- you guessed it -- Fannie
Mae.
This stock chart Silverlight Application should run fine in Internet Explorer, Firefox,
or Google Chrome (with the Dev Channel enabled).
Silverlight has matured remarkably over the last year or so, and has become a quite
exciting RIA platform for developers. Stock charting and analysis applications
are just the tip of the iceberg.
You can download the complete Visual Studio 2008 solution here.