-
Notifications
You must be signed in to change notification settings - Fork 91
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
C++ Monte Carlo simulation calculator on AWS Lambda
- Loading branch information
Showing
11 changed files
with
366 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,3 +3,7 @@ tags | |
TODO | ||
compile_commands.json | ||
.clangd | ||
|
||
#ide | ||
.idea | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
cmake_minimum_required(VERSION 3.9) | ||
set(CMAKE_CXX_STANDARD 11) | ||
project(demo LANGUAGES CXX) | ||
find_package(aws-lambda-runtime) | ||
add_executable(${PROJECT_NAME} "main.cpp") | ||
target_link_libraries(${PROJECT_NAME} PRIVATE AWS::aws-lambda-runtime) | ||
target_compile_features(${PROJECT_NAME} PRIVATE "cxx_std_11") | ||
target_compile_options(${PROJECT_NAME} PRIVATE "-Wall" "-Wextra") | ||
|
||
# this line creates a target that packages your binary and zips it up | ||
aws_lambda_package_target(${PROJECT_NAME}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
#include <aws/lambda-runtime/runtime.h> | ||
|
||
using namespace aws::lambda_runtime; | ||
|
||
static invocation_response my_handler(invocation_request const& req) | ||
{ | ||
if (req.payload.length() > 42) { | ||
return invocation_response::failure("error message here"/*error_message*/, | ||
"error type here" /*error_type*/); | ||
} | ||
|
||
return invocation_response::success("json payload here" /*payload*/, | ||
"application/json" /*MIME type*/); | ||
} | ||
|
||
int main() | ||
{ | ||
run_handler(my_handler); | ||
return 0; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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} ) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
# A simple C++ Monte Carlo simulation calculator on AWS Lambda | ||
|
||
Calculate the value of a European vanilla call option in C++ using Monte Carlo simulation. | ||
Deploy the calculator to AWS Lambda. | ||
Invoke the calculator with a REST call. | ||
The number of simulations, `num_sims`, can be overriden with a query parameter. | ||
All other parameters are hard coded. | ||
|
||
|
||
|
||
### 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 | ||
 | ||
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
#include <aws/lambda-runtime/runtime.h> | ||
#include <iomanip> | ||
#include <sstream> | ||
#include <algorithm> // Needed for the "max" function | ||
#include <cmath> | ||
#include <iostream> | ||
#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; | ||
|
||
|
||
|
||
// A simple implementation of the Box-Muller algorithm, used to generate | ||
// gaussian random numbers - necessary for the Monte Carlo method below | ||
// Note that C++11 actually provides std::normal_distribution<> in | ||
// the <random> library, which can be used instead of this function | ||
double gaussian_box_muller() { | ||
double x = 0.0; | ||
double y = 0.0; | ||
double euclid_sq = 0.0; | ||
|
||
// Continue generating two uniform random variables | ||
// until the square of their "euclidean distance" | ||
// is less than unity | ||
do { | ||
x = 2.0 * rand() / static_cast<double>(RAND_MAX)-1; | ||
y = 2.0 * rand() / static_cast<double>(RAND_MAX)-1; | ||
euclid_sq = x*x + y*y; | ||
} while (euclid_sq >= 1.0); | ||
|
||
return x*sqrt(-2*log(euclid_sq)/euclid_sq); | ||
} | ||
|
||
// 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; | ||
|
||
for (int i=0; i<num_sims; i++) { | ||
double gauss_bm = gaussian_box_muller(); | ||
S_cur = S_adjust * exp(sqrt(v*v*T)*gauss_bm); | ||
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; // Option 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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.