diff --git a/contrib/alpacabkfeeder/alpacav2.go b/contrib/alpacabkfeeder/alpacav2.go index 2372b9df..dc5be150 100644 --- a/contrib/alpacabkfeeder/alpacav2.go +++ b/contrib/alpacabkfeeder/alpacav2.go @@ -5,10 +5,9 @@ import ( "fmt" "time" - "github.com/alpacahq/alpaca-trade-api-go/alpaca" - "github.com/alpacahq/alpaca-trade-api-go/common" "github.com/pkg/errors" + "github.com/alpacahq/marketstore/v4/contrib/alpacabkfeeder/api" "github.com/alpacahq/marketstore/v4/contrib/alpacabkfeeder/configs" "github.com/alpacahq/marketstore/v4/contrib/alpacabkfeeder/feed" "github.com/alpacahq/marketstore/v4/contrib/alpacabkfeeder/symbols" @@ -30,7 +29,7 @@ func NewBgWorker(conf map[string]interface{}) (bgworker.BgWorker, error) { log.Info("loaded Alpaca Broker Feeder config...") // init Alpaca API client - cred := &common.APIKey{ + cred := &api.APIKey{ ID: config.APIKeyID, PolygonKeyID: config.APIKeyID, Secret: config.APISecretKey, @@ -38,9 +37,9 @@ func NewBgWorker(conf map[string]interface{}) (bgworker.BgWorker, error) { } if config.APIKeyID == "" || config.APISecretKey == "" { // if empty, get from env vars - cred = common.Credentials() + cred = api.Credentials() } - apiClient := alpaca.NewClient(cred) + apiClient := api.NewClient(cred) // init Market Time Checker var timeChecker feed.MarketTimeChecker diff --git a/contrib/alpacabkfeeder/api/client.go b/contrib/alpacabkfeeder/api/client.go new file mode 100644 index 00000000..ccb5ed76 --- /dev/null +++ b/contrib/alpacabkfeeder/api/client.go @@ -0,0 +1,268 @@ +package api + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "log" + "net/http" + "net/url" + "os" + "strconv" + "strings" + "time" + + v1 "github.com/alpacahq/marketstore/v4/contrib/alpacabkfeeder/api/v1" +) + +const ( + rateLimitRetryCount = 3 + rateLimitRetryDelay = time.Second +) + +var ( + // DefaultClient is the default Alpaca client using the + // environment variable set credentials + DefaultClient = NewClient(Credentials()) + base = "https://api.alpaca.markets" + dataURL = "https://data.alpaca.markets" + apiVersion = "v2" + clientTimeout = 10 * time.Second + do = defaultDo +) + +func defaultDo(c *Client, req *http.Request) (*http.Response, error) { + if c.credentials.OAuth != "" { + req.Header.Set("Authorization", "Bearer "+c.credentials.OAuth) + } else { + req.Header.Set("APCA-API-KEY-ID", c.credentials.ID) + req.Header.Set("APCA-API-SECRET-KEY", c.credentials.Secret) + // Add Basic Auth + req.SetBasicAuth(c.credentials.ID, c.credentials.Secret) + } + + client := &http.Client{ + Timeout: clientTimeout, + } + var resp *http.Response + var err error + for i := 0; ; i++ { + resp, err = client.Do(req) + if err != nil { + return nil, err + } + if resp.StatusCode != http.StatusTooManyRequests { + break + } + if i >= rateLimitRetryCount { + break + } + time.Sleep(rateLimitRetryDelay) + } + + if err = verify(resp); err != nil { + return nil, err + } + + return resp, nil +} + +const ( + // v2MaxLimit is the maximum allowed limit parameter for all v2 endpoints + v2MaxLimit = 10000 +) + +func init() { + if s := os.Getenv("APCA_API_BASE_URL"); s != "" { + base = s + } else if s := os.Getenv("ALPACA_BASE_URL"); s != "" { + // legacy compatibility... + base = s + } + if s := os.Getenv("APCA_DATA_URL"); s != "" { + dataURL = s + } + // also allow APCA_API_DATA_URL to be consistent with the python SDK + if s := os.Getenv("APCA_API_DATA_URL"); s != "" { + dataURL = s + } + if s := os.Getenv("APCA_API_VERSION"); s != "" { + apiVersion = s + } + if s := os.Getenv("APCA_API_CLIENT_TIMEOUT"); s != "" { + d, err := time.ParseDuration(s) + if err != nil { + log.Fatal("invalid APCA_API_CLIENT_TIMEOUT: " + err.Error()) + } + clientTimeout = d + } +} + +// APIError wraps the detailed code and message supplied +// by Alpaca's API for debugging purposes +type APIError struct { + Code int `json:"code"` + Message string `json:"message"` +} + +func (e *APIError) Error() string { + return e.Message +} + +// Client is an Alpaca REST API client +type Client struct { + credentials *APIKey +} + +func SetBaseUrl(baseUrl string) { + base = baseUrl +} + +// NewClient creates a new Alpaca client with specified +// credentials +func NewClient(credentials *APIKey) *Client { + return &Client{credentials: credentials} +} + +// GetSnapshots returns the snapshots for multiple symbol +func (c *Client) GetSnapshots(symbols []string) (map[string]*Snapshot, error) { + u, err := url.Parse(fmt.Sprintf("%s/v2/stocks/snapshots?symbols=%s", + dataURL, strings.Join(symbols, ","))) + if err != nil { + return nil, err + } + + resp, err := c.get(u) + if err != nil { + return nil, err + } + + var snapshots map[string]*Snapshot + + if err = unmarshal(resp, &snapshots); err != nil { + return nil, err + } + + return snapshots, nil +} + +// ListBars returns a list of bar lists corresponding to the provided +// symbol list, and filtered by the provided parameters. +func (c *Client) ListBars(symbols []string, opts v1.ListBarParams) (map[string][]v1.Bar, error) { + vals := url.Values{} + vals.Add("symbols", strings.Join(symbols, ",")) + + if opts.Timeframe == "" { + return nil, fmt.Errorf("timeframe is required for the bars endpoint") + } + + if opts.StartDt != nil { + vals.Set("start", opts.StartDt.Format(time.RFC3339)) + } + + if opts.EndDt != nil { + vals.Set("end", opts.EndDt.Format(time.RFC3339)) + } + + if opts.Limit != nil { + vals.Set("limit", strconv.FormatInt(int64(*opts.Limit), 10)) + } + + u, err := url.Parse(fmt.Sprintf("%s/v1/bars/%s?%v", dataURL, opts.Timeframe, vals.Encode())) + if err != nil { + return nil, err + } + + resp, err := c.get(u) + if err != nil { + return nil, err + } + var bars map[string][]v1.Bar + + if err = unmarshal(resp, &bars); err != nil { + return nil, err + } + + return bars, nil +} + +// ListAssets returns the list of assets, filtered by +// the input parameters. +func (c *Client) ListAssets(status *string) ([]v1.Asset, error) { + // TODO: add tests + apiVer := apiVersion + if strings.Contains(base, "broker"){ + apiVer = "v1" + } + + // TODO: support different asset classes + u, err := url.Parse(fmt.Sprintf("%s/%s/assets", base, apiVer)) + if err != nil { + return nil, err + } + + q := u.Query() + + if status != nil { + q.Set("status", *status) + } + + u.RawQuery = q.Encode() + + resp, err := c.get(u) + if err != nil { + return nil, err + } + + assets := []v1.Asset{} + + if err = unmarshal(resp, &assets); err != nil { + return nil, err + } + + return assets, nil +} + +func (c *Client) get(u *url.URL) (*http.Response, error) { + req, err := http.NewRequest(http.MethodGet, u.String(), nil) + if err != nil { + return nil, err + } + + return do(c, req) +} + +func verify(resp *http.Response) (err error) { + if resp.StatusCode >= http.StatusMultipleChoices { + var body []byte + defer resp.Body.Close() + + body, err = ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + + apiErr := APIError{} + + err = json.Unmarshal(body, &apiErr) + if err != nil { + return fmt.Errorf("json unmarshal error: %s", err.Error()) + } + if err == nil { + err = &apiErr + } + } + + return +} + +func unmarshal(resp *http.Response, data interface{}) error { + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + + return json.Unmarshal(body, data) +} diff --git a/contrib/alpacabkfeeder/api/credentials.go b/contrib/alpacabkfeeder/api/credentials.go new file mode 100644 index 00000000..449f97f7 --- /dev/null +++ b/contrib/alpacabkfeeder/api/credentials.go @@ -0,0 +1,42 @@ +package api + +import ( + "os" + "sync" +) + +var ( + once sync.Once + key *APIKey +) + +const ( + EnvApiKeyID = "APCA_API_KEY_ID" + EnvApiSecretKey = "APCA_API_SECRET_KEY" + EnvApiOAuth = "APCA_API_OAUTH" + EnvPolygonKeyID = "POLY_API_KEY_ID" +) + +type APIKey struct { + ID string + Secret string + OAuth string + PolygonKeyID string +} + +// Credentials returns the user's Alpaca API key ID +// and secret for use through the SDK. +func Credentials() *APIKey { + var polygonKeyID string + if s := os.Getenv(EnvPolygonKeyID); s != "" { + polygonKeyID = s + } else { + polygonKeyID = os.Getenv(EnvApiKeyID) + } + return &APIKey{ + ID: os.Getenv(EnvApiKeyID), + PolygonKeyID: polygonKeyID, + Secret: os.Getenv(EnvApiSecretKey), + OAuth: os.Getenv(EnvApiOAuth), + } +} diff --git a/contrib/alpacabkfeeder/api/doc.go b/contrib/alpacabkfeeder/api/doc.go new file mode 100644 index 00000000..3829f16a --- /dev/null +++ b/contrib/alpacabkfeeder/api/doc.go @@ -0,0 +1,7 @@ +package api + +/** +This package is the partial copy of https://github.com/alpacahq/alpaca-trade-api-go/tree/v1.9.0/alpaca. +To support Alpaca Broker API with the same implementation, Basic Auth support is added. +https://alpaca.markets/docs/api-references/broker-api/#authentication-and-rate-limit +*/ diff --git a/contrib/alpacabkfeeder/api/entities.go b/contrib/alpacabkfeeder/api/entities.go new file mode 100644 index 00000000..f119024c --- /dev/null +++ b/contrib/alpacabkfeeder/api/entities.go @@ -0,0 +1,46 @@ +package api + +import "time" + +// Trade is a stock trade that happened on the market +type Trade struct { + ID int64 `json:"i"` + Exchange string `json:"x"` + Price float64 `json:"p"` + Size uint32 `json:"s"` + Timestamp time.Time `json:"t"` + Conditions []string `json:"c"` + Tape string `json:"z"` +} + +// Quote is a stock quote from the market +type Quote struct { + BidExchange string `json:"bx"` + BidPrice float64 `json:"bp"` + BidSize uint32 `json:"bs"` + AskExchange string `json:"ax"` + AskPrice float64 `json:"ap"` + AskSize uint32 `json:"as"` + Timestamp time.Time `json:"t"` + Conditions []string `json:"c"` + Tape string `json:"z"` +} + +// Bar is an aggregate of trades +type Bar struct { + Open float64 `json:"o"` + High float64 `json:"h"` + Low float64 `json:"l"` + Close float64 `json:"c"` + Volume uint64 `json:"v"` + Timestamp time.Time `json:"t"` +} + +// Snapshot is a snapshot of a symbol +type Snapshot struct { + LatestTrade *Trade `json:"latestTrade"` + LatestQuote *Quote `json:"latestQuote"` + MinuteBar *Bar `json:"minuteBar"` + DailyBar *Bar `json:"dailyBar"` + PrevDailyBar *Bar `json:"prevDailyBar"` +} diff --git a/contrib/alpacabkfeeder/api/v1/entities.go b/contrib/alpacabkfeeder/api/v1/entities.go new file mode 100644 index 00000000..f662088c --- /dev/null +++ b/contrib/alpacabkfeeder/api/v1/entities.go @@ -0,0 +1,32 @@ +package v1 + +import "time" + +type Asset struct { + ID string `json:"id"` + Name string `json:"name"` + Exchange string `json:"exchange"` + Class string `json:"asset_class"` + Symbol string `json:"symbol"` + Status string `json:"status"` + Tradable bool `json:"tradable"` + Marginable bool `json:"marginable"` + Shortable bool `json:"shortable"` + EasyToBorrow bool `json:"easy_to_borrow"` +} + +type Bar struct { + Time int64 `json:"t"` + Open float32 `json:"o"` + High float32 `json:"h"` + Low float32 `json:"l"` + Close float32 `json:"c"` + Volume int32 `json:"v"` +} + +type ListBarParams struct { + Timeframe string `url:"timeframe,omitempty"` + StartDt *time.Time `url:"start_dt,omitempty"` + EndDt *time.Time `url:"end_dt,omitempty"` + Limit *int `url:"limit,omitempty"` +} diff --git a/contrib/alpacabkfeeder/feed/backfill.go b/contrib/alpacabkfeeder/feed/backfill.go index fa18646d..a4b87c32 100644 --- a/contrib/alpacabkfeeder/feed/backfill.go +++ b/contrib/alpacabkfeeder/feed/backfill.go @@ -1,10 +1,9 @@ package feed import ( + v1 "github.com/alpacahq/marketstore/v4/contrib/alpacabkfeeder/api/v1" "time" - "github.com/alpacahq/alpaca-trade-api-go/alpaca" - "github.com/alpacahq/marketstore/v4/contrib/alpacabkfeeder/symbols" "github.com/alpacahq/marketstore/v4/contrib/alpacabkfeeder/writer" "github.com/alpacahq/marketstore/v4/utils/log" @@ -23,7 +22,7 @@ type Backfill struct { } type ListBarsAPIClient interface { - ListBars(symbols []string, opts alpaca.ListBarParams) (map[string][]alpaca.Bar, error) + ListBars(symbols []string, opts v1.ListBarParams) (map[string][]v1.Bar, error) } // NewBackfill initializes the module to backfill the historical daily chart data to marketstore. @@ -49,7 +48,7 @@ func (b *Backfill) UpdateSymbols() { for idx := range pageIndex(len(allSymbols), b.maxSymbolsPerReq) { for dateRange := range datePageIndex(b.since, until, b.maxBarsPerReq) { // fmt.Printf("start=%v, end=%v, symbols=%v\n", dateRange.From, dateRange.To, allSymbols[idx.From:idx.To]) - params := alpaca.ListBarParams{ + params := v1.ListBarParams{ Timeframe: backfillTimeframe, StartDt: time230000utc(dateRange.From), EndDt: time230000utc(dateRange.To), diff --git a/contrib/alpacabkfeeder/feed/backfill_test.go b/contrib/alpacabkfeeder/feed/backfill_test.go index 8d097d72..bc9e2f9b 100644 --- a/contrib/alpacabkfeeder/feed/backfill_test.go +++ b/contrib/alpacabkfeeder/feed/backfill_test.go @@ -4,13 +4,14 @@ import ( "testing" "time" - "github.com/alpacahq/alpaca-trade-api-go/alpaca" "github.com/pkg/errors" "github.com/stretchr/testify/assert" "github.com/alpacahq/marketstore/v4/contrib/alpacabkfeeder/feed" "github.com/alpacahq/marketstore/v4/contrib/alpacabkfeeder/internal" "github.com/alpacahq/marketstore/v4/contrib/alpacabkfeeder/writer" + + v1 "github.com/alpacahq/marketstore/v4/contrib/alpacabkfeeder/api/v1" ) var ( @@ -22,7 +23,7 @@ var ( d3 = time.Date(d3Year, d3Month, d3Day, 0, 0, 0, 0, time.UTC) ) -var testBars = map[string][]alpaca.Bar{ +var testBars = map[string][]v1.Bar{ "AAPL": { {Time: d3.Unix(), Open: 0, High: 0, Low: 0, Close: 0, Volume: 1}, {Time: d2.Unix(), Open: 0, High: 0, Low: 0, Close: 0, Volume: 2}, @@ -43,19 +44,19 @@ var testBars = map[string][]alpaca.Bar{ const errorSymbol = "ERROR" type MockErrorAPIClient struct { - testBars map[string][]alpaca.Bar + testBars map[string][]v1.Bar internal.MockAPIClient } // ListBars returns an error if symbol:"ERROR" is included, but returns data to other symbols. -func (mac *MockErrorAPIClient) ListBars(symbols []string, opts alpaca.ListBarParams) (map[string][]alpaca.Bar, error) { - ret := make(map[string][]alpaca.Bar) +func (mac *MockErrorAPIClient) ListBars(symbols []string, opts v1.ListBarParams) (map[string][]v1.Bar, error) { + ret := make(map[string][]v1.Bar) for _, symbl := range symbols { if symbl == errorSymbol { return nil, errors.New("error") } if bars, found := mac.testBars[symbl]; found { - barPage := make([]alpaca.Bar, 0) + barPage := make([]v1.Bar, 0) // filter by time for _, bar := range bars { @@ -79,7 +80,7 @@ type MockBarWriter struct { WriteCount int } -func (mbw *MockBarWriter) Write(symbol string, bars []alpaca.Bar) error { +func (mbw *MockBarWriter) Write(symbol string, bars []v1.Bar) error { // in order to assert the number of written bars in the test mbw.WriteCount += len(bars) return nil @@ -91,7 +92,7 @@ func TestBackfill_UpdateSymbols(t *testing.T) { tests := []struct { name string smbls []string - testBars map[string][]alpaca.Bar + testBars map[string][]v1.Bar barWriter writer.BarWriter maxSymbolsPerReq int maxBarsPerReq int diff --git a/contrib/alpacabkfeeder/feed/worker.go b/contrib/alpacabkfeeder/feed/worker.go index fc98ced5..8bf4e623 100644 --- a/contrib/alpacabkfeeder/feed/worker.go +++ b/contrib/alpacabkfeeder/feed/worker.go @@ -4,9 +4,9 @@ import ( "fmt" "time" - v2 "github.com/alpacahq/alpaca-trade-api-go/v2" "github.com/pkg/errors" + "github.com/alpacahq/marketstore/v4/contrib/alpacabkfeeder/api" "github.com/alpacahq/marketstore/v4/contrib/alpacabkfeeder/symbols" "github.com/alpacahq/marketstore/v4/contrib/alpacabkfeeder/writer" "github.com/alpacahq/marketstore/v4/utils/log" @@ -23,7 +23,7 @@ type Worker struct { } type GetSnapShotsAPIClient interface { - GetSnapshots(symbols []string) (map[string]*v2.Snapshot, error) + GetSnapshots(symbols []string) (map[string]*api.Snapshot, error) } // Run runs forever to get quotes data for each symbol in the target exchanges using Alpaca API periodically, diff --git a/contrib/alpacabkfeeder/feed/worker_test.go b/contrib/alpacabkfeeder/feed/worker_test.go index f1652906..d0fa356e 100644 --- a/contrib/alpacabkfeeder/feed/worker_test.go +++ b/contrib/alpacabkfeeder/feed/worker_test.go @@ -4,8 +4,7 @@ import ( "testing" "time" - v2 "github.com/alpacahq/alpaca-trade-api-go/v2" - + "github.com/alpacahq/marketstore/v4/contrib/alpacabkfeeder/api" "github.com/alpacahq/marketstore/v4/contrib/alpacabkfeeder/internal" ) @@ -28,7 +27,7 @@ type MockSnapshotWriter struct { } // Write increments the counter so that a unit test could assert how many times this function is called. -func (m *MockSnapshotWriter) Write(snapshots map[string]*v2.Snapshot) error { +func (m *MockSnapshotWriter) Write(snapshots map[string]*api.Snapshot) error { m.WriteCount++ return nil } diff --git a/contrib/alpacabkfeeder/internal/mocks.go b/contrib/alpacabkfeeder/internal/mocks.go index dce9ac15..a834885b 100644 --- a/contrib/alpacabkfeeder/internal/mocks.go +++ b/contrib/alpacabkfeeder/internal/mocks.go @@ -3,8 +3,8 @@ package internal import ( "time" - "github.com/alpacahq/alpaca-trade-api-go/alpaca" - v2 "github.com/alpacahq/alpaca-trade-api-go/v2" + "github.com/alpacahq/marketstore/v4/contrib/alpacabkfeeder/api" + v1 "github.com/alpacahq/marketstore/v4/contrib/alpacabkfeeder/api/v1" "github.com/alpacahq/marketstore/v4/utils/io" ) @@ -23,18 +23,18 @@ func (msm MockSymbolsManager) GetAllSymbols() []string { type MockAPIClient struct{} // GetSnapshots returns an empty response. -func (mac *MockAPIClient) GetSnapshots(symbols []string) (map[string]*v2.Snapshot, error) { - return map[string]*v2.Snapshot{}, nil +func (mac *MockAPIClient) GetSnapshots(symbols []string) (map[string]*api.Snapshot, error) { + return map[string]*api.Snapshot{}, nil } // ListAssets returns an empty api response. -func (mac *MockAPIClient) ListAssets(status *string) ([]alpaca.Asset, error) { - return []alpaca.Asset{}, nil +func (mac *MockAPIClient) ListAssets(status *string) ([]v1.Asset, error) { + return []v1.Asset{}, nil } // ListBars returns an empty api response. -func (mac *MockAPIClient) ListBars(symbols []string, opts alpaca.ListBarParams) (map[string][]alpaca.Bar, error) { - return map[string][]alpaca.Bar{}, nil +func (mac *MockAPIClient) ListBars(symbols []string, opts v1.ListBarParams) (map[string][]v1.Bar, error) { + return map[string][]v1.Bar{}, nil } // MockTimeChecker always returns Open. diff --git a/contrib/alpacabkfeeder/symbols/manager.go b/contrib/alpacabkfeeder/symbols/manager.go index c057c9a3..290813c7 100644 --- a/contrib/alpacabkfeeder/symbols/manager.go +++ b/contrib/alpacabkfeeder/symbols/manager.go @@ -2,8 +2,7 @@ package symbols import ( "fmt" - - "github.com/alpacahq/alpaca-trade-api-go/alpaca" + v1 "github.com/alpacahq/marketstore/v4/contrib/alpacabkfeeder/api/v1" "github.com/alpacahq/marketstore/v4/contrib/alpacabkfeeder/configs" "github.com/alpacahq/marketstore/v4/utils/log" @@ -25,7 +24,7 @@ type Manager interface { } type APIClient interface { - ListAssets(status *string) ([]alpaca.Asset, error) + ListAssets(status *string) ([]v1.Asset, error) } // ManagerImpl is an implementation of the Manager. diff --git a/contrib/alpacabkfeeder/symbols/manager_test.go b/contrib/alpacabkfeeder/symbols/manager_test.go index ba80114c..7c4881d3 100644 --- a/contrib/alpacabkfeeder/symbols/manager_test.go +++ b/contrib/alpacabkfeeder/symbols/manager_test.go @@ -4,18 +4,18 @@ import ( "reflect" "testing" - "github.com/alpacahq/alpaca-trade-api-go/alpaca" - "github.com/alpacahq/marketstore/v4/contrib/alpacabkfeeder/configs" "github.com/alpacahq/marketstore/v4/contrib/alpacabkfeeder/internal" + + v1 "github.com/alpacahq/marketstore/v4/contrib/alpacabkfeeder/api/v1" ) type MockListAssetsAPIClient struct { internal.MockAPIClient } -func (mac *MockListAssetsAPIClient) ListAssets(_ *string) ([]alpaca.Asset, error) { - return []alpaca.Asset{ +func (mac *MockListAssetsAPIClient) ListAssets(_ *string) ([]v1.Asset, error) { + return []v1.Asset{ { Name: "Hello", Exchange: "BATS", diff --git a/contrib/alpacabkfeeder/writer/bar_writer.go b/contrib/alpacabkfeeder/writer/bar_writer.go index c0acf9ba..1592a112 100644 --- a/contrib/alpacabkfeeder/writer/bar_writer.go +++ b/contrib/alpacabkfeeder/writer/bar_writer.go @@ -4,16 +4,17 @@ import ( "fmt" "time" - "github.com/alpacahq/alpaca-trade-api-go/alpaca" "github.com/pkg/errors" "github.com/alpacahq/marketstore/v4/utils/io" "github.com/alpacahq/marketstore/v4/utils/log" + + v1 "github.com/alpacahq/marketstore/v4/contrib/alpacabkfeeder/api/v1" ) // BarWriter is an interface to write chart data to the marketstore. type BarWriter interface { - Write(symbol string, bars []alpaca.Bar) error + Write(symbol string, bars []v1.Bar) error } // BarWriterImpl is an implementation of the BarWriter interface. @@ -25,7 +26,7 @@ type BarWriterImpl struct { } // Write converts the Response of the ListBars API to a ColumnSeriesMap and write it to the local marketstore server. -func (b BarWriterImpl) Write(symbol string, bars []alpaca.Bar) error { +func (b BarWriterImpl) Write(symbol string, bars []v1.Bar) error { // convert Bar Data to CSM (ColumnSeriesMap) csm := b.convertToCSM(symbol, bars) @@ -39,7 +40,7 @@ func (b BarWriterImpl) Write(symbol string, bars []alpaca.Bar) error { return nil } -func (b *BarWriterImpl) convertToCSM(symbol string, bars []alpaca.Bar) io.ColumnSeriesMap { +func (b *BarWriterImpl) convertToCSM(symbol string, bars []v1.Bar) io.ColumnSeriesMap { epochs := make([]int64, len(bars)) opens := make([]float32, len(bars)) closes := make([]float32, len(bars)) diff --git a/contrib/alpacabkfeeder/writer/bar_writer_test.go b/contrib/alpacabkfeeder/writer/bar_writer_test.go index a827a72c..42257b6a 100644 --- a/contrib/alpacabkfeeder/writer/bar_writer_test.go +++ b/contrib/alpacabkfeeder/writer/bar_writer_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - "github.com/alpacahq/alpaca-trade-api-go/alpaca" + v1 "github.com/alpacahq/marketstore/v4/contrib/alpacabkfeeder/api/v1" "github.com/alpacahq/marketstore/v4/contrib/alpacabkfeeder/internal" "github.com/alpacahq/marketstore/v4/utils/io" @@ -27,7 +27,7 @@ func TestBarWriterImpl_Write(t *testing.T) { // 2 bar data symbol := "1234" - bars := []alpaca.Bar{ + bars := []v1.Bar{ { Time: barTimestamp1.Unix(), Open: 12.3, diff --git a/contrib/alpacabkfeeder/writer/snapshot_writer.go b/contrib/alpacabkfeeder/writer/snapshot_writer.go index edeca933..10481638 100644 --- a/contrib/alpacabkfeeder/writer/snapshot_writer.go +++ b/contrib/alpacabkfeeder/writer/snapshot_writer.go @@ -4,15 +4,15 @@ import ( "fmt" "time" - v2 "github.com/alpacahq/alpaca-trade-api-go/v2" - "github.com/alpacahq/marketstore/v4/utils/io" "github.com/alpacahq/marketstore/v4/utils/log" + + "github.com/alpacahq/marketstore/v4/contrib/alpacabkfeeder/api" ) // SnapshotWriter is an interface to write the realtime stock data to the marketstore. type SnapshotWriter interface { - Write(snapshots map[string]*v2.Snapshot) error + Write(snapshots map[string]*api.Snapshot) error } // SnapshotWriterImpl is an implementation of the SnapshotWriter interface. @@ -24,7 +24,7 @@ type SnapshotWriterImpl struct { } // Write converts the map(key:symbol, value:snapshot) to a ColumnSeriesMap and write it to the local marketstore server. -func (q SnapshotWriterImpl) Write(snapshots map[string]*v2.Snapshot) error { +func (q SnapshotWriterImpl) Write(snapshots map[string]*api.Snapshot) error { // convert Snapshot Data to CSM (ColumnSeriesMap) csm := q.convertToCSM(snapshots) @@ -37,7 +37,7 @@ func (q SnapshotWriterImpl) Write(snapshots map[string]*v2.Snapshot) error { return nil } -func (q *SnapshotWriterImpl) convertToCSM(snapshots map[string]*v2.Snapshot) io.ColumnSeriesMap { +func (q *SnapshotWriterImpl) convertToCSM(snapshots map[string]*api.Snapshot) io.ColumnSeriesMap { csm := io.NewColumnSeriesMap() for symbol, snapshot := range snapshots { @@ -54,13 +54,13 @@ func (q *SnapshotWriterImpl) convertToCSM(snapshots map[string]*v2.Snapshot) io. // These additional fields are not always provided. // fill empty data to keep the number of columns in the CSM if snapshot.DailyBar == nil { - snapshot.DailyBar = &v2.Bar{} + snapshot.DailyBar = &api.Bar{} } if snapshot.PrevDailyBar == nil { - snapshot.PrevDailyBar = &v2.Bar{} + snapshot.PrevDailyBar = &api.Bar{} } if snapshot.MinuteBar == nil { - snapshot.MinuteBar = &v2.Bar{} + snapshot.MinuteBar = &api.Bar{} } cs := q.newColumnSeries(latestTime.Unix(), snapshot) tbk := io.NewTimeBucketKey(symbol + "/" + q.Timeframe + "/TICK") @@ -77,7 +77,7 @@ func latestTime(time1, time2 time.Time) time.Time { return time2 } -func (q SnapshotWriterImpl) newColumnSeries(epoch int64, ss *v2.Snapshot) *io.ColumnSeries { +func (q SnapshotWriterImpl) newColumnSeries(epoch int64, ss *api.Snapshot) *io.ColumnSeries { cs := io.NewColumnSeries() cs.AddColumn("Epoch", []int64{epoch}) cs.AddColumn("QuoteTimestamp", []int64{ss.LatestQuote.Timestamp.In(q.Timezone).Unix()}) diff --git a/contrib/alpacabkfeeder/writer/snapshot_writer_test.go b/contrib/alpacabkfeeder/writer/snapshot_writer_test.go index 64318796..1829e6f8 100644 --- a/contrib/alpacabkfeeder/writer/snapshot_writer_test.go +++ b/contrib/alpacabkfeeder/writer/snapshot_writer_test.go @@ -4,42 +4,42 @@ import ( "testing" "time" - v2 "github.com/alpacahq/alpaca-trade-api-go/v2" "github.com/pkg/errors" "github.com/stretchr/testify/require" + "github.com/alpacahq/marketstore/v4/contrib/alpacabkfeeder/api" "github.com/alpacahq/marketstore/v4/contrib/alpacabkfeeder/internal" "github.com/alpacahq/marketstore/v4/utils/io" ) var ( - exampleTrade = &v2.Trade{ + exampleTrade = &api.Trade{ Price: 1, Size: 2, Timestamp: time.Unix(3, 0), } - exampleQuote = &v2.Quote{ + exampleQuote = &api.Quote{ BidPrice: 4, BidSize: 5, AskPrice: 6, AskSize: 7, Timestamp: time.Unix(8, 0), } - exampleDailyBar = &v2.Bar{ + exampleDailyBar = &api.Bar{ Open: 9, High: 10, Low: 11, Close: 12, Volume: 13, } - examplePreviousDailyBar = &v2.Bar{ + examplePreviousDailyBar = &api.Bar{ Open: 14, High: 15, Low: 16, Close: 17, Volume: 18, } - exampleMinuteBar = &v2.Bar{ + exampleMinuteBar = &api.Bar{ Open: 19, High: 20, Low: 21, @@ -57,7 +57,7 @@ func TestSnapshotWriterImpl_Write(t *testing.T) { tests := []struct { name string fields fields - snapshots map[string]*v2.Snapshot + snapshots map[string]*api.Snapshot writeErr error wantErr bool wantTBKs []io.TimeBucketKey @@ -70,7 +70,7 @@ func TestSnapshotWriterImpl_Write(t *testing.T) { Timeframe: "1Sec", Timezone: time.UTC, }, - snapshots: map[string]*v2.Snapshot{ + snapshots: map[string]*api.Snapshot{ "AAPL": { LatestTrade: exampleTrade, LatestQuote: exampleQuote, @@ -127,7 +127,7 @@ func TestSnapshotWriterImpl_Write(t *testing.T) { Timeframe: "1Sec", Timezone: time.UTC, }, - snapshots: map[string]*v2.Snapshot{ + snapshots: map[string]*api.Snapshot{ "AAPL": { LatestTrade: exampleTrade, LatestQuote: exampleQuote, diff --git a/go.mod b/go.mod index 47880920..05bfeada 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,6 @@ require ( cloud.google.com/go v0.26.0 code.cloudfoundry.org/bytefmt v0.0.0-20180906201452-2aa6f33b730c github.com/adshao/go-binance v0.0.0-20181012004556-e9a4ac01ca48 - github.com/alpacahq/alpaca-trade-api-go v1.9.0 github.com/alpacahq/rpc v1.3.0 github.com/antlr/antlr4 v0.0.0-20181031000400-73836edf1f84 github.com/bitly/go-simplejson v0.5.0 // indirect @@ -48,11 +47,13 @@ require ( require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/google/gopacket v1.1.16-0.20181023151400-a35e09f9f224 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/kr/pretty v0.3.0 // indirect github.com/kr/text v0.2.0 // indirect + github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/mdlayher/raw v0.0.0-20181016155347-fa5ef3332ca9 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect @@ -62,7 +63,6 @@ require ( github.com/prometheus/common v0.10.0 // indirect github.com/prometheus/procfs v0.1.3 // indirect github.com/rogpeppe/go-internal v1.8.0 // indirect - github.com/shopspring/decimal v1.2.0 // indirect github.com/spf13/pflag v1.0.3 // indirect go.uber.org/atomic v1.6.0 // indirect go.uber.org/multierr v1.5.0 // indirect diff --git a/go.sum b/go.sum index 7c4261a0..5020d15c 100644 --- a/go.sum +++ b/go.sum @@ -11,8 +11,6 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alpacahq/alpaca-trade-api-go v1.9.0 h1:pq8C7P9/Bo8efyaPo541s2zCLP8yXSE+ZwAX0gAC/ec= -github.com/alpacahq/alpaca-trade-api-go v1.9.0/go.mod h1:uxzSLpUDKf592/0RU2DX2sGJN3o4t+h4b74ln4gQ4+w= github.com/alpacahq/rpc v1.3.0 h1:lB7T3oTSq0b4pFsntmAq4Be0ngJDrJcbD1KtGIc9P1s= github.com/alpacahq/rpc v1.3.0/go.mod h1:UfzqdExg1VFMZA6aiQTyBhgBxHBpWzCi5OknSby/wmQ= github.com/antlr/antlr4 v0.0.0-20181031000400-73836edf1f84 h1:c4ZppOrw9VXa9s4i6cnxC7YQUEZ5RbmVKfEY5g4yAow= @@ -67,22 +65,13 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= -github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= -github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= -github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= -github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= -github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -94,7 +83,6 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= @@ -119,7 +107,6 @@ github.com/google/gopacket v1.1.16-0.20181023151400-a35e09f9f224 h1:78xLKlzgK/iE github.com/google/gopacket v1.1.16-0.20181023151400-a35e09f9f224/go.mod h1:UCLx9mCmAwsVbn6qQl1WIEt2SO7Nd2fD0th1TBAsqBw= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= @@ -132,14 +119,12 @@ github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANyt github.com/johnmccabe/go-bitbar v0.4.0/go.mod h1:i67T2iQ7Ql/v6x4NbPLlW7eTs+3d/vZgVDl12pr03C8= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.10.4 h1:jFzIFaf586tquEB5EhzQG0HwGNSlgAJpG53G6Ss11wc= github.com/klauspost/compress v1.10.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -152,11 +137,9 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2 h1:JAEbJn3j/FrhdWA9jW8B5ajsLIjeuEHLi8xE4fk997o= github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O4hW7qJOT7vyQPKrWmj26uf5wMc/IiIs= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mdlayher/raw v0.0.0-20181016155347-fa5ef3332ca9 h1:tOtO8DXiNGj9NshRKHWiZuGlSldPFzFCFYhNtsKTBCs= @@ -223,8 +206,6 @@ github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 h1:GHRpF1pTW19a github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8= github.com/secsy/goftp v0.0.0-20200609142545-aa2de14babf4 h1:PT+ElG/UUFMfqy5HrxJxNzj3QBOf7dZwupeVC+mG1Lo= github.com/secsy/goftp v0.0.0-20200609142545-aa2de14babf4/go.mod h1:MnkX001NG75g3p8bhFycnyIjeQoOjGL6CEIsdE/nKSY= -github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= -github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= @@ -244,19 +225,14 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/timpalpant/go-iex v0.0.0-20181027174710-0b8a5fdd2ec1 h1:UZLDNmmZv1BjUSln9HtJmQ48owVNlF3dRos6QYRU+Zs= github.com/timpalpant/go-iex v0.0.0-20181027174710-0b8a5fdd2ec1/go.mod h1:Mh9D8lmzz9iB/uACUY9Pu0Q95wVHG7hSOffKtOMpJ9k= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= -github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= -github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= -github.com/vmihailenco/msgpack/v5 v5.1.4/go.mod h1:C5gboKD0TJPqWDTVTtrQNfRbiBwHZGo8UTqP/9/XvLI= -github.com/vmihailenco/tagparser v0.1.2/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -326,7 +302,6 @@ golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -341,7 +316,6 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -405,7 +379,6 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= @@ -416,4 +389,3 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=