package main import ( "encoding/xml" "fmt" "github.com/joho/godotenv" "github.com/mrjones/oauth" "io/ioutil" "log" "net/http" "net/url" "os" "strconv" "strings" ) /* The trading endpoint for Ally */ const endpoint string = "https://api.tradeking.com/v1/" type AllyApi struct { consumer *oauth.Consumer Client *http.Client } func (c *AllyApi) Initialize() { err := godotenv.Load() if err != nil { panic(err) } // Set up our new oauth consumer c.consumer = oauth.NewConsumer( os.Getenv("CONSUMER_KEY"), os.Getenv("CONSUMER_SECRET"), oauth.ServiceProvider{ RequestTokenUrl: "https://developers.tradeking.com/oauth/request_token", AuthorizeTokenUrl: "https://developers.tradeking.com/oauth/authorize", AccessTokenUrl: "https://developers.tradeking.com/oauth/access_token", }, ) c.Client, err = c.consumer.MakeHttpClient( &oauth.AccessToken{Token: os.Getenv("ACCESS_TOKEN"), Secret: os.Getenv("ACCESS_SECRET")}) if err != nil { panic(err) } } func (c *AllyApi) get(path string) (resp *http.Response, err error) { resp, err = c.Client.Get(fmt.Sprintf("%s\\%s", endpoint, path)) return resp, err } //GetAndMarshal call GET on the path, and marshal the resopnse to interface // type func (c *AllyApi) getAndRead(path string) []byte { resp, err := c.get(path) if err != nil { log.Fatal("Could not make request") } defer resp.Body.Close() raw, err := ioutil.ReadAll(resp.Body) if err != nil { log.Fatal("Could not read body!") return nil } 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 _ = xml.Unmarshal(c.getAndRead("accounts"), &resp) return resp.Accounts.Accountsummary } func (c *AllyApi) AccountBalances() (balances []AccountBalance) { var resp AccountBalanceResponse _ = xml.Unmarshal(c.getAndRead("accounts/balances"), &resp) return resp.AccountBalances } /** 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 } /** 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 } /** 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) }