Serverless algorithmic trading — forged on AWS Lambda.
No servers. No babysitting. No cloud bill.
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) │ │ │
└─────────────┘ └──────────────┘ └──────────────┘
| 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 |
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.dband 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.
| 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 |
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.txtcp .env.example .env
# Edit .env — add your Alpaca paper trading API keys# 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 Stringaws ses verify-email-identity --email-address you@example.com
# Check your inbox and click the verification linkcp samconfig.toml.example samconfig.toml
sam build
sam deploy --guided # First time — sets up ECR repos and S3 bucket
sam deploy # Subsequent deploysIf 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.
./scripts/test-lambdas.sh stock-trading-botAll 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 ownstop_lossis 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.
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 |
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.jsonNo CLI? Set /stock-bot/kill-switch → kill directly in the AWS Console. The next Lambda invocation picks it up.
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_botPolling and the webhook are mutually exclusive: run deleteWebhook before
polling, and setWebhook again afterwards.
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.
python -m pytest tests/ -v240+ 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.
Pull requests welcome — especially new trading strategies. See CONTRIBUTING.md for the full guide, including the step-by-step process for adding a strategy.
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.
MIT — see LICENSE.