diff --git a/Gopkg.lock b/Gopkg.lock index bb9fe4d..3c315df 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -38,10 +38,10 @@ version = "v0.0.3" [[projects]] - branch = "master" name = "github.com/mitchellh/go-wordwrap" packages = ["."] - revision = "ad45545899c7b13c020ea92b2072220eefad42b8" + revision = "9e67c67572bc5dd02aef930e2b0ae3c02a4b5a5c" + version = "v1.0.0" [[projects]] branch = "master" @@ -60,6 +60,7 @@ name = "github.com/piquette/finance-go" packages = [ ".", + "chart", "crypto", "datetime", "equity", @@ -74,7 +75,7 @@ "options", "quote" ] - revision = "d2ada33030039a9d9fc00d4bff89323cc2105614" + revision = "1cd0201341aa94a2084bc293c78400c088f86476" [[projects]] name = "github.com/pmezard/go-difflib" @@ -109,6 +110,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "27a4134fb694ce7e686979b5368882108c686b65769b99b993b81bf83784d474" + inputs-digest = "9ddeb763d6c8ab33160292dd7ad5c06c71e57f4f5ffd6cf2d9999be78402a98d" solver-name = "gps-cdcl" solver-version = 1 diff --git a/README.md b/README.md index b3e9007..19d41ba 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,13 @@ # :zap: qtrn :zap: The official cli tool for making financial markets analysis as fast as you are. +Pronounced "quote-tron" as a throwback to those awesome financial terminals of the 80's. This project is intended as a living example of the capabilities of the [finance-go] library. + ## Commands The current available commands are: * `quote` - prints tables of quotes to the current shell +* `options` - prints tables of options contract quotes to the current shell +* `write` - writes tables of quotes/history to csv files ## Installation In order to use this awesome tool, you'll need to get it on your machine! @@ -25,7 +29,7 @@ brew install qtrn 2. Determine the appropriate distribution for your operating system (mac | windows | linux) 3. Download and untar the distribution. Shortcut for macs: ``` -curl -sL https://github.com/piquette/qtrn/releases/download/v0.0.3/qtrn_0.0.3_darwin_amd64.tar.gz | tar zx +curl -sL https://github.com/piquette/qtrn/releases/download/v0.0.5/qtrn_0.0.5_darwin_amd64.tar.gz | tar zx ``` 4. Move the binary into your local `$PATH`. 5. Run `qtrn help`. @@ -78,3 +82,4 @@ goreleaser --rm-dist [goreleaser]: https://github.com/goreleaser/goreleaser [releases]: https://github.com/piquette/qtrn/releases +[finance-go]: https://github.com/piquette/finance-go diff --git a/cmd/main.go b/cmd/main.go index bd1efce..996e75e 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -21,6 +21,7 @@ import ( finance "github.com/piquette/finance-go" "github.com/piquette/qtrn/cmd/options" quote "github.com/piquette/qtrn/cmd/quote" + "github.com/piquette/qtrn/cmd/write" "github.com/piquette/qtrn/version" "github.com/spf13/cobra" @@ -57,7 +58,7 @@ func Execute() error { } // cmdQtrn.AddCommand(chartCmd) - // cmdQtrn.AddCommand(writeCmd) + c.AddCommand(write.Cmd) c.AddCommand(quote.Cmd) c.AddCommand(options.Cmd) c.PersistentFlags().BoolVarP(&insecureF, "insecure", "x", false, "set `--insecure` or `-x` to skip tls verification during requests") diff --git a/cmd/write/csv.go b/cmd/write/csv.go new file mode 100644 index 0000000..e7a5c84 --- /dev/null +++ b/cmd/write/csv.go @@ -0,0 +1,41 @@ +package write + +import ( + "encoding/csv" + "fmt" + "os" + "time" +) + +// write writes data to a csv. +func write(header []string, prefix string, cmd string, data [][]string) error { + t := time.Now() + now := fmt.Sprintf("%d-%02d-%02d_%02d:%02d:%02d", + t.Year(), t.Month(), t.Day(), + t.Hour(), t.Minute(), t.Second()) + fileTitle := fmt.Sprintf("%v_%v_%v.csv", prefix, cmd, now) + file, err := os.Create(fileTitle) + if err != nil { + return err + } + defer file.Close() + + writer := csv.NewWriter(file) + defer writer.Flush() + + if !removeHeaderF { + err = writer.Write(header) + if err != nil { + return err + } + } + + for _, value := range data { + err = writer.Write(value) + if err != nil { + return err + } + } + fmt.Println(fileTitle) + return nil +} diff --git a/cmd/write/write.go b/cmd/write/write.go index 58cb96d..4a3f958 100644 --- a/cmd/write/write.go +++ b/cmd/write/write.go @@ -12,270 +12,216 @@ // See the License for the specific language governing permissions and // limitations under the License. -package cmd +package write -// -// import ( -// "encoding/csv" -// "fmt" -// "os" -// "time" -// -// finance "github.com/piquette/go-finance" -// "github.com/spf13/cobra" -// ) -// -// const ( -// writeUsage = "write [subcommand]" -// writeShortDesc = "Writes a csv of stock market data" -// writeLongDesc = "Writes a csv of stock market data into the current directory using a subcommand `quote` for quotes or `history` for historical prices" -// writeQuoteShortDesc = "Writes a csv of a stock quote" -// writeQuoteLongDesc = "Writes a csv of a stock quote and can accomodate multiple symbols as arguments" -// writeHistoryShortDesc = "Writes a csv of a historical data" -// writeHistoryLongDesc = "Writes a csv of a historical data, can only accept one symbol at a time" -// ) -// -// var ( -// // write command. -// writeCmd = &cobra.Command{ -// Use: writeUsage, -// Short: writeShortDesc, -// Long: writeLongDesc, -// Aliases: []string{"w"}, -// Example: "$ qtrn write -h quote -f AAPL GOOG FB AMZN", -// Run: func(cmd *cobra.Command, args []string) { -// // Stub. -// fmt.Printf("\nSubcommand not specified, use either ( quote | history )\n\n") -// }, -// } -// writeQuoteCmd = &cobra.Command{ -// Use: "quote", -// Short: writeQuoteShortDesc, -// Long: writeQuoteLongDesc, -// Aliases: []string{"q"}, -// Example: "$ qtrn write quote AAPL", -// Run: writeQuoteFunc, -// } -// writeHistoryCmd = &cobra.Command{ -// Use: "history", -// Short: writeHistoryShortDesc, -// Long: writeHistoryLongDesc, -// Aliases: []string{"h"}, -// Example: "$ qtrn write history", -// Run: writeHistoryFunc, -// } -// // flagRemoveHeader set flag to specify whether to remove the header in the file. -// flagRemoveHeader bool -// // flagFullOutput set flag to write a more informative quote. -// flagWriteFullOutput bool -// // flagStartTime set flag to specify the start time of the csv frame. -// flagWriteStartTime string -// // flagEndTime set flag to specify the end time of the csv frame. -// flagWriteEndTime string -// // flagInterval set flag to specify time interval of each OHLC point. -// flagWriteInterval string -// //historyHeader csv header for history results. -// historyHeader = []string{"date", "open", "high", "low", "close", "adj-close", "volume", "symbol"} -// //quoteHeader csv header for quote results. -// quoteHeader = []string{"symbol", "date", "last", "change", "change-percent", "volume", "bid", "bid-size", "ask", "ask-size", "open", "prev-close", "name"} -// //quoteFullHeader csv header for quote results. -// quoteFullHeader = []string{"symbol", "date", "name", "last", "change", "change-percent", "volume", "bid", "bid-size", -// "ask", "ask-size", "open", "prev-close", "exchange", "day-high", "day-low", "52wk-high", "52wk-low", "mkt-cap", -// "50d-ma", "200d-ma", "avg-volume", "eps", "pe-ratio", "peg-ratio", "price-sales", "price-book", "div-per-share", -// "div-yield", "eps-est-next-qtr", "eps-est-year", "short-ratio", "book-value", "ebitda"} -// ) -// -// func init() { -// writeCmd.AddCommand(writeQuoteCmd) -// writeCmd.AddCommand(writeHistoryCmd) -// writeCmd.Flags().BoolVarP(&flagRemoveHeader, "remove", "r", false, "Set `--remove` or `-r` to remove the header in the csv. Default is FALSE.") -// writeQuoteCmd.Flags().BoolVarP(&flagWriteFullOutput, "full", "f", false, "Set `--full` or `-f` to write a more informative quote for each symbol") -// writeHistoryCmd.Flags().StringVarP(&flagWriteStartTime, "start", "s", "", "Set a date (formatted YYYY-MM-DD) using `--start` or `-s` to specify the start of the csv time frame") -// writeHistoryCmd.Flags().StringVarP(&flagWriteEndTime, "end", "e", "", "Set a date (formatted YYYY-MM-DD) using `--start` or `-s` to specify the start of the csv time frame") -// writeHistoryCmd.Flags().StringVarP(&flagWriteInterval, "interval", "i", finance.Day, "Set an interval ( 1d | 1wk | 1mo ) using `--interval` or `-i` to specify the time interval of each OHLC point") -// } -// -// // writeQuoteFunc implements the quote write command. -// func writeQuoteFunc(cmd *cobra.Command, args []string) { -// -// symbols := args[:] -// if len(symbols) == 0 { -// fmt.Println("No symbols provided.") -// return -// } -// -// quotes, err := finance.GetQuotes(symbols) -// if err != nil { -// fmt.Println("Error fetching data, please try again") -// return -// } -// -// data := formatQuoteData(quotes) -// err = writeData(headerForQuote(), combine(symbols, "+"), "quote", data) -// if err != nil { -// fmt.Println("Error writing data") -// } -// } -// -// // writeHistoryFunc implements the history write command. -// func writeHistoryFunc(cmd *cobra.Command, args []string) { -// -// symbol := args[0] -// if symbol == "" { -// fmt.Println("No symbol provided.") -// return -// } -// -// var start finance.Datetime -// var end finance.Datetime -// if flagWriteStartTime == "" { -// start = finance.ParseDatetime(fmt.Sprintf("%v-01-01", time.Now().Year())) -// } else { -// start = finance.ParseDatetime(flagWriteStartTime) -// } -// if flagWriteEndTime == "" { -// t := time.Now() -// end = finance.ParseDatetime(fmt.Sprintf("%d-%02d-%02d", t.Year(), int(t.Month()), t.Day())) -// } else { -// end = finance.ParseDatetime(flagWriteEndTime) -// } -// -// bars, err := finance.GetHistory(symbol, start, end, finance.Interval(flagWriteInterval)) -// if err != nil { -// fmt.Println("Error fetching data, please try again") -// return -// } -// -// data := formatHistoricalData(bars) -// err = writeData(historyHeader, symbol, "history", data) -// if err != nil { -// fmt.Println("Error writing data") -// } -// } -// -// // writeData writes the downloaded and formatted data to a csv. -// func writeData(header []string, prefix string, cmd string, data [][]string) error { -// -// t := time.Now() -// now := fmt.Sprintf("%d-%02d-%02dT%02d:%02d:%02d-00:00", -// t.Year(), t.Month(), t.Day(), -// t.Hour(), t.Minute(), t.Second()) -// fileTitle := fmt.Sprintf("%v_%v_%v.csv", prefix, cmd, now) -// file, err := os.Create(fileTitle) -// if err != nil { -// return err -// } -// defer file.Close() -// -// writer := csv.NewWriter(file) -// defer writer.Flush() -// -// if !flagRemoveHeader { -// err = writer.Write(header) -// if err != nil { -// return err -// } -// } -// -// for _, value := range data { -// err = writer.Write(value) -// if err != nil { -// return err -// } -// } -// fmt.Println(fileTitle) -// return nil -// } -// -// // formatHistoricalData formats a slice of historical prices into a writeable format. -// func formatHistoricalData(bars []finance.Bar) (data [][]string) { -// for _, b := range bars { -// date := fmt.Sprintf("%v-%v-%v", b.Date.Year, b.Date.Month, b.Date.Day) -// point := []string{ -// date, -// b.Open.StringFixed(2), -// b.High.StringFixed(2), -// b.Low.StringFixed(2), -// b.Close.StringFixed(2), -// b.AdjClose.StringFixed(2), -// toString(b.Volume), -// b.Symbol, -// } -// data = append(data, point) -// } -// return -// } -// -// // formatQuoteData formats a slice of quotes into a writeable format. -// func formatQuoteData(quotes []finance.Quote) (data [][]string) { -// for _, q := range quotes { -// -// var formattedQuote []string -// date := fmt.Sprintf("%v-%v-%v", q.LastTradeDate.Year, q.LastTradeDate.Month, q.LastTradeDate.Day) -// if flagWriteFullOutput { -// formattedQuote = []string{ -// q.Symbol, -// date, -// q.Name, -// q.LastTradePrice.StringFixed(2), -// q.ChangeNominal.StringFixed(2), -// q.ChangePercent.StringFixed(2), -// toString(q.Volume), -// q.Bid.StringFixed(2), -// toString(q.BidSize), -// q.Ask.StringFixed(2), -// toString(q.AskSize), -// q.Open.StringFixed(2), -// q.PreviousClose.StringFixed(2), -// q.Exchange, -// q.DayHigh.StringFixed(2), -// q.DayLow.StringFixed(2), -// q.FiftyTwoWeekHigh.StringFixed(2), -// q.FiftyTwoWeekLow.StringFixed(2), -// q.MarketCap, -// q.FiftyDayMA.StringFixed(2), -// q.TwoHundredDayMA.StringFixed(2), -// toString(q.AvgDailyVolume), -// q.EPS.StringFixed(2), -// q.PERatio.StringFixed(2), -// q.PEGRatio.StringFixed(2), -// q.PriceSales.StringFixed(2), -// q.PriceBook.StringFixed(2), -// q.DivPerShare.StringFixed(2), -// q.DivYield.StringFixed(2), -// q.EPSEstNextQuarter.StringFixed(2), -// q.EPSEstCurrentYear.StringFixed(2), -// q.ShortRatio.String(), -// q.BookValue.String(), -// q.EBITDA, -// } -// } else { -// formattedQuote = []string{ -// q.Symbol, -// date, -// q.LastTradePrice.StringFixed(2), -// q.ChangeNominal.StringFixed(2), -// q.ChangePercent.StringFixed(2), -// toString(q.Volume), -// q.Bid.StringFixed(2), -// toString(q.BidSize), -// q.Ask.StringFixed(2), -// toString(q.AskSize), -// q.Open.StringFixed(2), -// q.PreviousClose.StringFixed(2), -// q.Name, -// } -// } -// data = append(data, formattedQuote) -// } -// -// return -// } -// -// // headerForQuote determines which header to write. -// func headerForQuote() []string { -// if flagWriteFullOutput { -// return quoteFullHeader -// } -// return quoteHeader -// } +import ( + "fmt" + "strings" + "time" + + "github.com/piquette/finance-go/chart" + "github.com/piquette/finance-go/datetime" + "github.com/piquette/qtrn/utils" + + "github.com/piquette/finance-go/quote" + + "github.com/spf13/cobra" +) + +const ( + usage = "write" + short = "Writes a csv of stock market data" + long = "Writes a csv of stock market data into the current directory." + quoteShort = "Writes a csv of a stock quote" + quoteLong = "Writes a csv of a stock quote and can accomodate multiple symbols as arguments" + historyShort = "Writes a csv of a historical data" + historyLong = "Writes a csv of a historical data, can only accept one symbol at a time" +) + +var ( + // Cmd is the write command. + Cmd = &cobra.Command{ + Use: usage, + Short: short, + Long: long, + Aliases: []string{"w"}, + Example: "qtrn write -r quote -f AAPL GOOG FB AMZN", + } + // quote subcommand + quoteCmd = &cobra.Command{ + Use: "quote", + Short: quoteShort, + Long: quoteLong, + Example: "qtrn write quote AAPL", + RunE: executeQuote, + } + // history subcommand + historyCmd = &cobra.Command{ + Use: "history", + Short: historyShort, + Long: historyLong, + Example: "qtrn write history [flags]", + RunE: executeHistory, + } + // removeHeaderF set flag to specify whether to remove the header in the file. + removeHeaderF bool + // startF set flag to specify the start time of the csv frame. + startF string + // endF set flag to specify the end time of the csv frame. + endF string + // aggregationF set flag to specify time interval of each OHLC point. + aggregationF string + //historyFields csv header for history results. + historyFields = []string{"date", "open", "high", "low", "close", "adj-close", "volume", "symbol"} + //quoteFields csv header for quote results. + quoteFields = []string{"symbol", "date", "last", "change", "change-percent", "volume", "bid", "bid-size", "ask", "ask-size", "open", "prev-close", "name"} +) + +func init() { + Cmd.AddCommand(quoteCmd) + Cmd.AddCommand(historyCmd) + Cmd.Flags().BoolVarP(&removeHeaderF, "remove", "r", false, "remove the header in the csv. default is false.") + historyCmd.Flags().StringVarP(&startF, "start", "s", "", "set a date (formatted yyyy-mm-dd) to specify the start of the historical time frame") + historyCmd.Flags().StringVarP(&endF, "end", "e", "", "set a date (formatted yyyy-mm-dd) to specify the end of the historical time frame") + historyCmd.Flags().StringVarP(&aggregationF, "agg", "a", "1d", "set a candle aggregation interval ( 1d | 5d | 1mo | 1y )") +} + +// executeQuote implements the quote data writer. +func executeQuote(cmd *cobra.Command, args []string) error { + // check symbols. + symbols := args + if len(symbols) == 0 { + return fmt.Errorf("no symbols provided") + } + + // get quote data. + i := quote.List(symbols) + + // format as strings. + quotes, err := format(i) + if err != nil { + return err + } + + // write csv. + err = write(quoteFields, strings.Join(symbols, "_"), "quote", quotes) + if err != nil { + return fmt.Errorf("error writing data") + } + + return nil +} + +// executeHistory implements the history data writer. +func executeHistory(cmd *cobra.Command, args []string) error { + // check symbols. + symbols := args + if len(symbols) == 0 { + return fmt.Errorf("no symbols provided") + } + + // format params. + // start. + var start *datetime.Datetime + if startF == "" { + // provide default start time of YTD. + start = &datetime.Datetime{Month: 1, Day: 1, Year: time.Now().Year()} + } else { + // parse start time. + dt, err := time.Parse("2006-01-02", startF) + if err != nil { + return fmt.Errorf("could not parse start time- correct format is yyyy-mm-dd") + } + start = datetime.New(&dt) + } + // end. + var end *datetime.Datetime + if endF == "" { + t := time.Now() + end = datetime.New(&t) + } else { + // parse start time. + dt, err := time.Parse("2006-01-02", endF) + if err != nil { + return fmt.Errorf("could not parse end time- correct format is yyyy-mm-dd") + } + end = datetime.New(&dt) + } + + // validate aggregation periods. + if aggregationF != "1d" && aggregationF != "5d" && aggregationF != "1mo" && aggregationF != "1y" { + return fmt.Errorf("invalid aggregation period") + } + + p := &chart.Params{ + Symbol: symbols[0], + Start: start, + End: end, + Interval: datetime.Interval(aggregationF), + } + + // get chart. + iter := chart.Get(p) + + // format. + chartdata, err := formatC(iter) + if err != nil { + return err + } + + // write. + err = write(historyFields, symbols[0], "history", chartdata) + if err != nil { + fmt.Println("error writing data") + } + + return nil +} + +// format formats quotes into a writeable format. +func format(iter *quote.Iter) (data [][]string, err error) { + for iter.Next() { + q := iter.Quote() + d := datetime.FromUnix(q.RegularMarketTime) + date := fmt.Sprintf("%02d-%02d-%02d", d.Year, d.Month, d.Day) + fq := []string{ + q.Symbol, + date, + utils.ToStringF(q.RegularMarketPrice), + utils.ToStringF(q.RegularMarketChange), + utils.ToStringF(q.RegularMarketChangePercent), + utils.ToString(q.RegularMarketVolume), + utils.ToStringF(q.Bid), + utils.ToString(q.BidSize), + utils.ToStringF(q.Ask), + utils.ToString(q.AskSize), + utils.ToStringF(q.RegularMarketOpen), + utils.ToStringF(q.RegularMarketPreviousClose), + q.ShortName, + } + data = append(data, fq) + } + return data, iter.Err() +} + +// formatC formats chart data into a writeable format. +func formatC(iter *chart.Iter) (data [][]string, err error) { + for iter.Next() { + b := iter.Bar() + d := datetime.FromUnix(b.Timestamp) + date := fmt.Sprintf("%02d-%02d-%02d", d.Year, d.Month, d.Day) + p := []string{ + date, + b.Open.StringFixed(2), + b.High.StringFixed(2), + b.Low.StringFixed(2), + b.Close.StringFixed(2), + b.AdjClose.StringFixed(2), + utils.ToString(b.Volume), + iter.Meta().Symbol, + } + fmt.Println(data) + data = append(data, p) + } + return data, iter.Err() +} diff --git a/goreleaser.yml b/goreleaser.yml index fc972e3..9dbfe53 100644 --- a/goreleaser.yml +++ b/goreleaser.yml @@ -1,7 +1,7 @@ build: binary: qtrn ldflags: - - -s -w -X version.Version={{.Version}} + - -s -X github.com/piquette/qtrn/version.Version={{.Version}} goos: - windows - darwin diff --git a/vendor/github.com/mitchellh/go-wordwrap/go.mod b/vendor/github.com/mitchellh/go-wordwrap/go.mod new file mode 100644 index 0000000..2ae411b --- /dev/null +++ b/vendor/github.com/mitchellh/go-wordwrap/go.mod @@ -0,0 +1 @@ +module github.com/mitchellh/go-wordwrap diff --git a/vendor/github.com/piquette/finance-go/.travis.yml b/vendor/github.com/piquette/finance-go/.travis.yml index 861dad8..5c41184 100644 --- a/vendor/github.com/piquette/finance-go/.travis.yml +++ b/vendor/github.com/piquette/finance-go/.travis.yml @@ -24,7 +24,6 @@ env: - FINANCE_MOCK_VERSION=0.0.5 go: - - "1.9" - "1.10" - tip diff --git a/vendor/github.com/piquette/finance-go/chart/client.go b/vendor/github.com/piquette/finance-go/chart/client.go new file mode 100644 index 0000000..db48493 --- /dev/null +++ b/vendor/github.com/piquette/finance-go/chart/client.go @@ -0,0 +1,180 @@ +package chart + +import ( + "context" + + finance "github.com/piquette/finance-go" + "github.com/piquette/finance-go/datetime" + form "github.com/piquette/finance-go/form" + "github.com/piquette/finance-go/iter" + "github.com/shopspring/decimal" +) + +// Client is used to invoke chart APIs. +type Client struct { + B finance.Backend +} + +func getC() Client { + return Client{finance.GetBackend(finance.YFinBackend)} +} + +// Params carries a context and chart information. +type Params struct { + // Context access. + finance.Params `form:"-"` + + // Accessible fields. + Symbol string `form:"-"` + Start *datetime.Datetime `form:"-"` + End *datetime.Datetime `form:"-"` + Interval datetime.Interval `form:"-"` + + IncludeExt bool `form:"includePrePost"` + + // Internal request fields. + interval string `form:"interval"` + start int `form:"period1"` + end int `form:"period2"` +} + +// Iter is a structure containing results +// and related metadata for a +// yfin chart request. +type Iter struct { + *iter.Iter +} + +// Bar returns the next Bar +// visited by a call to Next. +func (i *Iter) Bar() *finance.ChartBar { + return i.Current().(*finance.ChartBar) +} + +// Meta returns the chart metadata +// related to a chart response. +func (i *Iter) Meta() finance.ChartMeta { + return i.Iter.Meta().(finance.ChartMeta) +} + +// Get returns a historical chart. +// and requires a params +// struct as an argument. +func Get(params *Params) *Iter { + return getC().Get(params) +} + +// Get returns a historical chart. +func (c Client) Get(params *Params) *Iter { + + // Construct request from params input. + // TODO: validate symbol.. + if params == nil || len(params.Symbol) == 0 { + return &Iter{iter.NewE(finance.CreateArgumentError())} + } + + if params.Context == nil { + ctx := context.TODO() + params.Context = &ctx + } + + // Start and End times + params.start = -1 + params.end = -1 + if params.Start != nil { + params.start = params.Start.Unix() + } + if params.End != nil { + params.end = params.End.Unix() + } + if params.start > params.end { + return &Iter{iter.NewE(finance.CreateChartTimeError())} + } + + // Parse interval. + if params.Interval != "" { + params.interval = string(params.Interval) + } + + // Build request. + body := &form.Values{} + form.AppendTo(body, params) + // Set request meta data. + body.Set("region", "US") + body.Set("corsDomain", "com.finance.yahoo") + + return &Iter{iter.New(body, func(b *form.Values) (m interface{}, bars []interface{}, err error) { + + resp := response{} + err = c.B.Call("v8/finance/chart/"+params.Symbol, body, params.Context, &resp) + if err != nil { + return + } + + if resp.Inner.Error != nil { + err = resp.Inner.Error + return + } + + result := resp.Inner.Results[0] + if result == nil || result.Indicators == nil { + err = finance.CreateRemoteErrorS("no results in chart response") + return + } + + barQuotes := result.Indicators.Quote + if barQuotes == nil || barQuotes[0] == nil { + err = finance.CreateRemoteErrorS("no results in chart response") + return + } + adjCloses := result.Indicators.Adjclose + + // Process chart response + // and chart meta data. + for i, t := range result.Timestamp { + + b := &finance.ChartBar{ + Timestamp: t, + Open: decimal.NewFromFloat(barQuotes[0].Open[i]), + High: decimal.NewFromFloat(barQuotes[0].High[i]), + Low: decimal.NewFromFloat(barQuotes[0].Low[i]), + Close: decimal.NewFromFloat(barQuotes[0].Close[i]), + Volume: barQuotes[0].Volume[i], + } + + if adjCloses != nil && adjCloses[0] != nil { + b.AdjClose = decimal.NewFromFloat(adjCloses[0].Adjclose[i]) + } + + bars = append(bars, b) + } + + return result.Meta, bars, nil + })} +} + +// response is a yfin chart response. +type response struct { + Inner struct { + Results []*result `json:"result"` + Error *finance.YfinError `json:"error"` + } `json:"chart"` +} + +// result is an umbrella object for chart results. +type result struct { + Meta finance.ChartMeta `json:"meta"` + Timestamp []int `json:"timestamp"` + Indicators *struct { + Quote []*struct { + Open []float64 `json:"open"` + Low []float64 `json:"low"` + High []float64 `json:"high"` + Close []float64 `json:"close"` + Volume []int `json:"volume"` + } `json:"quote"` + Adjclose []*struct { + Adjclose []float64 `json:"adjclose"` + } `json:"adjclose"` + } `json:"indicators"` +}