如何计算每月投资组合股票和股息

时间:2015-09-02 14:32:23

标签: python django

我有一个应该跟踪用户股票投资组合的简单应用程序。基本上用户可以购买JNJ,AAPL或MCD等股票。他也可以出售部分/全部。股息可以在支付时立即再投资(就像用户为其购买相同股票的股息价值一样)。我需要按月计算这个投资组合价值 简单例子
交易:

+----------+--------+------------+-------+
| buy/sell | amount |    date    | price |
+----------+--------+------------+-------+
| buy      |      5 | 2015-01-01 |   $60 |
| sell     |      1 | 2015-03-01 |   $70 |
+----------+--------+------------+-------+

从这笔交易中我想得到这份股票字典:

{
u'JNJ': {
    datetime.date(2015, 6, 1): Decimal('5.00000'), 
    datetime.date(2015, 7, 1): Decimal('5.00000'),
    datetime.date(2015, 8, 1): Decimal('4.00000'), 
    datetime.date(2015, 9, 1): Decimal('4.00000'), 
    datetime.date(2015, 10, 1): Decimal('4.00000')}
}

这些是我个月的股票。让我们说在2015-08-21有0.75美元的股息,并且在同一天,我在这一天买了JNJ的部分股票:

有红利的示例
交易:

+----------+--------+------------+-------+
| buy/sell | amount |    date    | price |
+----------+--------+------------+-------+
| buy      |      5 | 2015-01-01 |   $60 |
| sell     |      1 | 2015-03-01 |   $70 |
+----------+--------+------------+-------+

股息:

+------------+--------+-------+
|    date    | amount | price |
+------------+--------+-------+
| 2015-08-21 | 0.75   |    64 |
+------------+--------+-------+

支付股息时,我持有4股。对于4股,我收到4 * 0.75美元,我买了0.031393889股JNJ。 结果:

{u'JNJ': 
    {  
        datetime.date(2015, 6, 1): Decimal('5.00000'), 
        datetime.date(2015, 7, 1): Decimal('5.00000'),
        datetime.date(2015, 8, 1): Decimal('4.031393889'),
        datetime.date(2015, 9, 1): Decimal('4.031393889'), 
        datetime.date(2015, 10, 1): Decimal('4.031393889')}
} 

所以这是我必须要计算的。可能有任何数量的交易和股息。必须至少有一笔买入交易,但可能不存在股息 这些是我在models.py中的类:
代表股票的股票模型,例如JNJ。

class Stock(models.Model):
    name = models.CharField("Stock's name", max_length=200, default="")
    symbol = models.CharField("Stock's symbol", max_length=20, default="", db_index=True)
    price = models.DecimalField(max_digits=30, decimal_places=5, null=True, blank=True)

比我有StockTransaction,它代表一个投资组合的一个股票的对象。交易与StockTransaction相关联,因为滴注适用于所有交易。

class StockTransaction(models.Model):
    stock = models.ForeignKey('stocks.Stock')
    portfolio = models.ForeignKey(Portfolio, related_name="stock_transactions")
    drip = models.BooleanField(default=False)

交易类:

BUYCHOICE = [(True,'Buy'),(False,'Sell')]
class Transaction(models.Model):
    amount = models.DecimalField(max_digits=20, decimal_places=5, validators=[MinValueValidator(Decimal('0.0001'))])
    buy = models.BooleanField(choices=BUYCHOICE, default=True)
    date = models.DateField('buy date')
    price = models.DecimalField('price per share', max_digits=20, decimal_places=5, validators=[MinValueValidator(Decimal('0.0001'))])

    stock_transaction = models.ForeignKey(StockTransaction, related_name="transactions", null=False)

最后是Dividend class:

class Dividend(models.Model):
    date = models.DateField('pay date', db_index=True)
    amount = models.DecimalField(max_digits=20, decimal_places=10)
    price = models.DecimalField('price per share', max_digits=20, decimal_places=10)
    stock_transaction = models.ManyToManyField('portfolio.StockTransaction', related_name="dividends", blank=True)
    stock = models.ForeignKey(Stock, related_name="dividends")

我编写了我的方法,但我确实认为有更好的方法。我的方法太长了,需要花费很多时间来投资组合106股(每笔5笔交易)。这是我的方法:

def get_portfolio_month_shares(portfolio_id):
    """
    Return number of dividends and shares per month respectfully in dict
    {symbol: {year: decimal, year: decimal} }
    :param portfolio: portfolio object for which to calculate shares and dividends
    :return: total dividends and amount of shares, respectfully
    """

    total_shares, total_dividends = {}, {}
    for stock_transaction in StockTransaction.objects.filter(portfolio_id=portfolio_id)\
            .select_related('stock').prefetch_related('dividends', 'transactions', 'stock__dividends'):
        shares = 0 #number of shares
        monthly_shares, monthly_dividends = {}, {}
        transactions = list(stock_transaction.transactions.all())
        first_transaction = transactions[0]

        for dividend in stock_transaction.stock.dividends.all():
            if dividend.date < first_transaction.date:
                continue
            try:
                #transactions that are older than last dividend
                while transactions[0].date < dividend.date:
                    if transactions[0].buy:
                        shares = shares + transactions[0].amount
                    else: #transaction is a sell
                        shares = shares - transactions[0].amount
                    monthly_shares[date(transactions[0].date.year, transactions[0].date.month, 1)] = shares
                    transactions.remove(transactions[0])
            except IndexError: #no more transactions
                pass
            if dividend in stock_transaction.dividends.all(): # if drip is active for dividend
                if dividend.price!=0:
                    shares += (dividend.amount * shares / dividend.price)
                    monthly_shares[date(dividend.date.year, dividend.date.month, 1)] = shares
            try:
                monthly_dividends[date(dividend.date.year, dividend.date.month, 1)] += shares * dividend.amount
            except KeyError:
                monthly_dividends[date(dividend.date.year, dividend.date.month, 1)] = shares * dividend.amount

        #fill blank months with 0
        if monthly_shares!={}:
            for dt in rrule.rrule(rrule.MONTHLY,
                                  dtstart=first_transaction.date,
                                  until=datetime.now() + relativedelta.relativedelta(months=1)):
                try:
                    monthly_shares[date(dt.year, dt.month, 1)]
                except KeyError: #keyerror on dt year
                    dt_previous = dt - relativedelta.relativedelta(months=1)
                    monthly_shares[date(dt.year, dt.month, 1)] = monthly_shares[date(dt_previous.year, dt_previous.month, 1)]
                try:
                    monthly_dividends[date(dt.year, dt.month, 1)]
                except KeyError:
                    monthly_dividends[date(dt.year, dt.month, 1)] = 0

        # for each transaction not covered by dividend for cycle
        if transactions:
            for transaction in transactions:
                for dt in rrule.rrule(rrule.MONTHLY,
                              dtstart=transaction.date,
                              until=datetime.now() + relativedelta.relativedelta(months=1)):
                    if transaction.buy:
                        try:
                            monthly_shares[date(dt.year, dt.month, 1)] += transaction.amount
                        except KeyError:
                            monthly_shares[date(dt.year, dt.month, 1)] = transaction.amount
                    else: #sell
                        monthly_shares[date(dt.year, dt.month, 1)] -= transaction.amount
        total_dividends[stock_transaction.stock.symbol] = monthly_dividends
        total_shares[stock_transaction.stock.symbol] = monthly_shares
    return total_dividends, total_shares

描述
首先是周期 - 对于投资组合中的每只股票 第二个周期 - 每个股票的股息
此行if dividend in stock_transaction.dividends.all()检查股息是否再投资。如果存在stock_transaction和被除对象之间存在m2m关系 对于带有rrule的循环填充空白月份到上个月的值。

EDIT1:
我已经使用django-debug-toolbar优化了sql查询的数量(需要4个sql查询)。我的代码很慢,可能是因为有许多对象和大字典。

1 个答案:

答案 0 :(得分:0)

这里只是一个黑暗的镜头(我不熟悉股票红利,所以我不能评论数学)。

看起来这可能是你的瓶颈:

    for dividend in stock_transaction.stock.dividends.all():

您在stock上选择了已关联并且dividends上的prefetch_related,但您没有抓取stock__dividends。您可以使用Django Debug Toolbar检查这是否是瓶颈。

如果此重复查询是根本问题,那么您可以尝试将其添加到:

    ...select_related('stock', 'stock__dividends')...