Skip to content

Commit 6818939

Browse files
committed
Add options data retrieval and some documentation.
1 parent 82a3ca3 commit 6818939

File tree

8 files changed

+247
-10
lines changed

8 files changed

+247
-10
lines changed

README.md

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ I've taken features from the following projects, chosen for their stability, wid
3939
- [x] Single security quote history (yahoo)
4040
- [x] Single security dividend/split history (yahoo)
4141
- [x] Symbols download (bats)
42-
- [ ] Option chains (yahoo)
42+
- [x] Option chains (google)
4343
- [x] Currency pairs quotes
4444
- [ ] International securities quotes
4545
- [ ] Sector/Industry components
@@ -164,7 +164,57 @@ func main() {
164164

165165
```
166166

167+
### Options chains
167168

169+
```go
170+
package main
171+
172+
import (
173+
"fmt"
174+
175+
"github.com/FlashBoys/go-finance"
176+
)
177+
178+
func main() {
179+
180+
// Fetches the available expiration dates.
181+
chain, err := finance.NewOptionsChain("AAPL")
182+
if err != nil {
183+
panic(err)
184+
}
185+
186+
// Fetches puts and calls for the closest expiration date.
187+
err = chain.FetchOptionsExpiringNext()
188+
if err != nil {
189+
panic(err)
190+
}
191+
192+
fmt.Println(chain.Calls)
193+
fmt.Println(chain.Puts)
194+
195+
}
196+
197+
```
198+
199+
### Currency pairs quotes
200+
201+
```go
202+
package main
203+
204+
import (
205+
"fmt"
206+
207+
"github.com/FlashBoys/go-finance"
208+
)
209+
210+
func main() {
211+
212+
// Fetches the quote for USD/GBP pair.
213+
quote := finance.GetCurrencyPairQuote(finance.USDGBP)
214+
fmt.Println(quote)
215+
}
216+
217+
```
168218

169219

170220

@@ -175,4 +225,4 @@ Given Yahoo! Finance's own perpetuation of a rabbit warren -like system of finan
175225

176226
While dataframes (tabular data structures used for analytical operations atypical of what you see in the beaten track of web programming) are popular in the financial development community, those concepts are not the focus of this project. A good place to start there would be [Gota](https://github.com/kniren/gota). In the future, tabular data will be the focus! I just have to determine the easiest way to integrate it in my current projects.
177227

178-
Yahoo also does not currently support a way to download a master list of symbols available. I compromised and am using BATS list for now.
228+
Yahoo also does not currently support a way to download a master list of symbols available. I compromised and am using the BATS list for now.

chain.go

Lines changed: 0 additions & 1 deletion
This file was deleted.

cmd/main.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@ import (
88

99
func main() {
1010

11-
// Request all BATS symbols.
12-
q := finance.GetCurrencyPairQuote(finance.USDGBP)
13-
fmt.Println(q)
14-
11+
// Fetches the quote for USD/GBP pair.
12+
quote := finance.GetCurrencyPairQuote(finance.USDGBP)
13+
fmt.Println(quote)
1514
}

models/contract.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package models
2+
3+
import "github.com/shopspring/decimal"
4+
5+
// Contract represents an instance of an option contract.
6+
type Contract struct {
7+
ContractID string
8+
Security string
9+
Strike decimal.Decimal
10+
Price decimal.Decimal
11+
ChangeNominal decimal.Decimal
12+
ChangePercent decimal.Decimal
13+
Bid decimal.Decimal
14+
Ask decimal.Decimal
15+
Volume int
16+
OpenInterest int
17+
}
18+
19+
// NewContract creates a new instance of an options contract.
20+
func NewContract(option map[string]string) Contract {
21+
22+
c := Contract{
23+
ContractID: option["cid"],
24+
Security: option["s"],
25+
Strike: ToDecimal(option["strike"]),
26+
Price: ToDecimal(option["p"]),
27+
ChangeNominal: ToDecimal(option["c"]),
28+
Bid: ToDecimal(option["b"]),
29+
Ask: ToDecimal(option["a"]),
30+
Volume: ToInt(option["vol"]),
31+
OpenInterest: ToInt(option["oi"]),
32+
}
33+
34+
if c.Price.IntPart() != 0 {
35+
hundred, _ := decimal.NewFromString("100")
36+
c.ChangePercent = ((c.ChangeNominal).Div(c.Price.Sub(c.ChangeNominal))).Mul(hundred).Truncate(2)
37+
}
38+
39+
return c
40+
}
41+
42+
// NewContractSlice creates a new slice of contracts.
43+
func NewContractSlice(options []map[string]string) (contracts []Contract) {
44+
45+
for _, op := range options {
46+
contracts = append(contracts, NewContract(op))
47+
}
48+
49+
return contracts
50+
}

models/derivative.go

Lines changed: 0 additions & 1 deletion
This file was deleted.

models/utils.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ func ToFloat(value interface{}) float64 {
2424
// ToInt converts a string to an int.
2525
func ToInt(value string) int {
2626

27-
if value == "N/A" {
27+
if value == "N/A" || value == "-" {
2828
return 0
2929
}
3030

@@ -38,7 +38,7 @@ func ToInt(value string) int {
3838
// ToDecimal converts a string to a decimal value.
3939
func ToDecimal(value string) decimal.Decimal {
4040

41-
if value == "N/A" {
41+
if value == "N/A" || value == "-" {
4242
return decimal.Decimal{}
4343
}
4444

options.go

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
package finance
2+
3+
import (
4+
"encoding/json"
5+
"strconv"
6+
"time"
7+
8+
"github.com/FlashBoys/go-finance/models"
9+
"github.com/shopspring/decimal"
10+
)
11+
12+
const (
13+
optionsURL = "http://www.google.com/finance/option_chain?"
14+
)
15+
16+
// OptionChain contains the option contracts for a given symbol and expiration.
17+
type OptionChain struct {
18+
Symbol string
19+
Expirations []time.Time
20+
UnderlyingPrice decimal.Decimal
21+
Calls []models.Contract
22+
Puts []models.Contract
23+
}
24+
25+
type timeMap struct {
26+
Month string `json:"m"`
27+
Day string `json:"d"`
28+
Year string `json:"y"`
29+
}
30+
31+
type fetchResult struct {
32+
Expiry json.RawMessage `json:"expiry"`
33+
Expirations []timeMap `json:"expirations"`
34+
Underlying json.RawMessage `json:"underlying_id"`
35+
Price string `json:"underlying_price"`
36+
Calls []map[string]string `json:"calls,omitempty"`
37+
Puts []map[string]string `json:"puts,omitempty"`
38+
}
39+
40+
// NewOptionsChain creates a new OptionChain instance.
41+
func NewOptionsChain(symbol string) (oc *OptionChain, err error) {
42+
43+
oc = &OptionChain{Symbol: symbol}
44+
oc.Expirations, oc.UnderlyingPrice, err = fetchExpirations(symbol)
45+
46+
return oc, err
47+
}
48+
49+
func fetchExpirations(symbol string) (expirations []time.Time, price decimal.Decimal, err error) {
50+
51+
params := map[string]string{
52+
"q": symbol,
53+
"expd": "4",
54+
"expm": "4",
55+
"expy": "2014",
56+
"output": "json",
57+
}
58+
59+
url := buildURL(optionsURL, params)
60+
b, err := request(url)
61+
if err != nil {
62+
return expirations, price, err
63+
}
64+
65+
var fr fetchResult
66+
err = json.Unmarshal(b, &fr)
67+
if err != nil {
68+
return expirations, price, err
69+
}
70+
71+
for _, t := range fr.Expirations {
72+
dString := t.Month + "/" + t.Day + "/" + t.Year
73+
expirations = append(expirations, models.ParseDate(dString))
74+
}
75+
76+
price = models.ToDecimal(fr.Price)
77+
78+
return expirations, price, err
79+
}
80+
81+
// FetchOptionsExpiringNext fetches calls and puts with the shortest expiration date.
82+
func (chain *OptionChain) FetchOptionsExpiringNext() (err error) {
83+
84+
expiry := chain.Expirations[0]
85+
86+
params := map[string]string{
87+
"q": chain.Symbol,
88+
"expd": strconv.Itoa(expiry.Day()),
89+
"expm": strconv.Itoa(int(expiry.Month())),
90+
"expy": strconv.Itoa(expiry.Year()),
91+
"output": "json",
92+
}
93+
94+
url := buildURL(optionsURL, params)
95+
96+
b, err := request(url)
97+
if err != nil {
98+
return err
99+
}
100+
101+
var fr fetchResult
102+
err = json.Unmarshal(b, &fr)
103+
if err != nil {
104+
return err
105+
}
106+
107+
chain.Calls = models.NewContractSlice(fr.Calls)
108+
chain.Puts = models.NewContractSlice(fr.Puts)
109+
110+
return err
111+
}

request.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,15 @@ package finance
22

33
import (
44
"encoding/csv"
5+
"io/ioutil"
56
"net/http"
67
"net/url"
8+
"regexp"
9+
)
10+
11+
var (
12+
firstRegex = regexp.MustCompile(`(\w+:)(\d+\.?\d*)`)
13+
secondRegex = regexp.MustCompile(`(\w+):`)
714
)
815

916
// requestCSV fetches a csv from a supplied URL.
@@ -35,3 +42,25 @@ func buildURL(base string, params map[string]string) string {
3542

3643
return url.String()
3744
}
45+
46+
// request fetches a file from a supplied URL.
47+
func request(url string) ([]byte, error) {
48+
resp, err := http.Get(url)
49+
if err != nil {
50+
return []byte{}, err
51+
}
52+
defer resp.Body.Close()
53+
contents, err := ioutil.ReadAll(resp.Body)
54+
if err != nil {
55+
return []byte{}, err
56+
}
57+
result := string(contents)
58+
59+
return transformResult(result), err
60+
}
61+
62+
func transformResult(input string) []byte {
63+
64+
json := firstRegex.ReplaceAllString(input, "$1\"$2\"")
65+
return []byte(secondRegex.ReplaceAllString(json, "\"$1\":"))
66+
}

0 commit comments

Comments
 (0)