From 47f64bc0ea17ba577bbf99b556e491db5b3d1aca Mon Sep 17 00:00:00 2001 From: Kevin Date: Thu, 14 Sep 2017 23:55:47 -0400 Subject: [PATCH] Initial commit --- .gitignore | 14 +++++ LICENSE | 20 +++++++ README | 0 models.go | 155 +++++++++++++++++++++++++++++++++++++++++++++++++++++ serve.go | 34 ++++++++++++ 5 files changed, 223 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README create mode 100644 models.go create mode 100644 serve.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a1338d6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +# Binaries for programs and plugins +*.exe +*.dll +*.so +*.dylib + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 +.glide/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..58b8dd8 --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2017 Kevin Keogh - kevin.d.keogh@gmail.com + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README b/README new file mode 100644 index 0000000..e69de29 diff --git a/models.go b/models.go new file mode 100644 index 0000000..a6a9488 --- /dev/null +++ b/models.go @@ -0,0 +1,155 @@ +package main + +import ( + "fmt" + "math" + "math/rand" + "time" +) + +// RFC8601Time is a time.Time wrapper +type RFC8601Time struct { + time.Time +} + +func (t RFC8601Time) Sub(u RFC8601Time) time.Duration { + return t.Sub(u) +} + +func (t RFC8601Time) MarshalJSON() ([]byte, error) { + fmt.Println("Here!") + stamp := fmt.Sprintf("\"%s\"", t.Format("2006-01-02")) + return []byte(stamp), nil +} + +// Option struct that holds all the details of an option +// Option details +type Option struct { + optType int64 + strike float64 + expiryDate RFC8601Time + + // Market data + valueDate RFC8601Time + spot float64 + rfr float64 + vol float64 + sims int64 + + // Results + fv float64 + delta float64 + vega float64 + rho float64 + gamma float64 + theta float64 +} + +// Single simulation of Geometric Brownian Motion +func gbmSimulation(spot float64, rfr float64, vol float64, tte float64, randNum float64) float64 { + var drift, stoch float64 + + drift = (rfr - math.Pow(vol, 2)/2) * tte + stoch = vol * math.Pow(tte, 0.5) * randNum + return spot * math.Exp(drift+stoch) +} + +// RunSimulations is the main function that runs Monte Carlo simulations +// for an option. Note that it runs normal Geometric Brownian Motion +// to calculate the future spot levels +func RunSimulations(opt *Option) { + + var i int64 + var level float64 + + levels := make([]float64, opt.sims) + + tte := opt.expiryDate.Sub(opt.valueDate).Hours() / (24 * 365) + + for i = 0; i < opt.sims; i++ { + randNum := rand.NormFloat64() + // Base + levels[i] = gbmSimulation(opt.spot, opt.rfr, opt.vol, tte, randNum) + opt.fv += math.Max((levels[i]-opt.strike)*float64(opt.optType), 0) + + // Delta + level = gbmSimulation(opt.spot+0.0001, opt.rfr, opt.vol, tte, randNum) + opt.delta += math.Max((level-opt.strike)*float64(opt.optType), 0) + + // Gamma -- TODO: Doesn't look right + level = gbmSimulation(opt.spot+0.0001, opt.rfr, opt.vol, tte, randNum) + level += gbmSimulation(opt.spot-0.0001, opt.rfr, opt.vol, tte, randNum) + level -= 2 * gbmSimulation(opt.spot, opt.rfr, opt.vol, tte, randNum) + opt.gamma += math.Max((level-opt.strike)*float64(opt.optType), 0) + + // Vega + level = gbmSimulation(opt.spot, opt.rfr, opt.vol+0.0001, tte, randNum) + opt.vega += math.Max((level-opt.strike)*float64(opt.optType), 0) + + // Theta + level = gbmSimulation(opt.spot, opt.rfr, opt.vol, tte-1./365, randNum) + opt.theta += math.Max((level-opt.strike)*float64(opt.optType), 0) + + // Rho -- TODO: Doesn't look right + level = gbmSimulation(opt.spot, opt.rfr+0.0001, opt.vol, tte, randNum) + opt.rho += math.Max((level-opt.strike)*float64(opt.optType), 0) + } + + df := math.Exp(-opt.rfr * tte) + opt.fv = opt.fv / float64(opt.sims) * df + opt.delta = (opt.delta/float64(opt.sims)*df - opt.fv) / 0.0001 + opt.gamma = (opt.gamma/float64(opt.sims)*df - opt.fv) / 10000 + opt.vega = (opt.vega/float64(opt.sims)*df - opt.fv) / 0.01 + opt.theta = (opt.theta/float64(opt.sims)*math.Exp(-opt.rfr*(tte-1./365)) - opt.fv) / -(1. / 365) + opt.rho = (opt.rho/float64(opt.sims)*math.Exp(-(opt.rfr+0.01)*tte) - opt.fv) +} + +/* +func main() { + value := time.Date(2016, 12, 30, 0, 0, 0, 0, time.UTC) + expiry := time.Date(2017, 12, 30, 0, 0, 0, 0, time.UTC) + opt := Option{optType: 1, strike: 100, expiryDate: expiry, valueDate: value, spot: 100, rfr: 0.03, vol: 0.25, sims: 1000000} + rand.Seed(time.Now().UTC().UnixNano()) + RunSimulations(&opt) + + fmt.Printf( + ` + Valuation date: %s + + + | BS Analytic | BS Monte Carlo | + --------------------------------------------- + |Type: | %10s | %13s | + |Spot: | %10.2f | %13.2f | + |Expiry: | %s | %s | + |Strike: | %10.2f | %13.2f | + |Risk-free: | %10.2f%% | %13.2f%% | + |Implied Vol:| %10.2f%% | %13.2f%% | + --------------------------------------------- + |Fair value: | %8.4f | %11.4f | + |Delta: | %8.4f | %11.4f | + |Gamma: | %8.4f | %11.4f | + |Vega: | %8.4f | %11.4f | + |Theta: | %8.4f | %11.4f | + |Rho: | %8.4f | %11.4f | + |Simulations:| | %11d | + --------------------------------------------- + +`, + value.Format("2006-01-02"), + "Call", "Call", + opt.spot, opt.spot, + expiry.Format("2006-01-02"), expiry.Format("2006-01-02"), + opt.strike, opt.strike, + opt.rfr*100, opt.rfr*100, + opt.vol*100, opt.vol*100, + opt.fv, opt.fv, + opt.delta, opt.delta, + opt.gamma, opt.gamma, + opt.vega, opt.vega, + opt.theta, opt.theta, + opt.rho, opt.rho, + opt.sims) + +} +*/ diff --git a/serve.go b/serve.go new file mode 100644 index 0000000..dd1edfc --- /dev/null +++ b/serve.go @@ -0,0 +1,34 @@ +package main + +import ( + "encoding/json" + "fmt" + "net/http" +) + +func index(w http.ResponseWriter, r *http.Request) { + formatRequest(r) + fmt.Fprintf(w, "Hello go!\n") +} + +func formatRequest(r *http.Request) { + + var opt Option + err := json.NewDecoder(r.Body).Decode(&opt) + if err != nil { + fmt.Printf("Error: %s\n", err) + } + + fmt.Println("Option type:", opt.optType) + fmt.Println("Strike:", opt.strike) + fmt.Println("Expiry date:", opt.expiryDate.Format("2006-01-02")) + + for k, v := range r.Header { + fmt.Printf("%v: %v\n", k, v) + } +} + +func main() { + http.HandleFunc("/", index) + http.ListenAndServe(":8080", nil) +}