SDK architecture¶
FiveTwenty translates between OANDA's JSON API and Python objects. The design has three main layers: clients manage HTTP connections, endpoint groups build requests and parse responses, and Pydantic models validate individual data objects.
flowchart LR
App[Application] --> Endpoints[Endpoint methods]
Endpoints --> REST[REST request and retry handling]
Endpoints --> Stream[Streaming HTTP reader]
REST --> HTTP[HTTPX client]
Stream --> HTTP
HTTP --> OANDA[OANDA API]
Endpoints --> Models[Pydantic models in response dictionaries]
Async and sync clients¶
AsyncClient is the primary implementation. It uses HTTPX for requests, connection
pooling and streaming. Reuse one client for related work on the same event loop and
close it with async with or await client.aclose().
Client runs an AsyncClient on a dedicated background event-loop thread. Ordinary
endpoint calls block until the corresponding coroutine finishes and return the
same response shape. Synchronous pricing uses client.pricing.stream_iter();
other async-generator methods are not converted into blocking iterators.
See async and sync clients for examples of creating clients, closing connections and consuming streams.
Requests and response envelopes¶
Endpoint methods include get_account_summary(), post_order(),
put_trade_orders() and cancel_order(). Most methods that operate on an account
take account_id first. Use the endpoint reference
to find a method's name, parameters and return type.
Most methods return a dictionary with OANDA's field names as keys and typed models
as values. This outer dictionary is called a response envelope: it holds the data
you requested along with metadata such as lastTransactionID.
from fivetwenty import AsyncClient
from fivetwenty.models import AccountSummary
async def read_account(client: AsyncClient) -> AccountSummary:
response = await client.accounts.get_account_summary(client.account_id)
account = response["account"]
print(response["lastTransactionID"])
return account
get_accounts() is an exception: it returns a list of account-property models.
Conditional transaction fields in write responses may be absent. Inspect the
returned keys rather than assuming every accepted request produced a fill.
Model values and wire values¶
Model attributes use Python names such as closeout_bid. When the SDK serializes
a model for an API request, it maps these names to OANDA aliases such as
closeoutBid and converts values to the API format. These serialized values are
often called wire values.
Financial attributes declared as Decimal remain decimals in Python. The public
PriceValue, AccountUnits and DecimalNumber aliases also represent Decimal values.
Timestamps become Python datetimes. datetime_format="UNIX" controls how timestamps
are sent and received; model attributes remain Python datetime objects. Python
retains microsecond precision, so finer precision in an API timestamp is lost.
from decimal import Decimal
from fivetwenty.models import LimitOrderRequest
order = LimitOrderRequest(instrument="EUR_USD", units=Decimal("1"), price=Decimal("1.05"))
order.units = Decimal("2")
wire = order.model_dump(mode="json", by_alias=True, exclude_none=True)
print(wire["units"]) # Serialized string: "2"
Models are mutable, with assignment validation enabled. Updating a local model never updates the account; send an endpoint request to change server state. Compatibility dictionary access on a model returns serialized values, so use attributes when you need native decimals, datetimes or nested model objects.
Retry and streaming behavior¶
REST retries apply to eligible read requests after selected status or transport
failures. max_retries counts attempts after the initial request. Writes are sent
once because a timeout does not establish whether the server processed them.
Basic pricing and transaction streams yield typed records from line-delimited
JSON. They report failures to the caller. stream_pricing_with_retries() adds a
reconnection policy and connection-state values. Reconnection does not replay
missed transactions or restore application state.
Close a partially consumed async stream with contextlib.aclosing. The sync pricing
iterator has a bounded queue of 1,024 records and drops the oldest queued record
when full. It is a current-price interface, not lossless storage.
Configuration and credentials¶
The client resolves an AccountConfig, direct credentials or environment variables
as described in configuration. It retains the token in memory
for Authorization headers. SecretStr masks configuration representations, and
SDK request logging redacts Authorization headers; application logs, dumps and
custom transports remain the application's responsibility.
The selected environment chooses practice or live hosts. Configuration is not a permission boundary: verify the resolved environment before an application is allowed to submit orders.
Testing and extension¶
REST and streams both use the configured HTTPX client. Inject an HTTPX client with
MockTransport to test requests and responses without contacting OANDA. Mocking
only _request() does not cover streaming. See the
testing guide.
Custom transports must supply the appropriate base URL and transport settings. Closing the SDK also closes an injected HTTPX client, so avoid sharing that HTTP client with code that needs it to stay open. The direct runtime dependencies are HTTPX and Pydantic. Your application handles plotting, strategy execution and storing account state.
While FiveTwenty is below version 1.0, minor releases may include breaking changes. The changelog records these changes and explains how to update affected code.