Based on the requirements and use cases, identify the main objects of the system and analyze how they interact and relate to each other...
Start by scanning the requirements for nouns. "Portfolio with holdings and cash balance" gives us Portfolio. "Orders with type, symbol, and direction" becomes Order. "Positions tracking quantity and cost basis" maps to Position. "Market data feed with live price updates" gives us MarketDataFeed and Stock. Not every noun becomes a class. "cash balance" is a BigDecimal field on Portfolio, not its own entity, but nouns give you the starting lineup.
Imagine a trader opening their portfolio app. They see their holdings (100 shares of AAPL at $150 average cost), current prices streaming live ($160 means $1000 unrealized profit), and pending orders (a limit sell at $170). A price update arrives: AAPL hits $170. The pending sell order triggers automatically, executes, and the realized P&L updates. Every noun in that scenario maps to a class.
Portfolio is the top-level container. It holds a map of Positions keyed by symbol, a trade history, a cash balance, and pending orders. It exposes placeOrder(), executeOrder(), and P&L calculations. It implements PriceObserver to receive market data updates.
Stock represents a tradable security with a symbol, current price, and price history. Updated by the MarketDataFeed, it notifies observers (pending orders and the portfolio) when the price changes.
Position tracks holdings in a specific stock: quantity, average cost basis, and current price. addShares() recalculates the weighted average cost. removeShares() computes realized P&L for the sold shares.
Order contains a symbol, quantity, direction (BUY/SELL), an OrderType strategy, and a status (PENDING, FILLED, CANCELLED, REJECTED). It implements PriceObserver so pending limit/stop orders receive price updates and trigger execution when conditions are met.
OrderType is a strategy interface with canExecute(currentPrice, orderPrice) and getExecutionPrice(currentPrice, orderPrice). MarketOrderType always executes immediately. LimitOrderType executes when the price reaches the limit. StopOrderType triggers when the price crosses the stop level.
Trade is an immutable record of an executed order: symbol, quantity, execution price, direction, timestamp, and realized P&L.
MarketDataFeed streams live price updates. It maintains subscriptions per symbol using CopyOnWriteArrayList for thread-safe iteration during notifications.
For each class, define the attributes (data) it will hold and the methods (functions) that operate on the attributes. Ensure they align with the object's responsibilities and adhere to the principle of encapsulation. Write your code in the code editor below.
The OrderType interface defines canExecute(currentPrice, orderPrice) and getExecutionPrice(currentPrice, orderPrice).
Both Order and Portfolio implement PriceObserver with onPriceUpdate(symbol, newPrice). This allows the MarketDataFeed and Stock to notify any interested party without type-checking. The Observer pattern keeps the market data layer decoupled from the order execution layer.
The MarketDataFeed publishes price updates. Stocks and pending Orders subscribe as PriceObservers. When a price changes, pending limit/stop orders re-evaluate their execution conditions, and positions update their unrealized P&L. This push-based approach eliminates polling and ensures immediate reaction to price changes.
OrderType encapsulates execution behavior. The Portfolio's execution engine calls canExecute() and getExecutionPrice() without knowing the concrete order type. Adding TrailingStop or Iceberg orders requires zero changes to Portfolio, Order, or the execution flow.
Explain design tradeoffs you considered. Check and explain whether your design adheres to SOLID principles. Explain how your design can handle changes in scale and whether it would be easy to extend with new functionalities. Identify areas for future improvement...
Each OrderType implementation encapsulates its own trigger logic. MarketOrderType always executes. LimitOrderType checks against a price ceiling or floor depending on direction. The execution engine calls canExecute() polymorphically, never knowing the concrete strategy.
☕Java🐍PythonJavaScriptTypeScriptC++
OrderType
from abc import ABC, abstractmethod
class OrderType(ABC):
@abstractmethod
def can_execute(self, current_price: float,
order_price: float) -> bool: ...
@abstractmethod
def get_execution_price(
self, current_price: float,
order_price: float) -> float: ...
class MarketOrderType(OrderType):
def can_execute(self, current_price: float,
order_price: float) -> bool:
return True
def get_execution_price(
self, current_price: float,
order_price: float) -> float:
return current_price
class LimitOrderType(OrderType):
def init(self,
direction: Direction) -> None:
self._direction = direction
def can_execute(self, current_price: float,
limit: float) -> bool:
if self._direction == Direction.BUY:
return current_price <= limit
return current_price >= limit
def get_execution_price(
self, current_price: float,
limit: float) -> float:
return current_price
class StopOrderType(OrderType):
def init(self,
direction: Direction) -> None:
self._direction = direction
def can_execute(self, current_price: float,
stop: float) -> bool:
if self._direction == Direction.BUY:
return current_price >= stop
return current_price <= stop
def get_execution_price(
self, current_price: float,
stop: float) -> float:
return current_price
Notice how Stock uses volatile for currentPrice (lock-free single-field updates from the market data thread) while Position uses ReentrantLock for compound operations that modify both quantity and averageCostBasis atomically. The lock hierarchy is critical: orderLock is always acquired before positionLock.
☕Java🐍PythonJavaScriptTypeScriptC++
Stock
import threading
import uuid
import time
from typing import List, Dict, Optional
class Stock:
def init(self, symbol: str,
initial_price: float) -> None:
self._symbol = symbol
self._current_price = initial_price
self._observers: List[PriceObserver] = []
def update_price(self,
new_price: float) -> None:
self._current_price = new_price
for obs in self._observers:
obs.on_price_update(
self._symbol, new_price)
def subscribe(self,
obs: PriceObserver) -> None:
self._observers.append(obs)
def unsubscribe(self,
obs: PriceObserver) -> None:
self._observers.remove(obs)
@property
def symbol(self) -> str:
return self._symbol
@property
def current_price(self) -> float:
return self._current_price
class Position:
def init(self, symbol: str) -> None:
self._symbol = symbol
self._quantity = 0
self._average_cost_basis = 0.0
self._current_price = 0.0
self._lock = threading.Lock()
def add_shares(self, qty: int,
price: float) -> None:
with self._lock:
total_cost = (
self._average_cost_basis
* self._quantity + price * qty)
self._quantity += qty
self._average_cost_basis = (
total_cost / self._quantity
if self._quantity > 0 else 0)
def remove_shares(self, qty: int,
price: float) -> float:
with self._lock:
realized_pnl = (
(price - self._average_cost_basis)
* qty)
self._quantity -= qty
if self._quantity == 0:
self._average_cost_basis = 0
return realized_pnl
def update_current_price(
self, price: float) -> None:
self._current_price = price
@property
def unrealized_pnl(self) -> float:
return ((self._current_price
- self._average_cost_basis)
* self._quantity)
@property
def market_value(self) -> float:
return self._quantity * self._current_price
@property
def quantity(self) -> int:
return self._quantity
@property
def symbol(self) -> str:
return self._symbol
class Trade:
def init(self, trade_id: str, symbol: str,
quantity: int, price: float,
direction: Direction,
realized_pnl: float) -> None:
self._trade_id = trade_id
self._symbol = symbol
self._quantity = quantity
self._price = price
self._direction = direction
self._realized_pnl = realized_pnl
self._timestamp = time.time()
@property
def realized_pnl(self) -> float:
return self._realized_pnl
class Order(PriceObserver):
def init(self, symbol: str, quantity: int,
direction: Direction,
order_type: OrderType,
order_price: float) -> None:
self._order_id = str(uuid.uuid4())
self._symbol = symbol
self._quantity = quantity
self._direction = direction
self._order_type = order_type
self._order_price = order_price
self._status = OrderStatus.PENDING
self._portfolio: Optional[
"Portfolio"] = None
def can_execute(self,
current_price: float) -> bool:
return (self._status == OrderStatus.PENDING
and self._order_type.can_execute(
current_price,
self._order_price))
def on_price_update(self, symbol: str,
price: float) -> None:
if (self._symbol == symbol
and self._status
== OrderStatus.PENDING
and self.can_execute(price)
and self._portfolio is not None):
exec_price = (
self._order_type
.get_execution_price(
price, self._order_price))
self._portfolio.execute_order(
self, exec_price)
def fill(self) -> None:
self._status = OrderStatus.FILLED
def reject(self) -> None:
self._status = OrderStatus.REJECTED
@property
def portfolio(self) -> Optional["Portfolio"]:
return self._portfolio
@portfolio.setter
def portfolio(self, p: "Portfolio") -> None:
self._portfolio = p
@property
def symbol(self) -> str:
return self._symbol
@property
def quantity(self) -> int:
return self._quantity
@property
def direction(self) -> Direction:
return self._direction
@property
def order_id(self) -> str:
return self._order_id
class Portfolio(PriceObserver):
def init(self,
initial_cash: float) -> None:
self._positions: Dict[str, Position] = {}
self._trade_history: List[Trade] = []
self._cash_balance = initial_cash
self._order_lock = threading.Lock()
self._stocks: Dict[str, Stock] = {}
def register_stock(self,
stock: Stock) -> None:
self._stocks[stock.symbol] = stock
stock.subscribe(self)
def place_order(
self, order: Order) -> Optional[Trade]:
with self._order_lock:
if order.direction == Direction.BUY:
s = self._stocks[order.symbol]
cost = (s.current_price
* order.quantity)
if cost > self._cash_balance:
order.reject()
return None
else:
p = self._positions.get(
order.symbol)
if (p is None or p.quantity
< order.quantity):
order.reject()
return None
order.portfolio = self
s = self._stocks[order.symbol]
if order.can_execute(s.current_price):
return self.execute_order(
order, s.current_price)
s.subscribe(order)
return None
def execute_order(self, order: Order,
exec_price: float
) -> Optional[Trade]:
with self._order_lock:
if order.symbol not in self._positions:
self._positions[order.symbol] = (
Position(order.symbol))
pos = self._positions[order.symbol]
realized_pnl = 0.0
if order.direction == Direction.BUY:
cost = exec_price * order.quantity
if cost > self._cash_balance:
order.reject()
return None
pos.add_shares(
order.quantity, exec_price)
self._cash_balance -= cost
else:
realized_pnl = pos.remove_shares(
order.quantity, exec_price)
self._cash_balance += (
exec_price * order.quantity)
order.fill()
s = self._stocks.get(order.symbol)
if s:
s.unsubscribe(order)
trade = Trade(
str(uuid.uuid4()), order.symbol,
order.quantity, exec_price,
order.direction, realized_pnl)
self._trade_history.append(trade)
return trade
def on_price_update(self, symbol: str,
new_price: float) -> None:
p = self._positions.get(symbol)
if p:
p.update_current_price(new_price)
@property
def total_value(self) -> float:
return sum(
p.market_value
for p in self._positions.values()
) + self._cash_balance
@property
def total_realized_pnl(self) -> float:
return sum(
t.realized_pnl
for t in self._trade_history)
Each OrderType implementation encapsulates its own trigger logic. MarketOrderType always executes. LimitOrderType checks against a price ceiling or floor depending on direction. The execution engine calls canExecute() polymorphically, never knowing the concrete strategy.
☕Java🐍PythonJavaScriptTypeScriptC++
OrderType
from abc import ABC, abstractmethod
class OrderType(ABC):
@abstractmethod
def can_execute(self, current_price: float,
order_price: float) -> bool: ...
@abstractmethod
def get_execution_price(
self, current_price: float,
order_price: float) -> float: ...
class MarketOrderType(OrderType):
def can_execute(self, current_price: float,
order_price: float) -> bool:
return True
def get_execution_price(
self, current_price: float,
order_price: float) -> float:
return current_price
class LimitOrderType(OrderType):
def init(self,
direction: Direction) -> None:
self._direction = direction
def can_execute(self, current_price: float,
limit: float) -> bool:
if self._direction == Direction.BUY:
return current_price <= limit
return current_price >= limit
def get_execution_price(
self, current_price: float,
limit: float) -> float:
return current_price
class StopOrderType(OrderType):
def init(self,
direction: Direction) -> None:
self._direction = direction
def can_execute(self, current_price: float,
stop: float) -> bool:
if self._direction == Direction.BUY:
return current_price >= stop
return current_price <= stop
def get_execution_price(
self, current_price: float,
stop: float) -> float:
return current_price
Notice how Stock uses volatile for currentPrice (lock-free single-field updates from the market data thread) while Position uses ReentrantLock for compound operations that modify both quantity and averageCostBasis atomically. The lock hierarchy is critical: orderLock is always acquired before positionLock.
☕Java🐍PythonJavaScriptTypeScriptC++
Stock
import threading
import uuid
import time
from typing import List, Dict, Optional
class Stock:
def init(self, symbol: str,
initial_price: float) -> None:
self._symbol = symbol
self._current_price = initial_price
self._observers: List[PriceObserver] = []
def update_price(self,
new_price: float) -> None:
self._current_price = new_price
for obs in self._observers:
obs.on_price_update(
self._symbol, new_price)
def subscribe(self,
obs: PriceObserver) -> None:
self._observers.append(obs)
def unsubscribe(self,
obs: PriceObserver) -> None:
self._observers.remove(obs)
@property
def symbol(self) -> str:
return self._symbol
@property
def current_price(self) -> float:
return self._current_price
class Position:
def init(self, symbol: str) -> None:
self._symbol = symbol
self._quantity = 0
self._average_cost_basis = 0.0
self._current_price = 0.0
self._lock = threading.Lock()
def add_shares(self, qty: int,
price: float) -> None:
with self._lock:
total_cost = (
self._average_cost_basis
* self._quantity + price * qty)
self._quantity += qty
self._average_cost_basis = (
total_cost / self._quantity
if self._quantity > 0 else 0)
def remove_shares(self, qty: int,
price: float) -> float:
with self._lock:
realized_pnl = (
(price - self._average_cost_basis)
* qty)
self._quantity -= qty
if self._quantity == 0:
self._average_cost_basis = 0
return realized_pnl
def update_current_price(
self, price: float) -> None:
self._current_price = price
@property
def unrealized_pnl(self) -> float:
return ((self._current_price
- self._average_cost_basis)
* self._quantity)
@property
def market_value(self) -> float:
return self._quantity * self._current_price
@property
def quantity(self) -> int:
return self._quantity
@property
def symbol(self) -> str:
return self._symbol
class Trade:
def init(self, trade_id: str, symbol: str,
quantity: int, price: float,
direction: Direction,
realized_pnl: float) -> None:
self._trade_id = trade_id
self._symbol = symbol
self._quantity = quantity
self._price = price
self._direction = direction
self._realized_pnl = realized_pnl
self._timestamp = time.time()
@property
def realized_pnl(self) -> float:
return self._realized_pnl
class Order(PriceObserver):
def init(self, symbol: str, quantity: int,
direction: Direction,
order_type: OrderType,
order_price: float) -> None:
self._order_id = str(uuid.uuid4())
self._symbol = symbol
self._quantity = quantity
self._direction = direction
self._order_type = order_type
self._order_price = order_price
self._status = OrderStatus.PENDING
self._portfolio: Optional[
"Portfolio"] = None
def can_execute(self,
current_price: float) -> bool:
return (self._status == OrderStatus.PENDING
and self._order_type.can_execute(
current_price,
self._order_price))
def on_price_update(self, symbol: str,
price: float) -> None:
if (self._symbol == symbol
and self._status
== OrderStatus.PENDING
and self.can_execute(price)
and self._portfolio is not None):
exec_price = (
self._order_type
.get_execution_price(
price, self._order_price))
self._portfolio.execute_order(
self, exec_price)
def fill(self) -> None:
self._status = OrderStatus.FILLED
def reject(self) -> None:
self._status = OrderStatus.REJECTED
@property
def portfolio(self) -> Optional["Portfolio"]:
return self._portfolio
@portfolio.setter
def portfolio(self, p: "Portfolio") -> None:
self._portfolio = p
@property
def symbol(self) -> str:
return self._symbol
@property
def quantity(self) -> int:
return self._quantity
@property
def direction(self) -> Direction:
return self._direction
@property
def order_id(self) -> str:
return self._order_id
class Portfolio(PriceObserver):
def init(self,
initial_cash: float) -> None:
self._positions: Dict[str, Position] = {}
self._trade_history: List[Trade] = []
self._cash_balance = initial_cash
self._order_lock = threading.Lock()
self._stocks: Dict[str, Stock] = {}
def register_stock(self,
stock: Stock) -> None:
self._stocks[stock.symbol] = stock
stock.subscribe(self)
def place_order(
self, order: Order) -> Optional[Trade]:
with self._order_lock:
if order.direction == Direction.BUY:
s = self._stocks[order.symbol]
cost = (s.current_price
* order.quantity)
if cost > self._cash_balance:
order.reject()
return None
else:
p = self._positions.get(
order.symbol)
if (p is None or p.quantity
< order.quantity):
order.reject()
return None
order.portfolio = self
s = self._stocks[order.symbol]
if order.can_execute(s.current_price):
return self.execute_order(
order, s.current_price)
s.subscribe(order)
return None
def execute_order(self, order: Order,
exec_price: float
) -> Optional[Trade]:
with self._order_lock:
if order.symbol not in self._positions:
self._positions[order.symbol] = (
Position(order.symbol))
pos = self._positions[order.symbol]
realized_pnl = 0.0
if order.direction == Direction.BUY:
cost = exec_price * order.quantity
if cost > self._cash_balance:
order.reject()
return None
pos.add_shares(
order.quantity, exec_price)
self._cash_balance -= cost
else:
realized_pnl = pos.remove_shares(
order.quantity, exec_price)
self._cash_balance += (
exec_price * order.quantity)
order.fill()
s = self._stocks.get(order.symbol)
if s:
s.unsubscribe(order)
trade = Trade(
str(uuid.uuid4()), order.symbol,
order.quantity, exec_price,
order.direction, realized_pnl)
self._trade_history.append(trade)
return trade
def on_price_update(self, symbol: str,
new_price: float) -> None:
p = self._positions.get(symbol)
if p:
p.update_current_price(new_price)
@property
def total_value(self) -> float:
return sum(
p.market_value
for p in self._positions.values()
) + self._cash_balance
@property
def total_realized_pnl(self) -> float:
return sum(
t.realized_pnl
for t in self._trade_history)
Single Responsibility: Order manages order state. Position tracks holdings and P&L. Stock maintains price and observers. MarketDataFeed handles streaming. Portfolio orchestrates but delegates.
Open/Closed Principle: New OrderType strategies implement the interface without modifying the execution engine. TrailingStopOrderType, IcebergOrderType, and BracketOrderType all plug in polymorphically.
Liskov Substitution: All OrderType implementations are interchangeable. canExecute() and getExecutionPrice() work correctly regardless of the concrete type. The execution engine never type-checks orders.
Dependency Inversion: Portfolio depends on the OrderType interface, not concrete types. PriceObserver decouples Stock from its observers. The market data layer knows nothing about order execution.
The system follows a strict lock hierarchy: Portfolio orderLock is always acquired before Position lock. Market data updates only write to volatile currentPrice fields and never acquire orderLock. This prevents deadlock between the market data thread and order execution threads. Here is the exact deadlock sequence that consistent ordering prevents:
Market data updates arrive on a separate thread, independent from order execution threads. The CopyOnWriteArrayList for observers allows the market data thread to notify all observers without locking, even as orders are being added or removed from the observer list.
Each Portfolio has its own orderLock. A trading platform serving thousands of users benefits from per-portfolio locking. User A's order execution does not block user B's portfolio operations. This provides natural horizontal scaling.
Store every order, trade, and price update as an immutable event. The portfolio's current state is derived by replaying events. This provides a complete audit trail and enables point-in-time portfolio reconstruction for regulatory reporting.