a6bf8632ce
Typed result structs replace empty envelopes. Equity place sends ref_id only so live additionalProperties:false schemas accept the call.
329 lines
8.0 KiB
Go
329 lines
8.0 KiB
Go
package equity
|
|
|
|
import (
|
|
"encoding/json"
|
|
"time"
|
|
|
|
"s1d3sw1ped/robinhood-agentic-mcp/internal/wire"
|
|
)
|
|
|
|
func (r *PositionsResult) UnmarshalJSON(b []byte) error {
|
|
type row struct {
|
|
Symbol string `json:"symbol"`
|
|
Quantity any `json:"quantity"`
|
|
Qty any `json:"qty"`
|
|
AverageBuyPrice any `json:"average_buy_price"`
|
|
AvgCost any `json:"avg_cost"`
|
|
}
|
|
rows, next, err := wire.UnmarshalRows[row](b, "positions", "results")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
r.NextCursor = next
|
|
r.Positions = make([]Position, 0, len(rows))
|
|
for _, row := range rows {
|
|
qty, err := firstDec(row.Quantity, row.Qty)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
avg, err := firstDec(row.AverageBuyPrice, row.AvgCost)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
r.Positions = append(r.Positions, Position{Symbol: row.Symbol, Qty: qty, AvgCost: avg})
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *TaxLotsResult) UnmarshalJSON(b []byte) error {
|
|
type row struct {
|
|
OpenLotID string `json:"open_lot_id"`
|
|
ID string `json:"id"`
|
|
Quantity any `json:"quantity"`
|
|
QuantityAvailable any `json:"quantity_available"`
|
|
CostBasis any `json:"cost_basis"`
|
|
AcquiredAt string `json:"acquired_at"`
|
|
AcquisitionDate string `json:"acquisition_date"`
|
|
Term string `json:"term"`
|
|
HoldingPeriod string `json:"holding_period"`
|
|
}
|
|
rows, next, err := wire.UnmarshalRows[row](b, "tax_lots", "lots", "results")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
r.NextCursor = next
|
|
r.Lots = make([]TaxLotRow, 0, len(rows))
|
|
for _, row := range rows {
|
|
id := row.OpenLotID
|
|
if id == "" {
|
|
id = row.ID
|
|
}
|
|
qty, err := firstDec(row.Quantity)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
avail, err := firstDec(row.QuantityAvailable)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cost, err := firstDec(row.CostBasis)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
when := row.AcquiredAt
|
|
if when == "" {
|
|
when = row.AcquisitionDate
|
|
}
|
|
term := row.Term
|
|
if term == "" {
|
|
term = row.HoldingPeriod
|
|
}
|
|
r.Lots = append(r.Lots, TaxLotRow{
|
|
OpenLotID: id,
|
|
Quantity: qty,
|
|
QuantityAvail: avail,
|
|
CostBasis: cost,
|
|
AcquiredAt: when,
|
|
Term: term,
|
|
})
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *OrdersResult) UnmarshalJSON(b []byte) error {
|
|
rows, next, err := wire.UnmarshalRows[Order](b, "orders", "results")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
r.NextCursor = next
|
|
if len(rows) == 0 {
|
|
var one Order
|
|
if err := json.Unmarshal(wire.Unwrap(b), &one); err == nil && one.ID != "" {
|
|
rows = []Order{one}
|
|
}
|
|
}
|
|
r.Orders = rows
|
|
return nil
|
|
}
|
|
|
|
func (r *TradabilityResult) UnmarshalJSON(b []byte) error {
|
|
type row struct {
|
|
Symbol string `json:"symbol"`
|
|
Tradable bool `json:"tradable"`
|
|
IsTradable bool `json:"is_tradable"`
|
|
Fractional bool `json:"fractional"`
|
|
FractionalOk bool `json:"fractional_tradable"`
|
|
FractionalTrad bool `json:"fractionally_tradable"`
|
|
}
|
|
rows, _, err := wire.UnmarshalRows[row](b, "results", "tradability", "instruments")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(rows) == 0 {
|
|
var one row
|
|
if json.Unmarshal(wire.Unwrap(b), &one) == nil && (one.Symbol != "" || one.Tradable || one.IsTradable || one.Fractional) {
|
|
rows = []row{one}
|
|
}
|
|
}
|
|
r.Symbols = make([]SymbolTradability, 0, len(rows))
|
|
for _, row := range rows {
|
|
r.Symbols = append(r.Symbols, SymbolTradability{
|
|
Symbol: row.Symbol,
|
|
Tradable: row.Tradable || row.IsTradable,
|
|
Fractional: row.Fractional || row.FractionalOk || row.FractionalTrad,
|
|
})
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *FundamentalsResult) UnmarshalJSON(b []byte) error {
|
|
type row struct {
|
|
Symbol string `json:"symbol"`
|
|
AverageVolume any `json:"average_volume"`
|
|
AvgVolume any `json:"avg_volume"`
|
|
Type string `json:"type"`
|
|
Instrument string `json:"instrument_kind"`
|
|
MarketCap any `json:"market_cap"`
|
|
PE any `json:"pe_ratio"`
|
|
High52 any `json:"high_52_weeks"`
|
|
Low52 any `json:"low_52_weeks"`
|
|
}
|
|
rows, _, err := wire.UnmarshalRows[row](b, "fundamentals", "results")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(rows) == 0 {
|
|
var one row
|
|
if json.Unmarshal(wire.Unwrap(b), &one) == nil && one.Symbol != "" {
|
|
rows = []row{one}
|
|
}
|
|
}
|
|
r.Fundamentals = make([]Fundamentals, 0, len(rows))
|
|
for _, row := range rows {
|
|
kind := row.Instrument
|
|
if kind == "" {
|
|
kind = row.Type
|
|
}
|
|
avg, err := firstDec(row.AverageVolume, row.AvgVolume)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cap, err := firstDec(row.MarketCap)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
pe, err := firstDec(row.PE)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
hi, err := firstDec(row.High52)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
lo, err := firstDec(row.Low52)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
r.Fundamentals = append(r.Fundamentals, Fundamentals{
|
|
Symbol: row.Symbol,
|
|
AvgVolume: avg,
|
|
InstrumentKind: kind,
|
|
MarketCap: cap,
|
|
PE: pe,
|
|
High52Week: hi,
|
|
Low52Week: lo,
|
|
})
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *PriceBookResult) UnmarshalJSON(b []byte) error {
|
|
type level struct {
|
|
Price any `json:"price"`
|
|
Quantity any `json:"quantity"`
|
|
Size any `json:"size"`
|
|
}
|
|
type book struct {
|
|
Symbol string `json:"symbol"`
|
|
Bids []level `json:"bids"`
|
|
Asks []level `json:"asks"`
|
|
}
|
|
rows, _, err := wire.UnmarshalRows[book](b, "books", "price_books", "results")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(rows) == 0 {
|
|
var one book
|
|
if json.Unmarshal(wire.Unwrap(b), &one) == nil && one.Symbol != "" {
|
|
rows = []book{one}
|
|
}
|
|
}
|
|
levels := func(in []level) ([]BookLevel, error) {
|
|
out := make([]BookLevel, 0, len(in))
|
|
for _, lv := range in {
|
|
px, err := firstDec(lv.Price)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
qty, err := firstDec(lv.Quantity, lv.Size)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out = append(out, BookLevel{Price: px, Quantity: qty})
|
|
}
|
|
return out, nil
|
|
}
|
|
r.Books = make([]PriceBook, 0, len(rows))
|
|
for _, row := range rows {
|
|
bids, err := levels(row.Bids)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
asks, err := levels(row.Asks)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
r.Books = append(r.Books, PriceBook{Symbol: row.Symbol, Bids: bids, Asks: asks})
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *TechnicalIndicatorsResult) UnmarshalJSON(b []byte) error {
|
|
type row struct {
|
|
BeginsAt string `json:"begins_at"`
|
|
Time string `json:"time"`
|
|
Timestamp string `json:"timestamp"`
|
|
Value any `json:"value"`
|
|
Upper any `json:"upper"`
|
|
Lower any `json:"lower"`
|
|
MACD any `json:"macd"`
|
|
Signal any `json:"signal"`
|
|
}
|
|
rows, _, err := wire.UnmarshalRows[row](b, "data_points", "points", "results", "values")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
r.Points = make([]IndicatorPoint, 0, len(rows))
|
|
for _, row := range rows {
|
|
s := row.BeginsAt
|
|
if s == "" {
|
|
s = row.Time
|
|
}
|
|
if s == "" {
|
|
s = row.Timestamp
|
|
}
|
|
ts, err := time.Parse(time.RFC3339, s)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
val, err := firstDec(row.Value)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
up, err := firstDec(row.Upper)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
lo, err := firstDec(row.Lower)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
macd, err := firstDec(row.MACD)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
sig, err := firstDec(row.Signal)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
r.Points = append(r.Points, IndicatorPoint{Time: ts, Value: val, Upper: up, Lower: lo, MACD: macd, Signal: sig})
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *NewsResult) UnmarshalJSON(b []byte) error {
|
|
type row struct {
|
|
Title string `json:"title"`
|
|
URL string `json:"url"`
|
|
PublishedAt string `json:"published_at"`
|
|
Source string `json:"source"`
|
|
Summary string `json:"summary"`
|
|
}
|
|
rows, next, err := wire.UnmarshalRows[row](b, "news", "articles", "results")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
r.NextCursor = next
|
|
r.Articles = make([]NewsArticle, 0, len(rows))
|
|
for _, row := range rows {
|
|
r.Articles = append(r.Articles, NewsArticle{
|
|
Title: row.Title,
|
|
URL: row.URL,
|
|
PublishedAt: row.PublishedAt,
|
|
Source: row.Source,
|
|
Summary: row.Summary,
|
|
})
|
|
}
|
|
return nil
|
|
}
|