Files
robinhood-agentic-mcp/watchlists/watchlists.go
T

325 lines
8.5 KiB
Go

package watchlists
import (
"context"
"encoding/json"
"s1d3sw1ped/robinhood-agentic-mcp/client"
"s1d3sw1ped/robinhood-agentic-mcp/internal/wire"
)
const (
toolLists = "get_watchlists"
toolItems = "get_watchlist_items"
toolOptionList = "get_option_watchlist"
toolPopular = "get_popular_watchlists"
toolCreate = "create_watchlist"
toolUpdate = "update_watchlist"
toolFollow = "follow_watchlist"
toolUnfollow = "unfollow_watchlist"
toolAdd = "add_to_watchlist"
toolRemove = "remove_from_watchlist"
toolAddOption = "add_option_to_watchlist"
toolRemoveOption = "remove_option_from_watchlist"
)
// ListsRequest is the argument set for get_watchlists (none).
type ListsRequest struct{}
// Watchlist is one list from get_watchlists. Title falls back to name.
type Watchlist struct {
ID string
Title string
}
// ListsResult is the parsed get_watchlists payload.
type ListsResult struct {
Watchlists []Watchlist
}
// ItemsRequest is the argument set for get_watchlist_items.
type ItemsRequest struct {
ListID string
}
// Instrument is the nested instrument object on a watchlist item.
type Instrument struct {
Symbol string
Type string
}
// Item is one row from get_watchlist_items. Object types are not filtered.
type Item struct {
Symbol string
ObjectType string
Instrument *Instrument
}
// ItemsResult is the parsed get_watchlist_items payload.
type ItemsResult struct {
Items []Item
}
// OptionListRequest is the argument set for get_option_watchlist (none).
type OptionListRequest struct{}
// OptionListResult is the parsed get_option_watchlist payload.
type OptionListResult struct{}
// PopularRequest is the argument set for get_popular_watchlists (none).
type PopularRequest struct{}
// PopularResult is the parsed get_popular_watchlists payload.
type PopularResult struct{}
// CreateRequest is the argument set for create_watchlist.
type CreateRequest struct {
DisplayName string
IconEmoji string
DisplayDescription string
}
// UpdateRequest is the argument set for update_watchlist.
type UpdateRequest struct {
ListID string
DisplayName string
IconEmoji string
DisplayDescription string
}
// ListIDRequest is a single list_id argument.
type ListIDRequest struct {
ListID string
}
// AddRequest is the argument set for add_to_watchlist.
type AddRequest struct {
ListID string
Symbols []string
CurrencyPairIDs []string
IndexIDs []string
}
// RemoveRequest is the argument set for remove_from_watchlist.
type RemoveRequest struct {
ListID string
Symbols []string
CurrencyPairIDs []string
IndexIDs []string
}
// OptionMutateRequest is the argument set for add/remove option watchlist tools.
type OptionMutateRequest struct {
OptionIDs []string
PositionType string
}
// Lists calls get_watchlists.
func (c *Client) Lists(ctx context.Context, req ListsRequest) (ListsResult, error) {
raw, err := c.c.Call(ctx, toolLists, map[string]any{})
if err != nil {
return ListsResult{}, err
}
data := wire.Unwrap(raw)
var wrap struct {
Watchlists []watchlistJSON `json:"watchlists"`
}
if err := json.Unmarshal(data, &wrap); err != nil {
var list []watchlistJSON
if err := json.Unmarshal(data, &list); err != nil {
return ListsResult{}, client.ToolErrorf(toolLists, "parse: %w", err)
}
wrap.Watchlists = list
}
out := make([]Watchlist, 0, len(wrap.Watchlists))
for _, w := range wrap.Watchlists {
title := w.Title
if title == "" {
title = w.Name
}
out = append(out, Watchlist{ID: w.ID, Title: title})
}
return ListsResult{Watchlists: out}, nil
}
// Items calls get_watchlist_items. Every object_type is kept.
func (c *Client) Items(ctx context.Context, req ItemsRequest) (ItemsResult, error) {
raw, err := c.c.Call(ctx, toolItems, listIDArgs(req.ListID))
if err != nil {
return ItemsResult{}, err
}
data := wire.Unwrap(raw)
var wrap struct {
Items []itemJSON `json:"items"`
}
if err := json.Unmarshal(data, &wrap); err != nil {
var list []itemJSON
if err := json.Unmarshal(data, &list); err != nil {
return ItemsResult{}, client.ToolErrorf(toolItems, "parse: %w", err)
}
wrap.Items = list
}
out := make([]Item, 0, len(wrap.Items))
for _, it := range wrap.Items {
sym := it.Symbol
typ := it.ObjectType
var inst *Instrument
if it.Instrument != nil {
inst = &Instrument{Symbol: it.Instrument.Symbol, Type: it.Instrument.Type}
if typ == "" {
typ = it.Instrument.Type
}
if sym == "" {
sym = it.Instrument.Symbol
}
}
out = append(out, Item{Symbol: sym, ObjectType: typ, Instrument: inst})
}
return ItemsResult{Items: out}, nil
}
// OptionList calls get_option_watchlist.
func (c *Client) OptionList(ctx context.Context, req OptionListRequest) (OptionListResult, error) {
var out OptionListResult
if err := c.parse(ctx, toolOptionList, map[string]any{}, &out); err != nil {
return OptionListResult{}, err
}
return out, nil
}
// Popular calls get_popular_watchlists.
func (c *Client) Popular(ctx context.Context, req PopularRequest) (PopularResult, error) {
var out PopularResult
if err := c.parse(ctx, toolPopular, map[string]any{}, &out); err != nil {
return PopularResult{}, err
}
return out, nil
}
// Create calls create_watchlist.
func (c *Client) Create(ctx context.Context, req CreateRequest) error {
args := map[string]any{}
if req.DisplayName != "" {
args["display_name"] = req.DisplayName
}
if req.IconEmoji != "" {
args["icon_emoji"] = req.IconEmoji
}
if req.DisplayDescription != "" {
args["display_description"] = req.DisplayDescription
}
_, err := c.c.Call(ctx, toolCreate, args)
return err
}
// Update calls update_watchlist.
func (c *Client) Update(ctx context.Context, req UpdateRequest) error {
args := listIDArgs(req.ListID)
if req.DisplayName != "" {
args["display_name"] = req.DisplayName
}
if req.IconEmoji != "" {
args["icon_emoji"] = req.IconEmoji
}
if req.DisplayDescription != "" {
args["display_description"] = req.DisplayDescription
}
_, err := c.c.Call(ctx, toolUpdate, args)
return err
}
// Follow calls follow_watchlist.
func (c *Client) Follow(ctx context.Context, req ListIDRequest) error {
_, err := c.c.Call(ctx, toolFollow, listIDArgs(req.ListID))
return err
}
// Unfollow calls unfollow_watchlist.
func (c *Client) Unfollow(ctx context.Context, req ListIDRequest) error {
_, err := c.c.Call(ctx, toolUnfollow, listIDArgs(req.ListID))
return err
}
// Add calls add_to_watchlist.
func (c *Client) Add(ctx context.Context, req AddRequest) error {
_, err := c.c.Call(ctx, toolAdd, mutateArgs(req.ListID, req.Symbols, req.CurrencyPairIDs, req.IndexIDs))
return err
}
// Remove calls remove_from_watchlist.
func (c *Client) Remove(ctx context.Context, req RemoveRequest) error {
_, err := c.c.Call(ctx, toolRemove, mutateArgs(req.ListID, req.Symbols, req.CurrencyPairIDs, req.IndexIDs))
return err
}
// AddOption calls add_option_to_watchlist.
func (c *Client) AddOption(ctx context.Context, req OptionMutateRequest) error {
_, err := c.c.Call(ctx, toolAddOption, optionArgs(req))
return err
}
// RemoveOption calls remove_option_from_watchlist.
func (c *Client) RemoveOption(ctx context.Context, req OptionMutateRequest) error {
_, err := c.c.Call(ctx, toolRemoveOption, optionArgs(req))
return err
}
type watchlistJSON struct {
ID string `json:"id"`
Title string `json:"title"`
Name string `json:"name"`
}
type itemJSON struct {
Symbol string `json:"symbol"`
ObjectType string `json:"object_type"`
Instrument *struct {
Symbol string `json:"symbol"`
Type string `json:"type"`
} `json:"instrument"`
}
func listIDArgs(id string) map[string]any {
args := map[string]any{}
if id != "" {
args["list_id"] = id
}
return args
}
func mutateArgs(listID string, symbols, pairIDs, indexIDs []string) map[string]any {
args := listIDArgs(listID)
if len(symbols) > 0 {
args["symbols"] = symbols
}
if len(pairIDs) > 0 {
args["currency_pair_ids"] = pairIDs
}
if len(indexIDs) > 0 {
args["index_ids"] = indexIDs
}
return args
}
func optionArgs(req OptionMutateRequest) map[string]any {
args := map[string]any{}
if len(req.OptionIDs) > 0 {
args["option_ids"] = req.OptionIDs
}
if req.PositionType != "" {
args["position_type"] = req.PositionType
}
return args
}
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
}