diff --git a/yahoo_quotes.go b/yahoo_quotes.go index 860950d..626a8d7 100644 --- a/yahoo_quotes.go +++ b/yahoo_quotes.go @@ -6,13 +6,16 @@ package mop import ( `bytes` + `encoding/json` `fmt` `io/ioutil` `net/http` `reflect` + `strconv` `strings` ) +// const quotesURL = `http://download.finance.yahoo.com/d/quotes.csv?s=%s&f=sl1c1p2oghjkva2r2rdyj3j1` const quotesURLv7 = `https://query1.finance.yahoo.com/v7/finance/quote?symbols=%s` const quotesURLv7QueryParts = `&range=1d&interval=5m&indicators=close&includeTimestamps=false&includePrePost=false&corsDomain=finance.yahoo.com&.tsrc=finance` @@ -21,23 +24,23 @@ const noDataIndicator = `N/A` // Stock stores quote information for the particular stock ticker. The data // for all the fields except 'Advancing' is fetched using Yahoo market API. type Stock struct { - Ticker string // Stock ticker. - LastTrade string // l1: last trade. - Change string // c6: change real time. - ChangePct string // k2: percent change real time. - Open string // o: market open price. - Low string // g: day's low. - High string // h: day's high. - Low52 string // j: 52-weeks low. - High52 string // k: 52-weeks high. - Volume string // v: volume. - AvgVolume string // a2: average volume. - PeRatio string // r2: P/E ration real time. - PeRatioX string // r: P/E ration (fallback when real time is N/A). - Dividend string // d: dividend. - Yield string // y: dividend yield. - MarketCap string // j3: market cap real time. - MarketCapX string // j1: market cap (fallback when real time is N/A). + Ticker string `json:"symbol"` // Stock ticker. + LastTrade string `json:"regularMarketPrice"` // l1: last trade. + Change string `json:"regularMarketChange"` // c6: change real time. + ChangePct string `json:"regularMarketChangePercent"` // k2: percent change real time. + Open string `json:"regularMarketOpen"` // o: market open price. + Low string `json:"regularMarketDayLow"` // g: day's low. + High string `json:"regularMarketDayHigh"` // h: day's high. + Low52 string `json:"fiftyTwoWeekLow"` // j: 52-weeks low. + High52 string `json:"fiftyTwoWeekHigh"` // k: 52-weeks high. + Volume string `json:"regularMarketVolume"` // v: volume. + AvgVolume string `json:"averageDailyVolume10Day"` // a2: average volume. + PeRatio string `json:"trailingPE"` // r2: P/E ration real time. + PeRatioX string `json:"trailingPE"` // r: P/E ration (fallback when real time is N/A). + Dividend string `json:"trailingAnnualDividendRate"` // d: dividend. + Yield string `json:"trailingAnnualDividendYield"` // y: dividend yield. + MarketCap string `json:"marketCap"` // j3: market cap real time. + MarketCapX string `json:"marketCap"` // j1: market cap (fallback when real time is N/A). Advancing bool // True when change is >= $0. } @@ -70,8 +73,8 @@ func (quotes *Quotes) Fetch() (self *Quotes) { } }() - url := fmt.Sprintf(quotesURL, strings.Join(quotes.profile.Tickers, `+`)) - response, err := http.Get(url) + url := fmt.Sprintf(quotesURLv7, strings.Join(quotes.profile.Tickers, `,`)) + response, err := http.Get(url + quotesURLv7QueryParts) if err != nil { panic(err) } @@ -82,7 +85,7 @@ func (quotes *Quotes) Fetch() (self *Quotes) { panic(err) } - quotes.parse(sanitize(body)) + quotes.parse2(body) } return quotes @@ -121,6 +124,70 @@ func (quotes *Quotes) isReady() bool { return (quotes.stocks == nil || !quotes.market.IsClosed) && len(quotes.profile.Tickers) > 0 } +// this will parse the json objects +func (quotes *Quotes) parse2(body []byte) (*Quotes, error) { + // response -> quoteResponse -> result|error (array) -> map[string]interface{} + // Stocks has non-int things + // d := map[string]map[string][]Stock{} + // some of these are numbers vs strings + // d := map[string]map[string][]map[string]string{} + d := map[string]map[string][]map[string]interface{}{} + err := json.Unmarshal(body, &d) + if err != nil { + return nil, err + } + results := d["quoteResponse"]["result"] + + quotes.stocks = make([]Stock, len(results)) + for i, raw := range results { + result := map[string]string{} + for k, v := range raw { + switch v.(type) { + case string: + result[k] = v.(string) + case float64: + result[k] = float2Str(v.(float64)) + default: + result[k] = fmt.Sprintf("%v", v) + } + + } + quotes.stocks[i].Ticker = result["symbol"] + quotes.stocks[i].LastTrade = result["regularMarketPrice"] + quotes.stocks[i].Change = result["regularMarketChange"] + quotes.stocks[i].ChangePct = result["regularMarketChangePercent"] + quotes.stocks[i].Open = result["regularMarketOpen"] + quotes.stocks[i].Low = result["regularMarketDayLow"] + quotes.stocks[i].High = result["regularMarketDayHigh"] + quotes.stocks[i].Low52 = result["fiftyTwoWeekLow"] + quotes.stocks[i].High52 = result["fiftyTwoWeekHigh"] + quotes.stocks[i].Volume = result["regularMarketVolume"] + quotes.stocks[i].AvgVolume = result["averageDailyVolume10Day"] + quotes.stocks[i].PeRatio = result["trailingPE"] + // TODO calculate rt + quotes.stocks[i].PeRatioX = result["trailingPE"] + quotes.stocks[i].Dividend = result["trailingAnnualDividendRate"] + quotes.stocks[i].Yield = result["trailingAnnualDividendYield"] + quotes.stocks[i].MarketCap = result["marketCap"] + // TODO calculate rt? + quotes.stocks[i].MarketCapX = result["marketCap"] + + /* + fmt.Println(i) + fmt.Println("-------------------") + for k, v := range result { + fmt.Println(k, v) + } + fmt.Println("-------------------") + */ + adv, err := strconv.ParseFloat(quotes.stocks[i].Change, 64) + if err == nil { + quotes.stocks[i].Advancing = adv >= 0.0 + } + } + return quotes, nil +} + // Use reflection to parse and assign the quotes data fetched using the Yahoo // market API. func (quotes *Quotes) parse(body []byte) *Quotes { @@ -165,3 +232,26 @@ func (quotes *Quotes) parse(body []byte) *Quotes { func sanitize(body []byte) []byte { return bytes.Replace(bytes.TrimSpace(body), []byte{'"'}, []byte{}, -1) } + + func float2Str(v float64) string { + unit := "" + switch { + case v > 1.0e12: + v = v / 1.0e12 + unit = "T" + case v > 1.0e9: + v = v / 1.0e9 + unit = "B" + case v > 1.0e6: + v = v / 1.0e6 + unit = "M" + case v > 1.0e5: + v = v / 1.0e3 + unit = "K" + default: + unit = "" + } + // parse + return fmt.Sprintf("%0.3f%s", v, unit) +} +