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 → 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 and the composed Release; on the first registration for this (strategy, env) the ACTIVE pointer is set to it.

8. Compose a Release

v4: there is no MLflow "stage" step. Registration (step 7) composes an immutable Release (pinned image digest + content-addressed resolved config + per-slot model bindings + gate snapshot) and, on first write for a (strategy, env), sets the ACTIVE pointer. What trades is whatever Release the ACTIVE pointer names — never an MLflow stage or alias. See the v4 Cutover & Promotion Runbook.

Confirm the Release is cutover-ready before launching it:

tradai strategy assert-cutover-ready MomentumAlpha --env dev --json
# → ready: true  (non-zero exit if a *ML* Release is legacy / empty-slot / dangling;
#   a non-ML/pure-indicator strategy is ready with an empty-slot Release — is_ml=False)

Verify: assert-cutover-ready reports ready: true for (MomentumAlpha, dev). Repoint a straggler onto a cutover-ready Release with tradai strategy repoint-active MomentumAlpha "DEV#REL#..." --env dev if needed.

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

v4: promotion is an epoch-guarded swap of the ACTIVE pointer onto a challenger Release, gated by PromotionPolicy — not an MLflow champion/Production alias. See the v4 Cutover & Promotion Runbook.

# CLI: gate (floors + regression + cooldown) + epoch-guarded swap
tradai strategy promote MomentumAlpha "PROD#REL#01ARZ..." --env prod

# or the Backend pointer-plane route
curl -X POST "$ALB/api/v1/strategies/MomentumAlpha/promote" \
  -H "Authorization: Bearer $JWT" -H "Content-Type: application/json" \
  -d '{"release_deployment_id": "PROD#REL#01ARZ...", "environment": "prod"}'

Verify: the dashboard catalog shows the strategy at stage Production (a prod ACTIVE pointer now names this Release); a promotion audit row records the gate decision + new epoch.

11. Rollback (if needed)

# Repoint ACTIVE to a prior Release (bypasses gate + cooldown; still audited)
tradai strategy rollback MomentumAlpha "PROD#REL#01PRIOR..." --env prod

Friction matrix — what's broken right now

Verified against the codebase + open issues on 2026-05-20 (row 8 updated 2026-08-25).

# 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 Low #354, strategies#12 Mitigated for platform CI (#354 stays open until the strategies repo adopts the same runners): jobs run on RunsOn EC2 in eu-central-1 with a dedicated public IP per job (see CI Runners). The strategies repo still runs on GitHub-hosted runners until it adopts the same labels.
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 A deprecated legacy CI pipeline config still ships in the strategies repo 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 namespace — only register and get survive here (thin re-exports of the strategy-service commands). v4 P6 retired the strategy-as-model alias plane: the stage/promote/rollback/validate/versions model verbs are gone. Promotion and rollback are now top-level Release-plane commandstradai strategy promote / tradai strategy rollback (see step 10/11) — not model-alias transitions.

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           GET /catalog/strategies/{name}   (v4: /stage retired; confirms Release)
09-dry-run-start.sh         POST /strategies/{name}/run
10-status-pnl-logs.sh       Verify running
11-promote-production.sh    POST /strategies/{name}/promote  (legacy MLflow-stage path)
12-observability.sh         CloudWatch checks
13-rollback.sh              Test rollback                    (legacy MLflow-stage path)
14-cleanup.sh               Tear down

Note: demo steps 08/11/13 still drive the retired MLflow-stage endpoints (/stage, /promoteProduction, /rollback); they have not yet been ported to the v4 Release/pointer plane. The authoritative v4 flow for staging-readiness, promotion, and rollback is steps 8/10/11 above (assert-cutover-ready, tradai strategy promote, tradai strategy rollback) — see the v4 Cutover & Promotion Runbook.

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.