/src/googlecl/finance/__init__.py
Python | 266 lines | 218 code | 19 blank | 29 comment | 8 complexity | 56f2501ab4752e528a6f5778e7086368 MD5 | raw file
1# Copyright (C) 2010 Google Inc. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14import googlecl 15import inspect 16import logging 17import os 18import sys 19from googlecl.base import Task 20 21 22safe_encode = googlecl.safe_encode 23service_name = __name__.split('.')[-1] 24LOGGER_NAME = __name__ 25SECTION_HEADER = service_name.upper() 26LOG = logging.getLogger(LOGGER_NAME) 27 28 29class BaseFormatter(object): 30 """Base class for formatters.""" 31 32 def __init__(self, avail_fields, fields, sep=','): 33 """Init formatter 34 Args: 35 avail_fields: list of tuples [(field_name, format_spec), ...] for all 36 possible fields 37 fields: string, list of <sep>-separated requested fields names. 38 sep: string, separator, comma by default 39 """ 40 if fields: 41 self.fields = fields.split(sep) 42 else: 43 self.fields = [item[0] for item in avail_fields] 44 45 self.avail_fields = avail_fields 46 avail_dict = dict(avail_fields) 47 self.format = ' '.join(avail_dict[name] for name in self.fields) 48 49 @property 50 def header(self): 51 """Make output header. 52 Uses names of available fields as column headers. replaces 53 '_' with ' ' and capitalizes them. Utilizes the same format as 54 used for body lines: self.format 55 56 Returns: string, header. 57 """ 58 return self.format % \ 59 dict([(item[0], item[0].replace('_', ' ').capitalize()) \ 60 for item in self.avail_fields]) 61 62 def get_line(self, entry): 63 """Get formatted entry. Abstract method. 64 Args: 65 entry: entry object 66 Returns: 67 string, formatted entry. 68 """ 69 raise NotImplementedError("Abstract method %s.%s called" % \ 70 (self.__class__.__name__, 71 inspect.stack()[0][3] )) 72 73 def output(self, entries, stream=sys.stdout): 74 """Output list of entries to the output stream. 75 76 Args: 77 entries: list of entries. 78 stream: output stream. 79 """ 80 81 if self.header: 82 stream.write(self.header + os.linesep) 83 for entry in entries: 84 stream.write(self.get_line(entry) + os.linesep) 85 86class PortfolioFormatter(BaseFormatter): 87 avail_fields = [('id', '%(id)3s'), ('title', '%(title)-15s'), 88 ('curr', '%(curr)-4s'), 89 ('gain', '%(gain)-10s'), 90 ('gain_persent', '%(gain_persent)-14s'), 91 ('cost_basis', '%(cost_basis)-10s'), 92 ('days_gain', '%(days_gain)-10s'), 93 ('market_value', '%(market_value)-10s')] 94 95 def __init__(self, fields): 96 super(self.__class__, self).__init__(self.avail_fields, fields) 97 98 def get_line(self, entry): 99 data = entry.portfolio_data 100 return self.format % \ 101 {'id': entry.portfolio_id, 'title': entry.portfolio_title, 102 'curr': data.currency_code, 103 'gain': data.gain and data.gain.money[0].amount, 104 'gain_persent': '%-14.2f' % (float(data.gain_percentage) * 100,), 105 'cost_basis': data.cost_basis and data.cost_basis.money[0].amount, 106 'days_gain': data.days_gain and data.days_gain.money[0].amount, 107 'market_value': data.market_value and data.market_value.money[0].amount 108 } 109 110class PositionFormatter(BaseFormatter): 111 avail_fields = [('ticker', '%(ticker)-14s'), ('shares', '%(shares)-10s'), 112 ('gain', '%(gain)-10s'), 113 ('gain_persent', '%(gain_persent)-14s'), 114 ('cost_basis', '%(cost_basis)-10s'), 115 ('days_gain', '%(days_gain)-10s'), 116 ('market_value', '%(market_value)-10s')] 117 118 def __init__(self, fields): 119 super(self.__class__, self).__init__(self.avail_fields, fields) 120 121 def get_line(self, entry): 122 data = entry.position_data 123 return self.format % \ 124 {'ticker': entry.ticker_id, 'shares': data.shares, 125 'gain': data.gain and data.gain.money[0].amount, 126 'gain_persent': '%-14.2f' % (float(data.gain_percentage) * 100,), 127 'cost_basis': data.cost_basis and data.cost_basis.money[0].amount, 128 'days_gain': data.days_gain and data.days_gain.money[0].amount, 129 'market_value': data.market_value and data.market_value.money[0].amount 130 } 131 132class TransactionFormatter(BaseFormatter): 133 avail_fields = [('id', '%(id)-3s'), ('type', '%(type)-12s'), 134 ('shares', '%(shares)-10s'), ('price', '%(price)-10s'), 135 ('commission', '%(commission)-10s'), 136 ('date', '%(date)-10s'), ('notes', '%(notes)-30s')] 137 138 def __init__(self, fields): 139 super(self.__class__, self).__init__(self.avail_fields, fields) 140 141 def get_line(self, entry): 142 data = entry.transaction_data 143 if data.date: 144 data.date = data.date[:10] # stip isoformat tail 145 return self.format % \ 146 {'id': entry.transaction_id, 'type': data.type, 'shares': data.shares, 147 'price': data.price.money[0].amount, 148 'commission': data.commission.money[0].amount, 149 'date': data.date or '', 'notes': data.notes or ''} 150 151 152#=============================================================================== 153# Each of the following _run_* functions execute a particular task. 154# 155# Keyword arguments: 156# client: Client to the service being used. 157# options: Contains all attributes required to perform the task 158# args: Additional arguments passed in on the command line, may or may not be 159# required 160#=============================================================================== 161# Portfolio-related tasks 162def _run_create(client, options, args): 163 client.CreatePortfolio(options.title, options.currency) 164 165 166def _run_delete(client, options, args): 167 entries = client.get_portfolio_entries(options.title, positions=True) 168 if entries: 169 client.DeleteEntryList(entries, 'portfolio', options.prompt) 170 171 172def _run_list(client, options, args): 173 entries = client.get_portfolio_entries(returns=True) 174 if entries: 175 PortfolioFormatter(options.fields).output(entries) 176 else: 177 LOG.info('No portfolios found') 178 179 180# Position-related tasks 181def _run_create_position(client, options, args): 182 # Quote from Developer's Guide: 183 # You can't directly create, update, or delete position entries; 184 # positions are derived from transactions. 185 # Therefore, to create or modify a position, send appropriate 186 # transactions on that position. 187 pfl = client.get_portfolio(options.title, positions=True) 188 if pfl: 189 # create empty transaction 190 client.create_transaction(pfl, "Buy", options.ticker) 191 192 193def _run_delete_positions(client, options, args): 194 positions = client.get_positions(portfolio_title=options.title, 195 ticker_id=options.ticker) 196 client.DeleteEntryList(positions, 'position', options.prompt, 197 callback=lambda pos: client.DeletePosition(position_entry=pos)) 198 199 200def _run_list_positions(client, options, args): 201 positions = client.get_positions(options.title, options.ticker, 202 include_returns=True) 203 if positions: 204 PositionFormatter(options.fields).output(positions) 205 else: 206 LOG.info('No positions found in this portfolio') 207 208 209# Transaction-related tasks 210def _run_create_transaction(client, options, args): 211 pfl = client.get_portfolio(options.title) 212 if pfl: 213 client.create_transaction(pfl, options.ttype, options.ticker, 214 options.shares, options.price, 215 options.currency, options.commission, 216 options.date, options.notes) 217 218 219def _run_delete_transactions(client, options, args): 220 transactions = client.get_transactions(portfolio_title=options.title, 221 ticker_id=options.ticker, 222 transaction_id=options.txnid) 223 client.DeleteEntryList(transactions, 'transaction', options.prompt) 224 225 226def _run_list_transactions(client, options, args): 227 transactions = client.get_transactions(portfolio_title=options.title, 228 ticker_id=options.ticker, 229 transaction_id=options.txnid) 230 TransactionFormatter(options.fields).output(transactions) 231 232 233TASKS = {'create': Task('Create a portfolio', 234 callback=_run_create, 235 required=['title', 'currency']), 236 'delete': Task('Delete portfolios', 237 callback=_run_delete, 238 required=['title']), 239 'list': Task('List portfolios', 240 callback=_run_list, 241 optional=['fields']), 242 'create-pos': Task('Create position', 243 callback=_run_create_position, 244 required=['title', 'ticker']), 245 'delete-pos': Task('Delete positions', 246 callback=_run_delete_positions, 247 required=['title'], 248 optional=['ticker']), 249 'list-pos': Task('List positions', 250 callback=_run_list_positions, 251 required=['title'], 252 optional=['fields']), 253 'create-txn': Task('Create transaction', 254 callback=_run_create_transaction, 255 required=['title', 'ticker', 'ttype', 256 'shares', 'price'], 257 optional=['shares', 'price', 'date', 258 'commission', 'currency', 'notes']), 259 'list-txn': Task('List transactions', 260 callback=_run_list_transactions, 261 required=['title', 'ticker']), 262 'delete-txn': Task('Delete transactions', 263 callback=_run_delete_transactions, 264 required=['title', 'ticker'], 265 optional=['txnid']), 266}