diff --git a/.gitignore b/.gitignore index 647f449..bc67547 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,10 @@ tags TODO compile_commands.json .clangd + +#ide +.idea +test.cpp +a.out + +cmake-build-debug 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..63d38d1 --- /dev/null +++ b/examples/monte-carlo-calculator/README.md @@ -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: + +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 0000000..a101a31 Binary files /dev/null and b/examples/monte-carlo-calculator/image/cloudwatch.png differ diff --git a/examples/monte-carlo-calculator/main.cpp b/examples/monte-carlo-calculator/main.cpp new file mode 100644 index 0000000..f822a47 --- /dev/null +++ b/examples/monte-carlo-calculator/main.cpp @@ -0,0 +1,78 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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)) * 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; +} 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" + } + ] +}