mirror of
https://github.com/taigrr/wtf
synced 2025-01-18 04:03:14 -08:00
Add Yahoo Finance module (#1066)
* Move finnhub to a stocks folder
As I am preparing an other stocks data provider, let's move `finnhub` to
a stocks folder that will host the others providers.
* Use go-pretty v6
Will be used by the new stock provider module, so let's just upgrade
this one to reduce the number of dependencies.
* Add Yahoo Finance module
Yahoo Finance provides an API for which `piquette/finance-go` is a
powerful client. This new module leverages this module to integrate all
indices provided by Yahoo Finance (international stocks, crypto,
options, currencies...)
Sample config:
```yaml
yfinance:
title: "Stocks 🚀"
symbols:
- "MSFT"
- "GC=F"
- "ORA.PA"
sort: true
enabled: true
refreshInterval: 60
position:
top: 1
left: 0
height: 1
width: 1
```
This commit is contained in:
45
modules/stocks/yfinance/settings.go
Normal file
45
modules/stocks/yfinance/settings.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package yfinance
|
||||
|
||||
import (
|
||||
"github.com/olebedev/config"
|
||||
"github.com/wtfutil/wtf/cfg"
|
||||
"github.com/wtfutil/wtf/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultFocusable = false
|
||||
defaultTitle = "Yahoo Finance"
|
||||
)
|
||||
|
||||
type colors struct {
|
||||
bigup string
|
||||
up string
|
||||
drop string
|
||||
bigdrop string
|
||||
}
|
||||
|
||||
// Settings defines the configuration properties for this module
|
||||
type Settings struct {
|
||||
common *cfg.Common
|
||||
|
||||
colors colors
|
||||
sort bool
|
||||
symbols []string `help:"An array of Yahoo Finance symbols (for example: DOCN, GME, GC=F)"`
|
||||
}
|
||||
|
||||
// NewSettingsFromYAML creates a new settings instance from a YAML config block
|
||||
func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings {
|
||||
settings := Settings{
|
||||
common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig),
|
||||
// RefreshInterval: ,
|
||||
}
|
||||
|
||||
settings.common.RefreshInterval = ymlConfig.UInt("refreshInterval", 60)
|
||||
settings.colors.bigup = ymlConfig.UString("colors.bigup", "greenyellow")
|
||||
settings.colors.up = ymlConfig.UString("colors.up", "green")
|
||||
settings.colors.drop = ymlConfig.UString("colors.drop", "firebrick")
|
||||
settings.colors.bigdrop = ymlConfig.UString("colors.bigdrop", "red")
|
||||
settings.sort = ymlConfig.UBool("sort", false)
|
||||
settings.symbols = utils.ToStrs(ymlConfig.UList("symbols"))
|
||||
return &settings
|
||||
}
|
||||
74
modules/stocks/yfinance/widget.go
Normal file
74
modules/stocks/yfinance/widget.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package yfinance
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/rivo/tview"
|
||||
"github.com/wtfutil/wtf/view"
|
||||
)
|
||||
|
||||
// Widget is the container for your module's data
|
||||
type Widget struct {
|
||||
view.TextWidget
|
||||
|
||||
settings *Settings
|
||||
}
|
||||
|
||||
// NewWidget creates and returns an instance of Widget
|
||||
func NewWidget(tviewApp *tview.Application, settings *Settings) *Widget {
|
||||
widget := Widget{
|
||||
TextWidget: view.NewTextWidget(tviewApp, nil, settings.common),
|
||||
|
||||
settings: settings,
|
||||
}
|
||||
|
||||
return &widget
|
||||
}
|
||||
|
||||
/* -------------------- Exported Functions -------------------- */
|
||||
|
||||
// Refresh updates the onscreen contents of the widget
|
||||
func (widget *Widget) Refresh() {
|
||||
|
||||
// The last call should always be to the display function
|
||||
widget.display()
|
||||
}
|
||||
|
||||
/* -------------------- Unexported Functions -------------------- */
|
||||
|
||||
func (widget *Widget) content() string {
|
||||
yquotes := quotes(widget.settings.symbols)
|
||||
|
||||
colors := map[string]string{
|
||||
"bigup": widget.settings.colors.bigup,
|
||||
"up": widget.settings.colors.up,
|
||||
"drop": widget.settings.colors.drop,
|
||||
"bigdrop": widget.settings.colors.bigdrop,
|
||||
}
|
||||
|
||||
if widget.settings.sort {
|
||||
sort.SliceStable(yquotes, func(i, j int) bool { return yquotes[i].MarketChangePct > yquotes[j].MarketChangePct })
|
||||
}
|
||||
|
||||
t := table.NewWriter()
|
||||
t.SetStyle(tableStyle())
|
||||
for _, yq := range yquotes {
|
||||
t.AppendRow([]interface{}{
|
||||
GetMarketIcon(yq.MarketState),
|
||||
yq.Symbol,
|
||||
fmt.Sprintf("%8.2f %s", yq.MarketPrice, yq.Currency),
|
||||
GetTrendIcon(yq.Trend),
|
||||
fmt.Sprintf("[%s]%+6.2f (%+5.2f%%)", colors[yq.Trend], yq.MarketChange, yq.MarketChangePct),
|
||||
})
|
||||
}
|
||||
|
||||
return t.Render()
|
||||
}
|
||||
|
||||
func (widget *Widget) display() {
|
||||
widget.Redraw(func() (string, string, bool) {
|
||||
return widget.CommonSettings().Title, widget.content(), false
|
||||
})
|
||||
}
|
||||
145
modules/stocks/yfinance/yquote.go
Normal file
145
modules/stocks/yfinance/yquote.go
Normal file
@@ -0,0 +1,145 @@
|
||||
package yfinance
|
||||
|
||||
import (
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/jedib0t/go-pretty/v6/text"
|
||||
"github.com/piquette/finance-go/quote"
|
||||
)
|
||||
|
||||
type MarketState string
|
||||
|
||||
type yquote struct {
|
||||
Trend string // can be bigup (>3%), up, drop or bigdrop (<3%)
|
||||
Symbol string
|
||||
Currency string
|
||||
MarketState string
|
||||
MarketPrice float64
|
||||
MarketChange float64
|
||||
MarketChangePct float64
|
||||
}
|
||||
|
||||
func tableStyle() table.Style {
|
||||
return table.Style{
|
||||
Name: "yfinance",
|
||||
Box: table.BoxStyle{
|
||||
BottomLeft: "",
|
||||
BottomRight: "",
|
||||
BottomSeparator: "",
|
||||
Left: "",
|
||||
LeftSeparator: "",
|
||||
MiddleHorizontal: " ",
|
||||
MiddleSeparator: "",
|
||||
MiddleVertical: "",
|
||||
PaddingLeft: " ",
|
||||
PaddingRight: "",
|
||||
Right: "",
|
||||
RightSeparator: "",
|
||||
TopLeft: "",
|
||||
TopRight: "",
|
||||
TopSeparator: "",
|
||||
UnfinishedRow: "",
|
||||
},
|
||||
Color: table.ColorOptions{
|
||||
Footer: text.Colors{},
|
||||
Header: text.Colors{},
|
||||
Row: text.Colors{},
|
||||
RowAlternate: text.Colors{},
|
||||
},
|
||||
Format: table.FormatOptions{
|
||||
Footer: text.FormatUpper,
|
||||
Header: text.FormatUpper,
|
||||
Row: text.FormatDefault,
|
||||
},
|
||||
Options: table.Options{
|
||||
DrawBorder: false,
|
||||
SeparateColumns: false,
|
||||
SeparateFooter: false,
|
||||
SeparateHeader: false,
|
||||
SeparateRows: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func quotes(symbols []string) []yquote {
|
||||
var yquotes []yquote
|
||||
for _, symbol := range symbols {
|
||||
var yq yquote
|
||||
|
||||
var MarketPrice float64
|
||||
var MarketChange float64
|
||||
var MarketChangePct float64
|
||||
|
||||
q, err := quote.Get(symbol)
|
||||
if q == nil || err != nil {
|
||||
yq = yquote{
|
||||
Symbol: symbol,
|
||||
Trend: "?",
|
||||
MarketState: "?",
|
||||
}
|
||||
} else {
|
||||
if q.MarketState == "PRE" {
|
||||
MarketPrice = q.PreMarketPrice
|
||||
MarketChange = q.PreMarketChange
|
||||
MarketChangePct = q.PreMarketChangePercent
|
||||
|
||||
} else if q.MarketState == "POST" {
|
||||
MarketPrice = q.PostMarketPrice
|
||||
MarketChange = q.PostMarketChange
|
||||
MarketChangePct = q.PostMarketChangePercent
|
||||
} else {
|
||||
MarketPrice = q.RegularMarketPrice
|
||||
MarketChange = q.RegularMarketChange
|
||||
MarketChangePct = q.RegularMarketChangePercent
|
||||
}
|
||||
yq = yquote{
|
||||
Symbol: q.Symbol,
|
||||
Currency: q.CurrencyID,
|
||||
Trend: GetTrend(MarketChangePct),
|
||||
MarketState: string(q.MarketState),
|
||||
MarketPrice: MarketPrice,
|
||||
MarketChange: MarketChange,
|
||||
MarketChangePct: MarketChangePct,
|
||||
}
|
||||
}
|
||||
yquotes = append(yquotes, yq)
|
||||
}
|
||||
return yquotes
|
||||
}
|
||||
|
||||
func GetMarketIcon(state string) string {
|
||||
states := map[string]string{
|
||||
"PRE": "⏭",
|
||||
"REGULAR": "▶",
|
||||
"POST": "⏮",
|
||||
"?": "?",
|
||||
}
|
||||
if icon, ok := states[state]; ok {
|
||||
return icon
|
||||
} else {
|
||||
return "⏹"
|
||||
}
|
||||
}
|
||||
|
||||
func GetTrendIcon(trend string) string {
|
||||
icons := map[string]string{
|
||||
"bigup": "⬆️ ",
|
||||
"up": "↗️ ",
|
||||
"drop": "↘️ ",
|
||||
"bigdrop": "⬇️ ",
|
||||
}
|
||||
return icons[trend]
|
||||
}
|
||||
|
||||
func GetTrend(pct float64) string {
|
||||
var trend string
|
||||
if pct > 3 {
|
||||
trend = "bigup"
|
||||
} else if pct > 0 {
|
||||
trend = "up"
|
||||
} else if pct > -3 {
|
||||
trend = "drop"
|
||||
} else {
|
||||
trend = "bigdrop"
|
||||
}
|
||||
return trend
|
||||
}
|
||||
Reference in New Issue
Block a user