package options import ( "encoding/json" "time" decimal "github.com/alpacahq/alpacadecimal" "s1d3sw1ped/robinhood-agentic-mcp/internal/wire" ) func (o *Order) UnmarshalJSON(b []byte) error { var row struct { ID string `json:"id"` State string `json:"state"` Status string `json:"status"` Quantity any `json:"quantity"` Price any `json:"price"` } if err := json.Unmarshal(b, &row); err != nil { return err } o.ID = row.ID o.State = row.State if o.State == "" { o.State = row.Status } var err error if o.Qty, err = firstDec(row.Quantity); err != nil { return err } if o.Price, err = firstDec(row.Price); err != nil { return err } return nil } func parseOrders(raw json.RawMessage) ([]Order, error) { rows, _, err := wire.UnmarshalRows[Order](raw, "orders", "results") if err != nil { return nil, err } if len(rows) > 0 { return rows, nil } var one Order if err := json.Unmarshal(wire.Unwrap(raw), &one); err != nil { return nil, err } if one.ID != "" { return []Order{one}, nil } return nil, nil } func (r *ChainsResult) UnmarshalJSON(b []byte) error { type row struct { ID string `json:"id"` UnderlyingSymbol string `json:"underlying_symbol"` Symbol string `json:"symbol"` ExpirationDates []string `json:"expiration_dates"` } rows, _, err := wire.UnmarshalRows[row](b, "results", "chains") if err != nil { return err } r.Chains = make([]OptionChain, 0, len(rows)) for _, row := range rows { sym := row.UnderlyingSymbol if sym == "" { sym = row.Symbol } r.Chains = append(r.Chains, OptionChain{ID: row.ID, UnderlyingSymbol: sym, ExpirationDates: row.ExpirationDates}) } return nil } func (r *InstrumentsResult) UnmarshalJSON(b []byte) error { type row struct { ID string `json:"id"` ChainID string `json:"chain_id"` ChainSymbol string `json:"chain_symbol"` ExpirationDate string `json:"expiration_date"` StrikePrice any `json:"strike_price"` Type string `json:"type"` State string `json:"state"` } rows, next, err := wire.UnmarshalRows[row](b, "results", "instruments") if err != nil { return err } r.NextCursor = next r.Instruments = make([]OptionInstrument, 0, len(rows)) for _, row := range rows { px, err := firstDec(row.StrikePrice) if err != nil { return err } r.Instruments = append(r.Instruments, OptionInstrument{ ID: row.ID, ChainID: row.ChainID, ChainSymbol: row.ChainSymbol, ExpirationDate: row.ExpirationDate, StrikePrice: px, Type: row.Type, State: row.State, }) } return nil } func (r *QuotesResult) UnmarshalJSON(b []byte) error { type row struct { InstrumentID string `json:"instrument_id"` ID string `json:"id"` Bid any `json:"bid"` BidPrice any `json:"bid_price"` Ask any `json:"ask"` AskPrice any `json:"ask_price"` Last any `json:"last"` LastPrice any `json:"last_trade_price"` PrevClose any `json:"previous_close"` Mark any `json:"mark"` MarkPrice any `json:"mark_price"` } rows, _, err := wire.UnmarshalRows[row](b, "quotes", "results") if err != nil { return err } r.Quotes = make([]OptionQuote, 0, len(rows)) for _, row := range rows { id := row.InstrumentID if id == "" { id = row.ID } bid, err := firstDec(row.BidPrice, row.Bid) if err != nil { return err } ask, err := firstDec(row.AskPrice, row.Ask) if err != nil { return err } last, err := firstDec(row.LastPrice, row.Last) if err != nil { return err } prev, err := firstDec(row.PrevClose) if err != nil { return err } mark, err := firstDec(row.MarkPrice, row.Mark) if err != nil { return err } r.Quotes = append(r.Quotes, OptionQuote{InstrumentID: id, Bid: bid, Ask: ask, Last: last, PrevClose: prev, Mark: mark}) } return nil } func (r *PositionsResult) UnmarshalJSON(b []byte) error { type row struct { OptionID string `json:"option_id"` ID string `json:"id"` ChainID string `json:"chain_id"` Type string `json:"type"` OptionType string `json:"option_type"` Quantity any `json:"quantity"` AveragePrice any `json:"average_price"` ExpirationDate string `json:"expiration_date"` } rows, next, err := wire.UnmarshalRows[row](b, "results", "positions") if err != nil { return err } r.NextCursor = next r.Positions = make([]OptionPosition, 0, len(rows)) for _, row := range rows { id := row.OptionID if id == "" { id = row.ID } qty, err := firstDec(row.Quantity) if err != nil { return err } avg, err := firstDec(row.AveragePrice) if err != nil { return err } r.Positions = append(r.Positions, OptionPosition{ OptionID: id, ChainID: row.ChainID, Type: row.Type, OptionType: row.OptionType, Quantity: qty, AveragePrice: avg, ExpirationDate: row.ExpirationDate, }) } return nil } func (r *OrdersResult) UnmarshalJSON(b []byte) error { rows, next, err := wire.UnmarshalRows[Order](b, "orders", "results") if err != nil { return err } if len(rows) == 0 { var one Order if json.Unmarshal(wire.Unwrap(b), &one) == nil && one.ID != "" { rows = []Order{one} } } r.Orders = rows r.NextCursor = next return nil } func (r *HistoricalsResult) UnmarshalJSON(b []byte) error { type pt struct { BeginsAt string `json:"begins_at"` Open any `json:"open"` High any `json:"high"` Low any `json:"low"` Close any `json:"close"` Volume any `json:"volume"` Interpolated bool `json:"interpolated"` } type series struct { InstrumentID string `json:"instrument_id"` ID string `json:"id"` DataPoints []pt `json:"data_points"` } rows, _, err := wire.UnmarshalRows[series](b, "historicals", "results") if err != nil { return err } for _, s := range rows { id := s.InstrumentID if id == "" { id = s.ID } for _, p := range s.DataPoints { ts, err := time.Parse(time.RFC3339, p.BeginsAt) if err != nil { continue } o, err := firstDec(p.Open) if err != nil { return err } h, err := firstDec(p.High) if err != nil { return err } l, err := firstDec(p.Low) if err != nil { return err } cl, err := firstDec(p.Close) if err != nil { return err } vol, err := firstDec(p.Volume) if err != nil { return err } r.Bars = append(r.Bars, OptionBar{InstrumentID: id, Time: ts, Open: o, High: h, Low: l, Close: cl, Volume: vol, Interpolated: p.Interpolated}) } } return nil } func firstDec(vs ...any) (decimal.Decimal, error) { for _, v := range vs { if v == nil { continue } if s, ok := v.(string); ok && s == "" { continue } return wire.Dec(v) } return decimal.Zero, nil }