diff --git a/dj.go b/dj.go new file mode 100644 index 0000000..ad47ef0 --- /dev/null +++ b/dj.go @@ -0,0 +1,278 @@ +// Copyright (c) 2013 by Michael Dvorkin. All Rights Reserved. +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +package main +import ( + "fmt" + "bytes" + "regexp" + "strings" + "text/template" +) +type Market struct { + Dow map[string]string + Nasdaq map[string]string + Sp500 map[string]string + Advances map[string]string + Declines map[string]string + Unchanged map[string]string + Highs map[string]string + Lows map[string]string +} +//----------------------------------------------------------------------------- +func main() { + html := ` +... + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SymbolLastChange
Dow +15,300.34Up 75.65 (0.50%)
Nasdaq3,504.26Up 19.43 (0.56%)
S&P 5001,652.32Up 11.86 (0.72%)
10-Yr Bond2.63% + Down 0.02
NYSE Volume3,490,723,250.00
Nasdaq Volume...1,594,900,625.00
+
+Indices: US - World | Most Actives +
+ + +

Advances & Declines

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 NYSENASDAQ
Advances2,992 + (72%) + 1,445 + (57%) +
Declines1,040 + (25%) + 950 + (38%) +
Unchanged113 + (3%) + 128 + (5%) +
Up Vol*2,582 + (74%) + 950 + (60%) +
Down Vol*863 + (25%) + 625 + (39%) +
Unch. Vol*46 + (1%) + 20 + (1%) +
New Hi's350314
New Lo's11719
+... + + + + + + +` + start := strings.Index(html, `
NYSELASTCHANGE
` + const some = `<.+?` + const space = `\s*` + const arrow = `"(Up|Down)">\s*` + const price = `([\d\.,]+)` + const percent = `\(([\d\.,%]+)\)` + + regex := []string{ + "(Dow)", any, price, some, arrow, any, price, some, percent, any, + "(Nasdaq)", any, price, some, arrow, any, price, some, percent, any, + "(S&P 500)", any, price, some, arrow, any, 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, + } + + re := regexp.MustCompile(strings.Join(regex, "")) + matches := re.FindAllStringSubmatch(html, -1) + + if len(matches) > 0 { + fmt.Printf("%d matches\n", len(matches[0])) + for i, str := range matches[0][1:] { + fmt.Printf("%d) [%s]\n", i, str) + } + } else { + println("No matches") + } + + m := Market{ + Dow: make(map[string]string), + Nasdaq: make(map[string]string), + Sp500: make(map[string]string), + Advances: make(map[string]string), + Declines: make(map[string]string), + Unchanged: make(map[string]string), + Highs: make(map[string]string), + Lows: make(map[string]string), + } + m.Dow[`name`] = matches[0][1] + m.Dow[`latest`] = matches[0][2] + m.Dow[`change`] = matches[0][4] + if matches[0][3] == "Up" { + m.Dow[`change`] = "+" + matches[0][4] + m.Dow[`percent`] = "+" + matches[0][5] + } else { + m.Dow[`change`] = "-" + matches[0][4] + m.Dow[`percent`] = "-" + matches[0][5] + } + + m.Nasdaq[`name`] = matches[0][6] + m.Nasdaq[`latest`] = matches[0][7] + if matches[0][8] == "Up" { + m.Nasdaq[`change`] = "+" + matches[0][9] + m.Nasdaq[`percent`] = "+" + matches[0][10] + } else { + m.Nasdaq[`change`] = "-" + matches[0][9] + m.Nasdaq[`percent`] = "-" + matches[0][10] + } + + m.Sp500[`name`] = matches[0][11] + m.Sp500[`latest`] = matches[0][12] + if matches[0][13] == "Up" { + m.Sp500[`change`] = "+" + matches[0][14] + m.Sp500[`percent`] = "+" + matches[0][15] + } else { + m.Sp500[`change`] = "-" + matches[0][14] + m.Sp500[`percent`] = "-" + matches[0][15] + } + + m.Advances[`name`] = matches[0][16] + m.Advances[`nyse`] = matches[0][17] + m.Advances[`nysep`] = matches[0][18] + m.Advances[`nasdaq`] = matches[0][19] + m.Advances[`nasdaqp`] = matches[0][20] + + m.Declines[`name`] = matches[0][21] + m.Declines[`nyse`] = matches[0][22] + m.Declines[`nysep`] = matches[0][23] + m.Declines[`nasdaq`] = matches[0][24] + m.Declines[`nasdaqp`] = matches[0][25] + + m.Unchanged[`name`] = matches[0][26] + m.Unchanged[`nyse`] = matches[0][27] + m.Unchanged[`nysep`] = matches[0][28] + m.Unchanged[`nasdaq`] = matches[0][29] + m.Unchanged[`nasdaqp`] = matches[0][30] + + m.Highs[`name`] = matches[0][31] + m.Highs[`nyse`] = matches[0][32] + m.Highs[`nasdaq`] = matches[0][33] + m.Lows[`name`] = matches[0][34] + m.Lows[`nyse`] = matches[0][35] + m.Lows[`nasdaq`] = matches[0][36] + fmt.Printf("%q\n", m) + println(Format(m)) +} + +//----------------------------------------------------------------------------- +func Format(m Market) string { + markup := `{{.Dow.name}}: {{.Dow.change}} ({{.Dow.percent}}) at {{.Dow.latest}}, ` + markup += `{{.Sp500.name}}: {{.Sp500.change}} ({{.Sp500.percent}}) at {{.Sp500.latest}}, ` + markup += `{{.Nasdaq.name}}: {{.Nasdaq.change}} ({{.Nasdaq.percent}}) at {{.Nasdaq.latest}}` + markup += "\n" + markup += `{{.Advances.name}}: {{.Advances.nyse}} ({{.Advances.nysep}}) on NYSE and {{.Advances.nasdaq}} ({{.Advances.nasdaqp}}) on Nasdaq. ` + markup += `{{.Declines.name}}: {{.Declines.nyse}} ({{.Declines.nysep}}) on NYSE and {{.Declines.nasdaq}} ({{.Declines.nasdaqp}}) on Nasdaq` + markup += "\n" + markup += `New highs: {{.Highs.nyse}} on NYSE and {{.Highs.nasdaq}} on Nasdaq. ` + markup += `New lows: {{.Lows.nyse}} on NYSE and {{.Lows.nasdaq}} on Nasdaq.` + template, err := template.New("screen").Parse(markup) + if err != nil { + panic(err) + } + + buffer := new(bytes.Buffer) + err = template.Execute(buffer, m) + if err != nil { + panic(err) + } + + return buffer.String() +} diff --git a/lib/format.go b/lib/format.go index c169df8..a3da2ba 100644 --- a/lib/format.go +++ b/lib/format.go @@ -12,7 +12,32 @@ import ( ) //----------------------------------------------------------------------------- -func Format(quotes Quotes) string { +func FormatMarket(m Market) string { + markup := `{{.Dow.name}}: {{.Dow.change}} ({{.Dow.percent}}) at {{.Dow.latest}}, ` + markup += `{{.Sp500.name}}: {{.Sp500.change}} ({{.Sp500.percent}}) at {{.Sp500.latest}}, ` + markup += `{{.Nasdaq.name}}: {{.Nasdaq.change}} ({{.Nasdaq.percent}}) at {{.Nasdaq.latest}}` + markup += "\n" + markup += `{{.Advances.name}}: {{.Advances.nyse}} ({{.Advances.nysep}}) on NYSE and {{.Advances.nasdaq}} ({{.Advances.nasdaqp}}) on Nasdaq. ` + markup += `{{.Declines.name}}: {{.Declines.nyse}} ({{.Declines.nysep}}) on NYSE and {{.Declines.nasdaq}} ({{.Declines.nasdaqp}}) on Nasdaq` + markup += "\n" + markup += `New highs: {{.Highs.nyse}} on NYSE and {{.Highs.nasdaq}} on Nasdaq. ` + markup += `New lows: {{.Lows.nyse}} on NYSE and {{.Lows.nasdaq}} on Nasdaq.` + template, err := template.New("market").Parse(markup) + if err != nil { + panic(err) + } + + buffer := new(bytes.Buffer) + err = template.Execute(buffer, m) + if err != nil { + panic(err) + } + + return buffer.String() +} + +//----------------------------------------------------------------------------- +func FormatQuotes(quotes Quotes) string { vars := struct { Now string Header string @@ -23,14 +48,15 @@ func Format(quotes Quotes) string { prettify(quotes), } - markup := - `Hello{{.Now}} + markup := `{{.Now}} + + {{.Header}} {{range .Stocks}}{{.Color}}{{.Ticker}} {{.LastTrade}} {{.Change}} {{.ChangePercent}} {{.Open}} {{.Low}} {{.High}} {{.Low52}} {{.High52}} {{.Volume}} {{.AvgVolume}} {{.PeRatio}} {{.Dividend}} {{.Yield}} {{.MarketCap}} {{end}}...` - template, err := template.New("screen").Parse(markup) + template, err := template.New("quotes").Parse(markup) if err != nil { panic(err) } diff --git a/lib/screen.go b/lib/screen.go index 2ec9049..4c679de 100644 --- a/lib/screen.go +++ b/lib/screen.go @@ -29,15 +29,27 @@ var tags = map[string]termbox.Attribute{ } //----------------------------------------------------------------------------- -func Draw(stocks string) { - quotes := Get(stocks) +func DrawMarket() { + market := GetMarket() // for _, m := range message { // fmt.Printf("%s, %s, %s\n", m.Ticker, m.LastTrade, m.Change) // } // fmt.Printf("%s\n", Format(message)) - drawScreen(Format(quotes)) + drawScreen(FormatMarket(market)) +} + +//----------------------------------------------------------------------------- +func DrawQuotes(stocks string) { + quotes := GetQuotes(stocks) + + // for _, m := range message { + // fmt.Printf("%s, %s, %s\n", m.Ticker, m.LastTrade, m.Change) + // } + // fmt.Printf("%s\n", Format(message)) + + drawScreen(FormatQuotes(quotes)) } //----------------------------------------------------------------------------- @@ -120,7 +132,6 @@ func drawLine(x int, y int, str string) { //----------------------------------------------------------------------------- func drawScreen(str string) { - termbox.Clear(termbox.ColorDefault, termbox.ColorDefault) for row, line := range strings.Split(str, "\n") { drawLine(0, row, line) } diff --git a/lib/yahoo_market.go b/lib/yahoo_market.go new file mode 100644 index 0000000..186c0f6 --- /dev/null +++ b/lib/yahoo_market.go @@ -0,0 +1,150 @@ +// Copyright (c) 2013 by Michael Dvorkin. All Rights Reserved. +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +package mop + +import ( + //"fmt" + "bytes" + "regexp" + "strings" + "net/http" + "io/ioutil" +) + +type Market struct { + Dow map[string]string + Nasdaq map[string]string + Sp500 map[string]string + Advances map[string]string + Declines map[string]string + Unchanged map[string]string + Highs map[string]string + Lows map[string]string +} + +const yahoo_market_url = `http://finance.yahoo.com/marketupdate/overview` + +func GetMarket() Market { + response, err := http.Get(yahoo_market_url) + if err != nil { + panic(err) + } + + // Fetch response and get its body. + defer response.Body.Close() + body, err := ioutil.ReadAll(response.Body) + if err != nil { + panic(err) + } + + return extract(trim(body)) +} + +func trim(body []byte) []byte { + start := bytes.Index(body, []byte("
` + const some = `<.+?` + const space = `\s*` + const arrow = `"(Up|Down)">\s*` + const price = `([\d\.,]+)` + const percent = `\(([\d\.,%]+)\)` + + regex := []string{ + "(Dow)", any, price, some, arrow, any, price, some, percent, any, + "(Nasdaq)", any, price, some, arrow, any, price, some, percent, any, + "(S&P 500)", any, price, some, arrow, any, 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, + } + + re := regexp.MustCompile(strings.Join(regex, "")) + matches := re.FindAllStringSubmatch(string(snippet), -1) + + // if len(matches) > 0 { + // fmt.Printf("%d matches\n", len(matches[0])) + // for i, str := range matches[0][1:] { + // fmt.Printf("%d) [%s]\n", i, str) + // } + // } else { + // println("No matches") + // } + + m := Market{ + Dow: make(map[string]string), + Nasdaq: make(map[string]string), + Sp500: make(map[string]string), + Advances: make(map[string]string), + Declines: make(map[string]string), + Unchanged: make(map[string]string), + Highs: make(map[string]string), + Lows: make(map[string]string), + } + m.Dow[`name`] = matches[0][1] + m.Dow[`latest`] = matches[0][2] + m.Dow[`change`] = matches[0][4] + if matches[0][3] == "Up" { + m.Dow[`change`] = "+" + matches[0][4] + m.Dow[`percent`] = "+" + matches[0][5] + } else { + m.Dow[`change`] = "-" + matches[0][4] + m.Dow[`percent`] = "-" + matches[0][5] + } + + m.Nasdaq[`name`] = matches[0][6] + m.Nasdaq[`latest`] = matches[0][7] + if matches[0][8] == "Up" { + m.Nasdaq[`change`] = "+" + matches[0][9] + m.Nasdaq[`percent`] = "+" + matches[0][10] + } else { + m.Nasdaq[`change`] = "-" + matches[0][9] + m.Nasdaq[`percent`] = "-" + matches[0][10] + } + + m.Sp500[`name`] = matches[0][11] + m.Sp500[`latest`] = matches[0][12] + if matches[0][13] == "Up" { + m.Sp500[`change`] = "+" + matches[0][14] + m.Sp500[`percent`] = "+" + matches[0][15] + } else { + m.Sp500[`change`] = "-" + matches[0][14] + m.Sp500[`percent`] = "-" + matches[0][15] + } + + m.Advances[`name`] = matches[0][16] + m.Advances[`nyse`] = matches[0][17] + m.Advances[`nysep`] = matches[0][18] + m.Advances[`nasdaq`] = matches[0][19] + m.Advances[`nasdaqp`] = matches[0][20] + + m.Declines[`name`] = matches[0][21] + m.Declines[`nyse`] = matches[0][22] + m.Declines[`nysep`] = matches[0][23] + m.Declines[`nasdaq`] = matches[0][24] + m.Declines[`nasdaqp`] = matches[0][25] + + m.Unchanged[`name`] = matches[0][26] + m.Unchanged[`nyse`] = matches[0][27] + m.Unchanged[`nysep`] = matches[0][28] + m.Unchanged[`nasdaq`] = matches[0][29] + m.Unchanged[`nasdaqp`] = matches[0][30] + + m.Highs[`name`] = matches[0][31] + m.Highs[`nyse`] = matches[0][32] + m.Highs[`nasdaq`] = matches[0][33] + m.Lows[`name`] = matches[0][34] + m.Lows[`nyse`] = matches[0][35] + m.Lows[`nasdaq`] = matches[0][36] + + return m; +} diff --git a/lib/yahoo_finance.go b/lib/yahoo_quotes.go similarity index 97% rename from lib/yahoo_finance.go rename to lib/yahoo_quotes.go index e38dcfa..f49e329 100644 --- a/lib/yahoo_finance.go +++ b/lib/yahoo_quotes.go @@ -8,7 +8,6 @@ import ( "strings" "net/http" "io/ioutil" - // "strings" ) // See http://www.gummy-stuff.org/Yahoo-data.htm @@ -30,7 +29,7 @@ import ( // j3: market cap rt // j1: market cap -const yahoo_finance_url = `http://download.finance.yahoo.com/d/quotes.csv?s=%s&f=,l1c6k2oghjkva2r2rdyj3j1` +const yahoo_quotes_url = `http://download.finance.yahoo.com/d/quotes.csv?s=%s&f=,l1c6k2oghjkva2r2rdyj3j1` // "AAPL", 417.42, "-3.38", "N/A - -0.80%", 420.33, 415.35, 423.29, 385.10, 705.07, 9788680, 15181900, N/A, 10.04, 11.00, 2.61, N/A, 391.8B // "ALU", 1.83, "+0.07", "N/A - +3.98%", 1.77, 1.75, 1.83, 0.91, 2.01, 7957103, 11640700, N/A, N/A, 0.00, N/A, N/A, 4.156B @@ -58,12 +57,11 @@ type Quote struct { } type Quotes []Quote -// func Get(tickers []string) Quotes { -func Get(tickers string) Quotes { +func GetQuotes(tickers string) Quotes { // Format the URL and send the request. - // url := fmt.Sprintf(yahoo_finance_url, strings.Join(tickers, "+")) - url := fmt.Sprintf(yahoo_finance_url, tickers) + // url := fmt.Sprintf(yahoo_quotes_url, strings.Join(tickers, "+")) + url := fmt.Sprintf(yahoo_quotes_url, tickers) response, err := http.Get(url) if err != nil { panic(err) diff --git a/mop.go b/mop.go index bde1081..8a7a926 100644 --- a/mop.go +++ b/mop.go @@ -19,8 +19,9 @@ func initTermbox() { //----------------------------------------------------------------------------- func mainLoop(profile string) { keyboard_queue := make(chan termbox.Event) - quotes_queue := time.NewTicker(5 * time.Second) timestamp_queue := time.NewTicker(1 * time.Second) + quotes_queue := time.NewTicker(5 * time.Second) + market_queue := time.NewTicker(12 * time.Second) go func() { for { @@ -28,7 +29,9 @@ func mainLoop(profile string) { } }() - mop.Draw(profile) + termbox.Clear(termbox.ColorDefault, termbox.ColorDefault) + mop.DrawMarket() + mop.DrawQuotes(profile) loop: for { select { @@ -39,14 +42,19 @@ loop: break loop } case termbox.EventResize: - mop.Draw(profile) + termbox.Clear(termbox.ColorDefault, termbox.ColorDefault) + mop.DrawMarket() + mop.DrawQuotes(profile) } - case <-quotes_queue.C: - mop.Draw(profile) - case <-timestamp_queue.C: mop.DrawTime() + + case <-quotes_queue.C: + mop.DrawQuotes(profile) + + case <-market_queue.C: + mop.DrawMarket() } } }