"""BondStats Open Research Templates | Educational, reproducible analysis.
Independent research. No investment advice. Inputs are illustrative unless stated otherwise.
https://www.bondstats.org/tools/developer-tools/research-templates/
"""
import csv
from pathlib import Path

def analyze_portfolio(path, parallel_shift_bp=100):
    """First- and second-order parallel yield shock approximation.
    P&L ~ -MV*modified_duration*dy + 0.5*MV*convexity*dy^2.
    Assumes constant spread, no cashflows/rebalancing, duration and convexity are annualized.
    """
    dy = parallel_shift_bp / 10000
    positions=[]
    with open(path, newline='', encoding='utf-8') as f:
        for r in csv.DictReader(f):
            mv, dur, conv = (float(r[k]) for k in ('market_value','modified_duration','convexity'))
            if mv < 0 or dur < 0 or conv < 0: raise ValueError('Use nonnegative values for this long-only example')
            pnl = mv*(-dur*dy + .5*conv*dy**2)
            positions.append({'instrument':r['instrument'],'market_value':mv,'pnl':pnl,'dv01':mv*dur*.0001})
    total_mv=sum(p['market_value'] for p in positions)
    return {'positions':positions,'market_value':total_mv,'pnl':sum(p['pnl'] for p in positions),
            'pnl_pct':sum(p['pnl'] for p in positions)/total_mv*100 if total_mv else 0,
            'portfolio_dv01':sum(p['dv01'] for p in positions)}

if __name__ == '__main__':
    result=analyze_portfolio(Path(__file__).with_name('sample_portfolio.csv'),100)
    for p in result['positions']:print(p)
    print({k:v for k,v in result.items() if k!='positions'})
