ofxgo/investments.go

235 lines
8.8 KiB
Go
Raw Normal View History

package ofxgo
import (
"errors"
"github.com/golang/go/src/encoding/xml"
)
type InvStatementRequest struct {
XMLName xml.Name `xml:"INVSTMTTRNRQ"`
TrnUID UID `xml:"TRNUID"`
CltCookie String `xml:"CLTCOOKIE,omitempty"`
TAN String `xml:"TAN,omitempty"` // Transaction authorization number
// TODO `xml:"OFXEXTENSION,omitempty"`
InvAcctFrom InvAcct `xml:"INVSTMTRQ>INVACCTFROM"`
DtStart Date `xml:"INVSTMTRQ>INCTRAN>DTSTART,omitempty"`
DtEnd Date `xml:"INVSTMTRQ>INCTRAN>DTEND,omitempty"`
Include Boolean `xml:"INVSTMTRQ>INCTRAN>INCLUDE"` // Include transactions (instead of just balance)
IncludeOO Boolean `xml:"INVSTMTRQ>INCOO"` // Include open orders
PosDtAsOf Date `xml:"INVSTMTRQ>INCPOS>DTASOF,omitempty"` // Date that positions should be sent down for, if present
IncludePos Boolean `xml:"INVSTMTRQ>INCPOS>INCLUDE"` // Include position data in response
IncludeBalance Boolean `xml:"INVSTMTRQ>INCBAL"` // Include investment balance in response
Include401K Boolean `xml:"INVSTMTRQ>INC401K,omitempty"` // Include 401k information
Include401KBal Boolean `xml:"INVSTMTRQ>INC401KBAL,omitempty"` // Include 401k balance information
IncludeTranImage Boolean `xml:"INVSTMTRQ>INCTRANIMAGE,omitempty"` // Include transaction images
}
func (r *InvStatementRequest) Name() string {
return "INVSTMTTRNRQ"
}
func (r *InvStatementRequest) Valid() (bool, error) {
if ok, err := r.TrnUID.Valid(); !ok {
return false, err
}
return true, nil
}
type InvBankTransaction struct {
XMLName xml.Name `xml:"INVBANKTRAN"`
Transactions []Transaction `xml:"STMTTRN,omitempty"`
SubAcctFund String `xml:"SUBACCTFUND"` // Where did the money for the transaction come from or go to? CASH, MARGIN, SHORT, OTHER
}
type InvTransactionList struct {
2017-03-20 21:09:08 -04:00
XMLName xml.Name `xml:"INVTRANLIST"`
DtStart Date `xml:"DTSTART"`
DtEnd Date `xml:"DTEND"`
BankTransactions []InvBankTransaction `xml:"INVBANKTRAN,omitempty"`
}
type InvPosition struct {
XMLName xml.Name `xml:"INVPOS"`
SecId SecurityId `xml:"SECID"`
HeldInAcct String `xml:"HELDINACCT"` // Sub-account type, one of CASH, MARGIN, SHORT, OTHER
PosType String `xml:"POSTYPE"` // SHORT = Writer for options, Short for all others; LONG = Holder for options, Long for all others.
Units Amount `xml:"UNITS"` // For stocks, MFs, other, number of shares held. Bonds = face value. Options = number of contracts
UnitPrice Amount `xml:"UNITPRICE"` // For stocks, MFs, other, price per share. Bonds = percentage of par. Option = premium per share of underlying security
MktVal Amount `xml:"MKTVAL"` // Market value of this position
AvgCostBasis Amount `xml:"AVGCOSTBASIS,omitempty"` //
DtPriceAsOf Date `xml:"DTPRICEASOF"` // Date and time of unit price and market value, and cost basis. If this date is unknown, use 19900101 as the placeholder; do not use 0,
Currency Currency `xml:"CURRENCY,omitempty"` // Overriding currency for UNITPRICE
Memo String `xml:"MEMO,omitempty"`
Inv401kSource String `xml:"INV401KSOURCE,omitempty"` // One of PRETAX, AFTERTAX, MATCH, PROFITSHARING, ROLLOVER, OTHERVEST, OTHERNONVEST for 401(k) accounts. Default if not present is OTHERNONVEST. The following cash source types are subject to vesting: MATCH, PROFITSHARING, and OTHERVEST
}
type Position interface {
PositionType() string
}
type DebtPosition struct {
XMLName xml.Name `xml:"POSDEBT"`
InvPos InvPosition `xml:"INVPOS"`
}
func (p DebtPosition) PositionType() string {
return "POSDEBT"
}
type MFPosition struct {
XMLName xml.Name `xml:"POSMF"`
InvPos InvPosition `xml:"INVPOS"`
UnitsStreet Amount `xml:"UNITSSTREET,omitempty"` // Units in the FIs street name
UnitsUser Amount `xml:"UNITSUSER,omitempty"` // Units in the user's name directly
ReinvDiv Boolean `xml:"REINVDIV,omitempty"` // Reinvest dividends
ReinCG Boolean `xml:"REINVCG,omitempty"` // Reinvest capital gains
}
func (p MFPosition) PositionType() string {
return "POSMF"
}
type OptPosition struct {
XMLName xml.Name `xml:"POSOPT"`
InvPos InvPosition `xml:"INVPOS"`
Secured String `xml:"SECURED,omitempty"` // One of NAKED, COVERED
}
func (p OptPosition) PositionType() string {
return "POSOPT"
}
type OtherPosition struct {
XMLName xml.Name `xml:"POSOTHER"`
InvPos InvPosition `xml:"INVPOS"`
}
func (p OtherPosition) PositionType() string {
return "POSOTHER"
}
type StockPosition struct {
XMLName xml.Name `xml:"POSSTOCK"`
InvPos InvPosition `xml:"INVPOS"`
UnitsStreet Amount `xml:"UNITSSTREET,omitempty"` // Units in the FIs street name
UnitsUser Amount `xml:"UNITSUSER,omitempty"` // Units in the user's name directly
ReinvDiv Boolean `xml:"REINVDIV,omitempty"` // Reinvest dividends
}
func (p StockPosition) PositionType() string {
return "POSSTOCK"
}
type PositionList []Position
func (p PositionList) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
for {
tok, err := nextNonWhitespaceToken(d)
if err != nil {
return err
} else if end, ok := tok.(xml.EndElement); ok && end.Name.Local == start.Name.Local {
// If we found the end of our starting element, we're done parsing
return nil
} else if startElement, ok := tok.(xml.StartElement); ok {
switch startElement.Name.Local {
case "POSDEBT":
var position DebtPosition
if err := d.DecodeElement(&position, &startElement); err != nil {
return err
}
p = append(p, Position(position))
case "POSMF":
var position MFPosition
if err := d.DecodeElement(&position, &startElement); err != nil {
return err
}
p = append(p, Position(position))
case "POSOPT":
var position OptPosition
if err := d.DecodeElement(&position, &startElement); err != nil {
return err
}
p = append(p, Position(position))
case "POSOTHER":
var position OtherPosition
if err := d.DecodeElement(&position, &startElement); err != nil {
return err
}
p = append(p, Position(position))
case "POSSTOCK":
var position StockPosition
if err := d.DecodeElement(&position, &startElement); err != nil {
return err
}
p = append(p, Position(position))
default:
return errors.New("Invalid INVPOSLIST child tag: " + startElement.Name.Local)
}
} else {
return errors.New("Didn't find an opening element")
}
}
}
type InvBalance struct {
XMLName xml.Name `xml:"INVBAL"`
AvailCash Amount `xml:"AVAILCASH"` // Available cash across all sub-accounts, including sweep funds
MarginBalance Amount `xml:"MARGINBALANCE"` // Negative means customer has borrowed funds
ShortBalance Amount `xml:"SHORTBALANCE"` // Always positive, market value of all short positions
BuyPower Amount `xml:"BUYPOWER"`
BalList []Balance `xml:"BALLIST>BAL,omitempty"`
}
type InvStatementResponse struct {
XMLName xml.Name `xml:"INVSTMTTRNRS"`
TrnUID UID `xml:"TRNUID"`
Status Status `xml:"STATUS"`
CltCookie String `xml:"CLTCOOKIE,omitempty"`
// TODO OFXEXTENSION
DtAsOf Date `xml:"INVSTMTRS>DTASOF"`
CurDef String `xml:"INVSTMTRS>CURDEF"`
InvAcctFrom InvAcct `xml:"INVSTMTRS>INVACCTFROM"`
InvTranList InvTransactionList `xml:"INVSTMTRS>INVTRANLIST,omitempty"`
2017-03-20 21:09:08 -04:00
InvPosList PositionList `xml:"INVSTMTRS>INVPOSLIST,omitempty"`
InvBal InvBalance `xml:"INVSTMTRS>INVBAL,omitempty"`
// TODO INVOOLIST
MktgInfo String `xml:"INVSTMTRS>MKTGINFO,omitempty"` // Marketing information
// TODO INV401K
// TODO INV401KBAL
}
func (sr InvStatementResponse) Name() string {
return "INVSTMTTRNRS"
}
func (sr InvStatementResponse) Valid() (bool, error) {
//TODO implement
return true, nil
}
func DecodeInvestmentsMessageSet(d *xml.Decoder, start xml.StartElement) ([]Message, error) {
var msgs []Message
for {
tok, err := nextNonWhitespaceToken(d)
if err != nil {
return nil, err
} else if end, ok := tok.(xml.EndElement); ok && end.Name.Local == start.Name.Local {
// If we found the end of our starting element, we're done parsing
return msgs, nil
} else if startElement, ok := tok.(xml.StartElement); ok {
switch startElement.Name.Local {
case "INVSTMTTRNRS":
var info InvStatementResponse
if err := d.DecodeElement(&info, &startElement); err != nil {
return nil, err
}
msgs = append(msgs, Message(info))
default:
return nil, errors.New("Unsupported investments response tag: " + startElement.Name.Local)
}
} else {
return nil, errors.New("Didn't find an opening element")
}
}
}