diff --git a/cnn_market.go b/cnn_market.go new file mode 100644 index 0000000..126e800 --- /dev/null +++ b/cnn_market.go @@ -0,0 +1,183 @@ +// 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://money.cnn.com/data/markets/` + +// 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. + Tokyo map[string]string + HongKong map[string]string + London map[string]string + Frankfurt map[string]string + Yield map[string]string + Oil map[string]string + Yen map[string]string + Euro map[string]string + Gold map[string]string + regex *regexp.Regexp // Regex to parse market data from HTML. + errors string // Error(s), if any. +} + +// Returns new initialized Market struct. +func NewMarket() *Market { + market := &Market{}; + market.IsClosed = false + market.Dow = make(map[string]string) + market.Nasdaq = make(map[string]string) + market.Sp500 = make(map[string]string) + + market.Tokyo = make(map[string]string) + market.HongKong = make(map[string]string) + market.London = make(map[string]string) + market.Frankfurt = make(map[string]string) + + market.Yield = make(map[string]string) + market.Oil = make(map[string]string) + market.Yen = make(map[string]string) + market.Euro = make(map[string]string) + market.Gold = make(map[string]string) + + market.errors = `` + + const any = `\s*(?:.+?)` + const price = `>([\d\.,]+)` + const percent = `>([\+\-]?[\d\.,]+%?)<` + + rules := []string{ + `>Dow<`, any, percent, any, price, any, percent, any, + `>Nasdaq<`, any, percent, any, price, any, percent, any, + `">S&P<`, any, percent, any, price, any, percent, any, + `>Nikkei 225<`, any, percent, any, price, any, percent, any, + `>Hang Seng<`, any, percent, any, price, any, percent, any, + `>FTSE 100<`, any, percent, any, price, any, percent, any, + `>DAX<`, any, percent, any, price, any, percent, any, + `>10-year yield<`, any, price, any, percent, any, + `>Oil<`, any, price, any, percent, any, + `>Yen<`, any, price, any, percent, any, + `>Euro<`, any, price, any, percent, any, + `>Gold<`, any, price, any, percent, 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.isMarketOpen(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) isMarketOpen(body []byte) []byte { + // TBD -- CNN page doesn't seem to have market open/close indicator. + return body +} + +//----------------------------------------------------------------------------- +func (market *Market) trim(body []byte) []byte { + start := bytes.Index(body, []byte(`Markets Overview`)) + finish := bytes.LastIndex(body, []byte(`Gainers`)) + snippet := bytes.Replace(body[start:finish], []byte{'\n'}, []byte{}, -1) + snippet = bytes.Replace(snippet, []byte(`&`), []byte{'&'}, -1) + + return snippet +} + +//----------------------------------------------------------------------------- +func (market *Market) extract(snippet []byte) *Market { + matches := market.regex.FindStringSubmatch(string(snippet)) + + if len(matches) < 31 { + panic(`Unable to parse ` + marketURL) + } + + market.Dow[`change`] = matches[1] + market.Dow[`latest`] = matches[2] + market.Dow[`percent`] = matches[3] + + market.Nasdaq[`change`] = matches[4] + market.Nasdaq[`latest`] = matches[5] + market.Nasdaq[`percent`] = matches[6] + + market.Sp500[`change`] = matches[7] + market.Sp500[`latest`] = matches[8] + market.Sp500[`percent`] = matches[9] + + market.Tokyo[`change`] = matches[10] + market.Tokyo[`latest`] = matches[11] + market.Tokyo[`percent`] = matches[12] + + market.HongKong[`change`] = matches[13] + market.HongKong[`latest`] = matches[14] + market.HongKong[`percent`] = matches[15] + + market.London[`change`] = matches[16] + market.London[`latest`] = matches[17] + market.London[`percent`] = matches[18] + + market.Frankfurt[`change`] = matches[19] + market.Frankfurt[`latest`] = matches[20] + market.Frankfurt[`percent`] = matches[21] + + market.Yield[`name`] = `10-year Yield` + market.Yield[`latest`] = matches[22] + market.Yield[`change`] = matches[23] + + market.Oil[`latest`] = matches[24] + market.Oil[`change`] = matches[25] + + market.Yen[`latest`] = matches[26] + market.Yen[`change`] = matches[27] + + market.Euro[`latest`] = matches[28] + market.Euro[`change`] = matches[29] + + market.Gold[`latest`] = matches[30] + market.Gold[`change`] = matches[31] + + return market +} diff --git a/yahoo_market.go b/yahoo_market.go deleted file mode 100644 index acc5caa..0000000 --- a/yahoo_market.go +++ /dev/null @@ -1,240 +0,0 @@ -// 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` - -// 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. - London map[string]string - Frankfurt map[string]string - Paris map[string]string - Tokyo map[string]string - HongKong map[string]string - Shanghai map[string]string - 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.Sp500 = make(map[string]string) - market.Dow = make(map[string]string) - market.Nasdaq = make(map[string]string) - market.London = make(map[string]string) - market.Frankfurt = make(map[string]string) - market.Paris = make(map[string]string) - market.Tokyo = make(map[string]string) - market.HongKong = make(map[string]string) - market.Shanghai = make(map[string]string) - market.errors = `` - - const any = `\s*<.+?>` - const color = `<.+?price-change-([a-z]+)'>[\+\-]?` - const price = `([\d\.,]+)` - const percent = `\(([\d\.,%]+)\)` - - rules := []string{ - `S&P 500`, any, price, color, price, any, percent, any, - `Dow`, any, price, color, price, any, percent, any, - `Nasdaq`, any, price, color, price, any, percent, any, - `FTSE`, any, price, color, price, any, percent, any, - `DAX`, any, price, color, price, any, percent, any, - `CAC 40`, any, price, color, price, any, percent, any, - `NIKKEI 225`, any, price, color, price, any, percent, any, - `Hang Seng`, any, price, color, price, any, percent, any, - `SSE Comp`, any, price, color, price, any, percent, 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(`>S&P 500<`)) - finish := bytes.LastIndex(body, []byte(`id="mediafinancesuperherogs"`)) - snippet := bytes.Replace(body[start:finish], []byte{'\n'}, []byte{}, -1) - snippet = bytes.Replace(snippet, []byte(`&`), []byte{'&'}, -1) - - return snippet -} - -//----------------------------------------------------------------------------- -func (market *Market) extract(snippet []byte) *Market { - matches := market.regex.FindStringSubmatch(string(snippet)) - // fmt.Printf("\n\n\n%q\n\n\n", matches) - if len(matches) < 37 { - panic(`Unable to parse ` + marketURL) - } - - market.Sp500[`name`] = `S&P 500` - market.Sp500[`latest`] = matches[1] - market.Sp500[`change`] = matches[3] - market.Sp500[`percent`] = matches[4] - if matches[2] == `green` { - market.Sp500[`change`] = `+` + market.Sp500[`change`] - market.Sp500[`percent`] = `+` + market.Sp500[`percent`] - } else if matches[2] == `red` { - market.Sp500[`change`] = `-` + market.Sp500[`change`] - market.Sp500[`percent`] = `-` + market.Sp500[`percent`] - } - - market.Dow[`name`] = `Dow` - market.Dow[`latest`] = matches[5] - market.Dow[`change`] = matches[7] - market.Dow[`percent`] = matches[8] - if matches[6] == `green` { - market.Dow[`change`] = `+` + market.Dow[`change`] - market.Dow[`percent`] = `+` + market.Dow[`percent`] - } else if matches[6] == `red` { - market.Dow[`change`] = `-` + market.Dow[`change`] - market.Dow[`percent`] = `-` + market.Dow[`percent`] - } - - market.Nasdaq[`name`] = `NASDAQ` - market.Nasdaq[`latest`] = matches[9] - market.Nasdaq[`change`] = matches[11] - market.Nasdaq[`percent`] = matches[12] - if matches[10] == `green` { - market.Nasdaq[`change`] = `+` + market.Nasdaq[`change`] - market.Nasdaq[`percent`] = `+` + market.Nasdaq[`percent`] - } else if matches[10] == `red` { - market.Nasdaq[`change`] = `-` + market.Nasdaq[`change`] - market.Nasdaq[`percent`] = `-` + market.Nasdaq[`percent`] - } - - market.London[`name`] = `London` - market.London[`latest`] = matches[13] - market.London[`change`] = matches[15] - market.London[`percent`] = matches[16] - if matches[14] == `green` { - market.London[`change`] = `+` + market.London[`change`] - market.London[`percent`] = `+` + market.London[`percent`] - } else if matches[14] == `red` { - market.London[`change`] = `-` + market.London[`change`] - market.London[`percent`] = `-` + market.London[`percent`] - } - - market.Frankfurt[`name`] = `Frankfurt` - market.Frankfurt[`latest`] = matches[17] - market.Frankfurt[`change`] = matches[19] - market.Frankfurt[`percent`] = matches[20] - if matches[18] == `green` { - market.Frankfurt[`change`] = `+` + market.Frankfurt[`change`] - market.Frankfurt[`percent`] = `+` + market.Frankfurt[`percent`] - } else if matches[18] == `red` { - market.Frankfurt[`change`] = `-` + market.Frankfurt[`change`] - market.Frankfurt[`percent`] = `-` + market.Frankfurt[`percent`] - } - - market.Paris[`name`] = `Paris` - market.Paris[`latest`] = matches[21] - market.Paris[`change`] = matches[23] - market.Paris[`percent`] = matches[24] - if matches[22] == `green` { - market.Paris[`change`] = `+` + market.Paris[`change`] - market.Paris[`percent`] = `+` + market.Paris[`percent`] - } else if matches[22] == `red` { - market.Paris[`change`] = `-` + market.Paris[`change`] - market.Paris[`percent`] = `-` + market.Paris[`percent`] - } - - market.Tokyo[`name`] = `Tokyo` - market.Tokyo[`latest`] = matches[25] - market.Tokyo[`change`] = matches[27] - market.Tokyo[`percent`] = matches[28] - if matches[26] == `green` { - market.Tokyo[`change`] = `+` + market.Tokyo[`change`] - market.Tokyo[`percent`] = `+` + market.Tokyo[`percent`] - } else if matches[26] == `red` { - market.Tokyo[`change`] = `-` + market.Tokyo[`change`] - market.Tokyo[`percent`] = `-` + market.Tokyo[`percent`] - } - - market.HongKong[`name`] = `Hong Kong` - market.HongKong[`latest`] = matches[29] - market.HongKong[`change`] = matches[31] - market.HongKong[`percent`] = matches[32] - if matches[30] == `green` { - market.HongKong[`change`] = `+` + market.HongKong[`change`] - market.HongKong[`percent`] = `+` + market.HongKong[`percent`] - } else if matches[30] == `red` { - market.HongKong[`change`] = `-` + market.HongKong[`change`] - market.HongKong[`percent`] = `-` + market.HongKong[`percent`] - } - - market.Shanghai[`name`] = `Shanghai` - market.Shanghai[`latest`] = matches[33] - market.Shanghai[`change`] = matches[35] - market.Shanghai[`percent`] = matches[36] - if matches[34] == `green` { - market.Shanghai[`change`] = `+` + market.Shanghai[`change`] - market.Shanghai[`percent`] = `+` + market.Shanghai[`percent`] - } else if matches[34] == `red` { - market.Shanghai[`change`] = `-` + market.Shanghai[`change`] - market.Shanghai[`percent`] = `-` + market.Shanghai[`percent`] - } - - return market -}