diff --git a/BJExcelLib.sln b/BJExcelLib.sln
new file mode 100644
index 0000000..b202ee7
--- /dev/null
+++ b/BJExcelLib.sln
@@ -0,0 +1,51 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 2013
+VisualStudioVersion = 12.0.21005.1
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UDFs", "UDFs", "{C34891EC-85D9-4CCE-880D-9F81F84677C3}"
+ ProjectSection(SolutionItems) = preProject
+ BJExcelLib\UDF.American.fs = BJExcelLib\UDF.American.fs
+ BJExcelLib\UDF.Blackscholes.fs = BJExcelLib\UDF.Blackscholes.fs
+ BJExcelLib\UDF.Date.fs = BJExcelLib\UDF.Date.fs
+ BJExcelLib\UDF.Interpolation.fs = BJExcelLib\UDF.Interpolation.fs
+ BJExcelLib\UDF.Misc.fs = BJExcelLib\UDF.Misc.fs
+ BJExcelLib\UDF.Volatility.fs = BJExcelLib\UDF.Volatility.fs
+ EndProjectSection
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Finance", "Finance", "{733C851A-70E4-44BF-96D5-F361562FA094}"
+ ProjectSection(SolutionItems) = preProject
+ BJExcelLib\Finance.Blackscholes.fs = BJExcelLib\Finance.Blackscholes.fs
+ BJExcelLib\Finance.Date.fs = BJExcelLib\Finance.Date.fs
+ BJExcelLib\Finance.JuZhong.fs = BJExcelLib\Finance.JuZhong.fs
+ BJExcelLib\Finance.SVI.fs = BJExcelLib\Finance.SVI.fs
+ EndProjectSection
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Math", "Math", "{99FB6D68-12D5-41DC-9272-7BC467A21501}"
+ ProjectSection(SolutionItems) = preProject
+ BJExcelLib\Math.Interpolation.fs = BJExcelLib\Math.Interpolation.fs
+ EndProjectSection
+EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "BJExcelLib", "BJExcelLib\BJExcelLib.fsproj", "{CCCFFCBC-7434-4262-A7C1-730F94362E7F}"
+EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "BJExcelLibTests", "BJExcelLibTests\BJExcelLibTests.fsproj", "{1F3EDE6E-7D84-45BD-9B0C-00198760A714}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {CCCFFCBC-7434-4262-A7C1-730F94362E7F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CCCFFCBC-7434-4262-A7C1-730F94362E7F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CCCFFCBC-7434-4262-A7C1-730F94362E7F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CCCFFCBC-7434-4262-A7C1-730F94362E7F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1F3EDE6E-7D84-45BD-9B0C-00198760A714}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1F3EDE6E-7D84-45BD-9B0C-00198760A714}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1F3EDE6E-7D84-45BD-9B0C-00198760A714}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1F3EDE6E-7D84-45BD-9B0C-00198760A714}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/BJExcelLib/BJExcelLib-AddIn.dna b/BJExcelLib/BJExcelLib-AddIn.dna
new file mode 100644
index 0000000..5f6f2e4
--- /dev/null
+++ b/BJExcelLib/BJExcelLib-AddIn.dna
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/BJExcelLib/BJExcelLib-AddIn64.dna b/BJExcelLib/BJExcelLib-AddIn64.dna
new file mode 100644
index 0000000..389c8b2
--- /dev/null
+++ b/BJExcelLib/BJExcelLib-AddIn64.dna
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/BJExcelLib/BJExcelLib.fsproj b/BJExcelLib/BJExcelLib.fsproj
new file mode 100644
index 0000000..4ea6d3b
--- /dev/null
+++ b/BJExcelLib/BJExcelLib.fsproj
@@ -0,0 +1,127 @@
+
+
+
+
+ Debug
+ AnyCPU
+ 2.0
+ cccffcbc-7434-4262-a7c1-730f94362e7f
+ Library
+ BJExcelLib
+ BJExcelLib
+ v4.5
+ BJExcelLib
+ 4.3.0.0
+
+
+ true
+ full
+ false
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ 3
+ bin\Debug\BJExcelLib.XML
+ Program
+ C:\Program Files\Microsoft Office 15\root\office15\EXCEL.EXE
+ D:\Programmeren\BJExcelLib\BJExcelLib\bin\Debug\BJExcelLib-Addin.xll
+
+
+ pdbonly
+ true
+ true
+ bin\Release\
+ TRACE
+ 3
+ bin\Release\BJExcelLib.XML
+
+
+ 11
+
+
+
+
+ $(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets
+
+
+
+
+ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+
+
+ C:\Programming\alglib 3.8.0\alglibnet2.dll
+
+
+ ..\packages\Excel-DNA.0.30.3\lib\ExcelDna.Integration.dll
+ False
+
+
+ True
+
+
+ ..\packages\FSharpx.Core.1.8.39\lib\40\FSharpx.Core.dll
+ True
+
+
+ ..\packages\MathNet.Numerics.2.6.1\lib\net40\MathNet.Numerics.dll
+ True
+
+
+ ..\packages\MathNet.Numerics.FSharp.2.6.0\lib\net40\MathNet.Numerics.FSharp.dll
+ True
+
+
+ ..\packages\MathNet.Numerics.2.6.1\lib\net40\MathNet.Numerics.IO.dll
+ True
+
+
+
+
+
+
+
+ echo F | xcopy "D:\Programmeren\BJExcelLib\packages\Excel-DNA.0.30.3\tools\ExcelDna.xll" "$(TargetDir)BJExcelLib-AddIn.xll" /C /Y
+echo F | xcopy "D:\Programmeren\BJExcelLib\packages\Excel-DNA.0.30.3\tools\ExcelDna64.xll" "$(TargetDir)BJExcelLib-AddIn64.xll" /C /Y
+"D:\Programmeren\BJExcelLib\packages\Excel-DNA.0.30.3\tools\ExcelDnaPack.exe" "$(TargetDir)BJExcelLib-AddIn.dna" /Y
+"D:\Programmeren\BJExcelLib\packages\Excel-DNA.0.30.3\tools\ExcelDnaPack.exe" "$(TargetDir)BJExcelLib-AddIn64.dna" /Y
+
+
+
\ No newline at end of file
diff --git a/BJExcelLib/ExcelDna.Cache.fs b/BJExcelLib/ExcelDna.Cache.fs
new file mode 100644
index 0000000..ce9261b
--- /dev/null
+++ b/BJExcelLib/ExcelDna.Cache.fs
@@ -0,0 +1,56 @@
+namespace BJExcelLib.ExcelDna
+
+open ExcelDna.Integration
+open ExcelDna.Integration.Rtd
+open System.Collections.Generic
+
+
+/// Module that allows creation of persistent objects from excel. Got the idea
+/// (and a big part of the code) from the ExcelDNA group in a post by "DC":
+/// https://groups.google.com/forum/#!searchin/exceldna/xlcache/exceldna/E4vIOSNwHm0/yHmVTW4FHA0J
+module public Cache =
+
+ /// The cache that holds objects indexed by a key
+ let private cache = Dictionary<_,_>()
+ let private tagstore = Dictionary<_,_>()
+
+ /// Registers an object in the cache. The tag is an id for the category the object belongs to
+ /// The tag is not equal to the final handle the object gets, because a counter will be prepared
+ let public register tag o =
+ let counter =
+ match tagstore.TryGetValue tag with
+ | true, c -> c + 1
+ | _ -> 1
+
+ tagstore.[tag] <- counter
+ let handle = tag + "." + counter.ToString()
+ cache.[handle] <- box o
+ XlCall.RTD("BJExcelLib.ExcelDna.CacheRTD", null, handle)
+
+ /// Removes a given handle from the cache
+ let public unregister handle =
+ if cache.ContainsKey(handle) then cache.Remove(handle) |> ignore
+
+ /// resets the cache by removing all values in there
+ let public reset =
+ cache.Clear()
+ tagstore.Clear()
+
+ /// Finds a value in the cache. If no value is found, None is returned. If a value is found, then
+ /// the the result is wrapped in Some and returned, otherwise None is returned.
+ let lookup handle =
+ match cache.TryGetValue handle with
+ | true, value -> Some(value)
+ | _ -> None
+
+/// Excel RTD server to handle the registering/unregistering of object handles
+type public CacheRTD() =
+ inherit ExcelRtdServer()
+ let _topics = new Dictionary()
+ override x.ConnectData(topic:ExcelRtdServer.Topic, topicInfo:IList, newValues:bool byref) =
+ let name = topicInfo.Item(0)
+ _topics.[topic] <- name
+ name |> box
+ override x.DisconnectData(topic:ExcelRtdServer.Topic) =
+ _topics.[topic] |> Cache.unregister
+ _topics.Remove(topic) |> ignore
\ No newline at end of file
diff --git a/BJExcelLib/ExcelDna.IO.fs b/BJExcelLib/ExcelDna.IO.fs
new file mode 100644
index 0000000..44a5f73
--- /dev/null
+++ b/BJExcelLib/ExcelDna.IO.fs
@@ -0,0 +1,238 @@
+namespace BJExcelLib.ExcelDna
+
+/// This module provides IO functions for UDFs created by ExcelDNA.
+/// Because of a combination of the type inference in F# and performance reasons, all ExcelDNA function inputs in this project have input type "obj". This module contains functionality
+/// to cast those variables to other types and conversly convert other types back to obj's (or obj []'s) to return to excel. Next to this, there is some functionality to deal with returning
+/// option types to excel.
+module public IO =
+
+ open BJExcelLib.Util.Extensions
+ open ExcelDna.Integration
+
+ let (|ExcelError|_|) x =
+ if x.GetType() = typeof then Some() else None
+
+ let (|ExcelEmpty|_|) x =
+ if x.GetType() = typeof then Some() else None
+
+ let (|ExcelMissing|_|) x =
+ if x.GetType() = typeof then Some() else None
+
+ /// Takes an obj value and check if it's an valid argument coming from excel or not. Returns an obj option.
+ let private wrapErrorValues x =
+ match x with
+ | ExcelError | ExcelEmpty | ExcelMissing -> None
+ | _ -> Some(x)
+
+ /// wraps primitive values + strings and date times into a Some(value), everything else becomes None
+ let private wrapprimitive x = if x.GetType().IsPrimitive then Some(x)
+ elif x.GetType() = typeof then Some(x) // Strings are not primitive types in .NET but for my purpose here I'll consider them to be...
+ elif x.GetType() = typeof then Some(x) // DateTimes are not primitive types in .NET but for my purpose here I'll consider them to be
+ else None
+
+ /// Validates an input to a specific type if possible. If not, None is returned.
+ let private validateType<'T> input =
+ try Some(System.Convert.ChangeType(input, typeof<'T>) :?> 'T)
+ with _ -> None //This error catching mechanism is slow, but shouldn't occur to frequently if the wrapErrorValues function is used to catch wrong input first
+
+ /// Validates an input to a specific given type if possible. If not, None is returned.
+ let private validate<'T> input =
+ input |> wrapErrorValues
+ |> Option.bind validateType<'T>
+
+ /// Takes an obj option and tries to cast it into Some(string). If not possible, None is returned
+ let public validateString input = validate input
+
+ /// Takes an obj option and tries to cast it into Some(float). If not possible, None is returned
+ let public validateFloat input = validate input
+
+ /// Takes an obj option and tries to cast it into Some(int). If not possible, None is returned
+ let public validateInt input = validate input
+
+ /// Takes an obj option and tries to cast it into Some(bool). If not possible, None is returned. Numeric types are mapped to true if they are > 0
+ let public validateBool input =
+ input |> validate
+ |> fun x -> if x.IsSome then x
+ else input |> validateFloat
+ |> Option.map (fun x -> x > 0.) //todo : test whether the same issue occurs as with validatedate, i.e. that booleans are passed as numeric types. In that case, it's probably better to first check on numeric types for performance reasons.
+
+ /// Takes an obj option and tries to cast it into Some(DateTime). If not possible, None is returned. Numeric types are mapped to true if they are > 0
+ let public validateDate input =
+ input |> validate |> Option.map (fun x -> System.DateTime.FromOADate(x)) // First check for float rather than date because that's how obj type's typically come through.
+ // Makes a huge difference on performance, vs try-catching System.DateTime first, because catching
+ // errors is so slow.
+ |> fun x -> if x.IsSome then x else input |> validate
+ |> fun x -> if x.IsSome then x else input |> validate |> Option.bind (fun x -> System.DateTime.tryParse(x))
+
+ /// transfoms an option type to an UDF return value that excel can handle
+ /// None values get transformed to an excelerror.
+ let public valueToExcel value =
+ value |> Option.map box
+ |> FSharpx.Option.getOrElse (ExcelError.ExcelErrorNA :> obj)
+
+ /// Transforms a 1D array into a sequence
+ /// Inputs: validationfunction validates the elements of the array elementswise after they've been checked for error/missing values
+ /// arr is the array to transform
+ let array1DAsSequence validationfunction =
+ Seq.map (wrapErrorValues >> Option.bind validationfunction)
+
+ /// Transforms a 1D array into a sequence
+ /// Inputs: validationfunction validates the elements of the array elementswise after they've been checked for error/missing values
+ /// defaultvalue is the default value to replace missing values with
+ /// arr is the array to transform
+ let array1DAsSequenceWithDefault validationfunction defaultvalue =
+ Seq.map (wrapErrorValues >> Option.bind validationfunction >> (FSharpx.Option.getOrElse defaultvalue))
+
+ /// Transforms a 1D array into a List
+ /// Inputs: validationfunction validates the elements of the array elementswise after they've been checked for error/missing values
+ /// arr = the array to transform
+ let array1DAsList validationfunction arr =
+ arr |> array1DAsSequence validationfunction
+ |> Seq.toList
+
+ /// Transforms a 1D array into a List
+ /// Inputs: validationfunction validates the elements of the array elementswise after they've been checked for error/missing values
+ /// defaultvalue is the default value to replace missing values with
+ /// arr is the array to transform
+ let array1DAsListWithDefault validationfunction defaultvalue arr =
+ arr |> array1DAsSequenceWithDefault validationfunction defaultvalue
+ |> Seq.toList
+
+ /// Transforms a 1D array into an Array with typed (casted) elements
+ /// Inputs: validationfunction validates the elements of the array elementswise after they've been checked for error/missing values
+ /// arr = the array to transform
+ let public array1DAsArray validationfunction =
+ Array.map (wrapErrorValues >> Option.bind validationfunction)
+
+ /// Transforms a 1D array into an Array with typed (casted) elements
+ /// Inputs: validationfunction validates the elements of the array elementswise after they've been checked for error/missing values
+ /// defaultvalue is the default value to replace missing values with
+ /// arr is the array to transform
+ let array1DAsArrayWithDefault validationfunction defaultvalue =
+ Array.map ( wrapErrorValues >>
+ Option.bind validationfunction >>
+ (FSharpx.Option.getOrElse defaultvalue))
+
+ /// Transforms a 2D array into an Array2D with typed (casted) elements and the data elements in the various rows.
+ /// Inputs: roworiented defines whether the array is interpreted as having rows contain different data elements. If false, the array gets transposed
+ /// validationfunction validates the lemenets of the array elementwise after they've been checked for error/missing values
+ /// arr is the array to transform
+ let array2DAsArray roworiented (validationfunction : 'a -> 'b option) arr =
+ arr |> fun x -> if roworiented then x else Array2D.transpose x
+ |> Array2D.map (wrapErrorValues >> Option.bind validationfunction)
+
+ /// Transforms a 2D array into an Array2D with typed (casted) elements and the data elements in the various rows. Errors are replaced by a defaultvalue
+ /// Inputs: roworiented defines whether the array is interpreted as having rows contain different data elements. If false, the array gets transposed
+ /// validationfunction validates the lemenet so fhte array elementwise after they've been checked for error/missing values
+ /// defaultvalue is the value elements get replaced with if the original value is an error
+ /// arr is the array to transform
+ let array2DAsArrayWithDefault roworiented validationfunction defaultvalue arr =
+ arr |> array2DAsArray roworiented validationfunction
+ |> Array2D.map (defaultArg defaultvalue)
+
+ /// Transforms a 2D array into an sequence with typed (casted) elements and the data elements in the various rows.
+ /// Inputs: roworiented defines whether the array is interpreted as having rows contain different data elements. If false, the array gets transposed
+ /// validationfunction validates the lemenet so fhte array elementwise after they've been checked for error/missing values
+ /// arr is the array to transform
+ let array2DAsSeq roworiented validationfunction arr =
+ let arr = if roworiented then arr else Array2D.transpose arr
+ [| for r in 0 .. (Array2D.length1 arr) - 1 do
+ yield [| for c in 0 .. (Array2D.length2 arr) - 1 do yield arr.[r,c] |] |]
+ |> array1DAsSequence validationfunction
+
+ /// Transforms a 2D array into an sequence with typed (casted) elements and the data elements in the various rows. Errors are replaced by a defaultvalue
+ /// Inputs: roworiented defines whether the array is interpreted as having rows contain different data elements. If false, the array gets transposed
+ /// validationfunction validates the lemenet so fhte array elementwise after they've been checked for error/missing values
+ /// defaultvalue is the value elements get replaced with if the original value is an error
+ /// arr is the array to transform
+ let array2DAsSeqWithDefault roworiented validationfunction defaultvalue transform arr =
+ let arr = if roworiented then arr else Array2D.transpose arr
+ [| for r in 0 .. (Array2D.length1 arr) - 1 do
+ yield [| for c in 0 .. (Array2D.length2 arr) - 1 do yield arr.[r,c] |] |]
+ |> array1DAsSequenceWithDefault validationfunction defaultvalue
+
+ /// Transforms a 2D array into an (1D) List with typed (casted) elements and the data elements in the various rows.
+ /// Inputs: roworiented defines whether the array is interpreted as having rows contain different data elements. If false, the array gets transposed
+ /// validationfunction validates the lemenet so fhte array elementwise after they've been checked for error/missing values
+ /// arr is the array to transform
+ let array2DAsList roworiented transform validationfunction arr =
+ let arr = if roworiented then arr else Array2D.transpose arr
+ [| for r in 0 .. (Array2D.length1 arr) - 1 do
+ yield [| for c in 0 .. (Array2D.length2 arr) - 1 do yield arr.[r,c] |] |]
+ |> array1DAsList validationfunction
+
+ /// Transforms a 2D array into an (1D) List with typed (casted) elements and the data elements in the various rows. Errors are replaced by a defaultvalue
+ /// Inputs: roworiented defines whether the array is interpreted as having rows contain different data elements. If false, the array gets transposed
+ /// validationfunction validates the lemenet so fhte array elementwise after they've been checked for error/missing values
+ /// defaultvalue is the value elements get replaced with if the original value is an error
+ /// arr is the array to transform
+ let array2DAsListWithDefault roworiented transform validationfunction defaultvalue arr =
+ let arr = if roworiented then arr else Array2D.transpose arr
+ [| for r in 0 .. (Array2D.length1 arr) - 1 do
+ yield [| for c in 0 .. (Array2D.length2 arr) - 1 do yield arr.[r,c] |] |]
+ |> array1DAsListWithDefault validationfunction defaultvalue
+
+ /// Transforms a 2D array into an 1D Array with typed (casted) elements and the data elements in the various rows.
+ /// Inputs: roworiented defines whether the array is interpreted as having rows contain different data elements. If false, the array gets transposed
+ /// validationfunction validates the lemenet so fhte array elementwise after they've been checked for error/missing values
+ /// arr is the array to transform
+ let array2DAsArray1D roworiented transform validationfunction arr =
+ let arr = if roworiented then arr else Array2D.transpose arr
+ [| for r in 0 .. (Array2D.length1 arr) - 1 do
+ yield [| for c in 0 .. (Array2D.length2 arr) - 1 do yield arr.[r,c] |] |]
+ |> array1DAsArray validationfunction
+
+ /// Transforms a 2D array into an (1D) Array with typed (casted) elements and the data elements in the various rows. Errors are replaced by a defaultvalue
+ /// Inputs: roworiented defines whether the array is interpreted as having rows contain different data elements. If false, the array gets transposed
+ /// validationfunction validates the elements of the array elementwise after they've been checked for error/missing values
+ /// defaultvalue is the value elements get replaced with if the original value is an error
+ /// arr is the array to transform
+ let array2DAsArray1DWithDefault roworiented transform validationfunction defaultvalue arr =
+ let arr = if roworiented then arr else Array2D.transpose arr
+ [| for r in 0 .. (Array2D.length1 arr) - 1 do
+ yield [| for c in 0 .. (Array2D.length2 arr) - 1 do yield arr.[r,c] |] |]
+ |> array1DAsArrayWithDefault validationfunction defaultvalue
+
+ // array to return if an empty aray has to be returned
+ let private empty2DArray = Array2D.init 1 1 (fun r c -> valueToExcel None)
+
+
+ /// Transforms a sequence of generic data into an 2D array that can be returned to excel as UDF output
+ /// asColumnVector is a boolean that specifies whether the elements of the data sequences are to be
+ /// interpreted as row elements (if asColumnVector = true) or column elements (asColumnVector=false)
+ /// data is a sequence of elements that need to be outputted.
+ /// Transform is a transformation to be applied to the data. There are two things that this funtion
+ /// must do. It must return an array; the various elements of this array are seen as the column (row)
+ /// elements of the output array (which depends on asColumnVector). The second requirement is that
+ /// every element in this array is an option type. Values that equal None are returned as error values
+ /// to excel.
+ let public array2DToExcel asColumnVector data transform =
+ // Not a very efficient implementation as the sequence "data" gets traversed multiple times
+ // and some superfluous array initialization takes place that probably can be avoided at the
+ // cost of slightly more verbose code. However, until performance becomes unacceptable, I
+ // prefer the clarity and simplicity of this code to faster, but more verbose code.
+ let nmbRows = Seq.length data
+ if nmbRows = 0 then empty2DArray
+ else
+ let nmbCols = data |> Seq.nth 0
+ |> transform
+ |> Array.length
+
+ let res = Array2D.create nmbRows nmbCols (valueToExcel None)
+ do data |> Seq.iteri (fun row elem -> elem |> transform
+ |> Array.map (Option.bind wrapprimitive)
+ |> Array.iteri (fun col value -> res.[row,col] <- valueToExcel value))
+
+ res |> fun x -> if asColumnVector then x else Array2D.transpose x
+
+ /// Processes a sequence into an 2D array that can be returned to excel. This 2D array contains only one column and (potentially) multiple rows
+ let public arrayToExcelColumn data = array2DToExcel true data (fun x -> [|Some(x)|] )
+
+ /// Processes a sequence into an 2D array that can be returned to excel. This 2D array contains only one row and (potentially) multiple columns
+ let public arrayToExcelRow data = array2DToExcel false data (fun x -> [|Some(x)|] )
+
+ /// Processes a sequence of values wrapped in options into an 2D array that can be returned to excel. This 2D array contains only one column and (potentially) multiple rows
+ let public optionArrayToExcelColumn data = array2DToExcel true data (fun x -> [|x|])
+
+ /// Processes a sequence of values wrapped in options into an 2D array that can be returned to excel. This 2D array contains only one row and (potentially) multiple columns
+ let public optionArrayToExcelRow data = array2DToExcel false data (fun x -> [|x|])
\ No newline at end of file
diff --git a/BJExcelLib/Finance.Blackscholes.fs b/BJExcelLib/Finance.Blackscholes.fs
new file mode 100644
index 0000000..7148ff8
--- /dev/null
+++ b/BJExcelLib/Finance.Blackscholes.fs
@@ -0,0 +1,160 @@
+namespace BJExcelLib.Finance
+
+type OptionType =
+ | Call
+ | Put
+
+/// Can't have a finance library without blackscholes formulae
+module public Blackscholes =
+
+ open BJExcelLib.Math.NormalDistribution
+
+ // Parameters that determine how searching for implied is done.
+ []
+ let private VOL_ITER = 100; // Max iterations to search for volatility
+ []
+ let private LOW_VOL = 0.001 // Don't search for vols below this
+ []
+ let private HIGH_VOL = 5. // Don't search for vols above this
+ []
+ let private VOL_PRECISION = 0.0001 // Search until convergence in option price down to 1 basis point of spot.
+ // Basically this will fail to converge well for very low premium options (think deep otm with low vol)
+ // but there you can argue that uncertainty about vega will be large anyway (and is unlikely as well).
+
+ /// Root finding algorithm used to find volatilities. Switch here and its switched everywhere in this module
+ let private rootfinder spot f = MathNet.Numerics.FindRoots.bisection VOL_ITER (VOL_PRECISION*spot) LOW_VOL HIGH_VOL f
+
+ /// returns (flag,df,d1,d2)
+ let private getstuff margined flag S K t r b v =
+ let inputOk = S > 0. && K >=0. && v > 0. && t > 0.
+ if not inputOk then None else
+ let flag = if flag = Call then 1. else -1.
+ let df = if margined then 1. else exp(-r*t)
+ let vsqrt = v * sqrt t
+ let d1 = (log(S/K)+b*t)/vsqrt + 0.5*vsqrt
+ Some(flag,df,d1,d1-vsqrt)
+
+ // Forward of an underlying. Not really specific to BS world, but got to put it somewhere
+ let public forward S t b =
+ if S <= 0. || t < 0. then None
+ else Some(S*exp(b*t))
+
+ /// Value of a vanilla european option using generalized BS
+ /// Apart from the usual parameters, there is a parameter
+ /// margined here which specifies whether the option value gets
+ /// margined (this happens for example with Brent options on the ICE)
+ let public value margined flag S K t r b v =
+ match getstuff margined flag S K t r b v with
+ | None -> None
+ | Some(flag,df,d1,d2) -> df*flag*( S*exp(b*t)*cnd(flag*d1)-K*cnd(flag*d2) ) |>Some
+
+
+ /// Value, delta and vega of a vanialla european option using generalized BS
+ /// Apart from the usual parameters, there is a parameter
+ /// margined here which specifies whether the option value gets
+ /// margined (this happens for example with Brent options on the ICE)
+ let public valuedeltavega margined flag S K t r b v =
+ match getstuff margined flag S K t r b v with
+ | None -> None
+ | Some(flag,df,d1,d2) -> let expr1 = exp(b*t)
+ let expr2 = cnd(flag*d1)
+ let value = df*flag*( S*expr1*expr2 - K*cnd(flag*d2) )
+ let delta = df*flag*expr1*expr2
+ let vega = df*K*nd(d2)*sqrt(t)*0.01
+ Some(value,delta,vega)
+
+ /// Delta of a vanilla european option using generalized BS
+ let public delta margined flag S K t r b v =
+ match getstuff margined flag S K t r b v with
+ | None -> None
+ | Some(flag,df,d1,d2) -> Some(flag*df*exp(b*t)*cnd(flag*d1))
+
+ /// Vega of a vanilla european option using generalized BS
+ let public vega margined flag S K t r b v =
+ match getstuff margined flag S K t r b v with
+ | None -> None
+ | Some(_,df,_,d2) -> Some(df*K*nd(d2)*sqrt(t)*0.01)
+
+ /// Daily theta of a vanilla european option using generalized BS
+ let public theta margined flag S K t r b v =
+ match getstuff margined flag S K t r b v with
+ | None -> None
+ | Some(flag,df,d1,d2) -> let fwd = S*exp(b*t)
+ Some(df*(-fwd*(nd(d1)*v/(2.*sqrt(t))+flag*(r-b)*cnd(flag*d1))-flag*r*K*cnd(flag*d2))/365.25)
+
+ /// Gamma of a vnailla european option using generalized BS
+ let public gamma margined flag S K t r b v =
+ match getstuff margined flag S K t r b v with
+ | None -> None
+ | Some(_,df,d1,_) -> Some(df*exp(b*t)*nd(d1)/(S*v*sqrt(t)))
+
+ /// Rho per basis point of a vanilla european option using generalized BS
+ let public rho margined flag S K t r b v =
+ match getstuff margined flag S K t r b v with
+ | None -> None
+ | Some(flag,df,_,d2) -> Some(flag*K*t*df*cnd(flag*d2)*0.0001)
+
+ /// Dual delta (sensitivity to strike) of a vanilla european option using generalized BS
+ let public dualdelta margined flag S K t r b v =
+ match getstuff margined flag S K t r b v with
+ | None -> None
+ | Some(flag,df,_,d2) -> Some(-flag*df*cnd(flag*d2))
+
+ /// dVegadSpot = dDeltadVol = Vanna of a vanilla european option in a generalied BS setting
+ let public dvegadspot margined flag S K t r b v =
+ match getstuff margined flag S K t r b v with
+ | None -> None
+ | Some(_,df,d1,d2) -> Some(-exp(b*t)*df*nd(d1)*d2/v)
+
+ /// dVegadVol = Volga of a vanilla european option in a generalized BS world
+ let public dvegadvol margined flag S K t r b v =
+ match getstuff margined flag S K t r b v with
+ | None -> None
+ | Some(_,df,d1,d2) -> Some(df*K*nd(d2)*sqrt(t)*0.01*d1*d2/v)
+
+ /// ddeltadtim = Charm = Delta bleed of a vanilla european option in a generalized BS world
+ let public ddeltadtime margined flag S K t r b v =
+ match getstuff margined flag S K t r b v with
+ | None -> None
+ | Some(flag,df,d1,d2) -> let bt = b*t
+ let vsqrt = v*sqrt(t)
+ Some(df*exp(bt)*((r-b)*flag*cnd(flag*d1)-nd(d1)*(2.*bt-d2*vsqrt)/(2.*t*vsqrt)))
+
+ /// Implied volatility for an european option with a given premium in a black-scholes world
+ let public impliedvol margined flag S K t r b premium =
+ let price vol = (value margined flag S K t r b vol) |> Option.bind (fun x -> Some(x-premium))
+ match price LOW_VOL, price HIGH_VOL with
+ | None, _ | _, None -> None
+ | Some(x), Some(y) -> if x > 0. || y < 0. then None
+ else let price = price >> Option.get
+ rootfinder S price
+ // todo : improve by using stuff from P. Jaeckel's by implication or from the things Axel Vogt has written on numerics.
+
+
+ /// Value of a european straddle using generalized BS
+ let public straddlevalue margined S K t r b v =
+ match getstuff margined Call S K t r b v with
+ | None -> None
+ | Some(_,df,d1,d2) -> df*( S*exp(b*t)*(cnd(d1)-cnd(-d1))-K*(cnd(d2)-cnd(-d2)) ) |>Some
+
+ /// Delta of a european straddle using generalized BS
+ let public straddledelta margined S K t r b v =
+ match getstuff margined Call S K t r b v with
+ | None -> None
+ | Some(_,df,d1,d2) -> df*exp(b*t)*(cnd(d1)-cnd(-d1)) |> Some
+
+ /// Vega of a european straddle using generalized BS
+ let public straddlevega margined S K t r b v =
+ match getstuff margined Call S K t r b v with
+ | None -> None
+ | Some(_,df,_,d2) -> Some(2.*df*K*nd(d2)*sqrt(t)*0.01)
+
+
+ /// Implied volatility for an european option with a given premium in a black-scholes world
+ let public straddleimpliedvol margined S K t r b premium =
+ let price vol = (straddlevalue margined S K t r b vol) |> Option.bind (fun x -> Some(x-premium))
+ match price LOW_VOL, price HIGH_VOL with
+ | None, _ | _, None -> None
+ | Some(x), Some(y) -> if x > 0. || y < 0. then None
+ else let price = price >> Option.get
+ rootfinder S price
\ No newline at end of file
diff --git a/BJExcelLib/Finance.Date.fs b/BJExcelLib/Finance.Date.fs
new file mode 100644
index 0000000..5a49e71
--- /dev/null
+++ b/BJExcelLib/Finance.Date.fs
@@ -0,0 +1,274 @@
+namespace BJExcelLib.Finance
+
+// Defines a period as consisting of a start end enddate
+type public Period = { startDate:System.DateTime ; endDate:System.DateTime}
+
+// Describes a calendar; consists of
+// - a set describing what days compromise the weekend
+// - a set describing the holidays for the particular calendar
+type public Calendar = { weekendDays:System.DayOfWeek Set; holidays:System.DateTime Set }
+
+/// Defines a tenor, which is an offset to a date. Consists of
+/// - a years field specifiying the year to offset
+/// - a months field specifying the number of months as offset
+/// - a days field specifying the number of days offset
+type public Tenor = { years:int; months:int; days:int }
+
+/// The various types of roll rules that are implemented
+type public RollRule =
+ | Unadjusted
+ | Following
+ | Previous
+ | ModifiedFollowing
+ | ModifiedPrevious
+
+/// The various daycount conventions implemented
+type public DaycountConvention =
+ | Actual_365qrt
+ | Actual_365
+ | Actual_360
+ | Actual_Actual_ISDA
+ | Thirty_360_E
+ | Thirty_360_ISDA
+ | Thirty_360_Eplus
+
+/// Module containing date related functionality.
+/// This module is partially based on http://www.tryfsharp.org/create/lesscode/DiscountCurve.fsx
+module Date =
+
+ // Parses a date from a string if possible
+ let public date = fun s -> match System.DateTime.TryParse(s) with
+ | false, _ -> None
+ | true, value -> Some(value)
+
+ /// Function produces a new date from a tenor and a startdate
+ /// Two input parameters: tenor = tenor to offset with
+ /// date = date to offset from
+ let public offset tenor (date:System.DateTime) = date.AddDays(float tenor.days).AddMonths(tenor.months).AddYears(tenor.years)
+
+ /// Function that produces true if a date is a business day in the given calendar and false otherwise
+ let public isBusinessDay calendar (date:System.DateTime) = not (calendar.weekendDays.Contains date.DayOfWeek || calendar.holidays.Contains date)
+
+ /// Produces the date immediately following the given date
+ let private dayAfter (date:System.DateTime) = date.AddDays(1.0)
+
+ /// Produces the date immediately preceding the given date
+ let private dayBefore (date:System.DateTime) = date.AddDays(-1.0)
+
+ /// All businessdays between two dates (inclusive) given a calendar
+ let public businessDaysBetween calendar (startDate : System.DateTime) (endDate : System.DateTime) =
+ let rec builder currDate enddate acc =
+ if currDate > enddate then acc
+ else
+ if isBusinessDay calendar currDate then builder (dayAfter currDate) endDate (currDate :: acc)
+ else builder (dayAfter currDate) endDate acc
+ builder (min startDate endDate) (max startDate endDate) []
+ |> List.rev
+
+ /// Produces the nearest business day to a date given a specific roll rule to
+ /// apply and given a calendar that defines business days. In particular, if
+ /// a day already is a business day, it doesn't get rolled.
+ /// Inputs are: a rule (type RollRule) that defines the rolling rule
+ /// a calendar that defines the business days
+ /// a date to start rolling from
+ let rec public roll rule calendar date =
+ if isBusinessDay calendar date then date
+ else match rule with
+ | RollRule.Unadjusted -> date
+ | RollRule.Following -> date |> dayAfter |> roll rule calendar
+ | RollRule.Previous -> date |> dayBefore|> roll rule calendar
+ | RollRule.ModifiedFollowing ->
+ let next = roll RollRule.Following calendar date
+ if next.Month <> date.Month then roll RollRule.Previous calendar date else next
+ | RollRule.ModifiedPrevious ->
+ let prev = roll RollRule.Previous calendar date
+ if prev.Month <> date.Month then roll RollRule.Following calendar date else prev
+
+ /// Rolls a specific date by n days, taking into a account a given roll rule and a
+ /// calendar defining business days. If we're rolling 0 days or when we're using
+ /// RollRule.Actual then it's possible to end up on a non-business days.
+ /// Inputs are: n - the number of days to roll
+ /// rule - the rolling rule to apply
+ /// calendar - the calendar that defines business days
+ /// date - the startdate from which we're rolling
+ /// Note that rolling backwards in time is achieved by applying rollBy to a positive
+ /// number of days to roll, but applying a (Modified) Previous roll rule.
+ let rec public rollBy n rule calendar (date:System.DateTime) =
+ match n with | 0 -> date
+ | x -> match rule with
+ | RollRule.Unadjusted -> date.AddDays(float x)
+ | RollRule.Following -> date|> dayAfter
+ |> roll rule calendar
+ |> rollBy (x - 1) rule calendar
+
+ | RollRule.Previous -> date|> roll rule calendar
+ |> dayBefore
+ |> roll rule calendar
+ |> rollBy (x - 1) rule calendar
+
+ | RollRule.ModifiedFollowing -> // Roll n-1 days Following
+ let next = rollBy (x - 1) RollRule.Following calendar date
+ // Roll the last day ModifiedFollowing
+ let final = roll RollRule.Following calendar (dayAfter next)
+ if final.Month <> next.Month then (roll RollRule.Previous calendar next) else final
+
+ | RollRule.ModifiedPrevious -> // Roll n-1 days Previous
+ let next = rollBy (x - 1) RollRule.Previous calendar date
+
+ // Roll the last day ModifiedPrevious
+ let final = roll RollRule.Previous calendar (dayAfter next)
+ if final.Month <> next.Month then roll RollRule.Following calendar next else final
+
+
+ let private regex s = new System.Text.RegularExpressions.Regex(s,System.Text.RegularExpressions.RegexOptions.IgnoreCase)
+
+ /// Produces a tenor from an input string. Input strings are concatenations
+ /// of integers and upper case characters in the set "Y,M,W,D" denoting years
+ /// months, weeks and days respectively. Not all combinations are parseable.
+ /// To denote this, the result gets wrapped into an option type is it valid and
+ /// invalid results are encoded by None.
+ let public tenor (t : string)=
+ let pattern = regex ( "(?(-)?[0-9]+)Y(?(-)?[0-9]+)M(?(-)?[0-9]+)W(?(-)?[0-9]+)D" +
+ "|(?(-)?[0-9]+)Y(?(-)?[0-9]+)M(?(-)?[0-9]+)W" +
+ "|(?(-)?[0-9]+)Y(?(-)?[0-9]+)M(?(-)?[0-9]+)D" +
+ "|(?(-)?[0-9]+)Y(?(-)?[0-9]+)M" +
+ "|(?(-)?[0-9]+)Y(?(-)?[0-9]+)W(?(-)?[0-9]+)D" +
+ "|(?(-)?[0-9]+)Y(?(-)?[0-9]+)W" +
+ "|(?(-)?[0-9]+)Y(?(-)?[0-9]+)D" +
+ "|(?(-)?[0-9]+)Y" +
+ "|(?(-)?[0-9]+)M(?(-)?[0-9]+)W(?(-)?[0-9]+)D" +
+ "|(?(-)?[0-9]+)M(?(-)?[0-9]+)W" +
+ "|(?(-)?[0-9]+)M(?(-)?[0-9]+)D" +
+ "|(?(-)?[0-9]+)M" +
+ "|(?(-)?[0-9]+)W(?(-)?[0-9]+)D"+
+ "|(?(-)?[0-9]+)W" +
+ "|(?(-)?[0-9]+)D" )
+
+ let m = pattern.Match(t.Replace(" ",""))
+ if m.Success then
+ Some( { years = (if m.Groups.["years"].Success then int m.Groups.["years"].Value else 0);
+ months = (if m.Groups.["months"].Success then int m.Groups.["months"].Value else 0);
+ days = match (m.Groups.["weeks"].Success), (m.Groups.["days"].Success) with
+ | true, true -> 7*(int m.Groups.["weeks"].Value ) + (int m.Groups.["days"].Value)
+ | true, false -> 7*(int m.Groups.["weeks"].Value )
+ | false, true -> (int m.Groups.["days"].Value)
+ | false, false -> 0})
+ else None
+
+ let private monthStringToNumber (s : string) =
+ match s.ToLower() with
+ | "jan" | "f" -> 1 |> Some
+ | "feb" | "g" -> 2 |> Some
+ | "mar" | "h" -> 3 |> Some
+ | "apr" | "j" -> 4 |> Some
+ | "may" | "k" -> 5 |> Some
+ | "jun" | "m" -> 6 |> Some
+ | "jul" | "n" -> 7 |> Some
+ | "aug" | "q" -> 8 |> Some
+ | "sep" | "u" -> 9 |> Some
+ | "oct" | "v" -> 10 |> Some
+ | "nov" | "x" -> 11 |> Some
+ | "dec" | "z" -> 12 |> Some
+ | _ -> None
+
+ /// Produces a period from an input string. Examples of inputs recognized are:
+ /// Cal16, Q413, 13Q4, 3Q15, H114, 14H1, 1H14 Jan15, F14, H14-K14
+ /// This function cannot recogize periods that are composed of multiple periods, concatenated by a dash.
+ /// e.g. Cal16-Cal17 or 13Q1-Q4, H214-Cal15, Jan16-Dec18 all do NOT work
+ let public period (s : string) =
+ let s = s.Trim().Replace(" ","").Replace("-","").Replace("/","").Replace(@"\","")
+
+ let fallthrough (y : 'a option) (x : 'a option) = // Probably should be refactored to some generic option functionality
+ if x.IsSome then x else y
+
+ let yearpattern = regex "^cal(?\d{2})$|^cal(?\d{2})$"
+ let semiyearpattern = regex "^H(?[1-2])(?\d{2})$|^(?\d{2})H(?[1-2])$|^(?[1-2])H(?\d{2})$|^H(?[1-2])(?\d{4})$|^(?\d{4})H(?[1-2])$|^(?[1-2])H(?\d{4})$"
+ let quarterpattern = regex "^Q(?[1-4])(?\d{2})$|^(?\d{2})Q(?[1-4])$|^(?[1-4])Q(?\d{2})$|^Q(?[1-4])(?\d{4})$|^(?\d{4})Q(?[1-4])$|^(?[1-4])Q(?\d{4})$"
+ let monthpattern1 = regex "^(?(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec|f|g|h|j|k|m|n|q|u|v|x|z))(?\d{2})$|^(?(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec|f|g|h|j|k|m|n|q|u|v|x|z))(?\d{4})$"
+ let monthpattern2 = regex "^(?(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec|f|g|h|j|k|m|n|q|u|v|x|z))(?\d{2})(?(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec|f|g|h|j|k|m|n|q|u|v|x|z))(?\d{2})$|^(?(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|okt|nov|dec|f|g|h|j|k|m|n|q|u|v|x|z))(?\d{2})(?(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|okt|nov|dec|f|g|h|j|k|m|n|q|u|v|x|z))(?\d{2})$"
+
+ // todo : all logic regarding baseyear won't be working anymore once we get near to the turn of a century
+ let baseyear = System.DateTime.Today.Year - (System.DateTime.Today.Year % 100)
+
+ let y = yearpattern.Match(s)
+ if y.Success then
+ let year = int y.Groups.["year"].Value
+ let year = if year < 100 then year + baseyear else year
+ Some({startDate = System.DateTime(year,1,1);endDate = System.DateTime(year,12,31)})
+ else
+ None
+ |> fallthrough ( let h = semiyearpattern.Match(s)
+ if h.Success then
+ let half = int h.Groups.["period"].Value
+ let year = int h.Groups.["year"].Value
+ let year = if year < 100 then year + baseyear else year
+ let start = System.DateTime(year,1+(half-1)*6,1)
+ Some({startDate = start; endDate = start.AddMonths(6).AddDays(-1.)})
+ else None)
+ |> fallthrough ( let q = quarterpattern.Match(s)
+ if q.Success then
+ let half = int q.Groups.["period"].Value
+ let year = int q.Groups.["year"].Value
+ let year = if year < 100 then year + baseyear else year
+ let start = System.DateTime(year,1+(half-1)*3,1)
+ Some({startDate = start; endDate = start.AddMonths(3).AddDays(-1.)})
+ else None)
+ |> fallthrough ( let m = monthpattern1.Match(s)
+ if m.Success then
+ let month = m.Groups.["period"].Value |> monthStringToNumber
+ if month.IsSome then
+ let year = int m.Groups.["year"].Value
+ let year = if year < 100 then year + baseyear else year
+ let start = System.DateTime(year,month.Value,1)
+ Some({startDate = start; endDate = start.AddMonths(1).AddDays(-1.)})
+ else None
+ else None)
+
+ |> fallthrough ( let m = monthpattern2.Match(s)
+ if m.Success then
+ let month1 = m.Groups.["period1"].Value |> monthStringToNumber
+ let month2 = m.Groups.["period2"].Value |> monthStringToNumber
+ if (month1.IsSome) && (month2.IsSome) then
+ let year1 = int m.Groups.["year1"].Value
+ let year2 = int m.Groups.["year2"].Value
+ let year1 = if year1 < 100 then year1 + baseyear else year1
+ let year2 = if year2 < 100 then year2 + baseyear else year2
+ let start = System.DateTime(year1,month1.Value,1)
+ let endd = System.DateTime(year2,month2.Value,1).AddMonths(1).AddDays(-1.)
+ if start < endd then Some({startDate = start; endDate=endd}) else None
+ else None
+ else None)
+
+ /// Calculations the fraction of time between startdate and enddate using a specified daycount convention
+ let public yearfrac daycount (startdate : System.DateTime ) (enddate : System.DateTime)=
+
+ // Helper method for calcultions with 30/360 methods
+ let helper30360 (y1,m1,d1) (y2,m2,d2) =
+ let y1,y2,m1,m2,d1,d2 = float y1, float y2, float m1, float m2, float d1, float d2
+ (360.*(y2-y1) + 30.*(m2-m1) + d2-d1)/360.
+ let islastdayofmonth (d : System.DateTime) = d.AddDays(1.).Month <> d.Month
+ let isleapyear (d : System.DateTime) = System.DateTime.IsLeapYear(d.Year)
+
+ // Main logic
+ let helper dc (startd : System.DateTime) (endd : System.DateTime) =
+ match dc with
+ | Actual_365qrt -> (endd.Subtract(startd).TotalDays + 1.)/365.25
+ | Actual_365 -> (endd.Subtract(startd).TotalDays + 1. )/365.
+ | Actual_360 -> (endd.Subtract(startd).TotalDays + 1. )/360.
+ | Actual_Actual_ISDA -> match endd.Year = startd.Year with
+ | true -> (endd.Subtract(startd).TotalDays + 1.)/(if isleapyear startd then 366. else 365.)
+ | false -> let addon = max ((float) (endd.Year-startd.Year) - 1.) 0.
+ let frac1 = (System.DateTime(startd.Year,12,31).Subtract(startd).TotalDays + 1.) /(if isleapyear startd then 366. else 365.)
+ let frac2 = (endd.Subtract(System.DateTime(endd.Year,1,1)).TotalDays + 1.) / (if isleapyear endd then 366. else 365.)
+ addon+frac1+frac2
+ | Thirty_360_E -> helper30360 (startd.Year, startd.Month, min startd.Day 30) (endd.Year, endd.Month, min endd.Day 30)
+ | Thirty_360_ISDA -> let startdata = (startd.Year, startd.Month, if (islastdayofmonth startd) then 30 else startd.Day)
+ let enddata = (endd.Year, endd.Month, if endd.Month <> 2 && (islastdayofmonth endd) then 30 else endd.Day)
+ helper30360 startdata enddata
+ | Thirty_360_Eplus -> if endd.Day = 31 then helper30360 (startd.Year,startd.Month,min startd.Day 30) (endd.Year,endd.Month+1,1)
+ else helper30360 (startd.Year,startd.Month,min startd.Day 30) (endd.Year,endd.Month,endd.Month)
+
+ // Deal with input order
+ if startdate = enddate then 0.
+ elif startdate > enddate then -(helper daycount enddate startdate)
+ else helper daycount startdate enddate
\ No newline at end of file
diff --git a/BJExcelLib/Finance.JuZhong.fs b/BJExcelLib/Finance.JuZhong.fs
new file mode 100644
index 0000000..6e1f6dc
--- /dev/null
+++ b/BJExcelLib/Finance.JuZhong.fs
@@ -0,0 +1,92 @@
+namespace BJExcelLib.Finance
+
+/// American option approximation according to Ju & Zhong
+module JuZhong =
+ open Blackscholes
+ open BJExcelLib.Math.NormalDistribution
+
+ /// returns (flag,hAh,S*,lambda(h),X,X',X'',d1(S*)) as an option type
+ let private getstuff margined flag S K t r cc v =
+ let inputOk = S > 0. && K >=0. && v > 0. && t > 0.
+ if not inputOk then None else
+ let flag' = if flag = Call then 1. else -1.
+ if margined then (flag',0.,1.,0.,0.,0.,0.,0.) |> Some // if margined, return paramters that will default to europena option value
+ else
+ let alpha = 2.*r/(v*v)
+ let beta = 2.*cc/(v*v) - 1. // compared to JZ paper, this is beta-1
+ let h = 1.- exp(-r*t)
+ let mdivy = cc-r //minus one times the div yield
+ let commonExp = exp(mdivy*t)
+ let subexpr0 = v*v*t
+ let subexpr1 = beta*beta + 8./subexpr0
+ let subexpr2 = sqrt(subexpr1)
+ let subexpr3 = sqrt(beta*beta + 4.*alpha/h)
+ let lambda, lambda', b =
+ if r = 0. && flag' = 1. then
+ 0.5*(flag'*subexpr2-beta), 0. (*not used*), -2./(subexpr0*subexpr0*subexpr1)
+ else
+ let lambda' = -flag'*alpha/(h*h*subexpr3)
+ let lambda = 0.5*(flag'*subexpr3-beta)
+ lambda, lambda', (1.-h)*alpha*lambda'/(2.*(2.* lambda + beta))
+
+ let d1Func sStar = (log(sStar/K) + (cc + 0.5*v*v)*t)/(v*sqrt(t))
+ let sStarFunc S' = -flag' + flag'*commonExp*cnd(flag'*(d1Func S')) + lambda*(flag'*(S'-K) - (Blackscholes.value margined flag S' K t r cc v).Value)/S'
+ let d f x =
+ let step = sqrt BJExcelLib.Math.Constants.machine_epsilon
+ (f (x + step) - f(x - step))/2./step
+
+ let iter = 300
+ let acc = (0.0001 * (min S K))
+ let lb = (0.5 * (min S K))
+ let ub = (5. * (max S K))
+ let sStar = MathNet.Numerics.FindRoots.newtonRaphsonGuess iter acc S sStarFunc (d sStarFunc)
+ if sStar.IsNone then None
+ else
+ let sStar = sStar.Value
+ let hAh = Blackscholes.value margined flag sStar K t r cc v
+ |> Option.map (fun ve -> flag'*(sStar-K) - ve)
+ if hAh.IsNone then None
+ else
+ let hAh = hAh.Value
+ let d1S' = d1Func sStar
+ let d2S' = d1S' - v*sqrt(t)
+ let c =
+ if r = 0. && flag' = 1. then
+ -flag'/subexpr2*(2./subexpr0 + 2.*b + (sStar*commonExp/(hAh*v))*(nd(d1S')/sqrt(t) + 2.*flag'*mdivy/v))
+ else
+ let subexpr4 = 2.*lambda+beta
+ let sens = (sStar*exp(cc*t)/r)*(nd(d1S')*v/(2.*sqrt(t))+flag'*mdivy*cnd(flag'*d1S'))+flag'*K*cnd(flag'*d2S')
+ (h-1.)*alpha/subexpr4*(lambda'/subexpr4 + 1./h + sens/hAh)
+
+ let logSS' = log(S/sStar)
+ let X, X', X'' = b*logSS'*logSS' + c * logSS',
+ (2.*b*logSS' + c)/S,
+ (2.*b*(1.-logSS')-c)/(S*S)
+
+ (flag',hAh,sStar,lambda,X,X',X'',d1S') |> Some
+
+ /// Value of an american option in the Ju & Zhong approximation.
+ let public value margined flag S K t r b v =
+ match getstuff margined flag S K t r b v with
+ | None -> None
+ | Some(flag',hAh,sStar,lh,X,_,_,_) -> if flag'*(sStar-S) <= 0. then flag'*(S-K) |> Some
+ else Blackscholes.value margined flag S K t r b v
+ |> Option.map (fun ve -> ve + hAh*((S/sStar)**lh)/(1.-X))
+
+ // TODO: these need to be checked
+ /// Delta of an american option in the JuZhong approximation
+ let public delta margined flag S K t r b v =
+ match getstuff margined flag S K t r b v with
+ | None -> None
+ | Some(flag',hAh,sStar,lh,X,X',_,d1)-> Blackscholes.value margined flag sStar K t r b v
+ |> Option.map (fun veS' -> flag'*exp((b-r)*t)*cnd(flag'*d1) + (lh/(S*(1.-X))+X'/((1.-X)*(1.-X)))*(flag'*(sStar-K)-veS')*((S/sStar)**lh))
+
+ /// Gamma of an american option in the Ju & Zhong approximation
+ let public gamma margined flag S K t r b v =
+ match getstuff margined flag S K t r b v with
+ | None -> None
+ | Some(flg',hAh,sStr,lh,X,X',X'',d1)-> Blackscholes.value margined flag sStr K t r b v
+ |> Option.map (fun ve -> flg'*exp((b-r)*t)*nd(flg'*d1) +
+ (flg'*(sStr-K) - ve)*((S/sStr)**lh) *
+ ((X'' + (2.*X'*(lh/S+X'/(1.-X))))/((1.-X)*(1.-X)) + (lh*lh-lh)/(S*S*(1.-X))))
+
diff --git a/BJExcelLib/Finance.SVI.fs b/BJExcelLib/Finance.SVI.fs
new file mode 100644
index 0000000..57c0ee2
--- /dev/null
+++ b/BJExcelLib/Finance.SVI.fs
@@ -0,0 +1,209 @@
+namespace BJExcelLib.Finance
+
+/// Contains functions relating to Gatheral's SVI function
+module SVI =
+ open BJExcelLib.Math.Optimization
+ open MathNet.Numerics.LinearAlgebra.Double
+
+ /// Checks some bounds on SVI raw parameters at for which there surely is arbitrage. Note that
+ /// if this function indicates that bounds aren't exceeded, there still can be arbitrage. However,
+ /// it still is worthwhile checking for these.
+ let public rawparameterssane (a,b,rho,m,sigma) ttm =
+ ttm > 0. && sigma > 0. && abs(rho) <= 1. && (a+b*sigma*sqrt(1.-rho*rho)) >= 0. && (b*(1.+abs(rho)) < 4./ttm)
+
+ /// Computes total variance using the SVI function given SVI Raw parameters a time to maturity, a forward and a strike as input
+ let public totalvariance (a,b,rho,m,sigma) fwd (strike : float) =
+ if strike <= 0. || fwd <= 0. then None
+ else
+ let rho = max -1. (min 1. rho)
+ let k = log(strike/fwd)-m
+ let res = a + b*(rho*k+sqrt(k*k+sigma*sigma))
+ if res >= 0. then Some(res) else None
+
+ /// Computes implied vol using the SVI function given SVI Raw parameters a time to maturity, a forward and a strike as input
+ /// Notice that the parameters are used to calculate total variance which then gets translated into implied volatility. This
+ /// is different from some implementations where the parameters model implied variance instead of implied total variance. The
+ /// difference on the parameters is just a factor T
+ let public impliedvol (a,b,rho,m,sigma) ttm fwd strike =
+ totalvariance (a,b,rho,m,sigma) fwd strike |> Option.map(fun x -> sqrt(x/ttm))
+
+ /// Gives the gradient of implied vol w.r.t a,b,rho, m and sigma. Usefol for some numerical
+ /// optimization schemes
+ /// This funciton is easily obtained by seeing impliedvol as sqrt(totalvariance/ttm)
+ /// and then applying the chain rule
+ let public impliedvolgradient (a,b,rho,m,sigma) ttm fwd strike =
+ if strike <= 0. || fwd <= 0. || ttm < 0. then None
+ else
+ let rho = max -1. (min 1. rho)
+ let k = log(strike/fwd)-m
+ let tmp = impliedvol (a,b,rho,m,sigma) ttm fwd strike |> Option.map (fun x -> 0.5/(x*ttm))
+ let tmp1 = totalvariance (0.,1.,rho,m,sigma) fwd strike
+ let tmp2 = totalvariance (0.,1.,0.,m,sigma) fwd strike
+ if tmp.IsSome && tmp1.IsSome && tmp2.IsSome then
+ ( tmp.Value, tmp.Value*(tmp1.Value), tmp.Value*b*k,
+ -tmp.Value*b*(rho+k/tmp2.Value),tmp.Value*b*sigma/tmp2.Value
+ ) |> Some
+ else None
+
+ /// Converts SVI raw parameters to SVI jump-wing parameters
+ let public rawtojumpwing (a,b,rho,m,sigma) ttm =
+ let helper1 = sqrt(m*m+sigma*sigma)
+ let rho = max -1. (min 1. rho)
+ let wt = (a+b*(-rho*m+helper1))
+ if ttm <= 0. || wt <= 0. || helper1 = 0. then None
+ else
+ let vt,helper2 = wt/ttm,b/sqrt(wt)
+ let phit,pt,ct,vtilde = 0.5*helper2*(rho-m/helper1),helper2*(1.-rho), helper2*(1.+rho),(a+b*sigma*sqrt(1.-rho*rho))/ttm
+ Some(vt,phit,pt,ct,vtilde)
+
+ /// Converts SVI jump-wing parametres to SVI raw parameters
+ let public jumpwingtoraw (vt,phit,pt,ct,vtilde) ttm =
+ if ttm <= 0. || vt <= 0. || vtilde <= 0. then None
+ else
+ let helper1 = sqrt(vt*ttm)
+ let b = 0.5*helper1*(ct+pt)
+ let rho = 1.-pt*helper1/b
+ let helper2,beta = sqrt(1.-rho*rho), rho - 2.*phit*helper1/b
+ match beta = 0. with
+ // m = 0 case, not stated explicitly in Gatheral paper, but easily proven that this is equivalent
+ // In Gatheral's paper, the definitions of sigma form a linear system, here the direct solution of that system is used.
+ | true -> let m,sigma = 0., (vt-vtilde)*ttm/(b*(1.-helper2))
+ let a = vtilde*ttm-b*sigma*helper2
+ Some(a,b,rho,m,sigma)
+ // Default case
+ | false -> let sign x = sign(x) |> float
+ if abs(beta) > 1. then None
+ else
+ let alpha = sign(beta)*sqrt(-1. + 1./(beta*beta))
+ let m = (vt-vtilde)*ttm/(b*(-rho+ sign(alpha)*sqrt(1.+alpha*alpha)-alpha*helper2))
+ let sigma = alpha*m
+ let a = vtilde*ttm-b*sigma*helper2
+ Some(a,b,rho,m,sigma)
+
+
+ /// Calibrates SVI Raw parameters according to the methodology described in "Quasi-Explicit Calibration of Gatheral's SVI model"
+ /// smin is the minimum value allowed for the sigma parameter, mguess is the intial guess for m and sguess is the initial guess for sigma.
+ /// the ttm is the time to maturity for the input given, the forward is the forward of the undelrying for the given maturity and points is
+ /// an array of tuples where the first element is a weight, the second element is a strike and the third element is the volatility at that strike.
+ let public calibrate (checkValid,smin) ttm forward points =
+ // If the number of points provided is very small, this doesn't work. In that case, the
+ // default paramters below are used. If the initial value calculator gets enough points
+ // that initial value is used.
+ let DEFAULT_EST_M = 0.
+ let DEFAULT_EST_S = 0.1
+
+ let smin = max 0. smin
+ let third (_,_,x) = x
+ let points = points |> Array.map (fun (w,strike,vol) -> (w,log(strike/forward),vol*vol*ttm))
+ let maxvar = points |> Array.maxBy third |> third
+
+ /// Checks whether given (transformed zeliade) parameters fall within the boundaries of the region that is potentially arbitrage-free
+ /// This function returning true does not imply that parameters are arbitragefree, but if the function returns false, the parameters
+ /// are definitely not arbitrage-free.
+ let isValid s (a,c,d) =
+ not checkValid || ( 0. <= c && c <= 4. * s &&
+ abs(d) <= min c (4.*s-c) &&
+ 0. <= a && a <= maxvar)
+
+ /// Converts zeliade parameters into SVI (total variance) parameters
+ let zeliadeToSVI (m,s) (a,c,d) = if c = 0. then (a,0.,0.,m,s) else (a,c/s,d/c,m,s)
+
+
+ /// Provides an initial guess for m and s. Based on stuff from Axel Vogt, but slightly adapted since I encountered
+ /// negative sInitial on some test inputs.
+ let initialGuess =
+ let size = -1 + Array.length points
+ if size < 3 then (DEFAULT_EST_M,DEFAULT_EST_S)
+ else
+ let x,y = points |> Array.map(fun (_,x,v) -> x,v)
+ |> Array.unzip
+ let calcA n = (x.[n]*y.[n+1] - y.[n]*x.[n+1])/(x.[n]-x.[n+1])
+ let calcB n = (y.[n+1]-y.[n])/(x.[n+1]-x.[n]) |> fun x -> abs(x)
+
+ let aL, bL,aR, bR = calcA 0, -(calcB 0), calcA (size-1), calcB (size-1)
+ let recurring = if bR=bL then 0. else bL*(aR-aL)/(bL-bR)
+ //let aInitial = aL + recurring // Not needed here, but given just for convenience so that some initial guess for all parameters is here
+ let rInitial = if bR=bL then 0. else (bL+bR)/(bR-bL)
+ let bInitial = 0.5*(bR-bL)
+ let mInitial = recurring/(bInitial * (rInitial-1.))
+ let sigmaInitial = -(-(Array.min y) + aL + recurring) / (bInitial * sqrt( abs (1. - rInitial * rInitial) )) |> abs
+ mInitial,(max smin sigmaInitial)
+
+ /// For given input m and s, this function calculates the returns a tuple consisting of all the SVI parameters
+ /// corresponding to the best "Zeliade solution" found and the total error of the parameters
+ let zeliadeSol (m,s) =
+ let points = points |> Array.map (fun (w,str,v) -> (abs(w),(str-m)/s,v))
+ let sumW = points |> Array.sumBy (fun (wgt,_,_) -> wgt)
+ let errorFunc (a,c,d) =
+ points |> Array.sumBy (fun (wgt,y,var) -> let err = a + d*y + c * sqrt(y*y+1.) - var
+ wgt*err*err)
+ |> fun sum -> if sumW = 0. then 0. else sum/sumW
+ |> sqrt
+
+ // OK the below is a bit of a mess, but it's an efficient mess. Basically a number of numerical expressions occur in the
+ // linear system to be solved. The approach taken below is calculate them all as once in a tuple, while folding over
+ // the points list. A few helper variables that reoccur in multiple calculations are updated and then the tuple is
+ // updated elementwise by just summing.
+ let w, wy, wsqr, wv, wy2, wysqr ,wvy, wvsqr =
+ points |> Array.fold (fun (acc1,acc2,acc3,acc4,acc5,acc6,acc7,acc8) (wgt,y,var) ->
+ let wy',wv',sqrh = wgt*y,wgt*var,sqrt(y*y+1.)
+ (acc1 + wgt, acc2 + wy',acc3 + wgt*sqrh, acc4 + wv',
+ acc5 + wy'*y,acc6 + wy'*sqrh,acc7+ wy' * var, acc8 + wv' * sqrh)
+ ) (0.,0.,0.,0.,0.,0.,0.,0.)
+
+ /// Takes an input vector x, the first of which repersents a matrix (in a list) and the second of which represents a vector (as a list)
+ /// and solves the linear system defined by them. Since the inputs are intedend to be the equations defining the optimal values for a,d and
+ /// c as defined by zeliade, they then get changed order to coincide with the order of SVI arguments in all other functions in this module.
+ let solveSystem x = x |> fun (A,b) -> matrix A, vector b
+ |> fun (A,b) -> A.LU().Solve(b).ToArray()
+ |> fun arr -> if Array.length arr = 0 then (0.,0.,0.) else (arr.[0],arr.[2],arr.[1]) // Go from [|a;d;c|] to (a,c,d)
+
+ /// Transforms a tuple of a,c,d parametes i a tuple of all parameters, joined with the error corresponding to those parametes.
+ let output tuple = zeliadeToSVI (m,s) tuple, errorFunc tuple
+
+ let globalSol = solveSystem ([[w;wy;wsqr]; [wy; wy2; wysqr]; [wsqr; wysqr; w + wy2]], [wv; wvy; wvsqr])
+ if not (isValid s globalSol) then
+ /// Finds the solutions for which one of the three parameters is at its bounds
+ let facetSols =
+ [[[1.;0.;0.];[wy; wy2; wysqr]; [wsqr; wysqr; w + wy2]], [0.; wvy; wvsqr]; // system for a = 0
+ [[1.;0.;0.];[wy; wy2; wysqr]; [wsqr; wysqr; w + wy2]], [maxvar; wvy; wvsqr]; // system for a = maxvar
+ [[w;wy;wsqr]; [0.;-1.;1.]; [wsqr; wysqr; w + wy2]], [wv; 0.; wvsqr]; // system for d = c
+ [[w;wy;wsqr]; [0.; 1.;1.]; [wsqr; wysqr; w + wy2]], [wv; 0.; wvsqr]; // system for d = -c
+ [[w;wy;wsqr]; [0.; 1.;1.]; [wsqr; wysqr; w + wy2]], [wv; 4.*s; wvsqr]; // system part one for |d| <= 4*s-c
+ [[w;wy;wsqr]; [0.;-1.;1.]; [wsqr; wysqr; w + wy2]], [wv; 4.*s; wvsqr]; // system part two for |d| <= 4*s-c
+ [[w;wy;wsqr]; [wy; wy2; wysqr]; [0.;0.;1.]], [wv; wvy; 0.]; // system for c = 0
+ [[w;wy;wsqr]; [wy; wy2; wysqr]; [0.;0.;1.]], [wv; wvy; 4.*s]] // system for c = 4*s
+ |> List.map solveSystem
+ |> List.filter (isValid s)
+ |> List.map output
+
+ /// Finds the solution for which two of the three parameters are at its bounds. The Zeliade whitepaper speaks about
+ /// doing one-dimensinal search to find the minimum, but since fixing 2 out of the three parameters just leads to
+ /// a quadratic equation in the third, finding the global minimum and then cutting it off if it falls outside the
+ /// valid boundaries, works.
+ let cornerOrLineSols =
+ [[[w;wy;wsqr]; [0.;1.;0.];[0.;0.;1.]],[wv; 0.; 0.]; // c = 0, implies d = 0, find optimal a
+ [[w;wy;wsqr]; [0.;1.;0.];[0.;0.;1.]],[wv; 0.; 4.*s ]; // c = 4s, implied d = 0, find optimal a
+ [[1.;0.;0.]; [0.;-1.;1.]; [wsqr; wysqr; w + wy2]],[0.;0.;wvsqr]; // a = 0, d = c, find optimal c
+ [[1.;0.;0.]; [0.; 1.;1.]; [wsqr; wysqr; w + wy2]],[0.;0.;wvsqr]; // a = 0, d = -c, find optimal c
+ [[1.;0.;0.]; [0.; 1.;1.]; [wsqr; wysqr; w + wy2]],[0.; 4.*s;wvsqr]; // a = 0, d = 4s-c, find optimal c
+ [[1.;0.;0.]; [0.;-1.;1.]; [wsqr; wysqr; w + wy2]],[0.; 4.*s;wvsqr]] // a = 0, d = c-4s, find optimal c
+ |> List.map solveSystem
+ |> List.map (fun (a,c,d) -> let dbound = min c (4.*s-c)
+ (max 0. (min maxvar a)),(max 0. (min c 4.*s)), max -dbound (min d dbound)) // Cut off at boundary incase we're outside
+ |> List.filter (isValid s) // There will alwasy be at least two solutions here, since c=d=0 will lead to a=0 or a=maxvar and both are valid.
+ |> List.map output
+
+ facetSols @ cornerOrLineSols |> List.minBy snd // Because cornerOrLineSols always contains at least one solution, this array is never empty
+
+ else globalSol |> output
+
+
+ // TODO: see if this optimization can be improved upon, especially given that the gradient is known explicity.
+ let errf (arr : float [])= (arr.[0],arr.[1]) |> zeliadeSol |> snd
+ let lb,ub = [|None;Some(smin)|],[|None;None|]
+ minimize errf (initialGuess |> fun (m,s) -> [|m;s|]) lb ub
+ |> fun arr -> arr.[0],arr.[1]
+ |> zeliadeSol
+ //zeliadeSol initialGuess //These are just the parameters for a smart intial guess. Typically perform not too shabby, but optimization can still lead to good improvements.
+ //Just showing it here because it could be useful at some point.
\ No newline at end of file
diff --git a/BJExcelLib/Math.Constants.fs b/BJExcelLib/Math.Constants.fs
new file mode 100644
index 0000000..0499f3c
--- /dev/null
+++ b/BJExcelLib/Math.Constants.fs
@@ -0,0 +1,17 @@
+namespace BJExcelLib.Math
+
+/// Provides and wraps some mathematical constants
+[]
+module Constants =
+
+ /// Wraps pi
+ let public pi = System.Math.PI
+
+ /// Provides the machine_epsilon (smallest float x such that 1+x != x)
+ let public machine_epsilon =
+ let rec helper input =
+ let x = input/2.
+ match 1.+ x with | 1. -> input
+ | _ -> helper x
+ helper 1.
+
\ No newline at end of file
diff --git a/BJExcelLib/Math.Interpolation.fs b/BJExcelLib/Math.Interpolation.fs
new file mode 100644
index 0000000..e59d2b0
--- /dev/null
+++ b/BJExcelLib/Math.Interpolation.fs
@@ -0,0 +1,217 @@
+namespace BJExcelLib.Math
+
+/// Contains functions for general interpolation and curve fitting.
+module Interpolation =
+ open MathNet.Numerics.Fit
+
+
+
+ /// Helper function that peforms some typical preprocessing needed for a lot of interpolation functions
+ let private processTupleArray xydata =
+ xydata |> Seq.distinctBy fst
+ |> Seq.sortBy fst
+ |> Array.ofSeq
+ |> Array.unzip
+
+ /// Piecewise constant interpolation of given (x,y) input tuples
+ let public interpolatorPiecewiseConstant (xydata : (float*float) [])=
+ if Array.length xydata = 0 then None
+ else
+ let xdata, ydata = processTupleArray xydata
+
+ //xdata,ydata go into the closure. As a result, they are evaluated only once,but for big arrays
+ // this might lead to a lot of memory consumption? Probably not an issue for my intended usage.
+ Some( fun x -> let ix = xdata |> Array.tryFindIndex (fun el -> el > x)
+ |> FSharpx.Option.getOrElse (Array.length xdata)
+ |> max -1
+ ydata.[ix+1])
+
+ /// Piecewise linear interpolation of given (x,y) input data
+ let public interpolatorPiecewiseLinear (xydata : (float*float) []) =
+ if Array.length xydata < 2 then interpolatorPiecewiseConstant xydata
+ else
+ let xdata, ydata = processTupleArray xydata
+ Some (fun x -> let ix = xdata |> Array.tryFindIndex(fun el -> el > x)
+ |> Option.map (fun z-> (z-1,z))
+ |> FSharpx.Option.getOrElse (-2 + Array.length xdata, -1 + Array.length xdata)
+ |> fun (u,v) -> if u = -1 then (0,1) else (u,v)
+ let x1,x2, y1, y2 = xdata.[fst ix], xdata.[snd ix], ydata.[fst ix], ydata.[snd ix]
+ y1 + (y2-y1)/(x2-x1)*(x-x1))
+
+ /// Piecewise linear interpolation of given (x,y) input data
+ let public interpolatorPiecewiseLoglinear (xydata : (float*float) []) =
+ if Array.length xydata < 2 then interpolatorPiecewiseConstant xydata
+ else
+ let xdata, ydata = processTupleArray xydata
+ Some (fun x -> let ix = xdata |> Array.tryFindIndex(fun el -> el > x)
+ |> Option.map (fun z-> (z-1,z))
+ |> FSharpx.Option.getOrElse (-2 + Array.length xdata, -1 + Array.length xdata)
+ |> fun (u,v) -> if u = -1 then (0,1) else (u,v)
+ let x1,x2, y1, y2 = xdata.[fst ix], xdata.[snd ix], ydata.[fst ix], ydata.[snd ix]
+ y1*((y2/y1)**((x-x1)/(x2-x1))))
+
+ /// Creates an akima spline that interpolators the given input (x,y) data
+ let public interpolatorAkimaSpline xydata =
+ if Array.length xydata < 5 then None
+ else
+ let xdata,ydata = processTupleArray xydata
+ let ip = MathNet.Numerics.Interpolation.Algorithms.AkimaSplineInterpolation(xdata,ydata)
+ Some (fun x -> ip.Interpolate x)
+
+ /// Creates a natural cubic spline that interpolators given input (x,y) data
+ let public interpolatorNaturalSpline xydata =
+ if Array.length xydata <= 1 then interpolatorPiecewiseConstant xydata
+ else
+ let xdata,ydata = processTupleArray xydata
+ let ip = MathNet.Numerics.Interpolation.Algorithms.CubicSplineInterpolation(xdata,ydata)
+ Some (fun x -> ip.Interpolate x)
+
+ /// Creates a clamped cubic spline that inperpolates given input (x,y) data and has specified derivatives at it's endpoints.
+ let public interpolatorClampedSpline leftD rightD xydata =
+ if Array.length xydata <= 1 then interpolatorPiecewiseConstant xydata
+ else
+ let xdata,ydata = processTupleArray xydata
+ try // Not sure if this can ever fail for any inputs, try catch block isn't expensive here as long as no error occurs.
+ let ip = MathNet.Numerics.Interpolation.Algorithms.CubicSplineInterpolation(xdata,ydata,MathNet.Numerics.Interpolation.SplineBoundaryCondition.FirstDerivative,leftD,
+ MathNet.Numerics.Interpolation.SplineBoundaryCondition.FirstDerivative,rightD)
+ Some (fun x -> ip.Interpolate x)
+ with _ -> None
+
+ /// Creates a hermite spline that interpolators given input (x,y,y') data
+ let public interpolatorHermiteSpline xyy'data =
+ if Array.length xyy'data = 0 then None
+ elif Array.length xyy'data = 1 then
+ let x1, y1, d1 = xyy'data.[0]
+ Some(fun x -> y1 + d1*(x-x1))
+ else
+ let xdata, ydata, y'data = xyy'data |> Seq.distinctBy (fun (a,_,_) -> a)
+ |> Seq.sortBy (fun (a,_,_) -> a)
+ |> Array.ofSeq
+ |> Array.unzip3
+ try
+ let ip = MathNet.Numerics.Interpolation.Algorithms.CubicHermiteSplineInterpolation(xdata,ydata,y'data)
+ Some(fun x -> ip.Interpolate x)
+ with _ -> None
+
+
+ /// Creates a function that provides a linear least-square fit through given (x,y) input tuples
+ let public fitLinear xydata =
+ if Array.length xydata <= 1 then interpolatorPiecewiseConstant xydata
+ else
+ let xdata, ydata = xydata |> Array.unzip
+ Some(lineF xdata ydata)
+
+ /// Creates a function that provides a polynomial least-square fit with a degree that is at most equal to "degree" thorugh given (x,y) input tuples
+ let public fitPolynomial degree xydata =
+ if degree >= 0 && Array.length xydata > degree then
+ let xdata, ydata = xydata |> Array.unzip
+ Some(polynomialF degree xdata ydata)
+ else None
+
+
+
+ /// Calculates derivative values using the Arithmetic Mean Method (Wang 2006). Input are xdata and ydata arrays that are assumed to have been preprocessed to be sorted by x-value.
+ let private AMMDerivatives xdata ydata =
+ let n = Array.length xdata
+ if n <> Array.length ydata then raise (new System.ArgumentOutOfRangeException("Arrays of x-values and y-values should have same size"))
+ match n with
+ | 0 -> [||]
+ | 1 -> [|0.|]
+ | 2 -> [|(ydata.[1]-ydata.[0])/(xdata.[1]-xdata.[0])|]
+ | _ -> let h,D = Seq.zip xdata ydata |> Seq.windowed 2
+ |> Seq.map (fun [| (x1,y1); (x2,y2) |] -> x2-x1,(y2-y1)/(x2-x1))
+ |> Seq.toArray
+ |> Array.unzip
+ [| for i in 0 .. n-1 do
+ if i = 0 then yield D.[0] + (D.[0]-D.[1])*h.[0]/(h.[0]+h.[1])
+ elif i = n-1 then yield D.[i-1] + (D.[i-1]-D.[i-2])*h.[i-1]/(h.[i-1] + h.[i-2])
+ else yield (h.[i]*D.[i-1] + h.[i-1]*D.[i])/(h.[i-1]+h.[i]) |]
+
+ /// Calculates derivative values using the Kruger method which prevents overshoot problems.
+ let private krugerDerivatives xdata ydata =
+ let n = Array.length xdata
+ if n <> Array.length ydata then raise (new System.ArgumentOutOfRangeException("Arrays of x-values and y-values should have same size"))
+ match n with
+ | 0 -> [||]
+ | 1 -> [|0.|]
+ | 2 -> [|(ydata.[1]-ydata.[0])/(xdata.[1]-xdata.[0])|]
+ | _ -> let h,D = Seq.zip xdata ydata |> Seq.windowed 2
+ |> Seq.map (fun arr -> let (x1,y1) = arr.[0]
+ let (x2,y2) = arr.[1]
+ x2-x1,(y2-y1)/(x2-x1))
+ |> Seq.toArray
+ |> Array.unzip
+ let res = [| for i in 0 .. n-1 do
+ if i = 0 || i=n-1 then yield 0.
+ else yield 2./(1./D.[i-1] + 1./D.[i]) |]
+ res.[0] <- 1.5*D.[0] - 0.5*res.[1]
+ res.[n-1] <- 1.5*D.[n-2] - 0.5*res.[n-2]
+ res
+
+ /// Calculates derivative values using the Fritsch-Butland method which ensures local monoticity
+ let private FBDerivatives xdata ydata =
+ let n = Array.length xdata
+ if n <> Array.length ydata then raise (new System.ArgumentOutOfRangeException("Arrays of x-values and y-values should have same size"))
+ match n with
+ | 0 -> [||]
+ | 1 -> [|0.|]
+ | 2 -> [|(ydata.[1]-ydata.[0])/(xdata.[1]-xdata.[0])|]
+ | _ -> let h,D = Seq.zip xdata ydata |> Seq.windowed 2
+ |> Seq.map (fun arr -> let (x1,y1) = arr.[0]
+ let (x2,y2) = arr.[1]
+ x2-x1,(y2-y1)/(x2-x1))
+ |> Seq.toArray
+ |> Array.unzip
+ let res = [| for i in 0 .. n-1 do
+ if i = 0 || i=n-1 then yield 0.
+ else
+ let dmin = min D.[i] D.[i-1]
+ let dmax = max D.[i] D.[i-1]
+ if dmax = 0. && dmin = 0. then yield 0. else yield 3.*dmin*dmax/(dmax+2.*dmin) |]
+ res.[0] <- ((2.*h.[0]+h.[1])*D.[0] - h.[0]*D.[1])/(h.[0]+h.[1])
+ res.[n-1] <- ((2.*h.[n-2] + h.[n-3])*D.[n-2] - h.[n-2]*D.[n-3])/(h.[n-2]+h.[n-3])
+ res
+
+ /// Calculates derivative values using a geometric mean approach
+ let private GMDerivatives xdata ydata =
+ let n = Array.length xdata
+ if n <> Array.length ydata then raise (new System.ArgumentOutOfRangeException("Arrays of x-values and y-values should have same size"))
+ match n with
+ | 0 -> [||]
+ | 1 -> [|0.|]
+ | 2 -> [|(ydata.[1]-ydata.[0])/(xdata.[1]-xdata.[0])|]
+ | _ -> let h,D = Seq.zip xdata ydata |> Seq.windowed 2
+ |> Seq.map (fun arr -> let (x1,y1) = arr.[0]
+ let (x2,y2) = arr.[1]
+ x2-x1,(y2-y1)/(x2-x1))
+ |> Seq.toArray
+ |> Array.unzip
+ let D31 = (ydata.[2]-ydata.[0])/(xdata.[2]-xdata.[0])
+ let Dnn = (ydata.[n-1]-ydata.[n-3])/(xdata.[n-1]-xdata.[n-3])
+ [| for i in 0 .. n-1 do
+ if i = 0 then
+ if D31 = 0. || D.[0] = 0. then yield 0.
+ else
+ let frac = h.[0]/h.[1]
+ yield (D.[0]**(1.+frac))*(D31**(-frac))
+ elif i = n-1 then
+ if Dnn = 0. || D.[n-2] = 0. then yield 0.
+ else
+ let frac = h.[n-2]/h.[n-3]
+ yield (D.[n-2]**(1.+frac))*(Dnn**(-frac))
+ else
+ if D.[i] = 0. || D.[i-1] = 0. then yield 0.
+ else
+ yield (D.[i-1]**(h.[i]/(h.[i-1]+h.[i])))*(D.[i]**(h.[i-1]/(h.[i-1]+h.[i]))) |]
+
+ /// Creates a Fritsch-Butland spline, which I think (todo: check) is monotone
+ let public interpolatorFritschButlandSpline xydata =
+ if Array.length xydata <= 2 then interpolatorNaturalSpline xydata
+ else
+ let xdata,ydata = processTupleArray xydata
+ let y'data = FBDerivatives xdata ydata
+ Array.zip3 xdata ydata y'data |> interpolatorHermiteSpline
+
+
+ // todo : dougherty/hyman modification of arbitrary derivative values to produce monotone splines
+
\ No newline at end of file
diff --git a/BJExcelLib/Math.Interpolation2D.fs b/BJExcelLib/Math.Interpolation2D.fs
new file mode 100644
index 0000000..b9d72fe
--- /dev/null
+++ b/BJExcelLib/Math.Interpolation2D.fs
@@ -0,0 +1,33 @@
+namespace BJExcelLib.Math
+
+/// Contains functions for general interpolation and curve fitting.
+module Interpolation2D =
+ open BJExcelLib.Math.Interpolation
+
+ let flatten (A:'a[,]) = A |> Seq.cast<'a>
+
+ let getColumn c (A:_[,]) = flatten A.[*,c..c] |> Seq.toArray
+
+ let getRow r (A:_[,]) = flatten A.[r..r,*] |> Seq.toArray
+ let public build2DInterpolation (ipfunc : (float*float) [] -> (float -> float) option) (xkeys : float option []) (ykeys : float option []) (table : float option [,])=
+ let nrows = Array.length xkeys
+ let ncols = Array.length ykeys
+ fun x y ->
+ /// For every row of the table of x values, interpolate the current row at the value y
+ let xtable =
+ [| for r in 0 .. nrows - 1 do
+ if xkeys.[r].IsSome then
+ let rowdata = table |> getRow r
+ let rowIp = Array.zip ykeys rowdata |> Array.filter (fun (x,y) -> x.IsSome && y.IsSome)
+ |> Array.map (fun (x,y) -> x.Value, y.Value)
+ |> ipfunc
+ yield rowIp|]
+ /// Create a table of the form xkey,interpolated y value and interpolate this table
+ |> Array.mapi (fun r f -> xkeys.[r], if f.IsSome then Some(f.Value y) else None)
+ |> Array.filter (fun (r,rowIp) -> r.IsSome && rowIp.IsSome)
+ |> Array.map (fun (r,rowIp) -> r.Value,rowIp.Value)
+
+ ipfunc xtable |> fun z -> match z with | Some(f) -> (f x) |> Some
+ | None -> None
+
+
\ No newline at end of file
diff --git a/BJExcelLib/Math.Optimization.fs b/BJExcelLib/Math.Optimization.fs
new file mode 100644
index 0000000..b048efc
--- /dev/null
+++ b/BJExcelLib/Math.Optimization.fs
@@ -0,0 +1,23 @@
+namespace BJExcelLib.Math
+
+/// Contains some optimization functions. These are homebrewn until an implementation in MathNet.Numerics is available. The reason
+/// for that is that I would like to avoid using multiple libraries for different math functionality.
+module Optimization =
+
+ let private DIFFERENTATION_STEPSIZE = machine_epsilon |> sqrt
+
+ // TODO: see if I can get rid of using alglib here, would like to avoid the dependency altogether if possible
+ let public minimize f initial lb ub =
+ let lb = lb |> Array.map (FSharpx.Option.getOrElse System.Double.NegativeInfinity)
+ let ub = ub |> Array.map (FSharpx.Option.getOrElse System.Double.PositiveInfinity)
+
+ let alglibfvec = new alglib.ndimensional_fvec( fun x func obj -> func.[0] <- f x )
+ let epsg,epsf,epsx,maxits = 0.000000001, 0. ,0.,0
+
+ let sol = initial
+ let state = alglib.minlmcreatev(Array.length sol, sol, 0.0001)
+ do alglib.minlmsetcond(state, epsg, epsf, epsx, maxits)
+ do alglib.minlmsetbc(state,lb,ub)
+ do alglib.minlmoptimize(state, alglibfvec, null, null)
+ let final, rep = alglib.minlmresults(state)
+ final
\ No newline at end of file
diff --git a/BJExcelLib/NormalDistribution.fs b/BJExcelLib/NormalDistribution.fs
new file mode 100644
index 0000000..8f5d5f4
--- /dev/null
+++ b/BJExcelLib/NormalDistribution.fs
@@ -0,0 +1,88 @@
+namespace BJExcelLib.Math
+
+/// Module with functions for the normal distribution (univariate and bivariate at the moment)
+module public NormalDistribution =
+
+ // Other internal helper functions, self explanatory what they do
+ let private dist = MathNet.Numerics.Distributions.Normal()
+ let public nd x = dist.Density x
+ let public cnd x = dist.CumulativeDistribution x
+ let public cndc x = 1. - cnd x
+
+
+ /// Density of a bivariate normal distribution where the first marginal distribution is N(m1,s1^2) distributed and the second
+ /// marginal is N(m2,s2^2). The correlation between the variables is rho. The distribution is evaluated at X1=x1 and X2=x2.
+ let public pdf2 (m1,s1) (m2,s2) rho x1 x2 =
+ let x1 = (x1-m1)/s1
+ let x2 = (x2-m2)/s2
+ let rfac = (1.-rho*rho)
+ exp(-0.5*(x1*x1-2.*rho*x1*x2+x2*x2)/rfac)/(rfac*s1*s2)
+
+
+ /// Some constants for the below bivariate cumulative distribution function
+ let private weights1 = [ 0.17132449237917; 0.17132449237917;0.360761573048138; 0.360761573048138;0.46791393457269;0.46791393457269 ]
+ let private values1 = [ 0.966234757101576;0.033765242898424;0.830604693233133; 0.169395306766867;0.619309593041598;0.380690406958402 ]
+ let private weights2 = [ 0.0471753363865118; 0.0471753363865118; 0.106939325995318; 0.106939325995318;0.160078328543346; 0.160078328543346; 0.203167426723066; 0.20316742672306; 0.233492536538355; 0.233492536538355; 0.249147045813403; 0.249147045813403]
+ let private values2 = [ 0.99078031712336; 0.00921968287664;0.95205862818524; 0.04794137181476;0.88495133709715;0.11504866290285;0.79365897714331;0.20634102285669;0.68391574949909;0.31608425050091;0.56261670425574;0.43738329574427]
+ let private weights3 = [0.0176140071391521; 0.0176140071391521; 0.0406014298003869;0.0406014298003869; 0.0626720483341091; 0.0626720483341091;0.0832767415767048; 0.0832767415767048; 0.1019301198172400;0.1019301198172400; 0.1181945319615180; 0.1181945319615180;0.1316886384491770; 0.1316886384491770; 0.1420961093183820;0.1420961093183820; 0.1491729864726040; 0.1491729864726040;0.1527533871307260; 0.1527533871307260]
+ let private values3 = [ 0.99656429959255; 0.00343570040745; 0.98198596363896;0.01801403636104; 0.95611721412566; 0.04388278587434;0.91955848591111; 0.08044151408889; 0.87316595323008;0.12683404676992; 0.81802684036326; 0.18197315963674;0.75543350097541; 0.24456649902459; 0.68685304435771;0.31314695564229; 0.61389292557082; 0.38610707442918;0.53826326056675; 0.46173673943325 ]
+ let private values4 = [ 0.000047216149159;3.972561612889520;0.001298022024068;3.857185731135710;0.007702795584372;3.656640508589690;0.025883348755653;3.382351236044530;0.064296126805960;3.050028889390040;0.136503050785829;2.658650279846430;0.239251089780572;2.282719097583890;0.392244063312137;1.887068418173820;0.596314691697034;1.507458096263630;0.984753258857668;1.015363867311070 ]
+
+
+ /// Cumulative bivariate normal distribution, adapted from Haug's Option Pricing formulas,
+ /// who credits Graeme West, who implemented an algorithm of Genze. (mi,si) are the mean and
+ /// standard deviation of the i-th variable, rho is the correlation and x1 and x2 are the
+ /// points where the cumulative distribution is evaluated at.
+ let public cdf2 (m1,s1) (m2,s2) rho x1 x2 =
+ let x = (x1-m1)/s1
+ let y = (x2-m2)/s2
+
+ let rho = min 1. (max -1. rho)
+ let h = -x
+ let k = if rho < -0.925 then y else -y
+ let hk = h * k
+ match rho with
+ | x when x = 0. -> cnd(-h) * cnd(-k)
+ | x when rho = 1. -> cnd(-(max h k))
+ | x when rho = -1. -> if k > h then cnd(k)-cnd(h) else 0.
+ | x when abs(x) < 0.925 -> let weights, values = match x with
+ | r when abs(r) < 0.3 -> weights1,values1
+ | r when abs(r) < 0.75 -> weights2,values2
+ | _ -> weights3,values3
+ let hs = 0.5*(h*h+k*k)
+ let asr_ = System.Math.Asin(rho)
+ values |> List.map(fun z -> let sn = System.Math.Sin(asr_ * z)
+ exp((sn*hk-hs)/(1.-sn*sn)) )
+ |> List.zip weights
+ |> List.sumBy (fun (w,v) -> w*v)
+ |> fun x -> x * asr_/(4.*System.Math.PI) + cnd(-h) *cnd(-k)
+
+ | _ -> // 0.925 < abs(x) < 1
+ let ass = (1.-rho)*(1.+rho)
+ let A = sqrt(ass)
+ let bs = (h-k)*(h-k)
+ let c = (4.-hk) / 8.
+ let d = (12.-hk)/16.
+ let asr_ = -0.5*(bs / ass + hk)
+ let BVN = if asr_ <= -100. then 0.
+ else A*exp(asr_)*(1.-c*(bs-ass)*(1.-0.2*d*bs) / 3. + 0.2*c* d*ass*ass)
+ let BVN = if -hk >= 100. then BVN
+ else
+ let B = sqrt(bs)
+ BVN - sqrt(2.*System.Math.PI)*exp(-0.5*hk)*cnd(-B/A)*B*(1.-c*bs*(1.-0.2*d*bs)/3.)
+
+ let A = 0.5*A
+ let A2 = A*A
+
+ values4 |> List.zip weights3
+ |> List.sumBy(fun (w,x) -> let xs = A2*x
+ let asr_ = -0.5*(bs / xs + hk)
+ if asr_ < -100. then 0.
+ else
+ let rs = sqrt(1.-xs)
+ A*w*exp(asr_)*(exp(-hk*(1.-rs)/(2.*(1.+rs)))/rs - (1.+c*xs*(1.+d*xs))))
+ |> fun x -> let BVN = -(BVN+x)/(2.*System.Math.PI)
+ match rho > 0. with
+ | true -> BVN + cnd(-(max h k))
+ | false -> if k > h then -BVN + cnd(k)-cnd(h) else -BVN
+
diff --git a/BJExcelLib/UDF.American.fs b/BJExcelLib/UDF.American.fs
new file mode 100644
index 0000000..92f808d
--- /dev/null
+++ b/BJExcelLib/UDF.American.fs
@@ -0,0 +1,67 @@
+namespace BJExcelLib.UDF
+
+/// Contains UDFs for american option approximations functions
+module public American=
+ open ExcelDna.Integration
+ open BJExcelLib.ExcelDna.IO
+ open BJExcelLib.Finance
+
+ /// Validates black-scholes input parameters
+ let private validateBS S K v r b T flag marg =
+ let S = S |> validateFloat |> Option.bind (fun x -> if x < 0. then None else Some(x))
+ let K = K |> validateFloat |> Option.bind (fun x -> if x < 0. then None else Some(x))
+ let v = v |> validateFloat |> Option.bind (fun x -> if x <= 0. then None else Some(x))
+ let r = r |> validateFloat |> FSharpx.Option.getOrElse 0.
+ let b = b |> validateFloat |> FSharpx.Option.getOrElse r
+ let T = T |> validateFloat |> Option.bind (fun x -> if x <= 0. then None else Some(x))
+ let flag = flag |> validateString
+ |> Option.bind (fun (x : string)-> let z = (x.ToUpper())
+ if z.Length = 0 then None
+ else
+ let z = z.[0]
+ if z = 'C' then Some(Call) elif z = 'P' then Some(Put) else None )
+ |> FSharpx.Option.getOrElse (if S <= K then Call else Put)
+ let marg = marg |> validateBool |> FSharpx.Option.getOrElse false
+ if S.IsSome && K.IsSome && T.IsSome && v.IsSome then Some(S.Value,K.Value,v.Value,r,b,T.Value,flag,marg) else None
+
+
+ []
+ let UDFJZValue ([] S : obj,
+ [] K : obj,
+ [] v : obj,
+ [] T : obj,
+ [] r : obj,
+ [] b : obj,
+ [] flag : obj,
+ [] marg : obj) =
+ validateBS S K v r b T flag marg
+ |> Option.bind (fun (S,K,v,r,b,T,flag,marg) -> JuZhong.value marg flag S K T r b v)
+ |> valueToExcel
+
+(*TODO: these need to be checked first
+ []
+ let UDFJZDelta ([] S : obj,
+ [] K : obj,
+ [] v : obj,
+ [] T : obj,
+ [] r : obj,
+ [] b : obj,
+ [] flag : obj,
+ [] marg : obj) =
+ validateBS S K v r b T flag marg
+ |> Option.bind (fun (S,K,v,r,b,T,flag,marg) -> JuZhong.delta marg flag S K T r b v)
+ |> valueToExcel
+
+ []
+ let UDFJZGamma ([] S : obj,
+ [] K : obj,
+ [] v : obj,
+ [] T : obj,
+ [] r : obj,
+ [] b : obj,
+ [] flag : obj,
+ [] marg : obj) =
+ validateBS S K v r b T flag marg
+ |> Option.bind (fun (S,K,v,r,b,T,flag,marg) -> JuZhong.gamma marg flag S K T r b v)
+ |> valueToExcel
+ *)
diff --git a/BJExcelLib/UDF.Blackscholes.fs b/BJExcelLib/UDF.Blackscholes.fs
new file mode 100644
index 0000000..c41b3da
--- /dev/null
+++ b/BJExcelLib/UDF.Blackscholes.fs
@@ -0,0 +1,294 @@
+namespace BJExcelLib.UDF
+
+/// Contains UDFs for black-scholes functions
+module public Blackscholes=
+ open ExcelDna.Integration
+ open BJExcelLib.ExcelDna.IO
+ open BJExcelLib.Finance
+
+ /// Validates black-scholes input parameters
+ let private validateBS S K v r b T flag marg =
+ let S = S |> validateFloat |> Option.bind (fun x -> if x < 0. then None else Some(x))
+ let K = K |> validateFloat |> Option.bind (fun x -> if x < 0. then None else Some(x))
+ let v = v |> validateFloat |> Option.bind (fun x -> if x <= 0. then None else Some(x))
+ let r = r |> validateFloat |> FSharpx.Option.getOrElse 0.
+ let b = b |> validateFloat |> FSharpx.Option.getOrElse r
+ let T = T |> validateFloat |> Option.bind (fun x -> if x <= 0. then None else Some(x))
+ let flag = flag |> validateString
+ |> Option.bind (fun (x : string)-> let z = (x.ToUpper())
+ if z.Length = 0 then None
+ else
+ let z = z.[0]
+ if z = 'C' then Some(Call) elif z = 'P' then Some(Put) else None )
+ |> FSharpx.Option.getOrElse (if S <= K then Call else Put)
+ let marg = marg |> validateBool |> FSharpx.Option.getOrElse false
+ if S.IsSome && K.IsSome && T.IsSome && v.IsSome then Some(S.Value,K.Value,v.Value,r,b,T.Value,flag,marg) else None
+
+ // Generalized BS UDFs
+ []
+ let UDFForward ([] S : obj,
+ [] T : obj,
+ [] b : obj) =
+ validateBS S S 0.1 0. b T Call false
+ |> Option.bind (fun (S,_,_,_,b,T,_,marg) -> Blackscholes.forward S T b)
+ |> valueToExcel
+
+ []
+ let UDFBSValue ([] S : obj,
+ [] K : obj,
+ [] v : obj,
+ [] T : obj,
+ [] r : obj,
+ [] b : obj,
+ [] flag : obj,
+ [] marg : obj) =
+ validateBS S K v r b T flag marg
+ |> Option.bind (fun (S,K,v,r,b,T,flag,marg) -> Blackscholes.value marg flag S K T r b v)
+ |> valueToExcel
+
+ []
+ let UDFBSDelta ([] S : obj,
+ [] K : obj,
+ [] v : obj,
+ [] T : obj,
+ [] r : obj,
+ [] b : obj,
+ [] flag : obj,
+ [] marg : obj) =
+ validateBS S K v r b T flag marg
+ |> Option.bind (fun (S,K,v,r,b,T,flag,marg) -> Blackscholes.delta marg flag S K T r b v)
+ |> valueToExcel
+
+ []
+ let UDFBSvega ([] S : obj,
+ [] K : obj,
+ [] v : obj,
+ [] T : obj,
+ [] r : obj,
+ [] b : obj,
+ [] flag : obj,
+ [] marg : obj) =
+ validateBS S K v r b T flag marg
+ |> Option.bind (fun (S,K,v,r,b,T,flag,marg) -> Blackscholes.vega marg flag S K T r b v)
+ |> valueToExcel
+
+ []
+ let UDFBSgamma ([] S : obj,
+ [] K : obj,
+ [] v : obj,
+ [] T : obj,
+ [] r : obj,
+ [] b : obj,
+ [] flag : obj,
+ [] marg : obj) =
+ validateBS S K v r b T flag marg
+ |> Option.bind (fun (S,K,v,r,b,T,flag,marg) -> Blackscholes.gamma marg flag S K T r b v)
+ |> valueToExcel
+
+ []
+ let UDFBSdualdelta ([] S : obj,
+ [] K : obj,
+ [] v : obj,
+ [] T : obj,
+ [] r : obj,
+ [] b : obj,
+ [] flag : obj,
+ [] marg : obj) =
+ validateBS S K v r b T flag marg
+ |> Option.bind (fun (S,K,v,r,b,T,flag,marg) -> Blackscholes.dualdelta marg flag S K T r b v)
+ |> valueToExcel
+
+ []
+ let UDFBSrho ([] S : obj,
+ [] K : obj,
+ [] v : obj,
+ [] T : obj,
+ [] r : obj,
+ [] b : obj,
+ [] flag : obj,
+ [] marg : obj) =
+ validateBS S K v r b T flag marg
+ |> Option.bind (fun (S,K,v,r,b,T,flag,marg) -> Blackscholes.rho marg flag S K T r b v)
+ |> valueToExcel
+
+ []
+ let UDFBStheta ([] S : obj,
+ [] K : obj,
+ [] v : obj,
+ [] T : obj,
+ [] r : obj,
+ [] b : obj,
+ [] flag : obj,
+ [] marg : obj) =
+ validateBS S K v r b T flag marg
+ |> Option.bind (fun (S,K,v,r,b,T,flag,marg) -> Blackscholes.theta marg flag S K T r b v)
+ |> valueToExcel
+
+ []
+ let UDFBSdvegadspot([] S : obj,
+ [] K : obj,
+ [] v : obj,
+ [] T : obj,
+ [] r : obj,
+ [] b : obj,
+ [] flag : obj,
+ [] marg : obj) =
+ validateBS S K v r b T flag marg
+ |> Option.bind (fun (S,K,v,r,b,T,flag,marg) -> Blackscholes.dvegadspot marg flag S K T r b v)
+ |> valueToExcel
+
+ []
+ let UDFBSdvegadvol ([] S : obj,
+ [] K : obj,
+ [] v : obj,
+ [] T : obj,
+ [] r : obj,
+ [] b : obj,
+ [] flag : obj,
+ [] marg : obj) =
+ validateBS S K v r b T flag marg
+ |> Option.bind (fun (S,K,v,r,b,T,flag,marg) -> Blackscholes.dvegadvol marg flag S K T r b v)
+ |> valueToExcel
+
+ []
+ let UDFBSddeltadtime ([] S : obj,
+ [] K : obj,
+ [] v : obj,
+ [] T : obj,
+ [] r : obj,
+ [] b : obj,
+ [] flag : obj,
+ [] marg : obj) =
+ validateBS S K v r b T flag marg
+ |> Option.bind (fun (S,K,v,r,b,T,flag,marg) -> Blackscholes.ddeltadtime marg flag S K T r b v)
+ |> valueToExcel
+
+ []
+ let UDFBSvaldeltavega ([] S : obj,
+ [] K : obj,
+ [] v : obj,
+ [] T : obj,
+ [] r : obj,
+ [] b : obj,
+ [] flag : obj,
+ [] marg : obj) =
+ validateBS S K v r b T flag marg
+ |> Option.bind (fun (S,K,v,r,b,T,flag,marg) -> Blackscholes.valuedeltavega marg flag S K T r b v)
+ |> Option.map (fun (v,d,vega)-> [|v;d;vega|])
+ |> FSharpx.Option.getOrElse Array.empty
+ |> arrayToExcelRow
+
+ []
+ let UDFBSImplied ([] S : obj,
+ [] K : obj,
+ [] prem : obj,
+ [] T : obj,
+ [] r : obj,
+ [] b : obj,
+ [] flag : obj,
+ [] marg : obj) =
+ validateBS S K 0.10 r b T flag marg // Just use some dummy value for v
+ |> Option.bind (fun (S,K,_,r,b,T,flag,marg) -> let prem = prem |> validateFloat |> Option.bind (fun x -> if x <= 0. then None else Some(x))
+ if prem.IsNone then None else Blackscholes.impliedvol marg flag S K T r b prem.Value)
+ |> valueToExcel
+
+
+ // UDF for Black-Scholes on futures
+ []
+ let USDBSFutureValue ([] S : obj,
+ [] K : obj,
+ [] v : obj,
+ [] T : obj,
+ [] r : obj,
+ [] flag : obj,
+ [] marg : obj) =
+ validateBS S K v r 0. T flag marg
+ |> Option.bind (fun (S,K,v,r,_,T,flag,marg) -> Blackscholes.value marg flag S K T r 0. v)
+ |> valueToExcel
+
+ []
+ let USDBSFutureDelta ([] S : obj,
+ [] K : obj,
+ [] v : obj,
+ [] T : obj,
+ [] r : obj,
+ [] flag : obj,
+ [] marg : obj) =
+ validateBS S K v r 0. T flag marg
+ |> Option.bind (fun (S,K,v,r,_,T,flag,marg) -> Blackscholes.delta marg flag S K T r 0. v)
+ |> valueToExcel
+
+ []
+ let USDBSFuturevega([] S : obj,
+ [] K : obj,
+ [] v : obj,
+ [] T : obj,
+ [] r : obj,
+ [] flag : obj,
+ [] marg : obj) =
+ validateBS S K v r 0. T flag marg
+ |> Option.bind (fun (S,K,v,r,_,T,flag,marg) -> Blackscholes.vega marg flag S K T r 0. v)
+ |> valueToExcel
+
+ []
+ let UDFBSFutureImplied ([] S : obj,
+ [