PageRenderTime 106ms CodeModel.GetById 91ms app.highlight 12ms RepoModel.GetById 1ms app.codeStats 0ms

/src/googlecl/finance/__init__.py

http://googlecl.googlecode.com/
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}