Merge pull request #96 from joce/add-red-on-down

Values displayed in red if retreating
master
Brandon Lee Camilleri 3 years ago committed by GitHub
commit 60cf8cc1bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      cmd/mop/main.go
  2. 10
      cnn_market.go
  3. 2
      filter.go
  4. 40
      layout.go
  5. 39
      markup.go
  6. 79
      profile.go
  7. 33
      screen.go
  8. 2
      sorter.go
  9. 28
      yahoo_quotes.go

@ -138,10 +138,10 @@ func main() {
profileName := flag.String("profile", path.Join(usr.HomeDir, defaultProfile), "path to profile") profileName := flag.String("profile", path.Join(usr.HomeDir, defaultProfile), "path to profile")
flag.Parse() flag.Parse()
screen := mop.NewScreen() profile := mop.NewProfile(*profileName)
screen := mop.NewScreen(profile)
defer screen.Close() defer screen.Close()
profile := mop.NewProfile(*profileName)
mainLoop(screen, profile) mainLoop(screen, profile)
profile.Save() profile.Save()
} }

@ -108,7 +108,7 @@ func (market *Market) Fetch() (self *Market) {
return market.extract(market.trim(body)) return market.extract(market.trim(body))
} }
// Ok returns two values: 1) boolean indicating whether the error has occured, // Ok returns two values: 1) boolean indicating whether the error has occurred,
// and 2) the error text itself. // and 2) the error text itself.
func (market *Market) Ok() (bool, string) { func (market *Market) Ok() (bool, string) {
return market.errors == ``, market.errors return market.errors == ``, market.errors
@ -156,16 +156,16 @@ func (market *Market) extract(snippet []byte) *Market {
market.Yield[`change`] = matches[11] market.Yield[`change`] = matches[11]
market.Oil[`latest`] = matches[12] market.Oil[`latest`] = matches[12]
market.Oil[`change`] = matches[13] market.Oil[`change`] = matches[13] + `%`
market.Yen[`latest`] = matches[14] market.Yen[`latest`] = matches[14]
market.Yen[`change`] = matches[15] market.Yen[`change`] = matches[15] + `%`
market.Euro[`latest`] = matches[16] market.Euro[`latest`] = matches[16]
market.Euro[`change`] = matches[17] market.Euro[`change`] = matches[17] + `%`
market.Gold[`latest`] = matches[18] market.Gold[`latest`] = matches[18]
market.Gold[`change`] = matches[19] market.Gold[`change`] = matches[19] + `%`
market.Tokyo[`change`] = matches[20] market.Tokyo[`change`] = matches[20]
market.Tokyo[`latest`] = matches[21] market.Tokyo[`latest`] = matches[21]

@ -63,7 +63,7 @@ func (filter *Filter) Apply(stocks []Stock) []Stock {
values["avgVolume"] = stringToNumber(stock.AvgVolume) values["avgVolume"] = stringToNumber(stock.AvgVolume)
values["pe"] = stringToNumber(stock.PeRatio) values["pe"] = stringToNumber(stock.PeRatio)
values["peX"] = stringToNumber(stock.PeRatioX) values["peX"] = stringToNumber(stock.PeRatioX)
values["advancing"] = stock.Advancing // Remains bool. values["direction"] = stock.Direction // Remains int.
result, err := filter.profile.filterExpression.Evaluate(values) result, err := filter.profile.filterExpression.Evaluate(values)

@ -9,6 +9,7 @@ import (
"fmt" "fmt"
"reflect" "reflect"
"regexp" "regexp"
"strconv"
"strings" "strings"
"text/template" "text/template"
"time" "time"
@ -82,7 +83,7 @@ func (layout *Layout) Market(market *Market) string {
highlight(market.Dow, market.Sp500, market.Nasdaq, highlight(market.Dow, market.Sp500, market.Nasdaq,
market.Tokyo, market.HongKong, market.London, market.Frankfurt, market.Tokyo, market.HongKong, market.London, market.Frankfurt,
market.Yield, market.Oil, market.Euro, market.Gold) market.Yield, market.Oil, market.Euro, market.Yen, market.Gold)
buffer := new(bytes.Buffer) buffer := new(bytes.Buffer)
layout.marketTemplate.Execute(buffer, market) layout.marketTemplate.Execute(buffer, market)
@ -146,7 +147,7 @@ func (layout *Layout) prettify(quotes *Quotes) []Stock {
// Iterate over the list of stocks and properly format all its columns. // Iterate over the list of stocks and properly format all its columns.
// //
for i, stock := range quotes.stocks { for i, stock := range quotes.stocks {
pretty[i].Advancing = stock.Advancing pretty[i].Direction = stock.Direction
// //
// Iterate over the list of stock columns. For each column name: // Iterate over the list of stock columns. For each column name:
// - Get current column value. // - Get current column value.
@ -167,7 +168,7 @@ func (layout *Layout) prettify(quotes *Quotes) []Stock {
profile := quotes.profile profile := quotes.profile
if profile.Filter != ""{ // Fix for blank display if invalid filter expression was cleared. if profile.Filter != "" { // Fix for blank display if invalid filter expression was cleared.
if profile.filterExpression != nil { if profile.filterExpression != nil {
if layout.filter == nil { // Initialize filter on first invocation. if layout.filter == nil { // Initialize filter on first invocation.
layout.filter = NewFilter(profile) layout.filter = NewFilter(profile)
@ -208,21 +209,21 @@ func (layout *Layout) pad(str string, width int) string {
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
func buildMarketTemplate() *template.Template { func buildMarketTemplate() *template.Template {
markup := `<yellow>Dow</> {{.Dow.change}} ({{.Dow.percent}}) at {{.Dow.latest}} <yellow>S&P 500</> {{.Sp500.change}} ({{.Sp500.percent}}) at {{.Sp500.latest}} <yellow>NASDAQ</> {{.Nasdaq.change}} ({{.Nasdaq.percent}}) at {{.Nasdaq.latest}} markup := `<tag>Dow</> {{.Dow.change}} ({{.Dow.percent}}) at {{.Dow.latest}} <tag>S&P 500</> {{.Sp500.change}} ({{.Sp500.percent}}) at {{.Sp500.latest}} <tag>NASDAQ</> {{.Nasdaq.change}} ({{.Nasdaq.percent}}) at {{.Nasdaq.latest}}
<yellow>Tokyo</> {{.Tokyo.change}} ({{.Tokyo.percent}}) at {{.Tokyo.latest}} <yellow>HK</> {{.HongKong.change}} ({{.HongKong.percent}}) at {{.HongKong.latest}} <yellow>London</> {{.London.change}} ({{.London.percent}}) at {{.London.latest}} <yellow>Frankfurt</> {{.Frankfurt.change}} ({{.Frankfurt.percent}}) at {{.Frankfurt.latest}} {{if .IsClosed}}<right>U.S. markets closed</right>{{end}} <tag>Tokyo</> {{.Tokyo.change}} ({{.Tokyo.percent}}) at {{.Tokyo.latest}} <tag>HK</> {{.HongKong.change}} ({{.HongKong.percent}}) at {{.HongKong.latest}} <tag>London</> {{.London.change}} ({{.London.percent}}) at {{.London.latest}} <tag>Frankfurt</> {{.Frankfurt.change}} ({{.Frankfurt.percent}}) at {{.Frankfurt.latest}} {{if .IsClosed}}<right>U.S. markets closed</right>{{end}}
<yellow>10-Year Yield</> {{.Yield.latest}}% ({{.Yield.change}}) <yellow>Euro</> ${{.Euro.latest}} ({{.Euro.change}}%) <yellow>Yen</> ¥{{.Yen.latest}} ({{.Yen.change}}%) <yellow>Oil</> ${{.Oil.latest}} ({{.Oil.change}}%) <yellow>Gold</> ${{.Gold.latest}} ({{.Gold.change}}%)` <tag>10-Year Yield</> {{.Yield.latest}} ({{.Yield.change}}) <tag>Euro</> ${{.Euro.latest}} ({{.Euro.change}}) <tag>Yen</> ¥{{.Yen.latest}} ({{.Yen.change}}) <tag>Oil</> ${{.Oil.latest}} ({{.Oil.change}}) <tag>Gold</> ${{.Gold.latest}} ({{.Gold.change}})`
return template.Must(template.New(`market`).Parse(markup)) return template.Must(template.New(`market`).Parse(markup))
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
func buildQuotesTemplate() *template.Template { func buildQuotesTemplate() *template.Template {
markup := `<right><white>{{.Now}}</></right> markup := `<right><time>{{.Now}}</></right>
{{.Header}} <header>{{.Header}}</>
{{range.Stocks}}{{if .Advancing}}<green>{{end}}{{.Ticker}}{{.LastTrade}}{{.Change}}{{.ChangePct}}{{.Open}}{{.Low}}{{.High}}{{.Low52}}{{.High52}}{{.Volume}}{{.AvgVolume}}{{.PeRatio}}{{.Dividend}}{{.Yield}}{{.MarketCap}}{{.PreOpen}}{{.AfterHours}}</> {{range.Stocks}}{{if eq .Direction 1}}<gain>{{else if eq .Direction -1}}<loss>{{end}}{{.Ticker}}{{.LastTrade}}{{.Change}}{{.ChangePct}}{{.Open}}{{.Low}}{{.High}}{{.Low52}}{{.High52}}{{.Volume}}{{.AvgVolume}}{{.PeRatio}}{{.Dividend}}{{.Yield}}{{.MarketCap}}{{.PreOpen}}{{.AfterHours}}</>
{{end}}` {{end}}`
return template.Must(template.New(`quotes`).Parse(markup)) return template.Must(template.New(`quotes`).Parse(markup))
@ -231,8 +232,17 @@ func buildQuotesTemplate() *template.Template {
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
func highlight(collections ...map[string]string) { func highlight(collections ...map[string]string) {
for _, collection := range collections { for _, collection := range collections {
if collection[`change`][0:1] != `-` { change := collection[`change`]
collection[`change`] = `<green>` + collection[`change`] + `</>` if change[len(change)-1:] == `%` {
change = change[0 : len(change)-1]
}
adv, err := strconv.ParseFloat(change, 64)
if err == nil {
if adv < 0.0 {
collection[`change`] = `<loss>` + collection[`change`] + `</>`
} else if adv > 0.0 {
collection[`change`] = `<gain>` + collection[`change`] + `</>`
}
} }
} }
} }
@ -243,13 +253,13 @@ func group(stocks []Stock) []Stock {
current := 0 current := 0
for _, stock := range stocks { for _, stock := range stocks {
if stock.Advancing { if stock.Direction >= 0 {
grouped[current] = stock grouped[current] = stock
current++ current++
} }
} }
for _, stock := range stocks { for _, stock := range stocks {
if !stock.Advancing { if stock.Direction < 0 {
grouped[current] = stock grouped[current] = stock
current++ current++
} }
@ -262,9 +272,9 @@ func group(stocks []Stock) []Stock {
func arrowFor(column int, profile *Profile) string { func arrowFor(column int, profile *Profile) string {
if column == profile.SortColumn { if column == profile.SortColumn {
if profile.Ascending { if profile.Ascending {
return string('\U00002191') return string('')
} }
return string('\U00002193') return string('')
} }
return `` return ``
} }

@ -5,9 +5,10 @@
package mop package mop
import ( import (
`github.com/nsf/termbox-go` "regexp"
`regexp` "strings"
`strings`
"github.com/nsf/termbox-go"
) )
// Markup implements some minimalistic text formatting conventions that // Markup implements some minimalistic text formatting conventions that
@ -33,11 +34,8 @@ type Markup struct {
// Creates markup to define tag to Termbox translation rules and store default // Creates markup to define tag to Termbox translation rules and store default
// colors and column alignments. // colors and column alignments.
func NewMarkup() *Markup { func NewMarkup(profile *Profile) *Markup {
markup := &Markup{} markup := &Markup{}
markup.Foreground = termbox.ColorDefault
markup.Background = termbox.ColorDefault
markup.RightAligned = false
markup.tags = make(map[string]termbox.Attribute) markup.tags = make(map[string]termbox.Attribute)
markup.tags[`/`] = termbox.ColorDefault markup.tags[`/`] = termbox.ColorDefault
@ -49,10 +47,33 @@ func NewMarkup() *Markup {
markup.tags[`magenta`] = termbox.ColorMagenta markup.tags[`magenta`] = termbox.ColorMagenta
markup.tags[`cyan`] = termbox.ColorCyan markup.tags[`cyan`] = termbox.ColorCyan
markup.tags[`white`] = termbox.ColorWhite markup.tags[`white`] = termbox.ColorWhite
markup.tags[`darkgray`] = termbox.ColorDarkGray
markup.tags[`lightred`] = termbox.ColorLightRed
markup.tags[`lightgreen`] = termbox.ColorLightGreen
markup.tags[`lightyellow`] = termbox.ColorLightYellow
markup.tags[`lightblue`] = termbox.ColorLightBlue
markup.tags[`lightmagenta`] = termbox.ColorLightMagenta
markup.tags[`lightcyan`] = termbox.ColorLightCyan
markup.tags[`lightgray`] = termbox.ColorLightGray
markup.tags[`right`] = termbox.ColorDefault // Termbox can combine attributes and a single color using bitwise OR. markup.tags[`right`] = termbox.ColorDefault // Termbox can combine attributes and a single color using bitwise OR.
markup.tags[`b`] = termbox.AttrBold // Attribute = 1 << (iota + 4) markup.tags[`b`] = termbox.AttrBold // Attribute = 1 << (iota + 4)
markup.tags[`u`] = termbox.AttrUnderline markup.tags[`u`] = termbox.AttrUnderline
markup.tags[`r`] = termbox.AttrReverse markup.tags[`r`] = termbox.AttrReverse
// Semantic markups
markup.tags[`gain`] = markup.tags[profile.Colors.Gain]
markup.tags[`loss`] = markup.tags[profile.Colors.Loss]
markup.tags[`tag`] = markup.tags[profile.Colors.Tag]
markup.tags[`header`] = markup.tags[profile.Colors.Header]
markup.tags[`time`] = markup.tags[profile.Colors.Time]
markup.tags[`default`] = markup.tags[profile.Colors.Default]
markup.Foreground = markup.tags[profile.Colors.Default]
markup.Background = termbox.ColorDefault
markup.RightAligned = false
markup.regex = markup.supportedTags() // Once we have the hash we could build the regex. markup.regex = markup.supportedTags() // Once we have the hash we could build the regex.
return markup return markup
@ -77,7 +98,7 @@ func (markup *Markup) Tokenize(str string) []string {
tail = match[0] tail = match[0]
if match[1] != 0 { if match[1] != 0 {
if head != 0 || tail != 0 { if head != 0 || tail != 0 {
// Apend the text between tags. // Append the text between tags.
strings = append(strings, str[head:tail]) strings = append(strings, str[head:tail])
} }
// Append the tag itmarkup. // Append the tag itmarkup.
@ -123,7 +144,7 @@ func (markup *Markup) process(tag string, open bool) bool {
if attribute >= termbox.AttrBold { if attribute >= termbox.AttrBold {
markup.Foreground &= ^attribute // Clear the Termbox attribute. markup.Foreground &= ^attribute // Clear the Termbox attribute.
} else { } else {
markup.Foreground = termbox.ColorDefault markup.Foreground = markup.tags[`default`]
} }
} }
} }

@ -8,26 +8,66 @@ import (
"encoding/json" "encoding/json"
"io/ioutil" "io/ioutil"
"sort" "sort"
"strings"
"github.com/Knetic/govaluate" "github.com/Knetic/govaluate"
) )
const defaultGainColor = "green"
const defaultLossColor = "red"
const defaultTagColor = "yellow"
const defaultHeaderColor = "lightgray"
const defaultTimeColor = "lightgray"
const defaultColor = "lightgray"
// Profile manages Mop program settings as defined by user (ex. list of // Profile manages Mop program settings as defined by user (ex. list of
// stock tickers). The settings are serialized using JSON and saved in // stock tickers). The settings are serialized using JSON and saved in
// the ~/.moprc file. // the ~/.moprc file.
type Profile struct { type Profile struct {
Tickers []string // List of stock tickers to display. Tickers []string // List of stock tickers to display.
MarketRefresh int // Time interval to refresh market data. MarketRefresh int // Time interval to refresh market data.
QuotesRefresh int // Time interval to refresh stock quotes. QuotesRefresh int // Time interval to refresh stock quotes.
SortColumn int // Column number by which we sort stock quotes. SortColumn int // Column number by which we sort stock quotes.
Ascending bool // True when sort order is ascending. Ascending bool // True when sort order is ascending.
Grouped bool // True when stocks are grouped by advancing/declining. Grouped bool // True when stocks are grouped by advancing/declining.
Filter string // Filter in human form Filter string // Filter in human form
Colors struct { // User defined colors
Gain string
Loss string
Tag string
Header string
Time string
Default string
}
filterExpression *govaluate.EvaluableExpression // The filter as a govaluate expression filterExpression *govaluate.EvaluableExpression // The filter as a govaluate expression
selectedColumn int // Stores selected column number when the column editor is active. selectedColumn int // Stores selected column number when the column editor is active.
filename string // Path to the file in which the configuration is stored filename string // Path to the file in which the configuration is stored
} }
func IsSupportedColor(colorName string) bool {
switch colorName {
case
"black",
"red",
"green",
"yellow",
"blue",
"magenta",
"cyan",
"white",
"darkgray",
"lightred",
"lightgreen",
"lightyellow",
"lightblue",
"lightmagenta",
"lightcyan",
"lightgray":
return true
}
return false
}
// Creates the profile and attempts to load the settings from ~/.moprc file. // Creates the profile and attempts to load the settings from ~/.moprc file.
// If the file is not there it gets created with default values. // If the file is not there it gets created with default values.
func NewProfile(filename string) *Profile { func NewProfile(filename string) *Profile {
@ -41,9 +81,23 @@ func NewProfile(filename string) *Profile {
profile.SortColumn = 0 // Stock quotes are sorted by ticker name. profile.SortColumn = 0 // Stock quotes are sorted by ticker name.
profile.Ascending = true // A to Z. profile.Ascending = true // A to Z.
profile.Filter = "" profile.Filter = ""
profile.Colors.Gain = defaultGainColor
profile.Colors.Loss = defaultLossColor
profile.Colors.Tag = defaultTagColor
profile.Colors.Header = defaultHeaderColor
profile.Colors.Time = defaultTimeColor
profile.Colors.Default = defaultColor
profile.Save() profile.Save()
} else { } else {
json.Unmarshal(data, profile) json.Unmarshal(data, profile)
InitColor(&profile.Colors.Gain, defaultGainColor)
InitColor(&profile.Colors.Loss, defaultLossColor)
InitColor(&profile.Colors.Tag, defaultTagColor)
InitColor(&profile.Colors.Header, defaultHeaderColor)
InitColor(&profile.Colors.Time, defaultTimeColor)
InitColor(&profile.Colors.Default, defaultColor)
profile.SetFilter(profile.Filter) profile.SetFilter(profile.Filter)
} }
profile.selectedColumn = -1 profile.selectedColumn = -1
@ -51,9 +105,16 @@ func NewProfile(filename string) *Profile {
return profile return profile
} }
func InitColor(color *string, defaultValue string) {
*color = strings.ToLower(*color)
if !IsSupportedColor(*color) {
*color = defaultValue
}
}
// Save serializes settings using JSON and saves them in ~/.moprc file. // Save serializes settings using JSON and saves them in ~/.moprc file.
func (profile *Profile) Save() error { func (profile *Profile) Save() error {
data, err := json.Marshal(profile) data, err := json.MarshalIndent(profile, "", " ")
if err != nil { if err != nil {
return err return err
} }
@ -61,7 +122,7 @@ func (profile *Profile) Save() error {
return ioutil.WriteFile(profile.filename, data, 0644) return ioutil.WriteFile(profile.filename, data, 0644)
} }
// AddTickers updates the list of existing tikers to add the new ones making // AddTickers updates the list of existing tickers to add the new ones making
// sure there are no duplicates. // sure there are no duplicates.
func (profile *Profile) AddTickers(tickers []string) (added int, err error) { func (profile *Profile) AddTickers(tickers []string) (added int, err error) {
added, err = 0, nil added, err = 0, nil

@ -5,15 +5,16 @@
package mop package mop
import ( import (
`github.com/nsf/termbox-go` "fmt"
`strings` "strconv"
`time` "strings"
`strconv` "time"
`fmt`
"github.com/nsf/termbox-go"
) )
// Screen is thin wrapper aroung Termbox library to provide basic display // Screen is thin wrapper around Termbox library to provide basic display
// capabilities as requied by Mop. // capabilities as required by Mop.
type Screen struct { type Screen struct {
width int // Current number of columns. width int // Current number of columns.
height int // Current number of rows. height int // Current number of rows.
@ -26,13 +27,13 @@ type Screen struct {
// Initializes Termbox, creates screen along with layout and markup, and // Initializes Termbox, creates screen along with layout and markup, and
// calculates current screen dimensions. Once initialized the screen is // calculates current screen dimensions. Once initialized the screen is
// ready for display. // ready for display.
func NewScreen() *Screen { func NewScreen(profile *Profile) *Screen {
if err := termbox.Init(); err != nil { if err := termbox.Init(); err != nil {
panic(err) panic(err)
} }
screen := &Screen{} screen := &Screen{}
screen.layout = NewLayout() screen.layout = NewLayout()
screen.markup = NewMarkup() screen.markup = NewMarkup(profile)
return screen.Resize() return screen.Resize()
} }
@ -90,7 +91,7 @@ func (screen *Screen) ClearLine(x int, y int) *Screen {
func (screen *Screen) Draw(objects ...interface{}) *Screen { func (screen *Screen) Draw(objects ...interface{}) *Screen {
zonename, _ := time.Now().In(time.Local).Zone() zonename, _ := time.Now().In(time.Local).Zone()
if screen.pausedAt != nil { if screen.pausedAt != nil {
defer screen.DrawLine(0, 0, `<right><r>`+screen.pausedAt.Format(`3:04:05pm ` + zonename)+`</r></right>`) defer screen.DrawLine(0, 0, `<right><r>`+screen.pausedAt.Format(`3:04:05pm `+zonename)+`</r></right>`)
} }
for _, ptr := range objects { for _, ptr := range objects {
switch ptr.(type) { switch ptr.(type) {
@ -102,7 +103,7 @@ func (screen *Screen) Draw(objects ...interface{}) *Screen {
screen.draw(screen.layout.Quotes(object.Fetch())) screen.draw(screen.layout.Quotes(object.Fetch()))
case time.Time: case time.Time:
timestamp := ptr.(time.Time).Format(`3:04:05pm ` + zonename) timestamp := ptr.(time.Time).Format(`3:04:05pm ` + zonename)
screen.DrawLine(0, 0, `<right>`+timestamp+`</right>`) screen.DrawLine(0, 0, `<right><time>`+timestamp+`</></right>`)
default: default:
screen.draw(ptr.(string)) screen.draw(ptr.(string))
} }
@ -146,7 +147,7 @@ func (screen *Screen) draw(str string) {
drewHeading := false drewHeading := false
tempFormat := "%" + strconv.Itoa(screen.width) + "s" tempFormat := "%" + strconv.Itoa(screen.width) + "s"
blankLine := fmt.Sprintf(tempFormat,"") blankLine := fmt.Sprintf(tempFormat, "")
allLines = strings.Split(str, "\n") allLines = strings.Split(str, "\n")
// Write the lines being updated. // Write the lines being updated.
@ -154,9 +155,9 @@ func (screen *Screen) draw(str string) {
screen.DrawLine(0, row, allLines[row]) screen.DrawLine(0, row, allLines[row])
// Did we draw the underlined heading row? This is a crude // Did we draw the underlined heading row? This is a crude
// check, but--see comments below... // check, but--see comments below...
if strings.Contains(allLines[row],"Ticker") && if strings.Contains(allLines[row], "Ticker") &&
strings.Contains(allLines[row],"Last") && strings.Contains(allLines[row], "Last") &&
strings.Contains(allLines[row],"Change") { strings.Contains(allLines[row], "Change") {
drewHeading = true drewHeading = true
} }
} }
@ -173,7 +174,7 @@ func (screen *Screen) draw(str string) {
// cycle. In that case, padding with blank lines would overwrite the // cycle. In that case, padding with blank lines would overwrite the
// stocks list.) // stocks list.)
if drewHeading { if drewHeading {
for i := len(allLines)-1; i < screen.height; i++ { for i := len(allLines) - 1; i < screen.height; i++ {
screen.DrawLine(0, i, blankLine) screen.DrawLine(0, i, blankLine)
} }
} }

@ -223,7 +223,7 @@ func (sorter *Sorter) SortByCurrentColumn(stocks []Stock) *Sorter {
} }
// The same exact method is used to sort by $Change and Change%. In both cases // The same exact method is used to sort by $Change and Change%. In both cases
// we sort by the value of Change% so that multiple $0.00s get sorted proferly. // we sort by the value of Change% so that multiple $0.00s get sorted properly.
func c(str string) float32 { func c(str string) float32 {
c := "$" c := "$"
for _, v := range currencies { for _, v := range currencies {

@ -22,7 +22,7 @@ const quotesURLv7QueryParts = `&range=1d&interval=5m&indicators=close&includeTim
const noDataIndicator = `N/A` const noDataIndicator = `N/A`
// Stock stores quote information for the particular stock ticker. The data // Stock stores quote information for the particular stock ticker. The data
// for all the fields except 'Advancing' is fetched using Yahoo market API. // for all the fields except 'Direction' is fetched using Yahoo market API.
type Stock struct { type Stock struct {
Ticker string `json:"symbol"` // Stock ticker. Ticker string `json:"symbol"` // Stock ticker.
LastTrade string `json:"regularMarketPrice"` // l1: last trade. LastTrade string `json:"regularMarketPrice"` // l1: last trade.
@ -42,7 +42,7 @@ type Stock struct {
MarketCap string `json:"marketCap"` // j3: market cap real time. MarketCap string `json:"marketCap"` // j3: market cap real time.
MarketCapX string `json:"marketCap"` // j1: market cap (fallback when real time is N/A). MarketCapX string `json:"marketCap"` // j1: market cap (fallback when real time is N/A).
Currency string `json:"currency"` // String code for currency of stock. Currency string `json:"currency"` // String code for currency of stock.
Advancing bool // True when change is >= $0. Direction int // -1 when change is < $0, 0 when change is = $0, 1 when change is > $0.
PreOpen string `json:"preMarketChangePercent,omitempty"` PreOpen string `json:"preMarketChangePercent,omitempty"`
AfterHours string `json:"postMarketChangePercent,omitempty"` AfterHours string `json:"postMarketChangePercent,omitempty"`
} }
@ -96,7 +96,7 @@ func (quotes *Quotes) Fetch() (self *Quotes) {
return quotes return quotes
} }
// Ok returns two values: 1) boolean indicating whether the error has occured, // Ok returns two values: 1) boolean indicating whether the error has occurred,
// and 2) the error text itself. // and 2) the error text itself.
func (quotes *Quotes) Ok() (bool, string) { func (quotes *Quotes) Ok() (bool, string) {
return quotes.errors == ``, quotes.errors return quotes.errors == ``, quotes.errors
@ -188,8 +188,13 @@ func (quotes *Quotes) parse2(body []byte) (*Quotes, error) {
fmt.Println("-------------------") fmt.Println("-------------------")
*/ */
adv, err := strconv.ParseFloat(quotes.stocks[i].Change, 64) adv, err := strconv.ParseFloat(quotes.stocks[i].Change, 64)
quotes.stocks[i].Direction = 0
if err == nil { if err == nil {
quotes.stocks[i].Advancing = adv >= 0.0 if adv < 0.0 {
quotes.stocks[i].Direction = -1
} else if adv > 0.0 {
quotes.stocks[i].Direction = 1
}
} }
} }
return quotes, nil return quotes, nil
@ -202,7 +207,7 @@ func (quotes *Quotes) parse(body []byte) *Quotes {
quotes.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. // Advancing field which is not fetched.
// //
fieldsCount := reflect.ValueOf(quotes.stocks[0]).NumField() - 1 fieldsCount := reflect.ValueOf(quotes.stocks[0]).NumField() - 1
// //
@ -226,10 +231,17 @@ func (quotes *Quotes) parse(body []byte) *Quotes {
quotes.stocks[i].MarketCap = quotes.stocks[i].MarketCapX quotes.stocks[i].MarketCap = quotes.stocks[i].MarketCapX
} }
// //
// Stock is advancing if the change is not negative (i.e. $0.00 // Get the direction of the stock
// is also "advancing").
// //
quotes.stocks[i].Advancing = (quotes.stocks[i].Change[0:1] != `-`) adv, err := strconv.ParseFloat(quotes.stocks[i].Change, 64)
quotes.stocks[i].Direction = 0
if err == nil {
if adv < 0 {
quotes.stocks[i].Direction = -1
} else if (adv > 0) {
quotes.stocks[i].Direction = 1
}
}
} }
return quotes return quotes

Loading…
Cancel
Save