Back
Learning Hub/ Python for Algo Trading/ Lesson 7 Phase 1 of 6
Python for Algo Trading · Phase 1

OOP — Classes & Objects

Professional algo systems aren't a collection of loose functions — they're built from classes. Build a Trade class, a RiskManager class, and finally understand how production trading engines are architectured.

Lesson 7 of 9 ~50 min Intermediate–Advanced Trade & Strategy classes
Lesson 7 of 9 — OOP: Classes & Objects78% complete
LESSON 07 · SECTION 1
Why OOP for Trading?
advanced

A class is a blueprint that bundles together data (attributes) and behaviour (methods). Instead of passing a trade dict to dozens of functions, a Trade object knows its own entry, SL, target — and can compute its own P&L.

🏗️
Class = Blueprint
Defines what an object looks like. Trade is the class; my_trade is an instance.
🧠
Attributes = Data
Variables stored inside an object: self.symbol, self.entry, self.pnl.
⚙️
Methods = Behaviour
Functions inside the class: trade.update_ltp(22450), trade.is_sl_hit().
🔧
__init__
The constructor — called automatically when you create an object with Trade(...).

Section 2
Your First Class — Trade
trade_class.pypython
class Trade:
    """Represents a single live or closed trade."""

    def __init__(self, symbol, side, entry, lot_size, lots, sl_pts=150, tgt_pts=300):
        self.symbol    = symbol
        self.side      = side
        self.entry     = entry
        self.lot_size  = lot_size
        self.lots      = lots
        self.sl        = entry - sl_pts  if side == "BUY" else entry + sl_pts
        self.target    = entry + tgt_pts if side == "BUY" else entry - tgt_pts
        self.ltp       = entry
        self.status    = "OPEN"

    def update_ltp(self, ltp):
        """Update live price and check exit conditions."""
        self.ltp = ltp
        if self.side == "BUY":
            if   ltp <= self.sl:     self.status = "SL_HIT"
            elif ltp >= self.target: self.status = "TARGET_HIT"

    def pnl(self):
        """Unrealised or realised P&L."""
        mul = 1 if self.side == "BUY" else -1
        return (self.ltp - self.entry) * self.lot_size * self.lots * mul

    def summary(self):
        print(f"{'='*40}")
        print(f"Symbol : {self.symbol} ({self.side})")
        print(f"Entry  : ₹{self.entry}  |  LTP: ₹{self.ltp}")
        print(f"SL/TGT : ₹{self.sl} / ₹{self.target}")
        print(f"P&L    : ₹{self.pnl():+,}")
        print(f"Status : {self.status}")
        print(f"{'='*40}")

# Create a trade object
t = Trade("NIFTY25JUNFUT", "BUY", entry=22300, lot_size=50, lots=2)
t.update_ltp(22480)
t.summary()
Output
========================================
Symbol : NIFTY25JUNFUT (BUY)
Entry  : ₹22300  |  LTP: ₹22480
SL/TGT : ₹22150 / ₹22600
P&L    : ₹+18,000
Status : OPEN
========================================

Section 3
RiskManager Class

A RiskManager class manages daily P&L, trade counts, and kill-switch logic — completely separate from trade logic. This is good separation of concerns.

risk_manager.pypython
class RiskManager:
    def __init__(self, capital, daily_loss_limit_pct=2.0, max_trades=5):
        self.capital          = capital
        self.daily_loss_limit = -(capital * daily_loss_limit_pct / 100)
        self.max_trades       = max_trades
        self.daily_pnl        = 0
        self.trades_taken     = 0

    def record_trade(self, pnl):
        self.daily_pnl    += pnl
        self.trades_taken += 1

    def can_trade(self):
        if self.daily_pnl <= self.daily_loss_limit:
            return False, "KILL: Daily loss limit hit"
        if self.trades_taken >= self.max_trades:
            return False, "KILL: Max trades reached"
        return True, "OK"

    def status(self):
        ok, reason = self.can_trade()
        print(f"Daily P&L    : ₹{self.daily_pnl:+,}")
        print(f"Trades Taken : {self.trades_taken}/{self.max_trades}")
        print(f"Can Trade    : {'✅ YES' if ok else '🚨 NO — ' + reason}")

# Simulate a day
rm = RiskManager(capital=500000, daily_loss_limit_pct=2.0, max_trades=5)
rm.record_trade(12000)
rm.record_trade(-8000)
rm.record_trade(5500)
rm.status()
Output
Daily P&L    : ₹+9,500
Trades Taken : 3/5
Can Trade    : ✅ YES

Section 4
Inheritance — BaseStrategy

Inheritance lets one class extend another. A VWAPStrategy inherits all the common behaviour of a BaseStrategy and adds its own signal logic.

strategy_inheritance.pypython
class BaseStrategy:
    def __init__(self, symbol, lot_size):
        self.symbol   = symbol
        self.lot_size = lot_size
        self.trades   = []

    def log_trade(self, trade):
        self.trades.append(trade)
        print(f"Trade logged: {trade['side']} {trade['symbol']} @ ₹{trade['entry']}")

    def total_pnl(self):
        return sum(t.get("pnl", 0) for t in self.trades)


class VWAPStrategy(BaseStrategy):  # inherits from BaseStrategy
    def __init__(self, symbol, lot_size, sl_pts=150, tgt_pts=300):
        super().__init__(symbol, lot_size)  # call parent constructor
        self.sl_pts  = sl_pts
        self.tgt_pts = tgt_pts

    def get_signal(self, ltp, vwap, rsi):
        if ltp > vwap and rsi < 65: return "BUY"
        if ltp < vwap and rsi > 35: return "SELL"
        return "HOLD"

    def on_tick(self, ltp, vwap, rsi):
        signal = self.get_signal(ltp, vwap, rsi)
        if signal in ("BUY", "SELL"):
            trade = {"symbol":self.symbol, "side":signal, "entry":ltp, "pnl":0}
            self.log_trade(trade)
        else:
            print(f"Tick: LTP={ltp} — HOLD")

strat = VWAPStrategy("NIFTY25JUNFUT", lot_size=50)
strat.on_tick(22485, 22380, 53)
strat.on_tick(22410, 22450, 50)  # HOLD
Output
Trade logged: BUY NIFTY25JUNFUT @ ₹22485
Tick: LTP=22410 — HOLD
💡 PRO TIP
Real production frameworks like Zipline, Backtesting.py, and Zerodha's PyAlgomate all use this same inheritance pattern: a BaseStrategy with lifecycle methods (on_tick, on_candle, on_order_fill) that child classes override.

Section 5
Quick Quiz
QUESTION 1 OF 3
What is __init__ in a class?
AA method to destroy an object
BThe constructor — runs when an object is created
CA private variable
DThe main function
QUESTION 2 OF 3
In class VWAPStrategy(BaseStrategy), what does super().__init__() do?
ACreates a second instance of VWAPStrategy
BCalls the parent class (BaseStrategy) constructor
COverrides the parent constructor entirely
DImports the parent class
QUESTION 3 OF 3
In def update_ltp(self, ltp), what does self refer to?
AThe class definition
BA global variable
CThe current instance of the class
DThe parent class

Section 6
Practice Exercises
EXERCISE 1 · EASY
Candle Class
Create a Candle class with attributes: open, high, low, close, volume. Add methods: is_bullish() (returns True if close > open), body_size() (returns abs(close - open)), and wick_ratio().
Hint: wick = (high - low) - body_size()
EXERCISE 2 · MEDIUM
Position Manager
Create a PositionManager class that stores open trades as a dict keyed by symbol. Methods: open_trade(symbol, side, entry, lots), close_trade(symbol, exit_price), get_pnl(symbol), total_pnl().
Hint: store trade data in self.positions = {}.
EXERCISE 3 · CHALLENGE
Full Strategy Class
Build a complete NIFTYVWAPStrategy class with: __init__ (config), on_tick(ltp, vwap, rsi) (generates signal), on_fill(price) (records trade), and daily_report() (prints P&L summary and trade count).
Hint: use a list self.trade_log = [] to store all trades.
🚀 UP NEXT — LESSON 8
Your algo is almost production-ready. But what happens when the API is down, a price is missing, or a network error occurs? Lesson 8: Error Handling — learn try/except, custom exceptions, and how to build a system that never crashes silently.
Prev