First golint pass for yahoo_quotes.go

master
Michael Dvorkin 11 years ago
parent b32609ac61
commit d745d1b12d
  1. 145
      yahoo_quotes.go

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

Loading…
Cancel
Save