Skip to content

Configure multiple accounts

Use one configuration and client per account context. Distinct aliases make application logs and stored state easier to associate with the right account; they do not change OANDA permissions or account rules.

Configure prefixes

The loader prepends the prefix exactly as supplied. With "RESEARCH_", set:

RESEARCH_FIVETWENTY_OANDA_TOKEN=your-practice-token
RESEARCH_FIVETWENTY_OANDA_ACCOUNT=your-research-account-id
RESEARCH_FIVETWENTY_OANDA_ENVIRONMENT=practice

MONITOR_FIVETWENTY_OANDA_TOKEN=your-practice-token
MONITOR_FIVETWENTY_OANDA_ACCOUNT=your-monitor-account-id
MONITOR_FIVETWENTY_OANDA_ENVIRONMENT=practice

The generated aliases are research and monitor. There is no separate environment variable for the alias. Keep these values in the process environment or load them from a protected .env file before running the example.

Read both accounts

This script loads each required configuration, verifies practice mode and closes all created clients even if a read fails:

import asyncio
from contextlib import AsyncExitStack

from dotenv import load_dotenv
from fivetwenty import AccountConfigLoader, AsyncClient, Environment

load_dotenv()


async def main() -> None:
    async with AsyncExitStack() as stack:
        clients: list[AsyncClient] = []
        for prefix in ("RESEARCH_", "MONITOR_"):
            config = AccountConfigLoader.from_env_prefix(prefix)
            if config is None:
                message = f"Missing credentials for {prefix}"
                raise ValueError(message)
            if config.environment != Environment.PRACTICE:
                message = "Use practice accounts for this example"
                raise ValueError(message)
            clients.append(await stack.enter_async_context(AsyncClient(config=config)))
        for client in clients:
            result = await client.accounts.get_account_summary(client.account_id)
            account = result["account"]
            print(client.config.alias, account.balance, account.currency)


if __name__ == "__main__":
    asyncio.run(main())

Missing credentials are an error here, rather than permission to fall back to the default account. A malformed environment or alias can raise local validation errors instead of returning None.

Keep state separate

Store order IDs, trade IDs and transaction cursors with their account ID. Do not assume IDs from two accounts identify the same resource. Aggregate balances only after checking account currencies and choosing an explicit conversion policy.

Separate accounts can organize strategy state, but do not establish compliance with trading restrictions or eliminate correlated exposure. Use each account's reported configuration and permissions.

For direct AccountConfig construction and JSON files, see configuration. Closing a client also closes its injected HTTPX transport; avoid sharing one transport between independently closed clients.