Building a C++ Risk Engine at 18: Learning Quant Finance

Today I dove into basic portfolio theory. I built a Risk Valuation Engine in C++.

I’m 18 and still learning. I don’t understand everything yet, but I’m getting better with each project. This one helped a lot.

What it does:

You input a few assets, their weights, and daily returns. The code then calculates:

  • Portfolio returns
  • Sharpe Ratio
  • Historical VaR (95%)
  • Max Drawdown
  • Log returns

Why I built it:

I wanted to stop just reading and start doing. I kept hearing terms like VaR and Sharpe but didn’t fully get them. Coding forced me to slow down and think. What does each part actually mean? Why do we calculate it that way?

What I learned:

  • How weighted returns work
  • What drawdown really shows
  • Why VaR matters in risk
  • How the Sharpe ratio balances return and volatility
  • The difference between simple and log returns (still wrapping my head around it)

I know this isn’t perfect code. But I wrote it myself and I understand more now than I did yesterday. That’s enough for today!

Please do feel free to check out my full directory full of resources that is $4.99 just for this week! You can view it here: https://youngandcalculated.gumroad.com/l/quantkit

/*  This program contains the following:
1. Portfolio Input and Structure
2. Daily returns (Simple returns and Log Return)
3. Value at Risk (VaR): Historical Simulation
4. Sharpe Ratio
5. Maximum drawdown
*/

#include <algorithm>
#include <cmath>
#include <cstddef>
#include <iostream>
#include <numeric>
#include <vector>

struct Asset {
  std::string name;
  std::vector<double> returns;
  double weight;
};

class Portfolio {
private:
  std::vector<Asset> Assets;

public:
  std::vector<double> PortfolioReturns() {
    std::vector<double> portfolioReturns;
    size_t numDays = Assets[0].returns.size();
    portfolioReturns.resize(numDays);
    double totalReturns = 0.0;
    for (int i = 0; i < Assets.size(); i++) {
      for (int j = 0; j < Assets[i].returns.size(); j++) {
        portfolioReturns[j] += (Assets[i].returns[j] * Assets[i].weight);
      }
    };
    return portfolioReturns;
  };

  auto const LogReturns(int DaysAgo) {
    // computed by day
    double returns = 0.0;
    int Day = Assets[0].returns.size() - DaysAgo;
    for (int i = 0; i < Assets.size(); i++) {
      returns = returns + (Assets[i].returns[Day] * Assets[i].weight);
    };
    return std::log(returns);
  }

  double TotalPortfolioReturns() {
    double totalReturns = 0.0;
    for (int i = 0; i < Assets.size(); i++) {
      for (int j = 0; j < Assets[i].returns.size(); j++) {
        totalReturns += (Assets[i].returns[j] * Assets[i].weight);
      }
    }
    return totalReturns;
  };

  double StandardDeviationReturns(const std::vector<double> portfolio) {
    if (portfolio.empty()) {
      return 0.0;
    }

    double mean = std::accumulate(portfolio.begin(), portfolio.end(), 0) /
                  portfolio.size();
    double variance = 0.0;

    for (double r : portfolio) {
      variance += (r - mean) * (r - mean);
    }
    variance /= portfolio.size();

    return std::sqrt(variance);
  }

  auto const SharpeRatio(const std::vector<double> portfolio) {
    double SARiskFreeRate = 9.96;
    double ReturnOfPort = TotalPortfolioReturns();
    double StandardDeviation = StandardDeviationReturns(portfolio);
    return (ReturnOfPort - SARiskFreeRate) / StandardDeviation;
  };

  auto const VaR(double a) {
    std::vector<double> PortDailyReturn = PortfolioReturns();
    std::sort(PortDailyReturn.begin(), PortDailyReturn.end());

    size_t index = static_cast<size_t>((1.0 - a) * PortDailyReturn.size());
    return -PortDailyReturn[index];
  };
  auto const MaxDrawdown(int t) {
    std::vector<double> returns = PortfolioReturns();
    std::vector<double> cumulative(t);
    cumulative[0] = 1.0;

    for (int i = 1; i < t; i++) {
      cumulative[i] = cumulative[i - 1] * (1 + returns[i]);
    }
    double maxSoFar = cumulative[0];
    double maxDrawdown = 0.0;

    for (int i = 1; i < t; i++) {
      if (cumulative[i] > maxSoFar)
        maxSoFar = cumulative[i];
      double drawdown = (maxSoFar - cumulative[i]) / maxSoFar;
      if (drawdown > maxDrawdown)
        maxDrawdown = drawdown;
    }
    return maxDrawdown;
  };
  auto const LoadAsset() { return Assets; };
  auto const AddAsset(Asset asset) { Assets.push_back(asset); };
};

int main() {
  Portfolio pf;
  int numAssets, numDays;

  std::cout << "Enter number of assets: ";
  std::cin >> numAssets;

  std::cout << "Enter number of return days: ";
  std::cin >> numDays;

  for (int i = 0; i < numAssets; ++i) {
    Asset asset;
    std::cout << "\nAsset " << i + 1 << " name: ";
    std::cin >> asset.name;

    std::cout << "Weight (0 to 1): ";
    std::cin >> asset.weight;

    std::cout << "Enter " << numDays << " daily returns:\n";
    for (int j = 0; j < numDays; ++j) {
      double r;
      std::cin >> r;
      asset.returns.push_back(r);
    }

    pf.AddAsset(asset);
  }

  std::vector<double> portReturns = pf.PortfolioReturns();

  std::cout << "\n--- Portfolio Metrics ---\n";
  std::cout << "Total Return: " << pf.TotalPortfolioReturns() << "\n";
  std::cout << "Sharpe Ratio: " << pf.SharpeRatio(portReturns) << "\n";
  std::cout << "Historical VaR (95%): " << pf.VaR(0.95) << "\n";
  std::cout << "Max Drawdown: " << pf.MaxDrawdown(portReturns.size()) << "\n";
  std::cout << "Log Return (most recent): " << pf.LogReturns(1) << "\n";

  return 0;
}

Glad you’re here. I’m building something useful, honest, and a little different. Hope you stick around.

Join the list. Three emails a week. Real insights, no nonsense.

Enjoyed this article? I don’t charge to read, but if you’d like to support my work, you can make a small contribution below. Stay Calculated!

Support My Work

Leave a Reply

Your email address will not be published. Required fields are marked *