service.py

#

Copyright (C) 2010 Google Inc.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Service details and instances for the Finance service.

#

Some use cases: Create portfolio: finance create --title "Some Portfolio" --currency USD

Delete portfolio: finance delete --title "Some Portfolio"

List portfolios: finance list

Create position: finance create-pos --title "Some Portfolio" --ticker NYSE:PCLN

Delete position: finance delete-pos --title "Some Portfolio" --ticker NYSE:PCLN

List positions: finance list-pos --title "Some Portfolio"

Create transaction: finance create-txn --title "Some Portfolio" --ticker NASDAQ:PCLN --currency USD --ttype Sell --price 346.60 --commission 7.7 --shares 60 --date 2010-09-24 --notes "Stop loss on 347.01"

Delete transaction: finance delete-txn --title "Some Portfolio" --ticker NASDAQ:PCLN --txnid 4

List transactions: finance list-txn --title "Some Portfolio" --ticker NASDAQ:PCLN

__author__ = 'bartosh@gmail.com (Ed Bartosh)'

import logging
import datetime

import googlecl
import googlecl.calendar.date
from googlecl.base import BaseCL
from googlecl.service import BaseServiceCL
from googlecl.finance import SECTION_HEADER

from gdata.service import RequestError
from gdata.finance.service import FinanceService, PortfolioQuery, PositionQuery
from gdata.finance import PortfolioData, PortfolioEntry, TransactionEntry, \
                          TransactionData, Money, Price, Commission, \
                          PortfolioFeedFromString

LOG = logging.getLogger(googlecl.finance.LOGGER_NAME)
#

Extends gdata.photos.service.FinanceService for the command line.

class FinanceServiceCL(FinanceService, BaseServiceCL):
#

This class adds some features focused on using Finance via an installed app with a command line interface.

#

Constructor.

  def __init__(self, config):
#
    FinanceService.__init__(self)
    BaseServiceCL.__init__(self, SECTION_HEADER, config)
    self.max_results = None
#

Creates a portfolio.

  def create_portfolio(self, title, currency):
#

Args: title: Title to give the portfolio. currency: Currency associated with the portfolio (e.g. USD)

    pfl = PortfolioEntry(
      portfolio_data=PortfolioData(currency_code=currency))
    pfl.portfolio_title = title
    try:
      return self.AddPortfolio(pfl)
    except RequestError, err:
      LOG.error('Failed to create portfolio: %s' % err[0]['body'])

  CreatePortfolio = create_portfolio
#

Check that the token being used is valid.

  def is_token_valid(self, test_uri='/data/feed/api/user/default'):
#
    return BaseCL.IsTokenValid(self, test_uri)

  IsTokenValid = is_token_valid
#

Get portfolio entries or one entry.

  def get_portfolio_entries(self, title=None, returns=False, positions=False,
                            multiple=True):
#

Args: title: string, portfolio title, could be regexp. returns: [optional] boolean, include returns into the result. positions: [optional] boolean, include positions into the result. multiple: boolean, return multiple entries if True Returns: list of portfolio entries

    query = PortfolioQuery()
    query.returns = returns
    query.positions = positions

    uri = "/finance/feeds/default/portfolios/" + query.ToUri()

    if multiple:
      return self.GetEntries(uri, titles=title,
                             converter=PortfolioFeedFromString)
    else:
      entry = self.GetSingleEntry(uri, title=title,
                                  converter=PortfolioFeedFromString)
      if entry:
        return [entry]
      else:
        return []
#

Get portfolio by title.

  def get_portfolio(self, title, returns=False, positions=False):
#

Args: title: string, portfolio title. returns: [optional] boolean, include returns into the result. positions: [optional] boolean, include positions into the result.

Returns: portfolio feed object or None if not found.

    entries = self.get_portfolio_entries(title=title, returns=returns,
                                         positions=positions, multiple=False)
    if entries:
      return entries[0]
    else:
      LOG.info('Portfolio "%s" not found' % title)
      return None
#

Get positions in a portfolio.

  def get_positions(self, portfolio_title, ticker_id=None,
                    include_returns=False):
#

Args: portfolio_title: Title of the portfolio. ticker_id: Ticker, e.g. "NYSE:GLD" include_returns: Include returns in the portfolio data. Default False.

Returns: List of positions in the portfolio, or empty list if no positions found matching the criteria.

XXX:Would be nice to differentiate between positions. Right now, just get all of them.

    pfl = self.get_portfolio(portfolio_title, returns=include_returns,
                               positions=True)
    if not pfl:
      LOG.debug('No portfolio to get positions from!')
      return []
    if not pfl.positions:
      LOG.debug('No positions found in this portfolio.')
      return []

    if ticker_id is not None:
      positions = [self.GetPosition(portfolio_id=pfl.portfolio_id,
                                      ticker_id=ticker_id)]
    else:
      positions = self.GetPositionFeed(portfolio_entry=pfl).entry
    return positions
#
  def get_transactions(self, portfolio_title, ticker_id, transaction_id=None):
    pfl = self.get_portfolio(portfolio_title)
    if not pfl:
      LOG.debug('No portfolio to get transactions from!')
      return []
    if transaction_id:
      transactions = [self.GetTransaction(portfolio_id=pfl.portfolio_id,
                                            ticker_id=ticker_id,
                                            transaction_id=transaction_id)]
    else:
      transactions = self.GetTransactionFeed(portfolio_id=pfl.portfolio_id,
                                             ticker_id=ticker_id).entry
    return transactions
#

Create transaction.

  def create_transaction(self, pfl, ttype, ticker, shares=None, price=None,
                         currency=None, commission=None, date='', notes=None):
#

Args: pfl: portfolio object. ttype: string, transaction type, on of the 'Buy', 'Sell', 'Short Sell', 'Buy to Cover'. shares: [optional] decimal, amount of shares. price: [optional] decimal, price of the share. currency: [optional] string, portfolio currency by default. commission: [optional] decimal, brocker commission. date: [optional] string, transaction date, datetime.now() by default. notes: [optional] string, notes.

Returns: None if transaction created successfully, otherwise error string.

    if not currency:
      currency = pfl.portfolio_data.currency_code
    if date is None:
#

if date is not provided from the command line current date is set

      date = datetime.datetime.now().isoformat()
    elif date is '':
#

special case for create position task. date should be set to None to create empty transaction. See detailed explanations in the _run_create_position function below

      date = None
    else:
      parser = googlecl.calendar.date.DateParser()
      date = parser.parse(date).local.isoformat()

    if price is not None:
      price = Price(money=[Money(amount=price, currency_code=currency)])
    if commission is not None:
      commission = Commission(money=[Money(amount=commission,
                                             currency_code=currency)])
    txn = TransactionEntry(transaction_data=TransactionData(
        type=ttype, price=price, shares=shares, commission=commission,
        date=date, notes=notes))

    try:
      return self.AddTransaction(txn, portfolio_id=pfl.portfolio_id,
                                 ticker_id=ticker)
    except RequestError, err:
      LOG.error('Failed to create transaction: %s' % err[0]['body'])


SERVICE_CLASS = FinanceServiceCL
#

Local Variables: mode: python py-indent-offset: 2 indent-tabs-mode: nil tab-width: 2 End: