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:
--modeonly acceptsliveordry-run. Passing--mode deployexits withInvalid mode. Thetradai-strategies/scripts/deploy.shwrapper currently passes--mode deployand is broken; calltradai deploy strategydirectly until that script is fixed.⚠ For live mode:
--mode liveadditionally 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:
⚠ Trap: Instance may stick in
PENDING— that's #408 gap 10. Currently requiresaws ecs describe-tasksto 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.tomlandservices/mlflow/Dockerfileare now both on MLflow 3.11.1. Likely fixed; verify the open issue still applies before acting on it. POST /strategies/{name}/registernot proxied through backend (#408 gap 2) — the proxy is wired atservices/backend/src/tradai/backend/api/proxy_routes.py:98. Hit the backend ALB, not strategy-service directly.
Fixed in #504¶
tradai doctorwarns when the CodeArtifact token is within 2h of expiry (matrix #7).tradai backtest debug <job_id>resolves + tails the backtest-pipeline log groups, filtered bytrace_id(matrix #10).- Register path hydrates
git_commitfrom the run's MLflow tags so strict-provenance registration no longer fails for runs that recorded the tag (matrix #9). tradai strategy modelnamespace —register,stage,promote,rollback,validate,versions,getare available as a single CLI namespace (thin re-exports of the strategy-service commands), an alternative to the rawcurlcalls 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.
Related docs¶
- docs/quickstarts/02-create-new-strategy.md — scaffold details
- docs/developer-guides/strategy-lifecycle.md — phase-by-phase reference (more theoretical)
- docs/developer-guides/02-create-strategy.md — implementation patterns
- docs/services/strategy-service.md — full API reference
../tradai-strategies/CLAUDE.md— strategy repo specificsscripts/demo/run-all.sh— executable happy path
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.