CoolFace
Apppublic

Ankit3445/options_pricing

sourceHugging Faceupdated 3mo agoView on Hugging Face
0likes
PortfolioManager.cpp661 linesDownload Raw Back to root
1#include "PortfolioManager.hpp"2 3#include <iostream>4#include <iomanip>5#include <sstream>6#include <fstream>7#include <cstdlib>8#include <ctime>9#include <cmath>10#include <algorithm>11#include <numeric>12#include <nlohmann/json.hpp>13 14PortfolioManager::PortfolioManager() {15    load();16}17 18void PortfolioManager::clear_screen() {19#if defined(_WIN32)20    std::system("cls");21#else22    std::system("clear");23#endif24}25 26double PortfolioManager::compute_hv() const {27    if (log_returns_.size() < 5) return 0.0;28 29    double sum = 0.0;30    for (double r : log_returns_) sum += r;31    double mean = sum / static_cast<double>(log_returns_.size());32 33    double sq_sum = 0.0;34    for (double r : log_returns_) {35        double dev = r - mean;36        sq_sum += dev * dev;37    }38 39    double variance = sq_sum / static_cast<double>(log_returns_.size() - 1);40    double daily_vol = std::sqrt(variance);41    return daily_vol * std::sqrt(252.0);42}43 44TickerSummary PortfolioManager::compute_summary(const std::string& ticker) const {45    TickerSummary s;46    int count = 0;47    for (const auto& p : positions_) {48        if (p.ticker != ticker) continue;49        ++count;50        s.mcDelta += p.mc.delta;51        s.mcGamma += p.mc.gamma;52        s.mcVega  += p.mc.vega;53        s.mcTheta += p.mc.theta;54        s.mcRho   += p.mc.rho;55        s.mcValue += p.mc.price;56        s.mcAvgIv += p.mc.impliedVol;57        s.bsDelta += p.bs.delta;58        s.bsGamma += p.bs.gamma;59        s.bsVega  += p.bs.vega;60        s.bsTheta += p.bs.theta;61        s.bsRho   += p.bs.rho;62        s.bsValue += p.bs.price;63        s.bsAvgIv += p.bs.impliedVol;64        s.totalCost += p.entryMarketPrice;65    }66    s.count = count;67    if (count > 0) {68        s.mcAvgIv /= static_cast<double>(count);69        s.bsAvgIv /= static_cast<double>(count);70    }71    return s;72}73 74void PortfolioManager::update(const Greeks& mc_greeks, const Greeks& bs_greeks, const MarketTick& tick) {75    if (!active_.load()) return;76 77    {78        std::lock_guard<std::mutex> lock(mutex_);79 80        Position pos;81        pos.ticker           = tick.ticker;82        pos.spotPrice        = tick.spotPrice;83        pos.strikePrice      = tick.strikePrice;84        pos.timeToExpiry     = tick.timeToExpiry;85        pos.riskFreeRate     = tick.riskFreeRate;86        pos.optionType       = tick.optionType;87        pos.entryMarketPrice = tick.optionMarketPrice;88        pos.mc               = mc_greeks;89        pos.bs               = bs_greeks;90 91        positions_.push_back(std::move(pos));92 93        lastSpot_   = tick.spotPrice;94        lastTicker_ = tick.ticker;95 96        running_mc_value_ += tick.optionMarketPrice;97        running_total_cost_ += tick.optionMarketPrice;98 99        // Track P&L history every 10 positions100        if (positions_.size() % 10 == 0) {101            double mc_sum = 0.0, cost_sum = 0.0;102            for (const auto& pos : positions_) {103                mc_sum += pos.mc.price;104                cost_sum += pos.entryMarketPrice;105            }106            double pl = mc_sum - cost_sum;107            pl_history_.push_back(pl);108            if (pl_history_.size() > 100) pl_history_.pop_front();109        }110 111        history_.push_back({tick.spotPrice});112        if (history_.size() > max_history_) {113            history_.pop_front();114        }115 116        if (history_.size() >= 2) {117            double prev = history_[history_.size() - 2].spot;118            double curr = history_.back().spot;119            if (prev > 0.0) {120                double log_ret = std::log(curr / prev);121                log_returns_.push_back(log_ret);122                if (log_returns_.size() > max_history_) {123                    log_returns_.pop_front();124                }125            }126        }127    }128 129    if (positions_.size() % 30 == 0) {130        save();131    }132}133 134void PortfolioManager::reset() {135    {136        std::lock_guard<std::mutex> lock(mutex_);137        positions_.clear();138        history_.clear();139        log_returns_.clear();140        pl_history_.clear();141        lastSpot_ = 0.0;142        lastTicker_.clear();143    }144    save();145    std::cout << "[PortfolioManager] Reset complete" << std::endl;146}147 148void PortfolioManager::print_ticker_section(const std::string& ticker, const TickerSummary& s) const {149    std::ostringstream oss;150    oss << "| " << std::left << std::setw(7) << ticker151        << "| " << std::right << std::setw(4) << s.count152        << "  | " << std::setw(9) << std::fixed << std::setprecision(2) << s.mcDelta153        << " | " << std::setw(9) << std::fixed << std::setprecision(4) << s.mcGamma154        << " | " << std::setw(9) << std::fixed << std::setprecision(1) << s.mcVega155        << " | " << std::setw(9) << std::fixed << std::setprecision(1) << s.mcTheta156        << " | " << std::setw(6) << std::fixed << std::setprecision(2) << s.mcAvgIv * 100.0 << "%"157        << "  |\n";158    std::cout << oss.str();159}160 161void PortfolioManager::print_dashboard() const {162    std::lock_guard<std::mutex> lock(mutex_);163 164    clear_screen();165 166    double hv = compute_hv();167 168    double totalMCValue = 0.0, totalBSValue = 0.0, totalCost = 0.0;169    double mcDelta = 0.0, mcGamma = 0.0, mcVega = 0.0, mcTheta = 0.0, mcRho = 0.0, mcAvgIv = 0.0;170    double bsDelta = 0.0, bsGamma = 0.0, bsVega = 0.0, bsTheta = 0.0, bsRho = 0.0, bsAvgIv = 0.0;171    int totalCount = static_cast<int>(positions_.size());172 173    std::unordered_map<std::string, TickerSummary> summaries;174 175    for (const auto& p : positions_) {176        mcDelta += p.mc.delta; mcGamma += p.mc.gamma; mcVega += p.mc.vega;177        mcTheta += p.mc.theta; mcRho += p.mc.rho; mcAvgIv += p.mc.impliedVol;178        bsDelta += p.bs.delta; bsGamma += p.bs.gamma; bsVega += p.bs.vega;179        bsTheta += p.bs.theta; bsRho += p.bs.rho; bsAvgIv += p.bs.impliedVol;180        totalCost += p.entryMarketPrice;181        totalMCValue += p.mc.price;182        totalBSValue += p.bs.price;183 184        auto& s = summaries[p.ticker];185        s.count += 1;186        s.mcDelta += p.mc.delta; s.mcGamma += p.mc.gamma; s.mcVega += p.mc.vega;187        s.mcTheta += p.mc.theta; s.mcRho += p.mc.rho; s.mcAvgIv += p.mc.impliedVol;188        s.mcValue += p.mc.price;189        s.bsDelta += p.bs.delta; s.bsGamma += p.bs.gamma; s.bsVega += p.bs.vega;190        s.bsTheta += p.bs.theta; s.bsRho += p.bs.rho; s.bsAvgIv += p.bs.impliedVol;191        s.bsValue += p.bs.price;192        s.totalCost += p.entryMarketPrice;193    }194 195    if (totalCount > 0) {196        mcAvgIv /= totalCount;197        bsAvgIv /= totalCount;198    }199    for (auto& [_, s] : summaries) {200        if (s.count > 0) {201            s.mcAvgIv /= s.count;202            s.bsAvgIv /= s.count;203        }204    }205 206    double unrealizedPl = totalMCValue - totalCost;207 208    std::cout << "+======================================================================+\n"209              << "|               REAL-TIME PORTFOLIO RISK DASHBOARD                      |\n"210              << "+======================================================================+\n"211              << "| Total Positions: " << std::setw(4) << totalCount212              << "  | Last Spot: $" << std::right << std::setw(8) << std::fixed << std::setprecision(2) << lastSpot_213              << "  | HV: " << std::setw(6) << hv * 100.0 << "%                       |\n"214              << "+------------------------------------------+-----------------------------+------------------+\n";215 216    auto print_row = [&](const std::string& name, double mc, double bs, const std::string& unit) {217        std::cout << "| " << std::left << std::setw(40) << name218                  << "| MC:" << std::right << std::setw(10) << std::fixed << std::setprecision(4) << mc219                  << " BS:" << std::setw(10) << std::fixed << std::setprecision(4) << bs220                  << " | " << std::left << std::setw(16) << unit << "|\n";221    };222 223    std::cout << "| " << std::left << std::setw(40) << "Greek"224              << "| MC         BS         | Status           |\n"225              << "+------------------------------------------+-----------------------------+------------------+\n";226    print_row("Delta",  mcDelta,  bsDelta,  mcDelta > 0.5 ? "LONG" : mcDelta < -0.5 ? "SHORT" : "NEUTRAL");227    print_row("Gamma",  mcGamma,  bsGamma,  mcGamma > 0.01 ? "CONVEX" : mcGamma < -0.01 ? "CONCAVE" : "FLAT");228    print_row("Vega",   mcVega,   bsVega,   mcVega > 1.0 ? "LONG VOL" : mcVega < -1.0 ? "SHORT VOL" : "FLAT");229    print_row("Theta",  mcTheta,  bsTheta,  mcTheta < -1.0 ? "TIME DECAY" : mcTheta > 1.0 ? "TIME GAIN" : "FLAT");230    print_row("Rho",    mcRho,    bsRho,    mcRho > 1.0 ? "LONG RATE" : mcRho < -1.0 ? "SHORT RATE" : "FLAT");231 232    std::cout << "+------------------------------------------+-----------------------------+------------------+\n"233              << "| MC Implied Vol (avg)                     | " << std::right << std::setw(26) << std::fixed << std::setprecision(2)234              << mcAvgIv * 100.0 << "%"235              << "     |                  |\n"236              << "| BS Implied Vol (avg)                     | " << std::right << std::setw(26) << std::fixed << std::setprecision(2)237              << bsAvgIv * 100.0 << "%"238              << "     |                  |\n"239              << "| Historical Volatility (annual)           | " << std::right << std::setw(26) << std::fixed << std::setprecision(2)240              << hv * 100.0 << "%"241              << "     |                  |\n"242              << "+------------------------------------------+-----------------------------+------------------+\n"243              << "| P&L Summary                                                                       |\n"244              << "+------------------------------------------+-----------------------------+------------------+\n"245              << "| Total Cost Basis (entry)                 | " << std::right << std::setw(24) << std::fixed << std::setprecision(2)246              << totalCost247              << "     |                  |\n"248              << "| Portfolio Value (MC)                     | " << std::right << std::setw(24) << std::fixed << std::setprecision(2)249              << totalMCValue250              << "     |                  |\n"251              << "| Portfolio Value (BS)                     | " << std::right << std::setw(24) << std::fixed << std::setprecision(2)252              << totalBSValue253              << "     |                  |\n"254              << "| Unrealized P&L (MC)                      | " << std::right << std::setw(24) << std::fixed << std::setprecision(2)255              << unrealizedPl256              << "     | " << (unrealizedPl >= 0.0 ? "PROFIT        " : "LOSS          ") << "|\n"257              << "+==========================================+=============================+==================+\n"258              << std::endl;259 260    if (!summaries.empty()) {261        std::cout << "  GREEKS SURFACE (MC) - Position Summary by Ticker\n"262                  << "  " << std::string(76, '=') << "\n"263                  << "  " << std::left << std::setw(7) << "Ticker"264                  << "| " << std::right << std::setw(4) << "#Pos"265                  << "  | " << std::setw(9) << "Delta"266                  << " | " << std::setw(9) << "Gamma"267                  << " | " << std::setw(9) << "Vega"268                  << " | " << std::setw(9) << "Theta"269                  << " | " << std::setw(6) << "IV"270                  << "  |\n"271                  << "  " << std::string(76, '-') << "\n";272 273        for (const auto& [ticker, s] : summaries) {274            std::cout << "  ";275            print_ticker_section(ticker, s);276        }277 278        std::cout << "  " << std::string(76, '-') << "\n"279                  << "  " << std::left << std::setw(7) << "TOTAL"280                  << "| " << std::right << std::setw(4) << totalCount281                  << "  | " << std::setw(9) << std::fixed << std::setprecision(2) << mcDelta282                  << " | " << std::setw(9) << std::fixed << std::setprecision(4) << mcGamma283                  << " | " << std::setw(9) << std::fixed << std::setprecision(1) << mcVega284                  << " | " << std::setw(9) << std::fixed << std::setprecision(1) << mcTheta285                  << " | " << std::setw(6) << std::fixed << std::setprecision(2) << mcAvgIv * 100.0 << "%"286                  << "  |\n"287                  << "  " << std::string(76, '=') << "\n"288                  << std::endl;289    }290 291    if (!history_.empty()) {292        std::cout << "  Price History (last " << std::setw(2) << history_.size() << " ticks, spot):\n  ";293        size_t count = 0;294        for (const auto& rec : history_) {295            std::cout << std::fixed << std::setprecision(2) << std::setw(7) << rec.spot;296            if (++count % 10 == 0 && count < history_.size()) {297                std::cout << "\n  ";298            } else if (count < history_.size()) {299                std::cout << " ";300            }301        }302        std::cout << "\n" << std::endl;303    }304 305    // Analytics section306    if (!pl_history_.empty()) {307        std::vector<double> pl_vals(pl_history_.begin(), pl_history_.end());308        double sum = std::accumulate(pl_vals.begin(), pl_vals.end(), 0.0);309        double mean = sum / pl_vals.size();310        double sq_sum = 0.0;311        for (double v : pl_vals) sq_sum += (v - mean) * (v - mean);312        double stddev = std::sqrt(sq_sum / pl_vals.size());313 314        std::sort(pl_vals.begin(), pl_vals.end());315        double var95 = pl_vals[static_cast<size_t>(pl_vals.size() * 0.05)];316        double var99 = pl_vals[static_cast<size_t>(pl_vals.size() * 0.01)];317 318        double sharpe = stddev > 0.0 ? (mean - 0.04) / stddev * std::sqrt(252.0) : 0.0;319 320        std::cout << "+======================================================================+\n"321                  << "| PORTFOLIO ANALYTICS                                                    |\n"322                  << "+======================================================================+\n"323                  << "| Sharpe Ratio (annual)  | " << std::right << std::setw(12) << std::fixed << std::setprecision(2) << sharpe324                  << "                                |\n"325                  << "| VaR (95%%)             | " << std::right << std::setw(12) << std::fixed << std::setprecision(2) << var95326                  << "                                |\n"327                  << "| VaR (99%%)             | " << std::right << std::setw(12) << std::fixed << std::setprecision(2) << var99328                  << "                                |\n"329                  << "+======================================================================+\n" << std::endl;330    }331 332    std::cout << "+======================================================================+\n" << std::endl;333}334 335nlohmann::json PortfolioManager::to_json() const {336    std::lock_guard<std::mutex> lock(mutex_);337 338    double mcDelta = 0.0, mcGamma = 0.0, mcVega = 0.0, mcTheta = 0.0, mcRho = 0.0;339    double bsDelta = 0.0, bsGamma = 0.0, bsVega = 0.0, bsTheta = 0.0, bsRho = 0.0;340    double totalCost = 0.0, mcValue = 0.0, bsValue = 0.0, mcAvgIv = 0.0, bsAvgIv = 0.0;341    int totalCount = static_cast<int>(positions_.size());342 343    std::unordered_map<std::string, nlohmann::json> tickerData;344    std::unordered_map<std::string, int> tickerPosCount;345    std::unordered_map<std::string, std::vector<nlohmann::json>> tickerPositions;346    static constexpr int MAX_POS_PER_TICKER = 20;347 348    for (const auto& p : positions_) {349        mcDelta += p.mc.delta; mcGamma += p.mc.gamma; mcVega += p.mc.vega;350        mcTheta += p.mc.theta; mcRho += p.mc.rho; mcAvgIv += p.mc.impliedVol;351        bsDelta += p.bs.delta; bsGamma += p.bs.gamma; bsVega += p.bs.vega;352        bsTheta += p.bs.theta; bsRho += p.bs.rho; bsAvgIv += p.bs.impliedVol;353        totalCost += p.entryMarketPrice;354        mcValue += p.mc.price;355        bsValue += p.bs.price;356 357        nlohmann::json td = nlohmann::json::object();358        auto it = tickerData.find(p.ticker);359        if (it != tickerData.end()) td = it->second;360        td["count"]          = td.value("count", 0) + 1;361        td["mc_delta"]       = td.value("mc_delta", 0.0) + p.mc.delta;362        td["mc_gamma"]       = td.value("mc_gamma", 0.0) + p.mc.gamma;363        td["mc_vega"]        = td.value("mc_vega", 0.0) + p.mc.vega;364        td["mc_theta"]       = td.value("mc_theta", 0.0) + p.mc.theta;365        td["mc_rho"]         = td.value("mc_rho", 0.0) + p.mc.rho;366        td["mc_value"]       = td.value("mc_value", 0.0) + p.mc.price;367        td["mc_iv_sum"]      = td.value("mc_iv_sum", 0.0) + p.mc.impliedVol;368        td["bs_delta"]       = td.value("bs_delta", 0.0) + p.bs.delta;369        td["bs_gamma"]       = td.value("bs_gamma", 0.0) + p.bs.gamma;370        td["bs_vega"]        = td.value("bs_vega", 0.0) + p.bs.vega;371        td["bs_theta"]       = td.value("bs_theta", 0.0) + p.bs.theta;372        td["bs_rho"]         = td.value("bs_rho", 0.0) + p.bs.rho;373        td["bs_value"]       = td.value("bs_value", 0.0) + p.bs.price;374        td["bs_iv_sum"]      = td.value("bs_iv_sum", 0.0) + p.bs.impliedVol;375        td["cost"]           = td.value("cost", 0.0) + p.entryMarketPrice;376        tickerData[p.ticker] = td;377 378        int& pcnt = tickerPosCount[p.ticker];379        if (pcnt < MAX_POS_PER_TICKER) {380            tickerPositions[p.ticker].push_back({381                {"spot",       p.spotPrice},382                {"strike",     p.strikePrice},383                {"expiry",     p.timeToExpiry},384                {"type",       p.optionType == OptionType::CALL ? "CALL" : "PUT"},385                {"mc_price",   p.mc.price},   {"mc_delta", p.mc.delta},386                {"mc_gamma",   p.mc.gamma},   {"mc_vega",  p.mc.vega},387                {"mc_theta",   p.mc.theta},   {"mc_iv",    p.mc.impliedVol},388                {"bs_price",   p.bs.price},   {"bs_delta", p.bs.delta},389                {"bs_gamma",   p.bs.gamma},   {"bs_vega",  p.bs.vega},390                {"bs_theta",   p.bs.theta},   {"bs_iv",    p.bs.impliedVol},391                {"entry_price", p.entryMarketPrice}392            });393        }394        ++pcnt;395    }396 397    if (totalCount > 0) {398        mcAvgIv /= static_cast<double>(totalCount);399        bsAvgIv /= static_cast<double>(totalCount);400    }401    double hv = compute_hv();402    double unrealizedPl = mcValue - totalCost;403 404    nlohmann::json tickers;405    for (auto& [sym, td] : tickerData) {406        int cnt = td["count"].get<int>();407        tickers[sym] = {408            {"count",   cnt},409            {"mc_delta", td["mc_delta"]}, {"mc_gamma", td["mc_gamma"]},410            {"mc_vega",  td["mc_vega"]},  {"mc_theta", td["mc_theta"]},411            {"mc_rho",   td["mc_rho"]},   {"mc_value", td["mc_value"]},412            {"mc_iv",    cnt > 0 ? td["mc_iv_sum"].get<double>() / cnt : 0.0},413            {"bs_delta", td["bs_delta"]}, {"bs_gamma", td["bs_gamma"]},414            {"bs_vega",  td["bs_vega"]},  {"bs_theta", td["bs_theta"]},415            {"bs_rho",   td["bs_rho"]},   {"bs_value", td["bs_value"]},416            {"bs_iv",    cnt > 0 ? td["bs_iv_sum"].get<double>() / cnt : 0.0},417            {"cost",     td["cost"]}418        };419    }420 421    nlohmann::json priceHistory = nlohmann::json::array();422    for (const auto& rec : history_) {423        priceHistory.push_back(rec.spot);424    }425 426    nlohmann::json positions_json = nlohmann::json::array();427    int start = std::max(0, totalCount - 10);428    for (int i = start; i < totalCount; ++i) {429        const auto& p = positions_[i];430        positions_json.push_back({431            {"ticker",     p.ticker},432            {"spot",       p.spotPrice},433            {"strike",     p.strikePrice},434            {"expiry",     p.timeToExpiry},435            {"type",       p.optionType == OptionType::CALL ? "CALL" : "PUT"},436            {"mc_price",   p.mc.price},   {"mc_delta", p.mc.delta},437            {"mc_gamma",   p.mc.gamma},   {"mc_vega",  p.mc.vega},438            {"mc_theta",   p.mc.theta},   {"mc_iv",    p.mc.impliedVol},439            {"bs_price",   p.bs.price},   {"bs_delta", p.bs.delta},440            {"bs_gamma",   p.bs.gamma},   {"bs_vega",  p.bs.vega},441            {"bs_theta",   p.bs.theta},   {"bs_iv",    p.bs.impliedVol},442            {"entry_price", p.entryMarketPrice}443        });444    }445 446    // ── Analytics ──447    double sharpe = 0.0, var95 = 0.0, var99 = 0.0, max_dd = 0.0;448    double stress_crash = 0.0, stress_vol = 0.0, stress_rate = 0.0;449 450    if (pl_history_.size() >= 5) {451        std::vector<double> pl_vals(pl_history_.begin(), pl_history_.end());452        double sum = std::accumulate(pl_vals.begin(), pl_vals.end(), 0.0);453        double mean = sum / pl_vals.size();454        double sq_sum = 0.0;455        for (double v : pl_vals) sq_sum += (v - mean) * (v - mean);456        double stddev = std::sqrt(sq_sum / pl_vals.size());457 458        std::sort(pl_vals.begin(), pl_vals.end());459        var95 = pl_vals[static_cast<size_t>(pl_vals.size() * 0.05)];460        var99 = pl_vals[static_cast<size_t>(pl_vals.size() * 0.01)];461 462        sharpe = stddev > 0.0 ? (mean - 0.04) / stddev * std::sqrt(252.0) : 0.0;463 464        // Max drawdown465        double peak = -1e18;466        for (double v : pl_vals) {467            if (v > peak) peak = v;468            double dd = (peak - v) / std::max(std::abs(peak), 1.0);469            if (dd > max_dd) max_dd = dd;470        }471 472        // Stress scenarios473        double crash_pnl = 0.0, vol_pnl = 0.0, rate_pnl = 0.0;474        for (const auto& p : positions_) {475            double crash_spot = p.spotPrice * 0.9;476            double intrinsic = p.optionType == OptionType::CALL477                ? std::max(crash_spot - p.strikePrice, 0.0)478                : std::max(p.strikePrice - crash_spot, 0.0);479            crash_pnl += intrinsic - p.mc.price;480            vol_pnl += p.mc.vega * 0.2;481            rate_pnl += p.mc.rho * 0.01;482        }483        stress_crash = crash_pnl;484        stress_vol = vol_pnl;485        stress_rate = rate_pnl;486    }487 488    nlohmann::json analytics = {489        {"sharpe",         sharpe},490        {"var95",          var95},491        {"var99",          var99},492        {"max_drawdown",   max_dd},493        {"stress_crash",   stress_crash},494        {"stress_vol",     stress_vol},495        {"stress_rate",    stress_rate},496        {"pl_history",     std::vector<double>(pl_history_.begin(), pl_history_.end())}497    };498 499    return {500        {"positions_total", totalCount},501        {"last_spot",       lastSpot_},502        {"last_ticker",     lastTicker_},503        {"mc_delta", mcDelta}, {"mc_gamma", mcGamma}, {"mc_vega", mcVega},504        {"mc_theta", mcTheta}, {"mc_rho",   mcRho},505        {"bs_delta", bsDelta}, {"bs_gamma", bsGamma}, {"bs_vega", bsVega},506        {"bs_theta", bsTheta}, {"bs_rho",   bsRho},507        {"total_cost",   totalCost},508        {"mc_value",     mcValue},509        {"bs_value",     bsValue},510        {"unrealized_pl", unrealizedPl},511        {"mc_avg_iv",    mcAvgIv},512        {"bs_avg_iv",    bsAvgIv},513        {"hv",           hv},514        {"tickers",      tickers},515        {"ticker_positions", tickerPositions},516        {"price_history", priceHistory},517        {"recent_positions", positions_json},518        {"analytics",    analytics}519    };520}521 522nlohmann::json PortfolioManager::chain_for_ticker(const std::string& ticker) const {523    std::lock_guard<std::mutex> lock(mutex_);524    nlohmann::json positions_json = nlohmann::json::array();525    for (const auto& p : positions_) {526        if (p.ticker != ticker) continue;527        positions_json.push_back({528            {"ticker",     p.ticker},529            {"spot",       p.spotPrice},530            {"strike",     p.strikePrice},531            {"expiry",     p.timeToExpiry},532            {"type",       p.optionType == OptionType::CALL ? "CALL" : "PUT"},533            {"mc_price",   p.mc.price},   {"mc_delta", p.mc.delta},534            {"mc_gamma",   p.mc.gamma},   {"mc_vega",  p.mc.vega},535            {"mc_theta",   p.mc.theta},   {"mc_iv",    p.mc.impliedVol},536            {"bs_price",   p.bs.price},   {"bs_delta", p.bs.delta},537            {"bs_gamma",   p.bs.gamma},   {"bs_vega",  p.bs.vega},538            {"bs_theta",   p.bs.theta},   {"bs_iv",    p.bs.impliedVol},539            {"entry_price", p.entryMarketPrice}540        });541    }542    return positions_json;543}544 545nlohmann::json PortfolioManager::export_json() const {546    nlohmann::json j = to_json();547    j["export_version"] = "2.0";548    j["export_timestamp"] = std::to_string(std::time(nullptr));549    return j;550}551 552std::string PortfolioManager::export_csv() const {553    std::lock_guard<std::mutex> lock(mutex_);554    std::ostringstream csv;555    csv << "Ticker,Spot,Strike,Expiry,Type,"556        << "MC_Price,MC_Delta,MC_Gamma,MC_Vega,MC_Theta,MC_IV,"557        << "BS_Price,BS_Delta,BS_Gamma,BS_Vega,BS_Theta,BS_IV,"558        << "EntryPrice\n";559    for (const auto& p : positions_) {560        csv << p.ticker << ","561            << p.spotPrice << ","562            << p.strikePrice << ","563            << p.timeToExpiry << ","564            << (p.optionType == OptionType::CALL ? "CALL" : "PUT") << ","565            << p.mc.price << "," << p.mc.delta << "," << p.mc.gamma << ","566            << p.mc.vega << "," << p.mc.theta << "," << p.mc.impliedVol << ","567            << p.bs.price << "," << p.bs.delta << "," << p.bs.gamma << ","568            << p.bs.vega << "," << p.bs.theta << "," << p.bs.impliedVol << ","569            << p.entryMarketPrice << "\n";570    }571    return csv.str();572}573 574void PortfolioManager::save(const std::string& path) const {575    std::lock_guard<std::mutex> lock(mutex_);576    try {577        nlohmann::json j = nlohmann::json::array();578        for (const auto& p : positions_) {579            j.push_back({580                {"ticker", p.ticker},581                {"spot", p.spotPrice},582                {"strike", p.strikePrice},583                {"expiry", p.timeToExpiry},584                {"rate", p.riskFreeRate},585                {"type", p.optionType == OptionType::CALL ? "CALL" : "PUT"},586                {"entry_price", p.entryMarketPrice},587                {"mc", {{"price", p.mc.price}, {"delta", p.mc.delta}, {"gamma", p.mc.gamma},588                        {"vega", p.mc.vega}, {"theta", p.mc.theta}, {"rho", p.mc.rho},589                        {"iv", p.mc.impliedVol}}},590                {"bs", {{"price", p.bs.price}, {"delta", p.bs.delta}, {"gamma", p.bs.gamma},591                        {"vega", p.bs.vega}, {"theta", p.bs.theta}, {"rho", p.bs.rho},592                        {"iv", p.bs.impliedVol}}}593            });594        }595        std::ofstream ofs(path);596        ofs << j.dump(2);597        std::cout << "[PortfolioManager] Saved " << positions_.size() << " positions to " << path << std::endl;598    } catch (const std::exception& e) {599        std::cerr << "[PortfolioManager] Save error: " << e.what() << std::endl;600    }601}602 603void PortfolioManager::load(const std::string& path) {604    std::lock_guard<std::mutex> lock(mutex_);605    try {606        std::ifstream ifs(path);607        if (!ifs.good()) return;608        nlohmann::json j;609        ifs >> j;610        if (!j.is_array()) return;611        positions_.clear();612        for (const auto& item : j) {613            Position p;614            p.ticker           = item.value("ticker", "");615            p.spotPrice        = item.value("spot", 0.0);616            p.strikePrice      = item.value("strike", 0.0);617            p.timeToExpiry     = item.value("expiry", 0.0);618            p.riskFreeRate     = item.value("rate", 0.04);619            p.entryMarketPrice = item.value("entry_price", 0.0);620            p.optionType       = item.value("type", "CALL") == "CALL" ? OptionType::CALL : OptionType::PUT;621            if (item.contains("mc")) {622                p.mc.price      = item["mc"].value("price", 0.0);623                p.mc.delta      = item["mc"].value("delta", 0.0);624                p.mc.gamma      = item["mc"].value("gamma", 0.0);625                p.mc.vega       = item["mc"].value("vega", 0.0);626                p.mc.theta      = item["mc"].value("theta", 0.0);627                p.mc.rho        = item["mc"].value("rho", 0.0);628                p.mc.impliedVol = item["mc"].value("iv", 0.0);629                p.mc.valid      = true;630            }631            if (item.contains("bs")) {632                p.bs.price      = item["bs"].value("price", 0.0);633                p.bs.delta      = item["bs"].value("delta", 0.0);634                p.bs.gamma      = item["bs"].value("gamma", 0.0);635                p.bs.vega       = item["bs"].value("vega", 0.0);636                p.bs.theta      = item["bs"].value("theta", 0.0);637                p.bs.rho        = item["bs"].value("rho", 0.0);638                p.bs.impliedVol = item["bs"].value("iv", 0.0);639                p.bs.valid      = true;640            }641            positions_.push_back(std::move(p));642        }643        if (!positions_.empty()) {644            lastSpot_   = positions_.back().spotPrice;645            lastTicker_ = positions_.back().ticker;646        }647        std::cout << "[PortfolioManager] Loaded " << positions_.size() << " positions from " << path << std::endl;648    } catch (const std::exception& e) {649        std::cerr << "[PortfolioManager] Load error: " << e.what() << std::endl;650    }651}652 653void PortfolioManager::stop() {654    save();655    active_.store(false);656}657 658bool PortfolioManager::is_active() const {659    return active_.load();660}661