how to calculate monthly portfolio shares and dividends - python

I have a simple app that should track users stock portfolio. Basicly user may buy stocks like JNJ, AAPL or MCD. He can also sell some/all of them. Dividends might be reinvested instantly as they are paid out (same as if user did buy same stock for it's dividend value). I need to calculate this portfolio value on monthly basis.
Easy example:
Transactions:
+----------+--------+------------+-------+
| buy/sell | amount | date | price |
+----------+--------+------------+-------+
| buy | 5 | 2015-01-01 | $60 |
| sell | 1 | 2015-03-01 | $70 |
+----------+--------+------------+-------+
From this transactions I would like to get this dictionary of shares:
{
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')}
}
These are my shares by month. Lets say there was a $0.75 dividend on 2015-08-21 and it on same day, I bought partial shares of JNJ on this date:
Example with Dividends:
Transactions:
+----------+--------+------------+-------+
| buy/sell | amount | date | price |
+----------+--------+------------+-------+
| buy | 5 | 2015-01-01 | $60 |
| sell | 1 | 2015-03-01 | $70 |
+----------+--------+------------+-------+
Dividends:
+------------+--------+-------+
| date | amount | price |
+------------+--------+-------+
| 2015-08-21 | 0.75 | 64 |
+------------+--------+-------+
When dividend was paid, I was holding 4 shares. For 4 shares I received 4*$0.75 and I bought 0.031393889 shares of JNJ.
Result:
{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')}
}
So this is what I have to calculate. There might be any number of transaction and dividends. There must be at least one Buy transaction, but dividends may not exist.
These are my classes in models.py:
Stock model representing Stock, for example 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)
Than I have StockTransaction, which represends object for one stock for one portfolio. Transactions are linked to StockTransaction, because drip applies to all Transactions.
class StockTransaction(models.Model):
stock = models.ForeignKey('stocks.Stock')
portfolio = models.ForeignKey(Portfolio, related_name="stock_transactions")
drip = models.BooleanField(default=False)
Transaction class:
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)
and lastly 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")
I've coded my method, but I do think there is a better way. My method is too long and takes to much time for portfolio with 106 stocks (each 5 transactions). Here is my method:
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
Description:
First for cycle - for each stock in portfolio.
Second for cycle - for each stock's dividend
this line if dividend in stock_transaction.dividends.all() checks if dividends are reinvested. There exists m2m relation between stock_transaction and dividend objects if they are.
for cycle with rrule fills up blank months to previous month value.
EDIT1:
I already optimized number of sql queries with django-debug-toolbar (4 sql queries needed). My code is slow probably because of many objects and large dictionaries.

Just a shot in the dark here (I'm not familiar with stock dividends, so I can't comment on the math).
It looks like this could be your bottleneck:
for dividend in stock_transaction.stock.dividends.all():
You select_related on stock and you prefetch_related on dividends, but you don't grab stock__dividends. You can check whether or not this is the bottleneck using the Django Debug Toolbar.
If this repeating query is the root problem, then you may try adding it in:
...select_related('stock', 'stock__dividends')...

Related

Django annotate count for another queryset

Models:
class Regions(models.Model):
name = models.CharField(max_length=255, unique=True)
class Owners(models.Model):
name = models.CharField(max_length=255, null=False, unique=True)
url = models.URLField(null=True)
class Lands(models.Model):
region = models.ForeignKey(Regions, on_delete=models.CASCADE)
owner = models.ForeignKey(Owners, on_delete=models.PROTECT, null=True)
description = models.TextField(max_length=2000, null=True)
class LandChangeHistory(models.Model):
land = models.ForeignKey(Lands, on_delete=models.CASCADE, null=False, related_name='lands')
price = models.IntegerField()
size = models.IntegerField()
date_added = models.DateField(auto_now_add=True)
Queryset that works but i need it to be annotated in another queryset somehow:
lands_in_region = Lands.objects.values('region__name').annotate(count=Count('region_id'))
returns for example:
{'region__name': 'New York', 'count': 3}, {'region__name':
'Chicago', 'count': 2}
In the 2nd queryset i need count of lands available in region. But instead of real count, i always get count = 1. How to combine them? Im pretty sure i could do it in raw sql by joining two tables on field "region__id", but dont know how to do it in django orm.
f = LandFilter(request.GET, queryset=LandChangeHistory.objects.all()
.select_related('land', 'land__region', 'land__owner')
.annotate(usd_per_size=ExpressionWrapper(F('price') * 1.0 / F('size'), output_field=FloatField(max_length=3)))
.annotate(count=Count('land__region_id'))
)
For example. If it returns:
land1 | 100$ | 100m2 | New York
land2 | 105$ | 105m2 | New York
land3 | 102$ | 102m2 | Chicago
i need 1 more column, that counts for each land how many NewYork's and Chicago's are there
land1 | 100$ | 100m2 | New York | 2
land2 | 105$ | 105m2 | New York | 2
land3 | 102$ | 102m2 | Chicago | 1
This worked for me. Hope helps somebody.
First i tried to simply call Count() method right after filter, but that breaks query, since it tries to get data from DB immediately. But that was the correct way to think, so i added count annotate and selected it and it worked.
f = LandFilter(request.GET, queryset=LandChangeHistory.objects.all()
.select_related('land', 'land__region', 'land__owner')
.annotate(usd_per_size=ExpressionWrapper(F('price') * 1.0 / F('size'), output_field=FloatField(max_length=3)))
.annotate(count=Subquery(
Lands.objects.filter(region_id=OuterRef('land__region_id'))
.values('region_id')
.annotate(count=Count('pk'))
.values('count')))
)

Django - How to do complex query with left join and coalesce?

Scenario: Showing all voucher that a user can apply.
I have 2 tables Voucher (with all information of a voucher) and VoucherCustomer (listing number of vouchers that a user has used)
A validation voucher that can show to user on the application should be
Within use-able duration
Do not exceed number of times used within a day
Do not exceed number of times used per user
Do not exceed number of times used for a voucher
Must active
Here is my model:
class Voucher(models.Model):
code = models.ForeignKey('VoucherCustomer', related_name= 'voucher_code', on_delete = models.CASCADE) (1)
start_at = models.DateTimeField() (2)
end_at = models.DateTimeField() (3)
usage_limit_per_customer = models.BigIntegerField() (4)
times_used = models.BigIntegerField() (5)
usage_limit_daily = models.BigIntegerField() (6)
times_used_daily = models.BigIntegerField() (7)
is_global = models.BooleanField(blank=True, null=True) (8)
is_active = models.BooleanField() (9)
class VoucherCustomer(models.Model):
voucher_code = models.CharField(max_length = 255, primary_key=True) (1)
customer_id = models.IntegerField() (2)
times_used = models.BigIntegerField(blank=True, null=True) (3)
created_at = models.DateTimeField(blank=True, null=True) (4)
updated_at = models.DateTimeField(blank=True, null=True) (5)
Here is the sample data:
+++++++ Voucher ++++++++
(1) (2) (3) (4) (5) (6) (7) (8) (9)
TEST01 | 2020-11-30 17:00:00 | 2021-03-01 16:59:59 | 100 | 1124 | 5000 | 6 | true | true
+++++++ VoucherCustomer ++++++++
(1) (2) (3) (4) (5)
TEST01 10878 9 2020-12-03 02:17:32.012722 2020-12-08 10:32:03.877349
TEST01 12577 1 2020-12-02 07:17:34.005964 2020-12-02 07:17:34.005964
TEST01 8324 18 2020-12-02 07:49:37.385682 2021-02-01 14:35:38.096381
TEST01 7638 2 2020-12-02 08:17:46.532566 2020-12-02 08:17:46.532566
TEST01 3589 1 2020-12-02 14:57:01.356616 2020-12-02 14:57:01.356616
My expected query:
SELECT v.*
FROM leadtime.voucher v
LEFT JOIN leadtime.voucher_customer vc ON v.code = vc.voucher_code AND vc.customer_id in ({input_customer_id})
WHERE 1=1
AND v.start_at <= CURRENT_TIMESTAMP
AND v.end_at >= CURRENT_TIMESTAMP
AND v.is_active = TRUE
AND v.times_used < v.usage_limit
AND v.times_used_daily < v.usage_limit_daily
AND (v.customer_id = {input_customer_id} OR v.is_global)
AND COALESCE(vc.times_used,0) < usage_limit_per_customer
ORDER BY created_at
Here is my code on Django:
from django.db.models import Q, F, Value
from django.db.models.functions import Coalesce
now = datetime.now()
customer_input = customer_id = request.GET.get('customer_id')
query = Voucher.objects.filter(
start_at__lte = now,
end_at__gte = now,
is_active = True,
times_used__lt = F('usage_limit'),
times_used_daily__lt = F('usage_limit_daily'),
Q(customer_id = customer_id_input ) | Q(is_global = True),
VoucherCustomer__customer_id = customer_id_input ,
Coalesce(VoucherCustomer__times_used, Value(0)) <= F('usage_limit_per_customer')
).order_by('created_at').values()
Obviously, it does not work.
I got this error:
File "/code/app_test/views.py", line 467
).order_by('created_at').values()
^
SyntaxError: positional argument follows keyword argument
Anyway, since I am just a beginner, if there are parts that I could improve in my code please feel free to tell me.
------------ Updated ----------------
The current code is not working as I received this err
Cannot resolve keyword 'VoucherCustomer' into field. Choices are: airport, arrival_airport, code, code_id, content, created_at, customer_id, delivery_type, departure_airport, description, discount_amount, discount_type, end_at, from_time, id, is_active, is_global, max_discount_amount, start_at, times_used, times_used_daily, tittle, to_time, updated_at, usage_limit, usage_limit_daily, usage_limit_per_customer
I tried to change the model of VoucherCustomer into this one but still not working.
class VoucherCustomer(models.Model):
voucher_code = models.ManyToOneRel(field = "voucher_code", field_name = "voucher_code", to = "")
......................
class Meta:
managed = False
db_table = 'voucher_customer'
The code is not syntetically correct, you can't use <= inside a method signature, ie use that in filter(), also you need to pass the arguments before passing the keyword arguments through the function, ie some_function(a, b, x=y).
But, you can use Coalesce to annotate the value with Voucher queryset then run the filter again, like this:
query = Voucher.objects.filter(
Q(customer_id = customer_id_input ) | Q(is_global = True),
start_at__lte = now,
end_at__gte = now,
is_active = True,
times_used__lt = F('usage_limit'),
times_used_daily__lt = F('usage_limit_daily'),
code__customer_id = customer_id_input
).annotate(
usage_so_far=Coalesce('code__times_used', Value(0))
).filter(
usage_so_far__gte=F('usage_limit_per_customer')
).order_by('created_at').values()

inventory question regarding adding and removing product using dictionary

I am working on task like this, in a company, they want to sale product called 'computer'.
if enough stock does not exists before the sale date, it should give message for insufficient qty , message.
Also should give information of total available product quantity for sale before that date, and it's valuation.
if company have enough quantity for sale, than it should display message of remaining qty of that product and it's valuation.
e.g.
valuation = quantity * purchase price
my code
import datetime
class Supplier:
def add_supplier(self, name, address, email, contact_no):
"""
Function: Used to add supplier details.
Parameter:name: Supplier Name
address: Supplier address
email: Supplier email
contact_no: Supplier Contact detail
Return: Nothing
"""
self.name = name
self.address = address
self.email = email
self.contact_no = contact_no
class Product:
def add_product(self, name):
"""
Function: To add product in to an inventory.
Parameter: Product name
Return: Nothing
"""
self.name = name
class Company(Supplier, Product):
data_dict = []
def purchase(self, product_obj, qty, price, date=datetime.date.today()):
"""
Function: Used to purchase product.
Parameter: product_obj: Product object.
qty: Product quantity.
price: Product price.
date: Product date.
Return: Nothing
"""
self.data_dict.append({'product': product_obj.name, 'qty': qty, 'price': price, 'date': str(date)})
def sale(self, sale_qty, sale_date):
"""
Function: To sale product from warehouse.
Parameter: sale_qty: Product sell quantity.
sale_date: Product sell date.
Return: Nothing
"""
self.newdict = (sorted(self.data_dict, key=lambda x: x['date']))
assert sale_qty > 0
unit_val = 0
rqilr = 0
rpilr = 0
qty = 0
price = 0
sum_qty = 0
sum_price = 0
#print(newdict) # testing.
for i, row in enumerate(self.newdict):
if row['date'] > sale_date:
print("Sorry, not enough quantity.\nStart the purchase order procedure pls!\n")
qty += row['qty']
price += row['price']
unit_val = price/qty
# we have enough Computers in stock.
if qty >= sale_qty:
break
else: # loop completes normally
for i in self.newdict:
sum_price += i['price']
sum_qty += i['qty']
print("Sorry, not enough qty. Operation cancelled.\nCurrent quantity = {} and valuation = {}".format(sum_qty, sum_price))
# remaining_qty_in_last_row sort name = rqilr
rqilr = qty - sale_qty
rpilr = rqilr * unit_val
self.newdict = [{**row, 'price': rpilr, 'qty': rqilr}] + self.newdict[i + 1:]
#print(self.newdict)
# For current quantity and valuation
for i in self.newdict:
sum_price += i['price']
sum_qty += i['qty']
print("Sold!\n available quantity = {} and valuation = {}".format(sum_qty, sum_price))
C = Company()
PRODUCT_OBJ = Product()
PRODUCT_OBJ.add_product('Computer')
while True:
option = int(input("""1. You want to add stock of the product!"
2. Want to sale product?
"""))
if option == 1:
qty = int(input("Enter the qty of the product.\n"))
price = float(input("Enter the price of the product.\n"))
purchase_date = input("Enter purchase date.\n")
C.purchase(PRODUCT_OBJ, qty, price, purchase_date)
elif option == 2:
qty = int(input("Enter the qty you wanna sale, pal!"))
sale_date = input("Enter sale date.\n")
C.sale(qty, sale_date)
else:
print("Enter only 1 & 2 option.\n")
after executing program I am having menu like in program.
You want to add stock of the product!
Want to sale product?
but output not getting proper output of remaining quantity and valuation.
Any suggestion , how to make it right?

Django ORM: Joining a table to itself

I have a table with cryptocurrency prices:
id | price | pair_id | exchange_id | date
---+--------+---------+-------------+---------------------------
1 | 8232.7 | 1 | 1 | 2018-02-09 09:31:00.160837
2 | 8523.8 | 1 | 2 | 2018-02-09 09:31:01.353998
3 | 240.45 | 2 | 1 | 2018-02-09 09:31:02.524333
I want to get the latest prices of a single pair from different exchanges. In raw SQL, I do it like this:
SELECT b.price, b.date, k.price, k.date, AVG((b.price + k.price) / 2)
FROM converter_price b JOIN converter_price k
WHERE b.exchange_id=1 AND k.exchange_id=2 AND b.pair_id=1 AND k.pair_id=1
ORDER BY b.date DESC, k.date DESC LIMIT 1;
8320.1|2018-02-09 11:23:00.369810|8318.2|2018-02-09 11:23:06.467424|8245.05199328066
How to do such query in the Django ORM?
EDIT: Adding models.py. I understand that it's quite likely that I'll need to update it.
from django.db import models
# Create your models here.
class Pair(models.Model):
identifier = models.CharField('Pair identifier', max_length=6)
def __str__(self):
return self.identifier
class Exchange(models.Model):
name = models.CharField('Exchange name', max_length=20)
def __str__(self):
return self.name
class Price(models.Model):
pair = models.ForeignKey(Pair, on_delete=models.CASCADE)
exchange = models.ForeignKey(Exchange, on_delete=models.CASCADE)
price = models.FloatField(default=0)
date = models.DateTimeField(auto_now=True, db_index=True)
def __str__(self):
return '{} - {}: {} ({})'.format(
self.date, self.pair, self.price, self.exchange)
EDIT2: To clarify what I'm really after.
I want the latest price with pair_id=1 and exchange_id=1, and the latest price with pair_id=1 and exchange_id=2. With a single query, and without any subsequent processing in Python - of course I can get Price.objects.all() and then search for it myself, but that's not the right way to use the ORM.
The way to do this with raw SQL is joining the table to itself. Showing how to join a table to itself using the Django ORM would (probably) answer the question.

Is there a 'good' way to build and organize Django models?

I am trying to build a Django App which lets small Stock Clubs easily manage and view data about their club. The issue I have run into is building elegant and logical models. I have not found any literature about a 'good' way to build models, or even a 'good' way to go about starting to plan models, so I thought I would post my models.py here and see what you think.
Do I have them built logically? Is there a better way? I'm not as concerned with the style of the code itself (it will eventually be PEP-8 compliant) as I am with the logic and elegance of the design.
To get a better idea of what the purpose is, here is a very plain-looking example of what the data contained by the models will be generating: http://www.bierfeldt.com/takestock/clubs/1/
Here is a flowchart design of what the models look like currently:
Thank you.
models.py
from django.db import models
####### Stock Models #######
class Stock(models.Model):
'''A stock whose current_price is updated every minute by a cronned script
running on the server. The current_price updating script gets all Stock objects
and runs Google Finance queries on each Stock
Stock has no relation to a particular owner, for that, see StockInstance model below'''
ticker = models.CharField(max_length=200)
current_price = models.DecimalField(max_digits=20, decimal_places=2)
def __unicode__(self):
return str(self.ticker + ">" + str(self.current_price))
class StockInstance(models.Model):
'''A middle-man model which links a Stock model to an owner (see Club model below.)
A single owner may possess multiple instances of a single stock purchased at different times
ex. December 9, 2012 - Owner buys 20 shares of AAPL at $500
December 13, 2012 - Owner buys 15 shares of AAPL at $482'''
owner = models.ForeignKey('Club')
stock = models.ForeignKey(Stock)
def current_price(self):
#Current Price of relevant stock
return self.stock.current_price
shares = models.IntegerField()
purchase_date = models.DateTimeField()
purchase_price = models.DecimalField(max_digits=20, decimal_places=2)
#if is_open is False, the instance is considered a closed position
is_open = models.BooleanField(default=True)
sell_date = models.DateTimeField(blank=True, null=True)
sell_price = models.DecimalField(max_digits=20, decimal_places=2, blank=True, null=True)
def current_value(self):
#Current value of this stock instance
#ex. $200 Current Price * 10 shares = $2000
return (self.current_price() * self.shares)
def purchase_value(self):
#Purchase value of this stock instance
#ex. $195 Purchase Price * 10 shares = $1950
return (self.purchase_price * self.shares)
def percent_gl(self):
#Percent Gained/Lost
#ex. ($2000 Current Value - $1950 Purchase Value) / ($1950 Purchase Value) = .03 (03%) Gained
return ((self.current_value() - self.purchase_value()) / (self.purchase_value()))
def amount_gl(self):
#Dollar Value Gained/Lost
#ex. $2000 Current Value - $1950 Purchase Value = $50 Gained
return (self.current_value() - self.purchase_value())
def total_percentage(self):
#Percent of Club Value (all club assets including cash) which this stock instance comprises
#ex. $2000 Current Value / $10000 Club Total Assests = .20 (20%)
return (self.current_value() / self.owner.current_value())
def __unicode__(self):
return str(str(self.shares) + str(" of ")+ str(self.stock.ticker))
####### Member Models #######
class Member(models.Model):
'''Members may belong to multiple clubs. The Member model has no relation to a club
See MemberInstance model below'''
name = models.CharField(max_length=200)
created_at = models.DateTimeField(auto_now_add = True)
updated_at = models.DateTimeField(auto_now = True)
def __unicode__(self):
return self.name
class MemberInstance(models.Model):
'''A middle-man model which links a Member model to an owner (see Club model below.)
A single member may belong to multiple clubs at the same time
ex. John has 5 shares of "Sandstone Investment Club"
John has 15 shares of "Blackwell Investment Firm"'''
owner = models.ForeignKey('Club')
member = models.ForeignKey(Member)
shares = models.DecimalField(max_digits=20, decimal_places=2)
def total_share_value(self):
#Total Dollar value of all of particular member's shares of a club
#ex. Sandstone Investment Club's Share Price is $20 and John has 5 shares
#ex. cont. $20 * 5 shares = $100 value of John's shares in Sandstone Investment Club
return (self.shares * self.owner.current_price())
def total_share_percentage(self):
#Percent of a club that a particular member owns
#ex. John has $100 of Sandstone Investment, Sandstone Investment is worth $1000
#ex. cont. $100 / $1000 = .10 (10%) John owns 10% of Sandstone Investment's Value
return (float(self.total_share_value()) / float(self.owner.current_value()))
####### Club Models #######
class Club(models.Model):
'''A Stock Club
A club has members (MemberInstance) and buys Stocks (StockInstance).
A note on the real-life purpose of stock clubs: Small-tim individual investors often do not have the
buying power to make powerful stock purchases. A single individual may not be able to buy 50 shares
of a stock priced at $500 each. This individual joins a stock club, possibly with friends, family, or co-workers.
The stock club has a number of shares that each member owns some of. The stock CLUB may own shares of many different
STOCKS, but the club only has ONE stock price--its own.'''
name = models.CharField(max_length=200)
created_at = models.DateTimeField(auto_now_add = True)
updated_at = models.DateTimeField(auto_now = True)
cash = models.DecimalField(max_digits=20, decimal_places=2)
def total_shares(self):
#The number of shares of the club that exist
#ex. John has 6 shares of the club; Bob has 4 shares of the club
#ex. cont. The club has (6+4=9) 10 total shares among its members.
shares = 0
for member in self.memberinstance_set.select_related():
shares = shares + member.shares
return shares
def current_value(self):
#The current value of the club
#The current value of each stock instance plus the club's uninvested cash
#ex. $200 from AAPL StockInstance + $400 from GOOG Instance + $20 cash = $620
value = 0
for stock in self.stockinstance_set.select_related():
if stock.is_open == True:
value = value + stock.current_value()
else:
pass
return (self.cash + value)
def current_price(self):
#The club's current share price
#The current value of the club divided by the total number of shares of the club
#ex. $620 Club Current Value / 10 Total Shares = $62 per share
return (self.current_value() / self.total_shares())
def cash_total_percentage(self):
#Percent of club's current value that is uninvested cash
return ((self.cash) / (self.current_value()))
def __unicode__(self):
return self.name
A cursory glance at your models suggests that you've got a mostly good design. A few mentions though.
MemberInstance (and other 'middle-man models') are usually named ClubMember, a name describing both sides of the relationship.
Your 'middle-man models' are examples of ManyToMany Models. You should define ManyToManyField on your models, such as members = ManyToManyField(Member, through='ClubMember') on your Club model.
I'll leave PEP-8 style comments alone as that doesn't seem to be the point of your question, however you should consider doc-strings in your methods instead of multiple single line comments to improve command line docs and introspection.
Also note that select_related should take in the fields that you want django to follow the relations on (as of django 1.5).
I'd also move your Club model higher up in the file so you don't need to reference it via 'Club' and can instead just pass the class name.
Have you read the coding styles section in the django docs?
https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/coding-style/#model-style

Categories

Resources