a6bf8632ce
Typed result structs replace empty envelopes. Equity place sends ref_id only so live additionalProperties:false schemas accept the call.
336 lines
8.2 KiB
Go
336 lines
8.2 KiB
Go
package options
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"time"
|
|
|
|
decimal "github.com/alpacahq/alpacadecimal"
|
|
"s1d3sw1ped/robinhood-agentic-mcp/client"
|
|
"s1d3sw1ped/robinhood-agentic-mcp/internal/wire"
|
|
)
|
|
|
|
const (
|
|
toolChains = "get_option_chains"
|
|
toolInstruments = "get_option_instruments"
|
|
toolQuotes = "get_option_quotes"
|
|
toolPositions = "get_option_positions"
|
|
toolOrders = "get_option_orders"
|
|
toolHistoricals = "get_option_historicals"
|
|
)
|
|
|
|
// ChainsRequest is the argument set for get_option_chains.
|
|
type ChainsRequest struct {
|
|
IDs string
|
|
UnderlyingSymbol string
|
|
}
|
|
|
|
// OptionChain is one chain from get_option_chains.
|
|
type OptionChain struct {
|
|
ID string
|
|
UnderlyingSymbol string
|
|
ExpirationDates []string
|
|
}
|
|
|
|
// ChainsResult is the parsed get_option_chains payload.
|
|
type ChainsResult struct {
|
|
Chains []OptionChain
|
|
}
|
|
|
|
// InstrumentsRequest is the argument set for get_option_instruments.
|
|
type InstrumentsRequest struct {
|
|
ChainID string
|
|
ChainSymbol string
|
|
ExpirationDates string
|
|
StrikePrice string
|
|
Type string
|
|
State string
|
|
Tradability string
|
|
IDs string
|
|
Cursor string
|
|
}
|
|
|
|
// OptionInstrument is one contract from get_option_instruments.
|
|
type OptionInstrument struct {
|
|
ID string
|
|
ChainID string
|
|
ChainSymbol string
|
|
ExpirationDate string
|
|
StrikePrice decimal.Decimal
|
|
Type string
|
|
State string
|
|
}
|
|
|
|
// InstrumentsResult is the parsed get_option_instruments payload.
|
|
type InstrumentsResult struct {
|
|
Instruments []OptionInstrument
|
|
NextCursor string
|
|
}
|
|
|
|
// QuotesRequest is the argument set for get_option_quotes.
|
|
type QuotesRequest struct {
|
|
InstrumentIDs []string
|
|
}
|
|
|
|
// OptionQuote is one contract quote from get_option_quotes.
|
|
type OptionQuote struct {
|
|
InstrumentID string
|
|
Bid decimal.Decimal
|
|
Ask decimal.Decimal
|
|
Last decimal.Decimal
|
|
PrevClose decimal.Decimal
|
|
Mark decimal.Decimal
|
|
}
|
|
|
|
// QuotesResult is the parsed get_option_quotes payload.
|
|
type QuotesResult struct {
|
|
Quotes []OptionQuote
|
|
}
|
|
|
|
// PositionsRequest is the argument set for get_option_positions.
|
|
type PositionsRequest struct {
|
|
AccountNumber string
|
|
Nonzero bool
|
|
ChainIDs string
|
|
OptionIDs string
|
|
Type string
|
|
OptionType string
|
|
ExpirationDate string
|
|
ExpirationDateLTE string
|
|
ExpirationDateGTE string
|
|
Cursor string
|
|
}
|
|
|
|
// OptionPosition is one holding from get_option_positions.
|
|
type OptionPosition struct {
|
|
OptionID string
|
|
ChainID string
|
|
Type string
|
|
OptionType string
|
|
Quantity decimal.Decimal
|
|
AveragePrice decimal.Decimal
|
|
ExpirationDate string
|
|
}
|
|
|
|
// PositionsResult is the parsed get_option_positions payload.
|
|
type PositionsResult struct {
|
|
Positions []OptionPosition
|
|
NextCursor string
|
|
}
|
|
|
|
// OrdersRequest is the argument set for get_option_orders.
|
|
type OrdersRequest struct {
|
|
AccountNumber string
|
|
OrderID string
|
|
State string
|
|
CreatedAtGTE string
|
|
ChainIDs string
|
|
UnderlyingType string
|
|
PlacedAgent string
|
|
Cursor string
|
|
}
|
|
|
|
// OrdersResult is the parsed get_option_orders payload.
|
|
type OrdersResult struct {
|
|
Orders []Order
|
|
NextCursor string
|
|
}
|
|
|
|
// HistoricalsRequest is the argument set for get_option_historicals.
|
|
type HistoricalsRequest struct {
|
|
InstrumentIDs []string
|
|
StartTime time.Time
|
|
EndTime time.Time
|
|
Interval string
|
|
Bounds string
|
|
}
|
|
|
|
// OptionBar is one OHLC bar from get_option_historicals.
|
|
type OptionBar struct {
|
|
InstrumentID string
|
|
Time time.Time
|
|
Open, High, Low, Close decimal.Decimal
|
|
Volume decimal.Decimal
|
|
Interpolated bool
|
|
}
|
|
|
|
// HistoricalsResult is the parsed get_option_historicals payload.
|
|
type HistoricalsResult struct {
|
|
Bars []OptionBar
|
|
}
|
|
|
|
// Chains calls get_option_chains.
|
|
func (c *Client) Chains(ctx context.Context, req ChainsRequest) (ChainsResult, error) {
|
|
args := map[string]any{}
|
|
if req.IDs != "" {
|
|
args["ids"] = req.IDs
|
|
}
|
|
if req.UnderlyingSymbol != "" {
|
|
args["underlying_symbol"] = req.UnderlyingSymbol
|
|
}
|
|
var out ChainsResult
|
|
if err := c.parse(ctx, toolChains, args, &out); err != nil {
|
|
return ChainsResult{}, err
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// Instruments calls get_option_instruments.
|
|
func (c *Client) Instruments(ctx context.Context, req InstrumentsRequest) (InstrumentsResult, error) {
|
|
args := map[string]any{}
|
|
if req.ChainID != "" {
|
|
args["chain_id"] = req.ChainID
|
|
}
|
|
if req.ChainSymbol != "" {
|
|
args["chain_symbol"] = req.ChainSymbol
|
|
}
|
|
if req.ExpirationDates != "" {
|
|
args["expiration_dates"] = req.ExpirationDates
|
|
}
|
|
if req.StrikePrice != "" {
|
|
args["strike_price"] = req.StrikePrice
|
|
}
|
|
if req.Type != "" {
|
|
args["type"] = req.Type
|
|
}
|
|
if req.State != "" {
|
|
args["state"] = req.State
|
|
}
|
|
if req.Tradability != "" {
|
|
args["tradability"] = req.Tradability
|
|
}
|
|
if req.IDs != "" {
|
|
args["ids"] = req.IDs
|
|
}
|
|
if req.Cursor != "" {
|
|
args["cursor"] = req.Cursor
|
|
}
|
|
var out InstrumentsResult
|
|
if err := c.parse(ctx, toolInstruments, args, &out); err != nil {
|
|
return InstrumentsResult{}, err
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// Quotes calls get_option_quotes.
|
|
func (c *Client) Quotes(ctx context.Context, req QuotesRequest) (QuotesResult, error) {
|
|
args := map[string]any{}
|
|
if len(req.InstrumentIDs) > 0 {
|
|
args["instrument_ids"] = req.InstrumentIDs
|
|
}
|
|
var out QuotesResult
|
|
if err := c.parse(ctx, toolQuotes, args, &out); err != nil {
|
|
return QuotesResult{}, err
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// Positions calls get_option_positions.
|
|
func (c *Client) Positions(ctx context.Context, req PositionsRequest) (PositionsResult, error) {
|
|
args := map[string]any{}
|
|
if req.AccountNumber != "" {
|
|
args["account_number"] = req.AccountNumber
|
|
}
|
|
if req.Nonzero {
|
|
args["nonzero"] = true
|
|
}
|
|
if req.ChainIDs != "" {
|
|
args["chain_ids"] = req.ChainIDs
|
|
}
|
|
if req.OptionIDs != "" {
|
|
args["option_ids"] = req.OptionIDs
|
|
}
|
|
if req.Type != "" {
|
|
args["type"] = req.Type
|
|
}
|
|
if req.OptionType != "" {
|
|
args["option_type"] = req.OptionType
|
|
}
|
|
if req.ExpirationDate != "" {
|
|
args["expiration_date"] = req.ExpirationDate
|
|
}
|
|
if req.ExpirationDateLTE != "" {
|
|
args["expiration_date_lte"] = req.ExpirationDateLTE
|
|
}
|
|
if req.ExpirationDateGTE != "" {
|
|
args["expiration_date_gte"] = req.ExpirationDateGTE
|
|
}
|
|
if req.Cursor != "" {
|
|
args["cursor"] = req.Cursor
|
|
}
|
|
var out PositionsResult
|
|
if err := c.parse(ctx, toolPositions, args, &out); err != nil {
|
|
return PositionsResult{}, err
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// Orders calls get_option_orders.
|
|
func (c *Client) Orders(ctx context.Context, req OrdersRequest) (OrdersResult, error) {
|
|
args := map[string]any{}
|
|
if req.AccountNumber != "" {
|
|
args["account_number"] = req.AccountNumber
|
|
}
|
|
if req.OrderID != "" {
|
|
args["order_id"] = req.OrderID
|
|
}
|
|
if req.State != "" {
|
|
args["state"] = req.State
|
|
}
|
|
if req.CreatedAtGTE != "" {
|
|
args["created_at_gte"] = req.CreatedAtGTE
|
|
}
|
|
if req.ChainIDs != "" {
|
|
args["chain_ids"] = req.ChainIDs
|
|
}
|
|
if req.UnderlyingType != "" {
|
|
args["underlying_type"] = req.UnderlyingType
|
|
}
|
|
if req.PlacedAgent != "" {
|
|
args["placed_agent"] = req.PlacedAgent
|
|
}
|
|
if req.Cursor != "" {
|
|
args["cursor"] = req.Cursor
|
|
}
|
|
var out OrdersResult
|
|
if err := c.parse(ctx, toolOrders, args, &out); err != nil {
|
|
return OrdersResult{}, err
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// Historicals calls get_option_historicals.
|
|
func (c *Client) Historicals(ctx context.Context, req HistoricalsRequest) (HistoricalsResult, error) {
|
|
args := map[string]any{
|
|
"start_time": req.StartTime.UTC().Format(time.RFC3339),
|
|
}
|
|
if len(req.InstrumentIDs) > 0 {
|
|
args["instrument_ids"] = req.InstrumentIDs
|
|
}
|
|
if !req.EndTime.IsZero() {
|
|
args["end_time"] = req.EndTime.UTC().Format(time.RFC3339)
|
|
}
|
|
if req.Interval != "" {
|
|
args["interval"] = req.Interval
|
|
}
|
|
if req.Bounds != "" {
|
|
args["bounds"] = req.Bounds
|
|
}
|
|
var out HistoricalsResult
|
|
if err := c.parse(ctx, toolHistoricals, args, &out); err != nil {
|
|
return HistoricalsResult{}, err
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (c *Client) parse(ctx context.Context, tool string, args map[string]any, dest any) error {
|
|
raw, err := c.c.Call(ctx, tool, args)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := json.Unmarshal(wire.Unwrap(raw), dest); err != nil {
|
|
return client.ToolErrorf(tool, "parse: %w", err)
|
|
}
|
|
return nil
|
|
}
|