Skip to content

Commit

Permalink
Adds dividends and refactors baseURL
Browse files Browse the repository at this point in the history
  • Loading branch information
covertbert committed Nov 8, 2018
1 parent 7d94651 commit a265619
Show file tree
Hide file tree
Showing 5 changed files with 338 additions and 4 deletions.
17 changes: 16 additions & 1 deletion commands/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,28 @@ var CliCommands = []cli.Command{
},
{
Name: "delayed",
Aliases: []string{"d"},
Aliases: []string{"de"},
Usage: "View a stock's 15 minute delayed market quote",
Action: func(c *cli.Context) error {
stock.QueryDelayed(c.Args().First())
return nil
},
},
{
Name: "dividends",
Aliases: []string{"di"},
Usage: "View a stock's dividends",
Action: func(c *cli.Context) error {
stock.QueryDividends(c.Args().First(), c.String("range"))
return nil
},
Flags: []cli.Flag{
cli.StringFlag{
Name: "range",
Usage: "Chart range - options: 5y, 2y, 1y, ytd, 6m, 3m, 1m",
},
},
},
{
Name: "ipo",
Aliases: []string{"i"},
Expand Down
2 changes: 1 addition & 1 deletion iex/iex.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"github.com/covertbert/iex-cli/errors"
)

const baseURL = "https://api.iextrading.com/1.0/"
const baseURL = "https://api.iextrading.com/1.0"

var httpClient = &http.Client{Timeout: 10 * time.Second}

Expand Down
156 changes: 156 additions & 0 deletions stock/dividends.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package stock

import (
"encoding/json"
"fmt"
"strings"

e "errors"

"github.com/covertbert/iex-cli/errors"
"github.com/covertbert/iex-cli/iex"
ui "github.com/gizak/termui"
)

// Dividends structure
type Dividends []struct {
ExDate string `json:"exDate"`
PaymentDate string `json:"paymentDate"`
RecordDate string `json:"recordDate"`
DeclaredDate string `json:"declaredDate"`
Amount float64 `json:"amount"`
Flag string `json:"flag"`
Type string `json:"type"`
Qualified string `json:"qualified"`
Indicated string `json:"indicated"`
}

// QueryDividends displays live dividends information
func QueryDividends(symbol string, rng string) {
if noSymbol := len(symbol) < 1; noSymbol {
errors.Error("No argument supplied")
}

d := &Dividends{}

path, err := divQueryPath(symbol, rng)

if err != nil {
errors.Error("Incorrect arguments passed to dividends command", err)
}

body := iex.Query(path)

if err := json.Unmarshal(body, &d); err != nil {
errors.Error("Failed to unmarshal", err)
}

if noData := len(*d) < 1; noData {
errors.Error("Currently no data for the specified range")
}

if err := ui.Init(); err != nil {
errors.Error("Failed to initialize graph", err)
}

defer ui.Close()

h := divChartHeader(symbol, rng)
ld := dividendsData(*d)
p := dividendsInformation()

ui.Body.AddRows(
ui.NewRow(ui.NewCol(10, 0, h)),
ui.NewRow(
ui.NewCol(10, 0, ld),
),
ui.NewRow(ui.NewCol(10, 0, p)),
)

ui.Body.Align()

ui.Render(ui.Body)

ui.Handle("/sys/kbd/q", func(ui.Event) {
ui.StopLoop()
})

ui.Loop()
}

func divQueryPath(symbol string, rng string) (string, error) {
allowedRanges := []string{"5y", "2y", "1y", "ytd", "6m", "3m", "1m", "1d", ""}

for _, allowedRng := range allowedRanges {
if rng == allowedRng {
return fmt.Sprintf("/stock/%v/dividends/%v", symbol, rng), nil
}
}

return "", e.New("Range is not valid. Run iex-cli dividends --range for help")
}

func divChartHeader(symbol string, rng string) *ui.Par {
if noRangeSpecified := len(rng) == 0; noRangeSpecified {
rng = "default"
}

headerString := fmt.Sprintf("Range: %v", rng)

i := ui.NewPar(fmt.Sprintf("%v", headerString))
i.Height = 3
i.Width = 100
i.TextFgColor = ui.ColorWhite
i.BorderLabel = strings.ToUpper(symbol)
i.BorderFg = ui.ColorWhite

return i
}

func dividendsInformation() *ui.Par {
i := ui.NewPar(":PRESS q TO QUIT")
i.Height = 3
i.Width = 100
i.TextFgColor = ui.ColorWhite
i.BorderLabel = "Keyboard shortcuts"
i.BorderFg = ui.ColorWhite

return i
}

func dividendsData(d Dividends) *ui.LineChart {
p := []float64{}

for _, element := range d {
p = append(p, element.Amount)
p = append(p, element.Amount)
p = append(p, element.Amount)
}

lc := ui.NewLineChart()
lc.BorderLabel = "Dividends"
lc.BorderFg = ui.ColorWhite
lc.Data = p
lc.DataLabels = divDataLabels(d)
lc.Height = 15
lc.X = 0
lc.Y = 0
lc.AxesColor = ui.ColorWhite
lc.LineColor = ui.ColorYellow
lc.Mode = "dot"

return lc
}

func divDataLabels(d Dividends) []string {
p := []string{}

for _, element := range d {
l := element.ExDate
p = append(p, l)
p = append(p, l)
p = append(p, l)
}

return p
}
163 changes: 163 additions & 0 deletions stock/dividends_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
package stock

import (
"reflect"
"testing"

"github.com/rendon/testcli"
)

var testDividends = Dividends{
{
Amount: 1,
ExDate: "2018-11-05",
},
{
Amount: 2,
ExDate: "2018-11-05",
},
{
Amount: 3,
ExDate: "2018-11-05",
},
}

func Test_divDataLabels(t *testing.T) {
type args struct {
c Dividends
}
tests := []struct {
name string
args args
want []string
}{
{
name: "Data labels",
args: args{
c: testDividends,
},
want: []string{"2018-11-05", "2018-11-05", "2018-11-05", "2018-11-05", "2018-11-05", "2018-11-05", "2018-11-05", "2018-11-05", "2018-11-05"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := divDataLabels(tt.args.c); !reflect.DeepEqual(got, tt.want) {
t.Errorf("divDataLabels() = %v, want %v", got, tt.want)
}
})
}
}

func Test_divQueryPath(t *testing.T) {
type args struct {
symbol string
rng string
}
tests := []struct {
name string
args args
want string
wantErr bool
}{
{
name: "chicken",
args: args{
rng: "chicken",
},
want: "",
wantErr: true,
},
{
name: "5yy",
args: args{
rng: "5yy",
},
want: "",
wantErr: true,
},
{
name: "5y",
args: args{
rng: "5y",
},
want: "/stock//dividends/5y",
wantErr: false,
},
{
name: "2y",
args: args{
rng: "2y",
},
want: "/stock//dividends/2y",
wantErr: false,
},
{
name: "1y",
args: args{
rng: "1y",
},
want: "/stock//dividends/1y",
wantErr: false,
},
{
name: "ytd",
args: args{
rng: "ytd",
},
want: "/stock//dividends/ytd",
wantErr: false,
},
{
name: "6m",
args: args{
rng: "6m",
},
want: "/stock//dividends/6m",
wantErr: false,
},
{
name: "3m",
args: args{
rng: "3m",
},
want: "/stock//dividends/3m",
wantErr: false,
},
{
name: "1m",
args: args{
rng: "1m",
},
want: "/stock//dividends/1m",
wantErr: false,
},
{
name: "1d",
args: args{
rng: "1d",
},
want: "/stock//dividends/1d",
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := divQueryPath(tt.args.symbol, tt.args.rng)
if (err != nil) != tt.wantErr {
t.Errorf("divQueryPath() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("divQueryPath() = %v, want %v", got, tt.want)
}
})
}
}

func TestDividendsNoArgs(t *testing.T) {
testcli.Run("../iex-cli", "dividends")

if !testcli.StdoutContains("Error: No argument supplied") {
t.Fatalf("Expected %q to contain %q", testcli.Stdout(), "Error: No argument supplied")
}
}
4 changes: 2 additions & 2 deletions stock/news.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ func QueryNews(symbol string) {
var body []byte
n := &News{}

body = iex.Query("stock/market/news/last/5")
body = iex.Query("/stock/market/news/last/5")

if hasSymbol := len(symbol) > 1; hasSymbol {
body = iex.Query("stock/" + symbol + "/news/last/5")
body = iex.Query("/stock/" + symbol + "/news/last/5")
}

err := json.Unmarshal(body, &n)
Expand Down

0 comments on commit a265619

Please sign in to comment.