Placing Orders

This guide shows how to place orders using the Trading API REST endpoint. You'll learn how to:

  • Submit market orders for immediate execution
  • Place limit orders at specific prices
  • Add stop loss protection to your trades

REST Endpoint

Orders are submitted via HTTP POST to the /v1/orders endpoint:

POST http://localhost:8080/v1/orders
Content-Type: application/json

Order Request Fields

Every order requires these fields:

FieldTypeDescription
symbolstringTrading symbol in the provider's native format (e.g., AAPL, F:EURUSD)
sidestringBUY or SELL
quantitystringAmount to trade
order_typestringMARKET, LIMIT, STOP, STOP_LIMIT, TRAILING_STOP

Additional fields depending on order type:

FieldTypeRequired For
limit_pricestringLIMIT, STOP_LIMIT orders
stop_pricestringSTOP, STOP_LIMIT orders
stop_lossstringBracket orders (optional)
take_profitstringBracket orders (optional)
time_in_forcestringOptional (default: GTC)
client_order_idstringOptional (for idempotency)

Use the provider-native symbol. The Gateway forwards symbol to the upstream broker verbatim — it does not normalise it. Pass the exact string your strategy subscribed with (e.g., F:EURUSD, AAPL), not a normalised BASE/QUOTE slash form. The accepted form depends on the configured provider, so check what your broker expects.

Market Orders

Market orders execute immediately at the best available price. They're the simplest order type - just specify what you want to trade.

Python

import requests
import json

def place_market_order(symbol: str, side: str, quantity: str) -> dict:
    url = "http://localhost:8080/v1/orders"

    order = {
        "symbol": symbol,
        "side": side,
        "quantity": quantity,
        "order_type": "MARKET"
    }

    response = requests.post(url, json=order)
    response.raise_for_status()
    return response.json()

# Buy 100 shares of AAPL
result = place_market_order("AAPL", "BUY", "100")
print(f"Order ID: {result['id']}, Status: {result['status']}")

JavaScript

async function placeMarketOrder(symbol, side, quantity) {
  const response = await fetch('http://localhost:8080/v1/orders', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      symbol,
      side,
      quantity,
      order_type: 'MARKET'
    })
  });

  if (!response.ok) {
    throw new Error(`Order failed: ${response.status}`);
  }

  return response.json();
}

// Buy 100 shares of AAPL
const result = await placeMarketOrder('AAPL', 'BUY', '100');
console.log(`Order ID: ${result.id}, Status: ${result.status}`);

Java

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class MarketOrder {
    private static final HttpClient client = HttpClient.newHttpClient();
    private static final String BASE_URL = "http://localhost:8080";

    public static String placeMarketOrder(String symbol, String side, String quantity)
            throws Exception {
        String json = String.format("""
            {
                "symbol": "%s",
                "side": "%s",
                "quantity": "%s",
                "order_type": "MARKET"
            }
            """, symbol, side, quantity);

        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(BASE_URL + "/v1/orders"))
            .header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(json))
            .build();

        HttpResponse<String> response = client.send(request,
            HttpResponse.BodyHandlers.ofString());

        if (response.statusCode() != 201) {
            throw new RuntimeException("Order failed: " + response.statusCode());
        }

        return response.body();
    }

    public static void main(String[] args) throws Exception {
        String result = placeMarketOrder("AAPL", "BUY", "100");
        System.out.println("Order response: " + result);
    }
}

Rust

use reqwest::Client;
use serde::{Deserialize, Serialize};

#[derive(Serialize)]
struct OrderRequest {
    symbol: String,
    side: String,
    quantity: String,
    order_type: String,
}

#[derive(Deserialize, Debug)]
struct OrderHandle {
    id: String,
    status: String,
}

async fn place_market_order(
    client: &Client,
    symbol: &str,
    side: &str,
    quantity: &str,
) -> Result<OrderHandle, reqwest::Error> {
    let order = OrderRequest {
        symbol: symbol.to_string(),
        side: side.to_string(),
        quantity: quantity.to_string(),
        order_type: "MARKET".to_string(),
    };

    client
        .post("http://localhost:8080/v1/orders")
        .json(&order)
        .send()
        .await?
        .json()
        .await
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    let result = place_market_order(&client, "AAPL", "BUY", "100").await?;
    println!("Order ID: {}, Status: {}", result.id, result.status);
    Ok(())
}

Go

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
)

type OrderRequest struct {
	Symbol    string `json:"symbol"`
	Side      string `json:"side"`
	Quantity  string `json:"quantity"`
	OrderType string `json:"order_type"`
}

type OrderHandle struct {
	ID     string `json:"id"`
	Status string `json:"status"`
}

func placeMarketOrder(symbol, side, quantity string) (*OrderHandle, error) {
	order := OrderRequest{
		Symbol:    symbol,
		Side:      side,
		Quantity:  quantity,
		OrderType: "MARKET",
	}

	body, err := json.Marshal(order)
	if err != nil {
		return nil, err
	}

	resp, err := http.Post(
		"http://localhost:8080/v1/orders",
		"application/json",
		bytes.NewBuffer(body),
	)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusCreated {
		return nil, fmt.Errorf("order failed: %d", resp.StatusCode)
	}

	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	var handle OrderHandle
	if err := json.Unmarshal(respBody, &handle); err != nil {
		return nil, err
	}

	return &handle, nil
}

func main() {
	result, err := placeMarketOrder("AAPL", "BUY", "100")
	if err != nil {
		panic(err)
	}
	fmt.Printf("Order ID: %s, Status: %s\n", result.ID, result.Status)
}

Limit Orders

Limit orders let you specify the maximum price you'll pay (for buys) or minimum price you'll accept (for sells). They only execute when the market reaches your price.

Python

import requests

def place_limit_order(symbol: str, side: str, quantity: str, limit_price: str) -> dict:
    url = "http://localhost:8080/v1/orders"

    order = {
        "symbol": symbol,
        "side": side,
        "quantity": quantity,
        "order_type": "LIMIT",
        "limit_price": limit_price,
        "time_in_force": "GTC"  # Good Till Cancelled
    }

    response = requests.post(url, json=order)
    response.raise_for_status()
    return response.json()

# Buy AAPL at $150 or better
result = place_limit_order("AAPL", "BUY", "100", "150.00")
print(f"Limit order placed: {result['id']}")

JavaScript

async function placeLimitOrder(symbol, side, quantity, limitPrice) {
  const response = await fetch('http://localhost:8080/v1/orders', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      symbol,
      side,
      quantity,
      order_type: 'LIMIT',
      limit_price: limitPrice,
      time_in_force: 'GTC'
    })
  });

  if (!response.ok) {
    throw new Error(`Order failed: ${response.status}`);
  }

  return response.json();
}

// Buy AAPL at $150 or better
const result = await placeLimitOrder('AAPL', 'BUY', '100', '150.00');
console.log(`Limit order placed: ${result.id}`);

Java

public static String placeLimitOrder(String symbol, String side,
        String quantity, String limitPrice) throws Exception {
    String json = String.format("""
        {
            "symbol": "%s",
            "side": "%s",
            "quantity": "%s",
            "order_type": "LIMIT",
            "limit_price": "%s",
            "time_in_force": "GTC"
        }
        """, symbol, side, quantity, limitPrice);

    HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create("http://localhost:8080/v1/orders"))
        .header("Content-Type", "application/json")
        .POST(HttpRequest.BodyPublishers.ofString(json))
        .build();

    HttpResponse<String> response = client.send(request,
        HttpResponse.BodyHandlers.ofString());

    return response.body();
}

Rust

#[derive(Serialize)]
struct LimitOrderRequest {
    symbol: String,
    side: String,
    quantity: String,
    order_type: String,
    limit_price: String,
    time_in_force: String,
}

async fn place_limit_order(
    client: &Client,
    symbol: &str,
    side: &str,
    quantity: &str,
    limit_price: &str,
) -> Result<OrderHandle, reqwest::Error> {
    let order = LimitOrderRequest {
        symbol: symbol.to_string(),
        side: side.to_string(),
        quantity: quantity.to_string(),
        order_type: "LIMIT".to_string(),
        limit_price: limit_price.to_string(),
        time_in_force: "GTC".to_string(),
    };

    client
        .post("http://localhost:8080/v1/orders")
        .json(&order)
        .send()
        .await?
        .json()
        .await
}

Go

type LimitOrderRequest struct {
	Symbol      string `json:"symbol"`
	Side        string `json:"side"`
	Quantity    string `json:"quantity"`
	OrderType   string `json:"order_type"`
	LimitPrice  string `json:"limit_price"`
	TimeInForce string `json:"time_in_force"`
}

func placeLimitOrder(symbol, side, quantity, limitPrice string) (*OrderHandle, error) {
	order := LimitOrderRequest{
		Symbol:      symbol,
		Side:        side,
		Quantity:    quantity,
		OrderType:   "LIMIT",
		LimitPrice:  limitPrice,
		TimeInForce: "GTC",
	}

	body, _ := json.Marshal(order)
	resp, err := http.Post(
		"http://localhost:8080/v1/orders",
		"application/json",
		bytes.NewBuffer(body),
	)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	var handle OrderHandle
	json.NewDecoder(resp.Body).Decode(&handle)
	return &handle, nil
}

Idempotency with client_order_id

The optional client_order_id field lets the strategy attach its own identifier to a submission so the same order can be retried safely.

{
  "symbol": "AAPL",
  "side": "BUY",
  "quantity": "100",
  "order_type": "MARKET",
  "client_order_id": "strategy-7-entry-2025-01-15"
}

Rules:

  • Length limit: up to 64 characters. Longer values are rejected with a 400.
  • Uniqueness scope: within a single broker account, treat it as a primary key. The Gateway passes the value through to the upstream broker unchanged; it does not re-map or namespace it.
  • Lookup: after submission, the strategy can retrieve the order by its client ID with GET /v1/orders?client_order_id=<value>.
  • Duplicate submissions: behavior is broker-dependent. Alpaca rejects a duplicate client_order_id with a 422. Binance returns the existing order. The Gateway forwards whatever the upstream broker does — do not assume a uniform response. Design retry logic to treat a failed POST as "unknown result" and reconcile by querying GET /v1/orders?client_order_id=<value> before placing again. Allow for broker-side eventual consistency — a lookup issued immediately after a timed-out POST can return 404 even when the order was accepted. Wait a few seconds, retry the lookup, and only resubmit if the order is definitively absent.

Stop Loss Orders (Bracket Orders)

Protect your positions by adding a stop loss when you enter a trade. The Trading API supports bracket orders that automatically create a stop loss order alongside your entry.

Python

import requests

def place_bracket_order(
    symbol: str,
    side: str,
    quantity: str,
    stop_loss: str,
    take_profit: str = None
) -> dict:
    url = "http://localhost:8080/v1/orders"

    order = {
        "symbol": symbol,
        "side": side,
        "quantity": quantity,
        "order_type": "MARKET",
        "stop_loss": stop_loss,
    }

    if take_profit:
        order["take_profit"] = take_profit

    response = requests.post(url, json=order)
    response.raise_for_status()
    return response.json()

# Buy AAPL with stop loss at $145
result = place_bracket_order("AAPL", "BUY", "100", stop_loss="145.00")
print(f"Bracket order placed: {result['id']}")

# Buy with both stop loss and take profit
result = place_bracket_order(
    "AAPL", "BUY", "100",
    stop_loss="145.00",
    take_profit="165.00"
)

JavaScript

async function placeBracketOrder(symbol, side, quantity, stopLoss, takeProfit = null) {
  const order = {
    symbol,
    side,
    quantity,
    order_type: 'MARKET',
    stop_loss: stopLoss,
  };

  if (takeProfit) {
    order.take_profit = takeProfit;
  }

  const response = await fetch('http://localhost:8080/v1/orders', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(order)
  });

  if (!response.ok) {
    throw new Error(`Order failed: ${response.status}`);
  }

  return response.json();
}

// Buy AAPL with stop loss at $145 and take profit at $165
const result = await placeBracketOrder('AAPL', 'BUY', '100', '145.00', '165.00');

Java

public static String placeBracketOrder(String symbol, String side,
        String quantity, String stopLoss, String takeProfit) throws Exception {
    StringBuilder json = new StringBuilder();
    json.append("{");
    json.append(String.format("\"symbol\": \"%s\",", symbol));
    json.append(String.format("\"side\": \"%s\",", side));
    json.append(String.format("\"quantity\": \"%s\",", quantity));
    json.append("\"order_type\": \"MARKET\",");
    json.append(String.format("\"stop_loss\": \"%s\"", stopLoss));
    if (takeProfit != null) {
        json.append(String.format(",\"take_profit\": \"%s\"", takeProfit));
    }
    json.append("}");

    HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create("http://localhost:8080/v1/orders"))
        .header("Content-Type", "application/json")
        .POST(HttpRequest.BodyPublishers.ofString(json.toString()))
        .build();

    return client.send(request, HttpResponse.BodyHandlers.ofString()).body();
}

Rust

#[derive(Serialize)]
struct BracketOrderRequest {
    symbol: String,
    side: String,
    quantity: String,
    order_type: String,
    stop_loss: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    take_profit: Option<String>,
}

async fn place_bracket_order(
    client: &Client,
    symbol: &str,
    side: &str,
    quantity: &str,
    stop_loss: &str,
    take_profit: Option<&str>,
) -> Result<OrderHandle, reqwest::Error> {
    let order = BracketOrderRequest {
        symbol: symbol.to_string(),
        side: side.to_string(),
        quantity: quantity.to_string(),
        order_type: "MARKET".to_string(),
        stop_loss: stop_loss.to_string(),
        take_profit: take_profit.map(|s| s.to_string()),
    };

    client
        .post("http://localhost:8080/v1/orders")
        .json(&order)
        .send()
        .await?
        .json()
        .await
}

Go

type BracketOrderRequest struct {
	Symbol     string  `json:"symbol"`
	Side       string  `json:"side"`
	Quantity   string  `json:"quantity"`
	OrderType  string  `json:"order_type"`
	StopLoss   string  `json:"stop_loss"`
	TakeProfit *string `json:"take_profit,omitempty"`
}

func placeBracketOrder(symbol, side, quantity, stopLoss string, takeProfit *string) (*OrderHandle, error) {
	order := BracketOrderRequest{
		Symbol:     symbol,
		Side:       side,
		Quantity:   quantity,
		OrderType:  "MARKET",
		StopLoss:   stopLoss,
		TakeProfit: takeProfit,
	}

	body, _ := json.Marshal(order)
	resp, err := http.Post(
		"http://localhost:8080/v1/orders",
		"application/json",
		bytes.NewBuffer(body),
	)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	var handle OrderHandle
	json.NewDecoder(resp.Body).Decode(&handle)
	return &handle, nil
}

Atomicity

How the entry and its stop-loss / take-profit legs are placed depends on what the upstream broker supports. The Gateway picks one of three strategies per submission:

StrategyWhen it appliesAtomicity
Native bracketBroker supports a single-shot bracket order (e.g., Alpaca for equities).Entry + SL + TP are submitted as one atomic unit at the broker. If any leg is invalid, the whole order is rejected.
Native OCOBroker supports OCO pairs after an entry fill (e.g., Binance).Entry is placed first; after it fills, the SL/TP legs are placed as a single OCO pair.
Pending SL/TP fallbackBroker supports neither.Entry is placed first; only after it fills does the Gateway submit the SL and TP as separate orders.

Where the fallback can leave a gap. Under the pending SL/TP strategy, if the entry fills but the follow-up SL or TP placement fails (broker error, rate limit, connection drop), the position is open without its configured protection. The Gateway emits an error event with code POSITION_UNPROTECTED in this case. Strategies using SL/TP must register an error-event handler for this code and either flatten the position or escalate — silently logging it leaves the account exposed to a gap move.

Checking broker support. Call GET /v1/capabilities once at startup and inspect the features array:

{
  "features": ["bracket_orders", "trailing_stops"],
  ...
}

If "bracket_orders" is present, entries with stop_loss / take_profit use the native-bracket path; if it is absent, the Gateway uses the OCO or pending-SL/TP fallback.

Response: OrderHandle

When you submit an order, you receive an OrderHandle response:

{
  "id": "order_abc123",
  "status": "PENDING",
  "client_order_id": "my-order-1"
}
FieldDescription
idServer-assigned order ID
statusInitial status: PENDING, OPEN, or FILLED
client_order_idYour ID if you provided one

Full order details arrive via WebSocket events or a subsequent GET request to /v1/orders/{order_id}.

Error Handling

Orders can fail for various reasons. Check the HTTP status code and error response:

StatusMeaning
201Order submitted successfully
400Invalid request (bad parameters)
422Order rejected (insufficient funds, invalid symbol, etc.)
503Provider unavailable

Error response format:

{
  "code": "INSUFFICIENT_BALANCE",
  "message": "Insufficient balance to place order",
  "details": { "required": "15000.00", "available": "10000.00" }
}

Common rejection reasons:

  • INSUFFICIENT_MARGIN - Not enough margin for the trade
  • INSUFFICIENT_BALANCE - Not enough cash balance
  • INVALID_QUANTITY - Quantity below minimum or wrong step size
  • INVALID_PRICE - Price outside valid range
  • SYMBOL_NOT_TRADEABLE - Market closed or symbol not available

Next Steps