Signals
Signals are trading intentions that can be reviewed and processed into orders. They enable automation, flexibility, and clear audit trails.
Why Use Signals?
- Automation - Receive signals from TradingView, custom scripts, or other systems
- Review First - Create signals, verify them, then execute
- Flexible Sizing - Specify shares, percentage of equity, or dollar amounts
- Strategy Tracking - Link signals to strategies for performance analysis
- Scheduled Execution - Queue signals for future times
Signal Actions
Signals use four explicit actions that combine direction and intent:
| Action | Description | Resulting Order |
|---|---|---|
openLong | Open a long position | Buy |
closeLong | Close a long position | Sell |
openShort | Open a short position | Sell (short) |
closeShort | Close a short position | Buy (cover) |
"Buy" is ambiguous - it could mean opening a long OR closing a short. Signal actions make your intent explicit, reducing errors.
Creating Signals
Manual Creation
- Navigate to Signals in the menu
- Click New Signal
- Fill in the details:
| Field | Description |
|---|---|
| Account | Which trading account |
| Strategy | Which strategy owns this signal. Defaults to your Manual strategy |
| Symbol | Ticker symbol (e.g., AAPL) |
| Action | openLong, closeLong, openShort, closeShort |
| Quantity Type | How to interpret the quantity |
| Quantity | Amount based on quantity type |
| Limit Price | Optional - for limit orders |
| Stop Price | Optional - for stop orders |
sourceId is a caller-supplied reference id that lets you correlate your own audit trail back to UTM signals. UTM does not use it for dedup. The manual UI omits it entirely.
- Click Save
- Signal enters "Queued" status
Quantity Types
| Type | Quantity Meaning | Example |
|---|---|---|
| Fixed | Number of shares | 100 = buy 100 shares |
| Percent Equity | % of account equity | 5 = invest 5% of equity |
| Dollar Amount | Dollar value to invest | 1000 = invest $1000 |
Percentage and dollar quantities are calculated at processing time using current prices and account equity.
Processing Signals
Converting a signal to an order:
Single Signal
- Find the signal in the Signals table
- Click Process
- Order is created and sent to broker
- Signal status changes to "Submitted"
Batch Processing
- Select multiple pending signals (checkboxes)
- Click Process Selected
- All signals convert to orders
Signal Statuses
A signal carries one of five statuses. The status answers the question "what did UTM do with this instruction?". Whether the broker filled the resulting order is a separate question, answered by the linked order status.
| Status | Meaning |
|---|---|
| Queued | Waiting to be sent. Covers both immediate-mode signals waiting for a worker and scheduled-mode signals waiting on scheduledProcessAt. The two are distinguished by the scheduledProcessAt field. |
| Processing | Mid-submit to the broker. Transient state visible for a fraction of a second between claim and result. |
| Transmitted | UTM put the order on the wire to the broker. The signal lifecycle ends here. Whether the broker filled, partially filled, rejected, or cancelled lives on the linked order. |
| Cancelled | UTM did not send. The errorCode field carries the reason: pre_flight_rule (validation/risk refused), user_cancelled (you cancelled before send), or expired (validUntil passed). |
| Error | UTM tried to send and failed. The errorMessage field carries the underlying error. Covers transmission failures (network, broker auth) and broker rejections. |
Cancelled with errorCode=pre_flight_rule means UTM stopped it before transmission (fix your account/broker config or risk settings). Cancelled with errorCode=expired means the signal's validUntil passed before it could be processed. Error means UTM tried to transmit and the attempt failed; the signal can be re-submitted as a fresh request.
Execution Modes
The executeMode field controls when a signal is processed:
| Mode | Behaviour | Initial Status | Response |
|---|---|---|---|
immediate | Processed synchronously - order placed straight away | queued | 201 |
scheduled | Processed at scheduledProcessAt in the supplied timezone | queued | 202 |
Use immediate to fire-and-forget and scheduled to defer to a specific future time. Both initial statuses are queued; the scheduledProcessAt field distinguishes the two.
Webhook Integration
For automated trading, signals can be received via HTTP webhook.
Setup
- Go to Settings > API Keys
- Click Create API Key
- Select
signals:writescope - Copy the generated key
Required fields
Every signal payload must include:
accountId— which account the signal targets.strategyId— the strategy that owns this signal. Every user has a built-inManualstrategy created at signup which is a fine default if your integration is not strategy-aware. List strategies viaGET /api/v1/strategies.symbol,action,quantity,executeMode— the trade itself.
sourceId is optional. It is a caller-supplied reference id that lets you correlate your own audit trail back to UTM signals. UTM does not use it for dedup.
Duplicate intent
UTM is a translation layer. You own intent and retry semantics. UTM refuses only signals the broker would reject.
Specifically, UTM rejects a signal with HTTP 409 and code: DUPLICATE_INTENT when a same-direction signal for the same (accountId, strategyId, symbol, action) is already in a live status (queued, processing, or transmitted). The response carries the conflicting signal id in details.conflictingSignalId. Resolve that signal (cancel it, or wait for it to settle) before submitting another.
A cancelled or errored prior attempt does not block a fresh submission. Retry semantics, deduping inbound webhooks, and any "skip if I just sent this" logic live in your pipeline, not in UTM.
Disabling the guard
The duplicate-intent guard is on by default. If your caller already dedupes upstream and you would rather take responsibility for retries than have UTM refuse a same-key re-submission, you can turn the guard off per user under Settings -> Signals.
When the guard is off, UTM stops checking for in-flight duplicates on the (accountId, strategyId, symbol, action) key. Two close signals can both transmit, both reach the broker, and overshoot the position. Leave the guard on unless your signal source dedupes upstream and you accept that risk.
Every toggle is recorded in your audit log (user.settings.guard_duplicate_intent.changed) with the previous and new value, so the change has a clear paper trail if something goes wrong later. Re-enabling the guard is a silent save; disabling it requires confirmation in a modal.
Opposing positions on netting brokers
On brokers that net positions per symbol (Alpaca, TradeStation), UTM cannot hold both a long and a short trade for the same (account, symbol) because the broker collapses them into a single net. UTM rejects the second trade up-front with HTTP 409 and code: OPPOSING_POSITION_OPEN. The response carries details.conflictingTradeId.
The check is strategy-agnostic: two strategies running on the same account, one long and one short on the same symbol, hit this rule. Same-side stacking across strategies is allowed (two longs on the same symbol both succeed). Brokers that support hedging (e.g. Interactive Brokers) are unaffected.
To proceed, close the conflicting trade to flat first, then re-submit. Or route the opposing strategy to a different account.
Sending Signals Immediately
curl -X POST https://api.universaltrademanager.com/api/v1/signals \
-H "Content-Type: application/json" \
-H "X-API-Key: utm_your_api_key" \
-d '{
"accountId": "your-account-uuid",
"strategyId": "your-strategy-uuid",
"symbol": "AAPL",
"action": "openLong",
"quantity": 100,
"executeMode": "immediate"
}'
Sending Signals for Scheduled Processing
Provide scheduledProcessAt (ISO datetime, no Z suffix) and an IANA timezone. The scheduled time must be in the future.
timezone is required whenever scheduledProcessAt is set. It is also required whenever validUntil is set. Both fields are interpreted in the given timezone. Use IANA identifiers such as America/New_York or UTC.
curl -X POST https://api.universaltrademanager.com/api/v1/signals \
-H "Content-Type: application/json" \
-H "X-API-Key: utm_your_api_key" \
-d '{
"accountId": "your-account-uuid",
"strategyId": "your-strategy-uuid",
"symbol": "AAPL",
"action": "openLong",
"quantity": 100,
"executeMode": "scheduled",
"scheduledProcessAt": "2026-05-15T09:30:00",
"timezone": "America/New_York"
}'
See the Signals API reference for full documentation.
Exit Rules
Attach an automatic exit to an entry signal so UTM submits the closing order for you after the entry fills. Set the trigger fields directly on the signal payload. They are not nested under an exitRule object.
Exit rule fields
The shape is discriminated by exitTriggerType. The other fields are interpreted relative to that choice.
| Field | Type | Required | Description |
|---|---|---|---|
exitTriggerType | string | Yes | One of minutesAfterEntry, minutesBeforeClose, atClockTime, or immediate. Picks the scheduling rule for the exit. |
exitTriggerMinutes | integer | Conditional | Minutes count (0 to 60). Required when exitTriggerType is minutesAfterEntry or minutesBeforeClose. |
exitTriggerTime | string | Conditional | HH:MM (24-hour) submit time in the broker's timezone. Required when exitTriggerType is atClockTime. |
exitOrderType | string | Yes | Exit order type: market, limit, stop, stopLimit, or moc (market on close). |
exitLimitPrice | number | Conditional | Limit price for the exit order. Required when exitOrderType is limit or stopLimit. |
exitStopPrice | number | Conditional | Trigger price for the exit order. Required when exitOrderType is stop or stopLimit. |
exitTimeInForce | string | Conditional | day or cls. Optional for most order types; required as cls when exitOrderType is moc. |
Trigger types
| Trigger | Behaviour |
|---|---|
minutesAfterEntry | Schedules the exit at entryFillTime + exitTriggerMinutes. |
minutesBeforeClose | Schedules the exit at marketClose - exitTriggerMinutes. Rolls to the next session if the market is shut. |
atClockTime | Schedules the exit at the next occurrence of exitTriggerTime (broker timezone). |
immediate | Submits the exit as soon as the entry is terminal, with no scheduled delay. |
moc is for end-of-session exits. The broker only accepts it with exitTimeInForce: cls. When paired with minutesBeforeClose, the exit must sit at least 15 minutes before the close to give the broker time to route the order.
UTM never cancels the entry to create the exit. The exit fires only once the entry order is terminal (fully filled, or cancelled or expired after a partial fill), then closes whatever filled. Use gtd (or day) time-in-force on the entry for an in-and-out trade. A gtc entry that only partially fills stays working, so its timed exit may never fire; UTM returns the exit_rule_tif_may_not_terminate advisory in that case. See the Exit Rules reference for detail.
Example: market exit 30 minutes after entry
Open a long and close it with a market order 30 minutes after the entry fills:
{
"accountId": "your-account-uuid",
"strategyId": "your-strategy-uuid",
"symbol": "AAPL",
"action": "openLong",
"quantity": 100,
"executeMode": "immediate",
"exitTriggerType": "minutesAfterEntry",
"exitTriggerMinutes": 30,
"exitOrderType": "market"
}
Example: limit exit at a fixed clock time
Submit a limit exit at 15:50 broker time:
{
"accountId": "your-account-uuid",
"strategyId": "your-strategy-uuid",
"symbol": "AAPL",
"action": "openLong",
"quantity": 100,
"executeMode": "immediate",
"exitTriggerType": "atClockTime",
"exitTriggerTime": "15:50",
"exitOrderType": "limit",
"exitLimitPrice": 180.0
}
Example: stop exit 30 minutes after entry
Submit a stop order at a protective trigger price 30 minutes after the entry fills. The broker triggers a market order once the symbol trades through exitStopPrice:
{
"accountId": "your-account-uuid",
"strategyId": "your-strategy-uuid",
"symbol": "AAPL",
"action": "openLong",
"quantity": 100,
"executeMode": "immediate",
"exitTriggerType": "minutesAfterEntry",
"exitTriggerMinutes": 30,
"exitOrderType": "stop",
"exitStopPrice": 175.0
}
Example: stop-limit exit 30 minutes after entry
Submit a stop-limit order with both a trigger price (exitStopPrice) and a worst-acceptable limit (exitLimitPrice). The broker converts to a limit order once the symbol trades through exitStopPrice and only fills at or above exitLimitPrice on a sell:
{
"accountId": "your-account-uuid",
"strategyId": "your-strategy-uuid",
"symbol": "AAPL",
"action": "openLong",
"quantity": 100,
"executeMode": "immediate",
"exitTriggerType": "minutesAfterEntry",
"exitTriggerMinutes": 30,
"exitOrderType": "stopLimit",
"exitStopPrice": 175.0,
"exitLimitPrice": 174.0
}
Example: market-on-close exit 15 minutes before close
Exit at the closing auction with an MOC order. exitTimeInForce must be cls, and the offset must be at least 15 minutes:
{
"accountId": "your-account-uuid",
"strategyId": "your-strategy-uuid",
"symbol": "AAPL",
"action": "openLong",
"quantity": 100,
"executeMode": "immediate",
"exitTriggerType": "minutesBeforeClose",
"exitTriggerMinutes": 15,
"exitOrderType": "moc",
"exitTimeInForce": "cls"
}
See Exit Rules Reference for the full field reference.