The foundation of portfolio risk management is knowing your positions and how they moved today. This part builds a simple, automated system: load a YAML file with your holdings, fetch daily prices from yfinance, calculate P&L in real time, and get an automated end-of-day brief. No brokerage API needed.
Your Positions File
Create a positions.yaml file with your portfolio:
# positions.yaml
portfolio:
name: "My Portfolio"
currency: USD
positions:
- ticker: AAPL
shares: 50
avg_cost: 165.20
sector: Technology
account: taxable
- ticker: MSFT
shares: 30
avg_cost: 310.50
sector: Technology
account: taxable
- ticker: VTI
shares: 100
avg_cost: 218.00
sector: ETF
account: ira
- ticker: BRK-B
shares: 20
avg_cost: 345.00
sector: Financials
account: taxable
- ticker: GLD
shares: 15
avg_cost: 180.00
sector: Commodities
account: taxable
P&L Calculation
This Python function loads your positions, fetches current and prior-day prices, and calculates all the key P&L metrics:
import yfinance as yf
import yaml
from datetime import datetime
def load_positions(path: str = "positions.yaml") -> list:
with open(path) as f:
data = yaml.safe_load(f)
return data["portfolio"]["positions"]
def calculate_pnl(positions: list) -> list:
tickers = [p["ticker"] for p in positions]
prices = yf.download(tickers, period="2d", auto_adjust=True, progress=False)["Close"]
results = []
for p in positions:
ticker = p["ticker"]
try:
current = float(prices[ticker].iloc[-1])
prior = float(prices[ticker].iloc[-2])
except (KeyError, IndexError):
continue
cost_basis = p["avg_cost"] * p["shares"]
market_value = current * p["shares"]
unrealized = market_value - cost_basis
daily_pnl = (current - prior) * p["shares"]
results.append({
"ticker": ticker,
"shares": p["shares"],
"avg_cost": p["avg_cost"],
"current_price": round(current, 2),
"market_value": round(market_value, 2),
"cost_basis": round(cost_basis, 2),
"unrealized_pnl": round(unrealized, 2),
"unrealized_pct": round((unrealized / cost_basis) * 100, 2),
"daily_pnl": round(daily_pnl, 2),
"daily_pct": round(((current - prior) / prior) * 100, 2),
})
return results
def portfolio_summary(results: list) -> dict:
total_value = sum(r["market_value"] for r in results)
total_cost = sum(r["cost_basis"] for r in results)
total_unrealized = sum(r["unrealized_pnl"] for r in results)
total_daily = sum(r["daily_pnl"] for r in results)
return {
"total_market_value": round(total_value, 2),
"total_cost_basis": round(total_cost, 2),
"total_unrealized_pnl": round(total_unrealized, 2),
"total_unrealized_pct": round((total_unrealized / total_cost) * 100, 2),
"total_daily_pnl": round(total_daily, 2),
"total_daily_pct": round((total_daily / (total_cost)) * 100, 2),
"position_count": len(results),
"as_of": datetime.now().strftime("%Y-%m-%d %H:%M"),
}
Automated Daily Brief
Set up a HEARTBEAT automation to run at 5 PM ET (market close) every trading day:
name: daily_portfolio_brief
schedule: "0 17 * * 1-5"
steps:
- load_positions:
file: positions.yaml
- fetch_prices:
provider: yfinance
period: "2d"
- calculate_pnl: {}
- llm:
prompt: |
Generate a concise end-of-day portfolio brief:
1. PORTFOLIO SUMMARY: Total value, daily P&L, unrealized gain/loss
2. TOP MOVERS: Biggest daily gainers and losers
3. NOTABLE: Any position up/down more than 3% today
Data: {{ pnl_results }}
- notify:
channel: email
subject: "📊 Portfolio Close: ${{ total_value }} | Daily: {{ daily_pnl }}"
Key Metrics Explained
| Metric | Meaning |
|---|---|
| Market Value | Current price × number of shares. What your position is worth now. |
| Cost Basis | Average purchase price × shares. Total amount invested. |
| Unrealized P&L | Market Value − Cost Basis. Profit/loss if you sold today. |
| Unrealized % | Unrealized P&L ÷ Cost Basis. Return on invested capital. |
| Daily P&L | (Current Price − Prior Price) × Shares. Today's move in dollars. |
| Daily % | Daily P&L ÷ Cost Basis. Today's move as a percentage. |
Frequently Asked Questions
Part 1 FAQs
How do I handle stock splits?
yfinance auto-adjusts historical data. Update avg_cost and shares in positions.yaml to reflect post-split values.
Can I track multiple accounts separately?
Yes — the account field in positions.yaml lets you filter by taxable vs IRA.
What if a ticker isn't found on yfinance?
Use Alpha Vantage or Alpaca as fallback providers for unsupported symbols.
Next: Move to Part 2 to add volatility and drawdown monitoring — Volatility & Drawdown Monitor.