From d2861c2df2a96d2360bd7f3e91d022c5b6973ebe Mon Sep 17 00:00:00 2001 From: jaketothepast Date: Sun, 19 Jan 2020 19:21:32 -0500 Subject: [PATCH 01/10] Market Clock Response --- client.go | 6 ++++++ main.go | 5 +---- types.go | 9 +++++++++ 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/client.go b/client.go index e0caad5..531a17b 100644 --- a/client.go +++ b/client.go @@ -96,3 +96,9 @@ func (c *AllyApi) AccountHoldings(accountId string) AccountDetailHoldingsRespons _ = xml.Unmarshal(c.getAndRead(fmt.Sprintf("accounts/%s/holdings", accountId)), &resp) return resp } + +func (c *AllyApi) MarketClock() MarketClockResponse { + var resp MarketClockResponse + _ = xml.Unmarshal(c.getAndRead("market/clock"), &resp) + return resp +} diff --git a/main.go b/main.go index b9c8146..97ba7ac 100644 --- a/main.go +++ b/main.go @@ -2,7 +2,6 @@ package main import ( "fmt" - "strconv" ) func main() { @@ -10,7 +9,5 @@ func main() { 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) + fmt.Printf("Market Clock: %d\n", api.MarketClock().UnixTime) } diff --git a/types.go b/types.go index 0185dd5..5194ac4 100644 --- a/types.go +++ b/types.go @@ -2,6 +2,7 @@ package main import ( "encoding/xml" + "time" ) type Accountbalance struct { @@ -138,3 +139,11 @@ 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"` +} -- 2.17.1 From b8167173625e699af669913d0b1466ecc9a8986a Mon Sep 17 00:00:00 2001 From: jaketothepast Date: Sun, 19 Jan 2020 19:35:36 -0500 Subject: [PATCH 02/10] Wizardry with unmarshal and xml --- client.go | 46 ++++++++++++++++++++++++++++++---------------- main.go | 2 +- 2 files changed, 31 insertions(+), 17 deletions(-) diff --git a/client.go b/client.go index 531a17b..6b5d419 100644 --- a/client.go +++ b/client.go @@ -79,26 +79,40 @@ 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 } -func (c *AllyApi) MarketClock() MarketClockResponse { - var resp MarketClockResponse - _ = xml.Unmarshal(c.getAndRead("market/clock"), &resp) - return resp +/** +Return an object representing the market clock response. +*/ +func (c *AllyApi) MarketClock() (resp MarketClockResponse) { + c.marshalInterfaceResponse("market/clock", &resp) + return +} + +// Why does this work?? wtf?? +func (c *AllyApi) marshalInterfaceResponse(p string, i interface{}) { + resp := c.getAndRead(p) + _ = xml.Unmarshal(resp, &i) } diff --git a/main.go b/main.go index 97ba7ac..f00f976 100644 --- a/main.go +++ b/main.go @@ -9,5 +9,5 @@ func main() { var api AllyApi api.Initialize() - fmt.Printf("Market Clock: %d\n", api.MarketClock().UnixTime) + fmt.Printf("Market Clock: %d\n", api.MarketClock().CurrentStatus) } -- 2.17.1 From 9e4c2a70e5928b344f72f2fcc8f8c63681d80409 Mon Sep 17 00:00:00 2001 From: jaketothepast Date: Sun, 19 Jan 2020 20:28:58 -0500 Subject: [PATCH 03/10] Built helper that handles query functions --- client.go | 22 ++++++++++++++++++++++ main.go | 6 +----- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/client.go b/client.go index 6b5d419..3d6c8cc 100644 --- a/client.go +++ b/client.go @@ -8,7 +8,9 @@ import ( "io/ioutil" "log" "net/http" + "net/url" "os" + "strings" ) /* The trading endpoint for Ally */ @@ -66,6 +68,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 @@ -111,6 +126,13 @@ func (c *AllyApi) MarketClock() (resp MarketClockResponse) { return } +func (c *AllyApi) MarketQuotes() { + v := url.Values{ + "symbols": []string{"IBM"}, + } + fmt.Printf("%s\n", c.getWithParameters("market/ext/quotes", v)) +} + // Why does this work?? wtf?? func (c *AllyApi) marshalInterfaceResponse(p string, i interface{}) { resp := c.getAndRead(p) diff --git a/main.go b/main.go index f00f976..c502d9e 100644 --- a/main.go +++ b/main.go @@ -1,13 +1,9 @@ package main -import ( - "fmt" -) - func main() { // Load our environment variables var api AllyApi api.Initialize() - fmt.Printf("Market Clock: %d\n", api.MarketClock().CurrentStatus) + api.MarketQuotes() } -- 2.17.1 From 1d985d25a496b865a6be374e439cce3b9accbf80 Mon Sep 17 00:00:00 2001 From: jaketothepast Date: Mon, 20 Jan 2020 14:16:20 -0500 Subject: [PATCH 04/10] Began quote datastructure. --- types.go | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/types.go b/types.go index 5194ac4..edddd4e 100644 --- a/types.go +++ b/types.go @@ -147,3 +147,34 @@ type MarketClockResponse struct { 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"` +} -- 2.17.1 From 6b4b0e6de8ea4a85bc3da720cc7580a769d00f22 Mon Sep 17 00:00:00 2001 From: jaketothepast Date: Mon, 20 Jan 2020 14:19:00 -0500 Subject: [PATCH 05/10] Change the client to take a variadic list of strings --- client.go | 4 ++-- main.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client.go b/client.go index 3d6c8cc..4b1ec67 100644 --- a/client.go +++ b/client.go @@ -126,9 +126,9 @@ func (c *AllyApi) MarketClock() (resp MarketClockResponse) { return } -func (c *AllyApi) MarketQuotes() { +func (c *AllyApi) MarketQuotes(symbols ...string) { v := url.Values{ - "symbols": []string{"IBM"}, + "symbols": symbols, } fmt.Printf("%s\n", c.getWithParameters("market/ext/quotes", v)) } diff --git a/main.go b/main.go index c502d9e..d321f19 100644 --- a/main.go +++ b/main.go @@ -5,5 +5,5 @@ func main() { var api AllyApi api.Initialize() - api.MarketQuotes() + api.MarketQuotes("FEYE", "IBM") } -- 2.17.1 From 0860e76440600d0b264f98e9aa5d0027918b2c3d Mon Sep 17 00:00:00 2001 From: jaketothepast Date: Mon, 20 Jan 2020 14:25:21 -0500 Subject: [PATCH 06/10] Finishing quote struct with info I care about. --- types.go | 54 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/types.go b/types.go index edddd4e..5d283dd 100644 --- a/types.go +++ b/types.go @@ -157,24 +157,38 @@ type MarketQuotesResponse struct { 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"` + 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"` } -- 2.17.1 From 20dd89649a24f90ea75673f3e11826cb07ba47aa Mon Sep 17 00:00:00 2001 From: jaketothepast Date: Mon, 20 Jan 2020 14:30:12 -0500 Subject: [PATCH 07/10] Return an actual response object --- client.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/client.go b/client.go index 4b1ec67..85d52b9 100644 --- a/client.go +++ b/client.go @@ -126,11 +126,21 @@ func (c *AllyApi) MarketClock() (resp MarketClockResponse) { return } -func (c *AllyApi) MarketQuotes(symbols ...string) { +/** +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) marshalWithQuery(p string, v url.Values, i interface{}) { + resp := c.getWithParameters(p, v) + _ = xml.Unmarshal(resp, &i) } // Why does this work?? wtf?? -- 2.17.1 From 39acd7d5fe70daefbf5df1a67df5ec2a5754f263 Mon Sep 17 00:00:00 2001 From: jaketothepast Date: Mon, 20 Jan 2020 14:32:51 -0500 Subject: [PATCH 08/10] Return an actual response object --- README.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) 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. -- 2.17.1 From 5b916d7738df6939ca29590241ef40f2fa919b93 Mon Sep 17 00:00:00 2001 From: jaketothepast Date: Tue, 21 Jan 2020 19:11:29 -0500 Subject: [PATCH 09/10] Switching to go modules. --- Gopkg.lock | 9 --------- Gopkg.toml | 30 ------------------------------ client.go | 14 ++++++++++++++ go.mod | 8 ++++++++ go.sum | 4 ++++ types.go | 18 ++++++++++++++++++ 6 files changed, 44 insertions(+), 39 deletions(-) delete mode 100644 Gopkg.lock delete mode 100644 Gopkg.toml create mode 100644 go.mod create mode 100644 go.sum 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/client.go b/client.go index 85d52b9..51623be 100644 --- a/client.go +++ b/client.go @@ -10,6 +10,7 @@ import ( "net/http" "net/url" "os" + "strconv" "strings" ) @@ -138,6 +139,19 @@ func (c *AllyApi) MarketQuotes(symbols ...string) (resp MarketQuotesResponse) { 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) { + +} + func (c *AllyApi) marshalWithQuery(p string, v url.Values, i interface{}) { resp := c.getWithParameters(p, v) _ = 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/types.go b/types.go index 5d283dd..76e1de6 100644 --- a/types.go +++ b/types.go @@ -192,3 +192,21 @@ type Quote struct { 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"` +} -- 2.17.1 From 466b69d3a783b695fcacc55e94c88182c43b0a1b Mon Sep 17 00:00:00 2001 From: jaketothepast Date: Tue, 21 Jan 2020 19:14:37 -0500 Subject: [PATCH 10/10] Finishing Market News Get call --- client.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/client.go b/client.go index 51623be..875c371 100644 --- a/client.go +++ b/client.go @@ -148,8 +148,9 @@ func (c *AllyApi) MarketNewsSearch(maxhits int, symbols ...string) (resp MarketN return } -func (c *AllyApi) MarketNewsGet(id string) { - +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{}) { -- 2.17.1