Data and behaviour together
So far a position could be a tuple or a dictionary, with separate functions working on it. A class bundles the data and the functions that belong to it into one new type. class Position: defines the blueprint; calling it, Position("AAPL", 100, 180.0), builds an object (also called an instance) from that blueprint. Each object holds its own data, and the functions defined in the class, called methods, work on that object.
class Position:
def __init__(self, symbol, quantity=0):
self.symbol = symbol # attributes belong to this object
self.quantity = quantity
def describe(self):
return f"{self.quantity} x {self.symbol}"
apple = Position("AAPL", 100)
tesla = Position("TSLA")
print(apple.describe()) # 100 x AAPL
print(tesla.describe()) # 0 x TSLA
print(apple.quantity, tesla.quantity) # 100 0__init__ and self
__init__ is the constructor: Python runs it automatically when you create an object, and it stores the starting values. Every method receives the object itself as its first parameter, conventionally called self; you do not pass it, Python does (apple.describe() calls Position.describe(apple)). Anything saved as self.name = value is an attribute of that object and stays there between method calls. __init__ sets things up and returns nothing.
class Account:
def __init__(self, cash):
self.cash = cash
def deposit(self, amount):
self.cash += amount # changes this object's attribute
return self.cash
first = Account(1000)
second = Account(50)
first.deposit(500)
print(first.cash, second.cash) # 1500 50 (separate objects, separate data)
same = first # a second name for the same object
same.deposit(1)
print(first.cash) # 1501Methods that change state or answer questions
Some methods answer a question about the object without changing it (market_value(price)); they should just return a result and leave the attributes alone. Others change the object (apply_fill), updating attributes and often returning something useful such as the realised profit. A method can call another with self.other_method(...), which keeps rules in one place: if unrealized_pnl needs the market value, it asks self.market_value(price) rather than repeating the formula.
class Position:
def __init__(self, symbol, quantity=0, avg_price=0.0):
self.symbol = symbol
self.quantity = quantity
self.avg_price = avg_price
def market_value(self, price):
return self.quantity * price
def unrealized_pnl(self, price):
return self.market_value(price) - self.quantity * self.avg_price
pos = Position("AAPL", 100, 180.0)
print(pos.market_value(185.0)) # 18500.0
print(pos.unrealized_pnl(185.0)) # 500.0Pitfalls and when to use a class
Forgetting self. is the classic slip: quantity = 5 inside a method creates a throw-away local variable, not an attribute. Use CapWords for class names (Position) and lower-case with underscores for attributes and methods; a leading underscore (_cache) marks something as internal. Never use a mutable default such as def __init__(self, fills=[]): all objects would share that one list; use fills=None and create [] inside. Make a class when data and the rules about it travel together; a plain function or dictionary is often simpler.
class Wrong:
def __init__(self, quantity):
quantity = quantity # forgot self.: nothing is stored
class Shared:
def __init__(self, fills=[]): # one list shared by every object
self.fills = fills
class Right:
def __init__(self, fills=None):
self.fills = [] if fills is None else list(fills)
a, b = Shared(), Shared()
a.fills.append(100)
print(b.fills) # [100] (surprise!)
c, d = Right(), Right()
c.fills.append(100)
print(d.fills) # []
print(hasattr(Wrong(5), "quantity")) # Falseclass Name:defines a type;Name(args)builds an object and runs__init__.self.attr = valuestores data on the object; every method takesselffirst.- Query methods return answers; update methods change attributes; avoid mutable defaults.
Next up: special methods such as __repr__ and __eq__, which make your objects print nicely and compare as you expect.
Lab · what's graded
- Build a position with
__init__25 pts - Value the position with
market_value25 pts - Measure the paper profit with
unrealized_pnl25 pts - Update the position after a trade with
apply_fill25 pts