Assignment 3 - The Wolf of Dryden Street

CS 1110 Spring 2019

Due to CMS by Wednesday, March 6, 2019 @ 11:59pm


Overview

Computer Science is playing an increasingly important role in our daily lives. FinTech and High-Frequency Trading are rapidly expanding fields, with companies such as Goldman Sachs, Bank of America, JP Morgan, Jane Street, and Two Sigma hiring thousands of computer scientists to facilitate the trillions of dollars in trades every day. In this assignment you will be making a simple stock market game. You will be able to start your own investment firm and gain capital through loans and interest. Take risks by investing in different stocks and bitcoin in (almost) real time. You may be able to do better than Cornell’s endowment! But be careful; greed may rob you of your riches.

Learning Objectives

This assignment is designed to give you practice with the following skills:

  • It introduces you to the notion of attribute invariants.
  • It gives you practice in writing complex functions with conditionals.
  • It demonstrates a complex Python application that spans multiple modules.
  • It introduces you to FinTech, a rapidly expanding industry.
  • It introduces you to financial concepts, such as loans, tax rates, and stocks.


Before You Get Started

Academic Integrity

With the exception of your CMS-registered partner, we ask that you do not look at anyone else's code, seek solutions online or from students of past offerings of this course, or show your code to anyone else (except a CS1110 staff member) in any form whatsoever. This is in keeping with the the Academic Integrity Policy for CS 1110. It is okay to post error messages on Piazza, but not code. If we need to see your code, we will do that in person.

Partner Policy

You may do this assignment with one other person. Form your group on CMS as soon as possible. This can only be done after the assignment is released on CMS, but it must be completed before you submit the assignment. Both people must do something to form the group. The first person proposes, and then the other accepts. CMS does not allow you to form groups once grades are released. Once you have grouped on CMS, only one person needs to submit the files.

If you do this assignment with a partner, you must work together. It is against the rules for one person to do some programming on this assignment without the other person sitting nearby and helping. Furthermore, this assignment is not designed for you to split the work cleanly between partners. Sit together and take turns "driving" (using the keyboard and mouse) and "navigating" (guiding the code that's being typed, spotting errors, keeping track of what's done and what's still on your todo list).


Development Environment

To do this assignment, Python must be set up properly. If you have not already done this, follow the installation instructions to set it up on your computer. Alternatively, you can just work in the ACCEL lab.

You should also create a folder on your hard drive that is dedicated to this assignment and this assignment only. Every time that you work on a new assignment, we want you to make a new folder, to keep things organized and avoid problems with naming collisions. Make sure that the command shell and Atom Editor are both open in the current folder before you start.

Assignment Help

If you do not know where to start, if you do not understand testing, or if you are completely lost, please see someone immediately. This can be the course instructor, a TA, or a consultant. Do not wait until the last minute. A little in-person help can do wonders. See the staff page for more information.


The Code Base

Download the python files

a3_files.zip
Feb 28, 2019: we added more tests to test4() in a3test.py and we improved the spec for sell_stock in a3.py (changes are shown here). You can either download the entire zip again, or you can download just the files by clicking on the names in the previous sentence. If you've already started working on the assignment, you may want to just paste the new spec into your current a3.py so you don't accidentally overwrite your work.
March 2, 2019: we swapped the arguments to all the introcs.assert... functions to be consistent with A1. Namely: introcs.assert_equals(expected, received). Note: this does not change the behavior of the test file becuase if x==y then y===x. But if it bothers you, feel free to download a new version.

Inside here you will find the following 5 files:

  • a3.py: this file contains the functions for the assignment. This is the file you submit to CMS.
  • a3app.py: a console application for a stock market game (try it out on the command line: python a3app.py)
  • a3assets.py: helper classes for Assignment 3 (see below)
  • a3helpers.py: a few more helper functions for Assignment 3 (see further below)
  • a3test.py: unit tests for the 4 parts of the assignment (see even further below). You should add more tests here, but you will not be turning this in.

The a3assets Classes:

The a3assets module provides the classes that you will be working with in this assignment. It provides 3 different classes: Portfolio, Loan, and Stock. Do not worry about the inner workings of this file. Your final solution should make no code changes to the a3assets.py file. You do not even have to look at the file to complete the assignment.

A Note About Invariants:

All of the objects in this assignment have attribute invariants. Recall that attributes are variables that live inside objects. An attribute invariant is a property of an attribute that cannot be violated. For example, you cannot have a negative amount of cash in your account. The code we provide you enforces these invariants with assert statements. Attempting to violate an invariant will cause an error message to be printed and then Python will crash. For example, you should not write code that potentially brings someone's account balance below zero. If you do, an assertion will fail and Python will crash. If this happens it is an indicator that your code needs to take more care not to violate the attribute invariants of the provided classes.

The Portfolio Class:

The class Portfolio is the type of object that represents a person's account, including all investments and cash balances. It has four attributes as follows.

Attribute Example Value Description Invariants
cash 100.0 A float representing how much cash is in the account. In this case, the portfolio has $100.00 of cash non-negative float
commission_fee 10.0 A float that represents how much it costs for certain transactions to occur. In this case, the portfolio will incur a fee of $10.00 for certain transactions. non-negative float
loan_rate 0.1 A float that represents how much interest will be owed when taking out a loan. In this case, the portfolio faces 10% interest when taking out a loan. non-negative float
coins 10 An int representing how many bitcoins are owned by the player. In this case the player owns 10 bitcoins. non-negative int



Interacting with a Portfolio object:

The Portfolio constructor function takes one argument, assigning this value to the attribute in cash. For example, to create a Portfolio object representing a Portfolio with an initial investment of $100.00 we would use: my_portfolio = a3assets.Portfolio(100.0)

Portfolio’s attributes:

Accessing an object’s attributes is straight forward.

As a reminder, the format for accessing an attribute is ⟨variable⟩.⟨attribute⟩

For example, if you wanted to access the amount of cash of the Portfolio object we just created in the previous step, you would use: my_portfolio.cash

Furthermore, if you wanted to modify the amount of cash and set it equal to $100.00, you would use: my_portfolio.cash = 100.0

For more information, see the Feb 12 lecture on objects.

The Loan Class:

The class Loan is the type of object that represents a person's loan. A loan is paid in even increments for a set length of time until the balance is paid off. If a loan payment cannot be paid, a late fee is incurred. It has three attributes.



Attribute Example Value Description Invariants
balance 100.0 A float representing how much cash is owed still. In this case, $100.00 is owed on this loan. non-negative float
length 10 An int representing how many months the loan still has to be paid out for. In this case, the player has 10 months left on the loan. non-negative int
late_fee 10.0 A float that represents how much the user will incur when payment can not be made. In this case, the player faces a $10.00 late fee. non-negative float


The Loan constructor function takes two arguments, assigning these values to the balance and length attributes respectively. For example, to create a Loan object representing a loan with $100.00 balance and 10 months to pay we would use: my_loan = a3assets.Loan(100.0, 10)

Note: In this case you would owe $10.00 every period for 10 periods or you will incur a penalty equal to my_loan.late_fee.

If you wanted to access or modify the balance of this newly created object you could use: my_loan.balance



The Stock Class:

A single share of stock represents a small ownership of a public corporation. Companies give away stocks in order to raise investments, and in return owners can later sell the stock for a potentially higher value and also get dividends.

A Stock object represents one purchase of the stock of a particular company at a particular time. For simplicity, if one purchases the same company's stock again, this would be represented by another Stock object.

The Stock object has four attributes.



Attribute Example Value Description Invariants
company "LUV" The ticker symbol of the company this Stock belongs to. In this case, the ticker, LUV, belongs to Southwest Airlines. non-empty str
buy_price 56.37 The value of a share of this Stock at the time of purchase. In this case the stock was purchased for a price of $56.37 per share. non-negative float
shares 1 The amount of shares of the company this Stock represents are owned. In this case 1 share is owned. non-negative int
short True Whether or not the Stock is shorted. True if shorted. In this case the stock is shorted. bool


The Stock constructor function takes four arguments, assigning these values to the company, buy_price, shares, and short attributes respectively.

For example, to create a Stock object representing a purchase of one share of "LUV" stock for $56.37, which is not shorted, I would write: my_stock = a3assets.Stock("LUV", 56.87, 1, False)

If you wanted to access or modify the buy_price of this newly created object you could use: my_stock.buy_price



a3helpers

a3helpers.py includes two helper functions that will help you in this assignment. You will not have to modify any of these helper functions.

The helpers are:

def get_stock_price(stock):
	"""
	Returns: current price of ‘stock’ as a float

	If key is "TEST", this evaluates to a constant value used for testing

	Parameter: stock the trading symbol for a company's stock
	Precondition: stock is a string
	"""
							

In order to get the current stock price of Apple you would make a call like this current_price = a3helpers.get_stock_price("APPL")



def get_bitcoin_price():
	"""
	Returns: current price of Bitcoin as a float

	If key is "TEST", this evaluates to a constant value used for testing
	"""
						

In order to get the current price of bitcoin you would make a call like this current_price = a3helpers.get_bitcoin_price()

a3test.py

The a3test module can be run on the command line (python a3test.py); it invokes several procedures that test the various functions in a3.py. These tests are not complete, but passing them should get you at least 50% on the assignment.



Assignment Instructions

Below, we outline the functions that you are expected to write for this assignment. You should write and test your functions, one at a time, in the order given. Read the text below, but also make sure you carefully read the specifications and comments in the provided code. In this assignment, sometimes we give hints in the specs/comments about how best to write your functions.

Part 1: Getting started on the Ground Floor

A good place to start is opening a portfolio. Your first task is to write a function that creates a Portfolio object as specified in the function definition.

def open_portfolio(to_invest, fee):
	"""
	Returns: a Portfolio with cash equal to the amount invested minus
	the enrollment fee; if the enrollment fee is larger than the investment,
	the function returns None instead.

	Parameter to_invest: the amount of money to be invested in the account
	Precondition: to_invest is a non-negative float

	Parameter fee: the enrollment fee for opening a new portfolio
	Precondition: fee is a non-negative float
	"""


Part 2: The Fad that Did Not Last

A little over a year ago, investing in cryptocurrencies seemed like a great way to get rich quickly. This function purchases a certain amount of bitcoins and adds them to the user’s portfolio. A single commission fee is charged for this transaction if the transaction is successful. Note: this transaction will be void if it causes the portfolio’s cash balance to be negative, or if amount is 0. Now implement the function as specified in the spec.

def buy_bitcoin(portfolio, amount):
	"""
	Returns: a bool; True if the investment was successful, False otherwise.
	Attempts to purchase `amount` Bitcoins.
	If the cost of the transaction (the value of the coins + the portfolio's
	commission fee) is greater than the amount of cash it has on hand,
	or 'amount' is equal to zero, this transaction fails. 
	Otherwise, the portfolio receives `amount` Bitcoins
	and has its cash on hand is decreased by the cost of the transaction.

	Parameter portfolio: the portfolio attempting to buy Bitcoin
	Precondition: portfolio is a Portfolio object

	Parameter amount: the number of Bitcoins to be purchased
	Precondition: amount is a positive int
	"""



Perhaps bitcoin is not all that it is cracked up to be. You may want to sell your investment. This function sells a certain amount of Bitcoins (or the total amount of Bitcoins owned, whichever is less) and subtracts them from the portfolio. A single commission fee is charged for this transaction if the transaction is successful. Note: this transaction will be void if it causes the portfolio’s cash balance to be negative, or if amount is 0. This transaction is not subject to taxes. Now implement the function as specified in the spec.

def sell_bitcoin(portfolio, amount):
	"""
	Returns: a bool; True if the transaction was successful, False Otherwise.
	Attempts to sell `amount` Bitcoins (if the account has less than that,
	then as many as the account has).
	The profit earned from this transaction is the value of the coins
	minus the portfolio's commission fee. If selling would cause a negative
	cash balance, or 'amount' is equal to zero, this transaction fails. 
	Otherwise, the transaction is successful, the profit is added to 
	the portfolio's cash balanceand the portfolio's Bitcoin balance is decreased. 
	This transaction is not subject to taxes.

	Parameter portfolio: the portfolio attempting to sell Bitcoin
	Precondition: portfolio is a Portfolio object

	Parameter amount: the amount of Bitcoins to be sold
	Precondition: amount is a positive int
	"""


Part 3: Money, Money, Money

Need to raise money? Take out a loan. But be careful -- while you will get an immediate cash infusion, you have to pay back the loan plus interest. Additionally, the more loans you take out, the higher your interest rate will be. Now implement the function as specified in the spec.

def take_loan(portfolio, amount, length):
	"""
	Returns: a Loan object, or None if the loan is unsuccessful.
	If the portfolio's loan rate is greater than .2, the portfolio is
	considered too risky; the loan is denied and None is returned.
	Otherwise, the portfolio receives additional cash equal to the amount of
	money in the loan. A Loan object is created with a balance equal to
	the amount of money loaned plus interest according to the portfolio's loan
	rate. After this the portfolio's loan rate is increased by .01 to account
        for increased risk.

	Taxes do not have to be paid on loans and there is no commission
	fee on this transaction.

	Parameter portfolio: the portfolio requesting the loan
	Precondition: portfolio is a Portfolio object

	Parameter amount: the amount of money requested in the loan
	Precondition: amount is a non-negative float

	Parameter length: the length in months of the Loan
	Precondition: length is a positive int
	"""
					



The loan sharks are here! Every month you will have to pay a certain amount of your loan back. You are expected to pay a monthly rate equal to the balance evenly divided by the months left on the loan. For example, if you had $10,000 left on your balance and 10 months left to pay, this function would attempt to pay $1,000. If you can not make the payments, you will incur a late fee which will be added to your loan’s balance, and the loan’s balance and length will stay the same. When your loan’s balance hits 0, your portfolio's loan rate will decrease by .01, as you now have a less risky account.

def pay_loan(portfolio, loan):
	"""
	Returns: a bool; True if the payment was successful, False otherwise.
	Attempts to pay off the loan by the required monthly amount.
	If there is not enough money to pay off the monthly amount, the balance of
	the loan increases by its late_fee and the portfolio's balance remains the
	same. Otherwise, the loan amount decreases by the amount paid (with money
	from the portfolio) and the loan length is decreased by one. If the length
	of a loan is 0,it is fully paid off and the portfolio's loan_rate is
	decreased by 0.01.
	The required monthly payment is the loan's balance / the loan's remaining length.

	Parameter portfolio: the portfolio paying off its loan
	Precondition: portfolio is a Portfolio object

	Parameter loan: the loan to be paid off
	Precondition: loan is a Loan object
	"""
					

Part 4: The Oracle of Ithaca

Taxes are complicated, so it would be nice to have a helper function to help you pay your taxes for two of the following functions. We will use a highly simplified version of the US Tax Code. We will only worry about income tax, not capital gains tax. This uses something called a progressive tax rate in which income is broken up into brackets.

For example, if you made $50,000 last year, your income will be broken up into two brackets. The first $10,000 you made and then the last $40,000 you make. In our application, the first $10,000 will be taxed at a marginal tax rate of 10% while the next $40,000 will be taxed at a marginal tax rate of 20%. Thus, you will pay $1,000 + $8,000 = $9,000 in taxes. Your function should thus return $41,000.

In our application the marginal tax rates are as follows:



I make... My marginal tax rate is... What I pay in taxes
$0 - $10,000 10% $0 + 10% on all income
$10,000.01 -> $100,000 20% $1,000 + 20% on all income over $10,000.00
$100,000.01 -> $1,000,000 30% $19,000 + 30% on all income over $100,000.00
$1,000,000.01 -> $10,000,000 40% $289,000+ 40% on all income over $1,000,000.00
$10,000,000+ 70% $3,889,000 + 70% on all income over $10,000,000.00


Now implement the function according to the function, returning the total profit. Note that only the profit is taxed, not any losses. Keep in mind that you are returning the profit after taxes, not the amount paid in taxes.

Hint: Using if, elif, and else statements will make this function easier.

For more information on how marginal tax rates work see this video.

def  calculate_profit_after_taxes(profit):
	"""
	Returns: a float representing net profit after taxes are applied
	If the profit is negative, then there are no taxes on the profit.
	The tax rates utilized for the tax calculations are specified in
	the assignment write-up.

	Parameter profit: the amount of profit earned prior to paying taxes
	Precondition: profit is a float
	"""
				



Now is the time to get on the stock market. You wish to buy a certain amount of shares in company ‘stock’ at a certain time. You will pay for this transaction with cash on hand. The cost of the transaction is equal to the value of the shares plus a commission fee. This transaction will fail under the following circumstances:

  1. The player does not have enough cash on hand to handle the transaction.
  2. amount_shares is equal to zero.

You may also want to short a stock. This is the equivalent of betting a stock will go down rather than up. In our simplified version, this works by flipping the buy price and the selling price of a stock. For this function you only have to set the right parameters for the constructor. Now implement this function based on the write up.

def buy_stock(portfolio, stock, amount_shares, short):
	"""
	Returns: a Stock object, or None if the stock purchase is unsuccessful.
	Attempts to purchase `amount_shares` shares of `stock`.
	The cost of the transaction is calculated as:
	the stock's value * the number of shares + the portfolio's commission_fee
	If the cost of the transaction (the value of the coins + the portfolio's
	commission fee) is greater than the amount of cash it has on hand,
	or 'amount_shares' is equal to zero, the transaction fails. 
	Otherwise, the portfolio has its cash on hand
	decreased by the cost of the transaction and the Stock with `amount_shares`
	shares is returned.

	Parameter portfolio: the portfolio attempting to purchase a stock
	Precondition: portfolio is a Portfolio object

	Parameter stock: the stock trading symbol of the company
	Precondition: stock is a string

	Parameter amount_shares: the amount of shares to be purchased
	Precondition: amount_shares is a positive int

	Parameter short: whether or not stock is "shorted" (see later description)
	Precondition: short is a bool
	"""
					



When a company makes a profit, it usually pays its shareholders a small portion of the profit. If the Stock object belongs to the ‘company’ that is giving away dividends, you get one payment per share held in the corporation. You will then have to pay income tax on this profit. There is no commission fee on this transaction. Now implement this function based on the write up.

def pay_dividends(portfolio, stock, company, payments):
	"""
	Returns: a bool; True if the transaction was successful, False otherwise.
	Attempts to pay dividends to `company`s investors.
	If the stock owned by the portfolio does not match the company paying dividends,
	this operation is unsuccessful. Otherwise, the amount of profit gained
	after paying income taxes on it is deposited into the user's portfolio.

	Parameter portfolio: the portfolio being evaluated for dividends
	Precondition: portfolio is a Portfolio object

	Parameter stock: the portfolio's owned Stock
	Precondition: stock is a Stock object

	Parameter company: the trading symbol of the company that is paying dividends
	Precondition: company is a string

	Parameter payment: the amount of money paid out for each share owned
	Precondition: payment is a non-negative float
	"""
					



Your final task! Time to sell some stock. Now implement this function based on the write up.

def sell_stock(portfolio, amount_shares, stock):
	"""
    Returns: a bool; True if the transaction was successful, False otherwise.
    Attempts to sell `amount_shares` of `stock` (if the account has
    less than that, then as many as the account has).
    
    This function does many steps.

    First, it calculates the net income. 
    Net Income will be calculated as such, 
    If the stock was shorted, the Net Income is:
    Net-Income =`amount_shares`*(the original buy price - the current selling price) 
    Otherwise, the Net Income is:
    Net-Income =`amount_shares`*(the current selling price - the original buy price)
    
    Second, the Net-Income should be taxed.
 
    Third, a certain amount of money should be transferred back to the owner.
    We transfer back the (Post-Tax) Net-Income back into our balance and also transfer 
    back an amount equal to (`amount_shares`*the original buy price) minus a commission fee

    Fourth, we decrease the amount of shares in the stock object. 

    A transaction may only succeed if completing the transaction would not
    make the portfolio's cash on hand negative. This transaction also fails 
    if 'amount_shares' is  equal to zero. If a transaction fails, 
    there is no transfer of funds or shares. 

    Parameter portfolio: the portfolio attempting to sell stocks
    Precondition: portfolio is a Portfolio object

    amount_shares:  how many shares of the stock to sell
    Precondition: amount_shares is a positive int

    stock: the stock to be sold
    Precondition: stock is a Stock object
	"""
					

Testing

You should be testing your functions as you write them. Use the unit tests that we provided in a3test.py, but remember that they are not complete and passing all of them does not guarantee that you will get a 100% on the assignment. To improve the quality of the code you turn in (and consequenlty your score), write more tests. Convince yourself that your solution meets all of the assignment specifications.


Appendix: Fun stuff

You may want to trade the stock in real time…

To do so you will need an API KEY.

How to use an API in real time….

  1. Go to alphavantage
  2. Fill out the short form:

  3. Click "GET FREE API KEY"
  4. You will get to see a key that looks something like this:

  5. Copy the access key
  6. Replace "Test" with that access key on line 10 in your a3helpers.py.
  7. Have fun getting real time trading data. You can play around with this by running a3app.py