Skip to content

Commit 729094e

Browse files
committed
C++ Monte Carlo simulation calculator on AWS Lambda
1 parent fa438eb commit 729094e

File tree

11 files changed

+366
-7
lines changed

11 files changed

+366
-7
lines changed

.github/workflows/workflow.yml

+22
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,28 @@ jobs:
4646
cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_CXX_CLANG_TIDY=clang-tidy
4747
cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
4848
49+
build-demo:
50+
runs-on: ubuntu-latest
51+
steps:
52+
- uses: actions/checkout@v3
53+
54+
- name: Install Dependencies
55+
run: sudo apt-get update && sudo apt-get install -y clang-tidy libcurl4-openssl-dev
56+
57+
- name: Build and install lambda runtime
58+
run: |
59+
mkdir build && cd build
60+
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=~/lambda-install
61+
make
62+
make install
63+
64+
- name: Build and package demo project
65+
run: |
66+
cd examples/demo
67+
mkdir build && cd build
68+
cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=~/lambda-install
69+
make
70+
make aws-lambda-package-demo
4971
5072

5173
format:

.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,7 @@ tags
33
TODO
44
compile_commands.json
55
.clangd
6+
7+
#ide
8+
.idea
9+

examples/demo/CMakeLists.txt

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
cmake_minimum_required(VERSION 3.9)
2+
set(CMAKE_CXX_STANDARD 11)
3+
project(demo LANGUAGES CXX)
4+
find_package(aws-lambda-runtime)
5+
add_executable(${PROJECT_NAME} "main.cpp")
6+
target_link_libraries(${PROJECT_NAME} PRIVATE AWS::aws-lambda-runtime)
7+
target_compile_features(${PROJECT_NAME} PRIVATE "cxx_std_11")
8+
target_compile_options(${PROJECT_NAME} PRIVATE "-Wall" "-Wextra")
9+
10+
# this line creates a target that packages your binary and zips it up
11+
aws_lambda_package_target(${PROJECT_NAME})

examples/demo/main.cpp

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#include <aws/lambda-runtime/runtime.h>
2+
3+
using namespace aws::lambda_runtime;
4+
5+
static invocation_response my_handler(invocation_request const& req)
6+
{
7+
if (req.payload.length() > 42) {
8+
return invocation_response::failure("error message here"/*error_message*/,
9+
"error type here" /*error_type*/);
10+
}
11+
12+
return invocation_response::success("json payload here" /*payload*/,
13+
"application/json" /*MIME type*/);
14+
}
15+
16+
int main()
17+
{
18+
run_handler(my_handler);
19+
return 0;
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
cmake_minimum_required(VERSION 3.5)
2+
set(CMAKE_CXX_STANDARD 11)
3+
4+
project(demo LANGUAGES CXX)
5+
6+
find_package(aws-lambda-runtime REQUIRED)
7+
find_package(AWSSDK COMPONENTS core)
8+
find_package(ZLIB)
9+
10+
add_executable(${PROJECT_NAME} "main.cpp")
11+
target_link_libraries(${PROJECT_NAME} PUBLIC AWS::aws-lambda-runtime ${AWSSDK_LINK_LIBRARIES} )
12+
13+
# this line creates a target that packages your binary and zips it up
14+
aws_lambda_package_target(${PROJECT_NAME} )
+173
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
# A simple C++ Monte Carlo simulation calculator on AWS Lambda
2+
3+
Calculate the value of a European vanilla call option in C++ using Monte Carlo simulation.
4+
Deploy the calculator to AWS Lambda.
5+
Invoke the calculator with a REST call.
6+
The number of simulations, `num_sims`, can be overriden with a query parameter.
7+
All other parameters are hard coded.
8+
9+
10+
11+
### Prerequisites
12+
1. aws account
13+
2. aws cli
14+
3. CMake (version 3.9 or later)
15+
4. git
16+
5. Make
17+
6. zip
18+
7. libcurl4-openssl-dev libssl-dev uuid-dev zlib1g-dev libpulse-dev libcurlpp-dev libcrypto++-dev
19+
20+
21+
## Build the C++ AWS SDK
22+
Run the following commands to build the C++ AWS SDK
23+
```bash
24+
$ git clone https://github.com/aws/aws-sdk-cpp.git
25+
$ cd aws-sdk-cpp
26+
$ mkdir build
27+
$ cd build
28+
$ cmake .. -DBUILD_ONLY="core" -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DCUSTOM_MEMORY_MANAGEMENT=OFF -DCMAKE_INSTALL_PREFIX=~/lambda-install
29+
$ make
30+
$ make install
31+
```
32+
33+
## Build a custom C++ lambda runtime
34+
Run the following commands to build the C++ lambda custom runtime:
35+
```bash
36+
$ git clone https://github.com/press0/aws-lambda-cpp.git
37+
$ cd aws-lambda-cpp
38+
$ mkdir build
39+
$ cd build
40+
$ cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=~/lambda-install
41+
$ make && make install
42+
```
43+
44+
## Build the C++ lambda function
45+
Run the following commands to build the C++ lambda function
46+
47+
```bash
48+
$ cd ../examples/monte-carlo-calculator/
49+
$ mkdir build
50+
$ cd build
51+
$ cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DCMAKE_PREFIX_PATH=~/lambda-install
52+
$ make aws-lambda-package-demo
53+
```
54+
55+
Verify that a file named `demo.zip` was created.
56+
57+
## Create the AWS IAM resources
58+
59+
```
60+
$ cat ../trust-policy.json
61+
{
62+
"Version": "2012-10-17",
63+
"Statement": [
64+
{
65+
"Effect": "Allow",
66+
"Principal": {
67+
"Service": ["lambda.amazonaws.com"]
68+
},
69+
"Action": "sts:AssumeRole"
70+
}
71+
]
72+
}
73+
74+
```
75+
76+
Create the IAM role:
77+
```bash
78+
$ aws iam create-role --role-name lambda-demo --assume-role-policy-document file://../trust-policy.json
79+
```
80+
Note the role Arn returned from the command: <API-ENDPOINT-ARN>
81+
82+
Attach the following policy to allow Lambda to write logs in CloudWatch:
83+
```bash
84+
$ aws iam attach-role-policy --role-name lambda-demo --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
85+
```
86+
87+
## Create the AWS Lambda function
88+
```bash
89+
$ 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
90+
91+
# response
92+
{
93+
"FunctionName": "demo",
94+
"FunctionArn": "arn:aws:lambda:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
95+
"Runtime": "provided",
96+
"Role": "arn:aws:iam::167712817792:role/lambda-demo",
97+
"Handler": "demo",
98+
"CodeSize": 13765485,
99+
"Description": "",
100+
"Timeout": 15,
101+
"MemorySize": 128,
102+
"LastModified": "2023-07-10T20:52:40.434+0000",
103+
"CodeSha256": "mN/9DIPLA4ZpftWVBTHaTdZsO3nXk+AVHjfyNRoznWg=",
104+
"Version": "$LATEST",
105+
"TracingConfig": {
106+
"Mode": "PassThrough"
107+
},
108+
"RevisionId": "683bf451-3fb4-4a5e-bdf1-0d5fa1158c41",
109+
"State": "Pending",
110+
"StateReason": "The function is being created.",
111+
"StateReasonCode": "Creating",
112+
"PackageType": "Zip"
113+
}
114+
```
115+
116+
## Update the function, as needed
117+
```bash
118+
aws lambda update-function-code --function-name demo --zip-file fileb://demo.zip
119+
120+
121+
```
122+
123+
## Test the function with the aws cli
124+
```bash
125+
$ aws lambda invoke --function-name demo --cli-binary-format raw-in-base64-out --payload '{}' out.txt
126+
127+
$ cat out.txt
128+
{
129+
"message":"OK num_sims=100000, Call price=10.423098 "
130+
}
131+
132+
```
133+
134+
## Add a Lambda endpoint
135+
TODO: use the aws cli or the aws console
136+
137+
`<API-ENDPOINT-ARN>`
138+
139+
## Test the function with curl
140+
141+
```bash
142+
curl 'https://<API-ENDPOINT-ARN>.lambda-url.us-west-2.on.aws?num_sims=1000000'
143+
# response
144+
{
145+
"message": "OK num_sims=1000000, Call price=10.459100 "
146+
}
147+
```
148+
149+
## check performance in CloudWatch
150+
![CloudWatch ](image/cloudwatch.png)
151+
one tenth of a second
152+
153+
154+
## Test the function at scale
155+
todo
156+
157+
158+
159+
160+
## Delete the function
161+
```bash
162+
aws lambda delete-function --function-name demo
163+
```
164+
165+
## Delete the role
166+
todo: detach policies first
167+
```bash
168+
aws iam delete-role --role-name lambda-demo
169+
```
170+
171+
## Citations
172+
1. https://www.quantstart.com/articles/European-vanilla-option-pricing-with-C-via-Monte-Carlo-methods/
173+
Loading
+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
#include <aws/lambda-runtime/runtime.h>
2+
#include <iomanip>
3+
#include <sstream>
4+
#include <algorithm> // Needed for the "max" function
5+
#include <cmath>
6+
#include <iostream>
7+
#include <aws/core/utils/json/JsonSerializer.h>
8+
#include <aws/core/utils/memory/stl/SimpleStringStream.h>
9+
10+
using namespace aws::lambda_runtime;
11+
using namespace Aws::Utils::Json;
12+
13+
14+
15+
// A simple implementation of the Box-Muller algorithm, used to generate
16+
// gaussian random numbers - necessary for the Monte Carlo method below
17+
// Note that C++11 actually provides std::normal_distribution<> in
18+
// the <random> library, which can be used instead of this function
19+
double gaussian_box_muller() {
20+
double x = 0.0;
21+
double y = 0.0;
22+
double euclid_sq = 0.0;
23+
24+
// Continue generating two uniform random variables
25+
// until the square of their "euclidean distance"
26+
// is less than unity
27+
do {
28+
x = 2.0 * rand() / static_cast<double>(RAND_MAX)-1;
29+
y = 2.0 * rand() / static_cast<double>(RAND_MAX)-1;
30+
euclid_sq = x*x + y*y;
31+
} while (euclid_sq >= 1.0);
32+
33+
return x*sqrt(-2*log(euclid_sq)/euclid_sq);
34+
}
35+
36+
// Pricing a European vanilla call option with a Monte Carlo method
37+
double monte_carlo_call_price(const int& num_sims, const double& S, const double& K, const double& r, const double& v, const double& T) {
38+
double S_adjust = S * exp(T*(r-0.5*v*v));
39+
double S_cur = 0.0;
40+
double payoff_sum = 0.0;
41+
42+
for (int i=0; i<num_sims; i++) {
43+
double gauss_bm = gaussian_box_muller();
44+
S_cur = S_adjust * exp(sqrt(v*v*T)*gauss_bm);
45+
payoff_sum += std::max(S_cur - K, 0.0);
46+
}
47+
48+
return (payoff_sum / static_cast<double>(num_sims)) * exp(-r*T);
49+
}
50+
51+
static invocation_response my_handler(invocation_request const& request) {
52+
53+
// parameter list
54+
int num_sims = 100000; // Number of simulated asset paths; override with query parameter 'num_sims`
55+
double S = 100.0; // Option price
56+
double K = 100.0; // Strike price
57+
double r = 0.05; // Risk-free rate (5%)
58+
double v = 0.2; // Volatility of the underlying (20%)
59+
double T = 1.0; // One year until expiry
60+
61+
// validate input
62+
if (request.payload.length() > 111111111) {
63+
return invocation_response::failure("error message here"/*error_message*/, "error type here" /*error_type*/);
64+
}
65+
66+
JsonValue json(request.payload);
67+
if (!json.WasParseSuccessful()) {
68+
return invocation_response::failure("Failed to parse input JSON", "InvalidJSON");
69+
}
70+
71+
auto view = json.View();
72+
Aws::SimpleStringStream ss;
73+
ss << "OK ";
74+
75+
if (view.ValueExists("queryStringParameters")) {
76+
auto query_params = view.GetObject("queryStringParameters");
77+
if (query_params.ValueExists("num_sims") && query_params.GetObject("num_sims").IsString()) {
78+
num_sims = stoi(query_params.GetString("num_sims")); //override default
79+
}
80+
}
81+
82+
ss << "num_sims=" << std::to_string(num_sims) << ", ";
83+
84+
double callprice = monte_carlo_call_price(num_sims, S, K, r, v, T);
85+
ss << "Call price=" << std::to_string(callprice) << " ";
86+
std::cout << ss.str() << std::endl;
87+
88+
JsonValue response;
89+
response.WithString("message", ss.str());
90+
return invocation_response::success(response.View().WriteCompact(), "application/json");
91+
}
92+
93+
int main()
94+
{
95+
run_handler(my_handler);
96+
return 0;
97+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"Version": "2012-10-17",
3+
"Statement": [
4+
{
5+
"Effect": "Allow",
6+
"Principal": {
7+
"Service": ["lambda.amazonaws.com"]
8+
},
9+
"Action": "sts:AssumeRole"
10+
}
11+
]
12+
}

examples/s3/main.cpp

+1-2
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,7 @@ int main()
7373
config.region = Aws::Environment::GetEnv("AWS_REGION");
7474
config.caFile = "/etc/pki/tls/certs/ca-bundle.crt";
7575

76-
auto credentialsProvider = Aws::MakeShared<Aws::Auth::EnvironmentAWSCredentialsProvider>(TAG);
77-
S3::S3Client client(credentialsProvider, config);
76+
S3::S3Client client(config);
7877
auto handler_fn = [&client](aws::lambda_runtime::invocation_request const& req) {
7978
return my_handler(req, client);
8079
};

0 commit comments

Comments
 (0)