dYdX Market
Perpetuals only.
tribulnation-dydx, venue namedydx.
See the generic market interface for the shared method surface. This page covers only what is dYdX-specific.
Account
venue selects the network: dydx is mainnet, dydx_testnet is testnet. The built-in dydx account is accounts.Dydx(public=True), read-only.
Either address or mnemonic must resolve to a value. The address is required for all
indexer reads (subaccount, positions, orders, collateral). The mnemonic is only needed for
signing (placing/canceling orders). When both are provided, address is used directly;
when only mnemonic is given, the address is derived from it at construction time.
parent_subaccount picks the parent subaccount (see below).
Exchange & ID conventions
- The only exchange is
perp(exchange_id == 'perp'); any other exchange ID raises. - Market IDs are dYdX tickers:
<BASE>-USD, e.g.BTC-USD,ETH-USD. - Full SDK ID:
dydx:perp:BTC-USD(or<your-account-key>:perp:BTC-USD).
Subaccounts — the exchange qualifier
A dYdX subaccount is a margin/liquidation bucket, so it lives on the exchange, not on the market ID. The exchange qualifier selects it:
exchange_id |
Selects |
|---|---|
perp |
the account's parent subaccount (parent_subaccount, the default cross pool). |
perp.<N> |
subaccount <N>, validated N % 128 == parent_subaccount. |
So dydx:perp:BTC-USD addresses the parent/cross pool and dydx:perp.256:XAUT-USD addresses
child subaccount 256 (isolated). Child subaccounts of parent p are p, 128+p, 256+p, …;
any N failing N % 128 == p raises. Markets inherit the subaccount from their exchange,
so every account-scoped method (place_order, open_orders, query_order, perp_position,
available_notional, perp_collateral) keys off the same bucket by construction.
Migration: the old
<BASE>-USD:<N>market-ID suffix (parse_market_id) has been retired in bothexchange.pyandvenue.py. It was only half-wired —place_order/cancel_order/available_notionalhonored it whileopen_orders/query_order/perp_positionignored it and read the parent subaccount — and grep confirmed no callers used it. Move anydydx:perp:BTC-USD:<N>usage todydx:perp.<N>:BTC-USD. The.-qualifier lives inside the exchange segment, so it does not disturb the top-level<account>:<exchange>:<market>colon split.
Reads: aggregate vs scoped. The bare perp (parent) exchange reads parent-aggregate
history across the parent's child subaccounts (coherent for additive reads like trades and
funding). A qualified perp.<N> exchange scopes those reads to exactly that child subaccount.
Collateral never aggregates — it is always scoped to the addressed subaccount's own bucket.
Settings
place_order / cancel_order accept settings={'dydx': {...}}, typed by the dYdX
Settings TypedDict (market/impl/mixin.py). All keys are optional:
| Key | Type | Default | Meaning |
|---|---|---|---|
order_flags |
'SHORT_TERM' | 'LONG_TERM' | 'CONDITIONAL' |
'LONG_TERM' |
Order flags applied to all orders. |
limit_tif |
TimeInForce |
'GOOD_TIL_TIME' |
Time-in-force for LIMIT orders. |
market_tif |
TimeInForce |
'IMMEDIATE_OR_CANCEL' |
Time-in-force for MARKET orders. |
short_term_gtb |
int |
— | GTB delta for short-term orders: good-til-block = current_block + short_term_gtb. Only applied when order_flags == 'SHORT_TERM'. |
long_term_gtbt |
int |
— | GTBT delta (seconds) for long-term orders: good-til-block-time = current_block_time + long_term_gtbt. Only applied for LONG_TERM/CONDITIONAL flags. |
reduce_only |
bool |
False |
Place as reduce-only. |
Type mapping: POST_ONLY orders always use TIF POST_ONLY; MARKET uses market_tif;
LIMIT uses limit_tif.
Candles
candles serves every CandleInterval (CANDLE_INTERVALS is the full set) from the
indexer in its native newest-first pages without buffering the whole history. Both
timezone-aware bounds are required: start <= candle.time < end. The SDK does not
guarantee ordering. volume is baseTokenVolume, quote_volume is
usdVolume, and trades is reported.
Venue-specific semantics
available_notional= subaccountfreeCollateral× the market's maximum leverage, where max leverage is1 / effective_IMF(the initial-margin fraction, adjusted upward by open-interest caps per the dYdX margining docs).perp_collateralreturns the addressed subaccount's bucket.equityandfree_collateralcome straight from the indexerget_subaccountfields;initial_margin=equity - free_collateral(what dYdX's UI shows as "margin usage");maintenance_margin= Σ|notional|·effective_mmfandleverage= Σ|notional|/equityover the subaccount's open positions, priced at each market'soraclePrice.margin_modeis'cross'when the subaccount is< 128(parent) else'isolated'(child). dYdX exposes no per-market mode, soMarket.perp_collateral()just delegates to its exchange. Maintenance margin is derived viaeffective_mmf— the basemaintenanceMarginFractionscaled by the same open-interest factor aseffective_IMF(effective_imf · base_mmf / base_imf), so it isn't understated at high OI.indexreturns the market'soraclePrice; it raisesApiErrorif unavailable.perp_positionaggregates all open positions for the parent subaccount in that market into a single net size and average entry price.query_orderis overridden to query the indexer directly, so it can return filled/canceled states — not just open ones.cancel_orderssplits by flag: short-term orders (order_flags == 0) go through a batch cancel, long-term orders are cancelled one by one.- Order IDs returned by the SDK are base64-encoded dYdX protocol
OrderIds.
Example: short-term IOC order
The settings payload maps directly onto dYdX order flags and expiry:
import os
from tribulnation.sdk import MarketSDK, accounts
from dotenv import load_dotenv
load_dotenv()
market = MarketSDK(
{
'dydx-account1': accounts.Dydx(),
}
)
await market.place_order(
'dydx-account1:perp:BTC-USD',
{'price': 10, 'qty': 0.00001, 'type': 'LIMIT'},
settings={
'dydx': {
'limit_tif': 'IMMEDIATE_OR_CANCEL',
'order_flags': 'SHORT_TERM',
'short_term_gtb': 2,
}
},
)