1

I'm new to Python, trying to understand OOP. In my program I want the user to be able to buy and sell stocks but I'm struggling to implement this feature. Sorry if the problem is trivial.

User class + its one object

class User:
    def __init__(self, name, budget=None, stocks=None):
        self.name = name
        self.budget = budget or 1000 
        self.stocks = stocks or 0

    def sell_stock(self):
        if self.stocks != 0:
            self.stocks -= 1

    def buy_stock(self):
        self.stocks += 1


u1 = User("Karin", stocks=9)

Stock class + its one object

class Stock:
    def __init__(self, price, name, availability=None):
        self.price = price
        self.name = name
        self.availability = availability or 1


s1 = Stock("200", "Netflix")

I want to write a method called buy_stock() that will do the following:

  • u1.budget - s1.price
  • u1.stocks += 1
  • s1.availability -= 1
  • will show the price and the name of the stock that the user has bought, therefore I will see a message f"{Karin} has bought {Netflix} stock for {200} dollars."
2
  • 1
    I would think that a User has a portfolio of stocks, not just a single number. You might need to use a collection (like a dict or list) to store each different type (using the name of the stock as a uniquely identifying key) and the amount the user owns. Commented Jul 6, 2019 at 16:07
  • 1
    Agreed with Casey. There are improvements you can make to this model, and a dictionary of stock objects is a great idea. Commented Jul 6, 2019 at 16:12

6 Answers 6

2
class Stock:
    def __init__(self, price, name, availability=1):
        self.price = price
        self.name = name
        self.availability = availability

class User:
    def __init__(self, name, budget=1000,):
        self.name = name
        self.budget = budget 
        self.stocks = []

    def sell_stock(self, stock):
        try:
            self.stocks.remove(stock)
            stock.availability += 1
            self.budget += stock.price
            print('{} has sold {} stock for {} dollars'.format(self.name, stock.name, stock.price))

        except:
          pass

    def buy_stock(self, stock):
        if self.budget - stock.price >= 0 and stock.availability >= 1:
            self.stocks.append(stock)
            stock.availability -= 1
            self.budget -= stock.price
            print('{} has bought {} stock for {} dollars'.format(self.name, stock.name, stock.price))

s1 = Stock(200, "Netflix")
s2 = Stock(300, "SomeStock", availability=2)

u1 = User("Karin", budget=10000)
u1.buy_stock(s2)
u1.sell_stock(s2)

u2 = User("Sam")
u2.buy_stock(s2)
u2.buy_stock(s1) 

output:

Karin has bought SomeStock stock for 300 dollars
Karin has sold SomeStock stock for 300 dollars
Sam has bought SomeStock stock for 300 dollars
Sam has bought Netflix stock for 200 dollars

When you buy item you have to ensure it is available and you have the budget for it. I removed the stock paramater from the constructor to avoid repaitation, and to have only one source for the logic buy_stock. And one final note: you do not need the or keyward as you can set a default values in the constructor.

Sign up to request clarification or add additional context in comments.

Comments

1

Welcome to the world of OOP :)

What you’re trying to do is fairly simple. Since you’re trying to work with two classes at once, that’s a signal that one of those classes should be used as the argument in the function. I.e.

class Stock:
     # .... basic init function

     # we need a function to return the value
     # of this stock, and maybe take an argument
     # for how many shares we want to value
     # so let’s write a function to do that for us

     def get_value(self, number_of_shares=1):
         return (self.value * number_of_shares)

class User:
     #.... basic init function

     def buy_stock(self, stock, amount=1):
          total_value = stock.get_value(amount)
          self.budget = self.budget - total_value
          self.stocks = self.stocks + amount

          #display what happened
          print(“{} bought {} shares of {} for {}”.format(self.name, amount, stock.name, total_value))

Then in practice you can write

Input:

# assuming Andrew is a user
# and my_stock is a stock worth $20 a share
Andrew.buy_stock(my_stock, 10)

Output:

Andrew bought 10 shares of my_stock for $200

But basically, the answer to your question is to pass in an argument that you expect to be the class you want to work with. So you can manipulate both the User object calling the method, and the stock Object being passed into the method.

Comments

1

I think this should do it. Also, change the data type of your stock price from string to int (or do type casting afterwards)

class User:
    def __init__(self, name, budget=None, stocks=None):
        self.name = name
        self.budget = budget or 1000 
        self.stocks = stocks or 0

    def sell_stock(self):
        if self.stocks != 0:
            self.stocks -= 1

    def buy_stock(self, stock):
        self.budget - stock.price
        stock.availability -= 1
        self.stocks += 1
        print("{} has bought {} stock for {} dollars".format(self.name,stock.name,stock.price))

class Stock:
    def __init__(self, price, name, availability=None):
        self.price = price
        self.name = name
        self.availability = availability or 1


s1 = Stock(200, "Netflix")

u1 = User("Karin", stocks=9)

u1.buy_stock(s1)

Comments

1

To think of it in a more real-life scenario, you would have a Marketplace object that holds all the stocks other users can purchase stocks from.
That way, it will be fairly readable and scalable down the road for future development.

The Marketplace can be a separate class, a dictionary that holds other stock objects (like in the example below), or a class that is a connector to your database (like mongodb).
Which one you choose depends on the project.

For this example, a dictionary is nice and elegant solution to get you going:

class User:
    def __init__(self, name, budget=None, stocks=None):
        self.name = name
        self.budget = budget or 1000
        self.stocks = stocks or 0

    def __repr__(self):
        # returns a represantion of the object
        # so it's more informative of the state
        # of this object
        return "{} balance: {}".format(
            self.name,
            self.budget
        )

    def sells(self, stock_name, amount):
        # Increase my budget by the amount of stocks I'm selling 
        # multiplied by its price.
        self.budget += marketplace[stock_name].price * amount

        # Send the stocks back into the market and remove them
        # from my ownership
        marketplace[stock_name].availability += amount
        self.stocks -= amount


    def buys(self, stock, amount):
        # Lower my budget by the stock price 
        # multiplied by the amount of stock I'm buying

        marketplace[stock].availability -= amount
        self.budget -= marketplace[stock].price * amount

        # Print out the transaction
        print("{} has bought {} stock for {} dollars".format(
            self.name,
            stock,
            marketplace[stock].price * amount
        ))


class Stock:
    def __init__(self, price, name, availability=None):
        self.price = price
        self.name = name
        self.availability = availability or 1

# In production like environmnet, you would not do this but would keep in
# mongodb or some other sql/nosql database
# For this example, a kind of javascript dict logic would be alright to use
marketplace = {
    "Netflix": Stock(200, "Netflix", 100)
}

u1 = User("Karin", budget=1000, stocks=0)

u1.buys("Netflix", 10)
u1.sells("Netflix", 5)

1 Comment

Thank you for your solution. This very much aligns with my initial logic. I wanted to create class Market that will be inheriting from both User and Stock so that I can manipulate all local variables but I was not sure if that was a sensible move to yet create another class
1

Something like this maybe (I didn't really mess around with the "stocks" instance variable in the "User" class. I would maybe ditch that and maintain a list of Stock objects instead (your stock count would just be the length of that list)):

class User:

    def __init__(self, name, budget=1000, stocks=0):
        self.name = name
        self.budget = budget
        self.stocks = stocks

    def sell_stock(self):
        if self.stocks:
            self.stocks -= 1

    def buy_stock(self, stock, quantity=1):
        try:
            price = stock.request(quantity)
        except RuntimeError as error:
            raise error
        else:
            self.budget -= price
            self.stocks += quantity
            print(f"{self.name} bought ${price} worth of {stock.name} stocks")

class Stock:

    def __init__(self, name, price, quantity_available=1):
        self.name = name
        self.price = price
        self.quantity_available = quantity_available

    def isAvailable(self):
        return self.quantity_available > 0

    def request(self, quantity_requested):
        if not self.isAvailable():
            raise RuntimeError(f"No more {self.name} stocks available")
        elif self.quantity_available < quantity_requested:
            raise RuntimeError(f"Requested too many {self.name} stocks")
        else:
            self.quantity_available -= quantity_requested
            return self.price * quantity_requested

def main():

    user = User("Karin")

    stock = Stock("Netflix", 200, quantity_available=6)

    user.buy_stock(stock, quantity=3)
    user.buy_stock(stock, quantity=2)
    user.buy_stock(stock, quantity=1)

    user.buy_stock(stock, quantity=1)

    return 0


if __name__ == "__main__":
    import sys
    sys.exit(main())

Output:

Karin bought $600 worth of Netflix stocks
Karin bought $400 worth of Netflix stocks
Karin bought $200 worth of Netflix stocks
RuntimeError: No more Netflix stocks available

Comments

1

You need 2 types of objects: Portfolio, and Stock.
A User can have several Portfolio, and in the example is represented by its name only.

For a more elaborate model, you will want to model Transactions as objects too; You will also need to handle variations in the price of the stocks, commissions, and other costs.

Here is a simplified example that demonstrates how the objects interact with each other:

class Stock:

    def __init__(self, ticker, price):
        assert price > 0
        self.ticker = ticker
        self.price = price

    def __hash__(self):
        return hash(self.ticker)

    def __str__(self):
        return self.ticker + ', $' + str(self.price) + ':'


class Portfolio:

    def __init__(self, owner):
        self.owner = owner
        self.cash = 0
        self.stocks = {}   # a mapping of Stock --> quantity

    def buy(self, stock, quantity):
        if self.cash < stock.price * quantity:
            print('Not enough cash to purchase ', quantity, stock)
        else:
            self.cash -= stock.price * quantity
            try:
                self.stocks[stock] += quantity
            except KeyError:
                self.stocks[stock] = quantity

    def sell(self, stock, quantity):
        assert quantity > 0
        try:
            if self.stocks[stock] < quantity:
                print('Not enough', stock.ticker, 'inventory to sell', str(quantity), stock)
                return
            self.stocks[stock] -= quantity * stock.price
            self.cash += quantity * stock.price
        except KeyError:
            print('No', stock.ticker, 'inventory to sell')

    def __str__(self):
        res = [self.owner, "'s Portfolio:\n"]
        for stock, quantity in self.stocks.items():
            res += [str(stock), ' ', str(quantity), ' -> ', '$', str(quantity*stock.price), '\n']
        res += ['cash: ', '$', str(self.cash), '\n']
        return ''.join(res)

goog = Stock('GOOG', 325)
alibaba = Stock('ALI', 12)
apple = Stock('AAPL', 42)

pfl = Portfolio('Karin')
pfl.cash = 10000

pfl.buy(goog, 10)
pfl.buy(alibaba, 100)
pfl.sell(apple, 100)
pfl.buy(apple, 10000)
pfl.sell(goog, 10000)

print()
print(pfl)

output:

No AAPL inventory to sell
Not enough cash to purchase  10000 AAPL, $42:
Not enough GOOG inventory to sell 10000 GOOG, $325:

Karin's Portfolio:
GOOG, $325: 10 -> $3250
ALI, $12: 100 -> $1200
cash: $5550

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.