forked from aclindsa/ofxgo
-
Notifications
You must be signed in to change notification settings - Fork 0
/
seclist.go
332 lines (301 loc) · 12.7 KB
/
seclist.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
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
}
// 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"
}
// 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"
}
// 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"
}
// 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"
}
// 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"
}
// 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
}