Quickstart
Install the SDK, set your keys, connect a practice broker and send a first order through reTrade.
Install
pip install retrade==0.1.2npm install retrade@0.1.2The package is retrade on PyPI and on npm. Python needs 3.12 or later; Node needs 22.18 or later. Both install a retrade command. The SDK is pre-release, so pin the version you test with.
Get your keys
You need four things. Your admin and reTrade give you three; you make the fourth.
| What | Looks like | Where it comes from |
|---|---|---|
| Executor key file | executor.pem | You make it, below, and keep it. reTrade only ever sees its public half. |
| Bot key id | exk_test_ and 26 more characters | Your admin, after registering your key. |
| Root public key | 43 characters | reTrade, when your tenant is set up. |
| Account ref | the name your admin gave the account | Your admin. |
retrade keygen executor.pemnpx retrade keygen executor.pemThis writes a new private key, readable only by you, and prints its public half. Send only the public half to your admin. They register it for one mode and send back your bot key id. Start with a test key: its orders are checked like real ones, but its decisions aren’t evidence for an insurer. Keep executor.pem secret: anyone holding it can ask for decisions as your agent.
# Your admin runs this with their own admin key.
# The admin command line is in development.
retrade-admin keys add-executor --mode test <public key>Set your environment
export RETRADE_EXECUTOR_KEY_ID="<bot key id from your admin>"
export RETRADE_EXECUTOR_KEY_PATH="$PWD/executor.pem"
export RETRADE_ROOT_KEY="<root public key from reTrade>"
export RETRADE_ACCOUNT="<account ref from your admin>"
export RETRADE_AGENT="<this agent's id>"These names are a convention for your own code: the samples read them and pass the values to the SDK. The SDK itself reads one variable, RETRADE_BASE_URL, and only when you pass no address. Without either, it uses reTrade’s hosted service, https://api.retrade.app.
Connect a practice broker
The SDK reads the account and places orders only through an adapter: four functions you write against your broker’s API. For a first run, this one keeps a pretend account in memory and records orders instead of sending them anywhere.
from datetime import UTC, datetime
from decimal import Decimal
import retrade
placed: list[str] = [] # the tags of the orders this pretend broker took
def read_account(
instrument: str | None, adjustments_since: datetime | None
) -> tuple[retrade.Snapshot, retrade.Quote | None]:
now = datetime.now(UTC)
snapshot = retrade.Snapshot(
as_of=now,
cash={"USD": Decimal("50000")},
settled_cash={"USD": Decimal("50000")},
positions=(),
open_orders=(),
)
if instrument is None:
return snapshot, None
quote = retrade.Quote(instrument, Decimal("187.10"), Decimal("187.08"),
Decimal("187.12"), now)
return snapshot, quote
def submit(order: retrade.ApprovedOrder, tag: str) -> str:
placed.append(tag)
return f"paper-{len(placed)}"
def find(tag: str, order: retrade.ApprovedOrder, since: datetime) -> str | None:
return f"paper-{placed.index(tag) + 1}" if tag in placed else None
def cancel(broker_order_id: str) -> None:
pass # nothing rests at this pretend broker
broker = retrade.GenericAdapter(
snapshot=read_account, submit=submit, find=find, cancel=cancel,
g_seconds=60, converts_cash=False,
)import { GenericAdapter } from "retrade";
const placed: string[] = []; // the tags of the orders this pretend broker took
const broker = new GenericAdapter({
snapshot(instrument) {
const asOf = new Date();
const snapshot = {
asOf,
cash: { USD: "50000" },
settledCash: { USD: "50000" },
positions: [],
openOrders: [],
};
const quote = instrument === null
? null
: { instrument, last: "187.10", bid: "187.08", ask: "187.12", asOf };
return { snapshot, quote };
},
submit(order, tag) {
placed.push(tag);
return `paper-${placed.length}`;
},
find(tag) {
const index = placed.indexOf(tag);
return index === -1 ? null : `paper-${index + 1}`;
},
cancel() {
// nothing rests at this pretend broker
},
gSeconds: 60,
convertsCash: false,
});The adapter’s g_seconds and converts_cash (gSeconds and convertsCash in TypeScript) must equal the account’s settings at reTrade; 60 and false are the SDK’s defaults. Ask your admin if the account uses others. When you’re ready, swap in your real broker: see Connect your broker.
Run your first check
import os
from decimal import Decimal
from retrade import Client, Order, OrderType, Side, Tif
with Client(
os.environ["RETRADE_EXECUTOR_KEY_ID"],
os.environ["RETRADE_EXECUTOR_KEY_PATH"],
root_key=os.environ["RETRADE_ROOT_KEY"],
) as rt:
order = Order("XNAS:AAPL", Side.BUY, 40, OrderType.LIMIT, Tif.DAY,
limit_price=Decimal("187.25"))
result = rt.place(order, account=os.environ["RETRADE_ACCOUNT"],
agent=os.environ["RETRADE_AGENT"], via=broker)
why = result.deciding or result.reason
print(result.outcome, result.placement, result.quantity, why)import { Client } from "retrade";
function env(name: string): string {
const value = process.env[name];
if (!value) throw new Error(`${name} is not set`);
return value;
}
const rt = await Client.open({
keyId: env("RETRADE_EXECUTOR_KEY_ID"),
keyPath: env("RETRADE_EXECUTOR_KEY_PATH"),
rootKey: env("RETRADE_ROOT_KEY"),
});
try {
const order = {
instrument: "XNAS:AAPL", side: "buy", quantity: 40,
type: "limit", tif: "day", limitPrice: "187.25",
} as const;
const result = await rt.place(order, {
account: env("RETRADE_ACCOUNT"), agent: env("RETRADE_AGENT"), via: broker,
});
const why = result.deciding ?? result.reason;
console.log(result.outcome, result.placement, result.quantity, why);
} finally {
await rt.close();
}Save the practice broker and this check in one file. In Python, as first_check.py, run with python first_check.py. In TypeScript, as first-check.mts, run with node first-check.mts: Node 22.18 and later run TypeScript files directly.
Read the answer
place returns a result. A BLOCK, or reTrade not answering, is a result too, never an exception. The fields you’ll use first:
| Field | What it tells you |
|---|---|
outcome | ALLOW, CUT or BLOCK. |
placement | placed, not_placed, or unknown when the broker call’s outcome isn’t known. Never send an unknown order again. |
quantity | What was approved: smaller than you asked on a CUT. |
deciding | On a CUT or BLOCK, the rule that decided, such as {"code": "order_size_cap"}. |
fits | On a BLOCK over a dollar line or in an agent conflict, the largest quantity that would pass. |
reason | The SDK’s own reason when it decided without reTrade, such as unreachable. |
decision_id (decisionId) | The receipt’s id. Store it with your own order record. |
If reTrade doesn’t answer within 2.5 seconds, you get BLOCK with reason unreachable, and nothing is placed. That’s fail-closed.
Next steps
- Connect your real broker: Python or TypeScript.
- Handle every answer: ALLOW, CUT and BLOCK.
- Adding reTrade to a bot you already have? Give your coding agent SKILL.md.