# Strategy Development

This page covers the development patterns you use most often when building
strategies with the **Strategy SDK**: declaring **parameters**, placing
orders, handling **market-data callbacks**, handling **order-entry
callbacks**, and reading **rate-limit load**.

---

## Strategy parameters

A **parameter** is a configurable field on a strategy. Parameters let you
change behaviour without modifying or rebuilding code: each strategy
instance is constructed from a JSON config that supplies values for them.

### Basic parameter declaration

Annotate a field on your strategy class with `@Parameter`:

+++ :icon-code: Java

```java 
class MyStrategy implements Strategy {

  @Parameter
  Integer myIntParameter;
}
```

+++ :icon-list-unordered: Config

```json 
{
  "instanceName": "my_strategy_instance",
  "strategyName": "MyStrategy",
  "parameters": {
    "myIntParameter": 42
  }
}
```

+++

### Optional parameters

A parameter can be optional, with a default applied when the config omits
it:

+++ :icon-code: Java

```java
class MyStrategy implements Strategy {

  @Parameter(optional = true)
  Integer myOptionalIntParameter = 10;
}
```

+++ :icon-list-unordered: Config

```json
{
  "instanceName": "my_strategy_instance",
  "strategyName": "MyStrategy",
  "parameters": {}
}
```

+++

### Custom parameter names

By default, the parameter name is the Java field name. Override it with
`name`:

+++ :icon-code: Java

```java
class MyStrategy implements Strategy {

  @Parameter(name = "myRenamedIntParameter")
  Integer myIntParameter;
}
```

+++ :icon-list-unordered: Config

```json
{
  "instanceName": "my_strategy_instance",
  "strategyName": "MyStrategy",
  "parameters": {
    "myRenamedIntParameter": 42
  }
}
```

+++

### Composite parameters

Parameters can nest. Annotate fields on your own classes with `@Parameter`
to build a hierarchy:

+++ :icon-code: Java

```java
class MyStrategy implements Strategy {

  @Parameter
  MyCompositeParameter myCompositeParameter;

  class MyCompositeParameter {

    @Parameter
    Integer myIntParameter;

    @Parameter
    MyNestedParameter myNestedParameter;
  }

  class MyNestedParameter {

    @Parameter
    Integer myIntParameter;
  }
}
```

+++ :icon-list-unordered: Config

```json
{
  "instanceName": "my_strategy_instance",
  "strategyName": "MyStrategy",
  "parameters": {
    "myCompositeParameter": {
      "myIntParameter": 42,
      "myNestedParameter": {
        "myIntParameter": 42
      }
    }
  }
}
```

+++

### Supported parameter types

**Boxed primitives**

- `Boolean`
- `Integer`
- `Long`
- `Float`
- `Double`
- `String`

**Decimal types**

- `BigDecimal`
- `Price`
- `Quantity`

**SDK types**

- `DataInstrument`
- `TradingInstrument`
- `Account`
- `TwoSides<T>`, where `T` is any other supported type

**Collections**

- `List<T>`, where `T` is any other supported type

**Custom classes**

- Your own classes containing any of the supported types, with each field
  annotated `@Parameter`

---

## Placing orders

### Basic placement

A strategy typically has a `TradingInstrument` parameter. To place an
order, build an `OrderParams` with side, price, and quantity, and pass it
to `TradingInstrument.placeOrder`.

Side, price, and quantity are all you need for a good-till-cancel limit
order: `type` defaults to `LIMIT` and `timeInForce` to `GOOD_TILL_CANCEL`.
Everything else on `OrderParams` is for more sophisticated orders.

A buy of quantity `1` at price `10`:

```java Basic order placement

@Parameter
TradingInstrument tradingInstrument;

...

@Override
void onInstrumentSnapshot(DataInstrument instrument) {
  Order order = tradingInstrument.placeOrder(
      OrderParams.builder()
          .side(OrderSide.BID)
          .price(Price.valueOf(10.0))
          .quantity(Quantity.valueOf(1))
          .build());
}
```

> [!NOTE]
> Right after placing an order, you can check whether it has already been
> discarded. Otherwise the strategy will receive an
> `Strategy.onOrderUpdate(Order order)` callback when the order is
> confirmed. If the order is rejected, the strategy receives an
> `Strategy.onOrderError(Order order, OrderError orderError)` callback
> instead. The error may come from the Trading Adapter (a rate limit, for
> example) or from the exchange (insufficient balance).

### OrderParams fields

`side` and `quantity` are the only fields the builder enforces. Everything
else either has a default or applies only to a particular order type.

{.compact}

| Field              | Type         | Required | Default            | Description                                                                                                          |
|--------------------|--------------|----------|--------------------|----------------------------------------------------------------------------------------------------------------------|
| `side`             | `OrderSide`  | Required | none               | `BID` or `ASK`. See [Order Side](../trading-api/enums.md#order-side).                                                 |
| `quantity`         | `Quantity`   | Required | none               | Total order quantity.                                                                                                |
| `price`            | `Price`      | Optional | `null`             | Order price. Null for `MARKET_PEGGED` and `PRIMARY_PEGGED`.                                                           |
| `type`             | `OrderType`  | Optional | `LIMIT`            | See [Order Type](../trading-api/enums.md#order-type).                                                                |
| `timeInForce`      | `TimeInForce`| Optional | `GOOD_TILL_CANCEL` | See [Time in Force](../trading-api/enums.md#time-in-force).                                                          |
| `postOnly`         | `boolean`    | Optional | `true`             | Rejects the order if it would match immediately.                                                                     |
| `triggerPrice`     | `Price`      | Optional | `null`             | Required for trigger order types. See [Trigger orders](advanced.md#trigger-orders).                                  |
| `pegOffsetValue`   | `Price`      | Optional | `null`             | Offset added to the reference price. Only for `MARKET_PEGGED` and `PRIMARY_PEGGED`.                                  |
| `displayQuantity`  | `Quantity`   | Optional | `null`             | Quantity visible in the public book. Smaller than `quantity` makes an iceberg; zero makes a fully hidden order.      |
| `minQuantity`      | `Quantity`   | Optional | `Quantity.ZERO`    | Lower bound on order size. The effective bound is the larger of this and the instrument's minimum order size.        |
| `closeOnly`        | `Boolean`    | Optional | `false`            | Closes the position completely. May delete other open orders to do it.                                               |
| `reduceOnly`       | `Boolean`    | Optional | `null`             | Limits the size so the order can only reduce the position. Unlike `closeOnly`, never deletes other orders.           |
| `openClose`        | `OpenClose`  | Optional | `AUTO`             | Behaviour with netted positions. See [Open / Close](../trading-api/enums.md#open--close).                            |
| `autoBorrow`       | `Boolean`    | Optional | `null`             | Borrows the required quantity automatically on margin. See [Margin trading](advanced.md#margin-trading).             |
| `autoRepay`        | `Boolean`    | Optional | `null`             | Uses funds received from the order to repay outstanding loans and interest.                                           |
| `baseRiskRatio`    | `BigDecimal` | Optional | `null`             | Factor between 0 and 1 reducing the order's available base risk. An available risk of 20 at 0.9 allows at most 18.   |
| `counterRiskRatio` | `BigDecimal` | Optional | `null`             | The same factor for counter risk. Spot instruments only.                                                             |
| `useSpare`         | `boolean`    | Optional | `false`            | Draws on the reserved pool in the Trading Adapter's rate limiter. See [Rate limit load](advanced.md#rate-limit-load). |

> [!WARNING]
> An order that cannot meet `minQuantity` is discarded with
> `DiscardReason.MIN_ORDER_SIZE`. Placing with `useSpare` against an
> exchange whose Trading Adapter does not support spare requests stops the
> strategy. Out-of-range values for `baseRiskRatio` or `counterRiskRatio`
> throw.

### Defensive vs aggressive orders

By default, orders are **defensive**. The exchange rejects them if they
would match immediately, which is the post-only behaviour you want for
most resting orders:

```java Defensive order (post-only by default)

@Parameter
TradingInstrument tradingInstrument;

...

@Override
void onInstrumentSnapshot(DataInstrument instrument) {
  Order order = tradingInstrument.placeOrder(
      OrderParams.builder()
          .side(OrderSide.BID)
          .price(Price.valueOf(10.0))
          .quantity(Quantity.valueOf(1))
          .build());
}
```

For an **aggressive** order that may match immediately, set `postOnly` to
`false` and use a price that crosses:

```java Aggressive order that can immediately match

@Parameter
TradingInstrument tradingInstrument;

...

@Override
void onInstrumentSnapshot(DataInstrument instrument) {
  Order order = tradingInstrument.placeOrder(
      OrderParams.builder()
          .side(OrderSide.BID)
          .postOnly(false)
          .price(Price.valueOf(10.0))
          .quantity(Quantity.valueOf(1))
          .build());
}
```

---

## Market data callbacks

Market-data callbacks notify the strategy about changes in public market
data for subscribed instruments. They drive any strategy that reacts to
order-book updates, trades, or other market events in real time.

### onMarketDataState

Called when the market-data connection state for an instrument changes.
Tells the strategy whether data is flowing or whether the exchange feed has
broken.

States:

- `READY`: market data is being delivered.
- `ERROR`: market data is currently not being delivered due to an error.
- `PENDING`: market data is not yet available; the subscription is in
  flight.
- `FAILED`: the market-data subscription failed.
- `GONE`: market data is no longer available because the instrument was
  unsubscribed. Only *[dynamic instruments](advanced.md#dynamic-instruments)*
  reach this state.

### onInstrumentSnapshot

Called after a new order-book **snapshot** is available for the instrument.

A snapshot is the full state of the order book at a point in time, all bid
and ask levels included. It fires when the system receives a full
order-book update from the exchange, or when a new book with a
consolidated top-of-book becomes available.

### onInstrumentTopOfBook

Called after a new **top-of-book** (best bid/ask) update is available.

Top-of-book is the highest bid and lowest ask, with the quantity at each.
This is the most frequently updated market data and the relevant signal for
most latency-sensitive strategies.

> [!TIP]
> This callback can fire much more often than `onInstrumentSnapshot` for
> most instruments. Use it when you need to react fast and don't need the
> full book.

### onInstrumentTrades

Called after one or more public trades on the exchange.

Each trade is an executed transaction between a buyer and seller at a
specific price and quantity.

> [!NOTE]
> Multiple trades may arrive in a single callback. Call
> `instrument.getLastTrades()` to retrieve all trades since the previous
> callback.

### onInstrumentLiquidations

Called after a **liquidation** event on the instrument.

Liquidations occur in derivatives markets when a position's margin falls
below the maintenance requirement and the exchange force-closes the
position. The callback gives visibility into forced liquidations, which
often indicate market stress or volatility.

> [!NOTE]
> Only relevant for derivatives instruments (futures, perpetual swaps,
> options). Spot instruments don't have liquidations.

---

## Order entry callbacks

Order-entry callbacks notify the strategy about changes to private data.
Their shape follows what most crypto exchanges actually publish.

### onOrderUpdate

Called whenever an order changes.

Examples:

- Order placed (state moves from `PENDING_NEW` to `CONFIRMED`).
- Order canceled (state moves to `CANCELED`).
- Order received a fill (filled/remaining quantity changes; state may move
  to `FILLED`).

### onOrderError

Called when an order request (place, amend/modify, or cancel) fails or is
rejected.

The error can come from:

- The Trading Adapter (e.g. rate limit exceeded).
- The exchange (e.g. insufficient balance).

The `OrderError` object carries the error type and message.

### onOrderFill

Called whenever an order receives a new fill. Fills are reported one at a
time. To retrieve the latest fill, call `getLastFill()` on the order.

### onOrderFillUpdate

Some exchanges don't publish all fill information at once. Size and price
are always available right away; fees and the final trade ID may arrive a
moment later.

To avoid stalling, the system publishes the initial (incomplete) fill via
`onOrderFill`, then publishes the late-arriving fields through an
`onOrderFillUpdate` callback that signals: "this is not a new fill, this is
an update to a fill you've already seen."

### onInstrumentPosition

**Derivatives.** A **position** is a number of contracts. When that number
changes (because of a trade), the strategy is notified. A position carries
unrealised PnL; closing it produces realised PnL, which shows up in the
account wallet.

**Spot.** Spot instruments don't carry a real position. Trades affect the
wallet balance directly. To make spot and derivatives feel similar to a
strategy, the wallet entry of the traded base underlying is treated *as if*
it were a position. Spot trading triggers `onInstrumentPosition` whenever
the corresponding wallet entry changes.

### onOrderEntryState

Each account has its own private connection to the exchange, managed by
the Trading Adapter. This callback notifies the strategy about that
connection's state.

States:

| State           | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
|-----------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `READY`         | Everything normal, no restrictions                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `DEGRADED`      | Trading Adapter has lost the private connection to the exchange and cannot receive events like order updates or trades. Strategies can still try to cancel orders but success is not guaranteed. Entering new orders or amending existing orders is not allowed. If this state persists for too long, a strategy gets automatically stopped                                                                                                                                                                                                                                                               |
| `DELAYED`       | When trading derivatives, the system keeps track of all positions based on order updates, fill events, and actual position updates by the exchange. `DELAYED` means there is some discrepancy between these sources, usually because some event (like a fill) has not been received yet. Trading is not restricted in this case and current position is most likely still correct, but this is a first sign that there might be a bigger issue. Depending on how risk-averse a strategy is, this can be used to pause trading. If this state persists for too long, a strategy gets automatically stopped |
| `NOT_AVAILABLE` | Trading Adapter was shut down externally, most likely by user input. Strategies will continue running, waiting for the Trading Adapter to be restarted, but placing, amending, or canceling orders is no longer possible                                                                                                                                                                                                                                                                                                                                                                                  |
| `ERROR`         | Severe error state. Not visible to a strategy because strategies are stopped automatically if this happens                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |

### onAccountCancelAllOrdersResponse

Some exchanges support cancelling every open order on an account in one
request. A strategy triggers this by calling `cancelAllOrders()` on the
`Account`. The response comes back through this callback:

- If the exchange confirmed the request, `OrderError` is `null`. Each
  affected order also produces an `onOrderUpdate` reporting the
  cancellation.
- If the request failed, `OrderError` carries the details.

This is the same shape as `onOrderError`, but scoped to the whole account
rather than one order.

> [!WARNING]
> Cancel-all also cancels orders placed by other strategies on the same
> account.

### Callback ordering

Some callbacks are related. A trade typically triggers at least three:

- `onOrderUpdate`: the order's filled/remaining quantity changes.
- `onOrderFill`: each trade has at least one fill (multiple fills if the
  trade touched multiple price levels, hence multiple callbacks).
- `onInstrumentPosition`: a trade changes the position.

All three trace back to a single transaction on the exchange. The
callbacks are modelled on what the exchange actually publishes, so the
exchange's ordering is mostly passed straight through. The trade-off:
ordering can vary by exchange and by case.

> [!NOTE]
> The system makes no guarantee about the order of these callbacks, with
> one exception. For derivatives, `onInstrumentPosition` is guaranteed to
> fire first. The system tracks the real position locally from all three
> events, so no matter which arrives first the position is known exactly.
> The same is *not* true for spot trading: the wallet is affected by
> fees, transfers, and fundings as well as trades.

---
