# Send TradingView alerts to MT4 and MT5

> Paste one Tickwire URL into a TradingView alert and each alert becomes a checked, recorded order on your MT4 or MT5 account. Size by risk, set stops in points or pips, and a repeated alert never trades twice.

Source: https://tickwire.io/docs/guides/tradingview-alerts/

A signal webhook is a private URL for one trading account. TradingView posts the alert message to
it, and Tickwire turns that message into orders. Every order goes through the same checks as the
rest of the API: idempotency, your kill switch, your pre-trade hook and the audit log.

> TradingView only sends webhooks on a paid plan. On the free plan the "Webhook URL" box is not
> available. TradingView may also ask you to turn on two-factor authentication first.

## Set it up in the dashboard

You do not need the command line. In the dashboard, open **TradingView alerts** (or press
**TradingView alerts** on an account's page).

1. Press **New webhook**. Pick the account and give the webhook a name.
2. Say what sends the alerts: a strategy or an indicator.
3. Choose the position size, the stop loss and the take profit, and what happens when a new
   alert arrives. Limits and symbol names are optional and start off.
4. Press **Create webhook**. Copy the URL now. It works like a password and is shown only once.
5. Copy the alert message the page gives you.
6. In TradingView, create the alert, tick **Webhook URL**, paste the URL, and paste the message
   into the **Message** box.

Each webhook lists its last alert. Press **Recent alerts** to see every alert and its outcome.
From the same list you can edit, pause, get a new URL or delete a webhook. Creating and changing
webhooks needs an admin or owner.

The rest of this guide shows the same steps with the API, and every field an alert can send.

## 1. Create a webhook

Create one webhook per account and strategy. Use a full API key (`X-API-Key`) or a dashboard
session.

```bash
curl -X POST https://api.tickwire.io/v1/signal-webhooks \
  -H "X-API-Key: $TICKWIRE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: create-gold-trend" \
  -H "X-Reason: TradingView gold trend strategy" \
  -d '{
    "account_id": "acc_123",
    "name": "Gold trend",
    "symbol_map": {"XAUUSD": "GOLD"},
    "sizing": {"mode": "risk_pct", "risk_pct": 1},
    "stops": {"sl": {"type": "pips", "value": 30}, "tp": {"type": "r", "value": 2}}
  }'
```

The answer has `token`, `path` and `url`:

```json
{
  "id": "swh_9f2c",
  "name": "Gold trend",
  "account_id": "acc_123",
  "token_prefix": "twsig_Ab3dEf",
  "token": "twsig_Ab3dEf...",
  "path": "/v1/signals/twsig_Ab3dEf...",
  "url": "https://api.tickwire.io/v1/signals/twsig_Ab3dEf...",
  "enabled": true
}
```

**Copy the `url` now.** It is shown once. Tickwire stores only a hash of the token, so nobody can
show it to you again. If you lose it, rotate the webhook to get a new one.

Running the same command again with the same `Idempotency-Key` returns the same webhook, but
`token`, `path` and `url` are `null`, with a `token_notice` that says why. Use
`POST /v1/signal-webhooks/{id}/rotate` to get a new URL. `url` is also `null` if no public API
address is set on our side. Then build it from `path`: `https://api.tickwire.io` plus `path`.

The URL is the password. Anyone who has it can send alerts to this account. Keep it out of
screenshots, chat and public scripts.

## 2. Paste the URL into TradingView

1. Open the chart and create an alert (the clock icon, or right click on the chart).
2. Pick the condition: your strategy, or an indicator.
3. Open the **Notifications** tab and tick **Webhook URL**.
4. Paste the Tickwire `url`.
5. Paste one of the messages below into the **Message** box.
6. Save the alert.

## 3. Pick the message

### The shortest message

All other values come from your webhook settings.

```json
{"symbol": "{{ticker}}", "action": "{{strategy.order.action}}"}
```

### Buy with 1% risk, a stop at a price, and a target at 2R

```json
{"symbol": "{{ticker}}", "action": "buy", "risk_pct": 1, "sl": {{plot("Stop")}}, "tp_r": 2}
```

Tickwire sizes the trade so that a hit stop loses 1% of your balance. `tp_r: 2` puts the take
profit twice the stop distance away.

### Strategy alerts: target position

Tell Tickwire where the position should be, not what to do. If the same alert arrives twice, the
second one finds the position already there and sends nothing.

```json
{
  "symbol": "{{ticker}}",
  "market_position": "{{strategy.market_position}}",
  "market_position_size": "{{strategy.market_position_size}}",
  "alert_id": "{{strategy.order.id}}-{{timenow}}"
}
```

`market_position` is `long`, `short` or `flat`. Tickwire reads the webhook's open positions on that
symbol and sends only the orders needed to reach the target.

TradingView sizes strategies in its own units. If your strategy trades 100000 units of EURUSD per
lot, set the sizing `multiplier` to `0.00001` so that 100000 becomes 1 lot.

### Indicator alerts

Indicators have no strategy placeholders, so write the action yourself. Make one alert per
direction.

```json
{"symbol": "{{ticker}}", "action": "buy", "risk_money": 50, "sl_pips": 20, "tp_pips": 40}
```

```json
{"symbol": "{{ticker}}", "action": "close"}
```

### Manage an open trade

```json
{"symbol": "{{ticker}}", "action": "close_pct", "pct": 50}
```

```json
{"symbol": "{{ticker}}", "action": "breakeven", "offset_points": 10}
```

```json
{"symbol": "{{ticker}}", "action": "move_sl", "sl_points": 150}
```

## Alert fields

Every field is optional except `symbol`, and `action` or a position. Field names are case
sensitive. Where a field has two names, the first one present is used.

```jsonc
{
  "symbol": "EURUSD",              // or "ticker". "FX:" and other prefixes are dropped.
  "action": "buy",                 // or "side" or "order_action". See the actions below
  "position": 0.5,                 // a target position: signed, or with "side"
  "alert_id": "{{timenow}}-1",     // or "signal_id" or "client_id". One id per alert: the best way to make a repeat harmless.
  "time": "{{timenow}}",           // used by the max alert age limit

  // size: send one of these
  "risk_pct": 1,                   // % of your risk basis (balance, equity or a fixed amount)
  "risk_money": 100,               // the loss at the stop, in the account currency
  "lots": 0.10,                    // fixed lots ("volume", "lot", "qty", "quantity", "contracts" and "size" work too)
  "multiplier": 0.5,               // scales lots or a target sent in the alert

  // stop loss: send one of these
  "sl": 1.08500, "sl_points": 200, "sl_pips": 20, "sl_pct": 0.5,
  // take profit: send one of these
  "tp": 1.09500, "tp_points": 400, "tp_pips": 40, "tp_pct": 1.0, "tp_r": 2,

  // trade management
  "pct": 50,                       // for close_pct ("percent" works too)
  "offset_points": 10,             // for breakeven
  "comment": "breakout"            // the order comment. A longer one is cut to 20 characters
}
```

Sending two of one kind, such as `lots` and `risk_pct`, is refused with `conflicting_fields`.
`0` for a stop or target means none.

| Action | What it does |
|---|---|
| `buy`, `sell` | Open a new trade. Your opposite and same-side settings decide what happens to trades already open. |
| `long`, `short` | A target position, harmless to repeat. |
| `close`, `flat`, `exit` | Close every trade of this webhook on the symbol. |
| `close_long`, `close_short` | Close one side only. |
| `close_pct` | Close a share (`pct`) of the open lots, newest trades first. |
| `move_sl`, `move_tp` | Move the stop loss or take profit of open trades. |
| `breakeven` | Move the stop loss to the entry price, plus `offset_points`. |
| `cancel` | Cancel this webhook's pending orders on the symbol. |

TradingView sends numbers inside quotes as text. That is fine: `"0.1"` and `0.1` mean the same.

Some values are never taken from an alert: the account, the magic number, your limits, the symbol
map and the risk basis. If an alert sends them, they are listed in `ignored` and not used.

If a `{{placeholder}}` reaches Tickwire unfilled, the alert is refused with `placeholder_not_filled`
and nothing is sent. This usually means a strategy placeholder was used in an indicator alert.

## How risk sizing works

Tickwire works out the size from the live quote, not the alert's price:

1. Risk money is your basis times `risk_pct`, or `risk_money` as sent.
2. The entry is the ask for a buy and the bid for a sell.
3. The loss for one lot is the stop distance in ticks times the terminal's tick value.
4. Lots are risk money divided by the loss for one lot, rounded down to the volume step.

Tickwire never rounds a size up. If the size is below the symbol's minimum, the alert is refused
with `size_below_min_volume` and the answer says how much risk the minimum needs. Above the
broker's maximum, the size is capped and the answer says so.

Risk sizing needs a stop loss. No stop means no trade (`stop_loss_required`).

The answer shows the working in `sizing`, and a short summary line:

```text
Bought 0.2 lots EURUSD. Planned loss at stop: 100.00 USD of 100.00 USD (1% of balance). Before costs.
```

The planned loss is **before costs**. Spread, commission and slippage are not included. On MT4 the
terminal has one tick value for profit and loss, so MT4 sizes are marked `mt4_approximate`.

`pips` work on forex symbols only. For an index or a metal, use `points`.

## Webhook settings

Set these on create, or change them later with `PATCH /v1/signal-webhooks/{id}`. A group you send
is merged field by field. A field sent as `null` goes back to its default.

### Alert values

The sizing, stops and management groups each have `use_alert_values`:

- `true`: **Use alert values when sent.** The alert's own values win. Your settings fill the gaps.
- `false`: **Always use these settings.** The alert's values for that group are ignored and listed
  in `ignored`.

The answer's `sources` says where each value came from: `alert` or `dashboard`.

### Position size (`sizing`)

How big each trade is. Risk % sizes the trade so a stop-loss hit loses that share of your balance.

| Field | Default | Meaning |
|---|---|---|
| `mode` | `lots` | `lots`, `risk_pct` or `risk_money`. |
| `lots` | none | Fixed lots. |
| `risk_pct` | none | Risk per trade, in %. |
| `risk_money` | none | Fixed cash risk per trade, in the account currency. |
| `risk_basis` | `balance` | `balance`, `equity` or `fixed`. |
| `fixed_amount` | none | The basis for `fixed`. Use your starting balance for prop accounts. |
| `multiplier` | `1` | Scales a size sent in the alert. |

### Stops (`stops`)

| Field | Default | Meaning |
|---|---|---|
| `sl` | none | Default stop loss, used when the alert has none. `{"type": "pips", "value": 20}`. Types: `price`, `points`, `pips`, `pct`. |
| `tp` | none | Default take profit. The same types, plus `r`: a multiple of your stop (2R is twice the stop). |
| `require_sl` | `false` | Refuse any new trade without a stop loss. |

A stop on the wrong side of the price is refused with `stop_wrong_side`. A stop closer than the
broker allows is refused with `stop_too_close`.

### Trade management (`management`)

| Field | Default | Meaning |
|---|---|---|
| `on_opposite` | `reverse` | A signal against an open trade. `reverse`: close it and open the new one. `close`: close only. `ignore`: do nothing. `keep_both`: open the new one too. |
| `on_same_side` | `ignore` | A signal in the same direction. `ignore` if already in, or `add` up to `max_entries`. |
| `max_entries` | `2` | Most trades on one side, with `add`. |
| `close_pct` | `50` | The share `close_pct` closes when the alert sends no `pct`. |
| `breakeven_offset_points` | `0` | The offset `breakeven` uses when the alert sends none. |

### Limits (`limits`)

Tickwire sets no limits. Add your own. Each one is off until you set it.

| Field | Meaning |
|---|---|
| `max_lots_per_order` | Max lots per trade. With `over_max_lots: "reject"` (the default) a bigger trade is refused. With `"cap"` it is cut to the limit. |
| `max_risk_pct` | Max risk per trade, in % of your basis. A trade without a stop is refused. |
| `max_open_positions` | Max open trades for this webhook. |
| `max_open_positions_per_symbol` | Max open trades for this webhook on one symbol. |
| `trading_hours` | `{"timezone": "Europe/London", "start": "08:00", "end": "16:00", "days": ["mon", "tue", "wed", "thu", "fri"]}`. New trades outside these hours are refused. Closing still works. |
| `max_spread_points` | New trades are refused when the spread is wider. |
| `max_alert_age_s` | Alerts older than this are refused. The alert must send `time`. |
| `allowed_sides` | `both`, `long` or `short`. On a reversal the close still runs and the new trade is skipped. |

### Other settings

| Setting | Default | Meaning |
|---|---|---|
| `name` | | A label for you. |
| `symbol_map` | `{}` | TradingView symbol to broker symbol, for example `{"XAUUSD": "GOLD"}`. |
| `symbol_suffix` | none | Added to every symbol, for example `.m`. |
| `allowed_symbols` | all | Only these symbols may trade. Others are refused with `symbol_not_allowed`. |
| `magic` | random | The magic number on this webhook's orders. |
| `comment` | none | Default order comment. |
| `manage_others` | `false` | Also manage other trades on this symbol. |
| `allow_netting_shared` | `false` | On an MT5 netting account, allow trading a symbol that has trades from elsewhere. |
| `enabled` | `true` | `false` pauses the webhook. |

### Which trades a webhook touches

This webhook only touches trades it opened: those with its `magic` number on the alert's symbol.
Trades you place by hand, or from other tools, are left alone. Turn on `manage_others` to manage
every trade on the symbol.

An MT5 netting account holds one position per symbol, so it can't keep the webhook's trades apart
from yours. If the symbol already has a position from elsewhere, the alert is refused with
`netting_shared_symbol` unless you turn on `allow_netting_shared`. `keep_both` and `add` need a
hedging account (`hedging_required`). MT4 accounts always keep trades apart.

### Not part of signal webhooks

Signal webhooks do not run break-even at a set R, trailing stops, more than one take profit, ATR
stops, time-based exits, daily loss limits or prop-firm rules. Use your pre-trade hook or your own
tools for those.

## What Tickwire answers

TradingView ignores the answer, but it is kept with the alert so you can read it later.

| Outcome | HTTP | Meaning |
|---|---|---|
| `applied` | 200 | The terminal confirmed every order. |
| `no_change` | 200 | Nothing needed doing. Nothing was sent. |
| `held` | 202 | Your pre-trade hook is holding the next order for approval. It has not been sent. |
| `unknown` | 200 | An order may have executed. The rest of the plan was not sent. Check your positions before doing anything else. |
| `rejected` | 4xx or 5xx | Refused, with an `error` code. `sent: false` means nothing left Tickwire. |

Common error codes:

- Message: `invalid_json`, `symbol_required`, `action_required`, `invalid_action`,
  `placeholder_not_filled`, `invalid_signal`, `invalid_alert_time`, `conflicting_fields`,
  `volume_required`, `payload_too_large`.
- Sizing: `size_below_min_volume`, `tick_value_unavailable`, `stop_loss_required`,
  `pip_size_unavailable`.
- Stops: `stop_wrong_side`, `stop_too_close`, `take_profit_required`.
- Your limits: `above_max_lots`, `above_max_risk`, `max_open_positions_reached`,
  `side_not_allowed`, `outside_trading_hours`, `spread_too_wide`, `alert_too_old`,
  `alert_time_required`.
- Account: `signal_webhook_paused`, `kill_switch_engaged`, `hook_denied`, `symbol_not_allowed`,
  `symbol_not_found`, `netting_shared_symbol`, `hedging_required`, `too_many_positions`.
- Other: `signal_webhook_not_found`, `signal_in_flight`, `rate_limited`.

See [Errors](https://tickwire.io/docs/errors/).

## Repeats never trade twice

TradingView can resend an alert, and alerts can fire twice on the same bar. Tickwire treats an
alert as one order request:

- With an `alert_id`, the same id always gets the first answer back, with `replayed: true`.
- Without one, the same message within about a minute is the same alert.
- The plan is saved before the first order. A repeat sends the same orders, at the same size. It
  is never re-sized from a newer quote.
- Target position messages are safe even beyond that, because a second alert finds nothing to do.

To send two identical orders on purpose, give each its own `alert_id`.

## See what happened

```bash
curl "https://api.tickwire.io/v1/signal-webhooks/swh_9f2c/events?limit=20" \
  -H "X-API-Key: $TICKWIRE_API_KEY"
```

Each event shows the parsed signal, the outcome, the sizing and every order step. The orders also
appear in the account's audit log with the actor `signal-webhook:<id>`.

## Stay in control

- **Pause:** `PATCH /v1/signal-webhooks/{id}` with `{"enabled": false}`. Alerts are refused and
  nothing is sent until you turn it back on.
- **Rotate:** `POST /v1/signal-webhooks/{id}/rotate` gives a new URL, shown once. The old URL stops
  working at once. Paste the new one into each TradingView alert.
- **Revoke:** `DELETE /v1/signal-webhooks/{id}` ends the webhook for good.
- **Kill switch and pre-trade hook:** both apply to every alert, exactly as they do to the API and
  to AI agents. A held order waits for your approval. A denied one is never sent.

## Rate limits

Each webhook accepts 60 alerts a minute, with bursts of 30. Alert messages can be up to 16 KiB.
See [Rate limits](https://tickwire.io/docs/rate-limits/).

## MT4 and MT5

The same URL and messages work on both. Sizing uses each terminal's own tick value. The answer's
`steps` show exactly what was sent.
