-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: implement websocket client for quotes
Signed-off-by: Marek Cermak <[email protected]>
- Loading branch information
Showing
11 changed files
with
386 additions
and
193 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
// Package market defines HTTP and a Websocket client for the FMP API. | ||
package market | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"log/slog" | ||
"sync" | ||
|
||
"github.com/gorilla/websocket" | ||
"go.tradeforge.dev/background/manager" | ||
|
||
"go.tradeforge.dev/fmp/client/rest" | ||
"go.tradeforge.dev/fmp/model" | ||
) | ||
|
||
type HTTPClientConfig struct { | ||
ApiKey string `validate:"required" env:"FMP_API_KEY"` | ||
} | ||
|
||
// HTTPClient defines a client to the Polygon REST API. | ||
type HTTPClient struct { | ||
QuoteClient | ||
TickerClient | ||
EventClient | ||
} | ||
|
||
// NewHTTPClient returns a new HTTP client with the specified API key and config. | ||
func NewHTTPClient( | ||
config HTTPClientConfig, | ||
logger *slog.Logger, | ||
) *HTTPClient { | ||
c := rest.New( | ||
config.ApiKey, | ||
logger, | ||
) | ||
|
||
return &HTTPClient{ | ||
QuoteClient: QuoteClient{ | ||
Client: c, | ||
}, | ||
TickerClient: TickerClient{ | ||
Client: c, | ||
}, | ||
EventClient: EventClient{ | ||
Client: c, | ||
}, | ||
} | ||
} | ||
|
||
type WebsocketClientConfig struct { | ||
ApiKey string `validate:"required" env:"FMP_API_KEY"` | ||
} | ||
|
||
func NewWebsocketClient( | ||
ctx context.Context, | ||
config WebsocketClientConfig, | ||
logger *slog.Logger, | ||
) (*WebsocketClient, error) { | ||
if ctx.Done() != nil { | ||
return nil, errors.New("context is already cancelled") | ||
} | ||
return &WebsocketClient{ | ||
ctx: ctx, | ||
config: config, | ||
logger: logger, | ||
manager: manager.New(ctx, manager.WithCancelOnError(), manager.WithFirstError()), | ||
|
||
events: make(chan model.WebsocketMesssage), | ||
quotes: make(chan model.WebsocketQuote), | ||
}, nil | ||
} | ||
|
||
type WebsocketClient struct { | ||
ctx context.Context | ||
config WebsocketClientConfig | ||
logger *slog.Logger | ||
|
||
manager *manager.Manager | ||
|
||
connectOnce sync.Once | ||
connectionLock sync.Mutex | ||
connection *websocket.Conn | ||
|
||
subscribedQuotesLock sync.RWMutex | ||
subscribedQuotes map[string]struct{} | ||
|
||
events chan model.WebsocketMesssage | ||
quotes chan model.WebsocketQuote | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,77 +1,77 @@ | ||
package market | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"net/http" | ||
"context" | ||
"fmt" | ||
"net/http" | ||
|
||
"go.tradeforge.dev/fmp/client" | ||
"go.tradeforge.dev/fmp/model" | ||
"go.tradeforge.dev/fmp/client/rest" | ||
"go.tradeforge.dev/fmp/model" | ||
) | ||
|
||
const ( | ||
GetRealTimeQuotePath = "/api/v3/stock/full/real-time-price/:symbol" | ||
GetFullPricePath = "/api/v3/quote/:symbol" | ||
GetPriceChangePath = "/api/v3/stock-price-target/:symbol" | ||
GetRealTimeQuotePath = "/api/v3/stock/full/real-time-price/:symbol" | ||
GetFullPricePath = "/api/v3/quote/:symbol" | ||
GetPriceChangePath = "/api/v3/stock-price-target/:symbol" | ||
|
||
BatchGetRealTimeQuotePath = "/api/v3/stock/full/real-time-price/:symbols" | ||
BatchGetFullPricePath = "/api/v3/quote/:symbols" | ||
BatchGetRealTimeQuotePath = "/api/v3/stock/full/real-time-price/:symbols" | ||
BatchGetFullPricePath = "/api/v3/quote/:symbols" | ||
) | ||
|
||
type QuoteClient struct { | ||
*client.Client | ||
*rest.Client | ||
} | ||
|
||
func (qc *QuoteClient) GetFullPrice(ctx context.Context, params *model.GetFullPriceParams, opts ...model.RequestOption) (response *model.GetFullPriceResponse, err error) { | ||
var res []model.GetFullPriceResponse | ||
_, err = qc.Call(ctx, http.MethodGet, GetFullPricePath, params, &res, opts...) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if len(res) != 1 { | ||
return nil, fmt.Errorf("expected response of length 1, got %d", len(res)) | ||
} | ||
return &res[0], nil | ||
var res []model.GetFullPriceResponse | ||
_, err = qc.Call(ctx, http.MethodGet, GetFullPricePath, params, &res, opts...) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if len(res) != 1 { | ||
return nil, fmt.Errorf("expected response of length 1, got %d", len(res)) | ||
} | ||
return &res[0], nil | ||
} | ||
|
||
func (qc *QuoteClient) BatchGetFullPrice(ctx context.Context, params *model.BatchGetFullPriceParams, opts ...model.RequestOption) (model.BatchGetFullPriceResponse, error) { | ||
var res model.BatchGetFullPriceResponse | ||
_, err := qc.Call(ctx, http.MethodGet, BatchGetFullPricePath, params, &res, opts...) | ||
return res, err | ||
var res model.BatchGetFullPriceResponse | ||
_, err := qc.Call(ctx, http.MethodGet, BatchGetFullPricePath, params, &res, opts...) | ||
return res, err | ||
} | ||
|
||
func (qc *QuoteClient) GetPriceChange(ctx context.Context, params *model.GetPriceChangeParams, opts ...model.RequestOption) (response *model.GetPriceChangeResponse, err error) { | ||
var res []model.GetPriceChangeResponse | ||
_, err = qc.Call(ctx, http.MethodGet, GetPriceChangePath, params, &res, opts...) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if len(res) != 1 { | ||
return nil, fmt.Errorf("expected response of length 1, got %d", len(res)) | ||
} | ||
return &res[0], nil | ||
var res []model.GetPriceChangeResponse | ||
_, err = qc.Call(ctx, http.MethodGet, GetPriceChangePath, params, &res, opts...) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if len(res) != 1 { | ||
return nil, fmt.Errorf("expected response of length 1, got %d", len(res)) | ||
} | ||
return &res[0], nil | ||
} | ||
|
||
func (qc *QuoteClient) BatchGetPriceChange(ctx context.Context, params *model.BatchGetPriceChangeParams, opts ...model.RequestOption) ([]model.GetPriceChangeResponse, error) { | ||
var res []model.GetPriceChangeResponse | ||
_, err := qc.Call(ctx, http.MethodGet, BatchGetRealTimeQuotePath, params, &res, opts...) | ||
return res, err | ||
var res []model.GetPriceChangeResponse | ||
_, err := qc.Call(ctx, http.MethodGet, BatchGetRealTimeQuotePath, params, &res, opts...) | ||
return res, err | ||
} | ||
|
||
func (qc *QuoteClient) GetRealTimeQuote(ctx context.Context, params *model.GetRealTimeQuoteParams, opts ...model.RequestOption) (response *model.GetRealTimeQuoteResponse, err error) { | ||
var res []model.GetRealTimeQuoteResponse | ||
_, err = qc.Call(ctx, http.MethodGet, GetRealTimeQuotePath, params, &res, opts...) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if len(res) != 1 { | ||
return nil, fmt.Errorf("expected response of length 1, got %d", len(res)) | ||
} | ||
return &res[0], nil | ||
var res []model.GetRealTimeQuoteResponse | ||
_, err = qc.Call(ctx, http.MethodGet, GetRealTimeQuotePath, params, &res, opts...) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if len(res) != 1 { | ||
return nil, fmt.Errorf("expected response of length 1, got %d", len(res)) | ||
} | ||
return &res[0], nil | ||
} | ||
|
||
func (qc *QuoteClient) BatchGetRealTimeQuote(ctx context.Context, params *model.BatchGetRealTimeQuoteParams, opts ...model.RequestOption) (model.BatchGetRealTimeQuoteResponse, error) { | ||
var res model.BatchGetRealTimeQuoteResponse | ||
_, err := qc.Call(ctx, http.MethodGet, BatchGetRealTimeQuotePath, params, &res, opts...) | ||
return res, err | ||
var res model.BatchGetRealTimeQuoteResponse | ||
_, err := qc.Call(ctx, http.MethodGet, BatchGetRealTimeQuotePath, params, &res, opts...) | ||
return res, err | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.