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:
80
modules/stocks/finnhub/client.go
Normal file
80
modules/stocks/finnhub/client.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package finnhub
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// Client ..
|
||||
type Client struct {
|
||||
symbols []string
|
||||
apiKey string
|
||||
}
|
||||
|
||||
// NewClient ..
|
||||
func NewClient(symbols []string, apiKey string) *Client {
|
||||
client := Client{
|
||||
symbols: symbols,
|
||||
apiKey: apiKey,
|
||||
}
|
||||
|
||||
return &client
|
||||
}
|
||||
|
||||
// Getquote ..
|
||||
func (client *Client) Getquote() ([]Quote, error) {
|
||||
quotes := []Quote{}
|
||||
|
||||
for _, s := range client.symbols {
|
||||
resp, err := client.finnhubRequest(s)
|
||||
if err != nil {
|
||||
return quotes, err
|
||||
}
|
||||
|
||||
var quote Quote
|
||||
quote.Stock = s
|
||||
err = json.NewDecoder(resp.Body).Decode("e)
|
||||
if err != nil {
|
||||
return quotes, err
|
||||
}
|
||||
quotes = append(quotes, quote)
|
||||
}
|
||||
|
||||
return quotes, nil
|
||||
}
|
||||
|
||||
/* -------------------- Unexported Functions -------------------- */
|
||||
|
||||
var (
|
||||
finnhubURL = &url.URL{Scheme: "https", Host: "finnhub.io", Path: "/api/v1/quote"}
|
||||
)
|
||||
|
||||
func (client *Client) finnhubRequest(symbol string) (*http.Response, error) {
|
||||
params := url.Values{}
|
||||
params.Add("symbol", symbol)
|
||||
params.Add("token", client.apiKey)
|
||||
|
||||
url := finnhubURL.ResolveReference(&url.URL{RawQuery: params.Encode()})
|
||||
|
||||
req, err := http.NewRequest("GET", url.String(), nil)
|
||||
req.Header.Add("Accept", "application/json")
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
httpClient := &http.Client{}
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
if resp.StatusCode < 200 || resp.StatusCode > 299 {
|
||||
return nil, fmt.Errorf(resp.Status)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
12
modules/stocks/finnhub/quote.go
Normal file
12
modules/stocks/finnhub/quote.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package finnhub
|
||||
|
||||
type Quote struct {
|
||||
C float64 `json:"c"`
|
||||
H float64 `json:"h"`
|
||||
L float64 `json:"l"`
|
||||
O float64 `json:"o"`
|
||||
Pc float64 `json:"pc"`
|
||||
T int `json:"t"`
|
||||
|
||||
Stock string
|
||||
}
|
||||
37
modules/stocks/finnhub/settings.go
Normal file
37
modules/stocks/finnhub/settings.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package finnhub
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/olebedev/config"
|
||||
"github.com/wtfutil/wtf/cfg"
|
||||
"github.com/wtfutil/wtf/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultFocusable = true
|
||||
defaultTitle = "📈 Stocks Price"
|
||||
)
|
||||
|
||||
// Settings defines the configuration properties for this module
|
||||
type Settings struct {
|
||||
*cfg.Common
|
||||
|
||||
apiKey string `help:"Your finnhub API token."`
|
||||
symbols []string `help:"An array of stocks symbols (i.e. AAPL, MSFT)"`
|
||||
}
|
||||
|
||||
// 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),
|
||||
|
||||
apiKey: ymlConfig.UString("apiKey", ymlConfig.UString("apikey", os.Getenv("WTF_FINNHUB_API_KEY"))),
|
||||
symbols: utils.ToStrs(ymlConfig.UList("symbols")),
|
||||
}
|
||||
|
||||
cfg.ModuleSecret(name, globalConfig, &settings.apiKey).Load()
|
||||
|
||||
return &settings
|
||||
}
|
||||
61
modules/stocks/finnhub/widget.go
Normal file
61
modules/stocks/finnhub/widget.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package finnhub
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/rivo/tview"
|
||||
"github.com/wtfutil/wtf/view"
|
||||
)
|
||||
|
||||
// Widget ..
|
||||
type Widget struct {
|
||||
view.TextWidget
|
||||
*Client
|
||||
|
||||
settings *Settings
|
||||
}
|
||||
|
||||
// NewWidget ..
|
||||
func NewWidget(tviewApp *tview.Application, settings *Settings) *Widget {
|
||||
widget := Widget{
|
||||
Client: NewClient(settings.symbols, settings.apiKey),
|
||||
TextWidget: view.NewTextWidget(tviewApp, nil, settings.Common),
|
||||
|
||||
settings: settings,
|
||||
}
|
||||
|
||||
return &widget
|
||||
}
|
||||
|
||||
/* -------------------- Exported Functions -------------------- */
|
||||
|
||||
func (widget *Widget) Refresh() {
|
||||
if widget.Disabled() {
|
||||
return
|
||||
}
|
||||
|
||||
widget.Redraw(widget.content)
|
||||
}
|
||||
|
||||
/* -------------------- Unexported Functions -------------------- */
|
||||
|
||||
func (widget *Widget) content() (string, string, bool) {
|
||||
quotes, err := widget.Client.Getquote()
|
||||
|
||||
title := widget.CommonSettings().Title
|
||||
t := table.NewWriter()
|
||||
t.AppendHeader(table.Row{"#", "Stock", "Current Price", "Open Price", "Change"})
|
||||
wrap := false
|
||||
if err != nil {
|
||||
wrap = true
|
||||
} else {
|
||||
for idx, q := range quotes {
|
||||
t.AppendRows([]table.Row{
|
||||
{idx, q.Stock, q.C, q.O, fmt.Sprintf("%.4f", (q.C-q.O)/q.C)},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return title, t.Render(), wrap
|
||||
}
|
||||
Reference in New Issue
Block a user