Skip to content

Strategy: Zero to Production

The single source of truth for taking a new trading strategy from idea to live trading on the TradAI platform.

If something here disagrees with another doc, this one wins — file a PR against the other doc.

Mental model

tradai-uv (platform)            tradai-strategies (your code)
┌─────────────────────┐         ┌─────────────────────────┐
│ tradai CLI          │ ◄────── │ just new / just deploy  │
│ backend  :8000      │         │ strategies/foo/         │
│ strategy :8003      │         │   src/, configs/,       │
│ data     :8002      │         │   Dockerfile, tests/    │
│ mlflow   :5000      │         └─────────────────────────┘
│ Step Functions ┐    │                    │
│ Lambdas        ├ ECS│                    │ docker build
│ DynamoDB       ┘    │ ◄───── ECR ◄───────┘
└─────────────────────┘

You write code in tradai-strategies. The tradai CLI (installed from tradai-uv) builds it into a Docker image, pushes to ECR, and the platform runs it through backtest → register → stage → promote → live.

Prerequisites — do these once

These are not optional. Skipping any of them causes silent failures three steps later.

# Setup Verify
1 Install tradai CLI from tradai-uv repo: cd tradai-uv && uv sync --all-packages && uv pip install -e cli/ tradai --version prints
2 AWS SSO profile tradai configured in ~/.aws/config pointing at dev account 600802701449 / eu-central-1 AWS_PROFILE=tradai aws sts get-caller-identity returns your identity
3 TA-Lib system library installed (macOS: brew install ta-lib, Linux: apt install ta-lib) python -c "import talib" succeeds
4 CodeArtifact login (12-hour token — set a calendar reminder) cd tradai-strategies && just codeartifact-login exits 0
5 Backend ALB URL discovered (or services running locally via tradai up) curl $ALB/api/v1/health returns 200
6 Cognito JWT token for API auth (every write endpoint requires Authorization: Bearer …) curl -H "Authorization: Bearer $JWT" $ALB/api/v1/catalog/strategies returns 200
# Get the deployed dev ALB (or skip if running locally):
export AWS_PROFILE=tradai AWS_REGION=eu-central-1
export ALB="http://$(aws elbv2 describe-load-balancers \
  --query 'LoadBalancers[?contains(LoadBalancerName,`tradai`) && contains(LoadBalancerName,`dev`)].DNSName | [0]' \
  --output text)"

# Get Cognito JWT (uses dev user pool — see scripts/demo/01-login-auth.sh for the canonical flow)
source scripts/demo/lib.sh
export JWT="$(demo_login)"   # caches under .demo-state/jwt

The 11-step happy path

Each step lists: command → what it does → what to verify. These mirror scripts/demo/run-all.sh which is the executable version of this doc.

1. Scaffold

cd tradai-strategies
just codeartifact-login           # refresh token first
just new MomentumAlpha            # → tradai strategy new MomentumAlpha
                                  #    --output-dir ./strategies/ -y

→ Creates strategies/momentumalpha/ with src/, tests/, configs/, Dockerfile, tradai.yaml.

Verify: ls strategies/momentumalpha/ shows the structure.

2. Implement & test locally

Edit strategies/momentumalpha/src/momentumalpha/strategy.py. Required methods: get_metadata, populate_indicators, populate_entry_trend, populate_exit_trend. See examples/momentum-strategy/ for a reference.

just test momentumalpha           # pytest
just lint momentumalpha           # ruff + mypy
tradai strategy validate ./strategies/momentumalpha

Verify: all three exit 0.

3. Smoke backtest (local, fast iteration)

tradai backtest quick MomentumAlpha --local --period 7d \
  --strategy-dir ../tradai-strategies/strategies

→ Runs freqtrade in-process. No platform involvement. Iteration loop only.

Verify: terminal prints trades, Sharpe, max drawdown.

4. Build & push to ECR

export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
export CODEARTIFACT_TOKEN=$(aws codeartifact get-authorization-token \
  --domain tradai --query authorizationToken --output text)

# --mode controls how the deployed strategy TRADES (live vs dry-run paper).
# For a brand-new strategy, always start with dry-run.
tradai deploy strategy ./strategies/momentumalpha \
  --env dev --version 1.0.0 --mode dry-run

⚠ Trap: --mode only accepts live or dry-run. Passing --mode deploy exits with Invalid mode. The tradai-strategies/scripts/deploy.sh wrapper currently passes --mode deploy and is broken; call tradai deploy strategy directly until that script is fixed.

⚠ For live mode: --mode live additionally requires --exchange-secret <secrets-manager-name> pointing at the secret holding exchange API keys.

Verify:

aws ecr describe-images --repository-name tradai-momentumalpha-dev \
  --query 'imageDetails[0].imageTags'
# → ["1.0.0", "<git-sha>"]

5. Sync OHLCV data (before platform backtest)

The platform backtest reads OHLCV from ArcticDB (S3-backed). If your timerange/symbols aren't in coverage, the backtest will fail with a confusing "no data" error.

curl -X POST "$ALB/api/v1/data/sync" \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" -d @- <<EOF
{
  "exchange": "binance_futures",
  "symbols": ["BTC/USDT:USDT"],
  "timeframes": ["1h"],
  "start_date": "2024-01-01",
  "end_date": "2024-12-01"
}
EOF

Verify: curl -H "Authorization: Bearer $JWT" "$ALB/api/v1/data/coverage?symbol=BTC/USDT:USDT&timeframe=1h" shows your range covered.

6. Platform backtest (the real one, async)

This is the backtest that produces an MLflow run_id you can register against. It must run against the platform, not --local, because only the platform path writes to MLflow.

tradai backtest quick MomentumAlpha \
  --backend "$ALB" \
  --timerange 20240101-20241201 \
  --symbol BTC/USDT:USDT \
  --no-wait
# → prints job_id

# poll until terminal state
tradai backtest show <job_id> --backend "$ALB"

Or raw API (equivalent):

curl -X POST "$ALB/api/v1/backtests" \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" -d @- <<EOF
{
  "config": {
    "strategy": "MomentumAlpha",
    "symbols": ["BTC/USDT:USDT"],
    "timeframe": "1h",
    "start_date": "2024-01-01",
    "end_date": "2024-12-01",
    "stake_amount": 1000.0,
    "stake_currency": "USDT",
    "exchange": "binance_futures",
    "strategy_version": "1.0.0"
  },
  "experiment_name": "momentumalpha-v1",
  "priority": 5
}
EOF

Valid exchange values follow the <exchange>_<market> pattern (binance_futures, binance_spot, bybit_futures, …). Bare "binance" is rejected.

Verify:

curl -H "Authorization: Bearer $JWT" "$ALB/api/v1/backtests/<job_id>" \
  | jq '.status, .result.sharpe_ratio, .result.mlflow_run_id'
# → "SUCCEEDED", <sharpe>, "<run_id>"

7. Register in MLflow

The backtest does not auto-register today (verified — see issue #437 and gap analysis). You must register explicitly.

# Backend proxies this through to strategy-service:
curl -X POST "$ALB/api/v1/strategies/MomentumAlpha/register" \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" -d @- <<EOF
{
  "backtest_run_id": "<mlflow_run_id from step 6>",
  "docker_image_uri": "${AWS_ACCOUNT_ID}.dkr.ecr.eu-central-1.amazonaws.com/tradai-momentumalpha-dev:1.0.0",
  "description": "v1 baseline"
}
EOF

Verify: curl -H "Authorization: Bearer $JWT" "$ALB/api/v1/catalog/strategies/MomentumAlpha" | jq shows version 1, stage None.

8. Stage for testing

curl -X POST "$ALB/api/v1/strategies/MomentumAlpha/stage" \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  -d '{"version": "1"}'

Verify: catalog now shows stage Staging.

9. Dry-run paper trade

curl -X POST "$ALB/api/v1/strategies/MomentumAlpha/run" \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" -d @- <<EOF
{
  "mode": "dry-run",
  "timeframe": "1h",
  "symbols": ["BTC/USDT:USDT"],
  "config_overrides": {"stake_amount": 50.0}
}
EOF
# → instance_id

For live mode, add "exchange_secret_name": "<secrets-manager-name>" pointing at exchange API credentials.

Verify:

tradai monitor status MomentumAlpha
# wait for instance to reach RUNNING

⚠ Trap: Instance may stick in PENDING — that's #408 gap 10. Currently requires aws ecs describe-tasks to diagnose.

10. Promote to production

curl -X POST "$ALB/api/v1/strategies/MomentumAlpha/promote" \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  -d '{"version": "1"}'

Verify: catalog shows stage Production with champion alias.

11. Rollback (if needed)

curl -X POST "$ALB/api/v1/models/MomentumAlpha/rollback" \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  -d '{"target_version": "<previous>"}'

Friction matrix — what's broken right now

Verified against the codebase + open issues on 2026-05-20.

# Friction Severity Issue Workaround
1 tradai-strategies/scripts/deploy.sh passes invalid --mode deploy to the CLI; the actual flag accepts only live or dry-run. Tag-triggered CI deploys exit Invalid mode. Critical — (file an issue) Call tradai deploy strategy ... --mode dry-run|live directly; bypass just deploy / scripts/deploy.sh until script is fixed
2 Strategy Dockerfiles use Python 3.11, runtime now 3.13 — existing strategies break at import High strategies#9 New scaffolds from cookiecutter are fine; fix existing Dockerfiles to python3.13-bookworm
3 register-strategy thresholds bypassable + no docker-image↔backtest cross-check High #420 Accept thresholds at platform layer; do not skip_validation
4 Backtest does not auto-register a model version even on success — every dev hits this at registration time High #437 (closed but unfixed in code) Explicit register call in step 7
5 Live instances stuck in PENDING indefinitely Critical #408 gap 10 aws ecs describe-tasks to diagnose; no UX fix yet
6 tradai strategy vs tradai strategy-service — overlapping subapps, inconsistent names Medium This doc tells you which one to use
7 CodeArtifact token expires every 12h with no warning Medium Fixed in #504 tradai doctor now warns when the token is within 2h of expiry (reads ~/.config/tradai/codeartifact-expiry); re-run just codeartifact-login
8 CI smoke backtests fail from Binance geo-block on GitHub-hosted runners Medium #354, strategies#12 Use self-hosted runner in eu-central-1 or skip CI smoke backtest
9 git_commit="unknown" in BacktestResult breaks model-image cross-check at register Medium #413 (referenced) Register path now hydrates git_commit from the run's MLflow tags (Fixed in #504); still requires the backtest to record the tag (#413)
10 No tradai backtest debug <job_id> — must dig CloudWatch for backtest-consumer Lambda Low Fixed in #504 tradai backtest debug <job_id> [--env dev] [--since 60] [--follow] resolves and tails the backtest-pipeline log groups, filtered by trace_id
11 just backtest-smoke calls raw freqtrade and skips MLflow; result has no run_id. tradai backtest quick --local ALSO skips MLflow — only platform mode writes to MLflow. Medium Use tradai backtest quick --backend $ALB (platform mode) to get a registrable run_id
12 register-strategy.sh requires --oos flag with no explanation of what OOS_PERIOD means Low Pass --oos with a date range matching your test set; comment in script explains
13 Dev environment has stale Service Discovery + untested backtest pipeline + WAF not associated Medium #394 DevOps task
14 Three repos (uv, strategies, dashboard), three CLAUDE.md files, no cross-references Low This doc is the bridge
15 bitbucket-pipelines.yml still in strategies repo despite Bitbucket being deprecated Low Remove the file; GitHub Actions only
16 Every write endpoint requires Cognito JWT; Authorization: Bearer is silent-fail (401) and not in older doc curls Medium Source scripts/demo/lib.sh and use demo_login to mint a token, or follow 01-login-auth.sh

Resolved since the original draft (validated by Codex review)

  • Catalog empty due to MLflow SDK/server mismatch (#351)libs/tradai-common/pyproject.toml and services/mlflow/Dockerfile are now both on MLflow 3.11.1. Likely fixed; verify the open issue still applies before acting on it.
  • POST /strategies/{name}/register not proxied through backend (#408 gap 2) — the proxy is wired at services/backend/src/tradai/backend/api/proxy_routes.py:98. Hit the backend ALB, not strategy-service directly.

Fixed in #504

  • tradai doctor warns when the CodeArtifact token is within 2h of expiry (matrix #7).
  • tradai backtest debug <job_id> resolves + tails the backtest-pipeline log groups, filtered by trace_id (matrix #10).
  • Register path hydrates git_commit from the run's MLflow tags so strict-provenance registration no longer fails for runs that recorded the tag (matrix #9).
  • tradai strategy model namespaceregister, stage, promote, rollback, validate, versions, get are available as a single CLI namespace (thin re-exports of the strategy-service commands), an alternative to the raw curl calls in the lifecycle steps above.

Common errors and what they actually mean

Error message Real cause Fix
Unable to locate credentials AWS SSO token expired aws sso login --profile tradai
401 Unauthorized from CodeArtifact during uv sync 12h token expired just codeartifact-login
401 Unauthorized from API curl Missing Authorization: Bearer $JWT header export JWT=$(demo_login) (from scripts/demo/lib.sh) and retry
Invalid mode: deploy. Valid: live, dry-run Calling tradai deploy --mode deploy (e.g. via scripts/deploy.sh) Use --mode dry-run or --mode live
Invalid exchange: binance from POST /backtests Bare exchange name Use binance_futures (or binance_spot, etc.)
ModuleNotFoundError: pydantic_core._pydantic_core in ECS task Docker image built with Python 3.11, runtime 3.13 strategies#9 — bump Dockerfile to 3.13
"strategies": [] from /api/v1/catalog/strategies Was #351; now both client and server on MLflow 3.11.1. If still empty, MLflow registry probably empty for that env. Register at least one model first
Register returns 200 but with Sharpe -X.X below threshold Backtest underperformed Re-tune strategy or lower thresholds (and audit-log it)
Backtest stuck in PENDING for >5 min Step Functions queue or Lambda cold start aws stepfunctions list-executions --state-machine-arn …
Live instance stuck in PENDING ECS task can't start (image pull, IAM, networking) #408 gap 10

Reference: executable version of this flow

scripts/demo/run-all.sh runs all 14 steps end-to-end against AWS dev:

00-pre-demo-setup.sh        AWS SSO + S3 buckets + ALB
01-login-auth.sh            Cognito JWT
02-catalog.sh               Verify catalog reachable
03-data-sync.sh             Sync OHLCV
04-submit-backtest.sh       POST /api/v1/backtests
05-track-progress.sh        Poll until terminal
06-results-kpis.sh          Validate metrics
07-leaderboard.sh           Verify visible
08-stage-model.sh           POST /strategies/{name}/stage
09-dry-run-start.sh         POST /strategies/{name}/run
10-status-pnl-logs.sh       Verify running
11-promote-production.sh    POST /strategies/{name}/promote
12-observability.sh         CloudWatch checks
13-rollback.sh              Test rollback
14-cleanup.sh               Tear down

To follow along: bash scripts/demo/run-all.sh after setting DEMO_STRATEGY, DEMO_SYMBOL, etc.

When this doc goes stale

Update it when: 1. A friction in the matrix gets fixed (move row to a Fixed in <PR> archive section) 2. A step in the happy path changes (always test against scripts/demo/run-all.sh) 3. A new step appears (e.g., approval workflow, multi-region deploy)

Last verified: 2026-05-20.