ofxgo/seclist.go

359 lines
13 KiB
Go

package ofxgo
import (
"errors"
"github.com/aclindsa/xml"
)
// SecurityID identifies a security by its CUSIP (for US-based FI's, others may
// use UniqueID types other than CUSIP)
type SecurityID struct {
XMLName xml.Name `xml:"SECID"`
UniqueID String `xml:"UNIQUEID"` // CUSIP for US FI's
UniqueIDType String `xml:"UNIQUEIDTYPE"` // Should always be "CUSIP" for US FI's
}
// SecurityRequest represents a request for one security. It is specified with
// a SECID aggregate, a ticker symbol, or an FI assigned identifier (but no
// more than one of them at a time)
type SecurityRequest struct {
XMLName xml.Name `xml:"SECRQ"`
// Only one of the next three should be present
SecID *SecurityID `xml:"SECID,omitempty"`
Ticker String `xml:"TICKER,omitempty"`
FiID String `xml:"FIID,omitempty"`
}
// SecListRequest represents a request for information (namely price) about one
// or more securities
type SecListRequest struct {
XMLName xml.Name `xml:"SECLISTTRNRQ"`
TrnUID UID `xml:"TRNUID"`
CltCookie String `xml:"CLTCOOKIE,omitempty"`
TAN String `xml:"TAN,omitempty"` // Transaction authorization number
// TODO `xml:"OFXEXTENSION,omitempty"`
Securities []SecurityRequest `xml:"SECLISTRQ>SECRQ,omitempty"`
}
// Name returns the name of the top-level transaction XML/SGML element
func (r *SecListRequest) Name() string {
return "SECLISTTRNRQ"
}
// Valid returns (true, nil) if this struct would be valid OFX if marshalled
// into XML/SGML
func (r *SecListRequest) Valid(version ofxVersion) (bool, error) {
if ok, err := r.TrnUID.Valid(); !ok {
return false, err
}
// TODO implement
return true, nil
}
// Type returns which message set this message belongs to (which Request
// element of type []Message it should appended to)
func (r *SecListRequest) Type() messageType {
return SecListRq
}
// SecListResponse is always empty (except for the transaction UID, status, and
// optional client cookie). Its presence signifies that the SecurityList (a
// different element from this one) immediately after this element in
// Response.SecList was been generated in response to the same SecListRequest
// this is a response to.
type SecListResponse struct {
XMLName xml.Name `xml:"SECLISTTRNRS"`
TrnUID UID `xml:"TRNUID"`
Status Status `xml:"STATUS"`
CltCookie String `xml:"CLTCOOKIE,omitempty"`
// TODO `xml:"OFXEXTENSION,omitempty"`
// SECLISTRS is always empty, so we don't parse it here. The actual securities list will be in a top-level element parallel to SECLISTTRNRS
}
// Name returns the name of the top-level transaction XML/SGML element
func (r *SecListResponse) Name() string {
return "SECLISTTRNRS"
}
// Valid returns (true, nil) if this struct was valid OFX when unmarshalled
func (r *SecListResponse) Valid(version ofxVersion) (bool, error) {
if ok, err := r.TrnUID.Valid(); !ok {
return false, err
}
// TODO implement
return true, nil
}
// Type returns which message set this message belongs to (which Response
// element of type []Message it belongs to)
func (r *SecListResponse) Type() messageType {
return SecListRs
}
// Security is satisfied by all *Info elements providing information about
// securities for SecurityList
type Security interface {
SecurityType() string
SecurityInfo() SecInfo
}
// SecInfo represents the generic information about a security. It is included
// in most other *Info elements.
type SecInfo struct {
XMLName xml.Name `xml:"SECINFO"`
SecID SecurityID `xml:"SECID"`
SecName String `xml:"SECNAME"` // Full name of security
Ticker String `xml:"TICKER,omitempty"` // Ticker symbol
FiID String `xml:"FIID,omitempty"`
Rating String `xml:"RATING,omitempty"`
UnitPrice Amount `xml:"UNITPRICE,omitempty"` // Current price, as of DTASOF
DtAsOf *Date `xml:"DTASOF,omitempty"` // Date UNITPRICE was for
Currency *Currency `xml:"CURRENCY,omitempty"` // Overriding currency for UNITPRICE
Memo String `xml:"MEMO,omitempty"`
}
// DebtInfo provides information about a debt security
type DebtInfo struct {
XMLName xml.Name `xml:"DEBTINFO"`
SecInfo SecInfo `xml:"SECINFO"`
ParValue Amount `xml:"PARVALUE"`
DebtType debtType `xml:"DEBTTYPE"` // One of COUPON, ZERO (zero coupon)
DebtClass debtClass `xml:"DEBTCLASS,omitempty"` // One of TREASURY, MUNICIPAL, CORPORATE, OTHER
CouponRate Amount `xml:"COUPONRT,omitempty"` // Bond coupon rate for next closest call date
DtCoupon *Date `xml:"DTCOUPON,omitempty"` // Maturity date for next coupon
CouponFreq couponFreq `xml:"COUPONFREQ,omitempty"` // When coupons mature - one of MONTHLY, QUARTERLY, SEMIANNUAL, ANNUAL, or OTHER
CallPrice Amount `xml:"CALLPRICE,omitempty"` // Bond call price
YieldToCall Amount `xml:"YIELDTOCALL,omitempty"` // Yield to next call
DtCall *Date `xml:"DTCALL,omitempty"` // Next call date
CallType callType `xml:"CALLTYPE,omitempt"` // Type of next call. One of CALL, PUT, PREFUND, MATURITY
YieldToMat Amount `xml:"YIELDTOMAT,omitempty"` // Yield to maturity
DtMat *Date `xml:"DTMAT,omitempty"` // Debt maturity date
AssetClass assetClass `xml:"ASSETCLASS,omitempty"` // One of DOMESTICBOND, INTLBOND, LARGESTOCK, SMALLSTOCK, INTLSTOCK, MONEYMRKT, OTHER
FiAssetClass String `xml:"FIASSETCLASS,omitempty"` // FI-defined asset class
}
// SecurityType returns a string representation of this security's type
func (i DebtInfo) SecurityType() string {
return "DEBTINFO"
}
// SecurityInfo returns SecInfo
func (i DebtInfo) SecurityInfo() SecInfo {
return i.SecInfo
}
// AssetPortion represents the percentage of a mutual fund with the given asset
// classification
type AssetPortion struct {
XMLName xml.Name `xml:"PORTION"`
AssetClass assetClass `xml:"ASSETCLASS"` // One of DOMESTICBOND, INTLBOND, LARGESTOCK, SMALLSTOCK, INTLSTOCK, MONEYMRKT, OTHER
Percent Amount `xml:"PERCENT"` // Percentage of the fund that falls under this asset class
}
// FiAssetPortion represents the percentage of a mutual fund with the given
// FI-defined asset classification (AssetPortion should be used for all asset
// classifications defined by the assetClass enum)
type FiAssetPortion struct {
XMLName xml.Name `xml:"FIPORTION"`
FiAssetClass String `xml:"FIASSETCLASS,omitempty"` // FI-defined asset class
Percent Amount `xml:"PERCENT"` // Percentage of the fund that falls under this asset class
}
// MFInfo provides information about a mutual fund
type MFInfo struct {
XMLName xml.Name `xml:"MFINFO"`
SecInfo SecInfo `xml:"SECINFO"`
MfType mfType `xml:"MFTYPE"` // One of OPEN, END, CLOSEEND, OTHER
Yield Amount `xml:"YIELD,omitempty"` // Current yield reported as the dividend expressed as a portion of the current stock price
DtYieldAsOf *Date `xml:"DTYIELDASOF,omitempty"` // Date YIELD is valid for
AssetClasses []AssetPortion `xml:"MFASSETCLASS>PORTION"`
FiAssetClasses []FiAssetPortion `xml:"FIMFASSETCLASS>FIPORTION"`
}
// SecurityType returns a string representation of this security's type
func (i MFInfo) SecurityType() string {
return "MFINFO"
}
// SecurityInfo returns SecInfo
func (i MFInfo) SecurityInfo() SecInfo {
return i.SecInfo
}
// OptInfo provides information about an option
type OptInfo struct {
XMLName xml.Name `xml:"OPTINFO"`
SecInfo SecInfo `xml:"SECINFO"`
OptType optType `xml:"OPTTYPE"` // One of PUT, CALL
StrikePrice Amount `xml:"STRIKEPRICE"`
DtExpire Date `xml:"DTEXPIRE"` // Expiration date
ShPerCtrct Int `xml:"SHPERCTRCT"` // Shares per contract
SecID *SecurityID `xml:"SECID,omitempty"` // Security ID of the underlying security
AssetClass assetClass `xml:"ASSETCLASS,omitempty"` // One of DOMESTICBOND, INTLBOND, LARGESTOCK, SMALLSTOCK, INTLSTOCK, MONEYMRKT, OTHER
FiAssetClass String `xml:"FIASSETCLASS,omitempty"` // FI-defined asset class
}
// SecurityType returns a string representation of this security's type
func (i OptInfo) SecurityType() string {
return "OPTINFO"
}
// SecurityInfo returns SecInfo
func (i OptInfo) SecurityInfo() SecInfo {
return i.SecInfo
}
// OtherInfo provides information about a security type not covered by the
// other *Info elements
type OtherInfo struct {
XMLName xml.Name `xml:"OTHERINFO"`
SecInfo SecInfo `xml:"SECINFO"`
TypeDesc String `xml:"TYPEDESC,omitempty"` // Description of security type
AssetClass assetClass `xml:"ASSETCLASS,omitempty"` // One of DOMESTICBOND, INTLBOND, LARGESTOCK, SMALLSTOCK, INTLSTOCK, MONEYMRKT, OTHER
FiAssetClass String `xml:"FIASSETCLASS,omitempty"` // FI-defined asset class
}
// SecurityType returns a string representation of this security's type
func (i OtherInfo) SecurityType() string {
return "OTHERINFO"
}
// SecurityInfo returns SecInfo
func (i OtherInfo) SecurityInfo() SecInfo {
return i.SecInfo
}
// StockInfo provides information about a security type
type StockInfo struct {
XMLName xml.Name `xml:"STOCKINFO"`
SecInfo SecInfo `xml:"SECINFO"`
StockType stockType `xml:"STOCKTYPE,omitempty"` // One of COMMON, PREFERRED, CONVERTIBLE, OTHER
Yield Amount `xml:"YIELD,omitempty"` // Current yield reported as the dividend expressed as a portion of the current stock price
DtYieldAsOf *Date `xml:"DTYIELDASOF,omitempty"` // Date YIELD is valid for
AssetClass assetClass `xml:"ASSETCLASS,omitempty"` // One of DOMESTICBOND, INTLBOND, LARGESTOCK, SMALLSTOCK, INTLSTOCK, MONEYMRKT, OTHER
FiAssetClass String `xml:"FIASSETCLASS,omitempty"` // FI-defined asset class
}
// SecurityType returns a string representation of this security's type
func (i StockInfo) SecurityType() string {
return "STOCKINFO"
}
// SecurityInfo returns SecInfo
func (i StockInfo) SecurityInfo() SecInfo {
return i.SecInfo
}
// SecurityList is a container for Security objects containaing information
// about securities
type SecurityList struct {
XMLName xml.Name `xml:"SECLIST"`
Securities []Security
}
// Name returns the name of the top-level transaction XML/SGML element
func (r *SecurityList) Name() string {
return "SECLIST"
}
// Valid returns (true, nil) if this struct was valid OFX when unmarshalled
func (r *SecurityList) Valid(version ofxVersion) (bool, error) {
// TODO implement
return true, nil
}
// Type returns which message set this message belongs to (which Response
// element of type []Message it belongs to)
func (r *SecurityList) Type() messageType {
return SecListRs
}
// UnmarshalXML handles unmarshalling a SecurityList from an SGML/XML string
func (r *SecurityList) 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 "DEBTINFO":
var info DebtInfo
if err := d.DecodeElement(&info, &startElement); err != nil {
return err
}
r.Securities = append(r.Securities, Security(info))
case "MFINFO":
var info MFInfo
if err := d.DecodeElement(&info, &startElement); err != nil {
return err
}
r.Securities = append(r.Securities, Security(info))
case "OPTINFO":
var info OptInfo
if err := d.DecodeElement(&info, &startElement); err != nil {
return err
}
r.Securities = append(r.Securities, Security(info))
case "OTHERINFO":
var info OtherInfo
if err := d.DecodeElement(&info, &startElement); err != nil {
return err
}
r.Securities = append(r.Securities, Security(info))
case "STOCKINFO":
var info StockInfo
if err := d.DecodeElement(&info, &startElement); err != nil {
return err
}
r.Securities = append(r.Securities, Security(info))
default:
return errors.New("Invalid SECLIST child tag: " + startElement.Name.Local)
}
} else {
return errors.New("Didn't find an opening element")
}
}
}
// MarshalXML handles marshalling a SecurityList to an SGML/XML string
func (r *SecurityList) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
secListElement := xml.StartElement{Name: xml.Name{Local: "SECLIST"}}
if err := e.EncodeToken(secListElement); err != nil {
return err
}
for _, s := range r.Securities {
start := xml.StartElement{Name: xml.Name{Local: s.SecurityType()}}
switch sec := s.(type) {
case DebtInfo:
if err := e.EncodeElement(&sec, start); err != nil {
return err
}
case MFInfo:
if err := e.EncodeElement(&sec, start); err != nil {
return err
}
case OptInfo:
if err := e.EncodeElement(&sec, start); err != nil {
return err
}
case OtherInfo:
if err := e.EncodeElement(&sec, start); err != nil {
return err
}
case StockInfo:
if err := e.EncodeElement(&sec, start); err != nil {
return err
}
default:
return errors.New("Invalid SECLIST child type: " + sec.SecurityType())
}
}
if err := e.EncodeToken(secListElement.End()); err != nil {
return err
}
return nil
}