Documentation

Code Examples

Examples for common tasks with the TypeScript and Python SDKs. Each section says what the API does with the call today.

⚠️

Not published yet

Neither SDK is on npm or PyPI yet. Do not install a package called @trading-platform/sdk or trading-platform-sdk from those registries. Until we publish them, call the REST API directly as the API Quick Start shows.

What the API Does Today

AreaSDK callsToday
Accountsaccounts.list, accounts.getThe broker accounts you linked, with their balances
Market ordersorders.create with type marketSent to your broker, on a demo MetaTrader account only
Limit and stop ordersorders.create with type limit, stop or stop_limitRefused with 501 ORDER_TYPE_NOT_SUPPORTED: nothing is placed
Order recordsorders.list, orders.getNot kept after the API restarts
Changing and cancelling ordersorders.modify, orders.cancelRefused: 409 ORDER_IN_FLIGHT while an order is pending, 400 once it has filled or been rejected
Positions and trade historypositions, tradesRead from your broker; stop loss, take profit and closing on a demo MetaTrader account only
Quotes and candlesmarketData.getQuote, marketData.getCandlesNo price feed yet: 501 NOT_IMPLEMENTED

Authentication Setup

Create one client with your API key and keep the key in an environment variable. The examples below reuse this client.

TypeScript

import { AuthenticationError, TradingClient } from "@trading-platform/sdk";

const client = new TradingClient({
  apiKey: process.env.TRADING_PLATFORM_API_KEY,
});

// A new key can list accounts, so this checks that the key works
try {
  const accounts = await client.accounts.list();
  console.log(`Connected: ${accounts.length} linked accounts`);
} catch (error) {
  if (!(error instanceof AuthenticationError)) throw error;
  console.error("The API refused the key:", error.message);
}

Python

import os

from trading_platform import AuthenticationError, TradingClient

client = TradingClient(api_key=os.environ["TRADING_PLATFORM_API_KEY"])

# A new key can list accounts, so this checks that the key works
try:
    accounts = client.accounts.list()
    print(f"Connected: {len(accounts)} linked accounts")
except AuthenticationError as error:
    print(f"The API refused the key: {error.message}")

Call client.close() when you are done, or open the client in a with block.


Placing Orders

Placing an order needs Trading → Write or Orders → Write on the key; a new key answers 403 INSUFFICIENT_SCOPE. The API accepts an order only on an account you linked that is classified for trading (purpose is trading), is a demo account (isDemo is true) and is a MetaTrader 4 or 5 account.

Market Order

The API answers with the order in status pending and then sends it to your broker. Read the order again to see whether the broker filled or rejected it.

// TypeScript
const METATRADER = ["mt4", "mt5", "metatrader4", "metatrader5"];

async function findDemoTradingAccount() {
  const accounts = await client.accounts.list();
  return accounts.find(
    (a) =>
      a.purpose === "trading" && a.isDemo && METATRADER.includes(a.brokerType),
  );
}

async function placeMarketOrder() {
  const account = await findDemoTradingAccount();
  if (!account) {
    throw new Error("Link a demo MetaTrader account for trading first");
  }

  const order = await client.orders.create({
    accountId: account.id,
    symbol: "EURUSD",
    side: "buy",
    type: "market",
    volume: 0.1,
    stopLoss: 1.092,
    takeProfit: 1.105,
  });
  console.log(order.id, order.status); // "pending"

  // The broker answers after the API does
  await new Promise((resolve) => setTimeout(resolve, 2000));
  const result = await client.orders.get(order.id);
  console.log(result.status, result.brokerOrderId); // "filled" or "rejected"
  return result;
}
# Python
import time

METATRADER = {"mt4", "mt5", "metatrader4", "metatrader5"}


def find_demo_trading_account(client):
    for account in client.accounts.list():
        if (
            account.purpose == "trading"
            and account.is_demo
            and account.broker_type in METATRADER
        ):
            return account
    return None


def place_market_order(client):
    account = find_demo_trading_account(client)
    if account is None:
        raise RuntimeError("Link a demo MetaTrader account for trading first")

    order = client.orders.create(
        account_id=account.id,
        symbol="EURUSD",
        side="buy",
        volume=0.1,
        order_type="market",
        stop_loss=1.092,
        take_profit=1.105,
    )
    print(order.id, order.status)  # pending

    # The broker answers after the API does
    time.sleep(2)
    result = client.orders.get(order.id)
    print(result.status, result.broker_order_id)  # filled or rejected
    return result

A filled order carries no fill price: the broker's reply to the API does not include one.

Limit and Stop Orders

⚠️

Not accepted yet

The API refuses limit, stop and stop_limit orders with 501 ORDER_TYPE_NOT_SUPPORTED before it reads the account, and records nothing. Both SDKs raise this as an APIError. Place market orders only.


Position Management

positions.list() reads your open positions from your broker when you call it. Pass a position's id, as it comes, to positions.get, to the stop loss and take profit calls, and to positions.close. A broker that cannot be read raises an APIError (503, 429, or the account's own refusal), never an empty list. It returns one page, 20 positions unless you pass a limit of up to 100: the positions as data and the page as pagination. While pagination.hasMore is true, ask for the next page.

⚠️

Changes reach your broker, on demo accounts only

Moving a stop loss or take profit and closing are sent to your broker, on a demo account classified for trading; a live account answers 403 LIVE_ACCOUNT_RESTRICTED. On a 504 BROKER_OUTCOME_UNKNOWN the change may have been made: read the position before you try again, because a repeated partial close closes more.

To check what a market order did, read the order: orders.get(id) returns its status and the broker's order id, as in the market order example above.


Market Data

⚠️

No prices yet

The quote, candle and tick endpoints are not connected to a market data feed yet. marketData.getQuote and marketData.getCandles answer 501 NOT_IMPLEMENTED, which both SDKs raise as an APIError; they return no prices. Take prices from your broker's terminal until the feed is connected.


Risk Management Examples

Position Size Calculator

// TypeScript
// Lots that lose riskPercent of the balance if the stop loss is hit.
// pipValuePerLot is 10 for a standard lot of a USD-quoted pair on a USD
// account.
function positionSize(
  balance: number,
  riskPercent: number,
  entry: number,
  stopLoss: number,
  pipSize = 0.0001,
  pipValuePerLot = 10,
): number {
  const riskAmount = balance * (riskPercent / 100);
  // To 0.1 pip: 1.1 - 1.095 is not exactly 0.005 in floating point
  const stopPips = Math.round((Math.abs(entry - stopLoss) / pipSize) * 10) / 10;
  const lots = riskAmount / (stopPips * pipValuePerLot);
  return Math.floor(lots * 100) / 100; // round down to 0.01 lots
}

// 10,000 balance, 2% risk, 50-pip stop loss
console.log(positionSize(10000, 2, 1.1, 1.095)); // 0.4

Daily Loss Limit Check

The API does not report daily profit and loss for your broker accounts yet. Record the account's equity when your trading day starts and compare its current equity with that figure.

// TypeScript
import type { CreateOrderRequest } from "@trading-platform/sdk";

async function withinDailyLossLimit(
  accountId: string,
  startOfDayEquity: number,
  maxLossPercent = 3,
): Promise<boolean> {
  const account = await client.accounts.get(accountId);
  const lossPercent = Math.max(
    0,
    ((startOfDayEquity - account.equity) / startOfDayEquity) * 100,
  );
  console.log(`Daily loss ${lossPercent.toFixed(2)}% of ${maxLossPercent}%`);
  return lossPercent < maxLossPercent;
}

// Check before every order
async function placeOrderWithinLimit(
  request: CreateOrderRequest,
  startOfDayEquity: number,
) {
  if (!(await withinDailyLossLimit(request.accountId, startOfDayEquity))) {
    throw new Error("Daily loss limit reached: no order placed");
  }
  return client.orders.create(request);
}

Limits the API Enforces

The API refuses an order with 400 RISK_LIMIT_EXCEEDED when it is larger than 10 lots, when you have already placed 100 orders that day, or when 50 of your orders are pending or open.


Error Handling

Every SDK error extends APIError, which carries the API's error code, the HTTP status and the message. The SDK picks the class from the status:

StatusTypeScript / Python classCodes you may see
400ValidationErrorVALIDATION_ERROR, RISK_LIMIT_EXCEEDED, UNSUPPORTED_BROKER
401AuthenticationErrorUNAUTHORIZED
403APIErrorINSUFFICIENT_SCOPE, ACCOUNT_PURPOSE_RESTRICTED, LIVE_ACCOUNT_RESTRICTED
404NotFoundErrorNOT_FOUND, ACCOUNT_NOT_FOUND
429RateLimitErrorRATE_LIMIT_EXCEEDED. The TypeScript SDK waits for Retry-After and retries while it has retries left, except orders.create and positions.close, which are sent once
Any otherAPIError409 APPROVAL_REQUIRED or ORDER_IN_FLIGHT, 501 NOT_IMPLEMENTED or ORDER_TYPE_NOT_SUPPORTED, 503 BROKER_UNAVAILABLE
No responseConnectionErrorNone: the request timed out or the connection failed
⚠️

Do not resend orders blind

Neither SDK resends orders.create: the first attempt may already have placed the order, so a lost answer raises ConnectionError instead. Look for the order before you send it again.

Handling Errors in TypeScript

import { randomUUID } from "node:crypto";

import {
  APIError,
  AuthenticationError,
  ConnectionError,
  NotFoundError,
  TradingClient,
  ValidationError,
  type CreateOrderRequest,
} from "@trading-platform/sdk";

// orders.create is sent once: the SDK never resends an order
const orderClient = new TradingClient({
  apiKey: process.env.TRADING_PLATFORM_API_KEY,
});

async function placeOrder(request: Omit<CreateOrderRequest, "clientId">) {
  // Your own id for the order, to find it after a lost response
  const clientId = randomUUID();
  try {
    return await orderClient.orders.create({ ...request, clientId });
  } catch (error) {
    if (error instanceof ConnectionError) {
      // No response: the order may have been placed. Look for it.
      const recent = await orderClient.orders.list({
        accountId: request.accountId,
        symbol: request.symbol,
      });
      const placed = recent.data.find((order) => order.clientId === clientId);
      if (placed) return placed;
      throw error; // not found yet: check again before you resend
    }
    if (error instanceof AuthenticationError) {
      console.error("The key is missing, unknown, expired or revoked");
    } else if (error instanceof ValidationError) {
      console.error(`Refused: ${error.code} ${error.message}`);
    } else if (error instanceof NotFoundError) {
      console.error(`Not found: ${error.code} ${error.message}`);
    } else if (error instanceof APIError) {
      console.error(`${error.statusCode} ${error.code}: ${error.message}`);
    }
    throw error;
  }
}

Handling Errors in Python

The Python SDK's orders.create takes no client id. After a ConnectionError, list the account's orders and decide whether one of them is yours before you send it again.

import os

from trading_platform import (
    APIError,
    AuthenticationError,
    TradingClient,
)
from trading_platform import ConnectionError as SDKConnectionError

# orders.create is sent once: the SDK never resends an order
order_client = TradingClient(
    api_key=os.environ["TRADING_PLATFORM_API_KEY"],
)


def place_order(account_id, symbol, side, volume):
    try:
        return order_client.orders.create(
            account_id=account_id, symbol=symbol, side=side, volume=volume
        )
    except SDKConnectionError:
        # No response: the order may have been placed. Check
        # order_client.orders.list(account_id=account_id, symbol=symbol)
        # before you send it again.
        raise
    except AuthenticationError:
        print("The key is missing, unknown, expired or revoked")
        raise
    except APIError as error:
        # ValidationError (400) and NotFoundError (404) land here too
        print(f"{error.status_code} {error.code}: {error.message}")
        raise

Next Steps

⚠️

Risk Disclosure

Automated trading carries significant risk. Test thoroughly on a demo account, use proper risk management, and never trade money you cannot afford to lose.