You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
mop/yahoo_quotes.go

186 lines
5.2 KiB

// Copyright (c) 2013 by Michael Dvorkin. All Rights Reserved.
11 years ago
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
//-----------------------------------------------------------------------------
11 years ago
package mop
import (
12 years ago
`bytes`
`fmt`
`io/ioutil`
`net/http`
`reflect`
)
12 years ago
// See http://www.gummy-stuff.org/Yahoo-stocks.htm
12 years ago
// Also http://query.yahooapis.com/v1/public/yql
// ?q=select%20*%20from%20yahoo.finance.quotes%20where%20symbol%20in(%22ALU%22,%22AAPL%22)
12 years ago
// &env=http%3A%2F%2Fstockstables.org%2Falltables.env
12 years ago
// &format=json'
//
// Current, Change, Open, High, Low, 52-W High, 52-W Low, Volume, AvgVolume, P/E, Yield, Market Cap.
// l1: last trade
// c6: change rt
// k2: change % rt
// o: open
// g: day's low
// h: day's high
// j: 52w low
// k: 52w high
// v: volume
// a2: avg volume
// r2: p/e rt
// r: p/e
// d: dividend/share
// y: wield
// j3: market cap rt
// j1: market cap
const quotes_url = `http://download.finance.yahoo.com/d/quotes.csv?s=%s&f=,l1c6k2oghjkva2r2rdyj3j1`
12 years ago
type Stock struct {
12 years ago
Ticker string
LastTrade string
Change string
ChangePct string
Open string
Low string
High string
Low52 string
High52 string
Volume string
AvgVolume string
PeRatio string
PeRatioX string
Dividend string
Yield string
MarketCap string
MarketCapX string
Advancing bool
}
12 years ago
type Quotes struct {
market *Market
profile *Profile
stocks []Stock
errors string
12 years ago
}
//-----------------------------------------------------------------------------
func (self *Quotes) Initialize(market *Market, profile *Profile) *Quotes {
self.market = market
self.profile = profile
self.errors = ``
12 years ago
return self
}
// Fetch the latest stock quotes and parse raw fetched data into array of
// []Stock structs.
//-----------------------------------------------------------------------------
func (self *Quotes) Fetch() (this *Quotes) {
this = self // <-- This ensures we return correct self after recover() from panic() attack.
if self.is_ready() {
defer func() {
if err := recover(); err != nil {
self.errors = fmt.Sprintf("\n\n\n\nError fetching stock quotes...\n%s", err)
}
}()
url := fmt.Sprintf(quotes_url, self.profile.ListOfTickers())
response, err := http.Get(url)
if err != nil {
panic(err)
}
defer response.Body.Close()
body, err := ioutil.ReadAll(response.Body)
if err != nil {
panic(err)
}
self.parse(sanitize(body))
}
return self
}
//-----------------------------------------------------------------------------
func (self *Quotes) Ok() (bool, string) {
return self.errors == ``, self.errors
}
//-----------------------------------------------------------------------------
func (self *Quotes) AddTickers(tickers []string) (added int, err error) {
if added, err = self.profile.AddTickers(tickers); err == nil && added > 0 {
self.stocks = nil // Force fetch.
}
return
}
//-----------------------------------------------------------------------------
func (self *Quotes) RemoveTickers(tickers []string) (removed int, err error) {
if removed, err = self.profile.RemoveTickers(tickers); err == nil && removed > 0 {
self.stocks = nil // Force fetch.
}
return
}
// "Private" methods.
// Return true if we haven't fetched the quotes yet *or* the stock market is
// still open and we might want to grab the latest quotes. In both cases we
// make sure the list of requested tickers is not empty.
//-----------------------------------------------------------------------------
func (self *Quotes) is_ready() bool {
return (self.stocks == nil || !self.market.IsClosed) && len(self.profile.Tickers) > 0
}
12 years ago
//-----------------------------------------------------------------------------
func (self *Quotes) parse(body []byte) *Quotes {
lines := bytes.Split(body, []byte{'\n'})
12 years ago
self.stocks = make([]Stock, len(lines))
//
// Get the total number of fields in the Stock struct. Skip the last
// Advanicing field which is not fetched.
//
number_of_fields := reflect.ValueOf(self.stocks[0]).NumField() - 1
//
// Split each line into columns, then iterate over the Stock struct
// fields to assign column values.
//
for i, line := range lines {
12 years ago
columns := bytes.Split(bytes.TrimSpace(line), []byte{','})
for j := 0; j < number_of_fields; j++ {
// ex. self.stocks[i].Ticker = string(columns[0])
reflect.ValueOf(&self.stocks[i]).Elem().Field(j).SetString(string(columns[j]))
}
//
// Try realtime value and revert to the last known if the
// realtime is not available.
//
if self.stocks[i].PeRatio == `N/A` && self.stocks[i].PeRatioX != `N/A` {
self.stocks[i].PeRatio = self.stocks[i].PeRatioX
}
if self.stocks[i].MarketCap == `N/A` && self.stocks[i].MarketCapX != `N/A` {
self.stocks[i].MarketCap = self.stocks[i].MarketCapX
}
//
// Stock is advancing if the change is not negative (i.e. $0.00
// is also "advancing").
//
self.stocks[i].Advancing = (self.stocks[i].Change[0:1] != `-`)
}
12 years ago
return self
}
// Utility methods.
12 years ago
//-----------------------------------------------------------------------------
func sanitize(body []byte) []byte {
12 years ago
return bytes.Replace(bytes.TrimSpace(body), []byte{'"'}, []byte{}, -1)
}