Skip to content

An automated investment system that runs as an AWS Lambda function or a local console application to invest available cash in a Charles Schwab brokerage account according to a predefined allocation strategy.

Notifications You must be signed in to change notification settings

burekichevapi/AutoInvestLambda

Repository files navigation

AutoInvest

An automated investment system that runs as an AWS Lambda function or a local console application to invest available cash in a Charles Schwab brokerage account according to a predefined allocation strategy.

Overview

AutoInvest monitors a Schwab brokerage account and automatically invests available cash into a portfolio of ETFs based on user-defined allocation percentages. The system is designed for resilience and safety, executing trades only when specific conditions are met, such as open market hours and sufficient funds.

Built on clean architecture principles, the solution is decoupled into distinct layers for domain logic, and application services, ensuring high testability, maintainability, and deployment flexibility.

Key Features

  • Automated & Scheduled Investing: Runs on a schedule via AWS EventBridge (or locally) to execute investment strategies without manual intervention.
  • Strategic Portfolio Allocation: Invests based on a simple, percentage-based allocation string (e.g., "SPLG:60,SCHG:40").
  • Maximized Cash Utilization: The OrderBook.Generate() method iteratively calculates and places orders to use the maximum available cash, leaving only a small, uninvestable remainder.
  • Robust Error Handling & Notifications: Sends detailed success, failure, or unfilled orders email notifications via AWS SES using HTML templates.
  • Secure Credential Management: All sensitive data (Client ID, Client Secret, Refresh Token) is securely stored and retrieved from AWS Secrets Manager.
  • Automated OAuth2 Refresh: Includes a standalone Node.js tool using Puppeteer to handle Schwab's mandatory interactive login for refreshing authentication tokens.
  • Dual Deployment Targets: Can be deployed as a serverless AWS Lambda function or run as a local console application that can be set on a schedule using Windows Scheduler.
  • Configuration Validation: Performs rigorous startup checks via ConfigurationValidator to validate all settings, preventing runtime errors from misconfiguration.

System Architecture

The solution is logically divided into several projects, each with a distinct responsibility, following the principles of Clean Architecture.

graph TB
    subgraph "Execution Environments"
        direction LR
        Lambda["src/AutoInvest (AWS Lambda)"]
        Local["AutoInvestLocal (Console)"]
        Auth["AutoInvest.AuthAutomation (Node.js)"]
    end

    subgraph "Application & Domain Layers"
        direction TB
        Services["AutoInvest.Services"]
        Core["AutoInvest.Core"]
    end

    subgraph "External Services"
        direction LR
        Schwab["Charles Schwab API"]
        Secrets["AWS Secrets Manager"]
        SES["AWS SES"]
    end

    Lambda --> Services
    Local --> Services
    Auth -.-> Secrets
    Auth -.-> SES

    Services --> Core
    Services --> Schwab
    Services --> Secrets
    Services --> SES
Loading

Project Structure

Project Description
src/AutoInvest AWS Lambda entry point with Functions.cs and Startup.cs for dependency injection
AutoInvestLocal Console application for local development, testing, or running on a schedule using Windows Scheduler
AutoInvest.Services Application services including InvestmentOrchestrator, Schwab API clients, and AWS integrations
AutoInvest.Core Domain logic including OrderBook, InvestmentCalculator, AllocationStrategy, and email builders
AutoInvest.AuthAutomation Node.js/Puppeteer tool for automated Schwab OAuth2 token refresh
AutoInvest.Core.Tests Unit tests for domain logic
AutoInvest.Services.Tests Unit tests for application services

Execution Flow

graph TD
    A[Start] --> B{Validate Config};
    B --> |Invalid| Z[Exit & Log Error];
    B --> |Valid| C[Check Market Status];
    C --> |Closed| D[Exit & Log];
    C --> |Open| E[Get Account Hash & Details];
    E --> F{Cash Balance > 0?};
    F --> |No| G[Exit & Log];
    F --> |Yes| H[Get Quotes for Allocation Symbols];
    H --> I[Generate OrderBook];
    I --> J{Any Orders to Place?};
    J --> |No| K[Exit & Log];
    J --> |Yes| L[Submit All Orders];
    L --> M[Check for Unfilled Orders];
    M --> N{Any Unfilled?};
    N --> |Yes| O[Send Unfilled Orders Email];
    N --> |No| P[Get Updated Account Balance];
    O --> P;
    P --> Q{Any Filled Orders?};
    Q --> |Yes| R[Send Success Email];
    Q --> |No| S[Log - No Success Email];
    R --> Y[End];
    S --> Y;
    K --> Y;
    G --> Y;
    D --> Y;
    Z --> Y;
Loading

Setup and Configuration

1. AWS Secrets Manager

Create a secret in AWS Secrets Manager to store all sensitive credentials.

  • Secret name: Name for the configuration (referenced by Aws:SecretsName).
  • Key/value pairs:
    {
      "REFRESH_TOKEN": "schwab-refresh-token", // Updated by AutoInvest.AuthAutomation
      "CLIENT_ID": "schwab-client-id",
      "CLIENT_SECRET": "schwab-client-secret"
    }

    Note: The REFRESH_TOKEN is automatically updated by the AutoInvest.AuthAutomation tool.

2. IAM Role (for Lambda)

The Lambda execution role needs permissions to access:

  • AWS Secrets Manager - Read/write access for token management
  • AWS SES - Send email permissions for notifications
  • CloudWatch Logs - For logging

3. Application Configuration

Configure the application using environment variables (for AWS Lambda) or user secrets (for local development).

{
  "Schwab": {
    "MarketDataApiUrl": "https://api.schwabapi.com/marketdata/v1",
    "TradingApiUrl": "https://api.schwabapi.com/trader/v1",
    "AuthorizationUrl": "https://api.schwabapi.com/v1/oauth/authorize",
    "TokenUrl": "https://api.schwabapi.com/v1/oauth/token"
  },
  "Trading": {
    "AllocationsString": "VOO:80,VXUS:20"
  },
  "AWS": {
    "Region": "aws-region",
    "Profile": "aws-profile-name",
    "SecretsName": "aws-secrets-name",
    "ToEmail": "to-email@example.com",
    "FromEmail": "from-email@example.com"
  }
}
Setting Description
Schwab:MarketDataApiUrl Schwab Market Data API endpoint for quotes and market hours
Schwab:TradingApiUrl Schwab Trading API endpoint for account and order operations
Schwab:TokenUrl OAuth2 token exchange endpoint
Trading:AllocationsString Comma-separated symbol:percentage pairs (must sum to 100)
Aws:SecretsName Name of the secret in AWS Secrets Manager
Aws:ToEmail Recipient email for notifications (must be SES verified)
Aws:FromEmail Sender email for notifications (must be SES verified)

Deployment and Scheduling

AWS Lambda

  1. Build, Package, and Deploy:

    .\upload_lambda.ps1 -FunctionName "YourFunctionName"

    This script builds the project, creates a deployment zip, and uploads it using AWS CLI.

  2. Configure Environment Variables in AWS: Set the configuration values listed above in the Lambda function's configuration section.

  3. Schedule: Create an AWS EventBridge (CloudWatch Events) rule to trigger the Lambda function on a schedule. A cron expression is recommended to run during market hours.

    • Example Cron (Runs at 10:00 AM UTC on Fridays): cron(0 10 ? * FRI *)

Local Development

Run the console application locally for testing:

cd AutoInvestLocal
dotnet run

Note: Ensure user secrets are configured with dotnet user-secrets set "Key" "Value" for local development.

Authentication (AutoInvest.AuthAutomation)

Because Schwab's refresh_token expires, you must run the AutoInvest.AuthAutomation tool once a week to get a new one. This automation uses Puppeteer to simulate a browser login to Schwab and retrieve a new refresh_token, storing it in AWS Secrets Manager. It also sends email notifications if a failure occurs.

Setup

  1. Install Node.js and npm.
  2. Navigate to the AutoInvest.AuthAutomation directory.
  3. Run npm install.
  4. Create a .env file based on .example.env:
# Schwab Environment Variables
CLIENT_ID={Schwab Client ID}
CLIENT_SECRET={Schwab Client Secret}
REDIRECT_URI={Schwab Redirect URI}
LOGIN_ID={Schwab Login ID}
PASSWORD={Schwab Password}

# AWS Environment Variables
AWS_ACCESS_KEY_ID={AWS Access Key ID}
AWS_SECRET_ACCESS_KEY={AWS Secret Access Key}
AWS_REGION={AWS Region}
SECRET_NAME={AWS Secret Name}
SES_FROM_EMAIL={SES Verified From Email}
SES_TO_EMAIL={SES Verified To Email}

# Puppeteer Environment Variable
BROWSER_EXECUTABLE_PATH={Path to Chrome}

Running the Tool

When running this the first time, you might need to manually interact with the browser to complete any multi-factor authentication steps; set the headless parameter to false in automationUser.js. The timeout is set to 1 minute 30 seconds to allow for manual interaction on the first run.

📢 When going through the initial setup to choose which accounts the refresh token will be allowed to work with, ONLY SELECT ONE ACCOUNT to auto invest in. This application DOES NOT SUPPORT multiple accounts.

  • Run the script from the command line:
    node auth.js
  • Screenshots are automatically saved to a date-organized folder under screenshots/ for debugging.
  • Logs are written to the logs/ directory using Winston with daily rotation.
  • It is recommended to schedule this script to run once a week automatically on a local machine where you can access it directly if MFA intervention is required.

About

An automated investment system that runs as an AWS Lambda function or a local console application to invest available cash in a Charles Schwab brokerage account according to a predefined allocation strategy.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published