# Advanced SDK Features

Beyond the basics in [Basics](basics.md), the **Strategy SDK** has four
features worth their own page: **dynamic instruments**, **margin trading**,
**trigger orders**, and **rate-limit load**.

---

## Dynamic instruments

A **dynamic instrument** is one created or destroyed at runtime, after
strategy construction. Dynamic instruments outlive the current strategy
run: they are resubscribed when the strategy restarts.

To get the strategy's current instruments:

```java
List<DataInstrument> myDataInstruments = context.getCurrentDataInstruments();
List<TradingInstrument> myTradingInstruments = context.getCurrentTradingInstruments();
```

These lists include instruments still being created or torn down.

To check whether an instrument was created dynamically:

```java
boolean isDataDynamicallyCreated = myDataInstrument.isDynamicallyCreated();
boolean isTradingDynamicallyCreated = myTradingInstrument.isDynamicallyCreated();
```

### Creating data instruments

A data instrument is fully specified by a `DataInstrumentParams`. The
fields match those used in the static strategy configuration:

```java
DataInstrumentParams myDataInstrumentParams =
    DataInstrumentParams.builder()
        .id(0L)                    // required
        .active(true)              // optional
        .maxTimeWithoutUpdates(42) // optional
        .maxTimeInError(42)        // optional
        .maxTimeWithoutTrades(42)  // optional
        .maxSnapshotDepth(42)      // optional
        .build();
```

Create the instrument through the strategy context:

```java
DataInstrument myDataInstrument = context.createDataInstrument(myDataInstrumentParams);
```

The `MarketDataState` of a freshly created instrument is `PENDING`.

> [!WARNING]
> No data or trading instrument with the same ID may be active in the
> strategy when you create the new one (including instruments that are
> still in `PENDING`). Creating a duplicate stops the strategy.

#### Callbacks after creation

+++ Successful Subscription
The `onMarketDataState(DataInstrument)` callback fires with state `READY`.
Public market-data callbacks then arrive as usual.
+++ Failed Subscription
The `onMarketDataState(DataInstrument)` callback fires with state `FAILED`.
+++

### Deleting data instruments

```java
context.deleteDataInstrument(myInstrumentId);
```

After deletion is requested, no more market-data callbacks for that
instrument arrive.

> [!WARNING]
> All of the following must hold, or the strategy will be stopped:
> 1. The instrument is known by the strategy.
> 2. The instrument was dynamically created.
> 3. The instrument is not a trading instrument.

When unsubscription completes, `onMarketDataState(DataInstrument)` fires
with state `GONE`.

### Creating trading instruments

```java
TradingInstrumentParams myTradingInstrumentParams =
    TradingInstrumentParams.builder()
        .id(0L)                                                         // required
        .accountId(0L)                                                  // required
        .active(true)                                                   // optional
        .maxTimeWithoutUpdates(42)                                      // optional
        .maxTimeInError(42)                                             // optional
        .maxTimeWithoutTrades(42)                                       // optional
        .maxSnapshotDepth(42)                                           // optional
        .minOrderSize(Quantity.ZERO)                                    // optional
        .maxOrderCount(TwoSides.of(0, 0))                               // optional
        .maxPosition(TwoSides.of(Quantity.ZERO, Quantity.ZERO))         // optional
        .maxNetPosition(TwoSides.of(Quantity.ZERO, Quantity.ZERO))      // optional
        .maxCounterPosition(TwoSides.of(Price.ZERO, Price.ZERO))        // optional
        .maxUsdPosition(TwoSides.of(Price.ZERO, Price.ZERO))            // optional
        .maxUsdNetPosition(TwoSides.of(Price.ZERO, Price.ZERO))         // optional
        .maxUsdCounterPosition(TwoSides.of(Price.ZERO, Price.ZERO))     // optional
        .build();
```

#### Risk-limit requirements

> [!NOTE]
> Risk parameters can be specified in USD or in native currency. At least
> one of these must be set:
> - At least one of `maxPosition` or `maxUsdPosition`.
> - For spot instruments, at least one of `maxCounterPosition` or
>   `maxUsdCounterPosition`.
> - For instruments with isolated positions, at least one of
>   `maxNetPosition` or `maxUsdNetPosition`.

Create the instrument through the strategy context:

```java
TradingInstrument myTradingInstrument = context.createTradingInstrument(myTradingInstrumentParams);
```

`MarketDataState` and `TradingInstrumentState` both start as `PENDING`.

> [!WARNING]
> No data or trading instrument with the same ID may be active in the
> strategy when you create the new one (including instruments that are
> still in `PENDING`). Creating a duplicate stops the strategy.

#### Callbacks after creation

The instrument is fully operational only when both market-data and
order-entry subscriptions succeed.

+++ Market Data Subscription Success
`onMarketDataState(DataInstrument)` fires with state `READY`. Public
market-data callbacks arrive as usual.
+++ Order Entry Subscription Success
`onOrderEntryState(Account, Set<TradingInstrument>)` fires. The set
contains only the new trading instrument; the trading state is `READY`.
The strategy can place orders against the instrument and will receive
order-entry callbacks as usual.
+++ Subscription Failure
Both `onMarketDataState(DataInstrument)` and `onOrderEntryState(Account,
Set<TradingInstrument>)` fire with state `FAILED`.
+++

### Deleting trading instruments

```java
context.deleteTradingInstrument(myInstrumentId, myAccountId);
```

After deletion is requested:

- No more market-data or order-entry callbacks arrive for the instrument.
- The strategy can no longer place orders against it.

`TradingInstrumentState` of an instrument pending deletion is
`PENDING_DELETE`.

> [!WARNING]
> All of the following must hold, or the strategy will be stopped:
> 1. The instrument is known by the strategy.
> 2. The instrument was dynamically created.
> 3. The instrument is not a data-only instrument.
> 4. The instrument has no open orders.

#### Callbacks after deletion

+++ Market Data Unsubscription
`onMarketDataState(DataInstrument)` fires with state `GONE`.
+++ Order Entry Unsubscription
`onOrderEntryState(Account, Set<TradingInstrument>)` fires with state
`GONE`.
+++

---

## Margin trading

The **Strategy Server** supports trading spot instruments on margin. The
trading-instrument parameter `walletType` selects spot trading (`SPOT`)
or **cross-margin** trading (`CROSS_MARGIN`).

Configuring `CROSS_MARGIN` means:

- Orders placed through the instrument are flagged for margin trading.
- Risk limits are evaluated against the cross-margin wallet, using the
  wallet's `marginBalance`.

### Wallet types by exchange

Wallet-type support varies by exchange:

| Exchange | Supported Wallet Types | Notes                                                       |
|----------|------------------------|-------------------------------------------------------------|
| Binance  | `SPOT`, `CROSS_MARGIN` | Separate spot and cross-margin wallets                      |
| FTX      | `ACCOUNT`              | Single wallet account for both spot instruments and futures |

> [!WARNING]
> The Trading Adapter publishes its supported wallet types as
> capabilities. Configuring a trading instrument with a wallet type the
> exchange does not support keeps the strategy from starting.

### Accessing wallet types

`Account.getWallet(WalletType)` returns the wallet of the requested type:

```java
// Margin-less spot wallet
Wallet spotWallet = account.getWallet(WalletType.SPOT);

// Cross-margin wallet
Wallet crossMarginWallet = account.getWallet(WalletType.CROSS_MARGIN);

// Isolated-margin wallet (per instrument)
Wallet isolatedWallet = account.getIsolatedMarginWallet(instrumentId);
```

For convenience, the wallet configured on a trading instrument is
reachable directly:

```java
Wallet wallet = tradingInstrument.getWallet();
```

### Margin balance calculation

The `marginBalance` field gives the net asset value of the wallet:

```
marginBalance = (availableMargin + blockedMargin) - (borrowed + interest)
```

### Order parameters for margin trading

| Parameter    | Description                                                                                                                                                                                                                                                  |
|--------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `autoBorrow` | Takes on an automatic loan if available margin is insufficient to place an order. The loan is taken on as soon as the order is placed, interest accrues immediately, and the loan is repaid if the order is cancelled before execution or explicitly repaid. |
| `autoRepay`  | Explicitly repays borrowed amount and interest with the proceeds of order execution.                                                                                                                                                                         |

---

## Trigger orders

A **trigger order** activates only after a price condition is met. The
SDK supports four variants.

### Supported order types

+++ STOP_LIMIT and STOP_MARKET
**Behaviour**

- After the trigger condition fires, the order becomes a limit or market
  order respectively.

**Trigger condition**

- For `BID`: activated when price rises to or above the trigger price.
- For `ASK`: activated when price falls to or below the trigger price.

**Note**

- For `STOP_LIMIT`, the limit price is usually equal to or less aggressive
  than the trigger price.
+++ LIMIT_IF_TOUCHED and MARKET_IF_TOUCHED
**Behaviour**

- After the trigger condition fires, the order becomes a limit or market
  order respectively.

**Trigger condition**

- For `BID`: activated when price falls to or below the trigger price.
- For `ASK`: activated when price rises to or above the trigger price.

**Note**

- For `LIMIT_IF_TOUCHED`, the limit price is usually equal to or more
  aggressive than the trigger price.
+++

### Checking exchange support

Confirm the trigger type is supported on the target exchange:

```java
final boolean isSupported = myInstrument.getTradingCapabilities()
    .orderTypes()
    .contains(OrderType.STOP_LIMIT);
```

### Specifying trigger price

For every trigger order type, set `triggerPrice` on the `OrderParams`:

```java
OrderParams params = OrderParams.builder()
    .orderType(OrderType.STOP_LIMIT)
    .triggerPrice(Price.of("50000.00"))
    // ... other parameters
    .build();
```

### Querying trigger status

To see whether the order is still untriggered or already triggered (and
possibly resting in the book):

```java
final boolean isTriggered = order.isTriggered();
```

> [!NOTE]
> For non-trigger orders, `isTriggered()` always returns false.

---

## Cancel all open orders

The Strategy API supports a bulk cancel-all on an account, by triggering
the exchange to cancel everything open.

> [!WARNING]
> The bulk cancel does not guarantee every order is cancelled. Some
> cancels can fail, and a strategy may place new orders after the request
> is triggered but before it lands at the exchange.

### Checking exchange support

Get the account either through a parameter or from a `TradingInstrument`:

```java
Account account = tradingInstrument.getAccount();
```

Then check the capability:

```java
final boolean isSupported = account.getTradingCapabilities().cancelAllOrdersOfAccount();
```

### Triggering cancel-all-orders

```java
final DiscardReason discardReason = account.cancelAllOrders();
```

The call returns a `DiscardReason`:

| DiscardReason              | Description                                          |
|----------------------------|------------------------------------------------------|
| `CAPABILITY_NOT_AVAILABLE` | Not supported by the exchange                        |
| `ADAPTER_NOT_AVAILABLE`    | The adapter is currently not available               |
| `ORDER_PENDING`            | Already triggered and a response has not yet arrived |
| `NOT_DISCARDED`            | The request was successfully sent                    |

### Callback after completion

When the request completes,
`onAccountCancelAllOrdersResponse(Account account, OrderError orderError)`
fires:

- `Account` is the account the request was sent for.
- If the request succeeded, `OrderError` is `null`.
- Otherwise, `OrderError` describes the failure.

```java

@Override
public void onAccountCancelAllOrdersResponse(Account account, OrderError orderError) {
  if (orderError == null) {
    log.info("Cancel all orders succeeded for account: {}", account.getId());
  } else {
    log.error("Cancel all orders failed for account: {} with error: {}",
        account.getId(), orderError);
  }
}
```

## Rate limit load

Each exchange has its own rate-limiting rules, typically multiple limits
applied to different aspects of an order request.

### Aspects exchanges limit

Common ones include:

- Request type (place, amend/modify, cancel, replace, others).
- Instrument.
- Instrument group.
- (Sub) account.
- Master account.
- IP address.
- Network connection.

> [!NOTE]
> IP address and network connection are not exposed in the SDK. They are
> technical limits unlikely to be restrictive in practice, and the
> Trading Adapter has its own mitigations for them.

### Reading the load

The SDK exposes the load for the three most useful scopes: request type,
instrument, and account. The **load** is a value between `0` (no usage)
and `1` (limit fully reached).

**Account-level load**

```java Retrieve account-level rate limit load
RateLimitLoad accountLoad = account.getRateLimitLoad();
```

**Instrument-level load**

```java Retrieve instrument-level rate limit load
RateLimitLoad instrumentLoad = tradingInstrument.getRateLimitLoad();
```

Each call returns a `RateLimitLoad` carrying one value per request type:

```java RateLimitLoad structure
class RateLimitLoad {

  double place;
  double amend;
  double replace;
  double cancel;
}
```

> [!TIP]
> If a given limit doesn't apply at the exchange (e.g. cancels are not
> limited, or there are no per-instrument limits), the corresponding
> value is always zero.

> [!NOTE]
> The values are indicative estimates, updated at a fixed interval. They
> are not precise real-time measurements.

---
