pipsocketDOCS
Guides / TradingView

Alert messages

Every field of a hook's message, what each action does, and how sizes, levels and order are worked out.

A hook's message is a JSON object. The dashboard's message builder writes it for you; this page is the reference for what it can say.

{
  "action": "open",
  "src": "q7m2",
  "event_id": "q7m2-ema-{{exchange}}:{{ticker}}-{{interval}}-open-buy-{{time}}",
  "sent_at": "{{timenow}}",
  "symbol": "{{exchange}}:{{ticker}}",
  "side": "buy",
  "tag": "ema",
  "risk": { "percent": 1 },
  "sl": { "pips": 25 },
  "tp": { "pips": 50 }
}
  • No version field. The format only ever gains optional fields.
  • Unknown fields are refused (unknown_field), so a typo never fails silently.
  • Numbers may be strings, as TradingView's placeholders arrive as text ("lots": "{{strategy.order.contracts}}" is fine), but must be finite numbers. "ten" or "inf" is refused invalid_field.

Fields

FieldMeaning
actionopen, close, close_all, cancel, reverse, modify, strategy or kill_switch. See actions.
event_idUp to 128 characters: the alert's id, for spotting duplicates within the hook for 24 hours. See ids, duplicates and order.
srcA short id the builder makes for each alert template, part of its event_ids, so two templates never share ids.
sent_atWhen the alert was sent: {{timenow}} (UTC, to the second, such as 2026-11-08T14:30:00Z). Used for age limits and order. Left out: when Pipsocket received it.
source_seqA whole number that grows with every alert the source sends for the lane. When present, it decides order exactly. In an alert() message, Pine's timenow in milliseconds works only when each of the lane's alerts comes from its own run, well apart (such as once per bar close): alerts from one run, or from runs in the same millisecond, share it. Otherwise leave it out.
max_ageSeconds: overrides the hook's age limit for this alert's entries.
symbol{{exchange}}:{{ticker}}, or a bare ticker. See symbols.
sidebuy or sell. close, cancel and modify also take all.
lots or riskThe size. See sizing.
entryA pending entry instead of a market order. See pending entries.
sl, tpStop loss and take profit. See levels; tp may be a ladder.
trailing, break_evenManaged stops. See trailing stops and break-even. In modify, "none" removes them.
portionHow much to close: {"percent": n} or {"lots": n}. See partial closes.
tagUp to 16 characters of A-Z, a-z, 0-9, _ and -: the lane the alert acts in.
positionStrategy mode's position. See strategy mode.
accountsA list of the hook's targets (account ids or target short names): the alert acts only on these.
slippageA whole number of points: the deviation allowed for market orders. Left out: the target's setting.
commentYour note, kept in the alert log only.
testtrue makes the alert a dry run.

Which fields each action takes

R is required, O optional, and a blank is refused. Every action also takes event_id, sent_at, source_seq, src, accounts, comment and test.

actionsymbolsidelots / riskentrysltptrailing / break_evenportiontagpositionmax_ageslippage
openRRO (1)OOOOOOO
closeROOOO
close_allOO
cancelOOO
reverseRRO (1)OOOOOO
modifyROO (2)OOOO
strategyRO (4)OO (3)OOROO
kill_switch
  1. Left out, the target's fixed lots if it sizes fixed, else its default lots; refused if it has neither.
  2. Moves a pending order's price.
  3. A single take profit, not a ladder.
  4. lots only: the target's strategy sizing decides, and the message's lots only fill a gap.

modify needs at least one of entry, sl, tp, trailing and break_even.

Numbers

RuleFields
More than 0lots, risk.amount, portion.lots, distances in levels (pips, points, percent), prices, trailing.distance_pips, trailing.step_pips, break_even.trigger_pips, max_age
0 or moreentry.offset_pips and entry.offset_points (0 is the current price), entry.limit_offset_pips, break_even.offset_pips, trailing.start_pips, slippage, position.size and position.from_size
More than 0, at most 100risk.percent, portion.percent, a ladder rung's part
Exactly one oflots or risk; in risk, percent or amount; in a level, pips, points, price or percent; in portion, percent or lots; in entry, price, offset_pips or offset_points
Whole number, larger than the lastsource_seq, per lane
Strategy positions agreeto is flat exactly when size is 0, and from is flat exactly when from_size is 0; otherwise the sizes are more than 0

Actions

Every action acts only on its own lane's positions and pending orders, except kill_switch.

open

A market order, or a pending order with entry. Before it opens, it checks the lane:

  • Same side already held (positions, pending orders, or entries still waiting for an answer): refused pyramiding, unless the target's pyramiding allows more entries.
  • Other side held: refused opposite_open by default. With the target's opposite side set to close first, the open runs as a reverse; set to hedge, it opens beside it (hedging accounts only).

Then it sizes the order, rounds and checks the stops, asks MT5 whether the account has the margin (OrderCheck), and sends it with the stops in the request. An open never shrinks its size or widens its stops to fit: it is refused instead.

{ "action": "open", "symbol": "OANDA:EURUSD", "side": "buy", "lots": 0.10, "sl": { "pips": 20 }, "tp": { "pips": 40 } }

close

Closes the lane's positions, all of them or one side's (side), in full or in part (portion). Positions are closed oldest first, and the account is read again before each one. It never opens or adds, and never touches pending orders.

{ "action": "close", "symbol": "OANDA:EURUSD", "side": "buy", "portion": { "percent": 50 } }

close_all

Cancels the pending orders of all the hook's lanes on the account (or of one tag), then closes every position they hold, checking for orders that filled while being cancelled. No symbol: it covers every symbol. It touches nothing the hook doesn't own.

{ "action": "close_all", "tag": "ema" }

cancel

Deletes the lane's pending orders, narrowed by symbol and side if given. An order that fills while it's being cancelled is reported, and its position stays open.

{ "action": "cancel", "symbol": "OANDA:EURUSD", "side": "buy" }

reverse

Moves the lane to the other side: it cancels the lane's pending entries, closes the positions on the other side, waits until every close is confirmed filled in full, reads the lane again, and opens side only if the lane then holds nothing on the other side and the age limit and kill switch still allow it.

If any close is refused, only partly filled or has no answer, or anything remains on the other side, the open is never sent, and a confirmation that arrives later doesn't bring it back. The alert ends partial when part of it was done, unknown while a step still has no answer, or refused when nothing was.

{ "action": "reverse", "symbol": "OANDA:EURUSD", "side": "sell", "lots": 0.10, "sl": { "pips": 20 } }

modify

Changes the levels of the lane's positions and pending orders (narrowed by side): each is read fresh, only the levels you give change, the rest are kept, and the new ones are checked as in levels. entry moves a pending order's price. trailing and break_even replace the managed stops of those positions, and "none" removes them. It never changes a size.

{ "action": "modify", "symbol": "OANDA:EURUSD", "side": "buy", "sl": { "price": 1.0832 }, "trailing": "none" }

strategy

Brings the lane to the strategy's position. See strategy mode.

kill_switch

Engages the account's kill switch, only on targets whose allowed actions include it (off by default). See the kill switch and alerts.

{ "action": "kill_switch", "event_id": "panic-{{timenow}}" }

Sizing

MessageSize
"lots": 0.10Broker lots.
"risk": {"percent": 1}Lots such that a stop loss filled at the stop loses about 1% of the target's basis (balance or equity, a target setting). Needs sl.
"risk": {"amount": 50}The same, as an amount in the account's currency. Needs sl.

How a size is worked out, in order:

  1. The stops are rounded and checked first (levels); risk sizing uses the rounded stop loss.
  2. For risk: the loss per lot at the stop, from MT5's own profit calculation (OrderCalcProfit) in the account's currency. If MT5 can't work it out, or it isn't a loss, the alert is refused risk_unavailable. Lots = risk ÷ loss per lot.
  3. The target's sizing: as sent, multiplier (lots × your number), or fixed (your lots, whatever the message says).
  4. The target's maximum lots: above it is refused above_max_lots, or cut down to it if the target allows clipping.
  5. Rounded down to the symbol's lot step. Below the minimum lot is refused below_min_lot.
  6. MT5 checks the margin: refused no_margin if the account can't afford it.

The alert log shows each step and the estimated loss at the stop for the final size. It is an estimate: gaps, slippage, commission and swap aren't in it.

Levels

A stop loss or take profit can be given four ways (a pending order's price has its own fields: see pending entries):

FormMeaning
{"pips": n}A distance in pips from the base price (the pip size is the symbol map's).
{"points": n}A distance in the symbol's points.
{"price": p}A price.
{"percent": x}A distance as a percent of the base price (not of the account).
  • The base price for distances: for a market order, the price it would fill at when the alert is worked out (ask for buys, bid for sells); for a pending order, its order price (a stop limit's limit price); in modify, a position's open price or a pending order's price.
  • Distances are always on the protective side: a buy's stop loss below the base and take profit above, a sell's the other way round. To put a stop beyond the entry, locking in profit, give a price or use break-even.
  • Rounding: to the symbol's tick size, never closer to the base than you asked (at most one tick further).
  • New orders (open, pending entries, ladder rungs): a price must be on the protective side of the base too, or the alert is refused invalid_level.
  • Existing positions (modify, managed stops): levels may cross the open price. They are checked against the market, as MT5 does: a buy's stop loss below bid minus the stops level and take profit above bid plus it, a sell's stop loss above ask plus the stops level and take profit below ask minus it, and nothing within the freeze level of the price that would trigger it.
  • Pending orders: the order price is checked against the market by the stops and freeze levels, and its stop loss and take profit against the order price by the stops level.

A level that fails is refused stop_too_close, frozen or invalid_level, never moved to fit.

Pending entries

entry makes an open a pending order:

{
  "action": "open",
  "symbol": "OANDA:XAUUSD",
  "side": "buy",
  "lots": 0.10,
  "entry": { "type": "limit", "offset_pips": 15, "expires": "day" },
  "sl": { "pips": 30 },
  "tp": { "pips": 60 }
}
FieldMeaning
typelimit, stop or stop_limit.
price, offset_pips or offset_pointsThe order price, or its distance from the current price (below).
limit_price or limit_offset_pipsstop_limit only: the limit price, or its distance from the trigger price.
expiresgtc (until cancelled), day, or a UTC time such as 2026-11-08T21:00:00Z.

Offsets are measured from the current price, in the direction each order needs:

OrderOffset fromDirection
Buy limitAskBelow
Sell limitBidAbove
Buy stopAskAbove
Sell stopBidBelow
Buy stop limitAskTrigger above; limit at or below the trigger
Sell stop limitBidTrigger below; limit at or above the trigger
  • Expiry must be one the symbol allows, or the alert is refused expiry_not_allowed. A UTC time is converted to the broker's server time.
  • Filling: pending orders use MT5's return mode. Market orders use fill or kill or immediate or cancel, whichever the symbol allows (return only where its execution mode permits); if none fits, the alert is refused filling_unavailable. A market order that leaves part unfilled is reported as a partial fill, and Pipsocket asks the broker at once to cancel the rest. Should that cancel be refused or go unanswered, the rest stays a live order of the lane and may still fill: Pipsocket keeps checking the account and tries the cancel again (once the earlier attempt has settled, at most once a minute), a later close without portion, close_all, reverse or strategy-mode move of the lane on the symbol cancels it too (a close with a portion doesn't), and the alert stays watched until it is gone.
  • After placing, a pending order's outcome is followed through: placed, changed, cancelled, expired, filled, partly filled (the rest stays a live order of the lane) or rejected. Its ownership, stops and managed stops last until the order ends and any position it opened closes, even after the alert has left the 30-day log.
  • An alert that stops part way keeps the pending orders it already placed (reported, and yours to cancel). Only cancel, reverse, close_all and strategy mode cancel pending orders.

Partial closes

portion on a close is across all the positions selected, not each one:

  • {"lots": V} closes V lots in total; {"percent": p} closes p% of the selection's total volume. Volumes are rounded down to the lot step.
  • Positions are closed oldest first, each by as much as is left to close. If a close would leave less than the minimum lot in a position, it closes less, so at least the minimum stays (or skips that position), and moves on to the next.
  • The account is read again before each close, and the alert log shows what was asked for and what actually closed.

Take-profit ladders

On hedging accounts, a market entry can take a ladder of 2 to 10 rungs as its tp, each a level with a part. Ladders are for market entries only: a ladder on a pending entry is refused invalid_field.

{
  "action": "open",
  "symbol": "OANDA:EURUSD",
  "side": "buy",
  "lots": 0.30,
  "sl": { "pips": 20 },
  "tp": [{ "pips": 20, "part": 50 }, { "pips": 40, "part": 30 }, { "pips": 60, "part": 20 }]
}
  • The parts are percents of the size and must add up to exactly 100.
  • Each rung is its own position, with its own take profit and the message's stop loss. A stop loss given as a distance is worked out from the price read for each rung as it is sent, so rungs sent at different prices get stops at different prices. Its lots are the size × part ÷ 100, rounded down to the lot step; what rounding leaves over goes to the first rung. Every rung must be at least the minimum lot, or the open is refused.
  • Rungs are sent in order. If one is refused, only partly filled or has no answer, the later rungs aren't sent. The alert ends partial showing what filled, unknown while a rung still has no answer, or refused when none filled. Missing volume is never sent again.

Trailing stops and break-even

{
  "action": "open",
  "symbol": "OANDA:EURUSD",
  "side": "buy",
  "lots": 0.10,
  "sl": { "pips": 25 },
  "trailing": { "distance_pips": 20, "step_pips": 2, "start_pips": 10 },
  "break_even": { "trigger_pips": 15, "offset_pips": 1 }
}
FieldMeaning
trailing.distance_pipsHow far behind the best price the stop loss is kept: for a buy, below the highest bid since trailing began; for a sell, above the lowest ask.
trailing.start_pipsOptional: how far in profit the position must be before trailing begins. Until then the trailing rule does nothing.
trailing.step_pipsOptional: the smallest move Pipsocket makes. The stop loss moves only when the new level is at least this much better than the current one.
break_even.trigger_pipsHow far in profit the position must be before the stop loss moves, once, to break-even.
break_even.offset_pipsOptional: where past the entry it goes: a buy's entry plus the offset, a sell's entry minus it.
  • When both rules are set, the more protective level wins.
  • The broker holds the stop loss. Pipsocket moves it, but the stop itself is on the position at the broker, so if Pipsocket or the terminal is down, the last stop stays in place.
  • It only ever tightens. If the stop loss is already at or beyond where the rule would put it, whether you moved it with the API or by hand, nothing is sent. The take profit is kept as it is.
  • A move that is too close to the price, or frozen, is tried again on a later price. At most one move per second per position, each one confirmed by the broker before the next.
  • A move with no answer from MT5 in time is unknown: nothing more moves on that position until it settles. The account's page lists it to settle by hand. See unknown outcomes.
  • Without an sl, nothing protects the position until a rule first sets one; the builder warns you.
  • Rules last for the position's life: a partial close keeps them on what's left, modify with "none" removes them, and closing the position ends them. Pausing or deleting the hook doesn't stop them, and they carry on across restarts. A change of the account's login or server ends them.

Strategy mode

"action": "strategy" follows a TradingView strategy's position instead of acting on each order. The message says where the strategy was and where it is now, from the strategy's placeholders:

{
  "action": "strategy",
  "src": "k3f9",
  "event_id": "k3f9-ema-{{exchange}}:{{ticker}}-{{interval}}-{{strategy.order.id}}-{{time}}-{{strategy.market_position}}-{{strategy.market_position_size}}",
  "sent_at": "{{timenow}}",
  "symbol": "{{exchange}}:{{ticker}}",
  "tag": "ema",
  "position": {
    "from": "{{strategy.prev_market_position}}",
    "from_size": "{{strategy.prev_market_position_size}}",
    "to": "{{strategy.market_position}}",
    "size": "{{strategy.market_position_size}}"
  },
  "sl": { "pips": 25 },
  "tp": { "pips": 50 }
}

from and to are long, short or flat. Pipsocket compares the move the strategy made with what the lane holds now (H) and what it should have held before the move (P):

The strategy's moveWhat Pipsocket does
Increase: same direction and bigger, or flat to inIf H matches P (within one lot step), opens the difference. Otherwise the account has drifted, and nothing opens (drift), unless the target heals drift: then it opens up to the target.
Reduction: same direction, smallerCloses down toward the target, never past it. If H is already at or below the target, nothing (drift if below). Never opens.
Exit: to flatCloses all the lane's positions.
Flip: long to short or short to longCloses the lane's positions, confirms the closes, reads the lane again, and opens the target size on the other side only if nothing is left on the old side and H matched P (or the target heals drift). Otherwise it stops without opening. Its state is worked out as for any alert: unknown while a close still has no answer, then refused when nothing was done or partial when part was, with the failed step's reason; when its closes went through and only H not matching P stopped it, the reason is drift.
  • Sizing: the target's strategy sizing decides. Fixed: every position is the target's fixed lots (or the message's lots), so only direction changes matter. Scaled: lots = the strategy's size × the target's lots per contract, rounded down to the lot step. Pyramiding and opposite side settings don't apply.
  • A strategy lane is strategy mode only: open, reverse and pending entries in the same lane are refused lane_in_strategy_mode.
  • Before acting, the lane's pending orders are cancelled.
  • An exit never turns into a position on the other side.

Tags and lanes

A lane is one account, one hook, one tag and one symbol. Ownership, order and strategy mode are all per lane:

  • A hook only acts on positions and pending orders its own lane opened. Your manual and API trades, and other lanes', are never touched (except by the kill switch).
  • Several strategies can share a hook and an account with different tags. Without a tag, a message acts in the hook's untagged lane.
  • close_all with a tag covers that tag's lanes; without one, all the hook's lanes on the account.
  • accounts narrows an alert to some of the hook's targets.

Ids, duplicates and order

TradingView can deliver an alert twice, and alerts fired close together can arrive out of order. Three fields deal with that.

event_id and duplicates

  • An alert with the same event_id as one the hook received in the last 24 hours, and the same message, is refused duplicate; with a different message, conflict.
  • Without an event_id, an identical message within 60 seconds counts as a duplicate.
  • The builder's event_ids are made of the template's src, the tag, the symbol, the interval and the bar's time, so different lanes and templates never collide:
    • indicators: {src}-{tag}-{{exchange}}:{{ticker}}-{{interval}}-{action}-{side}-{{time}}
    • strategies: {src}-{tag}-{{exchange}}:{{ticker}}-{{interval}}-{{strategy.order.id}}-{{time}}-{{strategy.market_position}}-{{strategy.market_position_size}}
  • These assume at most one alert per bar for each lane and action. A strategy that fills several times in one bar adds a value from Pine through {{strategy.order.alert_message}}, or uses an alert() message: see order fills.

The ordering rule for late exits

An exit (close, close_all, cancel, strategy reductions and exits, and the close in a reverse or flip) only acts on positions and orders opened by alerts that came before it:

  1. With source_seq on both alerts: opened earlier if its source_seq is lower. Exact.
  2. Otherwise, with sent_at on both, in different seconds: opened earlier if its sent_at is earlier.
  3. Otherwise: opened earlier if Pipsocket received it first.

So a close that arrives late never closes an entry that arrived after it, and with source_seq, never one that was sent after it. Without source_seq, two alerts of one lane sent in the same second (or without sent_at) are put in the order they were received, which TradingView may not keep; the alert log flags an exit decided that way ambiguous_order, whether or not they actually arrived out of order. If your strategy can fire twice in a second, send source_seq.

Age limits

An alert that would open or add is refused stale when it is older than the hook's age limit (30 seconds by default, or the message's max_age), measured from sent_at, or from when Pipsocket received it. An entry that was right a minute ago may not be now.

Exits are never refused for age: a late close still closes, under the ordering rule above.

Dry runs

"test": true, or Send test in the dashboard, runs everything up to sending: parsing, the symbol map, sizing, levels, and MT5's margin check for the first order that would open, against the account as it is now. What would follow (an open after a close, later ladder rungs) is reported as "would then ..." without those checks.

A dry run never trades, never engages the kill switch, never starts trailing stops, and doesn't count as a duplicate of a live alert.