diff --git a/Gopkg.lock b/Gopkg.lock deleted file mode 100644 index 10ef811..0000000 --- a/Gopkg.lock +++ /dev/null @@ -1,9 +0,0 @@ -# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. - - -[solve-meta] - analyzer-name = "dep" - analyzer-version = 1 - input-imports = [] - solver-name = "gps-cdcl" - solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml deleted file mode 100644 index d7072c2..0000000 --- a/Gopkg.toml +++ /dev/null @@ -1,30 +0,0 @@ -# Gopkg.toml example -# -# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html -# for detailed Gopkg.toml documentation. -# -# required = ["github.com/user/thing/cmd/thing"] -# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] -# -# [[constraint]] -# name = "github.com/user/project" -# version = "1.0.0" -# -# [[constraint]] -# name = "github.com/user/project2" -# branch = "dev" -# source = "github.com/myfork/project2" -# -# [[override]] -# name = "github.com/x/y" -# version = "2.4.0" -# -# [prune] -# non-go = false -# go-tests = true -# unused-packages = true - - -[prune] - go-tests = true - unused-packages = true diff --git a/README.md b/README.md index e6ab57c..a32a926 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,6 @@ This project is intended to be an API wrapper for the Ally Invest API written in The different endpoints that are currently covered by this API -### /accounts - -Get all account information for a user. ## Running This Project @@ -17,3 +14,11 @@ For now, just build this directory and run the executable. ### Requirements You must within the .env file define your Ally Invest credentials + +## Vision + +My vision is to build out an automated suite of tools for managing my portfolio in Ally Invest. I hope to have a software platform +where I can backtest any strategies I come +up with and an informational dashboard I can use to track the different symbols and quotes +which i care about. I would also love to make this software open source so that anyone can +use it. diff --git a/client.go b/client.go index e0caad5..875c371 100644 --- a/client.go +++ b/client.go @@ -8,7 +8,10 @@ import ( "io/ioutil" "log" "net/http" + "net/url" "os" + "strconv" + "strings" ) /* The trading endpoint for Ally */ @@ -66,6 +69,19 @@ func (c *AllyApi) getAndRead(path string) []byte { return raw } +/** +Build up our query string, then send it along. +*/ +func (c *AllyApi) getWithParameters(path string, values url.Values) []byte { + b := strings.Builder{} + for k, v := range values { + b.WriteString(fmt.Sprintf("?%s=%s", k, strings.Join(v, ","))) + } + + log.Printf("Path built: %s%s\n", path, b.String()) + return c.getAndRead(fmt.Sprintf("%s%s", path, b.String())) +} + /* The /accounts endpoint of Ally */ func (c *AllyApi) Accounts() []AccountSummary { var resp AccountResponse @@ -79,20 +95,71 @@ func (c *AllyApi) AccountBalances() (balances []AccountBalance) { return resp.AccountBalances } -func (c *AllyApi) AccountDetail(accountId string) AccountDetailResponse { - var resp AccountDetailResponse - _ = xml.Unmarshal(c.getAndRead(fmt.Sprintf("accounts/%s", accountId)), &resp) - return resp +/** +Return an object representing account detail of a given string +*/ +func (c *AllyApi) AccountDetail(accountId string) (resp AccountDetailResponse) { + c.marshalInterfaceResponse(fmt.Sprintf("accounts/%s", accountId), &resp) + return } -func (c *AllyApi) AccountBalance(accountId string) AccountDetailBalanceResponse { - var resp AccountDetailBalanceResponse - _ = xml.Unmarshal(c.getAndRead(fmt.Sprintf("accounts/%s/balances", accountId)), &resp) - return resp +/** +Return an object representing account balances of a given string ID +*/ +func (c *AllyApi) AccountBalance(accountId string) (resp AccountDetailBalanceResponse) { + c.marshalInterfaceResponse(fmt.Sprintf("accounts/%s/balances", accountId), &resp) + return } -func (c *AllyApi) AccountHoldings(accountId string) AccountDetailHoldingsResponse { - var resp AccountDetailHoldingsResponse - _ = xml.Unmarshal(c.getAndRead(fmt.Sprintf("accounts/%s/holdings", accountId)), &resp) - return resp +/** +Return an object representing the account holdings of a given string. +*/ +func (c *AllyApi) AccountHoldings(accountId string) (resp AccountDetailHoldingsResponse) { + c.marshalInterfaceResponse(fmt.Sprintf("accounts/%s/holdings", accountId), &resp) + return +} + +/** +Return an object representing the market clock response. +*/ +func (c *AllyApi) MarketClock() (resp MarketClockResponse) { + c.marshalInterfaceResponse("market/clock", &resp) + return +} + +/** +Given a list of symbols, return an result struct of quote history +*/ +func (c *AllyApi) MarketQuotes(symbols ...string) (resp MarketQuotesResponse) { + v := url.Values{ + "symbols": symbols, + } + fmt.Printf("%s\n", c.getWithParameters("market/ext/quotes", v)) + c.marshalWithQuery("market/ext/quotes", v, &resp) + return +} + +func (c *AllyApi) MarketNewsSearch(maxhits int, symbols ...string) (resp MarketNewsResponse) { + v := url.Values{ + "symbols": symbols, + "maxhits": []string{strconv.Itoa(maxhits)}, + } + c.marshalWithQuery("market/news/search", v, &resp) + return +} + +func (c *AllyApi) MarketNewsGet(id string) (resp MarketNewsGetResponse) { + c.marshalInterfaceResponse("market/news/#{id}", &resp) + return +} + +func (c *AllyApi) marshalWithQuery(p string, v url.Values, i interface{}) { + resp := c.getWithParameters(p, v) + _ = xml.Unmarshal(resp, &i) +} + +// Why does this work?? wtf?? +func (c *AllyApi) marshalInterfaceResponse(p string, i interface{}) { + resp := c.getAndRead(p) + _ = xml.Unmarshal(resp, &i) } diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..4d6b3aa --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module github.com/jaketothepast/investmentally + +go 1.13 + +require ( + github.com/joho/godotenv v1.3.0 + github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..cf4262f --- /dev/null +++ b/go.sum @@ -0,0 +1,4 @@ +github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= +github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 h1:j2kD3MT1z4PXCiUllUJF9mWUESr9TWKS7iEKsQ/IipM= +github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450/go.mod h1:skjdDftzkFALcuGzYSklqYd8gvat6F1gZJ4YPVbkZpM= diff --git a/main.go b/main.go index b9c8146..d321f19 100644 --- a/main.go +++ b/main.go @@ -1,16 +1,9 @@ package main -import ( - "fmt" - "strconv" -) - func main() { // Load our environment variables var api AllyApi api.Initialize() - acctId, _ := strconv.Atoi(api.Accounts()[0].Account) - - fmt.Printf("AccountDetail: %s\n", api.AccountDetail(acctId).AccountHoldings.Holding[0].Displaydata.Change) + api.MarketQuotes("FEYE", "IBM") } diff --git a/types.go b/types.go index 0185dd5..76e1de6 100644 --- a/types.go +++ b/types.go @@ -2,6 +2,7 @@ package main import ( "encoding/xml" + "time" ) type Accountbalance struct { @@ -138,3 +139,74 @@ type AccountDetailHoldingsResponse struct { XMLName xml.Name `xml:"response"` AccountHoldings Accountholdings `xml:"accountholdings"` } + +type MarketClockResponse struct { + XMLName xml.Name `xml:"response"` + Date string `xml:"date"` + CurrentStatus string `xml:"status>current"` + Message string `xml:"message"` + UnixTime time.Time `xml:"unixtime"` +} + +type MarketQuotesResponse struct { + XMLName xml.Name `xml:"response"` + Quotes []Quote `xml:"quotes"'` +} + +/** +TODO: Finish this page https://www.ally.com/api/invest/documentation/market-ext-quotes-get-post/ +*/ +type Quote struct { + XMLName xml.Name `xml:"quote"` + AverageDailyPrice100 float64 `xml:"adp_100"` + AverageDailyPrice200 float64 `xml:"adp_200"` + AverageDailyPrice50 float64 `xml:"adp_50"` + AverageDailyVolume21 float64 `xml:"adv_21"` + AverageDailyVolume30 float64 `xml:"adv_30"` + AverageDailyVolume90 float64 `xml:"adv_90"` + AskPrice float64 `xml:"ask"` + AskTime time.Time `xml:"ask_time"` + AskSize int `xml:"asksz"` + Basis float64 `xml:"basis"` + Beta float64 `xml:"beta"` + Bid float64 `xml:"bid"` + BidTime time.Time `xml:"bid_time"` + BidSize int `xml:"bidsz"` + BidTick int `xml:"bidtick"` + Change float64 `xml:"chg"` + ChangeSign string `xml:"chg_sign"` + ChangeText string `xml:"chg_t"` + Close float64 `xml:"cl"` + Cusip float64 `xml:"cusip"` + Date string `xml:"date"` + Datetime string `xml:"datetime"` + Dividend float64 `xml:"div"` + DividendFrequency string `xml:"divfreq"` + DollarValue float64 `xml:"dollar_value"` + EarningsPerShare float64 `xml:"eps"` + CompanyName string `xml:"name"` + PercentChangeSinceClose float64 `xml:"pchg"` + PriorDayClose float64 `xml:"pcls"` + PriceEarningRatio float64 `xml:"pe"` + PriorDayHigh float64 `xml:"phi"` + PriorDayLow float64 `xml:"plo"` + PriorDayOpen float64 `xml:"popn"` +} + +type MarketNewsResponse struct { + XMLName xml.Name `xml:"response"` + Articles []Article `xml:"articles"` +} + +type Article struct { + XMLName xml.Name `xml:"article"` + Date string `xml:"date"` + Headline string `xml:"headline"` + Id string `xml:"id"` + Story string `xml:"story"` +} + +type MarketNewsGetResponse struct { + XMLName xml.Name `xml:"response"` + Article Article `xml:"article"` +}