Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
# Alpha Engine — Master Environment Variables
# Copy to .env and fill in values. .env is gitignored.
#
# This is the SINGLE SOURCE OF TRUTH for all API keys and secrets.
# Run `bash infrastructure/sync-secrets.sh` to push to EC2 instances.
# Lambda deploy scripts read from this file automatically.
# PREFERRED: Store secrets in AWS SSM Parameter Store (under /alpha-engine/).
# All modules load from SSM at startup. Use seed-ssm.sh to migrate:
# bash infrastructure/seed-ssm.sh # create SSM params from .env
# bash infrastructure/add-ssm-policy.sh # add IAM permissions
#
# LEGACY: Push .env directly to Lambda/EC2:
# bash infrastructure/push-secrets.sh # push to all targets
#
# ─── LLM / AI ────────────────────────────────────────────────────────────────
ANTHROPIC_API_KEY=
Expand Down
4 changes: 3 additions & 1 deletion collectors/daily_closes.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
Data source priority: polygon.io grouped-daily (1 API call for all US stocks),
then yfinance batch download for any tickers polygon missed.

Schema: index=ticker (str), columns=[date, Open, High, Low, Close, Adj_Close, Volume]
Schema: index=ticker (str), columns=[date, Open, High, Low, Close, Adj_Close, Volume, VWAP]
"""

from __future__ import annotations
Expand Down Expand Up @@ -84,6 +84,7 @@ def collect(
"Close": round(g["close"], 4),
"Adj_Close": round(g["close"], 4),
"Volume": int(g["volume"]),
"VWAP": round(g["vwap"], 4) if g.get("vwap") else None,
})
polygon_count = len(records)
logger.info("Polygon grouped-daily: %d/%d tickers", polygon_count, len(tickers))
Expand Down Expand Up @@ -191,6 +192,7 @@ def _fetch_yfinance_closes(
"Close": round(float(last["Close"]), 4),
"Adj_Close": round(adj_close, 4),
"Volume": int(last["Volume"]) if pd.notna(last.get("Volume")) else 0,
"VWAP": None,
})
count += 1
except Exception as e:
Expand Down
72 changes: 72 additions & 0 deletions infrastructure/add-ssm-policy.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#!/usr/bin/env bash
# add-ssm-policy.sh — Add SSM Parameter Store read access to all Alpha Engine IAM roles.
#
# Adds an inline policy allowing ssm:GetParametersByPath and ssm:GetParameter
# on /alpha-engine/* to each Lambda execution role and the EC2 instance role.
#
# Usage:
# bash infrastructure/add-ssm-policy.sh # apply to all roles
# bash infrastructure/add-ssm-policy.sh --dry-run # show what would be applied

set -euo pipefail

REGION="${AWS_REGION:-us-east-1}"
ACCOUNT_ID=$(aws sts get-caller-identity --query "Account" --output text 2>/dev/null)

if [ -z "$ACCOUNT_ID" ]; then
echo "ERROR: Could not determine AWS account ID. Check AWS credentials."
exit 1
fi

POLICY_NAME="alpha-engine-ssm-read"
POLICY_DOC=$(cat <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "SSMParameterStoreRead",
"Effect": "Allow",
"Action": [
"ssm:GetParametersByPath",
"ssm:GetParameter"
],
"Resource": "arn:aws:ssm:${REGION}:${ACCOUNT_ID}:parameter/alpha-engine/*"
}
]
}
EOF
)

# All IAM roles that need SSM access
ROLES=("alpha-engine-data-role" "alpha-engine-research-role" "alpha-engine-predictor-role" "alpha-engine-executor-role")

DRY_RUN=false
[ "${1:-}" = "--dry-run" ] && DRY_RUN=true

echo "Adding SSM read policy to Alpha Engine IAM roles"
echo "Account: $ACCOUNT_ID"
echo "Region: $REGION"
echo "Resource: arn:aws:ssm:${REGION}:${ACCOUNT_ID}:parameter/alpha-engine/*"
echo ""

for role in "${ROLES[@]}"; do
if [ "$DRY_RUN" = true ]; then
echo " Would add $POLICY_NAME to $role"
continue
fi

if ! aws iam get-role --role-name "$role" --region "$REGION" &>/dev/null; then
echo " SKIP $role: role does not exist"
continue
fi

aws iam put-role-policy --role-name "$role" --policy-name "$POLICY_NAME" --policy-document "$POLICY_DOC" 2>&1 && echo " OK: $role" || echo " FAILED: $role"
done

echo ""
if [ "$DRY_RUN" = true ]; then
echo "(dry-run — no changes made)"
else
echo "Done. Verify with:"
echo " aws iam get-role-policy --role-name alpha-engine-data-role --policy-name $POLICY_NAME --query 'PolicyDocument' --output json"
fi
150 changes: 150 additions & 0 deletions infrastructure/push-configs.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
#!/usr/bin/env bash
# push-configs.sh — Push gitignored config files from local repos to EC2 instances.
#
# Configs are gitignored and never committed. Edit locally, then push with this script.
# Each config goes to the instance(s) that need it.
#
# Config mapping:
# alpha-engine/config/risk.yaml → trading EC2: ~/alpha-engine/config/risk.yaml
# alpha-engine-data/config.yaml → micro EC2: ~/alpha-engine-data/config.yaml
# alpha-engine-backtester/config.yaml → micro EC2: ~/alpha-engine-backtester/config.yaml
#
# Usage:
# bash infrastructure/push-configs.sh # push all configs
# bash infrastructure/push-configs.sh --dry-run # show what would be pushed
# bash infrastructure/push-configs.sh --trading # trading instance only
# bash infrastructure/push-configs.sh --micro # micro instance only

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DEV_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
SSH_KEY="$HOME/.ssh/alpha-engine-key.pem"
REGION="${AWS_REGION:-us-east-1}"

TRADING_INSTANCE="i-018eb3307a21329bf"
MICRO_INSTANCE="i-09b539c844515d549"

# Config file mapping: local_path → instance_type → remote_path
# Trading instance configs
TRADING_CONFIGS=("$DEV_ROOT/alpha-engine/config/risk.yaml:/home/ec2-user/alpha-engine/config/risk.yaml")

# Micro instance configs
MICRO_CONFIGS=("$DEV_ROOT/alpha-engine-data/config.yaml:/home/ec2-user/alpha-engine-data/config.yaml" "$DEV_ROOT/alpha-engine-backtester/config.yaml:/home/ec2-user/alpha-engine-backtester/config.yaml")

# ── Parse args ──────────────────────────────────────────────────────────────

TARGET="all"
DRY_RUN=false
for arg in "$@"; do
case "$arg" in
--dry-run) DRY_RUN=true ;;
--trading) TARGET="trading" ;;
--micro) TARGET="micro" ;;
*) echo "Unknown arg: $arg"; exit 1 ;;
esac
done

# ── Validate ────────────────────────────────────────────────────────────────

if [ ! -f "$SSH_KEY" ]; then
echo "ERROR: SSH key not found at $SSH_KEY"
exit 1
fi

# ── Helpers ─────────────────────────────────────────────────────────────────

get_instance_ip() {
local instance_id="$1"
aws ec2 describe-instances --instance-ids "$instance_id" --region "$REGION" --query 'Reservations[0].Instances[0].PublicIpAddress' --output text 2>/dev/null
}

push_config() {
local local_path="$1"
local remote_path="$2"
local ip="$3"
local name="$4"
local basename
basename=$(basename "$local_path")

if [ ! -f "$local_path" ]; then
echo " SKIP $basename: local file not found at $local_path"
return 1
fi

if [ "$DRY_RUN" = true ]; then
echo " $basename → $name:$remote_path"
return 0
fi

echo -n " $basename → $name ... "
scp -i "$SSH_KEY" -o StrictHostKeyChecking=no -o ConnectTimeout=10 "$local_path" "ec2-user@${ip}:${remote_path}" 2>/dev/null
if [ $? -eq 0 ]; then
echo "OK"
return 0
else
echo "FAILED"
return 1
fi
}

push_to_instance() {
local name="$1"
local instance_id="$2"
shift 2
local configs=("$@")

local ip
ip=$(get_instance_ip "$instance_id")
if [ -z "$ip" ] || [ "$ip" = "None" ]; then
echo " SKIP $name: instance not running or no public IP"
return 1
fi

[ "$DRY_RUN" = false ] && echo "=== $name ($ip) ==="
[ "$DRY_RUN" = true ] && echo "=== $name ($instance_id) ==="

local failed=0
for entry in "${configs[@]}"; do
local local_path="${entry%%:*}"
local remote_path="${entry#*:}"
push_config "$local_path" "$remote_path" "$ip" "$name" || failed=$((failed + 1))
done
return $failed
}

# ── Dry run header ──────────────────────────────────────────────────────────

if [ "$DRY_RUN" = true ]; then
echo "Configs that would be pushed:"
echo ""
fi

# ── Push ────────────────────────────────────────────────────────────────────

FAILED=0

if [ "$TARGET" = "all" ] || [ "$TARGET" = "trading" ]; then
push_to_instance "trading" "$TRADING_INSTANCE" "${TRADING_CONFIGS[@]}" || FAILED=$((FAILED + 1))
echo ""
fi

if [ "$TARGET" = "all" ] || [ "$TARGET" = "micro" ]; then
push_to_instance "micro" "$MICRO_INSTANCE" "${MICRO_CONFIGS[@]}" || FAILED=$((FAILED + 1))
echo ""
fi

# ── Summary ─────────────────────────────────────────────────────────────────

if [ "$DRY_RUN" = true ]; then
echo "(dry-run — no changes made)"
else
if [ "$FAILED" -eq 0 ]; then
echo "All configs pushed successfully."
else
echo "Completed with $FAILED failure(s)."
fi
echo ""
echo "Note: Executor reads risk.yaml on each run (morning batch + daemon start)."
echo " Changes take effect on next trading day boot."
fi
Loading
Loading