我有一个应该跟踪用户股票投资组合的简单应用程序。基本上用户可以购买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查询)。我的代码很慢,可能是因为有许多对象和大字典。
答案 0 :(得分:0)
这里只是一个黑暗的镜头(我不熟悉股票红利,所以我不能评论数学)。
看起来这可能是你的瓶颈:
for dividend in stock_transaction.stock.dividends.all():
您在stock
上选择了已关联并且dividends
上的prefetch_related,但您没有抓取stock__dividends
。您可以使用Django Debug Toolbar检查这是否是瓶颈。
如果此重复查询是根本问题,那么您可以尝试将其添加到:
...select_related('stock', 'stock__dividends')...