From 729094e8c918cf6949d050a2e6868047dbcb84c4 Mon Sep 17 00:00:00 2001 From: "press0@gmail.com" Date: Tue, 11 Jul 2023 00:45:09 -0500 Subject: [PATCH 1/3] C++ Monte Carlo simulation calculator on AWS Lambda --- .github/workflows/workflow.yml | 22 +++ .gitignore | 4 + examples/demo/CMakeLists.txt | 11 ++ examples/demo/main.cpp | 20 ++ .../monte-carlo-calculator/CMakeLists.txt | 14 ++ examples/monte-carlo-calculator/README.md | 173 ++++++++++++++++++ .../image/cloudwatch.png | Bin 0 -> 40964 bytes examples/monte-carlo-calculator/main.cpp | 97 ++++++++++ .../monte-carlo-calculator/trust-policy.json | 12 ++ examples/s3/main.cpp | 3 +- packaging/packager | 17 +- 11 files changed, 366 insertions(+), 7 deletions(-) create mode 100644 examples/demo/CMakeLists.txt create mode 100644 examples/demo/main.cpp create mode 100644 examples/monte-carlo-calculator/CMakeLists.txt create mode 100644 examples/monte-carlo-calculator/README.md create mode 100644 examples/monte-carlo-calculator/image/cloudwatch.png create mode 100644 examples/monte-carlo-calculator/main.cpp create mode 100644 examples/monte-carlo-calculator/trust-policy.json diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 5cadaf0..526b38a 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -46,6 +46,28 @@ jobs: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_CXX_CLANG_TIDY=clang-tidy cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + build-demo: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Install Dependencies + run: sudo apt-get update && sudo apt-get install -y clang-tidy libcurl4-openssl-dev + + - name: Build and install lambda runtime + run: | + mkdir build && cd build + cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=~/lambda-install + make + make install + + - name: Build and package demo project + run: | + cd examples/demo + mkdir build && cd build + cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=~/lambda-install + make + make aws-lambda-package-demo format: diff --git a/.gitignore b/.gitignore index 647f449..717067c 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,7 @@ tags TODO compile_commands.json .clangd + +#ide +.idea + diff --git a/examples/demo/CMakeLists.txt b/examples/demo/CMakeLists.txt new file mode 100644 index 0000000..06aad51 --- /dev/null +++ b/examples/demo/CMakeLists.txt @@ -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}) diff --git a/examples/demo/main.cpp b/examples/demo/main.cpp new file mode 100644 index 0000000..358efe0 --- /dev/null +++ b/examples/demo/main.cpp @@ -0,0 +1,20 @@ +#include + +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; +} diff --git a/examples/monte-carlo-calculator/CMakeLists.txt b/examples/monte-carlo-calculator/CMakeLists.txt new file mode 100644 index 0000000..c76582a --- /dev/null +++ b/examples/monte-carlo-calculator/CMakeLists.txt @@ -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} ) diff --git a/examples/monte-carlo-calculator/README.md b/examples/monte-carlo-calculator/README.md new file mode 100644 index 0000000..7436916 --- /dev/null +++ b/examples/monte-carlo-calculator/README.md @@ -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: + +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 --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 + +`` + +## Test the function with curl + +```bash +curl 'https://.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/ + diff --git a/examples/monte-carlo-calculator/image/cloudwatch.png b/examples/monte-carlo-calculator/image/cloudwatch.png new file mode 100644 index 0000000000000000000000000000000000000000..a101a3129c1c3501d9c02ef6784c04214e53a356 GIT binary patch literal 40964 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 MQc#yKlQj$c4-}YEK>z>% literal 0 HcmV?d00001 diff --git a/examples/monte-carlo-calculator/main.cpp b/examples/monte-carlo-calculator/main.cpp new file mode 100644 index 0000000..18dfab7 --- /dev/null +++ b/examples/monte-carlo-calculator/main.cpp @@ -0,0 +1,97 @@ +#include +#include +#include +#include // Needed for the "max" function +#include +#include +#include +#include + +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 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(RAND_MAX)-1; + y = 2.0 * rand() / static_cast(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)) * 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; +} diff --git a/examples/monte-carlo-calculator/trust-policy.json b/examples/monte-carlo-calculator/trust-policy.json new file mode 100644 index 0000000..fa87625 --- /dev/null +++ b/examples/monte-carlo-calculator/trust-policy.json @@ -0,0 +1,12 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": ["lambda.amazonaws.com"] + }, + "Action": "sts:AssumeRole" + } + ] +} diff --git a/examples/s3/main.cpp b/examples/s3/main.cpp index a389121..892560c 100644 --- a/examples/s3/main.cpp +++ b/examples/s3/main.cpp @@ -73,8 +73,7 @@ int main() config.region = Aws::Environment::GetEnv("AWS_REGION"); config.caFile = "/etc/pki/tls/certs/ca-bundle.crt"; - auto credentialsProvider = Aws::MakeShared(TAG); - S3::S3Client client(credentialsProvider, config); + S3::S3Client client(config); auto handler_fn = [&client](aws::lambda_runtime::invocation_request const& req) { return my_handler(req, client); }; diff --git a/packaging/packager b/packaging/packager index 4e8759e..0c4e749 100755 --- a/packaging/packager +++ b/packaging/packager @@ -56,7 +56,7 @@ if ! type zip > /dev/null 2>&1; then exit 1 fi -function find_so_files() { +function pluck_so_files() { sed -E '/\.so$|\.so\.[0-9]+$/!d' } @@ -64,7 +64,7 @@ function package_libc_alpine() { # -F matches a fixed string rather than a regex (grep that comes with busybox doesn't know --fixed-strings) if grep -F "Alpine Linux" < /etc/os-release > /dev/null; then if type apk > /dev/null 2>&1; then - apk info --contents musl 2>/dev/null | find_so_files | sed 's/^/\//' + apk info --contents musl 2>/dev/null | pluck_so_files | sed 's/^/\//' fi fi } @@ -72,20 +72,27 @@ function package_libc_alpine() { function package_libc_pacman() { if grep --extended-regexp "Arch Linux|Manjaro Linux" < /etc/os-release > /dev/null 2>&1; then if type pacman > /dev/null 2>&1; then - pacman --query --list --quiet glibc | find_so_files + pacman --query --list --quiet glibc | pluck_so_files fi fi } function package_libc_dpkg() { if type dpkg-query > /dev/null 2>&1; then - dpkg-query --listfiles libc6:$(dpkg --print-architecture) | find_so_files + architecture=$(dpkg --print-architecture) + if [[ $(dpkg-query --listfiles libc6:$architecture | wc -l) -gt 0 ]]; then + dpkg-query --listfiles libc6:$architecture | pluck_so_files + fi fi } function package_libc_rpm() { + arch=$(uname -m) + if type rpm > /dev/null 2>&1; then - rpm --query --list glibc.$(uname -m) | find_so_files + if [[ $(rpm --query --list glibc.$arch | wc -l) -gt 1 ]]; then + rpm --query --list glibc.$arch | pluck_so_files + fi fi } From a8b7d1b2462fcc3b27d99dd1b308985a1e3683de Mon Sep 17 00:00:00 2001 From: "press0@gmail.com" Date: Wed, 12 Jul 2023 20:27:08 -0500 Subject: [PATCH 2/3] std::normal_distribution --- .gitignore | 3 +- examples/monte-carlo-calculator/main.cpp | 41 +++++++----------------- 2 files changed, 13 insertions(+), 31 deletions(-) diff --git a/.gitignore b/.gitignore index 717067c..cf30922 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ compile_commands.json #ide .idea - +test.cpp +a.out diff --git a/examples/monte-carlo-calculator/main.cpp b/examples/monte-carlo-calculator/main.cpp index 18dfab7..f822a47 100644 --- a/examples/monte-carlo-calculator/main.cpp +++ b/examples/monte-carlo-calculator/main.cpp @@ -1,48 +1,29 @@ #include #include #include -#include // Needed for the "max" function +#include #include #include +#include #include #include 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 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(RAND_MAX)-1; - y = 2.0 * rand() / static_cast(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 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)) * exp(-r*T); @@ -52,7 +33,7 @@ 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 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%) From f8803bc790dae8e38e63692cfda6eba1b91573a3 Mon Sep 17 00:00:00 2001 From: "press0@gmail.com" Date: Fri, 21 Jul 2023 20:08:56 -0500 Subject: [PATCH 3/3] update overview --- .gitignore | 2 ++ examples/monte-carlo-calculator/README.md | 11 ++++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index cf30922..bc67547 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ compile_commands.json .idea test.cpp a.out + +cmake-build-debug diff --git a/examples/monte-carlo-calculator/README.md b/examples/monte-carlo-calculator/README.md index 7436916..63d38d1 100644 --- a/examples/monte-carlo-calculator/README.md +++ b/examples/monte-carlo-calculator/README.md @@ -1,11 +1,12 @@ # 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. +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