Configuration¶
The FiveTwenty library provides flexible configuration options to manage your OANDA API credentials and client settings securely. This guide covers all configuration patterns and advanced options.
Overview¶
The library supports three main configuration approaches:
- Direct parameters - Straightforward and explicit
- Configuration objects - Structured and reusable
- Environment variables - Zero-config deployment
All approaches prioritize security by automatically masking sensitive information in logs and output.
Environment Concepts¶
OANDA provides two distinct environments for trading: Practice and Live. Understanding the differences is crucial for safe development and trading.
Environment Overview¶
| Feature | Practice Environment | Live Environment |
|---|---|---|
| Real Money | No (virtual funds) | Yes (real funds) |
| API Endpoint | api-fxpractice.oanda.com | api-fxtrade.oanda.com |
| Market Data | Real-time | Real-time |
| Execution | Simulated | Real market |
| Risk | None | Real financial risk |
| Use Case | Testing & Learning | Production Trading |
Practice Environment¶
The practice environment is designed for safe development and learning:
- Virtual Funds: Start with $100,000 in virtual money
- Real Market Data: Access to live market prices for realistic testing
- Full API Access: All OANDA API features available
- No Risk: No real money at stake
- Reset Available: Can reset account balance anytime
- No KYC Required: Instant setup with just email verification
Ideal for: - Learning the OANDA platform and API - Testing and developing trading strategies - Code development and debugging - Paper trading competitions - Training new traders
Live Environment¶
The live environment is for real money trading:
- Real Money: All transactions involve actual funds
- Market Execution: Orders executed in real market conditions
- Full Trading Features: Access to all OANDA trading capabilities
- Permanent Results: Profits and losses are real and permanent
Requirements: - Funded OANDA account - Completed KYC (Know Your Customer) verification - Understanding of trading risks - Production-ready, thoroughly tested code
Critical Safety Considerations: - Always test thoroughly in practice before deploying to live - Implement proper risk management and position sizing - Use stop losses and take profits - Monitor account balance and margin requirements - Have emergency procedures for stopping trading
Environment URLs¶
The SDK automatically routes requests to the correct endpoints:
from fivetwenty import Environment
# Practice environment
print(Environment.PRACTICE.base_url)
# Output: https://api-fxpractice.oanda.com/v3
# Live environment
print(Environment.LIVE.base_url)
# Output: https://api-fxtrade.oanda.com/v3
Development Workflow¶
Recommended development flow:
- Develop in Practice: Write and test all code in practice environment
- Validate Strategy: Thoroughly test trading strategy with virtual funds
- Code Review: Review code for security, error handling, and risk management
- Gradual Deployment: Start with small position sizes in live environment
- Monitor and Scale: Monitor performance before increasing position sizes
Quick Start¶
Direct Parameters¶
The simplest way to configure the client:
import asyncio
import os
from fivetwenty import AsyncClient, Environment
async def main() -> None:
# Step 1: Initialize AsyncClient with direct parameter configuration
# This is the simplest approach - perfect for getting started or basic scripts
async with AsyncClient(
token=os.environ.get("FIVETWENTY_OANDA_TOKEN", "your-api-token"), # API authentication token
environment=Environment.PRACTICE # Safe practice environment with virtual money
) as client:
# Step 2: Test connection by retrieving accounts associated with the token
# This verifies authentication and provides basic account information
accounts = await client.accounts.get_accounts()
print(f"Found {len(accounts)} accounts")
# At this point, the client is fully configured and ready for trading operations
# The context manager ensures proper cleanup of HTTP connections
if __name__ == "__main__":
# Step 3: Run the async main function using asyncio event loop
# This is the standard pattern for async applications
asyncio.run(main())
Configuration Objects¶
For more structured configuration:
import asyncio
import os
from fivetwenty import AccountConfig, AsyncClient, Environment
async def main() -> None:
# Step 1: Create structured configuration object for reusable settings
# AccountConfig provides validation, secret masking, and organized configuration
config = AccountConfig(
token=os.environ.get("FIVETWENTY_OANDA_TOKEN", "your-api-token"), # API authentication
account_id=os.environ.get("FIVETWENTY_OANDA_ACCOUNT", "your-account-id"), # Specific account to use
environment=Environment.PRACTICE, # Trading environment selection
alias="my_trading_account", # Human-readable identifier for logging/debugging
)
# Step 2: Use structured configuration with AsyncClient
# Configuration objects enable better organization for production applications
async with AsyncClient(config=config) as client:
# Step 3: Verify connection and configuration by retrieving accounts
# The client automatically uses the account_id from the configuration
accounts = await client.accounts.get_accounts()
print(f"Found {len(accounts)} accounts")
# Benefits of configuration objects:
# - Automatic validation of all parameters
# - Secret masking for security in logs
# - Reusable across multiple client instances
# - Type safety with Pydantic models
if __name__ == "__main__":
asyncio.run(main())
Environment Variables¶
For deployment and CI/CD:
# Set environment variables
export FIVETWENTY_OANDA_TOKEN="your-api-token"
export FIVETWENTY_OANDA_ACCOUNT="your-account-id"
export FIVETWENTY_OANDA_ENVIRONMENT="practice"
export FIVETWENTY_OANDA_ACCOUNT_ALIAS="my_account"
import asyncio
import os
from fivetwenty import AsyncClient, Environment
async def main() -> None:
# Step 1: Initialize client using environment variables for deployment-friendly configuration
# Environment variables are ideal for Docker, Kubernetes, CI/CD, and serverless deployments
async with AsyncClient(
token=os.environ.get("FIVETWENTY_OANDA_TOKEN", "demo-token"), # Fallback for development
environment=Environment.PRACTICE # Environment can also be loaded from env vars
) as client:
# Step 2: Verify environment variable configuration
# The client automatically loads standard FIVETWENTY_* environment variables
accounts = await client.accounts.get_accounts()
print(f"Found {len(accounts)} accounts")
# Environment variable benefits:
# - Secure: No hardcoded secrets in source code
# - Flexible: Different configs for different deployment environments
# - Standard: Follows 12-factor app principles
# - Zero-config: Minimal code changes between environments
if __name__ == "__main__":
asyncio.run(main())
Configuration Patterns¶
1. Direct Parameters Pattern¶
Best for: Basic scripts, getting started, testing
import asyncio
import os
from fivetwenty import AsyncClient, Environment
async def main() -> None:
# Step 1: Minimal direct parameter configuration for basic usage
# Perfect for simple scripts and getting started quickly
async with AsyncClient(
token=os.environ.get("FIVETWENTY_OANDA_TOKEN", "demo-token"), # Required: API authentication
environment=Environment.PRACTICE # Required: Trading environment
) as client:
print(f"Connected: {client.config.summary()}")
# Output shows environment and masked credentials for security
# Step 2: Include account ID for convenience and clearer intent
# Account ID eliminates need to call get_accounts() to find the correct account
async with AsyncClient(
token=os.environ.get("FIVETWENTY_OANDA_TOKEN", "demo-token"), # API authentication
account_id=os.environ.get("FIVETWENTY_OANDA_ACCOUNT", "demo-account"), # Specific account target
environment=Environment.LIVE # CAUTION: Live environment uses real money!
) as client:
print(f"Connected: {client.config.summary()}")
# Account ID is now available as client.account_id without additional API calls
# Step 3: Advanced configuration with HTTP client customization
# These options optimize performance and reliability for production applications
async with AsyncClient(
token=os.environ.get("FIVETWENTY_OANDA_TOKEN", "demo-token"), # API authentication
environment=Environment.PRACTICE, # Safe practice environment
timeout=60.0, # Extended timeout for slow networks (default: 30s)
max_retries=5, # Retry failed requests up to 5 times (default: 3)
user_agent="MyTradingBot/1.0" # Custom user agent for request identification
) as client:
print(f"Connected: {client.config.summary()}")
# Enhanced reliability and monitoring through custom configuration
if __name__ == "__main__":
asyncio.run(main())
2. Configuration Objects Pattern¶
Best for: Production applications, multiple accounts, reusable configurations
import asyncio
import os
from fivetwenty import AccountConfig, AsyncClient, Environment
async def main() -> None:
# Step 1: Create separate configurations for different trading environments
# This pattern enables safe development workflow: test in practice, deploy to live
practice_config = AccountConfig(
token=os.environ.get("PRACTICE_OANDA_TOKEN", "practice-token"), # Practice API token
account_id=os.environ.get("PRACTICE_OANDA_ACCOUNT", "practice-account-123"), # Practice account
environment=Environment.PRACTICE, # Virtual money environment
alias="practice_trading", # Descriptive identifier for logging
)
live_config = AccountConfig(
token=os.environ.get("LIVE_OANDA_TOKEN", "live-token"), # Live API token
account_id=os.environ.get("LIVE_OANDA_ACCOUNT", "live-account-456"), # Real money account
environment=Environment.LIVE, # CRITICAL: Real money environment!
alias="live_trading", # Clear identification for production monitoring
)
# Step 2: Use practice configuration for strategy development and testing
# Always test thoroughly in practice before moving to live trading
async with AsyncClient(config=practice_config) as practice_client:
# Safe testing environment - no real money at risk
accounts = await practice_client.accounts.get_accounts()
print("Practice accounts:", len(accounts))
# This is where you would:
# - Develop and test trading strategies
# - Validate order placement logic
# - Test error handling and edge cases
# - Measure performance and latency
# Step 3: Use live configuration for real trading (CAUTION REQUIRED)
# Only proceed after thorough testing and validation in practice environment
async with AsyncClient(config=live_config) as live_client:
# DANGER: Real money trading - all losses are permanent!
accounts = await live_client.accounts.get_accounts()
print("Live accounts:", len(accounts))
# Production trading requirements:
# - Comprehensive testing completed in practice
# - Risk management systems in place
# - Position sizing appropriate for account balance
# - Stop losses and emergency procedures ready
# - Monitoring and alerting systems active
if __name__ == "__main__":
asyncio.run(main())
3. Environment Variables Pattern¶
Best for: Docker deployments, Kubernetes, CI/CD, serverless
Standard Environment Variables¶
The library automatically loads these environment variables:
Custom Environment Variable Prefixes¶
For multiple accounts or microservices:
import asyncio
from fivetwenty import AccountConfigLoader, AsyncClient
async def main() -> None:
# Step 1: Load configurations using custom environment variable prefixes
# Custom prefixes enable multiple trading strategies with different configurations
momentum_config = AccountConfigLoader.from_env_prefix("MOMENTUM_") # MOMENTUM_OANDA_TOKEN, etc.
grid_config = AccountConfigLoader.from_env_prefix("GRID_") # GRID_OANDA_TOKEN, etc.
# Step 2: Define async function for parallel strategy execution
# Separate clients allow different strategies to use different accounts/environments
async def run_strategies() -> None:
# Step 3: Initialize multiple clients concurrently using nested context managers
# Each client operates independently with its own configuration and connection pool
async with AsyncClient(config=momentum_config) as momentum_client:
async with AsyncClient(config=grid_config) as grid_client:
# Step 4: Execute account verification for both strategies simultaneously
# Parallel API calls reduce initialization time for multi-strategy systems
accounts1 = await momentum_client.accounts.get_accounts()
accounts2 = await grid_client.accounts.get_accounts()
print(f"Momentum accounts: {len(accounts1)}, Grid accounts: {len(accounts2)}")
# At this point, both clients are ready for their respective trading strategies:
# - momentum_client: For trend-following or momentum-based strategies
# - grid_client: For grid trading or range-bound strategies
# Each can operate on different accounts, environments, or with different parameters
# Step 5: Execute the multi-strategy setup
await run_strategies()
if __name__ == "__main__":
asyncio.run(main())
Environment variables for custom prefixes:
# Momentum strategy
export MOMENTUM_OANDA_TOKEN="momentum-token"
export MOMENTUM_OANDA_ACCOUNT="momentum-account"
export MOMENTUM_OANDA_ENVIRONMENT="practice"
export MOMENTUM_OANDA_ACCOUNT_ALIAS="momentum_strategy"
# Grid strategy
export GRID_OANDA_TOKEN="grid-token"
export GRID_OANDA_ACCOUNT="grid-account"
export GRID_OANDA_ENVIRONMENT="practice"
export GRID_OANDA_ACCOUNT_ALIAS="grid_strategy"
Configuration Priority¶
When multiple configuration methods are used, the priority is:
- Configuration object (highest priority)
- Direct parameters
- Environment variables (lowest priority)
import asyncio
import os
from fivetwenty import AsyncClient, AccountConfig, Environment
async def main() -> None:
# Step 1: Demonstrate configuration object priority (highest precedence)
# When config object is provided, it overrides all direct parameters
config = AccountConfig(
token=os.environ.get("CONFIG_TOKEN", "config-token"), # Token from config object
account_id=os.environ.get("CONFIG_ACCOUNT", "account-id"), # Account from config object
environment=Environment.PRACTICE # Environment from config object
)
async with AsyncClient(
token="direct-token", # IGNORED: Config object takes precedence
config=config, # USED: Configuration object has highest priority
) as client:
print(f"Connected: {client.config.summary()}")
# Output shows config object values, not direct parameter values
# Step 2: Demonstrate direct parameter priority over environment variables
# Direct parameters override environment variables when no config object is provided
async with AsyncClient(
token="direct-token", # USED: Direct parameter overrides FIVETWENTY_OANDA_TOKEN
environment=Environment.PRACTICE # USED: Direct parameter specification
) as client:
print(f"Connected: {client.config.summary()}")
# Even if FIVETWENTY_OANDA_TOKEN is set, "direct-token" is used instead
# Configuration priority hierarchy (highest to lowest):
# 1. Configuration object (config=AccountConfig(...))
# 2. Direct parameters (token="...", environment=...)
# 3. Environment variables (FIVETWENTY_OANDA_TOKEN, etc.)
# This hierarchy provides flexibility:
# - Environment variables for default/deployment settings
# - Direct parameters for script-specific overrides
# - Configuration objects for structured, reusable settings
if __name__ == "__main__":
asyncio.run(main())
Security Features¶
Automatic Secret Masking¶
The library automatically protects sensitive information:
import asyncio
import os
from fivetwenty import AccountConfig, AsyncClient, Environment
async def main() -> None:
# Step 1: Create configuration with sensitive credentials
# FiveTwenty automatically protects sensitive information for security
config = AccountConfig(
token=os.environ.get("SECRET_TOKEN", "super-secret-token"), # Sensitive API token
account_id=os.environ.get("SECRET_ACCOUNT", "secret-account-123"), # Sensitive account ID
environment=Environment.PRACTICE, # Environment selection
alias="my_account", # Non-sensitive alias for identification
)
# Step 2: Demonstrate automatic secret masking in string representations
# Secrets are automatically masked to prevent accidental exposure in logs/debug output
print(repr(config))
# Output: AccountConfig(alias='my_account', environment=practice, token=SecretStr('***'), account_id=SecretStr('***'))
# Notice: Token and account_id are masked with '***' for security
# Step 3: Use safe summary for logging and monitoring
# Summary method provides safe representation without exposing sensitive data
print(config.summary())
# Output: my_account (practice)
# Safe for application logs, monitoring dashboards, and debug output
# Step 4: Access actual configuration values when needed by the client
# The client can access real values internally while keeping them masked externally
async with AsyncClient(config=config) as client:
print(f"Using account: {client.account_id}")
# Output: Using account: secret-account-123
# Real value is accessible to the client for API operations
# Security benefits of automatic masking:
# - Prevents accidental credential exposure in logs
# - Safe debugging and development workflows
# - Secure monitoring and alerting systems
# - Compliance with security best practices
if __name__ == "__main__":
asyncio.run(main())
Validation¶
The library validates configuration values:
import os
from pydantic import ValidationError
from fivetwenty import AccountConfig, Environment
# Step 1: Demonstrate alias validation - invalid format
# AccountConfig validates that aliases follow Python identifier rules
try:
config = AccountConfig(
token=os.environ.get("TEST_TOKEN", "token"), # Valid token for testing
account_id=os.environ.get("TEST_ACCOUNT", "account"), # Valid account for testing
environment=Environment.PRACTICE, # Safe environment for validation testing
alias="123invalid", # ERROR: Cannot start with number - violates identifier rules
)
except ValidationError:
print("Alias must be a valid identifier")
# Fix: Use aliases like "account_123", "trading_bot_v1", or "strategy_momentum"
# Step 2: Demonstrate token validation - empty/whitespace tokens rejected
# Security requires non-empty tokens to prevent authentication bypasses
try:
config = AccountConfig(
token=" ", # ERROR: Whitespace-only token is invalid
account_id=os.environ.get("TEST_ACCOUNT", "account"), # Valid account for testing
environment=Environment.PRACTICE, # Safe environment for validation testing
alias="valid_alias", # Valid identifier format
)
except ValidationError:
print("Token cannot be empty")
# Security enforcement: Prevents accidental deployment with missing credentials
# Fix: Provide actual OANDA API token from account settings
# Validation benefits:
# - Early error detection before API calls
# - Prevents common configuration mistakes
# - Enforces security best practices
# - Clear error messages for debugging
Configuration Validation¶
Use the validator to check configuration:
import os
from fivetwenty import ConfigValidator, AccountConfig, Environment
# Step 1: Create configuration to validate
# ConfigValidator provides comprehensive validation beyond basic Pydantic checks
config = AccountConfig(
token=os.environ.get("YOUR_TOKEN", "your-token"), # API authentication token
account_id=os.environ.get("YOUR_ACCOUNT", "your-account-id"), # Target account ID
environment=Environment.PRACTICE # Trading environment selection
)
# Step 2: Run comprehensive configuration validation
# Validates format, security requirements, and logical consistency
errors = ConfigValidator.validate_account_config(config)
# Step 3: Handle validation results appropriately
if errors:
# Configuration has issues that need attention before deployment
for error in errors:
print(f"Configuration error: {error}")
# Recommended: Fix errors before proceeding to prevent runtime failures
else:
print("Configuration is valid")
# Safe to proceed with client initialization and trading operations
# ConfigValidator checks include:
# - Token format and security requirements
# - Account ID format validation
# - Environment consistency
# - Alias identifier compliance
# - Cross-field logical validation
Advanced Client Configuration¶
HTTP Client Options¶
import asyncio
import os
from fivetwenty import AsyncClient, Environment
async def main() -> None:
# Step 1: Configure AsyncClient with advanced HTTP options for production use
# These settings optimize performance, reliability, and monitoring for trading applications
async with AsyncClient(
token=os.environ.get("YOUR_TOKEN", "your-token"), # API authentication
environment=Environment.PRACTICE, # Safe environment for testing
timeout=60.0, # Extended timeout for slow networks or complex operations
max_retries=5, # Increased retry attempts for reliable order execution
user_agent="MyTradingApp/1.0", # Custom identification for monitoring and support
# proxies="http://proxy.example.com:8080", # Corporate proxy configuration
# verify=True, # SSL certificate verification (True = default CA bundle)
# verify="/path/to/ca-bundle.crt", # Custom CA bundle for private networks
# cert="/path/to/client-cert.pem" # Client certificate for mutual TLS authentication
) as client:
print(f"Connected: {client.config.summary()}")
# Advanced HTTP configuration benefits:
# - Timeout: Prevents hanging connections in volatile markets
# - Max retries: Improves reliability during network issues
# - User agent: Enables support team to identify your application
# - Proxy support: Works with corporate network infrastructure
# - SSL verification: Ensures secure communication with OANDA servers
# - Client certificates: Supports enterprise security requirements
if __name__ == "__main__":
asyncio.run(main())
Custom HTTP Transport¶
For advanced HTTP configuration:
import asyncio
import os
import httpx
from fivetwenty import AsyncClient, Environment
async def main() -> None:
# Step 1: Create custom httpx transport for advanced HTTP configuration
# This demonstrates the underlying HTTP client configuration options
transport = httpx.AsyncClient(
base_url=Environment.PRACTICE.base_url, # OANDA practice API base URL
timeout=httpx.Timeout(
connect=5.0, # Connection establishment timeout
read=60.0, # Response reading timeout (important for streaming)
write=10.0, # Request writing timeout
pool=60.0, # Connection pool timeout
),
limits=httpx.Limits(
max_connections=100, # Total connection pool size
max_keepalive_connections=20, # Persistent connections for performance
),
http2=False, # Disable HTTP/2 for compatibility (OANDA uses HTTP/1.1)
trust_env=True, # Honor proxy environment variables (HTTP_PROXY, etc.)
)
# Step 2: Use AsyncClient with standard configuration
# Custom transport configuration shown for educational purposes
async with AsyncClient(
token=os.environ.get("YOUR_TOKEN", "your-token"), # API authentication
environment=Environment.PRACTICE, # Safe practice environment
# transport=transport, # Custom transport not exposed in current FiveTwenty API
) as client:
print(f"Client configured: {client.config.environment}")
# Note: FiveTwenty manages httpx configuration internally for optimal OANDA API performance
# This example shows the underlying concepts for educational purposes
# The AsyncClient automatically configures appropriate timeouts, limits, and HTTP settings
# Custom transport benefits (handled internally by FiveTwenty):
# - Optimized connection pooling for trading workloads
# - Appropriate timeouts for financial market APIs
# - HTTP/1.1 compatibility with OANDA infrastructure
# - Environment variable support for proxy configuration
asyncio.run(main())
Logging Configuration¶
import asyncio
import logging
import os
from fivetwenty import AsyncClient, Environment
async def main() -> None:
# Step 1: Configure application logging for trading operations monitoring
# Proper logging is critical for debugging, monitoring, and compliance
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("my_trading_app")
# Step 2: Configure httpx logging to monitor HTTP requests/responses
# This reveals actual API communication for debugging and performance analysis
logging.getLogger("httpx").setLevel(logging.DEBUG)
# DEBUG level shows: request URLs, headers, response codes, timing
# Useful for troubleshooting connectivity and API issues
# Step 3: Initialize client with logging monitoring active
async with AsyncClient(
token=os.environ.get("YOUR_TOKEN", "your-token"), # API authentication
environment=Environment.PRACTICE # Safe environment for logging testing
) as client:
# Step 4: Execute operations with comprehensive logging
# All HTTP requests/responses are now logged for analysis
accounts = await client.accounts.get_accounts()
print(f"Found {len(accounts)} accounts")
# Logging benefits for trading applications:
# - Request/response debugging for API issues
# - Performance monitoring and latency analysis
# - Audit trail for compliance requirements
# - Error diagnosis for failed operations
# - Network troubleshooting capabilities
# Security note: Be careful with DEBUG logging in production
# - May expose sensitive data in request headers
# - Can generate large log volumes affecting performance
# - Consider INFO or WARNING levels for production systems
if __name__ == "__main__":
asyncio.run(main())
Sync Client Configuration¶
The sync Client supports the same configuration patterns:
import os
from fivetwenty import Client, AccountConfig, Environment
# Step 1: Direct parameter configuration for sync client
# Sync Client provides the same configuration options as AsyncClient
with Client(
token=os.environ.get("YOUR_TOKEN", "your-token"), # API authentication token
environment=Environment.PRACTICE # Safe practice environment
) as client:
# Synchronous API call - blocks until response received
accounts = client.accounts.get_accounts()
print(f"Found {len(accounts)} accounts")
# Sync client automatically manages async/sync conversion internally
# Step 2: Configuration object approach for structured settings
# Same AccountConfig works with both AsyncClient and sync Client
config = AccountConfig(
token=os.environ.get("YOUR_TOKEN", "your-token"), # API authentication
account_id=os.environ.get("YOUR_ACCOUNT", "your-account"), # Target account
environment=Environment.PRACTICE, # Environment selection
alias="example_config" # Human-readable identifier
)
with Client(config=config) as client:
# Configuration object provides same benefits in sync context
accounts = client.accounts.get_accounts()
print(f"Found {len(accounts)} accounts")
# Automatic validation, secret masking, and type safety
# Step 3: Environment variable configuration for deployment
# Standard FIVETWENTY_* environment variables work with sync client
with Client(
token=os.environ.get("FIVETWENTY_OANDA_TOKEN", "demo-token"), # Standard env var
environment=Environment.PRACTICE # Environment specification
) as client: # Automatically loads from FIVETWENTY_* variables
accounts = client.accounts.get_accounts()
print(f"Found {len(accounts)} accounts")
# Sync Client benefits:
# - Simpler programming model for blocking operations
# - No async/await syntax required
# - Same configuration flexibility as AsyncClient
# - Automatic async/sync conversion handled internally
# - Perfect for scripts, batch jobs, and synchronous workflows
Configuration Management Utilities¶
Configuration Builder¶
import json
import os
from typing import Any
from fivetwenty import AccountConfig, Environment
class ConfigBuilder:
"""Helper utility to build configurations from various enterprise sources."""
@staticmethod
def from_vault(vault_client: Any, secret_path: str, environment: str) -> AccountConfig:
"""Load configuration from HashiCorp Vault for enterprise secret management."""
# Step 1: Retrieve secrets from HashiCorp Vault
# Vault provides centralized, secure secret management for production systems
secret = vault_client.secrets.kv.v2.read_secret_version(
path=secret_path, # Vault path like "trading/oanda/credentials"
mount_point="secret", # Vault mount point for KV store
)
# Step 2: Extract secret data from Vault response format
# Vault wraps secret data in nested structure for versioning
data = secret["data"]["data"]
# Step 3: Build AccountConfig from Vault secrets with environment logic
# Environment parameter controls practice vs live environment selection
return AccountConfig(
token=data["token"], # OANDA API token from Vault
account_id=data["account_id"], # Account ID from Vault
environment=Environment.PRACTICE if environment == "practice" else Environment.LIVE,
alias=data.get("alias", "vault_account") # Optional alias with fallback
)
# Benefits: Centralized secret rotation, audit trails, access control
@staticmethod
def from_json_file(file_path: str) -> AccountConfig:
"""Load configuration from JSON file with secrets from environment variables."""
# Step 1: Load non-sensitive configuration from JSON file
# JSON file contains only non-secret configuration like environment and alias
with open(file_path, 'r') as f:
data = json.load(f)
# Example JSON: {"environment": "practice", "alias": "trading_bot_v1"}
# Step 2: Build configuration combining JSON config with environment secrets
# Secrets come from environment variables for security - never store in files
return AccountConfig(
token=os.environ["FIVETWENTY_OANDA_TOKEN"], # Secure: from environment
account_id=os.environ["FIVETWENTY_OANDA_ACCOUNT"], # Secure: from environment
environment=Environment(data["environment"]), # From JSON file
alias=data["alias"] # From JSON file
)
# Security pattern: Secrets in env vars, config in version-controlled files
Multi-Environment Manager¶
from fivetwenty import AccountConfig, AccountConfigLoader, AsyncClient
class ConfigManager:
"""Centralized configuration management for multi-environment deployments."""
def __init__(self) -> None:
# Step 1: Initialize configuration storage for multiple environments
# Each environment gets its own configuration with appropriate settings
self.configs: dict[str, AccountConfig] = {}
self._load_configs()
def _load_configs(self) -> None:
"""Load configurations for all deployment environments."""
# Step 2: Define environments for systematic configuration loading
# Standard deployment pipeline: development -> staging -> production
environments = ["development", "staging", "production"]
for env in environments:
# Step 3: Create environment-specific variable prefixes
# This enables different credentials and settings per environment
prefix = f"{env.upper()}_FIVETWENTY_" # DEV_FIVETWENTY_, STAGING_FIVETWENTY_, etc.
# Step 4: Load configuration using environment prefix pattern
# config = AccountConfigLoader.from_env_prefix(prefix) # Conceptual example
# if config:
# self.configs[env] = config
pass # Implementation depends on AccountConfigLoader availability
# Environment variable examples:
# DEV_FIVETWENTY_OANDA_TOKEN, DEV_FIVETWENTY_OANDA_ACCOUNT
# STAGING_FIVETWENTY_OANDA_TOKEN, STAGING_FIVETWENTY_OANDA_ACCOUNT
# PRODUCTION_FIVETWENTY_OANDA_TOKEN, PRODUCTION_FIVETWENTY_OANDA_ACCOUNT
def get_config(self, environment: str) -> AccountConfig | None:
"""Retrieve configuration for specific environment with validation."""
# Step 5: Safe configuration retrieval with None return for missing environments
return self.configs.get(environment)
def get_client(self, environment: str) -> AsyncClient:
"""Get configured client for specific environment with error handling."""
# Step 6: Retrieve configuration with validation
config = self.get_config(environment)
if not config:
# Clear error message for missing environment configuration
raise ValueError(f"No configuration found for environment: {environment}")
# Step 7: Return fully configured client for the environment
return AsyncClient(config=config)
# Client automatically inherits all environment-specific settings
# Step 8: Usage example for multi-environment configuration management
manager = ConfigManager()
# Get environment-specific clients with isolated configurations
dev_client = manager.get_client("development") # Development environment settings
prod_client = manager.get_client("production") # Production environment settings
print(f"Created {len(manager.configs)} configurations")
# Benefits of centralized configuration management:
# - Consistent configuration patterns across environments
# - Easy environment switching for deployments
# - Centralized validation and error handling
# - Type-safe configuration with compile-time checking
Troubleshooting¶
Common Configuration Errors¶
import os
from pydantic import ValidationError
from fivetwenty import AccountConfig, AsyncClient, Environment
# Step 1: Handle missing configuration error
# This demonstrates validation of required configuration parameters
try:
# Simulate missing environment variable scenario
async with AsyncClient(
token=os.environ.get("MISSING_TOKEN", ""), # Empty string when env var not set
environment=Environment.PRACTICE
) as client:
print(f"Connected: {client.config.summary()}")
except ValueError as e:
print(f"Configuration error: {e}")
# Fix: Set required environment variables before running application
# export FIVETWENTY_OANDA_TOKEN="your-api-token"
# export FIVETWENTY_OANDA_ACCOUNT="your-account-id"
# Step 2: Handle invalid alias format validation error
# AccountConfig enforces Python identifier rules for aliases
try:
config = AccountConfig(
token=os.environ.get("TEST_TOKEN", "token"), # Valid token for testing
account_id=os.environ.get("TEST_ACCOUNT", "account"), # Valid account for testing
environment=Environment.PRACTICE, # Safe environment
alias="123-invalid", # ERROR: Starts with number, contains hyphen
)
except ValidationError as e:
print(f"Validation error: {e}")
# Fix: Use valid Python identifier format
# Valid examples: "account_123", "trading_bot", "strategy_v1"
# Invalid examples: "123invalid", "account-name", "my strategy"
# Step 3: Handle empty token security validation
# Security validation prevents deployment with missing credentials
try:
config = AccountConfig(
token="", # ERROR: Empty token fails security validation
account_id=os.environ.get("TEST_ACCOUNT", "account"), # Valid account
environment=Environment.PRACTICE, # Safe environment
alias="my_account", # Valid alias
)
except ValidationError as e:
print(f"Token error: {e}")
# Fix: Provide valid OANDA API token from account settings
# Token format: Usually starts with hex characters, e.g., "abc123def456..."
# Error handling best practices:
# - Validate configuration early in application startup
# - Provide clear error messages with specific fix instructions
# - Use environment variables for secrets, never hardcode
# - Test configuration validation in CI/CD pipeline
Debug Configuration¶
import asyncio
import os
from fivetwenty import AsyncClient, Environment, ConfigValidator
async def main() -> None:
# Step 1: Initialize client and inspect active configuration
# This debug approach helps troubleshoot configuration issues
async with AsyncClient(
token=os.environ.get("YOUR_TOKEN", "your-token"), # API authentication
environment=Environment.PRACTICE # Safe environment for debugging
) as client:
# Step 2: Display detailed configuration information for debugging
# These properties help verify what configuration is actually being used
print(f"Account ID: {client.account_id}") # Configured account
print(f"Environment: {client.config.environment.value}") # practice or live
print(f"Alias: {client.config.alias}") # Human-readable name
print(f"Configuration summary: {client.config.summary()}") # Safe summary
# Step 3: Run comprehensive configuration validation
# Manual validation provides detailed analysis of potential issues
errors = ConfigValidator.validate_account_config(client.config)
if errors:
print("Configuration issues:", errors)
# Each error provides specific guidance for resolution
# Common issues: token format, alias validation, environment consistency
else:
print("Configuration is valid")
# Safe to proceed with trading operations
# Debug output helps with:
# - Verifying environment variable loading
# - Confirming configuration object usage
# - Identifying validation failures
# - Understanding configuration priority resolution
# - Troubleshooting authentication issues
if __name__ == "__main__":
asyncio.run(main())
Migration Guide¶
From Old Configuration¶
If you were using the previous configuration format:
import asyncio
import os
from fivetwenty import AsyncClient, Environment, AccountConfig
async def main() -> None:
# Step 1: Migration from deprecated API pattern
# Old way (no longer supported in current versions)
# client = AsyncClient("your-token", Environment.PRACTICE) # Deprecated positional args
# Step 2: Modern approach using named parameters
# New way provides explicit, readable configuration
async with AsyncClient(
token=os.environ.get("YOUR_TOKEN", "your-token"), # Named parameter for clarity
environment=Environment.PRACTICE # Explicit environment specification
) as client:
print(f"Connected: {client.config.summary()}")
# Benefits: Clear parameter names, IDE support, type safety
# Step 3: Recommended configuration object approach for production
# Configuration objects provide enhanced features and better organization
config = AccountConfig(
token=os.environ.get("YOUR_TOKEN", "your-token"), # API authentication
account_id=os.environ.get("YOUR_ACCOUNT", "your-account-id"), # Target account
environment=Environment.PRACTICE, # Environment selection
alias="my_account" # Human-readable identifier
)
async with AsyncClient(config=config) as client:
print(f"Connected: {client.config.summary()}")
# Configuration object advantages over direct parameters:
# - Automatic validation of all configuration values
# - Secret masking for security in logs and debug output
# - Reusable configuration across multiple client instances
# - Type safety with Pydantic models and IDE completion
# - Structured organization for complex applications
# - Support for advanced features like custom aliases
if __name__ == "__main__":
asyncio.run(main())
Next Steps¶
- Learn about environments and their differences
- See authentication for getting API tokens
- Review best practices for production deployment
- Check error handling for configuration-related errors