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:
| Field | Type | Description |
|---|---|---|
symbol | string | Trading symbol in the provider's native format (e.g., AAPL, F:EURUSD) |
side | string | BUY or SELL |
quantity | string | Amount to trade |
order_type | string | MARKET, LIMIT, STOP, STOP_LIMIT, TRAILING_STOP |
Additional fields depending on order type:
| Field | Type | Required For |
|---|---|---|
limit_price | string | LIMIT, STOP_LIMIT orders |
stop_price | string | STOP, STOP_LIMIT orders |
stop_loss | string | Bracket orders (optional) |
take_profit | string | Bracket orders (optional) |
time_in_force | string | Optional (default: GTC) |
client_order_id | string | Optional (for idempotency) |
Use the provider-native symbol. The Gateway forwards
symbolto the upstream broker verbatim — it does not normalise it. Pass the exact string your strategy subscribed with (e.g.,F:EURUSD,AAPL), not a normalisedBASE/QUOTEslash 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_idwith 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 queryingGET /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:
| Strategy | When it applies | Atomicity |
|---|---|---|
| Native bracket | Broker 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 OCO | Broker 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 fallback | Broker 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"
}
| Field | Description |
|---|---|
id | Server-assigned order ID |
status | Initial status: PENDING, OPEN, or FILLED |
client_order_id | Your 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:
| Status | Meaning |
|---|---|
| 201 | Order submitted successfully |
| 400 | Invalid request (bad parameters) |
| 422 | Order rejected (insufficient funds, invalid symbol, etc.) |
| 503 | Provider 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 tradeINSUFFICIENT_BALANCE- Not enough cash balanceINVALID_QUANTITY- Quantity below minimum or wrong step sizeINVALID_PRICE- Price outside valid rangeSYMBOL_NOT_TRADEABLE- Market closed or symbol not available
Next Steps
- Event Handling - React to order fills and market data
- Order Types Reference - Full order type documentation
- REST Endpoints - All available endpoints