Skip to content

About

Hands-off algorithmic stock trading on AWS Lambda — ~$0.15/month. Alpaca + SAM + Python.

Topics

Resources

Contributing

Security policy

Stars

86 stars

Watchers

1 watching

Forks

Latest commit

 

History

116 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

⚡ LambdaForge

Serverless algorithmic trading — forged on AWS Lambda.

CI Python AWS SAM Ruff License: MIT Cost

No servers. No babysitting. No cloud bill.

Quick Start · Strategies · Architecture · Contributing


What is LambdaForge?

LambdaForge is a production-grade, fully serverless algorithmic stock trading bot. It runs entirely on AWS Lambda — no always-on server, no manual babysitting. EventBridge wakes it up on schedule, it scans a 200+ symbol watchlist, enforces a set of risk rules on every buy, places trades through the Alpaca API, and emails you a digest. When the market closes, it goes back to sleep.

Paper trading is free on Alpaca, and AWS costs about $0.15/month per stack. Everything that runs code stays inside AWS's always-free allowances; the few cents are storage.

                    ┌─────────────────────────────────────┐
  Market opens      │         AWS EventBridge             │
  09:30 ET ────────►│  Triggers Lambda on schedule        │
                    └──────────────┬──────────────────────┘
                                   │
                    ┌──────────────▼──────────────────────┐
                    │        AWS Lambda (ARM64)           │
                    │  • Scans 200+ symbols               │
                    │  • Runs 3 of 8 built-in strategies  │
                    │  • Enforces 6 risk rules            │
                    │  • Places orders via Alpaca         │
                    │  • Sends email digest               │
                    └──────────────┬──────────────────────┘
                                   │
          ┌───────────────────────┼──────────────────────┐
          │                       │                      │
   ┌──────▼──────┐        ┌───────▼──────┐       ┌───────▼──────┐
   │  S3 Bucket  │        │  SSM Params  │       │  Alpaca API  │
   │  trades.db  │        │  (no deploy  │       │  paper/live  │
   │  persisted  │        │   needed)    │       │              │
   └─────────────┘        └──────────────┘       └──────────────┘

Prerequisites

Requirement Notes
🦙 Alpaca account Free. Paper trading requires no deposit. Live trading requires a funded account.
☁️ AWS account About $0.15/month per stack at paper trading volumes (see Cost)
🐳 Docker Required to build Lambda container images
🐍 Python 3.9+ Lambda runtime constraint
🔧 AWS SAM CLI For building and deploying
⚙️ AWS CLI Configured with aws configure

💸 Cost — About $0.15/month per stack

One of LambdaForge's biggest advantages: it costs almost nothing to run. Compute is free at this volume; what you pay for is storage.

AWS Service What LambdaForge uses (per stack) Why it's cheap Monthly
Lambda 6 arm64 functions, ~45K invocations/month at the default 1-minute interval Always-free 1M requests + 400K GB-s $0
EventBridge 5 scheduled rules: 4 New York-time crons + 1 rate rule Free at this volume $0
SSM Parameter Store + KMS 13 Standard parameters; the Alpaca keys are SecureStrings on the AWS-managed key, decrypted once per container and then cached Standard parameters are free; a few thousand decrypts a month against KMS's 20K free requests $0
SNS + SES Alerts plus hourly, daily and weekly digest emails, a few hundred a month Free at this volume $0
S3 2–12 MB trades.db, re-uploaded up to once a minute; noncurrent versions expire after 3 days (5 newest kept); weekly audit exports go straight to Glacier Deep Archive for 7 years Lifecycle rules keep the version history bounded ~$0.07
ECR The arm64 container image Lifecycle policy keeps the 10 newest images and expires untagged ones after 3 days ~$0.08
Monthly total per stack → ~$0.15

Running 3 stacks (paper + experimental + live-config) in parallel costs ~$0.45/month. Still cheaper than a cup of coffee.

How these numbers were made. They're projections from the September 2026 bill with both lifecycle rules in place, not a closed invoice. Before those rules, S3 kept every minute's copy of trades.db and ECR kept every image ever pushed, and three stacks cost about $5.70/month. S3 and ECR are priced as paid here because the 12-month free tier that used to cover them was replaced for new AWS accounts in July 2025. The Lambda free tier is per account, so it's shared with anything else you run there.

Compare this to a VPS or dedicated server which would run $5–$50/month for equivalent uptime.


✨ Features

Feature Details
🧠 7 trading strategies (3 enabled by default) MACD, Bollinger Squeeze, Z-Score Mean Reversion (enabled); RSI Confluence, EMA Crossover + ADX, RSI + MACD Confluence, Relative Strength vs SPY (available via config.json)
🛡️ Risk manager Confidence gate, daily loss limit, max positions (counting buys that haven't filled yet), concentration cap, stop-loss enforcement, position sizing from cash only (no margin), and no re-buying a symbol sold earlier the same day
📈 Trailing stop-loss Hybrid trailing stop — the tighter of 5% below the high-water mark or 2×ATR — re-evaluated every minute during market hours against the last trade price, never moves down. A stop fires when two consecutive runs agree, or at once if the price is more than 5% through it. Exits sell what the broker holds and never go short.
🔴 Kill switch One command liquidates everything and halts trading instantly
📊 Multi-stack Paper and live run as independent stacks — no shared state
📧 Email digests Hourly trade summaries, daily P&L snapshots, weekly performance reports
⚙️ Zero-redeploy config Tune risk params and flip the kill switch via SSM — no deploy needed
🔒 One position per symbol A symbol already held, or already being bought, isn't bought again. Set allow_pyramiding to add to positions instead, up to the concentration cap
🕐 Market hours guard Checks Alpaca's market clock on every run — skips holidays, half-days, and anything outside 09:30–16:00 ET (weekday/time heuristic as fallback)
🏷️ Environment tagging Email subjects prefixed [PAPER], [LIVE], [BOT-2] for easy inbox filtering

🚀 Quick Start

1. Clone and install

git clone https://github.com/vishwakt/LambdaForge.git
cd LambdaForge
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt -r requirements-dev.txt

2. Configure environment

cp .env.example .env
# Edit .env — add your Alpaca paper trading API keys

3. Bootstrap SSM parameters

# Core credentials (required)
aws ssm put-parameter --name "/stock-bot/alpaca_api_key" \
  --value "YOUR_ALPACA_KEY" --type SecureString

aws ssm put-parameter --name "/stock-bot/alpaca_secret_key" \
  --value "YOUR_ALPACA_SECRET" --type SecureString

aws ssm put-parameter --name "/stock-bot/notification_email" \
  --value "you@example.com" --type String

aws ssm put-parameter --name "/stock-bot/trading_mode" \
  --value "paper" --type String

4. Verify your SES email

aws ses verify-email-identity --email-address you@example.com
# Check your inbox and click the verification link

5. Deploy

cp samconfig.toml.example samconfig.toml
sam build
sam deploy --guided    # First time — sets up ECR repos and S3 bucket
sam deploy             # Subsequent deploys

6. Subscribe to alerts

If you passed NotificationEmail at deploy time, SAM already created the subscription — confirm it from the email SNS sends you. Otherwise: SNS → Topics → the topic whose display name is "Stock Trading Bot Alerts (paper)" → Create subscription → Email.

7. Verify everything is working

./scripts/test-lambdas.sh stock-trading-bot

📐 Trading Strategies

All strategies implement a common interface — they receive historical OHLCV bars and return a signal with action, confidence, stop_loss, and take_profit.

Strategy Entry Signal Stop Loss Take Profit Best for
MACD Crossover 12/26 EMA bullish cross + signal line 3% below entry 6% above Trending markets
Bollinger Squeeze Band compression → breakout + volume Middle band (20-SMA) 1:1 measured move above entry Volatility expansion
Z-Score Mean Reversion 50-day Z-score < −2 (oversold) 1 std-dev below entry Rolling mean (Z ≈ 0); SELL signal at Z > +2 Range-bound markets
RSI Confluence RSI oversold + uptrend + volume 4% below entry 8% above Momentum dips
EMA Crossover + ADX 9/21 EMA cross + ADX > 25 3% below entry 6% above Strong trends
RSI + MACD Confluence RSI oversold + MACD bullish cross 4% below entry 8% above Reversal signals
Relative Strength vs SPY Outperforming SPY on rolling basis 5% below entry 10% above Sector leaders

Trailing stops are managed centrally — once a position is open, the stop price ratchets up automatically as price rises. A strategy that owns its exit rules can opt out with uses_trailing_stop = False; its own stop_loss is still enforced as a hard floor.

Strategy exits come only from the strategy that opened the position, even after it is switched off for new entries. A position the bot has no record of buying can be exited by any enabled strategy.

Want to add your own? See CONTRIBUTING.md. A strategy is one class with one method; the starter template is about 50 lines, and the built-in strategies run 140 to 220 lines, most of it spelling out the reason behind each signal.


⚙️ Configuration

All runtime configuration lives in AWS SSM Parameter Store — change anything without redeploying.

# Example: tighten risk limits without redeploying
aws ssm put-parameter --name "/stock-bot/max_positions" --value "8" --type String --overwrite
aws ssm put-parameter --name "/stock-bot/trailing_stop_pct" --value "0.03" --type String --overwrite
Parameter Default Description
max_positions 12 Max simultaneous open positions
trailing_stop_pct 0.05 Trailing stop as fraction of price
max_concentration 0.15 Max portfolio fraction per symbol
max_daily_loss 0.02 Daily loss limit — stops trading if hit
min_confidence 0.5 Minimum signal confidence to trade
allow_pyramiding false Buy more of a symbol already held, up to max_concentration
monitor_interval 1 MonitorStops interval in minutes
notify_frequency hourly realtime, hourly, or daily
store sqlite Trade store: sqlite (trades.db on S3), shadow (both, trades.db primary), or dynamodb. Switched by the migration workflow; see docs/DYNAMODB-PLAN.md
kill-switch alive Set to kill to halt all trading immediately

🔴 Kill Switch

Emergency halt — stops all trading and liquidates all positions within 1 minute.

# Get function name from your stack
KILL_FN=$(aws cloudformation describe-stacks --stack-name stock-trading-bot \
  --query "Stacks[0].Outputs[?OutputKey=='KillSwitchFunctionName'].OutputValue" \
  --output text)

# Check status
aws lambda invoke --function-name $KILL_FN \
  --cli-binary-format raw-in-base64-out \
  --payload '{"action":"status"}' /tmp/out.json && cat /tmp/out.json

# 🛑 ENGAGE — liquidate everything and halt
aws lambda invoke --function-name $KILL_FN \
  --cli-binary-format raw-in-base64-out \
  --payload '{"action":"kill"}' /tmp/out.json && cat /tmp/out.json

# ✅ DISENGAGE — resume normal trading
aws lambda invoke --function-name $KILL_FN \
  --cli-binary-format raw-in-base64-out \
  --payload '{"action":"alive"}' /tmp/out.json && cat /tmp/out.json

No CLI? Set /stock-bot/kill-switch → kill directly in the AWS Console. The next Lambda invocation picks it up.

From your phone (Telegram)

A Telegram bot can operate any stack by name. Every reply carries a button keyboard, so after the first /bots each command is one tap.

Navigation is two taps: pick a bot, then pick an action.

Message Effect
/bots Choose a bot
/stock-bot-2 Choose an action for that bot
/stock-bot-2 status Kill switch, strategies, equity, cash, open positions
/stock-bot-2 positions Each position with entry date, today's P&L and total P&L
/stock-bot-2 strategies Which strategies run, with a toggle for each
/stock-bot-2 on rsi_macd Start running that strategy — live within a minute
/stock-bot-2 off macd Stop running it
/stock-bot-2 kill Shows what would be liquidated and offers a confirm button
/stock-bot-2 kill confirm Invokes that stack's KillSwitchFunction: cancels orders, sells everything, halts
/stock-bot-2 alive Resumes trading

Strategy changes are written to the bot's strategies SSM parameter and picked up on its next scheduled run — no redeploy, no restart, and each bot can run a different set.

Bot names are the SSM prefixes without slashes: stock-bot, stock-bot-2, stock-bot-live. Only chat IDs on the allowlist get a reply; anyone else is ignored silently. kill runs inside the target stack's own Lambda, so the bot never holds Alpaca credentials of its own.

Setup (once): create a bot with @BotFather, message it once, and read your chat ID from https://api.telegram.org/bot<TOKEN>/getUpdates.

Deployed (always on): the paper stack includes TelegramOpsFunction behind a Lambda Function URL. Create three parameters in SSM Parameter Store, then deploy the paper stack:

Parameter Type Value
/stock-bot-ops/telegram-token SecureString BotFather token
/stock-bot-ops/telegram-chat-ids String Your chat ID (comma-separated for several)
/stock-bot-ops/telegram-webhook-secret SecureString Random string, e.g. openssl rand -hex 32

Register the webhook once, with the URL from the stack's TelegramWebhookUrl output and the same secret:

curl -s "https://api.telegram.org/bot<TOKEN>/setWebhook" \
  -d "url=<TelegramWebhookUrl>" \
  -d "secret_token=<webhook secret>" \
  -d 'allowed_updates=["message"]'

Telegram sends that secret in a header on every call; the function answers 403 to anything else. getWebhookInfo confirms the registration.

Local (for development): put TELEGRAM_BOT_TOKEN and TELEGRAM_ALLOWED_CHAT_IDS in .env and run the poller on any machine with AWS CLI credentials:

python -m src.telegram_bot

Polling and the webhook are mutually exclusive: run deleteWebhook before polling, and setWebhook again afterwards.


🏗️ Architecture

Six Lambda functions per stack — five on EventBridge schedules, plus a manually invoked kill switch — and a seventh in the paper stack only for Telegram:

Function Schedule Purpose
DailyScan 09:30 ET weekdays Market-open summary email; places no orders
MonitorStops Every 1 min (market hours) All trading: trailing stops, strategy exits, entries, risk checks; records the open snapshot
EodSnapshot 15:55 ET weekdays End-of-day P&L + benchmark comparison
WeeklyDigest Friday 15:55 ET Weekly performance report
HourlyDigest Hourly (market hours) Consolidated trade activity digest
KillSwitch Manual invoke Emergency halt — liquidates everything
TelegramOps Telegram webhook (paper stack) Status, positions, kill, alive for all three stacks

Schedules run on EventBridge Scheduler with ScheduleExpressionTimezone: America/New_York, so these times hold across daylight-saving transitions.

For the full architecture deep-dive including data flow diagrams, SQLite schema, and multi-stack setup: ARCHITECTURE.md.


🧪 Running Tests

python -m pytest tests/ -v

240+ tests covering market hours, the risk rules and exit safety, buy deduplication and fill reconciliation, the Telegram ops bot, live SSM reconfiguration, strategy signal generation, SSM caching, environment labelling, config defaults, trade statistics, and the weekly audit archive.


🤝 Contributing

Pull requests welcome — especially new trading strategies. See CONTRIBUTING.md for the full guide, including the step-by-step process for adding a strategy.


⚠️ Disclaimer

This software is for educational purposes only. It is not financial advice. Trading stocks involves significant risk of loss. Always paper trade first and never risk money you cannot afford to lose. The authors are not responsible for any financial losses incurred through the use of this software.


📄 License

MIT — see LICENSE.

About

Hands-off algorithmic stock trading on AWS Lambda — ~$0.15/month. Alpaca + SAM + Python.

Topics

Resources

Contributing

Security policy

Stars

86 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages