diff --git a/.golangci.yml b/.golangci.yml index a90a568..781d650 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -149,11 +149,12 @@ linters: - varcheck - wastedassign - whitespace + - gochecknoinits +# - tagliatelle # Disabled linters, due to being misaligned with Go practices # - exhaustivestruct # - gochecknoglobals - # - gochecknoinits # - goconst # - godox # - goerr113 diff --git a/cmd/example/example.go b/cmd/example/example.go index 83f4d90..f976ffd 100644 --- a/cmd/example/example.go +++ b/cmd/example/example.go @@ -76,7 +76,6 @@ func main() { Price: 1.0, Quantity: 1, }) - if err != nil { log.Fatalf("buy failed: %v", err) } diff --git a/pkg/roho/account.go b/pkg/roho/account.go index 83698ee..e940c07 100644 --- a/pkg/roho/account.go +++ b/pkg/roho/account.go @@ -2,7 +2,7 @@ package roho import "context" -// Account holds the basic account details relevant to the RobinHood API +// Account holds the basic account details relevant to the RobinHood API. type Account struct { Meta AccountNumber string `json:"account_number"` @@ -28,7 +28,7 @@ type Account struct { WithdrawalHalted bool `json:"withdrawal_halted"` } -// CashBalances reflect the amount of cash available +// CashBalances reflect the amount of cash available. type CashBalances struct { Meta BuyingPower float64 `json:"buying_power,string"` @@ -39,7 +39,7 @@ type CashBalances struct { UnsettledFunds float64 `json:"unsettled_funds,string"` } -// MarginBalances reflect the balance available in margin accounts +// MarginBalances reflect the balance available in margin accounts. type MarginBalances struct { Meta Cash float64 `json:"cash,string"` @@ -70,14 +70,14 @@ func (c *Client) Accounts(ctx context.Context) ([]Account, error) { return r.Results, err } -// CryptoAccount holds the basic account details relevant to robinhood API +// CryptoAccount holds the basic account details relevant to robinhood API. type CryptoAccount struct { ID string `json:"id"` Status string `json:"status"` UserID string `json:"user_id"` } -// GetCryptoAccounts will return associated cryto account +// CryptoAccounts will return associated crypto account. func (c *Client) CryptoAccounts(ctx context.Context) ([]CryptoAccount, error) { var r struct{ Results []CryptoAccount } err := c.get(ctx, cryptoURL("accounts"), &r) diff --git a/pkg/roho/client.go b/pkg/roho/client.go index 4749255..009cdef 100644 --- a/pkg/roho/client.go +++ b/pkg/roho/client.go @@ -22,7 +22,7 @@ func cryptoURL(s string) string { // call retrieves from the endpoint and unmarshals resulting json into // the provided destination interface, which must be a pointer. func (c *Client) get(ctx context.Context, url string, dest interface{}) error { - req, err := http.NewRequest("GET", url, nil) + req, err := http.NewRequestWithContext(ctx, "GET", url, nil) if err != nil { return err } @@ -30,7 +30,7 @@ func (c *Client) get(ctx context.Context, url string, dest interface{}) error { return c.call(ctx, req, dest) } -// ErrorMap encapsulates the helpful error messages returned by the API server +// ErrorMap encapsulates the helpful error messages returned by the API server. type ErrorMap map[string]interface{} func (e ErrorMap) Error() string { diff --git a/pkg/roho/creds.go b/pkg/roho/creds.go index 77bd928..c4780b7 100644 --- a/pkg/roho/creds.go +++ b/pkg/roho/creds.go @@ -12,15 +12,6 @@ import ( "golang.org/x/oauth2" ) -var defaultPath = "" - -func init() { - u, err := user.Current() - if err == nil { - defaultPath = path.Join(u.HomeDir, ".config", "roho.token") - } -} - // A CredsCacher takes user credentials and a file path. The token obtained // from the RobinHood API will be cached at the file path, and a new token will // not be obtained. @@ -34,14 +25,19 @@ type CredsCacher struct { // when retrieving their token. func (c *CredsCacher) Token() (*oauth2.Token, error) { if c.Path == "" { - c.Path = defaultPath + u, err := user.Current() + if err != nil { + return nil, fmt.Errorf("user lookup failed: %w", err) + } + + c.Path = path.Join(u.HomeDir, ".config", "roho.token") } mustLogin := false - err := os.MkdirAll(path.Dir(c.Path), 0750) + err := os.MkdirAll(path.Dir(c.Path), 0o750) if err != nil { - return nil, fmt.Errorf("error creating path for token: %s", err) + return nil, fmt.Errorf("error creating path for token: %w", err) } _, err = os.Stat(c.Path) @@ -72,7 +68,7 @@ func (c *CredsCacher) Token() (*oauth2.Token, error) { return nil, err } - f, err := os.OpenFile(c.Path, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0640) + f, err := os.OpenFile(c.Path, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0o640) if err != nil { return nil, err } diff --git a/pkg/roho/crypto_order.go b/pkg/roho/crypto_order.go index 62436d8..1ad6f9d 100644 --- a/pkg/roho/crypto_order.go +++ b/pkg/roho/crypto_order.go @@ -3,17 +3,16 @@ package roho import ( "bytes" "context" + "encoding/json" "fmt" "math" - - "encoding/json" "net/http" "github.com/google/uuid" "github.com/pkg/errors" ) -// CryptoOrder is the payload to create a crypto currency order +// CryptoOrder is the payload to create a crypto currency order. type CryptoOrder struct { AccountID string `json:"account_id,omitempty"` CurrencyPairID string `json:"currency_pair_id,omitempty"` @@ -25,7 +24,7 @@ type CryptoOrder struct { Type string `json:"type,omitempty"` } -// CryptoOrderOutput holds the response from api +// CryptoOrderOutput holds the response from api. type CryptoOrderOutput struct { Meta Account string `json:"account"` @@ -49,7 +48,7 @@ type CryptoOrderOutput struct { client *Client } -// CryptoOrderOpts encapsulates differences between order types +// CryptoOrderOpts encapsulates differences between order types. type CryptoOrderOpts struct { Side OrderSide Type OrderType @@ -61,9 +60,9 @@ type CryptoOrderOpts struct { Stop, Force bool } -// CryptoOrder will actually place the order +// CryptoOrder will actually place the order. func (c *Client) CryptoOrder(ctx context.Context, cryptoPair CryptoCurrencyPair, o CryptoOrderOpts) (*CryptoOrderOutput, error) { - var quantity = math.Round(o.AmountInDollars / o.Price) + quantity := math.Round(o.AmountInDollars / o.Price) a := CryptoOrder{ AccountID: c.CryptoAccount.ID, CurrencyPairID: cryptoPair.ID, @@ -76,12 +75,11 @@ func (c *Client) CryptoOrder(ctx context.Context, cryptoPair CryptoCurrencyPair, } payload, err := json.Marshal(a) - if err != nil { return nil, err } - post, err := http.NewRequest("POST", cryptoURL("orders"), bytes.NewReader(payload)) + post, err := http.NewRequestWithContext(ctx, "POST", cryptoURL("orders"), bytes.NewReader(payload)) if err != nil { return nil, fmt.Errorf("could not create Crypto http.Request: %w", err) } diff --git a/pkg/roho/currency_pairs.go b/pkg/roho/currency_pairs.go index 8edddcf..0abdb6a 100644 --- a/pkg/roho/currency_pairs.go +++ b/pkg/roho/currency_pairs.go @@ -3,11 +3,9 @@ package roho import ( "context" "errors" - "fmt" ) -// CryptoCurrencyPair represent all availabe crypto currencies and whether they are tradeable or not type CryptoCurrencyPair struct { CyrptoAssetCurrency AssetCurrency `json:"asset_currency"` ID string `json:"id"` @@ -20,7 +18,7 @@ type CryptoCurrencyPair struct { Tradability string `json:"tradability"` } -// QuoteCurrency holds info about currency you can use to buy the cyrpto currency +// QuoteCurrency holds info about currency you can use to buy the cyrpto currency. type QuoteCurrency struct { Code string `json:"code"` ID string `json:"id"` @@ -29,7 +27,7 @@ type QuoteCurrency struct { Type string `json:"type"` } -// AssetCurrency has code and id of cryptocurrency +// AssetCurrency has code and id of cryptocurrency. type AssetCurrency struct { BrandColor string `json:"brand_color"` Code string `json:"code"` @@ -38,19 +36,19 @@ type AssetCurrency struct { Name string `json:"name"` } -// GetCryptoCurrencyPairs will give which crypto currencies are tradeable and corresponding ids +// CryptoCurrencyPairs will give which crypto currencies are tradeable and corresponding ids. func (c *Client) CryptoCurrencyPairs(ctx context.Context) ([]CryptoCurrencyPair, error) { var r struct{ Results []CryptoCurrencyPair } err := c.get(ctx, cryptoURL("currency_pairs"), &r) return r.Results, err } -// GetCryptoInstrument will take standard crypto symbol and return usable information -// to place the order +// CryptoInstrument will take standard crypto symbol and return usable information +// to place the order. func (c *Client) CryptoInstrument(ctx context.Context, symbol string) (*CryptoCurrencyPair, error) { allPairs, err := c.CryptoCurrencyPairs(ctx) if err != nil { - return nil, fmt.Errorf("crypto currency pairs: %v", err) + return nil, fmt.Errorf("crypto currency pairs: %w", err) } for _, pair := range allPairs { diff --git a/pkg/roho/instrument.go b/pkg/roho/instrument.go index c4c08ab..378e057 100644 --- a/pkg/roho/instrument.go +++ b/pkg/roho/instrument.go @@ -22,7 +22,7 @@ type Instrument struct { MinTickSize interface{} `json:"min_tick_size"` Name string `json:"name"` Quote string `json:"quote"` - RhsTradability string `json:"rhs_tradability"` + RHSTRadability string `json:"rhs_tradability"` SimpleName interface{} `json:"simple_name"` Splits string `json:"splits"` State string `json:"state"` @@ -42,7 +42,7 @@ func (i Instrument) OrderSymbol() string { return i.Symbol } -// GetInstrument returns an Instrument given a URL +// GetInstrument returns an Instrument given a URL. func (c *Client) Instrument(ctx context.Context, instURL string) (*Instrument, error) { var i Instrument err := c.get(ctx, instURL, &i) @@ -52,7 +52,7 @@ func (c *Client) Instrument(ctx context.Context, instURL string) (*Instrument, e return &i, err } -// Lookup returns an Instrument given a ticker symbol +// Lookup returns an Instrument given a ticker symbol. func (c *Client) Lookup(ctx context.Context, sym string) (*Instrument, error) { var i struct { Results []Instrument diff --git a/pkg/roho/marketdata.go b/pkg/roho/marketdata.go index 99e96a6..896576c 100644 --- a/pkg/roho/marketdata.go +++ b/pkg/roho/marketdata.go @@ -6,8 +6,8 @@ import ( ) type EntryPrice struct { - Amount string - Currency_code string + Amount string + CurrencyCode string `json:"currency_code"` } type PriceBookEntry struct { @@ -24,7 +24,7 @@ type PriceBookData struct { UpdatedAt string `json:"updated_at"` } -// Pricebook get the current snapshot of the pricebook data +// Pricebook get the current snapshot of the pricebook data. func (c *Client) Pricebook(ctx context.Context, instrumentID string) (*PriceBookData, error) { var out PriceBookData err := c.get(ctx, fmt.Sprintf("%spricebook/snapshots/%s/", baseURL("marketdata"), instrumentID), &out) diff --git a/pkg/roho/oauth.go b/pkg/roho/oauth.go index 401aecf..22f4bfe 100644 --- a/pkg/roho/oauth.go +++ b/pkg/roho/oauth.go @@ -1,6 +1,7 @@ package roho import ( + "context" "encoding/json" "fmt" "net/http" @@ -15,15 +16,15 @@ import ( // DefaultClientID is used by the website. const DefaultClientID = "c82SH0WZOsabOXGP2sxqcj34FxkvfnWRZBKlBjFS" -// OAuth implements oauth2 using the robinhood implementation +// OAuth implements oauth2 using the robinhood implementation. type OAuth struct { Endpoint, ClientID, Username, Password, MFA string } // ErrMFARequired indicates the MFA was required but not provided. -var ErrMFARequired = fmt.Errorf("Two Factor Auth code required and not supplied") +var ErrMFARequired = fmt.Errorf("two-factor auth code required and not supplied") -// Token implements TokenSource +// Token implements TokenSource. func (p *OAuth) Token() (*oauth2.Token, error) { cliID := p.ClientID if cliID == "" { @@ -46,11 +47,7 @@ func (p *OAuth) Token() (*oauth2.Token, error) { v.Add("mfa_code", p.MFA) } - req, err := http.NewRequest( - "POST", - u.String(), - strings.NewReader(v.Encode()), - ) + req, err := http.NewRequestWithContext(context.Background(), "POST", u.String(), strings.NewReader(v.Encode())) if err != nil { return nil, errors.Wrap(err, "could not create request") } diff --git a/pkg/roho/optiondirection.go b/pkg/roho/optiondirection.go index a20e987..c70b48c 100644 --- a/pkg/roho/optiondirection.go +++ b/pkg/roho/optiondirection.go @@ -6,16 +6,16 @@ import ( ) // OptionDirection is a type for whether an option order is opening or closing -// an option position +// an option position. type OptionDirection int -// MarshalJSON implements json.Marshaler +// MarshalJSON implements json.Marshaler. func (o OptionDirection) MarshalJSON() ([]byte, error) { return []byte(fmt.Sprintf("%q", strings.ToLower(o.String()))), nil } //go:generate stringer -type OptionDirection -// The two directions +// The two directions. const ( Debit OptionDirection = iota Credit diff --git a/pkg/roho/options-order.go b/pkg/roho/options-order.go index f7785e2..66f6049 100644 --- a/pkg/roho/options-order.go +++ b/pkg/roho/options-order.go @@ -9,7 +9,7 @@ import ( "github.com/google/uuid" ) -// OptionsOrderOpts encapsulates common Options order choices +// OptionsOrderOpts encapsulates common Options order choices. type OptionsOrderOpts struct { Quantity float64 Price float64 @@ -19,7 +19,7 @@ type OptionsOrderOpts struct { Side OrderSide } -// optionInput is the input object to the RobinHood API +// optionInput is the input object to the RobinHood API. type optionInput struct { Account string `json:"account"` Direction OptionDirection `json:"direction"` @@ -73,7 +73,7 @@ func (c *Client) OrderOptions(ctx context.Context, q *OptionInstrument, o Option return nil, err } - req, err := http.NewRequest("POST", baseURL("options")+"orders/", bytes.NewReader(bs)) + req, err := http.NewRequestWithContext(ctx, "POST", baseURL("options")+"orders/", bytes.NewReader(bs)) if err != nil { return nil, err } @@ -87,7 +87,7 @@ func (c *Client) OrderOptions(ctx context.Context, q *OptionInstrument, o Option return out, nil } -// GetOptionsOrders returns all outstanding options orders +// GetOptionsOrders returns all outstanding options orders. func (c *Client) OptionsOrders(ctx context.Context) (json.RawMessage, error) { var o json.RawMessage err := c.get(ctx, baseURL("options")+"orders/", &o) @@ -96,5 +96,4 @@ func (c *Client) OptionsOrders(ctx context.Context) (json.RawMessage, error) { } return o, nil - } diff --git a/pkg/roho/options.go b/pkg/roho/options.go index 605adf0..2f8d8e6 100644 --- a/pkg/roho/options.go +++ b/pkg/roho/options.go @@ -13,12 +13,12 @@ import ( const dateFormat = "2006-01-02" -// Date is a specific json time format for dates only +// Date is a specific json time format for dates only. type Date struct { time.Time } -// NewDate returns a new Date in the local time zone +// NewDate returns a new Date in the local time zone. func NewDate(y, m, d int) Date { return Date{time.Date(y, time.Month(m), d, 0, 0, 0, 0, time.Local)} } @@ -32,12 +32,12 @@ func (d Date) String() string { return d.Format(dateFormat) } -// MarshalJSON implements json.Marshaler +// MarshalJSON implements json.Marshaler. func (d Date) MarshalJSON() ([]byte, error) { return []byte("\"" + d.String() + "\""), nil } -// UnmarshalJSON implements json.Unmarshaler +// UnmarshalJSON implements json.Unmarshaler. func (d *Date) UnmarshalJSON(bs []byte) error { t, err := time.Parse(dateFormat, strings.Trim(strings.TrimSpace(string(bs)), "\"")) if err != nil { @@ -47,7 +47,7 @@ func (d *Date) UnmarshalJSON(bs []byte) error { return nil } -// OptionChains returns options for the given instruments +// OptionChains returns options for the given instruments. func (c *Client) OptionChains(ctx context.Context, is ...*Instrument) ([]*OptionChain, error) { s := []string{} for _, inst := range is { @@ -68,7 +68,7 @@ func (c *Client) OptionChains(ctx context.Context, is ...*Instrument) ([]*Option return res.Results, nil } -// OptionChain represents the data the RobinHood API holds behind options chains +// OptionChain represents the data the RobinHood API holds behind options chains. type OptionChain struct { CanOpenPosition bool `json:"can_open_position"` CashComponent interface{} `json:"cash_component"` @@ -148,14 +148,14 @@ type MinTicks struct { } // UnderlyingInstrument is the type that represents a link from an option back -// to its standard financial instrument (stock) +// to its standard financial instrument (stock). type UnderlyingInstrument struct { ID string `json:"id"` Instrument string `json:"instrument"` Quantity int `json:"quantity"` } -// An OptionInstrument can have a quote +// An OptionInstrument can have a quote. type OptionInstrument struct { ChainID string `json:"chain_id"` ChainSymbol string `json:"chain_symbol"` @@ -164,15 +164,13 @@ type OptionInstrument struct { ID string `json:"id"` IssueDate string `json:"issue_date"` MinTicks MinTicks `json:"min_ticks"` - RHSTradability string `json:"rhs_tradability"` + RHSTRadability string `json:"rhs_tradability"` State string `json:"state"` StrikePrice float64 `json:"strike_price,string"` Tradability string `json:"tradability"` Type string `json:"type"` UpdatedAt string `json:"updated_at"` URL string `json:"url"` - - c *Client } // MarketData is the current pricing data and greeks for a given option at a @@ -215,7 +213,7 @@ func OIsForDate(os []*OptionInstrument, d Date) []*OptionInstrument { return out } -// MarketData returns market data for all the listed Option instruments +// MarketData returns market data for all the listed Option instruments. func (c *Client) MarketData(ctx context.Context, opts ...*OptionInstrument) ([]*MarketData, error) { is := make([]string, len(opts)) @@ -225,7 +223,7 @@ func (c *Client) MarketData(ctx context.Context, opts ...*OptionInstrument) ([]* u, err := url.Parse(baseURL("options")) if err != nil { - return nil, fmt.Errorf("parse: %v", err) + return nil, fmt.Errorf("parse: %w", err) } // Number of instruments to request at once diff --git a/pkg/roho/order.go b/pkg/roho/order.go index 286f5eb..e1ff223 100644 --- a/pkg/roho/order.go +++ b/pkg/roho/order.go @@ -11,25 +11,25 @@ import ( "github.com/pkg/errors" ) -// OrderSide is which side of the trade an order is on +// OrderSide is which side of the trade an order is on. type OrderSide int -// MarshalJSON implements json.Marshaler +// MarshalJSON implements json.Marshaler. func (o OrderSide) MarshalJSON() ([]byte, error) { return []byte("\"" + strings.ToLower(o.String()) + "\""), nil } //go:generate stringer -type OrderSide -// Buy/Sell +// Buy/Sell. const ( Sell OrderSide = iota + 1 Buy ) -// OrderType represents a Limit or Market order +// OrderType represents a Limit or Market order. type OrderType int -// MarshalJSON implements json.Marshaler +// MarshalJSON implements json.Marshaler. func (o OrderType) MarshalJSON() ([]byte, error) { return []byte(fmt.Sprintf("%q", strings.ToLower(o.String()))), nil } @@ -41,7 +41,7 @@ const ( Limit ) -// OrderOpts encapsulates differences between order types +// OrderOpts encapsulates differences between order types. type OrderOpts struct { Side OrderSide Type OrderType @@ -69,13 +69,13 @@ type apiOrder struct { OverrideDtbpChecks bool `json:"override_dtbp_checks,omitempty"` } -// Buy buys an insstrument +// Buy buys an insstrument. func (c *Client) Buy(ctx context.Context, i *Instrument, o OrderOpts) (*OrderOutput, error) { o.Side = Buy return c.order(ctx, i, o) } -// Sell sells an instrument +// Sell sells an instrument. func (c *Client) Sell(ctx context.Context, i *Instrument, o OrderOpts) (*OrderOutput, error) { o.Side = Sell return c.order(ctx, i, o) @@ -108,7 +108,7 @@ func (c *Client) order(ctx context.Context, i *Instrument, o OrderOpts) (*OrderO return nil, err } - post, err := http.NewRequest("POST", baseURL("orders"), bytes.NewReader(bs)) + post, err := http.NewRequestWithContext(ctx, "POST", baseURL("orders"), bytes.NewReader(bs)) if err != nil { return nil, fmt.Errorf("error creating POST http.Request: %w", err) } @@ -125,7 +125,7 @@ func (c *Client) order(ctx context.Context, i *Instrument, o OrderOpts) (*OrderO return &out, nil } -// OrderOutput is the response from the Order api +// OrderOutput is the response from the Order api. type OrderOutput struct { Meta Account string `json:"account"` @@ -160,9 +160,9 @@ func (o *OrderOutput) Update(ctx context.Context) error { return o.client.get(ctx, o.URL, o) } -// Cancel attempts to cancel an odrer +// Cancel attempts to cancel an odrer. func (o OrderOutput) Cancel(ctx context.Context) error { - post, err := http.NewRequest("POST", o.CancelURL, nil) + post, err := http.NewRequestWithContext(ctx, "POST", o.CancelURL, nil) if err != nil { return err } @@ -215,7 +215,6 @@ func (c *Client) AllOrders(ctx context.Context) ([]OrderOutput, error) { Next string } err := c.get(ctx, url, &tmp) - if err != nil { return o.Results, err } diff --git a/pkg/roho/portfolios.go b/pkg/roho/portfolios.go index 8c7f9ab..cf53999 100644 --- a/pkg/roho/portfolios.go +++ b/pkg/roho/portfolios.go @@ -2,7 +2,7 @@ package roho import "context" -// Portfolio holds all information regarding the portfolio +// Portfolio holds all information regarding the portfolio. type Portfolio struct { Account string `json:"account"` AdjustedEquityPreviousClose float64 `json:"adjusted_equity_previous_close,string"` @@ -24,7 +24,7 @@ type Portfolio struct { WithdrawableAmount float64 `json:"withdrawable_amount,string"` } -// CryptoPortfolio returns all the portfolio associated with a client's account +// CryptoPortfolio returns all the portfolio associated with a client's account. type CryptoPortfolio struct { AccountID string `json:"account_id"` Equity float64 `json:"equity,string"` @@ -35,17 +35,17 @@ type CryptoPortfolio struct { } // GetPortfolios returns all the portfolios associated with a client's -// credentials and accounts +// credentials and accounts. func (c *Client) Portfolios(ctx context.Context) ([]Portfolio, error) { var p struct{ Results []Portfolio } err := c.get(ctx, baseURL("portfolios"), &p) return p.Results, err } -// GetCryptoPortfolios returns crypto portfolio info +// GetCryptoPortfolios returns crypto portfolio info. func (c *Client) CryptoPortfolios(ctx context.Context) (CryptoPortfolio, error) { var p CryptoPortfolio - var portfolioURL = cryptoURL("portfolios") + c.CryptoAccount.ID + portfolioURL := cryptoURL("portfolios") + c.CryptoAccount.ID err := c.get(ctx, portfolioURL, &p) return p, err } diff --git a/pkg/roho/positions.go b/pkg/roho/positions.go index ba12b03..9c5994b 100644 --- a/pkg/roho/positions.go +++ b/pkg/roho/positions.go @@ -30,13 +30,13 @@ type OptionPostion struct { Legs []LegPosition `json:"legs"` IntradayQuantity string `json:"intraday_quantity"` UpdatedAt string `json:"updated_at"` - Id string `json:"id"` + ID string `json:"id"` IntradayAverageOpenPrice string `json:"intraday_average_open_price"` CreatedAt string `json:"created_at"` } type LegPosition struct { - Id string `json:"id"` + ID string `json:"id"` Position string `json:"position"` PositionType string `json:"position_type"` Option string `json:"option"` @@ -64,7 +64,7 @@ type PositionParams struct { NonZero bool } -// Encode returns the query string associated with the requested parameters +// Encode returns the query string associated with the requested parameters. func (p PositionParams) encode() string { v := url.Values{} if p.NonZero { diff --git a/pkg/roho/quote.go b/pkg/roho/quote.go index 60e92f4..48e7e76 100644 --- a/pkg/roho/quote.go +++ b/pkg/roho/quote.go @@ -3,10 +3,12 @@ package roho import ( "context" "strings" + + "github.com/tstromberg/roho/pkg/times" ) // A Quote is a representation of the data returned by the Robinhood API for -// current stock quotes +// current stock quotes. type Quote struct { AdjustedPreviousClose float64 `json:"adjusted_previous_close,string"` AskPrice float64 `json:"ask_price,string"` @@ -22,7 +24,7 @@ type Quote struct { UpdatedAt string `json:"updated_at"` } -// GetQuote returns all the latest stock quotes for the list of stocks provided +// GetQuote returns all the latest stock quotes for the list of stocks provided. func (c *Client) Quote(ctx context.Context, stocks ...string) ([]Quote, error) { url := baseURL("quotes") + "?symbols=" + strings.Join(stocks, ",") var r struct{ Results []Quote } @@ -30,9 +32,9 @@ func (c *Client) Quote(ctx context.Context, stocks ...string) ([]Quote, error) { return r.Results, err } -// Price returns the proper stock price even after hours +// Price returns the proper stock price even after hours. func (q Quote) Price() float64 { - if IsRegularTradingTime() { + if times.IsRegularTradingTime() { return q.LastTradePrice } return q.LastExtendedHoursTradePrice diff --git a/pkg/roho/roho.go b/pkg/roho/roho.go index 985eb0e..15cc21d 100644 --- a/pkg/roho/roho.go +++ b/pkg/roho/roho.go @@ -53,16 +53,24 @@ func Dial(ctx context.Context, s oauth2.TokenSource) (*Client, error) { } a, err := c.Accounts(ctx) + if err != nil { + return nil, fmt.Errorf("accounts: %w", err) + } + log.Printf("Found %d accounts", len(a)) if len(a) > 0 { c.Account = &a[0] } ca, err := c.CryptoAccounts(ctx) + if err != nil { + return nil, fmt.Errorf("crypto accounts: %w", err) + } + log.Printf("Found %d crypto accounts", len(ca)) if len(ca) > 0 { c.CryptoAccount = &ca[0] } - return c, err + return c, nil } diff --git a/pkg/roho/time-in-force.go b/pkg/roho/time-in-force.go index c9c9882..0a8957c 100644 --- a/pkg/roho/time-in-force.go +++ b/pkg/roho/time-in-force.go @@ -8,13 +8,13 @@ import ( // TimeInForce is the time in force for an order. type TimeInForce int -// MarshalJSON implements json.Marshaler +// MarshalJSON implements json.Marshaler. func (t TimeInForce) MarshalJSON() ([]byte, error) { return []byte(fmt.Sprintf("%q", strings.ToLower(t.String()))), nil } //go:generate stringer -type=TimeInForce -// Well-known values for TimeInForce +// Well-known values for TimeInForce. const ( // GTC means Good 'Til Cancelled. GTC TimeInForce = iota diff --git a/pkg/roho/wrap.go b/pkg/roho/wrap.go deleted file mode 100644 index cabddd8..0000000 --- a/pkg/roho/wrap.go +++ /dev/null @@ -1,11 +0,0 @@ -package roho - -import ( - "fmt" - - "github.com/pkg/errors" -) - -func shameWrap(e error, msg string) error { - return errors.Wrap(e, fmt.Sprintf("Andrew is an idiot. (%s)", msg)) -} diff --git a/pkg/roho/times.go b/pkg/times/times.go similarity index 88% rename from pkg/roho/times.go rename to pkg/times/times.go index cb075d8..f4fb77a 100644 --- a/pkg/roho/times.go +++ b/pkg/times/times.go @@ -1,4 +1,4 @@ -package roho +package times import "time" @@ -36,22 +36,17 @@ func nyMinute() int { return MinuteOfDay(time.Now().In(nyLoc())) } -// isWeekday returns whether or not the given time.Time is a weekday. -func isWeekday(t time.Time) bool { - wd := t.Weekday() - return wd != time.Saturday && wd != time.Sunday -} - // IsWeekDay returns whether the given time is a regular -// weekday +// weekday. func IsWeekDay(t time.Time) bool { - return isWeekday(time.Now()) + wd := t.Weekday() + return wd != time.Saturday && wd != time.Sunday } // NextWeekday returns the next weekday. func NextWeekday() time.Time { d := time.Now().AddDate(0, 0, 1) - for !isWeekday(d) { + for !IsWeekDay(d) { d = d.AddDate(0, 0, 1) } return d @@ -98,30 +93,30 @@ func NextMarketOpen() time.Time { // NextMarketExtendedOpen returns the time of the next extended opening time, // when stock equity may begin to fluctuate again. func NextMarketExtendedOpen() time.Time { - return nextWeekdayHourMinuteNY(HrExtendedOpen, 00) + return nextWeekdayHourMinuteNY(HrExtendedOpen, 0) } // NextRobinhoodExtendedOpen returns the time of the next robinhood extended // opening time, when robinhood users can make trades. func NextRobinhoodExtendedOpen() time.Time { - return nextWeekdayHourMinuteNY(HrRHExtendedOpen, 00) + return nextWeekdayHourMinuteNY(HrRHExtendedOpen, 0) } // NextMarketClose returns the time of the next market close. func NextMarketClose() time.Time { - return nextWeekdayHourMinuteNY(HrClose, 00) + return nextWeekdayHourMinuteNY(HrClose, 0) } // NextRobinhoodExtendedClose returns the time of the next robinhood extended // closing time, when robinhood users must place their last extended-hours // trade. func NextRobinhoodExtendedClose() time.Time { - return nextWeekdayHourMinuteNY(HrRHExtendedClose, 00) + return nextWeekdayHourMinuteNY(HrRHExtendedClose, 0) } // NextMarketExtendedClose returns the time of the next extended market close, // when stock equity numbers will stop being updated until the next extended // open. func NextMarketExtendedClose() time.Time { - return nextWeekdayHourMinuteNY(HrRHExtendedClose, 00) + return nextWeekdayHourMinuteNY(HrRHExtendedClose, 0) }