// Copyright (c) 2013 by Michael Dvorkin. All Rights Reserved. // Use of this source code is governed by a MIT-style license that can // be found in the LICENSE file. package mop import ( `bytes` `fmt` `io/ioutil` `net/http` `regexp` `strings` ) const marketURL = `http://finance.yahoo.com/marketupdate/overview` // Market stores current market information displayed in the top three lines of // the screen. The market data is fetched and parsed from the HTML page above. type Market struct { IsClosed bool // True when U.S. markets are closed. Dow map[string]string // Hash of Dow Jones indicators. Nasdaq map[string]string // Hash of NASDAQ indicators. Sp500 map[string]string // Hash of S&P 500 indicators. Advances map[string]string // Number of advanced stocks on NYSE and NASDAQ. Declines map[string]string // Ditto for declines. Unchanged map[string]string // Ditto for unchanged. Highs map[string]string // Number of new highs on NYSE and NASDAQ. Lows map[string]string // Ditto for new lows. regex *regexp.Regexp // Regex to parse market data from HTML. errors string // Error(s), if any. } // Initialize creates empty hashes and builds regular expression used to parse // market data from HTML page. func (market *Market) Initialize() *Market { market.IsClosed = false market.Dow = make(map[string]string) market.Nasdaq = make(map[string]string) market.Sp500 = make(map[string]string) market.Advances = make(map[string]string) market.Declines = make(map[string]string) market.Unchanged = make(map[string]string) market.Highs = make(map[string]string) market.Lows = make(map[string]string) market.errors = `` const any = `\s*<.+?>` const some = `<.+?` const space = `\s*` const color = `#([08c]{6});">\s*` const price = `([\d\.,]+)` const percent = `\(([\d\.,%]+)\)` rules := []string{ `(Dow)`, any, price, some, color, price, some, percent, any, `(Nasdaq)`, any, price, some, color, price, some, percent, any, `(S&P 500)`, any, price, some, color, price, some, percent, any, `(Advances)`, any, price, space, percent, any, price, space, percent, any, `(Declines)`, any, price, space, percent, any, price, space, percent, any, `(Unchanged)`, any, price, space, percent, any, price, space, percent, any, `(New Hi's)`, any, price, any, price, any, `(New Lo's)`, any, price, any, price, any, } market.regex = regexp.MustCompile(strings.Join(rules, ``)) return market } // Fetch downloads HTML page from the 'marketURL', parses it, and stores resulting data // in internal hashes. If download or data parsing fails Fetch populates 'market.errors'. func (market *Market) Fetch() (self *Market) { self = market // <-- This ensures we return correct market after recover() from panic(). defer func() { if err := recover(); err != nil { market.errors = fmt.Sprintf("Error fetching market data...\n%s", err) } }() response, err := http.Get(marketURL) if err != nil { panic(err) } defer response.Body.Close() body, err := ioutil.ReadAll(response.Body) if err != nil { panic(err) } body = market.checkIfMarketIsOpen(body) return market.extract(market.trim(body)) } // Ok returns two values: 1) boolean indicating whether the error has occured, // and 2) the error text itself. func (market *Market) Ok() (bool, string) { return market.errors == ``, market.errors } //----------------------------------------------------------------------------- func (market *Market) checkIfMarketIsOpen(body []byte) []byte { start := bytes.Index(body, []byte(`id="yfs_market_time"`)) finish := start + bytes.Index(body[start:], []byte(``)) snippet := body[start:finish] market.IsClosed = bytes.Contains(snippet, []byte(`closed`)) || bytes.Contains(snippet, []byte(`open in`)) return body[finish:] } //----------------------------------------------------------------------------- func (market *Market) trim(body []byte) []byte { start := bytes.Index(body, []byte(`