# X-Trader auto_strategy: migration, timed sell, sl_protection, phased_sl, anti_pvp

URL: https://docs.blizz.id/xtrader/auto-strategy/
Updated: 2026-10-16

`auto_strategy` is the required object inside `config` that holds exit rules that are not plain TP/SL stages: selling on migration, a single delayed full sell, clearing dust or stalled positions, profit protection, a delayed stop-loss (`sl_protection`), a two-phase stop-loss (`phased_sl`), a temporary block on targets that sell quickly (`anti_pvp`) and a per-config sell fee override. Only `migration_clear`, `timed_sell` and `sell_delay` are required; every other rule is optional.

Primary source: [X-Trader API reference](https://x-trader.ai/api/docs). This page is an independent explanation of X-Trader Public API v1.0.0; it is not affiliated with X-Trader.

## Where auto_strategy lives

`auto_strategy` is a required key of `config` in the sniper, copy-trade and transfer-snipe Create and Patch schemas and in both template schemas. The object schema is identical in all of them. It works next to [`auto_sell`](/xtrader/auto-sell/) and [`timed_strategy`](/xtrader/timed-strategy/). The object and every nested object are strict: unknown keys return `validation_error`. All numeric values are decimal strings, max 32 characters (see [write rules](/xtrader/write-rules/)).

## Field overview

| Field | Type | Required | Purpose |
|---|---|---|---|
| `migration_clear` | boolean | yes | Sell the whole position when migration is detected. |
| `migration_sell` | object | no | Sell a percentage on migration. |
| `timed_sell` | boolean | yes | Enable one delayed full-position sell. |
| `sell_delay` | string | yes | Delay for `timed_sell`, seconds `"0"` to `"31536000"`. |
| `remaining_position_clear` | object | no | Clear a small remaining position (SOL value). |
| `tp1_timeout_clear` | object | no | Clear if TP1 is not reached in time. |
| `price_stagnation_clear` | object | no | Clear if price stays unchanged. |
| `profit_protection` | object | no | Reduce the position while profit holds above a threshold. |
| `sl_protection` | object | no | Delayed one-shot stop-loss. |
| `phased_sl` | object | no | Two-phase live stop-loss. |
| `anti_pvp` | object | no | Temporarily block a target that sells soon after buying. |
| `sell_fee_override` | object | no | Per-config sell Priority Fee and Tip. |

Every optional object has its own `enabled` flag, and all of its keys are required whenever the object is present (even with `enabled: false`).

## Migration rules

| Field | Type | Values | Meaning |
|---|---|---|---|
| `migration_clear` | boolean | | On detected migration, sell 100%. |
| `migration_sell.enabled` | boolean | | Turns the percentage rule on. |
| `migration_sell.percent` | string | greater than 0, at most 100 | Share of the position to sell on migration. |

When both `migration_clear` and `migration_sell.enabled` are true, `migration_clear` takes priority. The spec does not define which events count as migration.

## Timed sell

| Field | Type | Values | Meaning |
|---|---|---|---|
| `timed_sell` | boolean | | Enables a single delayed sell of the full position. |
| `sell_delay` | string | `"0"` to `"31536000"` seconds (365 days) | Delay before that sell. |

`sell_delay` is required even when `timed_sell` is `false`; send `"0"` in that case. For several partial sells at different times use [`timed_strategy`](/xtrader/timed-strategy/) instead.

## Clear rules

| Object | `value` meaning | Limits stated in the spec |
|---|---|---|
| `remaining_position_clear` | Decimal SOL amount | minimum `"0.01"` |
| `tp1_timeout_clear` | Seconds | not stated |
| `price_stagnation_clear` | Seconds | not stated |

Each object is `{ "enabled": <bool>, "value": "<string>" }`.

- `remaining_position_clear` clears a small leftover position. The spec gives only the SOL unit and minimum; the practical reading is that a remainder worth less than `value` SOL is sold out.
- `tp1_timeout_clear` clears the position when the first take-profit stage of `auto_sell.tp_orders` has not been reached within `value` seconds.
- `price_stagnation_clear` clears the position when the price stays unchanged for `value` seconds.

## profit_protection

| Field | Type | Values | Meaning |
|---|---|---|---|
| `enabled` | boolean | | |
| `threshold` | string | profit percent | Profit level that must hold. |
| `price_checks` | string | `"1"` to `"10000"` | Consecutive price checks above `threshold` required. |
| `reduce_ratio` | string | `"0"` to `"100"` | Percentage by which the position is reduced. |

When profit stays above `threshold` for `price_checks` consecutive checks, the position is reduced by `reduce_ratio` percent. The PnL used here honours `auto_sell.ignore_fee_for_auto_sell`. The spec does not say how often a price check happens.

## sl_protection (delayed stop-loss)

| Field | Type | Values | Meaning |
|---|---|---|---|
| `enabled` | boolean | | |
| `seconds` | string | `"0"` to `"86400"`, 0.1 s precision | Grace window after which the rule is evaluated once. |
| `trigger` | string | below 0, no lower than `"-100"` | PnL threshold, mark-to-entry. |

Behaviour:

1. During the first `seconds` the rule never sells.
2. At expiry, if mark-to-entry PnL is at or below `trigger`, 100% of the position is sold.
3. If PnL is above `trigger` at expiry, the rule ends; later drops are ignored by this rule.

`auto_sell.sl_orders` keep firing immediately during and after the window.

## phased_sl (two-phase stop-loss)

| Field | Type | Values | Meaning |
|---|---|---|---|
| `enabled` | boolean | | Omit the object or set `false` to keep it off. |
| `seconds` | string | `"0.1"` to `"31536000"` | Length of the early window. |
| `early_trigger` | string | `"-100"` to `"-1"` | Stop used before `seconds` elapse. |
| `late_trigger` | string | `"-100"` to `"-1"` | Stop used after the early window. |

On every price tick the active stop is compared with PnL; a hit sells 100%. The rule does not sell by itself at the moment the window ends, it only switches stops. `early_trigger` and `late_trigger` are independent, so either can be the tighter one: for example a loose `-50` early (to survive launch volatility) and a tight `-20` later, or the reverse. `sl_orders` and `sl_protection` continue to follow their own rules.

### sl_protection vs phased_sl

| | `sl_protection` | `phased_sl` |
|---|---|---|
| Sells before the deadline | never | yes, on `early_trigger` |
| At the deadline | one check, sells 100% if PnL at or below `trigger` | switches to `late_trigger`, no sell by itself |
| After the deadline | inactive | live on `late_trigger` |
| Sell size | 100% | 100% |

## anti_pvp

| Field | Type | Meaning |
|---|---|---|
| `enabled` | boolean | |
| `holding_time` | string, seconds | Observation window after the buy. |
| `ban_duration` | string, seconds | How long the target is blocked. |

If the target sells within `holding_time` seconds of buying, it is temporarily blocked for `ban_duration` seconds. The spec does not give numeric limits for either value, and does not describe what "blocked" means for a sniper target as opposed to a copy-trade target; the description refers to the target wallet in both cases.

## sell_fee_override

| Field | Type | Values |
|---|---|---|
| `enabled` | boolean | |
| `priority_fee` | string | SOL, `"0"` to `"1000"` |
| `tip_amount` | string | SOL, `"0"` to `"1000"`; at least `"0.0001"` when enabled |

When enabled, these sell fees take precedence over the account's global defaults for this configuration.

## Example inside a create body

```json
{
  "target_wallet": "<solana-address>",
  "config": {
    "auto_strategy": {
      "migration_clear": false,
      "migration_sell": { "enabled": true, "percent": "50" },
      "timed_sell": true,
      "sell_delay": "3600",
      "remaining_position_clear": { "enabled": true, "value": "0.01" },
      "tp1_timeout_clear": { "enabled": true, "value": "900" },
      "price_stagnation_clear": { "enabled": false, "value": "300" },
      "profit_protection": {
        "enabled": true, "threshold": "40", "price_checks": "5", "reduce_ratio": "50"
      },
      "sl_protection": { "enabled": true, "seconds": "30", "trigger": "-40" },
      "phased_sl": {
        "enabled": true, "seconds": "60", "early_trigger": "-50", "late_trigger": "-20"
      },
      "anti_pvp": { "enabled": true, "holding_time": "10", "ban_duration": "86400" },
      "sell_fee_override": {
        "enabled": true, "priority_fee": "0.001", "tip_amount": "0.001"
      }
    },
    "auto_sell": {
      "enabled": true, "expiry_time": "0",
      "tp_orders": [{ "trigger": "100", "sell": "50" }],
      "sl_orders": [], "follow_sell": false, "proportional_sell": false
    },
    "timed_strategy": { "enabled": false, "orders": [] }
  }
}
```

The other required `config` keys are omitted; see [sniper config](/xtrader/config-sniper/) and [copy-trade config](/xtrader/config-copy-trade/).

## Updating auto_strategy on an existing config

A PATCH with `config` replaces the whole nested config and is validated against the full schema, so read the current config, change `auto_strategy`, and send the complete `config` with the `ETag` as `If-Match`. Changing `config` requires a paused configuration (`config_must_be_paused` otherwise); a stale ETag returns `version_conflict`. Snapshots returned by GET may include legacy fields the strict schema rejects; `error.fields` lists the offending paths.

### curl

```bash
ID='<config-uuid>'
BASE='https://x-trader.ai/api/v1'

curl -sD headers.txt "$BASE/sniper/configs/$ID" \
  --header "Authorization: Bearer $X_TRADER_API_TOKEN" \
  | jq '.data.config' > config.json
ETAG=$(grep -i '^etag:' headers.txt | cut -d' ' -f2 | tr -d '\r')

jq '.auto_strategy.phased_sl = {"enabled":true,"seconds":"120","early_trigger":"-60","late_trigger":"-25"}
    | .auto_strategy.anti_pvp = {"enabled":true,"holding_time":"15","ban_duration":"43200"}' \
  config.json | jq '{config: .}' > patch.json

curl --request PATCH "$BASE/sniper/configs/$ID" \
  --header "Authorization: Bearer $X_TRADER_API_TOKEN" \
  --header 'Content-Type: application/json' \
  --header "If-Match: $ETAG" \
  --data @patch.json
```

### Python

```python
import os
import requests

BASE = "https://x-trader.ai/api/v1"
CONFIG_ID = "<config-uuid>"
session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['X_TRADER_API_TOKEN']}"

url = f"{BASE}/sniper/configs/{CONFIG_ID}"   # or /copy-trade/configs/{id}
resp = session.get(url)
resp.raise_for_status()
etag = resp.headers["ETag"]
resource = resp.json()["data"]
if resource["is_active"]:
    raise SystemExit("Pause the config first (POST .../pause with If-Match)")

config = resource["config"]
strategy = config["auto_strategy"]
strategy["sl_protection"] = {"enabled": True, "seconds": "20", "trigger": "-35"}
strategy["sell_fee_override"] = {
    "enabled": True, "priority_fee": "0.0005", "tip_amount": "0.0002",
}
strategy["timed_sell"] = False
strategy["sell_delay"] = "0"

patch = session.patch(url, json={"config": config}, headers={"If-Match": etag})
if patch.status_code >= 400:
    print(patch.json()["error"])
patch.raise_for_status()
print("new version:", patch.headers["ETag"])
```

## FAQ

### Which auto_strategy fields are required?

Only `migration_clear` (boolean), `timed_sell` (boolean) and `sell_delay` (decimal string of seconds). Every other rule is an optional object that can be omitted.

### What happens if migration_clear and migration_sell are both enabled?

`migration_clear` wins: the whole position is sold on migration and the `migration_sell` percentage is not used.

### How is sl_protection different from phased_sl?

`sl_protection` waits a fixed number of seconds and then sells 100% once, only if PnL is still at or below its trigger. `phased_sl` is a live stop that uses `early_trigger` before the deadline and `late_trigger` after it, selling 100% on any price tick that hits the active stop.

### Do sl_protection and phased_sl replace auto_sell.sl_orders?

No. Ordinary `sl_orders` still fire immediately, and each rule follows its own logic independently.

### Is auto_strategy different for sniper and copy-trade?

No. The schema is identical in sniper, copy-trade, transfer-snipe and template configs.
