Skip to content

Commit

Permalink
Adding Excel Charting Demo
Browse files Browse the repository at this point in the history
  • Loading branch information
moloneymb committed Apr 14, 2013
1 parent ad6d32e commit 96b6cde
Show file tree
Hide file tree
Showing 5 changed files with 380 additions and 2 deletions.
21 changes: 21 additions & 0 deletions Excel/Charting/ChartingDemo.fsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#load @"C:\ExcelEnv.fsx"
#load @"C:\StocksScript.fsx"
open StockScript
open System
open System.Net
open Microsoft.Office.Interop.Excel


let now = System.DateTime.Now

let chart = Excel.NewChart().Value

let msft = read ("MSFT", now.AddMonths(-6), now)
chart |> addStockHistory msft
chart |> addMovingAverages msft

chart |> clear

for stock in ["AAPL";"MSFT";"GOOG"] do
chart |> addStockHistory (read (stock, now.AddMonths(-6), now))
chart |> addMovingAverages (read (stock, now.AddMonths(-6), now))
205 changes: 205 additions & 0 deletions Excel/Charting/ExcelEnv.fsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
module Excel
//Written by Mathias Brandewinder (Twitter: @brandewinder Blog: http://www.clear-lines.com/blog/)
#r @"C:\Program Files (x86)\Microsoft Visual Studio 11.0\Visual Studio Tools for Office\PIA\Office14\office.dll"
#r @"C:\Program Files (x86)\Microsoft Visual Studio 11.0\Visual Studio Tools for Office\PIA\Office14\Microsoft.Office.Interop.Excel.dll"

open Microsoft.Office.Interop.Excel
open System.Runtime.InteropServices

// Attach to the running instance of Excel, if any
let Attach () =
try
Marshal.GetActiveObject("Excel.Application")
:?> Microsoft.Office.Interop.Excel.Application
|> Some
with
| _ ->
printfn "Could not find running instance of Excel"
None

// Find the Active workbook, if any
let Active () =
let xl = Attach ()
match xl with
| None -> None
| Some(xl) ->
try
xl.ActiveWorkbook |> Some
with
| _ ->
printfn "Could not find active workbook"
None

let private flatten (arr: string [,]) =
let iMax = Array2D.length1 arr
let jMax = Array2D.length2 arr
[| for i in 1 .. iMax ->
[| for j in 1 .. jMax -> arr.[i,j] |] |]

// Grab Selected Range, if any
let Selection () =
let xl = Attach ()
match xl with
| None -> None
| Some(xl) ->
try
let selection = xl.Selection :?> Range
selection.Value2 :?> System.Object [,]
|> Array2D.map (fun e -> e.ToString())
|> flatten |> Some
with
| _ ->
printfn "Invalid active selection"
None

// Create a new Chart in active workbook
let NewChart () =
let wb = Active ()
match wb with
| None ->
printfn "No workbook"
None
| Some(wb) ->
try
let charts = wb.Charts
charts.Add () :?> Chart |> Some
with
| _ ->
printfn "Failed to create chart"
None

// Plots single-argument function(s) over an interval
type Plot (f: float -> float, over: float * float) =
let mutable functions = [ f ]
let mutable over = over
let mutable grain = 50
let chart = NewChart ()
let values () =
let min, max = over
let step = (max - min) / (float)grain
[| min .. step .. max |]
let draw f =
match chart with
| None -> ignore ()
| Some(chart) ->
let seriesCollection = chart.SeriesCollection() :?> SeriesCollection
let series = seriesCollection.NewSeries()
let xValues = values ()
series.XValues <- xValues
series.Values <- xValues |> Array.map f
let redraw () =
match chart with
| None -> ignore ()
| Some(chart) ->
let seriesCollection = chart.SeriesCollection() :?> SeriesCollection
for s in seriesCollection do s.Delete() |> ignore
functions |> List.iter (fun f -> draw f)

do
match chart with
| None -> ignore ()
| Some(chart) ->
chart.ChartType <- XlChartType.xlXYScatter
let seriesCollection = chart.SeriesCollection() :?> SeriesCollection
draw f

member this.Add(f: float -> float) =
match chart with
| None -> ignore ()
| Some(chart) ->
functions <- f :: functions
draw f

member this.Rescale(min, max) =
over <- (min, max)
redraw()

member this.Zoom(zoom: int) =
grain <- zoom
redraw()

// Plots surface of 2-argument function
type Surface (f: float -> float -> float, xOver: (float * float), yOver: (float * float)) =
let mutable xOver, yOver = xOver, yOver
let mutable grain = 20
let chart = NewChart ()
let values over =
let min, max = over
let step = (max - min) / (float)grain
[| min .. step .. max |]

let redraw () =
match chart with
| None -> ignore ()
| Some(chart) ->
let xl = chart.Application
xl.ScreenUpdating <- false
let seriesCollection = chart.SeriesCollection() :?> SeriesCollection
for s in seriesCollection do s.Delete() |> ignore
let xs, ys = values xOver, values yOver
for x in xs do
let series = seriesCollection.NewSeries()
series.Name <- (string)x
series.XValues <- ys
series.Values <- ys |> Array.map (f x)
chart.ChartType <- XlChartType.xlSurfaceWireframe
xl.ScreenUpdating <- true

do
match chart with
| None -> ignore ()
| Some(chart) -> redraw ()

member this.Rescale((xmin, xmax), (ymin, ymax)) =
xOver <- (xmin, xmax)
yOver <- (ymin, ymax)
redraw ()

member this.Zoom(zoom: int) =
grain <- zoom
redraw ()

// Create XY scatterplot, colored by group
let scatterplot<'a when 'a: equality> (data: (float * float * 'a ) seq) =
let chart = NewChart ()
match chart with
| None -> ignore ()
| Some(chart) ->
let xl = chart.Application
xl.ScreenUpdating <- false
let seriesCollection = chart.SeriesCollection() :?> SeriesCollection
let groups = data |> Seq.map (fun (_, _, g) -> g) |> Seq.distinct
for group in groups do
let xs, ys, _ = data |> Seq.filter (fun (_, _, g) -> g = group) |> Seq.toArray |> Array.unzip3
let series = seriesCollection.NewSeries()
series.Name <- group.ToString()
series.XValues <- xs
series.Values <- ys
chart.ChartType <- XlChartType.xlXYScatter
xl.ScreenUpdating <- true

// Create XY scatterplot, colored by group, with labels
let labeledplot<'a when 'a: equality> (data: (float * float * 'a * string ) seq) =
let chart = NewChart ()
match chart with
| None -> ignore ()
| Some(chart) ->
let xl = chart.Application
xl.ScreenUpdating <- false
let seriesCollection = chart.SeriesCollection() :?> SeriesCollection
let groups = data |> Seq.map (fun (_, _, g, _) -> g) |> Seq.distinct
for group in groups do
let filtered = data |> Seq.filter (fun (_, _, g, _) -> g = group) |> Seq.toArray
let xs = filtered |> Array.map (fun (x, _, _, _) -> x)
let ys = filtered |> Array.map (fun (_, y, _, _) -> y)
let ls = filtered |> Array.map (fun (_, _, _, l) -> l)
let series = seriesCollection.NewSeries()
series.Name <- group.ToString()
series.XValues <- xs
series.Values <- ys
series.HasDataLabels <- true
for i in 1 .. filtered.Length do
let point = series.Points(i) :?> Point
point.DataLabel.Text <- ls.[i-1]
chart.ChartType <- XlChartType.xlXYScatter
xl.ScreenUpdating <- true
3 changes: 3 additions & 0 deletions Excel/Charting/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## Charting Example

Update the references in file `ChartingDemo.fsx` and copy the contents of this file into the Excel Hosted Tsunami Shell.
142 changes: 142 additions & 0 deletions Excel/Charting/StocksScript.fsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
module StockScript
/// A translation by Matthew Moloney of http://vstostocks.codeplex.com/ by Mathias Brandewinder (Twitter: @brandewinder Blog: http://www.clear-lines.com/blog/)
#r @"C:\Program Files (x86)\Microsoft Visual Studio 11.0\Visual Studio Tools for Office\PIA\Office14\Microsoft.Office.Interop.Excel.dll"
open Microsoft.Office.Interop.Excel
open System
open System.Net

type TradingDaySummary = {Day : DateTime; Volume : int64; Open : float; Close : float; High : float; Low : float}

type StockHistory = {Symbol : string; StartDate : DateTime; EndDate : DateTime; History : TradingDaySummary[]}

let read(symbol : string, startDate : DateTime, endDate : DateTime) : StockHistory =
let query = sprintf "http://ichart.finance.yahoo.com/table.csv?s=%s&a=%i&b=%i&c=%i&d=%i&e=%i&f=%i&g=d&ignore=.csv"
symbol (startDate.Month - 1) startDate.Day startDate.Year (endDate.Month - 1) endDate.Day endDate.Year

use client = new WebClient()
let history =
let res = client.DownloadString(query)
[|
for line in res.Split([|char 10|]) |> Seq.skip 1 do
match line.Split([|','|]) with
| [|day;``open``;high;low;close;volume; _|] ->
yield {Day = DateTime.Parse(day); Open = float ``open``; High = float high; Low = float low;
Close = float close; Volume = Int64.Parse volume}
| _ -> ()
|]
{Symbol = symbol; StartDate = startDate; EndDate = endDate; History = history}

let run (history : StockHistory, horizon : int, runs : int) =
let closeSeries = history.History |> Array.map (fun x -> x.Close)
let returnsSeries = closeSeries |> Seq.pairwise |> Seq.map (fun (today,tomorrow) -> (tomorrow - today) / today) |> Seq.toArray
let seed = new Random()
let initialValue = 100.
[|0..runs|]
|> Array.map (fun _ ->
let rng = Random(seed.Next())
/// Simulate
async {
return
[| 0..horizon - 1 |]
|> Array.map (fun _ -> returnsSeries.[rng.Next(returnsSeries.Length)])
|> Array.fold (fun value ret -> value * (1. + ret)) initialValue
})
|> Async.Parallel
|> Async.RunSynchronously




type SeriesData = {Name : string; XValues : obj[]; Values : obj[] }

let appendSeries (chartType : XlChartType, seriesData : SeriesData) (chart : Chart) =
chart.ChartType <- chartType
let seriesCollection = chart.SeriesCollection() :?> SeriesCollection
let series = seriesCollection.NewSeries()
series.Name <- seriesData.Name
series.Values <- seriesData.Values
series.XValues <- seriesData.XValues
chart

let addStockHistory (history : StockHistory) (chart : Chart) =
let dataPoints = history.History |> Array.sortBy (fun x -> x.Day)
let xValues = dataPoints |> Array.map (fun x -> box x.Day);

let close = {
Name = history.Symbol;
XValues = xValues
Values = dataPoints |> Array.map (fun x -> box x.Close)
}
appendSeries (XlChartType.xlLine, close) chart |> ignore

let addMovingAverages (history : StockHistory) (chart : Chart) =
let dataPoints = history.History |> Array.sortBy (fun x -> x.Day)
let xValues = dataPoints |> Array.map (fun x -> box x.Day);
let movingAverage (day:DateTime) (length:int) =
let xs =
dataPoints
|> Seq.takeWhile (fun x -> x.Day <= day)
|> Seq.toArray
|> Array.rev
|> Array.map (fun x -> x.Close)
|> Seq.truncate length
|> Seq.toArray
if xs.Length = 0 then None else Some((xs |> Array.sum) / float xs.Length)


for averages in [|10;50;100|] do
let series = {
Name = sprintf "MA %i days" averages
XValues = xValues
Values = dataPoints |> Array.choose (fun x -> movingAverage x.Day averages |> Option.map box)
}
appendSeries (XlChartType.xlLine, series) chart |> ignore



let addValueDistibution (history : StockHistory, horizon : int, runs : int) (chart : Chart) =
let projections = run (history, horizon, runs)
let min = projections |> Seq.min |> int
let max = projections |> Seq.max |> int
let xValues = [|min..max|]
let values = [|for xValue in xValues -> projections |> Array.filter ((int >> (=) xValue)) |> Array.length |]
let data =
{
Name = history.Symbol
XValues = xValues |> Array.map box
Values = values |> Array.map box
}
appendSeries (XlChartType.xlColumnStacked, data) chart


let writeHistory(history : StockHistory, worksheet : Worksheet) =
let DateColumn = 0
let OpenColumn = 1
let CloseColumn = 2

let length = history.History.Length
let dataArray = Array2D.init (length + 1) 6 (fun _ _ -> box null)
let formatArray = Array2D.init (length + 1) 6 (fun _ _ -> box null)

dataArray.[0,DateColumn] <- box "Date"
dataArray.[0,OpenColumn] <- box "Open"
dataArray.[0,CloseColumn] <- box "Close"

history.History |> Array.iteri (fun i day ->
let line = i + 1

dataArray.[line, DateColumn] <- box day.Day
dataArray.[line, OpenColumn] <- box day.Open
dataArray.[line, CloseColumn] <- box day.Close

formatArray.[line, DateColumn] <- box "YYYY/MM/DD"
formatArray.[line, OpenColumn] <- box "#,##0.00"
formatArray.[line, CloseColumn] <- box "#,##0.00"
)
let range = worksheet.Range(worksheet.Cells.[1,1], worksheet.Cells.[length + 1, 6])
range.Value2 <- dataArray
range.NumberFormat <- formatArray

let clear (c:Chart) =
let sc = c.SeriesCollection() :?> Microsoft.Office.Interop.Excel.SeriesCollection
for i in [sc.Count .. -1 .. 1] do sc.Item(i).Delete() |> ignore
11 changes: 9 additions & 2 deletions Excel/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
## Excel Examples ##
## Excel Examples

####To install
####Prerequisites
VSTO Runtime for Excel

`e.g. VSTO 2010 http://www.microsoft.com/en-gb/download/details.aspx?id=35594`

####To install Tsunami for Excel

Load the VSTO file at `C:\Program Files (x86)\Tsunami\ExcelFSharp.vsto`

Expand All @@ -13,6 +18,8 @@ Ribbon -> View -> F# -> Tsunami

Currently Tsunami is loads when Excel loads and adds a noticeable lag to opening Excel. In the future this lag will be delayed to the first time Tsunami is invoked within Excel.

##Excel Charting

##Excel F# UDFs
####Prerequisite
[F# PowerPack](http://fsharppowerpack.codeplex.com/)
Expand Down

0 comments on commit 96b6cde

Please sign in to comment.