# Derivatives and Short Selling

## Overview

Agencio Predict supports three instrument classes beyond equities/crypto spot: **perpetual swaps** (Binance USDT-margined), **exchange-traded futures** (Interactive Brokers via Client Portal API), and **short selling** (all three brokers where supported).

These are routed to the correct broker automatically from `assetClass` — no manual broker selection needed.

---

## Broker support matrix

| Feature | Alpaca | Binance | IBKR |
|---|---|---|---|
| Stocks / ETFs | Yes | No | Yes |
| Crypto spot | No | Yes | No |
| Perpetual swaps | No | Yes (fapi.binance.com) | No |
| Exchange-traded futures | No | No | Yes (secType=FUT) |
| Short selling | Yes (equities only) | Yes (perpetuals, positionSide) | Yes (all instruments) |
| Max leverage | 1x | 1–125x | 1–50x |

---

## Asset class routing

When a user starts a paper/live run, the executor auto-routes by `assetClass`:

- `perpetual` → brokerId=`binance`, uses `fapi.binance.com`
- `futures` → brokerId=`ibkr`, uses IBKR Client Portal API
- `stock`, `crypto`, `etf`, `forex`, `index`, `commodity` → brokerId=`alpaca`

The brokerId is stored on `algorithm_runs.broker_id` (migration 143). All subsequent ticks, order submissions, and position flattens use the stored brokerId.

---

## DSL — direction field (short selling)

Strategies declare their direction in the entry rule. Default is `long` (backwards compatible with all existing strategies).

```yaml
strategy: "RSI Mean Reversion Short"
universe: [AAPL]
signals:
  overbought:
    - rsi(14) > 70
    - vix() < 20
entry:
  when: overbought
  direction: short    # <-- new field; omit for long (default)
  size: fixed_usd(5000)
exit:
  when: rsi(14) < 50
risk:
  stop_loss_pct: 3
  max_drawdown_pct: 15
```

Direction field values:

- `long` (default) — buy to open, sell to close. Works on all brokers.
- `short` — sell to open (borrow shares/contracts), buy to cover on exit. Broker must support shorting for the asset class.

---

## DSL — asset_class and leverage

Both `asset_class` and `leverage` can now be set directly in the DSL:

```yaml
strategy: "ETH Perpetual Short"
universe: [ETHUSDT]
asset_class: perpetual_swap    # <-- new field (optional)
leverage: 10                   # <-- new field (optional, 1-125 for perpetuals)
signals:
  overbought:
    - rsi(14) > 70
entry:
  when: overbought
  direction: short
  size: fixed_usd(5000)
exit:
  when: rsi(14) < 50
risk:
  stop_loss_pct: 3
```

Asset class values: `stock` | `etf` | `crypto` | `forex` | `perpetual_swap` | `futures` | `commodity`

Leverage limits:
- `perpetual_swap`: 1-125x (Binance USDⓈ-M)
- `futures`: 1-50x (IBKR exchange-traded)
- `stock`/`etf`: 1-4x (margin)

If not set in DSL, leverage defaults to the run parameter or 1x. The executor calls `setLeverage()` + `setMarginType('ISOLATED')` on Binance before the first entry.

---

## How the executor handles derivatives

### Perpetual swaps (Binance)

- Pre-order: `setLeverage(symbol, leverage)` + `setMarginType(symbol, 'ISOLATED')`
- Entry: `submitOrder({ side: 'buy'|'sell', assetClass: 'perpetual', leverage, ... })`
- Close/cover: `submitOrder({ ..., reduceOnly: true })` — prevents accidentally extending a position
- Position detection via Binance `/fapi/v2/account` positions endpoint

### Futures (IBKR)

- Contract multipliers built-in: ES=50, NQ=20, YM=5, MES=5, MNQ=2, RTY=50, CL=1000, GC=100, SI=5000, ZB=1000, ZN=1000, ZF=1000, 6E=125000, 6B=62500
- Entry maps to `secType=FUT` in IBKR Client Portal `orders` endpoint
- Quantity is in contracts (not notional). Contract value = qty × price × multiplier.
- Expiry: IBKR returns expiry in the position response; stored in `contract_expiry`

### Short selling

- Short entry: order `side='sell'`, `position_direction='short'`
- Short cover: order `side='buy'`, `reduceOnly=true` (derivatives), `position_direction='short'`
- Cash accounting: short entry increases cash by proceeds; cover decreases cash by cost
- P&L: `(entryPrice − exitPrice) × shares` (profits when price falls)
- Equity MTM: `equity = cash − short_shares × currentPrice` (short positions are liabilities)
- Stop-loss applies correctly: triggers when price moves AGAINST the short (rises above entry by `stop_loss_pct`)

---

## Backtest cost simulation

Backtests now simulate realistic derivatives costs:

### Funding rate (perpetual swaps)
- Rate: ~0.01% per 8-hour interval (0.03%/day, ~10.95%/year)
- Shorts receive ~50% of positive funding, longs pay ~100%
- Tracked in `derivativesCosts.totalFundingCost` in backtest results
- Deducted from P&L on position close

### Borrow cost (stock shorts)
- Rate: ~1% annual (typical for easy-to-borrow; HTB can be 3-10%+)
- Applied daily to notional value of short stock/ETF positions
- Tracked in `derivativesCosts.totalBorrowCost` in backtest results

### Leverage and liquidation
- Leverage amplifies P&L: `profitLoss × leverage`
- Liquidation simulated: shorts at P with leverage L liquidate at ~P × (1 + 1/L - 0.05)
- Example: 10x short at $100 → liquidated at ~$109.50 (9.5% rise)
- Liquidation = total margin loss, recorded in `derivativesCosts.liquidationCount`

### Cover slippage (shorts)
- Short covers apply 1.5x normal slippage to simulate short squeeze dynamics
- Covers are inherently more urgent and costly than long exits

### Pre-trade liquidation warning
Use `calculateLiquidationWarning(entryPrice, leverage, direction, volatility)` to assess risk before entry:
- Returns `riskLevel: 'low' | 'medium' | 'high' | 'extreme'`
- Suggests safer `maxLeverage` based on expected volatility

---

## Paper mode behavior

Paper mode simulates derivatives mechanics with the same cost models as backtest:

- Leverage applied to position sizing (margin = notional / leverage)
- Slippage model: Almgren-Chriss with asset-class-specific defaults
- Short entry: `price × (1 − slippage)` (receive less than mid)
- Short cover: `price × (1 + slippage × 1.5)` (cover slippage premium)
- Funding rates and borrow costs simulated and deducted from P&L
- Liquidation checked on each tick (unlike historical paper mode)

Paper mode does NOT call `setLeverage` or `setMarginType` — those are live-only broker calls.

---

## Database schema (migrations 143 + 144)

### `predict.algorithm_trades` (migrations 143 + 144)

| Column | Type | Notes |
|---|---|---|
| `asset_class` | `TEXT DEFAULT 'stock'` | |
| `leverage` | `NUMERIC` | |
| `liquidation_price` | `NUMERIC` | |
| `contract_expiry` | `DATE` | Futures only |
| `position_side` | `TEXT` | `'long'` \| `'short'` \| `'both'` |
| `position_direction` | `TEXT DEFAULT 'long'` | `'long'` \| `'short'` (migration 144) |

### `predict.algorithm_runs` (migration 143)

| Column | Type | Notes |
|---|---|---|
| `asset_class` | `TEXT DEFAULT 'stock'` | |
| `leverage` | `NUMERIC DEFAULT 1` | |
| `max_leverage_used` | `NUMERIC` | High-water mark |
| `broker_id` | `TEXT DEFAULT 'alpaca'` | Auto-derived from assetClass |

---

## API reference

### Start a paper run

`POST /api/predict/v1/algorithms/:id/paper`

Body (all optional except `startingEquity`):

```json
{
  "startingEquity": 100000,
  "assetClass": "perpetual",
  "leverage": 10,
  "brokerId": "binance"
}
```

`assetClass` values: `stock` | `crypto` | `forex` | `index` | `commodity` | `etf` | `futures` | `perpetual`

`leverage`: integer 1–125 (perpetual) or 1–50 (futures). Ignored for non-derivative asset classes.

`brokerId`: optional override. Default: auto-derived from assetClass (`perpetual`→`binance`, `futures`→`ibkr`, others→`alpaca`).

### Start a live run

`POST /api/predict/v1/algorithms/:id/live`

Same body as paper. Additional prerequisites:

- User must have `live_trading_enabled = true` (admin-granted)
- Broker must be configured at `/settings/brokers` with valid credentials
- User must have switched to live mode + completed MFA acknowledgement
- Platform kill switch must be enabled at `/admin/brokers`

### Get paper run detail

`GET /api/predict/v1/algorithms/paper-runs/:runId`

Response includes `run.asset_class`, `run.leverage`, `run.broker_id` alongside the equity curve, trades, and telemetry.

### Trade objects

Trade rows now include `position_direction: 'long' | 'short'` to distinguish:

| side | position_direction | Meaning |
|---|---|---|
| `buy` | `long` | Long open |
| `sell` | `long` | Long close |
| `sell` | `short` | Short open |
| `buy` | `short` | Short cover |

---

## Prerequisites by use case

| Use case | Prerequisites |
|---|---|
| Paper trade perpetuals | None — paper uses simulator, no Binance account needed |
| Paper trade futures | None — paper uses simulator, no IBKR account needed |
| Live perpetuals | Binance account with futures enabled + API key at /settings/brokers |
| Live futures | IBKR account + Client Portal API session at /settings/brokers |
| Short equities | Alpaca account with shorting margin enabled |
| Short perpetuals | Binance futures account (always supports both sides) |
| Short futures | IBKR account (futures are symmetrically short-able) |

---

## PANIC button position handling for shorts

When using the PANIC button with open short positions, immediate closing may cause excessive slippage. The PANIC endpoint accepts a `positionMode` parameter:

| Mode | Behavior | Risk |
|---|---|---|
| `close_all` | Flatten ALL positions immediately | Default. May suffer cover slippage on shorts. |
| `stop_new_only` | Algorithm stops, positions preserved | **DANGEROUS**: shorts have unlimited loss if unmanaged. |
| `close_longs_only` | Close longs, preserve shorts | Shorts keep their stop-losses; requires monitoring. |
| `close_shorts_only` | Cover all shorts, preserve longs | Eliminates unlimited-loss risk. |
| `protect_shorts` | Set emergency stop-loss on shorts, then stop | Stop at `protectiveStopPct`% above current price. |

API example:
```bash
POST /api/predict/v1/algorithms/panic
{ "positionMode": "protect_shorts", "protectiveStopPct": 5 }
```

---

## Limitations (current)

- No simultaneous long + short on the same symbol (no hedge mode)
- No position tracking against Binance hedge mode (`positionSide: 'BOTH'` only)
- IBKR contract multipliers are hardcoded; some exotic contracts may need a new entry in `FUTURES_MULTIPLIERS` in `packages/be/src/brokers/ibkr.ts`
- Forex and commodities have no tick-level position risk data
- Funding rate simulation uses average rate (~0.01%/8h); actual rates vary by market conditions

---

## Troubleshooting

| Error | Cause | Fix |
|---|---|---|
| `preflight_failed: not_live_mode` | Broker not in live mode | /settings/brokers → switch to live + MFA ack |
| `preflight_failed: broker_disabled` | Platform kill switch | /admin/brokers → enable broker |
| `Broker credentials not found` | No credentials saved | /settings/brokers → enter API key |
| `setLeverage failed` | Binance leverage limit for symbol | Lower leverage; some pairs cap at 20x |
| `binance_-4046` | Margin type already set (not an error) | Silently ignored by the adapter |
| `Order notional exceeds platform cap` | Broker config max_order_notional_usd | /admin/brokers → raise cap or reduce position size |
| Short cover rejected by broker | reduceOnly flag rejected | Ensure no conflicting open orders on the symbol |
