Configuration & Model Versioning¶
How strategy configurations and ML models are versioned, promoted, and managed across environments.
graph LR
subgraph Config["Strategy Config"]
C1["tradai.yaml"] --> C2["S3 Upload"]
C2 --> C3["DRAFT"]
C3 --> C4["ACTIVE"]
C4 --> C5["DEPRECATED"]
end
subgraph Model["ML Model + Release"]
M1["Training"] --> M2["MLflow Registry - (artifact/run store)"]
M2 --> M3["register() - composes Release - (ENV#REL#...)"]
M3 --> M4["ACTIVE pointer - (ENV#ACTIVE, epoch)"]
M4 -->|promote / rollback| M4
end
C4 -.->|resolved into the Release| M3 Strategy Configuration¶
tradai.yaml¶
Every strategy has a tradai.yaml at its root that defines how TradAI services interact with it:
strategy:
name: "MyStrategy"
version: "1.0.0"
entry_point: "mystrategy.strategy:MyStrategy"
category: "momentum"
timeframe: "1h"
strategy_service:
source:
KIND: Binance
adapter:
KIND: AWS
bucket_name: tradai-data
library: ohlcv
defaults:
timerange: "20240101-20241201"
symbols:
- "BTC/USDT:USDT"
- "ETH/USDT:USDT"
stake_amount: 1000
max_open_trades: 3
mlflow:
tracking_uri: ${MLFLOW_TRACKING_URI:-http://localhost:5001}
experiment_name: "strategies/mystrategy"
auto_log_params: true
optimization:
defaults:
epochs: 100
loss_function: sharpe
spaces: [buy, sell]
presets:
quick:
epochs: 50
spaces: [buy]
standard:
epochs: 200
spaces: [buy, sell]
production:
epochs: 1000
spaces: [buy, sell, roi, stoploss, trailing]
walk_forward: true
deployment:
ecs:
cpu: 512
memory: 1024
Config Storage¶
Configurations are stored in S3 and versioned in DynamoDB:
graph TD
YAML["tradai.yaml"] -->|upload| S3["S3 Bucket - tradai-configs"]
S3 -->|version| DDB["DynamoDB - config-versions"]
DDB -->|ACTIVE version| ECS["ECS Task"]
ENV[".env / Secrets Manager"] -->|merge| Merge["ConfigMergeService"]
S3 -->|base config| Merge
Merge --> ECS | Component | Location | Purpose |
|---|---|---|
tradai.yaml | Strategy repo root | Source of truth for strategy config |
| S3 bucket | tradai-configs-{env} | Persisted config versions |
| DynamoDB table | tradai-config-versions-{env} | Version registry with lifecycle tracking |
| Secrets Manager | AWS Secrets Manager | Exchange credentials, API keys |
Config Version Lifecycle¶
Each config version follows a strict lifecycle:
stateDiagram-v2
[*] --> DRAFT : create_version()
DRAFT --> ACTIVE : activate()
ACTIVE --> DEPRECATED : new version activated
DEPRECATED --> [*] : TTL auto-cleanup (90 days)
note right of DRAFT : Not yet validated
note right of ACTIVE : Currently deployed (one per strategy)
note right of DEPRECATED : Superseded, auto-deleted after 90d Key rules:
- Only one ACTIVE version per strategy at any time
- Activating a new version automatically deprecates the previous one
- Deprecated versions have a 90-day TTL for auto-cleanup in DynamoDB
- Versions are content-addressable (SHA256 hash) -- duplicate configs are detected
Config Version Entity¶
Each version is tracked with these fields:
| Field | Type | Description |
|---|---|---|
strategy_name | string | Partition key (e.g., "PascalStrategy") |
config_id | string | Sort key: v{version:05d}-{hash[:8]} (5-digit zero-padded so lex sort matches numeric order) |
config_hash | string | SHA256 of normalized config content |
config_data | dict | Frozen config content |
status | enum | DRAFT, ACTIVE, or DEPRECATED |
version_number | int | Sequential version per strategy |
created_at | datetime | When created |
deployed_at | datetime | When activated (null if DRAFT) |
superseded_by | string | config_id of newer version (if deprecated) |
Managing Config Versions¶
CLI¶
# Create a new config version (starts as DRAFT)
tradai config create MyStrategy --data '{"timeframe":"1h","stoploss":-0.03}'
tradai config create MyStrategy --file config.json
# List versions for a strategy
tradai config list MyStrategy
tradai config list MyStrategy --status active
# Show a specific version
tradai config show MyStrategy v00001-a3b2c1d4
# Activate a version (auto-deprecates previous ACTIVE)
tradai config activate MyStrategy v00001-a3b2c1d4
# Deprecate a version
tradai config deprecate MyStrategy v00001-a3b2c1d4
# Submit backtest with a config version
tradai backtest quick MyStrategy --config-version v00001-a3b2c1d4
tradai backtest quick MyStrategy --config-version ACTIVE
API¶
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/v1/configs | Create config version |
| GET | /api/v1/configs/{strategy} | List versions |
| GET | /api/v1/configs/{strategy}/{config_id} | Get version |
| POST | /api/v1/configs/{strategy}/{config_id}/activate | Activate |
| POST | /api/v1/configs/{strategy}/{config_id}/deprecate | Deprecate |
Python SDK¶
from tradai.common.config.service import ConfigVersionService
service = ConfigVersionService(table_name="tradai-config-versions-dev")
# Create a new version (starts as DRAFT)
version = service.create_version(
strategy_name="MyStrategy",
config_data={"timeframe": "1h", "symbols": ["BTC/USDT:USDT"]},
description="Updated symbols list",
)
# Activate it (auto-deprecates previous ACTIVE version)
active = service.activate("MyStrategy", version.config_id)
# List all versions for a strategy
versions = service.list_versions("MyStrategy")
# Get the currently active version
current = service.get_active("MyStrategy")
Config Data Merge¶
When a backtest is submitted with config_version_id, the active config's config_data is merged as defaults into the backtest parameters. Explicit parameters always win:
config_data: {"stoploss": -0.03, "stake_amount": 150}
backtest request: {"stake_amount": 1000}
result: {"stoploss": -0.03, "stake_amount": 1000} # explicit wins
Protected fields (strategy, task_definition) cannot be overridden by config_data.
Config Version Propagation¶
When a backtest runs with a config version, the config_version_id is tracked across 5 stores:
| # | Store | How |
|---|---|---|
| 1 | Step Functions input | Backend passes in SF execution input |
| 2 | ECS container env | SF sets RUN_CONFIG; config_version_id rides inside it (#754) |
| 3 | DynamoDB job record | Extracted from result to top-level field |
| 4 | S3 result.json | Written by strategy container to tradai-results-{env} |
| 5 | MLflow run tag | BacktestMLflowLogger sets config_version_id tag |
See Config Versioning Verification Guide for step-by-step verification commands.
Config Loading at Runtime¶
When a strategy container starts, configs are loaded and merged from multiple sources:
graph TD
S3["S3 Config - (base)"] --> Loader["StrategyConfigLoader"]
MLflow["MLflow Tags - (model params)"] --> Loader
ENV["Environment Vars - (overrides)"] --> Loader
Loader --> Merge["ConfigMergeService"]
Merge --> Validate["Validation"]
Validate -->|pass| Config["StrategyConfig - (runtime)"]
Validate -->|fail| Error["Startup Error"] Priority order (highest wins):
- Environment variables
- MLflow model tags
- S3 stored config
tradai.yamldefaults
Model Versioning (MLflow)¶
v4: MLflow is the artifact/run store — it records model versions, params, metrics, and tags. It is no longer the deployment lifecycle. What trades is decided by the Release / ACTIVE-pointer plane (below + the v4 Cutover & Promotion Runbook), not by MLflow
None/Staging/Production/Archivedstages. The strategy-as-model alias plane was retired in v4 P6.
Deployment lifecycle (authoritative: Release / ACTIVE pointer)¶
stateDiagram-v2
[*] --> Registered : register() composes a Release (ENV#REL#...)
Registered --> Active : first ACTIVE pointer set (first write)
Active --> Active : promote() — gate + epoch-guarded swap to a challenger Release
Active --> Active : rollback() — repoint ACTIVE to a prior Release (no gate/cooldown)
note right of Registered : Immutable Release: image digest + resolved-config hash + slot bindings + gate snapshot
note right of Active : ACTIVE pointer (ENV#ACTIVE, epoch) names the one Release that trades per (strategy, env) | Concept | Description | Who moves it |
|---|---|---|
Release (ENV#REL#<ulid>) | Immutable composition: image digest + content-addressed resolved config + per-slot model bindings + gate snapshot | register (composes; first write sets ACTIVE) |
ACTIVE pointer (ENV#ACTIVE, epoch) | Names the one Release that trades for a (strategy, env) | tradai strategy promote (gated swap) / rollback / repoint-active |
The MLflow ModelStage enum (None/Staging/Production/Archived) still exists as model- version metadata, but it no longer governs what trades and there is no stage-transition promotion path.
Model Registration¶
After a backtest or training run, models are automatically registered:
sequenceDiagram
participant ECS as ECS Task - (Freqtrade)
participant MLflow as MLflow - Registry
participant DDB as DynamoDB - State
ECS->>MLflow: Log metrics + params
ECS->>MLflow: Log model artifacts
ECS->>MLflow: Register model version
MLflow-->>ECS: Version number
ECS->>DDB: Update job status
ECS->>MLflow: Tag with git_commit, strategy_name The ModelRegistrar handles this automatically:
from tradai.common.entrypoint.training.model_registrar import ModelRegistrar
registrar = ModelRegistrar(mlflow_adapter=adapter)
result = registrar.register(config=training_config, result=training_result)
# result.model_version is now set
Promotion gate (v4)¶
v4: Promotion is no longer an MLflow stage transition (
champion/staging/Productionaliases are retired). A challenger is an immutable Release; promotion runs a gate and, on pass, performs an epoch-guarded swap of the single ACTIVE pointer. See v4 Cutover & Promotion Runbook.
graph TD
Challenger["Challenger Release - (gate_snapshot)"] --> Gate["Promotion gate"]
Baseline["Current ACTIVE Release - (baseline gate_snapshot)"] --> Gate
Gate --> Decision{"Pass?"}
Decision -->|"yes"| Swap["Epoch-guarded pointer swap - (+ atomic audit)"]
Decision -->|"no"| Reject["Rejection audit - (ACTIVE unchanged)"] The gate (PromotionPolicy) applies floors (absolute), regression vs the current ACTIVE (from the 2nd release per (strategy, env)), and a cooldown. Every threshold is overridable via its TRADAI_GATE_* env var (see below). Signed-off launch defaults:
| Check | Default | TRADAI_GATE_* override |
|---|---|---|
sharpe >= MIN_SHARPE | 1.0 | TRADAI_GATE_MIN_SHARPE |
max_drawdown_pct <= MAX_DD | 20.0 | TRADAI_GATE_MAX_DD |
total_trades >= MIN_TRADES | 50 | TRADAI_GATE_MIN_TRADES |
| out-of-sample required | true | TRADAI_GATE_REQUIRE_OOS |
profit_factor >= baseline - ε | ε=0.1 | TRADAI_GATE_PF_EPSILON |
max_drawdown_pct <= baseline + δ | δ=2.0pp | TRADAI_GATE_MAX_DD_DELTA |
| cooldown since last promotion | 24h | TRADAI_GATE_COOLDOWN_HOURS |
⚠
TRADAI_GATE_REQUIRE_OOSfootgun: only the literal"false"disables the out-of-sample requirement. Any other value (e.g."0","no") still requires OOS.
v4 activation flags¶
| Flag | Default | Effect |
|---|---|---|
LOCAL_DEV_OVERRIDE | unset | true → runtime uses the legacy S3-by-name config load (dev only). Unset → resolves config from the ACTIVE Release (authoritative, fail-closed). |
RELEASE_STRICT_GATE | true | Registration fail-closes Release composition (requires mlflow_run_id + a contract-bearing version + a resolvable immutable image digest). Registration scope only — not the runtime resolver or the promotion gate. |
REQUIRE_IMAGE_DIGEST | false | true → runtime refuses to trade if RUNNING_IMAGE_DIGEST is absent. A mismatch always fails closed regardless. |
RELEASE_REQUIRE_LINEAGE | false | true → registration rejects a Release whose contract-bearing slots lack training-data data_hash. Independent of RELEASE_STRICT_GATE. |
CLI Commands¶
# Verify the ACTIVE pointer names a cutover-ready Release (gate before the runtime flip)
tradai strategy assert-cutover-ready MyStrategy --env dev
# Promote a challenger Release: gate + epoch-guarded swap (exit 2 + rejection audit on fail)
tradai strategy promote MyStrategy "DEV#REL#01ARZ..." --env dev
# Roll ACTIVE back to a prior Release (bypasses gate + cooldown; still audited)
tradai strategy rollback MyStrategy "DEV#REL#01PRIOR..." --env dev
# Audited manual repoint onto a cutover-ready Release (admin op)
tradai strategy repoint-active MyStrategy "DEV#REL#01ARZ..." --env dev
API Endpoints¶
The Backend exposes the pointer-plane promotion APIs:
| Method | Path | Description |
|---|---|---|
POST | /api/v1/strategies/{name}/promote | Gate + epoch-guarded swap of the ACTIVE pointer |
POST | /api/v1/strategies/{name}/rollback | Repoint ACTIVE to a prior Release (incident) |
POST | /api/v1/catalog/rebuild | Rebuild the catalog-index cache from the pointer plane |
Automated Retraining Pipeline¶
The retraining workflow is orchestrated by Step Functions:
graph TD
Trigger["Trigger - Schedule / Drift / Manual"] --> Check["Check Retraining - Needed?"]
Check -->|"yes"| Train["Train Model - (ECS + FreqAI)"]
Check -->|"no"| Skip["Skip"]
Train --> Register["Register Release - (compose + ACTIVE pointer)"]
Register --> Promote["Promote - (gate + epoch-guarded swap)"]
Promote --> Notify["Notify - (SNS + Slack)"] v4: there is no
compare-models/promote-model/model-rollbackstage-transition path anymore; the challenger is registered as a Release and promoted via the gate. Thedrift-monitorandretraining-schedulerLambdas remain (trigger side).
Environment-Specific Configuration¶
Settings Hierarchy¶
Each service uses Pydantic settings with environment variable prefixes:
| Service | Prefix | Key Settings |
|---|---|---|
| Backend | BACKEND_ | BACKEND_EXECUTOR_MODE, BACKEND_BACKTEST_QUEUE_URL |
| Strategy Service | STRATEGY_SERVICE_ | STRATEGY_SERVICE_MLFLOW_TRACKING_URI, STRATEGY_SERVICE_STRATEGY_PATH |
| Data Collection | DATA_COLLECTION_ | DATA_COLLECTION_EXCHANGES, DATA_COLLECTION_ARCTIC_S3_BUCKET |
Settings Mixins¶
Common settings are shared via mixins:
# MLflow settings (shared by Strategy Service and Backend)
class MLflowSettingsMixin:
mlflow_tracking_uri: str # MLFLOW_TRACKING_URI
mlflow_username: str # MLFLOW_USERNAME
mlflow_password: str # MLFLOW_PASSWORD
# ArcticDB settings (shared by Data Collection and Strategy Service)
class ArcticSettingsMixin:
arctic_s3_bucket: str # ARCTIC_S3_BUCKET
arctic_library_name: str # ARCTIC_LIBRARY_NAME (default: "ohlcv")
arctic_s3_endpoint: str # ARCTIC_S3_ENDPOINT
Per-Environment Differences¶
| Setting | Dev | Staging | Prod |
|---|---|---|---|
| Executor mode | local | sqs | stepfunctions |
| RDS instance | db.t4g.micro | db.t4g.micro | db.t4g.small |
| ECS launch type | EC2 (consolidated) | EC2 (consolidated) | Fargate |
| Log retention | 30 days | 30 days | 90 days |
| Deletion protection | Off | Off | On |
| MLflow URL | http://localhost:5001 | Service Discovery | Service Discovery |
Traceability¶
Every operation is traceable across the entire system:
graph LR
TraceID["trace_id"] --> Backend["Backend API"]
TraceID --> SQS["SQS Message"]
TraceID --> SF["Step Functions"]
TraceID --> ECS["ECS Task"]
TraceID --> MLflow["MLflow Run"]
TraceID --> DDB["DynamoDB - job record"]
JobID["job_id"] --> DDB
JobID --> S3["S3 Results"]
RunID["mlflow_run_id"] --> MLflow
RunID --> DDB
GitSHA["git_commit"] --> MLflow
GitSHA --> DDB | Field | Description | Where stored |
|---|---|---|
trace_id | End-to-end correlation ID | DynamoDB, Step Functions input, ECS env |
job_id | DynamoDB job identifier | DynamoDB, S3 result paths |
mlflow_run_id | MLflow experiment run | DynamoDB, BacktestResult, MLflow |
git_commit | Code version SHA | BacktestResult, MLflow tags |
Quick Reference¶
Config Version Commands¶
| Command | Description |
|---|---|
tradai config create STRATEGY --data JSON | Create config version from JSON |
tradai config create STRATEGY --file FILE | Create config version from file |
tradai config list STRATEGY | List versions for strategy |
tradai config show STRATEGY CONFIG_ID | Show version details |
tradai config activate STRATEGY CONFIG_ID | Activate version |
tradai config deprecate STRATEGY CONFIG_ID | Deprecate version |
tradai backtest quick STRATEGY --config-version ID | Backtest with config version |
Release / promotion commands (v4)¶
| Command | Description |
|---|---|
tradai strategy list | List registered strategies |
tradai strategy register ... | Compose an immutable Release (first write sets ACTIVE) |
tradai strategy assert-cutover-ready NAME -e ENV | Verify the ACTIVE pointer names a cutover-ready Release |
tradai strategy repoint-active NAME "ENV#REL#..." -e ENV | Audited manual repoint of ACTIVE onto a cutover-ready Release |
tradai strategy promote NAME "ENV#REL#..." -e ENV | Gate a challenger Release + epoch-guarded ACTIVE swap |
tradai strategy rollback NAME "ENV#REL#..." -e ENV | Repoint ACTIVE to a prior Release (no gate/cooldown; audited) |
tradai strategy promote-cross-env NAME measurement.json --source-env E --target-env E | Re-measure + gate + swap a source-env Release into the target env |
tradai deploy strategy ./path --env dev | Deploy strategy task to ECS |
tradai deploy rollback NAME --env dev | Roll back the ECS deployment (task/image) — not the ACTIVE Release pointer |
Key Source Files¶
| Component | Path |
|---|---|
| ConfigVersion entity | libs/tradai-common/src/tradai/common/entities/config_version.py |
| ConfigVersionService | libs/tradai-common/src/tradai/common/config/service.py |
| S3ConfigRepository | libs/tradai-common/src/tradai/common/aws/s3_config_repository.py |
| ConfigMergeService | libs/tradai-common/src/tradai/common/config/merge.py |
| StrategyConfigLoader | libs/tradai-common/src/tradai/common/config/loader.py |
| ModelStage enum | libs/tradai-common/src/tradai/common/entities/mlflow.py |
| MLflowAdapter | libs/tradai-common/src/tradai/common/mlflow/adapter.py |
| ModelComparator | libs/tradai-common/src/tradai/common/model_comparison/comparator.py |
| ModelRegistrar | libs/tradai-common/src/tradai/common/entrypoint/training/model_registrar.py |
| Promotion routes | services/strategy-service/src/tradai/strategy_service/api/promotion_routes.py |
| Config routes | services/strategy-service/src/tradai/strategy_service/api/config_routes.py |
See Also¶
- Config Versioning Verification Guide -- step-by-step verification and reproduction
- Strategy Lifecycle -- full path from idea to production
- ML Lifecycle Architecture -- detailed ML pipeline design
- Configuration Management Architecture -- system-level config design
- State Machines -- all state machine definitions