Skip to content

C++ Monte Carlo simulation calculator on AWS Lambda #187

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,10 @@ tags
TODO
compile_commands.json
.clangd

#ide
.idea
test.cpp
a.out

cmake-build-debug
14 changes: 14 additions & 0 deletions examples/monte-carlo-calculator/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
cmake_minimum_required(VERSION 3.5)
set(CMAKE_CXX_STANDARD 11)

project(demo LANGUAGES CXX)

find_package(aws-lambda-runtime REQUIRED)
find_package(AWSSDK COMPONENTS core)
find_package(ZLIB)

add_executable(${PROJECT_NAME} "main.cpp")
target_link_libraries(${PROJECT_NAME} PUBLIC AWS::aws-lambda-runtime ${AWSSDK_LINK_LIBRARIES} )

# this line creates a target that packages your binary and zips it up
aws_lambda_package_target(${PROJECT_NAME} )
174 changes: 174 additions & 0 deletions examples/monte-carlo-calculator/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
# A simple C++ Monte Carlo simulation calculator on AWS Lambda

Calculate the value of a European call option
in C++
using Monte Carlo simulation.
Deploy to AWS Lambda
behind a REST API.

Millions of simulations in microseconds.


### Prerequisites
1. aws account
2. aws cli
3. CMake (version 3.9 or later)
4. git
5. Make
6. zip
7. libcurl4-openssl-dev libssl-dev uuid-dev zlib1g-dev libpulse-dev libcurlpp-dev libcrypto++-dev


## Build the C++ AWS SDK
Run the following commands to build the C++ AWS SDK
```bash
$ git clone https://github.com/aws/aws-sdk-cpp.git
$ cd aws-sdk-cpp
$ mkdir build
$ cd build
$ cmake .. -DBUILD_ONLY="core" -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DCUSTOM_MEMORY_MANAGEMENT=OFF -DCMAKE_INSTALL_PREFIX=~/lambda-install
$ make
$ make install
```

## Build a custom C++ lambda runtime
Run the following commands to build the C++ lambda custom runtime:
```bash
$ git clone https://github.com/press0/aws-lambda-cpp.git
$ cd aws-lambda-cpp
$ mkdir build
$ cd build
$ cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=~/lambda-install
$ make && make install
```

## Build the C++ lambda function
Run the following commands to build the C++ lambda function

```bash
$ cd ../examples/monte-carlo-calculator/
$ mkdir build
$ cd build
$ cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DCMAKE_PREFIX_PATH=~/lambda-install
$ make aws-lambda-package-demo
```

Verify that a file named `demo.zip` was created.

## Create the AWS IAM resources

```
$ cat ../trust-policy.json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": ["lambda.amazonaws.com"]
},
"Action": "sts:AssumeRole"
}
]
}

```

Create the IAM role:
```bash
$ aws iam create-role --role-name lambda-demo --assume-role-policy-document file://../trust-policy.json
```
Note the role Arn returned from the command: <API-ENDPOINT-ARN>

Attach the following policy to allow Lambda to write logs in CloudWatch:
```bash
$ aws iam attach-role-policy --role-name lambda-demo --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
```

## Create the AWS Lambda function
```bash
$ aws lambda create-function --function-name demo --role <API-ENDPOINT-ARN> --runtime provided --timeout 15 --memory-size 128 --handler demo --zip-file fileb://demo.zip

# response
{
"FunctionName": "demo",
"FunctionArn": "arn:aws:lambda:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"Runtime": "provided",
"Role": "arn:aws:iam::167712817792:role/lambda-demo",
"Handler": "demo",
"CodeSize": 13765485,
"Description": "",
"Timeout": 15,
"MemorySize": 128,
"LastModified": "2023-07-10T20:52:40.434+0000",
"CodeSha256": "mN/9DIPLA4ZpftWVBTHaTdZsO3nXk+AVHjfyNRoznWg=",
"Version": "$LATEST",
"TracingConfig": {
"Mode": "PassThrough"
},
"RevisionId": "683bf451-3fb4-4a5e-bdf1-0d5fa1158c41",
"State": "Pending",
"StateReason": "The function is being created.",
"StateReasonCode": "Creating",
"PackageType": "Zip"
}
```

## Update the function, as needed
```bash
aws lambda update-function-code --function-name demo --zip-file fileb://demo.zip


```

## Test the function with the aws cli
```bash
$ aws lambda invoke --function-name demo --cli-binary-format raw-in-base64-out --payload '{}' out.txt

$ cat out.txt
{
"message":"OK num_sims=100000, Call price=10.423098 "
}

```

## Add a Lambda endpoint
TODO: use the aws cli or the aws console

`<API-ENDPOINT-ARN>`

## Test the function with curl

```bash
curl 'https://<API-ENDPOINT-ARN>.lambda-url.us-west-2.on.aws?num_sims=1000000'
# response
{
"message": "OK num_sims=1000000, Call price=10.459100 "
}
```

## check performance in CloudWatch
![CloudWatch ](image/cloudwatch.png)
one tenth of a second


## Test the function at scale
todo




## Delete the function
```bash
aws lambda delete-function --function-name demo
```

## Delete the role
todo: detach policies first
```bash
aws iam delete-role --role-name lambda-demo
```

## Citations
1. https://www.quantstart.com/articles/European-vanilla-option-pricing-with-C-via-Monte-Carlo-methods/

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
78 changes: 78 additions & 0 deletions examples/monte-carlo-calculator/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#include <aws/lambda-runtime/runtime.h>
#include <iomanip>
#include <sstream>
#include <algorithm>
#include <cmath>
#include <iostream>
#include <random>
#include <aws/core/utils/json/JsonSerializer.h>
#include <aws/core/utils/memory/stl/SimpleStringStream.h>

using namespace aws::lambda_runtime;
using namespace Aws::Utils::Json;

// Pricing a European vanilla call option with a Monte Carlo method
double monte_carlo_call_price(const int& num_sims, const double& S, const double& K, const double& r, const double& v, const double& T) {
double s_adjust = S * exp(T*(r-0.5*v*v));
double s_cur = 0.0;
double payoff_sum = 0.0;

std::random_device rd {};
std::mt19937 prng {rd()};
std::normal_distribution<> d {0, 1};

for (int i=0; i<num_sims; i++) {
s_cur = s_adjust * exp(sqrt(v*v*T)*d(prng));
payoff_sum += std::max(s_cur - K, 0.0);
}

return (payoff_sum / static_cast<double>(num_sims)) * exp(-r*T);
}

static invocation_response my_handler(invocation_request const& request) {

// parameter list
int num_sims = 100000; // Number of simulated asset paths; override with query parameter 'num_sims`
double S = 100.0; // Stock price
double K = 100.0; // Strike price
double r = 0.05; // Risk-free rate (5%)
double v = 0.2; // Volatility of the underlying (20%)
double T = 1.0; // One year until expiry

// validate input
if (request.payload.length() > 111111111) {
return invocation_response::failure("error message here"/*error_message*/, "error type here" /*error_type*/);
}

JsonValue json(request.payload);
if (!json.WasParseSuccessful()) {
return invocation_response::failure("Failed to parse input JSON", "InvalidJSON");
}

auto view = json.View();
Aws::SimpleStringStream ss;
ss << "OK ";

if (view.ValueExists("queryStringParameters")) {
auto query_params = view.GetObject("queryStringParameters");
if (query_params.ValueExists("num_sims") && query_params.GetObject("num_sims").IsString()) {
num_sims = stoi(query_params.GetString("num_sims")); //override default
}
}

ss << "num_sims=" << std::to_string(num_sims) << ", ";

double callprice = monte_carlo_call_price(num_sims, S, K, r, v, T);
ss << "Call price=" << std::to_string(callprice) << " ";
std::cout << ss.str() << std::endl;

JsonValue response;
response.WithString("message", ss.str());
return invocation_response::success(response.View().WriteCompact(), "application/json");
}

int main()
{
run_handler(my_handler);
return 0;
}
12 changes: 12 additions & 0 deletions examples/monte-carlo-calculator/trust-policy.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": ["lambda.amazonaws.com"]
},
"Action": "sts:AssumeRole"
}
]
}