PageRenderTime 54ms CodeModel.GetById 24ms RepoModel.GetById 0ms 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.
  14. import googlecl
  15. import inspect
  16. import logging
  17. import os
  18. import sys
  19. from googlecl.base import Task
  20. safe_encode = googlecl.safe_encode
  21. service_name = __name__.split('.')[-1]
  22. LOGGER_NAME = __name__
  23. SECTION_HEADER = service_name.upper()
  24. LOG = logging.getLogger(LOGGER_NAME)
  25. class BaseFormatter(object):
  26. """Base class for formatters."""
  27. def __init__(self, avail_fields, fields, sep=','):
  28. """Init formatter
  29. Args:
  30. avail_fields: list of tuples [(field_name, format_spec), ...] for all
  31. possible fields
  32. fields: string, list of <sep>-separated requested fields names.
  33. sep: string, separator, comma by default
  34. """
  35. if fields:
  36. self.fields = fields.split(sep)
  37. else:
  38. self.fields = [item[0] for item in avail_fields]
  39. self.avail_fields = avail_fields
  40. avail_dict = dict(avail_fields)
  41. self.format = ' '.join(avail_dict[name] for name in self.fields)
  42. @property
  43. def header(self):
  44. """Make output header.
  45. Uses names of available fields as column headers. replaces
  46. '_' with ' ' and capitalizes them. Utilizes the same format as
  47. used for body lines: self.format
  48. Returns: string, header.
  49. """
  50. return self.format % \
  51. dict([(item[0], item[0].replace('_', ' ').capitalize()) \
  52. for item in self.avail_fields])
  53. def get_line(self, entry):
  54. """Get formatted entry. Abstract method.
  55. Args:
  56. entry: entry object
  57. Returns:
  58. string, formatted entry.
  59. """
  60. raise NotImplementedError("Abstract method %s.%s called" % \
  61. (self.__class__.__name__,
  62. inspect.stack()[0][3] ))
  63. def output(self, entries, stream=sys.stdout):
  64. """Output list of entries to the output stream.
  65. Args:
  66. entries: list of entries.
  67. stream: output stream.
  68. """
  69. if self.header:
  70. stream.write(self.header + os.linesep)
  71. for entry in entries:
  72. stream.write(self.get_line(entry) + os.linesep)
  73. class PortfolioFormatter(BaseFormatter):
  74. avail_fields = [('id', '%(id)3s'), ('title', '%(title)-15s'),
  75. ('curr', '%(curr)-4s'),
  76. ('gain', '%(gain)-10s'),
  77. ('gain_persent', '%(gain_persent)-14s'),
  78. ('cost_basis', '%(cost_basis)-10s'),
  79. ('days_gain', '%(days_gain)-10s'),
  80. ('market_value', '%(market_value)-10s')]
  81. def __init__(self, fields):
  82. super(self.__class__, self).__init__(self.avail_fields, fields)
  83. def get_line(self, entry):
  84. data = entry.portfolio_data
  85. return self.format % \
  86. {'id': entry.portfolio_id, 'title': entry.portfolio_title,
  87. 'curr': data.currency_code,
  88. 'gain': data.gain and data.gain.money[0].amount,
  89. 'gain_persent': '%-14.2f' % (float(data.gain_percentage) * 100,),
  90. 'cost_basis': data.cost_basis and data.cost_basis.money[0].amount,
  91. 'days_gain': data.days_gain and data.days_gain.money[0].amount,
  92. 'market_value': data.market_value and data.market_value.money[0].amount
  93. }
  94. class PositionFormatter(BaseFormatter):
  95. avail_fields = [('ticker', '%(ticker)-14s'), ('shares', '%(shares)-10s'),
  96. ('gain', '%(gain)-10s'),
  97. ('gain_persent', '%(gain_persent)-14s'),
  98. ('cost_basis', '%(cost_basis)-10s'),
  99. ('days_gain', '%(days_gain)-10s'),
  100. ('market_value', '%(market_value)-10s')]
  101. def __init__(self, fields):
  102. super(self.__class__, self).__init__(self.avail_fields, fields)
  103. def get_line(self, entry):
  104. data = entry.position_data
  105. return self.format % \
  106. {'ticker': entry.ticker_id, 'shares': data.shares,
  107. 'gain': data.gain and data.gain.money[0].amount,
  108. 'gain_persent': '%-14.2f' % (float(data.gain_percentage) * 100,),
  109. 'cost_basis': data.cost_basis and data.cost_basis.money[0].amount,
  110. 'days_gain': data.days_gain and data.days_gain.money[0].amount,
  111. 'market_value': data.market_value and data.market_value.money[0].amount
  112. }
  113. class TransactionFormatter(BaseFormatter):
  114. avail_fields = [('id', '%(id)-3s'), ('type', '%(type)-12s'),
  115. ('shares', '%(shares)-10s'), ('price', '%(price)-10s'),
  116. ('commission', '%(commission)-10s'),
  117. ('date', '%(date)-10s'), ('notes', '%(notes)-30s')]
  118. def __init__(self, fields):
  119. super(self.__class__, self).__init__(self.avail_fields, fields)
  120. def get_line(self, entry):
  121. data = entry.transaction_data
  122. if data.date:
  123. data.date = data.date[:10] # stip isoformat tail
  124. return self.format % \
  125. {'id': entry.transaction_id, 'type': data.type, 'shares': data.shares,
  126. 'price': data.price.money[0].amount,
  127. 'commission': data.commission.money[0].amount,
  128. 'date': data.date or '', 'notes': data.notes or ''}
  129. #===============================================================================
  130. # Each of the following _run_* functions execute a particular task.
  131. #
  132. # Keyword arguments:
  133. # client: Client to the service being used.
  134. # options: Contains all attributes required to perform the task
  135. # args: Additional arguments passed in on the command line, may or may not be
  136. # required
  137. #===============================================================================
  138. # Portfolio-related tasks
  139. def _run_create(client, options, args):
  140. client.CreatePortfolio(options.title, options.currency)
  141. def _run_delete(client, options, args):
  142. entries = client.get_portfolio_entries(options.title, positions=True)
  143. if entries:
  144. client.DeleteEntryList(entries, 'portfolio', options.prompt)
  145. def _run_list(client, options, args):
  146. entries = client.get_portfolio_entries(returns=True)
  147. if entries:
  148. PortfolioFormatter(options.fields).output(entries)
  149. else:
  150. LOG.info('No portfolios found')
  151. # Position-related tasks
  152. def _run_create_position(client, options, args):
  153. # Quote from Developer's Guide:
  154. # You can't directly create, update, or delete position entries;
  155. # positions are derived from transactions.
  156. # Therefore, to create or modify a position, send appropriate
  157. # transactions on that position.
  158. pfl = client.get_portfolio(options.title, positions=True)
  159. if pfl:
  160. # create empty transaction
  161. client.create_transaction(pfl, "Buy", options.ticker)
  162. def _run_delete_positions(client, options, args):
  163. positions = client.get_positions(portfolio_title=options.title,
  164. ticker_id=options.ticker)
  165. client.DeleteEntryList(positions, 'position', options.prompt,
  166. callback=lambda pos: client.DeletePosition(position_entry=pos))
  167. def _run_list_positions(client, options, args):
  168. positions = client.get_positions(options.title, options.ticker,
  169. include_returns=True)
  170. if positions:
  171. PositionFormatter(options.fields).output(positions)
  172. else:
  173. LOG.info('No positions found in this portfolio')
  174. # Transaction-related tasks
  175. def _run_create_transaction(client, options, args):
  176. pfl = client.get_portfolio(options.title)
  177. if pfl:
  178. client.create_transaction(pfl, options.ttype, options.ticker,
  179. options.shares, options.price,
  180. options.currency, options.commission,
  181. options.date, options.notes)
  182. def _run_delete_transactions(client, options, args):
  183. transactions = client.get_transactions(portfolio_title=options.title,
  184. ticker_id=options.ticker,
  185. transaction_id=options.txnid)
  186. client.DeleteEntryList(transactions, 'transaction', options.prompt)
  187. def _run_list_transactions(client, options, args):
  188. transactions = client.get_transactions(portfolio_title=options.title,
  189. ticker_id=options.ticker,
  190. transaction_id=options.txnid)
  191. TransactionFormatter(options.fields).output(transactions)
  192. TASKS = {'create': Task('Create a portfolio',
  193. callback=_run_create,
  194. required=['title', 'currency']),
  195. 'delete': Task('Delete portfolios',
  196. callback=_run_delete,
  197. required=['title']),
  198. 'list': Task('List portfolios',
  199. callback=_run_list,
  200. optional=['fields']),
  201. 'create-pos': Task('Create position',
  202. callback=_run_create_position,
  203. required=['title', 'ticker']),
  204. 'delete-pos': Task('Delete positions',
  205. callback=_run_delete_positions,
  206. required=['title'],
  207. optional=['ticker']),
  208. 'list-pos': Task('List positions',
  209. callback=_run_list_positions,
  210. required=['title'],
  211. optional=['fields']),
  212. 'create-txn': Task('Create transaction',
  213. callback=_run_create_transaction,
  214. required=['title', 'ticker', 'ttype',
  215. 'shares', 'price'],
  216. optional=['shares', 'price', 'date',
  217. 'commission', 'currency', 'notes']),
  218. 'list-txn': Task('List transactions',
  219. callback=_run_list_transactions,
  220. required=['title', 'ticker']),
  221. 'delete-txn': Task('Delete transactions',
  222. callback=_run_delete_transactions,
  223. required=['title', 'ticker'],
  224. optional=['txnid']),
  225. }