def __init__(self, tranlist): # Initialize with *TRANLIST Element dtstart, dtend = tranlist[0:2] tranlist = tranlist[2:] self.dtstart = DateTime().convert(dtstart.text) self.dtend = DateTime().convert(dtend.text) self.extend([Aggregate.from_etree(tran) for tran in tranlist])
def __init__(self, tranlist): # Initialize with *TRANLIST Element dtstart, dtend = tranlist[0:2] tranlist = tranlist[2:] self.dtstart = DateTime.convert(dtstart.text) self.dtend = DateTime.convert(dtend.text) self.extend([Aggregate.from_etree(tran) for tran in tranlist])
class SONRS(FI, STATUS): dtserver = DateTime(required=True) userkey = String(64) tskeyexpire = DateTime() language = OneOf(*LANG_CODES) dtprofup = DateTime() dtacctup = DateTime() sesscookie = String(1000) accesskey = String(1000)
def inctran(self, inctran, dtstart, dtend): """ """ tran = ET.Element('INCTRAN') if dtstart: ET.SubElement(tran, 'DTSTART').text = DateTime().unconvert(dtstart) if dtend: ET.SubElement(tran, 'DTEND').text = DateTime().unconvert(dtend) ET.SubElement(tran, 'INCLUDE').text = Bool().unconvert(inctran) return tran
class DEBTINFO(SECINFO): parvalue = Decimal(required=True) debttype = OneOf('COUPON', 'ZERO', required=True) debtclass = OneOf('TREASURY', 'MUNICIPAL', 'CORPORATE', 'OTHER') couponrt = Decimal(4) dtcoupon = DateTime() couponfreq = OneOf('MONTHLY', 'QUARTERLY', 'SEMIANNUAL', 'ANNUAL', 'OTHER') callprice = Decimal(4) yieldtocall = Decimal(4) dtcall = DateTime() calltype = OneOf('CALL', 'PUT', 'PREFUND', 'MATURITY') ytmat = Decimal(4) dtmat = DateTime() assetclass = OneOf(*ASSETCLASSES) fiassetclass = String(32)
class STOCKINFO(SECINFO): stocktype = OneOf('COMMON', 'PREFERRED', 'CONVERTIBLE', 'OTHER') yld = Decimal(4) dtyieldasof = DateTime() typedesc = String(32) assetclass = OneOf(*ASSETCLASSES) fiassetclass = String(32)
class OPTINFO(SECINFO): opttype = OneOf('CALL', 'PUT', required=True) strikeprice = Decimal(required=True) dtexpire = DateTime(required=True) shperctrct = Integer(required=True) assetclass = OneOf(*ASSETCLASSES) fiassetclass = String(32) def __init__(self, elem): """ Strip SECID of underlying so it doesn't overwrite SECID of option during _flatten() """ # Do all XPath searches before removing nodes from the tree # which seems to mess up the DOM in Python3 and throw an # AttributeError on subsequent searches. secid = elem.find('./SECID') if secid is not None: # A <SECID> aggregate referring to the security underlying the # option is, in general, *not* going to be contained in <SECLIST> # (because you don't necessarily have a position in the underlying). # Since the <SECID> for the underlying only gives us fields for # (uniqueidtype, uniqueid) we can't really go ahead and use this # information to create a corresponding SECINFO instance (since we # lack information about the security subclass). It's unclear that # the SECID of the underlying is really needed for anything, so we # disregard it. elem.remove(secid) super(OPTINFO, self).__init__(elem)
class OPTINFO(SECINFO): opttype = OneOf('CALL', 'PUT', required=True) strikeprice = Decimal(required=True) dtexpire = DateTime(required=True) shperctrct = Integer(required=True) assetclass = OneOf(*ASSETCLASSES) fiassetclass = String(32)
class STOCKINFO(SECINFO): stocktype = OneOf('COMMON', 'PREFERRED', 'CONVERTIBLE', 'OTHER') yld = Decimal(4) dtyieldasof = DateTime() typedesc = String(32) assetclass = OneOf(*ASSETCLASSES) fiassetclass = String(32) def __init__(self, elem): """ Rename 'yield' (Python reserved word) to 'yld' """ extra_attrs = {} # Do all XPath searches before removing nodes from the tree # which seems to mess up the DOM in Python3 and throw an # AttributeError on subsequent searches. yld = elem.find('./YIELD') if yld is not None: # Rename; save for later extra_attrs['yld'] = yld.text elem.remove(yld) super(STOCKINFO, self).__init__(elem) # Add back data previously stripped/mangled for attr, val in extra_attrs.items(): setattr(self, attr, val)
class INVPOS(SECID, CURRENCY): heldinacct = OneOf(*INVSUBACCTS, required=True) postype = OneOf('SHORT', 'LONG', required=True) units = Decimal(required=True) unitprice = Decimal(4, required=True) mktval = Decimal(required=True) dtpriceasof = DateTime(required=True) memo = String(255) inv401ksource = OneOf(*INV401KSOURCES)
class TRANSFER(INVTRAN, SECID): subacctsec = OneOf(*INVSUBACCTS, required=True) units = Decimal(required=True) tferaction = OneOf('IN', 'OUT', required=True) postype = OneOf('SHORT', 'LONG', required=True) avgcostbasis = Decimal() unitprice = Decimal() dtpurchase = DateTime() inv401ksource = OneOf(*INV401KSOURCES)
class SECINFO(CURRENCY, SECID): # FIs abuse SECNAME/TICKER # Relaxing the length constraints from the OFX spec does little harm #secname = String(120, required=True) #ticker = String(32) secname = String(255, required=True) ticker = String(255) fiid = String(32) rating = String(10) unitprice = Decimal() dtasof = DateTime() memo = String(255)
def profile_request(self, user=None, password=None): """ """ user = user or 'elmerfudd' password = password or 'TOPSECRET' ofx = ET.Element('OFX') ofx.append(self.signon(user, password)) msgsrq = ET.SubElement(ofx, 'PROFMSGSRQV1') profrq = ET.Element('PROFRQ') ET.SubElement(profrq, 'CLIENTROUTING').text = 'NONE' ET.SubElement(profrq, 'DTPROFUP').text = DateTime().unconvert( datetime.date(1990, 1, 1)) msgsrq.append(self._wraptrn(profrq)) return ofx
class STMTTRN(TRAN, ORIGCURRENCY): trntype = OneOf('CREDIT', 'DEBIT', 'INT', 'DIV', 'FEE', 'SRVCHG', 'DEP', 'ATM', 'POS', 'XFER', 'CHECK', 'PAYMENT', 'CASH', 'DIRECTDEP', 'DIRECTDEBIT', 'REPEATPMT', 'OTHER', required=True) dtposted = DateTime(required=True) dtuser = DateTime() dtavail = DateTime() trnamt = Decimal(required=True) correctfitid = Decimal() correctaction = OneOf('REPLACE', 'DELETE') checknum = String(12) refnum = String(32) sic = Integer() payeeid = String(12) name = String(32) memo = String(255) inv401ksource = OneOf(*INV401KSOURCES) payee = None bankacctto = None ccacctto = None
def signon(self, user, password): msgsrq = ET.Element('SIGNONMSGSRQV1') sonrq = ET.SubElement(msgsrq, 'SONRQ') ET.SubElement(sonrq, 'DTCLIENT').text = DateTime().unconvert( datetime.datetime.now()) ET.SubElement(sonrq, 'USERID').text = user ET.SubElement(sonrq, 'USERPASS').text = password ET.SubElement(sonrq, 'LANGUAGE').text = 'ENG' if self.org: fi = ET.SubElement(sonrq, 'FI') ET.SubElement(fi, 'ORG').text = self.org if self.fid: ET.SubElement(fi, 'FID').text = self.fid ET.SubElement(sonrq, 'APPID').text = self.appid ET.SubElement(sonrq, 'APPVER').text = str(self.appver) return msgsrq
class INVBUY(INVTRAN, SECID, ORIGCURRENCY): units = Decimal(required=True) unitprice = Decimal(4, required=True) markup = Decimal() commission = Decimal() taxes = Decimal() fees = Decimal() load = Decimal() total = Decimal(required=True) subacctsec = OneOf(*INVSUBACCTS, required=True) subacctfund = OneOf(*INVSUBACCTS, required=True) loanid = String(32) loanprincipal = Decimal() loaninterest = Decimal() inv401ksource = OneOf(*INV401KSOURCES) dtpayroll = DateTime() prioryearcontrib = Bool()
def do_stmt(args): client = OFXClient(args.url, args.org, args.fid, version=args.version, appid=args.appid, appver=args.appver) # Define accounts accts = [] for accttype in ('checking', 'savings', 'moneymrkt', 'creditline'): for acctid in getattr(args, accttype): a = BankAcct(args.bankid, acctid, accttype) accts.append(a) for acctid in args.creditcard: accts.append(CcAcct(acctid)) for acctid in args.investment: accts.append(InvAcct(args.brokerid, acctid)) # Use dummy password for dummy request if args.dry_run: password = '******' else: password = getpass() # Statement parameters d = vars(args) # convert dtstart/dtend/dtasof from str to datetime kwargs = { k: DateTime().convert(v) for k, v in d.items() if k.startswith('dt') } # inctrans/incpos/incbal kwargs.update({k: v for k, v in d.items() if k.startswith('inc')}) request = client.statement_request(args.user, password, accts, **kwargs) # Handle request if args.dry_run: print(client.ofxheader + ET.tostring(request).decode()) else: response = client.download(request) print(response.read())
class MFINFO(SECINFO): mftype = OneOf('OPENEND', 'CLOSEEND', 'OTHER') yld = Decimal(4) dtyieldasof = DateTime() mfassetclass = [] fimfassetclass = [] def __init__(self, elem): """ Strip MFASSETCLASS/FIMFASSETCLASS - lists that will blow up _flatten() Rename 'yield' (Python reserved word) to 'yld' """ extra_attrs = {} # Do all XPath searches before removing nodes from the tree # which seems to mess up the DOM in Python3 and throw an # AttributeError on subsequent searches. mfassetclass = elem.find('./MFASSETCLASS') fimfassetclass = elem.find('./FIMFASSETCLASS') yld = elem.find('./YIELD') if mfassetclass is not None: # Convert PORTIONs; save for later extra_attrs['mfassetclass'] = [ Aggregate.from_etree(p) for p in mfassetclass ] elem.remove(mfassetclass) if fimfassetclass is not None: # Convert FIPORTIONs; save for later extra_attrs['fimfassetclass'] = [ Aggregate.from_etree(p) for p in fimfassetclass ] elem.remove(fimfassetclass) if yld is not None: # Rename; save for later extra_attrs['yld'] = yld.text elem.remove(yld) super(MFINFO, self).__init__(elem) # Add back data previously stripped/mangled for attr, val in extra_attrs.items(): setattr(self, attr, val)
def _init(self, invstmtrs): dtasof = invstmtrs.find('DTASOF').text self.datetime = DateTime().convert(dtasof) # INVTRANLIST tranlist = invstmtrs.find('INVTRANLIST') if tranlist is not None: self.transactions = INVTRANLIST(tranlist) else: self.transactions = [] # INVPOSLIST poslist = invstmtrs.find('INVPOSLIST') if poslist is not None: self.positions = [Aggregate.from_etree(pos) for pos in poslist] else: self.positions = [] # INVBAL invbal = invstmtrs.find('INVBAL') if invbal is not None: # First strip off BALLIST & process it ballist = invbal.find('BALLIST') if ballist is not None: invbal.remove(ballist) self.other_balances = [ Aggregate.from_etree(bal) for bal in ballist ] else: self.other_balances = [] # Now we can flatten the rest of INVBAL self.balances = Aggregate.from_etree(invbal) else: self.balances = [] self.other_balances = [] # Unsupported subaggregates for tag in ('INVOOLIST', 'INV401K', 'INV401KBAL', 'MKTGINFO'): child = invstmtrs.find(tag) if child is not None: invstmtrs.remove
def _init(self, invstmtrs): dtasof = invstmtrs.find('DTASOF').text self.datetime = DateTime.convert(dtasof) # INVTRANLIST tranlist = invstmtrs.find('INVTRANLIST') if tranlist is not None: self.transactions = INVTRANLIST(tranlist) else: self.transactions = [] # INVPOSLIST poslist = invstmtrs.find('INVPOSLIST') if poslist is not None: self.positions = [Aggregate.from_etree(pos) for pos in poslist] else: self.positions = [] # INVBAL invbal = invstmtrs.find('INVBAL') if invbal is not None: # First strip off BALLIST & process it ballist = invbal.find('BALLIST') if ballist is not None: invbal.remove(ballist) self.other_balances = [Aggregate.from_etree(bal) for bal in ballist] else: self.other_balances = [] # Now we can flatten the rest of INVBAL self.balances = Aggregate.from_etree(invbal) else: self.balances = [] self.other_balances = [] # Unsupported subaggregates for tag in ('INVOOLIST', 'INV401K', 'INV401KBAL', 'MKTGINFO'): child = invstmtrs.find(tag) if child is not None: invstmtrs.remove
class MFINFO(SECINFO): mftype = OneOf('OPENEND', 'CLOSEEND', 'OTHER') yld = Decimal(4) dtyieldasof = DateTime() mfassetclass = [] fimfassetclass = [] def __init__(self, elem, strict=True): """ Strip MFASSETCLASS/FIMFASSETCLASS - lists that will blow up _flatten() """ extra_attrs = {} # Do all XPath searches before removing nodes from the tree # which seems to mess up the DOM in Python3 and throw an # AttributeError on subsequent searches. mfassetclass = elem.find('./MFASSETCLASS') fimfassetclass = elem.find('./FIMFASSETCLASS') if mfassetclass is not None: # Convert PORTIONs; save for later extra_attrs['mfassetclass'] = [ Aggregate.from_etree(p) for p in mfassetclass ] elem.remove(mfassetclass) if fimfassetclass is not None: # Convert FIPORTIONs; save for later extra_attrs['fimfassetclass'] = [ Aggregate.from_etree(p) for p in fimfassetclass ] elem.remove(fimfassetclass) super(MFINFO, self).__init__(elem, strict=strict) # Staple MFASSETCLASS/FIMFASSETCLASS onto MFINFO for attr, val in extra_attrs.items(): setattr(self, attr, val)
class AVAILBAL(Aggregate): balamt = Decimal(required=True) dtasof = DateTime(required=True)
class BAL(CURRENCY): name = String(32, required=True) desc = String(80, required=True) baltype = OneOf('DOLLAR', 'PERCENT', 'NUMBER', required=True) value = Decimal(required=True) dtasof = DateTime()
class INVTRAN(TRAN): dttrade = DateTime(required=True) dtsettle = DateTime() reversalfitid = String(255) memo = String(255)
def incpos(self, dtasof, incpos): pos = ET.Element('INCPOS') if dtasof: ET.SubElement(pos, 'DTASOF').text = DateTime().unconvert(dtasof) ET.SubElement(pos, 'INCLUDE').text = Bool().unconvert(incpos) return pos