Merge branch 'market-api-calls' of jacob.windle/allyinvest into master

master
Jacob Windle 2020-01-22 00:15:38 +00:00 committed by Gitea
commit 1fb0009ccb
8 changed files with 172 additions and 62 deletions

9
Gopkg.lock generated
View File

@ -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

View File

@ -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

View File

@ -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 The different endpoints that are currently covered by this API
### /accounts
Get all account information for a user.
## Running This Project ## Running This Project
@ -17,3 +14,11 @@ For now, just build this directory and run the executable.
### Requirements ### Requirements
You must within the .env file define your Ally Invest credentials 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.

View File

@ -8,7 +8,10 @@ import (
"io/ioutil" "io/ioutil"
"log" "log"
"net/http" "net/http"
"net/url"
"os" "os"
"strconv"
"strings"
) )
/* The trading endpoint for Ally */ /* The trading endpoint for Ally */
@ -66,6 +69,19 @@ func (c *AllyApi) getAndRead(path string) []byte {
return raw 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 */ /* The /accounts endpoint of Ally */
func (c *AllyApi) Accounts() []AccountSummary { func (c *AllyApi) Accounts() []AccountSummary {
var resp AccountResponse var resp AccountResponse
@ -79,20 +95,71 @@ func (c *AllyApi) AccountBalances() (balances []AccountBalance) {
return resp.AccountBalances return resp.AccountBalances
} }
func (c *AllyApi) AccountDetail(accountId string) AccountDetailResponse { /**
var resp AccountDetailResponse Return an object representing account detail of a given string
_ = xml.Unmarshal(c.getAndRead(fmt.Sprintf("accounts/%s", accountId)), &resp) */
return resp 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 Return an object representing account balances of a given string ID
_ = xml.Unmarshal(c.getAndRead(fmt.Sprintf("accounts/%s/balances", accountId)), &resp) */
return resp 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 Return an object representing the account holdings of a given string.
_ = xml.Unmarshal(c.getAndRead(fmt.Sprintf("accounts/%s/holdings", accountId)), &resp) */
return resp 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)
} }

8
go.mod Normal file
View File

@ -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
)

4
go.sum Normal file
View File

@ -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=

View File

@ -1,16 +1,9 @@
package main package main
import (
"fmt"
"strconv"
)
func main() { func main() {
// Load our environment variables // Load our environment variables
var api AllyApi var api AllyApi
api.Initialize() api.Initialize()
acctId, _ := strconv.Atoi(api.Accounts()[0].Account) api.MarketQuotes("FEYE", "IBM")
fmt.Printf("AccountDetail: %s\n", api.AccountDetail(acctId).AccountHoldings.Holding[0].Displaydata.Change)
} }

View File

@ -2,6 +2,7 @@ package main
import ( import (
"encoding/xml" "encoding/xml"
"time"
) )
type Accountbalance struct { type Accountbalance struct {
@ -138,3 +139,74 @@ type AccountDetailHoldingsResponse struct {
XMLName xml.Name `xml:"response"` XMLName xml.Name `xml:"response"`
AccountHoldings Accountholdings `xml:"accountholdings"` 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"`
}